Merge lp:~jfb-tempo-consulting/unifield-server/US-7353-no7158 into lp:unifield-server
- US-7353-no7158
- Merge into trunk
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
UniField Reviewer Team | Pending | ||
Review via email: mp+382634@code.launchpad.net |
Commit message
Description of the change
- 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
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<0;blue:virtual_available>=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(): |