78
def _get_frequence_name(self, cr, uid, ids, field_name, arg, context=None):
64
def _get_frequence_name(self, cr, uid, ids, field_name, arg, context={}):
80
66
Returns the name_get value of the frequence
83
69
for proc in self.browse(cr, uid, ids):
85
res[proc.id] = self.pool.get('stock.frequence').name_get(cr, uid, [proc.frequence_id.id], context=context)[0][1]
89
def _get_product_ids(self, cr, uid, ids, field_name, arg, context=None):
91
Returns a list of products for the rule
95
for rule in self.browse(cr, uid, ids, context=context):
97
for line in rule.product_ids:
98
res[rule.id].append(line.product_id.id)
102
def _src_product_ids(self, cr, uid, obj, name, args, context=None):
109
if arg[0] == 'product_line_ids':
111
line_ids = self.pool.get('stock.warehouse.order.cycle.line').search(cr, uid, [('product_id', arg[1], arg[2])])
112
for l in self.pool.get('stock.warehouse.order.cycle.line').browse(cr, uid, line_ids):
113
if l.order_cycle_id.id not in rule_ids:
114
rule_ids.append(l.order_cycle_id.id)
115
res.append(('id', 'in', rule_ids))
70
res[proc.id] = self.pool.get('stock.frequence').name_get(cr, uid, [proc.frequence_id.id])[0][1]
120
'sequence': fields.integer(string='Order', required=False, help='A higher order value means a low priority'),
121
'name': fields.char(size=64, string='Reference', required=True),
75
'sequence': fields.integer(string='Order', required=True, help='A higher order value means a low priority'),
76
'name': fields.char(size=64, string='Name', required=True),
122
77
'category_id': fields.many2one('product.category', string='Category'),
123
78
'product_id': fields.many2one('product.product', string='Specific product'),
124
79
'warehouse_id': fields.many2one('stock.warehouse', string='Warehouse', required=True),
125
'location_id': fields.many2one('stock.location', 'Location', ondelete="cascade", required=True,
126
domain="[('is_replenishment', '=', warehouse_id)]",
127
help='Location where the computation is made'),
128
'frequence_name': fields.function(_get_frequence_name, method=True, string='Frequency', type='char',
129
help='Define the time between two replenishments'),
130
'frequence_id': fields.many2one('stock.frequence', string='Frequency', help='It\'s the time between two replenishments'),
131
'product_ids': fields.one2many('stock.warehouse.order.cycle.line', 'order_cycle_id', string='Products'),
80
'location_id': fields.many2one('stock.location', string='Location'),
81
'frequence_name': fields.function(_get_frequence_name, method=True, string='Frequence', type='char'),
82
'frequence_id': fields.many2one('stock.frequence', string='Frequence'),
83
'product_ids': fields.many2many('product.product', 'order_cycle_product_rel', 'order_cycle_id', 'product_id', string="Products"),
132
84
'company_id': fields.many2one('res.company','Company',required=True),
133
85
'active': fields.boolean('Active', help="If the active field is set to False, it will allow you to hide the automatic supply without removing it."),
134
86
# Parameters for quantity calculation
135
87
'leadtime': fields.float(digits=(16,2), string='Delivery lead time to consider', help='Delivery lead time in month'),
136
'order_coverage': fields.float(digits=(16,2), string='Order coverage', help='In months. \
137
Define the time between two replenishments. \
138
Time used to compute the quantity of products to order according to the monthly consumption.'),
139
'safety_stock_time': fields.float(digits=(16,2), string='Safety stock in time', help='In months. \
140
Define the time while the stock is not negative but should be replenished. \
141
Time used to compute the quantity of products to order according to the monthly consumption.'),
142
'past_consumption': fields.boolean(string='Average monthly consumption',
143
help='If checked, the system will used the average monthly consumption to compute the quantity to order'),
144
'consumption_period_from': fields.date(string='Period of calculation',
145
help='This period is a number of past months the system has to consider for AMC calculation.'\
146
'By default this value is equal to the order coverage of the rule.'),
147
'consumption_period_to': fields.date(string='-'),
148
'reviewed_consumption': fields.boolean(string='Forecasted monthly consumption',
149
help='If checked, the system will used the forecasted monthly consumption to compute the quantity to order'),
150
'manual_consumption': fields.float(digits=(16,2), string='Manual monthly consumption',
151
help='If not 0.00, the system will used the entered monthly consumption to compute the quantity to order'),
88
'order_coverage': fields.float(digits=(16,2), string='Order coverage'),
89
'safety_stock_time': fields.float(digits=(16,2), string='Safety stock in time'),
90
'safety_stock': fields.integer(string='Safety stock (quantity'),
91
'past_consumption': fields.boolean(string='Past monthly consumption'),
92
'reviewed_consumption': fields.boolean(string='Reviewed monthly consumption'),
93
'manual_consumption': fields.float(digits=(16,2), string='Manual monthly consumption'),
152
94
'next_date': fields.related('frequence_id', 'next_date', string='Next scheduled date', readonly=True, type='date',
153
help='As this date is not in the past, no new replenishment will be run',
154
store={'stock.warehouse.order.cycle': (lambda self, cr, uid, ids, context=None: ids, ['frequence_id'], 20),
95
store={'stock.warehouse.order.cycle': (lambda self, cr, uid, ids, context={}: ids, ['frequence_id'], 20),
155
96
'stock.frequence': (_get_frequence_change, None, 20)}),
156
'product_line_ids': fields.function(_get_product_ids, fnct_search=_src_product_ids,
157
type='many2many', relation='product.product', method=True, string='Products'),
158
'sublist_id': fields.many2one('product.list', string='List/Sublist'),
159
'nomen_manda_0': fields.many2one('product.nomenclature', 'Main Type'),
160
'nomen_manda_1': fields.many2one('product.nomenclature', 'Group'),
161
'nomen_manda_2': fields.many2one('product.nomenclature', 'Family'),
162
'nomen_manda_3': fields.many2one('product.nomenclature', 'Root'),
100
'sequence': lambda *a: 10,
166
101
'past_consumption': lambda *a: 1,
167
102
'active': lambda *a: 1,
168
103
'name': lambda x,y,z,c: x.pool.get('ir.sequence').get(y,z,'stock.order.cycle') or '',
104
'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'stock.warehouse.order.cycle', context=c),
169
105
'order_coverage': lambda *a: 3,
172
def default_get(self, cr, uid, fields, context=None):
174
Get the default values for the replenishment rule
176
res = super(stock_warehouse_order_cycle, self).default_get(cr, uid, fields, context=context)
178
company_id = res.get('company_id')
179
warehouse_id = res.get('warehouse_id')
181
if not 'company_id' in res:
182
company_id = self.pool.get('res.company')._company_default_get(cr, uid, 'stock.warehouse.automatic.supply', context=context)
183
res.update({'company_id': company_id})
185
if not 'warehouse_id' in res:
186
warehouse_id = self.pool.get('stock.warehouse').search(cr, uid, [('company_id', '=', company_id)], context=context)[0]
187
res.update({'warehouse_id': warehouse_id})
189
if not 'location_id' in res:
190
location_id = self.pool.get('stock.warehouse').browse(cr, uid, warehouse_id, context=context).lot_stock_id.id
191
res.update({'location_id': location_id})
193
if not 'consumption_period_from' in res:
194
res.update({'consumption_period_from': (DateFrom(now()) + RelativeDate(day=1)).strftime('%Y-%m-%d')})
196
if not 'consumption_period_to' in res:
197
res.update({'consumption_period_to': (DateFrom(now()) + RelativeDate(months=1, day=1, days=-1)).strftime('%Y-%m-%d')})
201
def on_change_period(self, cr, uid, ids, from_date, to_date):
203
Check if the from date is younger than the to date
208
if from_date and to_date and from_date > to_date:
209
warn = {'title': 'Issue on date',
210
'message': 'The start date must be younger than end date'}
212
# Set the from date to the first day of the month
214
val.update({'consumption_period_from': (DateFrom(from_date) + RelativeDate(day=1)).strftime('%Y-%m-%d')})
216
# Set the to date to the last day of the month
218
val.update({'consumption_period_to': (DateFrom(to_date) + RelativeDate(months=1, day=1, days=-1)).strftime('%Y-%m-%d')})
220
return {'value': val, 'warning': warn}
222
def onChangeSearchNomenclature(self, cr, uid, ids, position, n_type, nomen_manda_0, nomen_manda_1, nomen_manda_2, nomen_manda_3, num=True, context=None):
223
return self.pool.get('product.product').onChangeSearchNomenclature(cr, uid, 0, position, n_type, nomen_manda_0, nomen_manda_1, nomen_manda_2, nomen_manda_3, False, context={'withnum': 1})
225
def fill_lines(self, cr, uid, ids, context=None):
227
Fill all lines according to defined nomenclature level and sublist
229
line_obj = self.pool.get('stock.warehouse.order.cycle.line')
230
product_obj = self.pool.get('product.product')
235
for report in self.browse(cr, uid, ids, context=context):
240
# Get all products for the defined nomenclature
241
if report.nomen_manda_3:
242
nom = report.nomen_manda_3.id
243
field = 'nomen_manda_3'
244
elif report.nomen_manda_2:
245
nom = report.nomen_manda_2.id
246
field = 'nomen_manda_2'
247
elif report.nomen_manda_1:
248
nom = report.nomen_manda_1.id
249
field = 'nomen_manda_1'
250
elif report.nomen_manda_0:
251
nom = report.nomen_manda_0.id
252
field = 'nomen_manda_0'
254
product_ids.extend(self.pool.get('product.product').search(cr, uid, [(field, '=', nom)], context=context))
256
# Get all products for the defined list
257
if report.sublist_id:
258
for line in report.sublist_id.product_ids:
259
product_ids.append(line.name.id)
261
for product in product_obj.browse(cr, uid, product_ids, context=context):
262
# Check if the product is not already in the list
263
if product.type not in ('consu', 'service', 'service_recep') and \
264
not line_obj.search(cr, uid, [('order_cycle_id', '=', report.id),
265
('product_id', '=', product.id),
266
('uom_id', '=', product.uom_id.id)], context=context):
267
line_obj.create(cr, uid, {'order_cycle_id': report.id,
268
'product_id': product.id,
269
'safety_stock': 0.00,
270
'uom_id': product.uom_id.id}, context=context)
274
def get_nomen(self, cr, uid, ids, field):
275
return self.pool.get('product.nomenclature').get_nomen(cr, uid, self, ids, field, context={'withnum': 1})
277
def consumption_method_change(self, cr, uid, ids, past_consumption, reviewed_consumption, manual_consumption, order_coverage, field='past'):
108
def consumption_method_change(self, cr, uid, ids, past_consumption, reviewed_consumption, manual_consumption, product_id, field='past'):
279
110
Uncheck a box when the other is checked
283
date_from = now() + RelativeDate(day=1, months=-round(order_coverage, 1)+1)
284
date_to = now() + RelativeDate(days=-1, day=1, months=1)
285
dates = self.on_change_period(cr, uid, ids, date_from, date_to)
286
113
if field == 'past' and past_consumption:
287
v.update({'reviewed_consumption': 0, 'manual_consumption': 0.00,
288
'consumption_period_from': dates.get('value', {}).get('consumption_period_from'),
289
'consumption_period_to': dates.get('value', {}).get('consumption_period_to'),})
114
v.update({'reviewed_consumption': 0, 'manual_consumption': 0.00})
290
115
elif field == 'past' and not past_consumption:
291
v.update({'reviewed_consumption': 1, 'manual_consumption': 0.00,})
292
v.update({'consumption_period_from': False, 'consumption_period_to': False})
116
v.update({'reviewed_consumption': 1, 'manual_consumption': 0.00})
293
117
elif field == 'review' and reviewed_consumption:
294
118
v.update({'past_consumption': 0, 'manual_consumption': 0.00})
295
v.update({'consumption_period_from': False, 'consumption_period_to': False})
296
119
elif field == 'review' and not reviewed_consumption:
297
v.update({'past_consumption': 1, 'manual_consumption': 0.00,
298
'consumption_period_from': dates.get('value', {}).get('consumption_period_from'),
299
'consumption_period_to': dates.get('value', {}).get('consumption_period_to'),})
300
elif field == 'manual' and manual_consumption < 0.00:
301
v.update({'manual_consumption': 0.00})
302
w.update({'title': 'Negative consumption',
303
'message': 'You mustn\'t have a negative consumption'})
304
elif field == 'manual' and manual_consumption != 0.00 :
120
v.update({'past_consumption': 1, 'manual_consumption': 0.00})
121
elif field == 'manual' and manual_consumption != 0.00 and product_id:
305
122
v.update({'reviewed_consumption': 0, 'past_consumption': 0})
306
v.update({'consumption_period_from': False, 'consumption_period_to': False})
307
elif field == 'manual' and (manual_consumption == 0.00 ):
308
v.update({'past_consumption': 1,
309
'consumption_period_from': dates.get('value', {}).get('consumption_period_from'),
310
'consumption_period_to': dates.get('value', {}).get('consumption_period_to'),})
123
elif field == 'manual' and (manual_consumption == 0.00 or not product_id):
124
v.update({'past_consumption': 1})
312
return {'value': v, 'warning': w}
314
def choose_change_frequence(self, cr, uid, ids, context=None):
128
def choose_change_frequence(self, cr, uid, ids, context={}):
316
130
Open a wizard to define a frequency for the order cycle
317
131
or open a wizard to modify the frequency if frequency already exists
319
133
if isinstance(ids, (int, long)):
324
136
frequence_id = False
379
177
self.pool.get('stock.frequence').unlink(cr, uid, freq_ids, context)
380
178
return super(stock_warehouse_order_cycle, self).unlink(cr, uid, ids, context=context)
382
def copy(self, cr, uid, ids, default=None, context=None):
384
When duplicate an order cycle rule, duplicate the frequency
180
def copy(self, cr, uid, id, default=None, context=None):
388
obj = self.read(cr, uid, ids, ['frequence_id'])
183
obj = self.read(cr, uid, id, ['frequence_id'])
389
184
if obj['frequence_id']:
390
185
default['frequence_id'] = self.pool.get('stock.frequence').copy(cr, uid, obj['frequence_id'][0], context=context)
393
188
'name': self.pool.get('ir.sequence').get(cr, uid, 'stock.order.cycle') or '',
395
return super(stock_warehouse_order_cycle, self).copy(cr, uid, ids, default, context=context)
190
return super(stock_warehouse_order_cycle, self).copy(cr, uid, id, default, context=context)
397
192
stock_warehouse_order_cycle()
399
class stock_warehouse_order_cycle_line(osv.osv):
400
_name = 'stock.warehouse.order.cycle.line'
401
_rec_name = 'product_id'
402
_description = 'Products to replenish'
404
def _get_data(self, cr, uid, ids, field_name, args, context=None):
408
product_obj = self.pool.get('product.product')
409
proc_obj = self.pool.get('procurement.order')
410
prodlot_obj = self.pool.get('stock.production.lot')
412
if isinstance(ids, (int, long)):
417
for line in self.browse(cr, uid, ids, context=context):
419
location_id = line.order_cycle_id.location_id.id
420
stock_product = product_obj.browse(cr, uid, line.product_id.id, context=dict(context, location=location_id))
422
from_date = line.order_cycle_id.consumption_period_from
423
to_date = line.order_cycle_id.consumption_period_to
424
consu_product = product_obj.browse(cr, uid, line.product_id.id, context=dict(context, from_date=from_date, to_date=to_date))
426
if line.order_cycle_id.past_consumption:
427
consu = consu_product.product_amc
428
elif line.order_cycle_id.reviewed_consumption:
429
consu = consu_product.reviewed_consumption
431
consu = line.order_cycle_id.manual_consumption
434
d_values = {'reviewed_consumption': False,
435
'past_consumption': False,
436
'manual_consumption': consu,
437
'consumption_period_from': line.order_cycle_id.consumption_period_from,
438
'consumption_period_to': line.order_cycle_id.consumption_period_to,
439
'leadtime': line.order_cycle_id.leadtime,
440
'coverage': line.order_cycle_id.order_coverage,
441
'safety_stock': line.safety_stock,
442
'safety_time': line.order_cycle_id.safety_stock_time}
443
expiry_product_qty = product_obj.get_expiry_qty(cr, uid, line.product_id.id, location_id, False, d_values, context=dict(context, location=location_id, compute_child=True))
444
expiry_product_qty = expiry_product_qty or 0.00
446
qty_to_order, req_date = proc_obj._compute_quantity(cr, uid, False, line.product_id, line.order_cycle_id.location_id.id, d_values, context=dict(context, from_date=from_date, to_date=to_date, get_data=True))
448
consumed_in_period = (consu * d_values['coverage']) + (consu * d_values['safety_time']) + d_values['safety_stock'] + expiry_product_qty
449
if stock_product.perishable and stock_product.virtual_available < consumed_in_period:
450
prodlot_ids = prodlot_obj.search(cr, uid, [('product_id', '=', stock_product.id)], order='life_date desc', limit=1, context=context)
452
life_date = prodlot_obj.read(cr, uid, prodlot_ids[0], ['life_date'], context=context)['life_date']
453
if life_date < req_date:
456
res[line.id] = {'consumption': consu,
457
'real_stock': stock_product.qty_available,
458
'available_stock': stock_product.virtual_available,
459
'expiry_before': expiry_product_qty,
460
'qty_to_order': qty_to_order >= 0.00 and qty_to_order or 0.00,
461
'supplier_id': stock_product.seller_id and stock_product.seller_id.id or False,
462
'required_date': req_date,
468
'product_id': fields.many2one('product.product', required=True, string='Product'),
469
'uom_id': fields.many2one('product.uom', string='UoM', required=True),
470
'order_cycle_id': fields.many2one('stock.warehouse.order.cycle', string='Order cycle', required=True, ondelete='cascade'),
471
'safety_stock': fields.float(digits=(16,2), string='Safety stock (Qty)', required=True),
472
'consumption': fields.function(_get_data, method=True, type='float', digits=(16,3), string='AMC/FMC', multi='data', readonly=True),
473
'real_stock': fields.function(_get_data, method=True, type='float', digits=(16,3), string='Real stock', multi='data', readonly=True),
474
'available_stock': fields.function(_get_data, method=True, type='float', digits=(16,3), string='Available stock', multi='data', readonly=True),
475
'expiry_before': fields.function(_get_data, method=True, type='float', digits=(16,3), string='Exp. before consumption', multi='data', readonly=True),
476
'qty_to_order': fields.function(_get_data, method=True, type='float', digits=(16,3), string='Qty. to order', multi='data', readonly=True),
477
'supplier_id': fields.function(_get_data, method=True, type='many2one', relation='res.partner', string='Supplier', multi='data', readonly=True),
478
'required_date': fields.function(_get_data, method=True, type='date', string='Required by date', multi='data', readonly=True),
481
def _check_uniqueness(self, cr, uid, ids, context=None):
483
Check if the product is not already in the current rule
485
for line in self.browse(cr, uid, ids, context=context):
486
lines = self.search(cr, uid, [('id', '!=', line.id),
487
('product_id', '=', line.product_id.id),
488
('order_cycle_id', '=', line.order_cycle_id.id)], context=context)
495
(_check_uniqueness, 'You cannot have two times the same product on the same order cycle rule', ['product_id'])
498
def onchange_uom_qty(self, cr, uid, ids, uom_id, qty):
502
res = self.pool.get('product.uom')._change_round_up_qty(cr, uid, uom_id, qty, 'safety_stock', result=res)
506
def product_change(self, cr, uid, ids, product_id=False, context=None):
508
Set the UoM as the default UoM of the product
513
v.update({'product_uom': False, 'safety_stock': 0.00})
515
product = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
517
v.update({'uom_id': product.uom_id.id})
521
stock_warehouse_order_cycle_line()
523
194
class stock_frequence(osv.osv):
524
195
_name = 'stock.frequence'
525
196
_inherit = 'stock.frequence'