Skip to content

Commit 23792da

Browse files
authored
Merge pull request #2376 from ForgeFlow/13.0-mig-stock_account-script
[13.0][MIG] stock_account
2 parents 1602ea5 + b463aec commit 23792da

File tree

4 files changed

+353
-1
lines changed

4 files changed

+353
-1
lines changed
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
---Models in module 'stock_account'---
2+
new model stock.valuation.layer
3+
# DONE: post-migration: try to use data from product.price.history in some way (?)
4+
5+
---Fields in module 'stock_account'---
6+
stock_account / account.move.line / is_anglo_saxon_line (boolean) : NEW
7+
# TODO (?): is there a way to know which lines are anglosaxon??
8+
9+
stock_account / product.product / valuation (char) : selection_keys is now 'function' ('False')
10+
stock_account / product.product / valuation (char) : type is now 'selection' ('char')
11+
stock_account / product.product / cost_method (char) : selection_keys is now 'function' ('False')
12+
stock_account / product.product / cost_method (char) : type is now 'selection' ('char')
13+
stock_account / product.template / cost_method (char) : not a function anymore
14+
stock_account / product.template / cost_method (char) : now related
15+
stock_account / product.template / cost_method (char) : selection_keys is now 'function' ('False')
16+
stock_account / product.template / cost_method (char) : type is now 'selection' ('char')
17+
stock_account / product.template / valuation (char) : not a function anymore
18+
stock_account / product.template / valuation (char) : now related
19+
stock_account / product.template / valuation (char) : selection_keys is now 'function' ('False')
20+
stock_account / product.template / valuation (char) : type is now 'selection' ('char')
21+
# NOTHING TO DO: they are non stored fields
22+
23+
stock_account / product.template / property_cost_method (selection): DEL selection_keys: ['average', 'fifo', 'standard']
24+
stock_account / product.template / property_stock_account_input (many2one): DEL relation: account.account
25+
stock_account / product.template / property_stock_account_output (many2one): DEL relation: account.account
26+
stock_account / product.template / property_valuation (selection): DEL selection_keys: ['manual_periodic', 'real_time']
27+
# NOTHING TO DO: they use now the same fields from product.category instead
28+
29+
stock_account / stock.move / remaining_qty (float) : DEL
30+
stock_account / stock.move / remaining_value (float) : DEL
31+
stock_account / stock.move / value (float) : DEL
32+
stock_account / stock.valuation.layer / remaining_qty (float) : NEW
33+
stock_account / stock.valuation.layer / remaining_value (float) : NEW
34+
stock_account / stock.valuation.layer / value (float) : NEW
35+
# NOTHING TO DO: the relation between moves and valuation layers is new
36+
# TODO (?): if the link with the stock move is found, then fill values
37+
38+
stock_account / stock.valuation.layer / account_move_id (many2one) : NEW relation: account.move
39+
stock_account / stock.valuation.layer / company_id (many2one) : NEW relation: res.company, required
40+
stock_account / stock.valuation.layer / description (char) : NEW
41+
stock_account / stock.valuation.layer / product_id (many2one) : NEW relation: product.product, required
42+
stock_account / stock.valuation.layer / quantity (float) : NEW
43+
stock_account / stock.valuation.layer / stock_move_id (many2one) : NEW relation: stock.move
44+
stock_account / stock.valuation.layer / unit_cost (float) : NEW
45+
stock_account / stock.valuation.layer / stock_valuation_layer_id (many2one): NEW relation: stock.valuation.layer
46+
stock_account / account.move / stock_valuation_layer_ids (one2many): NEW relation: stock.valuation.layer
47+
stock_account / product.product / stock_valuation_layer_ids (one2many): NEW relation: stock.valuation.layer
48+
stock_account / stock.move / stock_valuation_layer_ids (one2many): NEW relation: stock.valuation.layer
49+
stock_account / stock.valuation.layer / stock_valuation_layer_ids (one2many): NEW relation: stock.valuation.layer
50+
# DONE: post-migration: handle the new model (try to use data from product.price.history in some way?)
51+
52+
---XML records in module 'stock_account'---
53+
NEW ir.actions.act_window: stock_account.stock_valuation_layer_action
54+
DEL ir.actions.act_window: stock_account.product_valuation_action
55+
NEW ir.model.access: stock_account.access_stock_valuation_layer
56+
NEW ir.rule: stock_account.stock_valuation_layer_company_rule
57+
NEW ir.ui.view: stock_account.product_template_tree_view
58+
NEW ir.ui.view: stock_account.stock_valuation_layer_form
59+
NEW ir.ui.view: stock_account.stock_valuation_layer_picking
60+
NEW ir.ui.view: stock_account.stock_valuation_layer_search
61+
NEW ir.ui.view: stock_account.stock_valuation_layer_tree
62+
NEW ir.ui.view: stock_account.view_inventory_tree
63+
NEW ir.ui.view: stock_account.view_stock_quant_tree_editable_inherit
64+
NEW ir.ui.view: stock_account.view_stock_quant_tree_inherit
65+
DEL ir.ui.view: stock_account.view_move_tree_valuation_at_date
66+
DEL ir.ui.view: stock_account.view_stock_account_aml
67+
DEL ir.ui.view: stock_account.view_stock_product_tree2
68+
DEL ir.ui.view: stock_account.view_stock_quantity_history
69+
# NOTHING TO DO
70+
71+
NEW ir.ui.view: stock_account.product_product_normal_form_view_inherit
72+
DEL ir.ui.view: stock_account.product_normal_form_view_inherit
73+
# DONE: pre-migration: xmlid renamed (to avoid a xpath issue)
74+
75+
DEL ir.property: stock_account.default_cost_method (noupdate)
76+
DEL ir.property: stock_account.default_valuation (noupdate)
77+
DEL ir.property: stock_account.property_stock_account_input_prd (noupdate)
78+
DEL ir.property: stock_account.property_stock_account_output_prd (noupdate)
79+
# DONE: post-migration: try to delete
Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
# Copyright 2020 ForgeFlow <http://www.forgeflow.com>
2+
# Copyright 2020 Andrii Skrypka
3+
# Copyright 2021 Tecnativa - Carlos Dauden
4+
# Copyright 2021 Tecnativa - Sergio Teruel
5+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
6+
7+
import logging
8+
9+
from openupgradelib import openupgrade
10+
from odoo import _
11+
from odoo.tools.float_utils import float_compare, float_is_zero, float_round
12+
from odoo.addons.base.models.ir_model import query_insert
13+
14+
_logger = logging.getLogger(__name__)
15+
16+
# Declare global variant to avoid that it is passed between methods
17+
precision_price = 0
18+
19+
20+
def _prepare_common_svl_vals(move, product):
21+
return {
22+
"create_uid": move["write_uid"],
23+
"create_date": move["date"],
24+
"write_uid": move["write_uid"],
25+
"write_date": move["date"],
26+
"stock_move_id": move["id"],
27+
"company_id": move["company_id"],
28+
"product_id": move["product_id"],
29+
"description": move["reference"] and "%s - %s" % (move["reference"], product.name) or product.name,
30+
"value": 0.0,
31+
"unit_cost": 0.0,
32+
"remaining_qty": 0.0,
33+
"remaining_value": 0.0,
34+
"quantity": 0.0,
35+
"old_product_price_history_id": None,
36+
"account_move_id": move["account_move_id"],
37+
}
38+
39+
40+
def _prepare_in_svl_vals(move, quantity, unit_cost, product, is_dropship):
41+
vals = _prepare_common_svl_vals(move, product)
42+
vals.update({
43+
"value": float_round(unit_cost * quantity, precision_digits=precision_price),
44+
"unit_cost": unit_cost,
45+
"quantity": quantity,
46+
})
47+
if product.cost_method in ("average", "fifo") and not is_dropship:
48+
vals["remaining_qty"] = quantity
49+
vals["remaining_value"] = vals["value"]
50+
return vals
51+
52+
53+
def _prepare_out_svl_vals(move, quantity, unit_cost, product):
54+
# Quantity is negative for out valuation layers.
55+
quantity = -quantity
56+
vals = _prepare_common_svl_vals(move, product)
57+
vals.update({
58+
"value": float_round(unit_cost * quantity, precision_digits=precision_price),
59+
"unit_cost": unit_cost,
60+
"quantity": quantity,
61+
"remaining_qty": 0.0,
62+
"remaining_value": 0.0,
63+
})
64+
return vals
65+
66+
67+
def _prepare_man_svl_vals(price_history_rec, previous_price, quantity, company, product):
68+
diff = price_history_rec["cost"] - previous_price
69+
value = float_round(diff * quantity, precision_digits=precision_price)
70+
svl_vals = {
71+
"create_uid": price_history_rec["write_uid"],
72+
"create_date": price_history_rec["datetime"],
73+
"write_uid": price_history_rec["write_uid"],
74+
"write_date": price_history_rec["datetime"],
75+
"stock_move_id": None,
76+
"company_id": company.id,
77+
"product_id": product.id,
78+
"description": _("Product value manually modified (from %s to %s)"
79+
) % (previous_price, price_history_rec["cost"]),
80+
"value": value,
81+
"unit_cost": 0.0,
82+
"remaining_qty": 0.0,
83+
"remaining_value": 0.0,
84+
"quantity": 0.0,
85+
"old_product_price_history_id": price_history_rec["id"],
86+
"account_move_id": price_history_rec["account_move_id"],
87+
}
88+
return svl_vals
89+
90+
91+
def get_product_price_history(env, company_id, product_id):
92+
env.cr.execute("""
93+
WITH account_move_rel AS (
94+
SELECT id, create_date
95+
FROM (
96+
SELECT id, create_date, COUNT(*) OVER(PARTITION BY create_date) AS qty
97+
FROM account_move
98+
WHERE stock_move_id IS NULL
99+
) foo
100+
WHERE qty = 1
101+
)
102+
SELECT pph.id, pph.company_id, pph.product_id, pph.datetime, pph.cost, rel.id AS account_move_id,
103+
pph.create_uid, pph.create_date, pph.write_uid, pph.write_date
104+
FROM product_price_history pph
105+
LEFT JOIN account_move_rel rel ON rel.create_date = pph.create_date
106+
WHERE pph.company_id = %s AND pph.product_id = %s
107+
ORDER BY pph.datetime, pph.id
108+
""", (company_id, product_id))
109+
return env.cr.dictfetchall()
110+
111+
112+
def get_stock_moves(env, company_id, product_id):
113+
env.cr.execute("""
114+
WITH account_move_rel AS (
115+
SELECT id, stock_move_id
116+
FROM (
117+
SELECT id, stock_move_id, COUNT(*) OVER(PARTITION BY stock_move_id) AS qty
118+
FROM account_move
119+
WHERE stock_move_id IS NOT NULL
120+
) foo
121+
WHERE qty = 1
122+
)
123+
SELECT sm.id, sm.company_id, sm.product_id, sm.date, sm.product_qty, sm.reference,
124+
COALESCE(sm.price_unit, 0.0) AS price_unit, rel.id AS account_move_id,
125+
sm.create_uid, sm.create_date, sm.write_uid, sm.write_date,
126+
CASE WHEN (sl.usage <> 'internal' AND (sl.usage <> 'transit' OR sl.company_id <> sm.company_id))
127+
AND (sld.usage = 'internal' OR (sld.usage = 'transit' AND sld.company_id = sm.company_id))
128+
THEN 'in'
129+
WHEN (sl.usage = 'internal' OR (sl.usage = 'transit' AND sl.company_id = sm.company_id))
130+
AND (sld.usage <> 'internal' AND (sld.usage <> 'transit' OR sld.company_id <> sm.company_id))
131+
THEN 'out'
132+
WHEN sl.usage = 'supplier' AND sld.usage = 'customer' THEN 'dropship'
133+
WHEN sl.usage = 'customer' AND sld.usage = 'supplier' THEN 'dropship_return'
134+
ELSE 'other'
135+
END AS move_type
136+
FROM stock_move sm
137+
LEFT JOIN stock_location sl ON sl.id = sm.location_id
138+
LEFT JOIN stock_location sld ON sld.id = sm.location_dest_id
139+
LEFT JOIN account_move_rel rel ON rel.stock_move_id = sm.id
140+
WHERE sm.company_id = %s AND sm.product_id = %s AND state = 'done'
141+
ORDER BY sm.date, sm.id
142+
""", (company_id, product_id))
143+
return env.cr.dictfetchall()
144+
145+
146+
@openupgrade.logging()
147+
def generate_stock_valuation_layer(env):
148+
openupgrade.logged_query(
149+
env.cr, """
150+
ALTER TABLE stock_valuation_layer
151+
ADD COLUMN old_product_price_history_id integer""",
152+
)
153+
company_obj = env["res.company"]
154+
product_obj = env["product.product"]
155+
# Needed to modify global variable
156+
global precision_price
157+
precision_price = env["decimal.precision"].precision_get("Product Price")
158+
precision_uom = env["decimal.precision"].precision_get(
159+
"Product Unit of Measure"
160+
)
161+
companies = company_obj.search([])
162+
products = product_obj.with_context(active_test=False).search([("type", "in", ("product", "consu"))])
163+
all_svl_list = []
164+
for product in products:
165+
for company in companies:
166+
history_lines = get_product_price_history(env, company.id, product.id)
167+
moves = get_stock_moves(env, company.id, product.id)
168+
svl_in_vals_list = []
169+
svl_out_vals_list = []
170+
svl_man_vals_list = []
171+
svl_in_index = 0
172+
h_index = 0
173+
previous_price = 0.0
174+
previous_qty = 0.0
175+
for move in moves:
176+
is_dropship = True if move["move_type"] in ("dropship", "dropship_return") else False
177+
if product.cost_method in ("average", "standard"):
178+
# useless for Fifo because we have price unit in stock.move
179+
# Add manual adjusts
180+
have_qty = not float_is_zero(previous_qty, precision_digits=precision_uom)
181+
while h_index < len(history_lines) and history_lines[h_index]["datetime"] < move["date"]:
182+
price_history_rec = history_lines[h_index]
183+
if float_compare(price_history_rec["cost"], previous_price, precision_digits=precision_price):
184+
if have_qty:
185+
svl_vals = _prepare_man_svl_vals(
186+
price_history_rec, previous_price, previous_qty, company, product)
187+
svl_man_vals_list.append(svl_vals)
188+
previous_price = price_history_rec["cost"]
189+
h_index += 1
190+
# Add in svl
191+
if move["move_type"] == "in" or is_dropship:
192+
total_qty = previous_qty + move["product_qty"]
193+
# TODO: is needed vaccum if total_qty is negative?
194+
if float_is_zero(total_qty, precision_digits=precision_uom):
195+
previous_price = move["price_unit"]
196+
else:
197+
previous_price = float_round(
198+
(previous_price * previous_qty + move["price_unit"] * move["product_qty"]) / total_qty,
199+
precision_digits=precision_price)
200+
svl_vals = _prepare_in_svl_vals(
201+
move, move["product_qty"], move["price_unit"], product, is_dropship)
202+
svl_in_vals_list.append(svl_vals)
203+
previous_qty = total_qty
204+
# Add out svl
205+
if move["move_type"] == "out" or is_dropship:
206+
qty = move["product_qty"]
207+
if product.cost_method in ("average", "fifo") and not is_dropship:
208+
# Reduce remaininig qty in svl of type "in"
209+
while qty > 0 and svl_in_index < len(svl_in_vals_list):
210+
if svl_in_vals_list[svl_in_index]["remaining_qty"] >= qty:
211+
candidate_cost = (svl_in_vals_list[svl_in_index]["remaining_value"] /
212+
svl_in_vals_list[svl_in_index]["remaining_qty"])
213+
svl_in_vals_list[svl_in_index]["remaining_qty"] -= qty
214+
svl_in_vals_list[svl_in_index]["remaining_value"] = float_round(
215+
candidate_cost * svl_in_vals_list[svl_in_index]["remaining_qty"],
216+
precision_digits=precision_price)
217+
qty = 0
218+
else:
219+
qty -= svl_in_vals_list[svl_in_index]["remaining_qty"]
220+
svl_in_vals_list[svl_in_index]["remaining_qty"] = 0.0
221+
svl_in_vals_list[svl_in_index]["remaining_value"] = 0.0
222+
svl_in_index += 1
223+
if product.cost_method == 'fifo':
224+
svl_vals = _prepare_out_svl_vals(
225+
move, move["product_qty"], move["price_unit"], product)
226+
else:
227+
svl_vals = _prepare_out_svl_vals(
228+
move, move["product_qty"], previous_price, product)
229+
svl_out_vals_list.append(svl_vals)
230+
previous_qty -= move["product_qty"]
231+
# Add manual adjusts after last move
232+
if product.cost_method in ("average", "standard") and not float_is_zero(
233+
previous_qty, precision_digits=precision_uom):
234+
# useless for Fifo because we have price unit on product form
235+
while h_index < len(history_lines):
236+
price_history_rec = history_lines[h_index]
237+
if float_compare(price_history_rec["cost"], previous_price, precision_digits=precision_price):
238+
svl_vals = _prepare_man_svl_vals(
239+
price_history_rec, previous_price, previous_qty, company, product)
240+
svl_man_vals_list.append(svl_vals)
241+
previous_price = price_history_rec["cost"]
242+
h_index += 1
243+
all_svl_list.extend(svl_in_vals_list + svl_out_vals_list + svl_man_vals_list)
244+
if all_svl_list:
245+
all_svl_list = sorted(all_svl_list, key=lambda k: (k["create_date"]))
246+
_logger.info("To create {} svl records".format(len(all_svl_list)))
247+
query_insert(env.cr, "stock_valuation_layer", all_svl_list)
248+
249+
250+
@openupgrade.migrate()
251+
def migrate(env, version):
252+
generate_stock_valuation_layer(env)
253+
openupgrade.delete_records_safely_by_xml_id(
254+
env, [
255+
"stock_account.default_cost_method",
256+
"stock_account.default_valuation",
257+
"stock_account.property_stock_account_input_prd",
258+
"stock_account.property_stock_account_output_prd",
259+
]
260+
)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Copyright 2020 ForgeFlow <http://www.forgeflow.com>
2+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
3+
from openupgradelib import openupgrade
4+
5+
_xmlid_renames = [
6+
('stock_account.product_normal_form_view_inherit',
7+
'stock_account.product_product_normal_form_view_inherit'),
8+
]
9+
10+
11+
@openupgrade.migrate()
12+
def migrate(env, version):
13+
openupgrade.rename_xmlids(env.cr, _xmlid_renames)

odoo/openupgrade/doc/source/modules120-130.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -597,7 +597,7 @@ missing in the new release are marked with |del|.
597597
+----------------------------------------------+-------------------------------------------------+
598598
|stock | Done |
599599
+----------------------------------------------+-------------------------------------------------+
600-
|stock_account | |
600+
|stock_account | Done |
601601
+----------------------------------------------+-------------------------------------------------+
602602
|stock_dropshipping | |
603603
+----------------------------------------------+-------------------------------------------------+

0 commit comments

Comments
 (0)