Compare commits
3 commits
3f27f626df
...
20b3f87dbe
Author | SHA1 | Date | |
---|---|---|---|
20b3f87dbe | |||
9e55717041 | |||
772b6610cb |
|
@ -2,7 +2,7 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# Rattail -- Retail Software Framework
|
# Rattail -- Retail Software Framework
|
||||||
# Copyright © 2010-2023 Lance Edgar
|
# Copyright © 2010-2024 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Rattail.
|
# This file is part of Rattail.
|
||||||
#
|
#
|
||||||
|
@ -27,6 +27,8 @@ Tools for displaying data diffs
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
import sqlalchemy_continuum as continuum
|
import sqlalchemy_continuum as continuum
|
||||||
|
|
||||||
|
from rattail.enum import CONTINUUM_OPERATION
|
||||||
|
|
||||||
from pyramid.renderers import render
|
from pyramid.renderers import render
|
||||||
from webhelpers2.html import HTML
|
from webhelpers2.html import HTML
|
||||||
|
|
||||||
|
@ -273,6 +275,8 @@ class VersionDiff(Diff):
|
||||||
return {
|
return {
|
||||||
'key': id(self.version),
|
'key': id(self.version),
|
||||||
'model_title': self.title,
|
'model_title': self.title,
|
||||||
|
'operation': CONTINUUM_OPERATION.get(self.version.operation_type,
|
||||||
|
self.version.operation_type),
|
||||||
'diff_class': self.nature,
|
'diff_class': self.nature,
|
||||||
'fields': self.fields,
|
'fields': self.fields,
|
||||||
'values': values,
|
'values': values,
|
||||||
|
|
|
@ -394,6 +394,11 @@ class TailboneMenuHandler(WuttaMenuHandler):
|
||||||
'route': 'products',
|
'route': 'products',
|
||||||
'perm': 'products.list',
|
'perm': 'products.list',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'title': "Product Costs",
|
||||||
|
'route': 'product_costs',
|
||||||
|
'perm': 'product_costs.list',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
'title': "Departments",
|
'title': "Departments",
|
||||||
'route': 'departments',
|
'route': 'departments',
|
||||||
|
@ -451,6 +456,11 @@ class TailboneMenuHandler(WuttaMenuHandler):
|
||||||
'route': 'vendors',
|
'route': 'vendors',
|
||||||
'perm': 'vendors.list',
|
'perm': 'vendors.list',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'title': "Product Costs",
|
||||||
|
'route': 'product_costs',
|
||||||
|
'perm': 'product_costs.list',
|
||||||
|
},
|
||||||
{'type': 'sep'},
|
{'type': 'sep'},
|
||||||
{
|
{
|
||||||
'title': "Ordering",
|
'title': "Ordering",
|
||||||
|
|
|
@ -196,6 +196,7 @@
|
||||||
|
|
||||||
<p class="block has-text-weight-bold">
|
<p class="block has-text-weight-bold">
|
||||||
{{ version.model_title }}
|
{{ version.model_title }}
|
||||||
|
({{ version.operation }})
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<table class="diff monospace is-size-7"
|
<table class="diff monospace is-size-7"
|
||||||
|
|
|
@ -903,7 +903,7 @@ class MasterView(View):
|
||||||
|
|
||||||
def valid_employee_uuid(self, node, value):
|
def valid_employee_uuid(self, node, value):
|
||||||
if value:
|
if value:
|
||||||
model = self.model
|
model = self.app.model
|
||||||
employee = self.Session.get(model.Employee, value)
|
employee = self.Session.get(model.Employee, value)
|
||||||
if not employee:
|
if not employee:
|
||||||
node.raise_invalid("Employee not found")
|
node.raise_invalid("Employee not found")
|
||||||
|
@ -939,7 +939,7 @@ class MasterView(View):
|
||||||
|
|
||||||
def valid_vendor_uuid(self, node, value):
|
def valid_vendor_uuid(self, node, value):
|
||||||
if value:
|
if value:
|
||||||
model = self.model
|
model = self.app.model
|
||||||
vendor = self.Session.get(model.Vendor, value)
|
vendor = self.Session.get(model.Vendor, value)
|
||||||
if not vendor:
|
if not vendor:
|
||||||
node.raise_invalid("Vendor not found")
|
node.raise_invalid("Vendor not found")
|
||||||
|
@ -1382,7 +1382,7 @@ class MasterView(View):
|
||||||
return classes
|
return classes
|
||||||
|
|
||||||
def make_revisions_grid(self, obj, empty_data=False):
|
def make_revisions_grid(self, obj, empty_data=False):
|
||||||
model = self.model
|
model = self.app.model
|
||||||
route_prefix = self.get_route_prefix()
|
route_prefix = self.get_route_prefix()
|
||||||
row_url = lambda txn, i: self.request.route_url(f'{route_prefix}.version',
|
row_url = lambda txn, i: self.request.route_url(f'{route_prefix}.version',
|
||||||
uuid=obj.uuid,
|
uuid=obj.uuid,
|
||||||
|
@ -2153,7 +2153,7 @@ class MasterView(View):
|
||||||
Thread target for executing an object.
|
Thread target for executing an object.
|
||||||
"""
|
"""
|
||||||
app = self.get_rattail_app()
|
app = self.get_rattail_app()
|
||||||
model = self.model
|
model = self.app.model
|
||||||
session = app.make_session()
|
session = app.make_session()
|
||||||
obj = self.get_instance_for_key(key, session)
|
obj = self.get_instance_for_key(key, session)
|
||||||
user = session.get(model.User, user_uuid)
|
user = session.get(model.User, user_uuid)
|
||||||
|
@ -2594,7 +2594,7 @@ class MasterView(View):
|
||||||
"""
|
"""
|
||||||
# nb. self.Session may differ, so use tailbone.db.Session
|
# nb. self.Session may differ, so use tailbone.db.Session
|
||||||
session = Session()
|
session = Session()
|
||||||
model = self.model
|
model = self.app.model
|
||||||
route_prefix = self.get_route_prefix()
|
route_prefix = self.get_route_prefix()
|
||||||
|
|
||||||
info = session.query(model.TailbonePageHelp)\
|
info = session.query(model.TailbonePageHelp)\
|
||||||
|
@ -2617,7 +2617,7 @@ class MasterView(View):
|
||||||
"""
|
"""
|
||||||
# nb. self.Session may differ, so use tailbone.db.Session
|
# nb. self.Session may differ, so use tailbone.db.Session
|
||||||
session = Session()
|
session = Session()
|
||||||
model = self.model
|
model = self.app.model
|
||||||
route_prefix = self.get_route_prefix()
|
route_prefix = self.get_route_prefix()
|
||||||
|
|
||||||
info = session.query(model.TailbonePageHelp)\
|
info = session.query(model.TailbonePageHelp)\
|
||||||
|
@ -2639,7 +2639,7 @@ class MasterView(View):
|
||||||
|
|
||||||
# nb. self.Session may differ, so use tailbone.db.Session
|
# nb. self.Session may differ, so use tailbone.db.Session
|
||||||
session = Session()
|
session = Session()
|
||||||
model = self.model
|
model = self.app.model
|
||||||
route_prefix = self.get_route_prefix()
|
route_prefix = self.get_route_prefix()
|
||||||
schema = colander.Schema()
|
schema = colander.Schema()
|
||||||
|
|
||||||
|
@ -2673,7 +2673,7 @@ class MasterView(View):
|
||||||
|
|
||||||
# nb. self.Session may differ, so use tailbone.db.Session
|
# nb. self.Session may differ, so use tailbone.db.Session
|
||||||
session = Session()
|
session = Session()
|
||||||
model = self.model
|
model = self.app.model
|
||||||
route_prefix = self.get_route_prefix()
|
route_prefix = self.get_route_prefix()
|
||||||
schema = colander.Schema()
|
schema = colander.Schema()
|
||||||
|
|
||||||
|
@ -5541,7 +5541,7 @@ class MasterView(View):
|
||||||
input_file_templates=True,
|
input_file_templates=True,
|
||||||
output_file_templates=True):
|
output_file_templates=True):
|
||||||
app = self.get_rattail_app()
|
app = self.get_rattail_app()
|
||||||
model = self.model
|
model = self.app.model
|
||||||
names = []
|
names = []
|
||||||
|
|
||||||
if simple_settings is None:
|
if simple_settings is None:
|
||||||
|
@ -6100,7 +6100,7 @@ class MasterView(View):
|
||||||
renderer='json')
|
renderer='json')
|
||||||
|
|
||||||
|
|
||||||
class ViewSupplement(object):
|
class ViewSupplement:
|
||||||
"""
|
"""
|
||||||
Base class for view "supplements" - which are sort of like plugins
|
Base class for view "supplements" - which are sort of like plugins
|
||||||
which can "supplement" certain aspects of the view.
|
which can "supplement" certain aspects of the view.
|
||||||
|
@ -6127,6 +6127,7 @@ class ViewSupplement(object):
|
||||||
def __init__(self, master):
|
def __init__(self, master):
|
||||||
self.master = master
|
self.master = master
|
||||||
self.request = master.request
|
self.request = master.request
|
||||||
|
self.app = master.app
|
||||||
self.model = master.model
|
self.model = master.model
|
||||||
self.rattail_config = master.rattail_config
|
self.rattail_config = master.rattail_config
|
||||||
self.Session = master.Session
|
self.Session = master.Session
|
||||||
|
@ -6160,7 +6161,7 @@ class ViewSupplement(object):
|
||||||
This is accomplished by subjecting the current base query to a
|
This is accomplished by subjecting the current base query to a
|
||||||
join, e.g. something like::
|
join, e.g. something like::
|
||||||
|
|
||||||
model = self.model
|
model = self.app.model
|
||||||
query = query.outerjoin(model.MyExtension)
|
query = query.outerjoin(model.MyExtension)
|
||||||
return query
|
return query
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -34,7 +34,7 @@ import sqlalchemy_continuum as continuum
|
||||||
|
|
||||||
from rattail import enum, pod, sil
|
from rattail import enum, pod, sil
|
||||||
from rattail.db import api, auth, Session as RattailSession
|
from rattail.db import api, auth, Session as RattailSession
|
||||||
from rattail.db.model import Product, PendingProduct, CustomerOrderItem
|
from rattail.db.model import Product, PendingProduct, ProductCost, CustomerOrderItem
|
||||||
from rattail.gpc import GPC
|
from rattail.gpc import GPC
|
||||||
from rattail.threads import Thread
|
from rattail.threads import Thread
|
||||||
from rattail.exceptions import LabelPrintingError
|
from rattail.exceptions import LabelPrintingError
|
||||||
|
@ -2668,6 +2668,78 @@ class PendingProductView(MasterView):
|
||||||
permission=f'{permission_prefix}.ignore_product')
|
permission=f'{permission_prefix}.ignore_product')
|
||||||
|
|
||||||
|
|
||||||
|
class ProductCostView(MasterView):
|
||||||
|
"""
|
||||||
|
Master view for Product Costs
|
||||||
|
"""
|
||||||
|
model_class = ProductCost
|
||||||
|
route_prefix = 'product_costs'
|
||||||
|
url_prefix = '/products/costs'
|
||||||
|
has_versions = True
|
||||||
|
|
||||||
|
grid_columns = [
|
||||||
|
'_product_key_',
|
||||||
|
'vendor',
|
||||||
|
'preference',
|
||||||
|
'code',
|
||||||
|
'case_size',
|
||||||
|
'case_cost',
|
||||||
|
'pack_size',
|
||||||
|
'pack_cost',
|
||||||
|
'unit_cost',
|
||||||
|
]
|
||||||
|
|
||||||
|
def query(self, session):
|
||||||
|
""" """
|
||||||
|
query = super().query(session)
|
||||||
|
model = self.app.model
|
||||||
|
|
||||||
|
# always join on Product
|
||||||
|
return query.join(model.Product)
|
||||||
|
|
||||||
|
def configure_grid(self, g):
|
||||||
|
""" """
|
||||||
|
super().configure_grid(g)
|
||||||
|
model = self.app.model
|
||||||
|
|
||||||
|
# product key
|
||||||
|
field = self.get_product_key_field()
|
||||||
|
g.set_renderer(field, self.render_product_key)
|
||||||
|
g.set_sorter(field, getattr(model.Product, field))
|
||||||
|
g.set_sort_defaults(field)
|
||||||
|
g.set_filter(field, getattr(model.Product, field))
|
||||||
|
|
||||||
|
# vendor
|
||||||
|
g.set_joiner('vendor', lambda q: q.join(model.Vendor))
|
||||||
|
g.set_sorter('vendor', model.Vendor.name)
|
||||||
|
g.set_filter('vendor', model.Vendor.name, label="Vendor Name")
|
||||||
|
|
||||||
|
def render_product_key(self, cost, field):
|
||||||
|
""" """
|
||||||
|
handler = self.app.get_products_handler()
|
||||||
|
return handler.render_product_key(cost.product)
|
||||||
|
|
||||||
|
def configure_form(self, f):
|
||||||
|
""" """
|
||||||
|
super().configure_form(f)
|
||||||
|
|
||||||
|
# product
|
||||||
|
f.set_renderer('product', self.render_product)
|
||||||
|
if 'product_uuid' in f and 'product' in f:
|
||||||
|
f.remove('product')
|
||||||
|
f.replace('product_uuid', 'product')
|
||||||
|
|
||||||
|
# vendor
|
||||||
|
f.set_renderer('vendor', self.render_vendor)
|
||||||
|
if 'vendor_uuid' in f and 'vendor' in f:
|
||||||
|
f.remove('vendor')
|
||||||
|
f.replace('vendor_uuid', 'vendor')
|
||||||
|
|
||||||
|
# futures
|
||||||
|
# TODO: should eventually show a subgrid here?
|
||||||
|
f.remove('futures')
|
||||||
|
|
||||||
|
|
||||||
def defaults(config, **kwargs):
|
def defaults(config, **kwargs):
|
||||||
base = globals()
|
base = globals()
|
||||||
|
|
||||||
|
@ -2677,6 +2749,9 @@ def defaults(config, **kwargs):
|
||||||
PendingProductView = kwargs.get('PendingProductView', base['PendingProductView'])
|
PendingProductView = kwargs.get('PendingProductView', base['PendingProductView'])
|
||||||
PendingProductView.defaults(config)
|
PendingProductView.defaults(config)
|
||||||
|
|
||||||
|
ProductCostView = kwargs.get('ProductCostView', base['ProductCostView'])
|
||||||
|
ProductCostView.defaults(config)
|
||||||
|
|
||||||
|
|
||||||
def includeme(config):
|
def includeme(config):
|
||||||
defaults(config)
|
defaults(config)
|
||||||
|
|
Loading…
Reference in a new issue