Add importer, datasync for CORE-POS (API) -> Rattail
This commit is contained in:
parent
9aefbc872e
commit
ab8894ef0d
|
@ -45,6 +45,23 @@ class ExportCore(commands.ImportSubcommand):
|
||||||
return load_object(spec)
|
return load_object(spec)
|
||||||
|
|
||||||
|
|
||||||
|
class ImportCOREPOSAPI(commands.ImportSubcommand):
|
||||||
|
"""
|
||||||
|
Import data from a CORE POS API
|
||||||
|
"""
|
||||||
|
name = 'import-corepos-api'
|
||||||
|
description = __doc__.strip()
|
||||||
|
default_handler_spec = 'rattail_corepos.importing.corepos.api:FromCOREPOSToRattail'
|
||||||
|
|
||||||
|
def get_handler_factory(self, **kwargs):
|
||||||
|
if self.config:
|
||||||
|
spec = self.config.get('rattail.importing', 'corepos_api.handler',
|
||||||
|
default=self.default_handler_spec)
|
||||||
|
else:
|
||||||
|
spec = self.default_handler_spec
|
||||||
|
return load_object(spec)
|
||||||
|
|
||||||
|
|
||||||
class ImportCOREPOSDB(commands.ImportSubcommand):
|
class ImportCOREPOSDB(commands.ImportSubcommand):
|
||||||
"""
|
"""
|
||||||
Import data from a CORE POS database
|
Import data from a CORE POS database
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
# -*- coding: utf-8; -*-
|
|
||||||
################################################################################
|
|
||||||
#
|
|
||||||
# Rattail -- Retail Software Framework
|
|
||||||
# Copyright © 2010-2018 Lance Edgar
|
|
||||||
#
|
|
||||||
# This file is part of Rattail.
|
|
||||||
#
|
|
||||||
# Rattail is free software: you can redistribute it and/or modify it under the
|
|
||||||
# terms of the GNU General Public License as published by the Free Software
|
|
||||||
# Foundation, either version 3 of the License, or (at your option) any later
|
|
||||||
# version.
|
|
||||||
#
|
|
||||||
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
||||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
||||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
||||||
# details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License along with
|
|
||||||
# Rattail. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
################################################################################
|
|
||||||
"""
|
|
||||||
CORE POS Interface
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import unicode_literals, absolute_import
|
|
||||||
|
|
||||||
from rattail_corepos._version import __version__
|
|
|
@ -2,7 +2,7 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# Rattail -- Retail Software Framework
|
# Rattail -- Retail Software Framework
|
||||||
# Copyright © 2010-2018 Lance Edgar
|
# Copyright © 2010-2020 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Rattail.
|
# This file is part of Rattail.
|
||||||
#
|
#
|
||||||
|
@ -21,23 +21,47 @@
|
||||||
#
|
#
|
||||||
################################################################################
|
################################################################################
|
||||||
"""
|
"""
|
||||||
DataSync for Milo
|
DataSync for Rattail DB
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import unicode_literals, absolute_import
|
|
||||||
|
|
||||||
from sqlalchemy.orm.exc import NoResultFound
|
from sqlalchemy.orm.exc import NoResultFound
|
||||||
|
|
||||||
|
from corepos.api import CoreWebAPI
|
||||||
|
from corepos.db.office_op import Session as CoreSession, model as corepos
|
||||||
|
|
||||||
from rattail.datasync import NewDataSyncImportConsumer
|
from rattail.datasync import NewDataSyncImportConsumer
|
||||||
|
|
||||||
from corepos.db import Session as CoreSession, model as corepos
|
|
||||||
|
class FromCOREAPIToRattail(NewDataSyncImportConsumer):
|
||||||
|
"""
|
||||||
|
Consumer for CORE POS (API) -> Rattail datasync
|
||||||
|
"""
|
||||||
|
handler_spec = 'rattail_corepos.importing.corepos.api:FromCOREPOSToRattail'
|
||||||
|
|
||||||
|
def setup(self):
|
||||||
|
super(FromCOREAPIToRattail, self).setup()
|
||||||
|
self.establish_api()
|
||||||
|
|
||||||
|
def establish_api(self):
|
||||||
|
url = self.config.require('corepos.api', 'url')
|
||||||
|
self.api = CoreWebAPI(url)
|
||||||
|
|
||||||
|
def get_host_object(self, session, change):
|
||||||
|
if change.payload_type == 'Department':
|
||||||
|
return self.api.get_department(change.payload_key)
|
||||||
|
if change.payload_type == 'Subdepartment':
|
||||||
|
return self.api.get_subdepartment(change.payload_key)
|
||||||
|
if change.payload_type == 'Vendor':
|
||||||
|
return self.api.get_vendor(change.payload_key)
|
||||||
|
if change.payload_type == 'Product':
|
||||||
|
return self.api.get_product(change.payload_key)
|
||||||
|
|
||||||
|
|
||||||
class FromCOREPOSToRattailBase(NewDataSyncImportConsumer):
|
class FromCOREPOSToRattailBase(NewDataSyncImportConsumer):
|
||||||
"""
|
"""
|
||||||
Base class for CORE POS -> Rattail data sync consumers.
|
Base class for CORE POS -> Rattail data sync consumers.
|
||||||
"""
|
"""
|
||||||
handler_spec = 'rattail_corepos.importing.corepos:FromCOREPOSToRattail'
|
handler_spec = 'rattail_corepos.importing.corepos.db:FromCOREPOSToRattail'
|
||||||
|
|
||||||
def begin_transaction(self):
|
def begin_transaction(self):
|
||||||
self.corepos_session = CoreSession()
|
self.corepos_session = CoreSession()
|
||||||
|
|
202
rattail_corepos/importing/corepos/api.py
Normal file
202
rattail_corepos/importing/corepos/api.py
Normal file
|
@ -0,0 +1,202 @@
|
||||||
|
# -*- coding: utf-8; -*-
|
||||||
|
################################################################################
|
||||||
|
#
|
||||||
|
# Rattail -- Retail Software Framework
|
||||||
|
# Copyright © 2010-2020 Lance Edgar
|
||||||
|
#
|
||||||
|
# This file is part of Rattail.
|
||||||
|
#
|
||||||
|
# Rattail is free software: you can redistribute it and/or modify it under the
|
||||||
|
# terms of the GNU General Public License as published by the Free Software
|
||||||
|
# Foundation, either version 3 of the License, or (at your option) any later
|
||||||
|
# version.
|
||||||
|
#
|
||||||
|
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||||
|
# details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License along with
|
||||||
|
# Rattail. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
"""
|
||||||
|
CORE POS (API) -> Rattail data importing
|
||||||
|
"""
|
||||||
|
|
||||||
|
import decimal
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from corepos.api import CoreWebAPI
|
||||||
|
|
||||||
|
from rattail import importing
|
||||||
|
from rattail.gpc import GPC
|
||||||
|
from rattail.util import OrderedDict
|
||||||
|
from rattail_corepos import importing as corepos_importing
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class FromCOREPOSToRattail(importing.ToRattailHandler):
|
||||||
|
"""
|
||||||
|
Import handler for data coming from a CORE POS API.
|
||||||
|
"""
|
||||||
|
host_title = "CORE-POS (API)"
|
||||||
|
|
||||||
|
def get_importers(self):
|
||||||
|
importers = OrderedDict()
|
||||||
|
importers['Department'] = DepartmentImporter
|
||||||
|
importers['Subdepartment'] = SubdepartmentImporter
|
||||||
|
importers['Vendor'] = VendorImporter
|
||||||
|
importers['Product'] = ProductImporter
|
||||||
|
return importers
|
||||||
|
|
||||||
|
|
||||||
|
class FromCOREPOSAPI(importing.Importer):
|
||||||
|
"""
|
||||||
|
Base class for all CORE POS API data importers.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setup(self):
|
||||||
|
super(FromCOREPOSAPI, self).setup()
|
||||||
|
self.establish_api()
|
||||||
|
|
||||||
|
def establish_api(self):
|
||||||
|
url = self.config.require('corepos.api', 'url')
|
||||||
|
self.api = CoreWebAPI(url)
|
||||||
|
|
||||||
|
|
||||||
|
class DepartmentImporter(FromCOREPOSAPI, importing.model.DepartmentImporter):
|
||||||
|
"""
|
||||||
|
Importer for department data from CORE POS API.
|
||||||
|
"""
|
||||||
|
key = 'number'
|
||||||
|
supported_fields = [
|
||||||
|
'number',
|
||||||
|
'name',
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_host_objects(self):
|
||||||
|
return self.api.get_departments()
|
||||||
|
|
||||||
|
def normalize_host_object(self, department):
|
||||||
|
return {
|
||||||
|
'number': int(department['dept_no']),
|
||||||
|
'name': department['dept_name'],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class SubdepartmentImporter(FromCOREPOSAPI, importing.model.SubdepartmentImporter):
|
||||||
|
"""
|
||||||
|
Importer for subdepartment data from CORE POS API.
|
||||||
|
"""
|
||||||
|
key = 'number'
|
||||||
|
supported_fields = [
|
||||||
|
'number',
|
||||||
|
'name',
|
||||||
|
'department_number',
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_host_objects(self):
|
||||||
|
return self.api.get_subdepartments()
|
||||||
|
|
||||||
|
def normalize_host_object(self, subdepartment):
|
||||||
|
return {
|
||||||
|
'number': int(subdepartment['subdept_no']),
|
||||||
|
'name': subdepartment['subdept_name'],
|
||||||
|
'department_number': int(subdepartment['dept_ID']),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class VendorImporter(FromCOREPOSAPI, corepos_importing.model.VendorImporter):
|
||||||
|
"""
|
||||||
|
Importer for vendor data from CORE POS API.
|
||||||
|
"""
|
||||||
|
key = 'corepos_id'
|
||||||
|
supported_fields = [
|
||||||
|
'corepos_id',
|
||||||
|
'name',
|
||||||
|
'abbreviation',
|
||||||
|
'special_discount',
|
||||||
|
'phone_number',
|
||||||
|
'fax_number',
|
||||||
|
'email_address',
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_host_objects(self):
|
||||||
|
return self.api.get_vendors()
|
||||||
|
|
||||||
|
def normalize_host_object(self, vendor):
|
||||||
|
return {
|
||||||
|
'corepos_id': int(vendor['vendorID']),
|
||||||
|
'name': vendor['vendorName'],
|
||||||
|
'abbreviation': vendor['vendorAbbreviation'] or None,
|
||||||
|
'special_discount': decimal.Decimal(vendor['discountRate']),
|
||||||
|
'phone_number': vendor.get('phone') or None,
|
||||||
|
'fax_number': vendor.get('fax') or None,
|
||||||
|
'email_address': vendor.get('email') or None,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ProductImporter(FromCOREPOSAPI, importing.model.ProductImporter):
|
||||||
|
"""
|
||||||
|
Importer for product data from CORE POS API.
|
||||||
|
"""
|
||||||
|
key = 'item_id'
|
||||||
|
supported_fields = [
|
||||||
|
'item_id',
|
||||||
|
'upc',
|
||||||
|
'brand_name',
|
||||||
|
'description',
|
||||||
|
'size',
|
||||||
|
'weighed',
|
||||||
|
'department_number',
|
||||||
|
'subdepartment_number',
|
||||||
|
'regular_price_price',
|
||||||
|
'regular_price_multiple',
|
||||||
|
'regular_price_type',
|
||||||
|
'food_stampable',
|
||||||
|
'tax1',
|
||||||
|
'tax2',
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_host_objects(self):
|
||||||
|
return self.api.get_products()
|
||||||
|
|
||||||
|
def normalize_host_object(self, product):
|
||||||
|
try:
|
||||||
|
upc = GPC(product['upc'], calc_check_digit='upc')
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
log.debug("CORE POS product has invalid UPC: %s", product['upc'])
|
||||||
|
if len(self.key) == 1 and self.key[0] == 'upc':
|
||||||
|
return
|
||||||
|
upc = 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,
|
||||||
|
|
||||||
|
'department_number': int(product['department']) or None,
|
||||||
|
'subdepartment_number': int(product['subdept']) or None,
|
||||||
|
|
||||||
|
'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?
|
||||||
|
|
||||||
|
'regular_price_price': price,
|
||||||
|
'regular_price_multiple': 1 if price is not None else None,
|
||||||
|
'regular_price_type': self.enum.PRICE_TYPE_REGULAR if price is not None else None,
|
||||||
|
}
|
1
setup.py
1
setup.py
|
@ -114,6 +114,7 @@ setup(
|
||||||
'rattail.commands': [
|
'rattail.commands': [
|
||||||
'export-corepos = rattail_corepos.commands:ExportCore',
|
'export-corepos = rattail_corepos.commands:ExportCore',
|
||||||
'corepos-import-square = rattail_corepos.commands:CoreImportSquare',
|
'corepos-import-square = rattail_corepos.commands:CoreImportSquare',
|
||||||
|
'import-corepos-api = rattail_corepos.commands:ImportCOREPOSAPI',
|
||||||
'import-corepos-db = rattail_corepos.commands:ImportCOREPOSDB',
|
'import-corepos-db = rattail_corepos.commands:ImportCOREPOSDB',
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue