Store product cost price revision

The "Modify Cost Price" wizard create a revision record for each template or
product. The cost price re-computation methods apply those revisions at the
beginning of their date.
The stock accounting move is created and posted when the cost price is modified
by the re-computation.

issue8930
review260751002
This commit is contained in:
Cédric Krier committed 2020-01-26 21:10:40 +01:00
1 parent 8664e3be56
commit aefdb3447c
8 files changed
+42 -216

No files matched your search

-5
View File
@@ -18,9 +18,4 @@ def register():
account.ConfigurationStockJournal,
account.FiscalYear,
account.AccountMove,
product.ModifyCostPriceAsk,
product.ModifyCostPriceShowMove,
module='account_stock_continental', type_='model')
Pool.register(
product.ModifyCostPrice,
module='account_stock_continental', type_='wizard')
-11
View File
@@ -1,11 +0,0 @@
<?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. -->
<tryton>
<data grouped="1">
<record model="ir.message" id="msg_product_change_cost_price">
<field name="text">You cannot change the cost price of a product which is associated to stock moves.
You must use the "Modify Cost Price" wizard.</field>
</record>
</data>
</tryton>
+40 -144
View File
@@ -3,19 +3,14 @@
from decimal import Decimal
from trytond import backend
from trytond.model import ModelView, fields
from trytond.wizard import Wizard, StateView, StateTransition, Button
from trytond.model import fields
from trytond.pyson import Eval
from trytond.transaction import Transaction
from trytond.pool import Pool, PoolMeta
from trytond.modules.account_product.product import (
account_used, template_property)
from trytond.modules.product import price_digits
__all__ = ['Category', 'CategoryAccount', 'Template',
'Product', 'ModifyCostPriceAsk',
'ModifyCostPriceShowMove', 'ModifyCostPrice']
account_names = [
'account_stock', 'account_stock_in', 'account_stock_out']
@@ -181,161 +176,62 @@ class Product(metaclass=PoolMeta):
account_stock_in_used = template_property('account_stock_in_used')
account_stock_out_used = template_property('account_stock_out_used')
class ModifyCostPriceAsk(ModelView):
'Modify Cost Price Ask'
__name__ = 'product.modify_cost_price.ask'
template = fields.Many2One('product.template', 'Product', readonly=True,
states={
'invisible': ~Eval('template'),
})
product = fields.Many2One('product.product', 'Variant', readonly=True,
states={
'invisible': ~Eval('product'),
})
cost_price = fields.Numeric('Cost Price', required=True,
digits=price_digits)
class ModifyCostPriceShowMove(ModelView):
'Modify Cost Price Show Move'
__name__ = 'product.modify_cost_price.show_move'
price_difference = fields.Numeric('Price Difference', readonly=True,
digits=price_digits)
amount = fields.Numeric('Amount', readonly=True,
digits=(16, Eval('currency_digits', 2)), depends=['currency_digits'])
currency_digits = fields.Integer('Currency Digits', readonly=True)
description = fields.Char('Description')
class ModifyCostPrice(Wizard):
'Modify Cost Price'
__name__ = 'product.modify_cost_price'
start_state = 'ask_price'
ask_price = StateView('product.modify_cost_price.ask',
'account_stock_continental.modify_cost_price_ask_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('OK', 'should_show_move', 'tryton-forward', default=True),
])
should_show_move = StateTransition()
show_move = StateView('product.modify_cost_price.show_move',
'account_stock_continental.modify_cost_price_show_move_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('OK', 'create_move', 'tryton-ok', default=True),
])
create_move = StateTransition()
modify_price = StateTransition()
@classmethod
def get_product(cls):
'Return the product instance'
pool = Pool()
Product = pool.get('product.product')
context = Transaction().context
return Product(context['active_id'])
def default_ask_price(self, fields):
default = {}
product = self.get_product()
default['product'] = product.id
default['cost_price'] = getattr(
product, 'recompute_cost_price_%s' % product.cost_price_method)()
return default
@classmethod
def get_quantity(cls):
def update_cost_price(cls, costs):
pool = Pool()
Date = pool.get('ir.date')
Stock = pool.get('stock.location')
Company = pool.get('company.company')
Move = pool.get('account.move')
context = Transaction().context
locations = Stock.search([('type', '=', 'storage')])
stock_date_end = Date.today()
company = Company(context['company'])
moves = []
with Transaction().set_context(locations=[l.id for l in locations],
stock_date_end=stock_date_end):
product = cls.get_product()
return product.quantity
for cost, products in costs.items():
products = cls.browse(products)
for product in products:
difference = cost - product.cost_price
quantity = product.quantity
amount = company.currency.round(
Decimal(str(quantity)) * difference)
if amount:
moves.append(product._update_cost_price_move(
amount, company))
Move.save(moves)
Move.post(moves)
super().update_cost_price(costs)
@property
def company(self):
pool = Pool()
User = pool.get('res.user')
user = User(Transaction().user)
return user.company
@property
def difference_price(self):
product = self.get_product()
return self.ask_price.cost_price - product.cost_price
def get_amount(self):
return self.company.currency.round(
Decimal(str(self.get_quantity())) * self.difference_price)
def transition_should_show_move(self):
if self.get_quantity() != 0:
return 'show_move'
return 'modify_price'
def default_show_move(self, fields):
return {
'amount': self.get_amount(),
'price_difference': self.difference_price,
'currency_digits': self.company.currency.digits,
}
def get_move_lines(self):
pool = Pool()
Line = pool.get('account.move.line')
product = self.get_product()
amount = self.get_amount()
if amount > 0:
account = product.account_stock_in_used
else:
account = product.account_stock_out_used
return [Line(
debit=amount if amount > 0 else 0,
credit=-amount if amount < 0 else 0,
account=product.account_stock_used,
),
Line(
debit=-amount if amount < 0 else 0,
credit=amount if amount > 0 else 0,
account=account,
),
]
def get_move(self):
def _update_cost_price_move(self, amount, company):
pool = Pool()
AccountConfiguration = pool.get('account.configuration')
Date = pool.get('ir.date')
Period = pool.get('account.period')
User = pool.get('res.user')
Move = pool.get('account.move')
AccountConfiguration = pool.get('account.configuration')
Line = pool.get('account.move.line')
config = AccountConfiguration(1)
user = User(Transaction().user)
period_id = Period.find(user.company.id)
if amount > 0:
account = self.account_stock_in_used
else:
account = self.account_stock_out_used
return Move(
description=self.show_move.description,
period=period_id,
period=Period.find(company.id),
journal=config.stock_journal,
date=Date.today(),
origin=self.get_product(),
lines=self.get_move_lines(),
origin=self,
lines=[Line(
debit=amount if amount > 0 else 0,
credit=-amount if amount < 0 else 0,
account=self.account_stock_used,
),
Line(
debit=-amount if amount < 0 else 0,
credit=amount if amount > 0 else 0,
account=account,
),
],
)
def transition_create_move(self):
Move = Pool().get('account.move')
move = self.get_move()
move.save()
Move.post([move])
return 'modify_price'
def transition_modify_price(self):
pool = Pool()
Product = pool.get('product.product')
with Transaction().set_context(_check_access=False):
Product.write([self.get_product()], {
'cost_price': self.ask_price.cost_price,
})
return 'end'
-25
View File
@@ -3,31 +3,6 @@
this repository contains the full copyright notices and license terms. -->
<tryton>
<data>
<record model="ir.ui.view" id="modify_cost_price_ask_form">
<field name="model">product.modify_cost_price.ask</field>
<field name="type">form</field>
<field name="name">modify_cost_price_ask_form</field>
</record>
<record model="ir.ui.view" id="modify_cost_price_show_move_form">
<field name="model">product.modify_cost_price.show_move</field>
<field name="type">form</field>
<field name="name">modify_cost_price_show_move_form</field>
</record>
<record model="ir.action.wizard" id="wizard_modify_cost_price">
<field name="name">Modify Cost Price</field>
<field name="wiz_name">product.modify_cost_price</field>
</record>
<record model="ir.action.keyword" id="modify_cost_price_product_keyword">
<field name="keyword">form_action</field>
<field name="model">product.product,-1</field>
<field name="action" ref="wizard_modify_cost_price"/>
</record>
<record model="ir.action-res.group"
id="wizard_modify_cost_price-group_account">
<field name="action" ref="wizard_modify_cost_price"/>
<field name="group" ref="account.group_account" />
</record>
<record model="ir.ui.view" id="category_view_form">
<field name="model">product.category</field>
<field name="inherit" ref="product.category_view_form"/>
+2 -4
View File
@@ -376,10 +376,8 @@ Modify cost price::
>>> Account = Model.get('account.account')
>>> modify_price = Wizard('product.modify_cost_price', [product])
>>> modify_price.form.cost_price = Decimal('3.00')
>>> modify_price.execute('should_show_move')
>>> modify_price.form.description = 'Change product cost price.'
>>> modify_price.execute('create_move')
>>> modify_price.form.cost_price = '3.00'
>>> modify_price.execute('modify')
>>> product.cost_price
Decimal('3.00')
>>> stock_out.reload()
-1
View File
@@ -19,4 +19,3 @@ xml:
minimal_chart_pt.xml
minimal_chart_ru.xml
minimal_chart_sl.xml
message.xml
-15
View File
@@ -1,15 +0,0 @@
<?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 cursor="cost_price">
<group id="label_product_template" col="1" xexpand="0">
<label name="template"/>
<label name="product"/>
</group>
<group id="product_template" col="1">
<field name="template"/>
<field name="product"/>
</group>
<label name="cost_price"/>
<field name="cost_price"/>
</form>
-11
View File
@@ -1,11 +0,0 @@
<?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="amount"/>
<field name="amount"/>
<label name="price_difference"/>
<field name="price_difference"/>
<label name="description"/>
<field name="description" colspan="3"/>
</form>