From 15d00bd957fa427d06b1ac28ae7e5ac9a6d6be11 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Wed, 8 Apr 2015 19:20:24 +0200 Subject: [PATCH] [FIX] stock_landed_cost: allow reversing valuation via negative quantity + safer cancel/unlink - Support negative costs by creating reverse valuation move, and undoing the quant computation. This may or may not produce the exact reverse effect on the quant, as it depends on the rounding of the unit cost, which uses decimal precision 'Account' - Hide 'compute' button when cost is not draft - Forbid cancel of posted cost - Forbid deletion of cost if not draft --- .../stock_landed_costs/stock_landed_costs.py | 76 +++++++++++-------- .../stock_landed_costs_view.xml | 2 +- 2 files changed, 46 insertions(+), 32 deletions(-) diff --git a/addons/stock_landed_costs/stock_landed_costs.py b/addons/stock_landed_costs/stock_landed_costs.py index a2b05640bde..a05a548fe87 100644 --- a/addons/stock_landed_costs/stock_landed_costs.py +++ b/addons/stock_landed_costs/stock_landed_costs.py @@ -21,6 +21,7 @@ from openerp.osv import fields, osv import openerp.addons.decimal_precision as dp +from openerp.exceptions import Warning from openerp.tools import float_compare from openerp.tools.translate import _ import product @@ -90,7 +91,7 @@ class stock_landed_cost(osv.osv): ), 'state': fields.selection([('draft', 'Draft'), ('done', 'Posted'), ('cancel', 'Cancelled')], 'State', readonly=True, track_visibility='onchange', copy=False), 'account_move_id': fields.many2one('account.move', 'Journal Entry', readonly=True, copy=False), - 'account_journal_id': fields.many2one('account.journal', 'Account Journal', required=True), + 'account_journal_id': fields.many2one('account.journal', 'Account Journal', required=True, states={'done': [('readonly', True)]}), } _defaults = { @@ -120,41 +121,43 @@ class stock_landed_cost(osv.osv): Afterwards, for the goods that are already out of stock, we should create the out moves """ aml_obj = self.pool.get('account.move.line') - aml_obj.create(cr, uid, { + base_line = { 'name': line.name, 'move_id': move_id, 'product_id': line.product_id.id, 'quantity': line.quantity, - 'debit': line.additional_landed_cost, - 'account_id': debit_account_id - }, context=context) - aml_obj.create(cr, uid, { - 'name': line.name, - 'move_id': move_id, - 'product_id': line.product_id.id, - 'quantity': line.quantity, - 'credit': line.additional_landed_cost, - 'account_id': credit_account_id - }, context=context) + } + debit_line = dict(base_line, account_id=debit_account_id) + credit_line = dict(base_line, account_id=credit_account_id) + diff = line.additional_landed_cost + if diff > 0: + debit_line['debit'] = diff + credit_line['credit'] = diff + else: + # negative cost, reverse the entry + debit_line['credit'] = -diff + credit_line['debit'] = -diff + aml_obj.create(cr, uid, debit_line, context=context) + aml_obj.create(cr, uid, credit_line, context=context) #Create account move lines for quants already out of stock if qty_out > 0: - aml_obj.create(cr, uid, { - 'name': line.name + ": " + str(qty_out) + _(' already out'), - 'move_id': move_id, - 'product_id': line.product_id.id, - 'quantity': qty_out, - 'credit': line.additional_landed_cost * qty_out / line.quantity, - 'account_id': debit_account_id - }, context=context) - aml_obj.create(cr, uid, { - 'name': line.name + ": " + str(qty_out) + _(' already out'), - 'move_id': move_id, - 'product_id': line.product_id.id, - 'quantity': qty_out, - 'debit': line.additional_landed_cost * qty_out / line.quantity, - 'account_id': already_out_account_id - }, context=context) + debit_line = dict(debit_line, + name=(line.name + ": " + str(qty_out) + _(' already out')), + quantity=qty_out) + credit_line = dict(credit_line, + name=(line.name + ": " + str(qty_out) + _(' already out')), + quantity=qty_out) + diff = diff * qty_out / line.quantity + if diff > 0: + debit_line['debit'] = diff + credit_line['credit'] = diff + else: + # negative cost, reverse the entry + debit_line['credit'] = -diff + credit_line['debit'] = -diff + aml_obj.create(cr, uid, debit_line, context=context) + aml_obj.create(cr, uid, credit_line, context=context) return True def _create_account_move(self, cr, uid, cost, context=None): @@ -192,6 +195,8 @@ class stock_landed_cost(osv.osv): quant_obj = self.pool.get('stock.quant') for cost in self.browse(cr, uid, ids, context=context): + if cost.state != 'draft': + raise Warning(_('Only draft landed costs can be validated')) if not cost.valuation_adjustment_lines or not self._check_sum(cr, uid, cost, context=context): raise osv.except_osv(_('Error!'), _('You cannot validate a landed cost which has no valid valuation lines.')) move_id = self._create_account_move(cr, uid, cost, context=context) @@ -208,6 +213,7 @@ class stock_landed_cost(osv.osv): else: quant_dict[quant.id] += diff for key, value in quant_dict.items(): + print value quant_obj.write(cr, uid, key, {'cost': value}, context=context) qty_out = 0 for quant in line.move_id.quant_ids: @@ -218,8 +224,16 @@ class stock_landed_cost(osv.osv): return True def button_cancel(self, cr, uid, ids, context=None): - self.write(cr, uid, ids, {'state': 'cancel'}, context=context) - return True + cost = self.browse(cr, uid, ids, context=context) + if cost.state == 'done': + raise Warning(_('Validated landed costs cannot be cancelled, ' + 'but you could create negative landed costs to reverse them')) + return cost.write({'state': 'cancel'}) + + def unlink(self, cr, uid, ids, context=None): + # cancel or raise first + self.button_cancel(cr, uid, ids, context) + return super(stock_landed_cost, self).unlink(cr, uid, ids, context=context) def compute_landed_cost(self, cr, uid, ids, context=None): line_obj = self.pool.get('stock.valuation.adjustment.lines') diff --git a/addons/stock_landed_costs/stock_landed_costs_view.xml b/addons/stock_landed_costs/stock_landed_costs_view.xml index 4bbeaddc78b..5c41d96196d 100644 --- a/addons/stock_landed_costs/stock_landed_costs_view.xml +++ b/addons/stock_landed_costs/stock_landed_costs_view.xml @@ -66,7 +66,7 @@ - +