Rename invoice method on shipment into on fulfillment

This commit is contained in:
Cédric Krier
2026-02-25 11:37:37 +01:00
parent 67b9c34b25
commit 905a2b20ba
30 changed files with 111 additions and 60 deletions
@@ -105,7 +105,7 @@ Purchase 12 products::
>>> purchase = Purchase()
>>> purchase.party = supplier
>>> purchase.payment_term = payment_term
>>> purchase.invoice_method = 'shipment'
>>> purchase.invoice_method = 'fulfillment'
>>> purchase_line = purchase.lines.new()
>>> purchase_line.product = product
>>> purchase_line.quantity = 5.0
@@ -189,7 +189,7 @@ Sale 5 products::
>>> sale = Sale()
>>> sale.party = customer
>>> sale.payment_term = payment_term
>>> sale.invoice_method = 'shipment'
>>> sale.invoice_method = 'fulfillment'
>>> sale_line = sale.lines.new()
>>> sale_line.product = product
>>> sale_line.quantity = 2.0
@@ -324,7 +324,7 @@ Now we will use a product with different unit of measure::
>>> purchase = Purchase()
>>> purchase.party = supplier
>>> purchase.payment_term = payment_term
>>> purchase.invoice_method = 'shipment'
>>> purchase.invoice_method = 'fulfillment'
>>> purchase_line = purchase.lines.new()
>>> purchase_line.product = product_by5
>>> purchase_line.quantity = 1.0
@@ -113,7 +113,7 @@ Purchase 12 products::
>>> purchase = Purchase()
>>> purchase.party = supplier
>>> purchase.payment_term = payment_term
>>> purchase.invoice_method = 'shipment'
>>> purchase.invoice_method = 'fulfillment'
>>> purchase_line = purchase.lines.new()
>>> purchase_line.product = product
>>> purchase_line.quantity = 5.0
@@ -185,7 +185,7 @@ Sale 5 products::
>>> sale = Sale()
>>> sale.party = customer
>>> sale.payment_term = payment_term
>>> sale.invoice_method = 'shipment'
>>> sale.invoice_method = 'fulfillment'
>>> sale_line = sale.lines.new()
>>> sale_line.product = product
>>> sale_line.quantity = 2.0
@@ -97,7 +97,7 @@ Sale products with cost on shipment::
>>> sale = Sale()
>>> sale.party = customer
>>> sale.carrier = carrier1
>>> sale.invoice_method = 'shipment'
>>> sale.invoice_method = 'fulfillment'
>>> sale.shipment_cost_method = 'shipment'
>>> before_carriage = sale.before_carriages.new(type='before')
>>> before_carriage.carrier = carrier2
@@ -139,7 +139,7 @@ Sale products with cost on shipment::
>>> sale.party = customer
>>> sale.carrier = carrier
>>> sale.payment_term = payment_term
>>> sale.invoice_method = 'shipment'
>>> sale.invoice_method = 'fulfillment'
>>> sale.shipment_cost_method = 'shipment'
>>> sale_line = sale.lines.new()
>>> sale_line.product = product
@@ -150,7 +150,7 @@ Sale products with cost on shipment::
>>> sale.party = customer
>>> sale.carrier = carrier
>>> sale.payment_term = payment_term
>>> sale.invoice_method = 'shipment'
>>> sale.invoice_method = 'fulfillment'
>>> sale.shipment_cost_method = 'shipment'
>>> sale_line = sale.lines.new()
>>> sale_line.product = product
+1 -1
View File
@@ -243,7 +243,7 @@ def order_line_mixin(prefix):
def _get_invoice_line_quantity(self):
quantity = super()._get_invoice_line_quantity()
if (getattr(self, prefix).invoice_method == 'shipment'
if (getattr(self, prefix).invoice_method == 'fulfillment'
and self.components):
ratio = min(c.get_moved_ratio() for c in self.components)
quantity = self.unit.round(self.quantity * ratio)
@@ -150,7 +150,7 @@ Purchase composed and kit products::
>>> Purchase = Model.get('purchase.purchase')
>>> purchase = Purchase()
>>> purchase.party = supplier
>>> purchase.invoice_method = 'shipment'
>>> purchase.invoice_method = 'fulfillment'
>>> line = purchase.lines.new()
>>> line.product = composed_product
>>> line.quantity = 1
@@ -137,7 +137,7 @@ Sale composed and kit products::
>>> Sale = Model.get('sale.sale')
>>> sale = Sale()
>>> sale.party = customer
>>> sale.invoice_method = 'shipment'
>>> sale.invoice_method = 'fulfillment'
>>> line = sale.lines.new()
>>> line.product = composed_product
>>> line.quantity = 2
+1
View File
@@ -1,3 +1,4 @@
* Rename invoice method on shipment into on fulfillment
* Add support for Python 3.14
* Remove support for Python 3.9
+13
View File
@@ -6,6 +6,7 @@ from trytond.modules.company.model import (
CompanyMultiValueMixin, CompanyValueMixin)
from trytond.pool import Pool
from trytond.pyson import Eval, Id, TimeDelta
from trytond.transaction import Transaction
purchase_invoice_method = fields.Selection(
'get_purchase_invoice_method', "Invoice Method")
@@ -88,6 +89,18 @@ class ConfigurationPurchaseMethod(ModelSQL, ValueMixin):
purchase_invoice_method = purchase_invoice_method
get_purchase_invoice_method = get_purchase_methods('invoice_method')
@classmethod
def __register__(cls, module_name):
cursor = Transaction().connection.cursor()
table = cls.__table__()
super().__register__(module_name)
# Migration from 7.8: rename invoice method shipment to fulfillment
cursor.execute(*table.update(
[table.purchase_invoice_method], ['fulfillment'],
where=table.purchase_invoice_method == 'shipment'))
@classmethod
def default_purchase_invoice_method(cls):
return 'order'
+16 -10
View File
@@ -187,9 +187,10 @@ class Purchase(
"Total Cache", currency='currency', digits='currency')
invoice_method = fields.Selection([
('manual', 'Manual'),
('order', 'Based On Order'),
('shipment', 'Based On Shipment'),
], 'Invoice Method', required=True, states=_states)
('order', "On Order"),
('fulfillment', "On Fulfillment"),
],
"Invoice Method", required=True, states=_states)
invoice_state = fields.Selection([
('none', 'None'),
('pending', "Pending"),
@@ -372,6 +373,11 @@ class Purchase(
[sql_table.invoice_state], ['pending'],
where=sql_table.invoice_state == 'waiting'))
# Migration from 7.8: rename invoice method shipment to fulfillment
cursor.execute(*sql_table.update(
[sql_table.invoice_method], ['fulfillment'],
where=sql_table.invoice_method == 'shipment'))
@classmethod
def order_number(cls, tables):
table, _ = tables[None]
@@ -1005,8 +1011,8 @@ class Purchase(
purchases = [p for p in purchases if p.state in states]
cls.lock(purchases)
cls._process_invoice(purchases)
cls._process_shipment(purchases)
cls._process_invoice_shipment_states(purchases)
cls._process_fulfillment(purchases)
cls._process_invoice_fulfillment_states(purchases)
cls._process_state(purchases)
@classmethod
@@ -1029,7 +1035,7 @@ class Purchase(
purchase.copy_resources_to(invoice)
@classmethod
def _process_shipment(cls, purchases):
def _process_fulfillment(cls, purchases):
pool = Pool()
Move = pool.get('stock.move')
ShipmentInReturn = pool.get('stock.shipment.in.return')
@@ -1050,7 +1056,7 @@ class Purchase(
purchase.copy_resources_to(shipment)
@classmethod
def _process_invoice_shipment_states(cls, purchases):
def _process_invoice_fulfillment_states(cls, purchases):
pool = Pool()
Line = pool.get('purchase.line')
lines = []
@@ -1459,7 +1465,7 @@ class Line(sequence_ordered(), ModelSQL, ModelView):
return
skips = set(self.purchase.invoices_ignored)
quantity = self.quantity
if self.purchase.invoice_method == 'shipment':
if self.purchase.invoice_method == 'fulfillment':
moves_ignored = set(self.moves_ignored)
for move in self.moves:
if move in moves_ignored:
@@ -1910,7 +1916,7 @@ class Line(sequence_ordered(), ModelSQL, ModelView):
or not self.product
or self.product.type == 'service'):
return self.quantity
elif self.purchase.invoice_method == 'shipment':
elif self.purchase.invoice_method == 'fulfillment':
quantity = 0.0
for move in self.moves:
if move.state != 'done':
@@ -1951,7 +1957,7 @@ class Line(sequence_ordered(), ModelSQL, ModelView):
moves = []
if self.purchase.invoice_method in {'order', 'manual'}:
moves.extend(self.moves)
elif (self.purchase.invoice_method == 'shipment'
elif (self.purchase.invoice_method == 'fulfillment'
and samesign(self.quantity, quantity)):
for move in self.moves:
if move.state == 'done':
+4 -4
View File
@@ -213,7 +213,7 @@ Purchase 5 products with an invoice method 'on shipment'::
>>> purchase = Purchase()
>>> purchase.party = supplier
>>> purchase.payment_term = payment_term
>>> purchase.invoice_method = 'shipment'
>>> purchase.invoice_method = 'fulfillment'
>>> purchase_line = PurchaseLine()
>>> purchase.lines.append(purchase_line)
>>> purchase_line.product = product
@@ -307,7 +307,7 @@ Create a Return::
>>> return_ = Purchase()
>>> return_.party = supplier
>>> return_.payment_term = payment_term
>>> return_.invoice_method = 'shipment'
>>> return_.invoice_method = 'fulfillment'
>>> return_line = PurchaseLine()
>>> return_.lines.append(return_line)
>>> return_line.product = product
@@ -434,7 +434,7 @@ Mixing stuff with an invoice method 'on shipment'::
>>> mix = Purchase()
>>> mix.party = supplier
>>> mix.payment_term = payment_term
>>> mix.invoice_method = 'shipment'
>>> mix.invoice_method = 'fulfillment'
>>> mixline = PurchaseLine()
>>> mix.lines.append(mixline)
>>> mixline.product = product
@@ -530,7 +530,7 @@ linked to invoices::
>>> purchase = Purchase()
>>> purchase.party = supplier
>>> purchase.payment_term = payment_term
>>> purchase.invoice_method = 'shipment'
>>> purchase.invoice_method = 'fulfillment'
>>> line = purchase.lines.new()
>>> line.product = product
>>> line.quantity = 10.0
@@ -48,7 +48,7 @@ Create product::
Purchase product::
>>> purchase = Purchase(party=supplier)
>>> purchase.invoice_method = 'shipment'
>>> purchase.invoice_method = 'fulfillment'
>>> line = purchase.lines.new()
>>> line.product = product
>>> line.quantity = 1
@@ -121,7 +121,7 @@ Purchase 5 products::
>>> purchase = Purchase()
>>> purchase.party = supplier
>>> purchase.payment_term = payment_term
>>> purchase.invoice_method = 'shipment'
>>> purchase.invoice_method = 'fulfillment'
>>> purchase_line = PurchaseLine()
>>> purchase.lines.append(purchase_line)
>>> purchase_line.product = product
+1
View File
@@ -1,3 +1,4 @@
* Rename invoice method on shipment into on fulfillment
* Add support for Python 3.14
* Remove support for Python 3.9
+13
View File
@@ -9,6 +9,7 @@ from trytond.modules.company.model import (
CompanyMultiValueMixin, CompanyValueMixin)
from trytond.pool import Pool
from trytond.pyson import Eval, Id, TimeDelta
from trytond.transaction import Transaction
sale_invoice_method = fields.Selection(
'get_sale_invoice_methods', "Sale Invoice Method")
@@ -107,6 +108,18 @@ class ConfigurationSaleMethod(ModelSQL, ValueMixin):
sale_shipment_method = sale_shipment_method
get_sale_shipment_methods = get_sale_methods('shipment_method')
@classmethod
def __register__(cls, module_name):
cursor = Transaction().connection.cursor()
table = cls.__table__()
super().__register__(module_name)
# Migration from 7.8: rename invoice method shipment to fulfillment
cursor.execute(*table.update(
[table.sale_invoice_method], ['fulfillment'],
where=table.sale_invoice_method == 'shipment'))
@classmethod
def default_sale_invoice_method(cls):
return 'order'
+12
View File
@@ -80,6 +80,18 @@ class PartySaleMethod(ModelSQL, CompanyValueMixin):
get_sale_invoice_method = get_sale_methods('invoice_method')
get_sale_shipment_method = get_sale_methods('shipment_method')
@classmethod
def __register__(cls, module_name):
cursor = Transaction().connection.cursor()
table = cls.__table__()
super().__register__(module_name)
# Migration from 7.8: rename invoice method shipment to fulfillment
cursor.execute(*table.update(
[table.sale_invoice_method], ['fulfillment'],
where=table.sale_invoice_method == 'shipment'))
class PartyCustomerCurrency(ModelSQL, ValueMixin):
__name__ = 'party.party.customer_currency'
+14 -9
View File
@@ -242,7 +242,7 @@ class Sale(
invoice_method = fields.Selection([
('manual', 'Manual'),
('order', 'On Order Processed'),
('shipment', 'On Shipment Sent'),
('fulfillment', "On Fulfillment"),
],
'Invoice Method', required=True, states={
'readonly': Eval('state') != 'draft',
@@ -443,6 +443,11 @@ class Sale(
[sql_table.invoice_state], ['pending'],
where=sql_table.invoice_state == 'waiting'))
# Migration from 7.8: rename invoice method shipment to fulfillment
cursor.execute(*sql_table.update(
[sql_table.invoice_method], ['fulfillment'],
where=sql_table.invoice_method == 'shipment'))
@classmethod
def order_number(cls, tables):
table, _ = tables[None]
@@ -818,7 +823,7 @@ class Sale(
'invoice_method', 'shipment_method'}):
return
for sale in sales:
if (sale.invoice_method == 'shipment'
if (sale.invoice_method == 'fulfillment'
and sale.shipment_method == 'invoice'):
raise SaleValidationError(
gettext('sale.msg_sale_invalid_method',
@@ -1193,8 +1198,8 @@ class Sale(
sales = [s for s in sales if s.state in states]
cls.lock(sales)
cls._process_invoice(sales)
cls._process_shipment(sales)
cls._process_invoice_shipment_states(sales)
cls._process_fulfillment(sales)
cls._process_invoice_fulfillment_states(sales)
cls._process_state(sales)
@classmethod
@@ -1213,7 +1218,7 @@ class Sale(
sale.copy_resources_to(invoice)
@classmethod
def _process_shipment(cls, sales):
def _process_fulfillment(cls, sales):
pool = Pool()
ShipmentOut = pool.get('stock.shipment.out')
ShipmentOutReturn = pool.get('stock.shipment.out.return')
@@ -1241,7 +1246,7 @@ class Sale(
sale.copy_resources_to(shipment)
@classmethod
def _process_invoice_shipment_states(cls, sales):
def _process_invoice_fulfillment_states(cls, sales):
pool = Pool()
Line = pool.get('sale.line')
lines = []
@@ -1608,7 +1613,7 @@ class SaleLine(TaxableMixin, sequence_ordered(), ModelSQL, ModelView):
return
skips = set(self.sale.invoices_ignored)
quantity = self.quantity
if self.sale.invoice_method == 'shipment':
if self.sale.invoice_method == 'fulfillment':
moves_ignored = set(self.moves_ignored)
for move in self.moves:
if move in moves_ignored:
@@ -2017,7 +2022,7 @@ class SaleLine(TaxableMixin, sequence_ordered(), ModelSQL, ModelView):
or not self.product
or self.product.type == 'service'):
return self.quantity
elif self.sale.invoice_method == 'shipment':
elif self.sale.invoice_method == 'fulfillment':
quantity = 0.0
for move in self.moves:
if move.state != 'done':
@@ -2059,7 +2064,7 @@ class SaleLine(TaxableMixin, sequence_ordered(), ModelSQL, ModelView):
if self.sale.invoice_method in {'order', 'manual'}:
if self.sale.shipment_method not in {'order', 'manual'}:
moves.extend(self.moves)
elif (self.sale.invoice_method == 'shipment'
elif (self.sale.invoice_method == 'fulfillment'
and samesign(self.quantity, quantity)):
for move in self.moves:
if move.state == 'done':
+4 -4
View File
@@ -214,7 +214,7 @@ Sale 5 products with an invoice method 'on shipment'::
>>> sale = Sale()
>>> sale.party = customer
>>> sale.payment_term = payment_term
>>> sale.invoice_method = 'shipment'
>>> sale.invoice_method = 'fulfillment'
>>> sale_line = SaleLine()
>>> sale.lines.append(sale_line)
>>> sale_line.product = product
@@ -372,7 +372,7 @@ Create a Return::
>>> return_ = Sale()
>>> return_.party = customer
>>> return_.payment_term = payment_term
>>> return_.invoice_method = 'shipment'
>>> return_.invoice_method = 'fulfillment'
>>> return_line = SaleLine()
>>> return_.lines.append(return_line)
>>> return_line.product = product
@@ -514,7 +514,7 @@ Mixing stuff with an invoice method 'on shipment'::
>>> mix = Sale()
>>> mix.party = customer
>>> mix.payment_term = payment_term
>>> mix.invoice_method = 'shipment'
>>> mix.invoice_method = 'fulfillment'
>>> mixline = SaleLine()
>>> mix.lines.append(mixline)
>>> mixline.product = product
@@ -626,7 +626,7 @@ to invoices::
>>> sale = Sale()
>>> sale.party = customer
>>> sale.payment_term = payment_term
>>> sale.invoice_method = 'shipment'
>>> sale.invoice_method = 'fulfillment'
>>> line = sale.lines.new()
>>> line.product = product
>>> line.quantity = 10.0
@@ -17,7 +17,7 @@ Create a party and set their default methods::
>>> Party = Model.get('party.party')
>>> customer = Party(name='Customer')
>>> customer.sale_shipment_method = 'invoice'
>>> customer.sale_invoice_method = 'shipment'
>>> customer.sale_invoice_method = 'fulfillment'
>>> customer.save()
Create a sale to to check default methods::
@@ -28,4 +28,4 @@ Create a sale to to check default methods::
>>> sale.shipment_method
'invoice'
>>> sale.invoice_method
'shipment'
'fulfillment'
@@ -49,7 +49,7 @@ Create product::
Sale product::
>>> sale = Sale(party=customer)
>>> sale.invoice_method = 'shipment'
>>> sale.invoice_method = 'fulfillment'
>>> line = sale.lines.new()
>>> line.product = product
>>> line.quantity = 1
@@ -82,7 +82,7 @@ Sell products::
>>> sale = Sale()
>>> sale.party = customer
>>> sale.advance_payment_term = advance_payment_term
>>> sale.invoice_method = 'shipment'
>>> sale.invoice_method = 'fulfillment'
>>> line = sale.lines.new()
>>> line.product = product
>>> line.quantity = 10
+1 -1
View File
@@ -40,7 +40,7 @@ class Line(metaclass=PoolMeta):
if (self.type != 'line') or (self.quantity <= 0):
return None
quantity = self.quantity
if self.sale.invoice_method == 'shipment':
if self.sale.invoice_method == 'fulfillment':
for move in self.moves_ignored:
quantity -= UoM.compute_qty(
move.unit, move.quantity, self.unit, round=False)
+3 -3
View File
@@ -243,7 +243,7 @@ class Sale(metaclass=PoolMeta):
super().process(sales)
@classmethod
def _process_invoice_shipment_states(cls, sales):
def _process_invoice_fulfillment_states(cls, sales):
pool = Pool()
ShipmentOut = pool.get('stock.shipment.out')
ShipmentCostSale = pool.get('stock.shipment.cost_sale')
@@ -256,7 +256,7 @@ class Sale(metaclass=PoolMeta):
else:
not_sent.append(sale)
super()._process_invoice_shipment_states(sales)
super()._process_invoice_fulfillment_states(sales)
to_save, to_delete, shipments = [], [], set()
for sale in sent:
@@ -453,7 +453,7 @@ class Line(metaclass=PoolMeta):
if self.sale.shipment_cost_method == 'shipment':
quantity = 0
elif (self.sale.shipment_cost_method == 'order'
and self.sale.invoice_method == 'shipment'):
and self.sale.invoice_method == 'fulfillment'):
shipments = [
s for s in self.sale.shipments
if s.cost_sale_method == 'order']
@@ -108,7 +108,7 @@ Sale products with cost on shipment::
>>> sale.party = customer
>>> sale.carrier = carrier
>>> sale.payment_term = payment_term
>>> sale.invoice_method = 'shipment'
>>> sale.invoice_method = 'fulfillment'
>>> sale.shipment_cost_method
'shipment'
>>> sale_line = sale.lines.new()
@@ -250,7 +250,7 @@ Sale products with cost on order and invoice method on shipment::
>>> sale.party = customer
>>> sale.carrier = carrier
>>> sale.payment_term = payment_term
>>> sale.invoice_method = 'shipment'
>>> sale.invoice_method = 'fulfillment'
>>> sale.shipment_cost_method = 'order'
>>> sale_line = sale.lines.new()
>>> sale_line.product = product
@@ -289,7 +289,7 @@ Sale products with no cost::
>>> sale.party = customer
>>> sale.carrier = carrier
>>> sale.payment_term = payment_term
>>> sale.invoice_method = 'shipment'
>>> sale.invoice_method = 'fulfillment'
>>> sale.shipment_cost_method = None
>>> sale_line = sale.lines.new()
>>> sale_line.product = product
@@ -81,7 +81,7 @@ Sale products with cost on shipment::
>>> sale = Sale()
>>> sale.party = customer
>>> sale.carrier = carrier
>>> sale.invoice_method = 'shipment'
>>> sale.invoice_method = 'fulfillment'
>>> sale.shipment_cost_method = 'shipment'
>>> sale_line = sale.lines.new()
>>> sale_line.product = product
+3 -3
View File
@@ -76,11 +76,11 @@ class Sale(metaclass=PoolMeta):
return shipment
@classmethod
def _process_shipment(cls, sales):
def _process_fulfillment(cls, sales):
for method, sales in groupby(
sales, lambda s: s.shipment_grouping_method):
if method:
for sale in sales:
super()._process_shipment([sale])
super()._process_fulfillment([sale])
else:
super()._process_shipment(list(sales))
super()._process_fulfillment(list(sales))
+2 -2
View File
@@ -23,7 +23,7 @@ class Sale(metaclass=PoolMeta):
return done
@classmethod
def _process_shipment(cls, sales):
def _process_fulfillment(cls, sales):
pool = Pool()
Product = pool.get('product.product')
@@ -49,7 +49,7 @@ class Sale(metaclass=PoolMeta):
# supplier
cls._process_supply(sub_sales, product_quantities)
product_quantities.clear()
super()._process_shipment(sales)
super()._process_fulfillment(sales)
@classmethod
def _process_supply(cls, sales, product_quantities):
@@ -124,7 +124,7 @@ class Purchase(metaclass=PoolMeta):
})
@classmethod
def _process_shipment(cls, purchases):
def _process_fulfillment(cls, purchases):
pool = Pool()
DropShipment = pool.get('stock.shipment.drop')
@@ -138,7 +138,7 @@ class Purchase(metaclass=PoolMeta):
drop_shipments.append(drop_shipment)
DropShipment.save(drop_shipments)
DropShipment.wait(drop_shipments)
super()._process_shipment(purchases)
super()._process_fulfillment(purchases)
def create_drop_shipment(self):
pool = Pool()
+1 -1
View File
@@ -293,7 +293,7 @@ class Shop(metaclass=PoolMeta):
def get_sale(self, party=None):
sale = super().get_sale(party=party)
if self.type == 'shopify':
sale.invoice_method = 'shipment'
sale.invoice_method = 'fulfillment'
return sale
def update_sales(self, sales):