Add support for phone, fax, email in Rattail -> CORE export

This commit is contained in:
Lance Edgar 2020-03-04 13:05:20 -06:00
parent 75ba08b9fc
commit 4180382250
2 changed files with 81 additions and 39 deletions

View file

@ -24,28 +24,16 @@
CORE-POS model importers (webservices API) CORE-POS model importers (webservices API)
""" """
from sqlalchemy.orm.exc import NoResultFound
from corepos.db.office_op import model as corepos
from corepos.api import CoreWebAPI from corepos.api import CoreWebAPI
from rattail import importing from rattail import importing
class ToCore(importing.ToSQLAlchemy): class ToCoreAPI(importing.Importer):
""" """
Base class for all CORE "operational" model importers. Base class for all CORE "operational" model importers, which use the API.
Note that this class inherits from
:class:`~rattail:rattail.importing.sqlalchemy.ToSQLAlchemy` even though our
goal is to *not* write directly to the CORE DB. However, (for now)
importers will need to override methods to ensure API is used instead,
where applicable. But even once all have been refactored to write via API,
we *still* may want to keep using ``ToSQLAlchemy`` for the sake of reading
"cached local" data. (May depend on how robust the API is.)
""" """
# TODO: should we standardize on the 'id' primary key? (can we even?) caches_local_data = True
# key = 'id'
def setup(self): def setup(self):
self.establish_api() self.establish_api()
@ -54,49 +42,85 @@ class ToCore(importing.ToSQLAlchemy):
url = self.config.require('corepos.api', 'url') url = self.config.require('corepos.api', 'url')
self.api = CoreWebAPI(url) self.api = CoreWebAPI(url)
# TODO: this looks an awful lot like it belongs in rattail proper def ensure_fields(self, data):
def get_single_local_object(self, key):
""" """
Fetch a particular record from CORE, via SQLAlchemy. This is used by Ensure each of our supported fields are included in the data. This is
the Rattail -> CORE datasync consumer. to handle cases where the API does not return all fields, e.g. when
some of them are empty.
""" """
query = self.session.query(self.model_class) for field in self.fields:
for i, field in enumerate(self.key): if field not in data:
query = query.filter(getattr(self.model_class, field) == key[i]) data[field] = None
for option in self.cache_query_options(): def fix_empties(self, data, fields):
query = query.options(option) """
Fix "empty" values for the given set of fields. This just uses an
empty string instead of ``None`` for each, to add some consistency
where the API might lack it.
try: Main example so far, is the Vendor API, which may not return some
return query.one() fields at all (and so our value is ``None``) in some cases, but in
except NoResultFound: other cases it *will* return a value, default of which is the empty
pass string. So we want to "pretend" that we get an empty string back even
when we actually get ``None`` from it.
"""
for field in fields:
if data[field] is None:
data[field] = ''
class VendorImporter(ToCore): class VendorImporter(ToCoreAPI):
""" """
Vendor model importer for CORE-POS Vendor model importer for CORE-POS
""" """
model_class = corepos.Vendor model_name = 'Vendor'
key = 'vendorID' key = 'vendorID'
supported_fields = [ supported_fields = [
'vendorID', 'vendorID',
'vendorName', 'vendorName',
'vendorAbbreviation', 'vendorAbbreviation',
'shippingMarkup',
'discountRate', 'discountRate',
'phone',
'fax',
'email',
'website',
'address',
'city',
'state',
'zip',
'notes',
'localOriginID',
'inactive',
'orderMinimum',
'halfCases',
] ]
# TODO: this importer is in a bit of an experimental state at the moment. # TODO: this importer is in a bit of an experimental state at the moment.
# we only allow "update" b/c it will use the API instead of direct DB # we only allow "update" b/c it will use the API instead of direct DB
allow_create = False allow_create = False
allow_delete = False allow_delete = False
def get_local_objects(self, host_data=None):
return self.api.get_vendors()
def get_single_local_object(self, key):
assert len(self.key) == 1
assert self.key[0] == 'vendorID'
return self.api.get_vendor(key[0])
def normalize_local_object(self, vendor): def normalize_local_object(self, vendor):
return { data = dict(vendor)
'vendorID': str(vendor.id),
'vendorName': vendor.name, # make sure all fields are present
'vendorAbbreviation': vendor.abbreviation, self.ensure_fields(data)
'discountRate': vendor.discount_rate,
} # fix some "empty" values
self.fix_empties(data, ['phone', 'fax', 'email'])
# convert some values to native type
data['discountRate'] = float(data['discountRate'])
return data
def update_object(self, vendor, data, local_data=None): def update_object(self, vendor, data, local_data=None):
""" """

View file

@ -30,13 +30,12 @@ from rattail import importing
from rattail.db import model from rattail.db import model
from rattail.util import OrderedDict from rattail.util import OrderedDict
from rattail_corepos.corepos import importing as corepos_importing from rattail_corepos.corepos import importing as corepos_importing
from rattail_corepos.corepos.importing.db.corepos import ToCoreHandler
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class FromRattailToCore(importing.FromRattailHandler, ToCoreHandler): class FromRattailToCore(importing.FromRattailHandler):
""" """
Rattail -> CORE-POS export handler Rattail -> CORE-POS export handler
""" """
@ -67,6 +66,9 @@ class VendorImporter(FromRattail, corepos_importing.model.VendorImporter):
'vendorName', 'vendorName',
'vendorAbbreviation', 'vendorAbbreviation',
'discountRate', 'discountRate',
'phone',
'fax',
'email',
] ]
def normalize_host_object(self, vendor): def normalize_host_object(self, vendor):
@ -75,9 +77,25 @@ class VendorImporter(FromRattail, corepos_importing.model.VendorImporter):
vendor.uuid, vendor.id) vendor.uuid, vendor.id)
return return
return { data = {
'vendorID': vendor.id, 'vendorID': vendor.id,
'vendorName': vendor.name, 'vendorName': vendor.name,
'vendorAbbreviation': vendor.abbreviation, 'vendorAbbreviation': vendor.abbreviation,
'discountRate': float(vendor.special_discount), 'discountRate': float(vendor.special_discount),
} }
if 'phone' in self.fields:
phones = [phone for phone in vendor.phones
if phone.type == 'Voice']
data['phone'] = phones[0].number if phones else ''
if 'fax' in self.fields:
phones = [phone for phone in vendor.phones
if phone.type == 'Fax']
data['fax'] = phones[0].number if phones else ''
if 'email' in self.fields:
email = vendor.email
data['email'] = email.address if email else ''
return data