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

Proposed by jftempo
Status: Needs review
Proposed branch: lp:~jfb-tempo-consulting/unifield-server/US-7353-no7158
Merge into: lp:unifield-server
Diff against target: 1067 lines (+577/-73) (has conflicts)
17 files modified
bin/addons/msf_audittrail/data/audittrail_data_products.yml (+1/-1)
bin/addons/msf_doc_import/wizard/wizard_import_product_line.py (+1/-1)
bin/addons/msf_profile/data/patches.xml (+8/-0)
bin/addons/msf_profile/i18n/fr_MF.po (+37/-0)
bin/addons/msf_profile/msf_profile.py (+31/-0)
bin/addons/msf_sync_data_server/data/sync_server.sync_rule.csv (+2/-1)
bin/addons/product/product.py (+2/-0)
bin/addons/product_asset/product_asset.py (+1/-1)
bin/addons/product_attributes/__openerp__.py (+1/-0)
bin/addons/product_attributes/product_attributes.py (+367/-50)
bin/addons/product_attributes/product_attributes_view.xml (+9/-13)
bin/addons/product_attributes/wizard/__init__.py (+1/-1)
bin/addons/product_attributes/wizard/product_merged.py (+64/-0)
bin/addons/product_attributes/wizard/product_merged_view.xml (+26/-0)
bin/addons/sync_common/common.py (+1/-0)
bin/addons/sync_so/so_po_common.py (+10/-4)
bin/sql_db.py (+15/-1)
Text conflict in bin/addons/msf_profile/data/patches.xml
Text conflict in bin/addons/msf_profile/i18n/fr_MF.po
Text conflict in bin/addons/msf_profile/msf_profile.py
To merge this branch: bzr merge lp:~jfb-tempo-consulting/unifield-server/US-7353-no7158
Reviewer Review Type Date Requested Status
UniField Reviewer Team Pending
Review via email: mp+382634@code.launchpad.net
To post a comment you must log in.
5690. By jftempo

Hidden merge button at HQ + Proj / reverse order of sync updates to prevent NR if attrib on Z prod has been changed at coordo

5691. By jftempo

Check if product is used

5692. By jftempo

Msg if NSL product used

5693. By jftempo

Wizard merge: order of checks

5694. By jftempo

Sync merge: keep cost and field prices

5695. By jftempo

Sync merge: keep cost price

5696. By jftempo

Merge all UD prod

5697. By jftempo

Merge prod: m2o autocomplete list must search only on NSL

5698. By jftempo

COO: deactivate UD + merge + sync: product is kept active at proj

5699. By jftempo

Filter

5700. By jftempo

US-7353: ST+NS must be active

5701. By jftempo

US-7353 Show UD

5702. By jftempo

the active test hell

Unmerged revisions

5702. By jftempo

the active test hell

5701. By jftempo

US-7353 Show UD

5700. By jftempo

US-7353: ST+NS must be active

5699. By jftempo

Filter

5698. By jftempo

COO: deactivate UD + merge + sync: product is kept active at proj

5697. By jftempo

Merge prod: m2o autocomplete list must search only on NSL

5696. By jftempo

Merge all UD prod

5695. By jftempo

Sync merge: keep cost price

5694. By jftempo

Sync merge: keep cost and field prices

5693. By jftempo

