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

Proposed by jftempo
Status: Merged
Merged at revision: 6286
Proposed branch: lp:~jfb-tempo-consulting/unifield-server/US-12719
Merge into: lp:unifield-server
Diff against target: 1905 lines (+1362/-90) (has conflicts)
11 files modified
bin/addons/msf_audittrail/data/audittrail_data_products.yml (+1/-1)
bin/addons/msf_profile/data/patches.xml (+8/-0)
bin/addons/msf_profile/data/ud_default_oc_value.csv (+177/-0)
bin/addons/msf_profile/msf_profile.py (+74/-0)
bin/addons/msf_sync_data_server/data/sync_server.sync_rule.csv (+1/-1)
bin/addons/product_attributes/product_attributes.py (+92/-17)
bin/addons/product_attributes/product_attributes_data.xml (+20/-0)
bin/addons/product_attributes/product_attributes_view.xml (+17/-1)
bin/addons/product_attributes/unidata_sync.py (+714/-69)
bin/addons/product_attributes/unidata_sync.xml (+159/-1)
tools/UD/create_sql.py (+99/-0)
Text conflict in bin/addons/msf_profile/data/patches.xml
To merge this branch: bzr merge lp:~jfb-tempo-consulting/unifield-server/US-12719
Reviewer Review Type Date Requested Status
UniField Reviewer Team Pending
Review via email: mp+465653@code.launchpad.net
To post a comment you must log in.
6272. By jftempo

[FIX] nomenclature label / don't touch product accounts / dangerous_good added / single_use 'Not Applicable' to no_know / thermosensitive: empty to No

6273. By jftempo

[MERGE] trunk

6274. By jftempo

UD Pull: deactivate cron

6275. By jftempo

Remove fit_value, form_value, function_value from pull

6276. By jftempo

dangeours_goods: typo

6277. By jftempo

Single use: NotĀ applicable mapped to No

6278. By jftempo

