# -*- coding: utf-8; -*- ################################################################################ # # pyCOREPOS -- Python Interface to CORE POS # Copyright © 2018-2019 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 Data Model """ from __future__ import unicode_literals, absolute_import import six import sqlalchemy as sa from sqlalchemy import orm from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.associationproxy import association_proxy Base = declarative_base() @six.python_2_unicode_compatible class Parameter(Base): """ Represents a "parameter" value. """ __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) @six.python_2_unicode_compatible class Department(Base): """ Represents a department within the organization. """ __tablename__ = 'departments' 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) 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) # TODO: probably should rename this attribute, but to what? dept_see_id = sa.Column(sa.Boolean(), 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, default=0) sales_code = sa.Column('salesCode', sa.Integer(), nullable=False, default=0) member_only = sa.Column('memberOnly', sa.SmallInteger(), nullable=False, default=0) def __str__(self): return self.name or '' @six.python_2_unicode_compatible class Subdepartment(Base): """ Represents a subdepartment within the organization. """ __tablename__ = 'subdepts' __table_args__ = ( sa.ForeignKeyConstraint(['dept_ID'], ['departments.dept_no']), ) number = sa.Column('subdept_no', sa.SmallInteger(), primary_key=True, autoincrement=False, nullable=False) name = sa.Column('subdept_name', sa.String(length=30), nullable=True) department_number = sa.Column('dept_ID', sa.SmallInteger(), nullable=True) department = orm.relationship( Department, doc=""" Reference to the parent :class:`Department` for this subdepartment. """) def __str__(self): return self.name or '' @six.python_2_unicode_compatible class Vendor(Base): """ Represents a vendor from which product may be purchased. """ __tablename__ = 'vendors' id = sa.Column('vendorID', sa.Integer(), primary_key=True, autoincrement=False, nullable=False) name = sa.Column('vendorName', sa.String(length=50), nullable=True) abbreviation = sa.Column('vendorAbbreviation', sa.String(length=10), nullable=True) discount_rate = sa.Column('discountRate', sa.Float(), nullable=True) contact = orm.relationship( 'VendorContact', uselist=False, doc=""" Reference to the :class:`VendorContact` instance for this vendor. """) phone = association_proxy( 'contact', 'phone', creator=lambda p: VendorContact(phone=p)) fax = association_proxy( 'contact', 'fax', creator=lambda f: VendorContact(fax=f)) email = association_proxy( 'contact', 'email', creator=lambda e: VendorContact(email=e)) website = association_proxy( 'contact', 'website', creator=lambda w: VendorContact(website=w)) notes = association_proxy( 'contact', 'notes', creator=lambda n: VendorContact(notes=n)) def __str__(self): return self.name or '' class VendorContact(Base): """ A general contact record for a vendor. """ __tablename__ = 'vendorContact' vendor_id = sa.Column('vendorID', sa.Integer(), sa.ForeignKey('vendors.vendorID'), primary_key=True, autoincrement=False, nullable=False) phone = sa.Column(sa.String(length=15), nullable=True) fax = sa.Column(sa.String(length=15), nullable=True) email = sa.Column(sa.String(length=50), nullable=True) website = sa.Column(sa.String(length=100), nullable=True) notes = sa.Column(sa.Text(), nullable=True) @six.python_2_unicode_compatible class Product(Base): """ Represents a product, purchased and/or sold by the organization. """ __tablename__ = 'products' __table_args__ = ( sa.ForeignKeyConstraint(['department'], ['departments.dept_no']), ) 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) size = sa.Column(sa.String(length=9), nullable=True) tax = sa.Column(sa.SmallInteger(), nullable=True) foodstamp = sa.Column(sa.Boolean(), nullable=True) scale = sa.Column(sa.Boolean(), nullable=True) scale_price = sa.Column('scaleprice', sa.Boolean(), nullable=True, default=False) mix_match_code = sa.Column('mixmatchcode', sa.String(length=13), 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.Boolean(), nullable=True) cost = sa.Column(sa.Float(), nullable=True, default=0) in_use = sa.Column('inUse', sa.Boolean(), nullable=True) flags = sa.Column('numflag', sa.Integer(), nullable=True, default=0) 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=0) default_vendor_id = sa.Column(sa.Integer(), nullable=True, default=0) current_origin_id = sa.Column(sa.Integer(), nullable=True, default=0) department = orm.relationship( Department, primaryjoin=Department.number == department_number, foreign_keys=[department_number], doc=""" Reference to the :class:`Department` to which the product belongs. """) 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. """) @property def full_description(self): fields = ['brand', 'description', 'size'] fields = [getattr(self, f) or '' for f in fields] fields = filter(bool, fields) return ' '.join(fields) def __str__(self): return self.description or '' @six.python_2_unicode_compatible class ProductFlag(Base): """ Represents a product flag attribute. """ __tablename__ = 'prodFlags' bit_number = sa.Column(sa.SmallInteger(), primary_key=True, autoincrement=False, nullable=False, default=0) description = sa.Column(sa.String(length=50), nullable=True) active = sa.Column(sa.Boolean(), nullable=True, default=True) def __str__(self): return self.description or '' @six.python_2_unicode_compatible class Employee(Base): """ Represents an employee within the organization. """ __tablename__ = 'employees' emp_no = sa.Column(sa.SmallInteger(), primary_key=True, autoincrement=False, nullable=False) CashierPassword = sa.Column(sa.String(length=50), nullable=True) AdminPassword = sa.Column(sa.String(length=50), nullable=True) FirstName = sa.Column(sa.String(length=255), nullable=True) LastName = sa.Column(sa.String(length=255), nullable=True) JobTitle = sa.Column(sa.String(length=255), nullable=True) EmpActive = sa.Column(sa.Boolean(), nullable=True) frontendsecurity = sa.Column(sa.SmallInteger(), nullable=True) backendsecurity = sa.Column(sa.SmallInteger(), nullable=True) birthdate = sa.Column(sa.DateTime(), nullable=True) def __str__(self): return ' '.join([self.FirstName or '', self.LastName or '']).strip() @six.python_2_unicode_compatible class MemberType(Base): """ Represents a type of membership within the organization. """ __tablename__ = 'memtype' id = sa.Column('memtype', sa.SmallInteger(), primary_key=True, nullable=False, default=0) description = sa.Column('memDesc', sa.String(length=20), nullable=True) customer_type = sa.Column('custdataType', sa.String(length=10), nullable=True) discount = sa.Column(sa.SmallInteger(), nullable=True) staff = sa.Column(sa.Boolean(), nullable=True) 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) def __str__(self): return self.description or "" class Customer(Base): """ Represents a customer of the organization. """ __tablename__ = 'custdata' id = sa.Column(sa.Integer(), primary_key=True, autoincrement=True, nullable=False) CardNo = sa.Column(sa.Integer(), nullable=True) personNum = sa.Column(sa.SmallInteger(), nullable=False, default=1) FirstName = sa.Column(sa.String(length=30), nullable=True) LastName = sa.Column(sa.String(length=30), nullable=True) CashBack = sa.Column(sa.Float(), nullable=False, default=60) Balance = sa.Column(sa.Float(), nullable=False, default=0) Discount = sa.Column(sa.SmallInteger(), nullable=True) MemDiscountLimit = sa.Column(sa.Float(), nullable=False, default=0) ChargeLimit = sa.Column(sa.Float(), nullable=False, default=0) ChargeOk = sa.Column(sa.Boolean(), nullable=False, default=False) WriteChecks = sa.Column(sa.Boolean(), nullable=False, default=True) StoreCoupons = sa.Column(sa.Boolean(), nullable=False, default=True) Type = sa.Column(sa.String(length=10), nullable=False, default='pc') memType = sa.Column(sa.SmallInteger(), nullable=True) staff = sa.Column(sa.Boolean(), nullable=False, default=False) SSI = sa.Column(sa.Boolean(), nullable=False, default=False) Purchases = sa.Column(sa.Float(), nullable=False, default=0) NumberOfChecks = sa.Column(sa.SmallInteger(), nullable=False, default=0) memCoupons = sa.Column(sa.Integer(), nullable=False, default=1) blueLine = sa.Column(sa.String(length=50), nullable=True) Shown = sa.Column(sa.Boolean(), nullable=False, default=True) LastChange = sa.Column(sa.DateTime(), nullable=False) member_type = orm.relationship( MemberType, primaryjoin=MemberType.id == memType, foreign_keys=[memType], doc=""" Reference to the :class:`MemberType` to which this member belongs. """) member_info = orm.relationship( 'MemberInfo', primaryjoin='MemberInfo.card_no == Customer.CardNo', foreign_keys=[CardNo], uselist=False, back_populates='customers', doc=""" Reference to the :class:`MemberInfo` instance for this customer. """) class MemberInfo(Base): """ Contact info regarding a member of the organization. """ __tablename__ = 'meminfo' card_no = sa.Column(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) othlast_name = sa.Column(sa.String(length=30), nullable=True) othfirst_name = sa.Column(sa.String(length=30), nullable=True) street = sa.Column(sa.String(length=255), nullable=True) city = sa.Column(sa.String(length=20), nullable=True) state = sa.Column(sa.String(length=2), nullable=True) zip = sa.Column(sa.String(length=10), nullable=True) phone = sa.Column(sa.String(length=30), nullable=True) email_1 = sa.Column(sa.String(length=50), nullable=True) email_2 = sa.Column(sa.String(length=50), nullable=True) ads_OK = sa.Column(sa.Boolean(), nullable=True, default=True) customers = orm.relationship( Customer, primaryjoin=Customer.CardNo == card_no, foreign_keys=[Customer.CardNo], back_populates='member_info', remote_side=Customer.CardNo, doc=""" List of :class:`Customer` instances which are associated with this member info. """) @property def full_name(self): return '{0} {1}'.format(self.first_name or '', self.last_name or '').strip() def __unicode__(self): return self.full_name @six.python_2_unicode_compatible class HouseCoupon(Base): """ Represents a "house" (store) coupon. """ __tablename__ = 'houseCoupons' __table_args__ = ( sa.ForeignKeyConstraint(['department'], ['departments.dept_no']), ) coupon_id = sa.Column('coupID', sa.Integer(), primary_key=True, nullable=False) description = sa.Column(sa.String(length=30), nullable=True) start_date = sa.Column('startDate', sa.DateTime(), nullable=True) end_date = sa.Column('endDate', sa.DateTime(), nullable=True) limit = sa.Column(sa.SmallInteger(), nullable=True) member_only = sa.Column('memberOnly', sa.SmallInteger(), nullable=True) discount_type = sa.Column('discountType', sa.String(length=2), nullable=True) discount_value = sa.Column('discountValue', sa.Numeric(precision=10, scale=2), nullable=True) min_type = sa.Column('minType', sa.String(length=2), nullable=True) min_value = sa.Column('minValue', sa.Numeric(precision=10, scale=2), nullable=True) department_id = sa.Column('department', sa.Integer(), nullable=True) department = orm.relationship(Department) auto = sa.Column(sa.Boolean(), nullable=True, default=False) # TODO: this isn't yet supported in all production DBs # virtual_only = sa.Column('virtualOnly', sa.Boolean(), nullable=True, default=False) def __str__(self): return self.description or ''