Compare commits

..

66 commits

Author SHA1 Message Date
feb2d3471e bump: version 0.5.0 → 0.5.1 2025-02-20 09:31:29 -06:00
9b9260ba4b fix: add Product.default_vendor_item convenience property 2025-02-20 09:30:24 -06:00
8359e5692e bump: version 0.4.0 → 0.5.0 2025-02-01 15:19:12 -06:00
97a1396a54 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
2025-01-25 17:04:43 -06:00
a2a1d7faee fix: define common base schema for Product model 2025-01-25 17:04:40 -06:00
2fe089bd57 fix: add Parameter model for lane_op 2025-01-25 16:41:17 -06:00
50351596ac fix: add model for lane_trans LocalTrans 2025-01-24 20:18:13 -06:00
28cb23adc4 bump: version 0.3.5 → 0.4.0 2025-01-24 19:59:18 -06:00
c3b639390d fix: add Employee model for lane_op
with abstract common base schema
2025-01-24 19:21:00 -06:00
97fb0b28cb 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
2025-01-24 19:20:18 -06:00
d21346bbff fix: fix ordering of name columns for MemberInfo
so they show up correctly by default e.g. in UI
2025-01-15 14:48:18 -06:00
6c1fc9a803 bump: version 0.3.4 → 0.3.5 2025-01-15 11:00:22 -06:00
7aaa35dac7 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
2025-01-15 10:59:14 -06:00
b8ca60b508 bump: version 0.3.3 → 0.3.4 2025-01-15 08:51:40 -06:00
01852ceecc fix: misc. cleanup for sales batch models 2025-01-15 08:45:59 -06:00
ab56a35acc fix: add more enums for batch discount type, editor UI
also fix column type for editor UI
2025-01-14 20:20:41 -06:00
e37cf88cd9 bump: version 0.3.2 → 0.3.3 2025-01-13 13:31:21 -06:00
023d826d31 fix: remove autoincrement option for composite PK fields 2025-01-13 12:57:41 -06:00
465e565f7b bump: version 0.3.1 → 0.3.2 2025-01-11 22:01:38 -06:00
47c23a65ae fix: add base class for all transaction tables, views
also rename some classes; add `dlogBig` for archive view
2025-01-11 21:55:54 -06:00
464f107a88 fix: add MemberType.ignore_sales column 2025-01-11 21:52:22 -06:00
cb6ed15eb8 fix: add model for MasterSuperDepartment 2025-01-11 21:51:28 -06:00
310a261b48 bump: version 0.3.0 → 0.3.1 2024-12-17 16:45:36 -06:00
3bb01f2397 fix: add wicable, active columns for Department model 2024-12-17 16:45:11 -06:00
Lance Edgar
ed48f9134a docs: update project links to forgejo 2024-09-14 10:43:39 -05:00
Lance Edgar
b44e5a3d60 docs: use markdown for readme file 2024-09-13 18:49:14 -05:00
Lance Edgar
b425095a8d bump: version 0.2.1 → 0.3.0 2024-08-06 23:21:26 -05:00
Lance Edgar
b5b29cdcf1 feat: add model for MemberContactPreference (op.memContactPrefs) 2024-08-06 11:34:07 -05:00
Lance Edgar
27a46ed18f feat: add model for CustomReceiptLine (op.customReceipt) 2024-08-06 10:38:01 -05:00
Lance Edgar
2df3885420 bump: version 0.2.0 → 0.2.1 2024-07-04 23:59:49 -05:00
Lance Edgar
bcff560555 fix: add API methods, get_employees() and get_employee() 2024-07-04 18:27:50 -05:00
Lance Edgar
66cf108b3f fix: remove Change data model
since it isn't actually part of CORE, and is now handled in other ways
with regard to datasync
2024-07-04 13:18:45 -05:00
Lance Edgar
e9638c73a4 fix: remove dependency for six package 2024-07-01 16:32:39 -05:00
Lance Edgar
3b54dbd068 docs: use more specific project homepage url 2024-06-14 19:48:40 -05:00
Lance Edgar
40e647d5c8 build: use newer convention when building for release 2024-06-10 15:57:09 -05:00
Lance Edgar
04948fb840 bump: version 0.1.20 → 0.2.0 2024-06-10 15:55:51 -05:00
Lance Edgar
5cf8d2ac05 feat: switch from setup.cfg to pyproject.toml + hatchling 2024-06-10 15:55:14 -05:00
Lance Edgar
f80d03daaa Fix default dist filename for release task
not sure why this fix was needed, did setuptools behavior change?
2024-05-29 10:10:25 -05:00
Lance Edgar
777b198e44 Update changelog 2024-05-29 10:09:15 -05:00
Lance Edgar
df6f0d9793 Add enum for CORE (Office) DB types
for use with typer commands
2024-05-16 19:13:35 -05:00
Lance Edgar
7cd89029ac Update changelog 2023-11-01 08:15:37 -05:00
Lance Edgar
909d75796b Fix synonym for dtransactions.tax 2023-10-20 19:03:20 -05:00
Lance Edgar
f217f00a8f Fix data types for tax, voided in dtransactions 2023-10-20 14:31:13 -05:00
Lance Edgar
67dd9777ba Update changelog 2023-10-12 10:38:44 -05:00
Lance Edgar
1597c163c6 Let MemberInfo.dates be an object, not a list 2023-10-12 10:34:13 -05:00
Lance Edgar
3a3fba19e4 Fix the Department.tax_rate relationship
whoops i mistook `dept_tax` for a boolean previously
2023-10-11 18:36:12 -05:00
Lance Edgar
7171531dce Update changelog 2023-10-07 18:58:17 -05:00
Lance Edgar
e5988102ad Rename module to corepos.db.office_arch 2023-10-05 11:52:39 -05:00
Lance Edgar
f06f236a60 Update changelog 2023-09-15 12:59:52 -05:00
Lance Edgar
83eecad28d Add model for office_op.Tender 2023-09-15 12:43:02 -05:00
Lance Edgar
541adb6979 Update changelog 2023-09-13 21:51:10 -05:00
Lance Edgar
5214db0a83 Add model for CustomerNotifications table 2023-09-13 16:23:33 -05:00
Lance Edgar
0dda359094 Update changelog 2023-09-07 18:37:48 -05:00
Lance Edgar
0e30303947 Tweak primary key for StockPurchase model
per having a better understanding now, i think..
2023-09-07 17:45:14 -05:00
Lance Edgar
5921a18f12 Update changelog 2023-09-02 13:56:59 -05:00
Lance Edgar
5a77d14a26 Add models for StockPurchase and EquityLiveBalance 2023-06-18 11:38:14 -05:00
Lance Edgar
b2622c473d Update changelog 2023-06-12 20:39:12 -05:00
Lance Edgar
c6144ab310 Add note about meminfo.email_2 field, aka. "alt. phone" 2023-06-12 20:38:12 -05:00
Lance Edgar
4952d2fa3d Rename custdata model to CustomerClassic 2023-06-12 17:28:55 -05:00
Lance Edgar
852a989bd5 Add get_member_types() method for CORE API 2023-06-06 13:13:51 -05:00
Lance Edgar
bb8278dcc8 Update changelog 2023-06-02 14:26:51 -05:00
Lance Edgar
d58426c073 Add support for htdigest auth when using CORE webservices API 2023-05-22 21:34:46 -05:00
Lance Edgar
757fb50a96 Update changelog 2023-05-17 06:57:14 -05:00
Lance Edgar
6e43449ecb Replace setup.py contents with setup.cfg 2023-05-16 13:16:41 -05:00
Lance Edgar
24fb8c8fea Update changelog 2023-05-01 22:17:38 -05:00
Lance Edgar
ad5837405f Require SQLAlchemy 1.4.x 2023-02-15 12:51:50 -06:00
22 changed files with 1109 additions and 566 deletions

