~numerigraphe-team/stock-logistic-warehouse/7.0-add-stock-valuation-history

« back to all changes in this revision

Viewing changes to stock_lot_valuation/stock.py

  • Committer: Joel Grand-Guillaume
  • Author(s): lorenzo.battistini at agilebg
  • Date: 2013-11-01 09:40:45 UTC
  • mfrom: (29.3.35 adding_stock_lot_costing)
  • Revision ID: joel.grandguillaume@camptocamp.com-20131101094045-2uac738fcfgfopb7
[MRG][ADD] stock_lot_valuation : Stock valuation (standard or average price, ...) based on lots.
This module extends standard stock valuation (based on products). Valuing lots allows to have different costs for different lots of the same product.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- coding: utf-8 -*-
 
2
##############################################################################
 
3
#
 
4
#    Copyright (C) 2013 Agile Business Group sagl (<http://www.agilebg.com>)
 
5
#
 
6
#    This program is free software: you can redistribute it and/or modify
 
7
#    it under the terms of the GNU Affero General Public License as published
 
8
#    by the Free Software Foundation, either version 3 of the License, or
 
9
#    (at your option) any later version.
 
10
#
 
11
#    This program is distributed in the hope that it will be useful,
 
12
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
 
13
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
14
#    GNU Affero General Public License for more details.
 
15
#
 
16
#    You should have received a copy of the GNU Affero General Public License
 
17
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
18
#
 
19
##############################################################################
 
20
 
 
21
from openerp.osv import fields, orm
 
22
from openerp.tools.translate import _
 
23
import openerp.addons.decimal_precision as dp
 
24
 
 
25
 
 
26
class stock_production_lot(orm.Model):
 
27
    _inherit = "stock.production.lot"
 
28
 
 
29
    _columns = {
 
30
        'standard_price': fields.float(
 
31
            'Cost', digits_compute=dp.get_precision('Lot Price'),
 
32
            help="Cost price (in company currency) of the lot used for "
 
33
            "standard stock valuation in accounting.",
 
34
            groups="base.group_user"
 
35
        ),
 
36
        'cost_method': fields.selection(
 
37
            [
 
38
                ('standard', 'Standard Price'),
 
39
                ('average', 'Average Price')
 
40
            ], 'Costing Method',
 
41
            help="Standard Price: The cost price is manually updated at the "
 
42
            "end of a specific period. \nAverage Price: The cost price is "
 
43
            "recomputed at each incoming shipment."
 
44
        ),
 
45
    }
 
46
 
 
47
    def price_get(self, cr, uid, ids, context=None):
 
48
        if context is None:
 
49
            context = {}
 
50
        res = {}
 
51
        product_uom_obj = self.pool.get('product.uom')
 
52
        for lot in self.browse(cr, uid, ids, context=context):
 
53
            res[lot.id] = lot['standard_price'] or 0.0
 
54
            if 'uom' in context:
 
55
                uom = lot.product_id.uom_id or lot.product_id.uos_id
 
56
                res[lot.id] = product_uom_obj._compute_price(
 
57
                    cr, uid,
 
58
                    uom.id, res[lot.id], context['uom'])
 
59
            # Convert from price_type currency to asked one
 
60
            if 'currency_id' in context:
 
61
                currency_id = False
 
62
                if lot.company_id and lot.company_id.currency_id:
 
63
                    currency_id = lot.company_id.currency_id.id
 
64
                elif (
 
65
                    lot.product_id.company_id
 
66
                    and lot.product_id.company_id.currency_id
 
67
                ):
 
68
                    currency_id = lot.product_id.company_id.currency_id.id
 
69
                if currency_id:
 
70
                    res[lot.id] = self.pool.get('res.currency').compute(
 
71
                        cr, uid,
 
72
                        currency_id,
 
73
                        context['currency_id'], res[lot.id], context=context)
 
74
        return res
 
75
 
 
76
    def do_change_standard_price(self, cr, uid, ids, datas, context=None):
 
77
        """ Changes the Standard Price of Lot and creates an account move
 
78
        accordingly.
 
79
        @param datas : dict. contain default datas like new_price,
 
80
        stock_output_account, stock_input_account, stock_journal
 
81
        @param context: A standard dictionary
 
82
        """
 
