diff --git a/modules/purchase_trade/docs/business-rules.md b/modules/purchase_trade/docs/business-rules.md
index 73a6b9b..af933f8 100644
--- a/modules/purchase_trade/docs/business-rules.md
+++ b/modules/purchase_trade/docs/business-rules.md
@@ -411,6 +411,29 @@ Owner technique: `a completer`
- Priorite:
- `importante`
+### BR-PT-019 - Le padding de facture provisoire vente augmente la quantite facturee sans modifier le lot physique
+
+- Intent: permettre de constituer une provision sur une facture provisoire
+ vente tout en gardant la trace de l'ecart avec la quantite reelle du lot.
+- Description:
+ - Le wizard `lot.invoice` expose un padding global uniquement pour les
+ factures provisoires cote vente.
+ - Ce padding global est reparti egalement entre les lots selectionnes.
+ - La quantite de chaque ligne de facture provisoire vente est augmentee de la
+ part de padding du lot.
+ - Le padding ne modifie pas la quantite physique du lot.
+- Resultat attendu:
+ - deux lots factures ensemble avec un padding global de `1000` recoivent
+ chacun `500` de padding
+ - la ligne facture affiche la quantite augmentee
+ - la ligne facture expose `Included padding`
+ - le lot conserve sa part de padding dans `sale_invoice_padding`
+- Hors scope:
+ - les ecritures comptables specifiques au padding seront traitees dans un
+ second temps
+- Priorite:
+ - `importante`
+
### BR-PT-012 - Fallback valuation basis sans summary: utiliser le prix economique de la ligne
- Intent: eviter qu'une valuation `basis` ouverte sorte a zero alors que la
diff --git a/modules/purchase_trade/invoice.xml b/modules/purchase_trade/invoice.xml
index d65fe66..ca8a2b8 100644
--- a/modules/purchase_trade/invoice.xml
+++ b/modules/purchase_trade/invoice.xml
@@ -1,5 +1,16 @@
+
+ account.invoice.line
+
+ invoice_line_form
+
+
+ account.invoice.line
+
+ invoice_line_tree_sequence
+
+
Packing List
account.invoice
diff --git a/modules/purchase_trade/lot.py b/modules/purchase_trade/lot.py
index 060bd49..fca5fe1 100755
--- a/modules/purchase_trade/lot.py
+++ b/modules/purchase_trade/lot.py
@@ -53,9 +53,11 @@ class Lot(metaclass=PoolMeta):
lot_move = fields.One2Many('lot.move','lot',"Move")
invoice_line = fields.Many2One('account.invoice.line',"Purch.Invoice line")
invoice_line_prov = fields.Many2One('account.invoice.line',"Purch. Invoice line prov")
- sale_invoice_line = fields.Many2One('account.invoice.line',"Sale Invoice line")
- sale_invoice_line_prov = fields.Many2One('account.invoice.line',"Sale Invoice line prov")
- delta_qt = fields.Numeric("Delta Qt")
+ sale_invoice_line = fields.Many2One('account.invoice.line',"Sale Invoice line")
+ sale_invoice_line_prov = fields.Many2One('account.invoice.line',"Sale Invoice line prov")
+ sale_invoice_padding = fields.Numeric(
+ "Sale invoice padding", digits='lot_unit_line', readonly=True)
+ delta_qt = fields.Numeric("Delta Qt")
delta_pr = fields.Numeric("Delta Pr")
delta_amt = fields.Numeric("Delta Amt")
warrant_nb = fields.Char("Warrant Nb")
@@ -2820,6 +2822,7 @@ class LotInvoice(Wizard):
break
else:
if sales:
+ self._apply_sale_invoice_padding(lots)
Sale._process_invoice(sales, lots, action, self.inv.pp_sale)
for lot in lots:
lot = Lot(lot.id)
@@ -2831,6 +2834,23 @@ class LotInvoice(Wizard):
self.message.invoice = invoice_line.invoice
return 'message'
+
+ @classmethod
+ def _split_sale_padding(cls, padding, lots):
+ padding = Decimal(str(padding or 0))
+ if not padding or not lots:
+ return {}
+ share = padding / Decimal(len(lots))
+ return {lot.id: share for lot in lots}
+
+ def _apply_sale_invoice_padding(self, lots):
+ Lot = Pool().get('lot.lot')
+ if self.inv.type != 'sale' or self.inv.action != 'prov':
+ return
+ padding_by_lot = self._split_sale_padding(self.inv.sale_padding, lots)
+ for lot in lots:
+ lot.sale_invoice_padding = padding_by_lot.get(lot.id, Decimal(0))
+ Lot.save(lots)
def default_message(self, fields):
return {
@@ -2870,10 +2890,14 @@ class LotInvoiceStart(ModelView):
quantity = fields.Numeric("Quantity to invoice",digits='unit',readonly=True,states={'invisible': Eval('type')=='sale'})
amount = fields.Numeric("Amount to invoice",digits='unit',readonly=True,states={'invisible': Eval('type')=='sale'})
- quantity_s = fields.Numeric("Quantity to invoice",digits='unit',readonly=True,states={'invisible': Eval('type')=='purchase'})
- amount_s = fields.Numeric("Amount to invoice",digits='unit',readonly=True,states={'invisible': Eval('type')=='purchase'})
-
- @fields.depends('type')
+ quantity_s = fields.Numeric("Quantity to invoice",digits='unit',readonly=True,states={'invisible': Eval('type')=='purchase'})
+ amount_s = fields.Numeric("Amount to invoice",digits='unit',readonly=True,states={'invisible': Eval('type')=='purchase'})
+ sale_padding = fields.Numeric("Global padding", digits='unit',
+ states={
+ 'invisible': (Eval('type') == 'purchase') | (Eval('action') != 'prov'),
+ })
+
+ @fields.depends('type')
def on_change_with_action(self, name=None):
if self.lot_p and self.type == 'purchase':
if self.lot_p[0].lot.line.purchase.wb.qt_type in [e.quantity_type for e in self.lot_p[0].lot.lot_hist]:
@@ -2908,27 +2932,33 @@ class LotInvoiceStart(ModelView):
amount += l.lot_amount
return amount
- @fields.depends('lot_s','type','quantity_s')
- def on_change_with_quantity_s(self, name=None):
- if self.lot_s and self.type == 'sale':
- quantity = Decimal(0)
- for l in self.lot_s:
- if l.lot.sale_invoice_line_prov:
- quantity += l.lot_diff_quantity
- else:
- quantity += l.lot_quantity
- return quantity
-
- @fields.depends('lot_s','type','amount_s')
- def on_change_with_amount_s(self, name=None):
- if self.lot_s and self.type == 'sale':
- amount = Decimal(0)
- for l in self.lot_s:
- if l.lot.sale_invoice_line_prov:
- amount += l.lot_diff_amount
- else:
- amount += l.lot_amount
- return amount
+ @fields.depends('lot_s','type','quantity_s','sale_padding')
+ def on_change_with_quantity_s(self, name=None):
+ if self.lot_s and self.type == 'sale':
+ quantity = Decimal(0)
+ for l in self.lot_s:
+ if l.lot.sale_invoice_line_prov:
+ quantity += l.lot_diff_quantity
+ else:
+ quantity += l.lot_quantity
+ quantity += Decimal(str(self.sale_padding or 0))
+ return quantity
+
+ @fields.depends('lot_s','type','amount_s','sale_padding')
+ def on_change_with_amount_s(self, name=None):
+ if self.lot_s and self.type == 'sale':
+ amount = Decimal(0)
+ padding = Decimal(str(self.sale_padding or 0))
+ padding_share = (
+ padding / Decimal(len(self.lot_s))
+ if padding and self.lot_s else Decimal(0))
+ for l in self.lot_s:
+ if l.lot.sale_invoice_line_prov:
+ amount += l.lot_diff_amount
+ else:
+ amount += l.lot_amount
+ amount += padding_share * Decimal(str(l.lot_price or 0))
+ return amount
@classmethod
def default_action(cls):
diff --git a/modules/purchase_trade/lot.xml b/modules/purchase_trade/lot.xml
index 8f013a0..a6a32ff 100755
--- a/modules/purchase_trade/lot.xml
+++ b/modules/purchase_trade/lot.xml
@@ -25,16 +25,26 @@ this repository contains the full copyright notices and license terms. -->
lot_report_graph
-
- lot.lot
- graph
- lot_graph
-
-
-
- Lots graph
- lot.lot
-
+
+ lot.lot
+ graph
+ lot_graph
+
+
+
+ lot.lot
+
+ lot_form
+
+
+ lot.lot
+
+ lot_tree_sequence
+
+
+ Lots graph
+ lot.lot
+
@@ -386,4 +396,4 @@ this repository contains the full copyright notices and license terms. -->
id="menu_lot_fcr_form"/>
-
\ No newline at end of file
+
diff --git a/modules/purchase_trade/sale.py b/modules/purchase_trade/sale.py
index ae41c13..803250d 100755
--- a/modules/purchase_trade/sale.py
+++ b/modules/purchase_trade/sale.py
@@ -1042,6 +1042,30 @@ class PriceComposition(metaclass=PoolMeta):
class SaleLine(metaclass=PoolMeta):
__name__ = 'sale.line'
+ def get_invoice_line(self, lots=None, action=None):
+ lines = super().get_invoice_line(lots, action)
+ if action != 'prov' or not lots:
+ return lines
+
+ padding_by_lot = {
+ lot.id: Decimal(str(getattr(lot, 'sale_invoice_padding', 0) or 0))
+ for lot in lots}
+ for invoice_line in lines:
+ if (getattr(invoice_line, 'invoice_type', None) != 'out'
+ or getattr(invoice_line, 'description', None) != 'Pro forma'
+ or Decimal(str(getattr(invoice_line, 'quantity', 0) or 0)) <= 0):
+ continue
+ lot = getattr(invoice_line, 'lot', None)
+ lot_id = getattr(lot, 'id', lot)
+ padding = padding_by_lot.get(lot_id, Decimal(0))
+ if not padding:
+ continue
+ quantity = Decimal(str(invoice_line.quantity or 0)) + padding
+ if invoice_line.unit:
+ quantity = Decimal(str(invoice_line.unit.round(quantity)))
+ invoice_line.quantity = quantity
+ return lines
+
@classmethod
def default_pricing_rule(cls):
try:
diff --git a/modules/purchase_trade/stock.py b/modules/purchase_trade/stock.py
index 15944c5..8fd1543 100755
--- a/modules/purchase_trade/stock.py
+++ b/modules/purchase_trade/stock.py
@@ -100,14 +100,25 @@ class Move(metaclass=PoolMeta):
LotMove.save([new_lm])
-class InvoiceLine(metaclass=PoolMeta):
- __name__ = 'account.invoice.line'
-
- lot = fields.Many2One('lot.lot',"Lot")
- fee = fields.Many2One('fee.fee',"Fee")
-
- @classmethod
- def validate(cls, lines):
+class InvoiceLine(metaclass=PoolMeta):
+ __name__ = 'account.invoice.line'
+
+ lot = fields.Many2One('lot.lot',"Lot")
+ fee = fields.Many2One('fee.fee',"Fee")
+ included_padding = fields.Function(
+ fields.Numeric("Included padding", digits=(1, 5)),
+ 'get_included_padding')
+
+ def get_included_padding(self, name):
+ if (self.lot
+ and self.invoice_type == 'out'
+ and self.description == 'Pro forma'
+ and Decimal(str(self.quantity or 0)) > 0):
+ return self.lot.sale_invoice_padding or Decimal(0)
+ return Decimal(0)
+
+ @classmethod
+ def validate(cls, lines):
super(InvoiceLine, cls).validate(lines)
Lot = Pool().get('lot.lot')
for line in lines:
diff --git a/modules/purchase_trade/tests/test_module.py b/modules/purchase_trade/tests/test_module.py
index 1d36da8..d145439 100644
--- a/modules/purchase_trade/tests/test_module.py
+++ b/modules/purchase_trade/tests/test_module.py
@@ -9,6 +9,7 @@ from trytond.pool import Pool
from trytond.tests.test_tryton import ModuleTestCase, with_transaction
from trytond.exceptions import UserError
from trytond.modules.purchase_trade import valuation as valuation_module
+from trytond.modules.purchase_trade import lot as lot_module
from trytond.modules.purchase_trade.service import ContractFactory
@@ -2148,5 +2149,25 @@ class PurchaseTradeTestCase(ModuleTestCase):
self.assertIs(invoice_line.invoice, sale_invoice)
+ def test_lot_invoice_sale_padding_is_split_per_lot(self):
+ 'sale provisional padding is split equally across selected lots'
+ lots = [Mock(id=1), Mock(id=2)]
+
+ self.assertEqual(
+ lot_module.LotInvoice._split_sale_padding(Decimal('1000'), lots),
+ {1: Decimal('500'), 2: Decimal('500')})
+
+ @with_transaction()
+ def test_invoice_line_included_padding_reads_sale_lot_padding(self):
+ 'invoice line exposes the sale provisional padding stored on the lot'
+ InvoiceLine = Pool().get('account.invoice.line')
+ line = InvoiceLine()
+ line.lot = Mock(sale_invoice_padding=Decimal('500'))
+ line.invoice_type = 'out'
+ line.description = 'Pro forma'
+ line.quantity = Decimal('10500')
+
+ self.assertEqual(line.get_included_padding(None), Decimal('500'))
+
del ModuleTestCase
diff --git a/modules/purchase_trade/view/invoice_line_form.xml b/modules/purchase_trade/view/invoice_line_form.xml
new file mode 100644
index 0000000..7358592
--- /dev/null
+++ b/modules/purchase_trade/view/invoice_line_form.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/modules/purchase_trade/view/invoice_line_tree_sequence.xml b/modules/purchase_trade/view/invoice_line_tree_sequence.xml
new file mode 100644
index 0000000..729a5aa
--- /dev/null
+++ b/modules/purchase_trade/view/invoice_line_tree_sequence.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/modules/purchase_trade/view/lot_form.xml b/modules/purchase_trade/view/lot_form.xml
new file mode 100644
index 0000000..43777fa
--- /dev/null
+++ b/modules/purchase_trade/view/lot_form.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/modules/purchase_trade/view/lot_invoice_start_form.xml b/modules/purchase_trade/view/lot_invoice_start_form.xml
index 58bb1ec..24b5c8e 100755
--- a/modules/purchase_trade/view/lot_invoice_start_form.xml
+++ b/modules/purchase_trade/view/lot_invoice_start_form.xml
@@ -16,6 +16,8 @@
+
+
diff --git a/modules/purchase_trade/view/lot_tree_sequence.xml b/modules/purchase_trade/view/lot_tree_sequence.xml
new file mode 100644
index 0000000..fb86e25
--- /dev/null
+++ b/modules/purchase_trade/view/lot_tree_sequence.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+