Add customer, member importers from CORE DB

API is just not cutting it, need more flexibility
This commit is contained in:
Lance Edgar 2023-10-12 10:34:46 -05:00
parent 124a510c17
commit d08c475223
5 changed files with 276 additions and 9 deletions

View file

@ -0,0 +1,38 @@
# -*- coding: utf-8; -*-
"""add core_member.corepos_card_number
Revision ID: 15bf65f68c52
Revises: 673ff7088d35
Create Date: 2023-10-12 07:39:34.608105
"""
# revision identifiers, used by Alembic.
revision = '15bf65f68c52'
down_revision = '673ff7088d35'
branch_labels = None
depends_on = None
from alembic import op
import sqlalchemy as sa
import rattail.db.types
def upgrade():
# corepos_member
op.alter_column('corepos_member', 'corepos_account_id',
existing_type=sa.INTEGER(),
nullable=True)
op.add_column('corepos_member', sa.Column('corepos_card_number', sa.Integer(), nullable=True))
op.add_column('corepos_member_version', sa.Column('corepos_card_number', sa.Integer(), autoincrement=False, nullable=True))
def downgrade():
# corepos_member
op.drop_column('corepos_member_version', 'corepos_card_number')
op.drop_column('corepos_member', 'corepos_card_number')
op.alter_column('corepos_member', 'corepos_account_id',
existing_type=sa.INTEGER(),
nullable=False)

View file

@ -205,14 +205,20 @@ class CoreMember(model.Base):
Reference to the CORE-POS extension record for this member. Reference to the CORE-POS extension record for this member.
""")) """))
corepos_account_id = sa.Column(sa.Integer(), nullable=False, doc=""" corepos_account_id = sa.Column(sa.Integer(), nullable=True, doc="""
``Customers.customerAccountID`` value for this member, within CORE-POS. ``Customers.customerAccountID`` value for this member, within CORE-POS.
""") """)
corepos_card_number = sa.Column(sa.Integer(), nullable=True, doc="""
``meminfo.card_no`` / ``CustomerAccounts.cardNo`` value for this
member, within CORE-POS.
""")
def __str__(self): def __str__(self):
return str(self.member) return str(self.member)
CoreMember.make_proxy(model.Member, '_corepos', 'corepos_account_id') CoreMember.make_proxy(model.Member, '_corepos', 'corepos_account_id')
CoreMember.make_proxy(model.Member, '_corepos', 'corepos_card_number')
class CoreMemberEquityPayment(model.Base): class CoreMemberEquityPayment(model.Base):

View file

