Deps updates, handle more tables + LK code

This commit is contained in:
Marcus Lindvall 2021-12-30 12:40:50 +01:00
parent f649b5f953
commit 5fe140714e
14 changed files with 583 additions and 79 deletions

2
.gitignore vendored
View file

@ -1,3 +1,5 @@
*.csv
*.py[cod]
# C extensions

22
README.md Normal file
View file

@ -0,0 +1,22 @@
# PyJeeves Module
This project is a Jeeves data extraction and integration project.
## Initial creation of database schema.
```bash
docker run --link db --network marcus_default -v /srv/pyjeeves/config.yml:/app/config.yml gitlab.lndvll.se:5500/lindvallskaffe/pyjeeves python ./pyjeeves/db_raw.py
```
## Connecting to DB with client
```bash
docker run -it --network marcus_default --link db:mysql --rm mysql sh -c 'exec mysql -h"db" -P"3306" -uroot -p"ROOT_PW"'
```
## Forcing updates
You may force updates of objects by setting RowUpdatedDt to null.
For example:
´update jvs_customers set RowUpdatedDt = null;´

View file

@ -1,22 +0,0 @@
PyJeeves Module
===============
This project is a Jeeves data extraction and integration project.
## Initial creation of database schema.
´docker run --link db --network marcus_default -v /srv/pyjeeves/config.yml:/app/config.yml gitlab.lndvll.se:5500/lindvallskaffe/pyjeeves python ./pyjeeves/db_raw.py´
## Connecting to DB with client
´docker run -it --network marcus_default --link db:mysql --rm mysql sh -c 'exec mysql -h"db" -P"3306" -uroot -p"ROOT_PW"'´
## Forcing updates
You may force updates of objects by setting RowUpdatedDt to null.
For example:
´update jvs_customers set RowUpdatedDt = null;´

View file

@ -101,6 +101,7 @@ declare @dbc int,
@jvss_OrderReservation char(1),
@kpwid integer,
@OrdBerLevDat DateTime,
@OrdBerednDat DateTime,
@SALES007 smallint,
@kus_AddArtEjAktiv char(1),
@OrdLevAdr4 Jeeves_StrVarChar64
@ -196,7 +197,8 @@ execute @wi = Jeeves_oh_ordervarde
@c_Foretagkod = @c_ForetagKod
--The estimated delivery date of the order will be set to the earliest delivery date of a row.
select @OrdBerLevDat = min(OrdBerLevDat) from orp where foretagkod = @c_ForetagKod AND OrderNr = @c_OrderNumber
-- Also fetch OrdBerednDat to set base later
select @OrdBerLevDat = min(OrdBerLevDat), @OrdBerednDat = min(OrdBerednDat) from orp where foretagkod = @c_ForetagKod AND OrderNr = @c_OrderNumber
begin tran
@ -304,6 +306,8 @@ if @c_PaymentType <> '1' begin
oh.levforetolv = @c_levforetolv,
oh.levsattkod = @Levsattkod,
oh.ordberlevdat = @OrdBerLevDat,
oh.ohordberlevdatbase = @OrdBerLevDat, -- Fix base LevDat and BerednDat. Used when adding rows in GUI.
oh.ohordberedndatbase = @OrdBerednDat,
oh.ordlevadr1 = @c_CoName,
oh.ordlevadr2 = @c_Addr1,
oh.ordlevadr3 = @c_Addr2,
@ -356,6 +360,8 @@ end else begin
oh.levforetolv = @c_levforetolv,
oh.levsattkod = @Levsattkod,
oh.ordberlevdat = @OrdBerLevDat,
oh.ohordberlevdatbase = @OrdBerLevDat, -- Fix base LevDat and BerednDat. Used when adding rows in GUI.
oh.ohordberedndatbase = @OrdBerednDat,
oh.ordlevadr1 = @c_CoName,
oh.ordlevadr2 = @c_Addr1,
oh.ordlevadr3 = @c_Addr2,

View file

