From 418038225087cae610cd5d260f8950238632fac6 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Wed, 4 Mar 2020 13:05:20 -0600 Subject: [PATCH] Add support for phone, fax, email in Rattail -> CORE export --- rattail_corepos/corepos/importing/model.py | 96 ++++++++++++-------- rattail_corepos/corepos/importing/rattail.py | 24 ++++- 2 files changed, 81 insertions(+), 39 deletions(-) diff --git a/rattail_corepos/corepos/importing/model.py b/rattail_corepos/corepos/importing/model.py index 77a1a92..03afffa 100644 --- a/rattail_corepos/corepos/importing/model.py +++ b/rattail_corepos/corepos/importing/model.py @@ -24,28 +24,16 @@ 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 rattail import importing -class ToCore(importing.ToSQLAlchemy): +class ToCoreAPI(importing.Importer): """ - Base class for all CORE "operational" model importers. - - 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.) + Base class for all CORE "operational" model importers, which use the API. """ - # TODO: should we standardize on the 'id' primary key? (can we even?) - # key = 'id' + caches_local_data = True def setup(self): self.establish_api() @@ -54,49 +42,85 @@ class ToCore(importing.ToSQLAlchemy): url = self.config.require('corepos.api', 'url') self.api = CoreWebAPI(url) - # TODO: this looks an awful lot like it belongs in rattail proper - def get_single_local_object(self, key): + def ensure_fields(self, data): """ - Fetch a particular record from CORE, via SQLAlchemy. This is used by - the Rattail -> CORE datasync consumer. + Ensure each of our supported fields are included in the data. This is + 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 i, field in enumerate(self.key): - query = query.filter(getattr(self.model_class, field) == key[i]) + for field in self.fields: + if field not in data: + data[field] = None - for option in self.cache_query_options(): - query = query.options(option) + def fix_empties(self, data, fields): + """ + 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: - return query.one() - except NoResultFound: - pass + Main example so far, is the Vendor API, which may not return some + fields at all (and so our value is ``None``) in some cases, but in + other cases it *will* return a value, default of which is the empty + 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 """ - model_class = corepos.Vendor + model_name = 'Vendor' key = 'vendorID' supported_fields = [ 'vendorID', 'vendorName', 'vendorAbbreviation', + 'shippingMarkup', '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. # we only allow "update" b/c it will use the API instead of direct DB allow_create = 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): - return { - 'vendorID': str(vendor.id), - 'vendorName': vendor.name, - 'vendorAbbreviation': vendor.abbreviation, - 'discountRate': vendor.discount_rate, - } + data = dict(vendor) + + # make sure all fields are present + self.ensure_fields(data) + + # 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): """ diff --git a/rattail_corepos/corepos/importing/rattail.py b/rattail_corepos/corepos/importing/rattail.py index 063b4ef..ae17551 100644 --- a/rattail_corepos/corepos/importing/rattail.py +++ b/rattail_corepos/corepos/importing/rattail.py @@ -30,13 +30,12 @@ from rattail import importing from rattail.db import model from rattail.util import OrderedDict from rattail_corepos.corepos import importing as corepos_importing -from rattail_corepos.corepos.importing.db.corepos import ToCoreHandler log = logging.getLogger(__name__) -class FromRattailToCore(importing.FromRattailHandler, ToCoreHandler): +class FromRattailToCore(importing.FromRattailHandler): """ Rattail -> CORE-POS export handler """ @@ -67,6 +66,9 @@ class VendorImporter(FromRattail, corepos_importing.model.VendorImporter): 'vendorName', 'vendorAbbreviation', 'discountRate', + 'phone', + 'fax', + 'email', ] def normalize_host_object(self, vendor): @@ -75,9 +77,25 @@ class VendorImporter(FromRattail, corepos_importing.model.VendorImporter): vendor.uuid, vendor.id) return - return { + data = { 'vendorID': vendor.id, 'vendorName': vendor.name, 'vendorAbbreviation': vendor.abbreviation, '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