Add generic "products" batch type, can convert to labels or pricing batch
This commit is contained in:
parent
8fa7c6e732
commit
437230b958
|
@ -193,8 +193,7 @@ class HandheldBatchHandler(BatchHandler):
|
||||||
default='rattail.batch.inventory:InventoryBatchHandler')
|
default='rattail.batch.inventory:InventoryBatchHandler')
|
||||||
session = orm.object_session(handheld_batches[0])
|
session = orm.object_session(handheld_batches[0])
|
||||||
batch = handler.make_batch(session, created_by=user, handheld_batches=handheld_batches)
|
batch = handler.make_batch(session, created_by=user, handheld_batches=handheld_batches)
|
||||||
handler.populate(batch, progress=progress)
|
handler.do_populate(batch, user, progress=progress)
|
||||||
batch.rowcount = len(batch.active_rows())
|
|
||||||
return batch
|
return batch
|
||||||
|
|
||||||
def make_label_batch(self, handheld_batches, user, progress=None):
|
def make_label_batch(self, handheld_batches, user, progress=None):
|
||||||
|
@ -202,6 +201,5 @@ class HandheldBatchHandler(BatchHandler):
|
||||||
default='rattail.batch.labels:LabelBatchHandler')
|
default='rattail.batch.labels:LabelBatchHandler')
|
||||||
session = orm.object_session(handheld_batches[0])
|
session = orm.object_session(handheld_batches[0])
|
||||||
batch = handler.make_batch(session, created_by=user, handheld_batches=handheld_batches)
|
batch = handler.make_batch(session, created_by=user, handheld_batches=handheld_batches)
|
||||||
handler.populate(batch, progress=progress)
|
handler.do_populate(batch, user, progress=progress)
|
||||||
batch.rowcount = len(batch.active_rows())
|
|
||||||
return batch
|
return batch
|
||||||
|
|
|
@ -87,35 +87,42 @@ class LabelBatchHandler(BatchHandler):
|
||||||
"""
|
"""
|
||||||
Pre-fill batch with row data from handheld batch, etc.
|
Pre-fill batch with row data from handheld batch, etc.
|
||||||
"""
|
"""
|
||||||
assert batch.handheld_batch or batch.filename or batch.products
|
|
||||||
session = orm.object_session(batch)
|
session = orm.object_session(batch)
|
||||||
if batch.label_profile:
|
if batch.label_profile:
|
||||||
self.label_profile = batch.label_profile
|
self.label_profile = batch.label_profile
|
||||||
else:
|
else:
|
||||||
self.label_profile = self.get_label_profile(session)
|
self.label_profile = self.get_label_profile(session)
|
||||||
self.setup_populate(batch, progress=progress)
|
|
||||||
|
if hasattr(batch, 'product_batch') and batch.product_batch:
|
||||||
|
self.populate_from_product_batch(batch, progress=progress)
|
||||||
|
return
|
||||||
|
|
||||||
|
assert batch.handheld_batch or batch.filename or batch.products
|
||||||
|
label_code = self.label_profile.code if self.label_profile else None
|
||||||
|
|
||||||
def append(item, i):
|
def append(item, i):
|
||||||
row = model.LabelBatchRow()
|
row = model.LabelBatchRow()
|
||||||
row.label_code = self.label_profile.code if self.label_profile else None
|
row.label_code = label_code
|
||||||
row.label_profile = self.label_profile
|
row.label_profile = self.label_profile
|
||||||
with session.no_autoflush:
|
with session.no_autoflush:
|
||||||
if isinstance(item, model.Product):
|
if isinstance(item, model.Product):
|
||||||
row.product = item
|
row.product = item
|
||||||
row.upc = row.product.upc
|
|
||||||
row.label_quantity = 1
|
row.label_quantity = 1
|
||||||
if batch.static_prices and hasattr(item, '_batch_price'):
|
if batch.static_prices and hasattr(item, '_batch_price'):
|
||||||
row.regular_price = item._batch_price
|
row.regular_price = item._batch_price
|
||||||
else: # item is handheld batch row
|
else: # item is handheld batch row
|
||||||
row.upc = item.upc
|
|
||||||
row.product = item.product
|
row.product = item.product
|
||||||
row.label_quantity = item.units or 1
|
row.label_quantity = item.units or 1
|
||||||
# copy these in case product is null
|
# copy these in case product is null
|
||||||
|
row.item_entry = item.item_entry
|
||||||
|
row.item_id = item.item_id
|
||||||
|
row.upc = item.upc
|
||||||
row.brand_name = item.brand_name
|
row.brand_name = item.brand_name
|
||||||
row.description = item.description
|
row.description = item.description
|
||||||
row.size = item.size
|
row.size = item.size
|
||||||
batch.add_row(row)
|
self.add_row(batch, row)
|
||||||
self.refresh_row(row)
|
if i % 200 == 0:
|
||||||
|
session.flush()
|
||||||
|
|
||||||
if batch.handheld_batch:
|
if batch.handheld_batch:
|
||||||
data = batch.handheld_batch.active_rows()
|
data = batch.handheld_batch.active_rows()
|
||||||
|
@ -131,6 +138,28 @@ class LabelBatchHandler(BatchHandler):
|
||||||
self.progress_loop(append, data, progress,
|
self.progress_loop(append, data, progress,
|
||||||
message="Adding initial rows to batch")
|
message="Adding initial rows to batch")
|
||||||
|
|
||||||
|
def populate_from_product_batch(self, batch, progress=None):
|
||||||
|
"""
|
||||||
|
Populate label batch from product batch.
|
||||||
|
"""
|
||||||
|
session = orm.object_session(batch)
|
||||||
|
product_batch = batch.product_batch
|
||||||
|
label_code = self.label_profile.code if self.label_profile else None
|
||||||
|
|
||||||
|
def add(prow, i):
|
||||||
|
row = model.LabelBatchRow()
|
||||||
|
row.label_code = label_code
|
||||||
|
row.label_profile = self.label_profile
|
||||||
|
row.label_quantity = 1
|
||||||
|
with session.no_autoflush:
|
||||||
|
row.product = prow.product
|
||||||
|
self.add_row(batch, row)
|
||||||
|
if i % 200 == 0:
|
||||||
|
session.flush()
|
||||||
|
|
||||||
|
self.progress_loop(add, product_batch.active_rows(), progress,
|
||||||
|
message="Adding initial rows to batch")
|
||||||
|
|
||||||
def set_options_from_file(self, batch):
|
def set_options_from_file(self, batch):
|
||||||
"""
|
"""
|
||||||
Set various batch options, if any are present within the data file.
|
Set various batch options, if any are present within the data file.
|
||||||
|
@ -211,14 +240,19 @@ class LabelBatchHandler(BatchHandler):
|
||||||
Inspect a row from the source data and populate additional attributes
|
Inspect a row from the source data and populate additional attributes
|
||||||
for it, according to what we find in the database.
|
for it, according to what we find in the database.
|
||||||
"""
|
"""
|
||||||
if not row.product and row.upc:
|
if not row.product:
|
||||||
session = orm.object_session(row)
|
session = orm.object_session(row)
|
||||||
|
if row.item_entry:
|
||||||
|
row.product = self.locate_product_for_entry(session, row.item_entry)
|
||||||
|
if not row.product and row.upc:
|
||||||
row.product = api.get_product_by_upc(session, row.upc)
|
row.product = api.get_product_by_upc(session, row.upc)
|
||||||
if not row.product:
|
if not row.product:
|
||||||
row.status_code = row.STATUS_PRODUCT_NOT_FOUND
|
row.status_code = row.STATUS_PRODUCT_NOT_FOUND
|
||||||
return
|
return
|
||||||
|
|
||||||
product = row.product
|
product = row.product
|
||||||
|
row.item_id = product.item_id
|
||||||
|
row.upc = product.upc
|
||||||
row.brand_name = six.text_type(product.brand or '')
|
row.brand_name = six.text_type(product.brand or '')
|
||||||
row.description = product.description
|
row.description = product.description
|
||||||
row.size = product.size
|
row.size = product.size
|
||||||
|
|
|
@ -60,6 +60,10 @@ class PricingBatchHandler(BatchHandler):
|
||||||
if batch.input_filename:
|
if batch.input_filename:
|
||||||
return self.populate_from_file(batch, progress=progress)
|
return self.populate_from_file(batch, progress=progress)
|
||||||
|
|
||||||
|
if hasattr(batch, 'product_batch') and batch.product_batch:
|
||||||
|
self.populate_from_product_batch(batch, progress=progress)
|
||||||
|
return
|
||||||
|
|
||||||
assert batch.products
|
assert batch.products
|
||||||
session = orm.object_session(batch)
|
session = orm.object_session(batch)
|
||||||
|
|
||||||
|
@ -97,6 +101,25 @@ class PricingBatchHandler(BatchHandler):
|
||||||
self.progress_loop(append, excel_rows, progress,
|
self.progress_loop(append, excel_rows, progress,
|
||||||
message="Adding initial rows to batch")
|
message="Adding initial rows to batch")
|
||||||
|
|
||||||
|
def populate_from_product_batch(self, batch, progress=None):
|
||||||
|
"""
|
||||||
|
Populate pricing batch from product batch.
|
||||||
|
"""
|
||||||
|
session = orm.object_session(batch)
|
||||||
|
product_batch = batch.product_batch
|
||||||
|
|
||||||
|
def add(prow, i):
|
||||||
|
row = model.PricingBatchRow()
|
||||||
|
row.item_entry = prow.item_entry
|
||||||
|
with session.no_autoflush:
|
||||||
|
row.product = prow.product
|
||||||
|
self.add_row(batch, row)
|
||||||
|
if i % 200 == 0:
|
||||||
|
session.flush()
|
||||||
|
|
||||||
|
self.progress_loop(add, product_batch.active_rows(), progress,
|
||||||
|
message="Adding initial rows to batch")
|
||||||
|
|
||||||
def refresh_row(self, row):
|
def refresh_row(self, row):
|
||||||
"""
|
"""
|
||||||
Inspect a row from the source data and populate additional attributes
|
Inspect a row from the source data and populate additional attributes
|
||||||
|
|
134
rattail/batch/product.py
Normal file
134
rattail/batch/product.py
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
# -*- coding: utf-8; -*-
|
||||||
|
################################################################################
|
||||||
|
#
|
||||||
|
# Rattail -- Retail Software Framework
|
||||||
|
# Copyright © 2010-2019 Lance Edgar
|
||||||
|
#
|
||||||
|
# This file is part of Rattail.
|
||||||
|
#
|
||||||
|
# Rattail is free software: you can redistribute it and/or modify it under the
|
||||||
|
# terms of the GNU General Public License as published by the Free Software
|
||||||
|
# Foundation, either version 3 of the License, or (at your option) any later
|
||||||
|
# version.
|
||||||
|
#
|
||||||
|
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||||
|
# details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License along with
|
||||||
|
# Rattail. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
"""
|
||||||
|
Handler for generic product batches
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import unicode_literals, absolute_import
|
||||||
|
|
||||||
|
from sqlalchemy import orm
|
||||||
|
|
||||||
|
from rattail.db import model
|
||||||
|
from rattail.batch import BatchHandler, get_batch_handler
|
||||||
|
|
||||||
|
|
||||||
|
class ProductBatchHandler(BatchHandler):
|
||||||
|
"""
|
||||||
|
Handler for generic product batches.
|
||||||
|
"""
|
||||||
|
batch_model_class = model.ProductBatch
|
||||||
|
|
||||||
|
def should_populate(self, batch):
|
||||||
|
if batch.input_filename:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def populate(self, batch, progress=None):
|
||||||
|
if batch.input_filename:
|
||||||
|
return self.populate_from_file(batch, progress=progress)
|
||||||
|
|
||||||
|
def populate_from_file(self, batch, progress=None):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def refresh_row(self, row):
|
||||||
|
if not row.product:
|
||||||
|
if not row.item_entry:
|
||||||
|
row.status_code = row.STATUS_MISSING_KEY
|
||||||
|
return
|
||||||
|
|
||||||
|
session = orm.object_session(row)
|
||||||
|
row.product = self.locate_product_for_entry(session, row.item_entry)
|
||||||
|
if not row.product:
|
||||||
|
row.status_code = row.STATUS_PRODUCT_NOT_FOUND
|
||||||
|
return
|
||||||
|
|
||||||
|
product = row.product
|
||||||
|
row.upc = product.upc
|
||||||
|
row.item_id = product.item_id
|
||||||
|
row.brand_name = product.brand.name if product.brand else None
|
||||||
|
row.description = product.description
|
||||||
|
row.size = product.size
|
||||||
|
|
||||||
|
dept = product.department
|
||||||
|
row.department = dept
|
||||||
|
row.department_number = dept.number if dept else None
|
||||||
|
row.department_name = dept.name if dept else None
|
||||||
|
|
||||||
|
subdept = product.subdepartment
|
||||||
|
row.subdepartment = subdept
|
||||||
|
row.subdepartment_number = subdept.number if subdept else None
|
||||||
|
row.subdepartment_name = subdept.name if subdept else None
|
||||||
|
|
||||||
|
cost = product.cost
|
||||||
|
row.vendor = cost.vendor if cost else None
|
||||||
|
row.vendor_item_code = cost.code if cost else None
|
||||||
|
row.regular_cost = cost.unit_cost if cost else None
|
||||||
|
row.current_cost = cost.discount_cost if cost else None
|
||||||
|
row.current_cost_starts = cost.discount_starts if row.current_cost else None
|
||||||
|
row.current_cost_ends = cost.discount_ends if row.current_cost else None
|
||||||
|
|
||||||
|
regprice = product.regular_price
|
||||||
|
curprice = product.current_price
|
||||||
|
sugprice = product.suggested_price
|
||||||
|
row.regular_price = regprice.price if regprice else None
|
||||||
|
row.current_price = curprice.price if curprice else None
|
||||||
|
row.current_price_starts = curprice.starts if curprice else None
|
||||||
|
row.current_price_ends = curprice.ends if curprice else None
|
||||||
|
row.suggested_price = sugprice.price if sugprice else None
|
||||||
|
|
||||||
|
row.status_code = row.STATUS_OK
|
||||||
|
|
||||||
|
def execute(self, batch, user=None, action='make_label_batch', progress=None, **kwargs):
|
||||||
|
|
||||||
|
if action == 'make_label_batch':
|
||||||
|
result = self.make_label_batch(batch, user, progress=progress)
|
||||||
|
|
||||||
|
elif action == 'make_pricing_batch':
|
||||||
|
result = self.make_pricing_batch(batch, user, progress=progress)
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise RuntimeError("Batch execution action is not supported: {}".format(action))
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def make_label_batch(self, product_batch, user, progress=None):
|
||||||
|
handler = get_batch_handler(self.config, 'labels',
|
||||||
|
default='rattail.batch.labels:LabelBatchHandler')
|
||||||
|
session = orm.object_session(product_batch)
|
||||||
|
label_batch = handler.make_batch(session, created_by=user,
|
||||||
|
description=product_batch.description,
|
||||||
|
notes=product_batch.notes)
|
||||||
|
label_batch.product_batch = product_batch
|
||||||
|
handler.do_populate(label_batch, user, progress=progress)
|
||||||
|
return label_batch
|
||||||
|
|
||||||
|
def make_pricing_batch(self, product_batch, user, progress=None):
|
||||||
|
handler = get_batch_handler(self.config, 'pricing',
|
||||||
|
default='rattail.batch.pricing:PricingBatchHandler')
|
||||||
|
session = orm.object_session(product_batch)
|
||||||
|
pricing_batch = handler.make_batch(session, created_by=user,
|
||||||
|
description=product_batch.description,
|
||||||
|
notes=product_batch.notes)
|
||||||
|
pricing_batch.product_batch = product_batch
|
||||||
|
handler.do_populate(pricing_batch, user, progress=progress)
|
||||||
|
return pricing_batch
|
|
@ -0,0 +1,97 @@
|
||||||
|
# -*- coding: utf-8; -*-
|
||||||
|
"""add product batches
|
||||||
|
|
||||||
|
Revision ID: e1685bf9f1ad
|
||||||
|
Revises: 5dee2f796f25
|
||||||
|
Create Date: 2019-04-18 19:17:58.520577
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import unicode_literals, absolute_import
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'e1685bf9f1ad'
|
||||||
|
down_revision = '5dee2f796f25'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
import rattail.db.types
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
|
||||||
|
# batch_product
|
||||||
|
op.create_table('batch_product',
|
||||||
|
sa.Column('uuid', sa.String(length=32), nullable=False),
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('description', sa.String(length=255), nullable=True),
|
||||||
|
sa.Column('created', sa.DateTime(), nullable=False),
|
||||||
|
sa.Column('created_by_uuid', sa.String(length=32), nullable=False),
|
||||||
|
sa.Column('cognized', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('cognized_by_uuid', sa.String(length=32), nullable=True),
|
||||||
|
sa.Column('rowcount', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('complete', sa.Boolean(), nullable=False),
|
||||||
|
sa.Column('executed', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('executed_by_uuid', sa.String(length=32), nullable=True),
|
||||||
|
sa.Column('purge', sa.Date(), nullable=True),
|
||||||
|
sa.Column('notes', sa.Text(), nullable=True),
|
||||||
|
sa.Column('extra_data', sa.Text(), nullable=True),
|
||||||
|
sa.Column('status_code', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('status_text', sa.String(length=255), nullable=True),
|
||||||
|
sa.Column('input_filename', sa.String(length=255), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(['cognized_by_uuid'], ['user.uuid'], name='batch_product_fk_cognized_by'),
|
||||||
|
sa.ForeignKeyConstraint(['created_by_uuid'], ['user.uuid'], name='batch_product_fk_created_by'),
|
||||||
|
sa.ForeignKeyConstraint(['executed_by_uuid'], ['user.uuid'], name='batch_product_fk_executed_by'),
|
||||||
|
sa.PrimaryKeyConstraint('uuid')
|
||||||
|
)
|
||||||
|
|
||||||
|
# batch_product_row
|
||||||
|
op.create_table('batch_product_row',
|
||||||
|
sa.Column('uuid', sa.String(length=32), nullable=False),
|
||||||
|
sa.Column('batch_uuid', sa.String(length=32), nullable=False),
|
||||||
|
sa.Column('sequence', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('status_code', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('status_text', sa.String(length=255), nullable=True),
|
||||||
|
sa.Column('modified', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('removed', sa.Boolean(), nullable=False),
|
||||||
|
sa.Column('item_entry', sa.String(length=20), nullable=True),
|
||||||
|
sa.Column('upc', rattail.db.types.GPCType(), nullable=True),
|
||||||
|
sa.Column('item_id', sa.String(length=20), nullable=True),
|
||||||
|
sa.Column('product_uuid', sa.String(length=32), nullable=True),
|
||||||
|
sa.Column('brand_name', sa.String(length=100), nullable=True),
|
||||||
|
sa.Column('description', sa.String(length=255), nullable=True),
|
||||||
|
sa.Column('size', sa.String(length=255), nullable=True),
|
||||||
|
sa.Column('department_number', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('department_name', sa.String(length=30), nullable=True),
|
||||||
|
sa.Column('subdepartment_number', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('subdepartment_name', sa.String(length=30), nullable=True),
|
||||||
|
sa.Column('department_uuid', sa.String(length=32), nullable=True),
|
||||||
|
sa.Column('subdepartment_uuid', sa.String(length=32), nullable=True),
|
||||||
|
sa.Column('vendor_uuid', sa.String(length=32), nullable=True),
|
||||||
|
sa.Column('vendor_item_code', sa.String(length=20), nullable=True),
|
||||||
|
sa.Column('regular_cost', sa.Numeric(precision=9, scale=5), nullable=True),
|
||||||
|
sa.Column('current_cost', sa.Numeric(precision=9, scale=5), nullable=True),
|
||||||
|
sa.Column('current_cost_starts', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('current_cost_ends', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('regular_price', sa.Numeric(precision=8, scale=3), nullable=True),
|
||||||
|
sa.Column('current_price', sa.Numeric(precision=8, scale=3), nullable=True),
|
||||||
|
sa.Column('current_price_starts', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('current_price_ends', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('suggested_price', sa.Numeric(precision=8, scale=3), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(['batch_uuid'], ['batch_product.uuid'], name='batch_product_row_fk_batch'),
|
||||||
|
sa.ForeignKeyConstraint(['product_uuid'], ['product.uuid'], name='batch_product_row_fk_product'),
|
||||||
|
sa.ForeignKeyConstraint(['department_uuid'], ['department.uuid'], name='batch_product_row_fk_department'),
|
||||||
|
sa.ForeignKeyConstraint(['subdepartment_uuid'], ['subdepartment.uuid'], name='batch_product_row_fk_subdepartment'),
|
||||||
|
sa.ForeignKeyConstraint(['vendor_uuid'], ['vendor.uuid'], name='batch_product_row_fk_vendor'),
|
||||||
|
sa.PrimaryKeyConstraint('uuid')
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
|
||||||
|
# batch_product*
|
||||||
|
op.drop_table('batch_product_row')
|
||||||
|
op.drop_table('batch_product')
|
|
@ -2,7 +2,7 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# Rattail -- Retail Software Framework
|
# Rattail -- Retail Software Framework
|
||||||
# Copyright © 2010-2017 Lance Edgar
|
# Copyright © 2010-2019 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Rattail.
|
# This file is part of Rattail.
|
||||||
#
|
#
|
||||||
|
@ -68,6 +68,7 @@ from .batch.inventory import InventoryBatch, InventoryBatchRow
|
||||||
from .batch.labels import LabelBatch, LabelBatchRow
|
from .batch.labels import LabelBatch, LabelBatchRow
|
||||||
from .batch.newproduct import NewProductBatch, NewProductBatchRow
|
from .batch.newproduct import NewProductBatch, NewProductBatchRow
|
||||||
from .batch.pricing import PricingBatch, PricingBatchRow
|
from .batch.pricing import PricingBatch, PricingBatchRow
|
||||||
|
from .batch.product import ProductBatch, ProductBatchRow
|
||||||
from .batch.purchase import PurchaseBatch, PurchaseBatchRow, PurchaseBatchRowClaim, PurchaseBatchCredit
|
from .batch.purchase import PurchaseBatch, PurchaseBatchRow, PurchaseBatchRowClaim, PurchaseBatchCredit
|
||||||
from .batch.vendorcatalog import VendorCatalog, VendorCatalogRow
|
from .batch.vendorcatalog import VendorCatalog, VendorCatalogRow
|
||||||
from .batch.vendorinvoice import VendorInvoice, VendorInvoiceRow
|
from .batch.vendorinvoice import VendorInvoice, VendorInvoiceRow
|
||||||
|
|
134
rattail/db/model/batch/product.py
Normal file
134
rattail/db/model/batch/product.py
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
# -*- coding: utf-8; -*-
|
||||||
|
################################################################################
|
||||||
|
#
|
||||||
|
# Rattail -- Retail Software Framework
|
||||||
|
# Copyright © 2010-2019 Lance Edgar
|
||||||
|
#
|
||||||
|
# This file is part of Rattail.
|
||||||
|
#
|
||||||
|
# Rattail is free software: you can redistribute it and/or modify it under the
|
||||||
|
# terms of the GNU General Public License as published by the Free Software
|
||||||
|
# Foundation, either version 3 of the License, or (at your option) any later
|
||||||
|
# version.
|
||||||
|
#
|
||||||
|
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||||
|
# details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License along with
|
||||||
|
# Rattail. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
"""
|
||||||
|
Data model for generic "product" batches
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import unicode_literals, absolute_import
|
||||||
|
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy import orm
|
||||||
|
from sqlalchemy.ext.declarative import declared_attr
|
||||||
|
|
||||||
|
from rattail.db.model import (Base, BatchMixin, ProductBatchRowMixin,
|
||||||
|
Vendor, Department, Subdepartment)
|
||||||
|
from rattail.db.core import filename_column
|
||||||
|
|
||||||
|
|
||||||
|
class ProductBatch(BatchMixin, Base):
|
||||||
|
"""
|
||||||
|
Primary data model for product batches.
|
||||||
|
"""
|
||||||
|
__tablename__ = 'batch_product'
|
||||||
|
__batchrow_class__ = 'ProductBatchRow'
|
||||||
|
batch_key = 'product'
|
||||||
|
model_title = "Product Batch"
|
||||||
|
model_title_plural = "Product Batches"
|
||||||
|
|
||||||
|
input_filename = filename_column(nullable=True, doc="""
|
||||||
|
Base name of the input data file, if applicable.
|
||||||
|
""")
|
||||||
|
|
||||||
|
|
||||||
|
class ProductBatchRow(ProductBatchRowMixin, Base):
|
||||||
|
"""
|
||||||
|
Row of data within a product batch.
|
||||||
|
"""
|
||||||
|
__tablename__ = 'batch_product_row'
|
||||||
|
__batch_class__ = ProductBatch
|
||||||
|
|
||||||
|
@declared_attr
|
||||||
|
def __table_args__(cls):
|
||||||
|
return cls.__default_table_args__() + (
|
||||||
|
sa.ForeignKeyConstraint(['department_uuid'], ['department.uuid'],
|
||||||
|
name='batch_product_row_fk_department'),
|
||||||
|
sa.ForeignKeyConstraint(['subdepartment_uuid'], ['subdepartment.uuid'],
|
||||||
|
name='batch_product_row_fk_subdepartment'),
|
||||||
|
sa.ForeignKeyConstraint(['vendor_uuid'], ['vendor.uuid'],
|
||||||
|
name='batch_product_row_fk_vendor'),
|
||||||
|
)
|
||||||
|
|
||||||
|
STATUS_OK = 1
|
||||||
|
STATUS_MISSING_KEY = 2
|
||||||
|
STATUS_PRODUCT_NOT_FOUND = 3
|
||||||
|
|
||||||
|
STATUS = {
|
||||||
|
STATUS_OK : "ok",
|
||||||
|
STATUS_MISSING_KEY : "missing product key",
|
||||||
|
STATUS_PRODUCT_NOT_FOUND : "product not found",
|
||||||
|
}
|
||||||
|
|
||||||
|
department_uuid = sa.Column(sa.String(length=32), nullable=True)
|
||||||
|
department = orm.relationship(Department, doc="""
|
||||||
|
Reference to the department to which the product belongs.
|
||||||
|
""")
|
||||||
|
|
||||||
|
subdepartment_uuid = sa.Column(sa.String(length=32), nullable=True)
|
||||||
|
subdepartment = orm.relationship(Subdepartment, doc="""
|
||||||
|
Reference to the subdepartment to which the product belongs.
|
||||||
|
""")
|
||||||
|
|
||||||
|
vendor_uuid = sa.Column(sa.String(length=32), nullable=True)
|
||||||
|
vendor = orm.relationship(Vendor, doc="""
|
||||||
|
Reference to the "preferred" vendor from which product may be purchased.
|
||||||
|
""")
|
||||||
|
|
||||||
|
vendor_item_code = sa.Column(sa.String(length=20), nullable=True, doc="""
|
||||||
|
Vendor-specific item code (SKU) for the product.
|
||||||
|
""")
|
||||||
|
|
||||||
|
regular_cost = sa.Column(sa.Numeric(precision=9, scale=5), nullable=True, doc="""
|
||||||
|
The "base" unit cost for the item, i.e. with no discounts applied.
|
||||||
|
""")
|
||||||
|
|
||||||
|
current_cost = sa.Column(sa.Numeric(precision=9, scale=5), nullable=True, doc="""
|
||||||
|
The "true" unit cost for the item, i.e. with discounts applied.
|
||||||
|
""")
|
||||||
|
|
||||||
|
current_cost_starts = sa.Column(sa.DateTime(), nullable=True, doc="""
|
||||||
|
Date/time when the current cost starts, if applicable.
|
||||||
|
""")
|
||||||
|
|
||||||
|
current_cost_ends = sa.Column(sa.DateTime(), nullable=True, doc="""
|
||||||
|
Date/time when the current cost ends, if applicable.
|
||||||
|
""")
|
||||||
|
|
||||||
|
regular_price = sa.Column(sa.Numeric(precision=8, scale=3), nullable=True, doc="""
|
||||||
|
The "regular" unit price for the item.
|
||||||
|
""")
|
||||||
|
|
||||||
|
current_price = sa.Column(sa.Numeric(precision=8, scale=3), nullable=True, doc="""
|
||||||
|
The "current" unit price for the item.
|
||||||
|
""")
|
||||||
|
|
||||||
|
current_price_starts = sa.Column(sa.DateTime(), nullable=True, doc="""
|
||||||
|
Date/time when the current price starts, if applicable.
|
||||||
|
""")
|
||||||
|
|
||||||
|
current_price_ends = sa.Column(sa.DateTime(), nullable=True, doc="""
|
||||||
|
Date/time when the current price ends, if applicable.
|
||||||
|
""")
|
||||||
|
|
||||||
|
suggested_price = sa.Column(sa.Numeric(precision=8, scale=3), nullable=True, doc="""
|
||||||
|
The "suggested" retail price for the item.
|
||||||
|
""")
|
Loading…
Reference in a new issue