3
.gitignore vendored
View file

@ -1 +1,4 @@
*~
*.pyc
dist/
pyCOREPOS.egg-info/

View file

@ -5,6 +5,141 @@ 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
- 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
- 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
- add workaround to avoid missing schema columns
## 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
- remove `autoincrement` option for composite PK fields
## 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
- add `wicable`, `active` columns for Department model
## 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
- add API methods, `get_employees()` and `get_employee()`
- remove `Change` data model
- remove dependency for `six` package
## 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.
## [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.
- Let `MemberInfo.dates` be an object, not a list.
## [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`.
## [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.
## [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.
- 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.
## [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.
## [0.1.8] - 2023-01-02
### Changed
- Add basic `TransactionDetail` for trans archive model.

5
README.md Normal file
View file

@ -0,0 +1,5 @@
# pyCOREPOS
A Python interface to the [CORE POS](https://github.com/CORE-POS)
system.

View file

@ -1,7 +0,0 @@
pyCOREPOS
=========
A Python interface to the `CORE POS`_ system.
.. _CORE POS: https://github.com/CORE-POS

View file

@ -1,3 +1,6 @@
# -*- coding: utf-8; -*-
__version__ = '0.1.8'
from importlib.metadata import version
__version__ = version('pyCOREPOS')

View file

@ -2,7 +2,7 @@
################################################################################
#
# pyCOREPOS -- Python Interface to CORE POS
# Copyright © 2018-2021 Lance Edgar
# Copyright © 2018-2024 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
@ -116,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.
@ -166,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.

View file

173
corepos/db/common/op.py Normal file
View file

@ -0,0 +1,173 @@
# -*- 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 <http://www.gnu.org/licenses/>.
#
################################################################################
"""
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.
"""
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()
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)