83
        location_obj = self.pool.get('stock.location')
 
84
        move_obj = self.pool.get('account.move')
 
85
        move_line_obj = self.pool.get('account.move.line')
 
86
        if context is None:
 
87
            context = {}
 
88
 
 
89
        new_price = datas.get('new_price', 0.0)
 
90
        stock_output_acc = datas.get('stock_output_account', False)
 
91
        stock_input_acc = datas.get('stock_input_account', False)
 
92
        journal_id = datas.get('stock_journal', False)
 
93
        lot_obj = self.browse(cr, uid, ids, context=context)[0]
 
94
        account_valuation = (
 
95
            lot_obj.product_id.categ_id.property_stock_valuation_account_id)
 
96
        account_valuation_id = (
 
97
            account_valuation and account_valuation.id or False)
 
98
        if not account_valuation_id:
 
99
            raise orm.except_orm(
 
100
                _('Error!'),
 
101
                _('Specify valuation Account for Product Category: %s.')
 
102
                % (lot_obj.product_id.categ_id.name))
 
103
        move_ids = []
 
104
        loc_ids = location_obj.search(
 
105
            cr, uid, [('usage', '=', 'internal')],
 
106
            context=context)
 
107
        for rec_id in ids:
 
108
            for location in location_obj.browse(
 
109
                cr, uid, loc_ids, context=context
 
110
            ):
 
111
                c = context.copy()
 
112
                c.update({
 
113
                    'location_id': location.id,
 
114
                    'compute_child': False
 
115
                })
 
116
 
 
117
                lot = self.browse(cr, uid, rec_id, context=c)
 
118
                qty = lot.stock_available
 
119
                diff = lot.standard_price - new_price
 
120
                if not diff:
 
121
                    raise orm.except_orm(
 
122
                        _('Error!'),
 
123
                        _("No difference between standard price!"
 
124
                            " and new price"))
 
125
                if qty:
 
126
                    company_id = (
 
127
                        location.company_id and location.company_id.id or False
 
128
                    )
 
129
                    if not company_id:
 
130
                        raise orm.except_orm(
 
131
                            _('Error!'),
 
132
                            _('Please specify company in Location.'))
 
133
                    #
 
134
                    # Accounting Entries
 
135
                    #
 
136
                    product = lot.product_id
 
137
                    if (
 
138
                        not journal_id
 
139
                        and product.categ_id.property_stock_journal
 
140
                    ):
 
141
                        journal_id = product.categ_id.property_stock_journal.id
 
142
                    if not journal_id:
 
143
                        raise orm.except_orm(
 
144
                            _('Error!'),
 
145
                            _("Please define journal "
 
146
                                "on the product category: '%s' (id: %d).") %
 
147
                            (product.categ_id.name, product.categ_id.id,))
 
148
                    move_id = move_obj.create(cr, uid, {
 
149
                        'journal_id': journal_id,
 
150
                        'company_id': company_id
 
151
                    }, context=context)
 
152
 
 
153
                    move_ids.append(move_id)
 
154
 
 
155
                    if diff > 0:
 
156
                        if not stock_input_acc:
 
157
                            stock_input_acc = (
 
158
                                product.property_stock_account_input.id
 
159
                            )
 
160
                        if not stock_input_acc:
 
161
                            stock_input_acc = (
 
162
                                product.categ_id.
 
163
                                property_stock_account_input_categ.id
 
164
                            )
 
165
                        if not stock_input_acc:
 
166
                            raise orm.except_orm(
 
167
                                _('Error!'),
 
168
                                _("Please define stock input account "
 
169
                                    "for this product: '%s' (id: %d).") %
 
170
                                (product.name, product.id,))
 
171
                        amount_diff = qty * diff
 
172
                        move_line_obj.create(cr, uid, {
 
173
                            'name': product.name,
 
174
                            'account_id': stock_input_acc,
 
175
                            'debit': amount_diff,
 
176
                            'move_id': move_id,
 
177
                        }, context=context)
 
178
                        move_line_obj.create(cr, uid, {
 
179
                            'name': product.categ_id.name,
 
180
                            'account_id': account_valuation_id,
 
181
                            'credit': amount_diff,
 
182
                            'move_id': move_id
 
183
                        }, context=context)
 
184
                    elif diff < 0:
 
185
                        if not stock_output_acc:
 
186
                            stock_output_acc = (
 
187
                                product.property_stock_account_output.id
 
188
                            )
 
189
                        if not stock_output_acc:
 
190
                            stock_output_acc = (
 
191
                                product.categ_id.
 
192
                                property_stock_account_output_categ.id
 
193
                            )
 
194
                        if not stock_output_acc:
 
195
                            raise orm.except_orm(
 
196
                                _('Error!'),
 
197
                                _("Please define stock output account "
 
198
                                    "for this product: '%s' (id: %d).") %
 
199
                                (product.name, product.id,))
 
200
                        amount_diff = qty * -diff
 
201
                        move_line_obj.create(cr, uid, {
 
202
                            'name': product.name,
 
203
                            'account_id': stock_output_acc,
 
204
                            'credit': amount_diff,
 
205
                            'move_id': move_id
 
206
                        }, context=context)
 
207
                        move_line_obj.create(cr, uid, {
 
208
                            'name': product.categ_id.name,
 
209
                            'account_id': account_valuation_id,
 
210
                            'debit': amount_diff,
 
211
                            'move_id': move_id
 
212
                        }, context=context)
 
213
 
 
214
            self.write(cr, uid, rec_id, {'standard_price': new_price})
 
215
 
 
216
        return move_ids
 
217
 
 
218
 
 
219
class stock_move(orm.Model):
 
220
    _inherit = "stock.move"
 
221
 
 
222
    def _get_reference_accounting_values_for_valuation(
 
223
        self, cr, uid, move, context=None
 
224
    ):
 
225
        res = super(
 
226
            stock_move, self)._get_reference_accounting_values_for_valuation(
 
227
                cr, uid, move, context=context)
 
228
        if move.product_id.lot_valuation and move.prodlot_id:
 
229
            product_uom_obj = self.pool.get('product.uom')
 
230
            qty = product_uom_obj._compute_qty(
 
231
                cr, uid, move.product_uom.id,
 
232
                move.product_qty, move.product_id.uom_id.id)
 
233
            if context is None:
 
234
                context = {}
 
235
            currency_ctx = dict(
 
236
                context, currency_id=move.company_id.currency_id.id)
 
237
            amount_unit = move.prodlot_id.price_get(
 
238
                context=currency_ctx)[move.prodlot_id.id]
 
239
            reference_amount = amount_unit * qty
 
240
            new_res = (reference_amount, move.company_id.currency_id.id)
 
241
            res = new_res
 
242
        return res
 
243
 
 
244
    def do_partial(self, cr, uid, ids, partial_datas, context=None):
 
245
        if context is None:
 
246
            context = {}
 
247
        pick_obj = self.pool.get('stock.picking')
 
248
        for move in self.browse(cr, uid, ids, context=context):
 
249
            pick_obj.write_lot(cr, uid, move, partial_datas, context=context)
 
250
        res = super(stock_move, self).do_partial(
 
251
            cr, uid, ids, partial_datas, context=context)
 
252
        return res
 
253
 
 
254
 
 
255
class stock_picking(orm.Model):
 
256
    _inherit = "stock.picking"
 
257
 
 
258
    def compute_price(self, cr, uid, partial_datas, move, context=None):
 
259
        if context is None:
 
260
            context = {}
 
261
        lot_obj = self.pool.get('stock.production.lot')
 
262
        uom_obj = self.pool.get('product.uom')
 
263
        move_obj = self.pool.get('stock.move')
 
264
        currency_obj = self.pool.get('res.currency')
 
265
        partial_data = partial_datas.get('move%s' % (move.id), {})
 
266
        product_uom = partial_data.get('product_uom', False)
 
267
        product_qty = partial_data.get('product_qty', 0.0)
 
268
        product_currency = partial_data.get('product_currency', False)
 
269
        product_price = partial_data.get('product_price', 0.0)
 
270
 
 
271
        lot = lot_obj.browse(cr, uid, move.prodlot_id.id, context=context)
 
