Initial commit

This commit is contained in:
Lance Edgar 2018-06-01 15:14:49 -05:00
commit 810d4726c3
17 changed files with 1414 additions and 0 deletions

View file

@ -0,0 +1,29 @@
# -*- coding: utf-8; -*-
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2018 Lance Edgar
#
# This file is part of Rattail.
#
# Rattail is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# Rattail. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
CORE POS Interface
"""
from __future__ import unicode_literals, absolute_import
from rattail_corepos._version import __version__

View file

@ -0,0 +1,3 @@
# -*- coding: utf-8; -*-
__version__ = '0.1.0'

View file

@ -0,0 +1,56 @@
# -*- coding: utf-8; -*-
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2018 Lance Edgar
#
# This file is part of Rattail.
#
# Rattail is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# Rattail. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
Rattail Commands
"""
from __future__ import unicode_literals, absolute_import
from rattail import commands
from rattail.util import load_object
class ImportCOREPOS(commands.ImportSubcommand):
"""
Import data from a CORE POS database
"""
name = 'import-corepos'
description = __doc__.strip()
def add_parser_args(self, parser):
super(ImportCOREPOS, self).add_parser_args(parser)
parser.add_argument('--corepos-dbkey', metavar='KEY', default='default',
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 "
"defined in the [rattail_corepos.db] section of your config file. "
"Defaults to 'default'.")
def get_handler_factory(self, **kwargs):
spec = self.config.get('rattail.importing', 'corepos.handler',
default='rattail_corepos.importing.corepos:FromCOREPOSToRattail')
return load_object(spec)
def get_handler_kwargs(self, **kwargs):
if 'args' in kwargs:
kwargs['corepos_dbkey'] = kwargs['args'].corepos_dbkey
return kwargs

45
rattail_corepos/config.py Normal file
View file

@ -0,0 +1,45 @@
# -*- coding: utf-8; -*-
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2018 Lance Edgar
#
# This file is part of Rattail.
#
# Rattail is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# Rattail. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
Rattail-COREPOS Config Extension
"""
from __future__ import unicode_literals, absolute_import
from rattail.config import ConfigExtension
from rattail.db.config import get_engines
class RattailCOREPOSExtension(ConfigExtension):
"""
Config extension for Rattail-COREPOS
"""
key = 'rattail-corepos'
def configure(self, config):
from rattail_corepos.db import Session
engines = get_engines(config, section='rattail_corepos.db')
config.corepos_engines = engines
config.corepos_engine = engines.get('default')
Session.configure(bind=config.corepos_engine)

View file

@ -0,0 +1,29 @@
# -*- coding: utf-8; -*-
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2018 Lance Edgar
#
# This file is part of Rattail.
#
# Rattail is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# Rattail. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
CORE POS Interface
"""
from __future__ import unicode_literals, absolute_import
from rattail_corepos._version import __version__

View file

@ -0,0 +1,102 @@
# -*- coding: utf-8; -*-
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2018 Lance Edgar
#
# This file is part of Rattail.
#
# Rattail is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# Rattail. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
DataSync for CORE POS
"""
from __future__ import unicode_literals, absolute_import
import six
from corepos.db import Session as CoreSession, model as corepos
from rattail.db import model
from rattail.datasync import DataSyncWatcher
class COREPOSProductWatcher(DataSyncWatcher):
"""
DataSync watcher for the CORE POS database.
"""
def get_changes(self, lastrun):
if not lastrun:
return
changes = []
session = CoreSession()
lastrun = self.localize_lastrun(session, lastrun)
# Department
departments = session.query(corepos.Department)\
.filter(corepos.Department.modified >= lastrun)\
.all()
if departments:
changes.extend([
(None,
model.DataSyncChange(
payload_type='Department',
payload_key=six.text_type(dept.dept_no)))
for dept in departments])
# TODO: subdepartment table doesn't have a modified flag?
# # Subdepartment
# subdepartments = session.query(corepos.Subdepartment)\
# .filter(corepos.Subdepartment.modified >= lastrun)\
# .all()
# if subdepartments:
# changes.extend([
# (None,
# model.DataSyncChange(
# payload_type='Subdepartment',
# payload_key=six.text_type(subdept.subdept_no)))
# for subdept in subdepartments])
# TODO: vendor table doesn't have a modified flag?
# # Vendor
# vendors = session.query(corepos.Vendor)\
# .filter(corepos.Vendor.modified >= lastrun)\
# .all()
# if vendors:
# changes.extend([
# (None,
# model.DataSyncChange(
# payload_type='Vendor',
# payload_key=six.text_type(vendor.vendorID)))
# for vendor in vendors])
# Product
products = session.query(corepos.Product)\
.filter(corepos.Product.modified >= lastrun)\
.all()
if products:
changes.extend([
(None,
model.DataSyncChange(
payload_type='Product',
payload_key=product.upc))
for product in products
if product.upc])
session.close()
return changes

View file

@ -0,0 +1,74 @@
# -*- coding: utf-8; -*-
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2018 Lance Edgar
#
# This file is part of Rattail.
#
# Rattail is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# Rattail. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
DataSync for Milo
"""
from __future__ import unicode_literals, absolute_import
from sqlalchemy.orm.exc import NoResultFound
from rattail.datasync import NewDataSyncImportConsumer
from corepos.db import Session as CoreSession, model as corepos
class FromCOREPOSToRattailBase(NewDataSyncImportConsumer):
"""
Base class for CORE POS -> Rattail data sync consumers.
"""
handler_spec = 'rattail_corepos.importing.corepos:FromCOREPOSToRattail'
def begin_transaction(self):
self.corepos_session = CoreSession()
def rollback_transaction(self):
self.corepos_session.rollback()
self.corepos_session.close()
def commit_transaction(self):
# always rollback here, we don't want any accidents in CORE POS
self.corepos_session.rollback()
self.corepos_session.close()
class FromCOREPOSToRattailProducts(FromCOREPOSToRattailBase):
"""
Handles CORE POS -> Rattail sync for product data.
"""
def get_host_object(self, session, change):
if change.payload_type == 'Product':
try:
return self.corepos_session.query(corepos.Product)\
.filter(corepos.Product.upc == change.payload_key)\
.one()
except NoResultFound:
pass
else:
# try to fetch CORE POS object via typical method
Model = getattr(corepos, change.payload_type)
return self.corepos_session.query(Model)\
.get(int(change.payload_key))