114
corepos/db/common/trans.py Normal file
View file

@ -0,0 +1,114 @@
# -*- 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 <http://www.gnu.org/licenses/>.
#
################################################################################
"""
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_no = sa.Column(sa.Integer(), nullable=True)
# txn
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)
# cashier
emp_no = sa.Column(sa.Integer(), nullable=True)
# customer
card_no = sa.Column(sa.Integer(), nullable=True)
memType = sa.Column(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 = sa.Column(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(sa.Numeric(precision=10, scale=2), nullable=True)
total = sa.Column(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)
foodstamp = sa.Column(sa.Boolean(), nullable=True)
discount = sa.Column(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)
discounttype = sa.Column(sa.Integer(), nullable=True)
voided = sa.Column(sa.Integer(), nullable=True)
percentDiscount = sa.Column(sa.Integer(), nullable=True)
ItemQtty = sa.Column(sa.Float(), nullable=True)
volDiscType = sa.Column(sa.Integer(), nullable=True)
volume = sa.Column(sa.Integer(), nullable=True)
VolSpecial = sa.Column(sa.Numeric(precision=10, scale=2), nullable=True)
mixMatch = sa.Column(sa.String(length=13), nullable=True)
matched = sa.Column(sa.Boolean(), nullable=True)
numflag = sa.Column(sa.Integer(), nullable=True, default=0)
charflag = sa.Column(sa.String(length=2), nullable=True)
def __str__(self):
txnid = '-'.join([str(val) for val in [self.register_no,
self.trans_no,
self.trans_id]])
return f"{txnid} {self.description or ''}"

View file

@ -2,7 +2,7 @@
################################################################################
#
# pyCOREPOS -- Python Interface to CORE POS
# Copyright © 2018-2021 Lance Edgar
# Copyright © 2018-2025 Lance Edgar
#
# This file is part of pyCOREPOS.
#
@ -25,11 +25,26 @@ 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
from corepos.db.common import op as common
Base = declarative_base()
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.
"""
__tablename__ = 'employees'
class Department(Base):
@ -73,144 +88,14 @@ 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 CustData(Base):
class CustomerClassic(Base):
"""
Represents a customer of the organization.
@ -276,3 +161,7 @@ class CustData(Base):
def __str__(self):
return "{} {}".format(self.first_name or '', self.last_name or '').strip()
# TODO: deprecate / remove this
CustData = CustomerClassic

View file

@ -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 <http://www.gnu.org/licenses/>.
#
################################################################################
"""
Lane Transaction Database
"""
from sqlalchemy import orm
Session = orm.sessionmaker()

View file

@ -0,0 +1,79 @@
# -*- 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 <http://www.gnu.org/licenses/>.
#
################################################################################
"""
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
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):
"""
Data model for ``dtransactions`` table.
"""
__tablename__ = 'dtransactions'
class LocalTransBase(common.TransactionDetailBase):
"""
Base class for ``localtrans`` and similar models.
"""
@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'