@ -144,6 +144,8 @@ class CustomerImporter(FromCOREPOSAPI, corepos_importing.model.CustomerImporter)
'corepos_account_id', 'corepos_account_id',
'number', 'number',
'name', 'name',
# 'account_holder_first_name',
# 'account_holder_last_name',
'address_street', 'address_street',
'address_street2', 'address_street2',
'address_city', 'address_city',
@ -182,6 +184,8 @@ class CustomerImporter(FromCOREPOSAPI, corepos_importing.model.CustomerImporter)
'name': normalize_full_name(customer['firstName'], 'name': normalize_full_name(customer['firstName'],
customer['lastName']), customer['lastName']),
# 'account_holder_first_name': customer['firstName'],
# 'account_holder_last_name': customer['lastName'],
'address_street': member['addressFirstLine'] or None, 'address_street': member['addressFirstLine'] or None,
'address_street2': member['addressSecondLine'] or None, 'address_street2': member['addressSecondLine'] or None,
'address_city': member['city'] or None, 'address_city': member['city'] or None,
@ -783,10 +787,13 @@ class MemberImporter(FromCOREPOSAPI, corepos_importing.model.MemberImporter):
""" """
Importer for member data from CORE POS API. Importer for member data from CORE POS API.
""" """
# TODO use this key instead
#key = 'corepos_card_number'
key = 'number' key = 'number'
supported_fields = [ supported_fields = [
'number', 'number',
'corepos_account_id', 'corepos_account_id',
'corepos_card_number',
'customer_uuid', 'customer_uuid',
'person_first_name', 'person_first_name',
'person_last_name', 'person_last_name',
@ -857,6 +864,8 @@ class MemberImporter(FromCOREPOSAPI, corepos_importing.model.MemberImporter):
joined = datetime.datetime.strptime(member['startDate'], joined = datetime.datetime.strptime(member['startDate'],
'%Y-%m-%d %H:%M:%S') '%Y-%m-%d %H:%M:%S')
joined = joined.date() joined = joined.date()
if joined == datetime.date(1900, 1, 1):
joined = None
withdrew = None withdrew = None
if (member['endDate'] if (member['endDate']
@ -865,19 +874,20 @@ class MemberImporter(FromCOREPOSAPI, corepos_importing.model.MemberImporter):
withdrew = datetime.datetime.strptime(member['endDate'], withdrew = datetime.datetime.strptime(member['endDate'],
'%Y-%m-%d %H:%M:%S') '%Y-%m-%d %H:%M:%S')
withdrew = withdrew.date() withdrew = withdrew.date()
if withdrew == datetime.date(1900, 1, 1):
withdrew = None
typeno = int(member['customerTypeID']) or None typeno = int(member['customerTypeID'])
if typeno: memtype = self.get_membership_type_by_number(typeno)
memtype = self.get_membership_type_by_number(typeno) if not memtype:
if not memtype: log.warning("unknown customerTypeID (membership_type_number) %s for: %s",
logger = log.warning if self.get_warn_for_unknown_membership_type() else log.debug member['customerTypeID'], member)
logger("unknown customerTypeID (membership_type_number) %s for: %s", typeno = None
member['customerTypeID'], member)
typeno = None
data = { data = {
'number': card_number, 'number': card_number,
'corepos_account_id': int(member['customerAccountID']), 'corepos_account_id': int(member['customerAccountID']),
'corepos_card_number': card_number,
'customer_uuid': customer.uuid if customer else None, 'customer_uuid': customer.uuid if customer else None,
'person_first_name': None, 'person_first_name': None,
'person_last_name': None, 'person_last_name': None,

View file

@ -24,10 +24,12 @@
CORE POS (DB) -> Rattail data importing CORE POS (DB) -> Rattail data importing
""" """
import datetime
import decimal import decimal
import logging import logging
from collections import OrderedDict from collections import OrderedDict
import sqlalchemy as sa
from sqlalchemy import orm from sqlalchemy import orm
from corepos.db.office_op import model as corepos, Session as CoreSession from corepos.db.office_op import model as corepos, Session as CoreSession
@ -67,6 +69,8 @@ class FromCOREPOSToRattail(importing.FromSQLAlchemyHandler, importing.ToRattailH
def get_importers(self): def get_importers(self):
importers = OrderedDict() importers = OrderedDict()
importers['Employee'] = EmployeeImporter importers['Employee'] = EmployeeImporter
importers['Customer'] = CustomerImporter
importers['Member'] = MemberImporter
importers['Tax'] = TaxImporter importers['Tax'] = TaxImporter
importers['Tender'] = TenderImporter importers['Tender'] = TenderImporter
importers['Vendor'] = VendorImporter importers['Vendor'] = VendorImporter
@ -90,6 +94,25 @@ class FromCOREPOS(importing.FromSQLAlchemy):
Base class for all CORE POS data importers. Base class for all CORE POS data importers.
""" """
def setup(self):
super().setup()
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 is_new_member(self, member):
if member.customers:
customer = member.customers[0]
if customer.last_name == 'NEW MEMBER'and not customer.first_name:
return True
return False
class EmployeeImporter(FromCOREPOS, corepos_importing.model.EmployeeImporter): class EmployeeImporter(FromCOREPOS, corepos_importing.model.EmployeeImporter):
""" """
@ -115,6 +138,195 @@ class EmployeeImporter(FromCOREPOS, corepos_importing.model.EmployeeImporter):
} }
class CustomerImporter(FromCOREPOS, corepos_importing.model.CustomerImporter):
"""
Importer for customer data from CORE POS.
"""
host_model_class = corepos.MemberInfo
key = 'corepos_card_number'
supported_fields = [
'corepos_card_number',
'number',
'name',
'account_holder_uuid',
'account_holder_first_name',
'account_holder_last_name',
'email_address',
'phone_number',
'address_street',
'address_street2',
'address_city',
'address_state',
'address_zipcode',
]
def setup(self):
super().setup()
model = self.model
query = self.session.query(model.Person)\
.outerjoin(model.Customer,
model.Customer.account_holder_uuid == model.Person.uuid)\
.outerjoin(model.CoreCustomer)\
.outerjoin(model.Member,
model.Member.person_uuid == model.Person.uuid)\
.outerjoin(model.CoreMember)\
.filter(sa.or_(
model.CoreCustomer.corepos_card_number != None,
model.CoreMember.corepos_card_number != None))\
.options(orm.joinedload(model.Person.customer_accounts)\
.joinedload(model.Customer._corepos))
def card_number(person, normal):
customer = self.app.get_customer(person)
if customer and customer.corepos_card_number:
return customer.corepos_card_number
member = self.app.get_member(person)
if member and member.corepos_card_number:
return member.corepos_card_number
self.people_by_card_number = self.cache_model(model.Person, query=query,
key=card_number)
def get_person(self, member):
if hasattr(self, 'people_by_card_number'):
return self.people_by_card_number.get(member.card_number)
model = self.model
try:
return self.session.query(model.Person)\
.join(model.Customer,
model.Customer.account_holder_uuid == model.Person.uuid)\
.join(model.CoreCustomer)\
.filter(model.CoreCustomer.corepos_card_number == member.card_number)\
.one()
except orm.exc.NoResultFound:
pass
try:
return self.session.query(model.Person)\
.join(model.Member,
model.Member.person_uuid == model.Person.uuid)\
.join(model.CoreMember)\
.filter(model.CoreMember.corepos_card_number == member.card_number)\
.one()
except orm.exc.NoResultFound:
pass
def normalize_host_object(self, member):
card_number = member.card_number
# maybe ignore NEW MEMBER accounts
if self.should_ignore_new_members():
if self.is_new_member(member):
return
contact = member
if member.customers:
contact = member.customers[0]
person = self.get_person(member)
street = (member.street or '').split('\n')
return {
'corepos_card_number': card_number,
'number': card_number,
'name': normalize_full_name(contact.first_name, contact.last_name),
'account_holder_uuid': person.uuid if person else None,
'account_holder_first_name': contact.first_name,
'account_holder_last_name': contact.last_name,
'email_address': (member.email or '').strip() or None,
'phone_number': self.app.format_phone_number((member.phone or '').strip() or None),
'address_street': street[0] or None,
'address_street2': (street[1] or None) if len(street) > 1 else None,
'address_city': member.city or None,
'address_state': member.state or None,
'address_zipcode': member.zip or None,
}
class MemberImporter(FromCOREPOS, corepos_importing.model.MemberImporter):
"""
Importer for member data from CORE POS.
"""
host_model_class = corepos.MemberInfo
# TODO use this key instead
#key = 'corepos_card_number'
key = 'number'
supported_fields = [
'number',
'corepos_card_number',
'customer_uuid',
'person_first_name',
'person_last_name',
'membership_type_number',
'joined',
'withdrew',
'active',
]
def setup(self):
super().setup()
model = self.model
self.customers_by_number = self.app.cache_model(self.session,
model.Customer,
key='number')
def get_customer_by_number(self, number):
if hasattr(self, 'customers_by_number'):
return self.customers_by_number.get(number)
model = self.model
try:
return self.session.query(model.Customer)\
.filter(model.Customer.number == number)\
.one()
except orm.exc.NoResultFound:
pass
def normalize_host_object(self, core_member):
# maybe ignore NEW MEMBER accounts
if self.should_ignore_new_members():
if self.is_new_member(core_member):
return
core_customer = core_member.customers[0] if core_member.customers else None
core_contact = core_customer or core_member
card_number = core_member.card_number
customer = self.get_customer_by_number(card_number)
typeno = None
if core_customer and core_customer.member_type:
typeno = core_customer.member_type.id
joined = None
withdrew = None
if core_member.dates:
dates = core_member.dates
joined = dates.start_date.date() if dates.start_date else None
withdrew = dates.end_date.date() if dates.end_date else None
if joined and joined == datetime.date(1900, 1, 1):
joined = None
if withdrew and withdrew == datetime.date(1900, 1, 1):
withdrew = None
return {
'number': card_number,
'corepos_card_number': card_number,
'customer_uuid': customer.uuid if customer else None,
'person_first_name': core_contact.first_name,
'person_last_name': core_contact.last_name,
'membership_type_number': typeno,
'joined': joined,
'withdrew': withdrew,
'active': not bool(withdrew),
}
class TaxImporter(FromCOREPOS, corepos_importing.model.TaxImporter): class TaxImporter(FromCOREPOS, corepos_importing.model.TaxImporter):
""" """
Importer for tax data from CORE POS. Importer for tax data from CORE POS.

View file

@ -87,6 +87,7 @@ class MemberImporter(importing.model.MemberImporter):
extensions = { extensions = {
'_corepos': [ '_corepos': [
'corepos_account_id', 'corepos_account_id',
'corepos_card_number',
], ],
} }