From 865dbb44d7fd311b4a857cf4c665d7d514b84ffc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Mon, 18 Jul 2022 00:16:26 +0200 Subject: [PATCH] Show cost allocation before and after posting issue11558 review413301003 --- CHANGELOG | 2 + __init__.py | 6 + account.py | 129 +++++++++++++++++-- account.xml | 32 ++++- tests/scenario_account_stock_landed_cost.rst | 16 ++- view/landed_cost_form.xml | 3 +- view/landed_cost_show_form.xml | 9 ++ view/landed_cost_show_move_list.xml | 7 + 8 files changed, 188 insertions(+), 16 deletions(-) create mode 100644 view/landed_cost_show_form.xml create mode 100644 view/landed_cost_show_move_list.xml 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. -->