@ -57,7 +57,9 @@ class DBConnector(object):
def __init__(self, enabled_clients=['raw'], metadata=None):
logger.info("Creating engines and sessionmakers")
self.raw, self.raw_engine = (self.raw_session() if 'raw' in enabled_clients else {})
self.enabled_clients = enabled_clients
self.raw_db, self.raw_session, self.raw_engine = (
self.raw_client() if 'raw' in enabled_clients else {})
self.meta = (self.meta_session() if 'meta' in enabled_clients else {})
def callproc(self, procedure="", params=[]):
@ -90,14 +92,22 @@ class DBConnector(object):
conn.close()
return results
def raw_session(self):
def raw_client(self):
if 'raw' not in self.enabled_clients:
logger.error('Raw client is not enabled')
logger.info("Using DB %s" % config.config['databases']['raw']['db'])
uri = 'mssql+pymssql://{user}:{pw}@{host}:{port}/{db}?charset=utf8'.format(
**config.config['databases']['raw'])
sql_client_config = {'SQL_DATABASE_URI': uri}
db = SQLClient(sql_client_config, query_class=BaseFilterQuery)
return db.session, db.engine
return db, db.session, db.engine
def set_model_class(self, model_class):
self.raw_db.model_class = model_class
self.raw_db.update_models_registry()
def meta_session(self):

View file

@ -26,10 +26,11 @@ logger.info("Reading Jeeves DB structure")
meta = MetaData()
try:
meta.reflect(bind=db.raw.connection(),
only=['ar', 'ars', 'xae', 'xare', 'fr', 'kus', 'x1k',
meta.reflect(bind=db.raw_session.connection(),
only=['ar', 'ars', 'arsh', 'arean', 'xae', 'xare', 'fr', 'kus', 'x1k',
'oh', 'orp', 'lp', 'vg', 'xp', 'xm', 'prh', 'prl',
'kp', 'kpw', 'cr', 'X4', 'xw', 'X1'])
'kp', 'kpw', 'cr', 'X4', 'xw', 'X1',
'JAPP_EWMS_Item_Replenishment_Levels'])
except OperationalError as e:
logger.error("Failed to read Jeeves DB structure")
raise e
@ -194,3 +195,6 @@ def receive_attribute_instrument(cls, key, inst):
"listen for the 'attribute_instrument' event"
install_validator_listner(cls, key, inst)
db.set_model_class(RawBaseModel)

View file

@ -52,3 +52,5 @@ class LengthValidator():
state.__class__.__name__, state.__class__._map_columns(self.col_name),
len(value), self.max_length))
return value
# Add more validators, such as type for ints.

View file