272
        product = lot.product_id
 
273
        move_currency_id = move.company_id.currency_id.id
 
274
        context['currency_id'] = move_currency_id
 
275
        qty = uom_obj._compute_qty(
 
276
            cr, uid, product_uom, product_qty, product.uom_id.id)
 
277
        if qty > 0:
 
278
            new_price = currency_obj.compute(
 
279
                cr, uid, product_currency,
 
280
                move_currency_id, product_price)
 
281
            new_price = uom_obj._compute_price(
 
282
                cr, uid, product_uom, new_price,
 
283
                product.uom_id.id)
 
284
            if lot.stock_available <= 0:
 
285
                new_std_price = new_price
 
286
            else:
 
287
                # Get the standard price
 
288
                amount_unit = lot.price_get(context=context)[lot.id]
 
289
                new_std_price = (
 
290
                    ((amount_unit * lot.stock_available)
 
291
                        + (new_price * qty)) / (lot.stock_available + qty)
 
292
                )
 
293
 
 
294
            lot_obj.write(
 
295
                cr, uid, [lot.id], {'standard_price': new_std_price},
 
296
                context=context
 
297
            )
 
298
 
 
299
            # Record the values that were chosen in the wizard, so they can be
 
300
            # used for inventory valuation if real-time valuation is enabled.
 
301
            move_obj.write(cr, uid, [move.id], {
 
302
                'price_unit': product_price,
 
303
                'price_currency_id': product_currency
 
304
            }, context=context)
 
305
 
 
306
    def write_lot(self, cr, uid, move, partial_datas, context=None):
 
307
        lot_obj = self.pool.get('stock.production.lot')
 
308
        currency_obj = self.pool.get('res.currency')
 
309
        uom_obj = self.pool.get('product.uom')
 
310
        if partial_datas.get('move%s' % (move.id)):
 
311
            partial_data = partial_datas.get('move%s' % (move.id), {})
 
312
            product_price = partial_data.get('product_price', 0.0)
 
313
            product_currency = partial_data.get('product_currency', False)
 
314
            product_uom = partial_data.get('product_uom', False)
 
315
            if partial_data.get('prodlot_id'):
 
316
                lot = lot_obj.browse(
 
317
                    cr, uid, partial_data['prodlot_id'], context)
 
318
                product = lot.product_id
 
319
                if (
 
320
                    move.product_id.lot_valuation and (
 
321
                        move.picking_id.type == 'in'
 
322
                    ) and (lot.cost_method == 'average')
 
323
                ):
 
324
                    self.compute_price(
 
325
                        cr, uid, partial_datas, move, context=context)
 
326
                if (
 
327
                    move.product_id.lot_valuation and product_price
 
328
                    and not lot.standard_price
 
329
                ):
 
330
                    new_price = currency_obj.compute(
 
331
                        cr, uid, product_currency,
 
332
                        move.company_id.currency_id.id, product_price)
 
333
                    new_price = uom_obj._compute_price(
 
334
                        cr, uid, product_uom, new_price,
 
335
                        product.uom_id.id)
 
336
                    lot.write({'standard_price': new_price})
 
337
 
 
338
    def do_partial(self, cr, uid, ids, partial_datas, context=None):
 
339
        if context is None:
 
340
            context = {}
 
341
        for pick in self.browse(cr, uid, ids, context=context):
 
342
            for move in pick.move_lines:
 
343
                self.write_lot(cr, uid, move, partial_datas, context=context)
 
344
        res = super(stock_picking, self).do_partial(
 
345
            cr, uid, ids, partial_datas, context=context)
 
346
        return res
 
347
 
 
348
 
 
349
class stock_partial_picking(orm.TransientModel):
 
350
    _inherit = "stock.partial.picking"
 
351
 
 
352
    def _product_cost_for_average_update(self, cr, uid, move):
 
353
        res = super(
 
354
            stock_partial_picking, self
 
355
        )._product_cost_for_average_update(cr, uid, move)
 
356
        if move.prodlot_id and move.product_id.lot_valuation:
 
357
            res['cost'] = move.prodlot_id.standard_price
 
358
        return res