# -*- coding: utf-8 -*- """ pyjeeves.models ~~~~~~~~~~~~~~~~~~~~~~ Jeeves raw data models """ from sqlalchemy.schema import ForeignKey, Column from sqlalchemy.orm import relationship from sqlalchemy.types import Integer, String from sqlalchemy.ext.hybrid import hybrid_property from sqlalchemy.sql.expression import and_ from pyjeeves import logging from .abc import RawBaseModel from .sp_classes import OrderHead, OrderRow, PlaceOrder import re logger = logging.getLogger("PyJeeves." + __name__) class ProductClass(RawBaseModel): __tablename__ = 'xp' __column_map__ = {'ArtProdKlass': 'ProductClassNumber', 'ArtProdklBeskr': 'ProductClassName'} __to_dict_only__ = ('ArtProdKlass', 'ArtProdklBeskr') # print_filter = ('Articles', 'articles_collection') class ArticleClass(RawBaseModel): __tablename__ = 'xm' __column_map__ = {'ArtKod': 'ArticleClassNumber', 'ArtTypBeskr': 'ArticleClassName'} __to_dict_only__ = ('ArtKod', 'ArtTypBeskr') # print_filter = ('Articles', 'articles_collection') class CommodityGroup(RawBaseModel): __tablename__ = 'vg' __column_map__ = {'VaruGruppKod': 'CommodityGroupNumber', 'VaruGruppBeskr': 'CommodityGroupName'} __to_dict_only__ = ('VaruGruppKod', 'VaruGruppBeskr', 'ArticleClass') print_filter = ('Articles', 'articles_collection') # to_json_filter = ('Articles', 'articles_collection') ArtKod = Column(Integer, ForeignKey('xm.ArtKod'), primary_key=True) ArticleClass = relationship(ArticleClass) class ArticleAlternativeUnit(RawBaseModel): __tablename__ = 'xae' __column_map__ = {'AltEnhetKod': 'UnitCode', 'AltEnhetBeskr': 'UnitName', 'AltEnhetOmrFaktor': 'DefaultUnitConv'} __to_dict_only__ = ('AltEnhetBeskr', 'AltEnhetOmrFaktor') class ArticleUnit(RawBaseModel): __tablename__ = 'xare' __column_map__ = {'ArtNr': 'ArticleNumber', 'AltEnhetKod': 'UnitCode', 'AltEnhetOmrFaktor': 'UnitConv', 'AltEnhetOrderStd': 'DefaultSalesUnit'} __to_dict_only__ = ('AltEnhetKod', 'AltEnhetOmrFaktor', 'AltEnhetOrderStd', 'ArticleAlternativeUnit') ArtNr = Column(String, ForeignKey('ar.ArtNr'), primary_key=True) AltEnhetKod = Column(Integer, ForeignKey('xae.AltEnhetKod'), primary_key=True) ArticleAlternativeUnit = relationship(ArticleAlternativeUnit) class ArticleBalance(RawBaseModel): __tablename__ = 'ars' __column_map__ = {'LagSaldo': 'Balance', 'LagResAnt': 'ReservedBalance', 'LagsaldoAltEnh': 'BalanceAlternative', 'LagResAntAltEnh': 'ReservedAlternativeBalance', 'LagStalle': 'StorageLocationNumber'} __to_dict_only__ = ('LagSaldo', 'LagResAnt', 'LagsaldoAltEnh', 'LagResAntAltEnh', 'LagStalle') # print_filter = ('Article', 'articles_collection') # to_json_filter = ('Article', 'articles_collection') ArtNr = Column(Integer, ForeignKey('ar.ArtNr'), primary_key=True) class Article(RawBaseModel): __tablename__ = 'ar' __column_map__ = {'ArtNr': 'ArticleNumber', 'ArtBeskr': 'ArticleName', 'ArtBeskrSpec': 'ArticleSpec', 'Edit': 'ArticleLongSpec', 'LagSaldoArtikel': 'UnitBalance', 'EnhetsKod': 'Unit', # 'Extra1': 'WholeSaleUnit', 'ArtListPris': 'UnitListPrice'} __to_dict_only__ = ( 'ArtNr', 'ArtBeskr', 'ArtBeskrSpec', 'Edit', 'CommodityGroup', 'ProductClass', 'ArticleClass', 'ArticleBalance', 'EnhetsKod', 'LagSaldoArtikel', 'RowCreatedDt', 'ArtListPris', 'PictureFileName', 'UnitListPrice', # 'Extra1', 'WholeSaleUnit', 'WholeSaleAmount', 'ListPrice', 'Balance') ArtNr = Column(Integer, primary_key=True) VaruGruppKod = Column(Integer, ForeignKey('vg.VaruGruppKod'), primary_key=True) ArtProdKlass = Column(Integer, ForeignKey('xp.ArtProdKlass'), primary_key=True) ArtKod = Column(Integer, ForeignKey('xm.ArtKod'), primary_key=True) CommodityGroup = relationship(CommodityGroup, lazy='joined') ProductClass = relationship(ProductClass, lazy='joined') ArticleClass = relationship(ArticleClass, lazy='joined') ArticleBalance = relationship(ArticleBalance) ArticleUnit = relationship(ArticleUnit, uselist=True) def _get_alt_unit(self): # Find matching alternative unit for amount per piece, or return default for the article. spec_conv = self.ArtFsgForp if self.ArtFsgForp else None for unit in self.ArticleUnit: if unit.AltEnhetOrderStd == "1" and not spec_conv: return unit elif (spec_conv and (unit.AltEnhetOmrFaktor == spec_conv or unit.ArticleAlternativeUnit.AltEnhetOmrFaktor == spec_conv)): return unit def get_unit_conv(self): # Return conversion factor for the article's alternative unit if self.ArtFsgForp: return self.ArtFsgForp unit = self._get_alt_unit() if unit: return (unit.AltEnhetOmrFaktor if unit.AltEnhetOmrFaktor else unit.ArticleAlternativeUnit.AltEnhetOmrFaktor) return 1 @hybrid_property def WholeSaleUnit(self): # Description of alternative unit, or Extra1 if no alternative unit is in use. unit = self._get_alt_unit() if unit: return unit.ArticleAlternativeUnit.AltEnhetBeskr else: return self.Extra1 @hybrid_property def WholeSaleAmount(self): # Amount of units in the alternative unit of the article, or 1 if none exist. return self.get_unit_conv() @hybrid_property def ListPrice(self): try: return self.ArtListPris * self.get_unit_conv() except TypeError: logger.debug("NoneType error, %s" % self.ArtNr) @hybrid_property def Balance(self): try: return self.LagSaldoArtikel / self.get_unit_conv() except TypeError: logger.debug("NoneType error, %s" % self.ArtNr) @classmethod def _base_filters(self, obj): return RawBaseModel._base_filters( obj, and_(obj.ItemStatusCode == 0) ) class ContactInformationType(RawBaseModel): __tablename__ = 'X4' __to_dict_only__ = ('ComKod', 'ComBeskr') class ContactInformation(RawBaseModel): __tablename__ = 'cr' __column_map__ = {'ComNr': 'Information'} __to_dict_only__ = ('InformationType', 'ComNr') FtgNr = Column(String, ForeignKey('fr.FtgNr'), primary_key=True) FtgKontaktNr = Column(Integer, ForeignKey('kp.FtgKontaktNr'), primary_key=True) ComKod = Column(String, ForeignKey('X4.ComKod'), primary_key=True) ContactInformationType = relationship(ContactInformationType) @hybrid_property def InformationType(self): return self.ContactInformationType.ComBeskr if self.ContactInformationType else "" class ContactPerson(RawBaseModel): __tablename__ = 'kp' __column_map__ = {'FtgKontaktNr': 'ContactNumber', 'FtgPerson': 'ContactName'} __to_dict_only__ = ('FtgKontaktNr', 'FtgPerson', 'ContactInformation') FtgNr = Column(String, ForeignKey('fr.FtgNr'), primary_key=True) FtgKontaktNr = Column(Integer, primary_key=True) ContactInformation = relationship( "ContactInformation", lazy='joined', foreign_keys='ContactInformation.FtgNr, ContactInformation.FtgKontaktNr', primaryjoin="and_(ContactInformation.FtgNr==ContactPerson.FtgNr," "ContactInformation.FtgKontaktNr==ContactPerson.FtgKontaktNr)") class WebUser(RawBaseModel): __tablename__ = 'kpw' __to_dict_only__ = ('WebUserName', 'ValidToUse', 'ContactPersonName', 'ContactPersonNumber') FtgNr = Column(String, ForeignKey('fr.FtgNr'), primary_key=True) FtgKontaktNr = Column(Integer, ForeignKey('kp.FtgKontaktNr'), primary_key=True) ContactPerson = relationship( ContactPerson, uselist=False, lazy='joined', foreign_keys='ContactPerson.FtgNr, ContactPerson.FtgKontaktNr', primaryjoin="and_(ContactPerson.FtgNr==WebUser.FtgNr," "ContactPerson.FtgKontaktNr==WebUser.FtgKontaktNr)") @hybrid_property def ContactPersonName(self): return self.ContactPerson.FtgPerson if self.ContactPerson else "" @hybrid_property def ContactPersonNumber(self): return self.ContactPerson.FtgKontaktNr if self.ContactPerson else "" class LanguageCodes(RawBaseModel): __tablename__ = 'xw' SprakKod = Column(Integer, primary_key=True) class Company(RawBaseModel): __tablename__ = 'fr' __column_map__ = {'FtgNr': 'CompanyNumber', 'FtgNamn': 'CompanyName', 'OrgNr': 'CompanyIdentityNumber', 'EUMomsNr': 'CompanyVATNumber', 'FtgPostAdr1': 'AddrCO', 'FtgPostadr5': 'AddrStreet', 'FtgLevPostNr': 'AddrPostalCode', 'FtgPostLevAdr3': 'AddrCity', 'LandsKod': 'AddrCountry', 'FtgPostAdr2': 'PostAddrStreet', 'FtgPostnr': 'PostAddrPostalCode', 'FtgPostAdr3': 'PostAddrCity'} __to_dict_only__ = ('FtgNr', 'FtgNamn', 'Customer', 'ContactPersons', 'ContactInformation', 'FtgPostadr5', 'FtgPostAdr1', 'FtgPostLevAdr3', 'FtgLevPostNr', 'LandsKod', 'FtgPostAdr2', 'FtgPostnr', 'FtgPostAdr3', 'OrgNr', 'EUMomsNr') FtgNr = Column(String, primary_key=True) Customer = relationship('Customer', uselist=False, back_populates='Company', lazy='joined') ContactPersons = relationship("ContactPerson", lazy='joined') ContactInformation = relationship( "ContactInformation", lazy='joined', foreign_keys='ContactInformation.FtgNr', primaryjoin="and_(ContactInformation.FtgNr==Company.FtgNr," "ContactInformation.FtgKontaktNr==null())") class DelivLoc(RawBaseModel): __tablename__ = 'lp' class CustomerCategory(RawBaseModel): __tablename__ = 'x1k' KundKategoriKod = Column(Integer, primary_key=True) class Customer(RawBaseModel): __tablename__ = 'kus' __column_map__ = {'FtgNr': 'CompanyNumber', 'kundkategorikod': 'CustomerCategoryCode', 'PrisListaKundSpec': 'PriceListPrimary', 'PrisLista': 'PriceListSecondary', 'GodsMarke1': 'DefaultShippingInfo', 'GodsMarke2': 'InternalInfo', 'LevSattKod': 'DefaultShippingTypeCode', 'ValKod': 'CustomerCurrency'} __to_dict_only__ = ('kundkategorikod', 'PriceList', 'PriceListCommon', 'CustomerCategory', 'PrisLista', 'PrisListaKundSpec', 'WebUsers', 'GodsMarke1', 'GodsMarke2', 'LevSattKod', 'Language', 'ValKod') FtgNr = Column(String, ForeignKey('fr.FtgNr'), primary_key=True) KundKategoriKod = Column(Integer, ForeignKey('x1k.KundKategoriKod')) SprakKod = Column(Integer, ForeignKey('xw.SprakKod')) PrisLista = Column(Integer, ForeignKey('prh.PrisLista')) PrisListaKundSpec = Column(Integer, ForeignKey('prh.PrisLista')) Company = relationship("Company", back_populates="Customer") PriceList = relationship("PriceList", uselist=False, lazy='joined', foreign_keys='PriceList.FtgNr') PriceListCommon = relationship("PriceList", uselist=False, foreign_keys='PriceList.PrisLista', primaryjoin="Customer.PrisLista==PriceList.PrisLista") KundKategori = relationship("CustomerCategory") LanguageCodes = relationship("LanguageCodes") WebUsers = relationship( WebUser, uselist=True, lazy='joined', foreign_keys='WebUser.FtgNr', primaryjoin="Customer.FtgNr==WebUser.FtgNr") @hybrid_property def CustomerCategory(self): return self.KundKategori.KundKatBeskr if self.KundKategori else "" @hybrid_property def Language(self): return self.LanguageCodes.Sprak if self.LanguageCodes else "" class PriceList(RawBaseModel): __tablename__ = 'prh' __column_map__ = {'PrisListaBeskr': 'Description', 'PrisLista': 'PriceListNumber', 'MarkUpBelopp': 'PriceFactor'} __to_dict_only__ = ('PrisListaBeskr', 'PrisLista', 'PriceListItems', 'MarkUpBelopp') PrisLista = Column(Integer, primary_key=True) FtgNr = Column(String, ForeignKey('kus.FtgNr')) Customer = relationship('Customer', uselist=False, foreign_keys='Customer.PrisListaKundSpec') PriceListItems = relationship('PriceListItem', back_populates="PriceList", lazy='joined') class PriceListItem(RawBaseModel): __tablename__ = 'prl' __column_map__ = {'ArtNr': 'ArticleNumber', 'vb_pris': 'UnitPrice', 'MarkUpBelopp': 'UnitPriceFactor', 'NollFaktor': 'NullPriceAllowed'} __to_dict_only__ = ('ArtNr', 'vb_pris', 'MarkUpBelopp', 'NollFaktor', 'Price') __to_dict_filter__ = ['PriceList'] # Do not serialize price list relationship __dict_args__ = { 'adapters': { **{ PriceList: None }, **RawBaseModel.__dict_args__['adapters'] } } PrisLista = Column(Integer, ForeignKey('prh.PrisLista'), primary_key=True) ArtNr = Column(Integer, ForeignKey('ar.ArtNr'), primary_key=True) PriceList = relationship('PriceList', uselist=False) Article = relationship(Article) # TODO: Could likely be optimized by getting all articles in one query and mangled in repo @hybrid_property def Price(self): if not self.vb_pris and not self.MarkUpBelopp: return ( (self.Article.ArtListPris + self.PriceList.MarkUpBelopp) * self.Article.get_unit_conv()) if self.vb_pris: return self.vb_pris * self.Article.get_unit_conv() else: return ( (self.Article.ArtListPris + self.MarkUpBelopp) * self.Article.get_unit_conv()) class Order(RawBaseModel): __tablename__ = 'oh' __column_map__ = {'OrderNr': 'OrderNumber', 'FtgNr': 'CompanyNumber', 'OrdDatum': 'OrderDate', 'OrdStat': 'OrderStatusCode', 'OrdLevAdr1': 'AddrName', 'OrdLevAdr2': 'AddrCO', 'OrdLevAdr3': 'AddrStreet', 'OrdLevAdrLandsKod': 'AddrCountry', 'KundBestNr': 'CustomerContact', 'KundRef2': 'CustomerReference', 'GodsMarke1': 'ShippingInfo', 'GodsMarke2': 'InternalInfo', 'TA_MailNotified': 'ShippingEmail', 'TA_PhonNotifiedNo': 'ShippingPhone', 'TA_SMSNotifiedNo': 'ShippingSMS', 'LevSattKod': 'ShippingTypeCode'} __to_dict_only__ = ('OrderNr', 'FtgNr', 'OrdDatum', 'OrdStat', 'CompanyName', 'LevSattKod', 'OrdLevAdr1', 'OrdLevAdr2', 'OrdLevAdr3', 'OrdLevAdrLandsKod', 'KundBestNr', 'KundRef2', 'GodsMarke1', 'GodsMarke2', 'OrderItems', 'AddrPostalCode', 'AddrCity', 'TA_MailNotified', 'TA_PhonNotifiedNo', 'TA_SMSNotifiedNo') __dict_args__ = { 'adapters': { **{ 'OrdDatum': lambda ord_date, *_: ord_date.strftime("%Y-%m-%d"), }, **RawBaseModel.__dict_args__['adapters'] } } OrderNr = Column(Integer, primary_key=True) FtgNr = Column(String, ForeignKey('fr.FtgNr')) Company = relationship('Company', uselist=False) OrderItems = relationship('OrderItem', uselist=True, back_populates="Order", lazy='joined') @hybrid_property def CompanyName(self): return self.Company.FtgNamn if self.Company else "" @CompanyName.setter def CompanyName(self, value): return @hybrid_property def AddrPostalCode(self): if not self.OrdLevAdr4: return s = re.split('(?!\d)\s(?!\d)', self.OrdLevAdr4) return s[0] if len(s) > 1 else '' @AddrPostalCode.setter def AddrPostalCode(self, value): self.OrdLevAdr4 = value + (self.OrdLevAdr4 if self.OrdLevAdr4 else '') @hybrid_property def AddrCity(self): if not self.OrdLevAdr4: return s = re.split('(?!\d)\s(?!\d)', self.OrdLevAdr4, maxsplit=1) return s[1] if len(s) > 1 else '' @AddrCity.setter def AddrCity(self, value): self.OrdLevAdr4 = (self.OrdLevAdr4 if self.OrdLevAdr4 else '') + ' ' + value def create(self, webusername=None): # TODO: Extend with additional functionlity if desired. self['OrderNr'], invoicing_possible = OrderHead(self['FtgNr'], webusername).callproc() return self, invoicing_possible def save(self, invoiced=False, webusername=None): payment_method = 'invoice' if not invoiced: payment_method = 'card' PlaceOrder( self['FtgNr'], self['OrderNr'], webusername, payment_method, data=self.to_dict()).callproc() return self class OrderItem(RawBaseModel): __tablename__ = 'orp' __column_map__ = {'OrdRadNr': 'OrderRowNumber', 'vb_pris': 'UnitPrice', 'ArtNr': 'ArticleNumber', 'OrdAntal': 'UnitAmount', 'OrdAntalAltEnh': 'AltUnitAmount', 'AltEnhetKod': 'AltUnit'} __to_dict_only__ = ('OrdRadNr', 'vb_pris', 'ArtNr', 'ArticleName', 'OrdAntal', 'OrdAntalAltEnh', 'AltEnhetKod') # Do not serialize order relationship __dict_args__ = { 'adapters': { **{ Order: None, }, **RawBaseModel.__dict_args__['adapters'] } } OrderNr = Column(Integer, ForeignKey('oh.OrderNr'), primary_key=True) OrdRadNr = Column(Integer, primary_key=True) OrdRadNrStrPos = Column(Integer, primary_key=True) OrdRestNr = Column(Integer, primary_key=True) ArtNr = Column(Integer, ForeignKey('ar.ArtNr')) Order = relationship('Order', uselist=False) Article = relationship(Article) @hybrid_property def ArticleName(self): return self.Article.ArtBeskr if self.Article else "" @ArticleName.setter def ArticleName(self, value): return def save(self): # TODO: Additional information may be returned if desired. row_no = OrderRow( company_no=self['FtgNr'], order_no=self['OrderNr'], item_no=self['ArtNr'], qty=self['OrdAntal'], qty_alt_unit=self['OrdAntalAltEnh'], alt_unit=self['AltEnhetKod'], pers_sign='marlin').callproc() self['OrdRadNr'] = row_no return self