def get_ls_kb_data(conditions, additional_conditions=None, limit=None, header_only=False, is_lieferschein=True): """Lieferscheindaten oder Kommsissionierbelegdaten entsprechend dem Lieferungsprotokoll. Wenn is_lieferschein = False, dann werden Kommiauftragsdaten zurückgebeben (Kommimengen) """ cachingtime = 60 * 60 * 12 if additional_conditions: conditions.extend(additional_conditions) condition = " AND ".join(conditions) koepfe = {} auftragsnr2satznr = {} satznr2auftragsnr = {} # Lieferscheinkopf JOIN Kundenadresse um die Anzahl der Queries zu minimieren # JOIN Lieferadresse geht nicht, weil wir "ADAART=1" mit DB2/400 nicht klappt for row in query(['ALK00'], ordering=['LKSANK DESC'], condition=condition, limit=limit, joins=[('XKD00', 'LKKDNR', 'KDKDNR'), ('AAK00', 'LKAUFS', 'AKAUFN')], cachingtime=cachingtime, ua='husoftm2.lieferscheine'): kopf = dict(positionen=[], auftragsnr="SO%s" % row['auftragsnr'], auftragsnr_kunde=row['auftragsnr_kunde'], erfassung=row['ALK_erfassung'], aenderung=row.get('ALK_aenderung'), anliefer_date=row['anliefer_date'], kundennr="SC%s" % row['rechnungsempfaenger'], lieferadresse=dict(kundennr="SC%s" % row['warenempfaenger']), anlieferdatum=row['anliefer_date'], lager="LG%03d" % int(row['lager']), kommiauftragnr="KA%s" % row['kommibelegnr'], kommiauftrag_datum=row['kommibeleg_date'], lieferscheinnr="SL%s" % row['lieferscheinnr'], name1=row.get('name1', ''), name2=row.get('name2', ''), name3=row.get('name3', ''), strasse=row.get('strasse', ''), land=husoftm2.tools.land2iso(row['laenderkennzeichen']), plz=row.get('plz', ''), ort=row.get('ort', ''), tel=row.get('tel', ''), fax=row.get('fax', ''), art=row.get('art', ''), softm_created_at=row.get('ALK_lieferschein'), ) # Wir hatten erhebliche Probleme, weil datumsfelder mal befüllt waren # und mal nicht (race condition) - wir gehen deswegen recht großzügig # bei der suceh nach einem geeigenten feld vor. for fieldname in ['ALK_lieferschein', 'ALK_aenderung']: if row.get(fieldname): kopf['datum'] = row.get(fieldname) break if not kopf.get('datum'): raise RuntimeError("Konnte kein Datum ermitteln %r", row) pos_key = remove_prefix((row['satznr']), 'SO') if row.get('bezogener_kopf'): pos_key = remove_prefix(row['bezogener_kopf'], 'SO') auftragsnr2satznr[remove_prefix(row['auftragsnr'], 'SO')] = pos_key satznr2auftragsnr[pos_key] = remove_prefix(row['auftragsnr'], 'SO') if row['ALK00_dfsl']: # Basis dieses Codes: # [LH #721] LS mit 0-mengen vermeiden # Wir sehen im Produktiv-Betrieb immer wieder Lieferscheine mit der Menge "0" # erzeugt werden. Wir vermuten hier eine race Condition, bei der die # ALK00 schon mit der Lieferscheinnummer geupdated ist, die ALN00 aber noch # nicht mit der Lieferscheinmenge. # Eine weitere Vermutung ist, dass wenn in der ALN00 die Menge noch cniht eingetragen # ist, dort auch noch die Lieferscheinnummer fehlt. Das versuchen wir hier abzufangen. # Lieber ein Crash, als ein Lieferschein mit (unbegründerter) 0-menge. raise RuntimeError("Dateiführungsschlüssel in ALK00: %r" % kopf) koepfe[pos_key] = kopf if header_only: return koepfe.values() satznr = koepfe.keys() allauftrnr = koepfe.keys() # Alle texte einlesen postexte, kopftexte, posdaten, kopfdaten = txt_auslesen([satznr2auftragsnr[x] for x in allauftrnr]) while satznr: # In 50er Schritten Auftragspositionen & Texte lesen und den 50 Aufträgen zuordnen batch = satznr[:50] satznr = satznr[50:] # Abweichende Lieferadressen condition = "ADAART=1 AND ADRGNR IN (%s) AND ADRGNR=AKAUFN" % ','.join([str(satznr2auftragsnr[x]) for x in batch]) for row in query(['XAD00', 'AAK00'], cachingtime=cachingtime, ua='husoftm2.lieferscheine', condition=condition): aktsatznr = auftragsnr2satznr[row['nr']] koepfe[aktsatznr]['lieferadresse'].update(dict(name1=row['name1'], name2=row['name2'], name3=row['name3'], strasse=row['strasse'], land=husoftm2.tools.land2iso(row['laenderkennzeichen']), plz=row['plz'], ort=row['ort'])) versandadressnr = row['versandadressnr'] warenempfaenger = koepfe[aktsatznr]['lieferadresse']['kundennr'] if versandadressnr: warenempfaenger = "%s.%03d" % (warenempfaenger, versandadressnr) koepfe[aktsatznr]['lieferadresse']['warenempfaenger'] = warenempfaenger # Positionen & Positionstexte zuordnen for row in query(['ALN00'], condition="LNSTAT<>'X' AND LNSANK IN (%s)" % ','.join([str(x) for x in batch]), cachingtime=cachingtime, ua='husoftm2.lieferscheine'): if is_lieferschein == True: lsmenge = int(row['menge']) if row['ALN00_dfsl']: # Basis dieses Codes: # [LH #721] LS mit 0-mengen vermeiden # Wir sehen im Produktiv-Betrieb immer wieder Lieferscheine mit der Menge "0" # erzeugt werden. Wir vermuten hier eine race Condition, bei der die # ALK00 schon mit der Lieferscheinnummer geupdated ist, die ALN00 aber noch # nicht mit der Lieferscheinmenge. # Eine weitere Vermutung ist, dass wenn in der ALN00 die Menge noch cniht eingetragen # ist, dort auch noch die Lieferscheinnummer fehlt. Das versuchen wir hier abzufangen. # Lieber ein Crash, als ein Lieferschein mit (unbegründerter) 0-menge. raise RuntimeError("Dateiführungsschlüssel in ALN00: %r" % row) else: lsmenge = int(row['menge_komissionierbeleg']) pos = dict(artnr=row['artnr'], guid='%s-%03d-%03d' % (row['kommibelegnr'], row['auftrags_position'], row['kommibeleg_position']), menge=lsmenge) texte = postexte.get(remove_prefix(row['auftragsnr'], 'SO'), {}).get(row['auftrags_position'], []) pos['infotext_kunde'] = texte if 'guid' in posdaten.get(remove_prefix(row['auftragsnr'], 'SO'), {}): pos['auftragpos_guid'] = posdaten.get(remove_prefix(row['auftragsnr'], 'SO'), {}).get('guid') else: pos['auftragpos_guid'] = "%s-%03d" % (row['auftragsnr'], row['auftrags_position']) lieferung = koepfe[remove_prefix(row['satznr_kopf'], 'SO')] lieferung['positionen'].append(pos) # *Sachbearbeiter* ist der, der den Vorgang tatsächlich bearbeitet hat. *Betreuer* ist # die (oder der), die für den Kunden zusändig ist. lieferung['sachbearbeiter'] = husoftm2.sachbearbeiter.resolve(row['sachbearbeiter_bearbeitung']) # Kopftexte zuordnen for auftragsnr, texte in kopftexte.items(): pos_key = auftragsnr2satznr[remove_prefix(auftragsnr, 'SO')] koepfe[pos_key]['infotext_kunde'] = texte for auftragsnr, werte in kopfdaten.items(): if 'guid' in werte: pos_key = auftragsnr2satznr[remove_prefix(auftragsnr, 'SO')] koepfe[pos_key]['guid_auftrag'] = werte['guid'] for aktsatznr in koepfe.keys(): # Entfernt Konstrukte wie das: # "kundennr": "SC19971", # "lieferadresse": { # "kundennr": "SC19971" # } if len(koepfe[aktsatznr]['lieferadresse']) == 1: if koepfe[aktsatznr]['lieferadresse']['kundennr'] == koepfe[aktsatznr]['kundennr']: del(koepfe[aktsatznr]['lieferadresse']) return koepfe.values()
def _auftraege(additional_conditions=None, addtables=None, mindate=None, maxdate=None, limit=None, header_only=False, canceled=False): """ Alle Aufträge ermitteln `additional_conditions` kann eine Liste von SQL-Bedingungen enthalten, die die Auftragssuche einschränken. `mindate` & `maxdate` können den Anliefertermin einschränken. `limit` kann die Zahl der zurückgelieferten Aufträge einschraenken. Dabei werden groessere Auftragsnummern zuerst zurueck gegeben. `header_only` ruft nur Auftragsköpfe ab und ist bedeutend schneller `canceled` wenn True, werden auch stornierte Aufträge zurück gegeben. Rückgabewert sind dicts nach dem Lieferungprotokoll. Wenn header_only == True, werden nur Auftragsköpfe zurück gegeben, was deutlich schneller ist. """ # Solange der Client das nciht gesondert verlangt, werden stornierte Aufträge ignoriert. if not canceled: conditions = ["AKSTAT<>'X'"] else: conditions = [] # Anliefertermin ist ein Range if mindate and maxdate: conditions.append("AKDTER BETWEEN %s AND %s" % (date2softm(mindate), date2softm(maxdate))) # Anliefertermin ist nach unten begrenzt elif mindate: conditions.append("AKDTER > %s" % date2softm(mindate)) # Anliefertermin ist nach oben begrenzt elif maxdate: conditions.append("AKDTER < %s" % date2softm(maxdate)) # vom Aufrufer direkt angegebenen, weitere SQL Bedingungen zufügen. Diese werden mit `AND` verkettet. if additional_conditions: conditions.extend(additional_conditions) condition = " AND ".join(conditions) koepfe = {} kopftexte = {} auftragsnr_to_lieferadresse_kdnr = {} if addtables is None: addtables = [] # Köpfe und Adressen einlesen for kopf in query(['AAK00'] + addtables, ordering=['AKAUFN DESC'], condition=condition, joins=[('XKD00', 'AKKDNR', 'KDKDNR')], limit=limit, ua='husoftm2.auftraege'): d = dict(kundennr="SC%s" % kopf['kundennr_warenempf'], kundennr_rechnung="SC%s" % kopf['kundennr_rechnungsempf'], name1=kopf['name1'], name2=kopf['name2'], name3=kopf['name3'], strasse=kopf['strasse'], land=husoftm2.tools.land2iso(kopf['laenderkennzeichen']), plz=kopf['plz'], ort=kopf['ort'], auftragsnr="SO%s" % kopf['auftragsnr'], auftragsnr_kunde=kopf['auftragsnr_kunde'], erfassung=kopf.get('AAK_erfassung'), aenderung=kopf.get('AAK_aenderung', None), sachbearbeiter=husoftm2.sachbearbeiter.resolve(kopf['sachbearbeiter']), anliefertermin=kopf['liefer_date'], teillieferung_erlaubt=(kopf['teillieferung_erlaubt'] == 1), # TODO: md: ich denke, "erledigt" ist ein Auftrag auch, wenn er storneirt wurde, # oder wenn alle Positionen auf voll_ausgeliefert stehen. erledigt=(kopf['voll_ausgeliefert'] == 1), positionen=[], art=kopf['art'], storniert=(kopf['AAK_status'] == 'X')) koepfe[kopf['auftragsnr']] = d # Auftrag geht an die 'normale' Lieferadresse: Kein .\d\d\d-Suffix an die `lieferadresse.kundennr` if kopf['versandadressnr'] == 0: auftragsnr_to_lieferadresse_kdnr[kopf['auftragsnr']] = add_prefix(kopf['kundennr_warenempf'], 'SC') # Auftrag geht an eine abweichende Lieferadresse: .00?-Suffix an die `lieferadresse.kundennr` hängen. else: lieferadresse_kdnr = add_prefix("%s.%03d" % (kopf['kundennr_warenempf'], kopf['versandadressnr']), "SC") auftragsnr_to_lieferadresse_kdnr[kopf['auftragsnr']] = lieferadresse_kdnr if header_only: return koepfe.values() allauftrnr = koepfe.keys() # Texte auslesen # Die dritte und vierte Position des Werts von txt_auslesen sind posdaten und kopfdaten. # Es handelt sich dabei wohl um Texte, die nicht angedruckt werden sollen. # Bis auf weiteres werden diese hier ignoriert. postexte, kopftexte, _, _ = txt_auslesen(allauftrnr) while allauftrnr: # In 50er Schritten Auftragspositionen lesen und den 50 Aufträgen zuordnen batch = allauftrnr[:50] allauftrnr = allauftrnr[50:] # Abweichende Lieferadressen for row in query(['XAD00'], ua='husoftm2.auftraege', condition="ADAART=1 AND ADRGNR IN (%s)" % ','.join([str(x) for x in batch])): koepfe[row['nr']]['lieferadresse'] = dict(name1=row['name1'], name2=row['name2'], name3=row['name3'], strasse=row['strasse'], land=husoftm2.tools.land2iso(row['laenderkennzeichen']), plz=row['plz'], kundennr=auftragsnr_to_lieferadresse_kdnr[row['nr']], ort=row['ort']) # Positionen einlesen if not canceled: poscondition = "APSTAT<>'X' AND APAUFN IN (%s)" % ','.join([str(x) for x in batch]) else: poscondition = "APAUFN IN (%s)" % ','.join([str(x) for x in batch]) for row in query(['AAP00'], condition=poscondition, ua='husoftm2.auftraege'): d = dict(menge=int(row['bestellmenge']), lager="LG%s" % row['lager'], artnr=row['artnr'], liefer_date=row['liefer_date'], menge_offen=int(row['menge_offen']), fakturierte_menge=int(row['fakturierte_menge']), erledigt=(row['voll_ausgeliefert'] == 1), storniert=(row['AAP_status'] == 'X'), posnr=int(row['position']), _aenderung=row.get('AAP_aenderung'), _erfassung=row.get('AAP_erfassung'), _zuteilung=row.get('AAP_zuteilung'), # 'position': 2, # 'teilzuteilungsverbot': u'0', ) # Preis einfügen if row.get('verkaufspreis'): d['preis'] = row['verkaufspreis'] texte = postexte.get(row['auftragsnr'], {}).get(row['position'], []) texte, attrs = texte_trennen(texte) d['infotext_kunde'] = texte if 'guid' in attrs: d['guid'] = attrs['guid'] koepfe[row['auftragsnr']]['positionen'].append(d) # Kopftexte zuordnen for auftragsnr, texte in kopftexte.items(): texte, attrs = texte_trennen(texte) koepfe[remove_prefix(auftragsnr, 'SO')]['infotext_kunde'] = texte if 'guid' in attrs: koepfe[remove_prefix(auftragsnr, 'SO')]['guid'] = attrs['guid'] return koepfe.values()
def _auftraege(additional_conditions=None, addtables=None, mindate=None, maxdate=None, limit=None, header_only=False): """ Alle Aufträge ermitteln `additional_conditions` kann eine Liste von SQL-Bedingungen enthalten, die die Auftragssuche einschränken. `mindate` & `maxdate` können den Anliefertermin einschränken. `limit` kann die Zahl der zurückgelieferten Aufträge einschraenken. Dabei werden groessere Auftragsnummern zuerst zurueck gegeben. Rückgabewert sind dicts nach dem Lieferungprotokoll. Wenn header_only == True, werden nur Auftragsköpfe zurück gegeben, was deutlich schneller ist. """ conditions = ["AKSTAT<>'X'"] if mindate and maxdate: conditions.append("AKDTER BETWEEN %s AND %s" % (date2softm(mindate), date2softm(maxdate))) elif mindate: conditions.append("AKDTER > %s" % date2softm(mindate)) elif maxdate: conditions.append("AKDTER < %s" % date2softm(maxdate)) if additional_conditions: conditions.extend(additional_conditions) condition = " AND ".join(conditions) koepfe = {} kopftexte = {} if addtables is None: addtables = [] # Köpfe und Adressen einlesen for kopf in query(['AAK00'] + addtables, ordering=['AKAUFN DESC'], condition=condition, joins=[('XKD00', 'AKKDNR', 'KDKDNR')], limit=limit, ua='husoftm2.auftraege'): d = dict(kundennr="SC%s" % kopf['kundennr_warenempf'], auftragsnr="SO%s" % kopf['auftragsnr'], auftragsnr_kunde=kopf['auftragsnr_kunde'], erfassung=kopf['AAK_erfassung_date'], aenderung=kopf['AAK_aenderung_date'], sachbearbeiter=husoftm2.sachbearbeiter.resolve(kopf['sachbearbeiter']), anliefertermin=kopf['liefer_date'], teillieferung_erlaubt=(kopf['teillieferung_erlaubt'] == 1), erledigt=(kopf['voll_ausgeliefert'] == 1), positionen=[], # * *auftragsnr_kunde* - id of the order submitted by the customer # * *info_kunde* - Freitext der für den Empfänger relevanz hat ) koepfe[kopf['auftragsnr']] = d if header_only: return koepfe.values() allauftrnr = koepfe.keys() # Texte auslesen # Die dritte und vierte Position des Werts von txt_auslesen sind posdaten und kopfdaten. # Es handelt sich dabei wohl um Texte, die nicht angedruckt werden sollen. # Bis auf weiteres werden diese hier ignoriert. postexte, kopftexte, _, _ = txt_auslesen(allauftrnr) while allauftrnr: # In 50er Schritten Auftragspositionen lesen und den 50 Aufträgen zuordnen batch = allauftrnr[:50] allauftrnr = allauftrnr[50:] # Abweichende Lieferadressen for row in query(['XAD00'], ua='husoftm2.lieferscheine', condition="ADAART=1 AND ADRGNR IN (%s)" % ','.join([str(x) for x in batch])): koepfe[row['nr']]['lieferadresse'] = dict(name1=kopf['name1'], name2=kopf['name2'], name3=kopf['name3'], strasse=row['strasse'], land=husoftm2.tools.land2iso(row['laenderkennzeichen']), plz=row['plz'], ort=row['ort']) # Positionen einlesen for row in query(['AAP00'], condition="APSTAT<>'X' AND APAUFN IN (%s)" % ','.join([str(x) for x in batch]), ua='husoftm2.auftraege'): d = dict(menge=int(row['bestellmenge']), artnr=row['artnr'], liefer_date=row['liefer_date'], menge_offen=int(row['menge_offen']), fakturierte_menge=int(row['fakturierte_menge']), erledigt=(row['voll_ausgeliefert'] == 1), # 'position': 2, # 'teilzuteilungsverbot': u'0', ) texte = postexte.get(row['auftragsnr'], {}).get(row['position'], []) texte, attrs = texte_trennen(texte) d['infotext_kunde'] = texte if 'guid' in attrs: d['guid'] = attrs['guid'] koepfe[row['auftragsnr']]['positionen'].append(d) # Kopftexte zuordnen for auftragsnr, texte in kopftexte.items(): texte, attrs = texte_trennen(texte) koepfe[remove_prefix(auftragsnr, 'SO')]['infotext_kunde'] = texte if 'guid' in attrs: koepfe[remove_prefix(auftragsnr, 'SO')]['guid'] = attrs['guid'] return koepfe.values()