[MERGE] Merge from trunk-wms-loconopreport-jco
[odoo/odoo.git] / addons / stock_account / product.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU Affero General Public License as
9 #    published by the Free Software Foundation, either version 3 of the
10 #    License, or (at your option) any later version.
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU Affero General Public License for more details.
16 #
17 #    You should have received a copy of the GNU Affero General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 #
20 ##############################################################################
21
22 from openerp.osv import fields, osv
23 from openerp.tools.translate import _
24
25 class product_product(osv.osv):
26     _inherit = "product.product"
27
28     def get_product_accounts(self, cr, uid, product_id, context=None):
29         """ To get the stock input account, stock output account and stock journal related to product.
30         @param product_id: product id
31         @return: dictionary which contains information regarding stock input account, stock output account and stock journal
32         """
33         if context is None:
34             context = {}
35         product_obj = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
36
37         stock_input_acc = product_obj.property_stock_account_input and product_obj.property_stock_account_input.id or False
38         if not stock_input_acc:
39             stock_input_acc = product_obj.categ_id.property_stock_account_input_categ and product_obj.categ_id.property_stock_account_input_categ.id or False
40
41         stock_output_acc = product_obj.property_stock_account_output and product_obj.property_stock_account_output.id or False
42         if not stock_output_acc:
43             stock_output_acc = product_obj.categ_id.property_stock_account_output_categ and product_obj.categ_id.property_stock_account_output_categ.id or False
44
45         journal_id = product_obj.categ_id.property_stock_journal and product_obj.categ_id.property_stock_journal.id or False
46         account_valuation = product_obj.categ_id.property_stock_valuation_account_id and product_obj.categ_id.property_stock_valuation_account_id.id or False
47         return {
48             'stock_account_input': stock_input_acc,
49             'stock_account_output': stock_output_acc,
50             'stock_journal': journal_id,
51             'property_stock_valuation_account_id': account_valuation
52         }
53
54     # FP Note:;too complex, not good, should be implemented at quant level TODO
55     def do_change_standard_price(self, cr, uid, ids, datas, context=None):
56         """ Changes the Standard Price of Product and creates an account move accordingly.
57         @param datas : dict. contain default datas like new_price, stock_output_account, stock_input_account, stock_journal
58         @param context: A standard dictionary
59         @return:
60
61         """
62         location_obj = self.pool.get('stock.location')
63         move_obj = self.pool.get('account.move')
64         move_line_obj = self.pool.get('account.move.line')
65         if context is None:
66             context = {}
67
68         new_price = datas.get('new_price', 0.0)
69         stock_output_acc = datas.get('stock_output_account', False)
70         stock_input_acc = datas.get('stock_input_account', False)
71         journal_id = datas.get('stock_journal', False)
72         product_obj=self.browse(cr, uid, ids, context=context)[0]
73         account_valuation = product_obj.categ_id.property_stock_valuation_account_id
74         account_valuation_id = account_valuation and account_valuation.id or False
75         if not account_valuation_id: raise osv.except_osv(_('Error!'), _('Specify valuation Account for Product Category: %s.') % (product_obj.categ_id.name))
76         move_ids = []
77         loc_ids = location_obj.search(cr, uid,[('usage','=','internal')])
78         for rec_id in ids:
79             for location in location_obj.browse(cr, uid, loc_ids, context=context):
80                 c = context.copy()
81                 c.update({
82                     'location': location.id,
83                     'compute_child': False
84                 })
85
86                 product = self.browse(cr, uid, rec_id, context=c)
87                 qty = product.qty_available
88                 diff = product.standard_price - new_price
89                 if not diff: raise osv.except_osv(_('Error!'), _("No difference between standard price and new price!"))
90                 if qty:
91                     company_id = location.company_id and location.company_id.id or False
92                     if not company_id: raise osv.except_osv(_('Error!'), _('Please specify company in Location.'))
93                     #
94                     # Accounting Entries
95                     #
96                     if not journal_id:
97                         journal_id = product.categ_id.property_stock_journal and product.categ_id.property_stock_journal.id or False
98                     if not journal_id:
99                         raise osv.except_osv(_('Error!'),
100                             _('Please define journal '\
101                               'on the product category: "%s" (id: %d).') % \
102                                 (product.categ_id.name,
103                                     product.categ_id.id,))
104                     move_id = move_obj.create(cr, uid, {
105                                 'journal_id': journal_id,
106                                 'company_id': company_id
107                                 })
108
109                     move_ids.append(move_id)
110
111
112                     if diff > 0:
113                         if not stock_input_acc:
114                             stock_input_acc = product.\
115                                 property_stock_account_input.id
116                         if not stock_input_acc:
117                             stock_input_acc = product.categ_id.\
118                                     property_stock_account_input_categ.id
119                         if not stock_input_acc:
120                             raise osv.except_osv(_('Error!'),
121                                     _('Please define stock input account for this product: "%s" (id: %d).') % \
122                                             (product.name,
123                                                 product.id,))
124                         amount_diff = qty * diff
125                         move_line_obj.create(cr, uid, {
126                                     'name': product.name,
127                                     'account_id': stock_input_acc,
128                                     'debit': amount_diff,
129                                     'move_id': move_id,
130                                     })
131                         move_line_obj.create(cr, uid, {
132                                     'name': product.categ_id.name,
133                                     'account_id': account_valuation_id,
134                                     'credit': amount_diff,
135                                     'move_id': move_id
136                                     })
137                     elif diff < 0:
138                         if not stock_output_acc:
139                             stock_output_acc = product.\
140                                 property_stock_account_output.id
141                         if not stock_output_acc:
142                             stock_output_acc = product.categ_id.\
143                                     property_stock_account_output_categ.id
144                         if not stock_output_acc:
145                             raise osv.except_osv(_('Error!'),
146                                     _('Please define stock output account ' \
147                                             'for this product: "%s" (id: %d).') % \
148                                             (product.name,
149                                                 product.id,))
150                         amount_diff = qty * -diff
151                         move_line_obj.create(cr, uid, {
152                                         'name': product.name,
153                                         'account_id': stock_output_acc,
154                                         'credit': amount_diff,
155                                         'move_id': move_id
156                                     })
157                         move_line_obj.create(cr, uid, {
158                                         'name': product.categ_id.name,
159                                         'account_id': account_valuation_id,
160                                         'debit': amount_diff,
161                                         'move_id': move_id
162                                     })
163
164             self.write(cr, uid, rec_id, {'standard_price': new_price})
165
166         return move_ids
167
168     _columns = {
169         'valuation': fields.property(type='selection', selection=[('manual_periodic', 'Periodical (manual)'),
170                                         ('real_time', 'Real Time (automated)')], string='Inventory Valuation',
171                                         help="If real-time valuation is enabled for a product, the system will automatically write journal entries corresponding to stock moves, with product price as specified by the 'Costing Method'" \
172                                              "The inventory variation account set on the product category will represent the current inventory value, and the stock input and stock output account will hold the counterpart moves for incoming and outgoing products."
173                                         , required=True),
174     }
175
176
177 class product_template(osv.osv):
178     _name = 'product.template'
179     _inherit = 'product.template'
180     _columns = {
181         'cost_method': fields.property(type='selection', selection=[('standard', 'Standard Price'), ('average', 'Average Price'), ('real', 'Real Price')],
182             help="""Standard Price: The cost price is manually updated at the end of a specific period (usually every year).
183                     Average Price: The cost price is recomputed at each incoming shipment and used for the product valuation.
184                     Real Price: The cost price displayed is the price of the last outgoing product (will be use in case of inventory loss for example).""",
185             string="Costing Method", required=True),
186         'property_stock_account_input': fields.property(
187             type='many2one',
188             relation='account.account',
189             string='Stock Input Account',
190             help="When doing real-time inventory valuation, counterpart journal items for all incoming stock moves will be posted in this account, unless "
191                  "there is a specific valuation account set on the source location. When not set on the product, the one from the product category is used."),
192         'property_stock_account_output': fields.property(
193             type='many2one',
194             relation='account.account',
195             string='Stock Output Account',
196             help="When doing real-time inventory valuation, counterpart journal items for all outgoing stock moves will be posted in this account, unless "
197                  "there is a specific valuation account set on the destination location. When not set on the product, the one from the product category is used."),
198     }
199
200
201 class product_category(osv.osv):
202     _inherit = 'product.category'
203     _columns = {
204         'property_stock_journal': fields.property(
205             relation='account.journal',
206             type='many2one',
207             string='Stock Journal',
208             help="When doing real-time inventory valuation, this is the Accounting Journal in which entries will be automatically posted when stock moves are processed."),
209         'property_stock_account_input_categ': fields.property(
210             type='many2one',
211             relation='account.account',
212             string='Stock Input Account',
213             help="When doing real-time inventory valuation, counterpart journal items for all incoming stock moves will be posted in this account, unless "
214                  "there is a specific valuation account set on the source location. This is the default value for all products in this category. It "
215                  "can also directly be set on each product"),
216         'property_stock_account_output_categ': fields.property(
217             type='many2one',
218             relation='account.account',
219             string='Stock Output Account',
220             help="When doing real-time inventory valuation, counterpart journal items for all outgoing stock moves will be posted in this account, unless "
221                  "there is a specific valuation account set on the destination location. This is the default value for all products in this category. It "
222                  "can also directly be set on each product"),
223         'property_stock_valuation_account_id': fields.property(
224             type='many2one',
225             relation='account.account',
226             string="Stock Valuation Account",
227             help="When real-time inventory valuation is enabled on a product, this account will hold the current value of the products.",),
228     }
229