View file

@ -0,0 +1,29 @@
# -*- coding: utf-8; -*-
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2018 Lance Edgar
#
# This file is part of Rattail.
#
# Rattail is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# Rattail. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
CORE POS Database Stuff
"""
from __future__ import unicode_literals, absolute_import
from corepos.db import Session

View file

View file

@ -0,0 +1,195 @@
# -*- coding: utf-8; -*-
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2018 Lance Edgar
#
# This file is part of Rattail.
#
# Rattail is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# Rattail. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
CORE POS -> Rattail data importing
"""
from __future__ import unicode_literals, absolute_import
import decimal
import six
from corepos.db import model as corepos
from corepos.db import Session as CoreSession
from rattail import importing
from rattail.gpc import GPC
from rattail.util import OrderedDict
class FromCOREPOSToRattail(importing.FromSQLAlchemyHandler, importing.ToRattailHandler):
"""
Import handler for data coming from a CORE POS database.
"""
corepos_dbkey = 'default'
@property
def host_title(self):
return "CORE POS ({})".format(self.corepos_dbkey)
def make_host_session(self):
return CoreSession(bind=self.config.corepos_engines[self.corepos_dbkey])
def get_importers(self):
importers = OrderedDict()
importers['Vendor'] = VendorImporter
importers['Department'] = DepartmentImporter
importers['Subdepartment'] = SubdepartmentImporter
importers['Product'] = ProductImporter
return importers
class FromCOREPOS(importing.FromSQLAlchemy):
"""
Base class for all CORE POS data importers.
"""
class VendorImporter(FromCOREPOS, importing.model.VendorImporter):
"""
Importer for vendor data from CORE POS.
"""
host_model_class = corepos.Vendor
key = 'id'
supported_fields = [
'id',
'name',
'abbreviation',
'special_discount',
'phone_number',
'fax_number',
'email_address',
]
def normalize_host_object(self, vendor):
special_discount = None
if vendor.discountRate is not None:
special_discount = decimal.Decimal('{:0.3f}'.format(vendor.discountRate))
return {
'id': six.text_type(vendor.vendorID),
'name': vendor.vendorName,
'abbreviation': vendor.vendorAbbreviation,
'special_discount': special_discount,
'phone_number': vendor.phone,
'fax_number': vendor.fax,
'email_address': vendor.email,
}
class DepartmentImporter(FromCOREPOS, importing.model.DepartmentImporter):
"""
Importer for department data from CORE POS.
"""
host_model_class = corepos.Department
key = 'number'
supported_fields = [
'number',
'name',
]
def normalize_host_object(self, department):
return {
'number': department.dept_no,
'name': department.dept_name,
}
class SubdepartmentImporter(FromCOREPOS, importing.model.SubdepartmentImporter):
"""
Importer for subdepartment data from CORE POS.
"""
host_model_class = corepos.Subdepartment
key = 'number'
supported_fields = [
'number',
'name',
'department_number',
]
def normalize_host_object(self, subdepartment):
return {
'number': subdepartment.subdept_no,
'name': subdepartment.subdept_name,
'department_number': subdepartment.dept_ID,
}
class ProductImporter(FromCOREPOS, importing.model.ProductImporter):
"""
Importer for product data from CORE POS.
"""
host_model_class = corepos.Product
key = 'upc'
supported_fields = [
'item_id',
'upc',
'brand_name',
'description',
'size',
'weighed',
'department_number',
'subdepartment_number',
'regular_price_price',
'regular_price_multiple',
'food_stampable',
'tax1',
]
def normalize_host_object(self, product):
try:
upc = GPC(product.upc, calc_check_digit='upc')
except (TypeError, ValueError):
log.debug("CORE POS product has invalid UPC: %s", product.upc)
if len(self.key) == 1 and self.key[0] == 'upc':
return
upc = None
price = None
if product.normal_price is not None:
price = decimal.Decimal('{:03f}'.format(product.normal_price))
size = (product.size or '').strip() or None
if size == '0': # TODO: this is only for sake of CORE sample data...
size = None
return {
'item_id': product.upc,
'upc': upc,
'brand_name': (product.brand or '').strip() or None,
'description': (product.description or '').strip(),
'size': size,
'department_number': product.dept_no or None,
'subdepartment_number': product.subdept or None,
'weighed': bool(product.scale),
'food_stampable': product.foodstamp,
'tax1': bool(product.tax), # TODO: is this right?
'regular_price_price': price,
'regular_price_multiple': 1 if price is not None else None,
}