Wizard merge: order of checks

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bin/addons/account/invoice.py'
2=== modified file 'bin/addons/account_override/account_invoice_view.xml'
3=== modified file 'bin/addons/account_override/invoice.py'
4=== modified file 'bin/addons/msf_audittrail/data/audittrail_data_products.yml'
5--- bin/addons/msf_audittrail/data/audittrail_data_products.yml 2019-10-28 09:41:26 +0000
6+++ bin/addons/msf_audittrail/data/audittrail_data_products.yml 2020-04-30 18:52:36 +0000
7@@ -31,7 +31,7 @@
8 object_ids = self.pool.get('ir.model').search(cr, uid, [('model', '=', 'product.product')], context=context)
9 rule_id = self.search(cr, uid, [('name', '=', name)], context=context)
10 if object_ids:
11- fields = ['default_code', 'old_code', 'new_code', 'type', 'subtype', 'categ_id', 'international_status', 'state', 'active', 'perishable', 'batch_management', 'heat_sensitive_item', 'controlled_substance', 'dangerous_goods', 'standard_ok', 'justification_code_id', 'restricted_country', 'country_restriction', 'form_value', 'fit_value', 'function_value', 'procure_delay', 'soq_volume', 'soq_weight', 'soq_quantity', 'valuation', 'donation_expense_account']
12+ fields = ['default_code', 'old_code', 'new_code', 'type', 'subtype', 'categ_id', 'international_status', 'state', 'active', 'perishable', 'batch_management', 'heat_sensitive_item', 'controlled_substance', 'dangerous_goods', 'standard_ok', 'justification_code_id', 'restricted_country', 'country_restriction', 'form_value', 'fit_value', 'function_value', 'procure_delay', 'soq_volume', 'soq_weight', 'soq_quantity', 'valuation', 'donation_expense_account', 'replace_product_id', 'replaced_by_product_id']
13 fields_ids = self.pool.get('ir.model.fields').search(cr, uid, [('model', '=' ,'product.product'), ('name', 'in', fields)], context=context)
14 vals = {'name': name,
15 'object_id': object_ids[0],
16
17=== modified file 'bin/addons/msf_doc_import/wizard/wizard_import_product_line.py'
18--- bin/addons/msf_doc_import/wizard/wizard_import_product_line.py 2019-10-07 10:36:15 +0000
19+++ bin/addons/msf_doc_import/wizard/wizard_import_product_line.py 2020-04-30 18:52:36 +0000
20@@ -152,7 +152,7 @@
21 # Cell 0: Product
22 if row[0]:
23 prod_ids = product_obj.search(cr, uid, [('default_code', '=ilike', row[0].data),
24- ('active', 'in', ['t', 'f'])], context=context)
25+ ('active', 'in', ['t', 'f']), ('replaced_by_product_id', '=', False)], context=context)
26 if prod_ids and prod_ids[0] not in product_ids:
27 prod = product_obj.browse(cr, uid, prod_ids[0], fields_to_fetch=['international_status'], context=context)
28 if prod_creator_id and prod.international_status.id in prod_creator_id:
29
30=== modified file 'bin/addons/msf_profile/data/patches.xml'
31--- bin/addons/msf_profile/data/patches.xml 2020-04-29 09:38:09 +0000
32+++ bin/addons/msf_profile/data/patches.xml 2020-04-30 18:52:36 +0000
33@@ -522,6 +522,7 @@
34 <field name="method">us_7025_7039_fix_nr_empty_ins</field>
35 </record>
36
37+<<<<<<< TREE
38 <!-- UF17.0 -->
39 <record id="us_7221_reset_starting_balance" model="patch.scripts">
40 <field name="method">us_7221_reset_starting_balance</field>
41@@ -535,5 +536,12 @@
42 <field name="method">us_7236_remove_reg_wkf_and_partial_close_state</field>
43 </record>
44
45+=======
46+ <!-- UF17.0 -->
47+ <record id="us_7221_reset_starting_balance" model="patch.scripts">
48+ <field name="method">us_7221_reset_starting_balance</field>
49+ </record>
50+
51+>>>>>>> MERGE-SOURCE
52 </data>
53 </openerp>
54
55=== modified file 'bin/addons/msf_profile/i18n/fr_MF.po'
56--- bin/addons/msf_profile/i18n/fr_MF.po 2020-04-29 09:38:09 +0000
57+++ bin/addons/msf_profile/i18n/fr_MF.po 2020-04-30 18:52:36 +0000
58@@ -109130,6 +109130,7 @@
59 msgid "This is an NSL (Non-standard Local) product, please ensure that there is no duplicate “Local” product with which this product should be merged. If you activate and start to use this product it will no longer be possible to merge it with another."
60 msgstr "Ce produit est NSL (Non-standard Local), veuillez vous assurer qu'il n'y a pas de produit “Local” doublon avec lequel ce produit devrait être fusionné. Si vous activez ce produit et commencez à l'utiliser, il ne sera plus possible de le fusionner avec un autre."
61
62+<<<<<<< TREE
63 #. module: msf_instance
64 #: view:res.company:0
65 msgid "Liquidity Accounts"
66@@ -109298,3 +109299,39 @@
67 #, python-format
68 msgid "Theoretical Balance is not equal to the Cashbox Balance."
69 msgstr "Le Solde Théorique n'est pas égal au Solde de Caisse."
70+=======
71+#. module: msf_instance
72+#: view:res.company:0
73+msgid "Liquidity Accounts"
74+msgstr "Comptes de Trésorerie"
75+
76+#. module: account_override
77+#: field:res.company,cheque_debit_account_id:0
78+msgid "Cheque Default Debit Account"
79+msgstr "Compte de Débit par défaut pour les Journaux de Chèques"
80+
81+#. module: account_override
82+#: field:res.company,cheque_credit_account_id:0
83+msgid "Cheque Default Credit Account"
84+msgstr "Compte de Crédit par défaut pour les Journaux de Chèques"
85+
86+#. module: account_override
87+#: field:res.company,bank_debit_account_id:0
88+msgid "Bank Default Debit Account"
89+msgstr "Compte de Débit par défaut pour les Journaux de Banque"
90+
91+#. module: account_override
92+#: field:res.company,bank_credit_account_id:0
93+msgid "Bank Default Credit Account"
94+msgstr "Compte de Crédit par défaut pour les Journaux de Banque"
95+
96+#. module: account_override
97+#: field:res.company,cash_debit_account_id:0
98+msgid "Cash Default Debit Account"
99+msgstr "Compte de Débit par défaut pour les Journaux de Liquidités"
100+
101+#. module: account_override
102+#: field:res.company,cash_credit_account_id:0
103+msgid "Cash Default Credit Account"
104+msgstr "Compte de Crédit par défaut pour les Journaux de Liquidités"
105+>>>>>>> MERGE-SOURCE
106
107=== modified file 'bin/addons/msf_profile/msf_profile.py'
108--- bin/addons/msf_profile/msf_profile.py 2020-04-29 09:38:09 +0000
109+++ bin/addons/msf_profile/msf_profile.py 2020-04-30 18:52:36 +0000
110@@ -53,6 +53,7 @@
111 }
112
113
114+<<<<<<< TREE
115 # UF17.0
116 def us_7015_del_rac_line_sql(self, cr, uid, *a, **b):
117 cr.drop_constraint_if_exists('real_average_consumption_line', 'real_average_consumption_line_unique_lot_poduct')
118@@ -123,6 +124,36 @@
119 return True
120
121 # UF16.1
122+=======
123+ # UF17.0
124+ def us_7221_reset_starting_balance(self, cr, uid, *a, **b):
125+ """
126+ Reset the Starting Balance of the first register created for each journal if it is still in Draft state
127+ """
128+ # Cashbox details set to zero
129+ cr.execute("""
130+ UPDATE account_cashbox_line
131+ SET number = 0
132+ WHERE starting_id IN (
133+ SELECT id FROM account_bank_statement
134+ WHERE state = 'draft'
135+ AND prev_reg_id IS NULL
136+ AND journal_id IN (SELECT id FROM account_journal WHERE type='cash')
137+ );
138+ """)
139+ # Starting Balance set to zero
140+ cr.execute("""
141+ UPDATE account_bank_statement
142+ SET balance_start = 0.0
143+ WHERE state = 'draft'
144+ AND prev_reg_id IS NULL
145+ AND journal_id IN (SELECT id FROM account_journal WHERE type in ('bank', 'cash'));
146+ """)
147+ self._logger.warn('Starting Balance set to zero in %s registers.' % (cr.rowcount,))
148+ return True
149+
150+ # UF16.1
151+>>>>>>> MERGE-SOURCE
152 def remove_ir_actions_linked_to_deleted_modules(self, cr, uid, *a, **b):
153 # delete remove actions
154 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')")
155
156=== modified file 'bin/addons/msf_sync_data_server/data/sync_server.sync_rule.csv'
157--- bin/addons/msf_sync_data_server/data/sync_server.sync_rule.csv 2020-03-04 17:30:01 +0000
158+++ bin/addons/msf_sync_data_server/data/sync_server.sync_rule.csv 2020-04-30 18:52:36 +0000
159@@ -123,9 +123,10 @@
160 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
161 msf_sync_data_server.country_restrictions,TRUE,TRUE,FALSE,TRUE,bidirectional,Down,[],['name'],MISSION,res.country.restriction,,Country restrictions,Valid,,570
162 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
163-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
164+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', 'local_activation_from_merge', '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
165 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
166 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
167+msf_sync_data_server.merge_product,TRUE,TRUE,FALSE,TRUE,bidirectional,Down,"[]","['new_product_id/id', 'old_product_id/id']",MISSION,product.merged,,Merge Product,Valid,,608
168 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
169 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
170 msf_sync_data_server.tax_code_tree,TRUE,TRUE,FALSE,TRUE,bidirectional,Down,"[('parent_id','!=','')]",['parent_id/id'],OC,account.tax.code,,Tax Code Tree,Valid,,611
171
172=== modified file 'bin/addons/product/product.py'
173--- bin/addons/product/product.py 2019-12-11 14:54:37 +0000
174+++ bin/addons/product/product.py 2020-04-30 18:52:36 +0000
175@@ -641,6 +641,8 @@
176 if arg[0] == 'expected_prod_creator':
177 prod_creator_ids = self._get_authorized_creator(cr, uid, arg[2]=='bned', context)
178
179+ if arg[2]!='bned':
180+ return [('international_status', 'in', prod_creator_ids), ('replaced_by_product_id', '=', False)]
181 return [('international_status', 'in', prod_creator_ids)]
182
183 _defaults = {
184
185=== modified file 'bin/addons/product_asset/product_asset.py'
186--- bin/addons/product_asset/product_asset.py 2019-09-18 14:06:52 +0000
187+++ bin/addons/product_asset/product_asset.py 2020-04-30 18:52:36 +0000
188@@ -411,7 +411,7 @@
189 #UF-2170: remove the standard price value from the list if the value comes from the sync
190 #US-803: If the price comes from rw_sync, then take it
191 # US-3254: update standard_pricde during initial sync (i.e if msf.instance is not set)
192- if 'standard_price' in vals and context.get('sync_update_execution', False) and not context.get('rw_sync', False):
193+ if 'standard_price' in vals and context.get('sync_update_execution', False) and not context.get('rw_sync', False) and not context.get('keep_standard_price'):
194 msf_instance = self.pool.get('res.users').browse(cr, uid, uid).company_id.instance_id
195 if msf_instance:
196 del vals['standard_price']
197
198=== modified file 'bin/addons/product_attributes/__openerp__.py'
199--- bin/addons/product_attributes/__openerp__.py 2017-10-09 21:06:10 +0000
200+++ bin/addons/product_attributes/__openerp__.py 2020-04-30 18:52:36 +0000
201@@ -31,6 +31,7 @@
202 'init_xml': [
203 'security/ir.model.access.csv',
204 'wizard/product_where_used_view.xml',
205+ 'wizard/product_merged_view.xml',
206 'report/standard_price_track_changes_report.xml',
207 'data/product_section_code.xml',
208 'data/product_supply_source.xml',
209
210=== modified file 'bin/addons/product_attributes/product_attributes.py'
211--- bin/addons/product_attributes/product_attributes.py 2020-04-06 08:43:29 +0000
212+++ bin/addons/product_attributes/product_attributes.py 2020-04-30 18:52:36 +0000
213@@ -268,8 +268,10 @@
214 class product_attributes_template(osv.osv):
215 _inherit = "product.template"
216
217+
218 _columns = {
219 '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."),
220+ 'state': fields.many2one('product.status', 'Status', help="Tells the user if he can use the product or not."),
221 }
222
223 _defaults = {
224@@ -290,19 +292,12 @@
225
226 product_country_restriction()
227
228-class product_template(osv.osv):
229- _inherit = 'product.template'
230-
231- _columns = {
232- 'state': fields.many2one('product.status', 'Status', help="Tells the user if he can use the product or not."),
233- }
234-
235-product_template()
236-
237
238 class product_attributes(osv.osv):
239 _inherit = "product.product"
240
241+ merged_fields_to_keep = ['procure_method', 'standard_price', 'list_price', 'soq_quantity', 'description_sale', 'description_purchase', 'procure_delay']
242+
243 def execute_migration(self, cr, moved_column, new_column):
244 super(product_attributes, self).execute_migration(cr, moved_column, new_column)
245
246@@ -655,16 +650,77 @@
247 dom += [ '&', ('batch_management', '=', False), ('perishable', '=', True)]
248 return dom
249
250+ def _search_show_ud(self, cr, uid, obj, name, args, context=None):
251+ dom = []
252+ for arg in args:
253+ if arg[1] != '=':
254+ raise osv.except_osv(_('Warning'), _('This filter is not implemented yet'))
255+ if arg[2]:
256+ dom = [('international_status', '=', 'UniData'), ('active', '=', True), ('standard_ok', 'in', ['non_standard', 'standard']), ('replace_product_id', '=', False)]
257+ else:
258+ dom = [('international_status', '=', 'UniData'), ('active', '=', False), ('standard_ok', '=', 'non_standard_local'), ('replace_product_id', '=', False)]
259+
260+ return dom
261
262 def _get_local_from_hq(self, cr, uid, ids, field_name, args, context=None):
263+ '''
264+ used by sync to set active=False at coo / proj
265+ '''
266+
267 res = {}
268 for _id in ids:
269 res[_id] = False
270
271 if self.pool.get('res.company')._get_instance_level(cr, uid) == 'section':
272- for _id in self.search(cr, uid, [('id', 'in', ids), ('standard_ok', '=', 'non_standard_local')], context=context):
273- res[_id] = True
274-
275+ for _id in self.search(cr, uid, [('id', 'in', ids), ('standard_ok', '=', 'non_standard_local'), ('international_status', '=', 'UniData'), ('active', 'in', ['t', 'f'])], context=context):
276+ res[_id] = True
277+
278+ return res
279+
280+ def _get_local_activation_from_merge(self, cr, uid, ids, field_name, args, context=None):
281+ '''
282+ used by sync to not sync down active=True from coo to proj, activation of UD prod from COO will be done by the sync merge update
283+ '''
284+ res = {}
285+ for _id in ids:
286+ res[_id] = False
287+
288+ if self.pool.get('res.company')._get_instance_level(cr, uid) == 'coordo':
289+ for _id in self.search(cr, uid, [('id', 'in', ids), ('international_status', '=', 'UniData'), ('active', '=', True), ('replace_product_id', '!=', False)], context=context):
290+ res[_id] = True
291+
292+ return res
293+
294+ def _get_product_instance_level(self, cr, uid, ids, field_name, args, context=None):
295+ res = {}
296+ level = self.pool.get('res.company')._get_instance_level(cr, uid)
297+ for _id in ids:
298+ res[_id] = level
299+ return res
300+
301+ def _get_allow_merge(self, cr, uid, ids, field_name, args, context=None):
302+ res = {}
303+ for _id in ids:
304+ res[_id] = False
305+ if context is None:
306+ context = {}
307+ if context.get('sync_update_execution') or self.pool.get('res.company')._get_instance_level(cr, uid) == 'coordo':
308+ dom = [('id', 'in', ids), ('international_status', '=', 'UniData'), ('replace_product_id', '=', False)]
309+ if context.get('sync_update_execution'):
310+ # UD prod deactivated in coordo + merge + sync : proj does not see the deactivation
311+ dom += [('active', 'in', ['t', 'f'])]
312+ else:
313+ dom += ['|', '&', ('active', '=', False), ('standard_ok', '=', 'non_standard_local'), '&', ('active', '=', True), ('standard_ok', 'in', ['non_standard', 'standard'])]
314+ for p_id in self.search(cr, uid, dom, context=context):
315+ res[p_id] = True
316+ return res
317+
318+ def _get_nsl_merged(self, cr, uid, ids, field_name, args, context=None):
319+ res = {}
320+ for _id in ids:
321+ res[_id] = False
322+ for _id in self.search(cr, uid, [('id', 'in', ids), ('replaced_by_product_id', '!=', False), ('active', 'in', ['t', 'f'])], context=context):
323+ res[_id] = True
324 return res
325
326 _columns = {
327@@ -958,13 +1014,20 @@
328 string='Standardization Level',
329 required=True,
330 ),
331- 'local_from_hq': fields.function(_get_local_from_hq, method=1, type='boolean', string='Non-Standard Local from HQ'),
332+ '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),
333+ 'local_activation_from_merge': fields.function(_get_local_activation_from_merge, method=1, type='boolean', string='Non-Standard Local from COO', help='Activate on COO from merge', internal=1),
334 'soq_weight': fields.float(digits=(16,5), string='SoQ Weight'),
335 'soq_volume': fields.float(digits=(16,5), string='SoQ Volume'),
336 '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."),
337 'vat_ok': fields.function(_get_vat_ok, method=True, type='boolean', string='VAT OK', store=False, readonly=True),
338+ 'nsl_merged': fields.function(_get_nsl_merged, method=True, type='boolean', string='UD / NSL merged'),
339+ 'replace_product_id': fields.many2one('product.product', string='Merged from', select=1),
340+ 'replaced_by_product_id': fields.many2one('product.product', string='Merged to'),
341+ 'allow_merge': fields.function(_get_allow_merge, type='boolean', method=True, string="UD Allow merge"),
342 'uf_write_date': fields.datetime(_('Write date')),
343 'uf_create_date': fields.datetime(_('Creation date')),
344+ 'instance_level': fields.function(_get_product_instance_level, method=True, string='Instance Level', internal=1, type='char'),
345+ 'show_ud': fields.function(_get_dummy, fnct_search=_search_show_ud, method=True, type='boolean', string='Search UD NSL or ST/NS', internal=1),
346 }
347
348 def _get_default_sensitive_item(self, cr, uid, context=None):
349@@ -1043,6 +1106,12 @@
350 field.set('invisible', '0')
351 res['arch'] = etree.tostring(root)
352
353+ if view_type == 'tree' and context.get('display_old_code'):
354+ root = etree.fromstring(res['arch'])
355+ for field in root.xpath('//field[@name="old_code"]'):
356+ field.set('invisible', '0')
357+ res['arch'] = etree.tostring(root)
358+
359 if view_type == 'search' and context.get('available_for_restriction'):
360 context.update({'search_default_not_restricted': 1})
361 root = etree.fromstring(res['arch'])
362@@ -1282,6 +1351,7 @@
363 if context.get('sync_update_execution') and vals.get('local_from_hq'):
364 vals['active'] = False
365
366+
367 def update_existing_translations(model, res_id, xmlid):
368 # If we are in the creation of product by sync. engine, attach the already existing translations to this product
369 if context.get('sync_update_execution'):
370@@ -1352,6 +1422,7 @@
371 vals['uf_create_date'] = vals.get('uf_create_date') or datetime.now()
372
373 self.convert_price(cr, uid, vals, context)
374+
375 res = super(product_attributes, self).create(cr, uid, vals, context=context)
376
377 if context.get('sync_update_execution'):
378@@ -1414,7 +1485,6 @@
379 if not ids:
380 return True
381 smrl_obj = self.pool.get('stock.mission.report.line')
382- prod_status_obj = self.pool.get('product.status')
383 int_stat_obj = self.pool.get('product.international.status')
384
385 if context is None:
386@@ -1424,8 +1494,7 @@
387 ids = [ids]
388
389 self.clean_standard(cr, uid, vals, context)
390- if context.get('sync_update_execution') and vals.get('local_from_hq') and vals.get('active'):
391- del vals['active']
392+
393 if 'batch_management' in vals:
394 vals['track_production'] = vals['batch_management']
395 vals['track_incoming'] = vals['batch_management']
396@@ -1466,21 +1535,35 @@
397 _("Product Code %s must include a 'Z' character") % (vals['default_code'],),
398 )
399
400- # update local stock mission report lines :
401- if 'state' in vals:
402- prod_state = ''
403- if vals['state']:
404- state_id = vals['state']
405- if isinstance(state_id, (int, long)):
406- state_id = [state_id]
407- prod_state = prod_status_obj.read(cr, uid, state_id, ['code'], context=context)[0]['code']
408- 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)
409+ if context.get('sync_update_execution') and vals.get('local_from_hq'):
410+ if vals.get('active'):
411+ del(vals['active'])
412+
413+ # do not erase values from merged product
414+ if self.search_exist(cr, uid, [('id', 'in', ids), ('replace_product_id', '!=', False)], context=context):
415+ for field in self.merged_fields_to_keep + ['old_code']:
416+ if field in vals:
417+ del(vals[field])
418+
419+ if context.get('sync_update_execution') and vals.get('local_activation_from_merge') and vals.get('active'):
420+ # active value on project will be set by the update to merge product
421+ del(vals['active'])
422+
423+
424+ if 'state_ud' in vals:
425+ # just update SMRL that belongs to our instance:
426+ local_smrl_ids = smrl_obj.search(cr, uid, [
427+ ('product_id', 'in', ids),
428+ ('full_view', '=', False),
429+ ('mission_report_id.local_report', '=', True),
430+ ('state_ud', '!=', vals['state_ud'] or ''),
431+ ], context=context)
432 if local_smrl_ids:
433 no_sync_context = context.copy()
434 no_sync_context['sync_update_execution'] = False
435- smrl_obj.write(cr, 1, local_smrl_ids, {'product_state': prod_state}, context=no_sync_context)
436+ smrl_obj.write(cr, 1, local_smrl_ids, {'state_ud': vals['state_ud'] or ''}, context=no_sync_context)
437
438- if intstat_code:
439+ if 'international_status' in vals:
440 # just update SMRL that belongs to our instance:
441 local_smrl_ids = smrl_obj.search(cr, uid, [
442 ('international_status_code', '!=', intstat_code),
443@@ -1493,19 +1576,6 @@
444 no_sync_context['sync_update_execution'] = False
445 smrl_obj.write(cr, 1, local_smrl_ids, {'international_status_code': intstat_code or ''}, context=no_sync_context)
446
447- if 'state_ud' in vals:
448- # just update SMRL that belongs to our instance:
449- local_smrl_ids = smrl_obj.search(cr, uid, [
450- ('product_id', 'in', ids),
451- ('full_view', '=', False),
452- ('mission_report_id.local_report', '=', True),
453- ('state_ud', '!=', vals['state_ud'] or ''),
454- ], context=context)
455- if local_smrl_ids:
456- no_sync_context = context.copy()
457- no_sync_context['sync_update_execution'] = False
458- smrl_obj.write(cr, 1, local_smrl_ids, {'state_ud': vals['state_ud'] or ''}, context=no_sync_context)
459-
460 product_uom_categ = []
461 if 'uom_id' in vals or 'uom_po_id' in vals:
462 if isinstance(ids, (int, long)):
463@@ -1528,13 +1598,20 @@
464 vals.update({
465 'active': True,
466 })
467- elif vals.get('active', None) is True:
468- vals.update({
469- 'active': True,
470- # 'state': phase_out_status,
471- })
472-
473- if 'active' in vals:
474+ elif vals.get('active', None) is True:
475+ vals.update({
476+ 'active': True,
477+ # 'state': phase_out_status,
478+ })
479+
480+ if 'state' in vals:
481+ local_smrl_ids = smrl_obj.search(cr, uid, [('product_state', '!=', vals['state']), ('product_id', 'in', ids), ('full_view', '=', False), ('mission_report_id.local_report', '=', True)], context=context)
482+ if local_smrl_ids:
483+ no_sync_context = context.copy()
484+ no_sync_context['sync_update_execution'] = False
485+ smrl_obj.write(cr, 1, local_smrl_ids, {'product_state': vals['state']}, context=no_sync_context)
486+
487+ if ids and 'active' in vals:
488 local_smrl_ids = smrl_obj.search(cr, uid, [
489 ('product_id', 'in', ids),
490 ('full_view', '=', False),
491@@ -1566,6 +1643,7 @@
492 self.set_as_edonly(cr, uid, ids, context=context)
493 else:
494 self.set_as_nobn_noed(cr, uid, ids, context=context)
495+
496 res = super(product_attributes, self).write(cr, uid, ids, vals, context=context)
497
498 if product_uom_categ:
499@@ -1577,7 +1655,6 @@
500
501 return res
502
503-
504 def reactivate_product(self, cr, uid, ids, context=None):
505 '''
506 Re-activate product.
507@@ -1603,7 +1680,7 @@
508
509 return True
510
511- def deactivate_product(self, cr, uid, ids, context=None):
512+ def deactivate_product(self, cr, uid, ids, context=None, try_only=False):
513 '''
514 De-activate product.
515 Check if the product is not used in any document in Unifield
516@@ -1614,7 +1691,6 @@
517 if isinstance(ids, (int, long)):
518 ids = [ids]
519
520- # TODO JFB RR: constraint
521 location_obj = self.pool.get('stock.location')
522 po_line_obj = self.pool.get('purchase.order.line')
523 tender_line_obj = self.pool.get('tender.line')
524@@ -1859,6 +1935,10 @@
525 'doc_ref': invoice.invoice_id.number,
526 'doc_id': invoice.invoice_id.id}, context=context)
527
528+
529+ if try_only:
530+ return {'ok': False, 'error': wizard_id}
531+
532 if context.get('sync_update_execution', False):
533 context['bypass_sync_update'] = True
534 self.write(cr, uid, product.id, {
535@@ -1873,6 +1953,9 @@
536 'target': 'new',
537 'context': context}
538
539+ if try_only:
540+ return {'ok': True, 'error': False}
541+
542 if context.get('sync_update_execution', False):
543 context['bypass_sync_update'] = True
544
545@@ -1969,6 +2052,11 @@
546 product2copy = self.read(cr, uid, [id], ['default_code', 'name'])[0]
547 if default is None:
548 default = {}
549+
550+ for to_reset in ['replace_product_id', 'replaced_by_product_id']:
551+ if to_reset not in default:
552+ default[to_reset] = False
553+
554 temp_status = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'product_attributes', 'int_5')[1]
555
556 copy_pattern = _("%s (copy)")
557@@ -2350,12 +2438,241 @@
558 nb += self.switch_bn_to_no(cr, uid, ids, context)
559 return nb
560
561+ def check_same_value(self, cr, uid, new_prod_id, old_prod_id, blocker=True, context=None):
562+
563+ if blocker:
564+ fields_to_check = [
565+ 'type', 'subtype', 'perishable', 'batch_management', 'uom_id'
566+ ]
567+ else:
568+ fields_to_check = [
569+ 'nomen_manda_0', 'nomen_manda_1', 'nomen_manda_2', 'heat_sensitive_item', 'controlled_substance', 'dangerous_goods'
570+ ]
571+
572+ old_values = self.read(cr, uid, old_prod_id, fields_to_check, context=context)
573+ new_values = self.read(cr, uid, new_prod_id, fields_to_check, context=context)
574+
575+ failed = []
576+ for f in fields_to_check:
577+ if old_values[f] != new_values[f]:
578+ failed.append(f)
579+
580+ if failed:
581+ fields_data = self.fields_get(cr, uid, failed, context=context)
582+ values = {'attr': ', '.join([fields_data[x].get('string') for x in failed])}
583+ if blocker:
584+ return _('There is an inconsistency between the selected products: %(attr)s need to be the same. Please update your local product %(attr)s and then proceed with the merge.') % values
585+ return _('There is an inconsistency between the selected products’ %(attr)s . Do you still want to proceed with the merge?') % values
586+
587+ return ''
588+
589+ def open_merge_product_wizard(self, cr, uid, prod_id, context=None):
590+ if self.pool.get('res.company')._get_instance_level(cr, uid) != 'coordo':
591+ raise osv.except_osv(_('Warning'), _('Merge products can only be done at Coordo level.'))
592+
593+ wiz_id = self.pool.get('product.merged.wizard').create(cr, uid, {'old_product_id': prod_id[0]}, context=context)
594+ return {
595+ 'type': 'ir.actions.act_window',
596+ 'res_model': 'product.merged.wizard',
597+ 'res_id': wiz_id,
598+ 'view_type': 'form',
599+ 'view_mode': 'form',
600+ 'target': 'new',
601+ 'context': context,
602+ 'height': '400px',
603+ 'width': '720px',
604+ }
605+
606+
607+ def _error_used_in_doc(self, cr, uid, prod_id, context=None):
608+ error = []
609+
610+ doc_field_error_dom = [
611+ ('stock.move', 'product_id', _('stock move(s)'), []),
612+ ('stock.production.lot', 'product_id', _('batch(es)'), []),
613+ ('purchase.order.line', 'product_id', _('PO line(s)'), [('rfq_ok', '=', False)]),
614+ ('purchase.order.line', 'product_id', _('RfQ line(s)'), [('rfq_ok', '=', True)]),
615+ ('sale.order.line', 'product_id', _('FO line(s)'), [('procurement_request', '=', False)]),
616+ ('sale.order.line', 'product_id', _('IR line(s)'), [('procurement_request', '=', True)]),
617+ ('tender.line', 'product_id', _('Tender line(s)'), []),
618+ ('physical.inventory.counting', 'product_id', _('Inventory line(s)'), []),
619+ ('initial.stock.inventory.line', 'product_id', _('Initial Stock Inventory line(s)'), []),
620+ ('real.average.consumption.line', 'product_id', _('RAC line(s)'), []),
621+ ('replenishment.segment.line', 'product_id', _('Replenishment Segment line(s)'), []),
622+ ('product.list.line', 'name', _('Product list line(s)'), []),
623+ ('composition.kit', 'composition_product_id', _('Composition Kit(s)'), []),
624+ ('composition.item', 'item_product_id', _('Composition Kit line(s)'), []),
625+ ]
626+
627+ for obj, field, msg, dom in doc_field_error_dom:
628+ nb = self.pool.get(obj).search(cr, uid, [(field, '=', prod_id)]+dom, count=True, context=context)
629+ if nb:
630+ error.append('%d %s' % (nb, msg))
631+
632+ return ', '.join(error)
633+
634+
635+ def _has_pipe(self, cr, uid, ids):
636+ if not ids:
637+ return False
638+
639+ if isinstance(ids, (int, long)):
640+ ids = [ids]
641+
642+ cr.execute('''
643+ select
644+ l.product_id
645+ from
646+ stock_mission_report r, msf_instance i, stock_mission_report_line l
647+ where
648+ i.id = r.instance_id and
649+ i.state = 'active' and
650+ l.mission_report_id = r.id and
651+ l.product_id in %s and
652+ r.full_view = 'f' and
653+ ( l.internal_qty > 0 or l.in_pipe_qty > 0)
654+ group by l.product_id
655+ ''' , (tuple(ids), ))
656+ return [x[0] for x in cr.fetchall()]
657+
658+
659+ def merge_product(self, cr, uid, nsl_prod_id, local_id, context=None):
660+ if context is None:
661+ context = {}
662+
663+ new_data = self.read(cr, uid, nsl_prod_id, ['default_code','old_code', 'allow_merge'], context=context)
664+ if not new_data['allow_merge']:
665+ raise osv.except_osv(_('Warning'), _('New product %s condition not met') % new_data['default_code'])
666+
667+ error_used = self._error_used_in_doc(cr, uid, nsl_prod_id, context=None)
668+ if error_used:
669+ raise osv.except_osv(_('Warning'), _('The selected UD product %s has already been used in the past. Merge cannot be done for this product') % (new_data['default_code'], ))
670+
671+ if self._has_pipe(cr, uid, nsl_prod_id):
672+ raise osv.except_osv(_('Warning'), _('Warning there is stock / pipeline in at least one of the instances in this mission! Therefore the product cannot be merged') % (new_data['default_code'], ))
673+
674+ local_dom = [('id', '=', local_id), ('international_status', '=', 'Local'), ('replaced_by_product_id', '=', False)]
675+ if not context.get('sync_update_execution'):
676+ local_dom += [('active', '=', True)]
677+ else:
678+ local_dom += [('active', 'in', ['t', 'f'])]
679+ if not self.search_exist(cr, uid, local_dom, context=context):
680+ old_prod = self.read(cr, uid, local_id, ['default_code'], context=context)
681+ raise osv.except_osv(_('Warning'), _('Old merged product %s: condition not met: active, local product') % old_prod['default_code'])
682+
683+
684+ block_msg = self.check_same_value( cr, uid, nsl_prod_id, local_id, blocker=True, context=context)
685+ if block_msg:
686+ prod_data = self.read(cr, uid, [nsl_prod_id, local_id], ['default_code'], context=context)
687+ raise osv.except_osv(_('Warning'), '%s\nProducts: %s and %s' % (block_msg, prod_data[0]['default_code'], prod_data[1]['default_code']))
688+
689+ blacklist_table = {
690+ 'product_template': [
691+ ('product_product', 'product_tmpl_id')
692+ ],
693+ 'product_product': [
694+ ('stock_mission_report_line', 'product_id'),
695+ ('stock_mission_report_line_location', 'product_id'),
696+ ('prod_mass_update_product_rel', 'prod_mass_update_id'),
697+ ('product_mass_update_errors', 'product_id'),
698+ ('product_product', 'replace_product_id'),
699+ ('product_product', 'replaced_by_product_id'),
700+ ]
701+ }
702+
703+
704+ default_code = new_data['default_code']
705+ for table in ['product_product', 'product_template']:
706+ for x in cr.get_referenced(table):
707+ if (x[0], x[1]) in blacklist_table.get(table):
708+ continue
709+
710+ params = {'old_prod': local_id, 'nsl_prod_id': nsl_prod_id}
711+ if cr.column_exists(x[0], 'default_code'):
712+ params['default_code'] = default_code
713+ add_query = ' , default_code=%(default_code)s '
714+ else:
715+ add_query = ''
716+
717+ cr.execute('update '+x[0]+' set '+x[1]+'=%(nsl_prod_id)s '+add_query+' where '+x[1]+'=%(old_prod)s', params) # not_a_user_entry
718+
719+ new_write_data = {'active': True, 'replace_product_id': local_id}
720+ old_prod_data = self.read(cr, uid, local_id, self.merged_fields_to_keep+['default_code'], context=context)
721+ write_context = context.copy()
722+ if not context.get('sync_update_execution'):
723+ fields_to_keep = self.merged_fields_to_keep
724+ new_write_data['old_code'] = '%s;%s' % (new_data['old_code'], old_prod_data['default_code']) if new_data['old_code'] else old_prod_data['default_code']
725+ else:
726+ fields_to_keep = ['list_price', 'standard_price']
727+ write_context['keep_standard_price'] = True
728+
729+ for field in fields_to_keep:
730+ new_write_data[field] = old_prod_data[field]
731+
732+ self.write(cr, uid, nsl_prod_id, new_write_data, context=write_context)
733+
734+ self.write(cr, uid, local_id, {'active': False, 'replaced_by_product_id': nsl_prod_id}, context=context)
735+ if not context.get('sync_update_execution'):
736+ self.pool.get('product.merged').create(cr, 1, {'new_product_id': nsl_prod_id, 'old_product_id': local_id}, context=context)
737+
738+ # reset mission stock on nsl + old to 0, will be computed on next mission stock update
739+ mission_stock_fields_reset = [
740+ 'stock_qty', 'stock_val',
741+ 'in_pipe_coor_qty', 'in_pipe_coor_val', 'in_pipe_qty', 'in_pipe_val',
742+ 'secondary_qty', 'secondary_val',
743+ 'cu_qty', 'cu_val',
744+ 'central_qty', 'central_val',
745+ 'cross_qty', 'cross_val',
746+ 'wh_qty', 'internal_qty'
747+ ]
748+ cr.execute('''
749+ update stock_mission_report_line set ''' + ', '.join(['%s=%%(zero)s' % field for field in mission_stock_fields_reset]) + '''
750+ where
751+ mission_report_id in (select id from stock_mission_report where full_view='f' and instance_id=%(local_instance_id)s) and
752+ product_id in %(product_ids)s
753+ ''', {'zero': 0, 'local_instance_id': self.pool.get('res.company')._get_instance_id(cr, uid), 'product_ids': (nsl_prod_id, local_id)}) # not_a_user_entry
754+ cr.execute("delete from mission_line_move_rel where move_id in (select id from stock_move where product_id = %s)", (nsl_prod_id,))
755+ cr.execute("delete from mission_move_rel where move_id in (select id from stock_move where product_id = %s)", (nsl_prod_id,))
756+
757+ return True
758+
759+
760 _constraints = [
761 (_check_gmdn_code, 'Warning! GMDN code must be digits!', ['gmdn_code'])
762 ]
763
764 product_attributes()
765
766+class product_merged(osv.osv):
767+ """
768+ Object mainly used to trigger sync update
769+ """
770+
771+ _name = 'product.merged'
772+ _description = 'NSL products merged'
773+ _rec_name = 'new_product_id'
774+
775+ _columns = {
776+ 'new_product_id': fields.many2one('product.product', 'UD NSL Product', required=1, select=1),
777+ 'old_product_id': fields.many2one('product.product', 'Old local Product', required=1, select=1),
778+ }
779+
780+ def create(self, cr, uid, vals, context=None):
781+ if context is None:
782+ context = {}
783+
784+ new_id = super(product_merged, self).create(cr, uid, vals, context=context)
785+ if context.get('sync_update_execution'):
786+ self.pool.get('product.product').merge_product(cr, uid, vals['new_product_id'], vals['old_product_id'], context=context)
787+
788+ return new_id
789+
790+ _sql_constraints = [
791+ ('unique_new_product_id', 'unique(new_product_id)', 'UD already used to merge a local product'),
792+ ('unique_old_product_id', 'unique(old_product_id)', 'Local product already merged'),
793+ ]
794+
795+product_merged()
796
797 class product_deactivation_error(osv.osv_memory):
798 _name = 'product.deactivation.error'
799
800=== modified file 'bin/addons/product_attributes/product_attributes_view.xml'
801--- bin/addons/product_attributes/product_attributes_view.xml 2020-03-31 12:31:03 +0000
802+++ bin/addons/product_attributes/product_attributes_view.xml 2020-04-30 18:52:36 +0000
803@@ -14,6 +14,7 @@
804 <tree colors="red:virtual_available&lt;0;blue:virtual_available&gt;=0 and state in ('draft', 'end', 'obsolete');grey:active == False" string="Products">
805 <field name="default_code" string="Code"/>
806 <field name="name" string="Description"/>
807+ <field name="old_code" invisible="1" />
808 <field name="categ_id" invisible="1"/>
809 <field name="uom_id" string="UoM"/>
810 <field name="type" invisible="1" />
811@@ -64,8 +65,7 @@
812 <group colspan="4" col="6">
813 <group colspan="1" col="2">
814 <separator string="Codes" colspan="2"/>
815- <field name="default_code" required="1" string="Code" on_change="onchange_code(default_code)"
816- attrs="{'readonly':[('duplicate_ok','=',False)]}" />
817+ <field name="default_code" required="1" string="Code" on_change="onchange_code(default_code)" attrs="{'readonly':[('duplicate_ok','=',False)]}" />
818 <field name="new_code"/>
819 <field name="duplicate_ok" invisible="1"/>
820 </group>
821@@ -117,10 +117,15 @@
822 <field name="state" colspan="3" widget="selection" />
823 <field name="state_ud" colspan="3" />
824 <field name="oc_subscription" colspan="3" readonly="1" attrs="{'invisible': [('int_status_code', '!=', 'unidata')]}"/>
825- <field name="active" readonly="1"/>
826+ <group colspan="2" col="4">
827+ <field name="active" readonly="1"/>
828+ <field name="nsl_merged" attrs="{'invisible': [('int_status_code', '!=', 'local')]}" />
829+ </group>
830+ <field name="instance_level" invisible="1" />
831 <group colspan="1" col="1">
832 <button name="deactivate_product" icon="gtk-execute" string="De-activate product" type="object" attrs="{'invisible': [('active', '=', False)]}" />
833- <button name="reactivate_product" icon="gtk-execute" string="Re-activate product" type="object" attrs="{'invisible': [('active', '=', True)]}" />
834+ <button name="reactivate_product" icon="gtk-execute" string="Re-activate product" type="object" attrs="{'invisible': ['|', ('active', '=', True), ('nsl_merged', '=', True)]}" />
835+ <button name="open_merge_product_wizard" icon="gtk-convert" string="Merge product" type="object" attrs="{'invisible': ['|', '|', '|', ('int_status_code', '!=', 'local'), ('nsl_merged', '=', True), ('active', '=', False), ('instance_level', '!=', 'coordo')]}"/>
836 </group>
837 </group>
838 <group colspan="2" col="3" name="quality">
839@@ -161,11 +166,6 @@
840 <group colspan="2" col="2">
841 <separator string="Diffusion" colspan="2"/>
842 <field name="justification_code_id" colspan="2" />
843- <!-- uf-1544
844- <field name="med_device_class" colspan="2" />
845- <field name="gmdn_code" colspan="2" />
846- <field name="gmdn_description" colspan="2" />
847- -->
848 <field name="closed_article" colspan="2"/>
849 <field name="manufacturer_ref" attrs="{'invisible': [('closed_article', '=', 'no')]}" />
850 <field name="manufacturer_txt" attrs="{'invisible': [('closed_article', '=', 'no')]}" />
851@@ -301,10 +301,6 @@
852 <!-- uf-1544 invisible-->
853 <group colspan="2" col="2" name="store">
854 </group>
855- <!--
856- <separator string="Storage Localisation" colspan="2"/>
857- <field name="loc_indic" attrs="{'readonly':[('type','=','service')]}" />
858- -->
859
860 </page>
861 </notebook>
862
863=== modified file 'bin/addons/product_attributes/wizard/__init__.py'
864--- bin/addons/product_attributes/wizard/__init__.py 2013-08-08 14:33:01 +0000
865+++ bin/addons/product_attributes/wizard/__init__.py 2020-04-30 18:52:36 +0000
866@@ -20,5 +20,5 @@
867 ##############################################################################
868
869 import product_where_used
870-
871+import product_merged
872 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
873
874=== added file 'bin/addons/product_attributes/wizard/product_merged.py'
875--- bin/addons/product_attributes/wizard/product_merged.py 1970-01-01 00:00:00 +0000
876+++ bin/addons/product_attributes/wizard/product_merged.py 2020-04-30 18:52:36 +0000
877@@ -0,0 +1,64 @@
878+# -*- coding: utf-8 -*-
879+
880+from osv import osv
881+from osv import fields
882+from tools.translate import _
883+
884+
885+class product_merged_wizard(osv.osv_memory):
886+ _name = 'product.merged.wizard'
887+
888+
889+ _columns = {
890+ 'old_product_id': fields.many2one('product.product', 'Old local Product', readonly=1),
891+ 'new_product_id': fields.many2one('product.product', 'UD Product'),
892+ 'show_ud': fields.boolean('Use Standard / Non Standard UD product', help="Unticked: display UD NSL inactive products\nTicked: display UD ST + NS active products"),
893+ 'ud_old_code': fields.char('UD Old code', readonly=1, size=1024),
894+ 'warning_msg': fields.text('Warning Message'),
895+ 'warning_checked': fields.boolean('Warning Checked'),
896+ }
897+
898+ def do_merge_product(self, cr, uid, ids, context=None):
899+ prod_obj = self.pool.get('product.product')
900+ wiz = self.browse(cr, uid, ids[0], context)
901+
902+ error_used = prod_obj._error_used_in_doc(cr, uid, wiz.new_product_id.id, context=context)
903+ if error_used:
904+ raise osv.except_osv(_('Warning'), _('The selected UD product has already been used in the past. Merge cannot be done for this product.'))
905+
906+ if prod_obj._has_pipe(cr, uid, wiz.new_product_id.id):
907+ raise osv.except_osv(_('Warning'), _('Warning there is stock / pipeline in at least one of the instances in this mission! Therefore this product cannot be merged.'))
908+
909+ block_msg = prod_obj.check_same_value(cr, uid, wiz.new_product_id.id, wiz.old_product_id.id, blocker=True, context=context)
910+ if block_msg:
911+ raise osv.except_osv(_('Warning'), block_msg)
912+
913+ if not wiz.warning_checked:
914+ warn_msg = prod_obj.check_same_value(cr, uid, wiz.new_product_id.id, wiz.old_product_id.id, blocker=False, context=context)
915+ if warn_msg:
916+ self.write(cr, uid, ids, {'warning_msg': warn_msg, 'warning_checked': True, 'ud_old_code': wiz.new_product_id.old_code}, context=context)
917+ return {
918+ 'type': 'ir.actions.act_window',
919+ 'res_model': 'product.merged.wizard',
920+ 'view_type': 'form',
921+ 'view_mode': 'form',
922+ 'target': 'new',
923+ 'res_id': ids[0],
924+ 'context': context,
925+ 'height': '400px',
926+ 'width': '720px',
927+ }
928+
929+
930+ prod_obj.merge_product(cr, uid, wiz.new_product_id.id, wiz.old_product_id.id, context=None)
931+ res = self.pool.get('ir.actions.act_window').open_view_from_xmlid(cr, uid, 'product.product_normal_action', ['form', 'tree'], context=context)
932+ res['res_id'] = wiz.new_product_id.id
933+ return res
934+
935+ def change_warning(self, cr, uid, ids, new_prod, context=None):
936+ old_code =False
937+ if new_prod:
938+ old_code = self.pool.get('product.product').browse(cr, uid, new_prod, fields_to_fetch=['old_code'], context=context).old_code
939+ return {'value': {'warning_checked': False, 'warning_msg': False, 'ud_old_code': old_code}}
940+
941+product_merged_wizard()
942
943=== added file 'bin/addons/product_attributes/wizard/product_merged_view.xml'
944--- bin/addons/product_attributes/wizard/product_merged_view.xml 1970-01-01 00:00:00 +0000
945+++ bin/addons/product_attributes/wizard/product_merged_view.xml 2020-04-30 18:52:36 +0000
946@@ -0,0 +1,26 @@
947+<?xml version="1.0" encoding="utf-8"?>
948+<openerp>
949+ <data>
950+
951+ <record id="product_merged_wizard_form_view" model="ir.ui.view">
952+ <field name="name">product.merged.wizard.form.view</field>
953+ <field name="model">product.merged.wizard</field>
954+ <field name="type">form</field>
955+ <field name="arch" type="xml">
956+ <form string="Merge Product">
957+ <field name="old_product_id" colspan="4" />
958+ <field name="show_ud" />
959+ <field name="new_product_id" colspan="4" required="1" on_change="change_warning(new_product_id)" domain="[('show_ud', '=', show_ud), ('active', 'in', ['t', 'f'])]" context="{'display_active_filter': 0, 'display_old_code': 1}"/>
960+ <field name="ud_old_code" />
961+ <field name="warning_checked" invisible="1" />
962+ <group colspan="4" attrs="{'invisible': [('warning_msg', '=', False)]}">
963+ <separator colspan="4" string="Warning" />
964+ <field name="warning_msg" colspan="4" nolabel="1" />
965+ </group>
966+ <button name="do_merge_product" string="Merge Product" type="object" icon="gtk-ok" colspan="2" />
967+ <button special="cancel" string="Cancel" icon="gtk-cancel" colspan="2" />
968+ </form>
969+ </field>
970+ </record>
971+ </data>
972+</openerp>
973
974=== modified file 'bin/addons/register_accounting/account_bank_statement.py'
975=== modified file 'bin/addons/register_accounting/account_invoice_view.xml'
976=== modified file 'bin/addons/register_accounting/account_view.xml'
977=== modified file 'bin/addons/sync_common/common.py'
978--- bin/addons/sync_common/common.py 2020-01-30 10:35:55 +0000
979+++ bin/addons/sync_common/common.py 2020-04-30 18:52:36 +0000
980@@ -92,6 +92,7 @@
981 'product.justification.code',
982 'product.list',
983 'product.list.line',
984+ 'product.merged',
985 'product.nomenclature',
986 'product.pricelist',
987 'product.pricelist.type',
988
989=== modified file 'bin/addons/sync_so/so_po_common.py'
990--- bin/addons/sync_so/so_po_common.py 2018-11-09 16:10:16 +0000
991+++ bin/addons/sync_so/so_po_common.py 2020-04-30 18:52:36 +0000
992@@ -359,6 +359,12 @@
993 header_result['currency_id'] = currency_id
994 return header_result
995
996+ def check_merge(self, cr, uid, prod_id):
997+ merged = self.pool.get('product.product').search(cr, uid, [('replace_product_id', '=', prod_id), ('active', 'in', ['t', 'f'])])
998+ if merged:
999+ return merged[0]
1000+ return prod_id
1001+
1002 def get_product_id(self, cr, uid, data, default_code=False, context=None):
1003 # us-1586: use msfid to search product in intersection flow, else use sdref
1004 # US-3282: intermission / intersection: last try on default_code
1005@@ -376,11 +382,11 @@
1006 # msfid has no uniq constraint if only 1 active product: ok, elif more than 1 product found raise
1007 prod_ids = prod_obj.search(cr, uid, [('msfid', '=', msfid), ('active', 'in', ['t', 'f'])], limit=2, order='NO_ORDER', context=context)
1008 if len(prod_ids) == 1:
1009- return prod_ids[0]
1010+ return self.check_merge(cr, uid, prod_ids[0])
1011 elif len(prod_ids) > 1:
1012 prod_ids = prod_obj.search(cr, uid, [('msfid', '=', msfid), ('active', '=', 't')], limit=2, order='NO_ORDER', context=context)
1013 if len(prod_ids) == 1:
1014- return prod_ids[0]
1015+ return self.check_merge(cr, uid, prod_ids[0])
1016 raise Exception("Duplicate product for msfid %s" % msfid)
1017
1018 if hasattr(data, 'id') and data.id:
1019@@ -391,12 +397,12 @@
1020 if pid:
1021 prod_id = prod_obj.find_sd_ref(cr, uid, xmlid_to_sdref(pid), context=context)
1022 if prod_id:
1023- return prod_id
1024+ return self.check_merge(cr, uid, prod_id)
1025
1026 if default_code:
1027 prod_ids = prod_obj.search(cr, uid, [('default_code', '=', default_code), ('active', 'in', ['t', 'f'])], limit=1, order='NO_ORDER', context=context)
1028 if prod_ids:
1029- return prod_ids[0]
1030+ return self.check_merge(cr, uid, prod_ids[0])
1031
1032 return False
1033
1034
1035=== modified file 'bin/sql_db.py'
1036--- bin/sql_db.py 2018-02-22 14:23:17 +0000
1037+++ bin/sql_db.py 2020-04-30 18:52:36 +0000
1038@@ -173,7 +173,7 @@
1039 if osv_pool:
1040 for key in osv_pool._sql_error.keys():
1041 if key in ie[0]:
1042- self.__logger.warn("Normal Constraint Error: %s : %s", self._obj.query or query, ie[0])
1043+ self.__logger.warn("Normal Constraint Error: %s : %s", self._obj.query or query, tools.misc.ustr(ie[0]))
1044 #US-88: if error occurred for account analytic then just clear the cache
1045 if 'account_analytic_account_parent_id_fkey' in ie[0]:
1046 cache.clean_caches_for_db(self.dbname)
1047@@ -306,6 +306,20 @@
1048 return self.rowcount
1049
1050 @check
1051+ def get_referenced(self, table, column='id'):
1052+ self.execute("""
1053+ SELECT tc.table_name, kcu.column_name, ref.delete_rule
1054+ FROM information_schema.table_constraints AS tc
1055+ JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name
1056+ JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name
1057+ JOIN information_schema.referential_constraints AS ref ON ref.constraint_name = tc.constraint_name
1058+ WHERE
1059+ tc.constraint_type = 'FOREIGN KEY' AND
1060+ ccu.table_name=%s AND
1061+ ccu.column_name=%s
1062+ """, (table, column))
1063+ return self.fetchall()
1064+ @check
1065 def drop_constraint_if_exists(self, table, constraint):
1066 self.execute("SELECT conname FROM pg_constraint WHERE conname = %s", (constraint, ))
1067 if self.fetchone():

Subscribers

People subscribed via source and target branches