427 lines
13 KiB
Python
427 lines
13 KiB
Python
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
|