View file

@ -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 <http://www.gnu.org/licenses/>.
#
################################################################################
"""
"Archive" Transaction Database Interface
"""
from sqlalchemy import orm
Session = orm.sessionmaker()

View file

@ -0,0 +1,63 @@
# -*- 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 <http://www.gnu.org/licenses/>.
#
################################################################################
"""
CORE Office "arch" data model
"""
import sqlalchemy as sa
from sqlalchemy import orm
from corepos.db.common import trans as common
from corepos.db.office_trans.model import DTransactionBase
Base = orm.declarative_base()
class BigArchive(DTransactionBase, Base):
"""
Data model for ``bigArchive`` table.
"""
__tablename__ = 'bigArchive'
# TODO: deprecate / remove this
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):
"""
Data model for ``dlogBig`` view.
"""
__tablename__ = 'dlogBig'

View file

@ -2,7 +2,7 @@
################################################################################
#
# pyCOREPOS -- Python Interface to CORE POS
# Copyright © 2018-2021 Lance Edgar
# Copyright © 2018-2025 Lance Edgar
#
# This file is part of pyCOREPOS.
#
@ -29,13 +29,14 @@ import logging
import sqlalchemy as sa
from sqlalchemy import orm
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.associationproxy import association_proxy
from corepos.db.common import op as common
log = logging.getLogger(__name__)
Base = declarative_base()
Base = orm.declarative_base()
class StringableDateTime(sa.TypeDecorator):
@ -55,42 +56,12 @@ class StringableDateTime(sa.TypeDecorator):
raise NotImplementedError
class Change(Base):
class Parameter(common.ParameterBase, 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.
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):
"""
@ -98,7 +69,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)
@ -187,6 +160,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.
@ -226,15 +218,25 @@ 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)
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)
@ -534,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.
"""
@ -545,125 +547,68 @@ 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),
)
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']
@ -813,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)
@ -978,35 +918,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):
"""
@ -1026,9 +943,11 @@ class MemberType(Base):
ssi = sa.Column(sa.Boolean(), nullable=True)
# 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
#ignore_sales = sa.Column('ignoreSales', sa.Boolean(), nullable=True, default=False)
# 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 ""
@ -1154,7 +1073,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.
@ -1220,7 +1139,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',
@ -1232,6 +1151,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.
@ -1240,14 +1163,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)
@ -1260,19 +1183,22 @@ 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)
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(
@ -1280,6 +1206,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.
""",
@ -1382,7 +1309,7 @@ class MemberDate(Base):
class MemberContact(Base):
"""
Contact preferences for members
Member contacts
"""
__tablename__ = 'memContact'
@ -1409,6 +1336,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.
@ -1444,6 +1384,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.
@ -1534,13 +1494,54 @@ 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 CustomReceiptLine(Base):
"""
Represents a "text string" line for a custom receipt.
"""
__tablename__ = 'customReceipt'
# 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):
return self.text or ""
class BatchType(Base):
"""
Represents the definition of a batch type.
"""
__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)
@ -1550,7 +1551,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)
@ -1565,9 +1566,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)
@ -1577,7 +1575,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)
@ -1599,16 +1598,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)
@ -1744,3 +1745,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

View file

