70
71
d_values = {'leadtime': cycle.leadtime,
71
72
'coverage': cycle.order_coverage,
72
73
'safety_time': cycle.safety_stock_time,
73
'consumption_period_from': cycle.consumption_period_from,
74
'consumption_period_to': cycle.consumption_period_to,
74
'safety': cycle.safety_stock,
75
75
'past_consumption': cycle.past_consumption,
76
76
'reviewed_consumption': cycle.reviewed_consumption,
77
77
'manual_consumption': cycle.manual_consumption,}
80
ran_proc.append(cycle.id)
81
for line in cycle.product_ids:
82
# Update the safety stock according to the safety stock defined in the line
83
d_values.update({'safety_stock': line.safety_stock})
84
proc_id = self.create_proc_cycle(cr, uid, cycle, line.product_id.id, location_id, d_values, line)
79
if not cycle.product_id:
81
for p in cycle.product_ids:
82
not_products.append(p.id)
84
product_ids = product_obj.search(cr, uid, [('categ_id', 'child_of', cycle.category_id.id), ('id', 'not in', not_products)])
86
for product in product_obj.browse(cr, uid, product_ids):
87
proc_id = self.create_proc_cycle(cr, uid, cycle, product.id, location_id, d_values, cache=cache)
87
91
created_proc.append(proc_id)
93
proc_id = self.create_proc_cycle(cr, uid, cycle, cycle.product_id.id, location_id, d_values, cache=cache)
96
created_proc.append(proc_id)
89
98
if cycle.frequence_id:
90
freq_obj.write(cr, uid, cycle.frequence_id.id, {'last_run': datetime.now()})
99
freq_obj.write(cr, uid, cycle.frequence_id.id, {'last_run': start_date.strftime('%Y-%m-%d')})
92
created_doc = '''################################
93
Created documents : \n'''
95
102
for proc in proc_obj.browse(cr, uid, created_proc):
96
103
if proc.state == 'exception':
141
139
cycle_obj = self.pool.get('stock.warehouse.order.cycle')
142
140
product_obj = self.pool.get('product.product')
143
141
wf_service = netsvc.LocalService("workflow")
151
145
if isinstance(product_id, (int, long)):
152
146
product_id = [product_id]
154
if d_values.get('past_consumption', False):
155
# If the AMC should be used, compute the period of calculation
156
if not d_values.get('consumption_period_from', False):
157
order_coverage = d_values.get('coverage', 3)
158
d_values.update({'consumption_period_from': (now() + RelativeDate(day=1, months=-round(order_coverage, 1)+1)).strftime('%Y-%m-%d')})
159
if not d_values.get('consumption_period_to', False):
160
d_values.update({'consumption_period_to': (now() + RelativeDate(days=-1, day=1, months=1)).strftime('%Y-%m-%d')})
161
context.update({'from_date': d_values.get('consumption_period_from'), 'to_date': d_values.get('consumption_period_to')})
163
product = product_obj.browse(cr, uid, product_id[0], context=context)
165
newdate = datetime.today().strftime('%Y-%m-%d %H:%M:%S')
166
if line and line.required_date:
167
newdate = line.required_date
169
quantity_to_order = self._compute_quantity(cr, uid, cycle, product, location_id, d_values, context=context)
171
# Create a procurement only if the quantity to order is more than 0.00
172
if quantity_to_order <= 0.00:
175
proc_id = proc_obj.create(cr, uid, {
176
'name': _('Procurement cycle: %s') % (cycle.name,),
177
'origin': cycle.name,
178
'date_planned': newdate,
179
'product_id': product.id,
180
'product_qty': quantity_to_order,
181
'product_uom': product.uom_id.id,
182
'location_id': location_id,
183
'procure_method': 'make_to_order',
185
# Confirm the procurement order
186
wf_service.trg_validate(uid, 'procurement.order', proc_id, 'button_confirm', cr)
187
wf_service.trg_validate(uid, 'procurement.order', proc_id, 'button_check', cr)
188
context.update({'button': 'scheduler'})
189
cycle_obj.write(cr, uid, [cycle.id], {'procurement_id': proc_id}, context=context)
148
product = product_obj.browse(cr, uid, product_id[0])
150
# Enter the stock location in cache to know which products has been already replenish for this location
151
if not cache.get(location_id, False):
152
cache.update({location_id: []})
154
# If a rule already exist for the category of the product or for the product
155
# itself for the same location, we don't create a procurement order
156
#cycle_ids = cycle_obj.search(cr, uid, [('category_id', '=', product.categ_id.id), ('product_id', '=', False), ('location_id', '=', location_id), ('id', '!=', cycle.id)])
157
#cycle2_ids = cycle_obj.search(cr, uid, [('product_id', '=', product.id), ('location_id', '=', location_id), ('id', '!=', cycle.id)])
159
# cr.execute('''SELECT order_cycle_id
160
# FROM order_cycle_product_rel
161
# WHERE order_cycle_id in %s
162
# AND product_id = %s''', (tuple(cycle_ids), product.id))
163
# res = cr.fetchall()
165
# cycle_ids.remove(r[0])
166
#if cycle2_ids or cycle_ids:
170
if product.id not in cache.get(location_id):
171
newdate = datetime.today()
172
quantity_to_order = self._compute_quantity(cr, uid, cycle, product.id, location_id, d_values)
174
if quantity_to_order <= 0:
177
proc_id = proc_obj.create(cr, uid, {
178
'name': _('Automatic Supply: %s') % (cycle.name,),
179
'origin': cycle.name,
180
'date_planned': newdate.strftime('%Y-%m-%d %H:%M:%S'),
181
'product_id': product.id,
182
'product_qty': quantity_to_order,
183
'product_uom': product.uom_id.id,
184
'location_id': location_id,
185
'procure_method': 'make_to_order',
187
wf_service.trg_validate(uid, 'procurement.order', proc_id, 'button_confirm', cr)
188
wf_service.trg_validate(uid, 'procurement.order', proc_id, 'button_check', cr)
189
context.update({'button': 'scheduler'})
190
cycle_obj.write(cr, uid, [cycle.id], {'procurement_id': proc_id}, context=context)
193
cache.get(location_id).append(product.id)
193
def _compute_quantity(self, cr, uid, cycle_id, product, location_id, d_values=None, context=None):
197
def _compute_quantity(self, cr, uid, cycle_id, product_id, location_id, d_values={}, context={}):
195
199
Compute the quantity of product to order like thid :
196
200
[Delivery lead time (from supplier tab of the product or by default or manually overwritten) x Monthly Consumption]
197
201
+ Order coverage (number of months : 3 by default, manually overwritten) x Monthly consumption
198
202
- Projected available quantity
204
product_obj = self.pool.get('product.product')
205
supplier_info_obj = self.pool.get('product.supplierinfo')
206
location_obj = self.pool.get('stock.location')
207
cycle_obj = self.pool.get('stock.warehouse.order.cycle')
208
review_obj = self.pool.get('monthly.review.consumption')
209
review_line_obj = self.pool.get('monthly.review.consumption.line')
211
product = product_obj.browse(cr, uid, product_id)
212
location = location_obj.browse(cr, uid, location_id)
203
# Get the delivery lead time of the product if the leadtime is not defined in rule and no supplier found in product form
204
delivery_leadtime = product.procure_delay and round(int(product.procure_delay)/30.0, 2) or 1
205
# Get the leadtime of the rule if defined
215
# Get the delivery lead time
216
delivery_leadtime = product.seller_delay and product.seller_delay != 'N/A' and round(int(product.seller_delay)/30.0, 2) or 1
206
217
if 'leadtime' in d_values and d_values.get('leadtime', 0.00) != 0.00:
207
218
delivery_leadtime = d_values.get('leadtime')
208
elif product.seller_ids:
209
# Get the supplier lead time if supplier is defined
210
# The seller delay is defined in days, so divide it by 30.0 to have a LT in months
211
delivery_leadtime = product.seller_delay and round(int(product.seller_delay)/30.0, 2) or 1
221
for supplier_info in product.seller_ids:
222
if sequence and supplier_info.sequence < sequence:
223
sequence = supplier_info.sequence
224
delivery_leadtime = round(supplier_info.delay/30.0, 2)
226
sequence = supplier_info.sequence
227
delivery_leadtime = round(supplier_info.delay/30.0, 2)
213
229
# Get the monthly consumption
214
230
monthly_consumption = 0.00
216
232
if 'reviewed_consumption' in d_values and d_values.get('reviewed_consumption'):
217
monthly_consumption = product.reviewed_consumption
218
elif 'past_consumption' in d_values and d_values.get('past_consumption'):
219
monthly_consumption = product.product_amc
233
review_ids = review_obj.search(cr, uid, [], order='period_to', context=context)
234
review_line_ids = review_line_obj.search(cr, uid, [('mrc_id', 'in', review_ids), ('name', '=', product_id)], context=context)
235
for line in review_line_obj.browse(cr, uid, review_line_ids, context=context):
237
if not last_date or last_date < line.mrc_id.period_to:
238
monthly_consumption = line.fmc
239
elif 'monthly_consumption' in d_values and d_values.get('monthly_consumption'):
240
monthly_consumption = product_obj.compute_amc(cr, uid, product.id, context=context)
221
242
monthly_consumption = d_values.get('manual_consumption', 0.00)
223
244
# Get the order coverage
224
order_coverage = d_values.get('coverage', 0.00)
245
order_coverage = d_values.get('coverage', 3)
226
247
# Get the projected available quantity
227
available_qty = self.get_available(cr, uid, product.id, location_id, monthly_consumption, d_values)
248
available_qty = self.get_available(cr, uid, product_id, location_id, monthly_consumption, d_values)
229
250
qty_to_order = (delivery_leadtime * monthly_consumption) + (order_coverage * monthly_consumption) - available_qty
231
if not context.get('get_data', False):
232
res = round(self.pool.get('product.uom')._compute_qty(cr, uid, product.uom_id.id, qty_to_order, product.uom_id.id), 2)
235
if monthly_consumption:
236
delta = available_qty / monthly_consumption * 30
239
req_date = now().strftime('%Y-%m-%d')
241
req_date = (now() + RelativeDateTime(days=delta)).strftime('%Y-%m-%d')
242
res = round(self.pool.get('product.uom')._compute_qty(cr, uid, product.uom_id.id, qty_to_order, product.uom_id.id), 2), req_date
246
def get_available(self, cr, uid, product_id, location_id, monthly_consumption, d_values=None, context=None):
252
return round(self.pool.get('product.uom')._compute_qty(cr, uid, product.uom_id.id, qty_to_order, product.uom_id.id), 2)
255
def get_available(self, cr, uid, product_id, location_id, monthly_consumption, d_values={}, context={}):
248
257
Compute the projected available quantity like this :
249
258
Available stock (real stock - picked reservation)
277
285
picked_resa = product_obj.get_product_available(cr, uid, [product_id], context={'states': ['assigned'],
278
286
'what': ('in, out'),
279
287
'location': location_id,
280
'compute_child': True,})
288
'compute_child': True,
289
'from_date': time.strftime('%Y-%m-%d')})
290
# Get the picked reservation
291
## TODO: To confirm by Magali
292
# picked_reservation = 0.00
294
# for location in location_obj.search(cr, uid, [('location_id', 'child_of', [location_id])]):
295
# for move_id in move_obj.search(cr, uid, [('product_id', '=', product_id), ('location_dest_id', '=', location),
296
# ('state', '!=', 'draft'), ('move_dest_id', '!=', False)]):
297
# move_ids.append(move_id)
299
# for move in move_obj.browse(cr, uid, move_ids):
300
# picked_reservation += move.product_qty
282
available_stock = product.qty_available + picked_resa.get(product.id)
302
available_stock = product.qty_available - picked_resa.get(product.id)
304
#available_stock = real_stock.get(product_id) - picked_reservation
306
# Get the quantity on order
307
## TODO : To confirm by Magali
308
# quantity_on_order = 0.00
310
# for location in location_obj.search(cr, uid, [('location_id', 'child_of', [location_id])]):
311
# for move_id in move_obj.search(cr, uid, [('product_id', '=', product_id), ('location_dest_id', '=', location)]):
312
# move_ids.append(move_id)
314
# for move in move_obj.browse(cr, uid, move_ids):
315
# quantity_on_order += move.product_qty
284
317
quantity_on_order = product_obj.get_product_available(cr, uid, [product_id], context={'states': ['confirmed'],
285
318
'what': ('in, out'),
286
319
'location': location_id,
287
'compute_child': True,})
320
'compute_child': True,
321
'from_date': time.strftime('%Y-%m-%d')})
289
323
# Get the safety stock
290
safety_stock = d_values.get('safety_stock', 0)
324
safety_stock = d_values.get('safety', 0)
292
326
# Get the safety time
293
327
safety_time = d_values.get('safety_time', 0)
295
329
# Get the expiry quantity
296
expiry_quantity = product_obj.get_expiry_qty(cr, uid, product_id, location_id, monthly_consumption, d_values, context=context)
297
expiry_quantity = expiry_quantity and expiry_quantity or 0.00
330
# Set as comment because expiry quantity will be developed in a future sprint
331
expiry_quantity = product_obj.get_expiry_qty(cr, uid, product_id, location_id, monthly_consumption, d_values)
332
expiry_quantity = expiry_quantity and available_stock - expiry_quantity or 0.00
333
#expiry_quantity = 0.00
300
335
# Set this part of algorithm as comments because this algorithm seems to be equal to virtual stock
336
return available_stock + quantity_on_order.get(product.id) - safety_stock - (safety_time * monthly_consumption) - expiry_quantity
301
338
# return product.virtual_available - safety_stock - (safety_time * monthly_consumption) - expiry_quantity
302
return available_stock + quantity_on_order.get(product.id) - safety_stock - (safety_time * monthly_consumption) - expiry_quantity
341
def get_expiry_qty(self, cr, uid, product_id, location_id, monthly_consumption, d_values={}, context={}):
343
Compute the expiry quantities
345
INFO : This method is not use on Sprint1 because the algorithm is
348
product_obj = self.pool.get('product.product')
349
stock_obj = self.pool.get('stock.location')
350
batch_obj = self.pool.get('stock.production.lot')
351
move_obj = self.pool.get('stock.move')
355
location_ids = stock_obj.search(cr, uid, [('location_id', 'child_of', location_id)])
356
available_stock = 0.00
358
# Get all batches for this product
359
batch_ids = batch_obj.search(cr, uid, [('product_id', '=', product_id)], offset=0, limit=None, order='life_date')
360
if len(batch_ids) == 1:
361
# Search all moves with this batch number
362
for location in location_ids:
363
context.update({'location_id': location})
364
available_stock += batch_obj.browse(cr, uid, batch_ids, context=context)[0].stock_available
365
expiry_date = batch_obj.browse(cr, uid, batch_ids)[0].life_date or time.strftime('%Y-%m-%d')
366
nb_month = self.get_diff_date(expiry_date)
367
res = available_stock - (nb_month * monthly_consumption)
369
# Get the stock available for the product
370
for location in location_ids:
371
context.update({'location_id': location})
372
for batch in batch_obj.browse(cr, uid, batch_ids, context=context):
373
available_stock += batch.stock_available
378
for batch in batch_obj.browse(cr, uid, batch_ids):
379
nb_month = self.get_diff_date(batch.life_date)
380
if (nb_month - sum_nb_month) > 0:
381
tmp_qty = (nb_month - sum_nb_month) * monthly_consumption
382
res += available_stock - (last_nb_month * monthly_consumption) - tmp_qty
304
388
def get_diff_date(self, date):