diff --git a/setup/stock_location_orderpoint_cleanup/odoo/addons/stock_location_orderpoint_cleanup b/setup/stock_location_orderpoint_cleanup/odoo/addons/stock_location_orderpoint_cleanup new file mode 120000 index 00000000..e07cc952 --- /dev/null +++ b/setup/stock_location_orderpoint_cleanup/odoo/addons/stock_location_orderpoint_cleanup @@ -0,0 +1 @@ +../../../../stock_location_orderpoint_cleanup \ No newline at end of file diff --git a/setup/stock_location_orderpoint_cleanup/setup.py b/setup/stock_location_orderpoint_cleanup/setup.py new file mode 100644 index 00000000..28c57bb6 --- /dev/null +++ b/setup/stock_location_orderpoint_cleanup/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/stock_location_orderpoint_cleanup/README.rst b/stock_location_orderpoint_cleanup/README.rst new file mode 100644 index 00000000..4e3e6ed8 --- /dev/null +++ b/stock_location_orderpoint_cleanup/README.rst @@ -0,0 +1,125 @@ +================================= +Stock Location Orderpoint Cleanup +================================= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:739484c7a49a9d7cfdcb53fbe8c0ac62ac0529692120c55ef0bd90119a2eb6c5 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fstock--logistics--orderpoint-lightgray.png?logo=github + :target: https://github.com/OCA/stock-logistics-orderpoint/tree/16.0/stock_location_orderpoint_cleanup + :alt: OCA/stock-logistics-orderpoint +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/stock-logistics-orderpoint-16-0/stock-logistics-orderpoint-16-0-stock_location_orderpoint_cleanup + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/stock-logistics-orderpoint&target_branch=16.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module will help to clean generated moves from stock location +orderpoints. + +**Table of contents** + +.. contents:: + :local: + +Use Cases / Context +=================== + +Location orderpoints module was designed (first) to generate +replenishment moves for particular stock locations. e.g.: we have a +reserve stock and a preparation stock. + +As warehouse life is not static, moves for a product can be canceled, +preparation stock could have been refilled with another move, ... + +So, generated moves from location orderpoints can become obsolete. + +This module should help to clean orderpoint moves and regenerate moves. + +Configuration +============= + +- There is a security group 'Location Orderpoint Cleanup Group'. Users + that can have access to cleanup action should be in that group. By + default, all users that are in 'Inventory/Administrator' are in that + group. +- You can configure crons to execute cleanup actions. + + - Enable debug mode, go to Settings > Technical > Scheduled Actions + - Add a cron with: + + - base model 'Stock Location Orderpoint' + - In python code, add a line 'model.run_cleanup(orderpoints, + run_after)' where 'orderpoints' is a list of orderpoint ids and + 'run_after' is True if you want to run the orderpoint(s) after + cleanup. + - If you want to cleanup ordepoints by replenish method, add a + line 'model.run_cleanup_method(replenish_method, run_after)' + where 'replenish_method' is 'fill_up' by default (depending on + extension modules you have installed) + +Usage +===== + +- You can have cron jobs to execute the cleanup actions (see + Configuration section). +- If you are in the 'Location Orderpoint Cleanup Group', you can launch + manually the cleanup on the orderpoint form level: + + - Click on 'Cleanup replenishments'. + - In the wizard, check the 'Run After' box if you want the + orderpoint to be run after cleanup. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues <https://github.com/OCA/stock-logistics-orderpoint/issues>`_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback <https://github.com/OCA/stock-logistics-orderpoint/issues/new?body=module:%20stock_location_orderpoint_cleanup%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* ACSONE SA/NV + +Contributors +------------ + +- Denis Roussel denis.roussel@acsone.eu + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/stock-logistics-orderpoint <https://github.com/OCA/stock-logistics-orderpoint/tree/16.0/stock_location_orderpoint_cleanup>`_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/stock_location_orderpoint_cleanup/__init__.py b/stock_location_orderpoint_cleanup/__init__.py new file mode 100644 index 00000000..aee8895e --- /dev/null +++ b/stock_location_orderpoint_cleanup/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import wizards diff --git a/stock_location_orderpoint_cleanup/__manifest__.py b/stock_location_orderpoint_cleanup/__manifest__.py new file mode 100644 index 00000000..b2d1a54b --- /dev/null +++ b/stock_location_orderpoint_cleanup/__manifest__.py @@ -0,0 +1,18 @@ +# Copyright 2023 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +{ + "name": "Stock Location Orderpoint Cleanup", + "summary": """ + This module allows to clean moves generated by orderpoint""", + "version": "16.0.1.0.0", + "license": "AGPL-3", + "author": "ACSONE SA/NV,Odoo Community Association (OCA)", + "website": "https://github.com/OCA/stock-logistics-orderpoint", + "depends": ["base_partition", "stock_location_orderpoint", "queue_job"], + "data": [ + "security/security.xml", + "wizards/stock_location_orderpoint_cleanup.xml", + "views/stock_location_orderpoint.xml", + ], +} diff --git a/stock_location_orderpoint_cleanup/models/__init__.py b/stock_location_orderpoint_cleanup/models/__init__.py new file mode 100644 index 00000000..6f19d60d --- /dev/null +++ b/stock_location_orderpoint_cleanup/models/__init__.py @@ -0,0 +1 @@ +from . import stock_location_orderpoint, stock_picking diff --git a/stock_location_orderpoint_cleanup/models/stock_location_orderpoint.py b/stock_location_orderpoint_cleanup/models/stock_location_orderpoint.py new file mode 100644 index 00000000..d7436712 --- /dev/null +++ b/stock_location_orderpoint_cleanup/models/stock_location_orderpoint.py @@ -0,0 +1,86 @@ +# Copyright 2024 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +import logging + +from odoo import _, api, models + +from odoo.addons.queue_job.job import identity_exact + +_logger = logging.getLogger(__name__) + + +class StockLocationOrderpoint(models.Model): + + _inherit = "stock.location.orderpoint" + + def _get_moves_to_cleanup_domain(self) -> list: + self.ensure_one() + return [ + ("location_orderpoint_id", "=", self.id), + ("state", "not in", ("done", "cancel")), + ("quantity_done", "<=", 0), + ("picking_id.printed", "=", False), + ] + + def _get_moves_to_cleanup(self): + self.ensure_one() + moves = self.env["stock.move"].search(self._get_moves_to_cleanup_domain()) + return moves + + def cleanup(self, run_after=False): + """ + This method will launch the cleanup process as a queue job. + """ + for orderpoint in self: + description = _( + "Running the cleanup for the orderpoint %(orderpoint_name)s", + orderpoint_name=orderpoint.name, + ) + orderpoint.with_delay( + description=description, identity_key=identity_exact + )._cleanup(run_after=run_after) + + def _cleanup(self, run_after=False): + """ + run_after: Run the orderpoint after cleanup + """ + + moves = self._get_moves_to_cleanup() + for picking, moves in moves.partition("picking_id").items(): + moves.exists()._action_cancel() + picking._log_location_orderpoint_cleanup_message(self, moves) + + if run_after: + self.run_replenishment() + + @api.model + def run_cleanup(self, orderpoints=False, run_after=False): + """ + This method should be called by crons + """ + self.browse(orderpoints).cleanup(run_after=run_after) + + @api.model + def run_cleanup_method(self, replenish_method="fill_up", run_after=False): + """ + This method should be called by crons. + + e.g.: We have plenty of orderpoints but we know which replenish method + should be cleaned. + """ + if replenish_method not in self._fields["replenish_method"].get_values( + self.env + ): + _logger.warning( + "You try to call 'run_cleanup_method' with an undefined replenish method '%s'", + replenish_method, + ) + self.search([("replenish_method", "=", replenish_method)]).cleanup( + run_after=run_after + ) + + def get_cleanup_action(self): + action = self.env["ir.actions.act_window"]._for_xml_id( + "stock_location_orderpoint_cleanup.stock_location_orderpoint_cleanup_act_window" + ) + return action diff --git a/stock_location_orderpoint_cleanup/models/stock_picking.py b/stock_location_orderpoint_cleanup/models/stock_picking.py new file mode 100644 index 00000000..88212e62 --- /dev/null +++ b/stock_location_orderpoint_cleanup/models/stock_picking.py @@ -0,0 +1,25 @@ +# Copyright 2023 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import _, models + + +class StockPicking(models.Model): + + _inherit = "stock.picking" + + def _log_location_orderpoint_cleanup_message(self, orderpoint, moves): + """ + This will log the moves that have been canceled through the cleanup + process. + """ + self.ensure_one() + orderpoint_name = orderpoint.name + moves_name = ",".join(moves.mapped("product_id.name")) + message = _( + "These moves have been cleaned up for location orderpoint " + "%(orderpoint_name)s: %(moves_name)s", + orderpoint_name=orderpoint_name, + moves_name=moves_name, + ) + self.message_post(body=message) diff --git a/stock_location_orderpoint_cleanup/readme/CONFIGURE.md b/stock_location_orderpoint_cleanup/readme/CONFIGURE.md new file mode 100644 index 00000000..94a7fad9 --- /dev/null +++ b/stock_location_orderpoint_cleanup/readme/CONFIGURE.md @@ -0,0 +1,9 @@ +- There is a security group 'Location Orderpoint Cleanup Group'. Users + that can have access to cleanup action should be in that group. By default, + all users that are in 'Inventory/Administrator' are in that group. +- You can configure crons to execute cleanup actions. + - Enable debug mode, go to Settings > Technical > Scheduled Actions + - Add a cron with: + - base model 'Stock Location Orderpoint' + - In python code, add a line 'model.run_cleanup(orderpoints, run_after)' where 'orderpoints' is a list of orderpoint ids and 'run_after' is True if you want to run the orderpoint(s) after cleanup. + - If you want to cleanup ordepoints by replenish method, add a line 'model.run_cleanup_method(replenish_method, run_after)' where 'replenish_method' is 'fill_up' by default (depending on extension modules you have installed) \ No newline at end of file diff --git a/stock_location_orderpoint_cleanup/readme/CONTEXT.md b/stock_location_orderpoint_cleanup/readme/CONTEXT.md new file mode 100644 index 00000000..5d58e51e --- /dev/null +++ b/stock_location_orderpoint_cleanup/readme/CONTEXT.md @@ -0,0 +1,10 @@ +Location orderpoints module was designed (first) to generate replenishment moves +for particular stock locations. e.g.: we have a reserve stock and a preparation +stock. + +As warehouse life is not static, moves for a product can be canceled, preparation +stock could have been refilled with another move, ... + +So, generated moves from location orderpoints can become obsolete. + +This module should help to clean orderpoint moves and regenerate moves. diff --git a/stock_location_orderpoint_cleanup/readme/CONTRIBUTORS.md b/stock_location_orderpoint_cleanup/readme/CONTRIBUTORS.md new file mode 100644 index 00000000..4e7e6847 --- /dev/null +++ b/stock_location_orderpoint_cleanup/readme/CONTRIBUTORS.md @@ -0,0 +1 @@ +- Denis Roussel <denis.roussel@acsone.eu> diff --git a/stock_location_orderpoint_cleanup/readme/DESCRIPTION.md b/stock_location_orderpoint_cleanup/readme/DESCRIPTION.md new file mode 100644 index 00000000..9d92621c --- /dev/null +++ b/stock_location_orderpoint_cleanup/readme/DESCRIPTION.md @@ -0,0 +1 @@ +This module will help to clean generated moves from stock location orderpoints. diff --git a/stock_location_orderpoint_cleanup/readme/USAGE.md b/stock_location_orderpoint_cleanup/readme/USAGE.md new file mode 100644 index 00000000..49c94c5f --- /dev/null +++ b/stock_location_orderpoint_cleanup/readme/USAGE.md @@ -0,0 +1,5 @@ +- You can have cron jobs to execute the cleanup actions (see Configuration section). +- If you are in the 'Location Orderpoint Cleanup Group', you can launch manually the cleanup + on the orderpoint form level: + - Click on 'Cleanup replenishments'. + - In the wizard, check the 'Run After' box if you want the orderpoint to be run after cleanup. diff --git a/stock_location_orderpoint_cleanup/security/security.xml b/stock_location_orderpoint_cleanup/security/security.xml new file mode 100644 index 00000000..e5b54f27 --- /dev/null +++ b/stock_location_orderpoint_cleanup/security/security.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8" ?> +<!-- Copyright 2024 ACSONE SA/NV + License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). --> +<odoo noupdate="1"> + + <record model="res.groups" id="group_location_cleanup"> + <field name="name">Location Orderpoint Cleanup Group</field> + </record> + <record model="res.groups" id="stock.group_stock_manager"> + <field + name="implied_ids" + eval="[(4, ref('stock_location_orderpoint_cleanup.group_location_cleanup'))]" + /> + </record> + + <record model="ir.model.access" id="stock_location_orderpoint_cleanup_access"> + <field name="name">stock.location.orderpoint.cleanup access</field> + <field name="model_id" ref="model_stock_location_orderpoint_cleanup" /> + <field name="group_id" ref="group_location_cleanup" /> + <field name="perm_read" eval="1" /> + <field name="perm_create" eval="1" /> + <field name="perm_write" eval="1" /> + <field name="perm_unlink" eval="1" /> + </record> + +</odoo> diff --git a/stock_location_orderpoint_cleanup/static/description/icon.png b/stock_location_orderpoint_cleanup/static/description/icon.png new file mode 100644 index 00000000..3a0328b5 Binary files /dev/null and b/stock_location_orderpoint_cleanup/static/description/icon.png differ diff --git a/stock_location_orderpoint_cleanup/static/description/index.html b/stock_location_orderpoint_cleanup/static/description/index.html new file mode 100644 index 00000000..f0c6d6d0 --- /dev/null +++ b/stock_location_orderpoint_cleanup/static/description/index.html @@ -0,0 +1,474 @@ +<?xml version="1.0" encoding="utf-8" ?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> +<meta name="generator" content="Docutils: http://docutils.sourceforge.net/" /> +<title>Stock Location Orderpoint Cleanup</title> +<style type="text/css"> + +/* +:Author: David Goodger (goodger@python.org) +:Id: $Id: html4css1.css 7952 2016-07-26 18:15:59Z milde $ +:Copyright: This stylesheet has been placed in the public domain. + +Default cascading style sheet for the HTML output of Docutils. + +See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to +customize this style sheet. +*/ + +/* used to remove borders from tables and images */ +.borderless, table.borderless td, table.borderless th { + border: 0 } + +table.borderless td, table.borderless th { + /* Override padding for "table.docutils td" with "! important". + The right padding separates the table cells. */ + padding: 0 0.5em 0 0 ! important } + +.first { + /* Override more specific margin styles with "! important". */ + margin-top: 0 ! important } + +.last, .with-subtitle { + margin-bottom: 0 ! important } + +.hidden { + display: none } + +.subscript { + vertical-align: sub; + font-size: smaller } + +.superscript { + vertical-align: super; + font-size: smaller } + +a.toc-backref { + text-decoration: none ; + color: black } + +blockquote.epigraph { + margin: 2em 5em ; } + +dl.docutils dd { + margin-bottom: 0.5em } + +object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] { + overflow: hidden; +} + +/* Uncomment (and remove this text!) to get bold-faced definition list terms +dl.docutils dt { + font-weight: bold } +*/ + +div.abstract { + margin: 2em 5em } + +div.abstract p.topic-title { + font-weight: bold ; + text-align: center } + +div.admonition, div.attention, div.caution, div.danger, div.error, +div.hint, div.important, div.note, div.tip, div.warning { + margin: 2em ; + border: medium outset ; + padding: 1em } + +div.admonition p.admonition-title, div.hint p.admonition-title, +div.important p.admonition-title, div.note p.admonition-title, +div.tip p.admonition-title { + font-weight: bold ; + font-family: sans-serif } + +div.attention p.admonition-title, div.caution p.admonition-title, +div.danger p.admonition-title, div.error p.admonition-title, +div.warning p.admonition-title, .code .error { + color: red ; + font-weight: bold ; + font-family: sans-serif } + +/* Uncomment (and remove this text!) to get reduced vertical space in + compound paragraphs. +div.compound .compound-first, div.compound .compound-middle { + margin-bottom: 0.5em } + +div.compound .compound-last, div.compound .compound-middle { + margin-top: 0.5em } +*/ + +div.dedication { + margin: 2em 5em ; + text-align: center ; + font-style: italic } + +div.dedication p.topic-title { + font-weight: bold ; + font-style: normal } + +div.figure { + margin-left: 2em ; + margin-right: 2em } + +div.footer, div.header { + clear: both; + font-size: smaller } + +div.line-block { + display: block ; + margin-top: 1em ; + margin-bottom: 1em } + +div.line-block div.line-block { + margin-top: 0 ; + margin-bottom: 0 ; + margin-left: 1.5em } + +div.sidebar { + margin: 0 0 0.5em 1em ; + border: medium outset ; + padding: 1em ; + background-color: #ffffee ; + width: 40% ; + float: right ; + clear: right } + +div.sidebar p.rubric { + font-family: sans-serif ; + font-size: medium } + +div.system-messages { + margin: 5em } + +div.system-messages h1 { + color: red } + +div.system-message { + border: medium outset ; + padding: 1em } + +div.system-message p.system-message-title { + color: red ; + font-weight: bold } + +div.topic { + margin: 2em } + +h1.section-subtitle, h2.section-subtitle, h3.section-subtitle, +h4.section-subtitle, h5.section-subtitle, h6.section-subtitle { + margin-top: 0.4em } + +h1.title { + text-align: center } + +h2.subtitle { + text-align: center } + +hr.docutils { + width: 75% } + +img.align-left, .figure.align-left, object.align-left, table.align-left { + clear: left ; + float: left ; + margin-right: 1em } + +img.align-right, .figure.align-right, object.align-right, table.align-right { + clear: right ; + float: right ; + margin-left: 1em } + +img.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left } + +.align-center { + clear: both ; + text-align: center } + +.align-right { + text-align: right } + +/* reset inner alignment in figures */ +div.align-right { + text-align: inherit } + +/* div.align-center * { */ +/* text-align: left } */ + +.align-top { + vertical-align: top } + +.align-middle { + vertical-align: middle } + +.align-bottom { + vertical-align: bottom } + +ol.simple, ul.simple { + margin-bottom: 1em } + +ol.arabic { + list-style: decimal } + +ol.loweralpha { + list-style: lower-alpha } + +ol.upperalpha { + list-style: upper-alpha } + +ol.lowerroman { + list-style: lower-roman } + +ol.upperroman { + list-style: upper-roman } + +p.attribution { + text-align: right ; + margin-left: 50% } + +p.caption { + font-style: italic } + +p.credits { + font-style: italic ; + font-size: smaller } + +p.label { + white-space: nowrap } + +p.rubric { + font-weight: bold ; + font-size: larger ; + color: maroon ; + text-align: center } + +p.sidebar-title { + font-family: sans-serif ; + font-weight: bold ; + font-size: larger } + +p.sidebar-subtitle { + font-family: sans-serif ; + font-weight: bold } + +p.topic-title { + font-weight: bold } + +pre.address { + margin-bottom: 0 ; + margin-top: 0 ; + font: inherit } + +pre.literal-block, pre.doctest-block, pre.math, pre.code { + margin-left: 2em ; + margin-right: 2em } + +pre.code .ln { color: grey; } /* line numbers */ +pre.code, code { background-color: #eeeeee } +pre.code .comment, code .comment { color: #5C6576 } +pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold } +pre.code .literal.string, code .literal.string { color: #0C5404 } +pre.code .name.builtin, code .name.builtin { color: #352B84 } +pre.code .deleted, code .deleted { background-color: #DEB0A1} +pre.code .inserted, code .inserted { background-color: #A3D289} + +span.classifier { + font-family: sans-serif ; + font-style: oblique } + +span.classifier-delimiter { + font-family: sans-serif ; + font-weight: bold } + +span.interpreted { + font-family: sans-serif } + +span.option { + white-space: nowrap } + +span.pre { + white-space: pre } + +span.problematic { + color: red } + +span.section-subtitle { + /* font-size relative to parent (h1..h6 element) */ + font-size: 80% } + +table.citation { + border-left: solid 1px gray; + margin-left: 1px } + +table.docinfo { + margin: 2em 4em } + +table.docutils { + margin-top: 0.5em ; + margin-bottom: 0.5em } + +table.footnote { + border-left: solid 1px black; + margin-left: 1px } + +table.docutils td, table.docutils th, +table.docinfo td, table.docinfo th { + padding-left: 0.5em ; + padding-right: 0.5em ; + vertical-align: top } + +table.docutils th.field-name, table.docinfo th.docinfo-name { + font-weight: bold ; + text-align: left ; + white-space: nowrap ; + padding-left: 0 } + +/* "booktabs" style (no vertical lines) */ +table.docutils.booktabs { + border: 0px; + border-top: 2px solid; + border-bottom: 2px solid; + border-collapse: collapse; +} +table.docutils.booktabs * { + border: 0px; +} +table.docutils.booktabs th { + border-bottom: thin solid; + text-align: left; +} + +h1 tt.docutils, h2 tt.docutils, h3 tt.docutils, +h4 tt.docutils, h5 tt.docutils, h6 tt.docutils { + font-size: 100% } + +ul.auto-toc { + list-style-type: none } + +</style> +</head> +<body> +<div class="document" id="stock-location-orderpoint-cleanup"> +<h1 class="title">Stock Location Orderpoint Cleanup</h1> + +<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +!! This file is generated by oca-gen-addon-readme !! +!! changes will be overwritten. !! +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +!! source digest: sha256:739484c7a49a9d7cfdcb53fbe8c0ac62ac0529692120c55ef0bd90119a2eb6c5 +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> +<p><a class="reference external" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external" href="https://github.com/OCA/stock-logistics-orderpoint/tree/16.0/stock_location_orderpoint_cleanup"><img alt="OCA/stock-logistics-orderpoint" src="https://img.shields.io/badge/github-OCA%2Fstock--logistics--orderpoint-lightgray.png?logo=github" /></a> <a class="reference external" href="https://translation.odoo-community.org/projects/stock-logistics-orderpoint-16-0/stock-logistics-orderpoint-16-0-stock_location_orderpoint_cleanup"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external" href="https://runboat.odoo-community.org/builds?repo=OCA/stock-logistics-orderpoint&target_branch=16.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p> +<p>This module will help to clean generated moves from stock location +orderpoints.</p> +<p><strong>Table of contents</strong></p> +<div class="contents local topic" id="contents"> +<ul class="simple"> +<li><a class="reference internal" href="#use-cases-context" id="id1">Use Cases / Context</a></li> +<li><a class="reference internal" href="#configuration" id="id2">Configuration</a></li> +<li><a class="reference internal" href="#usage" id="id3">Usage</a></li> +<li><a class="reference internal" href="#bug-tracker" id="id4">Bug Tracker</a></li> +<li><a class="reference internal" href="#credits" id="id5">Credits</a><ul> +<li><a class="reference internal" href="#authors" id="id6">Authors</a></li> +<li><a class="reference internal" href="#contributors" id="id7">Contributors</a></li> +<li><a class="reference internal" href="#maintainers" id="id8">Maintainers</a></li> +</ul> +</li> +</ul> +</div> +<div class="section" id="use-cases-context"> +<h1><a class="toc-backref" href="#id1">Use Cases / Context</a></h1> +<p>Location orderpoints module was designed (first) to generate +replenishment moves for particular stock locations. e.g.: we have a +reserve stock and a preparation stock.</p> +<p>As warehouse life is not static, moves for a product can be canceled, +preparation stock could have been refilled with another move, …</p> +<p>So, generated moves from location orderpoints can become obsolete.</p> +<p>This module should help to clean orderpoint moves and regenerate moves.</p> +</div> +<div class="section" id="configuration"> +<h1><a class="toc-backref" href="#id2">Configuration</a></h1> +<ul class="simple"> +<li>There is a security group ‘Location Orderpoint Cleanup Group’. Users +that can have access to cleanup action should be in that group. By +default, all users that are in ‘Inventory/Administrator’ are in that +group.</li> +<li>You can configure crons to execute cleanup actions.<ul> +<li>Enable debug mode, go to Settings > Technical > Scheduled Actions</li> +<li>Add a cron with:<ul> +<li>base model ‘Stock Location Orderpoint’</li> +<li>In python code, add a line ‘model.run_cleanup(orderpoints, +run_after)’ where ‘orderpoints’ is a list of orderpoint ids and +‘run_after’ is True if you want to run the orderpoint(s) after +cleanup.</li> +<li>If you want to cleanup ordepoints by replenish method, add a +line ‘model.run_cleanup_method(replenish_method, run_after)’ +where ‘replenish_method’ is ‘fill_up’ by default (depending on +extension modules you have installed)</li> +</ul> +</li> +</ul> +</li> +</ul> +</div> +<div class="section" id="usage"> +<h1><a class="toc-backref" href="#id3">Usage</a></h1> +<ul class="simple"> +<li>You can have cron jobs to execute the cleanup actions (see +Configuration section).</li> +<li>If you are in the ‘Location Orderpoint Cleanup Group’, you can launch +manually the cleanup on the orderpoint form level:<ul> +<li>Click on ‘Cleanup replenishments’.</li> +<li>In the wizard, check the ‘Run After’ box if you want the +orderpoint to be run after cleanup.</li> +</ul> +</li> +</ul> +</div> +<div class="section" id="bug-tracker"> +<h1><a class="toc-backref" href="#id4">Bug Tracker</a></h1> +<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/stock-logistics-orderpoint/issues">GitHub Issues</a>. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +<a class="reference external" href="https://github.com/OCA/stock-logistics-orderpoint/issues/new?body=module:%20stock_location_orderpoint_cleanup%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p> +<p>Do not contact contributors directly about support or help with technical issues.</p> +</div> +<div class="section" id="credits"> +<h1><a class="toc-backref" href="#id5">Credits</a></h1> +<div class="section" id="authors"> +<h2><a class="toc-backref" href="#id6">Authors</a></h2> +<ul class="simple"> +<li>ACSONE SA/NV</li> +</ul> +</div> +<div class="section" id="contributors"> +<h2><a class="toc-backref" href="#id7">Contributors</a></h2> +<ul class="simple"> +<li>Denis Roussel <a class="reference external" href="mailto:denis.roussel@acsone.eu">denis.roussel@acsone.eu</a></li> +</ul> +</div> +<div class="section" id="maintainers"> +<h2><a class="toc-backref" href="#id8">Maintainers</a></h2> +<p>This module is maintained by the OCA.</p> +<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a> +<p>OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.</p> +<p>This module is part of the <a class="reference external" href="https://github.com/OCA/stock-logistics-orderpoint/tree/16.0/stock_location_orderpoint_cleanup">OCA/stock-logistics-orderpoint</a> project on GitHub.</p> +<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p> +</div> +</div> +</div> +</body> +</html> diff --git a/stock_location_orderpoint_cleanup/tests/__init__.py b/stock_location_orderpoint_cleanup/tests/__init__.py new file mode 100644 index 00000000..c05925e7 --- /dev/null +++ b/stock_location_orderpoint_cleanup/tests/__init__.py @@ -0,0 +1 @@ +from . import test_location_orderpoint_cleanup diff --git a/stock_location_orderpoint_cleanup/tests/test_location_orderpoint_cleanup.py b/stock_location_orderpoint_cleanup/tests/test_location_orderpoint_cleanup.py new file mode 100644 index 00000000..275b52c0 --- /dev/null +++ b/stock_location_orderpoint_cleanup/tests/test_location_orderpoint_cleanup.py @@ -0,0 +1,369 @@ +# Copyright 2024 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo.fields import Command +from odoo.tests.common import users + +from odoo.addons.queue_job.tests.common import trap_jobs +from odoo.addons.stock_location_orderpoint.tests.common import ( + TestLocationOrderpointCommon, +) + + +class TestLocationOrderpointCleanup(TestLocationOrderpointCommon): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.user = cls.env["res.users"].create( + { + "name": "Cleanup user", + "login": "cleanup", + "email": "cleanup@test.com", + "groups_id": [ + Command.set( + [ + cls.env.ref("stock.group_stock_user").id, + cls.env.ref( + "stock_location_orderpoint_cleanup.group_location_cleanup" + ).id, + ] + ) + ], + } + ) + cls.product_obj = cls.env["product.product"] + cls.wizard_obj = cls.env["stock.location.orderpoint.cleanup"] + # Create some products in order to get a sample of several moves + + cls.product_2 = cls.product_obj.create( + { + "name": "Product 2", + "type": "product", + } + ) + cls.product_3 = cls.product_obj.create( + { + "name": "Product 3", + "type": "product", + } + ) + cls.product_4 = cls.product_obj.create( + { + "name": "Product 3", + "type": "product", + } + ) + + @users("cleanup") + def test_orderpoint(self): + """ """ + orderpoint, location_src = self._create_orderpoint_complete( + "Reserve", trigger="manual" + ) + + # Create quantities on Reserve location + self._create_quants(self.product, location_src, 20) + self._create_quants(self.product_2, location_src, 20) + self._create_quants(self.product_3, location_src, 20) + self._create_quants(self.product_4, location_src, 20) + + move_1 = self._create_outgoing_move(12, product=self.product) + move_2 = self._create_outgoing_move(13, product=self.product_2) + move_3 = self._create_outgoing_move(14, product=self.product_3) + move_4 = self._create_outgoing_move(15, product=self.product_4) + + moves = move_1 | move_2 | move_3 | move_4 + + self.assertEqual({"confirmed"}, set(moves.mapped("state"))) + + self._run_replenishment(orderpoint) + + replenish_move_1 = self._get_replenishment_move(orderpoint, self.product) + replenish_move_2 = self._get_replenishment_move(orderpoint, self.product_2) + replenish_move_3 = self._get_replenishment_move(orderpoint, self.product_3) + replenish_move_4 = self._get_replenishment_move(orderpoint, self.product_4) + + replenish_moves = ( + replenish_move_1 | replenish_move_2 | replenish_move_3 | replenish_move_4 + ) + + message_before = replenish_move_1.picking_id.message_ids + orderpoint._cleanup() + + self.assertEqual({"cancel"}, set(replenish_moves.mapped("state"))) + message_after = replenish_move_1.picking_id.message_ids - message_before + self.assertIn( + "These moves have been cleaned up for location orderpoint", + message_after.preview, + ) + + @users("cleanup") + def test_orderpoint_wizard(self): + """ """ + orderpoint, location_src = self._create_orderpoint_complete( + "Reserve", trigger="manual" + ) + + # Create quantities on Reserve location + self._create_quants(self.product, location_src, 20) + self._create_quants(self.product_2, location_src, 20) + self._create_quants(self.product_3, location_src, 20) + self._create_quants(self.product_4, location_src, 20) + + move_1 = self._create_outgoing_move(12, product=self.product) + move_2 = self._create_outgoing_move(13, product=self.product_2) + move_3 = self._create_outgoing_move(14, product=self.product_3) + move_4 = self._create_outgoing_move(15, product=self.product_4) + + moves = move_1 | move_2 | move_3 | move_4 + + self.assertEqual({"confirmed"}, set(moves.mapped("state"))) + + self._run_replenishment(orderpoint) + + replenish_move_1 = self._get_replenishment_move(orderpoint, self.product) + replenish_move_2 = self._get_replenishment_move(orderpoint, self.product_2) + replenish_move_3 = self._get_replenishment_move(orderpoint, self.product_3) + replenish_move_4 = self._get_replenishment_move(orderpoint, self.product_4) + + replenish_moves = ( + replenish_move_1 | replenish_move_2 | replenish_move_3 | replenish_move_4 + ) + + message_before = replenish_move_1.picking_id.message_ids + wizard = self.wizard_obj.with_context( + active_ids=orderpoint.ids, active_model="stock.location.orderpoint" + ).create({}) + + with trap_jobs() as trap: + wizard.doit() + trap.assert_jobs_count(1) + trap.perform_enqueued_jobs() + + self.assertEqual({"cancel"}, set(replenish_moves.mapped("state"))) + message_after = replenish_move_1.picking_id.message_ids - message_before + self.assertIn( + "These moves have been cleaned up for location orderpoint", + message_after.preview, + ) + + @users("cleanup") + def test_orderpoint_run_after(self): + """ """ + orderpoint, location_src = self._create_orderpoint_complete( + "Reserve", trigger="manual" + ) + + # Create quantities on Reserve location + self._create_quants(self.product, location_src, 20) + self._create_quants(self.product_2, location_src, 20) + self._create_quants(self.product_3, location_src, 20) + self._create_quants(self.product_4, location_src, 20) + + move_1 = self._create_outgoing_move(12, product=self.product) + move_2 = self._create_outgoing_move(13, product=self.product_2) + move_3 = self._create_outgoing_move(14, product=self.product_3) + move_4 = self._create_outgoing_move(15, product=self.product_4) + + moves = move_1 | move_2 | move_3 | move_4 + + self.assertEqual({"confirmed"}, set(moves.mapped("state"))) + + self._run_replenishment(orderpoint) + + replenish_move_1 = self._get_replenishment_move(orderpoint, self.product) + replenish_move_2 = self._get_replenishment_move(orderpoint, self.product_2) + replenish_move_3 = self._get_replenishment_move(orderpoint, self.product_3) + replenish_move_4 = self._get_replenishment_move(orderpoint, self.product_4) + + replenish_moves = ( + replenish_move_1 | replenish_move_2 | replenish_move_3 | replenish_move_4 + ) + + orderpoint._cleanup(True) + + self.assertEqual({"cancel"}, set(replenish_moves.mapped("state"))) + + replenish_move_1 = self._get_replenishment_move(orderpoint, self.product) + replenish_move_2 = self._get_replenishment_move(orderpoint, self.product_2) + replenish_move_3 = self._get_replenishment_move(orderpoint, self.product_3) + replenish_move_4 = self._get_replenishment_move(orderpoint, self.product_4) + + replenish_moves = ( + replenish_move_1 | replenish_move_2 | replenish_move_3 | replenish_move_4 + ) + + self.assertEqual({"assigned"}, set(replenish_moves.mapped("state"))) + + @users("cleanup") + def test_orderpoint_run_after_partial(self): + """ """ + orderpoint, location_src = self._create_orderpoint_complete( + "Reserve", trigger="manual" + ) + + # Create quantities on Reserve location + self._create_quants(self.product, location_src, 20) + self._create_quants(self.product_2, location_src, 20) + self._create_quants(self.product_3, location_src, 20) + self._create_quants(self.product_4, location_src, 20) + + move_1 = self._create_outgoing_move(12, product=self.product) + move_2 = self._create_outgoing_move(13, product=self.product_2) + move_3 = self._create_outgoing_move(14, product=self.product_3) + move_4 = self._create_outgoing_move(15, product=self.product_4) + + moves = move_1 | move_2 | move_3 | move_4 + + self.assertEqual({"confirmed"}, set(moves.mapped("state"))) + + self._run_replenishment(orderpoint) + + replenish_move_1 = self._get_replenishment_move(orderpoint, self.product) + replenish_move_2 = self._get_replenishment_move(orderpoint, self.product_2) + replenish_move_3 = self._get_replenishment_move(orderpoint, self.product_3) + replenish_move_4 = self._get_replenishment_move(orderpoint, self.product_4) + + replenish_moves = ( + replenish_move_1 | replenish_move_2 | replenish_move_3 | replenish_move_4 + ) + + # Cancel some outgoing moves + (move_3 | move_4)._action_cancel() + + orderpoint._cleanup(True) + + self.assertEqual({"cancel"}, set(replenish_moves.mapped("state"))) + + replenish_move_1 = self._get_replenishment_move(orderpoint, self.product) + replenish_move_2 = self._get_replenishment_move(orderpoint, self.product_2) + replenish_move_3 = self._get_replenishment_move(orderpoint, self.product_3) + replenish_move_4 = self._get_replenishment_move(orderpoint, self.product_4) + + self.assertFalse(replenish_move_3 | replenish_move_4) + + self.assertTrue(replenish_move_1 | replenish_move_2) + + @users("cleanup") + def test_orderpoint_cron(self): + """ """ + orderpoint, location_src = self._create_orderpoint_complete( + "Reserve", trigger="manual" + ) + + # Create quantities on Reserve location + self._create_quants(self.product, location_src, 20) + self._create_quants(self.product_2, location_src, 20) + self._create_quants(self.product_3, location_src, 20) + self._create_quants(self.product_4, location_src, 20) + + move_1 = self._create_outgoing_move(12, product=self.product) + move_2 = self._create_outgoing_move(13, product=self.product_2) + move_3 = self._create_outgoing_move(14, product=self.product_3) + move_4 = self._create_outgoing_move(15, product=self.product_4) + + moves = move_1 | move_2 | move_3 | move_4 + + self.assertEqual({"confirmed"}, set(moves.mapped("state"))) + + self._run_replenishment(orderpoint) + + replenish_move_1 = self._get_replenishment_move(orderpoint, self.product) + replenish_move_2 = self._get_replenishment_move(orderpoint, self.product_2) + replenish_move_3 = self._get_replenishment_move(orderpoint, self.product_3) + replenish_move_4 = self._get_replenishment_move(orderpoint, self.product_4) + + replenish_moves = ( + replenish_move_1 | replenish_move_2 | replenish_move_3 | replenish_move_4 + ) + with trap_jobs() as trap: + self.env["stock.location.orderpoint"].run_cleanup(orderpoint.id, True) + trap.assert_jobs_count(1) + trap.perform_enqueued_jobs() + + self.assertEqual({"cancel"}, set(replenish_moves.mapped("state"))) + + replenish_move_1 = self._get_replenishment_move(orderpoint, self.product) + replenish_move_2 = self._get_replenishment_move(orderpoint, self.product_2) + replenish_move_3 = self._get_replenishment_move(orderpoint, self.product_3) + replenish_move_4 = self._get_replenishment_move(orderpoint, self.product_4) + + replenish_moves = ( + replenish_move_1 | replenish_move_2 | replenish_move_3 | replenish_move_4 + ) + + self.assertEqual({"assigned"}, set(replenish_moves.mapped("state"))) + + @users("cleanup") + def test_orderpoint_cron_replenish(self): + """ """ + orderpoint, location_src = self._create_orderpoint_complete( + "Reserve", trigger="manual" + ) + + # Create quantities on Reserve location + self._create_quants(self.product, location_src, 20) + self._create_quants(self.product_2, location_src, 20) + self._create_quants(self.product_3, location_src, 20) + self._create_quants(self.product_4, location_src, 20) + + move_1 = self._create_outgoing_move(12, product=self.product) + move_2 = self._create_outgoing_move(13, product=self.product_2) + move_3 = self._create_outgoing_move(14, product=self.product_3) + move_4 = self._create_outgoing_move(15, product=self.product_4) + + moves = move_1 | move_2 | move_3 | move_4 + + self.assertEqual({"confirmed"}, set(moves.mapped("state"))) + + self._run_replenishment(orderpoint) + + replenish_move_1 = self._get_replenishment_move(orderpoint, self.product) + replenish_move_2 = self._get_replenishment_move(orderpoint, self.product_2) + replenish_move_3 = self._get_replenishment_move(orderpoint, self.product_3) + replenish_move_4 = self._get_replenishment_move(orderpoint, self.product_4) + + replenish_moves = ( + replenish_move_1 | replenish_move_2 | replenish_move_3 | replenish_move_4 + ) + with trap_jobs() as trap: + self.env["stock.location.orderpoint"].run_cleanup_method(run_after=True) + trap.assert_jobs_count(1) + trap.perform_enqueued_jobs() + + self.assertEqual({"cancel"}, set(replenish_moves.mapped("state"))) + + replenish_move_1 = self._get_replenishment_move(orderpoint, self.product) + replenish_move_2 = self._get_replenishment_move(orderpoint, self.product_2) + replenish_move_3 = self._get_replenishment_move(orderpoint, self.product_3) + replenish_move_4 = self._get_replenishment_move(orderpoint, self.product_4) + + replenish_moves = ( + replenish_move_1 | replenish_move_2 | replenish_move_3 | replenish_move_4 + ) + + self.assertEqual({"assigned"}, set(replenish_moves.mapped("state"))) + + def test_log(self): + # Test the warning message + with self.assertLogs( + "odoo.addons.stock_location_orderpoint_cleanup", level="WARNING" + ) as capture: + self.env["stock.location.orderpoint"].run_cleanup_method( + "brol", run_after=True + ) + self.assertIn( + "You try to call 'run_cleanup_method' with an undefined replenish method", + capture.output[0], + ) + + def test_action(self): + orderpoint, location_src = self._create_orderpoint_complete( + "Reserve", trigger="manual" + ) + + action = orderpoint.with_context( + active_ids=orderpoint.ids, active_model="stock.location.orderpoint" + ).get_cleanup_action() + self.assertDictContainsSubset( + {"res_model": "stock.location.orderpoint.cleanup"}, action + ) diff --git a/stock_location_orderpoint_cleanup/views/stock_location_orderpoint.xml b/stock_location_orderpoint_cleanup/views/stock_location_orderpoint.xml new file mode 100644 index 00000000..b1e723b8 --- /dev/null +++ b/stock_location_orderpoint_cleanup/views/stock_location_orderpoint.xml @@ -0,0 +1,23 @@ +<!-- Copyright 2024 ACSONE SA/NV + License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). --> +<odoo> + <record model="ir.ui.view" id="stock_location_orderpoint_form"> + <field name="name">stock.location.orderpoint.form</field> + <field name="model">stock.location.orderpoint</field> + <field + name="inherit_id" + ref="stock_location_orderpoint.stock_location_orderpoint_form" + /> + <field name="arch" type="xml"> + <button name="run_replenishment" position="after"> + <button + name="get_cleanup_action" + type="object" + string="Cleanup replenishments" + attrs="{'invisible': [('active', '=', False)] }" + groups="stock_location_orderpoint_cleanup.group_location_cleanup" + /> + </button> + </field> + </record> +</odoo> diff --git a/stock_location_orderpoint_cleanup/wizards/__init__.py b/stock_location_orderpoint_cleanup/wizards/__init__.py new file mode 100644 index 00000000..caa89e8a --- /dev/null +++ b/stock_location_orderpoint_cleanup/wizards/__init__.py @@ -0,0 +1 @@ +from . import stock_location_orderpoint_cleanup diff --git a/stock_location_orderpoint_cleanup/wizards/stock_location_orderpoint_cleanup.py b/stock_location_orderpoint_cleanup/wizards/stock_location_orderpoint_cleanup.py new file mode 100644 index 00000000..9f9e4975 --- /dev/null +++ b/stock_location_orderpoint_cleanup/wizards/stock_location_orderpoint_cleanup.py @@ -0,0 +1,37 @@ +# Copyright 2023 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo import api, fields, models +from odoo.fields import Command + + +class StockLocationOrderpointCleanup(models.TransientModel): + + _name = "stock.location.orderpoint.cleanup" + _description = "Wizard to cleanup the generated moves through location orderpoints" + + orderpoint_ids = fields.Many2many( + comodel_name="stock.location.orderpoint", + name="Orderpoints", + ondelete="cascade", + ) + run_after = fields.Boolean( + help="Check this if you want to run the orderpoint after cleanup." + ) + + @api.model + def default_get(self, fields_list): + res = super().default_get(fields_list=fields_list) + if "orderpoint_id" not in res: + if self.env.context.get( + "active_model" + ) == "stock.location.orderpoint" and self.env.context.get("active_ids"): + active_ids = self.env.context.get("active_ids") + res["orderpoint_ids"] = [Command.set(active_ids)] + + return res + + def doit(self): + for wizard in self: + for orderpoint in wizard.orderpoint_ids: + orderpoint.cleanup(run_after=wizard.run_after) + return {"type": "ir.actions.act_window_close"} diff --git a/stock_location_orderpoint_cleanup/wizards/stock_location_orderpoint_cleanup.xml b/stock_location_orderpoint_cleanup/wizards/stock_location_orderpoint_cleanup.xml new file mode 100644 index 00000000..d970e1e1 --- /dev/null +++ b/stock_location_orderpoint_cleanup/wizards/stock_location_orderpoint_cleanup.xml @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="utf-8" ?> +<!-- Copyright 2023 ACSONE SA/NV + License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). --> +<odoo> + + <record model="ir.ui.view" id="stock_location_orderpoint_cleanup_form_view"> + <field + name="name" + >stock.location.orderpoint.cleanup.form (in stock_location_orderpoint_cleanup)</field> + <field name="model">stock.location.orderpoint.cleanup</field> + <field name="arch" type="xml"> + <form string="Stock Location Orderpoint Cleanup"> + <!-- TODO --> + <p class="oe_grey"> + This will launch the cleanup process for all selected location orderpoints.<br + /> + Check the "Run After" box if you want to run the orderpoints after + that cleanup. + </p> + <group> + <field name="run_after" /> + <field name="orderpoint_ids" /> + </group> + <footer> + <button + name="doit" + string="Cleanup" + class="btn-primary" + type="object" + /> + <button string="Cancel" class="btn-default" special="cancel" /> + </footer> + </form> + </field> + </record> + + <record + model="ir.actions.act_window" + id="stock_location_orderpoint_cleanup_act_window" + > + <field name="name">Stock Location Orderpoint Cleanup</field> <!-- TODO --> + <field name="res_model">stock.location.orderpoint.cleanup</field> + <field name="view_mode">form</field> + <field name="context">{}</field> + <field name="target">new</field> + </record> + + +</odoo>