@ -2,7 +2,7 @@
################################################################################
#
# pyCOREPOS -- Python Interface to CORE POS
# Copyright © 2018-2020 Lance Edgar
# Copyright © 2018-2025 Lance Edgar
#
# This file is part of pyCOREPOS.
#
@ -24,108 +24,68 @@
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
from corepos.db.common import trans as common
Base = declarative_base()
Base = orm.declarative_base()
@six.python_2_unicode_compatible
class TransactionDetailBase(object):
# TODO: not sure what primary key should be for this? am trying a
# 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 POS transaction detail record.
Represents a member equity payment.
"""
__tablename__ = 'stockpurchases'
# store
store_row_id = sa.Column(sa.Integer(), primary_key=True, nullable=False)
store_id = sa.Column(sa.Integer(), nullable=True, default=0)
card_number = sa.Column('card_no', sa.Integer(), nullable=False, primary_key=True, autoincrement=False)
# register
register_number = sa.Column('register_no', sa.Integer(), nullable=True)
pos_row_id = sa.Column(sa.Integer(), nullable=True)
amount = sa.Column('stockPurchase', sa.Numeric(precision=10, scale=2), nullable=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)
# 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)
transaction_status = sa.Column('trans_status', sa.String(length=1), nullable=True)
# timestamps
date_time = sa.Column('datetime', sa.DateTime(), nullable=True)
# 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)
unit_price = sa.Column('unitPrice', 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)
tax = sa.Column(sa.Boolean(), nullable=True)
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.Boolean(), 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)
department_number = sa.Column('dept', sa.Integer(), nullable=True, primary_key=True, autoincrement=False)
def __str__(self):
return self.description or ''
return f"#{self.card_number} for ${self.amount}"
class TransactionDetail(TransactionDetailBase, Base):
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 DTransactionBase(common.TransactionDetailBase):
"""
Represents a POS transaction detail record.
Base class for ``dtransactions`` and similar models.
"""
store_row_id = sa.Column(sa.Integer(), primary_key=True, nullable=False)
pos_row_id = sa.Column(sa.Integer(), nullable=True)
store_id = sa.Column(sa.Integer(), nullable=True, default=0)
date_time = sa.Column('datetime', sa.DateTime(), nullable=True)
class DTransaction(DTransactionBase, Base):
"""
Data model for ``dtransactions`` table.
"""
__tablename__ = 'dtransactions'
# TODO: deprecate / remove this
TransactionDetail = DTransaction

View file

@ -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 *

View file

@ -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,16 +24,9 @@
CORE POS Transaction Data Model
"""
from sqlalchemy.ext.declarative import declarative_base
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 = declarative_base()
class TransactionDetail(TransactionDetailBase, Base):
"""
Represents a POS transaction detail record.
"""
__tablename__ = 'bigArchive'
from corepos.db.office_arch.model import *

View file

@ -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,22 +24,43 @@
CORE POS enumeration constants
"""
from __future__ import unicode_literals, absolute_import
try:
from collections import OrderedDict
except ImportError:
from ordereddict import OrderedDict
from collections import OrderedDict
from enum import Enum
class CoreDbType(str, Enum):
office_op = 'office_op'
office_trans = 'office_trans'
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"),
])

49
pyproject.toml Normal file
View file

@ -0,0 +1,49 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "pyCOREPOS"
version = "0.5.1"
description = "Python Interface to CORE POS"
readme = "README.md"
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",
"SQLAlchemy>=1.4",
]
[project.urls]
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]
version_provider = "pep621"
tag_format = "v$version"
update_changelog_on_bump = true
[tool.hatch.build.targets.wheel]
packages = ["corepos"]

View file

@ -1,97 +0,0 @@
# -*- coding: utf-8; -*-
################################################################################
#
# pyCOREPOS -- Python Interface to CORE POS
# Copyright © 2018-2020 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 <http://www.gnu.org/licenses/>.
#
################################################################################
import os
import sys
from setuptools import setup, find_packages
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', # 0.9.8
]
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',
'Programming Language :: Python :: 3.5',
'Topic :: Office/Business',
'Topic :: Software Development :: Libraries :: Python Modules',
],
install_requires = requires,
packages = find_packages(),
)

View file

@ -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.
#
@ -25,20 +25,32 @@ 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
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__))
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__))