Merge lp:~jfb-tempo-consulting/unifield-server/US-7646 into lp:unifield-server

Proposed by jftempo
Status: Merged
Merged at revision: 5751
Proposed branch: lp:~jfb-tempo-consulting/unifield-server/US-7646
Merge into: lp:unifield-server
Diff against target: 2182 lines (+879/-399) (has conflicts)
21 files modified
bin/addons/consumption_calculation/weekly_forecast_report.py (+46/-46)
bin/addons/mission_stock/mission_stock.py (+20/-7)
bin/addons/msf_doc_import/purchase_order.py (+6/-5)
bin/addons/msf_doc_import/wizard/wizard_po_simulation_screen.py (+39/-18)
bin/addons/msf_order_date/wizard/update_lines.py (+7/-1)
bin/addons/msf_profile/data/patches.xml (+7/-0)
bin/addons/msf_profile/msf_profile.py (+92/-0)
bin/addons/procurement_cycle/replenishment.py (+389/-235)
bin/addons/procurement_cycle/replenishment_view.xml (+38/-33)
bin/addons/procurement_cycle/replenishment_wizard.xml (+2/-2)
bin/addons/procurement_cycle/report/replenishment_inventory_review.mako (+9/-9)
bin/addons/procurement_cycle/report/replenishment_order_calc.mako (+30/-0)
bin/addons/procurement_cycle/report/replenishment_segment.mako (+23/-19)
bin/addons/purchase/purchase_order.py (+6/-14)
bin/addons/purchase/purchase_order_line.py (+49/-0)
bin/addons/purchase/purchase_view.xml (+55/-4)
bin/addons/purchase/purchase_workflow.py (+9/-5)
bin/addons/stock/product.py (+38/-0)
bin/addons/stock/stock_move.py (+9/-0)
bin/addons/stock/stock_view.xml (+1/-0)
bin/osv/orm.py (+4/-1)
Text conflict in bin/addons/msf_profile/data/patches.xml
Text conflict in bin/addons/msf_profile/msf_profile.py
To merge this branch: bzr merge lp:~jfb-tempo-consulting/unifield-server/US-7646
Reviewer Review Type Date Requested Status
UniField Reviewer Team Pending
Review via email: mp+386961@code.launchpad.net
To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bin/addons/consumption_calculation/weekly_forecast_report.py'
2--- bin/addons/consumption_calculation/weekly_forecast_report.py 2019-08-14 09:17:27 +0000
3+++ bin/addons/consumption_calculation/weekly_forecast_report.py 2020-07-27 12:47:56 +0000
4@@ -414,6 +414,10 @@
5 intervals.append((interval_name, interval_from, interval_to))
6 dict_int_from.setdefault(interval_from.strftime('%Y-%m-%d'), interval_name)
7
8+ max_date = fixed_now
9+ if intervals:
10+ max_date = intervals[-1][2]
11+
12 percent_completed = 0.00
13 progress_comment = ""
14 product_ids = []
15@@ -446,7 +450,12 @@
16
17 if len(product_ids) > 0:
18 ##### UFTP-220: Filter this list of products for those only appeared in the selected location of the report, not all product
19- new_cr.execute("select distinct product_id from report_stock_inventory where location_id in %s and product_id in %s and state !='cancel'", (tuple(loc_asked_by_user),tuple(product_ids),) )
20+ new_cr.execute("""
21+ select product_id from (
22+ select product_id from report_stock_inventory where location_id in %(loc)s and product_id in %(p_id)s and state !='cancel' group by product_id
23+ UNION
24+ select product_id from purchase_order_line where location_dest_id in %(loc)s and product_id in %(p_id)s and state in ('validated', 'validated_n', 'sourced_sy', 'sourced_v', 'sourced_n') group by product_id
25+ ) x group by product_id """, {'loc': tuple(loc_asked_by_user), 'p_id': tuple(product_ids)} )
26 product_ids = []
27 for row in new_cr.dictfetchall():
28 product_ids.append(row['product_id'])
29@@ -459,7 +468,7 @@
30 while t < nb_products:
31 # Get consumption, in-pipe and expired quantities for each product
32 product_cons.update(self._get_product_consumption(new_cr, uid, product_ids, location_ids, report, context=context))
33- in_pipe_vals.update(self._get_in_pipe_vals(new_cr, uid, product_ids, location_ids, report, context=context))
34+ in_pipe_vals.update(self._get_in_pipe_vals(new_cr, uid, product_ids, location_ids, report, max_date, context=context))
35 exp_vals.update(self._get_expiry_batch(new_cr, uid, product_cons, location_ids, report, fixed_now=fixed_now, context=context))
36
37 percent_completed = (t/nb_products) * 100
38@@ -766,7 +775,7 @@
39
40 return res
41
42- def _get_in_pipe_vals(self, cr, uid, product_ids, location_ids, report, context=None):
43+ def _get_in_pipe_vals(self, cr, uid, product_ids, location_ids, report, max_date, context=None):
44 """
45 Returns a dictionary with for each product in product_ids, the quantity in-pipe.
46
47@@ -785,14 +794,29 @@
48 context = {}
49
50 res = {}
51-
52 cr.execute("""
53- SELECT product_id, sum(qty) AS qty, date
54+ SELECT product_id, sum(qty) AS qty, date FROM (
55+ SELECT
56+ pol.product_id AS product_id,
57+ sum(pol.product_qty/u1.factor/u2.factor) AS qty,
58+ coalesce(pol.confirmed_delivery_date, pol.date_planned) AS date
59 FROM
60- ((SELECT
61+ purchase_order_line pol
62+ LEFT JOIN product_product p ON p.id = pol.product_id
63+ LEFT JOIN product_template pt ON p.product_tmpl_id = pt.id
64+ LEFT JOIN product_uom u1 ON pol.product_uom = u1.id
65+ LEFT JOIN product_uom u2 ON pt.uom_id = u2.id
66+ WHERE
67+ pol.location_dest_id IN %(location_ids)s AND
68+ pol.product_id IN %(product_ids)s AND
69+ pol.state IN ('validated', 'validated_n', 'sourced_sy', 'sourced_v', 'sourced_n') AND
70+ coalesce(pol.confirmed_delivery_date, pol.date_planned) <= %(max_date)s
71+ GROUP BY pol.product_id, coalesce(pol.confirmed_delivery_date, pol.date_planned)
72+ UNION
73+ SELECT
74 p.id AS product_id,
75 sum(-s.product_qty/u1.factor/u2.factor) AS qty,
76- s.date AS date
77+ date(s.date) AS date
78 FROM
79 stock_move s
80 LEFT JOIN product_product p ON p.id = s.product_id
81@@ -800,29 +824,16 @@
82 LEFT JOIN product_uom u1 ON s.product_uom = u1.id
83 LEFT JOIN product_uom u2 ON pt.uom_id = u2.id
84 WHERE
85- s.location_id IN %(location_ids)s
86- AND
87- s.product_id IN %(product_ids)s
88- AND
89- s.state IN ('assigned', 'confirmed')
90- AND
91- s.id NOT IN
92- (SELECT
93- l.move_dest_id
94- FROM
95- purchase_order_line l
96- LEFT JOIN purchase_order o ON o.id = l.order_id
97- WHERE
98- l.move_dest_id IS NOT NULL
99- AND
100- o.state NOT IN ('approved', 'except_picking', 'except_invoice', 'done')
101- )
102- GROUP BY p.id, s.date)
103+ s.location_id IN %(location_ids)s AND
104+ s.product_id IN %(product_ids)s AND
105+ s.state IN ('assigned', 'confirmed') AND
106+ s.date <= %(max_date)s
107+ GROUP BY p.id, date(s.date)
108 UNION
109- (SELECT
110+ SELECT
111 p.id AS product_id,
112 sum(s.product_qty/u1.factor/u2.factor) AS qty,
113- s.date AS date
114+ date(s.date) AS date
115 FROM
116 stock_move s
117 LEFT JOIN product_product p ON p.id = s.product_id
118@@ -830,29 +841,18 @@
119 LEFT JOIN product_uom u1 ON s.product_uom = u1.id
120 LEFT JOIN product_uom u2 ON pt.uom_id = u2.id
121 WHERE
122- s.location_dest_id IN %(location_ids)s
123- AND
124- s.product_id IN %(product_ids)s
125- AND
126- s.state IN ('assigned', 'confirmed')
127- AND
128- s.id NOT IN
129- (SELECT
130- l.move_dest_id
131- FROM
132- purchase_order_line l
133- LEFT JOIN purchase_order o ON o.id = l.order_id
134- WHERE
135- l.move_dest_id IS NOT NULL
136- AND
137- o.state NOT IN ('approved', 'except_picking', 'except_invoice', 'done')
138- )
139- GROUP BY p.id, s.date))
140+ s.location_dest_id IN %(location_ids)s AND
141+ s.product_id IN %(product_ids)s AND
142+ s.state IN ('assigned', 'confirmed') AND
143+ s.date <= %(max_date)s
144+ GROUP BY p.id, date(s.date)
145+ )
146 AS subrequest
147 GROUP BY product_id, date;
148 """, {
149 'location_ids': tuple(location_ids),
150- 'product_ids': tuple(product_ids)
151+ 'product_ids': tuple(product_ids),
152+ 'max_date': max_date.strftime('%Y-%m-%d'),
153 })
154
155 for r in cr.dictfetchall():
156
157=== modified file 'bin/addons/mission_stock/mission_stock.py'
158--- bin/addons/mission_stock/mission_stock.py 2020-03-02 07:55:11 +0000
159+++ bin/addons/mission_stock/mission_stock.py 2020-07-27 12:47:56 +0000
160@@ -934,13 +934,26 @@
161
162 for report_id in report_ids:
163 # In-Pipe moves
164- cr.execute('''SELECT m.product_id, sum(m.product_qty), m.product_uom, p.name
165- FROM stock_move m
166- LEFT JOIN stock_picking s ON m.picking_id = s.id
167- LEFT JOIN res_partner p ON s.partner_id2 = p.id
168- WHERE s.type = 'in' AND m.state in ('confirmed', 'waiting', 'assigned')
169- GROUP BY m.product_id, m.product_uom, p.name
170- ORDER BY m.product_id''')
171+ cr.execute('''
172+ SELECT product_id, sum(product_qty), uom_id, p_name
173+ FROM (
174+ SELECT pol.product_id as product_id, sum(pol.product_qty) as product_qty, pol.product_uom as uom_id, p.name as p_name
175+ FROM purchase_order_line pol, purchase_order po, res_partner p
176+ WHERE
177+ pol.state in ('validated', 'validated_n', 'sourced_sy', 'sourced_v', 'sourced_n') and
178+ po.id = pol.order_id and
179+ po.partner_id = p.id
180+ GROUP BY pol.product_id, pol.product_uom, p.name
181+ UNION
182+ SELECT m.product_id as product_id, sum(m.product_qty) as product_qty, m.product_uom as uom_id, p.name as p_name
183+ FROM stock_move m
184+ LEFT JOIN stock_picking s ON m.picking_id = s.id
185+ LEFT JOIN res_partner p ON s.partner_id2 = p.id
186+ WHERE
187+ s.type = 'in' AND m.state in ('confirmed', 'waiting', 'assigned')
188+ GROUP BY m.product_id, m.product_uom, p.name
189+ ) x GROUP BY product_id, product_qty, uom_id, p_name
190+ ORDER BY product_id''')
191
192 in_pipe_moves = cr.fetchall()
193 current_product = None
194
195=== modified file 'bin/addons/msf_doc_import/purchase_order.py'
196--- bin/addons/msf_doc_import/purchase_order.py 2020-04-21 16:07:23 +0000
197+++ bin/addons/msf_doc_import/purchase_order.py 2020-07-27 12:47:56 +0000
198@@ -210,9 +210,9 @@
199 if not po_name:
200 raise osv.except_osv(_('Error'), _('No PO name found in the given import file'))
201
202- po_id = self.search(cr, uid, [('name', '=', po_name), ('state', 'in', ['validated', 'validated_p'])], context=context)
203+ po_id = self.search(cr, uid, [('name', '=', po_name), ('state', 'in', ['validated', 'validated_p', 'confirmed', 'confirmed_p'])], context=context)
204 if not po_id:
205- raise osv.except_osv(_('Error'), _('No validated PO found with the name %s') % po_name)
206+ raise osv.except_osv(_('Error'), _('No available PO found with the name %s') % po_name)
207
208 return po_id[0]
209
210@@ -348,9 +348,10 @@
211 if pol.line_number in context.get('line_number_to_confirm', []) or \
212 (pol.external_ref and pol.external_ref in context.get('ext_ref_to_confirm', [])):
213 try:
214- self.pool.get('purchase.order.line').button_confirmed(cr, uid, [pol.id], context=context)
215- cr.commit()
216- nb_pol_confirmed += 1
217+ if pol.state not in ['confirmed', 'done', 'cancel', 'cancel_r']:
218+ self.pool.get('purchase.order.line').button_confirmed(cr, uid, [pol.id], context=context)
219+ cr.commit()
220+ nb_pol_confirmed += 1
221 except:
222 context['rejected_confirmation'] += 1
223 cr.rollback()
224
225=== modified file 'bin/addons/msf_doc_import/wizard/wizard_po_simulation_screen.py'
226--- bin/addons/msf_doc_import/wizard/wizard_po_simulation_screen.py 2020-02-17 11:35:43 +0000
227+++ bin/addons/msf_doc_import/wizard/wizard_po_simulation_screen.py 2020-07-27 12:47:56 +0000
228@@ -1537,7 +1537,7 @@
229 'in_ext_ref': fields.char(size=256, string='External Ref.', readonly=True),
230 'type_change': fields.selection([('', ''), ('error', 'Error'), ('new', 'New'),
231 ('split', 'Split'), ('del', 'Del'),
232- ('ignore', 'Ignore'), ('warning', 'Warning')],
233+ ('ignore', 'Ignore'), ('warning', 'Warning'), ('cdd', 'CDD')],
234 string='Change type', readonly=True),
235 'imp_product_id': fields.many2one('product.product', string='Product',
236 readonly=True),
237@@ -1615,10 +1615,39 @@
238 self.write(cr, uid, [line.id], {'type_change': 'ignore'}, context=context)
239 continue
240
241- if line.po_line_id.state in ('confirmed', 'done'):
242+ if line.po_line_id.state == 'done':
243 write_vals['type_change'] = 'warning'
244 warnings.append(_('PO line has been confirmed and consequently is not editable'))
245
246+ # Delivery Confirmed Date
247+ dcd_value = values[11]
248+ if dcd_value and type(dcd_value) == type(DateTime.now()):
249+ write_vals['imp_dcd'] = dcd_value.strftime('%Y-%m-%d')
250+ elif dcd_value and isinstance(dcd_value, str):
251+ try:
252+ time.strptime(dcd_value, '%Y-%m-%d')
253+ write_vals['imp_dcd'] = dcd_value
254+ except ValueError:
255+ err_msg = _('Incorrect date value for field \'Delivery Confirmed Date\'')
256+ errors.append(err_msg)
257+ write_vals['type_change'] = 'error'
258+ elif dcd_value:
259+ err_msg = _('Incorrect date value for field \'Delivery Confirmed Date\'')
260+ errors.append(err_msg)
261+ write_vals['type_change'] = 'error'
262+
263+ if line.po_line_id.state == 'confirmed':
264+ if write_vals.get('imp_dcd') and write_vals.get('imp_dcd') != line.in_dcd:
265+ if self.pool.get('stock.move').search_exists(cr ,uid, [('purchase_line_id', '=', line.po_line_id.id), ('type', '=', 'in'), ('state', '=', 'done')], context=context):
266+ write_vals['type_change'] = 'warning'
267+ warnings.append(_("IN for line %s has been parially processed, CDD can't be changed") % (line.in_line_number,))
268+ else:
269+ write_vals['type_change'] = 'cdd'
270+ if not write_vals.get('type_change'):
271+ write_vals['type_change'] = 'ignore'
272+ self.write(cr, uid, [line.id], write_vals, context=context)
273+ continue
274+
275 # External Ref.
276 write_vals['imp_external_ref'] = values[1]
277 pol_ids = None
278@@ -1840,22 +1869,6 @@
279 errors.append(err_msg)
280 write_vals['type_change'] = 'error'
281
282- # Delivery Confirmed Date
283- dcd_value = values[11]
284- if dcd_value and type(dcd_value) == type(DateTime.now()):
285- write_vals['imp_dcd'] = dcd_value.strftime('%Y-%m-%d')
286- elif dcd_value and isinstance(dcd_value, str):
287- try:
288- time.strptime(dcd_value, '%Y-%m-%d')
289- write_vals['imp_dcd'] = dcd_value
290- except ValueError:
291- err_msg = _('Incorrect date value for field \'Delivery Confirmed Date\'')
292- errors.append(err_msg)
293- write_vals['type_change'] = 'error'
294- elif dcd_value:
295- err_msg = _('Incorrect date value for field \'Delivery Confirmed Date\'')
296- errors.append(err_msg)
297- write_vals['type_change'] = 'error'
298
299 # ESC Confirmed
300 if write_vals.get('imp_dcd') and line.simu_id.order_id.partner_type == 'esc':
301@@ -1939,6 +1952,14 @@
302 cr.commit()
303 continue
304
305+ if line.type_change == 'cdd':
306+ line_obj.write(cr, uid, [line.po_line_id.id], {'confirmed_delivery_date': line.imp_dcd}, context=context)
307+ in_ids = self.pool.get('stock.move').search(cr ,uid, [('purchase_line_id', '=', line.po_line_id.id), ('type', '=', 'in'), ('state', 'in', ['confirmed', 'assigned'])], context=context)
308+ if in_ids:
309+ self.pool.get('stock.move').write(cr, uid, in_ids, {'date_expected': line.imp_dcd}, context=context)
310+
311+ cr.commit()
312+ continue
313 line_vals = {
314 'product_id': line.imp_product_id.id,
315 'product_uom': line.imp_uom.id,
316
317=== modified file 'bin/addons/msf_order_date/wizard/update_lines.py'
318--- bin/addons/msf_order_date/wizard/update_lines.py 2019-07-22 12:08:20 +0000
319+++ bin/addons/msf_order_date/wizard/update_lines.py 2020-07-27 12:47:56 +0000
320@@ -53,6 +53,8 @@
321
322 for obj in obj_obj.browse(cr, uid, obj_ids, context=context):
323 delivery_requested_date = obj.delivery_requested_date
324+ if type == 'purchase.order' and obj.state != 'draft':
325+ delivery_requested_date = obj.delivery_requested_date_modified
326 delivery_confirmed_date = obj.delivery_confirmed_date
327 stock_take_date = obj.stock_take_date
328
329@@ -153,13 +155,17 @@
330 # working objects
331 obj_ids = context.get('active_ids', [])
332
333+ ftf = ['delivery_requested_date']
334 if obj_type == 'purchase.order':
335 line_obj = self.pool.get('purchase.order.line')
336+ ftf += ['state', 'delivery_requested_date_modified']
337 else:
338 line_obj = self.pool.get('sale.order.line')
339
340- for obj in obj_obj.browse(cr, uid, obj_ids, fields_to_fetch=['delivery_requested_date'], context=context):
341+ for obj in obj_obj.browse(cr, uid, obj_ids, fields_to_fetch=ftf, context=context):
342 requested_date = obj.delivery_requested_date
343+ if obj_type == 'purchase.order' and obj.state != 'draft':
344+ requested_date = obj.delivery_requested_date_modified
345 dom = [('order_id', '=', obj.id), ('state', 'in',['draft', 'validated', 'validated_n'])]
346 if selected and context.get('button_selected_ids'):
347 dom += [('id', 'in', context['button_selected_ids'])]
348
349=== modified file 'bin/addons/msf_profile/data/patches.xml'
350--- bin/addons/msf_profile/data/patches.xml 2020-07-24 12:56:20 +0000
351+++ bin/addons/msf_profile/data/patches.xml 2020-07-27 12:47:56 +0000
352@@ -564,7 +564,14 @@
353 <field name="method">sync_msg_from_itself</field>
354 </record>
355
356+<<<<<<< TREE
357 <!-- UF18.0 -->
358+=======
359+ <record id="us_7646_dest_loc_on_pol" model="patch.scripts">
360+ <field name="method">us_7646_dest_loc_on_pol</field>
361+ </record>
362+
363+>>>>>>> MERGE-SOURCE
364 <record id="us_6544_no_sync_on_forced_out" model="patch.scripts">
365 <field name="method">us_6544_no_sync_on_forced_out</field>
366 </record>
367
368=== modified file 'bin/addons/msf_profile/msf_profile.py'
369--- bin/addons/msf_profile/msf_profile.py 2020-07-24 12:56:20 +0000
370+++ bin/addons/msf_profile/msf_profile.py 2020-07-27 12:47:56 +0000
371@@ -53,6 +53,7 @@
372 }
373
374 # UF18.0
375+<<<<<<< TREE
376 def us_7412_set_fy_closure_settings(self, cr, uid, *a, **b):
377 """
378 Sets the Fiscal Year Closure options depending on the OC.
379@@ -73,6 +74,97 @@
380 SET has_move_regular_bs_to_0 = %s, has_book_pl_results = %s;
381 """
382 cr.execute(update_company, (has_move_regular_bs_to_0, has_book_pl_results))
383+=======
384+ def us_7646_dest_loc_on_pol(self, cr, uid, *a, **b):
385+ data_obj = self.pool.get('ir.model.data')
386+ try:
387+ service_id = self.pool.get('stock.location').get_service_location(cr, uid)
388+ conso_id = data_obj.get_object_reference(cr, uid, 'stock_override', 'stock_location_non_stockable')[1]
389+ log_id = data_obj.get_object_reference(cr, uid, 'stock_override', 'stock_location_logistic')[1]
390+ med_id = data_obj.get_object_reference(cr, uid, 'msf_config_locations', 'stock_location_medical')[1]
391+ cross_dock_id = data_obj.get_object_reference(cr, uid, 'msf_cross_docking', 'stock_location_cross_docking')[1]
392+ except:
393+ return True
394+
395+ cr.execute('''update purchase_order_line set location_dest_id = %s where id in (
396+ select pol.id
397+ from purchase_order_line pol, purchase_order po, product_product prod, product_template tmpl
398+ where
399+ pol.order_id = po.id and
400+ pol.product_id = prod.id and
401+ tmpl.id = prod.product_tmpl_id and
402+ tmpl.type = 'service_recep' and
403+ coalesce(po.cross_docking_ok, 'f') = 'f' and
404+ pol.state in ('validated', 'validated_n', 'sourced_sy', 'sourced_v', 'sourced_n')
405+ ) ''', (service_id,))
406+ self._logger.warn('POL loc_dest service on %s lines' % (cr.rowcount,))
407+
408+ cr.execute('''update purchase_order_line set location_dest_id = %s where id in (
409+ select pol.id
410+ from purchase_order_line pol, purchase_order po, product_product prod, product_template tmpl
411+ where
412+ pol.order_id = po.id and
413+ pol.product_id = prod.id and
414+ tmpl.id = prod.product_tmpl_id and
415+ tmpl.type = 'consu' and
416+ pol.state in ('validated', 'validated_n', 'sourced_sy', 'sourced_v', 'sourced_n')
417+ ) ''', (conso_id,))
418+ self._logger.warn('POL loc_dest conso on %s lines' % (cr.rowcount,))
419+
420+ cr.execute('''update purchase_order_line set location_dest_id = so.location_requestor_id
421+ from sale_order so, sale_order_line sol, stock_location loc, product_product prod, product_template tmpl
422+ where
423+ so.id = sol.order_id and
424+ loc.id = so.location_requestor_id and
425+ loc.usage != 'customer' and
426+ so.procurement_request = 't' and
427+ purchase_order_line.linked_sol_id = sol.id and
428+ purchase_order_line.product_id = prod.id and
429+ tmpl.id = prod.product_tmpl_id and
430+ tmpl.type != 'consu' and
431+ purchase_order_line.state in ('validated', 'validated_n', 'sourced_sy', 'sourced_v', 'sourced_n') ''', (conso_id,))
432+ self._logger.warn('POL loc_dest IR on %s lines' % (cr.rowcount,))
433+
434+ cr.execute('''update purchase_order_line set location_dest_id = %s from purchase_order po
435+ where
436+ po.id = purchase_order_line.order_id and
437+ purchase_order_line.state in ('validated', 'validated_n', 'sourced_sy', 'sourced_v', 'sourced_n') and
438+ coalesce(po.cross_docking_ok, 'f') = 't' and
439+ location_dest_id is NULL ''', (cross_dock_id,))
440+ self._logger.warn('POL loc_dest Cross Dock on %s lines' % (cr.rowcount,))
441+
442+ cr.execute('''update purchase_order_line set location_dest_id = %s
443+ from product_product prod, product_template tmpl, product_nomenclature nom
444+ where
445+ purchase_order_line.product_id = prod.id and
446+ tmpl.nomen_manda_0 = nom.id and
447+ nom.name = 'MED' and
448+ tmpl.id = prod.product_tmpl_id and
449+ location_dest_id is NULL and
450+ purchase_order_line.state in ('validated', 'validated_n', 'sourced_sy', 'sourced_v', 'sourced_n') ''', (med_id,))
451+ self._logger.warn('POL loc_dest MED on %s lines' % (cr.rowcount,))
452+
453+ cr.execute('''update purchase_order_line set location_dest_id = %s
454+ from product_product prod, product_template tmpl, product_nomenclature nom
455+ where
456+ purchase_order_line.product_id = prod.id and
457+ tmpl.nomen_manda_0 = nom.id and
458+ nom.name = 'LOG' and
459+ tmpl.id = prod.product_tmpl_id and
460+ location_dest_id is NULL and
461+ purchase_order_line.state in ('validated', 'validated_n', 'sourced_sy', 'sourced_v', 'sourced_n') ''', (log_id,))
462+ self._logger.warn('POL loc_dest LOG on %s lines' % (cr.rowcount,))
463+ return True
464+
465+
466+ def sync_msg_from_itself(self, cr, uid, *a, **b):
467+ instance = self.pool.get('res.users').browse(cr, uid, uid, fields_to_fetch=['company_id']).company_id.instance_id
468+ if not instance:
469+ return True
470+ cr.execute(''' update sync_client_message_received set run='t', manually_ran='t', log='Set manually to run without execution', manually_set_run_date=now() where run='f' and source=%s ''', (instance.instance, ))
471+ self._logger.warn('Set %s self sync messages as Run' % (cr.rowcount,))
472+ return True
473+>>>>>>> MERGE-SOURCE
474
475
476 def us_6544_no_sync_on_forced_out(self, cr, uid, *a, **b):
477
478=== modified file 'bin/addons/procurement_cycle/replenishment.py'
479--- bin/addons/procurement_cycle/replenishment.py 2020-06-09 09:28:55 +0000
480+++ bin/addons/procurement_cycle/replenishment.py 2020-07-27 12:47:56 +0000
481@@ -15,6 +15,7 @@
482 import decimal_precision as dp
483 import math
484
485+life_cycle_status = [('active', _('Active')), ('new', _('New')), ('replaced', _('Replaced')), ('replacing', _('Replacing')), ('phasingout', _('Phasing Out')), ('activereplacing', _('Active-Replacing'))]
486 class replenishment_location_config(osv.osv):
487 _name = 'replenishment.location.config'
488 _description = 'Location Configuration'
489@@ -387,7 +388,7 @@
490 for prod in cr.fetchall():
491 self.pool.get('replenishment.segment.line').create(cr, uid, {'state': 'active', 'product_id': prod[0], 'segment_id': hidden_seg}, context=context)
492
493- # in pipe at coo / only project
494+ # move in pipe at coo / only project
495 cr.execute('''
496 select move.product_id from
497 stock_move move, stock_picking p
498@@ -410,6 +411,27 @@
499 for prod in cr.fetchall():
500 self.pool.get('replenishment.segment.line').create(cr, uid, {'state': 'active', 'product_id': prod[0], 'segment_id': hidden_seg}, context=context)
501
502+ # PO lines
503+ cr.execute('''
504+ select pol.product_id from
505+ purchase_order_line pol
506+ where
507+ pol.location_dest_id in %s and
508+ pol.state in ('validated', 'validated_n', 'sourced_sy', 'sourced_v', 'sourced_n') and
509+ pol.product_id not in (
510+ select
511+ seg_line.product_id
512+ from replenishment_segment_line seg_line, replenishment_segment seg
513+ where
514+ seg.state in ('draft', 'complete') and seg_line.segment_id = seg.id and seg.location_config_id = %s
515+
516+ )
517+ group by pol.product_id
518+ ''', (tuple(amc_location_ids), loc_config.id))
519+
520+ for prod in cr.fetchall():
521+ self.pool.get('replenishment.segment.line').create(cr, uid, {'state': 'active', 'product_id': prod[0], 'segment_id': hidden_seg}, context=context)
522+
523 return True
524
525 replenishment_location_config()
526@@ -693,15 +715,28 @@
527
528 loc_ids = [x.id for x in seg.local_location_ids]
529 cr.execute('''
530- select l.product_id, min(m.date) from stock_move m, stock_picking p, replenishment_segment_line l
531+ select prod_id, min(date) from (
532+ select pol.product_id as prod_id, min(coalesce(pol.confirmed_delivery_date, pol.date_planned)) as date
533+ from
534+ purchase_order_line pol, replenishment_segment_line l
535+ where
536+ l.product_id = pol.product_id and
537+ l.segment_id = %(seg_id)s and
538+ pol.state in ('validated', 'validated_n', 'sourced_sy', 'sourced_v', 'sourced_n') and
539+ location_dest_id in %(location_id)s
540+ group by pol.product_id
541+ UNION
542+ select l.product_id as prod_id, min(m.date) as date from stock_move m, stock_picking p, replenishment_segment_line l
543 where
544 m.picking_id = p.id and
545 m.state in ('assigned', 'confirmed') and
546- m.location_dest_id in %s and
547+ m.location_dest_id in %(location_id)s and
548 l.product_id = m.product_id and
549- l.segment_id = %s
550+ l.segment_id = %(seg_id)s
551 group by l.product_id
552- ''', (tuple(loc_ids), seg.id)
553+ ) x
554+ group by prod_id
555+ ''', {'location_id': tuple(loc_ids), 'seg_id': seg.id}
556 )
557 prod_eta = {}
558 for x in cr.fetchall():
559@@ -810,6 +845,7 @@
560 total_month_oc = 0
561
562 valid_rr_fmc = True
563+ valid_line = True
564 before_today = False
565 before_oc = False
566 before_rdd = False
567@@ -823,15 +859,29 @@
568 if seg.rule == 'cycle':
569
570 cr.execute('''
571- select m.date, sum(product_qty) from stock_move m, stock_picking p
572+ select date, sum(qty) from (
573+ select coalesce(pol.confirmed_delivery_date, pol.date_planned) as date, sum(pol.product_qty) as qty
574+ from
575+ purchase_order_line pol
576+ where
577+ pol.product_id=%(product_id)s and
578+ pol.state in ('validated', 'validated_n', 'sourced_sy', 'sourced_v', 'sourced_n') and
579+ location_dest_id in %(location_id)s and
580+ coalesce(pol.confirmed_delivery_date, pol.date_planned) <= %(date)s
581+ group by coalesce(pol.confirmed_delivery_date, pol.date_planned)
582+ UNION
583+
584+ select date(m.date) as date, sum(product_qty) as product_qty from stock_move m, stock_picking p
585 where
586 m.picking_id = p.id and
587 m.state in ('assigned', 'confirmed') and
588- m.location_dest_id in %s and
589- m.product_id = %s and
590- m.date <= %s
591- group by m.date
592- ''', (tuple(loc_ids), line.product_id.id, oc)
593+ m.location_dest_id in %(location_id)s and
594+ m.product_id = %(product_id)s and
595+ m.date <= %(date)s
596+ group by date(m.date)
597+ ) x
598+ group by date
599+ ''', {'location_id': tuple(loc_ids), 'product_id': line.product_id.id, 'date': oc}
600 )
601 pipe_data = {}
602 for x in cr.fetchall():
603@@ -967,6 +1017,8 @@
604 else:
605 valid_rr_fmc = before_today and before_rdd
606
607+ valid_line = valid_rr_fmc
608+
609 if review_id and loc_ids and seg.rule == 'cycle':
610 total_expired_qty = sum_line[line.id].get('expired_rdd_oc', 0) + sum_line[line.id].get('expired_before_rdd', 0)
611 for nb_month in range(1, line.segment_id.projected_view+1):
612@@ -996,19 +1048,31 @@
613 # sum fmc from today to ETC - qty in stock
614 #qty_lacking = max(0, total_fmc - sum_line.get(line.id, {}).get('pas_no_pipe_no_fmc', 0))
615 if total_month_oc+total_month:
616- ss_stock = seg.safety_stock * ((total_fmc_oc+total_fmc)/(total_month_oc+total_month))
617- if total_month and pas and pas <= line.buffer_qty + seg.safety_stock * (total_fmc / total_month):
618- wmsg = _('Projected use of safety stock/buffer')
619- warnings.append(wmsg)
620- warnings_html.append('<span title="%s">%s</span>' % (misc.escape_html(wmsg), misc.escape_html(_('SS used'))))
621- if qty_lacking:
622- wmsg = _('Stock-out before next RDD')
623- warnings.append(wmsg)
624- warnings_html.append('<span title="%s">%s</span>' % (misc.escape_html(wmsg), misc.escape_html(_('Stock out'))))
625+ if line.status == 'replacing':
626+ ss_stock = seg.safety_stock * ((total_fmc_oc+total_fmc)/(line.segment_id.order_coverage+int(line.segment_id.total_lt)/30.44))
627+ else:
628+ ss_stock = seg.safety_stock * ((total_fmc_oc+total_fmc)/(total_month_oc+total_month))
629+
630+ if line.status != 'phasingout':
631+ if total_month and pas and pas <= line.buffer_qty + seg.safety_stock * (total_fmc / total_month):
632+ wmsg = _('Projected use of safety stock/buffer')
633+ warnings.append(wmsg)
634+ warnings_html.append('<span title="%s">%s</span>' % (misc.escape_html(wmsg), misc.escape_html(_('SS used'))))
635+ if qty_lacking:
636+ wmsg = _('Stock-out before next RDD')
637+ warnings.append(wmsg)
638+ warnings_html.append('<span title="%s">%s</span>' % (misc.escape_html(wmsg), misc.escape_html(_('Stock out'))))
639+
640+ if line.status == 'activereplacing':
641+ replaced_lack = lacking_by_prod.get(line.replaced_product_id.id)
642+ if replaced_lack:
643+ wmsg = _('SODate of linked products is %s') % (self.pool.get('date.tools').get_date_formatted(cr, uid, datetime=replaced_lack.strftime('%Y-%m-%d'), context=context))
644+ warnings.append(wmsg)
645+ warnings_html.append('<span title="%s">%s</span>' % (misc.escape_html(wmsg), misc.escape_html(_('Replaced SO'))))
646
647 if lacking:
648 qty_lacking_needed_by = today + relativedelta(days=month_of_supply*30.44)
649- if review_id and round(sum_line.get(line.id, {}).get('expired_before_rdd',0)):
650+ if line.status != 'phasingout' and review_id and round(sum_line.get(line.id, {}).get('expired_before_rdd',0)):
651 wmsg = _('Forecasted expiries')
652 warnings.append(wmsg)
653 warnings_html.append('<span title="%s">%s</span>' % (misc.escape_html(wmsg), misc.escape_html(_('Expiries'))))
654@@ -1016,39 +1080,57 @@
655 if line.status == 'replaced':
656 proposed_order_qty = 0
657 qty_lacking = 0
658+ elif line.status == 'phasingout':
659+ proposed_order_qty = 0
660+ qty_lacking = False
661 else:
662 proposed_order_qty = max(0, total_fmc_oc + ss_stock + line.buffer_qty + sum_line.get(line.id, {}).get('expired_rdd_oc',0) - pas - line.pipeline_between_rdd_oc)
663
664 elif seg.rule == 'minmax':
665- proposed_order_qty = max(0, line.max_qty - sum_line.get(line.id, {}).get('real_stock') + sum_line.get(line.id, {}).get('reserved_stock_qty') + sum_line.get(line.id, {}).get('expired_qty_before_eta', 0) - line.pipeline_before_rdd)
666+ valid_line = bool(line.min_qty) and bool(line.max_qty)
667+ if line.status in ('phasingout', 'replaced'):
668+ proposed_order_qty = 0
669+ qty_lacking = False
670+ else:
671+ proposed_order_qty = max(0, line.max_qty - sum_line.get(line.id, {}).get('real_stock') + sum_line.get(line.id, {}).get('reserved_stock_qty') + sum_line.get(line.id, {}).get('expired_qty_before_eta', 0) - line.pipeline_before_rdd)
672
673- if line.status != 'new' and sum_line.get(line.id, {}).get('real_stock') - sum_line.get(line.id, {}).get('expired_qty_before_eta') <= line.min_qty:
674- if sum_line.get(line.id, {}).get('expired_qty_before_eta'):
675- wmsg = _('Alert: "inventory – batches expiring before ETA <= Min"')
676- warnings.append(wmsg)
677- warnings_html.append('<span title="%s">%s</span>' % (misc.escape_html(wmsg), misc.escape_html(_('Expiries'))))
678- else:
679- wmsg = _('Alert: "inventory <= Min"')
680- warnings.append(wmsg)
681- warnings_html.append('<span title="%s">%s</span>' % (misc.escape_html(wmsg), misc.escape_html(_('Insufficient'))))
682+ qty_lacking = line.min_qty - sum_line.get(line.id, {}).get('real_stock') + sum_line.get(line.id, {}).get('reserved_stock_qty') - sum_line.get(line.id, {}).get('expired_qty_before_eta')
683+ if line.status != 'new' and sum_line.get(line.id, {}).get('real_stock') - sum_line.get(line.id, {}).get('expired_qty_before_eta') <= line.min_qty:
684+ if sum_line.get(line.id, {}).get('expired_qty_before_eta'):
685+ wmsg = _('Alert: "inventory – batches expiring before ETA <= Min"')
686+ warnings.append(wmsg)
687+ warnings_html.append('<span title="%s">%s</span>' % (misc.escape_html(wmsg), misc.escape_html(_('Expiries'))))
688+ else:
689+ wmsg = _('Alert: "inventory <= Min"')
690+ warnings.append(wmsg)
691+ warnings_html.append('<span title="%s">%s</span>' % (misc.escape_html(wmsg), misc.escape_html(_('Insufficient'))))
692 else:
693- proposed_order_qty = line.auto_qty
694+ valid_line = bool(line.auto_qty)
695+ if line.status in ('phasingout', 'replaced'):
696+ proposed_order_qty = 0
697+ else:
698+ proposed_order_qty = line.auto_qty
699
700 if not valid_rr_fmc:
701 wmsg = _('Invalid FMC')
702 warnings.append(wmsg)
703 warnings_html.append('<span title="%s">%s</span>' % (misc.escape_html(wmsg), misc.escape_html(_('FMC'))))
704
705- if review_id and month_of_supply and month_of_supply*30.44 > (seg_rdd-today).days + line.segment_id.safety_stock*30.44:
706- wmsg = _('Excess Stock')
707- warnings.append(wmsg)
708- warnings_html.append('<span title="%s">%s</span>' % (misc.escape_html(wmsg), misc.escape_html(_('Excess'))))
709-
710- if review_id and seg.hidden:
711- wmsg = _('Product is not in any related segment, only in stock / pipeline of location')
712- warnings.append(wmsg)
713- warnings_html.append('<span title="%s">%s</span>' % (misc.escape_html(wmsg), misc.escape_html(_('No Segment'))))
714-
715+ if line.status != 'phasingout':
716+ if review_id and month_of_supply and month_of_supply*30.44 > (seg_rdd-today).days + line.segment_id.safety_stock*30.44:
717+ wmsg = _('Excess Stock')
718+ warnings.append(wmsg)
719+ warnings_html.append('<span title="%s">%s</span>' % (misc.escape_html(wmsg), misc.escape_html(_('Excess'))))
720+
721+ if review_id and seg.hidden:
722+ wmsg = _('Product is not in any related segment, only in stock / pipeline of location')
723+ warnings.append(wmsg)
724+ warnings_html.append('<span title="%s">%s</span>' % (misc.escape_html(wmsg), misc.escape_html(_('No Segment'))))
725+
726+ if prod_eta.get(line.product_id.id) and prod_eta.get(line.product_id.id) < time.strftime('%Y-%m-%d'):
727+ wmsg = _('Pipeline in the past')
728+ warnings.append(wmsg)
729+ warnings_html.append('<span title="%s">%s</span>' % (misc.escape_html(wmsg), misc.escape_html(_('Delay'))))
730
731 #lacking_by_prod[line.product_id.id] = qty_lacking_needed_by
732 line_data = {
733@@ -1058,17 +1140,22 @@
734 'pipeline_qty': round(line.pipeline_before_rdd or 0),
735 'eta_for_next_pipeline': prod_eta.get(line.product_id.id, False),
736 'reserved_stock_qty': sum_line.get(line.id, {}).get('reserved_stock_qty'),
737- 'qty_lacking': False if seg.rule !='cycle' else round(qty_lacking),
738+ 'qty_lacking': False if seg.rule not in ('cycle', 'minmax') else round(qty_lacking),
739 'qty_lacking_needed_by': qty_lacking_needed_by and qty_lacking_needed_by.strftime('%Y-%m-%d') or False,
740 'expired_qty_before_cons': False if seg.rule !='cycle' else round(sum_line.get(line.id, {}).get('expired_before_rdd',0)),
741 'expired_qty_before_eta': round(sum_line.get(line.id, {}).get('expired_qty_before_eta',0)),
742 'warning': False,
743 'warning_html': False,
744- 'valid_rr_fmc': valid_rr_fmc,
745+ 'valid_rr_fmc': valid_line,
746 'status': line.status,
747 'open_loan': sum_line.get(line.id, {}).get('open_loan', False),
748 'open_donation': sum_line.get(line.id, {}).get('open_donation', False),
749+ 'auto_qty': line.auto_qty if seg.rule =='auto' else False,
750+ 'buffer_qty': line.buffer_qty if seg.rule =='cycle' else False,
751+ 'min_max': '',
752 }
753+ if seg.rule == 'minmax':
754+ line_data['min_max'] = '%d / %d' % (line.min_qty, line.max_qty)
755
756 # order_cacl
757 if not review_id:
758@@ -1083,11 +1170,13 @@
759 'projected_stock_qty': round(pas),
760 'cost_price': line.product_id.standard_price,
761 })
762+
763 order_calc_line.create(cr, uid, line_data, context=context)
764
765 else: # review
766 if seg.hidden:
767 line_data['valid_rr_fmc'] = False
768+ line_data['paired_product_id'] = line.replacing_product_id and line.replacing_product_id.id or line.replaced_product_id and line.replaced_product_id.id
769 std_dev_hmc = False
770 amc = False
771 if total_fmc_hmc.get(line.product_id.id):
772@@ -1138,8 +1227,6 @@
773 'rule': seg.rule,
774 'min_qty': line.min_qty,
775 'max_qty': line.max_qty,
776- 'auto_qty': line.auto_qty,
777- 'buffer_qty': line.buffer_qty,
778 'safety_stock': seg.safety_stock * coeff,
779 'pas_ids': detailed_pas,
780 'segment_line_id': line.id,
781@@ -1251,164 +1338,178 @@
782 return True
783
784 def import_lines(self, cr, uid, ids, context=None):
785+ ''' import replenishment.segment '''
786+
787 product_obj = self.pool.get('product.product')
788 seg_line_obj = self.pool.get('replenishment.segment.line')
789+ wizard_obj = self.pool.get('physical.inventory.import.wizard')
790
791 seg = self.browse(cr, uid, ids[0], context=context)
792 if not seg.file_to_import:
793 raise osv.except_osv(_('Error'), _('Nothing to import.'))
794- file_data = SpreadsheetXML(xmlstring=base64.decodestring(seg.file_to_import))
795-
796- existing_line = {}
797- for line in seg.line_ids:
798- existing_line[line.product_id.default_code] =line.id
799-
800- idx = -1
801-
802- status = {
803- _('Active'): 'active',
804- _('New'): 'new',
805- _('Replaced'): 'replaced',
806- _('Replacing'): 'replacing',
807- }
808- error = []
809- code_created = {}
810- created = 0
811- updated = 0
812- ignored = 0
813- for row in file_data.getRows():
814- idx += 1
815- if idx < 8:
816- # header
817- continue
818- line_error = []
819- prod_code = row.cells[0].data
820- if not prod_code:
821- continue
822- prod_code = prod_code.strip()
823-
824- cells_nb = len(row.cells)
825-
826- data_towrite = {
827- 'status': cells_nb > 3 and status.get(row.cells[3].data and row.cells[3].data.strip()),
828- 'replacing_product_id': False,
829- 'replaced_product_id': False,
830- 'buffer_qty': False,
831- 'min_qty': 0,
832- 'max_qty': 0,
833- 'auto_qty': 0
834- }
835- for fmc in range(1, 13):
836- data_towrite.update({
837- 'rr_fmc_%d' % fmc: False,
838- 'rr_fmc_from_%d' % fmc: False,
839- 'rr_fmc_to_%d' % fmc: False,
840- })
841-
842-
843- col_replacing = 4
844- col_replaced = 5
845- col_buffer_min_qty = 8
846- col_first_fmc = 9
847-
848- if cells_nb > col_replacing and row.cells[col_replacing].data and row.cells[col_replacing].data.strip():
849- if data_towrite['status'] != 'replaced':
850- line_error.append(_('Line %d: you can not set a Replacing product on this line, please change the satus or remove the replacing product') % (idx+1, ))
851- else:
852- replacing_id = product_obj.search(cr, uid, [('default_code', '=ilike', row.cells[col_replacing].data.strip())], context=context)
853- if not replacing_id:
854- line_error.append(_('Line %d: replacing product code %s not found') % (idx+1, row.cells[col_replacing].data))
855- else:
856- data_towrite['replacing_product_id'] = replacing_id[0]
857- elif data_towrite['status'] == 'replaced' and not data_towrite['replacing_product_id']:
858- line_error.append(_('Line %d: replacing product must be set !') % (idx+1, ))
859-
860- if cells_nb > col_replaced and row.cells[col_replaced].data and row.cells[col_replaced].data.strip():
861- if data_towrite['status'] != 'replacing':
862- line_error.append(_('Line %d: you can not set a Replaced product on this line, please change the satus or remove the replaced product') % (idx+1, ))
863- else:
864- replaced_id = product_obj.search(cr, uid, [('default_code', '=ilike', row.cells[col_replaced].data.strip())], context=context)
865- if not replaced_id:
866- line_error.append(_('Line %d: replaced product code %s not found') % (idx+1, row.cells[col_replaced].data))
867- else:
868- data_towrite['replaced_product_id'] = replaced_id[0]
869- elif data_towrite['status'] == 'replacing' and not data_towrite['replaced_product_id']:
870- line_error.append(_('Line %d: replaced product must be set !') % (idx+1, ))
871-
872-
873- if cells_nb > col_buffer_min_qty and seg.rule == 'cycle':
874- if row.cells[col_buffer_min_qty].data and not isinstance(row.cells[col_buffer_min_qty].data, (int, long, float)):
875- line_error.append(_('Line %d: Buffer Qty must be a number, found %s') % (idx+1, row.cells[col_buffer_min_qty].data))
876- else:
877- data_towrite['buffer_qty'] = row.cells[col_buffer_min_qty].data
878+
879+ try:
880+ file_data = SpreadsheetXML(xmlstring=base64.decodestring(seg.file_to_import))
881+
882+ existing_line = {}
883+ for line in seg.line_ids:
884+ existing_line[line.product_id.default_code] = line.id
885+
886+ idx = -1
887+
888+ status = dict([(x[1], x[0]) for x in life_cycle_status])
889+ error = []
890+ code_created = {}
891+ created = 0
892+ updated = 0
893+ ignored = 0
894+ for row in file_data.getRows():
895+ idx += 1
896+ if idx < 8:
897+ # header
898+ continue
899+
900+ if not len(row.cells):
901+ # empty line
902+ continue
903+
904+ line_error = []
905+ prod_code = row.cells[0].data
906+ if not prod_code:
907+ continue
908+ prod_code = prod_code.strip()
909+
910+ cells_nb = len(row.cells)
911+
912+ data_towrite = {
913+ 'status': cells_nb > 3 and status.get(row.cells[3].data and row.cells[3].data.strip()),
914+ 'replacing_product_id': False,
915+ 'replaced_product_id': False,
916+ 'buffer_qty': False,
917+ 'min_qty': 0,
918+ 'max_qty': 0,
919+ 'auto_qty': 0
920+ }
921 for fmc in range(1, 13):
922- if cells_nb - 1 >= col_first_fmc and row.cells[col_first_fmc].data:
923- if cells_nb - 1 < col_first_fmc+1:
924- line_error.append(_('Line %d: FMC FROM %d, date expected') % (idx+1, fmc))
925- continue
926- if not row.cells[col_first_fmc+1].type == 'datetime':
927- line_error.append(_('Line %d: FMC FROM %d, date is not valid, found %s') % (idx+1, fmc, row.cells[col_first_fmc+1].data))
928- continue
929- if cells_nb - 1 < col_first_fmc+2:
930- line_error.append(_('Line %d: FMC TO %d, date expected') % (idx+1, fmc))
931- continue
932- if not row.cells[col_first_fmc+2].data or row.cells[col_first_fmc+2].type != 'datetime':
933- line_error.append(_('Line %d: FMC TO %d, date is not valid, found %s') % (idx+1, fmc, row.cells[col_first_fmc+2].data))
934- continue
935- if not isinstance(row.cells[col_first_fmc].data, (int, long, float)):
936- line_error.append(_('Line %d: FMC %d, number expected, found %s') % (idx+1, fmc, row.cells[col_first_fmc].data))
937- continue
938+ data_towrite.update({
939+ 'rr_fmc_%d' % fmc: False,
940+ 'rr_fmc_from_%d' % fmc: False,
941+ 'rr_fmc_to_%d' % fmc: False,
942+ })
943+
944+
945+ col_replacing = 4
946+ col_replaced = 5
947+ col_buffer_min_qty = 8
948+ col_first_fmc = 9
949+
950+ if cells_nb > col_replacing and row.cells[col_replacing].data and row.cells[col_replacing].data.strip():
951+ if data_towrite['status'] not in ('replaced', 'phasingout'):
952+ line_error.append(_('Line %d: you can not set a Replacing product on this line, please change the satus or remove the replacing product') % (idx+1, ))
953+ else:
954+ replacing_id = product_obj.search(cr, uid, [('default_code', '=ilike', row.cells[col_replacing].data.strip())], context=context)
955+ if not replacing_id:
956+ line_error.append(_('Line %d: replacing product code %s not found') % (idx+1, row.cells[col_replacing].data))
957+ else:
958+ data_towrite['replacing_product_id'] = replacing_id[0]
959+ elif data_towrite['status'] == 'replaced' and not data_towrite['replacing_product_id']:
960+ line_error.append(_('Line %d: replacing product must be set !') % (idx+1, ))
961+
962+ if cells_nb > col_replaced and row.cells[col_replaced].data and row.cells[col_replaced].data.strip():
963+ if data_towrite['status'] not in ('replacing', 'activereplacing'):
964+ line_error.append(_('Line %d: you can not set a Replaced product on this line, please change the satus or remove the replaced product') % (idx+1, ))
965+ else:
966+ replaced_id = product_obj.search(cr, uid, [('default_code', '=ilike', row.cells[col_replaced].data.strip())], context=context)
967+ if not replaced_id:
968+ line_error.append(_('Line %d: replaced product code %s not found') % (idx+1, row.cells[col_replaced].data))
969+ else:
970+ data_towrite['replaced_product_id'] = replaced_id[0]
971+ elif data_towrite['status'] in ('replacing', 'activereplacing') and not data_towrite['replaced_product_id']:
972+ line_error.append(_('Line %d: replaced product must be set !') % (idx+1, ))
973+
974+
975+ if cells_nb > col_buffer_min_qty and seg.rule == 'cycle':
976+ if row.cells[col_buffer_min_qty].data and not isinstance(row.cells[col_buffer_min_qty].data, (int, long, float)):
977+ line_error.append(_('Line %d: Buffer Qty must be a number, found %s') % (idx+1, row.cells[col_buffer_min_qty].data))
978+ else:
979+ data_towrite['buffer_qty'] = row.cells[col_buffer_min_qty].data
980+ for fmc in range(1, 13):
981+ if cells_nb - 1 >= col_first_fmc and row.cells[col_first_fmc].data:
982+ from_data = False
983+ fmc_data = row.cells[col_first_fmc].data
984+ if fmc == 1:
985+ if cells_nb - 1 < col_first_fmc+1:
986+ line_error.append(_('Line %d: FMC FROM %d, date expected') % (idx+1, fmc))
987+ continue
988+ if not row.cells[col_first_fmc+1].type == 'datetime':
989+ line_error.append(_('Line %d: FMC FROM %d, date is not valid, found %s') % (idx+1, fmc, row.cells[col_first_fmc+1].data))
990+ continue
991+ from_data = row.cells[col_first_fmc+1].data.strftime('%Y-%m-%d')
992+ col_first_fmc += 1
993+
994+ if cells_nb - 1 < col_first_fmc+1:
995+ line_error.append(_('Line %d: FMC TO %d, date expected') % (idx+1, fmc))
996+ continue
997+ if not row.cells[col_first_fmc+1].data or row.cells[col_first_fmc+1].type != 'datetime':
998+ line_error.append(_('Line %d: FMC TO %d, date is not valid, found %s') % (idx+1, fmc, row.cells[col_first_fmc+1].data))
999+ continue
1000+ if not isinstance(fmc_data, (int, long, float)):
1001+ line_error.append(_('Line %d: FMC %d, number expected, found %s') % (idx+1, fmc, fmc_data))
1002+ continue
1003+ data_towrite.update({
1004+ 'rr_fmc_%d' % fmc: fmc_data,
1005+ 'rr_fmc_from_%d' % fmc:from_data,
1006+ 'rr_fmc_to_%d' % fmc: row.cells[col_first_fmc+1].data.strftime('%Y-%m-%d'),
1007+ })
1008+ col_first_fmc += 2
1009+ elif cells_nb > col_buffer_min_qty and seg.rule == 'minmax':
1010+ if not row.cells[col_buffer_min_qty] or not isinstance(row.cells[col_buffer_min_qty].data, (int, long, float)):
1011+ line_error.append(_('Line %d: Min Qty, number expected, found %s') % (idx+1, row.cells[col_buffer_min_qty].data))
1012+ elif not row.cells[col_buffer_min_qty+1] or not isinstance(row.cells[col_buffer_min_qty+1].data, (int, long, float)):
1013+ line_error.append(_('Line %d: Max Qty, number expected, found %s') % (idx+1, row.cells[col_buffer_min_qty+1].data))
1014+ elif row.cells[col_buffer_min_qty+1].data < row.cells[col_buffer_min_qty].data:
1015+ line_error.append(_('Line %d: Max Qty (%s) must be larger than Min Qty (%s)') % (idx+1, row.cells[col_buffer_min_qty+1].data, row.cells[col_buffer_min_qty].data))
1016+ else:
1017 data_towrite.update({
1018- 'rr_fmc_%d' % fmc: row.cells[col_first_fmc].data,
1019- 'rr_fmc_from_%d' % fmc: row.cells[col_first_fmc+1].data.strftime('%Y-%m-%d'),
1020- 'rr_fmc_to_%d' % fmc: row.cells[col_first_fmc+2].data.strftime('%Y-%m-%d'),
1021+ 'min_qty': row.cells[col_buffer_min_qty].data,
1022+ 'max_qty': row.cells[col_buffer_min_qty+1].data,
1023 })
1024- col_first_fmc += 3
1025- elif cells_nb > col_buffer_min_qty and seg.rule == 'minmax':
1026- if not row.cells[col_buffer_min_qty] or not isinstance(row.cells[col_buffer_min_qty].data, (int, long, float)):
1027- line_error.append(_('Line %d: Min Qty, number expected, found %s') % (idx+1, row.cells[col_buffer_min_qty].data))
1028- elif not row.cells[col_buffer_min_qty+1] or not isinstance(row.cells[col_buffer_min_qty+1].data, (int, long, float)):
1029- line_error.append(_('Line %d: Max Qty, number expected, found %s') % (idx+1, row.cells[col_buffer_min_qty+1].data))
1030- elif row.cells[col_buffer_min_qty+1].data < row.cells[col_buffer_min_qty].data:
1031- line_error.append(_('Line %d: Max Qty (%s) must be larger than Min Qty (%s)') % (idx+1, row.cells[col_buffer_min_qty+1].data, row.cells[col_buffer_min_qty].data))
1032- else:
1033- data_towrite.update({
1034- 'min_qty': row.cells[col_buffer_min_qty].data,
1035- 'max_qty': row.cells[col_buffer_min_qty+1].data,
1036- })
1037- elif cells_nb > col_buffer_min_qty:
1038- if not row.cells[col_buffer_min_qty] or not isinstance(row.cells[col_buffer_min_qty].data, (int, long, float)):
1039- line_error.append(_('Line %d: Auto Supply Qty, number expected, found %s') % (idx+1, row.cells[col_buffer_min_qty].data))
1040- else:
1041- data_towrite['auto_qty'] = row.cells[col_buffer_min_qty].data
1042-
1043- if prod_code not in existing_line:
1044- prod_id = product_obj.search(cr, uid, [('default_code', '=ilike', prod_code)], context=context)
1045- if not prod_id:
1046- line_error.append(_('Line %d: product code %s not found') % (idx+1, prod_code))
1047- else:
1048- if prod_id[0] in code_created:
1049- line_error.append(_('Line %d: product code %s already defined in the file') % (idx+1, prod_code))
1050-
1051- code_created[prod_id[0]] = True
1052- data_towrite['product_id'] = prod_id[0]
1053- data_towrite['segment_id'] = seg.id
1054- else:
1055- line_id = existing_line[prod_code]
1056-
1057- if line_error:
1058- error += line_error
1059- ignored += 1
1060- continue
1061- if 'product_id' in data_towrite:
1062- seg_line_obj.create(cr, uid, data_towrite, context=context)
1063- created += 1
1064- else:
1065- seg_line_obj.write(cr, uid, line_id, data_towrite, context=context)
1066- updated += 1
1067+ elif cells_nb > col_buffer_min_qty:
1068+ if not row.cells[col_buffer_min_qty] or not isinstance(row.cells[col_buffer_min_qty].data, (int, long, float)):
1069+ line_error.append(_('Line %d: Auto Supply Qty, number expected, found %s') % (idx+1, row.cells[col_buffer_min_qty].data))
1070+ else:
1071+ data_towrite['auto_qty'] = row.cells[col_buffer_min_qty].data
1072+
1073+ if prod_code not in existing_line:
1074+ prod_id = product_obj.search(cr, uid, [('default_code', '=ilike', prod_code)], context=context)
1075+ if not prod_id:
1076+ line_error.append(_('Line %d: product code %s not found') % (idx+1, prod_code))
1077+ else:
1078+ if prod_id[0] in code_created:
1079+ line_error.append(_('Line %d: product code %s already defined in the file') % (idx+1, prod_code))
1080+
1081+ code_created[prod_id[0]] = True
1082+ data_towrite['product_id'] = prod_id[0]
1083+ data_towrite['segment_id'] = seg.id
1084+ else:
1085+ line_id = existing_line[prod_code]
1086+
1087+ if line_error:
1088+ error += line_error
1089+ ignored += 1
1090+ continue
1091+ if 'product_id' in data_towrite:
1092+ seg_line_obj.create(cr, uid, data_towrite, context=context)
1093+ created += 1
1094+ else:
1095+ seg_line_obj.write(cr, uid, line_id, data_towrite, context=context)
1096+ updated += 1
1097+
1098+ except Exception, e:
1099+ cr.rollback()
1100+ return wizard_obj.message_box_noclose(cr, uid, title=_('Importation errors'), message=_("Unexpected error during import:\n%s") % (misc.get_traceback(e), ))
1101
1102 self.write(cr, uid, seg.id, {'file_to_import': False}, context=context)
1103- wizard_obj = self.pool.get('physical.inventory.import.wizard')
1104 if error:
1105 error.insert(0, _('%d line(s) created, %d line(s) updated, %d line(s) in error') % (created, updated, ignored))
1106 return wizard_obj.message_box_noclose(cr, uid, title=_('Importation errors'), message='\n'.join(error))
1107@@ -1426,7 +1527,7 @@
1108 if self.pool.get('replenishment.segment.line').search_exist(cr, uid, [('segment_id', 'in', ids), ('status', '=', 'replaced'), ('replacing_product_id', '=', False)], context=context):
1109 raise osv.except_osv(_('Warning'), _('Please complete Replacing products with a paired product, see red lines'))
1110
1111- if self.pool.get('replenishment.segment.line').search_exist(cr, uid, [('segment_id', 'in', ids), ('status', '=', 'replacing'), ('replaced_product_id', '=', False)], context=context):
1112+ if self.pool.get('replenishment.segment.line').search_exist(cr, uid, [('segment_id', 'in', ids), ('status', 'in', ['replacing', 'activereplacing']), ('replaced_product_id', '=', False)], context=context):
1113 raise osv.except_osv(_('Warning'), _('Please complete Replaced products with a paired product, see red lines'))
1114
1115 cr.execute('''
1116@@ -1710,13 +1811,19 @@
1117
1118 prod_obj = self.pool.get('product.product')
1119 for seg_id in segment:
1120- # TODO JFB RR: compute_child ?
1121- for prod_id in prod_obj.browse(cr, uid, segment[seg_id]['prod_seg_line'].keys(), fields_to_fetch=['incoming_qty'], context={'to_date': segment[seg_id]['to_date_rdd'], 'location': segment[seg_id]['location_ids']}):
1122- ret[segment[seg_id]['prod_seg_line'][prod_id.id]]['pipeline_before_rdd'] = prod_id.incoming_qty
1123-
1124- if not inv_review:
1125+ # compute_child ?
1126+ if 'pipeline_before_rdd' in field_name:
1127+ for prod_id in prod_obj.browse(cr, uid, segment[seg_id]['prod_seg_line'].keys(), fields_to_fetch=['incoming_qty'], context={'to_date': segment[seg_id]['to_date_rdd'], 'location': segment[seg_id]['location_ids']}):
1128+ ret[segment[seg_id]['prod_seg_line'][prod_id.id]]['pipeline_before_rdd'] = prod_id.incoming_qty
1129+
1130+ for prod_id, qty in prod_obj.get_pipeline_from_po(cr, uid, segment[seg_id]['prod_seg_line'].keys(), to_date=segment[seg_id]['to_date_rdd'], location_ids=segment[seg_id]['location_ids']).iteritems():
1131+ ret[segment[seg_id]['prod_seg_line'][prod_id]]['pipeline_before_rdd'] += qty
1132+
1133+ if not inv_review and 'pipeline_between_rdd_oc' in field_name:
1134 for prod_id in prod_obj.browse(cr, uid, segment[seg_id]['prod_seg_line'].keys(), fields_to_fetch=['incoming_qty'], context={'from_strict_date': segment[seg_id]['to_date_rdd'], 'to_date': segment[seg_id]['to_date_oc'], 'location': segment[seg_id]['location_ids']}):
1135 ret[segment[seg_id]['prod_seg_line'][prod_id.id]]['pipeline_between_rdd_oc'] = prod_id.incoming_qty
1136+ for prod_id, qty in prod_obj.get_pipeline_from_po(cr, uid, segment[seg_id]['prod_seg_line'].keys(), from_date=segment[seg_id]['to_date_rdd'], to_date=segment[seg_id]['to_date_oc'], location_ids=segment[seg_id]['location_ids']).iteritems():
1137+ ret[segment[seg_id]['prod_seg_line'][prod_id]]['pipeline_between_rdd_oc'] += qty
1138
1139 return ret
1140
1141@@ -1750,7 +1857,7 @@
1142 ret = {}
1143 for _id in ids:
1144 ret[_id] = False
1145- for _id in self.search(cr, uid, [('id', 'in', ids), ('status', 'in', ['replaced', 'replacing'])], context=context):
1146+ for _id in self.search(cr, uid, [('id', 'in', ids), ('status', 'in', ['replaced', 'replacing', 'phasingout', 'activereplacing'])], context=context):
1147 ret[_id] = True
1148 return ret
1149
1150@@ -1760,6 +1867,7 @@
1151 ret[_id] = {'warning': False, 'warning_html': ''}
1152
1153
1154+ # has stock for new prod ?
1155 new_ids = self.search(cr, uid, [('id', 'in', ids), ('status', '=', 'new')], context=context)
1156 if new_ids:
1157 for line in self.browse(cr, uid, new_ids, fields_to_fetch=['real_stock'], context=context):
1158@@ -1770,23 +1878,41 @@
1159 'warning_html': '<img src="/openerp/static/images/stock/gtk-dialog-warning.png" title="%s" class="warning"/> <div>%s</div> ' % (misc.escape_html(warn), _('New?'))
1160 }
1161
1162+
1163+ # has pipe for replaced, phasingout statuses ?
1164 cr.execute('''
1165- select line.id from
1166+ select l_id, l_status from (
1167+ select line.id as l_id, line.status as l_status from
1168+ purchase_order_line pol, replenishment_segment_line line
1169+ where
1170+ pol.product_id = line.product_id and
1171+ line.id in %(ids)s and
1172+ pol.state in ('validated', 'validated_n', 'sourced_sy', 'sourced_v', 'sourced_n') and
1173+ line.status in ('replaced', 'phasingout')
1174+ UNION
1175+ select line.id as l_id, line.status as l_status from
1176 stock_move m, stock_picking p, replenishment_segment_line line
1177 where
1178 m.picking_id = p.id and
1179 m.product_id = line.product_id and
1180- line.id in %s and
1181+ line.id in %(ids)s and
1182 (p.type = 'in' or p.type = 'internal' and p.subtype = 'sysint') and
1183 m.state in ('confirmed','waiting','assigned') and
1184- line.status = 'replaced'
1185+ line.status in ('replaced', 'phasingout')
1186 group by line.id
1187- ''', (tuple(ids), ))
1188+ ) x group by l_id, l_status
1189+ ''', {'ids': tuple(ids)})
1190+
1191 for line in cr.fetchall():
1192 warn = _('Product has pipeline - check status!')
1193+ if line[1] == 'replaced':
1194+ error = _('Replaced?')
1195+ else:
1196+ error = _('Phased out?')
1197+
1198 ret[line[0]] = {
1199 'warning': warn,
1200- 'warning_html': '<img src="/openerp/static/images/stock/gtk-dialog-warning.png" title="%s" class="warning"/> <div>%s</div> ' % (misc.escape_html(warn), _('Replaced?'))
1201+ 'warning_html': '<img src="/openerp/static/images/stock/gtk-dialog-warning.png" title="%s" class="warning"/> <div>%s</div> ' % (misc.escape_html(warn), error)
1202 }
1203 return ret
1204
1205@@ -1798,7 +1924,7 @@
1206 'in_main_list': fields.function(_get_main_list, type='boolean', method=True, string='Prim. prod. list'),
1207 'status_tooltip': fields.function(_get_status_tooltip, type='char', method=True, string='Paired product'),
1208 'display_paired_icon': fields.function(_get_display_paired_icon, type='boolean', method=True, string='Display paired icon'),
1209- 'status': fields.selection([('active', 'Active'), ('new', 'New'), ('replaced', 'Replaced'), ('replacing', 'Replacing')], string='RR Lifecycle'),
1210+ 'status': fields.selection(life_cycle_status, string='RR Lifecycle'),
1211 'min_qty': fields.float('Min Qty', related_uom='uom_id'),
1212 'max_qty': fields.float('Max Qty', related_uom='uom_id'),
1213 'auto_qty': fields.float('Auto. Supply Qty', related_uom='uom_id'),
1214@@ -1862,21 +1988,16 @@
1215 return True
1216 for line in self.browse(cr, uid, line_ids, context=context):
1217 prev_to = False
1218- empty = 0
1219 for x in range(1, 13):
1220 rr_fmc = getattr(line, 'rr_fmc_%d'%x)
1221 rr_from = getattr(line, 'rr_fmc_from_%d'%x)
1222 rr_to = getattr(line, 'rr_fmc_to_%d'%x)
1223 if rr_from:
1224- if empty:
1225- error.append(_('%s, FMC FROM %d is not set, you can\'t have gap in FMC (%s is set)') % (line.product_id.default_code, empty, x))
1226- continue
1227 rr_from = datetime.strptime(rr_from, '%Y-%m-%d')
1228 if rr_from.day != 1:
1229 error.append(_('%s, FMC FROM %d must start the 1st day of the month') % (line.product_id.default_code, x))
1230 if not rr_to:
1231 if not rr_fmc:
1232- empty = x
1233 continue
1234 error.append(_("%s, FMC TO %d can't be empty if FMC from is set") % (line.product_id.default_code, x))
1235 else:
1236@@ -1892,8 +2013,6 @@
1237 if prev_to > rr_from:
1238 error.append(_("%s, FMC FROM %d must be later than FMC TO %d") % (line.product_id.default_code, x, x-1))
1239 prev_to = rr_to
1240- elif not empty:
1241- empty = x
1242 if error:
1243 raise osv.except_osv(_('Error'), _('Please correct the following FMC values:\n%s') % ("\n".join(error)))
1244
1245@@ -1940,19 +2059,25 @@
1246 'status': 'active',
1247 }
1248
1249- def _remove_paired_product(self, cr, uid, vals, context=None):
1250+ def _clean_data(self, cr, uid, vals, context=None):
1251 if vals and 'status' in vals:
1252- if vals['status'] != 'replacing':
1253+ if vals['status'] not in ('replacing', 'activereplacing'):
1254 vals['replaced_product_id'] = False
1255- if vals['status'] != 'replaced':
1256+ if vals['status'] not in ('replaced', 'phasingout'):
1257 vals['replacing_product_id'] = False
1258+ for x in range(1, 12):
1259+ if vals.get('rr_fmc_to_%d'%x):
1260+ try:
1261+ vals['rr_fmc_from_%d'%(x+1)] = (datetime.strptime(vals['rr_fmc_to_%d'%x], '%Y-%m-%d') + relativedelta(days=1)).strftime('%Y-%m-%d')
1262+ except:
1263+ pass
1264
1265 def create(self, cr, uid, vals, context=None):
1266- self._remove_paired_product(cr, uid, vals, context=context)
1267+ self._clean_data(cr, uid, vals, context=context)
1268 return super(replenishment_segment_line, self).create(cr, uid, vals, context=context)
1269
1270 def write(self, cr, uid, ids, vals, context=None):
1271- self._remove_paired_product(cr, uid, vals, context=context)
1272+ self._clean_data(cr, uid, vals, context=context)
1273 return super(replenishment_segment_line, self).write(cr, uid, ids, vals, context=context)
1274
1275 def create_multiple_lines(self, cr, uid, parent_id, product_ids, context=None):
1276@@ -1979,13 +2104,14 @@
1277 return {'msg': "Warning, duplicate products already in Segment have been ignored.\nProducts in duplicate:\n - %s" % '\n - '.join(exist_code)}
1278 return True
1279
1280- def change_fmc(selc, cr, uid, ids, ch_type, nb, value, context=None):
1281- if not value:
1282+ def change_fmc(selc, cr, uid, ids, ch_type, nb, date_str, update_next, context=None):
1283+ if not date_str:
1284 return {}
1285
1286 msg = False
1287+ value = {}
1288 try:
1289- fmc_date = datetime.strptime(value, '%Y-%m-%d')
1290+ fmc_date = datetime.strptime(date_str, '%Y-%m-%d')
1291 except:
1292 return {}
1293 if ch_type == 'from' and fmc_date.day != 1:
1294@@ -1993,11 +2119,13 @@
1295 elif ch_type == 'to':
1296 if fmc_date + relativedelta(months=1, day=1, days=-1) != fmc_date:
1297 msg = _('FMC TO %s must be the last day of the month') % (nb, )
1298+ if update_next:
1299+ value = {'rr_fmc_from_%s'%(int(nb)+1): fmc_date and (fmc_date + relativedelta(days=1)).strftime('%Y-%m-%d') or False}
1300
1301 if msg:
1302- return {'warning': {'message': msg}}
1303+ return {'warning': {'message': msg}, 'value': value}
1304
1305- return {}
1306+ return {'value': value}
1307
1308 def set_paired_product(self, cr, uid, ids, context=None):
1309
1310@@ -2419,6 +2547,8 @@
1311 }
1312
1313 def import_lines(self, cr, uid, ids, context=None):
1314+ ''' import replenishment.order_calc '''
1315+
1316 calc_line_obj = self.pool.get('replenishment.order_calc.line')
1317
1318 calc = self.browse(cr, uid, ids[0], context=context)
1319@@ -2431,11 +2561,11 @@
1320 existing_line[line.product_id.default_code] = line.id
1321
1322 if calc.rule == 'cycle':
1323+ qty_col = 16
1324+ comment_col = 19
1325+ elif calc.rule in ('auto', 'minmax'):
1326 qty_col = 14
1327 comment_col = 17
1328- elif calc.rule in ('auto', 'minmax'):
1329- qty_col = 12
1330- comment_col = 15
1331 idx = -1
1332
1333 error = []
1334@@ -2446,6 +2576,9 @@
1335 # header
1336 continue
1337
1338+ if not len(row.cells):
1339+ continue
1340+
1341 prod_code = row.cells[0].data
1342 if not prod_code:
1343 continue
1344@@ -2556,16 +2689,16 @@
1345 'order_calc_id': fields.many2one('replenishment.order_calc', 'Order Calc', required=1, select=1),
1346 'product_id': fields.many2one('product.product', 'Product Code', select=1, required=1, readonly=1),
1347 'product_description': fields.related('product_id', 'name', string='Description', type='char', size=64, readonly=True, select=True, write_relate=False),
1348- 'status': fields.selection([('active', 'Active'), ('new', 'New'), ('replaced', 'Replaced'), ('replacing', 'Replacing')], string='Life cycle status', readony=1),
1349+ 'status': fields.selection(life_cycle_status, string='Life cycle status', readony=1),
1350 'uom_id': fields.related('product_id', 'uom_id', string='UoM', type='many2one', relation='product.uom', readonly=True, select=True, write_relate=False),
1351 'in_main_list': fields.boolean('Prim. prod. list', readonly=1),
1352- 'valid_rr_fmc': fields.boolean('Valid FMC', readonly=1),
1353+ 'valid_rr_fmc': fields.boolean('Valid', readonly=1),
1354 'real_stock': fields.float('Real Stock', readonly=1, related_uom='uom_id'),
1355 'pipeline_qty': fields.float('Pipeline Qty', readonly=1, related_uom='uom_id'),
1356 'eta_for_next_pipeline': fields.date('ETA for Next Pipeline', readonly=1),
1357 'reserved_stock_qty': fields.float('Reserved Stock Qty', readonly=1, related_uom='uom_id'),
1358 'projected_stock_qty': fields.float('Projected Stock Level', readonly=1, related_uom='uom_id'),
1359- 'qty_lacking': fields.float('Qty lacking before next RDD', readonly=1, related_uom='uom_id'),
1360+ 'qty_lacking': fields.float_null('Qty lacking before next RDD', readonly=1, related_uom='uom_id', null_value='N/A'),
1361 'qty_lacking_needed_by': fields.date('Qty lacking needed by', readonly=1),
1362 'open_loan': fields.boolean('Open Loan', readonly=1),
1363 'open_donation': fields.boolean('Donations pending', readonly=1),
1364@@ -2578,6 +2711,9 @@
1365 'order_qty_comment': fields.char('Order Qty Comment', size=512),
1366 'warning': fields.text('Warning', readonly='1'),
1367 'warning_html': fields.text('Warning', readonly='1'),
1368+ 'buffer_qty': fields.float_null('Buffer Qty', related_uom='uom_id', readonly=1),
1369+ 'auto_qty': fields.float('Auto. Supply Qty', related_uom='uom_id', readonly=1),
1370+ 'min_max': fields.char('Min/Max', size=128, readonly=1),
1371 }
1372
1373 replenishment_order_calc_line()
1374@@ -2625,15 +2761,31 @@
1375 'inv_review': inv_review,
1376 }
1377
1378+ def pipeline_po(self, cr, uid, ids, context=None):
1379+ data = self._selected_data(cr, uid, ids, context=context)
1380+
1381+ product_ids = [x.id for x in data['products']]
1382+ product_code = [x.default_code for x in data['products']]
1383+
1384+
1385+ res = self.pool.get('ir.actions.act_window').open_view_from_xmlid(cr, uid, 'purchase.purchase_line_pipeline_action', ['tree'], new_tab=True, context=context)
1386+ res['domain'] = ['&', '&', ('location_dest_id', 'in', data['location_ids']), ('state', 'in', ['validated', 'validated_n', 'sourced_sy', 'sourced_v', 'sourced_n']), ('product_id', 'in', product_ids)]
1387+ res['name'] = _('Pipeline %s: %s') % (data['inv_review'].location_config_id.name, ', '.join(product_code))
1388+ res['nodestroy'] = True
1389+ res['target'] = 'new'
1390+ return res
1391+
1392 def pipeline(self, cr, uid, ids, context=None):
1393 data = self._selected_data(cr, uid, ids, context=context)
1394
1395 product_ids = [x.id for x in data['products']]
1396 product_code = [x.default_code for x in data['products']]
1397
1398- res = self.pool.get('ir.actions.act_window').open_view_from_xmlid(cr, uid, 'stock.action_move_form3', ['tree', 'form'], new_tab=True, context=context)
1399+ res = self.pool.get('ir.actions.act_window').open_view_from_xmlid(cr, uid, 'stock.action_move_form3', ['tree', 'form'], context=context)
1400 res['domain'] = ['&', '&', ('location_dest_id', 'in', data['location_ids']), ('state', 'in', ['confirmed', 'assigned']), ('product_id', 'in', product_ids)]
1401 res['name'] = _('Pipeline %s: %s') % (data['inv_review'].location_config_id.name, ', '.join(product_code))
1402+ res['nodestroy'] = True
1403+ res['target'] = 'new'
1404 return res
1405
1406 def stock_by_location(self, cr, uid, ids, context=None):
1407@@ -2657,18 +2809,20 @@
1408 'product_id': fields.many2one('product.product', 'Product Code', select=1, required=1), # OC
1409 'product_description': fields.related('product_id', 'name', string='Description', type='char', size=64, readonly=True, select=True, write_relate=False), # OC
1410 'uom_id': fields.related('product_id', 'uom_id', string='UoM', type='many2one', relation='product.uom', readonly=True, select=True, write_relate=False), # OC
1411- 'status': fields.selection([('active', 'Active'), ('new', 'New'), ('replaced', 'Replaced'), ('replacing', 'Replacing')], string='Life cycle status'), # OC
1412+ 'status': fields.selection(life_cycle_status, string='Life cycle status'), # OC
1413+ 'paired_product_id': fields.many2one('product.product', 'Replacing/Replaced product'),
1414 'primay_product_list': fields.char('Primary Product List', size=512), # OC
1415 'rule': fields.selection([('cycle', 'Order Cycle'), ('minmax', 'Min/Max'), ('auto', 'Automatic Supply')], string='Replenishment Rule (Order quantity)', required=1), #Seg
1416- 'min_qty': fields.float('Min Qty', related_uom='uom_id'), # Seg line
1417- 'max_qty': fields.float('Max Qty', related_uom='uom_id'), # Seg line
1418- 'auto_qty': fields.float('Auto. Supply Qty', related_uom='uom_id'), # Seg line
1419+ 'min_qty': fields.float_null('Min Qty', related_uom='uom_id'), # Seg line
1420+ 'max_qty': fields.float_null('Max Qty', related_uom='uom_id'), # Seg line
1421+ 'auto_qty': fields.float_null('Auto. Supply Qty', related_uom='uom_id'), # Seg line
1422 'buffer_qty': fields.float_null('Buffer Qty', related_uom='uom_id'), # Seg line
1423+ 'min_max': fields.char('Min / Max', size=128),
1424 'safety_stock': fields.integer('Safety Stock'), # Seg
1425 'segment_ref_name': fields.char('Segment Ref/Name', size=512), # Seg
1426 'rr_fmc_avg': fields.float_null('RR-FMC (average for period)', null_value='N/A'),
1427 'rr_amc': fields.float('RR-AMC'),
1428- 'valid_rr_fmc': fields.boolean('Valid FMC', readonly=1), # OC
1429+ 'valid_rr_fmc': fields.boolean('Valid', readonly=1), # OC
1430 'real_stock': fields.float('Real Stock', readonly=1, related_uom='uom_id'), # OC
1431 'pipeline_qty': fields.float('Pipeline Qty', readonly=1, related_uom='uom_id'), # OC
1432 'reserved_stock_qty': fields.float('Reserved Stock Qty', readonly=1, related_uom='uom_id'),# OC
1433
1434=== modified file 'bin/addons/procurement_cycle/replenishment_view.xml'
1435--- bin/addons/procurement_cycle/replenishment_view.xml 2020-06-02 14:41:41 +0000
1436+++ bin/addons/procurement_cycle/replenishment_view.xml 2020-07-27 12:47:56 +0000
1437@@ -286,7 +286,7 @@
1438 <button name="add_multiple_lines" type="object" icon="gtk-add" string="Add multiple products" colspan="8" />
1439 </group>
1440 <field name="line_ids" colspan="4" nolabel="1" default_get="rule=rule" scroll="1" attrs="{'readonly': [('state', '!=', 'draft')]}" o2m_selectable="2">
1441- <tree editable="top" editable_style="new" colors="red: not status or (display_paired_icon and not status_tooltip)">
1442+ <tree editable="top" editable_style="new" colors="red: not status or (display_paired_icon and not status_tooltip and status!='phasingout')">
1443 <field name="product_id" filter_selector="1" />
1444 <field name="product_description" displayon="noteditable" filter_selector="1" />
1445 <field name="uom_id" invisible="1" />
1446@@ -302,14 +302,12 @@
1447 <field name="auto_qty" invisible="context.get('rule')!='auto'"/>
1448 <field name="buffer_qty" invisible="context.get('rule')!='cycle'"/>
1449 <field name="rr_fmc_1" invisible="context.get('rule')!='cycle'" nolabel="1"/>
1450- <field name="rr_fmc_from_1" invisible="context.get('rule')!='cycle'" attrs="{'required': [('rr_fmc_1', '!=', False)]}" on_change="change_fmc('from', '1', rr_fmc_from_1)" nolabel="1"/>
1451- <field name="rr_fmc_to_1" invisible="context.get('rule')!='cycle'" attrs="{'required': [('rr_fmc_1', '!=', False)]}" on_change="change_fmc('to', '1', rr_fmc_to_1)" nolabel="1"/>
1452+ <field name="rr_fmc_from_1" invisible="context.get('rule')!='cycle'" attrs="{'required': [('rr_fmc_1', '!=', False)]}" on_change="change_fmc('from', '1', rr_fmc_from_1, False)" nolabel="1"/>
1453+ <field name="rr_fmc_to_1" invisible="context.get('rule')!='cycle'" attrs="{'required': [('rr_fmc_1', '!=', False)]}" on_change="change_fmc('to', '1', rr_fmc_to_1, False)" nolabel="1"/>
1454 <field name="rr_fmc_2" invisible="context.get('rule')!='cycle'" nolabel="1"/>
1455- <field name="rr_fmc_from_2" invisible="context.get('rule')!='cycle'" attrs="{'required': [('rr_fmc_2', '!=', False)]}" on_change="change_fmc('from', '2', rr_fmc_from_2)" nolabel="1"/>
1456- <field name="rr_fmc_to_2" invisible="context.get('rule')!='cycle'" attrs="{'required': [('rr_fmc_2', '!=', False)]}" on_change="change_fmc('to', '2', rr_fmc_to_2)" nolabel="1"/>
1457+ <field name="rr_fmc_to_2" invisible="context.get('rule')!='cycle'" attrs="{'required': [('rr_fmc_2', '!=', False)]}" on_change="change_fmc('to', '2', rr_fmc_to_2, False)" nolabel="1"/>
1458 <field name="rr_fmc_3" invisible="context.get('rule')!='cycle'" nolabel="1"/>
1459- <field name="rr_fmc_from_3" invisible="context.get('rule')!='cycle'" attrs="{'required': [('rr_fmc_3', '!=', False)]}" on_change="change_fmc('from', '3', rr_fmc_from_3)" nolabel="1"/>
1460- <field name="rr_fmc_to_3" invisible="context.get('rule')!='cycle'" attrs="{'required': [('rr_fmc_3', '!=', False)]}" on_change="change_fmc('to', '3', rr_fmc_to_3)" nolabel="1"/>
1461+ <field name="rr_fmc_to_3" invisible="context.get('rule')!='cycle'" attrs="{'required': [('rr_fmc_3', '!=', False)]}" on_change="change_fmc('to', '3', rr_fmc_to_3, False)" nolabel="1"/>
1462 <field name= "list_fmc" invisible="context.get('rule')!='cycle'" displayon="notedition"/>
1463 <button name="openform" special="openform" string="Edit 12 FMC" invisible="context.get('rule')!='cycle'" attrs="{'invisible': [('state', '!=', 'draft')]}" icon="terp-go-week" />
1464 <field name="warning_html" widget="html_text" displayon="noteditable"/>
1465@@ -333,41 +331,41 @@
1466 <separator string="To" colspan="1" />
1467
1468 <field name="rr_fmc_1" invisible="context.get('rule')!='cycle'" />
1469- <field name="rr_fmc_from_1" invisible="context.get('rule')!='cycle'" attrs="{'required': [('rr_fmc_1', '!=', False)]}" on_change="change_fmc('from', '1', rr_fmc_from_1)" nolabel="1"/>
1470- <field name="rr_fmc_to_1" invisible="context.get('rule')!='cycle'" attrs="{'required': [('rr_fmc_1', '!=', False)]}" on_change="change_fmc('to', '1', rr_fmc_to_1)" nolabel="1"/>
1471+ <field name="rr_fmc_from_1" invisible="context.get('rule')!='cycle'" attrs="{'required': [('rr_fmc_1', '!=', False)]}" on_change="change_fmc('from', '1', rr_fmc_from_1, False)" nolabel="1"/>
1472+ <field name="rr_fmc_to_1" invisible="context.get('rule')!='cycle'" attrs="{'required': [('rr_fmc_1', '!=', False)]}" on_change="change_fmc('to', '1', rr_fmc_to_1, True)" nolabel="1"/>
1473 <field name="rr_fmc_2" invisible="context.get('rule')!='cycle'" />
1474- <field name="rr_fmc_from_2" invisible="context.get('rule')!='cycle'" attrs="{'required': [('rr_fmc_2', '!=', False)]}" on_change="change_fmc('from', '2', rr_fmc_from_2)" nolabel="1"/>
1475- <field name="rr_fmc_to_2" invisible="context.get('rule')!='cycle'" attrs="{'required': [('rr_fmc_2', '!=', False)]}" on_change="change_fmc('to', '2', rr_fmc_to_2)" nolabel="1"/>
1476+ <field name="rr_fmc_from_2" invisible="context.get('rule')!='cycle'" readonly="1" nolabel="1"/>
1477+ <field name="rr_fmc_to_2" invisible="context.get('rule')!='cycle'" attrs="{'required': [('rr_fmc_2', '!=', False)]}" on_change="change_fmc('to', '2', rr_fmc_to_2, True)" nolabel="1"/>
1478 <field name="rr_fmc_3" invisible="context.get('rule')!='cycle'" />
1479- <field name="rr_fmc_from_3" invisible="context.get('rule')!='cycle'" attrs="{'required': [('rr_fmc_3', '!=', False)]}" on_change="change_fmc('from', '3', rr_fmc_from_3)" nolabel="1"/>
1480- <field name="rr_fmc_to_3" invisible="context.get('rule')!='cycle'" attrs="{'required': [('rr_fmc_3', '!=', False)]}" on_change="change_fmc('to', '3', rr_fmc_to_3)" nolabel="1"/>
1481+ <field name="rr_fmc_from_3" invisible="context.get('rule')!='cycle'" readonly="1" nolabel="1"/>
1482+ <field name="rr_fmc_to_3" invisible="context.get('rule')!='cycle'" attrs="{'required': [('rr_fmc_3', '!=', False)]}" on_change="change_fmc('to', '3', rr_fmc_to_3, True)" nolabel="1"/>
1483 <field name="rr_fmc_4" invisible="context.get('rule')!='cycle'" />
1484- <field name="rr_fmc_from_4" invisible="context.get('rule')!='cycle'" attrs="{'required': [('rr_fmc_4', '!=', False)]}" on_change="change_fmc('from', '4', rr_fmc_from_4)" nolabel="1"/>
1485- <field name="rr_fmc_to_4" invisible="context.get('rule')!='cycle'" attrs="{'required': [('rr_fmc_4', '!=', False)]}" on_change="change_fmc('to', '4', rr_fmc_to_4)" nolabel="1"/>
1486+ <field name="rr_fmc_from_4" invisible="context.get('rule')!='cycle'" readonly="1" nolabel="1"/>
1487+ <field name="rr_fmc_to_4" invisible="context.get('rule')!='cycle'" attrs="{'required': [('rr_fmc_4', '!=', False)]}" on_change="change_fmc('to', '4', rr_fmc_to_4, True)" nolabel="1"/>
1488 <field name="rr_fmc_5" invisible="context.get('rule')!='cycle'" />
1489- <field name="rr_fmc_from_5" invisible="context.get('rule')!='cycle'" attrs="{'required': [('rr_fmc_5', '!=', False)]}" on_change="change_fmc('from', '5', rr_fmc_from_5)" nolabel="1"/>
1490- <field name="rr_fmc_to_5" invisible="context.get('rule')!='cycle'" attrs="{'required': [('rr_fmc_5', '!=', False)]}" on_change="change_fmc('to', '5', rr_fmc_to_5)" nolabel="1"/>
1491+ <field name="rr_fmc_from_5" invisible="context.get('rule')!='cycle'" readonly="1" nolabel="1"/>
1492+ <field name="rr_fmc_to_5" invisible="context.get('rule')!='cycle'" attrs="{'required': [('rr_fmc_5', '!=', False)]}" on_change="change_fmc('to', '5', rr_fmc_to_5, True)" nolabel="1"/>
1493 <field name="rr_fmc_6" invisible="context.get('rule')!='cycle'" />
1494- <field name="rr_fmc_from_6" invisible="context.get('rule')!='cycle'" attrs="{'required': [('rr_fmc_6', '!=', False)]}" on_change="change_fmc('from', '6', rr_fmc_from_6)" nolabel="1"/>
1495- <field name="rr_fmc_to_6" invisible="context.get('rule')!='cycle'" attrs="{'required': [('rr_fmc_6', '!=', False)]}" on_change="change_fmc('to', '6', rr_fmc_to_6)" nolabel="1"/>
1496+ <field name="rr_fmc_from_6" invisible="context.get('rule')!='cycle'" readonly="1" nolabel="1"/>
1497+ <field name="rr_fmc_to_6" invisible="context.get('rule')!='cycle'" attrs="{'required': [('rr_fmc_6', '!=', False)]}" on_change="change_fmc('to', '6', rr_fmc_to_6, True)" nolabel="1"/>
1498 <field name="rr_fmc_7" invisible="context.get('rule')!='cycle'" />
1499- <field name="rr_fmc_from_7" invisible="context.get('rule')!='cycle'" attrs="{'required': [('rr_fmc_7', '!=', False)]}" on_change="change_fmc('from', '7', rr_fmc_from_7)" nolabel="1"/>
1500- <field name="rr_fmc_to_7" invisible="context.get('rule')!='cycle'" attrs="{'required': [('rr_fmc_7', '!=', False)]}" on_change="change_fmc('to', '7', rr_fmc_to_7)" nolabel="1"/>
1501+ <field name="rr_fmc_from_7" invisible="context.get('rule')!='cycle'" readonly="1" nolabel="1"/>
1502+ <field name="rr_fmc_to_7" invisible="context.get('rule')!='cycle'" attrs="{'required': [('rr_fmc_7', '!=', False)]}" on_change="change_fmc('to', '7', rr_fmc_to_7, True)" nolabel="1"/>
1503 <field name="rr_fmc_8" invisible="context.get('rule')!='cycle'" />
1504- <field name="rr_fmc_from_8" invisible="context.get('rule')!='cycle'" attrs="{'required': [('rr_fmc_8', '!=', False)]}" on_change="change_fmc('from', '8', rr_fmc_from_8)" nolabel="1"/>
1505- <field name="rr_fmc_to_8" invisible="context.get('rule')!='cycle'" attrs="{'required': [('rr_fmc_8', '!=', False)]}" on_change="change_fmc('to', '8', rr_fmc_to_8)" nolabel="1"/>
1506+ <field name="rr_fmc_from_8" invisible="context.get('rule')!='cycle'" readonly="1" nolabel="1"/>
1507+ <field name="rr_fmc_to_8" invisible="context.get('rule')!='cycle'" attrs="{'required': [('rr_fmc_8', '!=', False)]}" on_change="change_fmc('to', '8', rr_fmc_to_8, True)" nolabel="1"/>
1508 <field name="rr_fmc_9" invisible="context.get('rule')!='cycle'" />
1509- <field name="rr_fmc_from_9" invisible="context.get('rule')!='cycle'" attrs="{'required': [('rr_fmc_9', '!=', False)]}" on_change="change_fmc('from', '9', rr_fmc_from_9)" nolabel="1"/>
1510- <field name="rr_fmc_to_9" invisible="context.get('rule')!='cycle'" attrs="{'required': [('rr_fmc_9', '!=', False)]}" on_change="change_fmc('to', '9', rr_fmc_to_9)" nolabel="1"/>
1511+ <field name="rr_fmc_from_9" invisible="context.get('rule')!='cycle'" readonly="1" nolabel="1"/>
1512+ <field name="rr_fmc_to_9" invisible="context.get('rule')!='cycle'" attrs="{'required': [('rr_fmc_9', '!=', False)]}" on_change="change_fmc('to', '9', rr_fmc_to_9, True)" nolabel="1"/>
1513 <field name="rr_fmc_10" invisible="context.get('rule')!='cycle'" />
1514- <field name="rr_fmc_from_10" invisible="context.get('rule')!='cycle'" attrs="{'required': [('rr_fmc_10', '!=', False)]}" on_change="change_fmc('from', '10', rr_fmc_from_10)" nolabel="1"/>
1515- <field name="rr_fmc_to_10" invisible="context.get('rule')!='cycle'" attrs="{'required': [('rr_fmc_10', '!=', False)]}" on_change="change_fmc('to', '10', rr_fmc_to_10)" nolabel="1"/>
1516+ <field name="rr_fmc_from_10" invisible="context.get('rule')!='cycle'" readonly="1" nolabel="1"/>
1517+ <field name="rr_fmc_to_10" invisible="context.get('rule')!='cycle'" attrs="{'required': [('rr_fmc_10', '!=', False)]}" on_change="change_fmc('to', '10', rr_fmc_to_10, True)" nolabel="1"/>
1518 <field name="rr_fmc_11" invisible="context.get('rule')!='cycle'" />
1519- <field name="rr_fmc_from_11" invisible="context.get('rule')!='cycle'" attrs="{'required': [('rr_fmc_11', '!=', False)]}" on_change="change_fmc('from', '11', rr_fmc_from_11)" nolabel="1"/>
1520- <field name="rr_fmc_to_11" invisible="context.get('rule')!='cycle'" attrs="{'required': [('rr_fmc_11', '!=', False)]}" on_change="change_fmc('to', '11', rr_fmc_to_11)" nolabel="1"/>
1521+ <field name="rr_fmc_from_11" invisible="context.get('rule')!='cycle'" readonly="1" nolabel="1"/>
1522+ <field name="rr_fmc_to_11" invisible="context.get('rule')!='cycle'" attrs="{'required': [('rr_fmc_11', '!=', False)]}" on_change="change_fmc('to', '11', rr_fmc_to_11, True)" nolabel="1"/>
1523 <field name="rr_fmc_12" invisible="context.get('rule')!='cycle'" />
1524- <field name="rr_fmc_from_12" invisible="context.get('rule')!='cycle'" attrs="{'required': [('rr_fmc_12', '!=', False)]}" on_change="change_fmc('from', '12', rr_fmc_from_12)" nolabel="1"/>
1525- <field name="rr_fmc_to_12" invisible="context.get('rule')!='cycle'" attrs="{'required': [('rr_fmc_12', '!=', False)]}" on_change="change_fmc('to', '12', rr_fmc_to_12)" nolabel="1"/>
1526+ <field name="rr_fmc_from_12" invisible="context.get('rule')!='cycle'" readonly="1" nolabel="1"/>
1527+ <field name="rr_fmc_to_12" invisible="context.get('rule')!='cycle'" attrs="{'required': [('rr_fmc_12', '!=', False)]}" on_change="change_fmc('to', '12', rr_fmc_to_12, False)" nolabel="1"/>
1528 </group>
1529 </form>
1530 </field>
1531@@ -555,7 +553,10 @@
1532 <field name="uom_id" invisible="1" />
1533 <field name="status" invisible="1" />
1534 <field name="in_main_list" filter_selector="1" />
1535- <field name="valid_rr_fmc" invisible="context.get('rule')!='cycle'"/>
1536+ <field name="auto_qty" invisible="context.get('rule')!='auto'"/>
1537+ <field name="buffer_qty" invisible="context.get('rule')!='cycle'"/>
1538+ <field name="min_max" invisible="context.get('rule')!='minmax'"/>
1539+ <field name="valid_rr_fmc" />
1540 <field name="real_stock" />
1541 <field name="pipeline_qty" />
1542 <field name="eta_for_next_pipeline" />
1543@@ -680,7 +681,8 @@
1544 <field name="time_unit" />
1545 <newline />
1546 <group colspan="4">
1547- <button colspan="1" name="pipeline" string="Pipeline" type="object" icon="icons/pipe.png"/>
1548+ <button colspan="1" name="pipeline" string="Pipeline (Stock Moves)" type="object" icon="icons/pipe.png"/>
1549+ <button colspan="1" name="pipeline_po" string="Pipeline (PO)" type="object" icon="icons/pipe.png"/>
1550 <button colspan="1" name="stock_by_location" string="Stock By Location" type="object" icon="gtk-indent"/>
1551 </group>
1552 <field name="line_ids" colspan="4" nolabel="1" o2m_selectable="2" scroll="1">
1553@@ -691,9 +693,12 @@
1554 <field name="status" invisible="1" />
1555 <field name="primay_product_list" filter_selector="1" />
1556 <field name="segment_ref_name" />
1557+ <field name="min_max" />
1558+ <field name="auto_qty" />
1559 <field name="projected_stock_qty_amc" />
1560 <field name="rr_fmc_avg" />
1561 <field name="rr_amc" />
1562+ <field name="buffer_qty" />
1563 <field name="valid_rr_fmc" />
1564 <field name="real_stock" />
1565 <field name="pipeline_qty" />
1566
1567=== modified file 'bin/addons/procurement_cycle/replenishment_wizard.xml'
1568--- bin/addons/procurement_cycle/replenishment_wizard.xml 2020-05-04 15:28:58 +0000
1569+++ bin/addons/procurement_cycle/replenishment_wizard.xml 2020-07-27 12:47:56 +0000
1570@@ -41,8 +41,8 @@
1571 <form string="Set Paired Product">
1572 <field name="product_id" readonly="1" colspan="4"/>
1573 <field name="status" invisible="1" />
1574- <field name="replacing_product_id" attrs="{'invisible': [('status', '!=', 'replaced')], 'required': [('status', '=', 'replaced')]}" colspan="4" domain="[('id', '!=', product_id)]" />
1575- <field name="replaced_product_id" attrs="{'invisible': [('status', '!=', 'replacing')], 'required': [('status', '=', 'replacing')]}" colspan="4" domain="[('id', '!=', product_id)]"/>
1576+ <field name="replacing_product_id" attrs="{'invisible': [('status', 'not in', ['replaced', 'phasingout'])], 'required': [('status', '=', 'replaced')]}" colspan="4" domain="[('id', '!=', product_id)]" />
1577+ <field name="replaced_product_id" attrs="{'invisible': [('status', 'not in', ['replacing', 'activereplacing'])], 'required': [('status', 'in', ['replacing', 'activereplacing'])]}" colspan="4" domain="[('id', '!=', product_id)]"/>
1578 <group colspan="4">
1579 <button name="save_paired" icon="gtk-ok" string="Save" type="object"/>
1580 <button special="cancel" string="Close" icon="gtk-cancel" />
1581
1582=== modified file 'bin/addons/procurement_cycle/report/replenishment_inventory_review.mako'
1583--- bin/addons/procurement_cycle/report/replenishment_inventory_review.mako 2020-06-02 14:41:41 +0000
1584+++ bin/addons/procurement_cycle/report/replenishment_inventory_review.mako 2020-07-27 12:47:56 +0000
1585@@ -911,6 +911,8 @@
1586 <Row ss:Height="75">
1587 <Cell ss:StyleID="s128"><Data ss:Type="String">${_('Product Code')|x}</Data></Cell>
1588 <Cell ss:StyleID="s128"><Data ss:Type="String">${_('Product Description')|x}</Data></Cell>
1589+ <Cell ss:StyleID="s128"><Data ss:Type="String">${_('RR Lifecycle')|x}</Data></Cell>
1590+ <Cell ss:StyleID="s128"><Data ss:Type="String">${_('Replaced/Replacing')|x}</Data></Cell>
1591 <Cell ss:StyleID="s128"><Data ss:Type="String">${_('Primary Product list')|x}</Data></Cell>
1592 <Cell ss:StyleID="s129red"><Data ss:Type="String">${_('Warnings Recap')|x}</Data></Cell>
1593 <Cell ss:StyleID="s129"><Data ss:Type="String">${_('Segment Ref/name')|x}</Data></Cell>
1594@@ -925,7 +927,7 @@
1595 <Cell ss:StyleID="s130"><Data ss:Type="String">${_('Order coverage')|x} ${getSel(objects[0], 'time_unit')|x}</Data></Cell>
1596 <Cell ss:StyleID="s130"><Data ss:Type="String">${_('SS')|x} ${getSel(objects[0], 'time_unit')|x}</Data></Cell>
1597 <Cell ss:StyleID="s130"><Data ss:Type="String">${_('Buffer (Qty)')|x}</Data></Cell>
1598- <Cell ss:StyleID="s129"><Data ss:Type="String">${_('Valid RR-FMC(s)')|x}</Data></Cell>
1599+ <Cell ss:StyleID="s129"><Data ss:Type="String">${_('Valid')|x}</Data></Cell>
1600 <Cell ss:StyleID="s129"><Data ss:Type="String">${_('RR-FMC (average for period)')|x}</Data></Cell>
1601 <Cell ss:StyleID="s130"><Data ss:Type="String">${_('RR-AMC (average for AMC period)')|x}</Data></Cell>
1602
1603@@ -965,6 +967,8 @@
1604 <Row ss:AutoFitHeight="0" ss:Height="24">
1605 <Cell ss:StyleID="s141"><Data ss:Type="String">${line.product_id.default_code|x}</Data></Cell>
1606 <Cell ss:StyleID="s141"><Data ss:Type="String">${line.product_id.name|x}</Data></Cell>
1607+ <Cell ss:StyleID="s141"><Data ss:Type="String">${line.segment_ref_name and getSel(line, 'status') or _('N/A')|x}</Data></Cell>
1608+ <Cell ss:StyleID="s141"><Data ss:Type="String">${(line.paired_product_id and line.paired_product_id.default_code or '')|x}</Data></Cell>
1609 <Cell ss:StyleID="s141"><Data ss:Type="String">${(line.primay_product_list or '')|x}</Data></Cell>
1610 <Cell ss:StyleID="s141"><Data ss:Type="String">${line.warning or ''|xn}</Data></Cell>
1611 <Cell ss:StyleID="s141"><Data ss:Type="String">${(line.segment_ref_name or '')|x}</Data></Cell>
1612@@ -972,7 +976,7 @@
1613 <Cell ss:StyleID="s141"><Data ss:Type="String">${(line.segment_ref_name and getSel(line, 'rule') or '')|x}</Data></Cell>
1614 % if line.rule == 'minmax':
1615 <Cell ss:StyleID="s140">
1616- % if line.min_qty:
1617+ % if line.min_qty is not False:
1618 <Data ss:Type="Number">${line.min_qty}</Data>
1619 % endif
1620 </Cell>
1621@@ -982,7 +986,7 @@
1622
1623 % if line.rule == 'minmax':
1624 <Cell ss:StyleID="s140">
1625- % if line.max_qty:
1626+ % if line.max_qty is not False:
1627 <Data ss:Type="Number">${line.max_qty}</Data>
1628 % endif
1629 </Cell>
1630@@ -992,7 +996,7 @@
1631
1632 % if line.rule == 'auto':
1633 <Cell ss:StyleID="s140">
1634- % if line.auto_qty:
1635+ % if line.auto_qty is not False:
1636 <Data ss:Type="Number">${line.auto_qty}</Data>
1637 % endif
1638 </Cell>
1639@@ -1036,11 +1040,7 @@
1640 <Cell ss:StyleID="irrel" />
1641 % endif
1642
1643- % if line.rule == 'cycle':
1644- <Cell ss:StyleID="s141"><Data ss:Type="String">${line.valid_rr_fmc and _('Yes') or _('No') }</Data></Cell>
1645- % else:
1646- <Cell ss:StyleID="irrel" />
1647- % endif
1648+ <Cell ss:StyleID="s141"><Data ss:Type="String">${line.valid_rr_fmc and _('Yes') or _('No') }</Data></Cell>
1649
1650 <Cell ss:StyleID="s141">
1651 % if line.rr_fmc_avg:
1652
1653=== modified file 'bin/addons/procurement_cycle/report/replenishment_order_calc.mako'
1654--- bin/addons/procurement_cycle/report/replenishment_order_calc.mako 2020-06-02 14:41:41 +0000
1655+++ bin/addons/procurement_cycle/report/replenishment_order_calc.mako 2020-07-27 12:47:56 +0000
1656@@ -376,6 +376,7 @@
1657 <Column ss:AutoFitWidth="0" ss:Width="87.75"/> <!--prod code -->
1658 <Column ss:AutoFitWidth="0" ss:Width="210.75"/> <!--prod desc -->
1659 <Column ss:AutoFitWidth="0" ss:Width="63"/> <!-- in list -->
1660+ <Column ss:AutoFitWidth="0" ss:Width="66"/> <!-- buffer / minmax / auto qty -->
1661 <Column ss:AutoFitWidth="0" ss:Width="63"/> <!-- real stock -->
1662 <Column ss:AutoFitWidth="0" ss:Width="66"/> <!--pipeline -->
1663 <Column ss:AutoFitWidth="0" ss:Width="82.5" /> <!-- eta -->
1664@@ -492,6 +493,16 @@
1665 <Cell ss:StyleID="s89"><Data ss:Type="String">${_('Product code')|x}</Data><NamedCell ss:Name="Print_Titles"/><NamedCell ss:Name="_FilterDatabase"/><NamedCell ss:Name="Print_Area"/></Cell>
1666 <Cell ss:StyleID="s89"><Data ss:Type="String">${_('Description')|x}</Data><NamedCell ss:Name="Print_Titles"/><NamedCell ss:Name="_FilterDatabase"/><NamedCell ss:Name="Print_Area"/></Cell>
1667 <Cell ss:StyleID="s90"><Data ss:Type="String">${_('In prod. list')|x}</Data><NamedCell ss:Name="Print_Titles"/><NamedCell ss:Name="_FilterDatabase"/><NamedCell ss:Name="Print_Area"/></Cell>
1668+ <Cell ss:StyleID="s90">
1669+ % if objects[0].rule == 'cycle':
1670+ <Data ss:Type="String">${_('Buffer')|x}</Data>
1671+ % elif objects[0].rule == 'minmax':
1672+ <Data ss:Type="String">${_('Min / Max')|x}</Data>
1673+ % else:
1674+ <Data ss:Type="String">${_('Auto. Supply Qty')|x}</Data>
1675+ %endif
1676+ <NamedCell ss:Name="Print_Titles"/><NamedCell ss:Name="_FilterDatabase"/><NamedCell ss:Name="Print_Area"/></Cell>
1677+ <Cell ss:StyleID="s90"><Data ss:Type="String">${_('Valid')}</Data><NamedCell ss:Name="_FilterDatabase"/><NamedCell ss:Name="Print_Area"/></Cell>
1678 <Cell ss:StyleID="s93"><Data ss:Type="String">${_('Real Stock')|x}</Data><NamedCell ss:Name="Print_Titles"/></Cell>
1679 <Cell ss:StyleID="s93"><Data ss:Type="String">${_('Pipeline Qty')|x}</Data><NamedCell ss:Name="Print_Titles"/></Cell>
1680 <Cell ss:StyleID="s93"><Data ss:Type="String">${_('Eta For next Pipeline')|x}</Data><NamedCell ss:Name="Print_Titles"/></Cell>
1681@@ -526,6 +537,25 @@
1682 <Cell ss:StyleID="s96"><Data ss:Type="String">${prod.product_id.default_code|x}</Data><NamedCell ss:Name="_FilterDatabase"/><NamedCell ss:Name="Print_Area"/></Cell>
1683 <Cell ss:StyleID="s96"><Data ss:Type="String">${prod.product_id.name|x}</Data><NamedCell ss:Name="_FilterDatabase"/><NamedCell ss:Name="Print_Area"/></Cell>
1684 <Cell ss:StyleID="s95"><Data ss:Type="String">${prod.in_main_list and _('Yes') or _('No')}</Data><NamedCell ss:Name="_FilterDatabase"/><NamedCell ss:Name="Print_Area"/></Cell>
1685+ <Cell ss:StyleID="s95">
1686+ % if objects[0].rule == 'cycle':
1687+ % if prod.buffer_qty is not False:
1688+ ><Data ss:Type="Number">${prod.buffer_qty}</Data>
1689+ % else:
1690+ <Data ss:Type="String"/>
1691+ % endif
1692+ % elif objects[0].rule == 'minmax':
1693+ <Data ss:Type="String">${prod.min_max}</Data>
1694+ % else:
1695+ % if prod.auto_qty is not False:
1696+ <Data ss:Type="Number">${prod.auto_qty}</Data>
1697+ % else:
1698+ <Data ss:Type="String"/>
1699+ % endif
1700+ %endif
1701+ <NamedCell ss:Name="_FilterDatabase"/><NamedCell ss:Name="Print_Area"/>
1702+ </Cell>
1703+ <Cell ss:StyleID="s95"><Data ss:Type="String">${prod.valid_rr_fmc and _('Yes') or _('No') }</Data></Cell>
1704 <Cell ss:StyleID="s97"><Data ss:Type="Number">${prod.real_stock}</Data></Cell>
1705 <Cell ss:StyleID="s97"><Data ss:Type="Number">${prod.pipeline_qty}</Data></Cell>
1706 <Cell ss:StyleID="s97d">
1707
1708=== modified file 'bin/addons/procurement_cycle/report/replenishment_segment.mako'
1709--- bin/addons/procurement_cycle/report/replenishment_segment.mako 2020-05-14 09:26:30 +0000
1710+++ bin/addons/procurement_cycle/report/replenishment_segment.mako 2020-07-27 12:47:56 +0000
1711@@ -413,7 +413,9 @@
1712 <% styles = ['s143', 's149'] %>
1713 % for fmc in range(1, 13):
1714 <Cell ss:StyleID="${styles[i]}"><Data ss:Type="String">${_('RR FMC %d')%fmc|x}</Data><NamedCell ss:Name="Print_Titles"/></Cell>
1715- <Cell ss:StyleID="${styles[i]}"><Data ss:Type="String">${_('From %d')%fmc|x}</Data><NamedCell ss:Name="Print_Titles"/></Cell>
1716+ % if fmc == 1:
1717+ <Cell ss:StyleID="${styles[i]}"><Data ss:Type="String">${_('From %d')%fmc|x}</Data><NamedCell ss:Name="Print_Titles"/></Cell>
1718+ % endif
1719 <Cell ss:StyleID="${styles[i]}"><Data ss:Type="String">${_('To %d')%fmc|x}</Data><NamedCell ss:Name="Print_Titles"/></Cell>
1720 <% i = 1 - i %>
1721 % endfor
1722@@ -455,24 +457,26 @@
1723 <Data ss:Type="Number">${getattr(prod, 'rr_fmc_%d'%fmc) or ''}</Data>
1724 % endif
1725 </Cell>
1726- <% from_date = getattr(prod, 'rr_fmc_from_%d'%fmc) %>
1727- <Cell ss:StyleID="${styles[i][1]}">
1728- % if isDate(from_date):
1729- <Data ss:Type="DateTime">${from_date|n}T00:00:00.000</Data>
1730- % else:
1731- <Data ss:Type="String"></Data>
1732- % endif
1733- </Cell>
1734+ % if fmc == 1:
1735+ <% from_date = getattr(prod, 'rr_fmc_from_%d'%fmc) %>
1736+ <Cell ss:StyleID="${styles[i][1]}">
1737+ % if isDate(from_date):
1738+ <Data ss:Type="DateTime">${from_date|n}T00:00:00.000</Data>
1739+ % else:
1740+ <Data ss:Type="String"></Data>
1741+ % endif
1742+ </Cell>
1743+ % endif
1744
1745- <% to_date = getattr(prod, 'rr_fmc_to_%d'%fmc) %>
1746- <Cell ss:StyleID="${styles[i][1]}">
1747- % if isDate(to_date):
1748- <Data ss:Type="DateTime">${to_date|n}T00:00:00.000</Data>
1749- % else:
1750- <Data ss:Type="String"></Data>
1751- % endif
1752- </Cell>
1753- <% i = 1 - i %>
1754+ <% to_date = getattr(prod, 'rr_fmc_to_%d'%fmc) %>
1755+ <Cell ss:StyleID="${styles[i][1]}">
1756+ % if isDate(to_date):
1757+ <Data ss:Type="DateTime">${to_date|n}T00:00:00.000</Data>
1758+ % else:
1759+ <Data ss:Type="String"></Data>
1760+ % endif
1761+ </Cell>
1762+ <% i = 1 - i %>
1763 % endfor
1764 % endif
1765 </Row>
1766@@ -534,7 +538,7 @@
1767 <Range>R9C4:R${len(objects[0].line_ids)+9}C4</Range>
1768 <Type>List</Type>
1769 <CellRangeList/>
1770- <Value>&quot;${_('Active')|x},${_('New')|x},${_('Replaced')|x},${_('Replacing')|x}&quot;</Value>
1771+ <Value>&quot;${_('Active')|x},${_('New')|x},${_('Replaced')|x},${_('Replacing')|x},${_('Phasing Out')|x},${_('Active-Replacing')|x}&quot;</Value>
1772 </DataValidation>
1773
1774 </Worksheet>
1775
1776=== modified file 'bin/addons/purchase/purchase_order.py'
1777--- bin/addons/purchase/purchase_order.py 2020-04-22 15:55:54 +0000
1778+++ bin/addons/purchase/purchase_order.py 2020-07-27 12:47:56 +0000
1779@@ -828,6 +828,7 @@
1780 },
1781 ),
1782 'delivery_requested_date': fields.date(string='Delivery Requested Date', required=True),
1783+ 'delivery_requested_date_modified': fields.date(string='Delivery Requested Date (modified)'),
1784 'delivery_confirmed_date': fields.date(string='Delivery Confirmed Date'),
1785 'ready_to_ship_date': fields.date(string='Ready To Ship Date'),
1786 'shipment_date': fields.date(string='Shipment Date', help='Date on which picking is created at supplier'),
1787@@ -1261,7 +1262,7 @@
1788 default = {}
1789 if context is None:
1790 context = {}
1791- fields_to_reset = ['delivery_requested_date', 'ready_to_ship_date', 'date_order', 'delivery_confirmed_date', 'arrival_date', 'shipment_date', 'arrival_date', 'date_approve', 'analytic_distribution_id', 'empty_po_cancelled', 'stock_take_date']
1792+ fields_to_reset = ['delivery_requested_date', 'delivery_requested_date_modified', 'ready_to_ship_date', 'date_order', 'delivery_confirmed_date', 'arrival_date', 'shipment_date', 'arrival_date', 'date_approve', 'analytic_distribution_id', 'empty_po_cancelled', 'stock_take_date']
1793 to_del = []
1794 for ftr in fields_to_reset:
1795 if ftr not in default:
1796@@ -1359,6 +1360,8 @@
1797 context = {}
1798 # field name
1799 field_name = context.get('field_name', False)
1800+ if field_name == 'requested' and self.search_exists(cr, uid, [('id', 'in', ids), ('delivery_requested_date_modified', '=', False), ('state', '!=', 'draft')], context=context):
1801+ raise osv.except_osv(_('Warning'), _('Please fill the "Delivery Requested Date (modified)" field.'))
1802 assert field_name, 'The button is not correctly set.'
1803 # data
1804 data = getattr(self, field_name + '_data')(cr, uid, ids, context=context)
1805@@ -2259,7 +2262,6 @@
1806 if context is None:
1807 context = {}
1808 move_obj = self.pool.get('stock.move')
1809- data_obj = self.pool.get('ir.model.data')
1810
1811 pol = self.ensure_object(cr, uid, 'purchase.order.line', pol)
1812 internal = self.ensure_object(cr, uid, 'stock.picking', internal_id)
1813@@ -2269,18 +2271,8 @@
1814 # compute source location:
1815 src_location = pol.order_id.location_id
1816
1817- # compute destination location:
1818- dest = pol.order_id.location_id.id
1819- if pol.product_id.type == 'service_recep' and not pol.order_id.cross_docking_ok:
1820- # service with reception are directed to Service Location
1821- dest = self.pool.get('stock.location').get_service_location(cr, uid)
1822- elif pol.product_id.type == 'consu':
1823- dest = data_obj.get_object_reference(cr, uid, 'stock_override', 'stock_location_non_stockable')[1]
1824- elif pol.linked_sol_id and pol.linked_sol_id.order_id.procurement_request and pol.linked_sol_id.order_id.location_requestor_id.usage != 'customer':
1825- dest = pol.linked_sol_id.order_id.location_requestor_id.id
1826- elif self.pool.get('stock.location').chained_location_get(cr, uid, src_location, product=pol.product_id, context=context):
1827- # if input location has a chained location then use it
1828- dest = self.pool.get('stock.location').chained_location_get(cr, uid, src_location, product=pol.product_id, context=context)[0].id
1829+ # compute destination location
1830+ dest = self.pool.get('purchase.order.line').final_location_dest(cr, uid, pol, context=context)
1831
1832 values = {
1833 'name': ''.join((pol.order_id.name, ': ', (pol.name or ''))),
1834
1835=== modified file 'bin/addons/purchase/purchase_order_line.py'
1836--- bin/addons/purchase/purchase_order_line.py 2020-04-09 14:44:46 +0000
1837+++ bin/addons/purchase/purchase_order_line.py 2020-07-27 12:47:56 +0000
1838@@ -494,6 +494,7 @@
1839 'move_ids': fields.one2many('stock.move', 'purchase_line_id', 'Reservation', readonly=True,
1840 ondelete='set null'),
1841 'move_dest_id': fields.many2one('stock.move', 'Reservation Destination', ondelete='set null', select=True),
1842+ 'location_dest_id': fields.many2one('stock.location', 'Final Destination of move', ondelete='set null', select=True),
1843 'price_unit': fields.float('Unit Price', required=True,
1844 digits_compute=dp.get_precision('Purchase Price Computation')),
1845 'vat_ok': fields.function(_get_vat_ok, method=True, type='boolean', string='VAT OK', store=False,
1846@@ -1277,6 +1278,8 @@
1847
1848 if not context.get('split_line'):
1849 default.update({'stock_take_date': False})
1850+ if 'location_dest_id' not in default:
1851+ default['location_dest_id'] = False
1852
1853 # from RfQ line to PO line: grab the linked sol if has:
1854 if pol.order_id.rfq_ok and context.get('generate_po_from_rfq', False):
1855@@ -1333,6 +1336,7 @@
1856 if not 'soq_updated' in vals:
1857 vals['soq_updated'] = False
1858
1859+ check_location_dest_ids = []
1860 for line in self.browse(cr, uid, ids, context=context):
1861 new_vals = vals.copy()
1862 # check qty
1863@@ -1343,6 +1347,9 @@
1864 _('You can not have an order line with a negative or zero quantity')
1865 )
1866
1867+ if 'product_id' in vals and line.state in ('validated', 'validated_n', 'sourced_sy', 'sourced_v', 'sourced_n') and line.product_id.id != vals.get('product'):
1868+ check_location_dest_ids.append(line.id)
1869+
1870 # try to fill "link_so_id":
1871 if not line.link_so_id and not vals.get('link_so_id'):
1872 linked_so = False
1873@@ -1372,6 +1379,10 @@
1874 'po_line_id': line.id,
1875 }, context=context)
1876
1877+ for line in self.browse(cr, uid, check_location_dest_ids, context=context):
1878+ super(purchase_order_line, self).write(cr, uid, [line.id], {'location_dest_id': self.final_location_dest(cr, uid, line, context=context)}, context=context)
1879+
1880+
1881 if vals.get('stock_take_date'):
1882 self._check_stock_take_date(cr, uid, ids, context=context)
1883
1884@@ -2074,6 +2085,44 @@
1885 picking_id.write({}, context=context)
1886 return True
1887
1888+ def final_location_dest(self, cr, uid, pol_obj, fo_obj=False, context=None):
1889+ data_obj = self.pool.get('ir.model.data')
1890+
1891+ dest = pol_obj.order_id.location_id.id
1892+
1893+ if not pol_obj.product_id:
1894+ return dest
1895+
1896+ if pol_obj.product_id.type == 'service_recep' and not pol_obj.order_id.cross_docking_ok:
1897+ # service with reception are directed to Service Location
1898+ return self.pool.get('stock.location').get_service_location(cr, uid)
1899+
1900+ if pol_obj.product_id.type == 'consu':
1901+ return data_obj.get_object_reference(cr, uid, 'stock_override', 'stock_location_non_stockable')[1]
1902+
1903+ fo = fo_obj or pol_obj.linked_sol_id and pol_obj.linked_sol_id.order_id or False
1904+ if fo and fo.procurement_request and fo.location_requestor_id.usage != 'customer':
1905+ return fo.location_requestor_id.id
1906+
1907+ chained = self.pool.get('stock.location').chained_location_get(cr, uid, pol_obj.order_id.location_id, product=pol_obj.product_id, context=context)
1908+ if chained:
1909+ if chained[0].chained_location_type == 'nomenclature':
1910+ # 1st round : Input > Stock, 2nd round Stock -> MED/LOG
1911+ chained2 = self.pool.get('stock.location').chained_location_get(cr, uid, chained[0], product=pol_obj.product_id, context=context)
1912+ if chained2:
1913+ return chained2[0].id
1914+ return chained[0].id
1915+
1916+ return dest
1917+
1918+ def open_po_form(self, cr, uid, ids, context=None):
1919+ pol = self.browse(cr, uid, ids[0], fields_to_fetch=['order_id'], context=context)
1920+
1921+ res = self.pool.get('ir.actions.act_window').open_view_from_xmlid(cr, uid, 'purchase.purchase_form_action', ['form', 'tree'], new_tab=True, context=context)
1922+ res['keep_open'] = True
1923+ res['res_id'] = pol.order_id.id
1924+ return res
1925+
1926 purchase_order_line()
1927
1928
1929
1930=== modified file 'bin/addons/purchase/purchase_view.xml'
1931--- bin/addons/purchase/purchase_view.xml 2020-04-14 07:48:08 +0000
1932+++ bin/addons/purchase/purchase_view.xml 2020-07-27 12:47:56 +0000
1933@@ -221,8 +221,10 @@
1934 <button name="check_lines_to_fix" string="Check Lines" icon="gtk-dialog-warning" colspan="1" type="object" context="{'rfq_ok': False}"/>
1935 <button name="add_multiple_lines" string="Add multiple lines" icon="gtk-add" colspan="4" type="object"/>
1936 </group>
1937- <button name="wizard_import_file" string="Import PO confirmation" icon="gtk-execute" colspan="1" type="object" attrs="{'invisible':[('state', 'not in', ['validated', 'validated_p'])]}"/>
1938- <button name="export_po_integration" string="Export PO Validated" icon="gtk-execute" colspan="1" type="object" attrs="{'invisible':[('state', 'not in', ['validated', 'validated_p'])]}"/>
1939+ <group colspan="4" col="4" attrs="{'invisible':['&amp;', ('state', 'not in', ['validated', 'validated_p']), '|', ('partner_type', 'not in', ['esc', 'external']), ('state', 'not in', ['confirmed', 'confirmed_p'])]}">
1940+ <button name="wizard_import_file" string="Import PO confirmation" icon="gtk-execute" colspan="1" type="object" />
1941+ <button name="export_po_integration" string="Export PO Validated" icon="gtk-execute" colspan="1" type="object" />
1942+ </group>
1943 <field name="order_line" colspan="4" nolabel="1" mode="tree,form"
1944 context="{'purchase_id': active_id, 'partner_type': partner_type, 'categ': categ, 'pricelist_id': pricelist_id, 'rfq_ok': False, 'from_fo': po_from_fo or po_from_ir or False}"
1945 on_change="order_line_change(order_line)"
1946@@ -314,8 +316,11 @@
1947 <group colspan="4" col="4">
1948 <separator colspan="4" string="Dates"/>
1949 <group colspan="2" col="3">
1950- <field name="delivery_requested_date" attrs="{'readonly': [('state', 'not in', ('draft', 'draft_p', 'validated'))]}" on_change="onchange_requested_date(partner_id,date_order,delivery_requested_date,est_transport_lead_time, order_type, context)"/>
1951- <button colspan="1" name="update_date" string="Apply to lines" type="object" context="{'field_name': 'requested', 'type': 'purchase.order'}" icon="gtk-indent" attrs="{'invisible': [('state', 'not in', ('draft', 'draft_p', 'validated'))]}"/>
1952+ <group colspan="2" col="4">
1953+ <field name="delivery_requested_date" attrs="{'readonly': [('state', '!=', 'draft')]}" on_change="onchange_requested_date(partner_id,date_order,delivery_requested_date,est_transport_lead_time, order_type, context)"/>
1954+ <field name="delivery_requested_date_modified" attrs="{'readonly': [('state', 'not in', ['draft_p', 'validated', 'validated_p'])]}" />
1955+ </group>
1956+ <button colspan="1" name="update_date" string="Apply to lines" type="object" context="{'field_name': 'requested', 'type': 'purchase.order'}" icon="gtk-indent" attrs="{'invisible': [('state', 'not in', ('draft', 'draft_p', 'validated', 'validated_p'))]}"/>
1957 </group>
1958 <group colspan="2" col="3">
1959 <field name="delivery_confirmed_date" attrs="{'readonly': [('state', 'not in', ['draft', 'draft_p', 'validated', 'validated_p', 'sourced_p'])]}"/>
1960@@ -1007,6 +1012,52 @@
1961
1962 <menuitem action="purchase_line_form_action2" id="menu_purchase_line_order_draft" groups="base.group_extended" parent="menu_procurement_management_invoice" sequence="72"/>
1963
1964+ <record id="purchase_order_line_pipeline_tree" model="ir.ui.view">
1965+ <field name="name">purchase.order.line.tree</field>
1966+ <field name="model">purchase.order.line</field>
1967+ <field name="type">tree</field>
1968+ <field name="priority" eval="250" />
1969+ <field name="arch" type="xml">
1970+ <tree string="Purchase Order Lines" hide_delete_button="1" hide_new_button="1">
1971+ <field name="order_id"/>
1972+ <field name="partner_id" string="Supplier"/>
1973+ <field name="line_number"/>
1974+ <field name="product_id"/>
1975+ <field name="price_unit"/>
1976+ <field name="product_qty"/>
1977+ <field name="product_uom"/>
1978+ <field name="date_planned" widget="date" />
1979+ <field name="confirmed_delivery_date" widget="date" />
1980+ <field name="state" />
1981+ <button type="object" name="open_po_form" icon="terp-gtk-go-back-rtl" string="View PO" />
1982+ </tree>
1983+ </field>
1984+ </record>
1985+ <record id="purchase_order_line_pipeline_search" model="ir.ui.view">
1986+ <field name="name">purchase.order.line.search</field>
1987+ <field name="model">purchase.order.line</field>
1988+ <field name="type">search</field>
1989+ <field name="priority" eval="250" />
1990+ <field name="arch" type="xml">
1991+ <search>
1992+ <field name="order_id"/>
1993+ <field name="partner_id" string="Supplier"/>
1994+ <field name="product_id"/>
1995+ <field name="date_planned" widget="date" />
1996+ <field name="confirmed_delivery_date" widget="date" />
1997+ </search>
1998+ </field>
1999+ </record>
2000+ <record id="purchase_line_pipeline_action" model="ir.actions.act_window">
2001+ <field name="name">PO Line</field>
2002+ <field name="type">ir.actions.act_window</field>
2003+ <field name="res_model">purchase.order.line</field>
2004+ <field name="domain"></field>
2005+ <field name="view_type">form</field>
2006+ <field name="view_mode">tree</field>
2007+ <field name="view_id" ref="purchase_order_line_pipeline_tree" />
2008+ <field name="search_view_id" ref="purchase_order_line_pipeline_search"/>
2009+ </record>
2010
2011 </data>
2012 </openerp>
2013
2014=== modified file 'bin/addons/purchase/purchase_workflow.py'
2015--- bin/addons/purchase/purchase_workflow.py 2020-01-23 16:34:09 +0000
2016+++ bin/addons/purchase/purchase_workflow.py 2020-07-27 12:47:56 +0000
2017@@ -18,7 +18,7 @@
2018 if pol.is_line_split:
2019 split_po_ids = self.search(cr, uid, [('is_line_split', '=', False), ('line_number', '=', pol.line_number), ('order_id', '=', pol.order_id.id)], context=context)
2020 if split_po_ids:
2021- split_po = self.browse(cr, uid, split_po_ids[0], context=context)
2022+ split_po = self.browse(cr, uid, split_po_ids[0], fields_to_fetch=['linked_sol_id'], context=context)
2023 if split_po.linked_sol_id:
2024 sol_values['line_number'] = split_po.linked_sol_id.line_number
2025 return sol_values
2026@@ -148,6 +148,7 @@
2027 if pol.stock_take_date:
2028 line_stock_take = pol.stock_take_date
2029
2030+
2031 sol_values = {
2032 'product_id': pol.product_id and pol.product_id.id or False,
2033 'name': pol.name,
2034@@ -171,14 +172,16 @@
2035 'nomen_sub_3': pol.nomen_sub_3 and pol.nomen_sub_3.id or False,
2036 'nomen_sub_4': pol.nomen_sub_4 and pol.nomen_sub_4.id or False,
2037 'nomen_sub_5': pol.nomen_sub_5 and pol.nomen_sub_5.id or False,
2038- 'confirmed_delivery_date': line_confirmed,
2039 'stock_take_date': line_stock_take,
2040+ 'date_planned': pol.date_planned,
2041 'sync_sourced_origin': pol.instance_sync_order_ref and pol.instance_sync_order_ref.name or False,
2042 'type': 'make_to_order',
2043 'is_line_split': pol.is_line_split,
2044 'original_line_id': pol.original_line_id.linked_sol_id.id if pol.original_line_id else False,
2045 'procurement_request': sale_order.procurement_request,
2046 }
2047+ if pol.state not in ['confirmed', 'done', 'cancel', 'cancel_r']:
2048+ sol_values['confirmed_delivery_date'] = line_confirmed
2049
2050 # update modification comment if it is set
2051 if pol.modification_comment:
2052@@ -341,7 +344,7 @@
2053 'nomen_sub_5': pol.nomen_sub_5 and pol.nomen_sub_5.id or False,
2054 'confirmed_delivery_date': line_confirmed,
2055 'stock_take_date': line_stock_take,
2056- 'date_planned': (datetime.now() + relativedelta(days=+2)).strftime('%Y-%m-%d'),
2057+ 'date_planned': pol.date_planned or (datetime.now() + relativedelta(days=+2)).strftime('%Y-%m-%d'),
2058 'sync_sourced_origin': pol.instance_sync_order_ref and pol.instance_sync_order_ref.name or False,
2059 'set_as_sourced_n': True,
2060 }
2061@@ -363,7 +366,7 @@
2062 new_sol_id = self.pool.get('sale.order.line').create(cr, uid, sol_values, context=context)
2063
2064 # update current PO line:
2065- self.write(cr, uid, pol.id, {'link_so_id': fo_id, 'linked_sol_id': new_sol_id}, context=context)
2066+ self.write(cr, uid, pol.id, {'link_so_id': fo_id, 'linked_sol_id': new_sol_id, 'location_dest_id': self.final_location_dest(cr, uid, pol, fo_obj=sale_order, context=context)}, context=context)
2067
2068 context['from_back_sync'] = False
2069 return new_sol_id
2070@@ -582,7 +585,8 @@
2071 # doesn't update original qty and uom if already set (from IR)
2072 line_update = {
2073 'original_price': pol.price_unit,
2074- 'original_currency_id': pol.currency_id.id
2075+ 'original_currency_id': pol.currency_id.id,
2076+ 'location_dest_id': self.final_location_dest(cr, uid, pol, context=context),
2077 }
2078
2079 if not pol.original_product:
2080
2081=== modified file 'bin/addons/stock/product.py'
2082--- bin/addons/stock/product.py 2020-01-09 14:41:10 +0000
2083+++ bin/addons/stock/product.py 2020-07-27 12:47:56 +0000
2084@@ -426,6 +426,44 @@
2085 res['fields']['qty_available']['string'] = _('Produced Qty')
2086 return res
2087
2088+ def get_pipeline_from_po(self, cr, uid, ids, from_date=False, to_date=False, location_ids=False, context=None):
2089+ '''
2090+ ids: product_ids
2091+
2092+ return the pipeline from validated(-p) purchase order line
2093+ '''
2094+
2095+ params = []
2096+ query = ''
2097+ if location_ids:
2098+ query += ' and location_dest_id in %s '
2099+ if isinstance(location_ids, (int, long)):
2100+ params.append((location_ids, ))
2101+ else:
2102+ params.append(tuple(location_ids))
2103+
2104+ if from_date:
2105+ query += ' and coalesce(confirmed_delivery_date, date_planned) > %s '
2106+ params.append(from_date)
2107+
2108+ if to_date:
2109+ query += ' and coalesce(confirmed_delivery_date, date_planned) <= %s '
2110+ params.append(to_date)
2111+
2112+
2113+ cr.execute('''
2114+ select
2115+ pol.product_id, sum(pol.product_qty)
2116+ from
2117+ purchase_order_line pol
2118+ where
2119+ pol.product_id in %s and
2120+ pol.state in ('validated', 'validated_n', 'sourced_sy', 'sourced_v', 'sourced_n')
2121+ ''' + query + '''
2122+ group by pol.product_id''',
2123+ [tuple(ids)]+params) # not_a_user_entry
2124+ return dict(cr.fetchall())
2125+
2126 product_product()
2127
2128 class product_template(osv.osv):
2129
2130=== modified file 'bin/addons/stock/stock_move.py'
2131--- bin/addons/stock/stock_move.py 2020-01-29 16:41:23 +0000
2132+++ bin/addons/stock/stock_move.py 2020-07-27 12:47:56 +0000
2133@@ -2415,5 +2415,14 @@
2134 return {
2135 'value': {'integrity_error': 'empty'}
2136 }
2137+
2138+ def open_in_form(self, cr, uid, ids, context=None):
2139+ move = self.browse(cr, uid, ids[0], fields_to_fetch=['picking_id', 'linked_incoming_move'], context=context)
2140+
2141+ res = self.pool.get('ir.actions.act_window').open_view_from_xmlid(cr, uid, 'stock.action_picking_tree4', ['form', 'tree'], new_tab=True, context=context)
2142+ res['keep_open'] = True
2143+ res['res_id'] = move.linked_incoming_move and move.linked_incoming_move.picking_id.id or move.picking_id.id
2144+ return res
2145+
2146 stock_move()
2147
2148
2149=== modified file 'bin/addons/stock/stock_view.xml'
2150--- bin/addons/stock/stock_view.xml 2020-02-25 10:55:04 +0000
2151+++ bin/addons/stock/stock_view.xml 2020-07-27 12:47:56 +0000
2152@@ -1251,6 +1251,7 @@
2153 <field name="ssl_check" invisible="True" widget="null_boolean" />
2154 <field name="dg_check" widget="null_boolean" />
2155 <field name="np_check" widget="null_boolean" />
2156+ <button type="object" name="open_in_form" icon="terp-gtk-go-back-rtl" string="View IN" />
2157 </tree>
2158 </field>
2159 </record>
2160
2161=== modified file 'bin/osv/orm.py'
2162--- bin/osv/orm.py 2020-05-25 09:59:55 +0000
2163+++ bin/osv/orm.py 2020-07-27 12:47:56 +0000
2164@@ -2117,7 +2117,7 @@
2165 '''
2166 return self.search(cr, user, args, offset, limit, order, context, count)
2167
2168- def search_exist(self, cr, user, args, context=None):
2169+ def search_exists(self, cr, user, args, context=None):
2170 """
2171 return True if there is at least one element matching the criterions,
2172 False otherwise.
2173@@ -2125,6 +2125,9 @@
2174 return bool(self.search(cr, user, args, context=context,
2175 limit=1, order='NO_ORDER'))
2176
2177+ def search_exist(self, cr, user, args, context=None):
2178+ return self.search_exists(cr, user, args, context=context)
2179+
2180 def search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False):
2181 """
2182 Search for records based on a search domain.

Subscribers

People subscribed via source and target branches