Merge lp:~dorian-kemps/unifield-server/US-7158 into lp:unifield-server

Proposed by jftempo
Status: Merged
Merged at revision: 5781
Proposed branch: lp:~dorian-kemps/unifield-server/US-7158
Merge into: lp:unifield-server
Diff against target: 1701 lines (+747/-271)
23 files modified
bin/addons/delivery_mechanism/delivery_mechanism.py (+4/-3)
bin/addons/msf_doc_import/wizard/wizard_in_simulation_screen.py (+26/-4)
bin/addons/msf_doc_import/wizard/wizard_po_simulation_screen.py (+30/-6)
bin/addons/msf_outgoing/msf_outgoing.py (+26/-1)
bin/addons/msf_outgoing/wizard/incoming_shipment_processor.py (+4/-0)
bin/addons/msf_profile/data/patches.xml (+8/-0)
bin/addons/msf_profile/i18n/fr_MF.po (+35/-13)
bin/addons/msf_profile/msf_profile.py (+38/-1)
bin/addons/msf_sync_data_server/data/sync_server.sync_rule.csv (+4/-2)
bin/addons/msf_tools/report/report_stopped_products.py (+12/-12)
bin/addons/order_types/stock.py (+5/-1)
bin/addons/product/product.py (+1/-1)
bin/addons/product_attributes/product_attributes.py (+298/-67)
bin/addons/product_attributes/product_attributes_data.xml (+159/-131)
bin/addons/purchase/purchase_order_line.py (+4/-1)
bin/addons/sale/sale_order.py (+23/-8)
bin/addons/sale/sale_workflow.py (+6/-0)
bin/addons/sale/wizard/internal_request_import.py (+12/-5)
bin/addons/sourcing/sale_order_line.py (+6/-0)
bin/addons/specific_rules/specific_rules.py (+2/-2)
bin/addons/specific_rules/stock.py (+5/-4)
bin/addons/stock/stock_move.py (+11/-0)
bin/addons/sync_client/update.py (+28/-9)
To merge this branch: bzr merge lp:~dorian-kemps/unifield-server/US-7158
Reviewer Review Type Date Requested Status
UniField Reviewer Team Pending
Review via email: mp+383119@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/delivery_mechanism/delivery_mechanism.py'
2--- bin/addons/delivery_mechanism/delivery_mechanism.py 2020-03-20 14:08:02 +0000
3+++ bin/addons/delivery_mechanism/delivery_mechanism.py 2020-04-29 08:55:42 +0000
4@@ -25,6 +25,7 @@
5 from tools.translate import _
6 from order_types.stock import check_rw_warning
7 import logging
8+import tools
9
10
11 class stock_picking_processing_info(osv.osv_memory):
12@@ -701,9 +702,9 @@
13 logging.getLogger('stock.picking').warn('Exception do_incoming_shipment', exc_info=True)
14 for wiz in inc_proc_obj.read(new_cr, uid, wizard_ids, ['picking_id'], context=context):
15 self.update_processing_info(new_cr, uid, wiz['picking_id'][0], False, {
16- 'error_msg': '%s\n\nPlease reset the incoming shipment '\
17- 'processing and fix the source of the error'\
18- 'before re-try the processing.' % str(e),
19+ 'error_msg': _('Error: %s\n\nPlease reset the incoming shipment '\
20+ 'processing and fix the source of the error '\
21+ 'before re-try the processing.') % tools.ustr(e.value),
22 }, context=context)
23 finally:
24 # Close the cursor
25
26=== modified file 'bin/addons/msf_doc_import/wizard/wizard_in_simulation_screen.py'
27--- bin/addons/msf_doc_import/wizard/wizard_in_simulation_screen.py 2019-08-28 08:33:22 +0000
28+++ bin/addons/msf_doc_import/wizard/wizard_in_simulation_screen.py 2020-04-29 08:55:42 +0000
29@@ -1414,9 +1414,19 @@
30
31 # Product
32 prod_id = False
33+ loc_id = line.move_id and line.move_id.location_id.id or line.parent_line_id and \
34+ line.parent_line_id.move_id.location_id.id or False
35 if values.get('product_code') == line.move_product_id.default_code:
36- prod_id = line.move_product_id and line.move_product_id.id or False
37- write_vals['imp_product_id'] = prod_id
38+ if line.move_product_id:
39+ p_error, p_msg = prod_obj._test_restriction_error(cr, uid, [line.move_product_id.id],
40+ vals={'location_id': loc_id},
41+ context=context)
42+ if p_error: # Check constraints on products
43+ write_vals['type_change'] = 'error'
44+ errors.append(p_msg)
45+ else:
46+ prod_id = line.move_product_id.id
47+ write_vals['imp_product_id'] = prod_id
48 else:
49 prod_id = False
50 if values.get('product_code'):
51@@ -1438,9 +1448,21 @@
52 self.write(cr, uid, [line.id], write_vals, context=context)
53 continue
54 else:
55- write_vals['imp_product_id'] = prod_ids[0]
56+ p_error, p_msg = prod_obj._test_restriction_error(cr, uid, [prod_id], vals={'location_id': loc_id},
57+ context=context)
58+ if p_error: # Check constraints on products
59+ write_vals['type_change'] = 'error'
60+ errors.append(p_msg)
61+ else:
62+ write_vals['imp_product_id'] = prod_ids[0]
63 else:
64- write_vals['imp_product_id'] = prod_id
65+ p_error, p_msg = prod_obj._test_restriction_error(cr, uid, [prod_id], vals={'location_id': loc_id},
66+ context=context)
67+ if p_error: # Check constraints on products
68+ write_vals['type_change'] = 'error'
69+ errors.append(p_msg)
70+ else:
71+ write_vals['imp_product_id'] = prod_id
72
73 product = False
74 if write_vals.get('imp_product_id'):
75
76=== modified file 'bin/addons/msf_doc_import/wizard/wizard_po_simulation_screen.py'
77--- bin/addons/msf_doc_import/wizard/wizard_po_simulation_screen.py 2020-02-17 11:35:43 +0000
78+++ bin/addons/msf_doc_import/wizard/wizard_po_simulation_screen.py 2020-04-29 08:55:42 +0000
79@@ -1655,8 +1655,18 @@
80 write_vals['type_change'] = 'error'
81
82 # Product
83+ partner_id = line.simu_id.order_id.partner_id.id
84 if (values[2] and values[2] == line.in_product_id.default_code):
85- write_vals['imp_product_id'] = line.in_product_id and line.in_product_id.id or False
86+ if line.in_product_id:
87+ p_error, p_msg = prod_obj._test_restriction_error(cr, uid, [line.in_product_id.id],
88+ vals={'partner_id': partner_id}, context=context)
89+ if p_error: # Check constraints on products
90+ write_vals['type_change'] = 'error'
91+ errors.append(p_msg)
92+ else:
93+ write_vals['imp_product_id'] = line.in_product_id.id
94+ else:
95+ write_vals['imp_product_id'] = False
96 else:
97 prod_id = False
98 if values[2]:
99@@ -1664,14 +1674,28 @@
100
101 if not prod_id and values[2]:
102 prod_ids = prod_obj.search(cr, uid, [('default_code', '=', values[2])], context=context)
103- if not prod_ids:
104+ if prod_ids:
105+ p_error, p_msg = prod_obj._test_restriction_error(cr, uid, [prod_ids[0]],
106+ vals={'partner_id': partner_id},
107+ context=context)
108+ if p_error: # Check constraints on products
109+ write_vals['type_change'] = 'error'
110+ errors.append(p_msg)
111+ else:
112+ write_vals['imp_product_id'] = prod_ids[0]
113+ else:
114 write_vals['type_change'] = 'error'
115 errors.append(_('Product %s not found in database') % values[2])
116+ else:
117+ if prod_id:
118+ p_error, p_msg = prod_obj._test_restriction_error(cr, uid, [prod_id], vals={'partner_id': partner_id},
119+ context=context)
120+ if p_error: # Check constraints on products
121+ write_vals['type_change'] = 'error'
122+ errors.append(p_msg)
123+ else:
124+ write_vals['imp_product_id'] = prod_id
125 else:
126- write_vals['imp_product_id'] = prod_ids[0]
127- else:
128- write_vals['imp_product_id'] = prod_id
129- if not prod_id:
130 write_vals['type_change'] = 'error'
131
132 write_vals['ad_info'] = False
133
134=== modified file 'bin/addons/msf_outgoing/msf_outgoing.py'
135--- bin/addons/msf_outgoing/msf_outgoing.py 2020-02-10 17:16:32 +0000
136+++ bin/addons/msf_outgoing/msf_outgoing.py 2020-04-29 08:55:42 +0000
137@@ -569,7 +569,11 @@
138 ('from_pack', '=', family.from_pack),
139 ('to_pack', '=', family.to_pack)
140 ], context=context)
141- for move in move_obj.browse(cr, uid, move_ids, fields_to_fetch=['product_uom', 'qty_per_pack'], context=context):
142+ ftf = ['product_id', 'product_uom', 'qty_per_pack', 'location_dest_id']
143+ for move in move_obj.browse(cr, uid, move_ids, fields_to_fetch=ftf, context=context):
144+ if move.product_id and move.product_id.state.code == 'forbidden': # Check constraints on lines
145+ check_vals = {'location_dest_id': move.location_dest_id.id, 'move': move}
146+ self.pool.get('product.product')._get_restriction_error(cr, uid, [move.product_id.id], check_vals, context=context)
147 if family.selected_number < int(family.num_of_packs) and move.product_uom.rounding == 1 and \
148 move.qty_per_pack % move.product_uom.rounding != 0:
149 raise osv.except_osv(_('Error'), _('Warning, this range of packs contains one or more products with a decimal quantity per pack. All packs must be processed together'))
150@@ -1587,6 +1591,10 @@
151
152 # closing FO lines:
153 for stock_move in packing.move_lines:
154+ if stock_move.product_id and stock_move.product_id.state.code == 'forbidden': # Check constraints on lines
155+ check_vals = {'location_dest_id': stock_move.location_dest_id.id, 'move': stock_move}
156+ self.pool.get('product.product')._get_restriction_error(cr, uid, [stock_move.product_id.id],
157+ check_vals, context=context)
158 if stock_move.sale_line_id:
159 open_moves = self.pool.get('stock.move').search_exist(cr, uid, [
160 ('sale_line_id', '=', stock_move.sale_line_id.id),
161@@ -3050,6 +3058,11 @@
162 for line in wizard.move_ids:
163 move = line.move_id
164
165+ if move.product_id and move.product_id.state.code == 'forbidden': # Check constraints on lines
166+ check_vals = {'location_dest_id': move.location_dest_id.id, 'move': move}
167+ self.pool.get('product.product')._get_restriction_error(cr, uid, [move.product_id.id], check_vals,
168+ context=context)
169+
170 if move.picking_id.id != picking.id:
171 continue
172
173@@ -3327,6 +3340,10 @@
174 # for now, each new line from the wizard corresponds to a new stock.move
175 # it could be interesting to regroup according to production lot/asset id
176 for line in move_to_process:
177+ if line.product_id and line.product_id.state.code == 'forbidden': # Check constraints on lines
178+ check_vals = {'location_dest_id': line.location_dest_id.id, 'move': line}
179+ self.pool.get('product.product')._get_restriction_error(cr, uid, [line.product_id.id], check_vals, context=context)
180+
181 if line.qty_to_process <= 0 or line.state != 'assigned' or line.product_qty == 0:
182 continue
183
184@@ -3559,6 +3576,10 @@
185 # For each processed lines, save the processed quantity to update the draft picking ticket
186 # and create a new line on PPL
187 for line in picking.move_lines:
188+ if line.product_id: # Check constraints on lines
189+ check_vals = {'location_dest_id': line.location_dest_id.id, 'move': line}
190+ self.pool.get('product.product')._get_restriction_error(cr, uid, [line.product_id.id], check_vals, context=context)
191+
192 if line.state != 'assigned':
193 line.qty_to_process = 0
194
195@@ -3707,6 +3728,10 @@
196 picking = self.browse(cr, uid, ids[0], context=context)
197 rounding_issues = []
198 for move in picking.move_lines:
199+ if move.product_id and move.product_id.state.code == 'forbidden': # Check constraints on lines
200+ check_vals = {'location_dest_id': move.location_dest_id.id, 'move': move}
201+ self.pool.get('product.product')._get_restriction_error(cr, uid, [move.product_id.id], check_vals, context=context)
202+
203 if move.state == 'done':
204 continue
205 if not ppl_processor._check_rounding(cr, uid, move.product_uom, move.num_of_packs, move.product_qty, context=context):
206
207=== modified file 'bin/addons/msf_outgoing/wizard/incoming_shipment_processor.py'
208--- bin/addons/msf_outgoing/wizard/incoming_shipment_processor.py 2019-08-28 08:33:22 +0000
209+++ bin/addons/msf_outgoing/wizard/incoming_shipment_processor.py 2020-04-29 08:55:42 +0000
210@@ -336,6 +336,10 @@
211 )
212
213 for line in proc.move_ids:
214+ if line.product_id: # Check constraints on products
215+ self.pool.get('product.product')._get_restriction_error(cr, uid, [line.product_id.id],
216+ {'location_id': line.location_id.id},
217+ context=context)
218 # If one line as an error, return to wizard
219 if line.integrity_status not in ['empty', 'missing_1', 'to_smaller_than_from', 'overlap', 'gap', 'missing_weight']:
220 return {
221
222=== modified file 'bin/addons/msf_profile/data/patches.xml'
223--- bin/addons/msf_profile/data/patches.xml 2020-03-23 10:37:13 +0000
224+++ bin/addons/msf_profile/data/patches.xml 2020-04-29 08:55:42 +0000
225@@ -522,10 +522,18 @@
226 <field name="method">us_7025_7039_fix_nr_empty_ins</field>
227 </record>
228
229+ <record id="us_7215_prod_set_active_sync" model="patch.scripts">
230+ <field name="method">us_7215_prod_set_active_sync</field>
231+ </record>
232+
233 <!-- UF17.0 -->
234 <record id="us_7221_reset_starting_balance" model="patch.scripts">
235 <field name="method">us_7221_reset_starting_balance</field>
236 </record>
237
238+ <record id="us_7158_prod_set_uf_status" model="patch.scripts">
239+ <field name="method">us_7158_prod_set_uf_status</field>
240+ </record>
241+
242 </data>
243 </openerp>
244
245=== modified file 'bin/addons/msf_profile/i18n/fr_MF.po'
246--- bin/addons/msf_profile/i18n/fr_MF.po 2020-04-20 08:36:07 +0000
247+++ bin/addons/msf_profile/i18n/fr_MF.po 2020-04-29 08:55:42 +0000
248@@ -5408,7 +5408,7 @@
249 #: code:addons/product_attributes/product_attributes.py:1072
250 #, python-format
251 msgid "be %s externally"
252-msgstr "be %s externally"
253+msgstr "être %s à l'extérieur"
254
255 #. modules: procurement_auto, procurement, procurement_cycle
256 #: view:stock.warehouse.orderpoint:0
257@@ -8020,7 +8020,7 @@
258 #: code:addons/product_attributes/product_attributes.py:1080
259 #, python-format
260 msgid "be supplied/exchanged internally"
261-msgstr "be supplied/exchanged internally"
262+msgstr "être approvisionné/échangé en interne"
263
264 #. module: msf_outgoing
265 #: view:stock.move.memory.shipment.returnpacks:0
266@@ -14719,7 +14719,7 @@
267 #: code:addons/product_attributes/product_attributes.py:1068
268 #, python-format
269 msgid "be exchanged"
270-msgstr "be exchanged"
271+msgstr "être échangé"
272
273 #. modules: base, base_setup, unifield_setup
274 #: model:res.country,name:base.sl
275@@ -41825,7 +41825,7 @@
276 #: code:addons/product_attributes/product_attributes.py:1093
277 #, python-format
278 msgid "status"
279-msgstr "status"
280+msgstr "statut"
281
282 #. module: msf_doc_import
283 #: code:addons/msf_doc_import/check_line.py:187
284@@ -48544,7 +48544,7 @@
285 #: code:addons/product_attributes/product_attributes.py:1076
286 #, python-format
287 msgid "be %s ESC"
288-msgstr "be %s ESC"
289+msgstr "être %s l'ESC"
290
291 #. module: kit
292 #: code:addons/kit/kit_creation.py:83
293@@ -51500,7 +51500,7 @@
294 #: code:addons/product_attributes/product_attributes.py:1088
295 #, python-format
296 msgid "be stored anymore"
297-msgstr "be stored anymore"
298+msgstr "être stocké"
299
300 #. module: account
301 #: model:ir.actions.act_window,name:account.action_account_print_journal
302@@ -52998,7 +52998,7 @@
303 #: code:addons/product_attributes/product_attributes.py:1093
304 #, python-format
305 msgid "product creator"
306-msgstr "product creator"
307+msgstr "créateur produit"
308
309 #. modules: procurement, account_hq_entries
310 #: view:hq.entries:0
311@@ -54577,7 +54577,7 @@
312 #: code:addons/product_attributes/product_attributes.py:1076
313 #, python-format
314 msgid "purchased at"
315-msgstr "purchased at"
316+msgstr "acheté à"
317
318 #. module: msf_doc_import
319 #: model:product.nomenclature,name:msf_doc_import.nomen_tbd0
320@@ -64441,7 +64441,7 @@
321 #: code:addons/product_attributes/product_attributes.py:1076
322 #, python-format
323 msgid "shipped to"
324-msgstr "shipped to"
325+msgstr "envoyé vers"
326
327 #. module: account
328 #: code:addons/account/account.py:1247
329@@ -79019,7 +79019,7 @@
330 #: code:addons/product_attributes/product_attributes.py:1084
331 #, python-format
332 msgid "be consumed internally"
333-msgstr "be consumed internally"
334+msgstr "être consommé en interne"
335
336 #. module: msf_doc_import
337 #: code:addons/msf_doc_import/wizard/abstract_wizard_import.py:317
338@@ -83471,7 +83471,7 @@
339 #: code:addons/product_attributes/product_attributes.py:1072
340 #, python-format
341 msgid "shipped"
342-msgstr "Expédié"
343+msgstr "expédié"
344
345 #. module: account_period_closing_level
346 #: code:addons/account_period_closing_level/account_period.py:200
347@@ -89806,8 +89806,8 @@
348 #. module: product_attributes
349 #: code:addons/product_attributes/product_attributes.py:1096
350 #, python-format
351-msgid "The product [%s] %s gets the %s '%s' and consequently can't %s"
352-msgstr "The product [%s] %s gets the %s '%s' and consequently can't %s"
353+msgid "The product [%s] gets the %s '%s' and consequently can't %s"
354+msgstr "Le produit [%s] a le %s '%s' et ne peut donc plus %s"
355
356 #. module: sale
357 #: model:ir.actions.act_window,help:sale.action_shop_form
358@@ -109019,6 +109019,18 @@
359 msgid "more FMC"
360 msgstr "plus de PCM"
361
362+#. module: product_attributes
363+#: code:addons/product_attributes/product_attributes.py:1196
364+#, python-format
365+msgid "be sent"
366+msgstr "être envoyé"
367+
368+#. module: product_attributes
369+#: code:addons/product_attributes/product_attributes.py:1206
370+#, python-format
371+msgid "%s line %s: "
372+msgstr "%s ligne %s: "
373+
374 #. module: msf_doc_import
375 #: code:addons/msf_doc_import/account.py:287
376 #, python-format
377@@ -109195,3 +109207,13 @@
378 #: code:addons/account_override/invoice.py:1272
379 msgid "Split Stock Transfer Voucher"
380 msgstr "Fractionner Bon Client"
381+
382+#. module: delivery_mechanism
383+#: code:addons/delivery_mechanism/delivery_mechanism.py:705
384+#, python-format
385+msgid "Error: %s\n"
386+"\n"
387+"Please reset the incoming shipment processing and fix the source of the error before re-try the processing."
388+msgstr "Erreur: %s\n"
389+"\n"
390+"Veuillez réinitialiser le processus de la livraison entrante et corriger la source de cette erreur avant de recommencer le processus."
391
392=== modified file 'bin/addons/msf_profile/msf_profile.py'
393--- bin/addons/msf_profile/msf_profile.py 2020-04-01 10:25:53 +0000
394+++ bin/addons/msf_profile/msf_profile.py 2020-04-29 08:55:42 +0000
395@@ -52,8 +52,25 @@
396 'model': lambda *a: 'patch.scripts',
397 }
398
399-
400 # UF17.0
401+ def us_7158_prod_set_uf_status(self, cr, uid, *a, **b):
402+ st_obj = self.pool.get('product.status')
403+ stopped_ids = st_obj.search(cr, uid, [('code', '=', 'stopped'), ('active', 'in', ['t', 'f'])])
404+ phase_out_ids = st_obj.search(cr, uid, [('code', '=', 'phase_out')])
405+
406+ st12_ids = st_obj.search(cr, uid, [('code', 'in', ['status1', 'status2']), ('active', 'in', ['t', 'f'])])
407+ valid_ids = st_obj.search(cr, uid, [('code', '=', 'valid')])
408+
409+ # state 1, state2, blank to valid
410+ cr.execute('''update product_template set state = %s where state is NUll or state in %s''' , (valid_ids[0], tuple(st12_ids)))
411+ cr.execute('''update stock_mission_report_line set product_state='valid' where product_state is NULL or product_state in ('', 'status1', 'status2')''')
412+
413+ # stopped to pahse_out
414+ cr.execute('''update product_template set state = %s where state = %s''' , (phase_out_ids[0], stopped_ids[0]))
415+ cr.execute('''update stock_mission_report_line set product_state='phase_out' where product_state = 'stopped' ''')
416+
417+ return True
418+
419 def us_7221_reset_starting_balance(self, cr, uid, *a, **b):
420 """
421 Reset the Starting Balance of the first register created for each journal if it is still in Draft state
422@@ -78,9 +95,29 @@
423 AND journal_id IN (SELECT id FROM account_journal WHERE type in ('bank', 'cash'));
424 """)
425 self._logger.warn('Starting Balance set to zero in %s registers.' % (cr.rowcount,))
426+
427 return True
428
429 # UF16.1
430+ def us_7215_prod_set_active_sync(self, cr, uids, *a, **b):
431+ if not self.pool.get('sync.client.message_received'):
432+ # new instance
433+ return True
434+ cr.execute("""
435+ update product_product p set
436+ active_change_date=d.last_modification, active_sync_change_date=d.sync_date
437+ from
438+ ir_model_data d
439+ where
440+ d.model='product.product' and
441+ d.module='sd' and
442+ d.res_id = p.id and
443+ touched like '%''state''%'
444+ """)
445+ self._logger.warn('Set active_sync_change_date on %d product' % (cr.rowcount,))
446+
447+ return True
448+
449 def remove_ir_actions_linked_to_deleted_modules(self, cr, uid, *a, **b):
450 # delete remove actions
451 cr.execute("delete from ir_act_window where id in (select res_id from ir_model_data where module in ('procurement_report', 'threshold_value') and model='ir.actions.act_window')")
452
453=== modified file 'bin/addons/msf_sync_data_server/data/sync_server.sync_rule.csv'
454--- bin/addons/msf_sync_data_server/data/sync_server.sync_rule.csv 2020-03-04 17:30:01 +0000
455+++ bin/addons/msf_sync_data_server/data/sync_server.sync_rule.csv 2020-04-29 08:55:42 +0000
456@@ -123,8 +123,10 @@
457 msf_sync_data_server.price_list_version,FALSE,TRUE,FALSE,FALSE,bidirectional,Bidirectional,[],"['active', 'date_end', 'date_start', 'name', 'pricelist_id/id']",MISSION,product.pricelist.version,,Price List Version,Valid,,561
458 msf_sync_data_server.country_restrictions,TRUE,TRUE,FALSE,TRUE,bidirectional,Down,[],['name'],MISSION,res.country.restriction,,Country restrictions,Valid,,570
459 msf_sync_data_server.country_code_mapping,TRUE,TRUE,TRUE,TRUE,bidirectional,Down,[],"['instance_id/id', 'mapping_value']",COORDINATIONS,country.export.mapping,,Country Code Mapping,Valid,,571
460-msf_sync_data_server.oc_product_creator_itc_esc_hq,TRUE,TRUE,FALSE,TRUE,bidirectional,Down,"['|','|','|',('international_status','=','UniData'),('international_status','=','ITC'),('international_status','=','ESC'),('international_status','=','HQ'), ('active', 'in', ['t','f'])]","['alert_time', 'batch_management', 'categ_id/id', 'closed_article', 'manufacturer_txt', 'manufacturer_ref', 'code', 'cold_chain/id', 'composed_kit', 'xmlid_code', 'cost_method', 'country_restriction/id', 'dangerous_goods', 'default_code', 'description', 'description2', 'description_purchase', 'description_sale', 'gmdn_code', 'gmdn_description', 'heat_sensitive_item/id', 'international_status/id', 'justification_code_id/id', 'library', 'life_time', 'list_ids/id','med_device_class', 'name', 'name_template', 'controlled_substance', 'nomen_manda_0/id', 'nomen_manda_1/id', 'nomen_manda_2/id', 'nomen_manda_3/id', 'options_ids/id', 'perishable', 'procure_delay', 'procure_method', 'produce_delay', 'product_catalog_page', 'product_catalog_path', 'property_account_expense/id', 'property_account_income/id', 'property_stock_account_input/id', 'property_stock_account_output/id', 'restricted_country', 'short_shelf_life', 'single_use', 'sterilized', 'standard_price', 'sublist', 'subtype', 'asset_type_id', 'supply_method', 'type', 'un_code', 'uom_id/id', 'uom_po_id/id','use_time', 'valuation', 'weight', 'weight_net', 'active', 'state', 'state_ud', 'old_code', 'new_code', 'function_value', 'form_value', 'fit_value', 'standard_ok', 'local_from_hq', 'transport_ok','volume', 'soq_quantity', 'soq_weight', 'soq_volume', 'msfid', 'oc_subscription']",OC,product.product,,"OC Product (Creator = ITC, ESC, UniData or HQ)",Valid,,600
461-msf_sync_data_server.mission_product_creator_local,TRUE,TRUE,FALSE,TRUE,bidirectional,Down,"[('international_status','=','Local'), ('active', 'in', ['t','f'])]","['alert_time', 'batch_management', 'categ_id/id', 'closed_article', 'manufacturer_txt', 'manufacturer_ref', 'code', 'xmlid_code','cold_chain/id', 'composed_kit', 'cost_method', 'country_restriction/id', 'dangerous_goods', 'default_code', 'description', 'description2', 'description_purchase', 'description_sale', 'gmdn_code', 'gmdn_description', 'heat_sensitive_item/id', 'international_status/id', 'justification_code_id/id', 'library', 'life_time', 'list_ids/id','med_device_class', 'name', 'name_template', 'controlled_substance', 'nomen_manda_0/id', 'nomen_manda_1/id', 'nomen_manda_2/id', 'nomen_manda_3/id', 'options_ids/id', 'perishable', 'procure_delay', 'procure_method', 'produce_delay', 'product_catalog_page', 'product_catalog_path', 'property_account_expense/id', 'property_account_income/id', 'property_stock_account_input/id', 'property_stock_account_output/id', 'restricted_country', 'short_shelf_life', 'single_use', 'sterilized', 'standard_price', 'sublist', 'subtype', 'asset_type_id', 'supply_method', 'type', 'un_code', 'uom_id/id', 'uom_po_id/id','use_time', 'valuation', 'weight', 'weight_net', 'active', 'state', 'old_code', 'new_code', 'function_value', 'form_value', 'fit_value', 'standard_ok','transport_ok','volume', 'soq_quantity', 'soq_weight','soq_volume']",MISSION,product.product,,Mission Product (Creator = local),Valid,,601
462+msf_sync_data_server.oc_product_creator_itc_esc_hq,TRUE,TRUE,FALSE,TRUE,bidirectional,Down,"['|','|','|',('international_status','=','UniData'),('international_status','=','ITC'),('international_status','=','ESC'),('international_status','=','HQ'), ('active', 'in', ['t','f'])]","['alert_time', 'batch_management', 'categ_id/id', 'closed_article', 'manufacturer_txt', 'manufacturer_ref', 'code', 'cold_chain/id', 'composed_kit', 'xmlid_code', 'cost_method', 'country_restriction/id', 'dangerous_goods', 'default_code', 'description', 'description2', 'description_purchase', 'description_sale', 'gmdn_code', 'gmdn_description', 'heat_sensitive_item/id', 'international_status/id', 'justification_code_id/id', 'library', 'life_time', 'list_ids/id','med_device_class', 'name', 'name_template', 'controlled_substance', 'nomen_manda_0/id', 'nomen_manda_1/id', 'nomen_manda_2/id', 'nomen_manda_3/id', 'options_ids/id', 'perishable', 'procure_delay', 'procure_method', 'produce_delay', 'product_catalog_page', 'product_catalog_path', 'property_account_expense/id', 'property_account_income/id', 'property_stock_account_input/id', 'property_stock_account_output/id', 'restricted_country', 'short_shelf_life', 'single_use', 'sterilized', 'standard_price', 'sublist', 'subtype', 'asset_type_id', 'supply_method', 'type', 'un_code', 'uom_id/id', 'uom_po_id/id','use_time', 'valuation', 'weight', 'weight_net', 'state', 'state_ud', 'old_code', 'new_code', 'function_value', 'form_value', 'fit_value', 'standard_ok', 'local_from_hq', 'transport_ok','volume', 'soq_quantity', 'soq_weight', 'soq_volume', 'msfid', 'oc_subscription']",OC,product.product,,"OC Product (Creator = ITC, ESC, UniData or HQ)",Valid,,600
463+msf_sync_data_server.mission_product_creator_local,TRUE,TRUE,FALSE,TRUE,bidirectional,Down,"[('international_status','=','Local'), ('active', 'in', ['t','f'])]","['alert_time', 'batch_management', 'categ_id/id', 'closed_article', 'manufacturer_txt', 'manufacturer_ref', 'code', 'xmlid_code','cold_chain/id', 'composed_kit', 'cost_method', 'country_restriction/id', 'dangerous_goods', 'default_code', 'description', 'description2', 'description_purchase', 'description_sale', 'gmdn_code', 'gmdn_description', 'heat_sensitive_item/id', 'international_status/id', 'justification_code_id/id', 'library', 'life_time', 'list_ids/id','med_device_class', 'name', 'name_template', 'controlled_substance', 'nomen_manda_0/id', 'nomen_manda_1/id', 'nomen_manda_2/id', 'nomen_manda_3/id', 'options_ids/id', 'perishable', 'procure_delay', 'procure_method', 'produce_delay', 'product_catalog_page', 'product_catalog_path', 'property_account_expense/id', 'property_account_income/id', 'property_stock_account_input/id', 'property_stock_account_output/id', 'restricted_country', 'short_shelf_life', 'single_use', 'sterilized', 'standard_price', 'sublist', 'subtype', 'asset_type_id', 'supply_method', 'type', 'un_code', 'uom_id/id', 'uom_po_id/id','use_time', 'valuation', 'weight', 'weight_net', 'state', 'old_code', 'new_code', 'function_value', 'form_value', 'fit_value', 'standard_ok','transport_ok','volume', 'soq_quantity', 'soq_weight','soq_volume']",MISSION,product.product,,Mission Product (Creator = local),Valid,,601
464+msf_sync_data_server.oc_product_creator_itc_esc_hq_active,TRUE,TRUE,FALSE,TRUE,bidirectional,Down,"['|','|','|',('international_status','=','UniData'),('international_status','=','ITC'),('international_status','=','ESC'),('international_status','=','HQ'), ('active', 'in', ['t','f']), ('active_change_date', '!=', False)]","['active', 'local_from_hq']",OC,product.product,,"OC Product Active (Creator = ITC, ESC, UniData or HQ)",Valid,,602
465+msf_sync_data_server.mission_product_creator_local_active,TRUE,TRUE,FALSE,TRUE,bidirectional,Down,"[('international_status','=','Local'), ('active', 'in', ['t','f']), ('active_change_date', '!=', False)]","['active']",MISSION,product.product,,Mission Product Active (Creator = local),Valid,,603
466 msf_sync_data_server.standard_product_list,TRUE,TRUE,TRUE,TRUE,bidirectional,Down,"[('standard_list_ok','=','True')]","['creation_date', 'creator', 'description', 'last_update_date', 'name', 'order_list_print_ok', 'ref', 'standard_list_ok', 'type']",OC,product.list,,Standard Product List,Valid,,605
467 msf_sync_data_server.standard_product_list_line,TRUE,TRUE,TRUE,TRUE,bidirectional,Down,"[('list_id' , 'in', ('product.list', 'id', [('standard_list_ok','=','True')]))]","['comment','list_id/id','ref','name/id']",OC,product.list.line,,Standard Product List Line,Valid,,606
468 msf_sync_data_server.tax_code,TRUE,TRUE,FALSE,TRUE,bidirectional,Down,[],"['code', 'info', 'name', 'notprintable', 'sign']",OC,account.tax.code,,Tax Code,Valid,,610
469
470=== modified file 'bin/addons/msf_tools/report/report_stopped_products.py'
471--- bin/addons/msf_tools/report/report_stopped_products.py 2019-10-31 08:51:00 +0000
472+++ bin/addons/msf_tools/report/report_stopped_products.py 2020-04-29 08:55:42 +0000
473@@ -184,34 +184,34 @@
474 data_obj = self.pool.get('ir.model.data')
475 smrl_obj = self.pool.get('stock.mission.report.line')
476
477- stopped_state_id = data_obj.get_object_reference(self.cr, self.uid, 'product_attributes', 'status_3')[1]
478+ phase_out_state_id = data_obj.get_object_reference(self.cr, self.uid, 'product_attributes', 'status_2')[1]
479 status_local_id = data_obj.get_object_reference(self.cr, self.uid, 'product_attributes', 'int_4')[1]
480 temporary_status_id = data_obj.get_object_reference(self.cr, self.uid, 'product_attributes', 'int_5')[1]
481
482- hq_stopped_ids = prod_obj.search(self.cr, self.uid, [
483- ('state', '=', stopped_state_id),
484+ hq_phase_out_ids = prod_obj.search(self.cr, self.uid, [
485+ ('state', '=', phase_out_state_id),
486 ('international_status', '!=', status_local_id),
487 ('international_status', '!=', temporary_status_id)],
488 context=self.localcontext)
489
490 smrl_ids = smrl_obj.search(self.cr, self.uid, [
491 ('full_view', '=', False),
492- ('product_state', '=', 'stopped'),
493+ ('product_state', '=', 'phase_out'),
494 '|', ('internal_qty', '!=', 0),
495 ('in_pipe_qty', '!=', 0)
496 ], context=self.localcontext)
497
498- sm_stopped_ids = smrl_obj.read(self.cr, self.uid, smrl_ids, ['product_id'], context=self.localcontext)
499- sm_stopped_ids = [x.get('product_id')[0] for x in sm_stopped_ids]
500+ sm_phase_out_ids = smrl_obj.read(self.cr, self.uid, smrl_ids, ['product_id'], context=self.localcontext)
501+ sm_phase_out_ids = [x.get('product_id')[0] for x in sm_phase_out_ids]
502
503 # build a list of stopped products with unique ids and sorted by default_code:
504- stopped_ids = list(set(hq_stopped_ids + sm_stopped_ids))
505+ phase_out_ids = list(set(hq_phase_out_ids + sm_phase_out_ids))
506 ls = []
507- for prod in prod_obj.browse(self.cr, self.uid, stopped_ids, context=self.localcontext):
508+ for prod in prod_obj.browse(self.cr, self.uid, phase_out_ids, context=self.localcontext):
509 ls.append( (prod.id, prod.default_code) )
510- sorted_stopped_ids = [x[0] for x in sorted(ls, key=lambda tup: tup[1])]
511+ sorted_phase_out_ids = [x[0] for x in sorted(ls, key=lambda tup: tup[1])]
512
513- return prod_obj.browse(self.cr, self.uid, sorted_stopped_ids, context=self.localcontext)
514+ return prod_obj.browse(self.cr, self.uid, sorted_phase_out_ids, context=self.localcontext)
515
516 def get_stock_mission_report_lines(self, product):
517 '''
518@@ -221,10 +221,10 @@
519 smrl_obj = self.pool.get('stock.mission.report.line')
520 smrl_ids = smrl_obj.search(self.cr, self.uid, [('product_id', '=', product.id)], context=self.localcontext)
521
522- stopped_state_id = data_obj.get_object_reference(self.cr, self.uid, 'product_attributes', 'status_3')[1]
523+ phase_out_state_id = data_obj.get_object_reference(self.cr, self.uid, 'product_attributes', 'status_2')[1]
524
525 res = [smrl for smrl in smrl_obj.browse(self.cr, self.uid, smrl_ids, context=self.localcontext) if \
526- not smrl.full_view and (smrl.product_state == 'stopped' or product.state.id == stopped_state_id) and
527+ not smrl.full_view and (smrl.product_state == 'phase_out' or product.state.id == phase_out_state_id) and
528 (smrl.internal_qty != 0 or smrl.in_pipe_qty != 0) and smrl.mission_report_id.instance_id.state != 'inactive']
529
530 return res
531
532=== modified file 'bin/addons/order_types/stock.py'
533--- bin/addons/order_types/stock.py 2019-09-18 14:06:52 +0000
534+++ bin/addons/order_types/stock.py 2020-04-29 08:55:42 +0000
535@@ -212,8 +212,12 @@
536 certif = False
537 for pick in self.browse(cr, uid, ids, context=context):
538 if pick.type in ['in', 'out']:
539- if not context.get('yesorno', False) :
540+ if not context.get('yesorno', False):
541 for move in pick.move_lines:
542+ if pick.type == 'out' and move.product_id and move.product_id.state.code == 'forbidden': # Check constraints on lines
543+ check_vals = {'location_dest_id': move.location_dest_id.id, 'move': move}
544+ self.pool.get('product.product')._get_restriction_error(cr, uid, [move.product_id.id],
545+ check_vals, context=context)
546 if move.order_type in ['donation_exp', 'donation_st', 'in_kind']:
547 certif = True
548 break
549
550=== modified file 'bin/addons/product/product.py'
551--- bin/addons/product/product.py 2019-12-11 14:54:37 +0000
552+++ bin/addons/product/product.py 2020-04-29 08:55:42 +0000
553@@ -340,7 +340,7 @@
554 'warranty': fields.float('Warranty (months)'),
555 'sale_ok': fields.boolean('Can be Sold', help="Determines if the product can be visible in the list of product within a selection from a sale order line."),
556 'purchase_ok': fields.boolean('Can be Purchased', help="Determine if the product is visible in the list of products within a selection from a purchase order line."),
557- 'state': fields.integer('Status'),
558+ 'state': fields.integer('UniField Status', required=1),
559 'uom_id': fields.many2one('product.uom', 'Default Unit Of Measure', required=True, help="Default Unit of Measure used for all stock operation."),
560 'uom_po_id': fields.many2one('product.uom', 'Purchase Unit of Measure', required=True, help="Default Unit of Measure used for purchase orders. It must be in the same category than the default unit of measure."),
561 'uos_id' : fields.many2one('product.uom', 'Unit of Sale',
562
563=== modified file 'bin/addons/product_attributes/product_attributes.py'
564--- bin/addons/product_attributes/product_attributes.py 2020-04-06 08:43:29 +0000
565+++ bin/addons/product_attributes/product_attributes.py 2020-04-29 08:55:42 +0000
566@@ -25,6 +25,7 @@
567 from lxml import etree
568 import tools
569 from datetime import datetime
570+import logging
571
572 class product_section_code(osv.osv):
573 _name = "product.section.code"
574@@ -47,8 +48,13 @@
575 'no_internal': fields.boolean(string='Internal partners orders'),
576 'no_consumption': fields.boolean(string='Consumption'),
577 'no_storage': fields.boolean(string='Storage'),
578+ 'active': fields.boolean('Active'),
579+ 'mapped_to': fields.many2one('product.status', string='Replaced by'),
580 }
581
582+ _defaults = {
583+ 'active': True,
584+ }
585 def unlink(self, cr, uid, ids, context=None):
586 if context is None:
587 context = {}
588@@ -268,13 +274,20 @@
589 class product_attributes_template(osv.osv):
590 _inherit = "product.template"
591
592+
593 _columns = {
594 'type': fields.selection([('product','Stockable Product'),('consu', 'Non-Stockable')], 'Product Type', required=True, help="Will change the way procurements are processed. Consumables are stockable products with infinite stock, or for use when you have no inventory management in the system."),
595+ 'state': fields.many2one('product.status', 'UniField Status', help="Tells the user if he can use the product or not.", required=1),
596 }
597
598+ def _get_valid_stat(self, cr, uid, context=None):
599+ st_ids = self.pool.get('product.status').search(cr, uid, [('code', '=', 'valid')], context=context)
600+ return st_ids and st_ids[0]
601+
602 _defaults = {
603 'type': 'product',
604 'cost_method': lambda *a: 'average',
605+ 'state': _get_valid_stat,
606 }
607
608 product_attributes_template()
609@@ -290,19 +303,18 @@
610
611 product_country_restriction()
612
613-class product_template(osv.osv):
614- _inherit = 'product.template'
615-
616- _columns = {
617- 'state': fields.many2one('product.status', 'Status', help="Tells the user if he can use the product or not."),
618- }
619-
620-product_template()
621-
622
623 class product_attributes(osv.osv):
624 _inherit = "product.product"
625
626+ mapping_ud = {
627+ 'valid': 'valid',
628+ 'outdated': 'valid',
629+ 'discontinued': 'valid',
630+ 'forbidden': 'forbidden',
631+ 'archived': 'archived',
632+ }
633+
634 def execute_migration(self, cr, moved_column, new_column):
635 super(product_attributes, self).execute_migration(cr, moved_column, new_column)
636
637@@ -662,7 +674,7 @@
638 res[_id] = False
639
640 if self.pool.get('res.company')._get_instance_level(cr, uid) == 'section':
641- for _id in self.search(cr, uid, [('id', 'in', ids), ('standard_ok', '=', 'non_standard_local')], context=context):
642+ for _id in self.search(cr, uid, [('id', 'in', ids), ('standard_ok', '=', 'non_standard_local'), ('active', 'in', ['t', 'f'])], context=context):
643 res[_id] = True
644
645 return res
646@@ -958,7 +970,9 @@
647 string='Standardization Level',
648 required=True,
649 ),
650- 'local_from_hq': fields.function(_get_local_from_hq, method=1, type='boolean', string='Non-Standard Local from HQ'),
651+ 'local_from_hq': fields.function(_get_local_from_hq, method=1, type='boolean', string='Non-Standard Local from HQ', help='Set to True when HQ generates a sync update on NSL product', internal=1),
652+ 'active_change_date': fields.datetime('Date of last active change', readonly=1),
653+ 'active_sync_change_date': fields.datetime('Date of last active sync change', readonly=1),
654 'soq_weight': fields.float(digits=(16,5), string='SoQ Weight'),
655 'soq_volume': fields.float(digits=(16,5), string='SoQ Volume'),
656 'soq_quantity': fields.float(digits=(16,2), string='SoQ Quantity', related_uom='uom_id', help="Standard Ordering Quantity. Quantity according to which the product should be ordered. The SoQ is usually determined by the typical packaging of the product."),
657@@ -967,6 +981,21 @@
658 'uf_create_date': fields.datetime(_('Creation date')),
659 }
660
661+ def need_to_push(self, cr, uid, ids, touched_fields=None, field='sync_date', empty_ids=False, context=None):
662+ if touched_fields != ['active', 'local_from_hq', 'id']:
663+ return super(product_attributes, self).need_to_push(cr, uid, ids, touched_fields=touched_fields, field=field, empty_ids=empty_ids, context=context)
664+
665+ if not empty_ids and not ids:
666+ return ids
667+
668+ cr.execute("""
669+ SELECT id FROM product_product
670+ WHERE
671+ ( active_sync_change_date IS NULL AND active_change_date IS NOT NULL ) OR active_change_date > active_sync_change_date
672+ """)
673+ return [row[0] for row in cr.fetchall()]
674+
675+
676 def _get_default_sensitive_item(self, cr, uid, context=None):
677 """
678 Return the ID of the product.heat_sensitive item with 'No' value.
679@@ -1116,6 +1145,17 @@
680 if location.usage != 'inventory' and not location.destruction_location and (not bef_scrap_id or location.id != bef_scrap_id):
681 constraints.append('storage')
682
683+ # Compute the constraint if a destination location is passed in vals
684+ if vals.get('location_dest_id'):
685+ dest_location = self.pool.get('stock.location').browse(cr, uid, vals.get('location_dest_id'), context=context)
686+ if not (dest_location.destruction_location or dest_location.quarantine_location):
687+ if vals.get('move') and vals['move'].sale_line_id and not vals['move'].sale_line_id.order_id.procurement_request:
688+ if (vals['move'].picking_id.shipment_id and vals['move'].picking_id.shipment_id.partner_id.partner_type != 'internal') or \
689+ vals['move'].picking_id.partner_id.partner_type != 'internal':
690+ constraints.append('cant_use')
691+ else:
692+ constraints.append('cant_use')
693+
694 # Compute constraints if constraints is passed in vals
695 if vals.get('constraints'):
696 if isinstance(vals.get('constraints'), list):
697@@ -1151,17 +1191,21 @@
698 error = True
699 msg = _('be stored anymore')
700 st_cond = product.state.no_storage
701+ elif product.state.code == 'forbidden' and 'cant_use' in constraints:
702+ error = True
703+ msg = _('be sent')
704+ st_cond = product.state.no_consumption
705
706 if error:
707 # Build the error message
708 st_type = st_cond and _('status') or _('product creator')
709 st_name = st_cond and product.state.name or product.international_status.name
710
711- error_msg = _('The product [%s] %s gets the %s \'%s\' and consequently can\'t %s') % (product.default_code,
712- product.name,
713- st_type,
714- st_name,
715- msg)
716+ error_msg = ''
717+ if vals.get('move'):
718+ error_msg = _('%s line %s: ') % (vals['move'].picking_id.name, vals['move'].line_number)
719+ error_msg += _('The product [%s] gets the %s \'%s\' and consequently can\'t %s') \
720+ % (product.default_code, st_type, st_name, msg)
721 if context.get('noraise'):
722 error = False
723
724@@ -1174,10 +1218,7 @@
725 res, error_msg = self._test_restriction_error(cr, uid, ids, vals=vals, context=context)
726
727 if res:
728- if isinstance(error_msg, unicode):
729- error_msg = error_msg.encode('ascii', 'ignore')
730 raise osv.except_osv(_('Error'), error_msg)
731- return False
732
733
734 def change_soq_quantity(self, cr, uid, ids, soq, uom_id, context=None):
735@@ -1282,6 +1323,7 @@
736 if context.get('sync_update_execution') and vals.get('local_from_hq'):
737 vals['active'] = False
738
739+
740 def update_existing_translations(model, res_id, xmlid):
741 # If we are in the creation of product by sync. engine, attach the already existing translations to this product
742 if context.get('sync_update_execution'):
743@@ -1342,8 +1384,22 @@
744 vals['heat_sensitive_item'] = heat2_id
745 vals.update(self.onchange_heat(cr, uid, False, vals['heat_sensitive_item'], context=context).get('value', {}))
746
747- if intstat_code and 'oc_subscription' not in vals:
748- vals['oc_subscription'] = intstat_code == 'unidata'
749+ if intstat_code:
750+ if 'oc_subscription' not in vals:
751+ vals['oc_subscription'] = intstat_code == 'unidata'
752+
753+ if not context.get('sync_update_execution'):
754+ if 'state_ud' in vals:
755+ if self.mapping_ud.get(vals['state_ud']):
756+ vals['state'] = \
757+ self.pool.get('product.status').search(cr, uid, [('code', '=', self.mapping_ud.get(vals['state_ud']))],
758+ context=context)[0]
759+ if vals['state_ud'] == 'archived':
760+ vals['active'] = False
761+ if not vals['oc_subscription']:
762+ vals['active'] = False
763+ elif vals.get('state_ud') != 'archived':
764+ vals['active'] = True
765
766 for f in ['sterilized', 'closed_article', 'single_use']:
767 if f in vals and not vals.get(f):
768@@ -1352,6 +1408,11 @@
769 vals['uf_create_date'] = vals.get('uf_create_date') or datetime.now()
770
771 self.convert_price(cr, uid, vals, context)
772+
773+ if not context.get('sync_update_execution') and vals.get('active') is False:
774+ # tirgger sync update on state only if created as inactive (as active=Ture is the default)
775+ vals['active_change_date'] = datetime.now()
776+
777 res = super(product_attributes, self).create(cr, uid, vals, context=context)
778
779 if context.get('sync_update_execution'):
780@@ -1410,6 +1471,58 @@
781 elif vals['standard_ok'] == 'False':
782 vals['standard_ok'] = 'non_standard'
783
784+ if vals and 'state' in vals:
785+ # here to manage old sync updates
786+ st_obj = self.pool.get('product.status')
787+ if vals['state']:
788+ st = st_obj.browse(cr, uid, vals['state'], fields_to_fetch=['mapped_to'])
789+ if st and st.mapped_to:
790+ vals['state'] = st.mapped_to.id
791+ else:
792+ vals['state'] = st_obj.search(cr, uid, [('code', '=', 'valid')], context=context)[0]
793+
794+ def hq_cron_deactivate_ud_products(self, cr, uid, context=None):
795+ if self.pool.get('res.company')._get_instance_level(cr, uid) != 'section':
796+ return False
797+
798+ ids = []
799+ products_used = set()
800+
801+ ud_prod_ids = self.search(cr, uid, ['&', ('international_status', '=', 'UniData'), '|', '|', ('oc_subscription', '=', False), ('state_ud', '=', 'archived'), ('state', '=', 'Phase Out')], context=context)
802+ if ud_prod_ids:
803+ products_used = self.unidata_products_used(cr, uid, ud_prod_ids)
804+ ids = list(set(ud_prod_ids) - products_used)
805+ if ids:
806+ self.write(cr, uid, ids, {'active': False}, context=context)
807+
808+ logging.getLogger('UD deactivation').info('%d products deactivated, %d kept as active' % (len(ids), len(products_used)))
809+
810+ return True
811+
812+ def unidata_products_used(self, cr, uid, ids):
813+ if not ids:
814+ return set()
815+
816+ cr.execute('''
817+ select
818+ l.product_id
819+ from
820+ stock_mission_report r, msf_instance i, stock_mission_report_line l
821+ where
822+ i.id = r.instance_id and
823+ i.state = 'active' and
824+ l.mission_report_id = r.id and
825+ l.product_id in %s and
826+ r.full_view = 'f' and
827+ ( l.internal_qty > 0 or l.in_pipe_qty > 0)
828+ group by l.product_id
829+ ''' , (tuple(ids), ))
830+ ud_unable_to_inactive = set([x[0] for x in cr.fetchall()])
831+
832+ cr.execute('select name from product_list_line where name in %s', (tuple(ids), ))
833+ ud_unable_to_inactive = ud_unable_to_inactive.union([x[0] for x in cr.fetchall()])
834+ return ud_unable_to_inactive
835+
836 def write(self, cr, uid, ids, vals, context=None):
837 if not ids:
838 return True
839@@ -1466,45 +1579,86 @@
840 _("Product Code %s must include a 'Z' character") % (vals['default_code'],),
841 )
842
843+ if context.get('sync_update_execution') and vals.get('local_from_hq') and vals.get('active'):
844+ del(vals['active'])
845+
846+
847+ check_reactivate = False
848+ prod_state = ''
849+ if 'state_ud' in vals:
850+ # just update SMRL that belongs to our instance:
851+ local_smrl_ids = smrl_obj.search(cr, uid, [
852+ ('product_id', 'in', ids),
853+ ('full_view', '=', False),
854+ ('mission_report_id.local_report', '=', True),
855+ ('state_ud', '!=', vals['state_ud'] or ''),
856+ ], context=context)
857+ if local_smrl_ids:
858+ no_sync_context = context.copy()
859+ no_sync_context['sync_update_execution'] = False
860+ smrl_obj.write(cr, 1, local_smrl_ids, {'state_ud': vals['state_ud'] or ''}, context=no_sync_context)
861+
862+ if not context.get('sync_update_execution'):
863+ if self.mapping_ud.get(vals['state_ud']):
864+ prod_state = self.mapping_ud[vals['state_ud']]
865+ vals['state'] = prod_status_obj.search(cr, uid, [('code', '=', prod_state)], context=context)[0]
866+
867+ if vals['state_ud'] == 'archived':
868+ vals['active'] = False
869+ elif 'oc_subscription' not in vals:
870+ check_reactivate = True
871+
872+ unidata_product = False
873+ if 'international_status' in vals:
874+ intstat_code = ''
875+ if vals['international_status']:
876+ intstat_id = vals['international_status']
877+ if isinstance(intstat_id, (int,long)):
878+ intstat_id = [intstat_id]
879+ intstat_code = int_stat_obj.read(cr, uid, intstat_id, ['code'], context=context)[0]['code']
880+ unidata_product = intstat_code == 'unidata'
881+
882+ if intstat_code:
883+ # just update SMRL that belongs to our instance:
884+ local_smrl_ids = smrl_obj.search(cr, uid, [
885+ ('international_status_code', '!=', intstat_code),
886+ ('product_id', 'in', ids),
887+ ('full_view', '=', False),
888+ ('mission_report_id.local_report', '=', True)
889+ ], context=context)
890+ if local_smrl_ids:
891+ no_sync_context = context.copy()
892+ no_sync_context['sync_update_execution'] = False
893+ smrl_obj.write(cr, 1, local_smrl_ids, {'international_status_code': intstat_code or ''}, context=no_sync_context)
894+ else:
895+ unidata_product = self.search_exist(cr, uid, [('id', 'in', ids), ('international_status', '=', 'UniData'), ('active', 'in', ['t', 'f'])], context=context)
896+
897+
898+ reactivated_by_oc_subscription = False
899+ if unidata_product and not context.get('sync_update_execution') and 'oc_subscription' in vals:
900+ if 'international_status' not in vals:
901+ if self.search_exist(cr, uid, [('id', 'in', ids), ('international_status', '!=', 'UniData'), ('active', 'in', ['t', 'f'])], context=context):
902+ raise osv.except_osv(_('Waning'), _("You can write the oc_subscription field on multiple products only if all products are UniData !"))
903+
904+ if not vals['oc_subscription']:
905+ vals['active'] = False
906+ prod_state = 'archived'
907+ elif prod_state != 'archived':
908+ if not prod_state and 'state' not in vals:
909+ # uf state is archived or phase_out, we must map it with uf state
910+ reactivated_by_oc_subscription = True
911+
912+ vals['active'] = True
913+
914 # update local stock mission report lines :
915- if 'state' in vals:
916- prod_state = ''
917+ if not prod_state and 'state' in vals:
918 if vals['state']:
919 state_id = vals['state']
920 if isinstance(state_id, (int, long)):
921 state_id = [state_id]
922 prod_state = prod_status_obj.read(cr, uid, state_id, ['code'], context=context)[0]['code']
923- local_smrl_ids = smrl_obj.search(cr, uid, [('product_state', '!=', prod_state), ('product_id', 'in', ids), ('full_view', '=', False), ('mission_report_id.local_report', '=', True)], context=context)
924- if local_smrl_ids:
925- no_sync_context = context.copy()
926- no_sync_context['sync_update_execution'] = False
927- smrl_obj.write(cr, 1, local_smrl_ids, {'product_state': prod_state}, context=no_sync_context)
928-
929- if intstat_code:
930- # just update SMRL that belongs to our instance:
931- local_smrl_ids = smrl_obj.search(cr, uid, [
932- ('international_status_code', '!=', intstat_code),
933- ('product_id', 'in', ids),
934- ('full_view', '=', False),
935- ('mission_report_id.local_report', '=', True)
936- ], context=context)
937- if local_smrl_ids:
938- no_sync_context = context.copy()
939- no_sync_context['sync_update_execution'] = False
940- smrl_obj.write(cr, 1, local_smrl_ids, {'international_status_code': intstat_code or ''}, context=no_sync_context)
941-
942- if 'state_ud' in vals:
943- # just update SMRL that belongs to our instance:
944- local_smrl_ids = smrl_obj.search(cr, uid, [
945- ('product_id', 'in', ids),
946- ('full_view', '=', False),
947- ('mission_report_id.local_report', '=', True),
948- ('state_ud', '!=', vals['state_ud'] or ''),
949- ], context=context)
950- if local_smrl_ids:
951- no_sync_context = context.copy()
952- no_sync_context['sync_update_execution'] = False
953- smrl_obj.write(cr, 1, local_smrl_ids, {'state_ud': vals['state_ud'] or ''}, context=no_sync_context)
954+
955+
956
957 product_uom_categ = []
958 if 'uom_id' in vals or 'uom_po_id' in vals:
959@@ -1524,17 +1678,59 @@
960
961 if context.get('sync_update_execution') and not context.get('bypass_sync_update', False):
962 if vals.get('active', None) is False:
963- if self.deactivate_product(cr, uid, ids, context=context) is not True:
964- vals.update({
965- 'active': True,
966- })
967- elif vals.get('active', None) is True:
968- vals.update({
969- 'active': True,
970- # 'state': phase_out_status,
971- })
972-
973- if 'active' in vals:
974+ deactivate_result = self.deactivate_product(cr, uid, ids, context=context, try_only=True)
975+ if not deactivate_result['ok']:
976+ vals['active'] = True
977+ if unidata_product:
978+
979+ prod_code = self.read(cr, uid, ids[0], ['default_code'], context=context)
980+ error_msg = []
981+
982+ wiz_error = self.pool.get('product.deactivation.error').browse(cr, uid, deactivate_result['error'], context=context)
983+ if wiz_error.stock_exist:
984+ error_msg.append('Stock exists (internal locations)')
985+
986+ doc_errors = []
987+ for error in wiz_error.error_lines:
988+ doc_errors.append(error.doc_ref)
989+
990+ if doc_errors:
991+ error_msg.append('Product is contained in opened documents :\n - %s' % ' \n - '.join(doc_errors))
992+ raise osv.except_osv('Warning', 'Product %s cannot be deactivated: \n * %s ' % (prod_code['default_code'], "\n * ".join(error_msg)))
993+
994+ elif unidata_product:
995+ # unidata prodcut inactive must also be archived: 1st set as phase out by the update one
996+ vals['state'] = prod_status_obj.search(cr, uid, [('code', '=', 'archived')], context=context)[0]
997+
998+ if prod_state == 'archived' and unidata_product:
999+ # received archived: set as phase out, when the "active" update is processed it will set archived state if inactivation is allowed
1000+ vals['state'] = prod_status_obj.search(cr, uid, [('code', '=', 'phase_out')], context=context)[0]
1001+
1002+ ud_unable_to_inactive = []
1003+ if 'active' in vals and not vals['active'] and not context.get('sync_update_execution') and unidata_product:
1004+ ud_unable_to_inactive = self.unidata_products_used(cr, uid, ids)
1005+ if not prod_state:
1006+ vals['state'] = prod_status_obj.search(cr, uid, [('code', '=', 'archived')], context=context)[0]
1007+ if ud_unable_to_inactive:
1008+ ids = list(set(ids) - ud_unable_to_inactive)
1009+ ud_unable_to_inactive = list(ud_unable_to_inactive)
1010+
1011+ if 'state' in vals:
1012+ local_smrl_ids = smrl_obj.search(cr, uid, [('product_state', '!=', prod_state), ('product_id', 'in', ids), ('full_view', '=', False), ('mission_report_id.local_report', '=', True)], context=context)
1013+ if local_smrl_ids:
1014+ no_sync_context = context.copy()
1015+ no_sync_context['sync_update_execution'] = False
1016+ smrl_obj.write(cr, 1, local_smrl_ids, {'product_state': prod_state}, context=no_sync_context)
1017+
1018+ if ids and 'active' in vals:
1019+
1020+ # to manage sync update generation on active field
1021+ fields_to_update = ['active_change_date=%(now)s']
1022+ if context.get('sync_update_execution'):
1023+ fields_to_update += ['active_sync_change_date=%(now)s']
1024+ cr.execute('update product_product set '+', '.join(fields_to_update)+' where id in %(ids)s and active != %(active)s', {'now': fields.datetime.now(), 'ids': tuple(ids), 'active': vals['active']}) # not_a_user_entry
1025+
1026+
1027 local_smrl_ids = smrl_obj.search(cr, uid, [
1028 ('product_id', 'in', ids),
1029 ('full_view', '=', False),
1030@@ -1566,7 +1762,18 @@
1031 self.set_as_edonly(cr, uid, ids, context=context)
1032 else:
1033 self.set_as_nobn_noed(cr, uid, ids, context=context)
1034+
1035 res = super(product_attributes, self).write(cr, uid, ids, vals, context=context)
1036+ if ud_unable_to_inactive:
1037+ vals['active'] = True
1038+ vals['state'] = prod_status_obj.search(cr, uid, [('code', '=', 'phase_out')], context=context)[0]
1039+ super(product_attributes, self).write(cr, uid, ud_unable_to_inactive, vals, context=context)
1040+
1041+ local_smrl_ids = smrl_obj.search(cr, uid, [('product_state', '!=', 'phase_out'), ('product_id', 'in', ud_unable_to_inactive), ('full_view', '=', False), ('mission_report_id.local_report', '=', True)], context=context)
1042+ if local_smrl_ids:
1043+ no_sync_context = context.copy()
1044+ no_sync_context['sync_update_execution'] = False
1045+ smrl_obj.write(cr, 1, local_smrl_ids, {'product_state': 'phase_out'}, context=no_sync_context)
1046
1047 if product_uom_categ:
1048 uom_categ = 'uom_id' in vals and vals['uom_id'] and self.pool.get('product.uom').browse(cr, uid, vals['uom_id'], context=context).category_id.id or False
1049@@ -1575,9 +1782,27 @@
1050 if (uom_categ and uom_categ not in product_uom_categ) or (uos_categ and uos_categ not in product_uom_categ):
1051 raise osv.except_osv(_('Error'), _('You cannot choose an UoM which is not in the same UoM category of default UoM'))
1052
1053+ if ud_unable_to_inactive:
1054+ ids = ids + ud_unable_to_inactive
1055+
1056+ if reactivated_by_oc_subscription:
1057+ self.set_state_from_state_ud(cr, uid, ids, context=context)
1058+
1059+ if check_reactivate:
1060+ # ud set only state_ud != archived, check if product must be reactivated
1061+ set_as_active = self.search(cr, uid, [('active', '=', False), ('oc_subscription', '=', True), ('id', 'in', ids)], context=context)
1062+ if set_as_active:
1063+ self.write(cr, uid, set_as_active, {'active': True}, context=context)
1064 return res
1065
1066
1067+ def set_state_from_state_ud(self, cr, uid, ids, context=None):
1068+ for grp in self.read_group(cr, uid, [('id', 'in', ids)], fields=['state_ud'], groupby=['state_ud'], context=context):
1069+ ids_to_w = self.search(cr, uid, grp['__domain'], context=context)
1070+ if ids_to_w:
1071+ self.write(cr, uid, ids_to_w, {'state_ud': grp['state_ud']}, context=context)
1072+ return True
1073+
1074 def reactivate_product(self, cr, uid, ids, context=None):
1075 '''
1076 Re-activate product.
1077@@ -1603,7 +1828,7 @@
1078
1079 return True
1080
1081- def deactivate_product(self, cr, uid, ids, context=None):
1082+ def deactivate_product(self, cr, uid, ids, context=None, try_only=False):
1083 '''
1084 De-activate product.
1085 Check if the product is not used in any document in Unifield
1086@@ -1614,7 +1839,6 @@
1087 if isinstance(ids, (int, long)):
1088 ids = [ids]
1089
1090- # TODO JFB RR: constraint
1091 location_obj = self.pool.get('stock.location')
1092 po_line_obj = self.pool.get('purchase.order.line')
1093 tender_line_obj = self.pool.get('tender.line')
1094@@ -1859,6 +2083,10 @@
1095 'doc_ref': invoice.invoice_id.number,
1096 'doc_id': invoice.invoice_id.id}, context=context)
1097
1098+
1099+ if try_only:
1100+ return {'ok': False, 'error': wizard_id}
1101+
1102 if context.get('sync_update_execution', False):
1103 context['bypass_sync_update'] = True
1104 self.write(cr, uid, product.id, {
1105@@ -1873,6 +2101,9 @@
1106 'target': 'new',
1107 'context': context}
1108
1109+ if try_only:
1110+ return {'ok': True, 'error': False}
1111+
1112 if context.get('sync_update_execution', False):
1113 context['bypass_sync_update'] = True
1114
1115
1116=== modified file 'bin/addons/product_attributes/product_attributes_data.xml'
1117--- bin/addons/product_attributes/product_attributes_data.xml 2019-03-25 14:02:29 +0000
1118+++ bin/addons/product_attributes/product_attributes_data.xml 2020-04-29 08:55:42 +0000
1119@@ -2,86 +2,101 @@
1120 <openerp>
1121 <data noupdate="0">
1122
1123- <record model="product.status" id="status_1">
1124- <field name="code">valid</field>
1125- <field name="name">Valid</field>
1126- <field name="no_external" eval="False" />
1127- <field name="no_esc" eval="False" />
1128- <field name="no_internal" eval="False" />
1129- <field name="no_consumption" eval="False" />
1130- <field name="no_storage" eval="False" />
1131- </record>
1132- <record model="product.status" id="status_2">
1133- <field name="code">phase_out</field>
1134- <field name="name">Phase Out</field>
1135- <field name="no_external" eval="True" />
1136- <field name="no_esc" eval="False" />
1137- <field name="no_internal" eval="False" />
1138- <field name="no_consumption" eval="False" />
1139- <field name="no_storage" eval="False" />
1140- </record>
1141- <record model="product.status" id="status_3">
1142- <field name="code">stopped</field>
1143- <field name="name">Stopped</field>
1144- <field name="no_external" eval="True" />
1145- <field name="no_esc" eval="True" />
1146- <field name="no_internal" eval="True" />
1147- <field name="no_consumption" eval="False" />
1148- <field name="no_storage" eval="False" />
1149- </record>
1150- <record model="product.status" id="status_4">
1151- <field name="code">archived</field>
1152- <field name="name">Archived</field>
1153- <field name="no_external" eval="True" />
1154- <field name="no_esc" eval="True" />
1155- <field name="no_internal" eval="True" />
1156- <field name="no_consumption" eval="True" />
1157- <field name="no_storage" eval="True" />
1158- </record>
1159- <record model="product.status" id="status_5">
1160- <field name="code">status1</field>
1161- <field name="name">Status 1</field>
1162- <field name="no_external" eval="False" />
1163- <field name="no_esc" eval="False" />
1164- <field name="no_internal" eval="False" />
1165- <field name="no_consumption" eval="False" />
1166- <field name="no_storage" eval="False" />
1167- </record>
1168- <record model="product.status" id="status_6">
1169- <field name="code">status2</field>
1170- <field name="name">Status 2</field>
1171- <field name="no_external" eval="False" />
1172- <field name="no_esc" eval="False" />
1173- <field name="no_internal" eval="False" />
1174- <field name="no_consumption" eval="False" />
1175- <field name="no_storage" eval="False" />
1176- </record>
1177+ <record model="product.status" id="status_1">
1178+ <field name="code">valid</field>
1179+ <field name="name">Valid</field>
1180+ <field name="no_external" eval="False" />
1181+ <field name="no_esc" eval="False" />
1182+ <field name="no_internal" eval="False" />
1183+ <field name="no_consumption" eval="False" />
1184+ <field name="no_storage" eval="False" />
1185+ </record>
1186+ <record model="product.status" id="status_forbidden">
1187+ <field name="code">forbidden</field>
1188+ <field name="name">Forbidden</field>
1189+ <field name="no_external" eval="True" />
1190+ <field name="no_esc" eval="True" />
1191+ <field name="no_internal" eval="True" />
1192+ <field name="no_consumption" eval="True" />
1193+ <field name="no_storage" eval="True" />
1194+ </record>
1195+ <record model="product.status" id="status_2">
1196+ <field name="code">phase_out</field>
1197+ <field name="name">Phase Out</field>
1198+ <field name="no_external" eval="True" />
1199+ <field name="no_esc" eval="False" />
1200+ <field name="no_internal" eval="False" />
1201+ <field name="no_consumption" eval="False" />
1202+ <field name="no_storage" eval="False" />
1203+ </record>
1204+ <record model="product.status" id="status_3">
1205+ <field name="code">stopped</field>
1206+ <field name="name">Stopped (to del)</field>
1207+ <field name="no_external" eval="True" />
1208+ <field name="no_esc" eval="True" />
1209+ <field name="no_internal" eval="True" />
1210+ <field name="no_consumption" eval="False" />
1211+ <field name="no_storage" eval="False" />
1212+ <field name="active" eval="False" />
1213+ <field name="mapped_to" ref="status_2" />
1214+ </record>
1215+ <record model="product.status" id="status_4">
1216+ <field name="code">archived</field>
1217+ <field name="name">Archived</field>
1218+ <field name="no_external" eval="True" />
1219+ <field name="no_esc" eval="True" />
1220+ <field name="no_internal" eval="True" />
1221+ <field name="no_consumption" eval="True" />
1222+ <field name="no_storage" eval="True" />
1223+ </record>
1224+ <record model="product.status" id="status_5">
1225+ <field name="code">status1</field>
1226+ <field name="name">Status 1 (to del)</field>
1227+ <field name="no_external" eval="False" />
1228+ <field name="no_esc" eval="False" />
1229+ <field name="no_internal" eval="False" />
1230+ <field name="no_consumption" eval="False" />
1231+ <field name="no_storage" eval="False" />
1232+ <field name="active" eval="False" />
1233+ <field name="mapped_to" ref="status_1" />
1234+ </record>
1235+ <record model="product.status" id="status_6">
1236+ <field name="code">status2</field>
1237+ <field name="name">Status 2 (to_del)</field>
1238+ <field name="no_external" eval="False" />
1239+ <field name="no_esc" eval="False" />
1240+ <field name="no_internal" eval="False" />
1241+ <field name="no_consumption" eval="False" />
1242+ <field name="no_storage" eval="False" />
1243+ <field name="active" eval="False" />
1244+ <field name="mapped_to" ref="status_1" />
1245+ </record>
1246
1247- <record model="product.international.status" id="int_1">
1248- <field name="code">itc</field>
1249- <field name="name">ITC</field>
1250- </record>
1251- <record model="product.international.status" id="int_2">
1252- <field name="code">esc</field>
1253+ <record model="product.international.status" id="int_1">
1254+ <field name="code">itc</field>
1255+ <field name="name">ITC</field>
1256+ </record>
1257+ <record model="product.international.status" id="int_2">
1258+ <field name="code">esc</field>
1259 <field name="name">ESC</field>
1260- </record>
1261- <record model="product.international.status" id="int_3">
1262- <field name="code">hq</field>
1263- <field name="name">HQ</field>
1264- </record>
1265- <record model="product.international.status" id="int_4">
1266- <field name="code">local</field>
1267- <field name="name">Local</field>
1268- </record>
1269- <record model="product.international.status" id="int_5">
1270- <field name="code">temp</field>
1271- <field name="name">Temporary</field>
1272- </record>
1273- <record model="product.international.status" id="int_6">
1274- <field name="code">unidata</field>
1275+ </record>
1276+ <record model="product.international.status" id="int_3">
1277+ <field name="code">hq</field>
1278+ <field name="name">HQ</field>
1279+ </record>
1280+ <record model="product.international.status" id="int_4">
1281+ <field name="code">local</field>
1282+ <field name="name">Local</field>
1283+ </record>
1284+ <record model="product.international.status" id="int_5">
1285+ <field name="code">temp</field>
1286+ <field name="name">Temporary</field>
1287+ </record>
1288+ <record model="product.international.status" id="int_6">
1289+ <field name="code">unidata</field>
1290 <field name="name">UniData</field>
1291- </record>
1292-
1293+ </record>
1294+
1295 <record model="product.heat_sensitive" id="heat_yes">
1296 <field name="code">yes</field>
1297 <field name="name">Yes</field>
1298@@ -115,59 +130,72 @@
1299 <field name="active" eval="False" />
1300 </record>
1301
1302- <record model="product.cold_chain" id="cold_1">
1303- <field name="code">*</field>
1304- <field name="name">* - Keep Cool: used for a kit or article containing cold chain module or item(s)</field>
1305- </record>
1306- <record model="product.cold_chain" id="cold_2">
1307- <field name="code">*0</field>
1308- <field name="name">*0 - Problem if any window blue</field>
1309- </record>
1310- <record model="product.cold_chain" id="cold_3">
1311- <field name="code">*0F</field>
1312- <field name="name">*0F - Problem if any window blue or Freeze-tag = ALARM</field>
1313- </record>
1314- <record model="product.cold_chain" id="cold_4">
1315- <field name="code">*A</field>
1316- <field name="name">*A - Problem if A, B, C and/or D blue = ALARM</field>
1317- </record>
1318- <record model="product.cold_chain" id="cold_5">
1319- <field name="code">*AF</field>
1320- <field name="name">*AF - Problem if A, B, C and/or D blue or Freeze-tag = ALARM</field>
1321- </record>
1322- <record model="product.cold_chain" id="cold_6">
1323- <field name="code">*B</field>
1324- <field name="name">*B - Problem if B, C and/or D blue = ALARM</field>
1325- </record>
1326- <record model="product.cold_chain" id="cold_7">
1327- <field name="code">*BF</field>
1328- <field name="name">*BF - Problem if B, C and/or D blue or Freeze-tag = ALARM</field>
1329- </record>
1330- <record model="product.cold_chain" id="cold_8">
1331- <field name="code">*C</field>
1332- <field name="name">*C - Problem if C and D blue</field>
1333- </record>
1334- <record model="product.cold_chain" id="cold_9">
1335- <field name="code">*CF</field>
1336- <field name="name">*CF - Problem if C and/or D blue or Freeze-tag = ALARM</field>
1337- </record>
1338- <record model="product.cold_chain" id="cold_10">
1339- <field name="code">*D</field>
1340- <field name="name">*D - Store and transport at -25°C (store in deepfreezer, transport with dry-ice)</field>
1341- </record>
1342- <record model="product.cold_chain" id="cold_11">
1343- <field name="code">*F</field>
1344- <field name="name">*F - Cannot be frozen: check FreezeWatch</field>
1345- </record>
1346- <record model="product.cold_chain" id="cold_12">
1347- <field name="code">*25</field>
1348- <field name="name">*25 - Must be kept below 25°C (but not necesseraly in cold chain)</field>
1349- </record>
1350- <record model="product.cold_chain" id="cold_13">
1351- <field name="code">*25F</field>
1352- <field name="name">*25F - Must be kept below 25°C and cannot be frozen: check FreezeWatch</field>
1353- </record>
1354-
1355- </data>
1356+ <record model="product.cold_chain" id="cold_1">
1357+ <field name="code">*</field>
1358+ <field name="name">* - Keep Cool: used for a kit or article containing cold chain module or item(s)</field>
1359+ </record>
1360+ <record model="product.cold_chain" id="cold_2">
1361+ <field name="code">*0</field>
1362+ <field name="name">*0 - Problem if any window blue</field>
1363+ </record>
1364+ <record model="product.cold_chain" id="cold_3">
1365+ <field name="code">*0F</field>
1366+ <field name="name">*0F - Problem if any window blue or Freeze-tag = ALARM</field>
1367+ </record>
1368+ <record model="product.cold_chain" id="cold_4">
1369+ <field name="code">*A</field>
1370+ <field name="name">*A - Problem if A, B, C and/or D blue = ALARM</field>
1371+ </record>
1372+ <record model="product.cold_chain" id="cold_5">
1373+ <field name="code">*AF</field>
1374+ <field name="name">*AF - Problem if A, B, C and/or D blue or Freeze-tag = ALARM</field>
1375+ </record>
1376+ <record model="product.cold_chain" id="cold_6">
1377+ <field name="code">*B</field>
1378+ <field name="name">*B - Problem if B, C and/or D blue = ALARM</field>
1379+ </record>
1380+ <record model="product.cold_chain" id="cold_7">
1381+ <field name="code">*BF</field>
1382+ <field name="name">*BF - Problem if B, C and/or D blue or Freeze-tag = ALARM</field>
1383+ </record>
1384+ <record model="product.cold_chain" id="cold_8">
1385+ <field name="code">*C</field>
1386+ <field name="name">*C - Problem if C and D blue</field>
1387+ </record>
1388+ <record model="product.cold_chain" id="cold_9">
1389+ <field name="code">*CF</field>
1390+ <field name="name">*CF - Problem if C and/or D blue or Freeze-tag = ALARM</field>
1391+ </record>
1392+ <record model="product.cold_chain" id="cold_10">
1393+ <field name="code">*D</field>
1394+ <field name="name">*D - Store and transport at -25°C (store in deepfreezer, transport with dry-ice)</field>
1395+ </record>
1396+ <record model="product.cold_chain" id="cold_11">
1397+ <field name="code">*F</field>
1398+ <field name="name">*F - Cannot be frozen: check FreezeWatch</field>
1399+ </record>
1400+ <record model="product.cold_chain" id="cold_12">
1401+ <field name="code">*25</field>
1402+ <field name="name">*25 - Must be kept below 25°C (but not necesseraly in cold chain)</field>
1403+ </record>
1404+ <record model="product.cold_chain" id="cold_13">
1405+ <field name="code">*25F</field>
1406+ <field name="name">*25F - Must be kept below 25°C and cannot be frozen: check FreezeWatch</field>
1407+ </record>
1408+
1409+ <record id="ir_cron_deactivate_ud_products" model="ir.cron">
1410+ <field name="function">hq_cron_deactivate_ud_products</field>
1411+ <field name="user_id">1</field>
1412+ <field name="name">HQ: deactivate UD products</field>
1413+ <field name="interval_type">days</field>
1414+ <field eval="-1" name="numbercall"/>
1415+ <field eval="5" name="priority"/>
1416+ <field eval="0" name="doall"/>
1417+ <field eval="1" name="active"/>
1418+ <field eval="1" name="interval_number"/>
1419+ <field name="model">product.product</field>
1420+ </record>
1421+
1422+</data>
1423 </openerp>
1424
1425
1426=== modified file 'bin/addons/purchase/purchase_order_line.py'
1427--- bin/addons/purchase/purchase_order_line.py 2020-02-10 17:16:32 +0000
1428+++ bin/addons/purchase/purchase_order_line.py 2020-04-29 08:55:42 +0000
1429@@ -1263,9 +1263,12 @@
1430 default = {}
1431
1432 # do not copy canceled purchase.order.line:
1433- pol = self.browse(cr, uid, p_id, fields_to_fetch=['state', 'order_id', 'linked_sol_id'], context=context)
1434+ pol = self.browse(cr, uid, p_id, fields_to_fetch=['state', 'order_id', 'linked_sol_id', 'product_id'], context=context)
1435 if pol.state in ['cancel', 'cancel_r'] and not context.get('allow_cancelled_pol_copy', False):
1436 return False
1437+ if pol.product_id: # Check constraints on lines
1438+ self.pool.get('product.product')._get_restriction_error(cr, uid, [pol.product_id.id],
1439+ {'partner_id': pol.order_id.partner_id.id}, context=context)
1440
1441 default.update({'state': 'draft', 'move_ids': [], 'invoiced': 0, 'invoice_lines': [], 'commitment_line_ids': []})
1442
1443
1444=== modified file 'bin/addons/sale/sale_order.py'
1445--- bin/addons/sale/sale_order.py 2019-12-12 13:30:26 +0000
1446+++ bin/addons/sale/sale_order.py 2020-04-29 08:55:42 +0000
1447@@ -2958,6 +2958,7 @@
1448 context = {}
1449 if not vals.get('product_id') and context.get('sale_id', []):
1450 vals.update({'type': 'make_to_order'})
1451+ so_obj = self.pool.get('sale.order')
1452
1453 self.check_empty_line(cr, uid, False, vals, context=context)
1454 # UF-1739: as we do not have product_uos_qty in PO (only in FO), we recompute here the product_uos_qty for the SYNCHRO
1455@@ -2969,18 +2970,32 @@
1456 qty = float(qty)
1457 vals.update({'product_uos_qty' : qty * product_obj.read(cr, uid, product_id, ['uos_coeff'])['uos_coeff']})
1458
1459- # Internal request
1460 pricelist = False
1461 order_id = vals.get('order_id', False)
1462+ order_data = False
1463 if order_id:
1464- order_data = self.pool.get('sale.order').\
1465- read(cr, uid, order_id, ['procurement_request', 'pricelist_id', 'fo_created_by_po_sync'], context)
1466- if order_data['procurement_request']:
1467+ ftf = ['procurement_request', 'pricelist_id', 'fo_created_by_po_sync', 'partner_id']
1468+ order_data = so_obj.browse(cr, uid, order_id, fields_to_fetch=ftf, context=context)
1469+
1470+ if product_id: # Check constraints on lines
1471+ partner_id = False
1472+ if order_data:
1473+ partner_id = order_data.partner_id.id
1474+ if order_data and order_data.procurement_request:
1475+ self.pool.get('product.product')._get_restriction_error(cr, uid, [product_id],
1476+ {'constraints': 'consumption'}, context=context)
1477+ else:
1478+ self._check_product_constraints(cr, uid, vals.get('type'), vals.get('po_cft'), product_id, partner_id,
1479+ check_fnct=False, context=context)
1480+
1481+ # Internal request
1482+ if order_data:
1483+ if order_data.procurement_request:
1484 vals.update({'cost_price': vals.get('cost_price', False)})
1485- if order_data['pricelist_id']:
1486- pricelist = order_data['pricelist_id'][0]
1487+ if order_data.pricelist_id:
1488+ pricelist = order_data.pricelist_id.id
1489 # New line created out of synchro on a FO/IR created by synchro
1490- if order_data['fo_created_by_po_sync'] and not context.get('sync_message_execution'):
1491+ if order_data.fo_created_by_po_sync and not context.get('sync_message_execution'):
1492 vals.update({'created_by_sync': True})
1493
1494 # force the line creation with the good state, otherwise track changes for order state will
1495@@ -3003,7 +3018,7 @@
1496 so_line_ids = super(sale_order_line, self).create(cr, uid, vals, context=context)
1497 if not vals.get('sync_order_line_db_id', False): # 'sync_order_line_db_id' not in vals or vals:
1498 if vals.get('order_id', False):
1499- name = self.pool.get('sale.order').browse(cr, uid, vals.get('order_id'), context=context).name
1500+ name = so_obj.browse(cr, uid, vals.get('order_id'), context=context).name
1501 super(sale_order_line, self).write(cr, uid, so_line_ids, {'sync_order_line_db_id': name + "_" + str(so_line_ids), } , context=context)
1502
1503 if vals.get('stock_take_date'):
1504
1505=== modified file 'bin/addons/sale/sale_workflow.py'
1506--- bin/addons/sale/sale_workflow.py 2020-01-30 17:08:29 +0000
1507+++ bin/addons/sale/sale_workflow.py 2020-04-29 08:55:42 +0000
1508@@ -567,6 +567,12 @@
1509
1510 for sol in self.browse(cr, uid, ids, context=context):
1511 to_write = {}
1512+ if sol.product_id: # Check constraints on lines
1513+ if sol.procurement_request:
1514+ check_vals = {'constraints': 'consumption'}
1515+ else:
1516+ check_vals = {'obj_type': 'sale.order', 'partner_id': sol.order_id.partner_id.id}
1517+ self.pool.get('product.product')._get_restriction_error(cr, uid, [sol.product_id.id], vals=check_vals, context=context)
1518 if sol.order_id.procurement_request and not sol.order_id.location_requestor_id:
1519 raise osv.except_osv(_('Warning !'),
1520 _('You can not validate the line without a Location Requestor.'))
1521
1522=== modified file 'bin/addons/sale/wizard/internal_request_import.py'
1523--- bin/addons/sale/wizard/internal_request_import.py 2019-11-07 14:47:03 +0000
1524+++ bin/addons/sale/wizard/internal_request_import.py 2020-04-29 08:55:42 +0000
1525@@ -617,11 +617,18 @@
1526 if prod_ids:
1527 product_id = prod_ids[0]
1528 prod_cols = ['standard_price', 'uom_id', 'uom_po_id']
1529- product = prod_obj.read(cr, uid, product_id, prod_cols, context=context)
1530- line_data.update({
1531- 'imp_product_id': product_id,
1532- 'imp_comment': comment or '',
1533- })
1534+ p_error, p_msg = prod_obj._test_restriction_error(cr, uid, [product_id],
1535+ vals={'constraints': 'consumption'},
1536+ context=context)
1537+ if p_error: # Check constraints on products
1538+ red = True
1539+ line_errors += p_msg + '. '
1540+ else:
1541+ product = prod_obj.read(cr, uid, product_id, prod_cols, context=context)
1542+ line_data.update({
1543+ 'imp_product_id': product_id,
1544+ 'imp_comment': comment or '',
1545+ })
1546 else:
1547 if ir_imp.no_prod_as_comment:
1548 nb_treated_lines_by_nomen += 1
1549
1550=== modified file 'bin/addons/sourcing/sale_order_line.py'
1551--- bin/addons/sourcing/sale_order_line.py 2020-03-19 10:08:38 +0000
1552+++ bin/addons/sourcing/sale_order_line.py 2020-04-29 08:55:42 +0000
1553@@ -1613,6 +1613,12 @@
1554 company_currency_id = self.pool.get('res.users').get_company_currency_id(cr, uid)
1555
1556 for sourcing_line in self.browse(cr, uid, ids, context=context):
1557+ if sourcing_line.procurement_request: # Check constraints on lines
1558+ check_vals = {'constraints': 'consumption'}
1559+ else:
1560+ check_vals = {'obj_type': 'sale.order', 'partner_id': sourcing_line.order_id.partner_id.id}
1561+ self.pool.get('product.product')._get_restriction_error(cr, uid, [sourcing_line.product_id.id], vals=check_vals,
1562+ context=context)
1563 if sourcing_line.supplier and sourcing_line.supplier_type == 'esc' and \
1564 sourcing_line.supplier_split_po == 'yes' and not sourcing_line.related_sourcing_id:
1565 raise osv.except_osv(_('Error'), _('For this Supplier you have to select a Sourcing Group'))
1566
1567=== modified file 'bin/addons/specific_rules/specific_rules.py'
1568--- bin/addons/specific_rules/specific_rules.py 2020-03-19 10:08:38 +0000
1569+++ bin/addons/specific_rules/specific_rules.py 2020-04-29 08:55:42 +0000
1570@@ -532,7 +532,7 @@
1571 if line.hidden_batch_management_mandatory and not line.prod_lot_id:
1572 raise osv.except_osv(_('Error'), _('The product %s is batch mandatory but the line with this product has no batch') % product_obj.name_get(cr, uid, [line.product_id.id])[0][1])
1573
1574- if line.product_id:
1575+ if line.product_id and line.product_id.state.code != 'forbidden':
1576 # Check constraints on lines
1577 product_obj._get_restriction_error(cr, uid, [line.product_id.id], {'location_id': line.location_id.id}, context=context)
1578
1579@@ -769,7 +769,7 @@
1580 result.setdefault('value', {})['hidden_perishable_mandatory'] = False
1581 if product:
1582 product_obj = self.pool.get('product.product').browse(cr, uid, product)
1583- if location_id:
1584+ if location_id and product_obj.state.code != 'forbidden':
1585 result, test = self.pool.get('product.product')._on_change_restriction_error(cr, uid, product, field_name='product_id', values=result, vals={'location_id': location_id})
1586 if test:
1587 return result
1588
1589=== modified file 'bin/addons/specific_rules/stock.py'
1590--- bin/addons/specific_rules/stock.py 2019-09-18 14:06:52 +0000
1591+++ bin/addons/specific_rules/stock.py 2020-04-29 08:55:42 +0000
1592@@ -78,7 +78,7 @@
1593 raise osv.except_osv(_('Error'), _('Please enter at least one line in stock inventory before confirm it.'))
1594
1595 for inventory_line in inventory.inventory_line_id:
1596- if inventory_line.product_id:
1597+ if inventory_line.product_id and inventory_line.product_id.state.code != 'forbidden':
1598 # Check product constrainsts
1599 product_obj._get_restriction_error(cr, uid, [inventory_line.product_id.id], {'location_id': inventory_line.location_id.id}, context=context)
1600
1601@@ -412,13 +412,14 @@
1602 context = {}
1603 if location_id:
1604 context = {'location': location_id, 'compute_child': False}
1605+ if prodlot_id:
1606+ context.update({'prodlot_id': prodlot_id})
1607+ product = product_obj.browse(cr, uid, product_id, context=context)
1608+ if location_id and product.state.code != 'forbidden':
1609 # Test the compatibility of the product with the location
1610 value, test = product_obj._on_change_restriction_error(cr, uid, product_id, field_name=field_change, values=value, vals={'location_id': location_id})
1611 if test:
1612 return value
1613- if prodlot_id:
1614- context.update({'prodlot_id': prodlot_id})
1615- product = product_obj.browse(cr, uid, product_id, context=context)
1616 value.update({'product_uom': product.uom_id.id,
1617 'hidden_perishable_mandatory': product.perishable,
1618 'hidden_batch_management_mandatory': product.batch_management})
1619
1620=== modified file 'bin/addons/stock/stock_move.py'
1621--- bin/addons/stock/stock_move.py 2020-01-29 16:41:23 +0000
1622+++ bin/addons/stock/stock_move.py 2020-04-29 08:55:42 +0000
1623@@ -930,6 +930,8 @@
1624 def copy(self, cr, uid, id, default=None, context=None):
1625 if default is None:
1626 default = {}
1627+ prod_obj = self.pool.get('product.product')
1628+
1629 default = default.copy()
1630 if 'qty_processed' not in default:
1631 default['qty_processed'] = 0
1632@@ -941,6 +943,15 @@
1633 if 'integrity_error' not in default:
1634 default['integrity_error'] = 'empty'
1635
1636+ if 'product_id' in default: # Check constraints on lines
1637+ move = self.browse(cr, uid, id, fields_to_fetch=['type'], context=context)
1638+ if move.type == 'in':
1639+ prod_obj._get_restriction_error(cr, uid, [move.product_id.id], {'partner_id': move.picking_id.partner_id.id},
1640+ context=context)
1641+ elif move.type == 'out' and move.product_id.state.code == 'forbidden':
1642+ check_vals = {'location_dest_id': move.location_dest_id.id, 'move': move}
1643+ prod_obj._get_restriction_error(cr, uid, [move.product_id.id], check_vals, context=context)
1644+
1645 return super(stock_move, self).copy(cr, uid, id, default, context=context)
1646
1647 def copy_data(self, cr, uid, id, defaults=None, context=None):
1648
1649=== modified file 'bin/addons/sync_client/update.py'
1650--- bin/addons/sync_client/update.py 2020-01-03 13:16:16 +0000
1651+++ bin/addons/sync_client/update.py 2020-04-29 08:55:42 +0000
1652@@ -314,6 +314,20 @@
1653 max_offset = len(update_ids)
1654 while min_offset < max_offset:
1655 offset = (min_offset + 200) < max_offset and min_offset + 200 or max_offset
1656+
1657+ # specific case to split the active field on product.product
1658+ # i.e: at COO an update received on product must not block a possible update on active field to the project
1659+ cr.execute('''
1660+ update product_product set active_sync_change_date = upd.create_date
1661+ from sync_client_update_to_send upd, ir_model_data d, sync_client_rule rule
1662+ where
1663+ rule.id = upd.rule_id and
1664+ rule.sequence_number in (602, 603) and
1665+ d.model = 'product.product' and
1666+ d.name = upd.sdref and
1667+ product_product.id = d.res_id and
1668+ upd.id in %s
1669+ ''', (tuple(update_ids[min_offset:offset]),))
1670 for update in self.browse(cr, uid, update_ids[min_offset:offset], context=context):
1671 try:
1672 self.pool.get('ir.model.data').update_sd_ref(cr, uid,
1673@@ -569,15 +583,20 @@
1674 # Prepare updates
1675 # TODO: skip updates not preparable
1676 for update in updates:
1677- if self.search_exist(cr, uid,
1678- [('sdref', '=', update.sdref),
1679- ('is_deleted', '=', False),
1680- ('run', '=', False),
1681- ('rule_sequence', '=', update.rule_sequence),
1682- ('sequence_number', '<', update.sequence_number)]):
1683- # previous not run on the same (sdref, rule_sequence): do not execute
1684- self._set_not_run(cr, uid, [update.id], log="Cannot execute due to previous not run on the same record/rule.", context=context)
1685- continue
1686+ prev_nr_ids = self.search(cr, uid,
1687+ [('sdref', '=', update.sdref),
1688+ ('is_deleted', '=', False),
1689+ ('run', '=', False),
1690+ ('rule_sequence', '=', update.rule_sequence),
1691+ ('sequence_number', '<', update.sequence_number)])
1692+ # previous not run on the same (sdref, rule_sequence): do not execute
1693+ if prev_nr_ids:
1694+ if update.rule_sequence in (602, 603):
1695+ # update on product state, we don't care of previous NR
1696+ self.write(cr, uid, prev_nr_ids, {'run': 't', 'log': 'Set as Run due to a later update on the same record/rule.', 'editable': False, 'execution_date': datetime.now()}, context=context)
1697+ else:
1698+ self._set_not_run(cr, uid, [update.id], log="Cannot execute due to previous not run on the same record/rule.", context=context)
1699+ continue
1700
1701 row = eval(update.values)
1702

Subscribers

People subscribed via source and target branches