1
# -*- coding: utf-8 -*-
2
##############################################################################
4
# OpenERP, Open Source Management Solution
5
# Copyright (C) Copyright (C) 2011 MSF, TeMPO Consulting.
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.
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.
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/>.
20
##############################################################################
22
from osv import fields, osv
23
from tools.translate import _
24
import decimal_precision as dp
28
from msf_outgoing import INTEGRITY_STATUS_SELECTION
30
class kit_selection_sale(osv.osv_memory):
34
_name = "kit.selection.sale"
36
_columns = {'product_id_kit_selection_sale': fields.many2one('product.product', string='Kit Product', readonly=True),
37
'kit_id_kit_selection_sale': fields.many2one('composition.kit', string='Theoretical Kit'),
38
'order_line_id_kit_selection_sale': fields.many2one('sale.order.line', string='Sale Order Line', readonly=True, required=True),
40
'product_ids_kit_selection_sale': fields.one2many('kit.selection.sale.line', 'wizard_id_kit_selection_sale_line', string='Replacement Products'),
42
'partner_id_kit_selection_sale': fields.related('order_line_id_kit_selection_sale', 'order_id', 'partner_id', string='Partner', type='many2one', relation='res.partner', readonly=True, store=False),
43
'pricelist_id_kit_selection_sale': fields.related('order_line_id_kit_selection_sale', 'order_id', 'pricelist_id', string='Currency', type='many2one', relation='product.pricelist', readonly=True, store=False),
44
'warehouse_id_kit_selection_sale': fields.related('order_line_id_kit_selection_sale', 'order_id', 'warehouse_id', string='Warehouse', type='many2one', relation='stock.warehouse', readonly=True, store=False),
47
_defaults = {'product_id_kit_selection_sale': lambda s, cr, uid, c: c.get('product_id', False),
48
'order_line_id_kit_selection_sale': lambda s, cr, uid, c: c.get('active_ids') and c.get('active_ids')[0] or False,
51
def import_items(self, cr, uid, ids, context=None):
53
import lines into product_ids_kit_selection_sale
56
line_obj = self.pool.get('kit.selection.sale.line')
57
# purchase order line id
58
sol_ids = context['active_ids']
60
for obj in self.browse(cr, uid, ids, context=context):
61
if not obj.kit_id_kit_selection_sale:
62
raise osv.except_osv(_('Warning !'), _('A theoretical version should be selected.'))
63
if obj.kit_id_kit_selection_sale.state != 'completed':
64
raise osv.except_osv(_('Warning !'), _('The theoretical version must be completed.'))
65
for item in obj.kit_id_kit_selection_sale.composition_item_ids:
66
values = {'order_line_id_kit_selection_sale_line': obj.order_line_id_kit_selection_sale.id,
67
'wizard_id_kit_selection_sale_line': obj.id,
68
'product_id_kit_selection_sale_line': item.item_product_id.id,
69
'qty_kit_selection_sale_line': item.item_qty,
70
'uom_id_kit_selection_sale_line': item.item_uom_id.id,
72
line_obj.create(cr, uid, values, context=dict(context, sol_ids=context['active_ids']))
73
return self.pool.get('wizard').open_wizard(cr, uid, sol_ids, type='update', context=context)
75
def validate_lines(self, cr, uid, ids, context=None):
79
- qty > 0.0 (must_be_greater_than_0)
85
prod_obj = self.pool.get('product.product')
86
lot_obj = self.pool.get('stock.production.lot')
88
errors = {'must_be_greater_than_0': False,
89
'price_must_be_greater_than_0': False,
91
for obj in self.browse(cr, uid, ids, context=context):
92
for item in obj.product_ids_kit_selection_sale:
93
# reset the integrity status
94
item.write({'integrity_status': 'empty'}, context=context)
96
if item.qty_kit_selection_sale_line <= 0.0:
98
errors.update(must_be_greater_than_0=True)
99
item.write({'integrity_status': 'must_be_greater_than_0'}, context=context)
101
if item.price_unit_kit_selection_sale_line <= 0.0:
102
# unit price is needed
103
errors.update(price_must_be_greater_than_0=True)
104
item.write({'integrity_status': 'price_must_be_greater_than_0'}, context=context)
105
# check the encountered errors
106
return all([not x for x in errors.values()])
108
def do_de_kitting(self, cr, uid, ids, context=None):
110
create a sale order line for each kit item and delete the selected kit purchase order line
112
# quick integrity check
113
assert context, 'No context defined, problem on method call'
114
if isinstance(ids, (int, long)):
117
so_obj = self.pool.get('sale.order')
118
sol_obj = self.pool.get('sale.order.line')
119
# id of corresponding sale order line
120
sol_ids = context['active_ids']
121
sol_id = context['active_ids'][0]
122
sol = sol_obj.browse(cr, uid, sol_id, context=context)
123
if sol.order_id.state not in ['draft', 'validated']:
124
raise osv.except_osv(_('Warning !'), _('Sale order line kit replacement with components function is only available for Draft and Validated states.'))
125
# integrity constraint
126
integrity_check = self.validate_lines(cr, uid, ids, context=context)
127
if not integrity_check:
128
# the windows must be updated to trigger tree colors
129
return self.pool.get('wizard').open_wizard(cr, uid, sol_ids, type='update', context=context)
131
ctx_keep_info = context.copy()
132
ctx_keep_info['keepDateAndDistrib'] = True
133
for obj in self.browse(cr, uid, ids, context=context):
134
if not len(obj.product_ids_kit_selection_sale):
135
raise osv.except_osv(_('Warning !'), _('Replacement Items must be selected.'))
136
# to keep a link to previous line (for copy) and as a flag to write in the first loop
138
# for each item from the product_ids_kit_selection_sale
139
for item_v in obj.product_ids_kit_selection_sale:
140
# price unit is mandatory
141
if item_v.price_unit_kit_selection_sale_line <= 0.0:
142
raise osv.except_osv(_('Warning !'), _('Unit Price must be specified for each line.'))
143
# selected product_id
144
product_id = item_v.product_id_kit_selection_sale_line.id
146
qty = item_v.qty_kit_selection_sale_line
148
raise osv.except_osv(_('Warning !'), _('Quantity must be greater than 0.0.'))
150
uom_id = item_v.uom_id_kit_selection_sale_line.id
152
price_unit = item_v.price_unit_kit_selection_sale_line
153
# call purchase order line on change function
154
data = self.pool.get('kit.selection.sale.line')._call_sol_on_change(cr, uid, ids, product_id, qty, uom_id, price_unit,
155
type='product_id_change',
156
context=dict(context, sol_ids=context['active_ids']))
157
# common dictionary of data
158
values = {'product_id': product_id,
159
'price_unit': price_unit,
160
'product_uom': uom_id,
161
'product_uom_qty': sol.product_uom_qty*qty,
162
'product_uos': uom_id,
163
'product_uos_qty': sol.product_uos_qty*qty,
164
'default_code': data['value']['default_code'],
165
'name': data['value']['name'],
166
'default_name': data['value']['default_name'],
169
# following new sequencing policy, we check if resequencing occur (behavior 1).
170
# if not (behavior 2), the split line keeps the same line number as original line
171
if not sol_obj.allow_resequencing(cr, uid, [obj.order_line_id_kit_selection_sale.id], context=context):
172
# set default value for line_number as the same as original line
173
values.update({'line_number': obj.order_line_id_kit_selection_sale.line_number})
176
# the existing purchase order line has already been updated, we create a new one
177
# copy the original purchase order line
178
last_line_id = sol_obj.copy(cr, uid, last_line_id, values, context=ctx_keep_info)
179
# as so *line* state is draft anyhow, we do not need to process the created line
181
# first item to be treated, we update the existing line
182
last_line_id = obj.order_line_id_kit_selection_sale.id
183
sol_obj.write(cr, uid, [last_line_id], values, context=context)
185
return {'type': 'ir.actions.act_window_close'}
190
class kit_selection_sale_line(osv.osv_memory):
194
_name = 'kit.selection.sale.line'
196
def create(self, cr, uid, vals, context=None):
198
default price unit from sol on_change function
201
sol_obj = self.pool.get('sale.order.line')
202
# id of corresponding sale order line
203
sol_id = context.get('active_ids', False) and context['active_ids'][0] or False
204
if sol_id and ('price_unit_kit_selection_sale_line' not in vals or vals.get('price_unit_kit_selection_sale_line') == 0.0):
205
sol = sol_obj.browse(cr, uid, sol_id, context=context)
206
# selected product_id
207
product_id = vals.get('product_id_kit_selection_sale_line', False)
209
qty = vals.get('qty_kit_selection_sale_line', 0.0)
211
uom_id = vals.get('uom_id_kit_selection_sale_line', False)
213
price_unit = vals.get('price_unit_kit_selection_sale_line', 0.0)
214
# gather default values
215
data = self._call_sol_on_change(cr, uid, context['active_ids'],
216
product_id, qty, uom_id, price_unit, type='product_id_change',
219
# update price_unit value
220
vals.update({'price_unit_kit_selection_sale_line': data['value']['price_unit']})
221
return super(kit_selection_sale_line, self).create(cr, uid, vals, context=context)
223
def _call_sol_on_change(self, cr, uid, ids, product_id, qty, uom_id, price_unit, type, context=None):
225
core function from sale order line
227
def product_id_change(self, cr, uid, ids, pricelist, product, qty=0,
228
uom=False, qty_uos=0, uos=False, name='', partner_id=False,
229
lang=False, update_tax=True, date_order=False, packaging=False, fiscal_position=False, flag=False):
231
# quick integrity check
232
assert context, 'No context defined, problem on method call'
233
if isinstance(ids, (int, long)):
237
sol_obj = self.pool.get('sale.order.line')
238
prod_obj = self.pool.get('product.product')
239
# id of corresponding purchase order line
240
sol_id = context['sol_ids'][0]
241
sol = sol_obj.browse(cr, uid, sol_id, context=context)
242
# pricelist from purchase order
243
pricelist_id = sol.order_id.pricelist_id.id
244
# partner_id from purchase order
245
partner_id = sol.order_id.partner_id.id
246
# date_order from purchase order
247
date_order = sol.order_id.date_order
248
# fiscal_position from purchase order
249
fiscal_position_id = sol.order_id.fiscal_position.id
250
# date_planned from purchase order line
251
date_planned = sol.date_planned
255
name = prod_obj.read(cr, uid, product_id, ['name'], context=context)['name']
259
state = sol.order_id.state
260
# gather default values
261
if type == 'product_uom_change':
262
data = getattr(sol_obj, type)(cr, uid, ids, pricelist=pricelist_id, product=product_id, qty=qty,
263
uom=uom_id, qty_uos=qty, uos=uom_id, name=name, partner_id=partner_id,
264
lang='lang' in context and context['lang'], update_tax=False, date_order=date_order)
265
elif type == 'product_id_change':
266
data = getattr(sol_obj, type)(cr, uid, ids, pricelist=pricelist_id, product=product_id, qty=qty,
267
uom=uom_id, qty_uos=qty, uos=uom_id, name=name, partner_id=partner_id,
268
lang='lang' in context and context['lang'], update_tax=True, date_order=date_order, packaging=False, fiscal_position=fiscal_position_id, flag=False)
271
def on_product_id_change(self, cr, uid, ids, product_id, qty, uom_id, price_unit, context=None):
273
core function from purchase order line
275
def product_id_change(self, cr, uid, ids, pricelist, product, qty, uom,
276
partner_id, date_order=False, fiscal_position=False, date_planned=False,
277
name=False, price_unit=False, notes=False):
279
# quick integrity check
280
assert context, 'No context defined, problem on method call'
281
if isinstance(ids, (int, long)):
284
# product change, we reset everything
285
result = {'value': {'uom_id_kit_selection_sale_line': False,
286
'price_unit_kit_selection_sale_line': 0.0,
287
'qty_kit_selection_sale_line': 0.0}}
288
# gather default values
289
# data = self._call_sol_on_change(cr, uid, ids, product_id, qty, uom_id, price_unit, 'product_id_change', context=context)
290
data = self._call_sol_on_change(cr, uid, ids, product_id, qty, uom_id, price_unit, type='product_id_change', context=context)
292
# update result price_unit and default uom
293
if 'price_unit' in data['value']:
294
result['value'].update({'price_unit_kit_selection_sale_line': data['value']['price_unit']})
295
if 'product_qty' in data['value']:
296
result['value'].update({'qty_kit_selection_sale_line': data['value']['product_qty']})
297
if 'product_uom' in data['value']:
298
result['value'].update({'uom_id_kit_selection_sale_line': data['value']['product_uom']})
302
def on_uom_id_change(self, cr, uid, ids, product_id, qty, uom_id, price_unit, context=None):
304
core function from purchase order line
306
def product_uom_change(self, cursor, user, ids, pricelist, product, qty=0,
307
uom=False, qty_uos=0, uos=False, name='', partner_id=False,
308
lang=False, update_tax=True, date_order=False):
310
# quick integrity check
311
assert context, 'No context defined, problem on method call'
312
if isinstance(ids, (int, long)):
315
# uom change - we reset the qty and price
316
result = {'value': {'qty_kit_selection_sale_line': 0.0,
317
'price_unit_kit_selection_sale_line': 0.0}}
318
# gather default values
319
data = self._call_sol_on_change(cr, uid, ids, product_id, qty, uom_id, price_unit, type='product_uom_change', context=context)
320
# update result price_unit and default uom
321
if 'price_unit' in data['value']:
322
result['value'].update({'price_unit_kit_selection_sale_line': data['value']['price_unit']})
323
if 'product_qty' in data['value']:
324
result['value'].update({'qty_kit_selection_sale_line': data['value']['product_qty']})
327
def on_qty_change(self, cr, uid, ids, product_id, qty, uom_id, price_unit, context=None):
329
core function from purchase order line
331
def product_id_change(self, cr, uid, ids, pricelist, product, qty, uom,
332
partner_id, date_order=False, fiscal_position=False, date_planned=False,
333
name=False, price_unit=False, notes=False):
335
# quick integrity check
336
assert context, 'No context defined, problem on method call'
337
if isinstance(ids, (int, long)):
340
# qty changed - we do not reset anything
341
result = {'value': {}}
342
# gather default values
343
data = self._call_sol_on_change(cr, uid, ids, product_id, qty, uom_id, price_unit, type='product_id_change', context=context)
344
# update result price_unit and default uom
345
if 'price_unit' in data['value']:
346
result['value'].update({'price_unit_kit_selection_sale_line': data['value']['price_unit']})
347
if 'product_uom' in data['value'] and not uom_id:
348
result['value'].update({'uom_id_kit_selection_sale_line': data['value']['product_uom']})
351
uom_id = result.get('value', {}).get('uom_id_kit_selection_sale_line', uom_id)
352
result = self.pool.get('product.uom')._change_round_up_qty(cr, uid, uom_id, qty, 'qty_kit_selection_sale_line', result=result)
356
_columns = {'integrity_status': fields.selection(string=' ', selection=INTEGRITY_STATUS_SELECTION, readonly=True),
357
'order_line_id_kit_selection_sale_line': fields.many2one('sale.order.line', string='Sale Order Line', readonly=True, required=True),
358
'wizard_id_kit_selection_sale_line': fields.many2one('kit.selection.sale', string='Kit Selection wizard'),
360
'product_id_kit_selection_sale_line': fields.many2one('product.product', string='Product', required=True),
361
'qty_kit_selection_sale_line': fields.float(string='Qty', digits_compute=dp.get_precision('Product UoM'), required=True),
362
'uom_id_kit_selection_sale_line': fields.many2one('product.uom', string='UoM', required=True),
363
'price_unit_kit_selection_sale_line': fields.float('Unit Price', required=True, digits_compute=dp.get_precision('Purchase Price')),
366
_defaults = {'integrity_status': 'empty',
369
kit_selection_sale_line()