Add support for Department, Subdepartment, Product in Rattail -> CORE API
This commit is contained in:
parent
ab8894ef0d
commit
cd93d3e36b
|
@ -33,6 +33,11 @@ class ToCoreAPI(importing.Importer):
|
|||
"""
|
||||
Base class for all CORE "operational" model importers, which use the API.
|
||||
"""
|
||||
# TODO: these importers are in a bit of an experimental state at the
|
||||
# moment. we only allow create/update b/c it will use the API instead of
|
||||
# direct DB
|
||||
allow_delete = False
|
||||
|
||||
caches_local_data = True
|
||||
|
||||
def setup(self):
|
||||
|
@ -69,6 +74,98 @@ class ToCoreAPI(importing.Importer):
|
|||
data[field] = ''
|
||||
|
||||
|
||||
class DepartmentImporter(ToCoreAPI):
|
||||
"""
|
||||
Department model importer for CORE-POS
|
||||
"""
|
||||
model_name = 'Department'
|
||||
key = 'dept_no'
|
||||
supported_fields = [
|
||||
'dept_no',
|
||||
'dept_name',
|
||||
# TODO: should enable some of these fields?
|
||||
# 'dept_tax',
|
||||
# 'dept_fs',
|
||||
# 'dept_limit',
|
||||
# 'dept_minimum',
|
||||
# 'dept_discount',
|
||||
# 'dept_see_id',
|
||||
# 'modified',
|
||||
# 'modifiedby',
|
||||
# 'margin',
|
||||
# 'salesCode',
|
||||
# 'memberOnly',
|
||||
]
|
||||
|
||||
def get_local_objects(self, host_data=None):
|
||||
return self.api.get_departments()
|
||||
|
||||
def get_single_local_object(self, key):
|
||||
assert len(self.key) == 1
|
||||
assert self.key[0] == 'dept_no'
|
||||
return self.api.get_department(key[0])
|
||||
|
||||
def normalize_local_object(self, department):
|
||||
data = dict(department)
|
||||
return data
|
||||
|
||||
def create_object(self, key, data):
|
||||
# we can get away with using the same logic for both here
|
||||
return self.update_object(None, data)
|
||||
|
||||
def update_object(self, department, data, local_data=None):
|
||||
"""
|
||||
Push an update for the department, via the CORE API.
|
||||
"""
|
||||
if self.dry_run:
|
||||
return data
|
||||
|
||||
dept_no = data.pop('dept_no')
|
||||
department = self.api.set_department(dept_no, **data)
|
||||
return department
|
||||
|
||||
|
||||
class SubdepartmentImporter(ToCoreAPI):
|
||||
"""
|
||||
Subdepartment model importer for CORE-POS
|
||||
"""
|
||||
model_name = 'Subdepartment'
|
||||
key = 'subdept_no'
|
||||
supported_fields = [
|
||||
'subdept_no',
|
||||
'subdept_name',
|
||||
'dept_ID',
|
||||
]
|
||||
|
||||
def get_local_objects(self, host_data=None):
|
||||
return self.api.get_subdepartments()
|
||||
|
||||
def get_single_local_object(self, key):
|
||||
assert len(self.key) == 1
|
||||
assert self.key[0] == 'subdept_no'
|
||||
return self.api.get_subdepartment(key[0])
|
||||
|
||||
def normalize_local_object(self, subdepartment):
|
||||
data = dict(subdepartment)
|
||||
self.ensure_fields(data)
|
||||
return data
|
||||
|
||||
def create_object(self, key, data):
|
||||
# we can get away with using the same logic for both here
|
||||
return self.update_object(None, data)
|
||||
|
||||
def update_object(self, subdepartment, data, local_data=None):
|
||||
"""
|
||||
Push an update for the subdepartment, via the CORE API.
|
||||
"""
|
||||
if self.dry_run:
|
||||
return data
|
||||
|
||||
subdept_no = data.pop('subdept_no')
|
||||
subdepartment = self.api.set_subdepartment(subdept_no, **data)
|
||||
return subdepartment
|
||||
|
||||
|
||||
class VendorImporter(ToCoreAPI):
|
||||
"""
|
||||
Vendor model importer for CORE-POS
|
||||
|
@ -95,9 +192,6 @@ class VendorImporter(ToCoreAPI):
|
|||
'orderMinimum',
|
||||
'halfCases',
|
||||
]
|
||||
# TODO: this importer is in a bit of an experimental state at the moment.
|
||||
# we only allow create/update b/c it will use the API instead of direct DB
|
||||
allow_delete = False
|
||||
|
||||
def get_local_objects(self, host_data=None):
|
||||
return self.api.get_vendors()
|
||||
|
@ -135,3 +229,88 @@ class VendorImporter(ToCoreAPI):
|
|||
vendorID = data.pop('vendorID')
|
||||
vendor = self.api.set_vendor(vendorID, **data)
|
||||
return vendor
|
||||
|
||||
|
||||
class ProductImporter(ToCoreAPI):
|
||||
"""
|
||||
Product model importer for CORE-POS
|
||||
"""
|
||||
model_name = 'Product'
|
||||
key = 'upc'
|
||||
supported_fields = [
|
||||
'upc',
|
||||
'brand',
|
||||
'description',
|
||||
'size',
|
||||
'department',
|
||||
'normal_price',
|
||||
'foodstamp',
|
||||
'scale',
|
||||
# 'tax', # TODO!
|
||||
|
||||
# TODO: maybe enable some of these fields?
|
||||
# 'formatted_name',
|
||||
# 'pricemethod',
|
||||
# 'groupprice',
|
||||
# 'quantity',
|
||||
# 'special_price',
|
||||
# 'specialpricemethod',
|
||||
# 'specialgroupprice',
|
||||
# 'specialquantity',
|
||||
# 'start_date',
|
||||
# 'end_date',
|
||||
# 'scaleprice',
|
||||
# 'mixmatchcode',
|
||||
# 'modified',
|
||||
# 'tareweight',
|
||||
# 'discount',
|
||||
# 'discounttype',
|
||||
# 'line_item_discountable',
|
||||
# 'unitofmeasure',
|
||||
# 'wicable',
|
||||
# 'qttyEnforced',
|
||||
# 'idEnforced',
|
||||
# 'cost',
|
||||
# 'inUse',
|
||||
# 'numflag',
|
||||
# 'subdept',
|
||||
# 'deposit',
|
||||
# 'local',
|
||||
# 'store_id',
|
||||
# 'default_vendor_id',
|
||||
# 'current_origin_id',
|
||||
]
|
||||
|
||||
def get_local_objects(self, host_data=None):
|
||||
return self.api.get_products()
|
||||
|
||||
def get_single_local_object(self, key):
|
||||
assert len(self.key) == 1
|
||||
assert self.key[0] == 'upc'
|
||||
return self.api.get_product(key[0])
|
||||
|
||||
def normalize_local_object(self, product):
|
||||
data = dict(product)
|
||||
|
||||
# make sure all fields are present
|
||||
self.ensure_fields(data)
|
||||
|
||||
# fix some "empty" values
|
||||
self.fix_empties(data, ['brand'])
|
||||
|
||||
return data
|
||||
|
||||
def create_object(self, key, data):
|
||||
# we can get away with using the same logic for both here
|
||||
return self.update_object(None, data)
|
||||
|
||||
def update_object(self, product, data, local_data=None):
|
||||
"""
|
||||
Push an update for the product, via the CORE API.
|
||||
"""
|
||||
if self.dry_run:
|
||||
return data
|
||||
|
||||
upc = data.pop('upc')
|
||||
product = self.api.set_product(upc, **data)
|
||||
return product
|
||||
|
|
|
@ -47,7 +47,10 @@ class FromRattailToCore(importing.FromRattailHandler):
|
|||
|
||||
def get_importers(self):
|
||||
importers = OrderedDict()
|
||||
importers['Department'] = DepartmentImporter
|
||||
importers['Subdepartment'] = SubdepartmentImporter
|
||||
importers['Vendor'] = VendorImporter
|
||||
importers['Product'] = ProductImporter
|
||||
return importers
|
||||
|
||||
|
||||
|
@ -57,6 +60,45 @@ class FromRattail(importing.FromSQLAlchemy):
|
|||
"""
|
||||
|
||||
|
||||
class DepartmentImporter(FromRattail, corepos_importing.model.DepartmentImporter):
|
||||
"""
|
||||
Department data exporter
|
||||
"""
|
||||
host_model_class = model.Department
|
||||
key = 'dept_no'
|
||||
supported_fields = [
|
||||
'dept_no',
|
||||
'dept_name',
|
||||
]
|
||||
|
||||
def normalize_host_object(self, department):
|
||||
return {
|
||||
'dept_no': str(department.number),
|
||||
'dept_name': department.name,
|
||||
}
|
||||
|
||||
|
||||
class SubdepartmentImporter(FromRattail, corepos_importing.model.SubdepartmentImporter):
|
||||
"""
|
||||
Subdepartment data exporter
|
||||
"""
|
||||
host_model_class = model.Subdepartment
|
||||
key = 'subdept_no'
|
||||
supported_fields = [
|
||||
'subdept_no',
|
||||
'subdept_name',
|
||||
'dept_ID',
|
||||
]
|
||||
|
||||
def normalize_host_object(self, subdepartment):
|
||||
department = subdepartment.department
|
||||
return {
|
||||
'subdept_no': str(subdepartment.number),
|
||||
'subdept_name': subdepartment.name,
|
||||
'dept_ID': str(department.number) if department else None,
|
||||
}
|
||||
|
||||
|
||||
class VendorImporter(FromRattail, corepos_importing.model.VendorImporter):
|
||||
"""
|
||||
Vendor data exporter
|
||||
|
@ -135,3 +177,37 @@ class VendorImporter(FromRattail, corepos_importing.model.VendorImporter):
|
|||
rattail_vendor.corepos_id = int(vendor['vendorID'])
|
||||
|
||||
return vendor
|
||||
|
||||
|
||||
class ProductImporter(FromRattail, corepos_importing.model.ProductImporter):
|
||||
"""
|
||||
Product data exporter
|
||||
"""
|
||||
host_model_class = model.Product
|
||||
key = 'upc'
|
||||
supported_fields = [
|
||||
'upc',
|
||||
'brand',
|
||||
'description',
|
||||
'size',
|
||||
'department',
|
||||
'normal_price',
|
||||
'foodstamp',
|
||||
'scale',
|
||||
]
|
||||
|
||||
def query(self):
|
||||
query = super(ProductImporter, self).query()
|
||||
return query.filter(model.Product.item_id != None)
|
||||
|
||||
def normalize_host_object(self, product):
|
||||
return {
|
||||
'upc': product.item_id,
|
||||
'brand': product.brand.name if product.brand else '',
|
||||
'description': product.description or None,
|
||||
'size': product.size,
|
||||
'department': str(product.department.number) if product.department else None,
|
||||
'normal_price': '{:0.2f}'.format(product.regular_price.price) if product.regular_price else None,
|
||||
'foodstamp': '1' if product.food_stampable else '0',
|
||||
'scale': '1' if product.weighed else '0',
|
||||
}
|
||||
|
|
|
@ -152,6 +152,22 @@ class FromRattailToCore(NewDataSyncImportConsumer):
|
|||
# also establish the API client for each!
|
||||
importer.establish_api()
|
||||
|
||||
# sync all Department changes
|
||||
types = [
|
||||
'Department',
|
||||
]
|
||||
for change in [c for c in changes if c.payload_type in types]:
|
||||
if change.payload_type == 'Department' and change.deletion:
|
||||
# TODO: we have no way (yet) to delete a CORE department via API
|
||||
# # just do default logic for this one
|
||||
# self.invoke_importer(session, change)
|
||||
pass
|
||||
else: # we consider this an "add/update"
|
||||
department = self.get_department(session, change)
|
||||
if department:
|
||||
self.process_change(session, self.importers['Department'],
|
||||
host_object=department)
|
||||
|
||||
# sync all Vendor changes
|
||||
types = [
|
||||
'Vendor',
|
||||
|
@ -164,13 +180,17 @@ class FromRattailToCore(NewDataSyncImportConsumer):
|
|||
# self.invoke_importer(session, change)
|
||||
# TODO: we have no way to delete a CORE vendor via API, right?
|
||||
pass
|
||||
else: # we consider this a "vendor add/update"
|
||||
vendor = self.get_host_vendor(session, change)
|
||||
else: # we consider this an "add/update"
|
||||
vendor = self.get_vendor(session, change)
|
||||
if vendor:
|
||||
self.process_change(session, self.importers['Vendor'],
|
||||
host_object=vendor)
|
||||
|
||||
def get_host_vendor(self, session, change):
|
||||
def get_department(self, session, change):
|
||||
if change.payload_type == 'Department':
|
||||
return session.query(model.Department).get(change.payload_key)
|
||||
|
||||
def get_vendor(self, session, change):
|
||||
|
||||
if change.payload_type == 'Vendor':
|
||||
return session.query(model.Vendor).get(change.payload_key)
|
||||
|
|
|
@ -102,10 +102,14 @@ class SubdepartmentImporter(FromCOREPOSAPI, importing.model.SubdepartmentImporte
|
|||
return self.api.get_subdepartments()
|
||||
|
||||
def normalize_host_object(self, subdepartment):
|
||||
department_number = None
|
||||
if 'dept_ID' in subdepartment:
|
||||
department_number = int(subdepartment['dept_ID'])
|
||||
|
||||
return {
|
||||
'number': int(subdepartment['subdept_no']),
|
||||
'name': subdepartment['subdept_name'],
|
||||
'department_number': int(subdepartment['dept_ID']),
|
||||
'department_number': department_number,
|
||||
}
|
||||
|
||||
|
||||
|
@ -157,8 +161,8 @@ class ProductImporter(FromCOREPOSAPI, importing.model.ProductImporter):
|
|||
'regular_price_multiple',
|
||||
'regular_price_type',
|
||||
'food_stampable',
|
||||
'tax1',
|
||||
'tax2',
|
||||
# 'tax1',
|
||||
# 'tax2',
|
||||
]
|
||||
|
||||
def get_host_objects(self):
|
||||
|
@ -173,28 +177,32 @@ class ProductImporter(FromCOREPOSAPI, importing.model.ProductImporter):
|
|||
return
|
||||
upc = None
|
||||
|
||||
department_number = None
|
||||
if 'department' in product:
|
||||
department_number = int(product['department'])
|
||||
|
||||
subdepartment_number = None
|
||||
if 'subdept' in product:
|
||||
subdepartment_number = int(product['subdept']) or None
|
||||
|
||||
price = None
|
||||
if product['normal_price'] is not None:
|
||||
price = decimal.Decimal(product['normal_price'])
|
||||
|
||||
size = product.get('size', '').strip() or None
|
||||
if size == '0': # TODO: this is maybe just for sake of CORE sample data?
|
||||
size = None
|
||||
|
||||
return {
|
||||
'item_id': product['upc'],
|
||||
'upc': upc,
|
||||
'brand_name': product.get('brand') or None,
|
||||
'description': product.get('description') or '',
|
||||
'size': size,
|
||||
'size': product.get('size', '').strip() or None,
|
||||
|
||||
'department_number': int(product['department']) or None,
|
||||
'subdepartment_number': int(product['subdept']) or None,
|
||||
'department_number': department_number,
|
||||
'subdepartment_number': subdepartment_number,
|
||||
|
||||
'weighed': product['scale'] == '1',
|
||||
'food_stampable': product['foodstamp'] == '1',
|
||||
'tax1': product['tax'] == '1', # TODO: is this right?
|
||||
'tax2': product['tax'] == '2', # TODO: is this right?
|
||||
# 'tax1': product['tax'] == '1', # TODO: is this right?
|
||||
# 'tax2': product['tax'] == '2', # TODO: is this right?
|
||||
|
||||
'regular_price_price': price,
|
||||
'regular_price_multiple': 1 if price is not None else None,
|
||||
|
|
Loading…
Reference in a new issue