from pprint import pprint from translations import Translations from localconfig import * import re import itertools vrbl = Translations() def before_tax(grossAmount, taxPercentage): return round(grossAmount / (100.0 + taxPercentage) * 100.0, 2) class BaseInvoice(): def __init__(self, nr, country): self.nr = nr self.process_country = country self.buyer = None self.seller = None self.receipt = None self.header = None self.totals = None self.tenders = [] self.articles = [] self.taxes = [] self.poslogid = '' def add_receipt(self, receipt): self.receipt = receipt def add_header(self, header): self.header = header def add_totals(self, totals): totals.set_parent(self) self.totals = totals def add_buyer(self, organization, source): if organization is not None: organization.set_source(source) organization.set_parent(self) self.buyer = organization def add_seller(self, organization): self.seller = organization def add_tax(self, tax): tax.set_parent(self) self.taxes.append(tax) def total_taxes(self): total = 0.0 for t in self.taxes: total += t.amount return total def add_article(self, article): article.set_parent(self) self.articles.append(article) def add_tender(self, tender): tender.set_parent(self) self.tenders.append(tender) def get_total_lines_netsalesprice(self): total = 0.0 for a in self.articles: total += a.netSalesPrice() return total def roundingAllowed(self): if self.seller: return self.seller.roundingAllowed return 0 def fuck_prices_of_first_item_to_avoid_rounding(self, roundingamount): log(f"Receipt {self.receipt.id()}: Avoid rounding of {roundingamount:.2f} by updating max {len(self.articles)} articles.") article_cycle = itertools.cycle(self.articles) cents_to_round = int(round((roundingamount * 100),0)) number_of_tries = cents_to_round + len(self.articles) for a in article_cycle: # print(f" === {cents_to_round} {number_of_tries}") cents_to_round -= a.increasePrices((cents_to_round / abs(cents_to_round))) number_of_tries -= 1 if cents_to_round == 0 or number_of_tries == 0: break def currency(self): return self.header.currency def country(self): return self.header.country def receipt_id(self): return self.receipt.id() # def update_currency(self, newcurrency): # self.currency = newcurrency # # if self.sales: # # self.sales.update_currency(newcurrency) def __str__(self): result = f"==={'CREDITNOTE ' if self.debit else 'SALE'}=================================================\n" result += f"{self.nr} '{self.storename}' {self.sales}\n" if self.seller: result += str(self.seller) else: result += "[ == no country / PVH-ofice found ==]\n" if self.buyer: result += str(self.buyer) for a in self.articles: result += str(a) for t in self.tenders: result += str(t) for x in self.taxes: result += str(x) return result class BaseDiscount(): """ Docstring for Discount """ def __init__(self, idtext, action, amount, reasoncode): self.promotionID = idtext self.action = action #substract or add self.amount = float(amount) self.reasoncode = reasoncode self.sales = None self.parent = None def set_parent(self, parent): self.parent = parent def currency(self): return self.parent.currency() def net_amount(self): return before_tax(self.amount, self.parent.taxpercentage()) def __str__(self): return(f" {self.promotionID} {self.action} {self.amount} {self.reasoncode}\n") class BaseTax(): """ Docstring for tax """ spaces = ' ' def __init__(self, typesubtype, code, amount, percentage, group, taxincludedamount): self.typesubtype = typesubtype self.code = code self.amount = float(amount) self.percentage = float(percentage) self.group = group # GK / SAP taxcodes self.taxincludedamount = float(taxincludedamount) self.taxableamount = self.taxincludedamount - self.amount # net price self.parent = None def translated_group(self): return vrbl.translate(self.group, type='tax', country=self.country()) def set_parent(self, parent): self.parent = parent def currency(self): return self.parent.currency() def country(self): return self.parent.country() def __str__(self): return f"{self.spaces}{self.typesubtype} {self.code} {self.amount} {self.percentage}% {self.translated_group()} {self.taxableamount}\n" class BaseTotalTax(BaseTax): ''' Docstring for Totaltax ''' pass class BaseHeader(): """ will hold the invoice header info """ def __init__(self, country, brand, curreny, is_debit, is_return, is_postvoided, loyalty_nr, fiscalseq, is_fiscal, fiscalprinter): self.country = country self.brand = brand self.currency = curreny self.is_debit = is_debit self.is_return = is_return self.is_voided = is_postvoided self.loyalty = loyalty_nr self.fiscalseq = fiscalseq self.is_fiscal = is_fiscal self.fiscalprinter = fiscalprinter self.taxref = {} def add_taxref(self, key, value): self.taxref[key] = value def get_taxref(self, key): if key in self.taxref: return self.taxref[key] return '' def __str__(self): return f"{self.country} {self.brand} {self.currency} " class BaseReceipt(): """ will hold the receipt identification """ def __init__(self, store, year, month, day, till, seq): self.store = store self.year = year self.month = month self.day = day self.till = till self.seq = seq self.time_str = '' def id(self): return f"{self.store}{self.year}{self.month}{self.day}{self.till}{self.seq.zfill(3)}" def __str__(self): return f"{self.id()}\n" def add_time(self, hhmmss_with_colons): self.time_str = hhmmss_with_colons class BaseTotals(): """ will hold the finiancial totals """ def __init__(self, grand, net, tax): self.grandAmount = float(grand) self.netAmount = float(net) self.taxAmount = float(tax) self.totalLinesAmount = self.netAmount self.roundingAmount = 0.0 self.parent = None def set_parent(self, parent): self.parent = parent def currency(self): return self.parent.currency() def get_total_lines_netsalesprice(self): self.totalLinesAmount = self.parent.get_total_lines_netsalesprice() # print(f" ---- {self.parent.country()} {self.parent.roundingAllowed()} {type(self.parent.roundingAllowed())}") if self.parent.roundingAllowed(): self.roundingAmount = self.netAmount - self.totalLinesAmount else: roundingamount = self.netAmount - self.totalLinesAmount if abs(roundingamount) > 0.001: self.parent.fuck_prices_of_first_item_to_avoid_rounding(roundingamount) self.totalLinesAmount += roundingamount def __str__(self): return f"{self.grandAmount} {self.netAmount}{self.taxAmount}\n" class BaseOrganization(): ''' will hold the organizations detail, can be buyer or seller(PVH) ''' def __init__(self, orgid, name, city, name2='', street1='', street2='', zip='', country='EU', taxid='0', scheme=DEFAULTCUSTSCHEME, phone='', email='', legalid='', vrbl_receiver='BASE_VRBL', minimum=0.0, orgidlength=0, taxidlength=0, roundingAllowed=1): self.orgid = orgid self.scheme = scheme self.name = name self.name2 = name2 self.street1 = street1 self.street2 = street2 self.zip = zip self.city = city self.country = country self.taxID = taxid self.legalid = legalid self.phone = phone self.email = email self.vrbl_receiver = vrbl_receiver self.source = '' self.minimum = float(minimum) self.orgidlength = int(orgidlength) self.taxidlength = int(taxidlength) self.roundingAllowed = int(roundingAllowed) # 0: distribute rounding diffs over the items self.parent = None def set_source(self, source): self.source = source def set_parent(self, parent): self.parent = parent def __str__(self): return f" {self.orgid} {self.name} {self.city} {self.taxID}\n" class BaseBuyer(BaseOrganization): def update(self, buyerdata): self.orgid = buyerdata['OrgId'] self.name = buyerdata['Organization'] self.zip = buyerdata['Zip'] self.city = buyerdata['City'] self.country = buyerdata['Country'] self.taxID = buyerdata['TaxId'] self.street1 = buyerdata['Street'] def _country(self): return self.country if len(self.country)>1 else self.parent.country() def shorttxt(self): return f"{self.orgid}, {self.name}, {self.zip}, {self.country}, {self.taxID}, {self.email}, {self._country()}" def repair_and_check_if_data_is_broken(self): orgid_as_number = re.sub(r"\D", "", self.orgid) if len(orgid_as_number)<=2 and len(self.orgid)>=6 and len(self.name)<=1: self.name = self.orgid self.orgid = '' else: self.orgid = orgid_as_number self.taxID = re.sub(r"\D", "", self.taxID) if self.orgid == '0' or self.orgid== '1' or self.orgid == '': self.orgid = self.taxID if len(self.country)==0: self.country = self._country() # print(f"{len(self.name)<2} or {len(self.city)<2} or {len(self.zip)<2} or {(self.country not in EU_COUNTRIES)} or {len(self.orgid)!=self.parent.seller.orgidlength} or {len(self.taxID)!=self.parent.seller.taxidlength}") return (len(self.name)<2 or len(self.city)<2 or len(self.zip)<2 or (self.country not in EU_COUNTRIES) or len(self.orgid)!=self.parent.seller.orgidlength or len(self.taxID)!=self.parent.seller.taxidlength) class BaseSeller(BaseOrganization): pass class BaseTender(): ''' payment-line ''' def __init__(self, description, amount, type, code, currency=None): self.description = description self.type = type self.code = code self.amount = float(amount) self.my_currency = currency self.parent = None def set_parent(self, parent): self.parent = parent def translated_type(self): return vrbl.translate(self.type, type='tender', country=self.country()) def country(self): return self.parent.country() def __str__(self): return(f" {self.description} {self.amount} {self.translated_type} {self.code} \n") class BaseArticle(): ''' article info ''' def __init__(self, sequence, returnflag, itemid, upc, description, quantity, unit, basePrice, salesPrice): self.sequence = sequence self.returnflag = returnflag self.itemid = itemid self.upc = upc self.description = description self.quantity = quantity self.unit = unit self.basePrice = float(basePrice) # incl tax, before discounts self.salesPrice = float(salesPrice) # incl tax, after discounts self.discounts = [] self.tax = None self.parent = None self.priceIncreaseForRounding = 0.0 def set_parent(self, parent): self.parent = parent def add_discount(self, discount): discount.set_parent(self) self.discounts.append(discount) def add_tax(self, tax): tax.set_parent(self) self.tax = tax def currency(self): return self.parent.currency() def country(self): return self.parent.country() def taxpercentage(self): return self.tax.percentage def netBasePrice(self): return before_tax(self.basePrice, self.tax.percentage) + self.priceIncreaseForRounding def netSalesPrice(self): return before_tax(self.salesPrice, self.tax.percentage) + self.priceIncreaseForRounding def line_uuid(self): return f"{self.parent.receipt_id()}{self.sequence.zfill(5)}" def increasePrices(self, roundingamount): if self.netSalesPrice() - self.priceIncreaseForRounding - (roundingamount / 100) > 0.0: self.priceIncreaseForRounding += (roundingamount / 100) return roundingamount return 0 def __str__(self): result = f" {self.itemid} {self.description} {self.quantity} {self.unit} {self.salesPrice} [{self.basePrice}]\n" for d in self.discounts: result += str(d) for t in self.taxes: result += str(t) return result