@ -67,8 +67,8 @@ class ArticleUnit(RawBaseModel):
ArtNr = Column(String, ForeignKey('ar.ArtNr'), primary_key=True)
AltEnhetKod = Column(Integer, ForeignKey('xae.AltEnhetKod'), primary_key=True)
ArticleAlternativeUnit = relationship(ArticleAlternativeUnit)
AltEnhetKod = Column(String, ForeignKey('xae.AltEnhetKod'), primary_key=True)
ArticleAlternativeUnit = relationship(ArticleAlternativeUnit, lazy='joined')
class ArticleBalance(RawBaseModel):
@ -89,6 +89,30 @@ class ArticleBalance(RawBaseModel):
ArtNr = Column(Integer, ForeignKey('ar.ArtNr'), primary_key=True)
class ArticleEAN(RawBaseModel):
__tablename__ = 'arean'
__column_map__ = {'ArtNrEAN': 'EAN', 'ArtNr': 'ArticleNumber'}
__to_dict_only__ = ('ArtNr', 'ArtNrEAN', 'ArticleUnit')
ArtNr = Column(String, ForeignKey('ar.ArtNr'), primary_key=True)
ArtNrEAN = Column(String, primary_key=True)
AltEnhetKod = Column(String, ForeignKey('xare.AltEnhetKod'))
ArticleUnit = relationship(ArticleUnit, lazy='joined')
class ArticleShelf(RawBaseModel):
__tablename__ = 'arsh'
__column_map__ = {'LagPlats': 'Shelf',
'LagStalle': 'WarehouseID',
'JAPP_EWMS_zoneid': 'WMSZoneID',
'ArtNr': 'ArticleNumber'}
__to_dict_only__ = ('LagPlats', 'LagStalle', 'JAPP_EWMS_zoneid', 'ArtNr')
LagPlats = Column(String, ForeignKey('ar.ArtNr'), primary_key=True)
LagStalle = Column(String, ForeignKey('ar.ArtNr'), primary_key=True)
class VATRate(RawBaseModel):
__tablename__ = 'X1'
__column_map__ = {'MomsKod': 'VATID', 'MomsSats': 'VATRate'}
@ -200,12 +224,12 @@ class Article(RawBaseModel):
except TypeError:
logger.debug("NoneType error, %s" % self.ArtNr)
@classmethod
def _base_filters(self, obj):
return RawBaseModel._base_filters(
obj,
and_(obj.ItemStatusCode == 0)
)
# @classmethod
# def _base_filters(self, obj):
# return RawBaseModel._base_filters(
# obj,
# and_(obj.ItemStatusCode == 0)
# )
class ContactInformationType(RawBaseModel):
@ -535,3 +559,16 @@ class OrderItem(RawBaseModel):
pers_sign=self['PersSign']).callproc()
self['OrdRadNr'] = row_no
return self
class ItemReplenishmentLevels(RawBaseModel):
# __table_args__ = {'mssql_autoincrement': False, 'extend_existing': True}
# __table_args__ = {'implicit_returning': False, 'extend_existing': True}
__tablename__ = 'JAPP_EWMS_Item_Replenishment_Levels'
__column_map__ = {'ArtNr': 'ArticleNumber', 'LagPlats': 'Shelf', 'LagStalle': 'WarehouseID'}
__to_dict_only__ = ('LagPlats', 'ArtNr')
# Workaround for:
# "Table 'JAPP_EWMS_Item_Replenishment_Levels' does not have the identity property.
# Cannot perform SET operation."
ForetagKod = Column(Integer, primary_key=True, autoincrement=False)

View file

@ -24,9 +24,11 @@ class StoredProcedure(OrderedDict):
FROM sys.objects AS SO
INNER JOIN sys.parameters AS P
ON SO.OBJECT_ID = P.OBJECT_ID
WHERE SO.name LIKE '%Jeeves_Esales_%' OR
SO.name LIKE '%JAPP_spr_LogTrade_%' AND
SO.OBJECT_ID IN ( SELECT OBJECT_ID
WHERE (
SO.name LIKE '%Jeeves_Esales_%' OR
SO.name LIKE '%JAPP_spr_LogTrade_%'
) AND
SO.OBJECT_ID IN ( SELECT OBJECT_ID
FROM sys.objects
WHERE TYPE IN ('P','FN'))
ORDER BY [Schema], SO.name, P.parameter_id"""

View file

@ -3,3 +3,4 @@ from .article import Article, ArticleCategory
from .company import Company
from .pricelist import PriceList
from .order import Order
from .warehouse import Warehouse

View file

@ -1,9 +1,12 @@
# -*- coding: utf-8 -*-
from pyjeeves.models.raw import Article as ArticleModel, ProductClass, ArticleClass, CommodityGroup
from pyjeeves.models.raw import (
Article as ArticleModel,
ProductClass, ArticleClass, CommodityGroup, ArticleEAN, ArticleUnit)
from pyjeeves.models import db
from sqlalchemy.sql.expression import and_
from sqlalchemy.orm.exc import NoResultFound
from gtin import GTIN
from pyjeeves import logging
logger = logging.getLogger("PyJeeves." + __name__)
@ -17,22 +20,31 @@ class Article():
def get(art_no):
""" Query an article by number """
try:
return db.raw.query(ArticleModel).filter_by(
return db.raw_session.query(ArticleModel).filter_by(
ArtNr=str(art_no)
).one()
except NoResultFound:
raise KeyError
@staticmethod
def get_all(filter_=and_(ArticleModel.ItemStatusCode == 0, ArticleModel.ArtKod != 2)):
def get_all(filter_=and_(
ArticleModel.ItemStatusCode == 0,
ArticleModel.ArtKod != 2,
ArticleModel.VaruGruppKod != 90,
ArticleModel.ArtProdKlass != 0)
):
# .filter_by(ItemStatusCode=0, ArtKod=2)
return db.raw.query(ArticleModel).filter(filter_).all()
return db.raw_session.query(ArticleModel).filter(filter_).all()
@staticmethod
def get_article_units(filter_=and_()):
return db.raw_session.query(ArticleUnit).filter(filter_).all()
@staticmethod
def is_salable(art_no_list=[]):
""" Returns true if all articles are salable,
else false with error information """
articles = db.raw.query(ArticleModel).filter(
articles = db.raw_session.query(ArticleModel).filter(
and_(ArticleModel.ArtNr.in_(art_no_list))).all()
blocked_articles = [article.ArtNr for article in articles
@ -50,6 +62,43 @@ class Article():
return True, {}
@staticmethod
def get_article_gtins():
return db.raw_session.query(ArticleEAN).all()
@staticmethod
def add_article_gtins(gtins=[], dry_run=False):
# Expects a list of dicts like this:
# [{
# 'article_no': article.ArtNr,
# 'article_gtin': gtin,
# 'unit': unit.AltEnhetKod,
# }]
for gtin in gtins:
n1 = ArticleEAN(
ArtNr=gtin['article_no'], AltEnhetKod=gtin.get('unit', None),
ArtNrEAN=str(gtin['article_gtin']), ForetagKod=1)
if dry_run:
logger.info('Creating GTIN for %s, %s, %s' % (n1.ArtNr, n1.AltEnhetKod, n1.ArtNrEAN))
continue
db.raw_db.add(n1)
logger.debug('Created/updated Article EAN for %s - %s with GTIN %s' % (
gtin['article_no'], gtin.get('unit', 'no unit'), gtin['article_gtin']))
db.raw_db.commit()
logger.info('Succesfully commited %s GTINs to database' % (len(gtins)))
@staticmethod
def clear_article_gtins():
gtins = db.raw_session.query(ArticleEAN).all()
for gtin in gtins:
db.raw_db.delete(gtin)
db.raw_db.commit()
logger.info('Deleted %s GTINs' % (len(gtins)))
class ArticleCategory():
"""Handles article categories, such as classes and groups in Jeeves"""
@ -57,27 +106,362 @@ class ArticleCategory():
@staticmethod
def get_all():
# .filter_by(ItemStatusCode=0, ArtKod=2)
prod_classes = db.raw.query(ProductClass).all()
art_classes = db.raw.query(ArticleClass).all()
com_groups = db.raw.query(CommodityGroup).all()
prod_classes = db.raw_session.query(ProductClass).all()
art_classes = db.raw_session.query(ArticleClass).all()
com_groups = db.raw_session.query(CommodityGroup).all()
return {'ProductClasses': prod_classes,
'ArticleClasses': art_classes, 'CommodityGroups': com_groups}
# TODO: Should be moved to separate project with Lindvalls specific code
def get_gtin_for_article(article_ean, article_unit=None, use_prefix=True):
# If we don't want to prefix with 0, then exclude them here.
UNIT_MAPPING = {
'Påse': '',
'st': '',
'paket': 0,
'200g': 0,
'kg': 9,
'Kart': 1,
'Bricka': 1,
'½-pall': 2,
'tray_no_wrap': 8
}
prefixes = []
if article_unit:
# Find matching values in unit mapping
prefixes = [
val for key, val in UNIT_MAPPING.items()
if article_unit[0:len(key)].lower() in key.lower()]
if len(prefixes) > 1:
logger.warning('More than one unit match found in unit mapping')
# Use the first match
raw_gtin = (str(prefixes[0]) + article_ean) if prefixes and use_prefix else article_ean
# Handle GS1-128 GTIN code
if len(raw_gtin) >= 15 and raw_gtin[0:2] == '01':
raw_gtin = raw_gtin[2::]
article_gtin = GTIN(raw=raw_gtin)
return article_gtin
# TODO: Should be moved to separate project with Lindvalls specific code
def create_gtins_for_trading_goods(filename='gtin_trading_goods.csv'):
articles = Article.get_all(and_(
ArticleModel.ArtProdKlass == 4))
gtins = []
gtin_data = {}
import csv
with open(filename, newline='') as csvfile:
gtinreader = csv.reader(csvfile, delimiter=',')
headers = gtinreader.__next__()
logger.info('Found these columns: %s' % (', '.join(headers)))
for row in gtinreader:
gtin_data[row[0]] = row
logger.info("Found %s articles and updating with %s rows of data" % (
len(articles), len(gtin_data)))
for article in articles:
data = gtin_data.get(article.ArtNr)
if data:
default_set = False
if len(article.ArticleUnit) == 0 and data[3]:
logger.warning('Article %s has no ArticleUnits, but requires it' % (article.ArtNr))
for unit in article.ArticleUnit:
if unit.AltEnhetKod[0:3] == 'kart' and not data[3]:
logger.warning('Article %s missing kart unit' % (article.ArtNr))
if data[3] and unit.AltEnhetKod != 'st':
gtin = get_gtin_for_article(data[3], unit.AltEnhetKod, False)
# Only add GTINs for order units (not PO units)
if unit.AltEnhetOrder == '1':
gtins.append({
'article_no': article.ArtNr,
'article_gtin': gtin,
'unit': unit.AltEnhetKod
})
if unit.AltEnhetKod == 'st':
gtin = get_gtin_for_article(data[2], unit.AltEnhetKod, False)
# Only add GTINs for order units (not PO units)
if unit.AltEnhetOrder == '1':
gtins.append({
'article_no': article.ArtNr,
'article_gtin': gtin,
'unit': unit.AltEnhetKod
})
default_set = True
# Add default gtin if 'st' not used
if not default_set:
gtin = get_gtin_for_article(data[2], None, False)
gtins.append({
'article_no': article.ArtNr,
'article_gtin': gtin
})
else:
# Warn about active and stock items that didn't get updated.
if article.LagTyp == 0 and article.ItemStatusCode == 0:
logger.warning('Article %s has no GTIN data in CSV' % (article.ArtNr))
Article.add_article_gtins(gtins)
# TODO: Should be moved to separate project with Lindvalls specific code
def create_gtins(dry_run=True):
# GS1 Company Prefixes that we manage locally, prefixing etc.
LOCAL_GCPS = [
'731083', # Lindvalls Kaffe
'7392736', # Sackeus AB
'735007318', # Sarria Import AB
'732157', # Martin & Servera AB
'350096', # Scænsei Thee Kompani AB (Used by REKYL In Omnia Paratus AB)
'735003307', # Coffee Please
'735003711', # Emmas Skafferi AB (Used by Coffee Please)
'735003712', # Prefix no longer subscribed (Used by Coffee Please)
'735003302', # Josephine Selander - YogaGo (Used by Coffee Please)
]
articles = Article.get_all(and_(
ArticleModel.ItemStatusCode == 0,
ArticleModel.VaruGruppKod != 90,
ArticleModel.ArtProdKlass != 0))
articles_with_existing_gtins = [
gtin.ArtNr for i, gtin in enumerate(Article.get_article_gtins())]
gtins = []
for article in articles:
if article.ArtNr in articles_with_existing_gtins:
continue
if not article.ArtStreckKod:
logger.warning('No base GTIN for article %s' % (article.ArtNr))
continue
GCP = GTIN(raw=article.ArtStreckKod).gcp
if 12 < len(article.ArtStreckKod) < 12 and GCP in LOCAL_GCPS:
logger.error('Base GTIN is wrong length for article %s' % (article.ArtNr))
continue
# If GTIN is provided by vendor, skip prefixes and gohead if only one or no units exist.
if GCP not in LOCAL_GCPS and len(article.ArticleUnit) <= 1:
use_prefix = False
logger.info('Externally provided GTIN for %s, skipping prefixes' % (article.ArtNr))
elif GCP not in LOCAL_GCPS and len(article.ArticleUnit) > 1:
logger.warning('Externally provided GTIN for %s, too many units' % (article.ArtNr))
continue
else:
use_prefix = True
# Create gtin without ArticleUnit, for the base unit.
# gtins.append({
# 'article_no': article.ArtNr,
# 'article_gtin': get_gtin_for_article(article.ArtStreckKod, None, False)
# })
for unit in article.ArticleUnit:
# Skip paket for 21%, should only match HV with plastic wrapping.
if article.ArtNr[0:2] == '21' and unit.AltEnhetKod[0:6].lower() == 'paket':
logger.info('Skip paket unit for %s' % (article.ArtNr))
continue
# Special for 20%/30%, should only match HV without plastic wrapping.
if article.ArtNr[0:2] in ('20', '30') and unit.AltEnhetKod[0:6].lower() == 'bricka':
unit_code = 'tray_no_wrap'
else:
unit_code = unit.AltEnhetKod
gtin = get_gtin_for_article(article.ArtStreckKod, unit_code, use_prefix)
# Only add GTINs for order units (not PO units)
if unit.AltEnhetOrder == '1':
gtins.append({
'article_no': article.ArtNr,
'article_gtin': gtin,
'unit': unit.AltEnhetKod
})
# Workaround for scanning HV base units without plastic wrapping
if str(gtin)[0] == '0':
# Create gtin without ArticleUnit, for the base unit.
gtins.append({
'article_no': article.ArtNr,
'article_gtin': get_gtin_for_article(article.ArtStreckKod, None, False)
})
# Add GTIN to articles that don't use article units
# Should this still be added to arean/ArticleEAN???
# if len(article.ArticleUnit) == 0:
# gtin = get_gtin_for_article(article.ArtStreckKod, None, use_prefix)
# gtins.append({
# 'article_no': article.ArtNr,
# 'article_gtin': gtin,
# 'unit': None,
# })
# add_gtin_for_article(
# article.ArtNr, article.ArtStreckKod, None, use_prefix)
Article.add_article_gtins(gtins, dry_run)
# TODO: Should be moved to separate project with Lindvalls specific code
def find_articles_without_base_gtin():
articles = Article.get_all(and_(
ArticleModel.ItemStatusCode == 0,
ArticleModel.VaruGruppKod != 90,
ArticleModel.ArtProdKlass != 0))
_list = []
for article in articles:
if not article.ArtStreckKod:
_list.append(
{'artnr': article.ArtNr,
'artbeskr': article.ArtBeskr,
'error': 'no_base'})
continue
else:
if 12 < len(article.ArtStreckKod) < 12:
_list.append(
{'artnr': article.ArtNr,
'artbeskr': article.ArtBeskr,
'error': 'wrong_length'})
continue
for item in _list:
print('{artnr}, "{artbeskr}", {error}'.format(**item))
# TODO: Should be moved to separate project with Lindvalls specific code
def set_storage_type():
articles = Article.get_all(and_(
ArticleModel.LagTyp == 0,
ArticleModel.ItemStatusCode == 0,
ArticleModel.AnskaffningsSatt == 10))
for article in articles:
article.LagTyp = 4
db.raw_db.commit()
logger.info("Updated storage type for %s articles" % (len(articles)))
# TODO: Should be moved to separate project with Lindvalls specific code
def set_zone_placement():
# Logic for article groups and zones
# ArticleClass descides which zone to put it.
# set Article.ArticleBalance[0].japp_ewms_rec_zoneid to correct zoneid
article_class_map = {
'Kaffe': 'U',
'OoH-Kaffe': 'K',
'Private Label': 'S',
'Tillbehör och maskiner': 'U',
'Komplement': 'U'
}
articles = Article.get_all(and_(
ArticleModel.ItemStatusCode == 0,
ArticleModel.AnskaffningsSatt == 10))
zone_placements_update = 0
for article in articles:
zone_id = article_class_map.get(article.ArticleClass.ArtTypBeskr)
if zone_id and article.ArticleBalance:
article.ArticleBalance[0].JAPP_EWMS_REC_ZoneID = zone_id
zone_placements_update += 1
else:
logger.info("Excluded %s, wrong article class or no balance " % (article.ArtNr))
db.raw_db.commit()
logger.info("Updated placement zone for %s articles" % (zone_placements_update))
# a = Article.get('2109')
# print([ab.to_dict() for ab in a['ArticleBalance']])
def update_decimals_on_alt_units():
units = Article.get_article_units(ArticleUnit.AltEnhetKod == 'påse')
updated_units = 0
for unit in units:
if unit.AltEnhetOmrFaktor is not None:
dec_count = 0
for digit in unit.AltEnhetOmrFaktor.as_tuple().digits:
if digit != 0:
dec_count += 1
unit.AltEnhetAntDec = dec_count
updated_units += 1
db.raw_db.commit()
logger.info("Updated decimal count for %s article units" % (updated_units))
if __name__ == '__main__':
# print([column.key for column in Company.__table__.columns])
logger.info("Starting TEST")
# from pprint import pprint
# logger.info("Starting TEST")
# session = RawSession()
logger.info("Testing gettings an article")
# c1 = session.query(Company).filter_by(FtgNr="179580").first()
# print(ArticleModel)
c1 = ArticleModel.query.filter_by(ArtNr="2103").first()
print(c1)
logger.info(c1.json)
# logger.info("Testing gettings an article")
# # c1 = session.query(Company).filter_by(FtgNr="179580").first()
# # print(ArticleModel)
# c1 = db.raw_session.query(ArticleModel).filter_by(ArtNr="2003").first()
# c1 = Article.get("2003")
# pprint([unit.to_dict() for unit in c1.ArticleUnit])
# pprint(c1.to_dict())
# pprint([(au.to_dict(), au.AltEnhetOrder) for au in c1.ArticleUnit])
# logger.info(c1.to_dict())
print(
len(ArticleModel.get_all())
)
# print(
# len(Article.get_all())
# )
# c1 = db.raw_session.query(ArticleEAN).all()
# pprint([c.to_dict() for c in c1])
# c1 = db.raw_session.query(ArticleEAN).filter_by(ArtNr="1054").first()
# pprint(c1.to_dict())
# c1.ArtNrEAN = '7310830010548'
# pprint(c1.to_dict())
# c1.save()
# logger.info(c1.to_dict())
# create_gtins_for_trading_goods('gtin_trading_goods_test.csv')
# LIVE FUNCTIONS BELOW
# find_articles_without_base_gtin()
# logger.info("Truncating GTINs")
# Article.clear_article_gtins()
logger.info("Creating new GTINs from base GTIN")
create_gtins(dry_run=False)
# logger.info("Creating new GTINs from trading goods CSV")
# create_gtins_for_trading_goods()
# logger.info("Update articles for batch management")
# set_storage_type()
# logger.info("Set zone information on article balance")
# set_zone_placement()
# logger.info("Updating alt units")
# update_decimals_on_alt_units()

View file

@ -18,7 +18,7 @@ class Company():
def get(ftg_nr):
""" Query an article by number """
try:
return db.raw.query(CompanyModel).filter_by(
return db.raw_session.query(CompanyModel).filter_by(
FtgNr=ftg_nr
).one()
except NoResultFound:
@ -26,17 +26,21 @@ class Company():
@staticmethod
def get_all_active_customers():
cust = db.raw.query(CustomerModel).filter(and_(CustomerModel.Makulerad == 0)).all()
cust = db.raw_session.query(CustomerModel).filter(and_(CustomerModel.Makulerad == 0)).all()
return [c.CompanyModel for c in cust]
@staticmethod
def get_customer_numbers(category_list=[10], class_list=[], filter_inactive=True):
def get_customers(category_list=[10], class_list=[], filter_inactive=True, filters=and_()):
category_in = CustomerModel.kundkategorikod.in_(category_list) if category_list else and_()
class_in = CustomerModel.kundklass.in_(class_list) if class_list else and_()
inactive = and_(CustomerModel.Makulerad == 0) if filter_inactive else and_()
cust = db.raw.query(CustomerModel).options(
Load(CustomerModel).noload('*')).filter(
and_(category_in, class_in, inactive)).all()
return db.raw_session.query(CustomerModel).options(
Load(CompanyModel).noload('*')).filter(
and_(category_in, class_in, inactive, filters)).all()
@staticmethod
def get_customer_numbers(category_list=[10], class_list=[], filter_inactive=True):
cust = Company.get_customers(category_list, class_list, filter_inactive)
return [c.FtgNr for c in cust]
@staticmethod
@ -45,24 +49,71 @@ class Company():
if ftg_nr:
ftg_filter = CompanyModel.FtgNr.in_(ftg_nr)
return db.raw.query(CompanyModel).join(CustomerModel).filter(
return db.raw_session.query(CompanyModel).join(CustomerModel).filter(
and_(ftg_filter, filter_)).order_by(
CompanyModel.FtgNr.desc()).offset(offset).limit(limit).all()
# TODO: Should be moved to separate project with Lindvalls specific code
def update_customer_delivery_from_csv(filename='zip_codes_svhl.csv'):
SVHL_ZONES = {}
logger.info("Get customers")
customers = Company.get_customers(filters=(
and_(CustomerModel.LevSattKod == 3, CustomerModel.kundklass == None))) # noqa
logger.info("Amount of customers is %d" % len(customers))
import csv
with open(filename, newline='') as csvfile:
shelfreader = csv.reader(csvfile, delimiter=',')
headers = shelfreader.__next__()
logger.info('Found these columns: %s' % (', '.join(headers)))
for row in shelfreader:
SVHL_ZONES[row[0]] = '%s - %s' % (row[2], row[1])
logger.info('Length of zones dict is %d' % (len(SVHL_ZONES)))
customers_to_update = 0
for customer in customers:
if customer.Company.FtgLevPostNr:
FtgLevPostNr = customer.Company.FtgLevPostNr.strip().replace(" ", "")
if FtgLevPostNr and FtgLevPostNr in SVHL_ZONES:
logger.info('FtgLevPostNr: %s - %s is within SVHL zone %s' % (
customer.FtgNr, customer.Company.FtgNamn, SVHL_ZONES[FtgLevPostNr]))
customers_to_update += 1
customer.LevSattKod = 11
continue
# Return? Break?
if customer.Company.FtgPostnr:
FtgPostnr = customer.Company.FtgPostnr.strip().replace(" ", "")
if FtgPostnr and FtgPostnr in SVHL_ZONES:
logger.info('FtgPostnr: %s - %s is within SVHL zone %s' % (
customer.FtgNr, customer.Company.FtgNamn, SVHL_ZONES[FtgPostnr]))
customer.LevSattKod = 11
customers_to_update += 1
logger.info('Amount updated %d' % customers_to_update)
# db.raw_db.merge(n1)
db.raw_db.commit()
logger.info('Succesfully commited updated customers to database')
if __name__ == '__main__':
# print([column.key for column in CompanyModel.__table__.columns])
logger.info("Starting TEST")
# logger.info("Starting TEST")
# session = RawSession()
logger.info("Testing gettings a company")
# logger.info("Testing gettings a company")
# c1 = session.query(CompanyModel).filter_by(FtgNr="179580").first()
# print(CompanyModel)
# print(CompanyModel.get_list(['406569', '179580', '2440070', '179584']))
from pprint import pprint
pprint(CompanyModel.get('179584').to_dict())
# from pprint import pprint
# pprint(CompanyModel.get('179584').to_dict())
# c1 = CompanyModel.query.filter_by(FtgNr="406569").first()
# print(c1)
@ -73,3 +124,6 @@ if __name__ == '__main__':
# )
# print(CompanyModel.get_all_active_customers()[0].CompanyModel)
logger.info("Starting")
update_customer_delivery_from_csv()

View file

@ -1,8 +1,9 @@
nose==1.3.7
Sphinx==1.8.5
pymssql==2.1.4
SQLAlchemy==1.3.1
sqlservice==1.1.3
PyMySQL==0.9.3
alembic==1.0.8
PyYAML==5.1
Sphinx==3.2.1
pymssql-py38==2.1.4
SQLAlchemy==1.3.22
sqlservice==1.2.1
PyMySQL==0.10.0
alembic==1.4.2
PyYAML==5.3.1
gtin==0.1.13

View file

@ -12,7 +12,7 @@ with open('LICENSE') as f:
setup(
name='pyjeeves',
version='0.0.1',
description='PyJeeves syncronization module',
description='PyJeeves communication module',
long_description=readme,
author='Marcus Lindvall',
author_email='marcus.lindvall@lindvallskaffe.se',
@ -22,11 +22,12 @@ setup(
install_requires=[
'nose',
'sphinx',
'pymssql',
'pymssql-py38',
'sqlalchemy',
'sqlservice',
'PyMySQL',
'alembic',
'pyyaml',
'gtin'
]
)