details: https://code.tryton.org/tryton/commit/161020480088
branch: default
user: Cédric Krier <[email protected]>
date: Wed Dec 03 12:08:12 2025 +0100
description:
Recreate the invoice line for the cost based on shipment when handling
the exception
The invoice line for cost based on shipment is managed by the shipment
using
the "Cost Sale Invoice Line" field. Therefore when the cancelled
invoice for
this line is recreated, the link must be cleared.
Closes #14227
diffstat:
modules/sale_shipment_cost/sale.py
| 20 +
modules/sale_shipment_cost/tests/scenario_sale_shipment_cost_cancelled_on_shipment.rst
| 130 ++++++++++
modules/sale_shipment_cost/tryton.cfg
| 1 +
3 files changed, 151 insertions(+), 0 deletions(-)
diffs (175 lines):
diff -r ff89f16c993d -r 161020480088 modules/sale_shipment_cost/sale.py
--- a/modules/sale_shipment_cost/sale.py Tue Dec 09 13:33:34 2025 +0100
+++ b/modules/sale_shipment_cost/sale.py Wed Dec 03 12:08:12 2025 +0100
@@ -470,6 +470,26 @@
return quantity
+class HandleInvoiceException(metaclass=PoolMeta):
+ __name__ = 'sale.handle.invoice.exception'
+
+ def transition_handle(self):
+ pool = Pool()
+ Shipment = pool.get('stock.shipment.out')
+ shipment_cost_recreated = set()
+ for invoice in self.ask.domain_invoices:
+ if invoice in self.ask.recreate_invoices:
+ for line in invoice.lines:
+ for shipment in line.cost_sale_shipments:
+ if shipment in self.record.shipments:
+ shipment_cost_recreated.add(shipment)
+ if shipment_cost_recreated:
+ Shipment.write(list(shipment_cost_recreated), {
+ 'cost_sale_invoice_line': None,
+ })
+ return super().transition_handle()
+
+
class ReturnSale(metaclass=PoolMeta):
__name__ = 'sale.return_sale'
diff -r ff89f16c993d -r 161020480088
modules/sale_shipment_cost/tests/scenario_sale_shipment_cost_cancelled_on_shipment.rst
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++
b/modules/sale_shipment_cost/tests/scenario_sale_shipment_cost_cancelled_on_shipment.rst
Wed Dec 03 12:08:12 2025 +0100
@@ -0,0 +1,130 @@
+=================================================
+Sale Shipment Cost Cancelled On 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
+
+Activate modules::
+
+ >>> config = activate_modules([
+ ... 'sale_shipment_cost',
+ ... ],
+ ... create_company, create_chart)
+
+ >>> Carrier = Model.get('carrier')
+ >>> Party = Model.get('party.party')
+ >>> ProductCategory = Model.get('product.category')
+ >>> ProductTemplate = Model.get('product.template')
+ >>> UoM = Model.get('product.uom')
+ >>> Sale = Model.get('sale.sale')
+
+Get accounts::
+
+ >>> accounts = get_accounts()
+
+Create customer::
+
+ >>> customer = Party(name='Customer')
+ >>> customer.save()
+
+Create account category::
+
+ >>> account_category = ProductCategory(name="Account Category")
+ >>> account_category.accounting = True
+ >>> account_category.account_revenue = accounts['revenue']
+ >>> account_category.save()
+
+Create product::
+
+ >>> unit, = UoM.find([('name', '=', "Unit")])
+
+ >>> template = ProductTemplate()
+ >>> template.name = "Product"
+ >>> template.default_uom = unit
+ >>> template.type = 'goods'
+ >>> template.salable = True
+ >>> template.list_price = Decimal('20')
+ >>> template.account_category = account_category
+ >>> template.save()
+ >>> product, = template.products
+
+ >>> carrier_template = ProductTemplate()
+ >>> carrier_template.name = "Carrier Product"
+ >>> carrier_template.default_uom = unit
+ >>> carrier_template.type = 'service'
+ >>> carrier_template.salable = True
+ >>> carrier_template.list_price = Decimal('3')
+ >>> carrier_template.account_category = account_category
+ >>> carrier_template.save()
+ >>> carrier_product, = carrier_template.products
+ >>> carrier_product.cost_price = Decimal('2')
+ >>> carrier_product.save()
+
+Create carrier::
+
+ >>> carrier = Carrier()
+ >>> party = Party(name='Carrier')
+ >>> party.save()
+ >>> carrier.party = party
+ >>> carrier.carrier_product = carrier_product
+ >>> carrier.save()
+
+Sale products with cost on shipment::
+
+ >>> sale = Sale()
+ >>> sale.party = customer
+ >>> sale.carrier = carrier
+ >>> sale.invoice_method = 'shipment'
+ >>> sale.shipment_cost_method = 'shipment'
+ >>> sale_line = sale.lines.new()
+ >>> sale_line.product = product
+ >>> sale_line.quantity = 5.0
+ >>> sale.click('quote')
+ >>> sale.click('confirm')
+ >>> sale.click('process')
+ >>> sale.state
+ 'processing'
+
+Ship products::
+
+ >>> shipment, = sale.shipments
+ >>> shipment.click('assign_force')
+ >>> shipment.click('pick')
+ >>> shipment.click('pack')
+ >>> shipment.click('do')
+ >>> shipment.state
+ 'done'
+
+Cancel customer invoice::
+
+ >>> sale.reload()
+ >>> invoice, = sale.invoices
+ >>> invoice.untaxed_amount
+ Decimal('103.00')
+ >>> invoice.click('cancel')
+ >>> invoice.state
+ 'cancelled'
+
+ >>> sale.reload()
+ >>> sale.invoice_state
+ 'exception'
+
+Recreate invoice::
+
+ >>> invoice_handle_exception = sale.click('handle_invoice_exception')
+ >>> invoice_handle_exception.form.recreate_invoices.extend(
+ ... invoice_handle_exception.form.recreate_invoices.find())
+ >>> invoice_handle_exception.execute('handle')
+
+ >>> sale.invoice_state
+ 'pending'
+ >>> _, invoice = sale.invoices
+ >>> invoice.untaxed_amount
+ Decimal('103.00')
diff -r ff89f16c993d -r 161020480088 modules/sale_shipment_cost/tryton.cfg
--- a/modules/sale_shipment_cost/tryton.cfg Tue Dec 09 13:33:34 2025 +0100
+++ b/modules/sale_shipment_cost/tryton.cfg Wed Dec 03 12:08:12 2025 +0100
@@ -31,6 +31,7 @@
stock.ShipmentCostSale
stock.ShipmentOut
wizard:
+ sale.HandleInvoiceException
sale.ReturnSale
[register sale_promotion]