Add cost for shipment and allow shipment costs to not be sold

issue9740
review320181004
This commit is contained in:
Cédric Krier
2021-02-27 14:37:51 +01:00
parent ce36f08654
commit 2504c10bf0
7 changed files with 144 additions and 36 deletions
+2
View File
@@ -1,3 +1,5 @@
* Use purchase carrier price as cost for shipment
* Allow shipment costs to not be sold
* Add carrier on shipment list view
* Exclude by default shipment cost from promotion amount
* Allow shipment cost price to be edited
+4 -2
View File
@@ -8,8 +8,10 @@ Two new fields are added to *Sale* and *Sale Configuration*:
- *Carrier*: The carrier used for the sale.
- *Shipment Cost Method*: The method used to compute the cost.
- *On Order*: The cost will be computed once for the sale.
- *On Shipment*: The cost will be computed for each shipments.
- *On Order*: The cost will be invoiced once for the sale.
- *On Shipment*: The cost will be invoiced for each shipments.
- *None*: The cost will not be invoiced to the customer but added to the
outgoing moves cost.
At the quotation if a carrier is selected a new line is appended with the
shipment cost but the added line can be modified when going back to draft.
+24 -8
View File
@@ -90,12 +90,20 @@ class Sale(metaclass=PoolMeta):
fields.Many2Many('carrier', None, None, 'Available Carriers'),
'on_change_with_available_carriers')
shipment_cost_method = fields.Selection([
(None, "None"),
('order', 'On Order'),
('shipment', 'On Shipment'),
], 'Shipment Cost Method', required=True, states={
], 'Shipment Cost Method', states={
'readonly': Eval('state') != 'draft',
}, depends=['state'])
@classmethod
def __register__(cls, module):
super().__register__(module)
table_h = cls.__table_handler__(module)
# Migration from 5.8: remove required on shipment_cost_method
table_h.not_null_action('shipment_cost_method', 'remove')
@staticmethod
def default_shipment_cost_method():
Config = Pool().get('sale.configuration')
@@ -209,7 +217,9 @@ class Sale(metaclass=PoolMeta):
return 0
def set_shipment_cost(self):
cost = self.compute_shipment_cost()
cost = None
if self.shipment_cost_method:
cost = self.compute_shipment_cost()
removed = []
unit_price = None
lines = list(self.lines or [])
@@ -256,7 +266,9 @@ class Sale(metaclass=PoolMeta):
return cost_line
def create_shipment(self, shipment_type):
Shipment = Pool().get('stock.shipment.out')
pool = Pool()
Shipment = pool.get('stock.shipment.out')
Currency = pool.get('currency.currency')
shipments = super(Sale, self).create_shipment(shipment_type)
if shipment_type == 'out' and shipments and self.carrier:
@@ -264,11 +276,15 @@ class Sale(metaclass=PoolMeta):
shipment.carrier = self.carrier
with Transaction().set_context(
shipment.get_carrier_context()):
cost, currency_id = self.carrier.get_sale_price()
cost = round_price(cost)
shipment.carrier = self.carrier
shipment.cost = cost
shipment.cost_currency = currency_id
cost, currency_id = self.carrier.get_purchase_price()
shipment.cost = round_price(Currency.compute(
Currency(currency_id), cost, shipment.company.currency,
round=False))
with Transaction().set_context(
shipment.get_carrier_context()):
cost_sale, currency_id = self.carrier.get_sale_price()
shipment.cost_sale = round_price(cost_sale)
shipment.cost_sale_currency = currency_id
Shipment.save(shipments)
return shipments
+55 -19
View File
@@ -17,22 +17,42 @@ class ShipmentOut(metaclass=PoolMeta):
'picked', 'packed']),
},
depends=['state'])
cost_currency = fields.Many2One('currency.currency',
'Cost Currency', states={
'invisible': ~Eval('carrier'),
'required': Bool(Eval('carrier')),
'readonly': ~Eval('state').in_(['draft', 'waiting', 'assigned',
'picked', 'packed']),
}, depends=['carrier', 'state'])
cost = fields.Numeric('Cost',
cost = fields.Numeric("Cost",
digits=price_digits, states={
'invisible': ~Eval('carrier'),
'readonly': ~Eval('state').in_(['draft', 'waiting', 'assigned',
'picked', 'packed']),
'readonly': ~Eval('state').in_(
['draft', 'waiting', 'assigned', 'packed']),
}, depends=['carrier', 'state'])
cost_sale_currency = fields.Many2One(
'currency.currency', "Cost Sale Currency",
states={
'invisible': ~Eval('carrier'),
'required': Bool(Eval('carrier')),
'readonly': ~Eval('state').in_(
['draft', 'waiting', 'assigned', 'picked', 'packed']),
}, depends=['carrier', 'state'])
cost_sale = fields.Numeric(
"Cost Sale", digits=price_digits,
states={
'invisible': ~Eval('carrier'),
'readonly': ~Eval('state').in_(
['draft', 'waiting', 'assigned', 'picked', 'packed']),
}, depends=['carrier', 'state'])
cost_invoice_line = fields.Many2One('account.invoice.line',
'Cost Invoice Line', readonly=True)
@classmethod
def __register__(cls, module):
table_h = cls.__table_handler__(module)
# Migration from 5.8: rename cost into cost_sale
if (table_h.column_exist('cost')
and not table_h.column_exist('cost_sale')):
table_h.column_rename('cost', 'cost_sale')
if (table_h.column_exist('cost_currency')
and not table_h.column_exist('cost_sale_currency')):
table_h.column_rename('cost_currency', 'cost_sale_currency')
super().__register__(module)
def _get_carrier_context(self):
return {}
@@ -43,18 +63,27 @@ class ShipmentOut(metaclass=PoolMeta):
def on_change_carrier(self):
self.on_change_inventory_moves()
@fields.depends('carrier', methods=['_get_carrier_context'])
@fields.depends('carrier', 'company', methods=['_get_carrier_context'])
def on_change_inventory_moves(self):
pool = Pool()
Currency = pool.get('currency.currency')
try:
super(ShipmentOut, self).on_change_inventory_moves()
except AttributeError:
pass
if not self.carrier:
self.cost = None
self.cost_sale = None
self.cost_sale_currency = None
return
with Transaction().set_context(self._get_carrier_context()):
cost, currency_id = self.carrier.get_sale_price()
self.cost = round_price(cost)
self.cost_currency = currency_id
cost, currency_id = self.carrier.get_purchase_price()
cost_sale, sale_currency_id = self.carrier.get_sale_price()
self.cost = round_price(Currency.compute(
Currency(currency_id), cost, self.company.currency,
round=False))
self.cost_sale = round_price(cost_sale)
self.cost_sale_currency = sale_currency_id
def _get_cost_tax_rule_pattern(self):
'Get tax rule pattern for invoice line'
@@ -66,8 +95,8 @@ class ShipmentOut(metaclass=PoolMeta):
Currency = pool.get('currency.currency')
InvoiceLine = pool.get('account.invoice.line')
if not self.cost:
return {}
if not self.cost_sale:
return
invoice_line = InvoiceLine()
product = self.carrier.carrier_product
invoice_line.type = 'line'
@@ -82,10 +111,11 @@ class ShipmentOut(metaclass=PoolMeta):
invoice_line.quantity = 1 # XXX
invoice_line.unit = product.sale_uom.id
cost = self.cost
if invoice.currency != self.cost_currency:
cost = self.cost_sale
if invoice.currency != self.cost_sale_currency:
with Transaction().set_context(date=invoice.currency_date):
cost = Currency.compute(self.cost_currency, cost,
cost = Currency.compute(
self.cost_sale_currency, cost,
invoice.currency, round=False)
invoice_line.unit_price = round_price(cost)
invoice_line.currency = invoice.currency
@@ -114,3 +144,9 @@ class ShipmentOut(metaclass=PoolMeta):
shipment=self.rec_name,
product=product.rec_name))
return invoice_line
def _get_shipment_cost(self):
cost = super()._get_shipment_cost(self)
if all(not m.sale.shipment_cost_method for m in self.outgoing_moves):
cost += self.cost
return cost
+52 -3
View File
@@ -84,6 +84,8 @@ Create product::
>>> 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::
@@ -140,14 +142,16 @@ Send products::
>>> shipment.carrier == carrier
True
>>> shipment.cost
Decimal('2.0000')
>>> shipment.cost_sale
Decimal('3.0000')
>>> shipment.cost_currency == company.currency
>>> shipment.cost_sale_currency == company.currency
True
>>> move, = shipment.inventory_moves
>>> move.quantity = 4
>>> shipment.cost
>>> shipment.cost_sale
Decimal('3.0000')
>>> shipment.cost_currency == company.currency
>>> shipment.cost_sale_currency == company.currency
True
>>> shipment.state
'waiting'
@@ -267,3 +271,48 @@ Check customer invoice::
>>> invoice, = sale.invoices
>>> len(invoice.lines)
2
Sale products with no cost::
>>> sale = Sale()
>>> sale.party = customer
>>> sale.carrier = carrier
>>> sale.payment_term = payment_term
>>> sale.invoice_method = 'shipment'
>>> sale.shipment_cost_method = None
>>> sale_line = sale.lines.new()
>>> sale_line.product = product
>>> sale_line.quantity = 1.0
>>> sale.click('quote')
>>> len(sale.lines)
1
>>> sale.click('confirm')
>>> sale.click('process')
>>> sale.state
'processing'
Check no customer invoice::
>>> len(sale.invoices)
0
Send products::
>>> shipment, = sale.shipments
>>> shipment.cost
Decimal('2.0000')
>>> shipment.cost_sale
Decimal('3.0000')
>>> shipment.click('assign_force')
>>> shipment.click('pick')
>>> shipment.click('pack')
>>> shipment.click('done')
>>> shipment.state
'done'
Check customer invoice::
>>> sale.reload()
>>> invoice, = sale.invoices
>>> len(invoice.lines)
1
+1
View File
@@ -11,6 +11,7 @@ depends:
stock
extras_depend:
sale_promotion
stock_shipment_cost
xml:
sale.xml
stock.xml
+6 -4
View File
@@ -6,9 +6,11 @@ this repository contains the full copyright notices and license terms. -->
<label name="carrier"/>
<field name="carrier"/>
<label name="cost"/>
<group col="2" id="cost">
<field name="cost"/>
<field name="cost_currency"/>
</group>
<field name="cost"/>
<label name="cost_sale"/>
<field name="cost_sale"/>
<label name="cost_sale_currency"/>
<field name="cost_sale_currency"/>
</xpath>
</data>