Add schema, logic for importing CORE VendorItem -> ProductCost

This commit is contained in:
Lance Edgar 2020-09-04 19:10:48 -05:00
parent b96a4bc7b9
commit 580f2093ae
5 changed files with 200 additions and 1 deletions

View file

@ -0,0 +1,51 @@
# -*- coding: utf-8 -*-
"""add corepos_product_cost
Revision ID: 130e4632a28a
Revises: 9c5029effe93
Create Date: 2020-09-04 18:30:17.041521
"""
# revision identifiers, used by Alembic.
revision = '130e4632a28a'
down_revision = '9c5029effe93'
branch_labels = None
depends_on = None
from alembic import op
import sqlalchemy as sa
import rattail.db.types
def upgrade():
# corepos_product_cost
op.create_table('corepos_product_cost',
sa.Column('uuid', sa.String(length=32), nullable=False),
sa.Column('corepos_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['uuid'], ['product_cost.uuid'], name='corepos_product_cost_fk_cost'),
sa.PrimaryKeyConstraint('uuid')
)
op.create_table('corepos_product_cost_version',
sa.Column('uuid', sa.String(length=32), autoincrement=False, nullable=False),
sa.Column('corepos_id', sa.Integer(), autoincrement=False, nullable=True),
sa.Column('transaction_id', sa.BigInteger(), autoincrement=False, nullable=False),
sa.Column('end_transaction_id', sa.BigInteger(), nullable=True),
sa.Column('operation_type', sa.SmallInteger(), nullable=False),
sa.PrimaryKeyConstraint('uuid', 'transaction_id')
)
op.create_index(op.f('ix_corepos_product_cost_version_end_transaction_id'), 'corepos_product_cost_version', ['end_transaction_id'], unique=False)
op.create_index(op.f('ix_corepos_product_cost_version_operation_type'), 'corepos_product_cost_version', ['operation_type'], unique=False)
op.create_index(op.f('ix_corepos_product_cost_version_transaction_id'), 'corepos_product_cost_version', ['transaction_id'], unique=False)
def downgrade():
# corepos_product_cost
op.drop_index(op.f('ix_corepos_product_cost_version_transaction_id'), table_name='corepos_product_cost_version')
op.drop_index(op.f('ix_corepos_product_cost_version_operation_type'), table_name='corepos_product_cost_version')
op.drop_index(op.f('ix_corepos_product_cost_version_end_transaction_id'), table_name='corepos_product_cost_version')
op.drop_table('corepos_product_cost_version')
op.drop_table('corepos_product_cost')

View file

@ -26,4 +26,4 @@ Database schema extensions for CORE-POS integration
from .people import CorePerson, CoreCustomer, CoreMember from .people import CorePerson, CoreCustomer, CoreMember
from .products import (CoreDepartment, CoreSubdepartment, from .products import (CoreDepartment, CoreSubdepartment,
CoreVendor, CoreProduct) CoreVendor, CoreProduct, CoreProductCost)

View file

@ -168,3 +168,38 @@ class CoreProduct(model.Base):
return str(self.product) return str(self.product)
CoreProduct.make_proxy(model.Product, '_corepos', 'corepos_id') CoreProduct.make_proxy(model.Product, '_corepos', 'corepos_id')
class CoreProductCost(model.Base):
"""
CORE-specific extensions to :class:`rattail:rattail.db.model.ProductCost`.
"""
__tablename__ = 'corepos_product_cost'
__table_args__ = (
sa.ForeignKeyConstraint(['uuid'], ['product_cost.uuid'],
name='corepos_product_cost_fk_cost'),
)
__versioned__ = {}
uuid = model.uuid_column(default=None)
cost = orm.relationship(
model.ProductCost,
doc="""
Reference to the actual ProductCost record, which this one extends.
""",
backref=orm.backref(
'_corepos',
uselist=False,
cascade='all, delete-orphan',
doc="""
Reference to the CORE-POS extension record for this product cost.
"""))
corepos_id = sa.Column(sa.Integer(), nullable=False, doc="""
``vendorItemID`` value for the corresponding record within CORE-POS.
""")
def __str__(self):
return str(self.cost)
CoreProductCost.make_proxy(model.ProductCost, '_corepos', 'corepos_id')

View file

@ -61,6 +61,7 @@ class FromCOREPOSToRattail(importing.ToRattailHandler):
importers['Subdepartment'] = SubdepartmentImporter importers['Subdepartment'] = SubdepartmentImporter
importers['Vendor'] = VendorImporter importers['Vendor'] = VendorImporter
importers['Product'] = ProductImporter importers['Product'] = ProductImporter
importers['ProductCost'] = ProductCostImporter
importers['ProductMovement'] = ProductMovementImporter importers['ProductMovement'] = ProductMovementImporter
return importers return importers
@ -570,6 +571,99 @@ class ProductMovementImporter(FromCOREPOSAPI, corepos_importing.model.ProductImp
} }
class ProductCostImporter(FromCOREPOSAPI, corepos_importing.model.ProductCostImporter):
"""
Importer for product cost data from CORE POS API.
"""
key = 'corepos_id'
supported_fields = [
'corepos_id',
'product_upc',
'vendor_uuid',
'code',
'case_size',
'case_cost',
'unit_cost',
'preferred',
]
def setup(self):
super(ProductCostImporter, self).setup()
model = self.config.get_model()
query = self.session.query(model.Vendor)\
.join(model.CoreVendor)\
.filter(model.CoreVendor.corepos_id != None)
self.vendors = self.cache_model(model.Vendor, query=query,
key='corepos_id')
self.corepos_products = {}
def cache(product, i):
self.corepos_products[product['upc']] = product
self.progress_loop(cache, self.api.get_products(),
message="Caching Products from CORE-POS API")
def get_host_objects(self):
return self.api.get_vendor_items()
def get_vendor(self, item):
corepos_id = int(item['vendorID'])
if hasattr(self, 'vendors'):
return self.vendors.get(corepos_id)
try:
return self.session.query(model.Vendor)\
.join(model.CoreVendor)\
.filter(model.CoreVendor.corepos_id == corepos_id)\
.one()
except orm.exc.NoResultFound:
pass
def get_corepos_product(self, item):
if hasattr(self, 'corepos_products'):
return self.corepos_products.get(item['upc'])
return self.api.get_vendor_item(item['upc'], item['vendorID'])
def normalize_host_object(self, item):
try:
upc = GPC(item['upc'], calc_check_digit='upc')
except (TypeError, ValueError):
log.warning("CORE POS vendor item has invalid UPC: %s", item['upc'])
return
vendor = self.get_vendor(item)
if not vendor:
log.warning("CORE POS vendor not found for item: %s", item)
return
product = self.get_corepos_product(item)
# if not product:
# log.warning("CORE POS product not found for item: %s", item)
# return
preferred = False
if product and product['default_vendor_id'] == item['vendorID']:
preferred = True
case_size = decimal.Decimal(item['units'])
unit_cost = decimal.Decimal(item['cost'])
return {
'corepos_id': int(item['vendorItemID']),
'product_upc': upc,
'vendor_uuid': vendor.uuid,
'code': (item['sku'] or '').strip() or None,
'case_size': case_size,
'case_cost': case_size * unit_cost,
'unit_cost': unit_cost,
'preferred': preferred,
}
class MemberImporter(FromCOREPOSAPI, corepos_importing.model.MemberImporter): class MemberImporter(FromCOREPOSAPI, corepos_importing.model.MemberImporter):
""" """
Importer for member data from CORE POS API. Importer for member data from CORE POS API.

View file

@ -107,3 +107,22 @@ class ProductImporter(importing.model.ProductImporter):
.filter(model.CoreProduct.corepos_id != None) .filter(model.CoreProduct.corepos_id != None)
return query return query
class ProductCostImporter(importing.model.ProductCostImporter):
extension_attr = '_corepos'
extension_fields = [
'corepos_id',
]
def cache_query(self):
query = super(ProductCostImporter, self).cache_query()
model = self.config.get_model()
# we want to ignore items with no CORE ID, if that's (part of) our key
if 'corepos_id' in self.key:
query = query.join(model.CoreProductCost)\
.filter(model.CoreProductCost.corepos_id != None)
return query