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

@ -2,4 +2,5 @@ from .location import Location
from .article import Article, ArticleCategory
from .company import Company
from .pricelist import PriceList
from .order import Order
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'
]
)