Add initial/basic CustomerShopper importer for CORE -> Rattail
this replaces previous importers for Person and CustomerPerson no contact info support just yet..need to decide where to put that
This commit is contained in:
parent
2c38e4d5d3
commit
a45d28cd6f
|
@ -56,8 +56,7 @@ class FromCOREPOSToRattail(importing.ToRattailHandler):
|
|||
def get_importers(self):
|
||||
importers = OrderedDict()
|
||||
importers['Customer'] = CustomerImporter
|
||||
importers['Person'] = PersonImporter
|
||||
importers['CustomerPerson'] = CustomerPersonImporter
|
||||
importers['CustomerShopper'] = CustomerShopperImporter
|
||||
importers['MembershipType'] = MembershipTypeImporter
|
||||
importers['Member'] = MemberImporter
|
||||
importers['Store'] = StoreImporter
|
||||
|
@ -72,11 +71,6 @@ class FromCOREPOSToRattail(importing.ToRattailHandler):
|
|||
def get_default_keys(self):
|
||||
keys = super(FromCOREPOSToRattail, self).get_default_keys()
|
||||
|
||||
# normally this one is redundant, but it can be used for
|
||||
# double-check if desired
|
||||
if 'CustomerPerson' in keys:
|
||||
keys.remove('CustomerPerson')
|
||||
|
||||
if 'ProductMovement' in keys:
|
||||
keys.remove('ProductMovement')
|
||||
return keys
|
||||
|
@ -88,18 +82,51 @@ class FromCOREPOSAPI(importing.Importer):
|
|||
"""
|
||||
|
||||
def setup(self):
|
||||
super(FromCOREPOSAPI, self).setup()
|
||||
super().setup()
|
||||
|
||||
self.establish_api()
|
||||
|
||||
self.ignore_new_members = self.should_ignore_new_members()
|
||||
|
||||
def datasync_setup(self):
|
||||
super(FromCOREPOSAPI, self).datasync_setup()
|
||||
super().datasync_setup()
|
||||
|
||||
self.establish_api()
|
||||
|
||||
def establish_api(self):
|
||||
self.api = make_corepos_api(self.config)
|
||||
|
||||
def should_ignore_new_members(self):
|
||||
if hasattr(self, 'ignore_new_members'):
|
||||
return self.ignore_new_members
|
||||
|
||||
return self.config.getbool('rattail_corepos',
|
||||
'importing_ignore_new_members',
|
||||
default=False)
|
||||
|
||||
def get_core_members(self):
|
||||
return get_core_members(self.config, self.api, progress=self.progress)
|
||||
members = get_core_members(self.config, self.api, progress=self.progress)
|
||||
|
||||
# maybe ignore NEW MEMBER accounts
|
||||
if self.should_ignore_new_members():
|
||||
members = [member for member in members
|
||||
if not self.is_new_member(member)]
|
||||
|
||||
return members
|
||||
|
||||
def is_new_member(self, member):
|
||||
"""
|
||||
Convenience method to check if the given member represents a
|
||||
"NEW MEMBER" record in CORE-POS, and hence it should be
|
||||
ignored for the import.
|
||||
"""
|
||||
customers = member['customers']
|
||||
if customers:
|
||||
customer = customers[0]
|
||||
if (not customer['firstName']
|
||||
and customer['lastName'] == 'NEW MEMBER'):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class CustomerImporter(FromCOREPOSAPI, corepos_importing.model.CustomerImporter):
|
||||
|
@ -111,11 +138,11 @@ class CustomerImporter(FromCOREPOSAPI, corepos_importing.model.CustomerImporter)
|
|||
'corepos_card_number',
|
||||
'number',
|
||||
'name',
|
||||
'address_street',
|
||||
'address_street2',
|
||||
'address_city',
|
||||
'address_state',
|
||||
'address_zipcode',
|
||||
# 'address_street',
|
||||
# 'address_street2',
|
||||
# 'address_city',
|
||||
# 'address_state',
|
||||
# 'address_zipcode',
|
||||
]
|
||||
|
||||
def get_host_objects(self):
|
||||
|
@ -148,71 +175,77 @@ class CustomerImporter(FromCOREPOSAPI, corepos_importing.model.CustomerImporter)
|
|||
'name': normalize_full_name(customer['firstName'],
|
||||
customer['lastName']),
|
||||
|
||||
'address_street': member['addressFirstLine'] or None,
|
||||
'address_street2': member['addressSecondLine'] or None,
|
||||
'address_city': member['city'] or None,
|
||||
'address_state': member['state'] or None,
|
||||
'address_zipcode': member['zip'] or None,
|
||||
# 'address_street': member['addressFirstLine'] or None,
|
||||
# 'address_street2': member['addressSecondLine'] or None,
|
||||
# 'address_city': member['city'] or None,
|
||||
# 'address_state': member['state'] or None,
|
||||
# 'address_zipcode': member['zip'] or None,
|
||||
}
|
||||
|
||||
|
||||
class PersonImporter(FromCOREPOSAPI, corepos_importing.model.PersonImporter):
|
||||
class CustomerShopperImporter(FromCOREPOSAPI, importing.model.CustomerShopperImporter):
|
||||
"""
|
||||
Importer for person data from CORE POS API.
|
||||
Importer for customer shopper data from CORE POS API.
|
||||
"""
|
||||
key = ('customer_uuid', 'customer_person_ordinal')
|
||||
key = ('customer_uuid', 'shopper_number')
|
||||
supported_fields = [
|
||||
'customer_uuid',
|
||||
'customer_person_ordinal',
|
||||
'shopper_number',
|
||||
'first_name',
|
||||
'last_name',
|
||||
'display_name',
|
||||
'phone_number',
|
||||
'phone_number_2',
|
||||
'email_address',
|
||||
'active',
|
||||
# 'phone_number',
|
||||
# 'phone_number_2',
|
||||
# 'email_address',
|
||||
]
|
||||
|
||||
def setup(self):
|
||||
super().setup()
|
||||
model = self.model
|
||||
|
||||
# self.maxlen_phone_number = self.app.maxlen(model.PhoneNumber.number)
|
||||
|
||||
self.customers_by_card_number = self.app.cache_model(
|
||||
self.session,
|
||||
model.Customer,
|
||||
key='corepos_card_number',
|
||||
query_options=[orm.joinedload(model.Customer._corepos)])
|
||||
|
||||
self.ignore_new_members = self.should_ignore_new_members()
|
||||
|
||||
def should_ignore_new_members(self):
|
||||
if hasattr(self, 'ignore_new_members'):
|
||||
return self.ignore_new_members
|
||||
|
||||
return self.config.getbool('rattail_corepos',
|
||||
'importing_ignore_new_members',
|
||||
default=False)
|
||||
|
||||
def get_host_objects(self):
|
||||
|
||||
# first get all member data from CORE API
|
||||
members = self.get_core_members()
|
||||
normalized = []
|
||||
|
||||
# then collect all the "person" records
|
||||
# then collect all the "shopper" records
|
||||
def normalize(member, i):
|
||||
normalized.extend(self.get_person_objects_for_member(member))
|
||||
normalized.extend(self.get_shoppers_for_member(member))
|
||||
|
||||
self.progress_loop(normalize, members,
|
||||
message="Collecting Person data from CORE")
|
||||
return normalized
|
||||
|
||||
def get_person_objects_for_member(self, member):
|
||||
def get_customer_by_card_number(self, card_number):
|
||||
if hasattr(self, 'customers_by_card_number'):
|
||||
return self.customers_by_card_number.get(card_number)
|
||||
|
||||
model = self.model
|
||||
try:
|
||||
return self.session.query(model.Customer)\
|
||||
.join(model.CoreCustomer)\
|
||||
.filter(model.CoreCustomer.corepos_card_number == card_number)\
|
||||
.one()
|
||||
except orm.exc.NoResultFound:
|
||||
pass
|
||||
|
||||
def get_shoppers_for_member(self, member):
|
||||
"""
|
||||
Return a list of Person data objects for the given Member. This
|
||||
logic is split out separately so that datasync can leverage it too.
|
||||
Return a list of shopper info dicts associated with the given
|
||||
member info dict (latter having come from the CORE API).
|
||||
"""
|
||||
customers = member['customers']
|
||||
people = []
|
||||
shoppers = []
|
||||
|
||||
# make sure account holder is listed first
|
||||
account_holder = None
|
||||
|
@ -228,198 +261,62 @@ class PersonImporter(FromCOREPOSAPI, corepos_importing.model.PersonImporter):
|
|||
if mixedup:
|
||||
raise NotImplementedError("TODO: should re-sort the customers list for member {}".format(member['cardNo']))
|
||||
|
||||
ignore_new_members = self.should_ignore_new_members()
|
||||
|
||||
for i, customer in enumerate(customers, 1):
|
||||
person = dict(customer)
|
||||
shopper = dict(customer)
|
||||
shopper['card_number'] = member['cardNo']
|
||||
shopper['shopper_number'] = i
|
||||
shoppers.append(shopper)
|
||||
|
||||
if (ignore_new_members
|
||||
and not customer['firstName']
|
||||
and customer['lastName'] == 'NEW MEMBER'):
|
||||
return shoppers
|
||||
|
||||
log.debug("ignoring new member #%s: %s",
|
||||
member['cardNo'], member)
|
||||
|
||||
else:
|
||||
person['member_card_number'] = member['cardNo']
|
||||
person['customer_person_ordinal'] = i
|
||||
people.append(person)
|
||||
|
||||
return people
|
||||
|
||||
def get_customer_by_card_number(self, card_number):
|
||||
if hasattr(self, 'customers_by_card_number'):
|
||||
return self.customers_by_card_number.get(card_number)
|
||||
|
||||
model = self.model
|
||||
try:
|
||||
return self.session.query(model.Customer)\
|
||||
.join(model.CoreCustomer)\
|
||||
.filter(model.CoreCustomer.corepos_card_number == card_number)\
|
||||
.one()
|
||||
except orm.exc.NoResultFound:
|
||||
pass
|
||||
|
||||
def normalize_host_object(self, person):
|
||||
card_number = person['member_card_number']
|
||||
def normalize_host_object(self, shopper):
|
||||
card_number = shopper['card_number']
|
||||
|
||||
customer = self.get_customer_by_card_number(card_number)
|
||||
if not customer:
|
||||
log.warning("Rattail customer not found for CardNo %s: %s",
|
||||
card_number, person)
|
||||
card_number, shopper)
|
||||
return
|
||||
|
||||
data = {
|
||||
'customer_uuid': customer.uuid,
|
||||
'customer_person_ordinal': person['customer_person_ordinal'],
|
||||
'shopper_number': shopper['shopper_number'],
|
||||
|
||||
'first_name': person['firstName'],
|
||||
'last_name': person['lastName'],
|
||||
'display_name': normalize_full_name(person['firstName'],
|
||||
person['lastName']),
|
||||
'first_name': shopper['firstName'],
|
||||
'last_name': shopper['lastName'],
|
||||
'display_name': normalize_full_name(shopper['firstName'],
|
||||
shopper['lastName']),
|
||||
|
||||
'phone_number': person['phone'] or None,
|
||||
'phone_number_2': person['altPhone'] or None,
|
||||
'email_address': person['email'] or None,
|
||||
# TODO: can a CORE shopper be *not* active?
|
||||
'active': True,
|
||||
|
||||
# 'phone_number': shopper['phone'] or None,
|
||||
# 'phone_number_2': shopper['altPhone'] or None,
|
||||
|
||||
# 'email_address': shopper['email'] or None,
|
||||
}
|
||||
|
||||
# truncate phone number data if needed
|
||||
if data['phone_number'] and len(data['phone_number']) > self.maxlen_phone_number:
|
||||
log.warning("phone_number is too long (%s chars), will truncate to %s chars: %s",
|
||||
len(data['phone_number']),
|
||||
self.maxlen_phone_number,
|
||||
data['phone_number'])
|
||||
data['phone_number'] = data['phone_number'][:self.maxlen_phone_number]
|
||||
if data['phone_number_2'] and len(data['phone_number_2']) > self.maxlen_phone_number:
|
||||
log.warning("phone_number_2 is too long (%s chars), will truncate to %s chars: %s",
|
||||
len(data['phone_number_2']),
|
||||
self.maxlen_phone_number,
|
||||
data['phone_number_2'])
|
||||
data['phone_number_2'] = data['phone_number_2'][:self.maxlen_phone_number]
|
||||
# # truncate phone number data if needed
|
||||
# if data['phone_number'] and len(data['phone_number']) > self.maxlen_phone_number:
|
||||
# log.warning("phone_number is too long (%s chars), "
|
||||
# "will truncate to %s chars: %s",
|
||||
# len(data['phone_number']),
|
||||
# self.maxlen_phone_number,
|
||||
# data['phone_number'])
|
||||
# data['phone_number'] = data['phone_number'][:self.maxlen_phone_number]
|
||||
# if data['phone_number_2'] and len(data['phone_number_2']) > self.maxlen_phone_number:
|
||||
# log.warning("phone_number_2 is too long (%s chars), "
|
||||
# "will truncate to %s chars: %s",
|
||||
# len(data['phone_number_2']),
|
||||
# self.maxlen_phone_number,
|
||||
# data['phone_number_2'])
|
||||
# data['phone_number_2'] = data['phone_number_2'][:self.maxlen_phone_number]
|
||||
|
||||
# swap 1st and 2nd phone numbers if only latter has value
|
||||
self.prioritize_2(data, 'phone_number')
|
||||
# # swap 1st and 2nd phone numbers if only latter has value
|
||||
# self.prioritize_2(data, 'phone_number')
|
||||
|
||||
return data
|
||||
|
||||
def normalize_local_object(self, person):
|
||||
data = super().normalize_local_object(person)
|
||||
if data:
|
||||
|
||||
# ignore local Person records with no customer_uuid;
|
||||
# otherwise importer will try to delete them, and/or
|
||||
# they just cause "duplicate keys" noise
|
||||
if 'customer_uuid' in self.key:
|
||||
if not data['customer_uuid']:
|
||||
return
|
||||
|
||||
return data
|
||||
|
||||
|
||||
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()
|
||||
|
||||
query = self.session.query(model.Customer)\
|
||||
.join(model.CoreCustomer)
|
||||
self.customers = self.app.cache_model(self.session,
|
||||
model.Customer,
|
||||
query=query,
|
||||
key='corepos_account_id')
|
||||
|
||||
query = self.session.query(model.Person)\
|
||||
.join(model.CorePerson)\
|
||||
.filter(model.CorePerson.corepos_customer_id != None)
|
||||
self.people = self.app.cache_model(
|
||||
self.session,
|
||||
model.Person,
|
||||
key='corepos_customer_id',
|
||||
query=query,
|
||||
query_options=[orm.joinedload(model.Person._corepos)])
|
||||
|
||||
def get_host_objects(self):
|
||||
|
||||
# first get all member data from CORE API
|
||||
members = self.get_core_members()
|
||||
normalized = []
|
||||
|
||||
# then collect all customer/person combination records
|
||||
def normalize(member, i):
|
||||
# make sure we put the account holder first in the list!
|
||||
customers = sorted(member['customers'],
|
||||
key=lambda cust: 1 if cust['accountHolder'] else 0,
|
||||
reverse=True)
|
||||
for i, customer in enumerate(customers, 1):
|
||||
normalized.append({
|
||||
'customer_account_id': int(member['customerAccountID']),
|
||||
'person_customer_id': customer['customerID'],
|
||||
'ordinal': i,
|
||||
})
|
||||
|
||||
self.progress_loop(normalize, members,
|
||||
message="Collecting CustomerPerson data from CORE")
|
||||
return normalized
|
||||
|
||||
def get_customer(self, account_id):
|
||||
if hasattr(self, 'customers'):
|
||||
return self.customers.get(account_id)
|
||||
|
||||
model = self.config.get_model()
|
||||
try:
|
||||
return self.session.query(model.Customer)\
|
||||
.join(model.CoreCustomer)\
|
||||
.filter(model.CoreCustomer.corepos_account_id == account_id)\
|
||||
.one()
|
||||
except orm.exc.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 orm.exc.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'],
|
||||
}
|
||||
|
||||
|
||||
class StoreImporter(FromCOREPOSAPI, corepos_importing.model.StoreImporter):
|
||||
"""
|
||||
|
@ -920,7 +817,7 @@ class MemberImporter(FromCOREPOSAPI, corepos_importing.model.MemberImporter):
|
|||
card_number, member)
|
||||
return
|
||||
|
||||
person = customer.first_person()
|
||||
person = self.app.get_person(customer)
|
||||
if not person:
|
||||
log.warning("Rattail person not found for cardNo %s: %s",
|
||||
card_number, member)
|
||||
|
|
Loading…
Reference in a new issue