add publishonweb=False

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bin/addons/msf_audittrail/data/audittrail_data_products.yml'
2--- bin/addons/msf_audittrail/data/audittrail_data_products.yml 2024-04-12 14:41:30 +0000
3+++ bin/addons/msf_audittrail/data/audittrail_data_products.yml 2024-05-13 13:47:13 +0000
4@@ -6,7 +6,7 @@
5 object_ids = self.pool.get('ir.model').search(cr, uid, [('model', '=', 'product.product')], context=context)
6 rule_id = self.search(cr, uid, [('name', '=', name)], context=context)
7 if object_ids:
8- 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', 'oc_subscription', 'state_ud', 'un_code', 'msfid', 'oc_validation', 'oc_validation_date', 'oc_devalidation_date', 'oc_devalidation_reason', 'oc_comments', 'oc_project_restrictions', 'oc_country_restrictions']
9+ 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', 'oc_subscription', 'state_ud', 'un_code', 'hs_code', 'msfid', 'oc_validation', 'oc_validation_date', 'oc_devalidation_date', 'oc_devalidation_reason', 'oc_comments', 'oc_project_restrictions', 'oc_country_restrictions', 'closed_article', 'cold_chain', 'manufacturer_ref', 'manufacturer_txt', 'xmlid_code', 'product_catalog_path', 'short_shelf_life', 'single_use', 'sterilized']
10 fields_ids = self.pool.get('ir.model.fields').search(cr, uid, [('model', '=' ,'product.product'), ('name', 'in', fields)], context=context)
11 vals = {'name': name,
12 'object_id': object_ids[0],
13
14=== modified file 'bin/addons/msf_profile/data/patches.xml'
15--- bin/addons/msf_profile/data/patches.xml 2024-05-06 12:42:13 +0000
16+++ bin/addons/msf_profile/data/patches.xml 2024-05-13 13:47:13 +0000
17@@ -1044,6 +1044,7 @@
18 <field name="method">us_12294_force_email_popup</field>
19 </record>
20
21+
22 <!-- UF33.0 -->
23 <record id="us_12074_gdpr_remove_personal_data_from_track_changess" model="patch.scripts">
24 <field name="method">us_12074_gdpr_remove_personal_data_from_track_changes</field>
25@@ -1053,10 +1054,17 @@
26 <field name="method">us_11135_set_pol_confirmation_date</field>
27 </record>
28
29+<<<<<<< TREE
30 <record id="us_12350_is_default_update" model="patch.scripts">
31 <field name="method">us_12350_is_default_update</field>
32 </record>
33
34+=======
35+ <record id="us_12826_unidata_pull_info" model="patch.scripts">
36+ <field name="method">us_12826_unidata_pull_info</field>
37+ </record>
38+
39+>>>>>>> MERGE-SOURCE
40
41 </data>
42 </openerp>
43
44=== added file 'bin/addons/msf_profile/data/ud_default_oc_value.csv'
45--- bin/addons/msf_profile/data/ud_default_oc_value.csv 1970-01-01 00:00:00 +0000
46+++ bin/addons/msf_profile/data/ud_default_oc_value.csv 2024-05-13 13:47:13 +0000
47@@ -0,0 +1,177 @@
48+Identifier,MSF Entity,Attribute,OC Rules,Default Value
49+96,OCA,perishable,[OCA] D - Drugs | ,Yes
50+98,OCA,perishable,[OCA] K - Kits | MED - Medical kits,Yes
51+99,OCA,perishable,[OCA] N - Nutrition | FOO - Food commodities,Yes
52+100,OCA,perishable,[OCA] N - Nutrition | FOS - Specialized food,Yes
53+101,OCA,perishable,"[OCA] S - Renewable medical supplies | CTD - Catheters, tubes and drains",Yes
54+102,OCA,perishable,[OCA] S - Renewable medical supplies | INS - Injection supplies,Yes
55+103,OCA,perishable,[OCA] S - Renewable medical supplies | LAS - Laboratory reagents,Yes
56+104,OCA,perishable,[OCA] S - Renewable medical supplies | SDT - Stand-alone diagnostic tests,Yes
57+105,OCA,perishable,[OCA] S - Renewable medical supplies | SUT - Sutures,Yes
58+106,OCA,batch_management,[OCA] D - Drugs | ,Yes
59+107,OCA,batch_management,[OCA] K - Kits | MED - Medical kits,Yes
60+108,OCA,batch_management,[OCA] N - Nutrition | FOO - Food commodities,Yes
61+109,OCA,batch_management,[OCA] N - Nutrition | FOS - Specialized food,Yes
62+110,OCA,batch_management,"[OCA] S - Renewable medical supplies | CTD - Catheters, tubes and drains",Yes
63+111,OCA,batch_management,[OCA] S - Renewable medical supplies | INS - Injection supplies,Yes
64+112,OCA,batch_management,[OCA] S - Renewable medical supplies | LAS - Laboratory reagents,Yes
65+113,OCA,batch_management,[OCA] S - Renewable medical supplies | SDT - Stand-alone diagnostic tests,Yes
66+114,OCA,batch_management,[OCA] S - Renewable medical supplies | SUT - Sutures,Yes
67+115,OCA,type,[OCA] X - Services | ,Service with Reception
68+116,OCA,subtype,[OCA] K - Kits | ,Kit/Module
69+117,OCA,procure_method,[OCA] X - Services | ,Make to Order
70+196,OCA,procure_method,[OCA] L - Library | ,Make to Order
71+197,OCA,perishable,[OCA] S - Renewable medical supplies | AST - Antibiotic Susceptibility Testing Items,Yes
72+198,OCA,perishable,[OCA] S - Renewable medical supplies | BCM - Bacteriological culture media,Yes
73+199,OCA,perishable,[OCA] S - Renewable medical supplies | BID - Bacterial identification system,Yes
74+200,OCA,perishable,[OCA] S - Renewable medical supplies | BQC - Bacterial strains Quality Control,Yes
75+201,OCA,perishable,[OCA] S - Renewable medical supplies | DIS - Disinfectants,Yes
76+202,OCA,batch_management,[OCA] S - Renewable medical supplies | AST - Antibiotic Susceptibility Testing Items,Yes
77+203,OCA,batch_management,[OCA] S - Renewable medical supplies | BCM - Bacteriological culture media,Yes
78+204,OCA,batch_management,[OCA] S - Renewable medical supplies | BID - Bacterial identification system,Yes
79+205,OCA,batch_management,[OCA] S - Renewable medical supplies | BQC - Bacterial strains Quality Control,Yes
80+206,OCA,batch_management,[OCA] S - Renewable medical supplies | DIS - Disinfectants,Yes
81+6,OCB,perishable,[OCB] D - Drugs | ,Yes
82+7,OCB,batch_management,[OCB] D - Drugs | ,Yes
83+25,OCB,subtype,[OCB] K - Kits | ,Kit/Module
84+26,OCB,perishable,[OCB] N - Nutrition | ,Yes
85+27,OCB,batch_management,[OCB] N - Nutrition | ,Yes
86+31,OCB,perishable,"[OCB] S - Renewable medical supplies | CTD - Catheters, tubes and drains",Yes
87+32,OCB,batch_management,"[OCB] S - Renewable medical supplies | CTD - Catheters, tubes and drains",Yes
88+51,OCB,perishable,[OCB] S - Renewable medical supplies | INS - Injection supplies,Yes
89+52,OCB,batch_management,[OCB] S - Renewable medical supplies | INS - Injection supplies,Yes
90+56,OCB,perishable,[OCB] S - Renewable medical supplies | LAS - Laboratory reagents,Yes
91+57,OCB,batch_management,[OCB] S - Renewable medical supplies | LAS - Laboratory reagents,Yes
92+66,OCB,perishable,[OCB] S - Renewable medical supplies | MSU - Small medical supplies,Yes
93+67,OCB,batch_management,[OCB] S - Renewable medical supplies | MSU - Small medical supplies,Yes
94+71,OCB,perishable,[OCB] S - Renewable medical supplies | SUT - Sutures,Yes
95+72,OCB,batch_management,[OCB] S - Renewable medical supplies | SUT - Sutures,Yes
96+93,OCB,procure_method,[OCB] X - Services | ,Make to Order
97+94,OCB,type,[OCB] X - Services | ,Service with Reception
98+95,OCB,subtype,[OCB] X - Services | ,
99+136,OCB,batch_management,[OCB] K - Kits | MED - Medical kits,Yes
100+137,OCB,batch_management,[OCB] K - Kits | SUD - Dental surgical instruments sets,Yes
101+138,OCB,batch_management,[OCB] K - Kits | SUI - Internal fixation instruments set,Yes
102+139,OCB,batch_management,[OCB] K - Kits | SUO - Ophthalmic instruments boxes,Yes
103+140,OCB,batch_management,[OCB] K - Kits | SUR - Surgical instruments sets,Yes
104+141,OCB,perishable,[OCB] K - Kits | MED - Medical kits,Yes
105+142,OCB,perishable,[OCB] K - Kits | SUD - Dental surgical instruments sets,Yes
106+143,OCB,perishable,[OCB] K - Kits | SUI - Internal fixation instruments set,Yes
107+144,OCB,perishable,[OCB] K - Kits | SUO - Ophthalmic instruments boxes,Yes
108+145,OCB,perishable,[OCB] K - Kits | SUR - Surgical instruments sets,Yes
109+146,OCB,perishable,[OCB] S - Renewable medical supplies | AST - Antibiotic Susceptibility Testing Items,Yes
110+147,OCB,perishable,[OCB] S - Renewable medical supplies | BCM - Bacteriological culture media,Yes
111+148,OCB,perishable,[OCB] S - Renewable medical supplies | BID - Bacterial identification system,Yes
112+149,OCB,perishable,[OCB] S - Renewable medical supplies | BQC - Bacterial strains Quality Control,Yes
113+154,OCB,perishable,[OCB] S - Renewable medical supplies | SCO - Surgical consumables,Yes
114+155,OCB,perishable,[OCB] S - Renewable medical supplies | SDT - Stand-alone diagnostic tests,Yes
115+156,OCB,perishable,[OCB] S - Renewable medical supplies | SUR - Surgical instruments for single use,Yes
116+158,OCB,perishable,"[OCB] S - Renewable medical supplies | TSS - Transport, storage and sampling systems of biological specim",Yes
117+159,OCB,perishable,[OCB] S - Renewable medical supplies | DIS - Disinfectants,Yes
118+160,OCB,batch_management,[OCB] S - Renewable medical supplies | AST - Antibiotic Susceptibility Testing Items,Yes
119+161,OCB,batch_management,[OCB] S - Renewable medical supplies | BCM - Bacteriological culture media,Yes
120+162,OCB,batch_management,[OCB] S - Renewable medical supplies | BID - Bacterial identification system,Yes
121+163,OCB,batch_management,[OCB] S - Renewable medical supplies | BQC - Bacterial strains Quality Control,Yes
122+165,OCB,batch_management,[OCB] S - Renewable medical supplies | SCO - Surgical consumables,Yes
123+166,OCB,batch_management,[OCB] S - Renewable medical supplies | SDT - Stand-alone diagnostic tests,Yes
124+167,OCB,batch_management,[OCB] S - Renewable medical supplies | SUR - Surgical instruments for single use,Yes
125+168,OCB,batch_management,"[OCB] S - Renewable medical supplies | TSS - Transport, storage and sampling systems of biological specim",Yes
126+169,OCB,batch_management,[OCB] S - Renewable medical supplies | DIS - Disinfectants,Yes
127+263,OCB,procure_method,[OCB] L - Library | ,Make to Order
128+118,OCG,batch_management,[OCG] D - Drugs | ,Yes
129+119,OCG,batch_management,[OCG] K - Kits | MED - Medical kits,Yes
130+120,OCG,batch_management,[OCG] N - Nutrition | FOO - Food commodities,Yes
131+121,OCG,batch_management,[OCG] N - Nutrition | ,Yes
132+122,OCG,batch_management,"[OCG] S - Renewable medical supplies | CTD - Catheters, tubes and drains",Yes
133+123,OCG,batch_management,[OCG] S - Renewable medical supplies | INS - Injection supplies,Yes
134+124,OCG,batch_management,[OCG] S - Renewable medical supplies | LAS - Laboratory reagents,Yes
135+125,OCG,batch_management,[OCG] S - Renewable medical supplies | MSU - Small medical supplies,Yes
136+126,OCG,batch_management,[OCG] S - Renewable medical supplies | SUT - Sutures,Yes
137+127,OCG,perishable,[OCG] D - Drugs | ,Yes
138+128,OCG,perishable,[OCG] K - Kits | MED - Medical kits,Yes
139+129,OCG,perishable,[OCG] N - Nutrition | ,Yes
140+131,OCG,perishable,"[OCG] S - Renewable medical supplies | CTD - Catheters, tubes and drains",Yes
141+132,OCG,perishable,[OCG] S - Renewable medical supplies | INS - Injection supplies,Yes
142+133,OCG,perishable,[OCG] S - Renewable medical supplies | LAS - Laboratory reagents,Yes
143+134,OCG,perishable,[OCG] S - Renewable medical supplies | MSU - Small medical supplies,Yes
144+135,OCG,perishable,[OCG] S - Renewable medical supplies | SUT - Sutures,Yes
145+170,OCG,batch_management,[OCG] S - Renewable medical supplies | DRE - Dressings,Yes
146+171,OCG,batch_management,[OCG] S - Renewable medical supplies | SDT - Stand-alone diagnostic tests,Yes
147+172,OCG,perishable,[OCG] S - Renewable medical supplies | DRE - Dressings,Yes
148+173,OCG,perishable,[OCG] S - Renewable medical supplies | SDT - Stand-alone diagnostic tests,Yes
149+174,OCG,procure_method,[OCG] L - Library | ,Make to Order
150+175,OCG,procure_method,[OCG] X - Services | ,Make to Order
151+176,OCG,subtype,[OCG] K - Kits | ,Kit/Module
152+177,OCG,type,[OCG] X - Services | ,Service with Reception
153+178,OCG,batch_management,[OCG] S - Renewable medical supplies | AST - Antibiotic Susceptibility Testing Items,Yes
154+179,OCG,batch_management,[OCG] S - Renewable medical supplies | BCM - Bacteriological culture media,Yes
155+180,OCG,batch_management,[OCG] S - Renewable medical supplies | BID - Bacterial identification system,Yes
156+181,OCG,batch_management,[OCG] S - Renewable medical supplies | BQC - Bacterial strains Quality Control,Yes
157+183,OCG,batch_management,[OCG] S - Renewable medical supplies | SCO - Surgical consumables,Yes
158+184,OCG,batch_management,[OCG] S - Renewable medical supplies | SUR - Surgical instruments for single use,Yes
159+185,OCG,batch_management,"[OCG] S - Renewable medical supplies | TSS - Transport, storage and sampling systems of biological specim",Yes
160+186,OCG,batch_management,[OCG] S - Renewable medical supplies | DIS - Disinfectants,Yes
161+187,OCG,perishable,[OCG] S - Renewable medical supplies | AST - Antibiotic Susceptibility Testing Items,Yes
162+188,OCG,perishable,[OCG] S - Renewable medical supplies | BCM - Bacteriological culture media,Yes
163+189,OCG,perishable,[OCG] S - Renewable medical supplies | BID - Bacterial identification system,Yes
164+190,OCG,perishable,[OCG] S - Renewable medical supplies | BQC - Bacterial strains Quality Control,Yes
165+191,OCG,perishable,[OCG] S - Renewable medical supplies | SCO - Surgical consumables,Yes
166+192,OCG,perishable,[OCG] S - Renewable medical supplies | SUR - Surgical instruments for single use,Yes
167+193,OCG,perishable,"[OCG] S - Renewable medical supplies | TSS - Transport, storage and sampling systems of biological specim",Yes
168+194,OCG,perishable,[OCG] S - Renewable medical supplies | DIS - Disinfectants,Yes
169+207,OCP,type,[OCP] X - Services | ,Service with Reception
170+208,OCP,subtype,[OCP] X - Services | ,
171+209,OCP,subtype,[OCP] K - Kits | ,Kit/Module
172+210,OCP,procure_method,[OCP] X - Services | ,Make to Order
173+211,OCP,perishable,[OCP] D - Drugs | ,Yes
174+212,OCP,perishable,[OCP] E - Medical equipment | ANE - Anaesthesia equipment,Yes
175+213,OCP,perishable,[OCP] E - Medical equipment | DIM - Diagnostic Imaging equipment,Yes
176+214,OCP,perishable,[OCP] E - Medical equipment | LAB - Laboratory equipment,Yes
177+215,OCP,perishable,[OCP] E - Medical equipment | LAE - Electrical laboratory equipment,Yes
178+216,OCP,perishable,[OCP] E - Medical equipment | SUR - Surgical instruments,Yes
179+217,OCP,perishable,[OCP] K - Kits | MED - Medical kits,Yes
180+218,OCP,perishable,[OCP] N - Nutrition | FOO - Food commodities,Yes
181+219,OCP,perishable,[OCP] N - Nutrition | FOS - Specialized food,Yes
182+220,OCP,perishable,[OCP] S - Renewable medical supplies | AST - Antibiotic Susceptibility Testing Items,Yes
183+221,OCP,perishable,[OCP] S - Renewable medical supplies | BCM - Bacteriological culture media,Yes
184+222,OCP,perishable,[OCP] S - Renewable medical supplies | BID - Bacterial identification system,Yes
185+223,OCP,perishable,[OCP] S - Renewable medical supplies | BQC - Bacterial strains Quality Control,Yes
186+224,OCP,perishable,"[OCP] S - Renewable medical supplies | CTD - Catheters, tubes and drains",Yes
187+225,OCP,perishable,[OCP] S - Renewable medical supplies | DIM - Diagnostic imaging supplies,Yes
188+226,OCP,perishable,[OCP] S - Renewable medical supplies | DIS - Disinfectants,Yes
189+227,OCP,perishable,[OCP] S - Renewable medical supplies | DRE - Dressings,Yes
190+228,OCP,perishable,[OCP] S - Renewable medical supplies | INS - Injection supplies,Yes
191+229,OCP,perishable,[OCP] S - Renewable medical supplies | LAS - Laboratory reagents,Yes
192+230,OCP,perishable,[OCP] S - Renewable medical supplies | MSU - Small medical supplies,Yes
193+231,OCP,perishable,[OCP] S - Renewable medical supplies | PPE - Personal protective equipment for medical activities,Yes
194+232,OCP,perishable,[OCP] S - Renewable medical supplies | SCO - Surgical consumables,Yes
195+233,OCP,perishable,[OCP] S - Renewable medical supplies | SDT - Stand-alone diagnostic tests,Yes
196+234,OCP,perishable,[OCP] S - Renewable medical supplies | SUR - Surgical instruments for single use,Yes
197+235,OCP,perishable,[OCP] S - Renewable medical supplies | SUT - Sutures,Yes
198+236,OCP,perishable,"[OCP] S - Renewable medical supplies | TSS - Transport, storage and sampling systems of biological specim",Yes
199+237,OCP,batch_management,[OCP] D - Drugs | ,Yes
200+238,OCP,batch_management,[OCP] E - Medical equipment | ANE - Anaesthesia equipment,Yes
201+239,OCP,batch_management,[OCP] E - Medical equipment | DIM - Diagnostic Imaging equipment,Yes
202+240,OCP,batch_management,[OCP] E - Medical equipment | LAB - Laboratory equipment,Yes
203+241,OCP,batch_management,[OCP] E - Medical equipment | LAE - Electrical laboratory equipment,Yes
204+242,OCP,batch_management,[OCP] E - Medical equipment | SUR - Surgical instruments,Yes
205+243,OCP,batch_management,[OCP] K - Kits | MED - Medical kits,Yes
206+244,OCP,batch_management,[OCP] N - Nutrition | FOO - Food commodities,Yes
207+245,OCP,batch_management,[OCP] N - Nutrition | FOS - Specialized food,Yes
208+246,OCP,batch_management,[OCP] S - Renewable medical supplies | AST - Antibiotic Susceptibility Testing Items,Yes
209+247,OCP,batch_management,[OCP] S - Renewable medical supplies | BCM - Bacteriological culture media,Yes
210+248,OCP,batch_management,[OCP] S - Renewable medical supplies | BID - Bacterial identification system,Yes
211+249,OCP,batch_management,[OCP] S - Renewable medical supplies | BQC - Bacterial strains Quality Control,Yes
212+250,OCP,batch_management,"[OCP] S - Renewable medical supplies | CTD - Catheters, tubes and drains",Yes
213+251,OCP,batch_management,[OCP] S - Renewable medical supplies | DIM - Diagnostic imaging supplies,Yes
214+252,OCP,batch_management,[OCP] S - Renewable medical supplies | DIS - Disinfectants,Yes
215+253,OCP,batch_management,[OCP] S - Renewable medical supplies | DRE - Dressings,Yes
216+254,OCP,batch_management,[OCP] S - Renewable medical supplies | INS - Injection supplies,Yes
217+255,OCP,batch_management,[OCP] S - Renewable medical supplies | LAS - Laboratory reagents,Yes
218+256,OCP,batch_management,[OCP] S - Renewable medical supplies | MSU - Small medical supplies,Yes
219+257,OCP,batch_management,[OCP] S - Renewable medical supplies | PPE - Personal protective equipment for medical activities,Yes
220+258,OCP,batch_management,[OCP] S - Renewable medical supplies | SCO - Surgical consumables,Yes
221+259,OCP,batch_management,[OCP] S - Renewable medical supplies | SDT - Stand-alone diagnostic tests,Yes
222+260,OCP,batch_management,[OCP] S - Renewable medical supplies | SUR - Surgical instruments for single use,Yes
223+261,OCP,batch_management,[OCP] S - Renewable medical supplies | SUT - Sutures,Yes
224+262,OCP,batch_management,"[OCP] S - Renewable medical supplies | TSS - Transport, storage and sampling systems of biological specim",Yes
225
226=== modified file 'bin/addons/msf_profile/msf_profile.py'
227--- bin/addons/msf_profile/msf_profile.py 2024-05-06 12:42:13 +0000
228+++ bin/addons/msf_profile/msf_profile.py 2024-05-13 13:47:13 +0000
229@@ -58,6 +58,80 @@
230 }
231
232 # UF33.0
233+ def us_12826_unidata_pull_info(self, cr, uid, *a, **b):
234+ entity_obj = self.pool.get('sync.client.entity')
235+ instance = self.pool.get('res.users').browse(cr, uid, uid).company_id.instance_id
236+ if entity_obj and instance and instance.level == 'section':
237+ if instance.instance in ('OCP_HQ', 'OCBHQ', 'HQ_OCA', 'OCG_HQ'):
238+ ent = entity_obj.get_entity(cr, uid)
239+ oc = ent.oc.upper()
240+ values_mapping = {
241+ 'Yes': 't',
242+ 'Kit/Module': 'kit',
243+ 'Make to Order': 'make_to_order',
244+ 'Service with Reception': 'service_recep',
245+ '': '',
246+ }
247+ csv_file_name = os.path.join(os.path.abspath(tools.config['root_path']), 'addons/msf_profile/data/ud_default_oc_value.csv')
248+ line_number = 0
249+ with open(csv_file_name, 'r', newline='') as f:
250+ c = csv.reader(f, quotechar='"', delimiter=',')
251+ for line in c:
252+ line_number += 1
253+ if not line or line[1] != oc:
254+ continue
255+ nomen = line[3][5:].strip()
256+ all_nom = nomen.split('|')
257+ level = 1
258+ parent_id = False
259+
260+ if line[4] not in values_mapping:
261+ self._logger.warn('Line number %s, value %s not found' % (line_number, line[4]))
262+ return False
263+ for nom in all_nom:
264+ nom = nom.strip()[0:63]
265+ if nom:
266+ cond = ''
267+ params = [nom.strip(), level]
268+ if parent_id:
269+ cond = ' and n.parent_id in %s'
270+ params.append(tuple(parent_id))
271+ params[0] = '%%%s' % nom
272+
273+ cr.execute("""
274+ select
275+ n.id
276+ from
277+ product_nomenclature n
278+ left join ir_translation t on t.lang='en_MF' and t.name='product.nomenclature,name' and t.res_id = n.id
279+ where
280+ coalesce(t.value, n.name) like %s and
281+ n.level=%s
282+ """+cond, tuple(params)) # not_a_user_entry
283+ if not cr.rowcount:
284+ self._logger.warn('Line number %s, nomen %s not found' % (line_number, nom))
285+ return False
286+ parent_id = [x[0] for x in cr.fetchall()]
287+ level += 1
288+ for n_id in parent_id:
289+ query = "insert into unidata_default_product_value (field, value, nomenclature, create_date) values (%s, %s, %s, NOW());"
290+ values = (line[2], values_mapping[line[4]], n_id)
291+ cr.execute(query, values)
292+
293+ cr.execute("update product_cold_chain set ud_code=code")
294+ cr.execute("update product_cold_chain set ud_code='CT3+' where id in (select res_id from ir_model_data where name='product_attributes_cold_20')")
295+
296+ cr.execute("update unidata_sync set is_active='f'")
297+ cr.execute("update ir_cron set active='f' where model='unidata.sync'")
298+ # set next UD sync as full sync
299+ param_obj = self.pool.get('ir.config_parameter')
300+ param_obj.set_param(cr, 1, 'LAST_UD_DATE_SYNC', '')
301+ param_obj.set_param(cr, 1, 'LAST_MSFID_SYNC','')
302+
303+
304+
305+ return True
306+
307 def us_12074_gdpr_remove_personal_data_from_track_changes(self, cr, uid, *a, **b):
308 '''
309 GDPR - Remove from staff track changes the fields removed from US-7791
310
311=== modified file 'bin/addons/msf_sync_data_server/data/sync_server.sync_rule.csv'
312--- bin/addons/msf_sync_data_server/data/sync_server.sync_rule.csv 2023-11-17 14:06:38 +0000
313+++ bin/addons/msf_sync_data_server/data/sync_server.sync_rule.csv 2024-05-13 13:47:13 +0000
314@@ -132,7 +132,7 @@
315 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
316 msf_sync_data_server.country_restrictions,TRUE,TRUE,FALSE,TRUE,bidirectional,Down,[],['name'],MISSION,res.country.restriction,,Country restrictions,Valid,,570
317 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
318-msf_sync_data_server.oc_product_creator_itc_esc_hq,TRUE,TRUE,FALSE,TRUE,bidirectional,Down,"['|','|','|',('international_status','=','UniData'),('international_status','=','ITC'),('international_status','=','ESC'),('international_status','=','HQ'), ('active', 'in', ['t','f'])]","['alert_time', 'batch_management', 'categ_id/id', 'closed_article', 'manufacturer_txt', 'manufacturer_ref', 'code', 'cold_chain/id', 'composed_kit', 'xmlid_code', 'cost_method', 'country_restriction/id', 'dangerous_goods', 'default_code', 'description', 'description2', 'description_purchase', 'description_sale', 'gmdn_code', 'gmdn_description', 'heat_sensitive_item/id', 'international_status/id', 'justification_code_id/id', 'library', 'life_time', 'list_ids/id','med_device_class', 'name', 'name_template', 'controlled_substance', 'nomen_manda_0/id', 'nomen_manda_1/id', 'nomen_manda_2/id', 'nomen_manda_3/id', 'options_ids/id', 'perishable', 'procure_delay', 'procure_method', 'produce_delay', 'product_catalog_page', 'product_catalog_path', 'property_account_expense/id', 'property_account_income/id', 'property_stock_account_input/id', 'property_stock_account_output/id', 'restricted_country', 'short_shelf_life', 'single_use', 'sterilized', 'standard_price', 'sublist', 'subtype', 'asset_type_id', 'supply_method', 'type', 'un_code', 'uom_id/id', 'uom_po_id/id','use_time', 'valuation', 'weight', 'weight_net', 'state', 'state_ud', 'old_code', 'new_code', 'function_value', 'form_value', 'fit_value', 'standard_ok', 'local_from_hq', 'transport_ok','volume', 'soq_quantity', 'soq_weight', 'soq_volume', 'msfid', 'oc_subscription', 'donation_expense_account/id', 'oc_validation', 'oc_validation_date', 'oc_devalidation_date', 'oc_devalidation_reason', 'oc_comments', 'oc_project_restrictions/id', 'oc_country_restrictions/id']",OC,product.product,,"OC Product (Creator = ITC, ESC, UniData or HQ)",Valid,,600
319+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', 'hs_code', 'uom_id/id', 'uom_po_id/id','use_time', 'valuation', 'weight', 'weight_net', 'state', 'state_ud', 'old_code', 'new_code', 'function_value', 'form_value', 'fit_value', 'standard_ok', 'local_from_hq', 'transport_ok','volume', 'soq_quantity', 'soq_weight', 'soq_volume', 'msfid', 'oc_subscription', 'donation_expense_account/id', 'oc_validation', 'oc_validation_date', 'oc_devalidation_date', 'oc_devalidation_reason', 'oc_comments', 'oc_project_restrictions/id', 'oc_country_restrictions/id']",OC,product.product,,"OC Product (Creator = ITC, ESC, UniData or HQ)",Valid,,600
320 msf_sync_data_server.mission_product_creator_local,TRUE,TRUE,FALSE,TRUE,bidirectional,Down,"[('international_status','=','Local'), ('active', 'in', ['t','f'])]","['alert_time', 'batch_management', 'categ_id/id', 'closed_article', 'manufacturer_txt', 'manufacturer_ref', 'code', 'xmlid_code','cold_chain/id', 'composed_kit', 'cost_method', 'country_restriction/id', 'dangerous_goods', 'default_code', 'description', 'description2', 'description_purchase', 'description_sale', 'gmdn_code', 'gmdn_description', 'heat_sensitive_item/id', 'international_status/id', 'justification_code_id/id', 'library', 'life_time', 'list_ids/id','med_device_class', 'name', 'name_template', 'controlled_substance', 'nomen_manda_0/id', 'nomen_manda_1/id', 'nomen_manda_2/id', 'nomen_manda_3/id', 'options_ids/id', 'perishable', 'procure_delay', 'procure_method', 'produce_delay', 'product_catalog_page', 'product_catalog_path', 'property_account_expense/id', 'property_account_income/id', 'property_stock_account_input/id', 'property_stock_account_output/id', 'restricted_country', 'short_shelf_life', 'single_use', 'sterilized', 'standard_price', 'sublist', 'subtype', 'asset_type_id', 'supply_method', 'type', 'un_code', 'uom_id/id', 'uom_po_id/id','use_time', 'valuation', 'weight', 'weight_net', 'state', 'old_code', 'new_code', 'function_value', 'form_value', 'fit_value', 'standard_ok','transport_ok','volume', 'soq_quantity', 'soq_weight','soq_volume']",MISSION,product.product,,Mission Product (Creator = local),Valid,,601
321 msf_sync_data_server.oc_product_creator_itc_esc_hq_active,TRUE,TRUE,FALSE,TRUE,bidirectional,Down,"['|','|','|',('international_status','=','UniData'),('international_status','=','ITC'),('international_status','=','ESC'),('international_status','=','HQ'), ('active', 'in', ['t','f']), ('active_change_date', '!=', False)]","['active', 'local_from_hq', 'local_activation_from_merge']",OC,product.product,,"OC Product Active (Creator = ITC, ESC, UniData or HQ)",Valid,,602
322 msf_sync_data_server.mission_product_creator_local_active,TRUE,TRUE,FALSE,TRUE,bidirectional,Down,"[('international_status','=','Local'), ('active', 'in', ['t','f']), ('active_change_date', '!=', False)]","['active']",MISSION,product.product,,Mission Product Active (Creator = local),Valid,,603
323
324=== modified file 'bin/addons/product_attributes/product_attributes.py'
325--- bin/addons/product_attributes/product_attributes.py 2024-05-02 09:35:15 +0000
326+++ bin/addons/product_attributes/product_attributes.py 2024-05-13 13:47:13 +0000
327@@ -186,6 +186,7 @@
328 _name = "product.cold_chain"
329 _columns = {
330 'code': fields.char('Code', size=256),
331+ 'ud_code': fields.char(string='Code', size=256),
332 'name': fields.char('Name', size=256, required=True, translate=1),
333 'cold_chain': fields.boolean('Cold Chain'),
334 'mapped_to': fields.many2one('product.cold_chain', string='Mapped to', readonly=1),
335@@ -668,6 +669,39 @@
336 dom += [ '&', ('batch_management', '=', False), ('perishable', '=', True)]
337 return dom
338
339+ def _search_incompatible_oc_default_values(self, cr, uid, obj, name, args, context=None):
340+ dom = []
341+ oc_def = self.pool.get('unidata.default_product_value')
342+ for arg in args:
343+ if arg[1] == '=':
344+ if not arg[2]:
345+ raise osv.except_osv(_('Warning'), _('This filter is not implemented'))
346+
347+ oc_def_ids = oc_def.search(cr, uid, [], context=context)
348+ elif arg[1] == 'in':
349+ if not isinstance(arg[2], list):
350+ raise osv.except_osv(_('Warning'), _('This filter is not implemented'))
351+ oc_def_ids = arg[2]
352+ else:
353+ raise osv.except_osv(_('Warning'), _('This filter is not implemented'))
354+
355+ temp_dom = []
356+ for oc_val in oc_def.browse(cr, uid, oc_def_ids, context=context):
357+ value = oc_val.value
358+ if value == 'f':
359+ value = False
360+ temp_dom.append(['&', ('nomen_manda_%d' % oc_val.nomenclature.level, '=', oc_val.nomenclature.id), (oc_val.field, '!=', value)])
361+
362+ ud_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'product_attributes', 'int_6')[1]
363+ if temp_dom:
364+ dom += temp_dom[0]
365+ for d in temp_dom[1:]:
366+ dom.insert(0, '|')
367+ dom += d
368+ dom = ['&', ('international_status', '=', ud_id)] + dom
369+
370+ return dom
371+
372 def _search_show_ud(self, cr, uid, obj, name, args, context=None):
373 dom = []
374 for arg in args:
375@@ -1082,7 +1116,8 @@
376 ),
377 'oc_subscription': fields.boolean(string='OC Subscription'),
378 # TODO: validation on 'un_code' field
379- 'un_code': fields.char('UN Code', size=7),
380+ 'un_code': fields.char('UN Code', size=32),
381+ 'hs_code': fields.char('HS Code', size=12, readonly=1),
382 'gmdn_code' : fields.char('GMDN Code', size=5),
383 'gmdn_description' : fields.char('GMDN Description', size=64),
384 'life_time': fields.integer('Product Life Time',
385@@ -1217,6 +1252,8 @@
386 'in_mml_instance': fields.function(tools.misc.get_fake, method=True, type='many2one', relation='msf.instance', string='MML Valid for instance', domain=[('state', '=', 'active'), ('level', '!=', 'section')]),
387 'mml_restricted_instance': fields.function(tools.misc.get_fake, method=True, type='many2one', relation='msf.instance', string='MML Restricted to instance', domain=[('state', '=', 'active'), ('level', '!=', 'section')]),
388 'in_msl_instance': fields.function(_get_valid_msl_instance, method=True, type='many2many', relation='unifield.instance', domain=[('uf_active', '=', True)], string='MSL Valid for instance'),
389+
390+ 'incompatible_oc_default_values': fields.function(tools.misc.get_fake, method=True, type='boolean', string='Incompatible OC default', fnct_search=_search_incompatible_oc_default_values),
391 }
392
393
394@@ -3468,22 +3505,21 @@
395 return True
396
397 def pull_ud(self, cr, uid, ids, context=None):
398- ud = unidata_sync.ud_sync(cr, uid, self.pool, logger=logging.getLogger('single-ud-sync'), max_retries=1, context=context)
399-
400- code_updated = []
401- update = False
402- for x in self.read(cr, uid, ids, ['msfid', 'default_code'], context=context):
403- if x['msfid']:
404- try:
405- ud.update_products(q_filter='msfIdentifier=%d'%x['msfid'], record_date=False)
406- code_updated.append(x['default_code'])
407- if not update:
408- update = x['id']
409- except requests.exceptions.HTTPError as e:
410- raise osv.except_osv(_('Error'), _('Unidata error: %s, did you configure the UniData sync ?') % e.response)
411- if code_updated:
412- self.log(cr, uid, update, _('%s updated from UniData') % ', '.join(code_updated))
413- return True
414+ for x in self.read(cr, uid, ids, ['msfid'] , context=context):
415+ wiz_id = self.pool.get('product.pull_single_ud').create(cr, uid, {'msfid': x['msfid'] or False}, context=context)
416+ return {
417+ 'type': 'ir.actions.act_window',
418+ 'res_model': 'product.pull_single_ud',
419+ 'res_id': wiz_id,
420+ 'view_type': 'form',
421+ 'view_mode': 'form',
422+ 'target': 'new',
423+ 'context': context,
424+ 'height': '190px',
425+ 'width': '420px',
426+ }
427+
428+
429
430 def open_mml_nonconform_report(self, cr, uid, ids, context=None):
431 instance_level = self.pool.get('res.company')._get_instance_level(cr, uid)
432@@ -3839,5 +3875,44 @@
433
434 product_ask_activate_wizard()
435
436+class product_pull_single_ud(osv.osv_memory):
437+ _name = 'product.pull_single_ud'
438+ rec_name = 'msfid'
439+ _columns = {
440+ 'msfid': fields.integer_null('MSFID'),
441+ }
442+
443+ def pull_product(self, cr, uid, ids, context=None):
444+ session_obj = self.pool.get('unidata.sync.log')
445+ act_obj = self.pool.get('ir.actions.act_window')
446+ for x in self.read(cr, uid, ids, ['msfid'], context=context):
447+ if x['msfid']:
448+ session_id = session_obj.create(cr, uid, {'manual_single': True, 'server': 'ud', 'start_date': fields.datetime.now(), 'state': 'running', 'sync_type': 'single', 'msfid_min': x['msfid']}, context=context)
449+ ud = unidata_sync.ud_sync(cr, uid, self.pool, logger=logging.getLogger('single-ud-sync'), max_retries=1, context=context)
450+ try:
451+ trash1, nb_prod, updated, total_nb_created, total_nb_errors = ud.update_products(q_filter='msfIdentifier=%d'%x['msfid'], record_date=False, session_id=session_id)
452+ except requests.exceptions.HTTPError as e:
453+ raise osv.except_osv(_('Error'), _('Unidata error: %s, did you configure the UniData sync ?') % e.response)
454+ except Exception:
455+ raise
456+ else:
457+ raise osv.except_osv(_('Error'), _('Error: msfid is required'))
458+
459+ session_obj.write(cr, uid, session_id, {'end_date': fields.datetime.now(), 'state': 'done', 'number_products_pulled': nb_prod, 'number_products_updated': updated, 'number_products_created': total_nb_created, 'number_products_errors': total_nb_errors}, context=context)
460+
461+ p_ids = self.pool.get('product.product').search(cr, uid, [('msfid', '=', x['msfid'])], context=context)
462+ if p_ids:
463+ if len(p_ids) == 1:
464+ view = act_obj.open_view_from_xmlid(cr, uid, 'product.product_normal_action', ['form', 'tree'], context=context)
465+ view['res_id'] = p_ids[0]
466+ else:
467+ view = act_obj.open_view_from_xmlid(cr, uid, 'product.product_normal_action', context=context)
468+ view['domain'] = [('id','in', p_ids)]
469+ else:
470+ view = act_obj.open_view_from_xmlid(cr, uid, 'product_attributes.unidata_sync_log_action', ['form', 'tree'], new_tab=True, context=context)
471+ view['res_id'] = session_id
472+ return view
473+
474+product_pull_single_ud()
475
476 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
477
478=== modified file 'bin/addons/product_attributes/product_attributes_data.xml'
479--- bin/addons/product_attributes/product_attributes_data.xml 2023-08-29 09:17:44 +0000
480+++ bin/addons/product_attributes/product_attributes_data.xml 2024-05-13 13:47:13 +0000
481@@ -133,36 +133,43 @@
482 <!-- new code -->
483 <record model="product.cold_chain" id="cold_14">
484 <field name="code">0208</field>
485+ <field name="ud_code">0208</field>
486 <field name="name">0208 - Cold Chain / Refrigerated 2-8Ā°C</field>
487 <field name="cold_chain" eval="True" />
488 </record>
489 <record model="product.cold_chain" id="cold_15">
490 <field name="code">1525</field>
491+ <field name="ud_code">1525</field>
492 <field name="name">1525 - Controlled Room Temperature 15-25Ā°C</field>
493 <field name="cold_chain" eval="False" />
494 </record>
495 <record model="product.cold_chain" id="cold_16">
496 <field name="code">CT25</field>
497+ <field name="ud_code">CT25</field>
498 <field name="name">CT25 - Controlled Temperature 2-25Ā°C</field>
499 <field name="cold_chain" eval="False" />
500 </record>
501 <record model="product.cold_chain" id="cold_17">
502 <field name="code">CT30</field>
503+ <field name="ud_code">CT30</field>
504 <field name="name">CT30 - Controlled Temperature 2-30Ā°C</field>
505 <field name="cold_chain" eval="False" />
506 </record>
507 <record model="product.cold_chain" id="cold_18">
508 <field name="code">F-20</field>
509+ <field name="ud_code">F-20</field>
510 <field name="name">F-20 - Frozen &lt;-20Ā°C</field>
511 <field name="cold_chain" eval="False" />
512 </record>
513 <record model="product.cold_chain" id="cold_19">
514 <field name="code">FSRT</field>
515+ <field name="ud_code">FSRT</field>
516 <field name="name">FSRT - Frozen for Storage, Refrigerated for Transport</field>
517 <field name="cold_chain" eval="False" />
518 </record>
519 <record model="product.cold_chain" id="cold_20">
520 <field name="code">CT30</field>
521+ <field name="ud_code">CT30</field>
522 <field name="name">CT30 - Controlled Temperature 2-30Ā°C</field>
523 <field name="cold_chain" eval="False" />
524 </record>
525@@ -170,78 +177,91 @@
526 <!-- old code -->
527 <record model="product.cold_chain" id="cold_1">
528 <field name="code">*</field>
529+ <field name="ud_code">*</field>
530 <field name="name">* - Keep Cool: used for a kit or article containing cold chain module or item(s)</field>
531 <field name="cold_chain" eval="True" />
532 <field name="mapped_to" ref="cold_14" />
533 </record>
534 <record model="product.cold_chain" id="cold_2">
535 <field name="code">*0</field>
536+ <field name="ud_code">*0</field>
537 <field name="name">*0 - Problem if any window blue</field>
538 <field name="cold_chain" eval="True" />
539 <field name="mapped_to" ref="cold_14" />
540 </record>
541 <record model="product.cold_chain" id="cold_3">
542 <field name="code">*0F</field>
543+ <field name="ud_code">*0F</field>
544 <field name="name">*0F - Problem if any window blue or Freeze-tag = ALARM</field>
545 <field name="cold_chain" eval="True" />
546 <field name="mapped_to" ref="cold_14" />
547 </record>
548 <record model="product.cold_chain" id="cold_4">
549 <field name="code">*A</field>
550+ <field name="ud_code">*A</field>
551 <field name="name">*A - Problem if A, B, C and/or D blue = ALARM</field>
552 <field name="cold_chain" eval="True" />
553 <field name="mapped_to" ref="cold_14" />
554 </record>
555 <record model="product.cold_chain" id="cold_5">
556 <field name="code">*AF</field>
557+ <field name="ud_code">*AF</field>
558 <field name="name">*AF - Problem if A, B, C and/or D blue or Freeze-tag = ALARM</field>
559 <field name="cold_chain" eval="True" />
560 <field name="mapped_to" ref="cold_14" />
561 </record>
562 <record model="product.cold_chain" id="cold_6">
563 <field name="code">*B</field>
564+ <field name="ud_code">*B</field>
565 <field name="name">*B - Problem if B, C and/or D blue = ALARM</field>
566 <field name="cold_chain" eval="True" />
567 <field name="mapped_to" ref="cold_14" />
568 </record>
569 <record model="product.cold_chain" id="cold_7">
570 <field name="code">*BF</field>
571+ <field name="ud_code">*BF</field>
572 <field name="name">*BF - Problem if B, C and/or D blue or Freeze-tag = ALARM</field>
573 <field name="cold_chain" eval="True" />
574 <field name="mapped_to" ref="cold_14" />
575 </record>
576 <record model="product.cold_chain" id="cold_8">
577 <field name="code">*C</field>
578+ <field name="ud_code">*C</field>
579 <field name="name">*C - Problem if C and D blue</field>
580 <field name="cold_chain" eval="True" />
581 <field name="mapped_to" ref="cold_14" />
582 </record>
583 <record model="product.cold_chain" id="cold_9">
584 <field name="code">*CF</field>
585+ <field name="ud_code">*CF</field>
586 <field name="name">*CF - Problem if C and/or D blue or Freeze-tag = ALARM</field>
587 <field name="cold_chain" eval="True" />
588 <field name="mapped_to" ref="cold_14" />
589 </record>
590 <record model="product.cold_chain" id="cold_10">
591 <field name="code">*D</field>
592+ <field name="ud_code">*D</field>
593 <field name="name">*D - Store and transport at -25Ā°C (store in deepfreezer, transport with dry-ice)</field>
594 <field name="cold_chain" eval="False" />
595 <field name="mapped_to" ref="cold_18" />
596 </record>
597 <record model="product.cold_chain" id="cold_11">
598 <field name="code">*F</field>
599+ <field name="ud_code">*F</field>
600 <field name="name">*F - Cannot be frozen: check FreezeWatch</field>
601 <field name="cold_chain" eval="False" />
602 <field name="mapped_to" ref="cold_17" />
603 </record>
604 <record model="product.cold_chain" id="cold_12">
605 <field name="code">*25</field>
606+ <field name="ud_code">*25</field>
607 <field name="name">*25 - Must be kept below 25Ā°C (but not necesseraly in cold chain)</field>
608 <field name="cold_chain" eval="False" />
609 <field name="mapped_to" ref="cold_16" />
610 </record>
611 <record model="product.cold_chain" id="cold_13">
612 <field name="code">*25F</field>
613+ <field name="ud_code">*25F</field>
614 <field name="name">*25F - Must be kept below 25Ā°C and cannot be frozen: check FreezeWatch</field>
615 <field name="cold_chain" eval="False" />
616 <field name="mapped_to" ref="cold_15" />
617
618=== modified file 'bin/addons/product_attributes/product_attributes_view.xml'
619--- bin/addons/product_attributes/product_attributes_view.xml 2024-04-16 08:38:51 +0000
620+++ bin/addons/product_attributes/product_attributes_view.xml 2024-05-13 13:47:13 +0000
621@@ -136,7 +136,7 @@
622 <button name="reactivate_product" icon="gtk-execute" string="Re-activate product" type="object" attrs="{'invisible': ['|', '|', ('active', '=', True), ('nsl_merged', '=', True), ('unidata_merged', '=', True)]}" />
623 <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')]}"/>
624 <button name="open_merge_hq_product_wizard" icon="gtk-convert" string="HQ Merge product" type="object" attrs="{'invisible': [('can_be_hq_merged', '!=', 'True')]}"/>
625- <button name="pull_ud" icon="unidata.png" string="Pull UD OC Validation" type="object" attrs="{'invisible': ['|', ('int_status_code', '!=', 'unidata'), ('instance_level', '!=', 'section')]}" />
626+ <button name="pull_ud" icon="unidata.png" string="Pull UD Product" type="object" attrs="{'invisible': ['|', ('int_status_code', '!=', 'unidata'), ('instance_level', '!=', 'section')]}" />
627 <button name="debug_ud" icon="debug.png" string="Display MML API result" type="object" attrs="{'invisible': ['|', ('int_status_code', '!=', 'unidata'), ('instance_level', '!=', 'section')]}" invisible="1"/>
628 </group>
629 </group>
630@@ -201,6 +201,7 @@
631 <separator string="Transport" colspan="2"/>
632 <field name="dangerous_goods" colspan="2"/>
633 <field name="un_code" colspan="2"/>
634+ <field name="hs_code" colspan="2" attrs="{'invisible': [('int_status_code', '!=', 'unidata')]}"/>
635 </group>
636 <group colspan="2" col="2">
637 <separator string="Diffusion" colspan="2"/>
638@@ -1119,5 +1120,20 @@
639 </field>
640 </record>
641
642+ <record id="product_pull_single_ud_view" model="ir.ui.view">
643+ <field name="name">product.pull_single_ud.view</field>
644+ <field name="model">product.pull_single_ud</field>
645+ <field name="type">form</field>
646+ <field name="arch" type="xml">
647+ <form string="Pull msfid">
648+ <field name="msfid" colspan="2" required="1"/>
649+ <group colspan="4">
650+ <button name="cancel" type="object" string="Cancel" icon="gtk-cancel" />
651+ <button name="pull_product" type="object" string="Pull Product" icon="gtk-go-forward" />
652+ </group>
653+ </form>
654+ </field>
655+ </record>
656+
657 </data>
658 </openerp>
659
660=== modified file 'bin/addons/product_attributes/unidata_sync.py'
661--- bin/addons/product_attributes/unidata_sync.py 2023-11-28 13:36:32 +0000
662+++ bin/addons/product_attributes/unidata_sync.py 2024-05-13 13:47:13 +0000
663@@ -1,10 +1,13 @@
664 # encoding: utf-8
665 from osv import fields, osv
666+from osv.orm import browse_record, browse_null
667 from tools.translate import _
668 import tools
669 from datetime import datetime
670 from dateutil import tz
671 import time
672+import pprint
673+from tools.safe_eval import safe_eval
674
675 import logging
676 import logging.handlers
677@@ -16,6 +19,9 @@
678
679 import requests
680
681+class UDException(Exception):
682+ pass
683+
684 class unidata_country(osv.osv):
685 _name = 'unidata.country'
686 _description = 'UniData Country'
687@@ -271,10 +277,13 @@
688 'end_date': fields.datetime('End Date', readonly=1),
689 'number_products_pulled': fields.integer('# products pulled', readonly=1),
690 'number_products_updated': fields.integer('# products updated', readonly=1),
691+ 'number_products_created': fields.integer('# products created', readonly=1),
692+ 'number_products_errors': fields.integer('# products errors', readonly=1),
693+ 'sync_error': fields.one2many('unidata.pull_product.log', 'session_id', 'Sync Error', readonly=1),
694 'error': fields.text('Error', readonly=1),
695 'page_size': fields.integer('page size', readonly=1),
696 'state': fields.selection([('running', 'Running'), ('error', 'Error'), ('done', 'Done')], 'State', readonly=1),
697- 'sync_type': fields.selection([('full', 'Full'), ('cont', 'Continuation'), ('diff', 'Based on last modification date')], 'Sync Type', readonly=1),
698+ 'sync_type': fields.selection([('full', 'Full'), ('cont', 'Continuation'), ('diff', 'Based on last modification date'),('single', 'Single MSFID')], 'Sync Type', readonly=1),
699 'msfid_min': fields.integer('Min Msfid', readonly=1),
700 'last_date': fields.char('Last Date', size=64, readonly=1),
701 'log_file': fields.char('Path to log file', size=128, readonly=1),
702@@ -284,6 +293,9 @@
703 'number_lists_pulled': fields.integer('# projects pulled', readonly=1),
704 }
705
706+ _defaults = {
707+ }
708+
709 unidata_sync_log()
710
711
712@@ -300,6 +312,7 @@
713
714 sync_id = self.pool.get('ir.model.data').get_object_reference(self.cr, self.uid, 'product_attributes', 'unidata_sync_config')[1]
715 config = self.pool.get('unidata.sync').read(self.cr, self.uid, sync_id, context=self.context)
716+ self.unidata_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'product_attributes', 'int_6')[1]
717
718 self.page_size = config['page_size'] or 500
719 self.ud_params = {
720@@ -316,10 +329,243 @@
721 self.project_cache = {}
722 self.msf_intance_cache = {}
723 self.uf_instance_cache = {}
724+ self.uf_product_cache = {}
725+ self.categ_account_cache = {}
726+
727+ self.default_oc_values = {}
728+ default_ids = self.pool.get('unidata.default_product_value').search(self.cr, self.uid, [])
729+ for default in self.pool.get('unidata.default_product_value').browse(self.cr, self.uid, default_ids):
730+ if default.field in ('perishable', 'batch_management'):
731+ if default.value == 't':
732+ default.value = True
733+ else:
734+ default.value = False
735+
736+ self.default_oc_values.setdefault(default.nomenclature.id, {}).update({default.field: default.value})
737
738 if self.pool.get('res.company')._get_instance_level(self.cr, self.uid) != 'section':
739 raise osv.except_osv(_('Error'), _('UD/MSL sync can only be started at HQ level.'))
740
741+ self.uf_config = {
742+ 'nomen_manda_0': {
743+ 'ud': 'type',
744+ 'relation': 'product.nomenclature',
745+ 'key_field': 'msfid',
746+ 'domain': [('level', '=', 0)],
747+ },
748+ 'nomen_manda_1': {
749+ 'ud': 'group/code',
750+ 'relation': 'product.nomenclature',
751+ 'key_field': 'msfid',
752+ 'nomen_level': 1,
753+ },
754+ 'nomen_manda_2': {
755+ 'ud': 'family/code',
756+ 'relation': 'product.nomenclature',
757+ 'key_field': 'msfid',
758+ 'nomen_level': 2,
759+ },
760+ 'nomen_manda_3': {
761+ 'ud': 'root/code',
762+ 'relation': 'product.nomenclature',
763+ 'key_field': 'msfid',
764+ 'nomen_level': 3,
765+ },
766+ 'name': {
767+ 'lang': {
768+ 'en_MF': {'ud': 'labels/english'},
769+ 'fr_MF': {'ud': 'labels/french'},
770+ 'es_MF': {'ud': 'labels/spanish'},
771+ },
772+ 'function': lambda a: a.strip(),
773+ },
774+ 'closed_article': {
775+ 'ud': 'closedInfo/closed',
776+ 'mapping': {
777+ 'Open': 'no',
778+ 'Closed': 'yes',
779+ 'Restricted to': 'recommanded',
780+ False: 'no',
781+ }
782+ },
783+ 'justification_code_id': {
784+ 'ud': 'supply/justification/code',
785+ 'relation': 'product.justification.code',
786+ 'key_field': 'code',
787+ 'ignored_values': ['SPM', 'PMFE'], # tbc with Raff
788+ },
789+ 'controlled_substance': {
790+ 'ud': 'medical/controlledSubstanceGroup/controlledSubstanceInfo/code',
791+ 'mapping': {
792+ '!': '!',
793+ 'N1': 'N1',
794+ 'N2': 'N2',
795+ 'P1': 'P1',
796+ 'P2': 'P2',
797+ 'P3': 'P3',
798+ 'P4': 'P4',
799+ 'DP': 'DP',
800+ 'Y': 'Y',
801+ 'True': 'True',
802+ False: False
803+ }
804+ },
805+ 'default_code': {
806+ 'ud': 'code'
807+ },
808+ #'fit_value': {
809+ # 'lang': {
810+ # 'en_MF': {'ud': 'description/fitEnglishtext'},
811+ # 'fr_MF': {'ud': 'description/fitFrenchtext'},
812+ # }
813+ #},
814+ #'form_value': {
815+ # 'lang': {
816+ # 'en_MF': {'ud': 'description/formEnglishtext'},
817+ # 'fr_MF': {'ud': 'description/formFrenchtext'},
818+ # }
819+ #},
820+ #'function_value': {
821+ # 'lang': {
822+ # 'en_MF': {'ud': 'description/functionEnglishtext'},
823+ # 'fr_MF': {'ud': 'description/functionFrenchtext'},
824+ # }
825+ #},
826+ 'cold_chain': {
827+ 'ud': 'supply/thermosensitiveGroup/thermosensitiveInfo/code',
828+ 'relation': 'product.cold_chain',
829+ 'key_field': 'ud_code',
830+ },
831+ 'heat_sensitive_item': {
832+ 'ud': 'supply/thermosensitiveGroup/thermosensitive',
833+ 'relation': 'product.heat_sensitive',
834+ 'key_field': 'code',
835+ 'mapping': {
836+ "Don't know": 'no_know',
837+ False: 'no',
838+ 'No': 'no',
839+ 'Yes': 'yes',
840+ },
841+ },
842+ 'manufacturer_ref': {
843+ 'ud': 'closedInfo/manufacturerRef',
844+ },
845+ 'manufacturer_txt': {
846+ 'ud': 'closedInfo/manufacturer',
847+ },
848+ 'msfid': {
849+ 'ud': 'id',
850+ },
851+ 'international_status': {
852+ 'value': self.unidata_id,
853+ },
854+ #'name_template': tbc
855+ 'xmlid_code': {
856+ 'ud': 'id',
857+ 'on_update': False,
858+ 'function': lambda a: a and '%s'%a or False,
859+ },
860+ 'old_code': {
861+ 'ud': 'formerCodes',
862+ 'function': lambda a: ';'.join(a),
863+ },
864+ 'product_catalog_path': {
865+ 'ud': 'unicatURL',
866+ },
867+ 'short_shelf_life': {
868+ 'ud': 'medical/shortShelfLifeGroup/shortShelfLife',
869+ 'mapping': {
870+ 'Yes': 'True',
871+ 'No': 'False',
872+ "Don't know": 'no_know',
873+ False: 'no_know',
874+ },
875+ },
876+ 'single_use': {
877+ 'ud': 'medical/singleUse',
878+ 'mapping': {
879+ 'Single use': 'yes',
880+ 'Single patient multiple use': 'yes',
881+ 'Reusable': 'no',
882+ 'Implantable Device': 'no_know',
883+ "Don't know": 'no_know',
884+ 'Not Applicable': 'no',
885+ False: 'no',
886+ }
887+ },
888+ 'standard_ok': {
889+ 'ud': 'standardizationLevel',
890+ 'mapping': {
891+ 'NST': 'non_standard',
892+ 'STD': 'standard',
893+ 'NSL': 'non_standard_local',
894+ }
895+ },
896+ 'standard_price': {
897+ 'value': 1.0,
898+ 'on_update': False,
899+ },
900+ 'state_ud': {
901+ 'ud': 'lifeCycleStatus',
902+ 'mapping': {
903+ '01. Preparation': 'valid',
904+ '02. Valid': 'valid',
905+ '03. Outdated': 'outdated',
906+ '04. Discontinued': 'discontinued',
907+ '05. Forbidden': 'forbidden',
908+ '06. Rejected': 'stopped',
909+ '08. Archived': 'archived',
910+ '01. Temporary Golden:': 'stopped',
911+ '01. Temporary Merge': 'stopped',
912+ '07. Parked': 'archived',
913+ },
914+ },
915+ 'sterilized': {
916+ 'ud': 'medical/sterile',
917+ 'mapping': {
918+ 'Yes': 'yes',
919+ 'No': 'no',
920+ "Don't know": 'no_know',
921+ False: 'no_know',
922+ }
923+ },
924+ 'dangerous_goods': {
925+ 'ud': 'supply/dangerousGroup/dangerous',
926+ 'mapping': {
927+ "Don't know": 'no_know',
928+ 'Yes': 'True',
929+ 'No': 'False',
930+ False: 'False',
931+ }
932+ },
933+ 'un_code': {
934+ 'ud': 'supply/dangerousGroup/dangerousInfo/number',
935+ },
936+ 'hs_code': {
937+ 'ud': 'supply/hsCode',
938+ },
939+ 'oc_subscription': {
940+ 'ud': 'ocSubscriptions/%s' % self.oc,
941+ },
942+ 'oc_validation': {
943+ 'ud': 'ocValidations/%s/valid' % self.oc,
944+ },
945+ 'oc_validation_date': {
946+ 'ud': 'ocValidations/%s/lastValidationDate' % self.oc,
947+ 'type': 'date',
948+ },
949+ 'oc_devalidation_date': {
950+ 'ud': 'ocValidations/%s/lastDevalidationDate' % self.oc,
951+ 'type': 'date',
952+ },
953+ 'oc_devalidation_reason': {
954+ 'ud': 'ocValidations/%s/devalidationReason' % self.oc,
955+ },
956+ 'oc_comments': {
957+ 'ud': 'ocValidations/%s/comments' % self.oc,
958+ }
959+ }
960+
961 def create_msl_list(self):
962 oc_number = {
963 'oca': 5,
964@@ -524,15 +770,165 @@
965 llevel = logging.INFO
966 self.logger.log(llevel, msg)
967
968- def update_products(self, q_filter, record_date):
969+ def map_ud_fields(self, ud_data, new_prod):
970+ uf_values = {'en_MF': {}}
971+ for uf_key in self.uf_config:
972+ for lang in self.uf_config[uf_key].get('lang', ['default']):
973+ if lang == 'default':
974+ lang_values = uf_values['en_MF']
975+ field_desc = self.uf_config[uf_key]
976+ else:
977+ lang_values = uf_values.get(lang, {})
978+ field_desc = self.uf_config[uf_key]['lang'][lang]
979+
980+ if field_desc.get('value'):
981+ if new_prod or field_desc.get('on_update', True):
982+ lang_values[uf_key] = field_desc['value']
983+ continue
984+
985+ uf_value = ud_data
986+ for key in field_desc['ud'].split('/'):
987+ uf_value = uf_value.get(key, {})
988+ if not uf_value:
989+ uf_value = False
990+
991+ if field_desc.get('mapping'):
992+ if uf_value not in field_desc['mapping']:
993+ raise UDException('Mapping error, uf_key: %s, uf_value: %s' % (uf_key, uf_value))
994+ else:
995+ uf_value = field_desc['mapping'][uf_value]
996+
997+ if 'ignored_values' in field_desc and uf_value in field_desc.get('ignored_values'):
998+ uf_value = False
999+
1000+ if uf_key in ['nomen_manda_1', 'nomen_manda_2', 'nomen_manda_3']:
1001+ previous_nom = {
1002+ 'nomen_manda_1': 'nomen_manda_0',
1003+ 'nomen_manda_2': 'nomen_manda_1',
1004+ 'nomen_manda_3': 'nomen_manda_2',
1005+ }[uf_key]
1006+ self.uf_product_cache.setdefault(uf_key, {})
1007+
1008+ if uf_key == 'nomen_manda_1':
1009+ msfid = '%s-%s' % (ud_data['type'], ud_data['group']['code'])
1010+ ud_key = 'group'
1011+ elif uf_key == 'nomen_manda_2':
1012+ msfid = '%s-%s-%s%s' % (ud_data['type'], ud_data['group']['code'], ud_data['group']['code'], ud_data['family']['code'])
1013+ ud_key = 'family'
1014+ else:
1015+ msfid = '%s-%s-%s%s-%s' % (ud_data['type'], ud_data['group']['code'], ud_data['group']['code'], ud_data['family']['code'], ud_data['root']['code'])
1016+ ud_key = 'root'
1017+
1018+ #if previous_nom not in lang_values:
1019+ # continue
1020+ cache_key = (uf_key, lang_values[previous_nom], msfid)
1021+
1022+ if cache_key not in self.uf_product_cache[uf_key]:
1023+ domain = [('level', '=', self.uf_config[uf_key]['nomen_level']), ('parent_id', '=', uf_values['en_MF'][previous_nom]), ('msfid', '=', msfid)]
1024+ nomen_id = self.pool.get('product.nomenclature').search(self.cr, self.uid, domain, context=self.context)
1025+ created_nomen = False
1026+ if not nomen_id:
1027+ created_nomen = True
1028+ self.log('==== create nomenclature %s'%msfid)
1029+ nomen_data = {
1030+ 'name': ud_data[ud_key]['labels']['english'],
1031+ 'msfid': msfid,
1032+ 'parent_id': uf_values['en_MF'][previous_nom],
1033+ 'level': self.uf_config[uf_key]['nomen_level'],
1034+ }
1035+ nomen_id = [self.pool.get('product.nomenclature').create(self.cr, self.uid, nomen_data, context={'lang': 'en_MF'})]
1036+ if ud_data.get(ud_key, {}).get('labels', {}).get('french'):
1037+ self.pool.get('product.nomenclature').write(self.cr, self.uid, nomen_id, {'name': ud_data[ud_key]['labels']['french']}, context={'lang': 'fr_MF'})
1038+ if uf_key == 'nomen_manda_2':
1039+ self.log('===== create category %s'%msfid)
1040+ account_ids = self.pool.get('account.account').search(self.cr, self.uid, [('type', '!=', 'view'), ('code', '=', ud_data.get('accountCode', {}).get('code'))])
1041+ if not account_ids:
1042+ raise UDException('Account code %s not found' % (ud_data.get('accountCode', {}).get('code')))
1043+ account_id = account_ids[0]
1044+ categ_id = self.pool.get('product.category').create(self.cr, self.uid, {
1045+ 'name': ud_data['family']['labels']['english'],
1046+ 'msfid': msfid,
1047+ 'family_id': nomen_id[0],
1048+ 'property_account_income_categ': account_id,
1049+ 'property_account_expense_categ': account_id,
1050+ }, context={'lang': 'en_MF'})
1051+ if ud_data.get('family', {}).get('labels', {}).get('french'):
1052+ self.pool.get('product.category').write(self.cr, self.uid, categ_id, {'name': ud_data['family']['labels']['french']}, context={'lang': 'fr_MF'})
1053+ else:
1054+ self.uf_product_cache[uf_key][cache_key] = nomen_id
1055+ else:
1056+ nomen_id = self.uf_product_cache[uf_key][cache_key]
1057+ if not nomen_id or len(nomen_id) != 1:
1058+ raise UDException('%s error %s not found %s' % (uf_key, cache_key, nomen_id))
1059+ if False and uf_key == 'nomen_manda_2':
1060+ if msfid not in self.categ_account_cache:
1061+ if not created_nomen:
1062+ self.categ_account_cache[msfid] = self.pool.get('product.nomenclature').browse(self.cr, self.uid, nomen_id[0]).category_id.property_account_income_categ.code
1063+ categ_account_code = self.categ_account_cache[msfid]
1064+ else:
1065+ categ_account_code = ud_data.get('accountCode', {}).get('code')
1066+
1067+ else:
1068+ categ_account_code = self.categ_account_cache[msfid]
1069+
1070+ if ud_data.get('accountCode', {}).get('code') != categ_account_code:
1071+ account_ids = self.pool.get('account.account').search(self.cr, self.uid, [('type', '!=', 'view'), ('code', '=', ud_data.get('accountCode', {}).get('code'))])
1072+ if not account_ids:
1073+ raise UDException('Account code %s not found' % (ud_data.get('accountCode', {}).get('code')))
1074+ account_id = account_ids[0]
1075+ else:
1076+ account_id = False
1077+ lang_values['property_account_income'] = account_id
1078+ lang_values['property_account_expense'] = account_id
1079+
1080+ lang_values[uf_key] = nomen_id[0]
1081+
1082+ elif field_desc.get('relation'):
1083+ self.uf_product_cache.setdefault(uf_key, {})
1084+ if not uf_value:
1085+ self.uf_product_cache[uf_key][uf_value] = [False]
1086+ if uf_value not in self.uf_product_cache[uf_key]:
1087+ domain = [(field_desc['key_field'], '=', uf_value)]
1088+ if field_desc.get('domain'):
1089+ domain += field_desc['domain']
1090+ self.uf_product_cache[uf_key][uf_value] = self.pool.get(field_desc['relation']).search(self.cr, self.uid, domain, context=self.context)
1091+ if not self.uf_product_cache[uf_key][uf_value] or len(self.uf_product_cache[uf_key][uf_value]) > 1:
1092+ raise UDException('Field error %s: uf_value: %s, records found in UF: %d, %s' % (uf_key, uf_value, len(self.uf_product_cache[uf_key][uf_value]), self.uf_product_cache[uf_key][uf_value]))
1093+ else:
1094+ if new_prod or field_desc.get('on_update', True):
1095+ lang_values[uf_key] = self.uf_product_cache[uf_key][uf_value][0]
1096+ elif field_desc.get('function'):
1097+ if new_prod or field_desc.get('on_update', True):
1098+ lang_values[uf_key] = field_desc['function'](uf_value)
1099+ else:
1100+ if uf_value and field_desc.get('type') == 'date':
1101+ uf_value = self.ud_date(uf_value)
1102+
1103+ if new_prod or field_desc.get('on_update', True):
1104+ lang_values[uf_key] = uf_value
1105+ if new_prod:
1106+ for nomen in ['nomen_manda_0', 'nomen_manda_1', 'nomen_manda_2', 'nomen_manda_3']:
1107+ if uf_values['en_MF'].get(nomen) and uf_values['en_MF'][nomen] in self.default_oc_values:
1108+ uf_values['en_MF'].update(self.default_oc_values[uf_values['en_MF'][nomen]])
1109+
1110+ if uf_values['en_MF'].get('batch_management'):
1111+ uf_values['en_MF']['perishable'] = True
1112+
1113+ return uf_values
1114+
1115+ def update_products(self, q_filter, record_date, session_id=False):
1116 country_obj = self.pool.get('unidata.country')
1117 project_obj = self.pool.get('unidata.project')
1118 prod_obj = self.pool.get('product.product')
1119+ pull_log = self.pool.get('unidata.pull_product.log')
1120+
1121
1122 page = 1
1123 date_to_record = False
1124 prod_updated = 0
1125 rows_seen = 0
1126+ nb_errors = 0
1127+ nb_created = 0
1128 while True:
1129 js = self.query(q_filter, page=page)
1130
1131@@ -546,65 +942,171 @@
1132 for x in js.get('rows'):
1133 self.log('UD: %s' % x)
1134 rows_seen += 1
1135- prod_id = prod_obj.search(self.cr, self.uid, [('msfid', '=', x['id']), ('active', 'in', ['t', 'f'])], order='active desc, id', context=self.context)
1136- if not prod_id:
1137- self.log('Product not found in UF, msfid: %s, code: %s' % (x['id'], x['code'], ), 'warn')
1138- continue
1139-
1140- oc_data = x.get('ocValidations', {}).get(self.oc, {})
1141- data = {
1142- 'oc_validation': oc_data.get('valid'),
1143- 'oc_validation_date': False,
1144- 'oc_devalidation_date': False,
1145- 'oc_devalidation_reason': oc_data.get('devalidationReason'),
1146- 'oc_comments': oc_data.get('comments'),
1147-
1148- }
1149-
1150- if oc_data.get('lastValidationDate'):
1151- data['oc_validation_date'] = self.ud_date(oc_data['lastValidationDate'])
1152- if oc_data.get('lastDevalidationDate'):
1153- data['oc_devalidation_date'] = self.ud_date(oc_data['lastDevalidationDate'])
1154-
1155- c_restriction = []
1156- p_restriction = []
1157- for mr in oc_data.get('missionRestrictions', []):
1158- if mr.get('country', {}).get('labels', {}).get('english'):
1159- if mr['country']['labels']['english'] not in self.country_cache:
1160- c_id = country_obj.search(self.cr, self.uid, [('name', '=', mr['country']['labels']['english'])], context=self.context)
1161- if not c_id:
1162- c_id = country_obj.create(self.cr, self.uid, {'name': mr['country']['labels']['english']}, context=self.context)
1163-
1164- self.log('Create country %s' % (mr['country']['labels']['english'],))
1165- self.country_cache[mr['country']['labels']['english']] = c_id
1166- else:
1167- self.country_cache[mr['country']['labels']['english']] = c_id[0]
1168- c_restriction.append(self.country_cache[mr['country']['labels']['english']])
1169-
1170- for pr in mr.get('projectRestrictions', []):
1171- # TODO create UF instance
1172- if pr.get('code'):
1173- if pr.get('code') not in self.project_cache:
1174- p_id = project_obj.search(self.cr, self.uid, [('code', '=', pr['code'])], context=self.context)
1175- if not p_id:
1176- self.log('Create project %s' % (pr['code'], ))
1177- self.project_cache[pr['code']] = project_obj.create(self.cr, self.uid, {'code': pr['code'], 'name': pr.get('name')}, context=self.context)
1178+ try:
1179+ self.cr.execute("SAVEPOINT nom_ud_update")
1180+ #prod_id = prod_obj.search(self.cr, self.uid, [('msfid', '=', x['id']), ('active', 'in', ['t', 'f'])], order='active desc, id', context=self.context)
1181+ #if x.get('state') == 'Golden':
1182+ # continue
1183+ prod_ids = []
1184+ if not x.get('id'):
1185+ raise UDException('No msfid in API')
1186+
1187+ if not x.get('formerCodes'):
1188+ raise UDException('No formerCodes code')
1189+ if not x.get('type') or not x.get('group', {}).get('code') or not x.get('family', {}).get('code') or not x.get('root', {}).get('code'):
1190+ raise UDException('Nomenclature not set in UD')
1191+
1192+ prod_ids = prod_obj.search(self.cr, self.uid, [('msfid', '=', x.get('id')), ('active', 'in', ['t', 'f']), ('international_status', '=', self.unidata_id)], context=self.context)
1193+ if len(prod_ids) > 1:
1194+ raise UDException('%s products found for msfid %s: %s' % (len(prod_ids), x.get('id'), prod_obj.read(self.cr, self.uid, prod_ids, ['default_code'])))
1195+
1196+ if len(prod_ids) == 0:
1197+ if not x.get('ocSubscriptions').get(self.oc):
1198+ self.log('%s product ignored: ocSubscriptions False' % x['formerCodes'])
1199+ continue
1200+ self.log('%s product to create' % (x['formerCodes'], ))
1201+ else:
1202+ if not x.get('ocSubscriptions').get(self.oc):
1203+ if not prod_obj.search(self.cr, self.uid, [('id', 'in', prod_ids), ('oc_subscription', '=', True), ('active', 'in', ['t', 'f'])], context=self.context):
1204+ self.log('%s product ignored: ocSubscriptions False in UD and UF' % x['code'])
1205+ continue
1206+
1207+ self.log('%s product found %s' % (x['formerCodes'], prod_ids[0]))
1208+
1209+ product_values = self.map_ud_fields(x, new_prod=not bool(prod_ids))
1210+
1211+ c_restriction = []
1212+ p_restriction = []
1213+ oc_data = x.get('ocValidations', {}).get(self.oc, {})
1214+ for mr in oc_data.get('missionRestrictions', []):
1215+ if mr.get('country', {}).get('labels', {}).get('english'):
1216+ if mr['country']['labels']['english'] not in self.country_cache:
1217+ c_id = country_obj.search(self.cr, self.uid, [('name', '=', mr['country']['labels']['english'])], context=self.context)
1218+ if not c_id:
1219+ c_id = country_obj.create(self.cr, self.uid, {'name': mr['country']['labels']['english']}, context=self.context)
1220+
1221+ self.log('Create country %s' % (mr['country']['labels']['english'],))
1222+ self.country_cache[mr['country']['labels']['english']] = c_id
1223 else:
1224- self.project_cache[pr['code']] = p_id[0]
1225- p_restriction.append(self.project_cache[pr['code']])
1226-
1227- data.update({
1228- 'oc_country_restrictions': [(6, 0, list(set(c_restriction)))],
1229- 'oc_project_restrictions': [(6, 0, list(set(p_restriction)))],
1230- })
1231- self.log('Write product id: %d, code: %s, msfid: %s, data: %s' % (prod_id[0], x['code'], x['id'], data))
1232- prod_obj.write(self.cr, self.uid, prod_id[0], data, context=self.context)
1233- prod_updated += 1
1234+ self.country_cache[mr['country']['labels']['english']] = c_id[0]
1235+ c_restriction.append(self.country_cache[mr['country']['labels']['english']])
1236+
1237+ for pr in mr.get('projectRestrictions', []):
1238+ # TODO create UF instance
1239+ if pr.get('code'):
1240+ if pr.get('code') not in self.project_cache:
1241+ p_id = project_obj.search(self.cr, self.uid, [('code', '=', pr['code'])], context=self.context)
1242+ if not p_id:
1243+ self.log('Create project %s' % (pr['code'], ))
1244+ self.project_cache[pr['code']] = project_obj.create(self.cr, self.uid, {'code': pr['code'], 'name': pr.get('name')}, context=self.context)
1245+ else:
1246+ self.project_cache[pr['code']] = p_id[0]
1247+ p_restriction.append(self.project_cache[pr['code']])
1248+
1249+ product_values['en_MF'].update({
1250+ 'oc_country_restrictions': [(6, 0, list(set(c_restriction)))],
1251+ 'oc_project_restrictions': [(6, 0, list(set(p_restriction)))],
1252+ })
1253+
1254+
1255+ if prod_ids:
1256+ diff = []
1257+ for lang in ['en_MF', 'fr_MF', 'sp_MF']:
1258+ if not product_values.get(lang):
1259+ continue
1260+ current_value = prod_obj.browse(self.cr, self.uid, prod_ids[0], fields_to_fetch=product_values[lang].keys(), context={'lang': lang})
1261+ for key, value in product_values[lang].items():
1262+ tmp_diff = False
1263+ if key in ('oc_country_restrictions', 'oc_project_restrictions'):
1264+ if set(value[0][2]) != set([x.id for x in current_value[key]]):
1265+ self.log('Field diff %s, uf: *%s*, ud: *%s*'% (key, [x.id for x in current_value[key]], value[0][2]))
1266+ tmp_diff = True
1267+ elif isinstance(current_value[key], browse_record):
1268+ if current_value[key]['id'] != value:
1269+ self.log('Field diff m2o empty %s, uf: *%s*, ud: *%s*'% (key, current_value[key]['id'], value))
1270+ tmp_diff = True
1271+ elif isinstance(current_value[key], browse_null):
1272+ if value:
1273+ self.log('Field diff m2o %s, uf: *%s*, ud: *%s*'% (key, current_value[key]['id'], value))
1274+ tmp_diff = True
1275+ elif current_value[key] != value and ( value is False and current_value[key] or value is not False):
1276+ self.log('Field diff %s, uf: *%s*, ud: *%s*'% (key, current_value[key], value))
1277+ tmp_diff = True
1278+ if tmp_diff:
1279+ diff.append(key)
1280+ if diff:
1281+ # not not check fr/sp
1282+ break
1283+
1284+ if not diff:
1285+ self.log('==== same values %s %s' % (product_values.get('en_MF', {}).get('default_code'), prod_ids[0]))
1286+ continue
1287+ else:
1288+ self.log('==== diff values %s %s, key: %s' % (product_values.get('en_MF', {}).get('default_code'), prod_ids[0], diff))
1289+
1290+ try:
1291+ self.cr.execute("SAVEPOINT prod_ud_update")
1292+ if prod_ids:
1293+ self.log('==== write product id: %d, code: %s, msfid: %s, data: %s' % (prod_ids[0], x['code'], x['id'], product_values['en_MF']))
1294+ prod_obj.write(self.cr, self.uid, prod_ids[0], product_values['en_MF'], context={'lang': 'en_MF'})
1295+ prod_updated += 1
1296+ else:
1297+ prod_ids = [prod_obj.create(self.cr, self.uid, product_values['en_MF'], context={'lang': 'en_MF'})]
1298+ self.log('==== create product id: %d, code: %s, msfid: %s, data: %s' % (prod_ids[0], x['code'], x['id'], product_values['en_MF']))
1299+ nb_created += 1
1300+
1301+ for lang in ['fr_MF', 'sp_MF']:
1302+ if product_values.get(lang):
1303+ prod_obj.write(self.cr, self.uid, prod_ids[0], product_values['lang'], context={'lang': 'lang'})
1304+ except Exception as e:
1305+ self.cr.execute("ROLLBACK TO SAVEPOINT prod_ud_update")
1306+ raise UDException(tools.misc.get_traceback(e))
1307+ else:
1308+ self.cr.execute("RELEASE SAVEPOINT prod_ud_update")
1309+
1310+ except UDException as e:
1311+ self.cr.execute("ROLLBACK TO SAVEPOINT nom_ud_update")
1312+ self.log('ERROR %s'% e.args[0])
1313+ if session_id:
1314+ data_log = {
1315+ 'msfid': x.get('id', ''),
1316+ 'code': x.get('code', ''),
1317+ 'former_codes': x.get('formerCodes', ''),
1318+ 'log': e.args[0],
1319+ 'json_data': x,
1320+ 'session_id': session_id,
1321+ }
1322+ to_write = []
1323+ if x.get('id', ''):
1324+ to_write = pull_log.search(self.cr, self.uid,[('msfid', '=', x.get('id', '')), ('session_id', '=', session_id)])
1325+
1326+ if to_write:
1327+ pull_log.write(self.cr, self.uid, to_write, data_log)
1328+ else:
1329+ pull_log.create(self.cr, self.uid, data_log)
1330+ nb_errors += 1
1331+ else:
1332+ nb_errors += 1
1333+ if x.get('id', ''):
1334+ self.cr.execute('''insert into unidata_products_error (msfid, code, former_codes, date, log, uf_product_id, json_data)
1335+ values (%(msfid)s, %(code)s, %(former_codes)s, NOW(), %(log)s, %(uf_product_id)s, %(json_data)s)
1336+ on conflict (msfid) do update SET code = %(code)s, former_codes=%(former_codes)s, date=NOW(), log=%(log)s, uf_product_id=%(uf_product_id)s, json_data=%(json_data)s, fixed_date=NULL
1337+ ''', {
1338+ 'msfid': x.get('id'),
1339+ 'code': x.get('code', ''),
1340+ 'former_codes': '%s' % x.get('formerCodes', ''),
1341+ 'log': e.args[0],
1342+ 'uf_product_id': ','.join([str(x) for x in prod_ids]),
1343+ 'json_data': '%s'%x,
1344+ })
1345+ else:
1346+ self.cr.execute("RELEASE SAVEPOINT nom_ud_update")
1347+
1348 page += 1
1349 if len(js.get('rows')) < self.page_size:
1350 break
1351
1352- return date_to_record, rows_seen, prod_updated
1353+ return date_to_record, rows_seen, prod_updated, nb_created, nb_errors
1354
1355
1356
1357@@ -622,7 +1124,7 @@
1358 def _get_log(self, cr, uid, ids, field_name, args, context=None):
1359 res = {}
1360
1361- cr.execute("select start_date, end_date, state, sync_type from unidata_sync_log where server='ud' order by id desc limit 1")
1362+ cr.execute("select start_date, end_date, state, sync_type from unidata_sync_log where server='ud' and coalesce(sync_type, '')!='single' order by id desc limit 1")
1363 one = cr.fetchone()
1364 if one:
1365 last_execution_start_date = one[0]
1366@@ -640,7 +1142,6 @@
1367 last_msl_execution_status = one[2]
1368 last_msl_execution_sync_type = one[3]
1369 else:
1370- last_execution_start_date = last_execution_end_date = last_execution_status = last_execution_sync_type = False
1371 last_msl_execution_start_date = last_msl_execution_end_date = last_msl_execution_status = last_msl_execution_sync_type = False
1372
1373 param_obj = self.pool.get('ir.config_parameter')
1374@@ -661,7 +1162,6 @@
1375 'last_msl_execution_sync_type': last_msl_execution_sync_type,
1376 'eligible_msl_full_sync': eligible_msl_full_sync,
1377 }
1378-
1379 return res
1380
1381 def _get_next_planned_date(self, cr, uid, ids, field_name, args, context=None):
1382@@ -849,7 +1349,7 @@
1383 new_thread.start()
1384 new_thread.join(3.0)
1385 if not new_thread.is_alive() and self._error:
1386- raise self._error
1387+ raise Exception(self._error)
1388 return True
1389
1390 def start_ud_sync(self, cr, uid, context=None):
1391@@ -878,6 +1378,8 @@
1392
1393 nb_prod = 0
1394 updated = 0
1395+ total_nb_errors = 0
1396+ total_nb_created = 0
1397
1398 if full:
1399 param_obj.set_param(cr, 1, 'LAST_UD_DATE_SYNC', '')
1400@@ -889,7 +1391,8 @@
1401 sync_type = 'diff'
1402
1403
1404- logger = logging.getLogger('unidata-sync')
1405+ oc = self.pool.get('sync.client.entity').get_entity(cr, uid, context).oc
1406+ logger = logging.getLogger('unidata-sync-%s'% oc)
1407 sync_obj = ud_sync(cr, uid, self.pool, logger=logger, context=context)
1408 page_size = sync_obj.page_size
1409
1410@@ -926,15 +1429,32 @@
1411 logger.info('Sync start, page size: %s, last msfid: %s, last date: %s' % (page_size, min_msfid, last_ud_date_sync))
1412
1413 try:
1414- while True:
1415+ last_loop = False
1416+ max_id = 0
1417+ # first tries previous errors
1418+ cr.execute('select distinct(msfid) from unidata_products_error where fixed_date is null')
1419+ query = []
1420+ all_msfids = [x[0] for x in cr.fetchall()]
1421+ for x in all_msfids:
1422+ query.append("msfIdentifier=%s" % x)
1423+ if query:
1424+ cr.execute('update unidata_products_error set fixed_date=NOW() where msfid in %s', (tuple(all_msfids),))
1425+ trash1, nb_prod, updated, total_nb_created, total_nb_errors = sync_obj.update_products(" or ".join(query), False, session_id)
1426+ while not last_loop:
1427 cr.execute('SAVEPOINT unidata_sync_log')
1428 cr.execute("select min(msfid), max(msfid) from product_product p where id in (select id from product_product where coalesce(msfid,0)!=0 and msfid>%s order by msfid limit %s)", (min_msfid, page_size))
1429 min_id, max_id = cr.fetchone()
1430 min_msfid = max_id
1431 if not min_id:
1432- break
1433+ last_loop = True
1434+ cr.execute("select max(msfid) from product_product p")
1435+ min_msfid = cr.fetchone()[0] or 0
1436+ q_filter = "(msfIdentifier>=%s)" % min_msfid
1437+ else:
1438+ if first_query:
1439+ min_id = 0
1440+ q_filter = "(msfIdentifier>=%s and msfIdentifier<=%s)"%(min_id, max_id)
1441
1442- q_filter = "(msfIdentifier>=%s and msfIdentifier<=%s)"%(min_id, max_id)
1443 if last_ud_date_sync:
1444 lastsync = (datetime.strptime(last_ud_date_sync.split('T')[0], '%Y-%m-%d') + relativedelta(hours=-24)).strftime('%Y-%m-%dT00:00:00')
1445 createdOn = (datetime.strptime(last_ud_date_sync.split('T')[0], '%Y-%m-%d') + relativedelta(days=-3)).strftime('%Y-%m-%dT00:00:00')
1446@@ -944,7 +1464,7 @@
1447 'createdOn': createdOn,
1448 }
1449
1450- s_date_to_record, rows_seen, prod_updated = sync_obj.update_products(q_filter, first_query)
1451+ s_date_to_record, rows_seen, prod_updated, nb_created, nb_errors = sync_obj.update_products(q_filter, first_query, session_id)
1452 if first_query and s_date_to_record:
1453 logger.info('Set last date: %s', s_date_to_record)
1454 param_obj.set_param(cr, 1, 'LAST_UD_DATE_SYNC', s_date_to_record)
1455@@ -953,8 +1473,10 @@
1456
1457 updated += prod_updated
1458 nb_prod += rows_seen
1459+ total_nb_errors += nb_errors
1460+ total_nb_created += nb_created
1461
1462- session_obj.write(cr, uid, session_id, {'number_products_pulled': nb_prod, 'number_products_updated': updated}, context=context)
1463+ session_obj.write(cr, uid, session_id, {'number_products_pulled': nb_prod, 'number_products_updated': updated, 'number_products_created': total_nb_created, 'number_products_errors': total_nb_errors}, context=context)
1464 cr.commit()
1465
1466 # end of sql loop
1467@@ -965,13 +1487,13 @@
1468 logger.error('End of Script with error: %s' % error)
1469 handler.close()
1470 logger.removeHandler(handler)
1471- session_obj.write(cr, uid, session_id, {'end_date': fields.datetime.now(), 'state': 'error', 'number_products_pulled': nb_prod, 'error': error, 'number_products_updated': updated}, context=context)
1472+ session_obj.write(cr, uid, session_id, {'end_date': fields.datetime.now(), 'state': 'error', 'number_products_pulled': nb_prod, 'error': error, 'number_products_updated': updated, 'number_products_created': total_nb_created, 'number_products_errors': total_nb_errors}, context=context)
1473 return False
1474
1475 logger.info('End of Script')
1476 handler.close()
1477 logger.removeHandler(handler)
1478- session_obj.write(cr, uid, session_id, {'end_date': fields.datetime.now(), 'state': 'done', 'number_products_pulled': nb_prod, 'number_products_updated': updated}, context=context)
1479+ session_obj.write(cr, uid, session_id, {'end_date': fields.datetime.now(), 'state': 'done', 'number_products_pulled': nb_prod, 'number_products_updated': updated, 'number_products_created': total_nb_created, 'number_products_errors': total_nb_errors}, context=context)
1480 param_obj.set_param(cr, 1, 'LAST_MSFID_SYNC', '')
1481 return True
1482
1483@@ -988,4 +1510,127 @@
1484
1485 unidata_sync()
1486
1487-
1488+class unidata_default_product_value(osv.osv):
1489+ _name = 'unidata.default_product_value'
1490+ _description = 'OC Default values'
1491+
1492+ def _get_number_incompatible_products(self, cr, uid, ids, field_name, args, context=None):
1493+ res = {}
1494+ ud_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'product_attributes', 'int_6')[1]
1495+ prod_obj = self.pool.get('product.product')
1496+ for oc_val in self.browse(cr, uid, ids, fields_to_fetch=['nomenclature', 'value', 'field'], context=context):
1497+ value = oc_val.value
1498+ if value == 'f':
1499+ value = False
1500+ res[oc_val.id] = prod_obj.search(cr, uid, [('international_status', '=', ud_id), ('nomen_manda_%d' % oc_val.nomenclature.level, '=', oc_val.nomenclature.id), (oc_val.field, '!=', value)], count=True, context=context)
1501+
1502+ return res
1503+
1504+ _columns = {
1505+ 'field': fields.selection([('perishable', 'Expiry Date Mandatory'), ('batch_management', 'Batch Number Mandatory'), ('procure_method','Procurement Method'), ('type', 'Product Type'), ('subtype', 'Product SubType')], 'Field Name', required=1, select=1),
1506+ 'value': fields.char('Value', size=256, required=1, select=1),
1507+ 'nomenclature': fields.many2one('product.nomenclature', required=1, string="Nomenclature"),
1508+ 'number_incompatible_products': fields.function(_get_number_incompatible_products, method=True, type='integer', string='Nb inconsistent prod'),
1509+ }
1510+
1511+ _sql_constraints = [
1512+ ('unique_nomenclature_field', 'unique(field, nomenclature)', 'Field / nomenclature already exists')
1513+ ]
1514+
1515+ def _check_value(self, cr, uid, ids, context=None):
1516+ if not ids:
1517+ return True
1518+ for oc in self.browse(cr, uid, ids, context=context):
1519+ if oc.field in ['perishable', 'batch_management'] and oc.value not in ['t', 'f']:
1520+ raise osv.except_osv(_('Error'), _('Expiry Date Mandatory and Batch Number Mandatory: only t or f are allowed'))
1521+ if oc.field == 'procure_method' and oc.value not in ['make_to_stock', 'make_to_order']:
1522+ raise osv.except_osv(_('Error'), _('Procurement Method: only make_to_stock or make_to_order are allowed'))
1523+ if oc.field == 'type' and oc.value not in ['product', 'consu', 'service_recep']:
1524+ raise osv.except_osv(_('Error'), _('Product Type only product, consu or service_recep are allowed'))
1525+ if oc.field == 'subtype' and oc.value not in ['single', 'kit', 'asset', '']:
1526+ raise osv.except_osv(_('Error'), _("Product SubType only single, kit, asset or '' are allowed"))
1527+
1528+ return True
1529+
1530+
1531+ _constraints = [
1532+ (_check_value, 'Value not allowed', [])
1533+ ]
1534+
1535+ def open_products(self, cr, uid, ids, context=None):
1536+ txt = []
1537+ for d in self.browse(cr, uid, ids, context=context):
1538+ txt.append('%s %s not %s' % (d.nomenclature.name, d.field, d.value))
1539+
1540+ return {
1541+ 'type': 'ir.actions.act_window',
1542+ 'res_model': 'product.product',
1543+ 'view_type': 'form',
1544+ 'view_mode': 'tree,form',
1545+ 'domain': [('incompatible_oc_default_values', 'in', ids)],
1546+ 'name': ' or '.join(txt)
1547+ }
1548+
1549+unidata_default_product_value()
1550+
1551+class unidata_pull_product_log(osv.osv):
1552+ _name = 'unidata.pull_product.log'
1553+ _order = 'id desc'
1554+ def _get_json_data_formated(self, cr, uid, ids, field_name, args, context=None):
1555+ res = {}
1556+
1557+ for j in self.browse(cr, uid, ids, fields_to_fetch=['json_data'], context=context):
1558+ try:
1559+ res[j.id] = pprint.pformat(safe_eval(j.json_data), width=160)
1560+ except:
1561+ res[j.id] = False
1562+ return res
1563+
1564+ _columns = {
1565+ 'msfid': fields.integer('MSF ID', required=1),
1566+ 'code': fields.char('UD Code', size=64, select=1),
1567+ 'former_codes': fields.char('Former Code', size=1024, select=1),
1568+ 'date': fields.datetime('Date', required=1, select=1),
1569+ 'log': fields.text('Log'),
1570+ 'json_data': fields.text('UD Json'),
1571+ 'json_data_formated': fields.function(_get_json_data_formated, method=1, type='text',string='UD Json'),
1572+ 'session_id': fields.many2one('unidata.sync.log', 'Session', select=1),
1573+ }
1574+
1575+ _defaults = {
1576+ 'date': lambda *a, **b: fields.datetime.now(),
1577+ }
1578+unidata_pull_product_log()
1579+
1580+class unidata_products_error(osv.osv):
1581+ _name = 'unidata.products_error'
1582+ _order = 'date desc, msfid'
1583+ def _get_json_data_formated(self, cr, uid, ids, field_name, args, context=None):
1584+ res = {}
1585+
1586+ for j in self.browse(cr, uid, ids, fields_to_fetch=['json_data'], context=context):
1587+ try:
1588+ res[j.id] = pprint.pformat(safe_eval(j.json_data), width=160)
1589+ except:
1590+ res[j.id] = False
1591+ return res
1592+
1593+ _columns = {
1594+ 'msfid': fields.integer('MSF ID', required=1, select=1),
1595+ 'code': fields.char('UD Code', size=64, select=1),
1596+ 'former_codes': fields.char('Former Code', size=1024),
1597+ 'date': fields.datetime('Date of last error', required=1, select=1),
1598+ 'fixed_date': fields.datetime('Fixed at', select=1),
1599+ 'log': fields.text('Log'),
1600+ 'uf_product_id': fields.text('UF product db id'),
1601+ 'json_data': fields.text('UD Json'),
1602+ 'json_data_formated': fields.function(_get_json_data_formated, method=1, type='text',string='UD Json'),
1603+ }
1604+
1605+ _sql_constraints = [
1606+ ('unique_msfid', 'unique(msfid)', 'msfid already exists.'),
1607+ ]
1608+ _defaults = {
1609+ 'date': lambda *a, **b: fields.datetime.now(),
1610+ }
1611+unidata_products_error()
1612
1613=== modified file 'bin/addons/product_attributes/unidata_sync.xml'
1614--- bin/addons/product_attributes/unidata_sync.xml 2023-11-28 13:36:32 +0000
1615+++ bin/addons/product_attributes/unidata_sync.xml 2024-05-13 13:47:13 +0000
1616@@ -120,11 +120,29 @@
1617 <newline />
1618 <field name="number_products_pulled" />
1619 <field name="number_products_updated" />
1620+ <field name="number_products_created" />
1621+ <field name="number_products_errors" />
1622 <field name="msfid_min" />
1623 <field name="last_date" />
1624 <field name="sync_type" />
1625 <field name="state" />
1626 <field name="error" colspan="4" />
1627+ <field name="sync_error" colspan="4" nolabel="1">
1628+ <tree string="Product Errors">
1629+ <field name="msfid" />
1630+ <field name="code" />
1631+ <field name="former_codes" />
1632+ <field name="log" />
1633+ <field name="date" />
1634+ </tree>
1635+ <form string="Product Errors">
1636+ <field name="msfid" />
1637+ <field name="code" />
1638+ <field name="former_codes" />
1639+ <field name="log" />
1640+ <field name="json_data_formated" colspan="4" widget="full_text" nolabel="1"/>
1641+ </form>
1642+ </field>
1643 </form>
1644 </field>
1645 </record>
1646@@ -139,7 +157,9 @@
1647 <field name="end_date" />
1648 <field name="page_size" />
1649 <field name="number_products_pulled" />
1650- <field name="number_products_updated" />
1651+ <field name="number_products_updated" />
1652+ <field name="number_products_created" />
1653+ <field name="number_products_errors" />
1654 <field name="sync_type" />
1655 <field name="state" />
1656 <field name="log_exists" invisible="1" />
1657@@ -708,5 +728,143 @@
1658 key2="client_action_multi"
1659 search_view_id="product_msl_rel_search"
1660 context="{'search_default_product_id': active_id}"/>
1661+
1662+ <record id="unidata_sync_log_msl_action" model="ir.actions.act_window">
1663+ <field name="name">MSL Sync report</field>
1664+ <field name="res_model">unidata.sync.log</field>
1665+ <field name="view_type">form</field>
1666+ <field name="view_mode">tree,form</field>
1667+ <field name="search_view_id" ref="unidata_sync_log_msl_search" />
1668+ <field name="view_id" ref="unidata_sync_log_msl_tree" />
1669+ <field name="domain">[('server', '=', 'msl')]</field>
1670+ </record>
1671+
1672+ <record id="unidata_products_error_action" model="ir.actions.act_window">
1673+ <field name="name">UniData linkage errors</field>
1674+ <field name="res_model">unidata.products_error</field>
1675+ <field name="view_type">form</field>
1676+ <field name="view_mode">tree,form</field>
1677+ <field name="domain">[]</field>
1678+ <field name="context">{'search_default_hidde_fixed': True}</field>
1679+ </record>
1680+
1681+ <record model="ir.ui.view" id="unidata_products_error_form">
1682+ <field name="name">unidata.products_error.form</field>
1683+ <field name="model">unidata.products_error</field>
1684+ <field name="type">form</field>
1685+ <field name="arch" type="xml">
1686+ <form string="UniData linkage error" hide_new_button="1" hide_delete_button="1" hide_duplicate_button="1">
1687+ <field name="date" />
1688+ <field name="fixed_date" />
1689+ <field name="msfid" />
1690+ <field name="uf_product_id" />
1691+ <field name="code" />
1692+ <field name="former_codes" />
1693+ <field name="log" colspan="4" nolabel="1" />
1694+ <field name="json_data_formated" widget="full_text" nolabel="1" colspan="4" />
1695+ </form>
1696+ </field>
1697+ </record>
1698+
1699+ <record model="ir.ui.view" id="unidata_products_error_tree">
1700+ <field name="name">unidata.products_error.tree</field>
1701+ <field name="model">unidata.products_error</field>
1702+ <field name="type">tree</field>
1703+ <field name="priority" eval="20" />
1704+ <field name="arch" type="xml">
1705+ <tree string="UniData linkage errors" hide_new_button="1" hide_delete_button="1" noteditable="1" colors="red:not fixed_date">
1706+ <field name="date" />
1707+ <field name="fixed_date" />
1708+ <field name="msfid" />
1709+ <field name="uf_product_id" />
1710+ <field name="code" />
1711+ <field name="former_codes" />
1712+ <field name="log" />
1713+ </tree>
1714+ </field>
1715+ </record>
1716+
1717+ <record model="ir.ui.view" id="unidata_products_error_search">
1718+ <field name="name">unidata.products_error.search</field>
1719+ <field name="model">unidata.products_error</field>
1720+ <field name="type">search</field>
1721+ <field name="arch" type="xml">
1722+ <search string="UniData linkage errors">
1723+ <filter name="hidde_fixed" string="Hide Fixed" domain="[('fixed_date', '=', False)]" icon="terp-check" />
1724+ <field name="date" />
1725+ <field name="fixed_date" />
1726+ <field name="msfid" />
1727+ <newline />
1728+ <field name="uf_product_id" />
1729+ <field name="code" />
1730+ <field name="former_codes" />
1731+ <field name="log" />
1732+ </search>
1733+ </field>
1734+ </record>
1735+ <menuitem name="UniData linkage errors" action="unidata_products_error_action" id="unidata_products_error_menu" parent="unidata_main_menu" sequence="26"/>
1736+
1737+ <record id="unidata_products_not_oc_value_action" model="ir.actions.act_window">
1738+ <field name="name">UD Products incompatible with OC values</field>
1739+ <field name="res_model">product.product</field>
1740+ <field name="view_type">form</field>
1741+ <field name="view_mode">tree,form</field>
1742+ <field name="domain">[('incompatible_oc_default_values', '=', True)]</field>
1743+ <field name="context">{}</field>
1744+ </record>
1745+
1746+ <menuitem name="UD Products incompatible with OC values" action="unidata_products_not_oc_value_action" id="unidata_products_not_oc_value_menu" parent="unidata_main_menu" sequence="27"/>
1747+
1748+ <record id="unidata_default_product_value_action" model="ir.actions.act_window">
1749+ <field name="name">OC Default values</field>
1750+ <field name="res_model">unidata.default_product_value</field>
1751+ <field name="view_type">form</field>
1752+ <field name="view_mode">tree,form</field>
1753+ <field name="domain">[]</field>
1754+ <field name="context">{}</field>
1755+ </record>
1756+
1757+ <record model="ir.ui.view" id="unidata_default_product_value_form">
1758+ <field name="name">unidata.default_product_value.form</field>
1759+ <field name="model">unidata.default_product_value</field>
1760+ <field name="type">form</field>
1761+ <field name="arch" type="xml">
1762+ <form string="OC Default Values" hide_duplicate_button="1">
1763+ <field name="nomenclature" />
1764+ <field name="field" />
1765+ <field name="value" />
1766+ </form>
1767+ </field>
1768+ </record>
1769+
1770+ <record model="ir.ui.view" id="unidata_default_product_value_tree">
1771+ <field name="name">unidata.default_product_value.tree</field>
1772+ <field name="model">unidata.default_product_value</field>
1773+ <field name="type">tree</field>
1774+ <field name="priority" eval="20" />
1775+ <field name="arch" type="xml">
1776+ <tree string="OC Default Values">
1777+ <field name="nomenclature" />
1778+ <field name="field" />
1779+ <field name="value" />
1780+ <field name="number_incompatible_products" />
1781+ <button name="open_products" string="View products" icon="gtk-go-forward" type="object" attrs="{'invisible': [('number_incompatible_products', '=', 0)]}" />
1782+ </tree>
1783+ </field>
1784+ </record>
1785+
1786+ <record model="ir.ui.view" id="unidata_default_product_value_search">
1787+ <field name="name">unidata.default_product_value.search</field>
1788+ <field name="model">unidata.default_product_value</field>
1789+ <field name="type">search</field>
1790+ <field name="arch" type="xml">
1791+ <search string="OC Default Values">
1792+ <field name="nomenclature" />
1793+ <field name="field" />
1794+ <field name="value" />
1795+ </search>
1796+ </field>
1797+ </record>
1798+ <menuitem name="OC Default Values" action="unidata_default_product_value_action" id="unidata_default_product_value_menu" parent="unidata_main_menu" sequence="27"/>
1799 </data>
1800 </openerp>
1801
1802=== added directory 'tools/UD'
1803=== added file 'tools/UD/create_sql.py'
1804--- tools/UD/create_sql.py 1970-01-01 00:00:00 +0000
1805+++ tools/UD/create_sql.py 2024-05-13 13:47:13 +0000
1806@@ -0,0 +1,99 @@
1807+#!/usr/bin/env python
1808+
1809+import psycopg2
1810+import sys
1811+import csv
1812+
1813+if len(sys.argv) < 3:
1814+ print('%s dbname oc' % sys.argv[0])
1815+ sys.exit(1)
1816+
1817+dbname = sys.argv[1]
1818+oc = sys.argv[2]
1819+
1820+if oc not in ('OCP', 'OCA', 'OCG', 'OCB'):
1821+ print('OC unknown')
1822+ sys.exit(1)
1823+
1824+db_host = '127.0.0.1'
1825+
1826+conn = psycopg2.connect(database=dbname)
1827+cr = conn.cursor()
1828+
1829+line_number = 1
1830+
1831+values_mapping = {
1832+ 'Yes': 't',
1833+ 'Kit/Module': 'kit',
1834+ 'Make to Order': 'make_to_order',
1835+ 'Service with Reception': 'service_recep',
1836+ '': '',
1837+}
1838+with open('ud_default_value.csv', 'r', newline='') as f:
1839+ c = csv.reader(f, quotechar='"', delimiter=',')
1840+ for line in c:
1841+ line_number += 1
1842+ if not line or line[1] != oc:
1843+ continue
1844+ nomen = line[3][5:].strip()
1845+ all_nom = nomen.split('|')
1846+ level = 1
1847+ parent_id = False
1848+ error = False
1849+
1850+ if line[4] not in values_mapping:
1851+ print('Line number %s, value %s not found' % (line_number, line[4]))
1852+ break
1853+ for nom in all_nom:
1854+ nom = nom.strip()[0:63]
1855+ if nom:
1856+ cond = ''
1857+ params = [nom.strip(), level]
1858+ if parent_id:
1859+ cond = ' and n.parent_id in %s'
1860+ params.append(tuple(parent_id))
1861+ params[0] = '%%%s' % nom
1862+
1863+ cr.execute("""
1864+ select
1865+ n.id
1866+ from
1867+ product_nomenclature n
1868+ left join ir_translation t on t.lang='en_MF' and t.name='product.nomenclature,name' and t.res_id = n.id
1869+ where
1870+ coalesce(t.value, n.name) like %s and
1871+ n.level=%s
1872+ """+cond, tuple(params)) # not_a_user_entry
1873+ if not cr.rowcount:
1874+ print(all_nom)
1875+ print(str(cr.mogrify("""
1876+ select
1877+ n.id
1878+ from
1879+ product_nomenclature n
1880+ left join ir_translation t on t.lang='en_MF' and t.name='product.nomenclature,name' and t.res_id = n.id
1881+ where
1882+ coalesce(t.value, n.name) like %s and
1883+ n.level=%s
1884+ """+cond, tuple(params)), 'utf8'))
1885+ print('Line number %s, nomen %s not found' % (line_number, nom))
1886+ error = True
1887+ break
1888+ parent_id = [x[0] for x in cr.fetchall()]
1889+ level += 1
1890+ if error:
1891+ continue
1892+ for n_id in parent_id:
1893+ query = "insert into unidata_default_product_value (field, value, nomenclature) values (%s, %s, %s);"
1894+ values = (line[2], values_mapping[line[4]], n_id)
1895+ print('-- %s' %line)
1896+ print(str(cr.mogrify(query, values), 'utf8'))
1897+ cr.execute(query, values)
1898+
1899+query = "update product_cold_chain set ud_code=code;"
1900+print(query)
1901+cr.execute(query)
1902+query = "update product_cold_chain set ud_code='CT3+' where id in (select res_id from ir_model_data where name='product_attributes_cold_20');"
1903+print(query)
1904+cr.execute(query)
1905+conn.commit()

Subscribers

People subscribed via source and target branches