1
##############################################################################
3
# Copyright (c) 2005-2006 TINY SPRL. (http://tiny.be) All Rights Reserved.
5
# $Id: wizard_edi_import.py 1825 2005-12-13 11:04:20Z ede $
7
# WARNING: This program as such is intended to be used by professional
8
# programmers who take the whole responsability of assessing all potential
9
# consequences resulting from its eventual inadequacies and bugs
10
# End users who are looking for a ready-to-use solution with commercial
11
# garantees and support are strongly adviced to contract a Free Software
14
# This program is Free Software; you can redistribute it and/or
15
# modify it under the terms of the GNU General Public License
16
# as published by the Free Software Foundation; either version 2
17
# of the License, or (at your option) any later version.
19
# This program is distributed in the hope that it will be useful,
20
# but WITHOUT ANY WARRANTY; without even the implied warranty of
21
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22
# GNU General Public License for more details.
24
# You should have received a copy of the GNU General Public License
25
# along with this program; if not, write to the Free Software
26
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
28
##############################################################################
33
from edi_exchange import edi_exchange
39
_import_form = '''<?xml version="1.0"?>
40
<form string="EDI file import">
41
<separator string="Import the following files" colspan="4" />
42
<field name="ediimportdir" colspan="4" />
43
<field name="current" />
44
<field name="error" />
47
_import_fields = { 'ediimportdir' : { 'string' : 'EDI Import Dir',
50
'default' : lambda *a: '/edi/reception',
53
'current' : { 'string' : 'Current',
55
'default' : lambda *a: True,
57
'error' : { 'string' : 'Error',
59
'default' : lambda *a: False,
63
_import_done_form = '''<?xml version="1.0"?>
64
<form string="EDI file imported">
65
<separator string="EDI file imported" colspan="4" />
68
_import_done_fields = {}
70
def _child_of_partner(cr, uid, child, parent):
73
direct_parent_list=pooler.get_pool(cr.dbname).get('res.partner').read(cr, uid, [child], ['parent_id'])
74
#print str(direct_parent_list)
75
if len(direct_parent_list)!=1:
77
if not direct_parent_list[0]['parent_id']:
79
direct_parent=direct_parent_list[0]['parent_id'][0]
80
if direct_parent and direct_parent!='':
81
return _child_of_partner( cr, uid, direct_parent, parent)
85
def _prepare_import(self, cr, uid, data, context):
87
for (type, dir) in [('current', 'encours'), ('error', 'erreur')]:
88
if (data['form'][type]):
89
edi_path = os.path.join(data['form']['ediimportdir'], dir)
90
if not os.path.isdir(edi_path):
92
for file in os.listdir(edi_path):
94
if file[:9]=="COMMANDE.":
95
files.append((file, os.path.join(data['form']['ediimportdir'], dir, file)))
96
for (filename, file) in files:
97
#print "Importing %s" % filename
100
sr = {'sender':None, 'receiver':None}
102
log_id=pooler.get_pool(cr.dbname).get('edi.log').create(cr, uid, {})
103
logline=pooler.get_pool(cr.dbname).get('edi.log.line')
104
logline.create(cr, uid, {
106
'logdesc': "Checking %s" % filename,
112
line_type, line_content = edi_exchange.parse_line(line)
113
if line_type == "HINT":
115
sale_orders.append(sale_order_o)
117
sale_order_o = sale_order(cr, uid, sr)
118
elif line_type == "HPTY":
121
elif line_type == "DART":
124
sale_order_o.parse_line(line_type, line_content, status)
126
sale_orders.append(sale_order_o)
131
for (order_id, order_timestamp, sender, message) in status.messages:
132
logline.create(cr, uid, { 'log_id': log_id,
133
'logdesc': "\t%s" % message,
134
'timestamp': order_timestamp,
135
'order_num': order_id
137
logline.create(cr, uid, { 'log_id': log_id,
138
'logdesc': "Messages:%s\tErrors:%s\tWarnings:%s" % (len(status.messages), status.error_count, status.warning_count),
140
logline.create(cr, uid, { 'log_id': log_id,
141
'logdesc': "Finished Checking %s" % filename,
144
if status.error_count==0:
145
print "Integrating %s" % filename
146
logline.create(cr, uid, { 'log_id': log_id,
147
'logdesc': "Importing %s" % filename,
149
for order in sale_orders:
151
logline.create(cr, uid, { 'log_id': log_id,
152
'logdesc': "Import Finished",
154
if not os.path.isdir(os.path.join(data['form']['ediimportdir'],'archive')):
155
os.mkdir(os.path.join(data['form']['ediimportdir'],'archive'))
156
os.rename(file, os.path.join(data['form']['ediimportdir'],'archive', filename))
157
logline.create(cr, uid, { 'log_id': log_id,
158
'logdesc': "Moved %s to archive" % filename,
161
ids=pooler.get_pool(cr.dbname).get('res.users').search(cr,1, [('roles_id', 'ilike', 'EDI')])
163
pooler.get_pool(cr.dbname).get('res.request').create(cr, uid, { 'act_to': id,
164
'name' : "Error while importing %s" % filename,
165
'ref_doc1' : "edi.log,%s" % log_id,
169
os.rename(file, os.path.join(data['form']['ediimportdir'],'erreur', filename))
170
logline.create(cr, uid, { 'log_id': log_id,
171
'logdesc': "Moved %s to erreur" % filename,
174
logline.create(cr, uid, { 'log_id': log_id,
175
'logdesc': "Couldn't move %s to erreur" % filename,
181
def _do_import(self, cr, uid, data, context):
184
class wiz_edi_import(wizard.interface):
188
'result' : { 'type' : 'form', 'arch' : _import_form, 'fields' : _import_fields, 'state' : [('end', 'Cancel'),('check', 'Check EDI')]},
191
'actions' : [ _prepare_import ],
192
# 'result' : { 'type' : 'print', 'report' : 'edi.import-results', 'state' : [('do_import', 'Import EDI'), ('end', 'End')] },
193
'result' : { 'type' : 'form', 'arch' : _import_done_form, 'fields': _import_done_fields, 'state' : [('end','end')]},
196
'actions' : [ _do_import ],
197
'result' : { 'type' : 'form', 'arch' : _import_done_form, 'fields' : _import_done_fields, 'state' : [('end','end')]},
203
def __init__(self, cr, uid, sr):
206
self.shop_id=pooler.get_pool(cr.dbname).get('sale.shop').search(cr, uid, [])[0]
207
self.pricelist_id = None
209
self.partner_id=sr['sender']
210
self.partner_order_id=0
211
self.partner_invoice_id=0
213
self.timesatmp_edi=time.strftime('%Y%m%d%H%M')
215
self.deliverdate=None
219
if not hasattr(self, 'partner_invoice_id'):
220
self.partner_invoice_id = self.partner_id
221
if not hasattr(self, 'partner_shipping_id'):
222
self.partner_shipping_id = self.partner_invoice_id
223
order_id = pooler.get_pool(cr.dbname).get('sale.order').create(self.cr, self.uid, { 'partner_id': self.partner_id,
224
'partner_order_id': self.partner_order_id,
225
'partner_invoice_id': self.partner_invoice_id,
226
'partner_shipping_id': self.partner_shipping_id,
227
'shop_id': self.shop_id,
228
'pricelist_id': self.pricelist_id,
229
'client_order_ref': self.ordernum,
230
'date_order': self.orderdate,
233
for orderline in self.order_lines:
234
orderline.store(order_id)
236
def addline(self, line):
237
self.orderlines.append(line)
239
def parse_line(self, line_type, line_content, status):
240
if hasattr(self, "_parse_%s" % line_type):
241
myFct=getattr(self, "_parse_%s" % line_type)
242
myFct(line_content, status)
244
status.add_warning("ignoring line type: %s" % line_type, self.ordernum, self.timestamp_edi, self.sr['sender'])
246
def _parse_HINT(self, line_content, status):
247
self.timestamp_edi="%s%s" % (line_content["date"], line_content["time"])
248
self.sr['sender'] = line_content['sender']
250
partners=pooler.get_pool(cr.dbname).get('res.partner').search(self.cr, self.uid, [('ean13','=',line_content["receiver"]),])
251
if len(partners) != 1:
252
status.add_error("unknown receiver: %s" % line_content["receiver"], self.ordernum, self.timestamp_edi, self.sr['sender'])
254
self.sr['receiver']=partners[0]
255
thisadd=pooler.get_pool(cr.dbname).get('res.users').read(self.cr, self.uid, [self.uid], ['address_id'])[0]['address_id'][0]
256
partner=pooler.get_pool(cr.dbname).get('res.partner.address').read(self.cr, self.uid, [thisadd], ['partner_id'])[0]['partner_id'][0]
257
if not partner or partner!=self.sr['receiver']:
258
status.add_error("This message is not for us (%s)" % line_content["receiver"], self.ordernum, self.timestamp_edi, self.sr['sender'])
260
def _parse_HGEN(self, line_content, status):
261
if line_content["order-num"]=='':
262
status.add_error("No client order reference", self.ordernum, self.timestamp_edi, self.sr['sender'])
263
self.ordernum=line_content["order-num"]
264
self.orderdate=line_content["order-date"]
265
if line_content["message-type"]!="ORDERS93A" and line_content["message-type"]!="ORDERS96A" and line_content["message-type"]!="GENCOD02303" and line_content["message-type"]!="GENCOD08604":
266
status.add_error("Unknown message type %s" % line_content["message-type"], self.ordernum, self.timestamp_edi, self.sr['sender'])
268
def _parse_HDEL(self, line_content, status):
269
if (not self.deliverdate or self.deliverdate > line_content['deliv-date']) and line_content['deliv-q'] in ('137', '200'):
270
self.deliverdate = line_content['deliv-date']
271
if self.deliverdate < self.orderdate:
272
status.add_error("%s (order date) is after %s (delivery date)" % (self.orderdate,self.deliverdate), self.ordernum, self.timestamp_edi, self.sr['sender'])
274
def _parse_HPTY(self, line_content, status):
275
partner_table = pooler.get_pool(cr.dbname).get('res.partner')
277
partners = partner_table.search(self.cr, self.uid, [('ean13', '=', line_content['partner-code'])])
278
if partners and len(partners) == 1:
279
default_addresses = pooler.get_pool(cr.dbname).get('res.partner.address').search(self.cr, self.uid, [('partner_id', '=', partners[0]), ('type', 'ilike', 'default')])
280
if not default_addresses:
281
default_addresses = pooler.get_pool(cr.dbname).get('res.partner.address').search(self.cr, self.uid, [('partner_id', '=', partners[0]), ('type', 'ilike', '')])
282
if not default_addresses:
283
default_addresses = pooler.get_pool(cr.dbname).get('res.partner.address').search(self.cr, self.uid, [('partner_id', '=', partners[0])])
284
self.hpty_dispatchers[line_content['partner-type']](self, partners[0], default_addresses, line_content, status)
286
status.add_error("unknown %s: %s" % (line_content["partner-type"], line_content["partner-code"]), self.ordernum, self.timestamp_edi, self.sr['sender'])
288
def _parse_HPTYBY(self, partner, default_addresses, line_content, status):
289
partner_table = pooler.get_pool(cr.dbname).get('res.partner')
291
self.sr['sender'] = partner
292
self.partner_id = partner
293
self.partner_order_id = default_addresses[0]
294
self.pricelist_id = ir.ir_get(self.cr, self.uid, 'meta', 'product.pricelist', [('res.partner', self.partner_id)])[0][2]
295
orders=pooler.get_pool(cr.dbname).get("sale.order").search(self.cr, self.uid, [('client_order_ref', 'ilike', self.ordernum), ('partner_order_id',"=",self.partner_order_id)])
296
if orders and len(orders)>0:
297
status.add_warning("This client order reference (%s) already exists for this client" % self.ordernum, self.ordernum, self.timestamp_edi, self.sr['sender'])
299
def _parse_HPTYSU(self, partner, default_addresses, line_content, status):
300
partner_table = pooler.get_pool(cr.dbname).get('res.partner')
301
if not (partner_table._is_related_to(self.cr, self.uid, [partner], self.sr['receiver'])[0] or self.sr['receiver'] == partner):
302
status.add_error("unknown %s: %s" % (line_content["partner-type"], line_content["partner-code"]), self.ordernum, self.timestamp_edi, self.sr['sender'])
304
def _parse_HPTYDP(self, partner, default_addresses, line_content, status):
305
shipping_addresses=pooler.get_pool(cr.dbname).get('res.partner.address').search(self.cr, self.uid, [('partner_id','=',partner), ('type', 'ilike', 'delivery')])
306
if len(shipping_addresses) < 1:
307
self.partner_shipping_id=default_addresses[0]
309
self.partner_shipping_id=shipping_addresses[0]
311
invoice_addresses=pooler.get_pool(cr.dbname).get('res.partner.address').search(self.cr, self.uid, [('partner_id','=',partner), ('type', 'ilike', 'invoice')])
312
if len(invoice_addresses) < 1:
313
self.partner_invoice_id=default_addresses[0]
315
self.partner_invoice_id=invoice_addresses[0]
317
def _parse_HPTYIV(self, partner, default_addresses, line_content, status):
318
invoice_addresses=pooler.get_pool(cr.dbname).get('res.partner.address').search(self.cr, self.uid, [('partner_id','=',partner), ('type', 'ilike', 'invoice')])
319
if len(invoice_addresses) < 1:
320
self.partner_invoice_id=default_addresses[0]
322
self.partner_invoice_id=invoice_addresses[0]
324
hpty_dispatchers = { 'BY' : _parse_HPTYBY, 'SU' : _parse_HPTYSU, 'DP' : _parse_HPTYDP, 'IV' : _parse_HPTYIV }
326
def _parse_HFTX(self, line_content, status):
327
self.note+=line_content['text']+'\n'
329
def _parse_DART(self, line_content, status):
330
products=pooler.get_pool(cr.dbname).get('product.product').search(self.cr, self.uid, [('ean13','=',line_content["barcode"]),])
331
#sale_order_line_o=sale_order_line(self.cr, self.uid, self.deliverdate, self.partner_invoice_id, status)
332
sale_order_line_o=sale_order_line(self.cr, self.uid, self, self.deliverdate)
333
if len(products) != 1:
334
status.add_error("unknown product: %s" % line_content["barcode"], self.ordernum, self.timestamp_edi, self.sr['sender'])
337
sale_order_line_o.product=products[0]
338
sale_order_line_o.product_ean=line_content["barcode"]
339
if (line_content["unit21"]==''):
340
status.add_warning("Using default Unit Of Measure", self.ordernum, self.timestamp_edi, self.sr['sender'])
342
uoms=pooler.get_pool(cr.dbname).get('product.uom').search(self.cr, self.uid, [('name', 'ilike', line_content["unit21"]),])
344
status.add_error("unknown uom: %s" % line_content["unit21"], self.ordernum, self.timestamp_edi, self.sr['sender'])
347
sale_order_line_o.uom=uoms[0]
348
sale_order_line_o.quantity=float(line_content["quantity21"])
349
sale_order_line_o.uoc_quantity=float(line_content["quantity59"])
350
sale_order_line_o.lineid=line_content["line-num"]
351
sale_order_line_o.partner_address=None
352
sale_order_line_o.price=line_content["price"]
353
if sale_order_line_o.partner==0:
354
partner=self.partner_id
356
partner=sale_order_line_o.partner
357
pricelist_id = ir.ir_get(self.cr, self.uid, 'meta', 'product.pricelist', [('res.partner', partner)])[0][2]
358
sale_order_line_o.price = pooler.get_pool(cr.dbname).get('product.pricelist').price_get(self.cr, self.uid, [pricelist_id], sale_order_line_o.product, sale_order_line_o.quantity)[pricelist_id]
359
sale_order_line_o.pricelist_id=pricelist_id
360
if float(line_content["price"])!=sale_order_line_o.price:
361
status.add_warning("Price from EDI (%s) different from what we have (%s) for product %s" % (str(float(line_content["price"])), sale_order_line_o.price, line_content["barcode"]), self.ordernum, self.timestamp_edi, self.sr['sender'])
362
product_infos = pooler.get_pool(cr.dbname).get('product.product').read(self.cr, self.uid, [sale_order_line_o.product])[0]
363
if line_content['price-unit']=="":
364
status.add_warning("Blank Unit Of Price for product %s should be %s" % (line_content['barcode'], product_infos['uos_id'][1]), self.ordernum, self.timestamp_edi, self.sr['sender'])
365
sale_order_line_o.price_unit= product_infos['uos_id'][0]
366
elif product_infos['uos_id'][1] != line_content['price-unit']:
367
status.add_error('Invalid Unit Of Price for product %s Should be "%s" instead of "%s"' % (line_content['barcode'], product_infos['uos_id'][1], line_content["price-unit"]), self.ordernum, self.timestamp_edi, self.sr['sender'])
369
sale_order_line_o.price_unit= product_infos['uos_id'][0]
370
sale_order_line_o.price_unit_customer=float(line_content['hint-price'])
371
sale_order_line_o.check(status)
372
self.order_lines.append(sale_order_line_o)
374
def _parse_DDEL(self, line_content, status):
375
sale_order_line_o = self.order_lines[len(self.order_lines)-1]
376
sale_order_line_o.deliv_date = "%s%s" % (line_content["deliv-date"], line_content["deliv-time"])
378
def _parse_DPTY(self, line_content, status):
379
if len(self.order_lines)<1:
380
status.add_error("no DART line parsed before this DPTY line", self.ordernum, self.timestamp_edi, self.sr['sender'])
382
sale_order_line_o = self.order_lines[len(self.order_lines)-1]
383
partners=pooler.get_pool(cr.dbname).get('res.partner').search(self.cr, self.uid, [('ean13','=',line_content["shop-barcode"]),])
384
if len(partners) != 1:
385
status.add_error("unknown address: %s" % line_content["shop-barcode"], self.ordernum, self.timestamp_edi, self.sr['sender'])
386
elif not _child_of_partner(self.cr, self.uid, partners[0], self.sr['sender']):
387
status.add_error("unknown address: %s" % line_content["shop-barcode"], self.ordernum, self.timestamp_edi, self.sr['sender'])
389
sale_order_line_o.partner_address=partners[0]
391
def _parse_DPID(self, line_content, status):
392
if len(self.order_lines)<1:
393
status.add_error("no DART line parsed before this DPTY line", self.ordernum, self.timestamp_edi, self.sr['sender'])
395
sale_order_line_o = self.order_lines[len(self.order_lines)-1]
396
sale_order_line_o.note+=line_content['ident-art']+'\n'
398
def _parse_DFTX(self, line_content, status):
399
if len(self.order_lines)<1:
400
status.add_error("no DART line parsed before this DPTY line", self.ordernum, self.timestamp_edi, self.sr['sender'])
402
sale_order_line_o = self.order_lines[len(self.order_lines)-1]
403
sale_order_line_o.note+=line_content['text']+'\n'
406
class sale_order_line:
408
def __init__(self, cr, uid, sale_order_o, deliv_date):
409
#, partner_address, status):
413
self.partner_address=None
415
self.deliv_date=deliv_date
418
self.sale_order= sale_order_o
420
if sale_order_o.partner_invoice_id!=0:
421
self.partner=pooler.get_pool(cr.dbname).get('res.partner.address').read(self.cr, self.uid, [sale_order_o.partner_invoice_id], ['partner_id'])[0]['partner_id'][0]
426
def check(self, status):
427
self.cr.execute('select id from product_packaging where product_id=%d and qty=%f limit 1', (self.product, float(self.uoc_quantity)))
428
packs = self.cr.fetchone()
429
if packs is None or not len(packs):
430
status.add_error('Invalid package for product %s (%s)' % (self.product_ean, float(self.uoc_quantity)), self.sale_order.ordernum, self.sale_order.timestamp_edi, self.sale_order.sr['sender'])
432
self.pack_id=packs[0]
434
#print "PriceList: %s, product: %s, quantity: %s " % (self.pricelist_id, self.product, self.quantity)
436
dico=pooler.get_pool(cr.dbname).get('sale.order.line').product_id_change(self.cr, self.uid, [], self.pricelist_id, self.product, int(float(self.quantity)))
437
self.insertdict.update({'product_uos_qty': dico['value']['product_uos_qty'], 'product_uos': dico['value']['product_uos'][0], 'price_unit': dico['value']['price_unit']})
439
status.add_error('No price defined for product %s, line ommited !' % (self.product_ean,), self.sale_order.ordernum, self.sale_order.timestamp_edi, self.sale_order.sr['sender'])
441
# Checking the unit used for the price computation
442
# product_infos = pooler.get_pool(cr.dbname).get('product.product').read(self.cr, self.uid, [self.product])[0]
443
# if product_infos['uos_id'][1] != self.price_unit:
444
# status.add_warning('Invalid unit for Sale price : Should be "%s"' % product_infos['uos_id'][1], self.sale_order.ordernum, self.sale_order.timestamp_edi, self.sale_order.sr['sender'])
448
# if unit_price != self.price:
449
# status.add_warning('Invalid price', self.sale_order.ordernum, self.sale_order.timestamp_edi, self.sale_order.sr['sender'])
451
def store (self, order_id):
452
insertdict.update( { 'order_id': order_id,
453
'product_id': self.product,
454
'product_uom_qty': self.quantity,
455
'name': "%s%s" %(order_id, self.lineid),
456
'address_allotment_id': self.partner_address,
458
'product_packaging': self.pack_id,
459
# 'unit_price': self.price_unit,
460
'price_unit': self.price,
461
'price_unit_customer': self.price_unit_customer,
463
self.unit_price=self.price
464
if hasattr(self, 'uom'):
465
insertdict['product_uom'] = self.uom
467
insertdict['product_uom'], desc = pooler.get_pool(cr.dbname).get('product.product').read(self.cr, self.uid, [self.product], ['uom_id'])[0]['uom_id']
468
if hasattr(self, 'deliv_date'):
469
insertdict['date_planned'] = self.deliv_date
470
id=pooler.get_pool(cr.dbname).get('sale.order.line').create(self.cr, self.uid, insertdict)
479
def add_error(self, message, order_id, timestamp_edi, sender):
480
self.messages.append((order_id, timestamp_edi, sender, "ERROR:\t%s" % message))
483
def add_warning(self, message, order_id, timestamp_edi, sender):
484
self.messages.append((order_id, timestamp_edi, sender, "WARNING:\t%s" % message))
485
self.warning_count+=1
487
wiz_edi_import('edi.import')