From 12a978dd7d90f9e95724726d6bbb683a0921bf44 Mon Sep 17 00:00:00 2001 From: Raman Marikanti Date: Thu, 8 Jan 2026 16:53:46 +0530 Subject: [PATCH] set force move date --- custom_addons/stock_force_date/__init__.py | 5 + .../stock_force_date/__manifest__.py | 17 ++ .../stock_force_date/models/__init__.py | 3 + .../models/stock_inventory.py | 163 ++++++++++++++++++ .../security/stock_force_security.xml | 9 + .../views/stock_inventory.xml | 29 ++++ 6 files changed, 226 insertions(+) create mode 100755 custom_addons/stock_force_date/__init__.py create mode 100755 custom_addons/stock_force_date/__manifest__.py create mode 100755 custom_addons/stock_force_date/models/__init__.py create mode 100755 custom_addons/stock_force_date/models/stock_inventory.py create mode 100755 custom_addons/stock_force_date/security/stock_force_security.xml create mode 100755 custom_addons/stock_force_date/views/stock_inventory.xml diff --git a/custom_addons/stock_force_date/__init__.py b/custom_addons/stock_force_date/__init__.py new file mode 100755 index 000000000..fa7ea9f7f --- /dev/null +++ b/custom_addons/stock_force_date/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- + +from . import models + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/custom_addons/stock_force_date/__manifest__.py b/custom_addons/stock_force_date/__manifest__.py new file mode 100755 index 000000000..b21d238a1 --- /dev/null +++ b/custom_addons/stock_force_date/__manifest__.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +{ + 'name': 'Force date in Stock Transfer and Inventory Adjustment', + "author": "Edge Technologies", + 'summary': "Stock Force Date Inventory force date Inventory Adjustment force date Stock Transfer force date stock picking force date receipt force date shipment force date delivery force date in stock backdate stock back date inventory back date receipt back date", + 'description': """ + This Odoo module will helps you to allow stock force date in picking operations and inventory adjustment. auto pass stock force date in stock move when validate picking operations and inventory adjustment. + """, + 'depends': ['stock','purchase','purchase_stock','stock_account'], + 'data': [ + 'security/stock_force_security.xml', + 'views/stock_inventory.xml', + ], + 'installable': True, + 'auto_install': False, + 'category': 'Warehouse', +} diff --git a/custom_addons/stock_force_date/models/__init__.py b/custom_addons/stock_force_date/models/__init__.py new file mode 100755 index 000000000..f89493181 --- /dev/null +++ b/custom_addons/stock_force_date/models/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import stock_inventory \ No newline at end of file diff --git a/custom_addons/stock_force_date/models/stock_inventory.py b/custom_addons/stock_force_date/models/stock_inventory.py new file mode 100755 index 000000000..cf67688dd --- /dev/null +++ b/custom_addons/stock_force_date/models/stock_inventory.py @@ -0,0 +1,163 @@ +# -*- coding: utf-8 -*- + +import time +from odoo import api, fields, models, _ +from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT +from odoo.tools.float_utils import float_compare, float_is_zero +from odoo.exceptions import UserError, ValidationError + + +class StockQuant(models.Model): + _inherit = 'stock.quant' + + force_date = fields.Datetime(string="Force Date") + + + @api.model + def _get_inventory_fields_create(self): + """ Returns a list of fields user can edit when he want to create a quant in `inventory_mode`. + """ + return ['product_id', 'location_id', 'lot_id', 'package_id', 'owner_id','force_date'] + self._get_inventory_fields_write() + + @api.model + def _get_inventory_fields_write(self): + """ Returns a list of fields user can edit when he want to edit a quant in `inventory_mode`. + """ + fields = ['inventory_quantity', 'inventory_quantity_auto_apply', 'inventory_diff_quantity', + 'inventory_date', 'user_id', 'inventory_quantity_set', 'is_outdated', 'force_date'] + return fields + + @api.model + def create(self, vals): + """ Override to handle the "inventory mode" and create a quant as + superuser the conditions are met. + """ + if self._is_inventory_mode() and any(f in vals for f in ['inventory_quantity', 'inventory_quantity_auto_apply']): + allowed_fields = self._get_inventory_fields_create() + if not self.env.user.has_group('stock_force_date.group_stock_force_date'): + if any(field for field in vals.keys() if field not in allowed_fields): + raise UserError(_("Quant's creation is restricted, you can't do this operation.")) + + inventory_quantity = vals.pop('inventory_quantity', False) or vals.pop( + 'inventory_quantity_auto_apply', False) or 0 + # Create an empty quant or write on a similar one. + product = self.env['product.product'].browse(vals['product_id']) + location = self.env['stock.location'].browse(vals['location_id']) + lot_id = self.env['stock.production.lot'].browse(vals.get('lot_id')) + package_id = self.env['stock.quant.package'].browse(vals.get('package_id')) + owner_id = self.env['res.partner'].browse(vals.get('owner_id')) + quant = self._gather(product, location, lot_id=lot_id, package_id=package_id, owner_id=owner_id, strict=True) + + if quant: + quant = quant[0].sudo() + else: + quant = self.sudo().create(vals) + # Set the `inventory_quantity` field to create the necessary move. + quant.inventory_quantity = inventory_quantity + quant.user_id = vals.get('user_id', self.env.user.id) + quant.inventory_date = fields.Date.today() + + return quant + res = super(StockQuant, self).create(vals) + if self._is_inventory_mode(): + res._check_company() + return res + + def _get_inventory_move_values(self, qty, location_id, location_dest_id): + res = super(StockQuant, self)._get_inventory_move_values(qty, location_id, location_dest_id) + if res: + if self.force_date: + res.update({'date': self.force_date}) + else: + res.update({'date': self.in_date}) + return res + + + def _apply_inventory(self): + move_vals = [] + if not self.env.user.has_group('stock.group_stock_manager'): + raise UserError(_('Only a stock manager can validate an inventory adjustment.')) + for quant in self: + # Create and validate a move so that the quant matches its `inventory_quantity`. + if float_compare(quant.inventory_diff_quantity, 0, precision_rounding=quant.product_uom_id.rounding) > 0: + _get_inventory_move_values(qty, location_id, location_dest_id, package_id, package_dest_id) + move_vals.append( + quant._get_inventory_move_values(quant.inventory_diff_quantity, + quant.product_id.with_company(quant.company_id).property_stock_inventory, + quant.location_id)) + else: + move_vals.append( + quant._get_inventory_move_values(-quant.inventory_diff_quantity, + quant.location_id, + quant.product_id.with_company(quant.company_id).property_stock_inventory + )) + moves = self.env['stock.move'].with_context(inventory_mode=False).create(move_vals) + if self.env.user.has_group('stock_force_date.group_stock_force_date'): + for quant in self: + moves = self.env['stock.move'].with_context(inventory_mode=False, force_date=quant.force_date).create(move_vals) + moves._action_done() + self.location_id.write({'last_inventory_date': fields.Date.today()}) + date_by_location = {loc: loc._get_next_inventory_date() for loc in self.mapped('location_id')} + for quant in self: + quant.inventory_date = date_by_location[quant.location_id] + self.write({'inventory_quantity': 0, 'user_id': False}) + self.write({'inventory_diff_quantity': 0}) + + +class StockPicking(models.Model): + _inherit = 'stock.picking' + + force_date = fields.Datetime(string="Force Date") + + +class StockMove(models.Model): + _inherit = 'stock.move' + + def _action_done(self, cancel_backorder=False): + force_date = time.strftime(DEFAULT_SERVER_DATETIME_FORMAT) + if self.env.user.has_group('stock_force_date.group_stock_force_date'): + for move in self: + if self._context.get('force_date'): + force_date = self._context.get('force_date') + if move.picking_id: + if move.picking_id.force_date: + force_date = move.picking_id.force_date + else: + force_date = move.picking_id.scheduled_date + res = super(StockMove, self)._action_done(cancel_backorder=cancel_backorder) + if self.env.user.has_group('stock_force_date.group_stock_force_date'): + if force_date: + for move in res: + move.write({'date':force_date}) + if move.move_line_ids: + for move_line in move.move_line_ids: + move_line.write({'date':force_date}) + if move.account_move_ids: + for account_move in move.account_move_ids: + self.env.cr.execute( + "UPDATE account_move SET date = %s WHERE id = %s", + [force_date, account_move.id]) + # account_move.write({'date':force_date}) + return res + + + def _create_account_move_line(self, credit_account_id, debit_account_id, journal_id, qty, description, svl_id, cost): + self.ensure_one() + AccountMove = self.env['account.move'].with_context(default_journal_id=journal_id) + + move_lines = self._prepare_account_move_line(qty, cost, credit_account_id, debit_account_id, description) + if move_lines: + date = self._context.get('force_period_date', fields.Date.context_today(self)) + if self.env.user.has_group('stock_force_date.group_stock_force_date'): + if self.picking_id.force_date: + date = self.picking_id.force_date.date() + new_account_move = AccountMove.sudo().create({ + 'journal_id': journal_id, + 'line_ids': move_lines, + 'date': date, + 'ref': description, + 'stock_move_id': self.id, + 'stock_valuation_layer_ids': [(6, None, [svl_id])], + 'move_type': 'entry', + }) + new_account_move._post() \ No newline at end of file diff --git a/custom_addons/stock_force_date/security/stock_force_security.xml b/custom_addons/stock_force_date/security/stock_force_security.xml new file mode 100755 index 000000000..6ec38bc2c --- /dev/null +++ b/custom_addons/stock_force_date/security/stock_force_security.xml @@ -0,0 +1,9 @@ + + + + + Allow Stock Force Date (Inventory) + + + + diff --git a/custom_addons/stock_force_date/views/stock_inventory.xml b/custom_addons/stock_force_date/views/stock_inventory.xml new file mode 100755 index 000000000..17fb0e729 --- /dev/null +++ b/custom_addons/stock_force_date/views/stock_inventory.xml @@ -0,0 +1,29 @@ + + + + + + + stock.quant.view.form + stock.picking + + + + + + + + + + stock.quant.inventory.tree.editable.extended.stock.account + stock.quant + + + + + + + + + + \ No newline at end of file