diff --git a/__init__.py b/__init__.py
index 034be880af..d69059be1c 100644
--- a/__init__.py
+++ b/__init__.py
@@ -2,12 +2,14 @@
# this repository contains the full copyright notices and license terms.
from trytond.pool import Pool
+from . import account
from . import sale
from . import stock
def register():
Pool.register(
+ account.InvoiceLine,
sale.Configuration,
sale.ConfigurationSaleMethod,
sale.Sale,
diff --git a/account.py b/account.py
new file mode 100644
index 0000000000..f512ac4561
--- /dev/null
+++ b/account.py
@@ -0,0 +1,12 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+from trytond.model import fields
+from trytond.pool import PoolMeta
+
+
+class InvoiceLine(metaclass=PoolMeta):
+ __name__ = 'account.invoice.line'
+
+ cost_shipments = fields.One2Many(
+ 'stock.shipment.out', 'cost_invoice_line', "Cost of Shipments",
+ readonly=True)
diff --git a/message.xml b/message.xml
index 7b6020a990..720ef46876 100644
--- a/message.xml
+++ b/message.xml
@@ -6,6 +6,9 @@ this repository contains the full copyright notices and license terms. -->
To confirm sale "%(sale)s" you must change the carrier "%(carrier)s".
+
+ A sale can have only one shipment cost line.
+
To invoice shipment cost of "%(shipment)s", you must define an account revenue for product "%(product)s".
diff --git a/sale.py b/sale.py
index b2519dbf2a..8833254594 100644
--- a/sale.py
+++ b/sale.py
@@ -4,7 +4,7 @@ from decimal import Decimal
from trytond import backend
from trytond.i18n import gettext
-from trytond.model import ModelView, Workflow, fields
+from trytond.model import ModelView, Workflow, Unique, fields
from trytond.transaction import Transaction
from trytond.pyson import Eval, If
from trytond.pool import Pool, PoolMeta
@@ -205,6 +205,12 @@ class Sale(metaclass=PoolMeta):
carrier=sale.carrier.rec_name))
super(Sale, cls).confirm(sales)
+ @classmethod
+ @ModelView.button
+ def process(cls, sales):
+ with Transaction().set_context(_shipment_cost_invoiced=set()):
+ super().process(sales)
+
def _get_carrier_context(self):
return {}
@@ -301,47 +307,59 @@ class Sale(metaclass=PoolMeta):
shipment.carrier = self.carrier
return shipment
- def create_invoice(self):
- pool = Pool()
- InvoiceLine = pool.get('account.invoice.line')
- Shipment = pool.get('stock.shipment.out')
-
- invoice = super().create_invoice()
- if invoice and self.shipment_cost_method == 'shipment':
- invoice_lines = []
- # Copy shipments to avoid losing changes as the cache is cleared
- # after invoice line save because shipment is a Function field
- shipments = list(self.shipments)
- for shipment in shipments:
- if (shipment.state == 'done'
- and shipment.carrier
- and not shipment.cost_invoice_line):
- invoice_line = shipment.get_cost_invoice_line(invoice)
- if not invoice_line:
- continue
- invoice_line.invoice = invoice
- invoice_lines.append(invoice_line)
- shipment.cost_invoice_line = invoice_line
- InvoiceLine.save(invoice_lines)
- Shipment.save(shipments)
- return invoice
-
class Line(metaclass=PoolMeta):
__name__ = 'sale.line'
shipment_cost = fields.Numeric('Shipment Cost', digits=price_digits)
+ @classmethod
+ def __setup__(cls):
+ super().__setup__()
+ t = cls.__table__()
+ cls._sql_constraints += [
+ ('sale_shipment_cost_unique',
+ Unique(t, t.sale, t.shipment_cost),
+ 'sale_shipment_cost.msg_sale_shipment_cost_unique'),
+ ]
+
+ def get_invoice_line(self):
+ context = Transaction().context
+ shipment_cost_invoiced = context.get('_shipment_cost_invoiced')
+ lines = super().get_invoice_line()
+ if (self.sale.shipment_cost_method == 'shipment'
+ and self.shipment_cost
+ and shipment_cost_invoiced is not None):
+ for shipment in self.sale.shipments:
+ if (shipment.state == 'done'
+ and shipment.carrier
+ and not shipment.cost_invoice_line
+ and shipment.id not in shipment_cost_invoiced):
+ invoice_line = shipment.get_cost_invoice_line(
+ self.sale._get_invoice_sale())
+ if invoice_line:
+ invoice_line.origin = self
+ lines.append(invoice_line)
+ shipment_cost_invoiced.add(shipment.id)
+ return lines
+
def _get_invoice_line_quantity(self):
quantity = super()._get_invoice_line_quantity()
if self.shipment_cost:
if self.sale.shipment_cost_method == 'shipment':
- return 0
+ quantity = 0
elif (self.sale.shipment_cost_method == 'order'
and self.sale.invoice_method == 'shipment'):
shipments = self.sale.shipments
if (not shipments
or all(s.state != 'done' for s in shipments)):
- return 0
+ quantity = 0
+ return quantity
+
+ def _get_invoiced_quantity(self):
+ quantity = super()._get_invoiced_quantity()
+ if self.shipment_cost:
+ if self.sale.shipment_cost_method == 'shipment':
+ quantity = 0
return quantity
diff --git a/stock.py b/stock.py
index ec1919bad3..c958e32637 100644
--- a/stock.py
+++ b/stock.py
@@ -152,6 +152,7 @@ class ShipmentOut(metaclass=PoolMeta):
invoice_line.unit_price = round_price(cost)
invoice_line.product = product
invoice_line.on_change_product()
+ invoice_line.cost_shipments = [self]
if not invoice_line.account:
raise InvoiceShipmentCostError(
diff --git a/tests/scenario_sale_shipment_cost.rst b/tests/scenario_sale_shipment_cost.rst
index cb6e14fa9f..673e5ac9ee 100644
--- a/tests/scenario_sale_shipment_cost.rst
+++ b/tests/scenario_sale_shipment_cost.rst
@@ -170,6 +170,9 @@ Send products::
>>> shipment.state
'done'
+ >>> shipment.cost_invoice_line.amount
+ Decimal('3.00')
+
Check customer invoice::
>>> sale.reload()