Add 2-way sync for basic Member data, CORE <-> Rattail
This commit is contained in:
parent
9dbdb81f07
commit
8917316a21
|
@ -92,8 +92,8 @@ class MemberImporter(ToCoreAPI):
|
||||||
# 'chargeBalance',
|
# 'chargeBalance',
|
||||||
# 'chargeLimit',
|
# 'chargeLimit',
|
||||||
# 'idCardUPC',
|
# 'idCardUPC',
|
||||||
# 'startDate',
|
'startDate',
|
||||||
# 'endDate',
|
'endDate',
|
||||||
'addressFirstLine',
|
'addressFirstLine',
|
||||||
'addressSecondLine',
|
'addressSecondLine',
|
||||||
'city',
|
'city',
|
||||||
|
@ -123,6 +123,8 @@ class MemberImporter(ToCoreAPI):
|
||||||
# 'modified',
|
# 'modified',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
empty_date_value = '0000-00-00 00:00:00'
|
||||||
|
|
||||||
def get_local_objects(self, host_data=None):
|
def get_local_objects(self, host_data=None):
|
||||||
return get_core_members(self.api, progress=self.progress)
|
return get_core_members(self.api, progress=self.progress)
|
||||||
|
|
||||||
|
@ -144,6 +146,15 @@ class MemberImporter(ToCoreAPI):
|
||||||
if not self.customer_data_differs(local_data, host_data):
|
if not self.customer_data_differs(local_data, host_data):
|
||||||
diffs.remove('customers')
|
diffs.remove('customers')
|
||||||
|
|
||||||
|
# also the start/end dates should be looked at more closely. if they
|
||||||
|
# contain the special '__omit__' value then we won't ever count as diff
|
||||||
|
if 'startDate' in self.fields and 'startDate' in diffs:
|
||||||
|
if host_data['startDate'] == '__omit__':
|
||||||
|
diffs.remove('startDate')
|
||||||
|
if 'endDate' in self.fields and 'endDate' in diffs:
|
||||||
|
if host_data['endDate'] == '__omit__':
|
||||||
|
diffs.remove('endDate')
|
||||||
|
|
||||||
return diffs
|
return diffs
|
||||||
|
|
||||||
def customer_data_differs(self, local_data, host_data):
|
def customer_data_differs(self, local_data, host_data):
|
||||||
|
@ -211,6 +222,11 @@ class MemberImporter(ToCoreAPI):
|
||||||
return data
|
return data
|
||||||
|
|
||||||
cardNo = data.pop('cardNo')
|
cardNo = data.pop('cardNo')
|
||||||
|
data = dict(data)
|
||||||
|
if data.get('startDate') == '__omit__':
|
||||||
|
data.pop('startDate')
|
||||||
|
if data.get('endDate') == '__omit__':
|
||||||
|
data.pop('endDate')
|
||||||
member = self.api.set_member(cardNo, **data)
|
member = self.api.set_member(cardNo, **data)
|
||||||
return member
|
return member
|
||||||
|
|
||||||
|
|
|
@ -78,6 +78,8 @@ class MemberImporter(FromRattail, corepos_importing.model.MemberImporter):
|
||||||
'city',
|
'city',
|
||||||
'state',
|
'state',
|
||||||
'zip',
|
'zip',
|
||||||
|
'startDate',
|
||||||
|
'endDate',
|
||||||
]
|
]
|
||||||
supported_customer_fields = [
|
supported_customer_fields = [
|
||||||
'customerID',
|
'customerID',
|
||||||
|
@ -120,6 +122,20 @@ class MemberImporter(FromRattail, corepos_importing.model.MemberImporter):
|
||||||
'email': email.address if email else '',
|
'email': email.address if email else '',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
member = customer.only_member(require=False)
|
||||||
|
if member:
|
||||||
|
if member.joined:
|
||||||
|
start_date = member.joined.strftime('%Y-%m-%d 00:00:00')
|
||||||
|
else:
|
||||||
|
start_date = self.empty_date_value
|
||||||
|
if member.withdrew:
|
||||||
|
end_date = member.withdrew.strftime('%Y-%m-%d 00:00:00')
|
||||||
|
else:
|
||||||
|
end_date = self.empty_date_value
|
||||||
|
else:
|
||||||
|
start_date = '__omit__'
|
||||||
|
end_date = '__omit__'
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'cardNo': customer.number,
|
'cardNo': customer.number,
|
||||||
'customerAccountID': customer.id,
|
'customerAccountID': customer.id,
|
||||||
|
@ -128,6 +144,8 @@ class MemberImporter(FromRattail, corepos_importing.model.MemberImporter):
|
||||||
'city': address.city if address else '',
|
'city': address.city if address else '',
|
||||||
'state': address.state if address else '',
|
'state': address.state if address else '',
|
||||||
'zip': address.zipcode if address else '',
|
'zip': address.zipcode if address else '',
|
||||||
|
'startDate': start_date,
|
||||||
|
'endDate': end_date,
|
||||||
'customers': people,
|
'customers': people,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -160,6 +160,7 @@ class FromRattailToCore(NewDataSyncImportConsumer):
|
||||||
'CustomerMailingAddress',
|
'CustomerMailingAddress',
|
||||||
'PersonPhoneNumber',
|
'PersonPhoneNumber',
|
||||||
'PersonEmailAddress',
|
'PersonEmailAddress',
|
||||||
|
'Member',
|
||||||
]
|
]
|
||||||
for change in [c for c in changes if c.payload_type in types]:
|
for change in [c for c in changes if c.payload_type in types]:
|
||||||
if change.payload_type == 'Customer' and change.deletion:
|
if change.payload_type == 'Customer' and change.deletion:
|
||||||
|
@ -252,6 +253,11 @@ class FromRattailToCore(NewDataSyncImportConsumer):
|
||||||
if email:
|
if email:
|
||||||
return email.person.customers
|
return email.person.customers
|
||||||
|
|
||||||
|
if change.payload_type == 'Member':
|
||||||
|
member = session.query(model.Member).get(change.payload_key)
|
||||||
|
if member:
|
||||||
|
return [member.customer]
|
||||||
|
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def get_vendor(self, session, change):
|
def get_vendor(self, session, change):
|
||||||
|
|
|
@ -73,6 +73,8 @@ class FromCOREAPIToRattail(NewDataSyncImportConsumer):
|
||||||
for person in people:
|
for person in people:
|
||||||
self.process_change(session, self.importers['Person'],
|
self.process_change(session, self.importers['Person'],
|
||||||
host_object=person)
|
host_object=person)
|
||||||
|
self.process_change(session, self.importers['Member'],
|
||||||
|
host_object=member)
|
||||||
|
|
||||||
# process all remaining supported models with typical logic
|
# process all remaining supported models with typical logic
|
||||||
types = [
|
types = [
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
CORE POS (API) -> Rattail data importing
|
CORE POS (API) -> Rattail data importing
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import datetime
|
||||||
import decimal
|
import decimal
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
@ -55,6 +56,7 @@ class FromCOREPOSToRattail(importing.ToRattailHandler):
|
||||||
importers['Customer'] = CustomerImporter
|
importers['Customer'] = CustomerImporter
|
||||||
importers['Person'] = PersonImporter
|
importers['Person'] = PersonImporter
|
||||||
importers['CustomerPerson'] = CustomerPersonImporter
|
importers['CustomerPerson'] = CustomerPersonImporter
|
||||||
|
importers['Member'] = MemberImporter
|
||||||
importers['Department'] = DepartmentImporter
|
importers['Department'] = DepartmentImporter
|
||||||
importers['Subdepartment'] = SubdepartmentImporter
|
importers['Subdepartment'] = SubdepartmentImporter
|
||||||
importers['Vendor'] = VendorImporter
|
importers['Vendor'] = VendorImporter
|
||||||
|
@ -100,20 +102,32 @@ class CustomerImporter(FromCOREPOSAPI, importing.model.CustomerImporter):
|
||||||
|
|
||||||
def normalize_host_object(self, member):
|
def normalize_host_object(self, member):
|
||||||
|
|
||||||
# figure out the "account holder" customer for the member
|
if member['customerAccountID'] == 0:
|
||||||
|
log.debug("member %s has customerAccountID of 0: %s",
|
||||||
|
member['cardNo'], member)
|
||||||
|
return
|
||||||
|
|
||||||
|
# figure out the "account holder" customer for the member. note that
|
||||||
|
# we only use this to determine the `Customer.name` in Rattail
|
||||||
customers = member['customers']
|
customers = member['customers']
|
||||||
account_holders = [customer for customer in customers
|
account_holders = [customer for customer in customers
|
||||||
if customer['accountHolder']]
|
if customer['accountHolder']]
|
||||||
|
if account_holders:
|
||||||
if len(account_holders) > 1:
|
if len(account_holders) > 1:
|
||||||
log.warning("member %s has %s account holders in CORE: %s",
|
log.warning("member %s has %s account holders in CORE: %s",
|
||||||
member['cardNo'], len(account_holders), member)
|
member['cardNo'], len(account_holders), member)
|
||||||
elif not account_holders:
|
|
||||||
raise NotImplementedError("TODO: how to handle member with no account holders?")
|
|
||||||
customer = account_holders[0]
|
customer = account_holders[0]
|
||||||
|
elif customers:
|
||||||
|
if len(customers) > 1:
|
||||||
|
log.warning("member %s has %s customers but no account holders: %s",
|
||||||
|
member['cardNo'], len(customers), member)
|
||||||
|
customer = customers[0]
|
||||||
|
else:
|
||||||
|
raise NotImplementedError("TODO: how to handle member with no customers?")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': member['customerAccountID'],
|
'id': member['customerAccountID'],
|
||||||
'number': member['cardNo'],
|
'number': int(member['cardNo']),
|
||||||
'name': normalize_full_name(customer['firstName'],
|
'name': normalize_full_name(customer['firstName'],
|
||||||
customer['lastName']),
|
customer['lastName']),
|
||||||
|
|
||||||
|
@ -167,12 +181,22 @@ class PersonImporter(FromCOREPOSAPI, corepos_importing.model.PersonImporter):
|
||||||
Return a list of Person data objects for the given Member. This
|
Return a list of Person data objects for the given Member. This
|
||||||
logic is split out separately so that datasync can leverage it too.
|
logic is split out separately so that datasync can leverage it too.
|
||||||
"""
|
"""
|
||||||
|
customers = member['customers']
|
||||||
people = []
|
people = []
|
||||||
|
|
||||||
# make sure we put the account holder first in the list!
|
# make sure account holder is listed first
|
||||||
customers = sorted(member['customers'],
|
account_holder = None
|
||||||
key=lambda cust: 1 if cust['accountHolder'] else 0,
|
secondary = False
|
||||||
reverse=True)
|
mixedup = False
|
||||||
|
for customer in customers:
|
||||||
|
if customer['accountHolder'] and not secondary:
|
||||||
|
account_holder = customer
|
||||||
|
elif not customer['accountHolder']:
|
||||||
|
secondary = True
|
||||||
|
elif customer['accountHolder'] and secondary:
|
||||||
|
mixedup = True
|
||||||
|
if mixedup:
|
||||||
|
raise NotImplementedError("TODO: should re-sort the customers list for member {}".format(member['cardNo']))
|
||||||
|
|
||||||
for i, customer in enumerate(customers, 1):
|
for i, customer in enumerate(customers, 1):
|
||||||
person = dict(customer)
|
person = dict(customer)
|
||||||
|
@ -450,3 +474,81 @@ class ProductImporter(FromCOREPOSAPI, importing.model.ProductImporter):
|
||||||
'regular_price_multiple': 1 if price is not None else None,
|
'regular_price_multiple': 1 if price is not None else None,
|
||||||
'regular_price_type': self.enum.PRICE_TYPE_REGULAR if price is not None else None,
|
'regular_price_type': self.enum.PRICE_TYPE_REGULAR if price is not None else None,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class MemberImporter(FromCOREPOSAPI, importing.model.MemberImporter):
|
||||||
|
"""
|
||||||
|
Importer for member data from CORE POS API.
|
||||||
|
"""
|
||||||
|
key = 'id'
|
||||||
|
supported_fields = [
|
||||||
|
'id',
|
||||||
|
'customer_uuid',
|
||||||
|
'joined',
|
||||||
|
'withdrew',
|
||||||
|
]
|
||||||
|
|
||||||
|
# TODO: should make this configurable
|
||||||
|
member_status_codes = [
|
||||||
|
'PC',
|
||||||
|
'TERM',
|
||||||
|
]
|
||||||
|
|
||||||
|
# TODO: should make this configurable
|
||||||
|
non_member_status_codes = [
|
||||||
|
'REG',
|
||||||
|
'INACT',
|
||||||
|
]
|
||||||
|
|
||||||
|
def setup(self):
|
||||||
|
super(MemberImporter, self).setup()
|
||||||
|
self.customers = self.cache_model(model.Customer, key='number')
|
||||||
|
|
||||||
|
def get_host_objects(self):
|
||||||
|
return self.get_core_members()
|
||||||
|
|
||||||
|
def get_customer(self, number):
|
||||||
|
if hasattr(self, 'customers'):
|
||||||
|
return self.customers.get(number)
|
||||||
|
|
||||||
|
try:
|
||||||
|
return self.session.query(model.Customer)\
|
||||||
|
.filter(model.Customer.number == number)\
|
||||||
|
.one()
|
||||||
|
except NoResultFound:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def normalize_host_object(self, member):
|
||||||
|
customer = self.get_customer(member['cardNo'])
|
||||||
|
if not customer:
|
||||||
|
log.warning("Rattail customer not found for cardNo %s: %s",
|
||||||
|
member['cardNo'], member)
|
||||||
|
return
|
||||||
|
|
||||||
|
if member['memberStatus'] in self.non_member_status_codes:
|
||||||
|
log.debug("skipping non-member %s with status '%s': %s",
|
||||||
|
member['memberStatus'], member['cardNo'], member)
|
||||||
|
return
|
||||||
|
if member['memberStatus'] not in self.member_status_codes:
|
||||||
|
# note that we will still import this one! we don't skip it
|
||||||
|
log.warning("unexpected status '%s' for member %s: %s",
|
||||||
|
member['memberStatus'], member['cardNo'], member)
|
||||||
|
|
||||||
|
joined = None
|
||||||
|
if member['startDate'] and member['startDate'] != '0000-00-00 00:00:00':
|
||||||
|
joined = datetime.datetime.strptime(member['startDate'],
|
||||||
|
'%Y-%m-%d %H:%M:%S')
|
||||||
|
joined = joined.date()
|
||||||
|
|
||||||
|
withdrew = None
|
||||||
|
if member['endDate'] and member['endDate'] != '0000-00-00 00:00:00':
|
||||||
|
withdrew = datetime.datetime.strptime(member['endDate'],
|
||||||
|
'%Y-%m-%d %H:%M:%S')
|
||||||
|
withdrew = withdrew.date()
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': str(member['cardNo']),
|
||||||
|
'customer_uuid': customer.uuid,
|
||||||
|
'joined': joined,
|
||||||
|
'withdrew': withdrew,
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue