fix: misc. improvements for CORE API importer, per flaky data
handle some edge cases better; let config dictate whether some warnings should be logged etc.
This commit is contained in:
parent
dca2c1bfe2
commit
4752409a45
|
@ -2,7 +2,7 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# Rattail -- Retail Software Framework
|
# Rattail -- Retail Software Framework
|
||||||
# Copyright © 2010-2023 Lance Edgar
|
# Copyright © 2010-2024 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Rattail.
|
# This file is part of Rattail.
|
||||||
#
|
#
|
||||||
|
@ -26,8 +26,6 @@ DataSync for Rattail DB
|
||||||
|
|
||||||
from sqlalchemy import orm
|
from sqlalchemy import orm
|
||||||
|
|
||||||
from corepos.db.office_op import Session as CoreSession, model as corepos
|
|
||||||
|
|
||||||
from rattail.datasync import DataSyncImportConsumer
|
from rattail.datasync import DataSyncImportConsumer
|
||||||
|
|
||||||
|
|
||||||
|
@ -143,7 +141,7 @@ class FromCOREAPIToRattail(DataSyncImportConsumer):
|
||||||
if len(fields) == 2:
|
if len(fields) == 2:
|
||||||
sku, vendorID = fields
|
sku, vendorID = fields
|
||||||
vendor_item = self.api.get_vendor_item(sku, vendorID)
|
vendor_item = self.api.get_vendor_item(sku, vendorID)
|
||||||
if vendor_item:
|
if vendor_item and vendor_item.get('upc'):
|
||||||
return self.api.get_product(vendor_item['upc'])
|
return self.api.get_product(vendor_item['upc'])
|
||||||
|
|
||||||
|
|
||||||
|
@ -154,7 +152,8 @@ class FromCOREPOSToRattailBase(DataSyncImportConsumer):
|
||||||
handler_spec = 'rattail_corepos.importing.corepos.db:FromCOREPOSToRattail'
|
handler_spec = 'rattail_corepos.importing.corepos.db:FromCOREPOSToRattail'
|
||||||
|
|
||||||
def begin_transaction(self):
|
def begin_transaction(self):
|
||||||
self.corepos_session = CoreSession()
|
corepos = self.app.get_corepos_handler()
|
||||||
|
self.corepos_session = corepos.make_session_office_op()
|
||||||
|
|
||||||
def rollback_transaction(self):
|
def rollback_transaction(self):
|
||||||
self.corepos_session.rollback()
|
self.corepos_session.rollback()
|
||||||
|
@ -172,16 +171,18 @@ class FromCOREPOSToRattailProducts(FromCOREPOSToRattailBase):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def get_host_object(self, session, change):
|
def get_host_object(self, session, change):
|
||||||
|
corepos = self.app.get_corepos_handler()
|
||||||
|
op_model = corepos.get_model_office_op()
|
||||||
|
|
||||||
if change.payload_type == 'Product':
|
if change.payload_type == 'Product':
|
||||||
try:
|
try:
|
||||||
return self.corepos_session.query(corepos.Product)\
|
return self.corepos_session.query(op_model.Product)\
|
||||||
.filter(corepos.Product.upc == change.payload_key)\
|
.filter(op_model.Product.upc == change.payload_key)\
|
||||||
.one()
|
.one()
|
||||||
except orm.exc.NoResultFound:
|
except orm.exc.NoResultFound:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# try to fetch CORE POS object via typical method
|
# try to fetch CORE POS object via typical method
|
||||||
Model = getattr(corepos, change.payload_type)
|
Model = getattr(op_model, change.payload_type)
|
||||||
return self.corepos_session.get(Model, int(change.payload_key))
|
return self.corepos_session.get(Model, int(change.payload_key))
|
||||||
|
|
|
@ -489,15 +489,25 @@ class ProductImporter(FromCOREPOSAPI, corepos_importing.model.ProductImporter):
|
||||||
self.vendor_items_by_upc = {}
|
self.vendor_items_by_upc = {}
|
||||||
|
|
||||||
def cache(item, i):
|
def cache(item, i):
|
||||||
self.vendor_items_by_upc.setdefault(item['upc'], []).append(item)
|
if item.get('upc'):
|
||||||
|
self.vendor_items_by_upc.setdefault(item['upc'], []).append(item)
|
||||||
|
|
||||||
self.progress_loop(cache, self.api.get_vendor_items(),
|
self.progress_loop(cache, self.api.get_vendor_items(),
|
||||||
message="Caching CORE Vendor Items")
|
message="Caching CORE Vendor Items")
|
||||||
|
|
||||||
self.maxval_unit_size = self.app.maxval(model.Product.unit_size)
|
|
||||||
|
|
||||||
def get_host_objects(self):
|
def get_host_objects(self):
|
||||||
return self.api.get_products()
|
products = OrderedDict()
|
||||||
|
|
||||||
|
def collect(product, i):
|
||||||
|
if product['upc'] in products:
|
||||||
|
log.warning("duplicate UPC encountered for '%s'; will discard: %s",
|
||||||
|
product['upc'], product)
|
||||||
|
else:
|
||||||
|
products[product['upc']] = product
|
||||||
|
|
||||||
|
self.progress_loop(collect, self.api.get_products(),
|
||||||
|
message="Fetching product info from CORE-POS")
|
||||||
|
return list(products.values())
|
||||||
|
|
||||||
def identify_product(self, corepos_product):
|
def identify_product(self, corepos_product):
|
||||||
model = self.config.get_model()
|
model = self.config.get_model()
|
||||||
|
@ -537,6 +547,7 @@ class ProductImporter(FromCOREPOSAPI, corepos_importing.model.ProductImporter):
|
||||||
return self.api.get_vendor_items(upc=api_product['upc'])
|
return self.api.get_vendor_items(upc=api_product['upc'])
|
||||||
|
|
||||||
def normalize_host_object(self, product):
|
def normalize_host_object(self, product):
|
||||||
|
model = self.model
|
||||||
if 'upc' not in product:
|
if 'upc' not in product:
|
||||||
log.warning("CORE-POS product has no UPC: %s", product)
|
log.warning("CORE-POS product has no UPC: %s", product)
|
||||||
return
|
return
|
||||||
|
@ -594,7 +605,8 @@ class ProductImporter(FromCOREPOSAPI, corepos_importing.model.ProductImporter):
|
||||||
'uom_abbreviation': (size_info['uom_abbrev'] or '').strip() or None,
|
'uom_abbreviation': (size_info['uom_abbrev'] or '').strip() or None,
|
||||||
})
|
})
|
||||||
|
|
||||||
if data['unit_size'] and data['unit_size'] >= self.maxval_unit_size:
|
maxval = self.app.maxval(model.Product.unit_size)
|
||||||
|
if data['unit_size'] and data['unit_size'] >= maxval:
|
||||||
log.warning("unit_size too large (%s) for product %s, will use null instead: %s",
|
log.warning("unit_size too large (%s) for product %s, will use null instead: %s",
|
||||||
data['unit_size'], data['upc'], product)
|
data['unit_size'], data['upc'], product)
|
||||||
data['unit_size'] = None
|
data['unit_size'] = None
|
||||||
|
@ -693,6 +705,10 @@ class ProductCostImporter(FromCOREPOSAPI, corepos_importing.model.ProductCostImp
|
||||||
model.Product,
|
model.Product,
|
||||||
key='item_id')
|
key='item_id')
|
||||||
|
|
||||||
|
def should_warn_for_missing_vendor_id(self):
|
||||||
|
return self.config.getbool('rattail.importing.corepos.vendor_items.warn_for_missing_vendor_id',
|
||||||
|
default=True)
|
||||||
|
|
||||||
def get_host_objects(self):
|
def get_host_objects(self):
|
||||||
|
|
||||||
# first we will cache API products by upc
|
# first we will cache API products by upc
|
||||||
|
@ -707,13 +723,15 @@ class ProductCostImporter(FromCOREPOSAPI, corepos_importing.model.ProductCostImp
|
||||||
|
|
||||||
# next we cache API vendor items, also by upc
|
# next we cache API vendor items, also by upc
|
||||||
vendor_items = {}
|
vendor_items = {}
|
||||||
|
warn_for_missing_vendor_id = self.should_warn_for_missing_vendor_id()
|
||||||
|
|
||||||
def cache(item, i):
|
def cache(item, i):
|
||||||
if not item['upc']:
|
if not item['upc']:
|
||||||
log.warning("CORE vendor item has no upc: %s", item)
|
log.warning("CORE vendor item has no upc: %s", item)
|
||||||
return
|
return
|
||||||
if item['vendorID'] == '0':
|
if item['vendorID'] == '0':
|
||||||
log.warning("CORE vendor item has no vendorID: %s", item)
|
logger = log.warning if warn_for_missing_vendor_id else log.debug
|
||||||
|
logger("CORE vendor item has no vendorID: %s", item)
|
||||||
return
|
return
|
||||||
vendor_items.setdefault(item['upc'], []).append(item)
|
vendor_items.setdefault(item['upc'], []).append(item)
|
||||||
|
|
||||||
|
@ -766,7 +784,9 @@ class ProductCostImporter(FromCOREPOSAPI, corepos_importing.model.ProductCostImp
|
||||||
return self.api.get_product(item['upc'])
|
return self.api.get_product(item['upc'])
|
||||||
|
|
||||||
def get_product(self, item):
|
def get_product(self, item):
|
||||||
item_id = item['upc']
|
item_id = item.get('upc')
|
||||||
|
if not item_id:
|
||||||
|
return
|
||||||
|
|
||||||
if hasattr(self, 'products_by_item_id'):
|
if hasattr(self, 'products_by_item_id'):
|
||||||
return self.products_by_item_id.get(item_id)
|
return self.products_by_item_id.get(item_id)
|
||||||
|
@ -873,7 +893,7 @@ class ProductCostImporter(FromCOREPOSAPI, corepos_importing.model.ProductCostImp
|
||||||
# vendor items and then filter locally
|
# vendor items and then filter locally
|
||||||
items = [item
|
items = [item
|
||||||
for item in self.api.get_vendor_items()
|
for item in self.api.get_vendor_items()
|
||||||
if item['upc'] == product['upc']]
|
if item.get('upc') == product['upc']]
|
||||||
|
|
||||||
vendor_id = product['default_vendor_id']
|
vendor_id = product['default_vendor_id']
|
||||||
self.sort_these_vendor_items(items, vendor_id)
|
self.sort_these_vendor_items(items, vendor_id)
|
||||||
|
@ -973,6 +993,18 @@ class MemberImporter(FromCOREPOSAPI, corepos_importing.model.MemberImporter):
|
||||||
self.people_by_card_number = self.cache_model(model.Person, query=query,
|
self.people_by_card_number = self.cache_model(model.Person, query=query,
|
||||||
key=card_number)
|
key=card_number)
|
||||||
|
|
||||||
|
self.membership_type_number_non_member = self.get_membership_type_number_non_member()
|
||||||
|
|
||||||
|
def get_membership_type_number_non_member(self):
|
||||||
|
if hasattr(self, 'membership_type_number_non_member'):
|
||||||
|
return self.membership_type_number_non_member
|
||||||
|
|
||||||
|
return self.config.getint('corepos.membership_type.non_member')
|
||||||
|
|
||||||
|
def should_warn_for_unknown_membership_type(self):
|
||||||
|
return self.config.getbool('rattail.importing.corepos.warn_for_unknown_membership_type',
|
||||||
|
default=True)
|
||||||
|
|
||||||
def get_host_objects(self):
|
def get_host_objects(self):
|
||||||
return self.get_core_members()
|
return self.get_core_members()
|
||||||
|
|
||||||
|
@ -1030,8 +1062,9 @@ class MemberImporter(FromCOREPOSAPI, corepos_importing.model.MemberImporter):
|
||||||
# important to import the full member info from CORE, so that
|
# important to import the full member info from CORE, so that
|
||||||
# we have it to sync back. therefore can't afford to "skip"
|
# we have it to sync back. therefore can't afford to "skip"
|
||||||
# any member records here
|
# any member records here
|
||||||
if (member['memberStatus'] not in self.member_status_codes
|
memstatus = (member['memberStatus'] or '').upper() or None
|
||||||
and member['memberStatus'] not in self.non_member_status_codes):
|
if (memstatus not in self.member_status_codes
|
||||||
|
and memstatus not in self.non_member_status_codes):
|
||||||
log.warning("unexpected status '%s' for member %s: %s",
|
log.warning("unexpected status '%s' for member %s: %s",
|
||||||
member['memberStatus'], card_number, member)
|
member['memberStatus'], card_number, member)
|
||||||
|
|
||||||
|
@ -1056,9 +1089,17 @@ class MemberImporter(FromCOREPOSAPI, corepos_importing.model.MemberImporter):
|
||||||
typeno = int(member['customerTypeID'] or 0)
|
typeno = int(member['customerTypeID'] or 0)
|
||||||
memtype = self.get_membership_type_by_number(typeno)
|
memtype = self.get_membership_type_by_number(typeno)
|
||||||
if not memtype:
|
if not memtype:
|
||||||
log.warning("unknown customerTypeID (membership_type_number) '%s' for: %s",
|
typeno = self.get_membership_type_number_non_member()
|
||||||
member['customerTypeID'], member)
|
if typeno is not None:
|
||||||
typeno = None
|
memtype = self.get_membership_type_by_number(typeno)
|
||||||
|
if not memtype:
|
||||||
|
raise ValueError("configured membership type for non-members is invalid!")
|
||||||
|
|
||||||
|
logger = log.warning if self.should_warn_for_unknown_membership_type() else log.debug
|
||||||
|
logger("unknown customerTypeID (membership_type_number) '%s' for: %s",
|
||||||
|
member['customerTypeID'], member)
|
||||||
|
if typeno is not None:
|
||||||
|
log.debug("(will override with membership_type_number: %s)", typeno)
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'number': card_number,
|
'number': card_number,
|
||||||
|
|
Loading…
Reference in a new issue