Add customer, member importers from CORE DB
API is just not cutting it, need more flexibility
This commit is contained in:
		
							parent
							
								
									124a510c17
								
							
						
					
					
						commit
						d08c475223
					
				
					 5 changed files with 276 additions and 9 deletions
				
			
		| 
						 | 
				
			
			@ -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)
 | 
			
		||||
| 
						 | 
				
			
			@ -205,14 +205,20 @@ class CoreMember(model.Base):
 | 
			
		|||
            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.
 | 
			
		||||
    """)
 | 
			
		||||
 | 
			
		||||
    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):
 | 
			
		||||
        return str(self.member)
 | 
			
		||||
 | 
			
		||||
CoreMember.make_proxy(model.Member, '_corepos', 'corepos_account_id')
 | 
			
		||||
CoreMember.make_proxy(model.Member, '_corepos', 'corepos_card_number')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CoreMemberEquityPayment(model.Base):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -144,6 +144,8 @@ class CustomerImporter(FromCOREPOSAPI, corepos_importing.model.CustomerImporter)
 | 
			
		|||
        'corepos_account_id',
 | 
			
		||||
        'number',
 | 
			
		||||
        'name',
 | 
			
		||||
        # 'account_holder_first_name',
 | 
			
		||||
        # 'account_holder_last_name',
 | 
			
		||||
        'address_street',
 | 
			
		||||
        'address_street2',
 | 
			
		||||
        'address_city',
 | 
			
		||||
| 
						 | 
				
			
			@ -182,6 +184,8 @@ class CustomerImporter(FromCOREPOSAPI, corepos_importing.model.CustomerImporter)
 | 
			
		|||
            'name': normalize_full_name(customer['firstName'],
 | 
			
		||||
                                        customer['lastName']),
 | 
			
		||||
 | 
			
		||||
            # 'account_holder_first_name': customer['firstName'],
 | 
			
		||||
            # 'account_holder_last_name': customer['lastName'],
 | 
			
		||||
            'address_street': member['addressFirstLine'] or None,
 | 
			
		||||
            'address_street2': member['addressSecondLine'] 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.
 | 
			
		||||
    """
 | 
			
		||||
    # TODO use this key instead
 | 
			
		||||
    #key = 'corepos_card_number'
 | 
			
		||||
    key = 'number'
 | 
			
		||||
    supported_fields = [
 | 
			
		||||
        'number',
 | 
			
		||||
        'corepos_account_id',
 | 
			
		||||
        'corepos_card_number',
 | 
			
		||||
        'customer_uuid',
 | 
			
		||||
        'person_first_name',
 | 
			
		||||
        'person_last_name',
 | 
			
		||||
| 
						 | 
				
			
			@ -857,6 +864,8 @@ class MemberImporter(FromCOREPOSAPI, corepos_importing.model.MemberImporter):
 | 
			
		|||
            joined = datetime.datetime.strptime(member['startDate'],
 | 
			
		||||
                                                '%Y-%m-%d %H:%M:%S')
 | 
			
		||||
            joined = joined.date()
 | 
			
		||||
            if joined == datetime.date(1900, 1, 1):
 | 
			
		||||
                joined = None
 | 
			
		||||
 | 
			
		||||
        withdrew = None
 | 
			
		||||
        if (member['endDate']
 | 
			
		||||
| 
						 | 
				
			
			@ -865,19 +874,20 @@ class MemberImporter(FromCOREPOSAPI, corepos_importing.model.MemberImporter):
 | 
			
		|||
            withdrew = datetime.datetime.strptime(member['endDate'],
 | 
			
		||||
                                                  '%Y-%m-%d %H:%M:%S')
 | 
			
		||||
            withdrew = withdrew.date()
 | 
			
		||||
            if withdrew == datetime.date(1900, 1, 1):
 | 
			
		||||
                withdrew = None
 | 
			
		||||
 | 
			
		||||
        typeno = int(member['customerTypeID']) or None
 | 
			
		||||
        if typeno:
 | 
			
		||||
            memtype = self.get_membership_type_by_number(typeno)
 | 
			
		||||
            if not memtype:
 | 
			
		||||
                logger = log.warning if self.get_warn_for_unknown_membership_type() else log.debug
 | 
			
		||||
                logger("unknown customerTypeID (membership_type_number) %s for: %s",
 | 
			
		||||
                       member['customerTypeID'], member)
 | 
			
		||||
                typeno = None
 | 
			
		||||
        typeno = int(member['customerTypeID'])
 | 
			
		||||
        memtype = self.get_membership_type_by_number(typeno)
 | 
			
		||||
        if not memtype:
 | 
			
		||||
            log.warning("unknown customerTypeID (membership_type_number) %s for: %s",
 | 
			
		||||
                        member['customerTypeID'], member)
 | 
			
		||||
            typeno = None
 | 
			
		||||
 | 
			
		||||
        data = {
 | 
			
		||||
            'number': card_number,
 | 
			
		||||
            'corepos_account_id': int(member['customerAccountID']),
 | 
			
		||||
            'corepos_card_number': card_number,
 | 
			
		||||
            'customer_uuid': customer.uuid if customer else None,
 | 
			
		||||
            'person_first_name': None,
 | 
			
		||||
            'person_last_name': None,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -24,10 +24,12 @@
 | 
			
		|||
CORE POS (DB) -> Rattail data importing
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import datetime
 | 
			
		||||
import decimal
 | 
			
		||||
import logging
 | 
			
		||||
from collections import OrderedDict
 | 
			
		||||
 | 
			
		||||
import sqlalchemy as sa
 | 
			
		||||
from sqlalchemy import orm
 | 
			
		||||
 | 
			
		||||
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):
 | 
			
		||||
        importers = OrderedDict()
 | 
			
		||||
        importers['Employee'] = EmployeeImporter
 | 
			
		||||
        importers['Customer'] = CustomerImporter
 | 
			
		||||
        importers['Member'] = MemberImporter
 | 
			
		||||
        importers['Tax'] = TaxImporter
 | 
			
		||||
        importers['Tender'] = TenderImporter
 | 
			
		||||
        importers['Vendor'] = VendorImporter
 | 
			
		||||
| 
						 | 
				
			
			@ -90,6 +94,25 @@ class FromCOREPOS(importing.FromSQLAlchemy):
 | 
			
		|||
    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):
 | 
			
		||||
    """
 | 
			
		||||
| 
						 | 
				
			
			@ -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):
 | 
			
		||||
    """
 | 
			
		||||
    Importer for tax data from CORE POS.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -87,6 +87,7 @@ class MemberImporter(importing.model.MemberImporter):
 | 
			
		|||
    extensions = {
 | 
			
		||||
        '_corepos': [
 | 
			
		||||
            'corepos_account_id',
 | 
			
		||||
            'corepos_card_number',
 | 
			
		||||
        ],
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue