From 621fded36b153915192ad8302267bb6db15e61e4 Mon Sep 17 00:00:00 2001 From: Maxime Richez Date: Wed, 23 Sep 2026 09:46:34 +0200 Subject: [PATCH] Allow sales configurations to create shipments in draft Closes #15088 --- modules/sale/CHANGELOG | 1 + modules/sale/configuration.py | 16 +++- modules/sale/sale.py | 12 ++- .../tests/scenario_sale_wait_shipment.json | 4 + .../tests/scenario_sale_wait_shipment.rst | 73 +++++++++++++++++++ modules/sale/view/configuration_form.xml | 6 ++ 6 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 modules/sale/tests/scenario_sale_wait_shipment.json create mode 100644 modules/sale/tests/scenario_sale_wait_shipment.rst diff --git a/modules/sale/CHANGELOG b/modules/sale/CHANGELOG index 671c278864..6b857d0665 100644 --- a/modules/sale/CHANGELOG +++ b/modules/sale/CHANGELOG @@ -1,3 +1,4 @@ +* Allow sales configurations to create shipments in draft Version 8.0.0 - 2026-04-20 -------------------------- diff --git a/modules/sale/configuration.py b/modules/sale/configuration.py index 1f4b71c052..9027e441fc 100644 --- a/modules/sale/configuration.py +++ b/modules/sale/configuration.py @@ -15,6 +15,9 @@ sale_invoice_method = fields.Selection( 'get_sale_invoice_methods', "Sale Invoice Method") sale_shipment_method = fields.Selection( 'get_sale_shipment_methods', "Sale Shipment Method") +sale_wait_shipment = fields.Boolean( + "Sale Wait Shipment", + help="Create customer shipments in a waiting state") sale_quotation_validity = fields.TimeDelta( "Sale Quotation Validity", domain=['OR', @@ -55,6 +58,7 @@ class Configuration( sale_invoice_method = fields.MultiValue(sale_invoice_method) get_sale_invoice_methods = get_sale_methods('invoice_method') sale_shipment_method = fields.MultiValue(sale_shipment_method) + sale_wait_shipment = fields.MultiValue(sale_wait_shipment) get_sale_shipment_methods = get_sale_methods('shipment_method') sale_process_after = fields.TimeDelta( "Process Sale after", @@ -69,7 +73,11 @@ class Configuration( @classmethod def multivalue_model(cls, field): pool = Pool() - if field in {'sale_invoice_method', 'sale_shipment_method'}: + if field in { + 'sale_invoice_method', + 'sale_shipment_method', + 'sale_wait_shipment', + }: return pool.get('sale.configuration.sale_method') if field == 'sale_sequence': return pool.get('sale.configuration.sequence') @@ -80,6 +88,7 @@ class Configuration( default_sale_sequence = default_func('sale_sequence') default_sale_invoice_method = default_func('sale_invoice_method') default_sale_shipment_method = default_func('sale_shipment_method') + default_sale_wait_shipment = default_func('sale_wait_shipment') class ConfigurationSequence(ModelSQL, CompanyValueMixin): @@ -107,6 +116,7 @@ class ConfigurationSaleMethod(ModelSQL, ValueMixin): get_sale_invoice_methods = get_sale_methods('invoice_method') sale_shipment_method = sale_shipment_method get_sale_shipment_methods = get_sale_methods('shipment_method') + sale_wait_shipment = sale_wait_shipment @classmethod def __register__(cls, module_name): @@ -128,6 +138,10 @@ class ConfigurationSaleMethod(ModelSQL, ValueMixin): def default_sale_shipment_method(cls): return 'order' + @classmethod + def default_sale_wait_shipment(cls): + return True + class ConfigurationQuotation(ModelSQL, ValueMixin): __name__ = 'sale.configuration.quotation' diff --git a/modules/sale/sale.py b/modules/sale/sale.py index c03fdb42d2..306e8443c9 100644 --- a/modules/sale/sale.py +++ b/modules/sale/sale.py @@ -1242,6 +1242,8 @@ class Sale( pool = Pool() ShipmentOut = pool.get('stock.shipment.out') ShipmentOutReturn = pool.get('stock.shipment.out.return') + Config = pool.get('sale.configuration') + config = Config(1) shipments_out, shipments_return = {}, {} for sale in sales: @@ -1254,7 +1256,15 @@ class Sale( shipments = sum((v for v in shipments_out.values()), []) ShipmentOut.save(shipments) - ShipmentOut.wait(shipments) + + to_wait = [] + for company, c_shipments in groupby( + shipments, key=lambda s: s.company): + if config.get_multivalue('sale_wait_shipment', company=company.id): + to_wait.extend(c_shipments) + if to_wait: + ShipmentOut.wait(to_wait) + for sale, shipments in shipments_out.items(): for shipment in shipments: sale.copy_resources_to(shipment) diff --git a/modules/sale/tests/scenario_sale_wait_shipment.json b/modules/sale/tests/scenario_sale_wait_shipment.json new file mode 100644 index 0000000000..5e9e723cf1 --- /dev/null +++ b/modules/sale/tests/scenario_sale_wait_shipment.json @@ -0,0 +1,4 @@ +[ + {"sale_wait_shipment": true} + ,{"sale_wait_shipment": false} +] diff --git a/modules/sale/tests/scenario_sale_wait_shipment.rst b/modules/sale/tests/scenario_sale_wait_shipment.rst new file mode 100644 index 0000000000..1767acf5a3 --- /dev/null +++ b/modules/sale/tests/scenario_sale_wait_shipment.rst @@ -0,0 +1,73 @@ +=========================== +Sale Wait Shipment Scenario +=========================== + +Imports:: + + >>> from decimal import Decimal + + >>> from proteus import Model + >>> from trytond.modules.account.tests.tools import create_chart, get_accounts + >>> from trytond.modules.company.tests.tools import create_company + >>> from trytond.tests.tools import activate_modules, assertEqual + + >>> sale_wait_shipment = globals().get('sale_wait_shipment', True) + +Activate modules:: + + >>> config = activate_modules('sale', create_company, create_chart) + + >>> Party = Model.get('party.party') + >>> ProductCategory = Model.get('product.category') + >>> ProductTemplate = Model.get('product.template') + >>> ProductUom = Model.get('product.uom') + >>> Sale = Model.get('sale.sale') + >>> SaleConfiguration = Model.get('sale.configuration') + +Create party:: + + >>> customer = Party(name='Customer') + >>> customer.save() + +Create account categories:: + + >>> accounts = get_accounts() + + >>> account_category = ProductCategory(name="Account Category") + >>> account_category.accounting = True + >>> account_category.account_revenue = accounts['revenue'] + >>> account_category.save() + +Create product:: + + >>> unit, = ProductUom.find([('name', '=', "Unit")]) + + >>> template = ProductTemplate() + >>> template.name = 'product' + >>> template.default_uom = unit + >>> template.type = 'goods' + >>> template.salable = True + >>> template.list_price = Decimal('10.0000') + >>> template.account_category = account_category + >>> template.save() + >>> product, = template.products + +Set wait shipment:: + + >>> sale_config = SaleConfiguration(1) + >>> sale_config.sale_wait_shipment = sale_wait_shipment + >>> sale_config.save() + +Create a sale:: + + >>> sale = Sale() + >>> sale.party = customer + >>> line = sale.lines.new() + >>> line.product = product + >>> line.quantity = 10 + >>> sale.click('quote') + >>> sale.click('confirm') + >>> sale.state + 'processing' + >>> shipment, = sale.shipments + >>> assertEqual(shipment.state, 'waiting' if sale_wait_shipment else 'draft') diff --git a/modules/sale/view/configuration_form.xml b/modules/sale/view/configuration_form.xml index 519fe5028d..cf83f1a33c 100644 --- a/modules/sale/view/configuration_form.xml +++ b/modules/sale/view/configuration_form.xml @@ -5,11 +5,17 @@ this repository contains the full copyright notices and license terms. -->