Add support for importing MemberEquityPayment from CORE-POS DB
SQL only, no API for now
This commit is contained in:
parent
35e24422a2
commit
a57f29fe1a
|
@ -2,7 +2,7 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# Rattail -- Retail Software Framework
|
# Rattail -- Retail Software Framework
|
||||||
# Copyright © 2010-2020 Lance Edgar
|
# Copyright © 2010-2023 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Rattail.
|
# This file is part of Rattail.
|
||||||
#
|
#
|
||||||
|
@ -55,7 +55,14 @@ class ImportCOREPOSDB(commands.ImportSubcommand):
|
||||||
handler_key = 'to_rattail.from_corepos_db_office_op.import'
|
handler_key = 'to_rattail.from_corepos_db_office_op.import'
|
||||||
|
|
||||||
def add_parser_args(self, parser):
|
def add_parser_args(self, parser):
|
||||||
super(ImportCOREPOSDB, self).add_parser_args(parser)
|
super().add_parser_args(parser)
|
||||||
|
|
||||||
|
parser.add_argument('--corepos-dbtype', metavar='TYPE', default='office_op',
|
||||||
|
choices=['office_op', 'office_trans'],
|
||||||
|
help="Config *type* for CORE-POS database engine to be used as "
|
||||||
|
"host. Default type is 'office_op' - this determines which "
|
||||||
|
"config section is used with regard to the --corepos-dbkey arg.")
|
||||||
|
|
||||||
parser.add_argument('--corepos-dbkey', metavar='KEY', default='default',
|
parser.add_argument('--corepos-dbkey', metavar='KEY', default='default',
|
||||||
help="Config key for CORE POS database engine to be used as the \"host\", "
|
help="Config key for CORE POS database engine to be used as the \"host\", "
|
||||||
"i.e. the source of the data to be imported. This key " "must be "
|
"i.e. the source of the data to be imported. This key " "must be "
|
||||||
|
@ -64,6 +71,7 @@ class ImportCOREPOSDB(commands.ImportSubcommand):
|
||||||
|
|
||||||
def get_handler_kwargs(self, **kwargs):
|
def get_handler_kwargs(self, **kwargs):
|
||||||
if 'args' in kwargs:
|
if 'args' in kwargs:
|
||||||
|
kwargs['corepos_dbtype'] = kwargs['args'].corepos_dbtype
|
||||||
kwargs['corepos_dbkey'] = kwargs['args'].corepos_dbkey
|
kwargs['corepos_dbkey'] = kwargs['args'].corepos_dbkey
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
# -*- coding: utf-8; -*-
|
||||||
|
"""add MemberEquityPayment extension
|
||||||
|
|
||||||
|
Revision ID: 08d879bbe118
|
||||||
|
Revises: b025df7cf41b
|
||||||
|
Create Date: 2023-09-06 17:44:43.874500
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '08d879bbe118'
|
||||||
|
down_revision = 'b025df7cf41b'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
import rattail.db.types
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
|
||||||
|
# corepos_member_equity_payment
|
||||||
|
op.create_table('corepos_member_equity_payment',
|
||||||
|
sa.Column('uuid', sa.String(length=32), nullable=False),
|
||||||
|
sa.Column('corepos_card_number', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('corepos_transaction_number', sa.String(length=50), nullable=True),
|
||||||
|
sa.Column('corepos_transaction_id', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('corepos_department_number', sa.Integer(), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(['uuid'], ['member_equity_payment.uuid'], name='corepos_member_equity_payment_fk_payment'),
|
||||||
|
sa.PrimaryKeyConstraint('uuid')
|
||||||
|
)
|
||||||
|
op.create_table('corepos_member_equity_payment_version',
|
||||||
|
sa.Column('uuid', sa.String(length=32), autoincrement=False, nullable=False),
|
||||||
|
sa.Column('corepos_card_number', sa.Integer(), autoincrement=False, nullable=True),
|
||||||
|
sa.Column('corepos_transaction_number', sa.String(length=50), autoincrement=False, nullable=True),
|
||||||
|
sa.Column('corepos_transaction_id', sa.Integer(), autoincrement=False, nullable=True),
|
||||||
|
sa.Column('corepos_department_number', sa.Integer(), autoincrement=False, nullable=True),
|
||||||
|
sa.Column('transaction_id', sa.BigInteger(), autoincrement=False, nullable=False),
|
||||||
|
sa.Column('end_transaction_id', sa.BigInteger(), nullable=True),
|
||||||
|
sa.Column('operation_type', sa.SmallInteger(), nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint('uuid', 'transaction_id')
|
||||||
|
)
|
||||||
|
op.create_index(op.f('ix_corepos_member_equity_payment_version_end_transaction_id'), 'corepos_member_equity_payment_version', ['end_transaction_id'], unique=False)
|
||||||
|
op.create_index(op.f('ix_corepos_member_equity_payment_version_operation_type'), 'corepos_member_equity_payment_version', ['operation_type'], unique=False)
|
||||||
|
op.create_index(op.f('ix_corepos_member_equity_payment_version_transaction_id'), 'corepos_member_equity_payment_version', ['transaction_id'], unique=False)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
|
||||||
|
# corepos_member_equity_payment
|
||||||
|
op.drop_index(op.f('ix_corepos_member_equity_payment_version_transaction_id'), table_name='corepos_member_equity_payment_version')
|
||||||
|
op.drop_index(op.f('ix_corepos_member_equity_payment_version_operation_type'), table_name='corepos_member_equity_payment_version')
|
||||||
|
op.drop_index(op.f('ix_corepos_member_equity_payment_version_end_transaction_id'), table_name='corepos_member_equity_payment_version')
|
||||||
|
op.drop_table('corepos_member_equity_payment_version')
|
||||||
|
op.drop_table('corepos_member_equity_payment')
|
|
@ -25,7 +25,8 @@ Database schema extensions for CORE-POS integration
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from .stores import CoreStore
|
from .stores import CoreStore
|
||||||
from .people import CorePerson, CoreCustomer, CoreCustomerShopper, CoreMember
|
from .people import (CorePerson, CoreCustomer, CoreCustomerShopper,
|
||||||
|
CoreMember, CoreMemberEquityPayment)
|
||||||
from .products import (CoreDepartment, CoreSubdepartment,
|
from .products import (CoreDepartment, CoreSubdepartment,
|
||||||
CoreVendor, CoreProduct, CoreProductCost)
|
CoreVendor, CoreProduct, CoreProductCost)
|
||||||
|
|
||||||
|
|
|
@ -178,3 +178,54 @@ class CoreMember(model.Base):
|
||||||
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')
|
||||||
|
|
||||||
|
|
||||||
|
class CoreMemberEquityPayment(model.Base):
|
||||||
|
"""
|
||||||
|
CORE-specific extensions to
|
||||||
|
:class:`~rattail:rattail.db.model.MemberEquityPayment`.
|
||||||
|
"""
|
||||||
|
__tablename__ = 'corepos_member_equity_payment'
|
||||||
|
__table_args__ = (
|
||||||
|
sa.ForeignKeyConstraint(['uuid'], ['member_equity_payment.uuid'],
|
||||||
|
name='corepos_member_equity_payment_fk_payment'),
|
||||||
|
)
|
||||||
|
__versioned__ = {}
|
||||||
|
|
||||||
|
uuid = model.uuid_column(default=None)
|
||||||
|
payment = orm.relationship(
|
||||||
|
model.MemberEquityPayment,
|
||||||
|
doc="""
|
||||||
|
Reference to the actual payment record, which this one extends.
|
||||||
|
""",
|
||||||
|
backref=orm.backref(
|
||||||
|
'_corepos',
|
||||||
|
uselist=False,
|
||||||
|
cascade='all, delete-orphan',
|
||||||
|
doc="""
|
||||||
|
Reference to the CORE-POS extension record for this payment.
|
||||||
|
"""))
|
||||||
|
|
||||||
|
corepos_card_number = sa.Column(sa.Integer(), nullable=False, doc="""
|
||||||
|
``stockpurchases.card_no`` value for this payment, within CORE-POS.
|
||||||
|
""")
|
||||||
|
|
||||||
|
corepos_transaction_number = sa.Column(sa.String(length=50), nullable=True, doc="""
|
||||||
|
``stockpurchases.trans_num`` value for this payment, within CORE-POS.
|
||||||
|
""")
|
||||||
|
|
||||||
|
corepos_transaction_id = sa.Column(sa.Integer(), nullable=True, doc="""
|
||||||
|
``stockpurchases.trans_id`` value for this payment, within CORE-POS.
|
||||||
|
""")
|
||||||
|
|
||||||
|
corepos_department_number = sa.Column(sa.Integer(), nullable=True, doc="""
|
||||||
|
``stockpurchases.dept`` value for this payment, within CORE-POS.
|
||||||
|
""")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.payment)
|
||||||
|
|
||||||
|
CoreMemberEquityPayment.make_proxy(model.MemberEquityPayment, '_corepos', 'corepos_card_number')
|
||||||
|
CoreMemberEquityPayment.make_proxy(model.MemberEquityPayment, '_corepos', 'corepos_transaction_number')
|
||||||
|
CoreMemberEquityPayment.make_proxy(model.MemberEquityPayment, '_corepos', 'corepos_transaction_id')
|
||||||
|
CoreMemberEquityPayment.make_proxy(model.MemberEquityPayment, '_corepos', 'corepos_department_number')
|
||||||
|
|
|
@ -835,16 +835,7 @@ class MemberImporter(FromCOREPOSAPI, corepos_importing.model.MemberImporter):
|
||||||
def normalize_host_object(self, member):
|
def normalize_host_object(self, member):
|
||||||
card_number = member['cardNo']
|
card_number = member['cardNo']
|
||||||
customer = self.get_customer_by_number(card_number)
|
customer = self.get_customer_by_number(card_number)
|
||||||
if not customer:
|
person = self.app.get_person(customer) if customer else None
|
||||||
log.warning("Rattail customer not found for cardNo %s: %s",
|
|
||||||
card_number, member)
|
|
||||||
return
|
|
||||||
|
|
||||||
person = self.app.get_person(customer)
|
|
||||||
if not person:
|
|
||||||
log.warning("Rattail person not found for cardNo %s: %s",
|
|
||||||
card_number, member)
|
|
||||||
return
|
|
||||||
|
|
||||||
# TODO: at first i was *skipping* non-member status records,
|
# TODO: at first i was *skipping* non-member status records,
|
||||||
# but since CORE sort of assumes all customers are members,
|
# but since CORE sort of assumes all customers are members,
|
||||||
|
@ -883,8 +874,8 @@ class MemberImporter(FromCOREPOSAPI, corepos_importing.model.MemberImporter):
|
||||||
return {
|
return {
|
||||||
'number': card_number,
|
'number': card_number,
|
||||||
'corepos_account_id': int(member['customerAccountID']),
|
'corepos_account_id': int(member['customerAccountID']),
|
||||||
'customer_uuid': customer.uuid,
|
'customer_uuid': customer.uuid if customer else None,
|
||||||
'person_uuid': person.uuid,
|
'person_uuid': person.uuid if person else None,
|
||||||
'membership_type_number': typeno,
|
'membership_type_number': typeno,
|
||||||
'joined': joined,
|
'joined': joined,
|
||||||
'withdrew': withdrew,
|
'withdrew': withdrew,
|
||||||
|
|
|
@ -25,19 +25,27 @@ CORE POS (DB) -> Rattail data importing
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import decimal
|
import decimal
|
||||||
|
import logging
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
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
|
||||||
|
from corepos.db.office_trans import model as coretrans, Session as CoreTransSession
|
||||||
|
|
||||||
from rattail import importing
|
from rattail import importing
|
||||||
from rattail.gpc import GPC
|
from rattail.gpc import GPC
|
||||||
from rattail_corepos import importing as corepos_importing
|
from rattail_corepos import importing as corepos_importing
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class FromCOREPOSToRattail(importing.FromSQLAlchemyHandler, importing.ToRattailHandler):
|
class FromCOREPOSToRattail(importing.FromSQLAlchemyHandler, importing.ToRattailHandler):
|
||||||
"""
|
"""
|
||||||
Import handler for data coming from a CORE POS database.
|
Import handler for data coming from a CORE POS database.
|
||||||
"""
|
"""
|
||||||
|
# TODO: these should be changed, it now allows for "trans" DB too..
|
||||||
generic_host_title = 'CORE Office (DB "op")'
|
generic_host_title = 'CORE Office (DB "op")'
|
||||||
host_key = 'corepos_db_office_op'
|
host_key = 'corepos_db_office_op'
|
||||||
corepos_dbkey = 'default'
|
corepos_dbkey = 'default'
|
||||||
|
@ -47,6 +55,12 @@ class FromCOREPOSToRattail(importing.FromSQLAlchemyHandler, importing.ToRattailH
|
||||||
return "CORE-POS (DB/{})".format(self.corepos_dbkey)
|
return "CORE-POS (DB/{})".format(self.corepos_dbkey)
|
||||||
|
|
||||||
def make_host_session(self):
|
def make_host_session(self):
|
||||||
|
|
||||||
|
# session type depends on the --corepos-dbtype arg
|
||||||
|
if self.corepos_dbtype == 'office_trans':
|
||||||
|
return CoreTransSession(bind=self.config.coretrans_engines[self.corepos_dbkey])
|
||||||
|
|
||||||
|
# assume office_op by default
|
||||||
return CoreSession(bind=self.config.corepos_engines[self.corepos_dbkey])
|
return CoreSession(bind=self.config.corepos_engines[self.corepos_dbkey])
|
||||||
|
|
||||||
def get_importers(self):
|
def get_importers(self):
|
||||||
|
@ -55,8 +69,17 @@ class FromCOREPOSToRattail(importing.FromSQLAlchemyHandler, importing.ToRattailH
|
||||||
importers['Department'] = DepartmentImporter
|
importers['Department'] = DepartmentImporter
|
||||||
importers['Subdepartment'] = SubdepartmentImporter
|
importers['Subdepartment'] = SubdepartmentImporter
|
||||||
importers['Product'] = ProductImporter
|
importers['Product'] = ProductImporter
|
||||||
|
importers['MemberEquityPayment'] = MemberEquityPaymentImporter
|
||||||
return importers
|
return importers
|
||||||
|
|
||||||
|
def get_default_keys(self):
|
||||||
|
keys = super().get_default_keys()
|
||||||
|
|
||||||
|
if 'MemberEquityPayment' in keys:
|
||||||
|
keys.remove('MemberEquityPayment')
|
||||||
|
|
||||||
|
return keys
|
||||||
|
|
||||||
|
|
||||||
class FromCOREPOS(importing.FromSQLAlchemy):
|
class FromCOREPOS(importing.FromSQLAlchemy):
|
||||||
"""
|
"""
|
||||||
|
@ -228,3 +251,84 @@ class ProductImporter(FromCOREPOS, corepos_importing.model.ProductImporter):
|
||||||
})
|
})
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
class MemberEquityPaymentImporter(FromCOREPOS, corepos_importing.model.MemberEquityPaymentImporter):
|
||||||
|
"""
|
||||||
|
Imports equity payment data from CORE-POS
|
||||||
|
"""
|
||||||
|
host_model_class = coretrans.StockPurchase
|
||||||
|
# TODO: this is composite key for StockPurchase, but may need to change?
|
||||||
|
key = ('member_uuid', 'received', 'transaction_identifier')
|
||||||
|
supported_fields = [
|
||||||
|
'member_uuid',
|
||||||
|
'amount',
|
||||||
|
'received',
|
||||||
|
'transaction_identifier',
|
||||||
|
'corepos_card_number',
|
||||||
|
'corepos_transaction_number',
|
||||||
|
'corepos_transaction_id',
|
||||||
|
'corepos_department_number',
|
||||||
|
]
|
||||||
|
|
||||||
|
def setup(self):
|
||||||
|
super().setup()
|
||||||
|
model = self.model
|
||||||
|
|
||||||
|
query = self.session.query(model.Member)\
|
||||||
|
.join(model.Customer)\
|
||||||
|
.join(model.CoreCustomer)\
|
||||||
|
.options(orm.joinedload(model.Member.customer)\
|
||||||
|
.joinedload(model.Customer._corepos))
|
||||||
|
key = lambda member, normal: member.customer.corepos_card_number
|
||||||
|
self.members_by_card_number = self.cache_model(model.Member,
|
||||||
|
query=query,
|
||||||
|
key=key)
|
||||||
|
|
||||||
|
def get_member(self, card_number):
|
||||||
|
if hasattr(self, 'members_by_card_number'):
|
||||||
|
return self.members_by_card_number.get(card_number)
|
||||||
|
|
||||||
|
model = self.model
|
||||||
|
try:
|
||||||
|
return self.session.query(model.Member)\
|
||||||
|
.join(model.Customer)\
|
||||||
|
.join(model.CoreCustomer)\
|
||||||
|
.filter(model.CoreCustomer.corepos_card_number == card_number)\
|
||||||
|
.one()
|
||||||
|
except orm.exc.NoResultFound:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def normalize_host_object(self, stock_purchase):
|
||||||
|
|
||||||
|
card_number = stock_purchase.card_number
|
||||||
|
member = self.get_member(card_number)
|
||||||
|
if not member:
|
||||||
|
log.warning("member not found for card number %s: %s",
|
||||||
|
card_number, stock_purchase)
|
||||||
|
return
|
||||||
|
|
||||||
|
received = stock_purchase.datetime
|
||||||
|
if received:
|
||||||
|
received = self.app.localtime(received)
|
||||||
|
received = self.app.make_utc(received)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'member_uuid': member.uuid,
|
||||||
|
'amount': stock_purchase.amount,
|
||||||
|
'received': received,
|
||||||
|
'transaction_identifier': stock_purchase.transaction_number,
|
||||||
|
'corepos_card_number': stock_purchase.card_number,
|
||||||
|
'corepos_transaction_number': stock_purchase.transaction_number,
|
||||||
|
'corepos_transaction_id': stock_purchase.transaction_id,
|
||||||
|
'corepos_department_number': stock_purchase.department_number,
|
||||||
|
}
|
||||||
|
|
||||||
|
def create_object(self, key, host_data):
|
||||||
|
payment = super().create_object(key, host_data)
|
||||||
|
if payment:
|
||||||
|
|
||||||
|
# track where each payment comes from!
|
||||||
|
payment.source = 'corepos'
|
||||||
|
|
||||||
|
return payment
|
||||||
|
|
|
@ -82,6 +82,18 @@ class MemberImporter(importing.model.MemberImporter):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class MemberEquityPaymentImporter(importing.model.MemberEquityPaymentImporter):
|
||||||
|
|
||||||
|
extensions = {
|
||||||
|
'_corepos': [
|
||||||
|
'corepos_card_number',
|
||||||
|
'corepos_transaction_number',
|
||||||
|
'corepos_transaction_id',
|
||||||
|
'corepos_department_number',
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class StoreImporter(importing.model.StoreImporter):
|
class StoreImporter(importing.model.StoreImporter):
|
||||||
|
|
||||||
extensions = {
|
extensions = {
|
||||||
|
|
|
@ -37,6 +37,7 @@ class CoreposVersionMixin(object):
|
||||||
importers['CoreCustomer'] = CoreCustomerImporter
|
importers['CoreCustomer'] = CoreCustomerImporter
|
||||||
importers['CoreCustomerShopper'] = CoreCustomerShopperImporter
|
importers['CoreCustomerShopper'] = CoreCustomerShopperImporter
|
||||||
importers['CoreMember'] = CoreMemberImporter
|
importers['CoreMember'] = CoreMemberImporter
|
||||||
|
importers['CoreMemberEquityPayment'] = CoreMemberEquityPaymentImporter
|
||||||
importers['CoreStore'] = CoreStoreImporter
|
importers['CoreStore'] = CoreStoreImporter
|
||||||
importers['CoreDepartment'] = CoreDepartmentImporter
|
importers['CoreDepartment'] = CoreDepartmentImporter
|
||||||
importers['CoreSubdepartment'] = CoreSubdepartmentImporter
|
importers['CoreSubdepartment'] = CoreSubdepartmentImporter
|
||||||
|
@ -76,6 +77,14 @@ class CoreMemberImporter(base.VersionImporter):
|
||||||
return model.CoreMember
|
return model.CoreMember
|
||||||
|
|
||||||
|
|
||||||
|
class CoreMemberEquityPaymentImporter(base.VersionImporter):
|
||||||
|
|
||||||
|
@property
|
||||||
|
def host_model_class(self):
|
||||||
|
model = self.config.get_model()
|
||||||
|
return model.CoreMemberEquityPayment
|
||||||
|
|
||||||
|
|
||||||
class CoreStoreImporter(base.VersionImporter):
|
class CoreStoreImporter(base.VersionImporter):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
Loading…
Reference in a new issue