Add support for Rattail -> CORE export/sync for Member data
also refactor CORE -> Rattail logic to use `api.set_member()` etc.
This commit is contained in:
parent
8d47a1449c
commit
cb63644c7d
6 changed files with 326 additions and 79 deletions
|
@ -27,6 +27,8 @@ CORE-POS model importers (webservices API)
|
|||
from corepos.api import CoreWebAPI
|
||||
|
||||
from rattail import importing
|
||||
from rattail.util import data_diffs
|
||||
from rattail_corepos.corepos.util import get_core_members
|
||||
|
||||
|
||||
class ToCoreAPI(importing.Importer):
|
||||
|
@ -74,6 +76,145 @@ class ToCoreAPI(importing.Importer):
|
|||
data[field] = ''
|
||||
|
||||
|
||||
class MemberImporter(ToCoreAPI):
|
||||
"""
|
||||
Member model importer for CORE-POS
|
||||
"""
|
||||
model_name = 'Member'
|
||||
key = 'cardNo'
|
||||
supported_fields = [
|
||||
'cardNo'
|
||||
'customerAccountID',
|
||||
'customers',
|
||||
# 'memberStatus',
|
||||
# 'activeStatus',
|
||||
# 'customerTypeID',
|
||||
# 'chargeBalance',
|
||||
# 'chargeLimit',
|
||||
# 'idCardUPC',
|
||||
# 'startDate',
|
||||
# 'endDate',
|
||||
# 'addressFirstLine',
|
||||
# 'addressSecondLine',
|
||||
# 'city',
|
||||
# 'state',
|
||||
# 'zip',
|
||||
# 'contactAllowed',
|
||||
# 'contactMethod',
|
||||
# 'modified',
|
||||
]
|
||||
supported_customer_fields = [
|
||||
'customerID',
|
||||
# 'customerAccountID',
|
||||
# 'cardNo',
|
||||
'firstName',
|
||||
'lastName',
|
||||
# 'chargeAllowed',
|
||||
# 'checksAllowed',
|
||||
# 'discount',
|
||||
'accountHolder',
|
||||
# 'staff',
|
||||
# 'phone',
|
||||
# 'altPhone',
|
||||
# 'email',
|
||||
# 'memberPricingAllowed',
|
||||
# 'memberCouponsAllowed',
|
||||
# 'lowIncomeBenefits',
|
||||
# 'modified',
|
||||
]
|
||||
|
||||
def get_local_objects(self, host_data=None):
|
||||
return get_core_members(self.api, progress=self.progress)
|
||||
|
||||
def get_single_local_object(self, key):
|
||||
assert len(self.key) == 1
|
||||
assert self.key[0] == 'cardNo'
|
||||
return self.api.get_member(key[0])
|
||||
|
||||
def normalize_local_object(self, member):
|
||||
data = dict(member)
|
||||
return data
|
||||
|
||||
def data_diffs(self, local_data, host_data):
|
||||
diffs = super(MemberImporter, self).data_diffs(local_data, host_data)
|
||||
|
||||
# the 'customers' field requires a more granular approach, since the
|
||||
# data coming from API may have different fields than our local data
|
||||
if 'customers' in self.fields and 'customers' in diffs:
|
||||
if not self.customer_data_differs(local_data, host_data):
|
||||
diffs.remove('customers')
|
||||
|
||||
return diffs
|
||||
|
||||
def customer_data_differs(self, local_data, host_data):
|
||||
local_customers = local_data['customers']
|
||||
host_customers = host_data['customers']
|
||||
|
||||
# if both are empty, we're good
|
||||
if not local_customers and not host_customers:
|
||||
return False
|
||||
|
||||
# obviously we differ if record count doesn't match
|
||||
if len(local_customers) != len(host_customers):
|
||||
return True
|
||||
|
||||
# okay then, let's traverse the "new" list
|
||||
for host_customer in host_customers:
|
||||
|
||||
# we differ if can't locate corresponding "old" local record
|
||||
local_customer = self.find_local_customer(local_customers, host_customer)
|
||||
if not local_customer:
|
||||
return True
|
||||
|
||||
# we differ if old and new records differ
|
||||
if data_diffs(local_customer, host_customer,
|
||||
fields=self.supported_customer_fields):
|
||||
return True
|
||||
|
||||
# okay, now let's traverse the "old" list
|
||||
for local_customer in local_customers:
|
||||
|
||||
# we differ if can't locate corresponding "new" host record
|
||||
host_customer = self.find_host_customer(host_customers, local_customer)
|
||||
if not host_customer:
|
||||
return True
|
||||
|
||||
# guess we don't differ after all
|
||||
return False
|
||||
|
||||
def find_local_customer(self, local_customers, host_customer):
|
||||
assert 'customerID' in self.supported_customer_fields
|
||||
|
||||
if not host_customer['customerID']:
|
||||
return # new customer
|
||||
|
||||
for local_customer in local_customers:
|
||||
if local_customer['customerID'] == host_customer['customerID']:
|
||||
return local_customer
|
||||
|
||||
def find_host_customer(self, host_customers, local_customer):
|
||||
assert 'customerID' in self.supported_customer_fields
|
||||
|
||||
for host_customer in host_customers:
|
||||
if host_customer['customerID'] == local_customer['customerID']:
|
||||
return host_customer
|
||||
|
||||
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, member, data, local_data=None):
|
||||
"""
|
||||
Push an update for the member, via the CORE API.
|
||||
"""
|
||||
if self.dry_run:
|
||||
return data
|
||||
|
||||
cardNo = data.pop('cardNo')
|
||||
member = self.api.set_member(cardNo, **data)
|
||||
return member
|
||||
|
||||
|
||||
class DepartmentImporter(ToCoreAPI):
|
||||
"""
|
||||
Department model importer for CORE-POS
|
||||
|
|
|
@ -47,6 +47,7 @@ class FromRattailToCore(importing.FromRattailHandler):
|
|||
|
||||
def get_importers(self):
|
||||
importers = OrderedDict()
|
||||
importers['Member'] = MemberImporter
|
||||
importers['Department'] = DepartmentImporter
|
||||
importers['Subdepartment'] = SubdepartmentImporter
|
||||
importers['Vendor'] = VendorImporter
|
||||
|
@ -60,6 +61,42 @@ class FromRattail(importing.FromSQLAlchemy):
|
|||
"""
|
||||
|
||||
|
||||
class MemberImporter(FromRattail, corepos_importing.model.MemberImporter):
|
||||
"""
|
||||
Member data exporter
|
||||
"""
|
||||
host_model_class = model.Customer
|
||||
key = 'cardNo'
|
||||
supported_fields = [
|
||||
'cardNo',
|
||||
'customerAccountID',
|
||||
'customers',
|
||||
]
|
||||
supported_person_fields = [
|
||||
'customerID',
|
||||
'firstName',
|
||||
'lastName',
|
||||
'accountHolder',
|
||||
]
|
||||
|
||||
def normalize_host_object(self, customer):
|
||||
|
||||
people = []
|
||||
for i, person in enumerate(customer.people, 1):
|
||||
people.append({
|
||||
'customerID': str(person.corepos_customer_id),
|
||||
'firstName': person.first_name,
|
||||
'lastName': person.last_name,
|
||||
'accountHolder': i == 1,
|
||||
})
|
||||
|
||||
return {
|
||||
'cardNo': customer.number,
|
||||
'customerAccountID': customer.id,
|
||||
'customers': people,
|
||||
}
|
||||
|
||||
|
||||
class DepartmentImporter(FromRattail, corepos_importing.model.DepartmentImporter):
|
||||
"""
|
||||
Department data exporter
|
||||
|
|
|
@ -24,11 +24,48 @@
|
|||
CORE-POS misc. utilities
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
import sqlalchemy as sa
|
||||
|
||||
from corepos.db.office_op import Session as CoreSession, model as corepos
|
||||
|
||||
from rattail.db.util import short_session
|
||||
from rattail.util import OrderedDict, progress_loop
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_core_members(api, progress=None):
|
||||
"""
|
||||
Shared logic for fetching *all* customer accounts from CORE-POS API.
|
||||
"""
|
||||
# TODO: ideally could do this, but API doesn't let us fetch "all"
|
||||
# return api.get_members()
|
||||
|
||||
# first we fetch all customer records from CORE DB
|
||||
with short_session(Session=CoreSession) as s:
|
||||
db_customers = s.query(corepos.Customer).all()
|
||||
s.expunge_all()
|
||||
|
||||
# now we must fetch each customer account individually from API
|
||||
members = OrderedDict()
|
||||
|
||||
def fetch(dbcust, i):
|
||||
if dbcust.card_number in members:
|
||||
return # already fetched this one
|
||||
member = api.get_member(dbcust.card_number)
|
||||
if member:
|
||||
members[dbcust.card_number] = member
|
||||
else:
|
||||
logger = log.warning if dbcust.account_holder else log.debug
|
||||
logger("could not fetch member from CORE API: %s",
|
||||
dbcust.card_number)
|
||||
|
||||
progress_loop(fetch, db_customers, progress,
|
||||
message="Fetching Member data from CORE API")
|
||||
return list(members.values())
|
||||
|
||||
|
||||
def get_max_existing_vendor_id(session=None):
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue