Add proper importing for Customer/Person data from CORE API

includes datasync support.  i think it even works right, but we'll see
This commit is contained in:
Lance Edgar 2020-03-16 19:45:54 -05:00
parent 4a409bb80a
commit 8f9f77b6b7
3 changed files with 256 additions and 16 deletions

View file

@ -46,6 +46,44 @@ class FromCOREAPIToRattail(NewDataSyncImportConsumer):
url = self.config.require('corepos.api', 'url') url = self.config.require('corepos.api', 'url')
self.api = CoreWebAPI(url) self.api = CoreWebAPI(url)
def process_changes(self, session, changes):
if self.runas_username:
session.set_continuum_user(self.runas_username)
# update all importers with current Rattail session
for importer in self.importers.values():
importer.session = session
# also establish the API client for each!
importer.establish_api()
# sync all Customer-related changes
types = [
'Customer',
]
for change in [c for c in changes if c.payload_type in types]:
if change.deletion:
# normal logic works fine for this (maybe?)
self.invoke_importer(session, change)
else:
# import customer data from API, into various Rattail tables
customer = self.get_host_object(session, change)
self.process_change(session, self.importers['Customer'],
host_object=customer)
people = self.importers['Person'].get_person_objects_for_customer(customer)
for person in people:
self.process_change(session, self.importers['Person'],
host_object=person)
# process all remaining supported models with typical logic
types = [
'Department',
'Subdepartment',
'Vendor',
'Product',
]
for change in [c for c in changes if c.payload_type in types]:
self.invoke_importer(session, change)
def get_host_object(self, session, change): def get_host_object(self, session, change):
if change.payload_type == 'Customer': if change.payload_type == 'Customer':
return self.api.get_customer(change.payload_key) return self.api.get_customer(change.payload_key)

View file

