Add cost for shipment and allow shipment costs to not be sold
issue9740 review320181004
This commit is contained in:
@@ -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
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -11,6 +11,7 @@ depends:
|
||||
stock
|
||||
extras_depend:
|
||||
sale_promotion
|
||||
stock_shipment_cost
|
||||
xml:
|
||||
sale.xml
|
||||
stock.xml
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user