Show cost allocation before and after posting
issue11558 review413301003
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
* Show cost allocation before and after posting
|
||||
|
||||
Version 6.4.0 - 2022-05-02
|
||||
* Bug fixes (see mercurial logs for details)
|
||||
* Add support for Python 3.10
|
||||
|
||||
@@ -16,6 +16,12 @@ def register():
|
||||
account.LandedCost_Shipment,
|
||||
account.LandedCost_ProductCategory,
|
||||
account.LandedCost_Product,
|
||||
account.LandedCostShow,
|
||||
account.LandedCostShowMove,
|
||||
account.InvoiceLine,
|
||||
stock.Move,
|
||||
module='account_stock_landed_cost', type_='model')
|
||||
Pool.register(
|
||||
account.PostLandedCost,
|
||||
account.ShowLandedCost,
|
||||
module='account_stock_landed_cost', type_='wizard')
|
||||
|
||||
+116
-13
@@ -8,11 +8,12 @@ from trytond import backend
|
||||
from trytond.i18n import gettext
|
||||
from trytond.model import MatchMixin, ModelSQL, ModelView, Workflow, fields
|
||||
from trytond.modules.company.model import CompanyValueMixin
|
||||
from trytond.modules.product import round_price
|
||||
from trytond.modules.product import price_digits, round_price
|
||||
from trytond.pool import Pool, PoolMeta
|
||||
from trytond.pyson import Eval, Id
|
||||
from trytond.tools.multivalue import migrate_property
|
||||
from trytond.transaction import Transaction
|
||||
from trytond.wizard import Button, StateTransition, StateView, Wizard
|
||||
|
||||
from .exceptions import FilterUnusedWarning, NoMoveWarning
|
||||
|
||||
@@ -169,10 +170,14 @@ class LandedCost(Workflow, ModelSQL, ModelView, MatchMixin):
|
||||
'invisible': Eval('state') != 'cancelled',
|
||||
'depends': ['state'],
|
||||
},
|
||||
'post': {
|
||||
'post_wizard': {
|
||||
'invisible': Eval('state') != 'draft',
|
||||
'depends': ['state'],
|
||||
},
|
||||
'show': {
|
||||
'invisible': Eval('state').in_(['draft', 'cancelled']),
|
||||
'depends': ['state']
|
||||
},
|
||||
})
|
||||
|
||||
@classmethod
|
||||
@@ -290,13 +295,18 @@ class LandedCost(Workflow, ModelSQL, ModelView, MatchMixin):
|
||||
product=product.rec_name))
|
||||
|
||||
def allocate_cost_by_value(self):
|
||||
self.factors = self._get_value_factors()
|
||||
self.factors = self._get_factors('value')
|
||||
self._allocate_cost(self.factors)
|
||||
|
||||
def unallocate_cost_by_value(self):
|
||||
factors = self.factors or self._get_value_factors()
|
||||
factors = self.factors or self._get_factors('value')
|
||||
self._allocate_cost(factors, sign=-1)
|
||||
|
||||
def _get_factors(self, method=None):
|
||||
if method is None:
|
||||
method = self.allocation_method
|
||||
return getattr(self, '_get_%s_factors' % method)()
|
||||
|
||||
def _get_value_factors(self):
|
||||
"Return the factor for each move based on value"
|
||||
pool = Pool()
|
||||
@@ -325,17 +335,10 @@ class LandedCost(Workflow, ModelSQL, ModelView, MatchMixin):
|
||||
quantity * unit_prices[move.id] / sum_value)
|
||||
return factors
|
||||
|
||||
def _allocate_cost(self, factors, sign=1):
|
||||
"Allocate cost on moves using factors"
|
||||
def _costs_to_allocate(self, moves, factors):
|
||||
pool = Pool()
|
||||
Move = pool.get('stock.move')
|
||||
Currency = pool.get('currency.currency')
|
||||
assert sign in {1, -1}
|
||||
|
||||
cost = self.cost
|
||||
currency = self.company.currency
|
||||
moves = [m for m in self.stock_moves() if m.quantity]
|
||||
|
||||
costs = []
|
||||
digit = Move.unit_price.digits[1]
|
||||
exp = Decimal(str(10.0 ** -digit))
|
||||
@@ -360,6 +363,18 @@ class LandedCost(Workflow, ModelSQL, ModelView, MatchMixin):
|
||||
difference -= exp * quantity
|
||||
if difference < exp:
|
||||
break
|
||||
return costs
|
||||
|
||||
def _allocate_cost(self, factors, sign=1):
|
||||
"Allocate cost on moves using factors"
|
||||
pool = Pool()
|
||||
Move = pool.get('stock.move')
|
||||
Currency = pool.get('currency.currency')
|
||||
assert sign in {1, -1}
|
||||
|
||||
currency = self.company.currency
|
||||
moves = [m for m in self.stock_moves() if m.quantity]
|
||||
costs = self._costs_to_allocate(moves, factors)
|
||||
|
||||
for cost in costs:
|
||||
move = cost['move']
|
||||
@@ -376,7 +391,18 @@ class LandedCost(Workflow, ModelSQL, ModelView, MatchMixin):
|
||||
Move.save(moves)
|
||||
|
||||
@classmethod
|
||||
@ModelView.button
|
||||
@ModelView.button_action(
|
||||
'account_stock_landed_cost.wizard_landed_cost_post')
|
||||
def post_wizard(cls, landed_costs):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
@ModelView.button_action(
|
||||
'account_stock_landed_cost.wizard_landed_cost_show')
|
||||
def show(cls, landed_costs):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
@Workflow.transition('posted')
|
||||
def post(cls, landed_costs):
|
||||
pool = Pool()
|
||||
@@ -459,6 +485,83 @@ class LandedCost_Product(ModelSQL):
|
||||
'product.product', "Product", required=True, ondelete='CASCADE')
|
||||
|
||||
|
||||
class ShowLandedCostMixin(Wizard):
|
||||
start_state = 'show'
|
||||
show = StateView('account.landed_cost.show',
|
||||
'account_stock_landed_cost.landed_cost_show_view_form', [])
|
||||
|
||||
@property
|
||||
def factors(self):
|
||||
return self.record._get_factors()
|
||||
|
||||
def default_show(self, fields):
|
||||
moves = []
|
||||
default = {
|
||||
'cost': round_price(self.record.cost),
|
||||
'moves': moves,
|
||||
}
|
||||
stock_moves = [m for m in self.record.stock_moves() if m.quantity]
|
||||
costs = self.record._costs_to_allocate(stock_moves, self.factors)
|
||||
for cost in costs:
|
||||
moves.append({
|
||||
'move': cost['move'].id,
|
||||
'cost': round_price(
|
||||
cost['unit_landed_cost'], rounding=ROUND_HALF_EVEN),
|
||||
})
|
||||
return default
|
||||
|
||||
|
||||
class PostLandedCost(ShowLandedCostMixin):
|
||||
"Post Landed Cost"
|
||||
__name__ = 'account.landed_cost.post'
|
||||
post = StateTransition()
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super().__setup__()
|
||||
cls.show.buttons.extend([
|
||||
Button("Cancel", 'end', 'tryton-cancel'),
|
||||
Button("Post", 'post', 'tryton-ok', default=True),
|
||||
])
|
||||
|
||||
def transition_post(self):
|
||||
self.model.post([self.record])
|
||||
return 'end'
|
||||
|
||||
|
||||
class ShowLandedCost(ShowLandedCostMixin):
|
||||
"Show Landed Cost"
|
||||
__name__ = 'account.landed_cost.show'
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super().__setup__()
|
||||
cls.show.buttons.extend([
|
||||
Button("Close", 'end', 'tryton-close', default=True),
|
||||
])
|
||||
|
||||
@property
|
||||
def factors(self):
|
||||
return self.record.factors or super().factors
|
||||
|
||||
|
||||
class LandedCostShow(ModelView):
|
||||
"Landed Cost Show"
|
||||
__name__ = 'account.landed_cost.show'
|
||||
|
||||
cost = fields.Numeric("Cost", digits=price_digits, readonly=True)
|
||||
moves = fields.One2Many(
|
||||
'account.landed_cost.show.move', None, "Moves", readonly=True)
|
||||
|
||||
|
||||
class LandedCostShowMove(ModelView):
|
||||
"Landed Cost Show"
|
||||
__name__ = 'account.landed_cost.show.move'
|
||||
|
||||
move = fields.Many2One('stock.move', "Move", readonly=True)
|
||||
cost = fields.Numeric("Cost", digits=price_digits, readonly=True)
|
||||
|
||||
|
||||
class InvoiceLine(metaclass=PoolMeta):
|
||||
__name__ = 'account.invoice.line'
|
||||
landed_cost = fields.Many2One('account.landed_cost', 'Landed Cost',
|
||||
|
||||
+31
-1
@@ -127,13 +127,19 @@ this repository contains the full copyright notices and license terms. -->
|
||||
</record>
|
||||
|
||||
<record model="ir.model.button" id="landed_cost_post_button">
|
||||
<field name="name">post</field>
|
||||
<field name="name">post_wizard</field>
|
||||
<field name="string">Post</field>
|
||||
<field name="confirm" eval="None"/>
|
||||
<field name="model"
|
||||
search="[('model', '=', 'account.landed_cost')]"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.model.button" id="landed_cost_show_button">
|
||||
<field name="name">show</field>
|
||||
<field name="string">Show</field>
|
||||
<field name="model" search="[('model', '=', 'account.landed_cost')]"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.model.button" id="landed_cost_draft_button">
|
||||
<field name="name">draft</field>
|
||||
<field name="string">Draft</field>
|
||||
@@ -153,6 +159,30 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="rule_group" ref="rule_group_landed_cost_companies"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.action.wizard" id="wizard_landed_cost_post">
|
||||
<field name="name">Post Landed Cost</field>
|
||||
<field name="wiz_name">account.landed_cost.post</field>
|
||||
<field name="model">account.landed_cost</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.action.wizard" id="wizard_landed_cost_show">
|
||||
<field name="name">Show Landed Cost</field>
|
||||
<field name="wiz_name">account.landed_cost.show</field>
|
||||
<field name="model">account.landed_cost</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="landed_cost_show_view_form">
|
||||
<field name="model">account.landed_cost.show</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">landed_cost_show_form</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="landed_cost_show_move_view_list">
|
||||
<field name="model">account.landed_cost.show.move</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">landed_cost_show_move_list</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="invoice_line_view_form">
|
||||
<field name="model">account.invoice.line</field>
|
||||
<field name="inherit" ref="account_invoice.invoice_line_view_form"/>
|
||||
|
||||
@@ -166,7 +166,13 @@ Add landed cost::
|
||||
>>> landed_cost.save()
|
||||
>>> landed_cost.state
|
||||
'draft'
|
||||
>>> landed_cost.click('post')
|
||||
|
||||
>>> post_landed_cost = Wizard('account.landed_cost.post', [landed_cost])
|
||||
>>> post_landed_cost.form.cost
|
||||
Decimal('10.0000')
|
||||
>>> sorted([m.cost for m in post_landed_cost.form.moves])
|
||||
[Decimal('1.0000')]
|
||||
>>> post_landed_cost.execute('post')
|
||||
>>> landed_cost.state
|
||||
'posted'
|
||||
>>> bool(landed_cost.posted_date)
|
||||
@@ -174,6 +180,14 @@ Add landed cost::
|
||||
>>> bool(landed_cost.factors)
|
||||
True
|
||||
|
||||
Show landed cost::
|
||||
|
||||
>>> show_landed_cost = Wizard('account.landed_cost.show', [landed_cost])
|
||||
>>> show_landed_cost.form.cost
|
||||
Decimal('10.0000')
|
||||
>>> sorted([m.cost for m in show_landed_cost.form.moves])
|
||||
[Decimal('1.0000')]
|
||||
|
||||
Check move unit price is 101::
|
||||
|
||||
>>> shipment.reload()
|
||||
|
||||
@@ -21,6 +21,7 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<group col="-1" colspan="2" id="buttons">
|
||||
<button name="cancel" icon="tryton-cancel"/>
|
||||
<button name="draft" icon="tryton-clear"/>
|
||||
<button name="post" icon="tryton-ok"/>
|
||||
<button name="post_wizard" icon="tryton-ok"/>
|
||||
<button name="show" icon="tryton-search"/>
|
||||
</group>
|
||||
</form>
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0"?>
|
||||
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
this repository contains the full copyright notices and license terms. -->
|
||||
<form>
|
||||
<label name="cost"/>
|
||||
<field name="cost"/>
|
||||
|
||||
<field name="moves" mode="tree" colspan="4"/>
|
||||
</form>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0"?>
|
||||
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
this repository contains the full copyright notices and license terms. -->
|
||||
<tree>
|
||||
<field name="move" expand="1"/>
|
||||
<field name="cost"/>
|
||||
</tree>
|
||||
Reference in New Issue
Block a user