def kundenpreise(kundennr, gueltig_von=None, gueltig_bis=None): """Alle kundenspezifischen Preis für einen Kunden""" kundennr = remove_prefix(kundennr, 'SC') conditions = ["PNSANR=PRSANR", "PRANW='A'", "PRSTAT=' '", "PNSTAT=' '", "PRKDNR=%s" % pad('PRKDNR', kundennr) ] if gueltig_von: conditions.append("PRDTVO>=%s" % sql_quote(date2softm(gueltig_von))) if gueltig_bis: conditions.append("PRDTBI>=%s" % sql_quote(date2softm(gueltig_bis))) rows = query(tables=['XPN00', 'XPR00'], fields=['PRARTN', 'PNPRB', 'PRDTVO', 'PRDTBI', 'PRSANR'], condition=' AND '.join(conditions), ordering='PRDTVO' ) preise = {} for row in rows: preise[row['artnr']] = dict(preis=int(row['preis'] * 100), gueltig_von=row.get('gueltig_ab_date'), gueltig_bis=row.get('gueltig_bis_date'), satznr=str(row['satznr_xpr00'])) return preise
def get_kommibeleg(komminr, header_only=False): """Gibt einen Kommissionierbeleg zurück""" prefix = 'KA' if komminr.startswith('KB'): prefix = 'KB' komminr = remove_prefix(komminr, prefix) # In der Tabelle ALK00 stehen Kommissionierbelege und Lieferscheine. # Die Kommissionierbelege haben '0' als Lieferscheinnr. # Zusätzlich werden die (logisch) gelöschten Lieferscheine rausgefiltert. conditions = ["LKLFSN = 0", "LKKBNR = %s" % sql_quote(komminr), "LKSTAT<>'X'"] try: belege = get_ls_kb_data(conditions, header_only=header_only, is_lieferschein=False) except RuntimeError: return {} if belege: beleg = belege[0] # Falls es bereits einen Lieferschein gibt, die Lieferscheinnr in das dict schreiben. # Ansonsten die Eintrag 'lieferscheinnr' entfernen (wäre sonst SL0) rows = query(['ALK00'], condition="LKLFSN <> 0 AND LKKBNR = %s" % sql_quote(komminr)) if rows: beleg['lieferscheinnr'] = rows[0]['lieferscheinnr'] else: beleg.pop('lieferscheinnr', None) return beleg return {}
def durchschnittlicher_abgabepreis(artnr, kundennr=None, startdatum=None): """Gibt eine Liste mit den durchschnittlichen Rechnungspreisen pro Monat zurück. Liefert eine Liste von 4-Tuples (datum, AVG(preis), menge, umsatz) Wenn eine Kundennummer mitgeliefert wird, werden nur Rechungen für diesen Kunden betrachtet. Wenn ein startdatum angegebenw wird, werden nur vorgänge nach diesem Datum betrachtet. [ ... (datetime.date(2009, 2, 1), 3295, 2, 6590), (datetime.date(2009, 10, 1), 1744, 2, 3488)] Die Funktion ist ausgesprochen langsam - bis zu 8 Sekunden. """ conditions = [ "FUARTN=%s" % (sql_quote(artnr)), # nur bestimmten Artikel beachten "FKRGNR=FURGNR", # JOIN "FKAUFA<>'U'", # Keine Umlagerung "FKSTAT<>'X'", # nicht gelöscht "FKDTFA>0", # Druckdatum nicht leer "FUKZRV=2", # ? "FURGNI<>0", # ? "FKFORM=' '", # ? "FURGNR<>0", # es gibt eine Rechnungsnummer "FUPNET>0", # keine Gutschriften ] if kundennr: kundennr = remove_prefix(kundennr, 'SC') conditions = ["(FKKDNR=%s OR FKKDRG=%s)" % (sql_quote('%8s' % kundennr), sql_quote('%8s' % kundennr))] + conditions if not startdatum: conditions = ["FKDTFA>'10501'"] + conditions # keine legacy Daten else: conditions = ["FKDTFA>%s" % sql_quote(date2softm(startdatum))[:5]] + conditions rows = query(['AFU00', 'AFK00'], fields=["FKDTFA", 'SUM(FUMNG)', 'SUM(FUPNET)', 'COUNT(FKRGNR)'], condition=' AND '.join(conditions), grouping='FKDTFA', cachingtime=60 * 60 * 24 * 3, querymappings={'SUM(FUMNG)': 'menge', 'SUM(FUPNET)': 'nettopreis', 'COUNT(FKRGNR)': 'rechnungen', 'FKDTFA': 'rechnung_date'}) mengen = {} umsatz = {} for row in rows: menge = int(float(row['menge'])) nettopreis = float(row['nettopreis']) * 100 if menge: datum = datetime.date(row['rechnung_date'].year, row['rechnung_date'].month, 1) if datum not in mengen: mengen[datum] = umsatz[datum] = 0 mengen[datum] += int(menge) umsatz[datum] += int(nettopreis) ret = [] for datum in sorted(mengen.keys()): ret.append((datum, int(umsatz[datum] / mengen[datum]), mengen[datum], umsatz[datum])) return ret
def get_guid(auftragsnr): """Gibt den GUID zu einer Auftragsnr zurück, sofern vorhanden.""" auftragsnr = remove_prefix(auftragsnr, 'SO') condition = "ATTX60 LIKE %s AND ATAUFN = %s AND ATAUPO = 0 AND ATTART = 8" % (sql_quote("#:guid:%%"), sql_quote(auftragsnr)) rows = query('AAT00', fields=['ATTX60'], condition=condition) if rows: return rows[0][0].replace('#:guid:', '') return ''
def abgabepreis_kunde(artnr, kundennr, auftragsdatum=None): """ Verkaufspreis für einen Artikel in Abhängigkeit von kundennr und Auftragsdatum ermitteln. Höchste Priorität hat der für einen Kunden hinterlegt Preis. Zweithöchste Priorität hat der für die Preisliste (Kundengruppe) hinterlegte Preis Niedrigste Priorität hat der Listenpreis aus den Artikelstammdaten. Rückgabe ist tuple mit Preis und Herkunft des Preises. >>> abgabepreis_kunde('04711', 99954) (1500, 'Preisliste 95') >>> abgabepreis_kunde('04711', 98000) (1400, 'Listenpreis') >>> abgabepreis_kunde('04711', 94763) (1300, 'Kundenpreis') """ if not auftragsdatum: auftragsdatum = datetime.date.today() # Kundennr als Zeichenkette kundennr = remove_prefix(kundennr, 'SC') date_str = sql_quote(date2softm(auftragsdatum)) # 1. Preis für Kunde hinterlegt? conditions = ["PNSANR=PRSANR", "PRANW='A'", "PRSTAT=' '", "PNSTAT=' '", "PRARTN=%s" % sql_quote(artnr), "PRDTBI>=%s" % date_str, "PRDTVO<=%s" % date_str, ] condition_kunde = conditions + ["PRKDNR=%s" % pad('PRKDNR', kundennr)] rows = query(['XPN00', 'XPR00'], fields=['PNPRB'], condition=' AND '.join(condition_kunde), ordering='PRDTVO', limit=1) if rows: return (int(rows[0][0] * 100), 'Kundenpreis') # 2. Preis aus Preislistennr. des Kunden ermitteln condition_gruppe = conditions + [ # "PRPRLK = %s" % sql_quote(kunde['kunden_gruppe']), "KDKDNR=%s" % pad('KDKDNR', kundennr), "PRPRLK=KDKGRP" ] rows = query(['XPN00', 'XPR00', 'XKD00'], fields=['PNPRB', 'PRPRLK'], ordering='PRDTVO', condition=' AND '.join(condition_gruppe), limit=1) if rows: return (int(rows[0]['preis'] * 100), 'Preisliste %s' % rows[0]['preisliste_kunde']) # 3. Listenpreis aus Artikelstammdaten return (listenpreis(artnr), 'Listenpreis')
def mark_processed(lieferscheinnr): """Markiert einen Lieferschein, so dass er von get_new() nicht mehr zurücuk gegeben wird.""" conditions = ["LKLFSN=%s" % sql_quote(remove_prefix(lieferscheinnr, 'SL')), "LKSTAT<>'X'", "LKKZ02=0", ] return x_en('ALK00', condition=' AND '.join(conditions), ua='husoftm2.lieferscheine')
def get_auftragsarten_by_auftragsnrs(auftragsnrs): """Ermittelt die Auftragsart (Freitext) der Aufträge der gegebenen Auftragsnummern. auftragsnrs: liste von Auftragsnummern return: Dictionary Auftragnr -> Auftragsart """ condition = "AKAUFN in (%s)" % ','.join(sql_quote(remove_prefix(nr, 'SO')) for nr in auftragsnrs) rows = query(['AAK00'], fields=['AKAUFN', 'AKAUFA'], condition=condition) return dict(("SO%s" % row['auftragsnr'], AUFTRAGSARTEN[row['art']]) for row in rows)
def lieferschein_for_kommiauftrag(komminr, header_only=False): """Gibt den zu dem Kommiauftrag passenden Lieferschein zurück Falls der Lieferschein (noch) nicht existiert, wird None zurückgegeben """ komminr = remove_prefix(komminr, 'KA') lieferscheine = _lieferscheine(["LKKBNR = %s" % sql_quote(komminr)], limit=1, header_only=header_only) if lieferscheine: return lieferscheine[0]
def get_auftrag_by_guid(guid, header_only=False): """Auftrag mit GUID guid zurueckgeben. ACHTUNG guids sind cniht zwingend eindeutig!""" # TO BE FIXED condition = "ATTX60 = %s AND ATAUPO = 0 AND ATTART = 8 AND ATAUFN=AKAUFN" % sql_quote("#:guid:" + guid) auftraege = _auftraege([condition], addtables=['AAT00'], header_only=header_only) if len(auftraege) > 1: raise RuntimeError("Mehr als ein Auftrag mit guid %s vorhanden" % guid) if not auftraege: return None return auftraege[0]
def get_rahmenauftraege(kundennr, artnr): """Gib die Auftragsnummern aller offenen Rahmenaufträge für den Kunden und den Artikel als Liste zurück. >>> get_rahmenauftraege('SC66663', '14600') ['SO1205711'] """ kundennr = remove_prefix(kundennr, 'SC') conditions = ["AKAUFN=APAUFN", "AKKZVA=0", "APKZVA=0", "AKSTAT<>'X'", "APSTAT<>'X'", "AKAUFN>0", "AKAUFA='R'", "AKKDNR=%s" % pad('AKKDNR', kundennr), "APARTN=%s" % sql_quote(artnr)] rows = query(['AAK00', 'AAP00'], condition=' AND '.join(conditions), fields=['AKAUFN'], ua='husoftm2.auftraege.get_rahmenauftraege') return [add_prefix(row[0], 'SO') for row in rows]
def listenpreise(artnrs=None): """Gibt den (aktuellen) Listenpreis in Cent für eine Liste von Artikeln zurück. Wenn keine Artikelnummern angegeben werden, gibt es alle Listenpreise zurück. >>> preise(['04711']) {'04711': 1365} """ conditions = ["ARSTAT<>'X'"] if artnrs: conditions += ['ARARTN IN (%s)' % ','.join([sql_quote(x) for x in artnrs])] rows = query('XAR00', fields=['ARARTN', 'ARPREV'], condition=' AND '.join(conditions)) return dict([(x['artnr'], int(100 * float(x['listenpreis']))) for x in rows])
def get_auftrag_by_guid(guid, header_only=False, canceled=False): """Auftrag mit GUID guid zurueckgeben. ACHTUNG guids sind nicht zwingend eindeutig! Wenn `canceled == False` werden keine stornierten Aufträge und Positionen zurückgegeben.""" # TO BE FIXED - md: was ist hier zu fixen? condition = "ATTX60 = %s AND ATAUPO = 0 AND ATTART = 8 AND ATAUFN=AKAUFN" % sql_quote("#:guid:" + guid) auftraege = _auftraege([condition], addtables=['AAT00'], header_only=header_only, canceled=canceled) if len(auftraege) > 1: raise RuntimeError("Mehr als ein Auftrag mit guid %s vorhanden" % guid) if not auftraege: return None return auftraege[0]
def get_lieferschein(lieferscheinnr, header_only=False): """Gibt ein Lieferscheindict für eine Lieferscheinnummer zurück""" lieferscheinnr = remove_prefix(lieferscheinnr, 'SL') lscheine = _lieferscheine(["LKLFSN = %s" % sql_quote(lieferscheinnr)], limit=1, header_only=header_only) if lscheine: if len(lscheine) > 1: raise RuntimeError('Suche nach %s hat mehr als einen Lieferschein ergeben: %r' % (lieferscheinnr, lscheine)) lschein = lscheine[0] infotext = lschein.get('infotext_kunde') if infotext and isinstance(infotext, list): lschein['infotext_kunde'] = ', '.join(infotext) if not lschein.get('datum'): raise RuntimeError('LS %s hat kein Datum: %r' % (lieferscheinnr, lschein)) return lschein return {}
def get_auftrag_by_auftragsnr_tmp(auftragsnr_tmp, header_only=False, canceled=False): """Auftrag anhand der auftragsnr_tmp zurueckgeben. Wird von ic2/auftrag verwendet. Wenn `canceled == False` werden keine stornierten Aufträge und Positionen zurückgegeben.""" conditions = ["ATTX60=%s" % sql_quote("#:auftragsnr_tmp:" + auftragsnr_tmp), "ATAUPO=0", "ATTART=8", "ATAUFN=AKAUFN"] auftraege = _auftraege([" AND ".join(conditions)], addtables=['AAT00'], header_only=header_only, canceled=canceled) if len(auftraege) > 1: raise RuntimeError("Mehr als ein Auftrag mit auftragsnr_tmp %s vorhanden" % auftragsnr_tmp) if not auftraege: return None return auftraege[0]
def buchdurchschnittspreise(artnrs=None): """Gibt die (aktuellen) Buchdurchschnittspreise in Cent für ein Dict von Artikeln zurück. Wenn keine Artikelnummern angegeben werden, gibt es alle Buchdurchschinttspreise zurück. >>> buchdurchschnittspreise(['14600', '14600/00', '14600/01', '14600/02', '14600/03']) {u'14600': 3165, u'14600/00': 0, u'14600/01': 0, u'14600/02': 3132, u'14600/03': 3944} """ conditions = ["LFLGNR=0", "LFSTAT<>'X'"] if artnrs: conditions += ["LFARTN IN (%s)" % ','.join([sql_quote(x) for x in artnrs])] rows = query('XLF00', fields=['LFARTN', 'LFPRBD'], condition=' AND '.join(conditions)) return dict([(artnr, int(float(preis * 100))) for (artnr, preis) in rows])
def artikel_mengenumsatz_range(artnr, startdate, enddate): """Liefert die FakturirtenUmsatz-MEngen für den Interval [startdate; enddata[ >>> artikel_mengenumsatz_range('10101', datetime.date(2009, 6, 2), datetime.date(2009, 6, 5)) {datetime.date(2009, 6, 2): 500, datetime.date(2009, 6, 4): 1000, datetime.date(2009, 6, 3): 0} """ conditions = ['FUDTRI>=%s' % date2softm(startdate), 'FUDTRI<%s' % date2softm(enddate), 'FUARTN=%s' % sql_quote(artnr), ] rows = query(tables=['AFU00'], fields=['SUM(FUMNG)', 'FUDTRI'], grouping=['FUDTRI'], querymappings={'FUDTRI': 'tag', 'SUM(FUMNG)': 'menge'}, condition=' AND '.join(conditions), ua='husoftm2.umsatz', cachingtime=86400) ret = {} for row in rows: ret[softm2date(row['tag'])] = as400_2_int(row['menge']) return ret
def get_lagerabgang(day): """Liefert im Grunde einen ALN00 Auszug für einen Tag - dient statistischen Zwecken.""" conditions = ["(LKLFSN<>0 OR LNLFSN<>0)", # durch Umparametrisierung ist mal das und mal das leer ... "AKLGN2='0'", "LNSTAT<>'X'", "LKSTAT<>'X'", "LNDTLF=%s" % (sql_quote(day.strftime('1%y%m%d')))] # SoftM Freuden! Das Feld LKSANKB kann ausser zwischen Oktober 2005 und November 2007 # für den join genommen werden, ansonsten kann man LKSANK nehmen. rows = query(['ALN00'], condition=" AND ".join(conditions), fields=['LNAUFN', 'LNAUPO', 'LNARTN', 'LNKZKO', 'LNKDRG', 'LNKDNR', 'LNLFSN', 'LNMNGL', 'LNDTLF', 'LNDTVS', 'LNMNGF', 'LNDTER', 'LNLWA2', 'LKKDRG', 'LKKDNR', 'LKLFSN', 'LKDTLF', 'LKDTKB', 'LKAUFS', 'LKDTLT', 'AKAUFN', 'AKAUFA', 'AKDTLT', 'AKDTER', 'LNBELP', 'LNDTLT'], joins=[('ALK00', 'LNSANK', 'LKSANK'), ('AAK00', 'LNAUFN', 'AKAUFN')]) ret = [] for row in rows: data = dict(auftragsnr="SO%s" % row['auftragsnr'], lieferscheinnr="SL%s" % (int(row['lieferscheinnr']) or int(row['ALN_lieferscheinnr'])), menge=int(row['menge_fakturierung']), auftragsart=row['art'], artnr=row['artnr'], warenempfaenger="SC%s" % row['warenempfaenger'], kundennr="SC%s" % row['rechnungsempfaenger'], setartikel=(int(row['setartikel']) == 1), wert=int(row['wert'] * 100), auftrag_positionsnr=row['auftrags_position'], positionsnr=row['kommibeleg_position'], vorlauf_h=None, durchlauf_h=None, termintreue_h=None, datum=row['ALK_lieferschein_date'], ) anliefer_date = row['ALN_anliefer_date'] or row['anliefer_date'] versand_date = row['versand_date'] or row['lieferschein_date'] if anliefer_date and row['AAK_erfassung_date']: row['vorlauf_h'] = _timedelta_to_hours(anliefer_date - row['AAK_erfassung_date']), if versand_date and anliefer_date: row['termintreue_h'] = _timedelta_to_hours(versand_date - anliefer_date) if versand_date and row['AAK_erfassung_date']: row['durchlauf_h'] = _timedelta_to_hours(versand_date - row['AAK_erfassung_date']) ret.append(data) return ret
def lieferscheine_auftrag(auftragsnr, header_only=False): """Gibt eine Liste mit Lieferscheindicts für einen Auftrag zurück""" auftragsnr = remove_prefix(auftragsnr, 'SO') return _lieferscheine(["LKAUFS = %s" % sql_quote(auftragsnr)], header_only=header_only)
def abgabepreise_kunde(artnrs, kundennr, auftragsdatum=None): """ Verkaufspreis für einen oder mehrere Artikel in Abhängigkeit von kundennr und Auftragsdatum ermitteln. Der Rückgabewert ist ein dict mit den ArtNr. als Schlüssel. Die Werte sind Preisinformationen als Tupel (Preis, Herkunft) oder None, falls zu der ArtNr. kein Preis ermittelt werden konnte. Die Logik funktioniert genau wie bei abgabepreis_kunde: Es werden zuerst kundenspezifische Preise, dann kundengruppen-spezifische Preise und als letztes Listenpreise ermittelt. """ warnings.warn("use `cs.salesforce.preise` instead!", DeprecationWarning, stacklevel=2) if not auftragsdatum: auftragsdatum = datetime.date.today() artnrs = set(artnrs) kundennr = remove_prefix(kundennr, 'SC') date_str = sql_quote(date2softm(auftragsdatum)) abgabepreise = {} # 1. Preise für Kunden hinterlegt? conditions = ["PNSANR=PRSANR", "PRANW='A'", "PRPRLK<>''" "PRSTAT=' '", "PNSTAT=' '", "PRDTBI>=%s" % date_str, "PRDTVO<=%s" % date_str, ] condition_kunde = conditions + ["PRKDNR=%s" % sql_quote("%8s" % kundennr), "PRARTN IN (%s)" % ",".join([sql_quote(artnr) for artnr in artnrs])] rows = query(tables=['XPN00', 'XPR00'], fields=['PRARTN', 'PNPRB'], condition=' AND '.join(condition_kunde), ordering='PRDTVO') for row in rows: if row['artnr'] in artnrs: artnrs.remove(row['artnr']) abgabepreise[row['artnr']] = (int(row['preis'] * 100), u'Kundenpreis') if not artnrs: return abgabepreise # 2. Preise aus Preislistennr. des Kunden ermitteln condition_gruppe = conditions + [ # "PRPRLK = %s" % sql_quote(kunde['kunden_gruppe']), "PRARTN IN (%s)" % ",".join([sql_quote(artnr) for artnr in artnrs]), "KZKDNR=%s" % pad('KZKDNR', kundennr), "PRPRLK=KZPREL" ] rows = query(tables=['XPN00', 'XPR00', 'AKZ00'], fields=['PRARTN', 'PNPRB', 'PRPRLK'], condition=' AND '.join(condition_gruppe), ordering='PRDTVO') for row in rows: if row['artnr'] in artnrs: artnrs.remove(row['artnr']) abgabepreise[row['artnr']] = (int(row['preis'] * 100), u'Preisliste %s' % row['preisliste_kunde']) if not artnrs: return abgabepreise # 3. Listenpreis aus Artikelstammdaten for artnr, preis in listenpreise(artnrs).iteritems(): if artnr in artnrs: artnrs.remove(artnr) abgabepreise[artnr] = (preis, u'Listenpreis') for artnr in artnrs: abgabepreise[artnr] = None return abgabepreise