Deps updates, handle more tables + LK code
This commit is contained in:
parent
f649b5f953
commit
5fe140714e
14 changed files with 583 additions and 79 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -1,3 +1,5 @@
|
|||
*.csv
|
||||
|
||||
*.py[cod]
|
||||
|
||||
# C extensions
|
||||
|
|
|
|||
22
README.md
Normal file
22
README.md
Normal 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;´
|
||||
|
||||
22
README.rst
22
README.rst
|
|
@ -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;´
|
||||
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -24,8 +24,10 @@ 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
|
||||
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'))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
5
setup.py
5
setup.py
|
|
@ -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'
|
||||
]
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue