first commit
This commit is contained in:
commit
66770bc471
1
__init__.py
Normal file
1
__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from . import models
|
||||
14
__manifest__.py
Normal file
14
__manifest__.py
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "Stock Inventory Backdate",
|
||||
"summary": "Allow backdating of physical stock adjustments and valuations.",
|
||||
"version": "17.0.1.0.0",
|
||||
"category": "Warehouse",
|
||||
"author": "Antigravity",
|
||||
"license": "AGPL-3",
|
||||
"depends": ["stock_account"],
|
||||
"data": [
|
||||
"security/ir.model.access.csv",
|
||||
"views/stock_quant_views.xml",
|
||||
],
|
||||
"installable": True,
|
||||
}
|
||||
BIN
__pycache__/__init__.cpython-312.pyc
Normal file
BIN
__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
2
models/__init__.py
Normal file
2
models/__init__.py
Normal file
@ -0,0 +1,2 @@
|
||||
from . import stock_quant
|
||||
from . import stock_move
|
||||
BIN
models/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
models/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
models/__pycache__/stock_move.cpython-312.pyc
Normal file
BIN
models/__pycache__/stock_move.cpython-312.pyc
Normal file
Binary file not shown.
BIN
models/__pycache__/stock_quant.cpython-312.pyc
Normal file
BIN
models/__pycache__/stock_quant.cpython-312.pyc
Normal file
Binary file not shown.
81
models/stock_move.py
Normal file
81
models/stock_move.py
Normal file
@ -0,0 +1,81 @@
|
||||
from odoo import api, models, fields
|
||||
from odoo.tools import float_round
|
||||
|
||||
class StockMove(models.Model):
|
||||
_inherit = 'stock.move'
|
||||
|
||||
def _action_done(self, cancel_backorder=False):
|
||||
moves = super(StockMove, self)._action_done(cancel_backorder=cancel_backorder)
|
||||
|
||||
forced_inventory_date = self.env.context.get('force_inventory_date')
|
||||
if forced_inventory_date:
|
||||
for move in moves:
|
||||
move.write({'date': forced_inventory_date})
|
||||
# If valuation is real-time, we might need to adjust the account move date too.
|
||||
# But account move creation usually happens in _action_done -> _create_account_move_line
|
||||
# which might use the move date.
|
||||
# Let's check if we need to update account moves.
|
||||
if move.account_move_ids:
|
||||
move.account_move_ids.write({'date': forced_inventory_date})
|
||||
|
||||
return moves
|
||||
|
||||
def _create_account_move_line(self, credit_account_id, debit_account_id, journal_id, qty, description, svl_id, cost):
|
||||
# Override to force date on account move creation if needed.
|
||||
# However, if we update the move date in _action_done, it might be too late for this method
|
||||
# if it's called during super()._action_done().
|
||||
# So we might need to rely on context here too.
|
||||
|
||||
forced_inventory_date = self.env.context.get('force_inventory_date')
|
||||
forced_valuation_date = self.env.context.get('force_valuation_date')
|
||||
|
||||
# Use valuation date if present, otherwise inventory date
|
||||
target_date = forced_valuation_date or forced_inventory_date
|
||||
|
||||
if target_date:
|
||||
# We can't easily change the arguments passed to this method without signature change,
|
||||
# but we can patch the context or check if we can modify the created move later.
|
||||
# Actually, this method creates 'account.move'.
|
||||
# Let's see if we can intercept the creation.
|
||||
pass
|
||||
|
||||
return super(StockMove, self)._create_account_move_line(credit_account_id, debit_account_id, journal_id, qty, description, svl_id, cost)
|
||||
|
||||
def _prepare_account_move_vals(self, credit_account_id, debit_account_id, journal_id, qty, description, svl_id, cost):
|
||||
# This method prepares the values for account.move.create.
|
||||
vals = super(StockMove, self)._prepare_account_move_vals(credit_account_id, debit_account_id, journal_id, qty, description, svl_id, cost)
|
||||
|
||||
forced_inventory_date = self.env.context.get('force_inventory_date')
|
||||
forced_valuation_date = self.env.context.get('force_valuation_date')
|
||||
target_date = forced_valuation_date or forced_inventory_date
|
||||
|
||||
if target_date:
|
||||
vals['date'] = target_date
|
||||
|
||||
return vals
|
||||
|
||||
def _create_in_svl(self, forced_quantity=None):
|
||||
# Override to force date on stock valuation layer
|
||||
svl = super(StockMove, self)._create_in_svl(forced_quantity=forced_quantity)
|
||||
self._update_svl_date(svl)
|
||||
return svl
|
||||
|
||||
def _create_out_svl(self, forced_quantity=None):
|
||||
# Override to force date on stock valuation layer
|
||||
svl = super(StockMove, self)._create_out_svl(forced_quantity=forced_quantity)
|
||||
self._update_svl_date(svl)
|
||||
return svl
|
||||
|
||||
def _update_svl_date(self, svl):
|
||||
forced_inventory_date = self.env.context.get('force_inventory_date')
|
||||
forced_valuation_date = self.env.context.get('force_valuation_date')
|
||||
target_date = forced_valuation_date or forced_inventory_date
|
||||
|
||||
if target_date and svl:
|
||||
# create_date is a magic field, we need to update it via SQL
|
||||
self.env.cr.execute(
|
||||
"UPDATE stock_valuation_layer SET create_date = %s WHERE id IN %s",
|
||||
(target_date, tuple(svl.ids))
|
||||
)
|
||||
svl.invalidate_recordset(['create_date'])
|
||||
|
||||
53
models/stock_quant.py
Normal file
53
models/stock_quant.py
Normal file
@ -0,0 +1,53 @@
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
class StockQuant(models.Model):
|
||||
_inherit = 'stock.quant'
|
||||
|
||||
force_inventory_date = fields.Date(
|
||||
string="Force Inventory Date",
|
||||
help="Choose a specific date for the inventory adjustment. "
|
||||
"If set, the stock move will be created with this date."
|
||||
)
|
||||
force_valuation_date = fields.Date(
|
||||
string="Force Valuation Date",
|
||||
help="Choose a specific date for the stock valuation. "
|
||||
"If set, the valuation layer will be created with this date."
|
||||
)
|
||||
|
||||
@api.model
|
||||
def _get_inventory_fields_create(self):
|
||||
""" Allow the new fields to be set during inventory creation """
|
||||
res = super(StockQuant, self)._get_inventory_fields_create()
|
||||
res += ['force_inventory_date', 'force_valuation_date']
|
||||
return res
|
||||
|
||||
@api.model
|
||||
def _get_inventory_fields_write(self):
|
||||
""" Allow the new fields to be set during inventory write """
|
||||
res = super(StockQuant, self)._get_inventory_fields_write()
|
||||
res += ['force_inventory_date', 'force_valuation_date']
|
||||
return res
|
||||
|
||||
def _apply_inventory(self):
|
||||
"""Override to pass forced dates to the context"""
|
||||
# We need to handle quants with different forced dates separately
|
||||
# Group by (force_inventory_date, force_valuation_date)
|
||||
# If no forced date, key is (False, False)
|
||||
|
||||
grouped_quants = {}
|
||||
for quant in self:
|
||||
key = (quant.force_inventory_date, quant.force_valuation_date)
|
||||
if key not in grouped_quants:
|
||||
grouped_quants[key] = self.env['stock.quant']
|
||||
grouped_quants[key] |= quant
|
||||
|
||||
for (force_inventory_date, force_valuation_date), quants in grouped_quants.items():
|
||||
ctx = dict(self.env.context)
|
||||
if force_inventory_date:
|
||||
ctx['force_inventory_date'] = force_inventory_date
|
||||
if force_valuation_date:
|
||||
ctx['force_valuation_date'] = force_valuation_date
|
||||
|
||||
super(StockQuant, quants.with_context(ctx))._apply_inventory()
|
||||
|
||||
2
security/ir.model.access.csv
Normal file
2
security/ir.model.access.csv
Normal file
@ -0,0 +1,2 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_stock_quant_backdate,stock.quant.backdate,stock.model_stock_quant,stock.group_stock_user,1,1,1,1
|
||||
|
1
tests/__init__.py
Normal file
1
tests/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from . import test_stock_backdate
|
||||
59
tests/test_stock_backdate.py
Normal file
59
tests/test_stock_backdate.py
Normal file
@ -0,0 +1,59 @@
|
||||
from odoo.tests.common import TransactionCase
|
||||
from odoo import fields
|
||||
from datetime import timedelta
|
||||
|
||||
class TestStockBackdate(TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestStockBackdate, self).setUp()
|
||||
self.product = self.env['product.product'].create({
|
||||
'name': 'Test Product Backdate',
|
||||
'type': 'product',
|
||||
'categ_id': self.env.ref('product.product_category_all').id,
|
||||
})
|
||||
# Enable automated valuation for the category if needed,
|
||||
# but for simplicity we test the move date primarily.
|
||||
self.product.categ_id.property_valuation = 'real_time'
|
||||
self.product.categ_id.property_cost_method = 'average'
|
||||
|
||||
self.stock_location = self.env.ref('stock.stock_location_stock')
|
||||
|
||||
def test_inventory_backdate(self):
|
||||
"""Test that inventory adjustment backdating works"""
|
||||
backdate = fields.Date.today() - timedelta(days=10)
|
||||
|
||||
quant = self.env['stock.quant'].create({
|
||||
'product_id': self.product.id,
|
||||
'location_id': self.stock_location.id,
|
||||
'inventory_quantity': 100,
|
||||
})
|
||||
|
||||
# Set forced dates
|
||||
quant.force_inventory_date = backdate
|
||||
quant.force_valuation_date = backdate
|
||||
|
||||
# Apply inventory
|
||||
quant.action_apply_inventory()
|
||||
|
||||
# Check stock move date
|
||||
move = self.env['stock.move'].search([
|
||||
('product_id', '=', self.product.id),
|
||||
('is_inventory', '=', True)
|
||||
], limit=1)
|
||||
|
||||
self.assertTrue(move, "Stock move should be created")
|
||||
self.assertEqual(move.date.date(), backdate, "Stock move date should be backdated")
|
||||
|
||||
# Check account move date if exists
|
||||
if move.account_move_ids:
|
||||
self.assertEqual(move.account_move_ids[0].date, backdate, "Account move date should be backdated")
|
||||
|
||||
# Check valuation layer date (create_date)
|
||||
svl = self.env['stock.valuation.layer'].search([
|
||||
('stock_move_id', '=', move.id)
|
||||
], limit=1)
|
||||
|
||||
if svl:
|
||||
# create_date is datetime, backdate is date.
|
||||
# We check if the date part matches.
|
||||
self.assertEqual(svl.create_date.date(), backdate, "SVL create_date should be backdated")
|
||||
14
views/stock_quant_views.xml
Normal file
14
views/stock_quant_views.xml
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="view_stock_quant_tree_inventory_editable_inherit_backdate" model="ir.ui.view">
|
||||
<field name="name">stock.quant.inventory.tree.editable.inherit.backdate</field>
|
||||
<field name="model">stock.quant</field>
|
||||
<field name="inherit_id" ref="stock.view_stock_quant_tree_inventory_editable"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='inventory_date']" position="after">
|
||||
<field name="force_inventory_date" optional="show"/>
|
||||
<field name="force_valuation_date" optional="hide"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
Loading…
Reference in New Issue
Block a user