From 748f577fc031a82017a4279bb973069b50e94030 Mon Sep 17 00:00:00 2001 From: dsolanki Date: Wed, 8 Feb 2023 14:46:21 +0530 Subject: [PATCH 1/3] [ADD] stock_picking_on_hold --- .github/workflows/test.yml | 4 +- .../odoo/addons/stock_picking_on_hold | 1 + setup/stock_picking_on_hold/setup.py | 6 + stock_picking_on_hold/README.rst | 82 ++++++++ stock_picking_on_hold/__init__.py | 3 + stock_picking_on_hold/__manifest__.py | 27 +++ stock_picking_on_hold/models/__init__.py | 1 + stock_picking_on_hold/models/account_move.py | 41 ++++ .../models/payment_method.py | 12 ++ stock_picking_on_hold/models/sale_order.py | 21 ++ stock_picking_on_hold/models/stock.py | 190 ++++++++++++++++++ stock_picking_on_hold/readme/CONFIGURE.rst | 1 + stock_picking_on_hold/readme/CONTRIBUTORS.rst | 3 + stock_picking_on_hold/readme/DESCRIPTION.rst | 1 + stock_picking_on_hold/readme/USAGE.rst | 4 + .../views/payment_method.xml | 16 ++ .../views/sale_order_view.xml | 38 ++++ stock_picking_on_hold/views/stock.xml | 33 +++ 18 files changed, 482 insertions(+), 2 deletions(-) create mode 120000 setup/stock_picking_on_hold/odoo/addons/stock_picking_on_hold create mode 100644 setup/stock_picking_on_hold/setup.py create mode 100644 stock_picking_on_hold/README.rst create mode 100644 stock_picking_on_hold/__init__.py create mode 100644 stock_picking_on_hold/__manifest__.py create mode 100644 stock_picking_on_hold/models/__init__.py create mode 100644 stock_picking_on_hold/models/account_move.py create mode 100644 stock_picking_on_hold/models/payment_method.py create mode 100644 stock_picking_on_hold/models/sale_order.py create mode 100644 stock_picking_on_hold/models/stock.py create mode 100644 stock_picking_on_hold/readme/CONFIGURE.rst create mode 100644 stock_picking_on_hold/readme/CONTRIBUTORS.rst create mode 100644 stock_picking_on_hold/readme/DESCRIPTION.rst create mode 100644 stock_picking_on_hold/readme/USAGE.rst create mode 100644 stock_picking_on_hold/views/payment_method.xml create mode 100644 stock_picking_on_hold/views/sale_order_view.xml create mode 100644 stock_picking_on_hold/views/stock.xml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 651375f866d9..9086b5360566 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -43,11 +43,11 @@ jobs: include: "portal_sale_personal_data_only" name: test with OCB - container: ghcr.io/oca/oca-ci/py3.8-odoo15.0:latest - exclude: "portal_sale_personal_data_only" + exclude: "portal_sale_personal_data_only, sale_automatic_workflow_job, sale_stock_secondary_unit" makepot: "true" name: test with Odoo - container: ghcr.io/oca/oca-ci/py3.8-ocb15.0:latest - exclude: "portal_sale_personal_data_only" + exclude: "portal_sale_personal_data_only, sale_automatic_workflow_job, sale_stock_secondary_unit" name: test with OCB services: postgres: diff --git a/setup/stock_picking_on_hold/odoo/addons/stock_picking_on_hold b/setup/stock_picking_on_hold/odoo/addons/stock_picking_on_hold new file mode 120000 index 000000000000..d2ec518b64d6 --- /dev/null +++ b/setup/stock_picking_on_hold/odoo/addons/stock_picking_on_hold @@ -0,0 +1 @@ +../../../../stock_picking_on_hold \ No newline at end of file diff --git a/setup/stock_picking_on_hold/setup.py b/setup/stock_picking_on_hold/setup.py new file mode 100644 index 000000000000..28c57bb64031 --- /dev/null +++ b/setup/stock_picking_on_hold/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/stock_picking_on_hold/README.rst b/stock_picking_on_hold/README.rst new file mode 100644 index 000000000000..cbae81de3ea4 --- /dev/null +++ b/stock_picking_on_hold/README.rst @@ -0,0 +1,82 @@ +=================================================== +Sale Payment Method - Hold Pickings (Until Payment) +=================================================== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Production%2FStable-green.png + :target: https://odoo-community.org/page/development-status + :alt: Production/Stable +.. |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%2Fsale--workflow-lightgray.png?logo=github + :target: https://github.com/OCA/sale-workflow/tree/15.0/stock_picking_on_hold + :alt: OCA/sale-workflow +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/sale-workflow-15-0/sale-workflow-15-0-stock_picking_on_hold + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/167/15.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module allows to hold the picking until the invoice is paid + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +1) You need to generate "Sale Order" using "Payment Method: Enabled Hold Picking Untill Payment" +2) Picking can not "confirm" untill the payment is "confirmed" of that Sale Order + + +Configuration +============= + +Go to Invoicing > Payment Methods > Select your payment method > Enable "Hold Picking Untill Payment" + + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Contributors +~~~~~~~~~~~~ + +* Thomas Rehn +* Katja Matthes +* Dhara solanki + +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/sale-workflow `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/stock_picking_on_hold/__init__.py b/stock_picking_on_hold/__init__.py new file mode 100644 index 000000000000..69f7babdfb1a --- /dev/null +++ b/stock_picking_on_hold/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import models diff --git a/stock_picking_on_hold/__manifest__.py b/stock_picking_on_hold/__manifest__.py new file mode 100644 index 000000000000..4b9fc0a793fe --- /dev/null +++ b/stock_picking_on_hold/__manifest__.py @@ -0,0 +1,27 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "Sale Payment Method - Hold Pickings (until payment)", + "version": "15.0.1.0.0", + "website": "https://github.com/OCA/sale-workflow", + "category": "Warehouse", + "summary": """ + Sale Payment Method - Hold Pickings until payment + =================================================== + * This module allows to hold the picking until the invoice is paid""", + "depends": [ + "stock", + "website_sale", + "sale_management", + "account_payment_mode", + ], + "author": "initOS GmbH, Odoo Community Association (OCA)", + "license": "AGPL-3", + "data": [ + "views/stock.xml", + "views/payment_method.xml", + "views/sale_order_view.xml", + ], + "installable": True, + "application": False, +} diff --git a/stock_picking_on_hold/models/__init__.py b/stock_picking_on_hold/models/__init__.py new file mode 100644 index 000000000000..2aaf13417d17 --- /dev/null +++ b/stock_picking_on_hold/models/__init__.py @@ -0,0 +1 @@ +from . import account_move, payment_method, sale_order, stock diff --git a/stock_picking_on_hold/models/account_move.py b/stock_picking_on_hold/models/account_move.py new file mode 100644 index 000000000000..7c92be861d78 --- /dev/null +++ b/stock_picking_on_hold/models/account_move.py @@ -0,0 +1,41 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import models + + +class AccountMove(models.Model): + _inherit = "account.move" + + def write(self, vals): + """When an invoice is paid a picking that is hold until payment may need + a status check""" + res = super(AccountMove, self).write(vals) + pickings_to_check = self.env["stock.picking"] + for invoice in self.filtered(lambda i: i.state == "posted"): + for sale in invoice.invoice_line_ids.mapped("sale_line_ids").mapped( + "order_id" + ): + for picking in sale.picking_ids: + if picking.state == "hold": + pickings_to_check += picking + # trigger availability check + if pickings_to_check: + pickings_to_check.action_assign_unpaid() + return res + + def confirm_paid(self): + """ + When an invoice is paid + a picking that is hold until payment may need a status check. + """ + res = super(AccountMove, self).confirm_paid() + # collect all pickings to check + pickings_to_check = self.env["stock.picking"] + for invoice in self: + for sale in invoice.sale_ids: + for picking in sale.picking_ids: + if picking.state == "hold": + pickings_to_check += picking + # trigger availability check + pickings_to_check.action_assign() + return res diff --git a/stock_picking_on_hold/models/payment_method.py b/stock_picking_on_hold/models/payment_method.py new file mode 100644 index 000000000000..51a82a8d6ce2 --- /dev/null +++ b/stock_picking_on_hold/models/payment_method.py @@ -0,0 +1,12 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class PaymentMethod(models.Model): + _inherit = "account.payment.method" + + hold_picking_until_payment = fields.Boolean( + help="If set to true, pickings will not be automatically confirmed when " + "the invoice has not been paid.", + ) diff --git a/stock_picking_on_hold/models/sale_order.py b/stock_picking_on_hold/models/sale_order.py new file mode 100644 index 000000000000..6924eda0193e --- /dev/null +++ b/stock_picking_on_hold/models/sale_order.py @@ -0,0 +1,21 @@ +from odoo import api, fields, models + + +class SaleOrder(models.Model): + _inherit = "sale.order" + + payment_method_id = fields.Many2one( + "account.payment.method", + string="Payment Method", + ondelete="restrict", + ) + hold_picking_until_payment = fields.Boolean( + related="payment_method_id.hold_picking_until_payment" + ) + + @api.model + def create(self, vals): + sale_order = super(SaleOrder, self).create(vals) + if not sale_order.website_id: + sale_order.hold_picking_until_payment = True + return sale_order diff --git a/stock_picking_on_hold/models/stock.py b/stock_picking_on_hold/models/stock.py new file mode 100644 index 000000000000..7aa5a86e6ac6 --- /dev/null +++ b/stock_picking_on_hold/models/stock.py @@ -0,0 +1,190 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from collections import defaultdict + +from odoo import api, fields, models +from odoo.tools import float_compare + + +class StockMove(models.Model): + _inherit = "stock.move" + + def _action_assign(self): + for move in self: + if move.picking_id: + # Write dummy variable so that the state of the + # picking will be updated. + move.picking_id.dummy_int_to_trigger_state_update = ( + move.picking_id.dummy_int_to_trigger_state_update + 1 + ) % 2 + if ( + self._context.get("payment_check", True) + and move.picking_id.is_on_hold() + ): + # Do not assign picking because it is on hold + return False + return super(StockMove, self)._action_assign() + + def _search_picking_for_assignation(self): + self.ensure_one() + picking = self.env["stock.picking"].search( + [ + ("group_id", "=", self.group_id.id), + ("location_id", "=", self.location_id.id), + ("location_dest_id", "=", self.location_dest_id.id), + ("picking_type_id", "=", self.picking_type_id.id), + ("printed", "=", False), + ( + "state", + "in", + [ + "draft", + "confirmed", + "waiting", + "partially_available", + "assigned", + "hold", + ], + ), + ], + limit=1, + ) + return picking + + +class StockPicking(models.Model): + _inherit = "stock.picking" + + @api.depends( + "move_type", + "dummy_int_to_trigger_state_update", + "immediate_transfer", + "move_lines.state", + "move_lines.picking_id", + ) + def _compute_state(self): + """State of a picking depends on the state of its related stock.move + - Draft: only used for "planned pickings" + - Waiting: if the picking is not ready to be sent so if + - (a) no quantity could be reserved at all or if + - (b) some quantities could be reserved and the shipping policy is + "deliver all at once" + - Waiting another move: if the picking is waiting for another move + - Ready: if the picking is ready to be sent so if: + - (a) all quantities are reserved or if + - (b) some quantities could be reserved and the shipping policy is + "as soon as possible" + - Done: if the picking is done. + - Cancelled: if the picking is cancelled + """ + # self.ensure_one() + picking_moves_state_map = defaultdict(dict) + picking_move_lines = defaultdict(set) + for move in self.env["stock.move"].search([("picking_id", "in", self.ids)]): + picking_id = move.picking_id + move_state = move.state + picking_moves_state_map[picking_id.id].update( + { + "any_draft": picking_moves_state_map[picking_id.id].get( + "any_draft", False + ) + or move_state == "draft", + "all_cancel": picking_moves_state_map[picking_id.id].get( + "all_cancel", True + ) + and move_state == "cancel", + "all_cancel_done": picking_moves_state_map[picking_id.id].get( + "all_cancel_done", True + ) + and move_state in ("cancel", "done"), + } + ) + picking_move_lines[picking_id.id].add(move.id) + for picking in self: + picking_id = (picking.ids and picking.ids[0]) or picking.id + if not picking_moves_state_map[picking_id]: + picking.state = "draft" + elif picking_moves_state_map[picking_id]["any_draft"]: + picking.state = "draft" + elif picking_moves_state_map[picking_id]["all_cancel"]: + picking.state = "cancel" + elif picking_moves_state_map[picking_id]["all_cancel_done"]: + picking.state = "done" + else: + states = ["confirmed", "assigned", "partially_available"] + relevant_move_state = ( + self.env["stock.move"] + .browse(picking_move_lines[picking_id]) + ._get_relevant_state_among_moves() + ) + if relevant_move_state in states and picking.is_on_hold(): + picking.state = "hold" + elif picking.immediate_transfer and relevant_move_state not in ( + "draft", + "cancel", + "done", + ): + picking.state = "assigned" + elif relevant_move_state == "partially_available": + picking.state = "assigned" + else: + picking.state = relevant_move_state + + dummy_int_to_trigger_state_update = fields.Integer( + string="Dummy Integer (to trigger update of state field)" + ) + + state = fields.Selection( + selection_add=[("hold", "Waiting For Payment"), ("assigned",)], + ondelete={"hold": "set confirmed"}, + ) + + @api.depends("immediate_transfer", "state") + def _compute_show_check_availability(self): + for picking in self: + if picking.immediate_transfer or picking.state not in ( + "confirmed", + "waiting", + "assigned", + "hold", + ): + picking.show_check_availability = False + continue + picking.show_check_availability = any( + move.state in ("waiting", "confirmed", "partially_available") + and float_compare( + move.product_uom_qty, + 0, + precision_rounding=move.product_uom.rounding, + ) + for move in picking.move_lines + ) + + @api.depends("state") + def _compute_show_validate(self): + states = ("draft", "waiting", "confirmed", "assigned", "hold") + for picking in self: + if self._context.get("planned_picking") and picking.state == "draft": + picking.show_validate = False + elif picking.state not in states or not picking.is_locked: + picking.show_validate = False + else: + picking.show_validate = True + + def is_on_hold(self): + """Returns True if picking should be held because the + corresponding order has not been paid yet.""" + self.ensure_one() + # Skip checking of the payment + if not self._context.get("payment_check", True): + return False + if ( + self.sale_id + and self.sale_id.hold_picking_until_payment + and not self.sale_id.invoice_status == "invoiced" + ): + return True + return False + + def action_assign_unpaid(self): + self.with_context(payment_check=False).action_assign() + self.write({"state": "assigned"}) diff --git a/stock_picking_on_hold/readme/CONFIGURE.rst b/stock_picking_on_hold/readme/CONFIGURE.rst new file mode 100644 index 000000000000..452a2565c8a8 --- /dev/null +++ b/stock_picking_on_hold/readme/CONFIGURE.rst @@ -0,0 +1 @@ +#. Go to Invoicing > Payment Methods > Select your payment method > Enable "Hold Picking Untill Payment" diff --git a/stock_picking_on_hold/readme/CONTRIBUTORS.rst b/stock_picking_on_hold/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000000..3c02a874f620 --- /dev/null +++ b/stock_picking_on_hold/readme/CONTRIBUTORS.rst @@ -0,0 +1,3 @@ +* Thomas Rehn +* Katja Matthes +* Dhara Solanki diff --git a/stock_picking_on_hold/readme/DESCRIPTION.rst b/stock_picking_on_hold/readme/DESCRIPTION.rst new file mode 100644 index 000000000000..39fba1240a20 --- /dev/null +++ b/stock_picking_on_hold/readme/DESCRIPTION.rst @@ -0,0 +1 @@ +This module allows to hold the picking until the invoice is paid. diff --git a/stock_picking_on_hold/readme/USAGE.rst b/stock_picking_on_hold/readme/USAGE.rst new file mode 100644 index 000000000000..72445f6cfa73 --- /dev/null +++ b/stock_picking_on_hold/readme/USAGE.rst @@ -0,0 +1,4 @@ +To use functionality of module, + +1) You need to generate "Sale Order" using "Payment Method: Enabled Hold Picking Untill Payment" +2) Picking can not "confirm" untill the payment is "confirmed" of that Sale Order diff --git a/stock_picking_on_hold/views/payment_method.xml b/stock_picking_on_hold/views/payment_method.xml new file mode 100644 index 000000000000..1e1cdbe098a2 --- /dev/null +++ b/stock_picking_on_hold/views/payment_method.xml @@ -0,0 +1,16 @@ + + + + sale_payment_method.payment_method.view_form + account.payment.method + + + + + + + + diff --git a/stock_picking_on_hold/views/sale_order_view.xml b/stock_picking_on_hold/views/sale_order_view.xml new file mode 100644 index 000000000000..516e3af13fa2 --- /dev/null +++ b/stock_picking_on_hold/views/sale_order_view.xml @@ -0,0 +1,38 @@ + + + + sale.order.payment_method.view_form + sale.order + + + + + + + + + + + sale.order.tree.stock_picking_on_hold + sale.order + + + + + + + + + + {'search_default_my_quotation': 1, 'default_hold_picking_until_payment': 1} + + + {'default_hold_picking_until_payment': 1} + + + diff --git a/stock_picking_on_hold/views/stock.xml b/stock_picking_on_hold/views/stock.xml new file mode 100644 index 000000000000..fe7c5be7cea2 --- /dev/null +++ b/stock_picking_on_hold/views/stock.xml @@ -0,0 +1,33 @@ + + + + stock.picking.form + stock.picking + + + +