@ -27,10 +27,14 @@ CORE POS (API) -> Rattail data importing
import decimal import decimal
import logging import logging
from sqlalchemy import orm
from sqlalchemy.orm.exc import NoResultFound
from corepos.api import CoreWebAPI from corepos.api import CoreWebAPI
from corepos.db.office_op import Session as CoreSession, model as corepos from corepos.db.office_op import Session as CoreSession, model as corepos
from rattail import importing from rattail import importing
from rattail.db import model
from rattail.gpc import GPC from rattail.gpc import GPC
from rattail.util import OrderedDict from rattail.util import OrderedDict
from rattail.db.util import normalize_full_name, short_session from rattail.db.util import normalize_full_name, short_session
@ -49,6 +53,8 @@ class FromCOREPOSToRattail(importing.ToRattailHandler):
def get_importers(self): def get_importers(self):
importers = OrderedDict() importers = OrderedDict()
importers['Customer'] = CustomerImporter importers['Customer'] = CustomerImporter
importers['Person'] = PersonImporter
importers['CustomerPerson'] = CustomerPersonImporter
importers['Department'] = DepartmentImporter importers['Department'] = DepartmentImporter
importers['Subdepartment'] = SubdepartmentImporter importers['Subdepartment'] = SubdepartmentImporter
importers['Vendor'] = VendorImporter importers['Vendor'] = VendorImporter
@ -69,21 +75,7 @@ class FromCOREPOSAPI(importing.Importer):
url = self.config.require('corepos.api', 'url') url = self.config.require('corepos.api', 'url')
self.api = CoreWebAPI(url) self.api = CoreWebAPI(url)
def get_core_customers(self):
class CustomerImporter(FromCOREPOSAPI, importing.model.CustomerImporter):
"""
Importer for customer data from CORE POS API.
"""
key = 'number'
supported_fields = [
'id',
'number',
'name',
'first_name',
'last_name',
]
def get_host_objects(self):
# TODO: ideally could do this, but API doesn't let us fetch "all" # TODO: ideally could do this, but API doesn't let us fetch "all"
# return self.api.get_customers() # return self.api.get_customers()
@ -107,9 +99,24 @@ class CustomerImporter(FromCOREPOSAPI, importing.model.CustomerImporter):
dbcust.card_number) dbcust.card_number)
self.progress_loop(fetch, db_customers, self.progress_loop(fetch, db_customers,
message="Fetching Customer data from CORE-POS API") message="Fetching Customer data from CORE API")
return list(customers.values()) return list(customers.values())
class CustomerImporter(FromCOREPOSAPI, importing.model.CustomerImporter):
"""
Importer for customer data from CORE POS API.
"""
key = 'number'
supported_fields = [
'id',
'number',
'name',
]
def get_host_objects(self):
return self.get_core_customers()
def normalize_host_object(self, customer): def normalize_host_object(self, customer):
# figure out the "account holder" person for the customer # figure out the "account holder" person for the customer
@ -128,8 +135,184 @@ class CustomerImporter(FromCOREPOSAPI, importing.model.CustomerImporter):
'number': customer['cardNo'], 'number': customer['cardNo'],
'name': normalize_full_name(person['firstName'], 'name': normalize_full_name(person['firstName'],
person['lastName']), person['lastName']),
}
class PersonImporter(FromCOREPOSAPI, corepos_importing.model.PersonImporter):
"""
Importer for person data from CORE POS API.
"""
key = 'corepos_customer_id'
supported_fields = [
'corepos_customer_id',
'first_name',
'last_name',
'display_name',
'customer_uuid',
'customer_person_ordinal',
]
def setup(self):
super(PersonImporter, self).setup()
model = self.config.get_model()
self.customers = self.cache_model(model.Customer, key='id')
def get_host_objects(self):
# first get all customer data from CORE API
customers = self.get_core_customers()
normalized = []
# then collect all the "person" records
def normalize(customer, i):
normalized.extend(self.get_person_objects_for_customer(customer))
self.progress_loop(normalize, customers,
message="Collecting Person data from CORE")
return normalized
def get_person_objects_for_customer(self, customer):
"""
Return a list of Person data objects for the given Customer. This
logic is split out separately so that datasync can leverage it too.
"""
records = []
# make sure we put the account holder first in the list!
people = sorted(customer['customers'],
key=lambda cust: 1 if cust['accountHolder'] else 0,
reverse=True)
for i, person in enumerate(people, 1):
person = dict(person)
person['customer_person_ordinal'] = i
records.append(person)
return records
def get_customer(self, id):
if hasattr(self, 'customers'):
return self.customers.get(id)
try:
return self.session.query(model.Customer)\
.filter(model.Customer.id == id)\
.one()
except NoResultFound:
pass
def normalize_host_object(self, person):
customer = self.get_customer(person['customerAccountID'])
if not customer:
log.warning("Rattail customer not found for customerAccountID: %s",
person['customerAccountID'])
return
return {
'corepos_customer_id': int(person['customerID']),
'first_name': person['firstName'], 'first_name': person['firstName'],
'last_name': person['lastName'], 'last_name': person['lastName'],
'display_name': normalize_full_name(person['firstName'],
person['lastName']),
'customer_uuid': customer.uuid,
'customer_person_ordinal': person['customer_person_ordinal'],
}
class CustomerPersonImporter(FromCOREPOSAPI, importing.model.CustomerPersonImporter):
"""
Importer for customer-person linkage data from CORE POS API.
Note that we don't use this one in datasync, it's just for nightly
double-check.
"""
key = ('customer_uuid', 'person_uuid')
supported_fields = [
'customer_uuid',
'person_uuid',
'ordinal',
]
def setup(self):
super(CustomerPersonImporter, self).setup()
model = self.config.get_model()
self.customers = self.cache_model(model.Customer, key='id')
query = self.session.query(model.Person)\
.join(model.CorePerson)\
.filter(model.CorePerson.corepos_customer_id != None)
self.people = self.cache_model(model.Person, query=query,
key='corepos_customer_id',
query_options=[orm.joinedload(model.Person._corepos)])
def get_host_objects(self):
# first get all customer data from CORE API
customers = self.get_core_customers()
normalized = []
# then collect all customer/person combination records
def normalize(customer, i):
# make sure we put the account holder first in the list!
people = sorted(customer['customers'],
key=lambda cust: 1 if cust['accountHolder'] else 0,
reverse=True)
for i, person in enumerate(people, 1):
normalized.append({
'customer_account_id': customer['customerAccountID'],
'person_customer_id': person['customerID'],
'ordinal': i,
})
self.progress_loop(normalize, customers,
message="Collecting CustomerPerson data from CORE")
return normalized
def get_customer(self, id):
if hasattr(self, 'customers'):
return self.customers.get(id)
try:
return self.session.query(model.Customer)\
.filter(model.Customer.id == id)\
.one()
except NoResultFound:
pass
def get_person(self, corepos_customer_id):
if hasattr(self, 'people'):
return self.people.get(corepos_customer_id)
model = self.config.get_model()
try:
return self.session.query(model.Person)\
.join(model.CorePerson)\
.filter(model.CorePerson.corepos_customer_id == corepos_customer_id)\
.one()
except NoResultFound:
pass
def normalize_host_object(self, cp):
customer = self.get_customer(cp['customer_account_id'])
if not customer:
log.warning("Rattail customer not found for customerAccountID: %s",
cp['customer_account_id'])
return
person = self.get_person(int(cp['person_customer_id']))
if not person:
log.warning("Rattail person not found for customerID: %s",
cp['person_customer_id'])
return
return {
'customer_uuid': customer.uuid,
'person_uuid': person.uuid,
'ordinal': cp['ordinal'],
} }

View file

@ -31,6 +31,25 @@ from rattail import importing
# core importer overrides # core importer overrides
############################## ##############################
class PersonImporter(importing.model.PersonImporter):
extension_attr = '_corepos'
extension_fields = [
'corepos_customer_id',
]
def cache_query(self):
query = super(PersonImporter, self).cache_query()
model = self.config.get_model()
# we want to ignore people with no CORE ID, if that's (part of) our key
if 'corepos_customer_id' in self.key:
query = query.join(model.CorePerson)\
.filter(model.CorePerson.corepos_customer_id != None)
return query
class VendorImporter(importing.model.VendorImporter): class VendorImporter(importing.model.VendorImporter):
extension_attr = '_corepos' extension_attr = '_corepos'