From 29638c062c6c1df8e7d9fc4a0e61b6389036e548 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Mon, 7 Dec 2020 17:41:01 -0600 Subject: [PATCH 01/95] Remove "default" values from Product model definition i'm a bit torn about this. on the one hand i like there being some default values here, but realistically they didn't all make sense. also it could be said that default values amount to business logic and that should stay in CORE basically. but in the end, i needed these to go away in order to simplify generating some SQL INSERT statements. was using SQLAlchemy core and it kept insisting that all fields with a "default" defined should be part of the INSERT statement, but really i didn't want them to be. so they no longer have defaults. --- corepos/db/office_op/model.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/corepos/db/office_op/model.py b/corepos/db/office_op/model.py index 24a185e..6799fbe 100644 --- a/corepos/db/office_op/model.py +++ b/corepos/db/office_op/model.py @@ -481,7 +481,10 @@ class Product(Base): scale = sa.Column(sa.Boolean(), nullable=True) - scale_price = sa.Column('scaleprice', sa.Boolean(), nullable=True, default=False) + # TODO: yikes, did i just code this all wrong the first time? pretty sure + # this needs to change to a decimal column... + scale_price = sa.Column('scaleprice', sa.Boolean(), nullable=True) + # scale_price = sa.Column('scaleprice', sa.Numeric(precision=10, scale=2), nullable=True) mix_match_code = sa.Column('mixmatchcode', sa.String(length=13), nullable=True) @@ -505,11 +508,11 @@ class Product(Base): id_enforced = sa.Column('idEnforced', sa.SmallInteger(), nullable=True) - cost = sa.Column(sa.Float(), nullable=True, default=0) + cost = sa.Column(sa.Float(), nullable=True) in_use = sa.Column('inUse', sa.Boolean(), nullable=True) - flags = sa.Column('numflag', sa.Integer(), nullable=True, default=0) + flags = sa.Column('numflag', sa.Integer(), nullable=True) subdepartment_number = sa.Column('subdept', sa.SmallInteger(), nullable=True) subdepartment = orm.relationship( @@ -522,11 +525,11 @@ class Product(Base): deposit = sa.Column(sa.Float(), nullable=True) - local = sa.Column(sa.Integer(), nullable=True, default=0) + local = sa.Column(sa.Integer(), nullable=True) - store_id = sa.Column(sa.SmallInteger(), nullable=True, default=0) + store_id = sa.Column(sa.SmallInteger(), nullable=True) - default_vendor_id = sa.Column(sa.Integer(), nullable=True, default=0) + default_vendor_id = sa.Column(sa.Integer(), nullable=True) default_vendor = orm.relationship( Vendor, primaryjoin=Vendor.id == default_vendor_id, @@ -537,7 +540,7 @@ class Product(Base): # TODO: deprecate / remove this? vendor = orm.synonym('default_vendor') - current_origin_id = sa.Column(sa.Integer(), nullable=True, default=0) + current_origin_id = sa.Column(sa.Integer(), nullable=True) # TODO: some older DB's might not have this? guess we'll see last_sold = sa.Column(sa.DateTime(), nullable=True) From ff428c4635218c3acfd6f1e1df36186f04debd61 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Wed, 9 Dec 2020 13:09:58 -0600 Subject: [PATCH 02/95] Misc. tweaks to product-related schema, for sake of generating SQL e.g. from IFPS data --- corepos/db/office_op/model.py | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/corepos/db/office_op/model.py b/corepos/db/office_op/model.py index 6799fbe..ea53110 100644 --- a/corepos/db/office_op/model.py +++ b/corepos/db/office_op/model.py @@ -24,6 +24,7 @@ Data model for CORE POS "office_op" DB """ +import datetime import logging import sqlalchemy as sa @@ -37,6 +38,23 @@ log = logging.getLogger(__name__) Base = declarative_base() +class StringableDateTime(sa.TypeDecorator): + """ + Sort of a hack, to let us string-ify certain DateTime values when + generating "raw" SQL output. + + cf. https://docs.sqlalchemy.org/en/14/faq/sqlexpressions.html#rendering-bound-parameters-inline + """ + impl = sa.DateTime + + def process_literal_param(self, value, dialect): + if value is None: + return 'NULL' + if isinstance(value, datetime.datetime): + return "'{}'".format(value.strftime('%Y-%m-%d %H:%M:%S')) + raise NotImplementedError + + class Change(Base): """ Represents a changed (or deleted) record, which is pending synchronization @@ -135,11 +153,11 @@ class Department(Base): modified_by_id = sa.Column('modifiedby', sa.Integer(), nullable=True) - margin = sa.Column(sa.Float(), nullable=False, default=0) + margin = sa.Column(sa.Float(), nullable=False) - sales_code = sa.Column('salesCode', sa.Integer(), nullable=False, default=0) + sales_code = sa.Column('salesCode', sa.Integer(), nullable=False) - member_only = sa.Column('memberOnly', sa.SmallInteger(), nullable=False, default=0) + member_only = sa.Column('memberOnly', sa.SmallInteger(), nullable=False) def __str__(self): return self.name or '' @@ -488,6 +506,8 @@ class Product(Base): mix_match_code = sa.Column('mixmatchcode', sa.String(length=13), nullable=True) + created = sa.Column(StringableDateTime(), nullable=True) + modified = sa.Column(sa.DateTime(), nullable=True) # advertised = sa.Column(sa.Boolean(), nullable=True) @@ -649,7 +669,7 @@ class ProductUser(Base): enable_online = sa.Column('enableOnline', sa.Boolean(), nullable=True) - sold_out = sa.Column('soldOut', sa.Boolean(), nullable=True, default=False) + sold_out = sa.Column('soldOut', sa.Boolean(), nullable=True) # TODO: this was not in some older DBs # sign_count = sa.Column('signCount', sa.SmallInteger(), nullable=True, default=1) @@ -657,6 +677,9 @@ class ProductUser(Base): # TODO: this was not in some older DBs # narrow = sa.Column(sa.Boolean(), nullable=True, default=False) + def __str__(self): + return str(self.product or '') + class VendorItem(Base): """ From 52d959533146b957b9dd42cc1aed7abada10f9d3 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Thu, 31 Dec 2020 19:01:31 -0600 Subject: [PATCH 03/95] Fix `Department.see_id` field definition this is not a flag, but a minimum age requirement --- corepos/db/office_op/model.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/corepos/db/office_op/model.py b/corepos/db/office_op/model.py index ea53110..f5c415d 100644 --- a/corepos/db/office_op/model.py +++ b/corepos/db/office_op/model.py @@ -146,8 +146,7 @@ class Department(Base): discount = sa.Column('dept_discount', sa.Boolean(), nullable=True) - # TODO: probably should rename this attribute, but to what? - dept_see_id = sa.Column(sa.Boolean(), nullable=True) + see_id = sa.Column('dept_see_id', sa.Integer(), nullable=True) modified = sa.Column(sa.DateTime(), nullable=True) From 19d62b535f51212508f17f2a5c1223271d0dd57a Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Thu, 31 Dec 2020 19:25:33 -0600 Subject: [PATCH 04/95] Tweak that thing again... --- corepos/db/office_op/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/corepos/db/office_op/model.py b/corepos/db/office_op/model.py index f5c415d..6677e98 100644 --- a/corepos/db/office_op/model.py +++ b/corepos/db/office_op/model.py @@ -146,7 +146,7 @@ class Department(Base): discount = sa.Column('dept_discount', sa.Boolean(), nullable=True) - see_id = sa.Column('dept_see_id', sa.Integer(), nullable=True) + see_id = sa.Column('dept_see_id', sa.SmallInteger(), nullable=True) modified = sa.Column(sa.DateTime(), nullable=True) From d398e706c421dda069a1ffe64246c0ce0bcda656 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Wed, 13 Jan 2021 19:18:17 -0600 Subject: [PATCH 05/95] Add `MemberBarcode` to op model --- corepos/db/office_op/model.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/corepos/db/office_op/model.py b/corepos/db/office_op/model.py index 6677e98..a4da4fc 100644 --- a/corepos/db/office_op/model.py +++ b/corepos/db/office_op/model.py @@ -1179,6 +1179,21 @@ class MemberInfo(Base): Reference to the member to whom the date record applies. """)) + barcodes = orm.relationship( + 'MemberBarcode', + primaryjoin='MemberBarcode.card_number == MemberInfo.card_number', + foreign_keys='MemberBarcode.card_number', + order_by='MemberBarcode.upc', + # cascade='all, delete-orphan', + doc=""" + List of extra barcode records for the member. + """, + backref=orm.backref( + 'member_info', + doc=""" + Reference to the :class:`MemberInfo` record to which the barcode applies. + """)) + notes = orm.relationship( 'MemberNote', primaryjoin='MemberNote.card_number == MemberInfo.card_number', @@ -1284,6 +1299,21 @@ class MemberContact(Base): return str(self.preference) +class MemberBarcode(Base): + """ + Additional barcode for a member. + """ + __tablename__ = 'memberCards' + + card_number = sa.Column('card_no', sa.Integer(), nullable=False, + primary_key=True, autoincrement=False) + + upc = sa.Column(sa.String(length=13), nullable=False, primary_key=True) + + def __str__(self): + return self.upc or "" + + class MemberNote(Base): """ Additional notes for a member. From ee9451588ca8f5a9067eff5c49e13202633548cd Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Wed, 27 Jan 2021 22:19:38 -0600 Subject: [PATCH 06/95] Add basic support for Stores schema and API --- corepos/api.py | 23 +++++++++++++++++++++++ corepos/db/office_op/model.py | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/corepos/api.py b/corepos/api.py index abe2afc..3a8b624 100644 --- a/corepos/api.py +++ b/corepos/api.py @@ -166,6 +166,29 @@ class CoreWebAPI(object): if result: return result + def get_stores(self, **columns): + """ + Fetch some or all of Store records from CORE. + + :returns: A (potentially empty) list of store dict records. + + To fetch all stores:: + + api.get_stores() + + To fetch only stores named "Headquarters":: + + api.get_stores(description='Headquarters') + """ + params = { + 'entity': 'Stores', + 'submethod': 'get', + 'columns': columns, + } + response = self.post(params) + result = self.parse_response(response) + return [json.loads(rec) for rec in result] + def get_departments(self, **columns): """ Fetch some or all of Department records from CORE. diff --git a/corepos/db/office_op/model.py b/corepos/db/office_op/model.py index a4da4fc..0e8bf49 100644 --- a/corepos/db/office_op/model.py +++ b/corepos/db/office_op/model.py @@ -92,6 +92,41 @@ class Parameter(Base): return "{}-{} {}".format(self.store_id, self.lane_id, self.param_key) +class Store(Base): + """ + Represents a known store. + """ + __tablename__ = 'Stores' + + storeID = sa.Column(sa.Integer(), nullable=False, primary_key=True, autoincrement=True) + id = orm.synonym('storeID') + + description = sa.Column(sa.String(length=50), nullable=True) + + db_host = sa.Column('dbHost', sa.String(length=50), nullable=True) + + db_driver = sa.Column('dbDriver', sa.String(length=15), nullable=True) + + db_user = sa.Column('dbUser', sa.String(length=25), nullable=True) + + db_password = sa.Column('dbPassword', sa.String(length=25), nullable=True) + + trans_db = sa.Column('transDB', sa.String(length=20), nullable=True) + + op_db = sa.Column('opDB', sa.String(length=20), nullable=True) + + push = sa.Column(sa.Boolean(), nullable=True, default=True) + + pull = sa.Column(sa.Boolean(), nullable=True, default=True) + + has_own_items = sa.Column('hasOwnItems', sa.Boolean(), nullable=True, default=True) + + web_service_url = sa.Column('webServiceUrl', sa.String(length=255), nullable=True) + + def __str__(self): + return self.description or "" + + class SuperDepartment(Base): """ Represents a "super" (parent/child) department mapping. From 1757d0978175662c5d7b8c66ea8448075f8e06fc Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Mon, 1 Feb 2021 15:30:55 -0600 Subject: [PATCH 07/95] Add schema model for Purchase Orders --- corepos/db/office_op/model.py | 118 ++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/corepos/db/office_op/model.py b/corepos/db/office_op/model.py index 0e8bf49..8e24e05 100644 --- a/corepos/db/office_op/model.py +++ b/corepos/db/office_op/model.py @@ -1547,3 +1547,121 @@ class BatchItem(Base): def __str__(self): return self.upc or "" + + +class PurchaseOrder(Base): + """ + Represents a purchase order. + """ + __tablename__ = 'PurchaseOrder' + # TODO: would be simpler to declare these, but is it safe? + # __table_args__ = ( + # sa.ForeignKeyConstraint(['vendorID'], ['vendors.vendorID']), + # sa.ForeignKeyConstraint(['storeID'], ['Stores.storeID']), + # ) + + orderID = sa.Column(sa.Integer(), nullable=False, primary_key=True, autoincrement=True) + id = orm.synonym('orderID') + + vendor_id = sa.Column('vendorID', sa.Integer(), nullable=True) + vendor = orm.relationship( + Vendor, + primaryjoin=Vendor.id == vendor_id, + foreign_keys=[vendor_id]) + + store_id = sa.Column('storeID', sa.Integer(), nullable=True) + store = orm.relationship( + Store, + primaryjoin=Store.id == store_id, + foreign_keys=[store_id]) + + creation_date = sa.Column('creationDate', sa.DateTime(), nullable=True) + + placed = sa.Column(sa.Boolean(), nullable=True, default=False) + + placed_date = sa.Column('placedDate', sa.DateTime(), nullable=True) + + user_id = sa.Column('userID', sa.Integer(), nullable=True) + + vendor_order_id = sa.Column('vendorOrderID', sa.String(length=25), nullable=True) + + vendor_invoice_id = sa.Column('vendorInvoiceID', sa.String(length=25), nullable=True) + + standing_id = sa.Column('standingID', sa.Integer(), nullable=True) + + inventory_ignore = sa.Column('inventoryIgnore', sa.Boolean(), nullable=True, default=False) + + transfer_id = sa.Column('transferID', sa.Integer(), nullable=True) + + notes = association_proxy('notes', 'notes') + + def __str__(self): + return "#{} for {}".format(self.id, self.vendor or "??") + + +class PurchaseOrderItem(Base): + """ + Represents a line item in a purchase order. + """ + __tablename__ = 'PurchaseOrderItems' + __table_args__ = ( + sa.ForeignKeyConstraint(['orderID'], ['PurchaseOrder.orderID']), + ) + + order_id = sa.Column('orderID', sa.Integer(), nullable=False, + primary_key=True, autoincrement=False) + order = orm.relationship(PurchaseOrder, backref=orm.backref('items')) + + sku = sa.Column(sa.String(length=13), nullable=False, + primary_key=True) + + quantity = sa.Column(sa.Numeric(precision=10, scale=2), nullable=True) + + unit_cost = sa.Column('unitCost', sa.Numeric(precision=10, scale=4), nullable=True) + + case_size = sa.Column('caseSize', sa.Float(), nullable=True) + + received_date = sa.Column('receivedDate', sa.DateTime(), nullable=True) + + received_quantity = sa.Column('receivedQty', sa.Float(), nullable=True) + + received_total_cost = sa.Column('receivedTotalCost', sa.Numeric(precision=10, scale=4), nullable=True) + + unit_size = sa.Column('unitSize', sa.String(length=25), nullable=True) + + brand = sa.Column(sa.String(length=50), nullable=True) + + description = sa.Column(sa.String(length=50), nullable=True) + + internal_upc = sa.Column('internalUPC', sa.String(length=13), nullable=True) + + sales_code = sa.Column('salesCode', sa.Integer(), nullable=True) + + is_special_order = sa.Column('isSpecialOrder', sa.Boolean(), nullable=True, + default=False) + + # TODO: this probably is FK to e.g. User.id ? + received_by = sa.Column('receivedBy', sa.Integer(), nullable=True, + default=0) + + def __str__(self): + return self.description or "" + + +class PurchaseOrderNote(Base): + """ + Represents a note attached to a purchase order. + """ + __tablename__ = 'PurchaseOrderNotes' + __table_args__ = ( + sa.ForeignKeyConstraint(['orderID'], ['PurchaseOrder.orderID']), + ) + + order_id = sa.Column('orderID', sa.Integer(), nullable=False, + primary_key=True, autoincrement=False) + order = orm.relationship(PurchaseOrder, backref=orm.backref('notes')) + + notes = sa.Column(sa.Text(), nullable=True) + + def __str__(self): + return self.notes or "" From 76f743f3b86060edf8ed77b4ab9d8ca5e5daf7a7 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Tue, 9 Feb 2021 14:25:18 -0600 Subject: [PATCH 08/95] Add `set_vendor_item()` method for API client --- corepos/api.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/corepos/api.py b/corepos/api.py index 3a8b624..b6ee1ed 100644 --- a/corepos/api.py +++ b/corepos/api.py @@ -2,7 +2,7 @@ ################################################################################ # # pyCOREPOS -- Python Interface to CORE POS -# Copyright © 2018-2020 Lance Edgar +# Copyright © 2018-2021 Lance Edgar # # This file is part of pyCOREPOS. # @@ -487,3 +487,26 @@ class CoreWebAPI(object): if len(result) > 1: log.warning("CORE API returned %s VendorItem results", len(result)) return json.loads(result[0]) + + def set_vendor_item(self, sku, vendorID, **columns): + """ + Update an existing VendorItem record in CORE. + + :returns: Boolean indicating success of the operation. + + .. note:: + Currently this is being used to create a *new* product also. CORE's + ``vendorItems`` table does not use auto-increment for its PK, which + means we must provide one even when creating; therefore this method + may be used for that. + """ + columns['sku'] = sku + columns['vendorID'] = vendorID + params = { + 'entity': 'VendorItems', + 'submethod': 'set', + 'columns': columns, + } + response = self.post(params) + result = self.parse_response(response) + return json.loads(result) From 7cf4ec129545aac2dc2ab295af98706046443138 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Tue, 9 Feb 2021 16:11:50 -0600 Subject: [PATCH 09/95] Fetch single `vendorItems` record by `sku` instead of `upc` b/c that is what we must use as PK when updating the record etc. --- corepos/api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/corepos/api.py b/corepos/api.py index b6ee1ed..e7310a0 100644 --- a/corepos/api.py +++ b/corepos/api.py @@ -468,13 +468,13 @@ class CoreWebAPI(object): result = self.parse_response(response) return [json.loads(rec) for rec in result] - def get_vendor_item(self, upc, vendorID, **columns): + def get_vendor_item(self, sku, vendorID, **columns): """ Fetch an existing VendorItem record from CORE. :returns: Either a vendor item dict record, or ``None``. """ - columns['upc'] = upc + columns['sku'] = sku columns['vendorID'] = vendorID params = { 'entity': 'VendorItems', From b40fbf7cab0f01e06afe60f3879e96ea283b1898 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Mon, 15 Feb 2021 12:59:21 -0600 Subject: [PATCH 10/95] Add the `Product.complete_size` convenience attribute also define `str(VendorItem)` --- corepos/db/office_op/model.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/corepos/db/office_op/model.py b/corepos/db/office_op/model.py index 8e24e05..c7fa505 100644 --- a/corepos/db/office_op/model.py +++ b/corepos/db/office_op/model.py @@ -611,6 +611,18 @@ class Product(Base): fields = filter(bool, fields) return ' '.join(fields) + @property + def complete_size(self): + """ + Returns the "complete" size string for the product. This is based on + the :attr:`size` and :attr:`unit_of_measure` attributes. + """ + parts = [self.size, self.unit_of_measure] + parts = [(part or '').strip() + for part in parts] + parts = [part for part in parts if part] + return " ".join(parts).strip() + def __str__(self): return self.description or '' @@ -771,6 +783,9 @@ class VendorItem(Base): modified = sa.Column(sa.DateTime(), nullable=True) + def __str__(self): + return "{} from {}".format(self.sku, self.vendor) + class ScaleItem(Base): """ From d94189c40432efc881122c58cccd740fb57fbeaa Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Tue, 4 May 2021 20:10:14 -0500 Subject: [PATCH 11/95] Add FK constraint for ProductLikeCode.upc not sure why that wasn't already present? --- corepos/db/office_op/model.py | 1 + 1 file changed, 1 insertion(+) diff --git a/corepos/db/office_op/model.py b/corepos/db/office_op/model.py index c7fa505..2e25c34 100644 --- a/corepos/db/office_op/model.py +++ b/corepos/db/office_op/model.py @@ -633,6 +633,7 @@ class ProductLikeCode(Base): """ __tablename__ = 'upcLike' __table_args__ = ( + sa.ForeignKeyConstraint(['upc'], ['products.upc']), sa.ForeignKeyConstraint(['likeCode'], ['likeCodes.likeCode']), ) From 170f0a769aaf616c6a305ea01d1d5197a9c167a7 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Fri, 7 May 2021 11:12:44 -0500 Subject: [PATCH 12/95] Remove duplicated column name --- corepos/db/office_op/model.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/corepos/db/office_op/model.py b/corepos/db/office_op/model.py index 2e25c34..5e22e10 100644 --- a/corepos/db/office_op/model.py +++ b/corepos/db/office_op/model.py @@ -2,7 +2,7 @@ ################################################################################ # # pyCOREPOS -- Python Interface to CORE POS -# Copyright © 2018-2020 Lance Edgar +# Copyright © 2018-2021 Lance Edgar # # This file is part of pyCOREPOS. # @@ -815,8 +815,6 @@ class ScaleItem(Base): exception_price = sa.Column('exceptionprice', sa.Numeric(precision=10, scale=2), nullable=True) - exception_price = sa.Column('exceptionprice', sa.Numeric(precision=10, scale=2), nullable=True) - weight = sa.Column(sa.SmallInteger(), nullable=True, default=0) by_count = sa.Column('bycount', sa.Boolean(), nullable=True, default=False) From 0d3d007bd198684631de0540b86af4eb6a9e002d Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Fri, 11 Jun 2021 18:04:42 -0500 Subject: [PATCH 13/95] Update changelog --- CHANGELOG.md | 4 ++++ corepos/_version.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 93af3bf..ff62b28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to pyCOREPOS will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [0.1.2] - 2021-06-11 +### Changed +- Several more updates, mostly a "save point" release. + ## [0.1.1] - 2020-09-16 ### Added - A ton of updates, mostly a "save point" release. diff --git a/corepos/_version.py b/corepos/_version.py index 4984097..de58c20 100644 --- a/corepos/_version.py +++ b/corepos/_version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8; -*- -__version__ = '0.1.1' +__version__ = '0.1.2' From 178acdac31443cccbe43f6743d9c26f6902cb83a Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Wed, 21 Jul 2021 20:02:17 -0500 Subject: [PATCH 14/95] Add basic 'lane_op' DB schema just the 'products' table so far --- corepos/db/lane_op/__init__.py | 30 ++++++ corepos/db/lane_op/model.py | 169 +++++++++++++++++++++++++++++++++ 2 files changed, 199 insertions(+) create mode 100644 corepos/db/lane_op/__init__.py create mode 100644 corepos/db/lane_op/model.py diff --git a/corepos/db/lane_op/__init__.py b/corepos/db/lane_op/__init__.py new file mode 100644 index 0000000..884fe9f --- /dev/null +++ b/corepos/db/lane_op/__init__.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8; -*- +################################################################################ +# +# pyCOREPOS -- Python Interface to CORE POS +# Copyright © 2018-2021 Lance Edgar +# +# This file is part of pyCOREPOS. +# +# pyCOREPOS 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. +# +# pyCOREPOS 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 +# pyCOREPOS. If not, see . +# +################################################################################ +""" +"Lane Operational" Database Interface +""" + +from sqlalchemy import orm + + +Session = orm.sessionmaker() diff --git a/corepos/db/lane_op/model.py b/corepos/db/lane_op/model.py new file mode 100644 index 0000000..2faa87b --- /dev/null +++ b/corepos/db/lane_op/model.py @@ -0,0 +1,169 @@ +# -*- coding: utf-8; -*- +################################################################################ +# +# pyCOREPOS -- Python Interface to CORE POS +# Copyright © 2018-2021 Lance Edgar +# +# This file is part of pyCOREPOS. +# +# pyCOREPOS 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. +# +# pyCOREPOS 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 +# pyCOREPOS. If not, see . +# +################################################################################ +""" +Data model for CORE POS "lane_op" DB +""" + +import sqlalchemy as sa +# from sqlalchemy import orm +from sqlalchemy.ext.declarative import declarative_base + + +Base = declarative_base() + + +class Product(Base): + """ + Represents a product, purchased and/or sold by the organization. + """ + __tablename__ = 'products' + # __table_args__ = ( + # sa.ForeignKeyConstraint(['department'], ['departments.dept_no']), + # sa.ForeignKeyConstraint(['subdept'], ['subdepts.subdept_no']), + # sa.ForeignKeyConstraint(['tax'], ['taxrates.id']), + # ) + + id = sa.Column(sa.Integer(), nullable=False, + primary_key=True, autoincrement=True) + + upc = sa.Column(sa.String(length=13), nullable=True) + + description = sa.Column(sa.String(length=30), nullable=True) + + brand = sa.Column(sa.String(length=30), nullable=True) + + formatted_name = sa.Column(sa.String(length=30), nullable=True) + + normal_price = sa.Column(sa.Float(), nullable=True) + + price_method = sa.Column('pricemethod', sa.SmallInteger(), nullable=True) + + group_price = sa.Column('groupprice', sa.Float(), nullable=True) + + quantity = sa.Column(sa.SmallInteger(), nullable=True) + + special_price = sa.Column(sa.Float(), nullable=True) + + special_price_method = sa.Column('specialpricemethod', sa.SmallInteger(), nullable=True) + + special_group_price = sa.Column('specialgroupprice', sa.Float(), nullable=True) + + special_quantity = sa.Column('specialquantity', sa.SmallInteger(), nullable=True) + + special_limit = sa.Column(sa.SmallInteger(), nullable=True) + + start_date = sa.Column(sa.DateTime(), nullable=True) + + end_date = sa.Column(sa.DateTime(), nullable=True) + + department_number = sa.Column('department', sa.SmallInteger(), nullable=True) + # department = orm.relationship( + # Department, + # primaryjoin=Department.number == department_number, + # foreign_keys=[department_number], + # doc=""" + # Reference to the :class:`Department` to which the product belongs. + # """) + + size = sa.Column(sa.String(length=9), nullable=True) + + tax_rate_id = sa.Column('tax', sa.SmallInteger(), nullable=True) + # tax_rate = orm.relationship(TaxRate) + + foodstamp = sa.Column(sa.Boolean(), nullable=True) + + scale = sa.Column(sa.Boolean(), nullable=True) + + scale_price = sa.Column('scaleprice', sa.Float(), nullable=True) + + mix_match_code = sa.Column('mixmatchcode', sa.String(length=13), nullable=True) + + created = sa.Column(sa.DateTime(), nullable=True) + + modified = sa.Column(sa.DateTime(), nullable=True) + + # 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 = sa.Column('tareweight', sa.Float(), nullable=True) + + discount = sa.Column(sa.SmallInteger(), nullable=True) + + discount_type = sa.Column('discounttype', sa.SmallInteger(), nullable=True) + + line_item_discountable = sa.Column(sa.Boolean(), nullable=True) + + unit_of_measure = sa.Column('unitofmeasure', sa.String(length=15), nullable=True) + + wicable = sa.Column(sa.SmallInteger(), nullable=True) + + quantity_enforced = sa.Column('qttyEnforced', sa.Boolean(), nullable=True) + + id_enforced = sa.Column('idEnforced', sa.SmallInteger(), nullable=True) + + cost = sa.Column(sa.Float(), nullable=True) + + special_cost = sa.Column(sa.Float(), nullable=True) + + received_cost = sa.Column(sa.Float(), nullable=True) + + in_use = sa.Column('inUse', sa.Boolean(), nullable=True) + + flags = sa.Column('numflag', sa.Integer(), nullable=True) + + subdepartment_number = sa.Column('subdept', sa.SmallInteger(), nullable=True) + # subdepartment = orm.relationship( + # Subdepartment, + # primaryjoin=Subdepartment.number == subdepartment_number, + # foreign_keys=[subdepartment_number], + # doc=""" + # Reference to the :class:`Subdepartment` to which the product belongs. + # """) + + deposit = sa.Column(sa.Float(), nullable=True) + + local = sa.Column(sa.Integer(), nullable=True, + default=0) # TODO: do we want a default here? + + store_id = sa.Column(sa.SmallInteger(), nullable=True) + + default_vendor_id = sa.Column(sa.Integer(), nullable=True) + # default_vendor = orm.relationship( + # Vendor, + # primaryjoin=Vendor.id == default_vendor_id, + # foreign_keys=[default_vendor_id], + # doc=""" + # Reference to the default :class:`Vendor` from which the product is obtained. + # """) + + current_origin_id = sa.Column(sa.Integer(), nullable=True) + + auto_par = sa.Column(sa.Float(), nullable=True, + default=0) # TODO: do we want a default here? + + price_rule_id = sa.Column(sa.Integer(), nullable=True) + + # TODO: some older DB's might not have this? guess we'll see + last_sold = sa.Column(sa.DateTime(), nullable=True) From b4a07f9875c4a5f973ffa3f6b0dc7e5f1dcf0467 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Wed, 21 Jul 2021 20:17:56 -0500 Subject: [PATCH 15/95] Update changelog --- CHANGELOG.md | 4 ++++ corepos/_version.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff62b28..dcaf8dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to pyCOREPOS will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [0.1.3] - 2021-07-21 +### Changed +- Add basic 'lane_op' DB schema. + ## [0.1.2] - 2021-06-11 ### Changed - Several more updates, mostly a "save point" release. diff --git a/corepos/_version.py b/corepos/_version.py index de58c20..d60824a 100644 --- a/corepos/_version.py +++ b/corepos/_version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8; -*- -__version__ = '0.1.2' +__version__ = '0.1.3' From e4c46b3fa4c601f44e0455df5f0835571c8592ce Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Mon, 2 Aug 2021 08:56:05 -0500 Subject: [PATCH 16/95] Add schema for `TableSyncRules` --- corepos/db/office_op/model.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/corepos/db/office_op/model.py b/corepos/db/office_op/model.py index 5e22e10..182f3fe 100644 --- a/corepos/db/office_op/model.py +++ b/corepos/db/office_op/model.py @@ -92,6 +92,22 @@ class Parameter(Base): return "{}-{} {}".format(self.store_id, self.lane_id, self.param_key) +class TableSyncRule(Base): + """ + Represents a "table sync rule" value. + """ + __tablename__ = 'TableSyncRules' + + id = sa.Column('tableSyncRuleID', sa.Integer(), nullable=False, primary_key=True, autoincrement=True) + + table_name = sa.Column('tableName', sa.String(length=255), nullable=False, primary_key=True) + + rule = sa.Column(sa.String(length=255), nullable=True) + + def __str__(self): + return "{}: {}".format(self.table_name, self.rule) + + class Store(Base): """ Represents a known store. From 07e3c62b6c575dbdcb4b3c1f381908ddb4c53e5e Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Mon, 2 Aug 2021 09:07:50 -0500 Subject: [PATCH 17/95] Update changelog --- CHANGELOG.md | 4 ++++ corepos/_version.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dcaf8dc..cfab1d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to pyCOREPOS will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [0.1.4] - 2021-08-02 +### Changed +- Add schema for `TableSyncRules`. + ## [0.1.3] - 2021-07-21 ### Changed - Add basic 'lane_op' DB schema. diff --git a/corepos/_version.py b/corepos/_version.py index d60824a..483f94f 100644 --- a/corepos/_version.py +++ b/corepos/_version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8; -*- -__version__ = '0.1.3' +__version__ = '0.1.4' From 092884eab366cefb51c9b10a6569c4a84e4e1ef6 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Tue, 31 Aug 2021 22:40:58 -0500 Subject: [PATCH 18/95] Add lane_op model for Department --- corepos/db/lane_op/model.py | 41 +++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/corepos/db/lane_op/model.py b/corepos/db/lane_op/model.py index 2faa87b..b3e5901 100644 --- a/corepos/db/lane_op/model.py +++ b/corepos/db/lane_op/model.py @@ -32,6 +32,47 @@ from sqlalchemy.ext.declarative import declarative_base Base = declarative_base() +class Department(Base): + """ + Represents a department within the organization. + """ + __tablename__ = 'departments' + + number = sa.Column('dept_no', sa.SmallInteger(), nullable=False, + primary_key=True, autoincrement=False) + + name = sa.Column('dept_name', sa.String(length=30), nullable=True) + + tax = sa.Column('dept_tax', sa.Boolean(), nullable=True) + + food_stampable = sa.Column('dept_fs', sa.Boolean(), nullable=True) + + limit = sa.Column('dept_limit', sa.Float(), nullable=True) + + minimum = sa.Column('dept_minimum', sa.Float(), nullable=True) + + discount = sa.Column('dept_discount', sa.Boolean(), nullable=True) + + see_id = sa.Column('dept_see_id', sa.SmallInteger(), nullable=True) + + modified = sa.Column(sa.DateTime(), nullable=True) + + modified_by_id = sa.Column('modifiedby', sa.Integer(), nullable=True) + + margin = sa.Column(sa.Float(), nullable=False) + + sales_code = sa.Column('salesCode', sa.Integer(), nullable=False) + + member_only = sa.Column('memberOnly', sa.SmallInteger(), nullable=False) + + line_item_discount = sa.Column(sa.Boolean(), nullable=True) + + wicable = sa.Column('dept_wicable', sa.Boolean(), nullable=True) + + def __str__(self): + return self.name or "" + + class Product(Base): """ Represents a product, purchased and/or sold by the organization. From 69c7be6356aadc0c81f684b27eab063f1fde0b6b Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Tue, 31 Aug 2021 22:42:22 -0500 Subject: [PATCH 19/95] Update changelog --- CHANGELOG.md | 4 ++++ corepos/_version.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cfab1d1..78a864d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to pyCOREPOS will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [0.1.5] - 2021-08-31 +### Changed +- Add lane_op model for Department. + ## [0.1.4] - 2021-08-02 ### Changed - Add schema for `TableSyncRules`. diff --git a/corepos/_version.py b/corepos/_version.py index 483f94f..67ed0c1 100644 --- a/corepos/_version.py +++ b/corepos/_version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8; -*- -__version__ = '0.1.4' +__version__ = '0.1.5' From 0d81a41b54ccad1362f2e25f83e231cf6015c40f Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Tue, 7 Sep 2021 15:45:51 -0500 Subject: [PATCH 20/95] Add `User` model for office_op --- corepos/db/office_op/model.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/corepos/db/office_op/model.py b/corepos/db/office_op/model.py index 182f3fe..1cb0bd9 100644 --- a/corepos/db/office_op/model.py +++ b/corepos/db/office_op/model.py @@ -108,6 +108,32 @@ class TableSyncRule(Base): return "{}: {}".format(self.table_name, self.rule) +class User(Base): + """ + Represents a user within CORE Office. + """ + __tablename__ = 'Users' + + name = sa.Column(sa.String(length=50), nullable=False, primary_key=True) + + password = sa.Column(sa.String(length=255), nullable=True) + + salt = sa.Column(sa.String(length=10), nullable=True) + + uid = sa.Column(sa.String(length=4), nullable=True) + + session_id = sa.Column(sa.String(length=50), nullable=True) + + real_name = sa.Column(sa.String(length=75), nullable=True) + + email = sa.Column(sa.String(length=75), nullable=True) + + totp_url = sa.Column('totpURL', sa.String(length=255), nullable=True) + + def __str__(self): + return self.name or "" + + class Store(Base): """ Represents a known store. From 754d8697e8a53c917534605f9044e37e1e3bdc53 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Thu, 30 Sep 2021 18:44:53 -0400 Subject: [PATCH 21/95] Add proper support for `str(Suspension)` --- corepos/db/office_op/model.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/corepos/db/office_op/model.py b/corepos/db/office_op/model.py index 1cb0bd9..97c61d0 100644 --- a/corepos/db/office_op/model.py +++ b/corepos/db/office_op/model.py @@ -1469,6 +1469,10 @@ class Suspension(Base): reason_code = sa.Column('reasoncode', sa.Integer(), nullable=True) reason_object = orm.relationship(ReasonCode) + def __str__(self): + return "#{} on {}".format(self.card_number, + self.suspension_date) + class HouseCoupon(Base): """ From 41c142b8379e7f958339174bdd0a77898a677577 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Thu, 4 Nov 2021 17:41:39 -0500 Subject: [PATCH 22/95] Add the `custdata` model for lane_op DB also use Numeric instead of Float for "MONEY" columns, for custdata in both lane_op and office_op --- corepos/db/lane_op/model.py | 68 +++++++++++++++++++++++++++++++++++ corepos/db/office_op/model.py | 10 +++--- 2 files changed, 73 insertions(+), 5 deletions(-) diff --git a/corepos/db/lane_op/model.py b/corepos/db/lane_op/model.py index b3e5901..80051ab 100644 --- a/corepos/db/lane_op/model.py +++ b/corepos/db/lane_op/model.py @@ -208,3 +208,71 @@ class Product(Base): # TODO: some older DB's might not have this? guess we'll see last_sold = sa.Column(sa.DateTime(), nullable=True) + + +class CustData(Base): + """ + Represents a customer of the organization. + + https://github.com/CORE-POS/IS4C/blob/master/pos/is4c-nf/lib/models/op/CustdataModel.php + """ + __tablename__ = 'custdata' + # __table_args__ = ( + # sa.ForeignKeyConstraint(['memType'], ['memtype.memtype']), + # ) + + id = sa.Column(sa.Integer(), nullable=False, primary_key=True, autoincrement=True) + + card_number = sa.Column('CardNo', sa.Integer(), nullable=True) + + person_number = sa.Column('personNum', sa.SmallInteger(), nullable=True) + + first_name = sa.Column('FirstName', sa.String(length=30), nullable=True) + + last_name = sa.Column('LastName', sa.String(length=30), nullable=True) + + cash_back = sa.Column('CashBack', sa.Numeric(precision=10, scale=2), nullable=True) + + balance = sa.Column('Balance', sa.Numeric(precision=10, scale=2), nullable=True) + + discount = sa.Column('Discount', sa.SmallInteger(), nullable=True) + + member_discount_limit = sa.Column('MemDiscountLimit', sa.Numeric(precision=10, scale=2), nullable=True) + + charge_limit = sa.Column('ChargeLimit', sa.Numeric(precision=10, scale=2), nullable=True) + + charge_ok = sa.Column('ChargeOk', sa.Boolean(), nullable=True, default=True) + + write_checks = sa.Column('WriteChecks', sa.Boolean(), nullable=True, default=True) + + store_coupons = sa.Column('StoreCoupons', sa.Boolean(), nullable=True, default=True) + + type = sa.Column('Type', sa.String(length=10), nullable=True, default='PC') + + member_type_id = sa.Column('memType', sa.SmallInteger(), nullable=True) + # member_type = orm.relationship( + # MemberType, + # primaryjoin=MemberType.id == member_type_id, + # foreign_keys=[member_type_id], + # doc=""" + # Reference to the :class:`MemberType` to which this member belongs. + # """) + + staff = sa.Column(sa.Boolean(), nullable=True, default=False) + + ssi = sa.Column('SSI', sa.Boolean(), nullable=True, default=False) + + purchases = sa.Column('Purchases', sa.Numeric(precision=10, scale=2), nullable=True, default=0) + + number_of_checks = sa.Column('NumberOfChecks', sa.SmallInteger(), nullable=True, default=0) + + member_coupons = sa.Column('memCoupons', sa.Integer(), nullable=True, default=1) + + blue_line = sa.Column('blueLine', sa.String(length=50), nullable=True) + + shown = sa.Column('Shown', sa.Boolean(), nullable=True, default=True) + + last_change = sa.Column('LastChange', sa.DateTime(), nullable=True) + + def __str__(self): + return "{} {}".format(self.first_name or '', self.last_name or '').strip() diff --git a/corepos/db/office_op/model.py b/corepos/db/office_op/model.py index 97c61d0..ce43a59 100644 --- a/corepos/db/office_op/model.py +++ b/corepos/db/office_op/model.py @@ -1156,15 +1156,15 @@ class CustData(Base): last_name = sa.Column('LastName', sa.String(length=30), nullable=True) - cash_back = sa.Column('CashBack', sa.Float(), nullable=False, default=60) + cash_back = sa.Column('CashBack', sa.Numeric(precision=10, scale=2), nullable=False, default=60) - balance = sa.Column('Balance', sa.Float(), nullable=False, default=0) + balance = sa.Column('Balance', sa.Numeric(precision=10, scale=2), nullable=False, default=0) discount = sa.Column('Discount', sa.SmallInteger(), nullable=True) - member_discount_limit = sa.Column('MemDiscountLimit', sa.Float(), nullable=False, default=0) + member_discount_limit = sa.Column('MemDiscountLimit', sa.Numeric(precision=10, scale=2), nullable=False, default=0) - charge_limit = sa.Column('ChargeLimit', sa.Float(), nullable=False, default=0) + charge_limit = sa.Column('ChargeLimit', sa.Numeric(precision=10, scale=2), nullable=False, default=0) charge_ok = sa.Column('ChargeOk', sa.Boolean(), nullable=False, default=False) @@ -1187,7 +1187,7 @@ class CustData(Base): ssi = sa.Column('SSI', sa.Boolean(), nullable=False, default=False) - purchases = sa.Column('Purchases', sa.Float(), nullable=False, default=0) + purchases = sa.Column('Purchases', sa.Numeric(precision=10, scale=2), nullable=False, default=0) number_of_checks = sa.Column('NumberOfChecks', sa.SmallInteger(), nullable=False, default=0) From ae45615717066509b4cf95898d57ff55c026f6f7 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Thu, 4 Nov 2021 21:26:22 -0500 Subject: [PATCH 23/95] Update changelog --- CHANGELOG.md | 6 ++++++ corepos/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 78a864d..884a776 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to pyCOREPOS will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [0.1.6] - 2021-11-04 +### Changed +- Add `User` model for office_op. +- Add proper support for `str(Suspension)`. +- Add the `custdata` model for lane_op DB. + ## [0.1.5] - 2021-08-31 ### Changed - Add lane_op model for Department. diff --git a/corepos/_version.py b/corepos/_version.py index 67ed0c1..955f1d8 100644 --- a/corepos/_version.py +++ b/corepos/_version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8; -*- -__version__ = '0.1.5' +__version__ = '0.1.6' From a6306a48821315267ff1557069c8e6bde6761f1d Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Thu, 30 Dec 2021 21:47:55 -0600 Subject: [PATCH 24/95] Remove deprecation warning for `corepos.db` everything should be using the right imports now --- corepos/db/__init__.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/corepos/db/__init__.py b/corepos/db/__init__.py index 174fee4..db5c837 100644 --- a/corepos/db/__init__.py +++ b/corepos/db/__init__.py @@ -2,7 +2,7 @@ ################################################################################ # # pyCOREPOS -- Python Interface to CORE POS -# Copyright © 2018-2020 Lance Edgar +# Copyright © 2018-2021 Lance Edgar # # This file is part of pyCOREPOS. # @@ -23,12 +23,3 @@ """ Database Interface """ - -from __future__ import unicode_literals, absolute_import - -import warnings -warnings.warn("The `corepos.db` module is deprecated! " - "Please use `corepos.db.office_op` instead.", - DeprecationWarning) - -from corepos.db.office_op import * From b46282264c808792a2492807c8305e2943b9b566 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Mon, 17 Jan 2022 18:59:19 -0600 Subject: [PATCH 25/95] Add model for `UserGroup` --- corepos/db/office_op/model.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/corepos/db/office_op/model.py b/corepos/db/office_op/model.py index ce43a59..c913ae0 100644 --- a/corepos/db/office_op/model.py +++ b/corepos/db/office_op/model.py @@ -108,6 +108,24 @@ class TableSyncRule(Base): return "{}: {}".format(self.table_name, self.rule) +class UserGroup(Base): + """ + Represents a user/group assignment. + """ + __tablename__ = 'userGroups' + + group_id = sa.Column('gid', sa.Integer(), nullable=False, + primary_key=True, autoincrement=False) + + name = sa.Column(sa.String(length=50), nullable=True) + + username = sa.Column(sa.String(length=50), nullable=False, + primary_key=True) + + def __str__(self): + return self.name or "" + + class User(Base): """ Represents a user within CORE Office. From af6189b23724c4af9b645090f5a9ee0832925af5 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Wed, 2 Mar 2022 21:35:57 -0600 Subject: [PATCH 26/95] Update changelog --- CHANGELOG.md | 5 +++++ corepos/_version.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 884a776..f83bdb7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ All notable changes to pyCOREPOS will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [0.1.7] - 2022-03-02 +### Changed +- Remove deprecation warning for `corepos.db`. +- Add model for `UserGroup`. + ## [0.1.6] - 2021-11-04 ### Changed - Add `User` model for office_op. diff --git a/corepos/_version.py b/corepos/_version.py index 955f1d8..4cdc594 100644 --- a/corepos/_version.py +++ b/corepos/_version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8; -*- -__version__ = '0.1.6' +__version__ = '0.1.7' From 2b80fd6a6ba5add0ba82fb0ad30ed5768c113b4a Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sat, 26 Mar 2022 23:04:05 -0500 Subject: [PATCH 27/95] Add basic `TransactionDetail` for trans archive model --- corepos/db/office_trans/model.py | 10 ++++-- corepos/db/office_trans_archive/__init__.py | 30 ++++++++++++++++ corepos/db/office_trans_archive/model.py | 39 +++++++++++++++++++++ 3 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 corepos/db/office_trans_archive/__init__.py create mode 100644 corepos/db/office_trans_archive/model.py diff --git a/corepos/db/office_trans/model.py b/corepos/db/office_trans/model.py index 55aff86..7b6240c 100644 --- a/corepos/db/office_trans/model.py +++ b/corepos/db/office_trans/model.py @@ -35,11 +35,10 @@ Base = declarative_base() @six.python_2_unicode_compatible -class TransactionDetail(Base): +class TransactionDetailBase(object): """ Represents a POS transaction detail record. """ - __tablename__ = 'dtransactions' # store store_row_id = sa.Column(sa.Integer(), primary_key=True, nullable=False) @@ -123,3 +122,10 @@ class TransactionDetail(Base): def __str__(self): return self.description or '' + + +class TransactionDetail(TransactionDetailBase, Base): + """ + Represents a POS transaction detail record. + """ + __tablename__ = 'dtransactions' diff --git a/corepos/db/office_trans_archive/__init__.py b/corepos/db/office_trans_archive/__init__.py new file mode 100644 index 0000000..e25b200 --- /dev/null +++ b/corepos/db/office_trans_archive/__init__.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8; -*- +################################################################################ +# +# pyCOREPOS -- Python Interface to CORE POS +# Copyright © 2018-2022 Lance Edgar +# +# This file is part of pyCOREPOS. +# +# pyCOREPOS 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. +# +# pyCOREPOS 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 +# pyCOREPOS. If not, see . +# +################################################################################ +""" +"Archive" Transaction Database Interface +""" + +from sqlalchemy import orm + + +Session = orm.sessionmaker() diff --git a/corepos/db/office_trans_archive/model.py b/corepos/db/office_trans_archive/model.py new file mode 100644 index 0000000..ce70603 --- /dev/null +++ b/corepos/db/office_trans_archive/model.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8; -*- +################################################################################ +# +# pyCOREPOS -- Python Interface to CORE POS +# Copyright © 2018-2022 Lance Edgar +# +# This file is part of pyCOREPOS. +# +# pyCOREPOS 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. +# +# pyCOREPOS 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 +# pyCOREPOS. If not, see . +# +################################################################################ +""" +CORE POS Transaction Data Model +""" + +from sqlalchemy.ext.declarative import declarative_base + +from corepos.db.office_trans.model import TransactionDetailBase + + +Base = declarative_base() + + +class TransactionDetail(TransactionDetailBase, Base): + """ + Represents a POS transaction detail record. + """ + __tablename__ = 'bigArchive' From c2723de467a920f9d6b7aea3fed3692eba5502cc Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sun, 21 Aug 2022 00:11:24 -0500 Subject: [PATCH 28/95] Delete `productUser` record when `products` record is deleted --- corepos/db/office_op/model.py | 1 + 1 file changed, 1 insertion(+) diff --git a/corepos/db/office_op/model.py b/corepos/db/office_op/model.py index c913ae0..107da66 100644 --- a/corepos/db/office_op/model.py +++ b/corepos/db/office_op/model.py @@ -757,6 +757,7 @@ class ProductUser(Base): backref=orm.backref( 'user_info', uselist=False, + cascade='all, delete-orphan', doc=""" Reference to the :class:`ProductUser` record for this product, if any. """)) From 2444628c13e652ebaebbdbffc497448ba56e2e45 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Mon, 2 Jan 2023 16:56:30 -0600 Subject: [PATCH 29/95] Update changelog --- CHANGELOG.md | 5 +++++ corepos/_version.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f83bdb7..881ced9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ All notable changes to pyCOREPOS will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [0.1.8] - 2023-01-02 +### Changed +- Add basic `TransactionDetail` for trans archive model. +- Delete `productUser` record when `products` record is deleted. + ## [0.1.7] - 2022-03-02 ### Changed - Remove deprecation warning for `corepos.db`. diff --git a/corepos/_version.py b/corepos/_version.py index 4cdc594..4e18c2b 100644 --- a/corepos/_version.py +++ b/corepos/_version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8; -*- -__version__ = '0.1.7' +__version__ = '0.1.8' From ad5837405f975b31b292ce9d3d7e8b9519103086 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Wed, 15 Feb 2023 12:51:50 -0600 Subject: [PATCH 30/95] Require SQLAlchemy 1.4.x --- corepos/db/lane_op/model.py | 7 +++---- corepos/db/office_op/model.py | 5 ++--- corepos/db/office_trans/model.py | 10 +++------- corepos/db/office_trans_archive/model.py | 6 +++--- setup.py | 5 ++--- 5 files changed, 13 insertions(+), 20 deletions(-) diff --git a/corepos/db/lane_op/model.py b/corepos/db/lane_op/model.py index 80051ab..1c687ab 100644 --- a/corepos/db/lane_op/model.py +++ b/corepos/db/lane_op/model.py @@ -2,7 +2,7 @@ ################################################################################ # # pyCOREPOS -- Python Interface to CORE POS -# Copyright © 2018-2021 Lance Edgar +# Copyright © 2018-2023 Lance Edgar # # This file is part of pyCOREPOS. # @@ -25,11 +25,10 @@ Data model for CORE POS "lane_op" DB """ import sqlalchemy as sa -# from sqlalchemy import orm -from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy import orm -Base = declarative_base() +Base = orm.declarative_base() class Department(Base): diff --git a/corepos/db/office_op/model.py b/corepos/db/office_op/model.py index 107da66..132fd33 100644 --- a/corepos/db/office_op/model.py +++ b/corepos/db/office_op/model.py @@ -2,7 +2,7 @@ ################################################################################ # # pyCOREPOS -- Python Interface to CORE POS -# Copyright © 2018-2021 Lance Edgar +# Copyright © 2018-2023 Lance Edgar # # This file is part of pyCOREPOS. # @@ -29,13 +29,12 @@ import logging import sqlalchemy as sa from sqlalchemy import orm -from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.associationproxy import association_proxy log = logging.getLogger(__name__) -Base = declarative_base() +Base = orm.declarative_base() class StringableDateTime(sa.TypeDecorator): diff --git a/corepos/db/office_trans/model.py b/corepos/db/office_trans/model.py index 7b6240c..20c244c 100644 --- a/corepos/db/office_trans/model.py +++ b/corepos/db/office_trans/model.py @@ -2,7 +2,7 @@ ################################################################################ # # pyCOREPOS -- Python Interface to CORE POS -# Copyright © 2018-2020 Lance Edgar +# Copyright © 2018-2023 Lance Edgar # # This file is part of pyCOREPOS. # @@ -24,17 +24,13 @@ CORE POS Transaction Data Model """ -from __future__ import unicode_literals, absolute_import - -import six import sqlalchemy as sa -from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy import orm -Base = declarative_base() +Base = orm.declarative_base() -@six.python_2_unicode_compatible class TransactionDetailBase(object): """ Represents a POS transaction detail record. diff --git a/corepos/db/office_trans_archive/model.py b/corepos/db/office_trans_archive/model.py index ce70603..8f4a079 100644 --- a/corepos/db/office_trans_archive/model.py +++ b/corepos/db/office_trans_archive/model.py @@ -2,7 +2,7 @@ ################################################################################ # # pyCOREPOS -- Python Interface to CORE POS -# Copyright © 2018-2022 Lance Edgar +# Copyright © 2018-2023 Lance Edgar # # This file is part of pyCOREPOS. # @@ -24,12 +24,12 @@ CORE POS Transaction Data Model """ -from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy import orm from corepos.db.office_trans.model import TransactionDetailBase -Base = declarative_base() +Base = orm.declarative_base() class TransactionDetail(TransactionDetailBase, Base): diff --git a/setup.py b/setup.py index 098d0fc..2c5f0f2 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ ################################################################################ # # pyCOREPOS -- Python Interface to CORE POS -# Copyright © 2018-2020 Lance Edgar +# Copyright © 2018-2023 Lance Edgar # # This file is part of pyCOREPOS. # @@ -63,7 +63,7 @@ requires = [ 'mysql-connector-python', # 8.0.6 'requests', # 2.23.0 'six', # 1.12.0 - 'SQLAlchemy', # 0.9.8 + 'SQLAlchemy>=1.4', # 1.4.0 ] @@ -87,7 +87,6 @@ setup( 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', 'Topic :: Office/Business', 'Topic :: Software Development :: Libraries :: Python Modules', ], From 24fb8c8feab6cd276de999f76793485f6460aa89 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Mon, 1 May 2023 22:17:38 -0500 Subject: [PATCH 31/95] Update changelog --- CHANGELOG.md | 4 ++++ corepos/_version.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 881ced9..a7925f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to pyCOREPOS will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [0.1.9] - 2023-05-01 +### Changed +- Require SQLAlchemy 1.4.x. + ## [0.1.8] - 2023-01-02 ### Changed - Add basic `TransactionDetail` for trans archive model. diff --git a/corepos/_version.py b/corepos/_version.py index 4e18c2b..def08d5 100644 --- a/corepos/_version.py +++ b/corepos/_version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8; -*- -__version__ = '0.1.8' +__version__ = '0.1.9' From 6e43449ecbd863ca80975fb0b63b0e74c3019dfc Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Tue, 16 May 2023 13:16:41 -0500 Subject: [PATCH 32/95] Replace `setup.py` contents with `setup.cfg` --- setup.cfg | 33 +++++++++++++++++++++++++ setup.py | 74 ++----------------------------------------------------- 2 files changed, 35 insertions(+), 72 deletions(-) create mode 100644 setup.cfg diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..728f179 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,33 @@ +# -*- coding: utf-8; -*- + +[metadata] +name = pyCOREPOS +version = attr: corepos.__version__ +author = Lance Edgar +author_email = lance@edbob.org +url = https://rattailproject.org/ +license = GNU GPL v3 +description = Python Interface to CORE POS +long_description = file: README.rst +classifiers = + Development Status :: 3 - Alpha + Environment :: Console + Environment :: Web Environment + Intended Audience :: Developers + License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+) + Natural Language :: English + Operating System :: OS Independent + Programming Language :: Python + Programming Language :: Python :: 3 + Topic :: Office/Business + Topic :: Software Development :: Libraries :: Python Modules + + +[options] +install_requires = + mysql-connector-python + requests + six + SQLAlchemy>=1.4 + +packages = find: diff --git a/setup.py b/setup.py index 2c5f0f2..a02ef90 100644 --- a/setup.py +++ b/setup.py @@ -21,76 +21,6 @@ # ################################################################################ -import os -import sys -from setuptools import setup, find_packages +from setuptools import setup - -here = os.path.abspath(os.path.dirname(__file__)) -exec(open(os.path.join(here, 'corepos', '_version.py')).read()) -README = open(os.path.join(here, 'README.rst')).read() - - -requires = [ - # - # Version numbers within comments below have specific meanings. - # Basically the 'low' value is a "soft low," and 'high' a "soft high." - # In other words: - # - # If either a 'low' or 'high' value exists, the primary point to be - # made about the value is that it represents the most current (stable) - # version available for the package (assuming typical public access - # methods) whenever this project was started and/or documented. - # Therefore: - # - # If a 'low' version is present, you should know that attempts to use - # versions of the package significantly older than the 'low' version - # may not yield happy results. (A "hard" high limit may or may not be - # indicated by a true version requirement.) - # - # Similarly, if a 'high' version is present, and especially if this - # project has laid dormant for a while, you may need to refactor a bit - # when attempting to support a more recent version of the package. (A - # "hard" low limit should be indicated by a true version requirement - # when a 'high' version is present.) - # - # In any case, developers and other users are encouraged to play - # outside the lines with regard to these soft limits. If bugs are - # encountered then they should be filed as such. - # - # package # low high - - 'mysql-connector-python', # 8.0.6 - 'requests', # 2.23.0 - 'six', # 1.12.0 - 'SQLAlchemy>=1.4', # 1.4.0 -] - - -setup( - name = "pyCOREPOS", - version = __version__, - author = "Lance Edgar", - author_email = "lance@edbob.org", - url = "https://rattailproject.org/", - license = "GNU GPL v3", - description = "Python Interface to CORE POS", - long_description = README, - - classifiers = [ - 'Development Status :: 3 - Alpha', - 'Environment :: Console', - 'Environment :: Web Environment', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)', - 'Natural Language :: English', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3', - 'Topic :: Office/Business', - 'Topic :: Software Development :: Libraries :: Python Modules', - ], - - install_requires = requires, - packages = find_packages(), -) +setup() From 757fb50a967a624323fe16df0597b8cf237176ae Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Wed, 17 May 2023 06:57:14 -0500 Subject: [PATCH 33/95] Update changelog --- CHANGELOG.md | 4 ++++ corepos/_version.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7925f0..7686346 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to pyCOREPOS will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [0.1.10] - 2023-05-17 +### Changed +- Replace `setup.py` contents with `setup.cfg`. + ## [0.1.9] - 2023-05-01 ### Changed - Require SQLAlchemy 1.4.x. diff --git a/corepos/_version.py b/corepos/_version.py index def08d5..18c177e 100644 --- a/corepos/_version.py +++ b/corepos/_version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8; -*- -__version__ = '0.1.9' +__version__ = '0.1.10' From d58426c07369724e8d33fe8dbb41e36a6086eba6 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Mon, 22 May 2023 21:34:46 -0500 Subject: [PATCH 34/95] Add support for htdigest auth when using CORE webservices API --- corepos/api.py | 54 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/corepos/api.py b/corepos/api.py index e7310a0..c93ff9d 100644 --- a/corepos/api.py +++ b/corepos/api.py @@ -2,7 +2,7 @@ ################################################################################ # # pyCOREPOS -- Python Interface to CORE POS -# Copyright © 2018-2021 Lance Edgar +# Copyright © 2018-2023 Lance Edgar # # This file is part of pyCOREPOS. # @@ -28,6 +28,7 @@ import json import logging import requests +from requests.auth import HTTPDigestAuth log = logging.getLogger(__name__) @@ -48,27 +49,42 @@ class CoreAPIError(Exception): class CoreWebAPI(object): """ Client implementation for the CORE webservices API. + + :param str url: URL to the CORE webservices API, + e.g. ``'http://localhost/fannie/ws/'`` + + :param bool verify: How to handle certificate validation for HTTPS + URLs. This value is passed as-is to the ``requests`` library, + so see those docs for more info. The default value for this is + ``True`` because the assumption is that security should be on + by default. Set it to ``False`` in order to disable validation + entirely, e.g. for self-signed certs. (This may also be needed + for basic HTTP URLs?) Other values may be possible also; again + see the ``requests`` docs for more info. + + :param htdigest_username: Username for htdigest authentication, if + applicable. + + :param htdigest_password: Password for htdigest authentication, if + applicable. """ - def __init__(self, url, verify=True): - """ - Constructor for the API client. - - :param str url: URL to the CORE webservices API, - e.g. ``'http://localhost/fannie/ws/'`` - - :param bool verify: How to handle certificate validation for HTTPS - URLs. This value is passed as-is to the ``requests`` library, so - see those docs for more info. The default value for this is - ``True`` because the assumption is that security should be on by - default. Set it to ``False`` in order to disable validation - entirely, e.g. for self-signed certs. (This may also be needed for - basic HTTP URLs?) Other values may be possible also; again see the - ``requests`` docs for more info. - """ + def __init__( + self, + url, + verify=True, + htdigest_username=None, + htdigest_password=None, + ): self.url = url self.verify = verify + self.session = requests.Session() + + if htdigest_username and htdigest_password: + self.session.auth = HTTPDigestAuth(htdigest_username, + htdigest_password) + def post(self, params, method=None): """ Issue a POST request to the API, with the given ``params``. If not @@ -88,8 +104,8 @@ class CoreWebAPI(object): 'id': 1, } - response = requests.post(self.url, data=json.dumps(payload), - verify=self.verify) + response = self.session.post(self.url, data=json.dumps(payload), + verify=self.verify) response.raise_for_status() return response From bb8278dcc8a7c91656958532f300994e34e55233 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Fri, 2 Jun 2023 14:26:51 -0500 Subject: [PATCH 35/95] Update changelog --- CHANGELOG.md | 4 ++++ corepos/_version.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7686346..20d31e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to pyCOREPOS will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [0.1.11] - 2023-06-02 +### Changed +- Add support for htdigest auth when using CORE webservices API. + ## [0.1.10] - 2023-05-17 ### Changed - Replace `setup.py` contents with `setup.cfg`. diff --git a/corepos/_version.py b/corepos/_version.py index 18c177e..862eaaa 100644 --- a/corepos/_version.py +++ b/corepos/_version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8; -*- -__version__ = '0.1.10' +__version__ = '0.1.11' From 852a989bd53516b1371cb1d901d3b33a32cf042f Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Tue, 6 Jun 2023 13:13:51 -0500 Subject: [PATCH 36/95] Add `get_member_types()` method for CORE API --- corepos/api.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/corepos/api.py b/corepos/api.py index c93ff9d..97ad8c7 100644 --- a/corepos/api.py +++ b/corepos/api.py @@ -132,6 +132,22 @@ class CoreWebAPI(object): assert set(js['result'].keys()) == set(['result']) return js['result']['result'] + def get_member_types(self): + """ + Fetch all Member Type records from CORE. + + :returns: A (potentially empty) list of member type dict records. + """ + params = { + 'entity': 'Memtype', + 'submethod': 'get', + 'columns': {}, + } + + response = self.post(params) + result = self.parse_response(response) + return [json.loads(rec) for rec in result] + def get_members(self): """ Fetch all Member records from CORE. From 4952d2fa3d3e80056f016723e9529c9659d980aa Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Mon, 12 Jun 2023 17:28:55 -0500 Subject: [PATCH 37/95] Rename `custdata` model to `CustomerClassic` --- corepos/db/lane_op/model.py | 6 +++++- corepos/db/office_op/model.py | 20 ++++++++++++-------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/corepos/db/lane_op/model.py b/corepos/db/lane_op/model.py index 1c687ab..c7ed5b2 100644 --- a/corepos/db/lane_op/model.py +++ b/corepos/db/lane_op/model.py @@ -209,7 +209,7 @@ class Product(Base): last_sold = sa.Column(sa.DateTime(), nullable=True) -class CustData(Base): +class CustomerClassic(Base): """ Represents a customer of the organization. @@ -275,3 +275,7 @@ class CustData(Base): def __str__(self): return "{} {}".format(self.first_name or '', self.last_name or '').strip() + + +# TODO: deprecate / remove this +CustData = CustomerClassic diff --git a/corepos/db/office_op/model.py b/corepos/db/office_op/model.py index 132fd33..9ec20d2 100644 --- a/corepos/db/office_op/model.py +++ b/corepos/db/office_op/model.py @@ -1153,7 +1153,7 @@ class Customer(Base): return "{} {}".format(self.first_name or '', self.last_name or '').strip() -class CustData(Base): +class CustomerClassic(Base): """ Represents a customer of the organization. @@ -1219,7 +1219,7 @@ class CustData(Base): member_info = orm.relationship( 'MemberInfo', - primaryjoin='MemberInfo.card_number == CustData.card_number', + primaryjoin='MemberInfo.card_number == CustomerClassic.card_number', foreign_keys=[card_number], uselist=False, back_populates='customers', @@ -1231,6 +1231,10 @@ class CustData(Base): return "{} {}".format(self.first_name or '', self.last_name or '').strip() +# TODO: deprecate / remove this +CustData = CustomerClassic + + class MemberInfo(Base): """ Contact info regarding a member of the organization. @@ -1264,14 +1268,14 @@ class MemberInfo(Base): ads_ok = sa.Column('ads_OK', sa.Boolean(), nullable=True, default=True) customers = orm.relationship( - CustData, - primaryjoin=CustData.card_number == card_number, - order_by=CustData.person_number, - foreign_keys=[CustData.card_number], + CustomerClassic, + primaryjoin=CustomerClassic.card_number == card_number, + order_by=CustomerClassic.person_number, + foreign_keys=[CustomerClassic.card_number], back_populates='member_info', - remote_side=CustData.card_number, + remote_side=CustomerClassic.card_number, doc=""" - List of :class:`CustData` instances which are associated with this member info. + List of :class:`CustomerClassic` instances which are associated with this member info. """) dates = orm.relationship( From c6144ab31025f65e963b8a80a85d5c3d7deb0c0f Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Mon, 12 Jun 2023 20:38:12 -0500 Subject: [PATCH 38/95] Add note about `meminfo.email_2` field, aka. "alt. phone" --- corepos/db/office_op/model.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/corepos/db/office_op/model.py b/corepos/db/office_op/model.py index 9ec20d2..7328525 100644 --- a/corepos/db/office_op/model.py +++ b/corepos/db/office_op/model.py @@ -1263,7 +1263,10 @@ class MemberInfo(Base): email = sa.Column('email_1', sa.String(length=50), nullable=True) - email2 = sa.Column('email_2', sa.String(length=50), nullable=True) + email2 = sa.Column('email_2', sa.String(length=50), nullable=True, doc=""" + NB. this is labeled "Alt. Phone" in CORE Office member view, and + is named `altPhone` when dealing with CORE Office webservices API. + """) ads_ok = sa.Column('ads_OK', sa.Boolean(), nullable=True, default=True) From b2622c473d669908c989286bde30c0ae873ce098 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Mon, 12 Jun 2023 20:39:12 -0500 Subject: [PATCH 39/95] Update changelog --- CHANGELOG.md | 6 ++++++ corepos/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 20d31e0..58781cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to pyCOREPOS will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [0.1.12] - 2023-06-12 +### Changed +- Add `get_member_types()` method for CORE API. +- Rename model for `custdata` to `CustomerClassic`. +- Add note about `meminfo.email_2` field, aka. "alt. phone". + ## [0.1.11] - 2023-06-02 ### Changed - Add support for htdigest auth when using CORE webservices API. diff --git a/corepos/_version.py b/corepos/_version.py index 862eaaa..002a140 100644 --- a/corepos/_version.py +++ b/corepos/_version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8; -*- -__version__ = '0.1.11' +__version__ = '0.1.12' From 5a77d14a261dc770a4f13fc3e632065ca8ec48b1 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sun, 18 Jun 2023 11:38:14 -0500 Subject: [PATCH 40/95] Add models for StockPurchase and EquityLiveBalance --- corepos/db/office_trans/model.py | 35 ++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/corepos/db/office_trans/model.py b/corepos/db/office_trans/model.py index 20c244c..d484b4b 100644 --- a/corepos/db/office_trans/model.py +++ b/corepos/db/office_trans/model.py @@ -31,6 +31,41 @@ from sqlalchemy import orm Base = orm.declarative_base() +# TODO: not sure what primary key should be for this? am trying a +# composite one so far, we'll see... +class StockPurchase(Base): + """ + Represents a member equity payment. + """ + __tablename__ = 'stockpurchases' + + card_number = sa.Column('card_no', sa.Integer(), nullable=False, primary_key=True, autoincrement=False) + + amount = sa.Column('stockPurchase', sa.Numeric(precision=10, scale=2), nullable=True) + + datetime = sa.Column('tdate', sa.DateTime(), nullable=True, primary_key=True) + + transaction_number = sa.Column('trans_num', sa.String(length=50), nullable=True, primary_key=True) + + transaction_id = sa.Column('trans_id', sa.Integer(), nullable=True) + + department_number = sa.Column('dept', sa.Integer(), nullable=True) + + def __str__(self): + return f"#{self.card_number} for ${self.amount}" + + +class EquityLiveBalance(Base): + + __tablename__ = 'equity_live_balance' + + member_number = sa.Column('memnum', sa.Integer(), nullable=False, primary_key=True, autoincrement=False) + + payments = sa.Column(sa.Numeric(precision=10, scale=2), nullable=True) + + start_date = sa.Column('startdate', sa.DateTime(), nullable=True) + + class TransactionDetailBase(object): """ Represents a POS transaction detail record. From 5921a18f12b9dd3c43e3957aa3561d6a095e5c6b Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sat, 2 Sep 2023 13:56:59 -0500 Subject: [PATCH 41/95] Update changelog --- CHANGELOG.md | 4 ++++ corepos/_version.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 58781cd..7e949e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to pyCOREPOS will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [0.1.13] - 2023-09-02 +### Changed +- Add models for StockPurchase and EquityLiveBalance. + ## [0.1.12] - 2023-06-12 ### Changed - Add `get_member_types()` method for CORE API. diff --git a/corepos/_version.py b/corepos/_version.py index 002a140..ca4de46 100644 --- a/corepos/_version.py +++ b/corepos/_version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8; -*- -__version__ = '0.1.12' +__version__ = '0.1.13' From 0e3030394796b39117a2c5d9671188b9bc3db6ad Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Thu, 7 Sep 2023 17:45:14 -0500 Subject: [PATCH 42/95] Tweak primary key for StockPurchase model per having a better understanding now, i think.. --- corepos/db/office_trans/model.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/corepos/db/office_trans/model.py b/corepos/db/office_trans/model.py index d484b4b..b203240 100644 --- a/corepos/db/office_trans/model.py +++ b/corepos/db/office_trans/model.py @@ -32,7 +32,8 @@ Base = orm.declarative_base() # TODO: not sure what primary key should be for this? am trying a -# composite one so far, we'll see... +# composite one so far, we'll see...cf. also andy's comments in +# https://github.com/CORE-POS/IS4C/pull/1189#issuecomment-1597481138 class StockPurchase(Base): """ Represents a member equity payment. @@ -43,13 +44,13 @@ class StockPurchase(Base): amount = sa.Column('stockPurchase', sa.Numeric(precision=10, scale=2), nullable=True) - datetime = sa.Column('tdate', sa.DateTime(), nullable=True, primary_key=True) + datetime = sa.Column('tdate', sa.DateTime(), nullable=True, primary_key=True, autoincrement=False) transaction_number = sa.Column('trans_num', sa.String(length=50), nullable=True, primary_key=True) transaction_id = sa.Column('trans_id', sa.Integer(), nullable=True) - department_number = sa.Column('dept', sa.Integer(), nullable=True) + department_number = sa.Column('dept', sa.Integer(), nullable=True, primary_key=True, autoincrement=False) def __str__(self): return f"#{self.card_number} for ${self.amount}" From 0dda359094d11627b27807eb88a923f0f2b24e3a Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Thu, 7 Sep 2023 18:37:48 -0500 Subject: [PATCH 43/95] Update changelog --- CHANGELOG.md | 4 ++++ corepos/_version.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e949e4..9ef75b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to pyCOREPOS will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [0.1.14] - 2023-09-07 +### Changed +- Tweak primary key for StockPurchase model. + ## [0.1.13] - 2023-09-02 ### Changed - Add models for StockPurchase and EquityLiveBalance. diff --git a/corepos/_version.py b/corepos/_version.py index ca4de46..19bf267 100644 --- a/corepos/_version.py +++ b/corepos/_version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8; -*- -__version__ = '0.1.13' +__version__ = '0.1.14' From 5214db0a83208e37bd85c81e635807fbe6489b36 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Wed, 13 Sep 2023 16:23:33 -0500 Subject: [PATCH 44/95] Add model for `CustomerNotifications` table --- corepos/db/office_op/model.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/corepos/db/office_op/model.py b/corepos/db/office_op/model.py index 7328525..68d69d9 100644 --- a/corepos/db/office_op/model.py +++ b/corepos/db/office_op/model.py @@ -1450,6 +1450,26 @@ class MemberNote(Base): return self.note or "" +class CustomerNotification(Base): + """ + Represents a customer notification for display at the lane. + + https://github.com/CORE-POS/IS4C/blob/master/fannie/classlib2.0/data/models/op/CustomerNotificationsModel.php + """ + __tablename__ = 'CustomerNotifications' + + id = sa.Column('customerNotificationID', sa.Integer(), primary_key=True, autoincrement=True, nullable=False) + card_number = sa.Column('cardNo', sa.Integer(), nullable=True) + customer_id = sa.Column('customerID', sa.Integer(), nullable=True) + source = sa.Column(sa.String(length=50), nullable=True) + type = sa.Column(sa.String(length=50), nullable=True) + message = sa.Column(sa.String(length=255), nullable=True) + modifier_module = sa.Column('modifierModule', sa.String(length=50), nullable=True) + + def __str__(self): + return self.message or '' + + class ReasonCode(Base): """ Reason codes for legacy account suspensions. From 541adb69794a8d4eb92f4d8431ae8b314eb62ba8 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Wed, 13 Sep 2023 21:51:10 -0500 Subject: [PATCH 45/95] Update changelog --- CHANGELOG.md | 4 ++++ corepos/_version.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ef75b6..4faaeb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to pyCOREPOS will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [0.1.15] - 2023-09-13 +### Changed +- Add model for `CustomerNotifications` table. + ## [0.1.14] - 2023-09-07 ### Changed - Tweak primary key for StockPurchase model. diff --git a/corepos/_version.py b/corepos/_version.py index 19bf267..f877d42 100644 --- a/corepos/_version.py +++ b/corepos/_version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8; -*- -__version__ = '0.1.14' +__version__ = '0.1.15' From 83eecad28d68871abbcc09ed224b78f3998f747a Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Fri, 15 Sep 2023 12:43:02 -0500 Subject: [PATCH 46/95] Add model for `office_op.Tender` --- corepos/db/office_op/model.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/corepos/db/office_op/model.py b/corepos/db/office_op/model.py index 68d69d9..f2a6884 100644 --- a/corepos/db/office_op/model.py +++ b/corepos/db/office_op/model.py @@ -1560,6 +1560,28 @@ class HouseCoupon(Base): return self.description or '' +class Tender(Base): + """ + Represents a tender for payment at POS + """ + __tablename__ = 'tenders' + + tender_id = sa.Column('TenderID', sa.Integer(), primary_key=True, nullable=False) + + tender_code = sa.Column('TenderCode', sa.String(length=2), nullable=True) + tender_name = sa.Column('TenderName', sa.String(length=25), nullable=True) + tender_type = sa.Column('TenderType', sa.String(length=2), nullable=True) + change_message = sa.Column('ChangeMessage', sa.String(length=25), nullable=True) + min_amount = sa.Column('MinAmount', sa.Numeric(precision=10, scale=2), nullable=True) + max_amount = sa.Column('MaxAmount', sa.Numeric(precision=10, scale=2), nullable=True) + max_refund = sa.Column('MaxRefund', sa.Numeric(precision=10, scale=2), nullable=True) + tender_module = sa.Column('TenderModule', sa.String(length=50), nullable=True) + sales_code = sa.Column('SalesCode', sa.Integer(), nullable=True) + + def __str__(self): + return self.tender_name or '' + + class BatchType(Base): """ Represents the definition of a batch type. From f06f236a604bb10a1e8d9f92f676647d19532b5b Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Fri, 15 Sep 2023 12:59:52 -0500 Subject: [PATCH 47/95] Update changelog --- CHANGELOG.md | 4 ++++ corepos/_version.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4faaeb5..56b70c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to pyCOREPOS will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [0.1.16] - 2023-09-15 +### Changed +- Add model for `office_op.Tender`. + ## [0.1.15] - 2023-09-13 ### Changed - Add model for `CustomerNotifications` table. diff --git a/corepos/_version.py b/corepos/_version.py index f877d42..05c3021 100644 --- a/corepos/_version.py +++ b/corepos/_version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8; -*- -__version__ = '0.1.15' +__version__ = '0.1.16' From e5988102ad13c03f5d516dca1245b3959fc852ef Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Thu, 5 Oct 2023 11:52:39 -0500 Subject: [PATCH 48/95] Rename module to `corepos.db.office_arch` --- corepos/db/office_arch/__init__.py | 30 ++++++++++++++++ corepos/db/office_arch/model.py | 39 +++++++++++++++++++++ corepos/db/office_trans_archive/__init__.py | 10 +++--- corepos/db/office_trans_archive/model.py | 17 +++------ 4 files changed, 80 insertions(+), 16 deletions(-) create mode 100644 corepos/db/office_arch/__init__.py create mode 100644 corepos/db/office_arch/model.py diff --git a/corepos/db/office_arch/__init__.py b/corepos/db/office_arch/__init__.py new file mode 100644 index 0000000..70292e9 --- /dev/null +++ b/corepos/db/office_arch/__init__.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8; -*- +################################################################################ +# +# pyCOREPOS -- Python Interface to CORE POS +# Copyright © 2018-2023 Lance Edgar +# +# This file is part of pyCOREPOS. +# +# pyCOREPOS 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. +# +# pyCOREPOS 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 +# pyCOREPOS. If not, see . +# +################################################################################ +""" +"Archive" Transaction Database Interface +""" + +from sqlalchemy import orm + + +Session = orm.sessionmaker() diff --git a/corepos/db/office_arch/model.py b/corepos/db/office_arch/model.py new file mode 100644 index 0000000..bf01a4d --- /dev/null +++ b/corepos/db/office_arch/model.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8; -*- +################################################################################ +# +# pyCOREPOS -- Python Interface to CORE POS +# Copyright © 2018-2023 Lance Edgar +# +# This file is part of pyCOREPOS. +# +# pyCOREPOS 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. +# +# pyCOREPOS 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 +# pyCOREPOS. If not, see . +# +################################################################################ +""" +CORE Office "arch" data model +""" + +from sqlalchemy import orm + +from corepos.db.office_trans.model import TransactionDetailBase + + +Base = orm.declarative_base() + + +class TransactionDetail(TransactionDetailBase, Base): + """ + Represents a POS transaction detail record. + """ + __tablename__ = 'bigArchive' diff --git a/corepos/db/office_trans_archive/__init__.py b/corepos/db/office_trans_archive/__init__.py index e25b200..6ddb31d 100644 --- a/corepos/db/office_trans_archive/__init__.py +++ b/corepos/db/office_trans_archive/__init__.py @@ -2,7 +2,7 @@ ################################################################################ # # pyCOREPOS -- Python Interface to CORE POS -# Copyright © 2018-2022 Lance Edgar +# Copyright © 2018-2023 Lance Edgar # # This file is part of pyCOREPOS. # @@ -24,7 +24,9 @@ "Archive" Transaction Database Interface """ -from sqlalchemy import orm +import warnings +warnings.warn("The `corepos.db.office_trans_archive` module is deprecated! " + "Please use `corepos.db.office_arch` instead.", + DeprecationWarning, stacklevel=2) - -Session = orm.sessionmaker() +from corepos.db.office_arch import * diff --git a/corepos/db/office_trans_archive/model.py b/corepos/db/office_trans_archive/model.py index 8f4a079..e57d071 100644 --- a/corepos/db/office_trans_archive/model.py +++ b/corepos/db/office_trans_archive/model.py @@ -24,16 +24,9 @@ CORE POS Transaction Data Model """ -from sqlalchemy import orm +import warnings +warnings.warn("The `corepos.db.office_trans_archive.model` module is deprecated! " + "Please use `corepos.db.office_arch.model` instead.", + DeprecationWarning, stacklevel=2) -from corepos.db.office_trans.model import TransactionDetailBase - - -Base = orm.declarative_base() - - -class TransactionDetail(TransactionDetailBase, Base): - """ - Represents a POS transaction detail record. - """ - __tablename__ = 'bigArchive' +from corepos.db.office_arch.model import * From 7171531dce6cd465c9a14f29f2437c829c21021d Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sat, 7 Oct 2023 18:58:17 -0500 Subject: [PATCH 49/95] Update changelog --- CHANGELOG.md | 4 ++++ corepos/_version.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 56b70c3..247807d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to pyCOREPOS will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [0.1.17] - 2023-10-07 +### Changed +- Rename module to `corepos.db.office_arch`. + ## [0.1.16] - 2023-09-15 ### Changed - Add model for `office_op.Tender`. diff --git a/corepos/_version.py b/corepos/_version.py index 05c3021..9a17824 100644 --- a/corepos/_version.py +++ b/corepos/_version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8; -*- -__version__ = '0.1.16' +__version__ = '0.1.17' From 3a3fba19e461a9acbf75410c25bb333494674c2d Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Wed, 11 Oct 2023 18:36:12 -0500 Subject: [PATCH 50/95] Fix the `Department.tax_rate` relationship whoops i mistook `dept_tax` for a boolean previously --- corepos/db/office_op/model.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/corepos/db/office_op/model.py b/corepos/db/office_op/model.py index f2a6884..c99f6a8 100644 --- a/corepos/db/office_op/model.py +++ b/corepos/db/office_op/model.py @@ -225,12 +225,18 @@ class Department(Base): Represents a department within the organization. """ __tablename__ = 'departments' + __table_args__ = ( + sa.ForeignKeyConstraint(['dept_tax'], ['taxrates.id']), + ) number = sa.Column('dept_no', sa.SmallInteger(), primary_key=True, autoincrement=False, nullable=False) name = sa.Column('dept_name', sa.String(length=30), nullable=True) - tax = sa.Column('dept_tax', sa.Boolean(), nullable=True) + tax_rate_id = sa.Column('dept_tax', sa.SmallInteger(), nullable=True) + tax_rate = orm.relationship('TaxRate') + # TODO: deprecate / remove this + tax = orm.synonym('tax_rate_id') food_stampable = sa.Column('dept_fs', sa.Boolean(), nullable=True) From 1597c163c6f40dfd97887b8e78560186f4e16cb0 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Thu, 12 Oct 2023 10:34:13 -0500 Subject: [PATCH 51/95] Let `MemberInfo.dates` be an object, not a list --- corepos/db/office_op/model.py | 1 + 1 file changed, 1 insertion(+) diff --git a/corepos/db/office_op/model.py b/corepos/db/office_op/model.py index c99f6a8..f6787bf 100644 --- a/corepos/db/office_op/model.py +++ b/corepos/db/office_op/model.py @@ -1292,6 +1292,7 @@ class MemberInfo(Base): primaryjoin='MemberDate.card_number == MemberInfo.card_number', foreign_keys='MemberDate.card_number', cascade='all, delete-orphan', + uselist=False, doc=""" List of date records for the member. """, From 67dd9777ba827806cf95afd5bb47a7ce05923e70 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Thu, 12 Oct 2023 10:38:44 -0500 Subject: [PATCH 52/95] Update changelog --- CHANGELOG.md | 5 +++++ corepos/_version.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 247807d..8e0fbd0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ All notable changes to pyCOREPOS will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [0.1.18] - 2023-10-12 +### Changed +- Fix the `Department.tax_rate` relationship. +- Let `MemberInfo.dates` be an object, not a list. + ## [0.1.17] - 2023-10-07 ### Changed - Rename module to `corepos.db.office_arch`. diff --git a/corepos/_version.py b/corepos/_version.py index 9a17824..a6dbf47 100644 --- a/corepos/_version.py +++ b/corepos/_version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8; -*- -__version__ = '0.1.17' +__version__ = '0.1.18' From f217f00a8f409cc1a29ab965da3d79f383a4da43 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Fri, 20 Oct 2023 14:31:13 -0500 Subject: [PATCH 53/95] Fix data types for tax, voided in `dtransactions` --- corepos/db/office_trans/model.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/corepos/db/office_trans/model.py b/corepos/db/office_trans/model.py index b203240..ce5304b 100644 --- a/corepos/db/office_trans/model.py +++ b/corepos/db/office_trans/model.py @@ -120,7 +120,8 @@ class TransactionDetailBase(object): reg_price = sa.Column('regPrice', sa.Numeric(precision=10, scale=2), nullable=True) - tax = sa.Column(sa.Boolean(), nullable=True) + tax = sa.Column(sa.SmallInteger(), nullable=True) + tax_rate_id = orm.synonym('tax') food_stamp = sa.Column('foodstamp', sa.Boolean(), nullable=True) @@ -132,7 +133,7 @@ class TransactionDetailBase(object): discount_type = sa.Column('discounttype', sa.Integer(), nullable=True) - voided = sa.Column(sa.Boolean(), nullable=True) + voided = sa.Column(sa.Integer(), nullable=True) percent_discount = sa.Column('percentDiscount', sa.Integer(), nullable=True) From 909d75796b40b7b1e527ef621c733799aeb9f9d4 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Fri, 20 Oct 2023 19:03:20 -0500 Subject: [PATCH 54/95] Fix synonym for `dtransactions.tax` --- corepos/db/office_trans/model.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/corepos/db/office_trans/model.py b/corepos/db/office_trans/model.py index ce5304b..2848af5 100644 --- a/corepos/db/office_trans/model.py +++ b/corepos/db/office_trans/model.py @@ -26,6 +26,7 @@ CORE POS Transaction Data Model import sqlalchemy as sa from sqlalchemy import orm +from sqlalchemy.ext.declarative import declared_attr Base = orm.declarative_base() @@ -121,7 +122,10 @@ class TransactionDetailBase(object): reg_price = sa.Column('regPrice', sa.Numeric(precision=10, scale=2), nullable=True) tax = sa.Column(sa.SmallInteger(), nullable=True) - tax_rate_id = orm.synonym('tax') + + @declared_attr + def tax_rate_id(self): + return orm.synonym('tax') food_stamp = sa.Column('foodstamp', sa.Boolean(), nullable=True) From 7cd89029ac1d5461d026f1d4e70ecb498a26aa6f Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Wed, 1 Nov 2023 08:15:37 -0500 Subject: [PATCH 55/95] Update changelog --- CHANGELOG.md | 5 +++++ corepos/_version.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e0fbd0..a52aee7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ All notable changes to pyCOREPOS will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [0.1.19] - 2023-11-01 +### Changed +- Fix data types for tax, voided in `dtransactions`. +- Fix synonym for `dtransactions.tax`. + ## [0.1.18] - 2023-10-12 ### Changed - Fix the `Department.tax_rate` relationship. diff --git a/corepos/_version.py b/corepos/_version.py index a6dbf47..560cfb5 100644 --- a/corepos/_version.py +++ b/corepos/_version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8; -*- -__version__ = '0.1.18' +__version__ = '0.1.19' From df6f0d97937272b4ae6f07eabba6262b4b0367b8 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Thu, 16 May 2024 19:13:35 -0500 Subject: [PATCH 56/95] Add enum for CORE (Office) DB types for use with typer commands --- corepos/enum.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/corepos/enum.py b/corepos/enum.py index 55619d8..28e33ea 100644 --- a/corepos/enum.py +++ b/corepos/enum.py @@ -2,7 +2,7 @@ ################################################################################ # # pyCOREPOS -- Python Interface to CORE POS -# Copyright © 2018-2019 Lance Edgar +# Copyright © 2018-2024 Lance Edgar # # This file is part of pyCOREPOS. # @@ -24,12 +24,14 @@ CORE POS enumeration constants """ -from __future__ import unicode_literals, absolute_import +from collections import OrderedDict +from enum import Enum -try: - from collections import OrderedDict -except ImportError: - from ordereddict import OrderedDict + +class CoreDbType(str, Enum): + office_op = 'office_op' + office_trans = 'office_trans' + office_arch = 'office_arch' BATCH_DISCOUNT_TYPE_PRICE_CHANGE = 0 From 777b198e4441aa3ca88b92453a1b445012bf2441 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Wed, 29 May 2024 10:09:15 -0500 Subject: [PATCH 57/95] Update changelog --- CHANGELOG.md | 4 ++++ corepos/_version.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a52aee7..f9feb94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to pyCOREPOS will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [0.1.20] - 2024-05-29 +### Changed +- Add enum for CORE (Office) DB types. + ## [0.1.19] - 2023-11-01 ### Changed - Fix data types for tax, voided in `dtransactions`. diff --git a/corepos/_version.py b/corepos/_version.py index 560cfb5..0275ddd 100644 --- a/corepos/_version.py +++ b/corepos/_version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8; -*- -__version__ = '0.1.19' +__version__ = '0.1.20' From f80d03daaa301f2b98a482945e0bfa6726dbe571 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Wed, 29 May 2024 10:10:25 -0500 Subject: [PATCH 58/95] Fix default dist filename for release task not sure why this fix was needed, did setuptools behavior change? --- tasks.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tasks.py b/tasks.py index a9b9fc3..9156d43 100644 --- a/tasks.py +++ b/tasks.py @@ -2,7 +2,7 @@ ################################################################################ # # pyCOREPOS -- Python Interface to CORE POS -# Copyright © 2018-2020 Lance Edgar +# Copyright © 2018-2024 Lance Edgar # # This file is part of pyCOREPOS. # @@ -35,10 +35,10 @@ exec(open(os.path.join(here, 'corepos', '_version.py')).read()) @task -def release(ctx): +def release(c): """ Release a new version of 'pyCOREPOS'. """ shutil.rmtree('pyCOREPOS.egg-info') - ctx.run('python setup.py sdist --formats=gztar') - ctx.run('twine upload dist/pyCOREPOS-{}.tar.gz'.format(__version__)) + c.run('python setup.py sdist --formats=gztar') + c.run('twine upload dist/pycorepos-{}.tar.gz'.format(__version__)) From 5cf8d2ac05917f73001f9cdfd32fcfc74305cd56 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Mon, 10 Jun 2024 15:55:14 -0500 Subject: [PATCH 59/95] feat: switch from setup.cfg to pyproject.toml + hatchling --- .gitignore | 3 +++ corepos/_version.py | 5 ++++- pyproject.toml | 50 +++++++++++++++++++++++++++++++++++++++++++++ setup.cfg | 33 ------------------------------ setup.py | 26 ----------------------- tasks.py | 13 +++++++++++- 6 files changed, 69 insertions(+), 61 deletions(-) create mode 100644 pyproject.toml delete mode 100644 setup.cfg delete mode 100644 setup.py diff --git a/.gitignore b/.gitignore index 9a52e5b..07ddefb 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ +*~ +*.pyc +dist/ pyCOREPOS.egg-info/ diff --git a/corepos/_version.py b/corepos/_version.py index 0275ddd..555fee3 100644 --- a/corepos/_version.py +++ b/corepos/_version.py @@ -1,3 +1,6 @@ # -*- coding: utf-8; -*- -__version__ = '0.1.20' +from importlib.metadata import version + + +__version__ = version('pyCOREPOS') diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..4701f62 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,50 @@ + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + + +[project] +name = "pyCOREPOS" +version = "0.1.20" +description = "Python Interface to CORE POS" +readme = "README.rst" +authors = [{name = "Lance Edgar", email = "lance@edbob.org"}] +license = {text = "GNU GPL v3+"} +classifiers = [ + "Development Status :: 3 - Alpha", + "Environment :: Console", + "Environment :: Web Environment", + "Intended Audience :: Developers", + "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", + "Natural Language :: English", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Topic :: Office/Business", + "Topic :: Software Development :: Libraries :: Python Modules", +] + +dependencies = [ + "mysql-connector-python", + "requests", + "six", + "SQLAlchemy>=1.4", +] + + +[project.urls] +Homepage = "https://rattailproject.org" +Repository = "https://kallithea.rattailproject.org/rattail-project/pycorepos" +Issues = "https://redmine.rattailproject.org/projects/corepos-integration/issues" +Changelog = "https://kallithea.rattailproject.org/rattail-project/pycorepos/files/master/CHANGELOG.md" + + +[tool.commitizen] +version_provider = "pep621" +tag_format = "v$version" +update_changelog_on_bump = true + + +[tool.hatch.build.targets.wheel] +packages = ["corepos"] diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 728f179..0000000 --- a/setup.cfg +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: utf-8; -*- - -[metadata] -name = pyCOREPOS -version = attr: corepos.__version__ -author = Lance Edgar -author_email = lance@edbob.org -url = https://rattailproject.org/ -license = GNU GPL v3 -description = Python Interface to CORE POS -long_description = file: README.rst -classifiers = - Development Status :: 3 - Alpha - Environment :: Console - Environment :: Web Environment - Intended Audience :: Developers - License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+) - Natural Language :: English - Operating System :: OS Independent - Programming Language :: Python - Programming Language :: Python :: 3 - Topic :: Office/Business - Topic :: Software Development :: Libraries :: Python Modules - - -[options] -install_requires = - mysql-connector-python - requests - six - SQLAlchemy>=1.4 - -packages = find: diff --git a/setup.py b/setup.py deleted file mode 100644 index a02ef90..0000000 --- a/setup.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf-8; -*- -################################################################################ -# -# pyCOREPOS -- Python Interface to CORE POS -# Copyright © 2018-2023 Lance Edgar -# -# This file is part of pyCOREPOS. -# -# pyCOREPOS 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. -# -# pyCOREPOS 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 -# pyCOREPOS. If not, see . -# -################################################################################ - -from setuptools import setup - -setup() diff --git a/tasks.py b/tasks.py index 9156d43..caf7777 100644 --- a/tasks.py +++ b/tasks.py @@ -25,13 +25,24 @@ Tasks for 'pyCOREPOS' package """ import os +import re import shutil from invoke import task here = os.path.abspath(os.path.dirname(__file__)) -exec(open(os.path.join(here, 'corepos', '_version.py')).read()) +__version__ = None +pattern = re.compile(r'^version = "(\d+\.\d+\.\d+)"$') +with open(os.path.join(here, 'pyproject.toml'), 'rt') as f: + for line in f: + line = line.rstrip('\n') + match = pattern.match(line) + if match: + __version__ = match.group(1) + break +if not __version__: + raise RuntimeError("could not parse version!") @task From 04948fb840e19d31a16d144a00256cf6c9229221 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Mon, 10 Jun 2024 15:55:51 -0500 Subject: [PATCH 60/95] =?UTF-8?q?bump:=20version=200.1.20=20=E2=86=92=200.?= =?UTF-8?q?2.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 6 ++++++ pyproject.toml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f9feb94..d40d286 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to pyCOREPOS will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## v0.2.0 (2024-06-10) + +### Feat + +- switch from setup.cfg to pyproject.toml + hatchling + ## [0.1.20] - 2024-05-29 ### Changed - Add enum for CORE (Office) DB types. diff --git a/pyproject.toml b/pyproject.toml index 4701f62..2f3c924 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "hatchling.build" [project] name = "pyCOREPOS" -version = "0.1.20" +version = "0.2.0" description = "Python Interface to CORE POS" readme = "README.rst" authors = [{name = "Lance Edgar", email = "lance@edbob.org"}] From 40e647d5c8c8d756b29d460e37125e9dbbf7e74e Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Mon, 10 Jun 2024 15:57:09 -0500 Subject: [PATCH 61/95] build: use newer convention when building for release --- tasks.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tasks.py b/tasks.py index caf7777..6946a7f 100644 --- a/tasks.py +++ b/tasks.py @@ -50,6 +50,7 @@ def release(c): """ Release a new version of 'pyCOREPOS'. """ - shutil.rmtree('pyCOREPOS.egg-info') - c.run('python setup.py sdist --formats=gztar') + if os.path.exists('pyCOREPOS.egg-info'): + shutil.rmtree('pyCOREPOS.egg-info') + c.run('python -m build --sdist') c.run('twine upload dist/pycorepos-{}.tar.gz'.format(__version__)) From 3b54dbd0686c5afe30fe7780665f27aacd0bd5c3 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Fri, 14 Jun 2024 19:48:40 -0500 Subject: [PATCH 62/95] docs: use more specific project homepage url --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 2f3c924..b5c6fb0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,7 +34,7 @@ dependencies = [ [project.urls] -Homepage = "https://rattailproject.org" +Homepage = "https://redmine.rattailproject.org/projects/corepos-integration" Repository = "https://kallithea.rattailproject.org/rattail-project/pycorepos" Issues = "https://redmine.rattailproject.org/projects/corepos-integration/issues" Changelog = "https://kallithea.rattailproject.org/rattail-project/pycorepos/files/master/CHANGELOG.md" From e9638c73a4c7c8ac832eeb72b1731bb422998d13 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Mon, 1 Jul 2024 16:32:39 -0500 Subject: [PATCH 63/95] fix: remove dependency for `six` package --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b5c6fb0..1e356fd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,6 @@ classifiers = [ dependencies = [ "mysql-connector-python", "requests", - "six", "SQLAlchemy>=1.4", ] From 66cf108b3f1f52c3bb2ac965abd924a35250739d Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Thu, 4 Jul 2024 13:18:45 -0500 Subject: [PATCH 64/95] fix: remove `Change` data model since it isn't actually part of CORE, and is now handled in other ways with regard to datasync --- corepos/db/office_op/model.py | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/corepos/db/office_op/model.py b/corepos/db/office_op/model.py index f6787bf..d5da2f7 100644 --- a/corepos/db/office_op/model.py +++ b/corepos/db/office_op/model.py @@ -2,7 +2,7 @@ ################################################################################ # # pyCOREPOS -- Python Interface to CORE POS -# Copyright © 2018-2023 Lance Edgar +# Copyright © 2018-2024 Lance Edgar # # This file is part of pyCOREPOS. # @@ -54,23 +54,6 @@ class StringableDateTime(sa.TypeDecorator): raise NotImplementedError -class Change(Base): - """ - Represents a changed (or deleted) record, which is pending synchronization - to another system(s). - - .. note:: - This table may or may not be installed to a given CORE Office Op DB. Its - presence is required if Rattail datasync needs to "watch" the DB. - """ - __tablename__ = 'datasync_changes' - - id = sa.Column(sa.Integer(), nullable=False, primary_key=True) - object_type = sa.Column(sa.String(length=255), nullable=False) - object_key = sa.Column(sa.String(length=255), nullable=False) - deleted = sa.Column(sa.Boolean(), nullable=False, default=False) - - class Parameter(Base): """ Represents a "parameter" value. From bcff5605559c64a83197c413ff754c94177ce0ee Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Thu, 4 Jul 2024 18:27:50 -0500 Subject: [PATCH 65/95] fix: add API methods, `get_employees()` and `get_employee()` --- corepos/api.py | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/corepos/api.py b/corepos/api.py index 97ad8c7..a24b906 100644 --- a/corepos/api.py +++ b/corepos/api.py @@ -2,7 +2,7 @@ ################################################################################ # # pyCOREPOS -- Python Interface to CORE POS -# Copyright © 2018-2023 Lance Edgar +# Copyright © 2018-2024 Lance Edgar # # This file is part of pyCOREPOS. # @@ -198,6 +198,40 @@ class CoreWebAPI(object): if result: return result + def get_employees(self, **columns): + """ + Fetch some or all of Employee records from CORE. + + :returns: A (potentially empty) list of employee dict records. + """ + params = { + 'entity': 'Employees', + 'submethod': 'get', + 'columns': columns, + } + response = self.post(params) + result = self.parse_response(response) + return [json.loads(rec) for rec in result] + + def get_employee(self, emp_no, **columns): + """ + Fetch an existing Employee record from CORE. + + :returns: Either a employee dict record, or ``None``. + """ + columns['emp_no'] = emp_no + params = { + 'entity': 'Employees', + 'submethod': 'get', + 'columns': columns, + } + response = self.post(params) + result = self.parse_response(response) + if result: + if len(result) > 1: + log.warning("CORE API returned %s employee results", len(result)) + return json.loads(result[0]) + def get_stores(self, **columns): """ Fetch some or all of Store records from CORE. From 2df38854204d32b3a34c4d96e8e86b5a6fdc53bd Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Thu, 4 Jul 2024 23:59:49 -0500 Subject: [PATCH 66/95] =?UTF-8?q?bump:=20version=200.2.0=20=E2=86=92=200.2?= =?UTF-8?q?.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 8 ++++++++ pyproject.toml | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d40d286..e97814c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ All notable changes to pyCOREPOS will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## v0.2.1 (2024-07-04) + +### Fix + +- add API methods, `get_employees()` and `get_employee()` +- remove `Change` data model +- remove dependency for `six` package + ## v0.2.0 (2024-06-10) ### Feat diff --git a/pyproject.toml b/pyproject.toml index 1e356fd..29dc012 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "hatchling.build" [project] name = "pyCOREPOS" -version = "0.2.0" +version = "0.2.1" description = "Python Interface to CORE POS" readme = "README.rst" authors = [{name = "Lance Edgar", email = "lance@edbob.org"}] From 27a46ed18f72cc721f65861408dc0905205368de Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Tue, 6 Aug 2024 10:38:01 -0500 Subject: [PATCH 67/95] feat: add model for `CustomReceiptLine` (`op.customReceipt`) --- corepos/db/office_op/model.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/corepos/db/office_op/model.py b/corepos/db/office_op/model.py index d5da2f7..33c8313 100644 --- a/corepos/db/office_op/model.py +++ b/corepos/db/office_op/model.py @@ -1572,6 +1572,20 @@ class Tender(Base): return self.tender_name or '' +class CustomReceiptLine(Base): + """ + Represents a "text string" line for a custom receipt. + """ + __tablename__ = 'customReceipt' + + sequence = sa.Column('seq', sa.Integer(), primary_key=True, autoincrement=True, nullable=False) + type = sa.Column(sa.String(length=20), primary_key=True, autoincrement=False, nullable=False) + text = sa.Column(sa.String(length=80), nullable=True) + + def __str__(self): + return self.text or "" + + class BatchType(Base): """ Represents the definition of a batch type. From b5b29cdcf1f0a5c5c04b7b70430cf7cc37623be4 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Tue, 6 Aug 2024 11:34:07 -0500 Subject: [PATCH 68/95] feat: add model for `MemberContactPreference` (`op.memContactPrefs`) --- corepos/db/office_op/model.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/corepos/db/office_op/model.py b/corepos/db/office_op/model.py index 33c8313..5f6fc3d 100644 --- a/corepos/db/office_op/model.py +++ b/corepos/db/office_op/model.py @@ -1378,7 +1378,7 @@ class MemberDate(Base): class MemberContact(Base): """ - Contact preferences for members + Member contacts """ __tablename__ = 'memContact' @@ -1405,6 +1405,19 @@ class MemberContact(Base): return str(self.preference) +class MemberContactPreference(Base): + """ + Member contact preferences + """ + __tablename__ = 'memContactPrefs' + + id = sa.Column('pref_id', sa.Integer(), primary_key=True, autoincrement=False, nullable=False) + description = sa.Column('pref_description', sa.String(length=50), nullable=True) + + def __str__(self): + return self.description or "" + + class MemberBarcode(Base): """ Additional barcode for a member. From b425095a8da82f065e0c7c25de24f67c3af0b2b4 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Tue, 6 Aug 2024 23:21:26 -0500 Subject: [PATCH 69/95] =?UTF-8?q?bump:=20version=200.2.1=20=E2=86=92=200.3?= =?UTF-8?q?.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 7 +++++++ pyproject.toml | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e97814c..f6e7048 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ All notable changes to pyCOREPOS will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## v0.3.0 (2024-08-06) + +### Feat + +- add model for `MemberContactPreference` (`op.memContactPrefs`) +- add model for `CustomReceiptLine` (`op.customReceipt`) + ## v0.2.1 (2024-07-04) ### Fix diff --git a/pyproject.toml b/pyproject.toml index 29dc012..58ee2a2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "hatchling.build" [project] name = "pyCOREPOS" -version = "0.2.1" +version = "0.3.0" description = "Python Interface to CORE POS" readme = "README.rst" authors = [{name = "Lance Edgar", email = "lance@edbob.org"}] From b44e5a3d6062fbbc1a7ee009022c3e380f918c8f Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Fri, 13 Sep 2024 18:49:14 -0500 Subject: [PATCH 70/95] docs: use markdown for readme file --- README.md | 5 +++++ README.rst | 7 ------- pyproject.toml | 2 +- 3 files changed, 6 insertions(+), 8 deletions(-) create mode 100644 README.md delete mode 100644 README.rst diff --git a/README.md b/README.md new file mode 100644 index 0000000..dd69797 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ + +# pyCOREPOS + +A Python interface to the [CORE POS](https://github.com/CORE-POS) +system. diff --git a/README.rst b/README.rst deleted file mode 100644 index 0b34d2f..0000000 --- a/README.rst +++ /dev/null @@ -1,7 +0,0 @@ - -pyCOREPOS -========= - -A Python interface to the `CORE POS`_ system. - -.. _CORE POS: https://github.com/CORE-POS diff --git a/pyproject.toml b/pyproject.toml index 58ee2a2..de0f54b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ build-backend = "hatchling.build" name = "pyCOREPOS" version = "0.3.0" description = "Python Interface to CORE POS" -readme = "README.rst" +readme = "README.md" authors = [{name = "Lance Edgar", email = "lance@edbob.org"}] license = {text = "GNU GPL v3+"} classifiers = [ From ed48f9134a841756a455143adccc269b807f5592 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sat, 14 Sep 2024 10:43:39 -0500 Subject: [PATCH 71/95] docs: update project links to forgejo --- pyproject.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index de0f54b..869d6fb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,10 +33,10 @@ dependencies = [ [project.urls] -Homepage = "https://redmine.rattailproject.org/projects/corepos-integration" -Repository = "https://kallithea.rattailproject.org/rattail-project/pycorepos" -Issues = "https://redmine.rattailproject.org/projects/corepos-integration/issues" -Changelog = "https://kallithea.rattailproject.org/rattail-project/pycorepos/files/master/CHANGELOG.md" +Homepage = "https://forgejo.wuttaproject.org/rattail/pycorepos" +Repository = "https://forgejo.wuttaproject.org/rattail/pycorepos" +Issues = "https://forgejo.wuttaproject.org/rattail/pycorepos/issues" +Changelog = "https://forgejo.wuttaproject.org/rattail/pycorepos/src/branch/master/CHANGELOG.md" [tool.commitizen] From 3bb01f23979db35065276d91b0bc058d113d7c19 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Tue, 17 Dec 2024 16:45:11 -0600 Subject: [PATCH 72/95] fix: add `wicable`, `active` columns for Department model --- corepos/db/office_op/model.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/corepos/db/office_op/model.py b/corepos/db/office_op/model.py index 5f6fc3d..8590375 100644 --- a/corepos/db/office_op/model.py +++ b/corepos/db/office_op/model.py @@ -223,6 +223,10 @@ class Department(Base): food_stampable = sa.Column('dept_fs', sa.Boolean(), nullable=True) + wicable = sa.Column('dept_wicable', sa.SmallInteger(), nullable=True) + + active = sa.Column(sa.Boolean(), default=True) + limit = sa.Column('dept_limit', sa.Float(), nullable=True) minimum = sa.Column('dept_minimum', sa.Float(), nullable=True) From 310a261b48d93701199b6326aec4be3d277f3809 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Tue, 17 Dec 2024 16:45:36 -0600 Subject: [PATCH 73/95] =?UTF-8?q?bump:=20version=200.3.0=20=E2=86=92=200.3?= =?UTF-8?q?.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 6 ++++++ pyproject.toml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f6e7048..6c20cbc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to pyCOREPOS will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## v0.3.1 (2024-12-17) + +### Fix + +- add `wicable`, `active` columns for Department model + ## v0.3.0 (2024-08-06) ### Feat diff --git a/pyproject.toml b/pyproject.toml index 869d6fb..0bbc23a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "hatchling.build" [project] name = "pyCOREPOS" -version = "0.3.0" +version = "0.3.1" description = "Python Interface to CORE POS" readme = "README.md" authors = [{name = "Lance Edgar", email = "lance@edbob.org"}] From cb6ed15eb8c162a9aa9a4c4644a8105a6d8fdc6b Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sat, 11 Jan 2025 21:51:28 -0600 Subject: [PATCH 74/95] fix: add model for `MasterSuperDepartment` --- corepos/db/office_op/model.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/corepos/db/office_op/model.py b/corepos/db/office_op/model.py index 8590375..12688af 100644 --- a/corepos/db/office_op/model.py +++ b/corepos/db/office_op/model.py @@ -169,6 +169,25 @@ class Store(Base): return self.description or "" +class MasterSuperDepartment(Base): + """ + A department may belong to more than one superdepartment, but has + one "master" superdepartment. This avoids duplicating rows in + some reports. By convention, a department's "master" + superdepartment is the one with the lowest superID. + """ + __tablename__ = 'MasterSuperDepts' + + super_id = sa.Column('superID', sa.Integer(), primary_key=True, autoincrement=False, nullable=False) + + department_id = sa.Column('dept_ID', sa.Integer(), primary_key=True, autoincrement=False, nullable=False) + + super_name = sa.Column(sa.String(length=50), nullable=True) + + def __str__(self): + return self.super_name or "" + + class SuperDepartment(Base): """ Represents a "super" (parent/child) department mapping. From 464f107a88ed996dc5d627206f55a3d41fa453cf Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sat, 11 Jan 2025 21:52:22 -0600 Subject: [PATCH 75/95] fix: add `MemberType.ignore_sales` column --- corepos/db/office_op/model.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/corepos/db/office_op/model.py b/corepos/db/office_op/model.py index 12688af..5e9a34a 100644 --- a/corepos/db/office_op/model.py +++ b/corepos/db/office_op/model.py @@ -1037,6 +1037,9 @@ class MemberType(Base): ssi = sa.Column(sa.Boolean(), nullable=True) + ignoreSales = sa.Column(sa.Boolean(), nullable=True, default=False) + ignore_sales = orm.synonym('ignoreSales') + # TODO: this was apparently added "recently" - isn't present in all DBs # (need to figure out how to conditionally include it in model?) # sales_code = sa.Column('salesCode', sa.Integer(), nullable=True) From 47c23a65aee63fa4018d3b66d6610a837e5aea8a Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sat, 11 Jan 2025 21:55:54 -0600 Subject: [PATCH 76/95] fix: add base class for all transaction tables, views also rename some classes; add `dlogBig` for archive view --- corepos/db/office_arch/model.py | 19 +++++++++++++---- corepos/db/office_trans/model.py | 36 +++++++++++++++++++++++++------- 2 files changed, 43 insertions(+), 12 deletions(-) diff --git a/corepos/db/office_arch/model.py b/corepos/db/office_arch/model.py index bf01a4d..49b0bbd 100644 --- a/corepos/db/office_arch/model.py +++ b/corepos/db/office_arch/model.py @@ -2,7 +2,7 @@ ################################################################################ # # pyCOREPOS -- Python Interface to CORE POS -# Copyright © 2018-2023 Lance Edgar +# Copyright © 2018-2024 Lance Edgar # # This file is part of pyCOREPOS. # @@ -26,14 +26,25 @@ CORE Office "arch" data model from sqlalchemy import orm -from corepos.db.office_trans.model import TransactionDetailBase +from corepos.db.office_trans.model import DTransactionBase, DLogBase Base = orm.declarative_base() -class TransactionDetail(TransactionDetailBase, Base): +class BigArchive(DTransactionBase, Base): """ - Represents a POS transaction detail record. + Represents a record from ``bigArchive`` table. """ __tablename__ = 'bigArchive' + + +# TODO: deprecate / remove this +TransactionDetail = BigArchive + + +class DLogBig(DLogBase, Base): + """ + Represents a record from ``dlogBig`` view. + """ + __tablename__ = 'dlogBig' diff --git a/corepos/db/office_trans/model.py b/corepos/db/office_trans/model.py index 2848af5..b4659cd 100644 --- a/corepos/db/office_trans/model.py +++ b/corepos/db/office_trans/model.py @@ -2,7 +2,7 @@ ################################################################################ # # pyCOREPOS -- Python Interface to CORE POS -# Copyright © 2018-2023 Lance Edgar +# Copyright © 2018-2024 Lance Edgar # # This file is part of pyCOREPOS. # @@ -68,7 +68,7 @@ class EquityLiveBalance(Base): start_date = sa.Column('startdate', sa.DateTime(), nullable=True) -class TransactionDetailBase(object): +class TransactionDetailBase: """ Represents a POS transaction detail record. """ @@ -86,10 +86,12 @@ class TransactionDetailBase(object): transaction_number = sa.Column('trans_no', sa.Integer(), nullable=True) transaction_type = sa.Column('trans_type', sa.String(length=1), nullable=True) transaction_subtype = sa.Column('trans_subtype', sa.String(length=2), nullable=True) - transaction_status = sa.Column('trans_status', sa.String(length=1), nullable=True) - # timestamps - date_time = sa.Column('datetime', sa.DateTime(), nullable=True) + trans_status = sa.Column(sa.String(length=1), nullable=True) + + @declared_attr + def transaction_status(self): + return orm.synonym('trans_status') # cashier employee_number = sa.Column('emp_no', sa.Integer(), nullable=True) @@ -115,7 +117,11 @@ class TransactionDetailBase(object): cost = sa.Column(sa.Numeric(precision=10, scale=2), nullable=True) - unit_price = sa.Column('unitPrice', sa.Numeric(precision=10, scale=2), nullable=True) + unitPrice = sa.Column('unitPrice', sa.Numeric(precision=10, scale=2), nullable=True) + + @declared_attr + def unit_price(self): + return orm.synonym('unitPrice') total = sa.Column(sa.Numeric(precision=10, scale=2), nullable=True) @@ -161,8 +167,22 @@ class TransactionDetailBase(object): return self.description or '' -class TransactionDetail(TransactionDetailBase, Base): +class DTransactionBase(TransactionDetailBase): + + date_time = sa.Column('datetime', sa.DateTime(), nullable=True) + + +class DLogBase(TransactionDetailBase): + + date_time = sa.Column('tdate', sa.DateTime(), nullable=True) + + +class DTransaction(DTransactionBase, Base): """ - Represents a POS transaction detail record. + Represents a record from ``dtransactions`` table. """ __tablename__ = 'dtransactions' + + +# TODO: deprecate / remove this +TransactionDetail = DTransaction From 465e565f7b975219647dfbe548187584cdd74192 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sat, 11 Jan 2025 22:01:38 -0600 Subject: [PATCH 77/95] =?UTF-8?q?bump:=20version=200.3.1=20=E2=86=92=200.3?= =?UTF-8?q?.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 8 ++++++++ pyproject.toml | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c20cbc..ad1845a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ All notable changes to pyCOREPOS will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## v0.3.2 (2025-01-11) + +### Fix + +- add base class for all transaction tables, views +- add `MemberType.ignore_sales` column +- add model for `MasterSuperDepartment` + ## v0.3.1 (2024-12-17) ### Fix diff --git a/pyproject.toml b/pyproject.toml index 0bbc23a..80bf76b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "hatchling.build" [project] name = "pyCOREPOS" -version = "0.3.1" +version = "0.3.2" description = "Python Interface to CORE POS" readme = "README.md" authors = [{name = "Lance Edgar", email = "lance@edbob.org"}] From 023d826d316ccd87035a8fe1384a662712224894 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Mon, 13 Jan 2025 12:57:41 -0600 Subject: [PATCH 78/95] fix: remove `autoincrement` option for composite PK fields --- corepos/db/office_op/model.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/corepos/db/office_op/model.py b/corepos/db/office_op/model.py index 5e9a34a..16bbc57 100644 --- a/corepos/db/office_op/model.py +++ b/corepos/db/office_op/model.py @@ -80,7 +80,9 @@ class TableSyncRule(Base): """ __tablename__ = 'TableSyncRules' - id = sa.Column('tableSyncRuleID', sa.Integer(), nullable=False, primary_key=True, autoincrement=True) + # nb. this should be autoincrement, but we can't do that + # automatically via sqlalchemy when PK is composite + id = sa.Column('tableSyncRuleID', sa.Integer(), nullable=False, primary_key=True) table_name = sa.Column('tableName', sa.String(length=255), nullable=False, primary_key=True) @@ -1617,8 +1619,11 @@ class CustomReceiptLine(Base): """ __tablename__ = 'customReceipt' - sequence = sa.Column('seq', sa.Integer(), primary_key=True, autoincrement=True, nullable=False) - type = sa.Column(sa.String(length=20), primary_key=True, autoincrement=False, nullable=False) + # nb. this should be autoincrement, but we can't do that + # automatically via sqlalchemy when PK is composite + sequence = sa.Column('seq', sa.Integer(), primary_key=True, nullable=False) + + type = sa.Column(sa.String(length=20), primary_key=True, nullable=False) text = sa.Column(sa.String(length=80), nullable=True) def __str__(self): From e37cf88cd91ace61ba27b0bdea53bdfca4488d38 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Mon, 13 Jan 2025 13:31:21 -0600 Subject: [PATCH 79/95] =?UTF-8?q?bump:=20version=200.3.2=20=E2=86=92=200.3?= =?UTF-8?q?.3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 6 ++++++ pyproject.toml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad1845a..ccc62cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to pyCOREPOS will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## v0.3.3 (2025-01-13) + +### Fix + +- remove `autoincrement` option for composite PK fields + ## v0.3.2 (2025-01-11) ### Fix diff --git a/pyproject.toml b/pyproject.toml index 80bf76b..a59e830 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "hatchling.build" [project] name = "pyCOREPOS" -version = "0.3.2" +version = "0.3.3" description = "Python Interface to CORE POS" readme = "README.md" authors = [{name = "Lance Edgar", email = "lance@edbob.org"}] From ab56a35acc3c08ee6d9992dee59086d8654c987c Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Tue, 14 Jan 2025 20:20:41 -0600 Subject: [PATCH 80/95] fix: add more enums for batch discount type, editor UI also fix column type for editor UI --- corepos/db/office_op/model.py | 2 +- corepos/enum.py | 23 +++++++++++++++++++++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/corepos/db/office_op/model.py b/corepos/db/office_op/model.py index 16bbc57..68627c0 100644 --- a/corepos/db/office_op/model.py +++ b/corepos/db/office_op/model.py @@ -1646,7 +1646,7 @@ class BatchType(Base): special_order_eligible = sa.Column('specialOrderEligible', sa.Boolean(), nullable=True, default=True) - editor_ui = sa.Column('editorUI', sa.Boolean(), nullable=True, default=True) + editor_ui = sa.Column('editorUI', sa.SmallInteger(), nullable=True, default=True) allow_single_store = sa.Column('allowSingleStore', sa.Boolean(), nullable=True, default=False) diff --git a/corepos/enum.py b/corepos/enum.py index 28e33ea..93780c3 100644 --- a/corepos/enum.py +++ b/corepos/enum.py @@ -34,14 +34,33 @@ class CoreDbType(str, Enum): office_arch = 'office_arch' +BATCH_DISCOUNT_TYPE_TRACKING = -1 BATCH_DISCOUNT_TYPE_PRICE_CHANGE = 0 BATCH_DISCOUNT_TYPE_SALE_EVERYONE = 1 BATCH_DISCOUNT_TYPE_SALE_RESTRICTED = 2 +BATCH_DISCOUNT_TYPE_SLIDING_PERCENT = 3 +BATCH_DISCOUNT_TYPE_SLIDING_AMOUNT = 5 BATCH_DISCOUNT_TYPE = OrderedDict([ - (BATCH_DISCOUNT_TYPE_PRICE_CHANGE, "Price Change"), + (BATCH_DISCOUNT_TYPE_PRICE_CHANGE, "None (Change regular price)"), (BATCH_DISCOUNT_TYPE_SALE_EVERYONE, "Sale for everyone"), - (BATCH_DISCOUNT_TYPE_SALE_RESTRICTED, "Member/Owner only sale"), + (BATCH_DISCOUNT_TYPE_SALE_RESTRICTED, "Sale for Members"), + (BATCH_DISCOUNT_TYPE_SLIDING_PERCENT, "Sliding % Off for Members"), + (BATCH_DISCOUNT_TYPE_SLIDING_AMOUNT, "Sliding $ Off for Members"), + (BATCH_DISCOUNT_TYPE_TRACKING, "Tracking (does not change any prices)"), +]) + + +BATCH_EDITOR_UI_STANDARD = 1 +BATCH_EDITOR_UI_PAIRED_SALE = 2 +BATCH_EDITOR_UI_PARTIAL = 3 +BATCH_EDITOR_UI_TRACKING = 4 + +BATCH_EDITOR_UI = OrderedDict([ + (BATCH_EDITOR_UI_STANDARD, "Standard"), + (BATCH_EDITOR_UI_PAIRED_SALE, "Paired Sale"), + (BATCH_EDITOR_UI_PARTIAL, "Partial"), + (BATCH_EDITOR_UI_TRACKING, "Tracking"), ]) From 01852ceecc437ac7b5f5c429d7d1f5487c40daa4 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Wed, 15 Jan 2025 08:45:59 -0600 Subject: [PATCH 81/95] fix: misc. cleanup for sales batch models --- corepos/db/office_op/model.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/corepos/db/office_op/model.py b/corepos/db/office_op/model.py index 68627c0..6087f67 100644 --- a/corepos/db/office_op/model.py +++ b/corepos/db/office_op/model.py @@ -1636,7 +1636,9 @@ class BatchType(Base): """ __tablename__ = 'batchType' - id = sa.Column('batchTypeID', sa.Integer(), primary_key=True, autoincrement=False, nullable=False) + # nb. this is *not* autoincrement for some reason; must + # calculate new ID manually based on max existing + id = sa.Column('batchTypeID', sa.Integer(), nullable=False, primary_key=True, autoincrement=False) description = sa.Column('typeDesc', sa.String(length=50), nullable=True) @@ -1661,9 +1663,6 @@ class Batch(Base): Represents a batch. """ __tablename__ = 'batches' - __table_args__ = ( - sa.ForeignKeyConstraint(['batchType'], ['batchType.batchTypeID']), - ) id = sa.Column('batchID', sa.Integer(), primary_key=True, autoincrement=True, nullable=False) @@ -1673,7 +1672,8 @@ class Batch(Base): name = sa.Column('batchName', sa.String(length=80), nullable=True) - batch_type_id = sa.Column('batchType', sa.Integer(), nullable=True) + batch_type_id = sa.Column('batchType', sa.Integer(), + sa.ForeignKey('batchType.batchTypeID'), nullable=True) batch_type = orm.relationship(BatchType) discount_type = sa.Column('discountType', sa.Integer(), nullable=True) @@ -1695,16 +1695,18 @@ class BatchItem(Base): Represents a batch "list" item. """ __tablename__ = 'batchList' - __table_args__ = ( - sa.ForeignKeyConstraint(['batchID'], ['batches.batchID']), - ) id = sa.Column('listID', sa.Integer(), primary_key=True, autoincrement=True, nullable=False) - batch_id = sa.Column('batchID', sa.Integer(), nullable=True) + batch_id = sa.Column('batchID', sa.Integer(), + sa.ForeignKey('batches.batchID'), nullable=True) batch = orm.relationship(Batch, backref=orm.backref('items')) upc = sa.Column(sa.String(length=13), nullable=True) + product = orm.relationship( + Product, + primaryjoin=Product.upc == upc, + foreign_keys=[upc]) sale_price = sa.Column('salePrice', sa.Numeric(precision=9, scale=3), nullable=True) From b8ca60b508a2e3689ee7061be0d2c15841f07a64 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Wed, 15 Jan 2025 08:51:40 -0600 Subject: [PATCH 82/95] =?UTF-8?q?bump:=20version=200.3.3=20=E2=86=92=200.3?= =?UTF-8?q?.4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 7 +++++++ pyproject.toml | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ccc62cb..39309fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ All notable changes to pyCOREPOS will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## v0.3.4 (2025-01-15) + +### Fix + +- misc. cleanup for sales batch models +- add more enums for batch discount type, editor UI + ## v0.3.3 (2025-01-13) ### Fix diff --git a/pyproject.toml b/pyproject.toml index a59e830..143843f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "hatchling.build" [project] name = "pyCOREPOS" -version = "0.3.3" +version = "0.3.4" description = "Python Interface to CORE POS" readme = "README.md" authors = [{name = "Lance Edgar", email = "lance@edbob.org"}] From 7aaa35dac70b059f2d43eb1b05d98a8b2963e51d Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Wed, 15 Jan 2025 10:59:14 -0600 Subject: [PATCH 83/95] fix: add workaround to avoid missing schema columns more columns will need to be added to this workaround i'm sure, but this takes care of a couple small ones --- corepos/db/office_op/model.py | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/corepos/db/office_op/model.py b/corepos/db/office_op/model.py index 6087f67..4d79e88 100644 --- a/corepos/db/office_op/model.py +++ b/corepos/db/office_op/model.py @@ -1039,12 +1039,11 @@ class MemberType(Base): ssi = sa.Column(sa.Boolean(), nullable=True) - ignoreSales = sa.Column(sa.Boolean(), nullable=True, default=False) - ignore_sales = orm.synonym('ignoreSales') + # nb. this must be added explicitly if DB is new enough + #ignore_sales = sa.Column('ignoreSales', sa.Boolean(), nullable=True, default=False) - # TODO: this was apparently added "recently" - isn't present in all DBs - # (need to figure out how to conditionally include it in model?) - # sales_code = sa.Column('salesCode', sa.Integer(), nullable=True) + # nb. this must be added explicitly if DB is new enough + #sales_code = sa.Column('salesCode', sa.Integer(), nullable=True) def __str__(self): return self.description or "" @@ -1842,3 +1841,22 @@ class PurchaseOrderNote(Base): def __str__(self): return self.notes or "" + + +# the rest of this is a workaround to deal with the fact that some +# CORE databases have columns which others do not. i had assumed that +# all would be more or less the same but not so in practice. so if +# your DB *does* have these columns, you must invoke the function +# below in order to merge them into your schema. you should do this +# on app startup and they'll be available normally from then on. + +RUNTIME = {'added_latest_columns': False} + +def use_latest_columns(): + if RUNTIME['added_latest_columns']: + return + + MemberType.ignore_sales = sa.Column('ignoreSales', sa.Boolean(), nullable=True, default=False) + MemberType.sales_code = sa.Column('salesCode', sa.Integer(), nullable=True) + + RUNTIME['added_latest_columns'] = True From 6c1fc9a8031a7dc8c8efd682349922f3915bf4e2 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Wed, 15 Jan 2025 11:00:22 -0600 Subject: [PATCH 84/95] =?UTF-8?q?bump:=20version=200.3.4=20=E2=86=92=200.3?= =?UTF-8?q?.5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 6 ++++++ pyproject.toml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39309fe..bba74ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to pyCOREPOS will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## v0.3.5 (2025-01-15) + +### Fix + +- add workaround to avoid missing schema columns + ## v0.3.4 (2025-01-15) ### Fix diff --git a/pyproject.toml b/pyproject.toml index 143843f..2aaef92 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "hatchling.build" [project] name = "pyCOREPOS" -version = "0.3.4" +version = "0.3.5" description = "Python Interface to CORE POS" readme = "README.md" authors = [{name = "Lance Edgar", email = "lance@edbob.org"}] From d21346bbffb618594d630e5230589c69839c2891 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Wed, 15 Jan 2025 14:48:18 -0600 Subject: [PATCH 85/95] fix: fix ordering of name columns for MemberInfo so they show up correctly by default e.g. in UI --- corepos/db/office_op/model.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/corepos/db/office_op/model.py b/corepos/db/office_op/model.py index 4d79e88..ff3f493 100644 --- a/corepos/db/office_op/model.py +++ b/corepos/db/office_op/model.py @@ -1259,14 +1259,14 @@ class MemberInfo(Base): card_number = sa.Column('card_no', sa.Integer(), primary_key=True, autoincrement=False, nullable=False) - last_name = sa.Column(sa.String(length=30), nullable=True) - first_name = sa.Column(sa.String(length=30), nullable=True) - other_last_name = sa.Column('othlast_name', sa.String(length=30), nullable=True) + last_name = sa.Column(sa.String(length=30), nullable=True) other_first_name = sa.Column('othfirst_name', sa.String(length=30), nullable=True) + other_last_name = sa.Column('othlast_name', sa.String(length=30), nullable=True) + street = sa.Column(sa.String(length=255), nullable=True) city = sa.Column(sa.String(length=20), nullable=True) From 97fb0b28cba228ef435b954a5a9784e0f547f212 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Fri, 24 Jan 2025 19:14:13 -0600 Subject: [PATCH 86/95] feat: add common base class for `dtransactions` and similar models more work to be done here i'm sure, but this should hopefully be a conservative change in the right direction --- corepos/db/common/__init__.py | 0 corepos/db/common/trans.py | 123 ++++++++++++++++++++++++++++++ corepos/db/lane_trans/__init__.py | 30 ++++++++ corepos/db/lane_trans/model.py | 62 +++++++++++++++ corepos/db/office_arch/model.py | 21 ++++- corepos/db/office_trans/model.py | 111 ++------------------------- 6 files changed, 239 insertions(+), 108 deletions(-) create mode 100644 corepos/db/common/__init__.py create mode 100644 corepos/db/common/trans.py create mode 100644 corepos/db/lane_trans/__init__.py create mode 100644 corepos/db/lane_trans/model.py diff --git a/corepos/db/common/__init__.py b/corepos/db/common/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/corepos/db/common/trans.py b/corepos/db/common/trans.py new file mode 100644 index 0000000..2b4e503 --- /dev/null +++ b/corepos/db/common/trans.py @@ -0,0 +1,123 @@ +# -*- coding: utf-8; -*- +################################################################################ +# +# pyCOREPOS -- Python Interface to CORE POS +# Copyright © 2018-2025 Lance Edgar +# +# This file is part of pyCOREPOS. +# +# pyCOREPOS 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. +# +# pyCOREPOS 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 +# pyCOREPOS. If not, see . +# +################################################################################ +""" +Common schema for transaction data models +""" + +import sqlalchemy as sa +from sqlalchemy import orm +from sqlalchemy.ext.declarative import declared_attr + + +class TransactionDetailBase: + """ + Base class for POS transaction detail models, shared by Office + + Lane. + """ + + # register + register_number = sa.Column('register_no', sa.Integer(), nullable=True) + + # txn + transaction_id = sa.Column('trans_id', sa.Integer(), nullable=True) + transaction_number = sa.Column('trans_no', sa.Integer(), nullable=True) + transaction_type = sa.Column('trans_type', sa.String(length=1), nullable=True) + transaction_subtype = sa.Column('trans_subtype', sa.String(length=2), nullable=True) + trans_status = sa.Column(sa.String(length=1), nullable=True) + + @declared_attr + def transaction_status(self): + return orm.synonym('trans_status') + + # cashier + employee_number = sa.Column('emp_no', sa.Integer(), nullable=True) + + # customer + card_number = sa.Column('card_no', sa.Integer(), nullable=True) + member_type = sa.Column('memType', sa.Integer(), nullable=True) + staff = sa.Column(sa.Boolean(), nullable=True) + + ############################## + # remainder is "line item" ... + ############################## + + upc = sa.Column(sa.String(length=13), nullable=True) + + department_number = sa.Column('department', sa.Integer(), nullable=True) + + description = sa.Column(sa.String(length=30), nullable=True) + + quantity = sa.Column(sa.Float(), nullable=True) + + scale = sa.Column(sa.Boolean(), nullable=True, default=False) + + cost = sa.Column(sa.Numeric(precision=10, scale=2), nullable=True) + + unitPrice = sa.Column('unitPrice', sa.Numeric(precision=10, scale=2), nullable=True) + + @declared_attr + def unit_price(self): + return orm.synonym('unitPrice') + + total = sa.Column(sa.Numeric(precision=10, scale=2), nullable=True) + + reg_price = sa.Column('regPrice', sa.Numeric(precision=10, scale=2), nullable=True) + + tax = sa.Column(sa.SmallInteger(), nullable=True) + + @declared_attr + def tax_rate_id(self): + return orm.synonym('tax') + + food_stamp = sa.Column('foodstamp', sa.Boolean(), nullable=True) + + discount = sa.Column(sa.Numeric(precision=10, scale=2), nullable=True) + + member_discount = sa.Column('memDiscount', sa.Numeric(precision=10, scale=2), nullable=True) + + discountable = sa.Column(sa.Boolean(), nullable=True) + + discount_type = sa.Column('discounttype', sa.Integer(), nullable=True) + + voided = sa.Column(sa.Integer(), nullable=True) + + percent_discount = sa.Column('percentDiscount', sa.Integer(), nullable=True) + + item_quantity = sa.Column('ItemQtty', sa.Float(), nullable=True) + + volume_discount_type = sa.Column('volDiscType', sa.Integer(), nullable=True) + + volume = sa.Column(sa.Integer(), nullable=True) + + volume_special = sa.Column('VolSpecial', sa.Numeric(precision=10, scale=2), nullable=True) + + mix_match = sa.Column('mixMatch', sa.String(length=13), nullable=True) + + matched = sa.Column(sa.Boolean(), nullable=True) + + num_flag = sa.Column('numflag', sa.Integer(), nullable=True, default=0) + + char_flag = sa.Column('charflag', sa.String(length=2), nullable=True) + + def __str__(self): + return self.description or '' diff --git a/corepos/db/lane_trans/__init__.py b/corepos/db/lane_trans/__init__.py new file mode 100644 index 0000000..8e8c706 --- /dev/null +++ b/corepos/db/lane_trans/__init__.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8; -*- +################################################################################ +# +# pyCOREPOS -- Python Interface to CORE POS +# Copyright © 2018-2025 Lance Edgar +# +# This file is part of pyCOREPOS. +# +# pyCOREPOS 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. +# +# pyCOREPOS 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 +# pyCOREPOS. If not, see . +# +################################################################################ +""" +Lane Transaction Database +""" + +from sqlalchemy import orm + + +Session = orm.sessionmaker() diff --git a/corepos/db/lane_trans/model.py b/corepos/db/lane_trans/model.py new file mode 100644 index 0000000..b656d8f --- /dev/null +++ b/corepos/db/lane_trans/model.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8; -*- +################################################################################ +# +# pyCOREPOS -- Python Interface to CORE POS +# Copyright © 2018-2025 Lance Edgar +# +# This file is part of pyCOREPOS. +# +# pyCOREPOS 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. +# +# pyCOREPOS 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 +# pyCOREPOS. If not, see . +# +################################################################################ +""" +Data model for CORE POS "lane_trans" DB +""" + +import sqlalchemy as sa +from sqlalchemy import orm + +from corepos.db.common import trans as common + + +Base = orm.declarative_base() + + +class DTransactionBase(common.TransactionDetailBase): + """ + Base class for ``dtransactions`` and similar models. + """ + pos_row_id = sa.Column(sa.Integer(), primary_key=True, nullable=False) + + store_id = sa.Column(sa.Integer(), nullable=True, default=0) + date_time = sa.Column('datetime', sa.DateTime(), nullable=True) + + +class DTransaction(DTransactionBase, Base): + """ + Represents a record from ``dtransactions`` table. + """ + __tablename__ = 'dtransactions' + + +class LocalTempTrans(common.TransactionDetailBase, Base): + """ + Represents a record from ``localtemptrans`` table. + """ + __tablename__ = 'localtemptrans' + __table_args__ = ( + sa.PrimaryKeyConstraint('trans_id'), + ) + + date_time = sa.Column('datetime', sa.DateTime(), nullable=True) diff --git a/corepos/db/office_arch/model.py b/corepos/db/office_arch/model.py index 49b0bbd..bc5838f 100644 --- a/corepos/db/office_arch/model.py +++ b/corepos/db/office_arch/model.py @@ -2,7 +2,7 @@ ################################################################################ # # pyCOREPOS -- Python Interface to CORE POS -# Copyright © 2018-2024 Lance Edgar +# Copyright © 2018-2025 Lance Edgar # # This file is part of pyCOREPOS. # @@ -24,9 +24,11 @@ CORE Office "arch" data model """ +import sqlalchemy as sa from sqlalchemy import orm -from corepos.db.office_trans.model import DTransactionBase, DLogBase +from corepos.db.common import trans as common +from corepos.db.office_trans.model import DTransactionBase Base = orm.declarative_base() @@ -34,7 +36,7 @@ Base = orm.declarative_base() class BigArchive(DTransactionBase, Base): """ - Represents a record from ``bigArchive`` table. + Data model for ``bigArchive`` table. """ __tablename__ = 'bigArchive' @@ -43,8 +45,19 @@ class BigArchive(DTransactionBase, Base): TransactionDetail = BigArchive +class DLogBase(common.TransactionDetailBase): + """ + Base class for ``dlogBig`` and similar models. + """ + store_row_id = sa.Column(sa.Integer(), primary_key=True, nullable=False) + + store_id = sa.Column(sa.Integer(), nullable=True, default=0) + pos_row_id = sa.Column(sa.Integer(), nullable=True) + date_time = sa.Column('tdate', sa.DateTime(), nullable=True) + + class DLogBig(DLogBase, Base): """ - Represents a record from ``dlogBig`` view. + Data model for ``dlogBig`` view. """ __tablename__ = 'dlogBig' diff --git a/corepos/db/office_trans/model.py b/corepos/db/office_trans/model.py index b4659cd..c2b0959 100644 --- a/corepos/db/office_trans/model.py +++ b/corepos/db/office_trans/model.py @@ -2,7 +2,7 @@ ################################################################################ # # pyCOREPOS -- Python Interface to CORE POS -# Copyright © 2018-2024 Lance Edgar +# Copyright © 2018-2025 Lance Edgar # # This file is part of pyCOREPOS. # @@ -26,7 +26,8 @@ CORE POS Transaction Data Model import sqlalchemy as sa from sqlalchemy import orm -from sqlalchemy.ext.declarative import declared_attr + +from corepos.db.common import trans as common Base = orm.declarative_base() @@ -68,118 +69,20 @@ class EquityLiveBalance(Base): start_date = sa.Column('startdate', sa.DateTime(), nullable=True) -class TransactionDetailBase: +class DTransactionBase(common.TransactionDetailBase): """ - Represents a POS transaction detail record. + Base class for ``dtransactions`` and similar models. """ - - # store store_row_id = sa.Column(sa.Integer(), primary_key=True, nullable=False) - store_id = sa.Column(sa.Integer(), nullable=True, default=0) - # register - register_number = sa.Column('register_no', sa.Integer(), nullable=True) pos_row_id = sa.Column(sa.Integer(), nullable=True) - - # txn - transaction_id = sa.Column('trans_id', sa.Integer(), nullable=True) - transaction_number = sa.Column('trans_no', sa.Integer(), nullable=True) - transaction_type = sa.Column('trans_type', sa.String(length=1), nullable=True) - transaction_subtype = sa.Column('trans_subtype', sa.String(length=2), nullable=True) - - trans_status = sa.Column(sa.String(length=1), nullable=True) - - @declared_attr - def transaction_status(self): - return orm.synonym('trans_status') - - # cashier - employee_number = sa.Column('emp_no', sa.Integer(), nullable=True) - - # customer - card_number = sa.Column('card_no', sa.Integer(), nullable=True) - member_type = sa.Column('memType', sa.Integer(), nullable=True) - staff = sa.Column(sa.Boolean(), nullable=True) - - ############################## - # remainder is "line item" ... - ############################## - - upc = sa.Column(sa.String(length=13), nullable=True) - - department_number = sa.Column('department', sa.Integer(), nullable=True) - - description = sa.Column(sa.String(length=30), nullable=True) - - quantity = sa.Column(sa.Float(), nullable=True) - - scale = sa.Column(sa.Boolean(), nullable=True, default=False) - - cost = sa.Column(sa.Numeric(precision=10, scale=2), nullable=True) - - unitPrice = sa.Column('unitPrice', sa.Numeric(precision=10, scale=2), nullable=True) - - @declared_attr - def unit_price(self): - return orm.synonym('unitPrice') - - total = sa.Column(sa.Numeric(precision=10, scale=2), nullable=True) - - reg_price = sa.Column('regPrice', sa.Numeric(precision=10, scale=2), nullable=True) - - tax = sa.Column(sa.SmallInteger(), nullable=True) - - @declared_attr - def tax_rate_id(self): - return orm.synonym('tax') - - food_stamp = sa.Column('foodstamp', sa.Boolean(), nullable=True) - - discount = sa.Column(sa.Numeric(precision=10, scale=2), nullable=True) - - member_discount = sa.Column('memDiscount', sa.Numeric(precision=10, scale=2), nullable=True) - - discountable = sa.Column(sa.Boolean(), nullable=True) - - discount_type = sa.Column('discounttype', sa.Integer(), nullable=True) - - voided = sa.Column(sa.Integer(), nullable=True) - - percent_discount = sa.Column('percentDiscount', sa.Integer(), nullable=True) - - item_quantity = sa.Column('ItemQtty', sa.Float(), nullable=True) - - volume_discount_type = sa.Column('volDiscType', sa.Integer(), nullable=True) - - volume = sa.Column(sa.Integer(), nullable=True) - - volume_special = sa.Column('VolSpecial', sa.Numeric(precision=10, scale=2), nullable=True) - - mix_match = sa.Column('mixMatch', sa.String(length=13), nullable=True) - - matched = sa.Column(sa.Boolean(), nullable=True) - - num_flag = sa.Column('numflag', sa.Integer(), nullable=True, default=0) - - char_flag = sa.Column('charflag', sa.String(length=2), nullable=True) - - def __str__(self): - return self.description or '' - - -class DTransactionBase(TransactionDetailBase): - + store_id = sa.Column(sa.Integer(), nullable=True, default=0) date_time = sa.Column('datetime', sa.DateTime(), nullable=True) -class DLogBase(TransactionDetailBase): - - date_time = sa.Column('tdate', sa.DateTime(), nullable=True) - - class DTransaction(DTransactionBase, Base): """ - Represents a record from ``dtransactions`` table. + Data model for ``dtransactions`` table. """ __tablename__ = 'dtransactions' From c3b639390d1abfc3b13c0a5e4313b3161997de8f Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Fri, 24 Jan 2025 19:21:00 -0600 Subject: [PATCH 87/95] fix: add `Employee` model for lane_op with abstract common base schema --- corepos/db/common/op.py | 56 +++++++++++++++++++++++++++++++++++ corepos/db/lane_op/model.py | 11 ++++++- corepos/db/office_op/model.py | 31 ++++--------------- 3 files changed, 71 insertions(+), 27 deletions(-) create mode 100644 corepos/db/common/op.py diff --git a/corepos/db/common/op.py b/corepos/db/common/op.py new file mode 100644 index 0000000..7cf84fc --- /dev/null +++ b/corepos/db/common/op.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8; -*- +################################################################################ +# +# pyCOREPOS -- Python Interface to CORE POS +# Copyright © 2018-2025 Lance Edgar +# +# This file is part of pyCOREPOS. +# +# pyCOREPOS 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. +# +# pyCOREPOS 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 +# pyCOREPOS. If not, see . +# +################################################################################ +""" +Common schema for operational data models +""" + +import sqlalchemy as sa + + +class EmployeeBase: + """ + Base class for Employee models, shared by Office + Lane. + """ + number = sa.Column('emp_no', sa.SmallInteger(), nullable=False, + primary_key=True, autoincrement=False) + + cashier_password = sa.Column('CashierPassword', sa.String(length=50), nullable=True) + + admin_password = sa.Column('AdminPassword', sa.String(length=50), nullable=True) + + first_name = sa.Column('FirstName', sa.String(length=255), nullable=True) + + last_name = sa.Column('LastName', sa.String(length=255), nullable=True) + + job_title = sa.Column('JobTitle', sa.String(length=255), nullable=True) + + active = sa.Column('EmpActive', sa.Boolean(), nullable=True) + + frontend_security = sa.Column('frontendsecurity', sa.SmallInteger(), nullable=True) + + backend_security = sa.Column('backendsecurity', sa.SmallInteger(), nullable=True) + + birth_date = sa.Column('birthdate', sa.DateTime(), nullable=True) + + def __str__(self): + return ' '.join([self.first_name or '', self.last_name or '']).strip() diff --git a/corepos/db/lane_op/model.py b/corepos/db/lane_op/model.py index c7ed5b2..08da4f2 100644 --- a/corepos/db/lane_op/model.py +++ b/corepos/db/lane_op/model.py @@ -2,7 +2,7 @@ ################################################################################ # # pyCOREPOS -- Python Interface to CORE POS -# Copyright © 2018-2023 Lance Edgar +# Copyright © 2018-2025 Lance Edgar # # This file is part of pyCOREPOS. # @@ -27,10 +27,19 @@ Data model for CORE POS "lane_op" DB import sqlalchemy as sa from sqlalchemy import orm +from corepos.db.common import op as common + Base = orm.declarative_base() +class Employee(common.EmployeeBase, Base): + """ + Data model for ``employees`` table. + """ + __tablename__ = 'employees' + + class Department(Base): """ Represents a department within the organization. diff --git a/corepos/db/office_op/model.py b/corepos/db/office_op/model.py index ff3f493..67f405a 100644 --- a/corepos/db/office_op/model.py +++ b/corepos/db/office_op/model.py @@ -2,7 +2,7 @@ ################################################################################ # # pyCOREPOS -- Python Interface to CORE POS -# Copyright © 2018-2024 Lance Edgar +# Copyright © 2018-2025 Lance Edgar # # This file is part of pyCOREPOS. # @@ -31,6 +31,8 @@ import sqlalchemy as sa from sqlalchemy import orm from sqlalchemy.ext.associationproxy import association_proxy +from corepos.db.common import op as common + log = logging.getLogger(__name__) @@ -991,35 +993,12 @@ class ProductPhysicalLocation(Base): location = sa.Column(sa.SmallInteger(), nullable=True, default=0) -class Employee(Base): +class Employee(common.EmployeeBase, Base): """ - Represents an employee within the organization. + Data model for ``employees`` table. """ __tablename__ = 'employees' - number = sa.Column('emp_no', sa.SmallInteger(), primary_key=True, autoincrement=False, nullable=False) - - cashier_password = sa.Column('CashierPassword', sa.String(length=50), nullable=True) - - admin_password = sa.Column('AdminPassword', sa.String(length=50), nullable=True) - - first_name = sa.Column('FirstName', sa.String(length=255), nullable=True) - - last_name = sa.Column('LastName', sa.String(length=255), nullable=True) - - job_title = sa.Column('JobTitle', sa.String(length=255), nullable=True) - - active = sa.Column('EmpActive', sa.Boolean(), nullable=True) - - frontend_security = sa.Column('frontendsecurity', sa.SmallInteger(), nullable=True) - - backend_security = sa.Column('backendsecurity', sa.SmallInteger(), nullable=True) - - birth_date = sa.Column('birthdate', sa.DateTime(), nullable=True) - - def __str__(self): - return ' '.join([self.first_name or '', self.last_name or '']).strip() - class MemberType(Base): """ From 28cb23adc4a60cc7a3f9c37fa906281a45487f64 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Fri, 24 Jan 2025 19:59:18 -0600 Subject: [PATCH 88/95] =?UTF-8?q?bump:=20version=200.3.5=20=E2=86=92=200.4?= =?UTF-8?q?.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 11 +++++++++++ pyproject.toml | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bba74ba..ca4e4bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ All notable changes to pyCOREPOS will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## v0.4.0 (2025-01-24) + +### Feat + +- add common base class for `dtransactions` and similar models + +### Fix + +- add `Employee` model for lane_op +- fix ordering of name columns for MemberInfo + ## v0.3.5 (2025-01-15) ### Fix diff --git a/pyproject.toml b/pyproject.toml index 2aaef92..75ef20f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "hatchling.build" [project] name = "pyCOREPOS" -version = "0.3.5" +version = "0.4.0" description = "Python Interface to CORE POS" readme = "README.md" authors = [{name = "Lance Edgar", email = "lance@edbob.org"}] From 50351596acaced2b5b14fd4ad4f3488f9d09b111 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Fri, 24 Jan 2025 20:18:13 -0600 Subject: [PATCH 89/95] fix: add model for lane_trans `LocalTrans` --- corepos/db/lane_trans/model.py | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/corepos/db/lane_trans/model.py b/corepos/db/lane_trans/model.py index b656d8f..f2245f5 100644 --- a/corepos/db/lane_trans/model.py +++ b/corepos/db/lane_trans/model.py @@ -26,6 +26,7 @@ Data model for CORE POS "lane_trans" DB import sqlalchemy as sa from sqlalchemy import orm +from sqlalchemy.ext.declarative import declared_attr from corepos.db.common import trans as common @@ -45,18 +46,34 @@ class DTransactionBase(common.TransactionDetailBase): class DTransaction(DTransactionBase, Base): """ - Represents a record from ``dtransactions`` table. + Data model for ``dtransactions`` table. """ __tablename__ = 'dtransactions' -class LocalTempTrans(common.TransactionDetailBase, Base): +class LocalTransBase(common.TransactionDetailBase): """ - Represents a record from ``localtemptrans`` table. + Base class for ``localtrans`` and similar models. """ - __tablename__ = 'localtemptrans' - __table_args__ = ( - sa.PrimaryKeyConstraint('trans_id'), - ) + + @declared_attr + def __table_args__(self): + return ( + sa.PrimaryKeyConstraint('trans_id'), + ) date_time = sa.Column('datetime', sa.DateTime(), nullable=True) + + +class LocalTrans(LocalTransBase, Base): + """ + Data model for ``localtrans`` table. + """ + __tablename__ = 'localtrans' + + +class LocalTempTrans(LocalTransBase, Base): + """ + Data model for ``localtemptrans`` table. + """ + __tablename__ = 'localtemptrans' From 2fe089bd57b93388908a7b5adff3709ebde2e0fb Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sat, 25 Jan 2025 16:41:17 -0600 Subject: [PATCH 90/95] fix: add `Parameter` model for lane_op --- corepos/db/common/op.py | 18 ++++++++++++++++++ corepos/db/lane_op/model.py | 7 +++++++ corepos/db/office_op/model.py | 17 ++--------------- 3 files changed, 27 insertions(+), 15 deletions(-) diff --git a/corepos/db/common/op.py b/corepos/db/common/op.py index 7cf84fc..c0d40f5 100644 --- a/corepos/db/common/op.py +++ b/corepos/db/common/op.py @@ -27,6 +27,24 @@ Common schema for operational data models import sqlalchemy as sa +class ParameterBase: + """ + Base class for Parameter models, shared by Office + Lane. + """ + store_id = sa.Column(sa.SmallInteger(), primary_key=True, nullable=False) + + lane_id = sa.Column(sa.SmallInteger(), primary_key=True, nullable=False) + + param_key = sa.Column(sa.String(length=100), primary_key=True, nullable=False) + + param_value = sa.Column(sa.String(length=255), nullable=True) + + is_array = sa.Column(sa.Boolean(), nullable=True) + + def __str__(self): + return f"{self.store_id}-{self.lane_id} {self.param_key}" + + class EmployeeBase: """ Base class for Employee models, shared by Office + Lane. diff --git a/corepos/db/lane_op/model.py b/corepos/db/lane_op/model.py index 08da4f2..3074dd1 100644 --- a/corepos/db/lane_op/model.py +++ b/corepos/db/lane_op/model.py @@ -33,6 +33,13 @@ from corepos.db.common import op as common Base = orm.declarative_base() +class Parameter(common.ParameterBase, Base): + """ + Data model for ``parameters`` table. + """ + __tablename__ = 'parameters' + + class Employee(common.EmployeeBase, Base): """ Data model for ``employees`` table. diff --git a/corepos/db/office_op/model.py b/corepos/db/office_op/model.py index 67f405a..ad4b89b 100644 --- a/corepos/db/office_op/model.py +++ b/corepos/db/office_op/model.py @@ -56,25 +56,12 @@ class StringableDateTime(sa.TypeDecorator): raise NotImplementedError -class Parameter(Base): +class Parameter(common.ParameterBase, Base): """ - Represents a "parameter" value. + Data model for ``parameters`` table. """ __tablename__ = 'parameters' - store_id = sa.Column(sa.SmallInteger(), primary_key=True, nullable=False) - - lane_id = sa.Column(sa.SmallInteger(), primary_key=True, nullable=False) - - param_key = sa.Column(sa.String(length=100), primary_key=True, nullable=False) - - param_value = sa.Column(sa.String(length=255), nullable=True) - - is_array = sa.Column(sa.Boolean(), nullable=True) - - def __str__(self): - return "{}-{} {}".format(self.store_id, self.lane_id, self.param_key) - class TableSyncRule(Base): """ From a2a1d7faee837c0a0216693a635fe5f237ce6366 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sat, 25 Jan 2025 16:58:17 -0600 Subject: [PATCH 91/95] fix: define common base schema for Product model --- corepos/db/common/op.py | 99 +++++++++++++++++++++++++ corepos/db/lane_op/model.py | 134 +--------------------------------- corepos/db/office_op/model.py | 99 ++----------------------- 3 files changed, 109 insertions(+), 223 deletions(-) diff --git a/corepos/db/common/op.py b/corepos/db/common/op.py index c0d40f5..30ecc1c 100644 --- a/corepos/db/common/op.py +++ b/corepos/db/common/op.py @@ -72,3 +72,102 @@ class EmployeeBase: def __str__(self): return ' '.join([self.first_name or '', self.last_name or '']).strip() + + +class ProductBase: + """ + Base class for Product models, shared by Office + Lane. + """ + id = sa.Column(sa.Integer(), nullable=False, primary_key=True, autoincrement=True) + + upc = sa.Column(sa.String(length=13), nullable=True) + + description = sa.Column(sa.String(length=30), nullable=True) + + brand = sa.Column(sa.String(length=30), nullable=True) + + formatted_name = sa.Column(sa.String(length=30), nullable=True) + + normal_price = sa.Column(sa.Float(), nullable=True) + + price_method = sa.Column('pricemethod', sa.SmallInteger(), nullable=True) + + group_price = sa.Column('groupprice', sa.Float(), nullable=True) + + quantity = sa.Column(sa.SmallInteger(), nullable=True) + + special_price = sa.Column(sa.Float(), nullable=True) + + special_price_method = sa.Column('specialpricemethod', sa.SmallInteger(), nullable=True) + + special_group_price = sa.Column('specialgroupprice', sa.Float(), nullable=True) + + special_quantity = sa.Column('specialquantity', sa.SmallInteger(), nullable=True) + + special_limit = sa.Column(sa.SmallInteger(), nullable=True) + + start_date = sa.Column(sa.DateTime(), nullable=True) + + end_date = sa.Column(sa.DateTime(), nullable=True) + + department_number = sa.Column('department', sa.SmallInteger(), nullable=True) + + size = sa.Column(sa.String(length=9), nullable=True) + + tax_rate_id = sa.Column('tax', sa.SmallInteger(), nullable=True) + + foodstamp = sa.Column(sa.Boolean(), nullable=True) + + scale = sa.Column(sa.Boolean(), nullable=True) + + scale_price = sa.Column('scaleprice', sa.Float(), nullable=True) + + mix_match_code = sa.Column('mixmatchcode', sa.String(length=13), nullable=True) + + created = sa.Column(sa.DateTime(), nullable=True) + + modified = sa.Column(sa.DateTime(), nullable=True) + + tare_weight = sa.Column('tareweight', sa.Float(), nullable=True) + + discount = sa.Column(sa.SmallInteger(), nullable=True) + + discount_type = sa.Column('discounttype', sa.SmallInteger(), nullable=True) + + line_item_discountable = sa.Column(sa.Boolean(), nullable=True) + + unit_of_measure = sa.Column('unitofmeasure', sa.String(length=15), nullable=True) + + wicable = sa.Column(sa.SmallInteger(), nullable=True) + + quantity_enforced = sa.Column('qttyEnforced', sa.Boolean(), nullable=True) + + id_enforced = sa.Column('idEnforced', sa.SmallInteger(), nullable=True) + + cost = sa.Column(sa.Float(), nullable=True) + + special_cost = sa.Column(sa.Float(), nullable=True) + + received_cost = sa.Column(sa.Float(), nullable=True) + + in_use = sa.Column('inUse', sa.Boolean(), nullable=True) + + numflag = sa.Column(sa.Integer(), nullable=True) + + subdepartment_number = sa.Column('subdept', sa.SmallInteger(), nullable=True) + + deposit = sa.Column(sa.Float(), nullable=True) + + local = sa.Column(sa.Integer(), nullable=True, default=0) + + store_id = sa.Column(sa.SmallInteger(), nullable=True) + + default_vendor_id = sa.Column(sa.Integer(), nullable=True) + + current_origin_id = sa.Column(sa.Integer(), nullable=True) + + auto_par = sa.Column(sa.Float(), nullable=True, default=0) + + price_rule_id = sa.Column(sa.Integer(), nullable=True, default=0) + + last_sold = sa.Column(sa.DateTime(), nullable=True) diff --git a/corepos/db/lane_op/model.py b/corepos/db/lane_op/model.py index 3074dd1..456b1b8 100644 --- a/corepos/db/lane_op/model.py +++ b/corepos/db/lane_op/model.py @@ -88,141 +88,11 @@ class Department(Base): return self.name or "" -class Product(Base): +class Product(common.ProductBase, Base): """ - Represents a product, purchased and/or sold by the organization. + Data model for ``products`` table. """ __tablename__ = 'products' - # __table_args__ = ( - # sa.ForeignKeyConstraint(['department'], ['departments.dept_no']), - # sa.ForeignKeyConstraint(['subdept'], ['subdepts.subdept_no']), - # sa.ForeignKeyConstraint(['tax'], ['taxrates.id']), - # ) - - id = sa.Column(sa.Integer(), nullable=False, - primary_key=True, autoincrement=True) - - upc = sa.Column(sa.String(length=13), nullable=True) - - description = sa.Column(sa.String(length=30), nullable=True) - - brand = sa.Column(sa.String(length=30), nullable=True) - - formatted_name = sa.Column(sa.String(length=30), nullable=True) - - normal_price = sa.Column(sa.Float(), nullable=True) - - price_method = sa.Column('pricemethod', sa.SmallInteger(), nullable=True) - - group_price = sa.Column('groupprice', sa.Float(), nullable=True) - - quantity = sa.Column(sa.SmallInteger(), nullable=True) - - special_price = sa.Column(sa.Float(), nullable=True) - - special_price_method = sa.Column('specialpricemethod', sa.SmallInteger(), nullable=True) - - special_group_price = sa.Column('specialgroupprice', sa.Float(), nullable=True) - - special_quantity = sa.Column('specialquantity', sa.SmallInteger(), nullable=True) - - special_limit = sa.Column(sa.SmallInteger(), nullable=True) - - start_date = sa.Column(sa.DateTime(), nullable=True) - - end_date = sa.Column(sa.DateTime(), nullable=True) - - department_number = sa.Column('department', sa.SmallInteger(), nullable=True) - # department = orm.relationship( - # Department, - # primaryjoin=Department.number == department_number, - # foreign_keys=[department_number], - # doc=""" - # Reference to the :class:`Department` to which the product belongs. - # """) - - size = sa.Column(sa.String(length=9), nullable=True) - - tax_rate_id = sa.Column('tax', sa.SmallInteger(), nullable=True) - # tax_rate = orm.relationship(TaxRate) - - foodstamp = sa.Column(sa.Boolean(), nullable=True) - - scale = sa.Column(sa.Boolean(), nullable=True) - - scale_price = sa.Column('scaleprice', sa.Float(), nullable=True) - - mix_match_code = sa.Column('mixmatchcode', sa.String(length=13), nullable=True) - - created = sa.Column(sa.DateTime(), nullable=True) - - modified = sa.Column(sa.DateTime(), nullable=True) - - # 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 = sa.Column('tareweight', sa.Float(), nullable=True) - - discount = sa.Column(sa.SmallInteger(), nullable=True) - - discount_type = sa.Column('discounttype', sa.SmallInteger(), nullable=True) - - line_item_discountable = sa.Column(sa.Boolean(), nullable=True) - - unit_of_measure = sa.Column('unitofmeasure', sa.String(length=15), nullable=True) - - wicable = sa.Column(sa.SmallInteger(), nullable=True) - - quantity_enforced = sa.Column('qttyEnforced', sa.Boolean(), nullable=True) - - id_enforced = sa.Column('idEnforced', sa.SmallInteger(), nullable=True) - - cost = sa.Column(sa.Float(), nullable=True) - - special_cost = sa.Column(sa.Float(), nullable=True) - - received_cost = sa.Column(sa.Float(), nullable=True) - - in_use = sa.Column('inUse', sa.Boolean(), nullable=True) - - flags = sa.Column('numflag', sa.Integer(), nullable=True) - - subdepartment_number = sa.Column('subdept', sa.SmallInteger(), nullable=True) - # subdepartment = orm.relationship( - # Subdepartment, - # primaryjoin=Subdepartment.number == subdepartment_number, - # foreign_keys=[subdepartment_number], - # doc=""" - # Reference to the :class:`Subdepartment` to which the product belongs. - # """) - - deposit = sa.Column(sa.Float(), nullable=True) - - local = sa.Column(sa.Integer(), nullable=True, - default=0) # TODO: do we want a default here? - - store_id = sa.Column(sa.SmallInteger(), nullable=True) - - default_vendor_id = sa.Column(sa.Integer(), nullable=True) - # default_vendor = orm.relationship( - # Vendor, - # primaryjoin=Vendor.id == default_vendor_id, - # foreign_keys=[default_vendor_id], - # doc=""" - # Reference to the default :class:`Vendor` from which the product is obtained. - # """) - - current_origin_id = sa.Column(sa.Integer(), nullable=True) - - auto_par = sa.Column(sa.Float(), nullable=True, - default=0) # TODO: do we want a default here? - - price_rule_id = sa.Column(sa.Integer(), nullable=True) - - # TODO: some older DB's might not have this? guess we'll see - last_sold = sa.Column(sa.DateTime(), nullable=True) class CustomerClassic(Base): diff --git a/corepos/db/office_op/model.py b/corepos/db/office_op/model.py index ad4b89b..e374d37 100644 --- a/corepos/db/office_op/model.py +++ b/corepos/db/office_op/model.py @@ -536,7 +536,7 @@ class Origin(Base): return self.name or self.short_name or "" -class Product(Base): +class Product(common.ProductBase, Base): """ Represents a product, purchased and/or sold by the organization. """ @@ -547,120 +547,37 @@ class Product(Base): sa.ForeignKeyConstraint(['tax'], ['taxrates.id']), ) - id = sa.Column(sa.Integer(), primary_key=True, autoincrement=True, nullable=False) - - upc = sa.Column(sa.String(length=13), nullable=True) - - description = sa.Column(sa.String(length=30), nullable=True) - - brand = sa.Column(sa.String(length=30), nullable=True) - - formatted_name = sa.Column(sa.String(length=30), nullable=True) - - normal_price = sa.Column(sa.Float(), nullable=True) - - price_method = sa.Column('pricemethod', sa.SmallInteger(), nullable=True) - - group_price = sa.Column('groupprice', sa.Float(), nullable=True) - - quantity = sa.Column(sa.SmallInteger(), nullable=True) - - special_price = sa.Column(sa.Float(), nullable=True) - - special_price_method = sa.Column('specialpricemethod', sa.SmallInteger(), nullable=True) - - special_group_price = sa.Column('specialgroupprice', sa.Float(), nullable=True) - - special_quantity = sa.Column('specialquantity', sa.SmallInteger(), nullable=True) - - start_date = sa.Column(sa.DateTime(), nullable=True) - - end_date = sa.Column(sa.DateTime(), nullable=True) - - department_number = sa.Column('department', sa.SmallInteger(), nullable=True) department = orm.relationship( Department, - primaryjoin=Department.number == department_number, - foreign_keys=[department_number], + primaryjoin='Department.number == Product.department_number', + foreign_keys='Product.department_number', doc=""" Reference to the :class:`Department` to which the product belongs. """) - size = sa.Column(sa.String(length=9), nullable=True) - - tax_rate_id = sa.Column('tax', sa.SmallInteger(), nullable=True) tax_rate = orm.relationship(TaxRate) - foodstamp = sa.Column(sa.Boolean(), nullable=True) - - scale = sa.Column(sa.Boolean(), nullable=True) - - # TODO: yikes, did i just code this all wrong the first time? pretty sure - # this needs to change to a decimal column... - scale_price = sa.Column('scaleprice', sa.Boolean(), nullable=True) - # scale_price = sa.Column('scaleprice', sa.Numeric(precision=10, scale=2), nullable=True) - - mix_match_code = sa.Column('mixmatchcode', sa.String(length=13), nullable=True) - - created = sa.Column(StringableDateTime(), nullable=True) - - modified = sa.Column(sa.DateTime(), nullable=True) - # advertised = sa.Column(sa.Boolean(), nullable=True) - tare_weight = sa.Column('tareweight', sa.Float(), nullable=True) - - discount = sa.Column(sa.SmallInteger(), nullable=True) - - discount_type = sa.Column('discounttype', sa.SmallInteger(), nullable=True) - - line_item_discountable = sa.Column(sa.Boolean(), nullable=True) - - unit_of_measure = sa.Column('unitofmeasure', sa.String(length=15), nullable=True) - - wicable = sa.Column(sa.SmallInteger(), nullable=True) - - quantity_enforced = sa.Column('qttyEnforced', sa.Boolean(), nullable=True) - - id_enforced = sa.Column('idEnforced', sa.SmallInteger(), nullable=True) - - cost = sa.Column(sa.Float(), nullable=True) - - in_use = sa.Column('inUse', sa.Boolean(), nullable=True) - - flags = sa.Column('numflag', sa.Integer(), nullable=True) - - subdepartment_number = sa.Column('subdept', sa.SmallInteger(), nullable=True) subdepartment = orm.relationship( Subdepartment, - primaryjoin=Subdepartment.number == subdepartment_number, - foreign_keys=[subdepartment_number], + primaryjoin='Subdepartment.number == Product.subdepartment_number', + foreign_keys='Product.subdepartment_number', doc=""" Reference to the :class:`Subdepartment` to which the product belongs. """) - deposit = sa.Column(sa.Float(), nullable=True) - - local = sa.Column(sa.Integer(), nullable=True) - - store_id = sa.Column(sa.SmallInteger(), nullable=True) - - default_vendor_id = sa.Column(sa.Integer(), nullable=True) default_vendor = orm.relationship( Vendor, - primaryjoin=Vendor.id == default_vendor_id, - foreign_keys=[default_vendor_id], + primaryjoin='Vendor.id == Product.default_vendor_id', + foreign_keys='Product.default_vendor_id', doc=""" Reference to the default :class:`Vendor` from which the product is obtained. """) + # TODO: deprecate / remove this? vendor = orm.synonym('default_vendor') - current_origin_id = sa.Column(sa.Integer(), nullable=True) - - # TODO: some older DB's might not have this? guess we'll see - last_sold = sa.Column(sa.DateTime(), nullable=True) - like_code = association_proxy( '_like_code', 'like_code', creator=lambda lc: ProductLikeCode(like_code=lc), From 97a1396a543e052caf9bd24c5ad1837f9e68eb39 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sat, 25 Jan 2025 17:01:10 -0600 Subject: [PATCH 92/95] feat: use true column names for transaction data models as much as i kind of want to "rename" some of these for convenience, it seems safest here to just stick with true names to avoid confusion --- corepos/db/common/trans.py | 59 ++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 34 deletions(-) diff --git a/corepos/db/common/trans.py b/corepos/db/common/trans.py index 2b4e503..9ec5601 100644 --- a/corepos/db/common/trans.py +++ b/corepos/db/common/trans.py @@ -36,25 +36,21 @@ class TransactionDetailBase: """ # register - register_number = sa.Column('register_no', sa.Integer(), nullable=True) + register_no = sa.Column(sa.Integer(), nullable=True) # txn - transaction_id = sa.Column('trans_id', sa.Integer(), nullable=True) - transaction_number = sa.Column('trans_no', sa.Integer(), nullable=True) - transaction_type = sa.Column('trans_type', sa.String(length=1), nullable=True) - transaction_subtype = sa.Column('trans_subtype', sa.String(length=2), nullable=True) + trans_id = sa.Column(sa.Integer(), nullable=True) + trans_no = sa.Column(sa.Integer(), nullable=True) + trans_type = sa.Column(sa.String(length=1), nullable=True) + trans_subtype = sa.Column(sa.String(length=2), nullable=True) trans_status = sa.Column(sa.String(length=1), nullable=True) - @declared_attr - def transaction_status(self): - return orm.synonym('trans_status') - # cashier - employee_number = sa.Column('emp_no', sa.Integer(), nullable=True) + emp_no = sa.Column(sa.Integer(), nullable=True) # customer - card_number = sa.Column('card_no', sa.Integer(), nullable=True) - member_type = sa.Column('memType', sa.Integer(), nullable=True) + card_no = sa.Column(sa.Integer(), nullable=True) + memType = sa.Column(sa.Integer(), nullable=True) staff = sa.Column(sa.Boolean(), nullable=True) ############################## @@ -63,7 +59,7 @@ class TransactionDetailBase: upc = sa.Column(sa.String(length=13), nullable=True) - department_number = sa.Column('department', sa.Integer(), nullable=True) + department = sa.Column(sa.Integer(), nullable=True) description = sa.Column(sa.String(length=30), nullable=True) @@ -73,51 +69,46 @@ class TransactionDetailBase: cost = sa.Column(sa.Numeric(precision=10, scale=2), nullable=True) - unitPrice = sa.Column('unitPrice', sa.Numeric(precision=10, scale=2), nullable=True) - - @declared_attr - def unit_price(self): - return orm.synonym('unitPrice') + unitPrice = sa.Column(sa.Numeric(precision=10, scale=2), nullable=True) total = sa.Column(sa.Numeric(precision=10, scale=2), nullable=True) - reg_price = sa.Column('regPrice', sa.Numeric(precision=10, scale=2), nullable=True) + regPrice = sa.Column(sa.Numeric(precision=10, scale=2), nullable=True) tax = sa.Column(sa.SmallInteger(), nullable=True) - @declared_attr - def tax_rate_id(self): - return orm.synonym('tax') - - food_stamp = sa.Column('foodstamp', sa.Boolean(), nullable=True) + foodstamp = sa.Column(sa.Boolean(), nullable=True) discount = sa.Column(sa.Numeric(precision=10, scale=2), nullable=True) - member_discount = sa.Column('memDiscount', sa.Numeric(precision=10, scale=2), nullable=True) + memDiscount = sa.Column(sa.Numeric(precision=10, scale=2), nullable=True) discountable = sa.Column(sa.Boolean(), nullable=True) - discount_type = sa.Column('discounttype', sa.Integer(), nullable=True) + discounttype = sa.Column(sa.Integer(), nullable=True) voided = sa.Column(sa.Integer(), nullable=True) - percent_discount = sa.Column('percentDiscount', sa.Integer(), nullable=True) + percentDiscount = sa.Column(sa.Integer(), nullable=True) - item_quantity = sa.Column('ItemQtty', sa.Float(), nullable=True) + ItemQtty = sa.Column(sa.Float(), nullable=True) - volume_discount_type = sa.Column('volDiscType', sa.Integer(), nullable=True) + volDiscType = sa.Column(sa.Integer(), nullable=True) volume = sa.Column(sa.Integer(), nullable=True) - volume_special = sa.Column('VolSpecial', sa.Numeric(precision=10, scale=2), nullable=True) + VolSpecial = sa.Column(sa.Numeric(precision=10, scale=2), nullable=True) - mix_match = sa.Column('mixMatch', sa.String(length=13), nullable=True) + mixMatch = sa.Column(sa.String(length=13), nullable=True) matched = sa.Column(sa.Boolean(), nullable=True) - num_flag = sa.Column('numflag', sa.Integer(), nullable=True, default=0) + numflag = sa.Column(sa.Integer(), nullable=True, default=0) - char_flag = sa.Column('charflag', sa.String(length=2), nullable=True) + charflag = sa.Column(sa.String(length=2), nullable=True) def __str__(self): - return self.description or '' + txnid = '-'.join([str(val) for val in [self.register_no, + self.trans_no, + self.trans_id]]) + return f"{txnid} {self.description or ''}" From 8359e5692e62e5c289f90871b48be160d141c8c3 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sat, 1 Feb 2025 15:19:12 -0600 Subject: [PATCH 93/95] =?UTF-8?q?bump:=20version=200.4.0=20=E2=86=92=200.5?= =?UTF-8?q?.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 12 ++++++++++++ pyproject.toml | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca4e4bb..0c46d8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ All notable changes to pyCOREPOS will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## v0.5.0 (2025-02-01) + +### Feat + +- use true column names for transaction data models + +### Fix + +- define common base schema for Product model +- add `Parameter` model for lane_op +- add model for lane_trans `LocalTrans` + ## v0.4.0 (2025-01-24) ### Feat diff --git a/pyproject.toml b/pyproject.toml index 75ef20f..35eb3a8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "hatchling.build" [project] name = "pyCOREPOS" -version = "0.4.0" +version = "0.5.0" description = "Python Interface to CORE POS" readme = "README.md" authors = [{name = "Lance Edgar", email = "lance@edbob.org"}] From 9b9260ba4b9428da5f2ced0d542e1307c88a90ec Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Thu, 20 Feb 2025 09:30:24 -0600 Subject: [PATCH 94/95] fix: add `Product.default_vendor_item` convenience property --- corepos/db/office_op/model.py | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/corepos/db/office_op/model.py b/corepos/db/office_op/model.py index e374d37..60ad478 100644 --- a/corepos/db/office_op/model.py +++ b/corepos/db/office_op/model.py @@ -583,6 +583,32 @@ class Product(common.ProductBase, Base): creator=lambda lc: ProductLikeCode(like_code=lc), ) + vendor_items = orm.relationship( + 'VendorItem', + back_populates='product', + primaryjoin='VendorItem.upc == Product.upc', + foreign_keys='VendorItem.upc', + order_by='VendorItem.vendor_item_id', + doc=""" + List of :class:`VendorItem` records for this product. + """) + + @property + def default_vendor_item(self): + """ + Returns the "default" vendor item record. This will + correspond to the :attr:`default_vendor` if possible. + + :rtype: :class:`VendorItem` or ``None`` + """ + if self.default_vendor: + for item in self.vendor_items: + if item.vendor_id == self.default_vendor.id: + return item + + if self.vendor_items: + return self.vendor_items[0] + @property def full_description(self): fields = ['brand', 'description', 'size'] @@ -732,17 +758,12 @@ class VendorItem(Base): upc = sa.Column(sa.String(length=13), nullable=False) product = orm.relationship( Product, + back_populates='vendor_items', primaryjoin=Product.upc == upc, foreign_keys=[upc], doc=""" Reference to the :class:`Product` to which this record applies. - """, - backref=orm.backref( - 'vendor_items', - order_by=vendor_item_id, - doc=""" - List of :class:`VendorItem` records for this product. - """)) + """) brand = sa.Column(sa.String(length=50), nullable=True) From feb2d3471e3451a674ca77c26fac6dba0e371f28 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Thu, 20 Feb 2025 09:31:29 -0600 Subject: [PATCH 95/95] =?UTF-8?q?bump:=20version=200.5.0=20=E2=86=92=200.5?= =?UTF-8?q?.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 6 ++++++ pyproject.toml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c46d8c..2c26baa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to pyCOREPOS will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## v0.5.1 (2025-02-20) + +### Fix + +- add `Product.default_vendor_item` convenience property + ## v0.5.0 (2025-02-01) ### Feat diff --git a/pyproject.toml b/pyproject.toml index 35eb3a8..cf50f41 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "hatchling.build" [project] name = "pyCOREPOS" -version = "0.5.0" +version = "0.5.1" description = "Python Interface to CORE POS" readme = "README.md" authors = [{name = "Lance Edgar", email = "lance@edbob.org"}]