Add basic transaction importer for Square -> CORE-POS
This commit is contained in:
parent
810d4726c3
commit
4193503719
|
@ -54,3 +54,12 @@ class ImportCOREPOS(commands.ImportSubcommand):
|
||||||
if 'args' in kwargs:
|
if 'args' in kwargs:
|
||||||
kwargs['corepos_dbkey'] = kwargs['args'].corepos_dbkey
|
kwargs['corepos_dbkey'] = kwargs['args'].corepos_dbkey
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
|
|
||||||
|
class CoreImportSquare(commands.ImportFromCSV):
|
||||||
|
"""
|
||||||
|
Import transaction data from Square into CORE
|
||||||
|
"""
|
||||||
|
name = 'corepos-import-square'
|
||||||
|
description = __doc__.strip()
|
||||||
|
handler_spec = 'rattail_corepos.corepos.importing.square:FromSquareToCoreTrans'
|
||||||
|
|
|
@ -37,9 +37,15 @@ class RattailCOREPOSExtension(ConfigExtension):
|
||||||
key = 'rattail-corepos'
|
key = 'rattail-corepos'
|
||||||
|
|
||||||
def configure(self, config):
|
def configure(self, config):
|
||||||
from rattail_corepos.db import Session
|
from corepos.db import Session as CoreSession
|
||||||
|
from corepos.trans.db import Session as CoreTransSession
|
||||||
|
|
||||||
engines = get_engines(config, section='rattail_corepos.db')
|
engines = get_engines(config, section='rattail_corepos.db')
|
||||||
config.corepos_engines = engines
|
config.corepos_engines = engines
|
||||||
config.corepos_engine = engines.get('default')
|
config.corepos_engine = engines.get('default')
|
||||||
Session.configure(bind=config.corepos_engine)
|
CoreSession.configure(bind=config.corepos_engine)
|
||||||
|
|
||||||
|
engines = get_engines(config, section='rattail_coretrans.db')
|
||||||
|
config.coretrans_engines = engines
|
||||||
|
config.coretrans_engine = engines.get('default')
|
||||||
|
CoreTransSession.configure(bind=config.coretrans_engine)
|
||||||
|
|
0
rattail_corepos/corepos/__init__.py
Normal file
0
rattail_corepos/corepos/__init__.py
Normal file
8
rattail_corepos/corepos/importing/__init__.py
Normal file
8
rattail_corepos/corepos/importing/__init__.py
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# -*- coding: utf-8; -*-
|
||||||
|
"""
|
||||||
|
Importing data into CORE-POS
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import unicode_literals, absolute_import
|
||||||
|
|
||||||
|
from . import model
|
33
rattail_corepos/corepos/importing/model.py
Normal file
33
rattail_corepos/corepos/importing/model.py
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
# -*- coding: utf-8; -*-
|
||||||
|
"""
|
||||||
|
CORE-POS model importers
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import unicode_literals, absolute_import
|
||||||
|
|
||||||
|
from rattail import importing
|
||||||
|
|
||||||
|
from corepos.db import model as corepos
|
||||||
|
from corepos.trans.db import model as coretrans
|
||||||
|
|
||||||
|
|
||||||
|
class ToCore(importing.ToSQLAlchemy):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ToCoreTrans(importing.ToSQLAlchemy):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class CustomerImporter(ToCore):
|
||||||
|
"""
|
||||||
|
CORE-POS customer data importer.
|
||||||
|
"""
|
||||||
|
model_class = corepos.Customer
|
||||||
|
|
||||||
|
|
||||||
|
class TransactionDetailImporter(ToCoreTrans):
|
||||||
|
"""
|
||||||
|
CORE-POS transaction data importer.
|
||||||
|
"""
|
||||||
|
model_class = coretrans.TransactionDetail
|
174
rattail_corepos/corepos/importing/square.py
Normal file
174
rattail_corepos/corepos/importing/square.py
Normal file
|
@ -0,0 +1,174 @@
|
||||||
|
# -*- coding: utf-8; -*-
|
||||||
|
"""
|
||||||
|
Square -> CORE-POS data importing
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import unicode_literals, absolute_import
|
||||||
|
|
||||||
|
import re
|
||||||
|
import datetime
|
||||||
|
import decimal
|
||||||
|
|
||||||
|
import six
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
from corepos.trans.db import Session as CoreTransSession, model as coretrans
|
||||||
|
|
||||||
|
from rattail import importing
|
||||||
|
from rattail.util import OrderedDict
|
||||||
|
from rattail_corepos.corepos import importing as corepos_importing
|
||||||
|
|
||||||
|
|
||||||
|
class FromSquareToCoreTrans(importing.ToSQLAlchemyHandler):
|
||||||
|
"""
|
||||||
|
Square -> CORE-POS import handler.
|
||||||
|
"""
|
||||||
|
host_title = "Square"
|
||||||
|
local_title = "CORE-POS"
|
||||||
|
|
||||||
|
def make_session(self):
|
||||||
|
return CoreTransSession()
|
||||||
|
|
||||||
|
def get_importers(self):
|
||||||
|
importers = OrderedDict()
|
||||||
|
importers['TransactionDetail'] = TransactionDetailImporter
|
||||||
|
return importers
|
||||||
|
|
||||||
|
|
||||||
|
class FromSquare(importing.FromCSV):
|
||||||
|
"""
|
||||||
|
Base class for Square -> CORE-POS importers.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class TransactionDetailImporter(FromSquare, corepos_importing.model.TransactionDetailImporter):
|
||||||
|
"""
|
||||||
|
Transaction detail importer.
|
||||||
|
"""
|
||||||
|
key = 'store_row_id'
|
||||||
|
supported_fields = [
|
||||||
|
'store_row_id',
|
||||||
|
'date_time',
|
||||||
|
'card_number',
|
||||||
|
'upc',
|
||||||
|
'description',
|
||||||
|
'quantity',
|
||||||
|
'unit_price',
|
||||||
|
'discount',
|
||||||
|
'tax',
|
||||||
|
'total',
|
||||||
|
]
|
||||||
|
|
||||||
|
def setup(self):
|
||||||
|
super(TransactionDetailImporter, self).setup()
|
||||||
|
|
||||||
|
# cache existing transactions by ID
|
||||||
|
self.transaction_details = self.cache_model(coretrans.TransactionDetail,
|
||||||
|
key=self.transaction_detail_key)
|
||||||
|
|
||||||
|
# keep track of new IDs
|
||||||
|
self.new_ids = {}
|
||||||
|
self.last_new_id = self.get_last_new_id()
|
||||||
|
|
||||||
|
def transaction_detail_key(self, detail, normal):
|
||||||
|
return (
|
||||||
|
detail.store_id,
|
||||||
|
detail.register_number,
|
||||||
|
detail.date_time,
|
||||||
|
detail.upc,
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_last_new_id(self):
|
||||||
|
# TODO: pretty sure there is a better way to do this...
|
||||||
|
return self.session.query(sa.func.max(coretrans.TransactionDetail.store_row_id))\
|
||||||
|
.scalar() or 0
|
||||||
|
|
||||||
|
currency_pattern = re.compile(r'^\$(?P<amount>\d+\.\d\d)$')
|
||||||
|
currency_pattern_negative = re.compile(r'^\(\$(?P<amount>\d+\.\d\d)\)$')
|
||||||
|
|
||||||
|
def parse_currency(self, value):
|
||||||
|
value = (value or '').strip() or None
|
||||||
|
if value:
|
||||||
|
|
||||||
|
# first check for positive amount
|
||||||
|
match = self.currency_pattern.match(value)
|
||||||
|
if match:
|
||||||
|
return float(match.group('amount'))
|
||||||
|
|
||||||
|
# okay then, check for negative amount
|
||||||
|
match = self.currency_pattern_negative.match(value)
|
||||||
|
if match:
|
||||||
|
return 0 - float(match.group('amount'))
|
||||||
|
|
||||||
|
def normalize_host_object(self, csvrow):
|
||||||
|
|
||||||
|
# date_time
|
||||||
|
date = datetime.datetime.strptime(csvrow['Date'], '%m/%d/%Y').date()
|
||||||
|
time = datetime.datetime.strptime(csvrow['Time'], '%H:%M:%S').time()
|
||||||
|
date_time = datetime.datetime.combine(date, time)
|
||||||
|
|
||||||
|
# upc
|
||||||
|
upc = csvrow['SKU']
|
||||||
|
|
||||||
|
# store_row_id
|
||||||
|
key = (
|
||||||
|
0, # store_id
|
||||||
|
None, # register_number
|
||||||
|
date_time,
|
||||||
|
upc,
|
||||||
|
)
|
||||||
|
if key in self.transaction_details:
|
||||||
|
store_row_id = self.transaction_details[key].store_row_id
|
||||||
|
else:
|
||||||
|
store_row_id = self.last_new_id + 1
|
||||||
|
self.new_ids[store_row_id] = csvrow
|
||||||
|
self.last_new_id = store_row_id
|
||||||
|
|
||||||
|
# card_number
|
||||||
|
card_number = csvrow['Customer Reference ID'] or None
|
||||||
|
if card_number:
|
||||||
|
card_number = int(card_number)
|
||||||
|
|
||||||
|
# quantity
|
||||||
|
quantity = float(csvrow['Qty'])
|
||||||
|
|
||||||
|
# unit_price
|
||||||
|
unit_price = self.parse_currency(csvrow['Gross Sales'])
|
||||||
|
if unit_price is not None:
|
||||||
|
unit_price /= quantity
|
||||||
|
unit_price = decimal.Decimal('{:0.2f}'.format(unit_price))
|
||||||
|
elif csvrow['Gross Sales']:
|
||||||
|
log.warning("cannot parse 'unit_price' from: %s", csvrow['Gross Sales'])
|
||||||
|
|
||||||
|
# discount
|
||||||
|
discount = self.parse_currency(csvrow['Discounts'])
|
||||||
|
if discount is not None:
|
||||||
|
discount = decimal.Decimal('{:0.2f}'.format(discount))
|
||||||
|
elif csvrow['Discounts']:
|
||||||
|
log.warning("cannot parse 'discount' from: %s", csvrow['Discounts'])
|
||||||
|
|
||||||
|
# tax
|
||||||
|
tax = self.parse_currency(csvrow['Tax'])
|
||||||
|
if csvrow['Tax'] and tax is None:
|
||||||
|
log.warning("cannot parse 'tax' from: %s", csvrow['Tax'])
|
||||||
|
tax = bool(tax)
|
||||||
|
|
||||||
|
# total
|
||||||
|
total = self.parse_currency(csvrow['Net Sales'])
|
||||||
|
if total is not None:
|
||||||
|
total = decimal.Decimal('{:0.2f}'.format(total))
|
||||||
|
elif csvrow['Net Sales']:
|
||||||
|
log.warning("cannot parse 'total' from: %s", csvrow['Net Sales'])
|
||||||
|
|
||||||
|
return {
|
||||||
|
'store_row_id': store_row_id,
|
||||||
|
'date_time': date_time,
|
||||||
|
'card_number': card_number,
|
||||||
|
'upc': upc,
|
||||||
|
'description': csvrow['Item'],
|
||||||
|
'quantity': quantity,
|
||||||
|
'unit_price': unit_price,
|
||||||
|
'discount': discount,
|
||||||
|
'tax': tax,
|
||||||
|
'total': total,
|
||||||
|
}
|
Loading…
Reference in a new issue