diff --git a/CHANGELOG b/CHANGELOG
index a3ec3d37aa..ef402550c5 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -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
diff --git a/__init__.py b/__init__.py
index b7891bf7f5..893b0ab356 100644
--- a/__init__.py
+++ b/__init__.py
@@ -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')
diff --git a/account.py b/account.py
index e88ff9bd20..a3fe3567e3 100644
--- a/account.py
+++ b/account.py
@@ -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',
diff --git a/account.xml b/account.xml
index 4abc3765e8..24365802c8 100644
--- a/account.xml
+++ b/account.xml
@@ -127,13 +127,19 @@ this repository contains the full copyright notices and license terms. -->
- post
+ post_wizard
Post
+
+ show
+ Show
+
+
+
draft
Draft
@@ -153,6 +159,30 @@ this repository contains the full copyright notices and license terms. -->
+
+ Post Landed Cost
+ account.landed_cost.post
+ account.landed_cost
+
+
+
+ Show Landed Cost
+ account.landed_cost.show
+ account.landed_cost
+
+
+
+ account.landed_cost.show
+ form
+ landed_cost_show_form
+
+
+
+ account.landed_cost.show.move
+ tree
+ landed_cost_show_move_list
+
+
account.invoice.line
diff --git a/tests/scenario_account_stock_landed_cost.rst b/tests/scenario_account_stock_landed_cost.rst
index 6154f10089..0cfacce6cf 100644
--- a/tests/scenario_account_stock_landed_cost.rst
+++ b/tests/scenario_account_stock_landed_cost.rst
@@ -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()
diff --git a/view/landed_cost_form.xml b/view/landed_cost_form.xml
index 8b4646bf1d..c1153e921b 100644
--- a/view/landed_cost_form.xml
+++ b/view/landed_cost_form.xml
@@ -21,6 +21,7 @@ this repository contains the full copyright notices and license terms. -->
-
+
+
diff --git a/view/landed_cost_show_form.xml b/view/landed_cost_show_form.xml
new file mode 100644
index 0000000000..f31399b61d
--- /dev/null
+++ b/view/landed_cost_show_form.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/view/landed_cost_show_move_list.xml b/view/landed_cost_show_move_list.xml
new file mode 100644
index 0000000000..cbbff02888
--- /dev/null
+++ b/view/landed_cost_show_move_list.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+