diff --git a/rattail_corepos/config.py b/rattail_corepos/config.py index 1e3e9e9..1d9994b 100644 --- a/rattail_corepos/config.py +++ b/rattail_corepos/config.py @@ -2,7 +2,7 @@ ################################################################################ # # Rattail -- Retail Software Framework -# Copyright © 2010-2020 Lance Edgar +# Copyright © 2010-2021 Lance Edgar # # This file is part of Rattail. # @@ -35,18 +35,33 @@ class RattailCOREPOSExtension(ConfigExtension): key = 'rattail-corepos' def configure(self, config): - from corepos.db.office_op import Session as CoreSession - from corepos.db.office_trans import Session as CoreTransSession + # office_op + from corepos.db.office_op import Session engines = get_engines(config, section='corepos.db.office_op') + config.core_office_op_engines = engines + config.core_office_op_engine = engines.get('default') + Session.configure(bind=config.core_office_op_engine) + # TODO: deprecate / remove these next 2 lines config.corepos_engines = engines config.corepos_engine = engines.get('default') - CoreSession.configure(bind=config.corepos_engine) + # office_trans + from corepos.db.office_trans import Session engines = get_engines(config, section='corepos.db.office_trans') + config.core_office_trans_engines = engines + config.core_office_trans_engine = engines.get('default') + Session.configure(bind=config.core_office_trans_engine) + # TODO: deprecate / remove these next 2 lines config.coretrans_engines = engines config.coretrans_engine = engines.get('default') - CoreTransSession.configure(bind=config.coretrans_engine) + + # lane_op + from corepos.db.lane_op import Session + engines = get_engines(config, section='corepos.db.lane_op') + config.core_lane_op_engines = engines + config.core_lane_op_engine = engines.get('default') + Session.configure(bind=config.core_lane_op_engine) def core_office_url(config, require=False, **kwargs): diff --git a/rattail_corepos/corepos/common/__init__.py b/rattail_corepos/corepos/common/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/rattail_corepos/corepos/common/importing.py b/rattail_corepos/corepos/common/importing.py new file mode 100644 index 0000000..a8b3ee2 --- /dev/null +++ b/rattail_corepos/corepos/common/importing.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8; -*- +################################################################################ +# +# Rattail -- Retail Software Framework +# Copyright © 2010-2021 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 . +# +################################################################################ +""" +Common importing logic for CORE-POS +""" + +from rattail import importing + + +class ToCore(importing.ToSQLAlchemy): + """ + Base class for all CORE model importers; i.e. anything which uses + a CORE DB for the local side. + """ + + def create_object(self, key, host_data): + + # NOTE! some tables in CORE DB may be using the MyISAM storage engine, + # which means it is *not* transaction-safe and therefore we cannot rely + # on "rollback" if in dry-run mode! in other words we better not touch + # the record at all, for dry run + if self.dry_run: + return host_data + + return super(ToCore, self).create_object(key, host_data) + + def update_object(self, obj, host_data, **kwargs): + + # NOTE! some tables in CORE DB may be using the MyISAM storage engine, + # which means it is *not* transaction-safe and therefore we cannot rely + # on "rollback" if in dry-run mode! in other words we better not touch + # the record at all, for dry run + if self.dry_run: + return obj + + return super(ToCore, self).update_object(obj, host_data, **kwargs) + + def delete_object(self, obj): + + # NOTE! some tables in CORE DB may be using the MyISAM storage engine, + # which means it is *not* transaction-safe and therefore we cannot rely + # on "rollback" if in dry-run mode! in other words we better not touch + # the record at all, for dry run + if self.dry_run: + return True + + return super(ToCore, self).delete_object(obj) diff --git a/rattail_corepos/corepos/lane/__init__.py b/rattail_corepos/corepos/lane/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/rattail_corepos/corepos/lane/importing/__init__.py b/rattail_corepos/corepos/lane/importing/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/rattail_corepos/corepos/lane/importing/op/__init__.py b/rattail_corepos/corepos/lane/importing/op/__init__.py new file mode 100644 index 0000000..1dbd5fa --- /dev/null +++ b/rattail_corepos/corepos/lane/importing/op/__init__.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8; -*- +################################################################################ +# +# Rattail -- Retail Software Framework +# Copyright © 2010-2021 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 . +# +################################################################################ +""" +Importing "operational" data into CORE Lane +""" + +from . import model diff --git a/rattail_corepos/corepos/lane/importing/op/model.py b/rattail_corepos/corepos/lane/importing/op/model.py new file mode 100644 index 0000000..e9f4163 --- /dev/null +++ b/rattail_corepos/corepos/lane/importing/op/model.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8; -*- +################################################################################ +# +# Rattail -- Retail Software Framework +# Copyright © 2010-2021 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 . +# +################################################################################ +""" +"operational" model importers for CORE Lane + +.. warning:: + All classes in this module are "direct DB" importers, which will + write directly to MySQL. They are meant to be used in dry-run mode + only, and/or for sample data import to a dev system etc. They are + *NOT* meant for production use, as they will completely bypass any + CORE business rules logic which may exist. +""" + +from rattail import importing +from rattail_corepos.corepos.common.importing import ToCore + +from corepos.db.lane_op import model as corepos + + +class ProductImporter(ToCore): + model_class = corepos.Product + key = 'id' diff --git a/rattail_corepos/corepos/lane/importing/op/office.py b/rattail_corepos/corepos/lane/importing/op/office.py new file mode 100644 index 0000000..43756f3 --- /dev/null +++ b/rattail_corepos/corepos/lane/importing/op/office.py @@ -0,0 +1,150 @@ +# -*- coding: utf-8; -*- +################################################################################ +# +# Rattail -- Retail Software Framework +# Copyright © 2010-2021 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 . +# +################################################################################ +""" +CORE Office -> CORE Lane import +""" + +from corepos.db.office_op import Session as CoreOfficeSession, model as coreoffice +from corepos.db.lane_op import Session as CoreLaneSession + +from rattail import importing +from rattail.importing.handlers import FromSQLAlchemyHandler, ToSQLAlchemyHandler +from rattail.util import OrderedDict +from rattail_corepos.corepos.lane.importing import op as corepos_importing + + +# TODO: this surely belongs in some other/common place? (is not lane-specific) +class FromCoreOfficeHandler(FromSQLAlchemyHandler): + """ + Base class for import handlers which use CORE Office as the host. + """ + host_title = "CORE Office" + + def make_host_session(self): + return CoreOfficeSession() + + +# TODO: this surely belongs in some other/common place? (is not office-specific) +class ToCoreLaneHandler(ToSQLAlchemyHandler): + """ + Base class for import handlers which target CORE Lane on the local side. + """ + local_title = "CORE Lane" + + def make_session(self): + return CoreLaneSession() + + +class FromCoreOfficeToCoreLane(FromCoreOfficeHandler, ToCoreLaneHandler): + """ + Handler for CORE Office -> CORE Lane data export. + """ + direction = 'export' + + @property + def local_title(self): + return "CORE Lane ({})".format(self.dbkey) + + def make_session(self): + return CoreLaneSession(bind=self.config.core_lane_op_engines[self.dbkey]) + + def get_importers(self): + importers = OrderedDict() + importers['Product'] = ProductImporter + return importers + + +class FromCore(importing.FromSQLAlchemy): + """ + Base class for CORE Office -> CORE Lane data importers. + """ + + +class ProductImporter(FromCore, corepos_importing.model.ProductImporter): + host_model_class = coreoffice.Product + + # these fields are held in common, between Office and Lane tables + common_fields = [ + 'id', + 'upc', + 'description', + 'brand', + 'formatted_name', + 'normal_price', + 'price_method', + 'group_price', + 'quantity', + 'special_price', + 'special_price_method', + 'special_group_price', + 'special_quantity', + # 'special_limit', + 'start_date', + 'end_date', + 'department_number', + 'size', + 'tax_rate_id', + 'foodstamp', + 'scale', + 'scale_price', + 'mix_match_code', + # 'created', + # 'modified', + + # TODO: what to do about this 'replaces' thing? + # 'batchID'=>array('type'=>'TINYINT', 'replaces'=>'advertised'), + # batch_id = sa.Column('batchID', sa.SmallInteger(), nullable=True) + # advertised = sa.Column(sa.Boolean(), nullable=True) + + 'tare_weight', + 'discount', + 'discount_type', + 'line_item_discountable', + 'unit_of_measure', + 'wicable', + 'quantity_enforced', + 'id_enforced', + 'cost', + # 'special_cost', + # 'received_cost', + 'in_use', + 'flags', + 'subdepartment_number', + 'deposit', + 'local', + 'store_id', + 'default_vendor_id', + 'current_origin_id', + # 'auto_par', + # 'price_rule_id', + 'last_sold', + ] + + @property + def supported_fields(self): + return self.common_fields + + def normalize_host_object(self, product): + data = dict([(field, getattr(product, field)) + for field in self.common_fields]) + return data diff --git a/rattail_corepos/corepos/office/__init__.py b/rattail_corepos/corepos/office/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/rattail_corepos/corepos/office/commands.py b/rattail_corepos/corepos/office/commands.py new file mode 100644 index 0000000..d4995a7 --- /dev/null +++ b/rattail_corepos/corepos/office/commands.py @@ -0,0 +1,87 @@ +# -*- coding: utf-8; -*- +################################################################################ +# +# Rattail -- Retail Software Framework +# Copyright © 2010-2021 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 . +# +################################################################################ +""" +CORE Office commands +""" + +import sys + +from rattail import commands +from rattail_corepos import __version__ +from rattail.util import load_object + + +def main(*args): + """ + Entry point for 'core-office' commands + """ + if args: + args = list(args) + else: + args = sys.argv[1:] + + cmd = Command() + cmd.run(*args) + + +class Command(commands.Command): + """ + Primary command for CORE Office + """ + name = 'core-office' + version = __version__ + description = "core-office -- command line interface for CORE Office" + long_description = "" + + +class ExportLaneOp(commands.ImportSubcommand): + """ + Export "op" data from CORE Office to CORE Lane + """ + name = 'export-lane-op' + description = __doc__.strip() + default_handler_spec = 'rattail_corepos.corepos.lane.importing.op.office:FromCoreOfficeToCoreLane' + default_dbkey = 'default' + + def get_handler_factory(self, **kwargs): + if self.config: + spec = self.config.get('corepos.lane.importing', 'office.handler', + default=self.default_handler_spec) + else: + # just use default, for sake of cmd line help + spec = self.default_handler_spec + return load_object(spec) + + def add_parser_args(self, parser): + super(ExportLaneOp, self).add_parser_args(parser) + parser.add_argument('--dbkey', metavar='KEY', default=self.default_dbkey, + help="Config key for database engine to be used as the " + "\"target\" CORE Lane DB, i.e. where data will be " + " exported. This key must be defined in the " + " [rattail_corepos.db.lane_op] section of your " + "config file.") + + def get_handler_kwargs(self, **kwargs): + if 'args' in kwargs: + kwargs['dbkey'] = kwargs['args'].dbkey + return kwargs diff --git a/setup.py b/setup.py index 5dcc8b4..2d32337 100644 --- a/setup.py +++ b/setup.py @@ -98,6 +98,11 @@ setup( 'console_scripts': [ 'crepes = rattail_corepos.corepos.commands:main', + 'core-office = rattail_corepos.corepos.office.commands:main', + ], + + 'core_office.commands': [ + 'export-lane-op = rattail_corepos.corepos.office.commands:ExportLaneOp', ], 'crepes.commands': [