Compare commits

..

3 commits

Author SHA1 Message Date
Lance Edgar 20b3f87dbe fix: add basic master view for Product Costs 2024-11-12 18:30:50 -06:00
Lance Edgar 9e55717041 fix: show continuum operation type when viewing version history 2024-11-12 18:28:41 -06:00
Lance Edgar 772b6610cb fix: always define app attr for ViewSupplement 2024-11-12 18:26:36 -06:00
5 changed files with 104 additions and 13 deletions

View file

@ -2,7 +2,7 @@
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2023 Lance Edgar
# Copyright © 2010-2024 Lance Edgar
#
# This file is part of Rattail.
#
@ -27,6 +27,8 @@ Tools for displaying data diffs
import sqlalchemy as sa
import sqlalchemy_continuum as continuum
from rattail.enum import CONTINUUM_OPERATION
from pyramid.renderers import render
from webhelpers2.html import HTML
@ -273,6 +275,8 @@ class VersionDiff(Diff):
return {
'key': id(self.version),
'model_title': self.title,
'operation': CONTINUUM_OPERATION.get(self.version.operation_type,
self.version.operation_type),
'diff_class': self.nature,
'fields': self.fields,
'values': values,

View file

@ -394,6 +394,11 @@ class TailboneMenuHandler(WuttaMenuHandler):
'route': 'products',
'perm': 'products.list',
},
{
'title': "Product Costs",
'route': 'product_costs',
'perm': 'product_costs.list',
},
{
'title': "Departments",
'route': 'departments',
@ -451,6 +456,11 @@ class TailboneMenuHandler(WuttaMenuHandler):
'route': 'vendors',
'perm': 'vendors.list',
},
{
'title': "Product Costs",
'route': 'product_costs',
'perm': 'product_costs.list',
},
{'type': 'sep'},
{
'title': "Ordering",

View file

@ -196,6 +196,7 @@
<p class="block has-text-weight-bold">
{{ version.model_title }}
({{ version.operation }})
</p>
<table class="diff monospace is-size-7"

View file

@ -903,7 +903,7 @@ class MasterView(View):
def valid_employee_uuid(self, node, value):
if value:
model = self.model
model = self.app.model
employee = self.Session.get(model.Employee, value)
if not employee:
node.raise_invalid("Employee not found")
@ -939,7 +939,7 @@ class MasterView(View):
def valid_vendor_uuid(self, node, value):
if value:
model = self.model
model = self.app.model
vendor = self.Session.get(model.Vendor, value)
if not vendor:
node.raise_invalid("Vendor not found")
@ -1382,7 +1382,7 @@ class MasterView(View):
return classes
def make_revisions_grid(self, obj, empty_data=False):
model = self.model
model = self.app.model
route_prefix = self.get_route_prefix()
row_url = lambda txn, i: self.request.route_url(f'{route_prefix}.version',
uuid=obj.uuid,
@ -2153,7 +2153,7 @@ class MasterView(View):
Thread target for executing an object.
"""
app = self.get_rattail_app()
model = self.model
model = self.app.model
session = app.make_session()
obj = self.get_instance_for_key(key, session)
user = session.get(model.User, user_uuid)
@ -2594,7 +2594,7 @@ class MasterView(View):
"""
# nb. self.Session may differ, so use tailbone.db.Session
session = Session()
model = self.model
model = self.app.model
route_prefix = self.get_route_prefix()
info = session.query(model.TailbonePageHelp)\
@ -2617,7 +2617,7 @@ class MasterView(View):
"""
# nb. self.Session may differ, so use tailbone.db.Session
session = Session()
model = self.model
model = self.app.model
route_prefix = self.get_route_prefix()
info = session.query(model.TailbonePageHelp)\
@ -2639,7 +2639,7 @@ class MasterView(View):
# nb. self.Session may differ, so use tailbone.db.Session
session = Session()
model = self.model
model = self.app.model
route_prefix = self.get_route_prefix()
schema = colander.Schema()
@ -2673,7 +2673,7 @@ class MasterView(View):
# nb. self.Session may differ, so use tailbone.db.Session
session = Session()
model = self.model
model = self.app.model
route_prefix = self.get_route_prefix()
schema = colander.Schema()
@ -5541,7 +5541,7 @@ class MasterView(View):
input_file_templates=True,
output_file_templates=True):
app = self.get_rattail_app()
model = self.model
model = self.app.model
names = []
if simple_settings is None:
@ -6100,7 +6100,7 @@ class MasterView(View):
renderer='json')
class ViewSupplement(object):
class ViewSupplement:
"""
Base class for view "supplements" - which are sort of like plugins
which can "supplement" certain aspects of the view.
@ -6127,6 +6127,7 @@ class ViewSupplement(object):
def __init__(self, master):
self.master = master
self.request = master.request
self.app = master.app
self.model = master.model
self.rattail_config = master.rattail_config
self.Session = master.Session
@ -6160,7 +6161,7 @@ class ViewSupplement(object):
This is accomplished by subjecting the current base query to a
join, e.g. something like::
model = self.model
model = self.app.model
query = query.outerjoin(model.MyExtension)
return query
"""

View file

@ -34,7 +34,7 @@ import sqlalchemy_continuum as continuum
from rattail import enum, pod, sil
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.threads import Thread
from rattail.exceptions import LabelPrintingError
@ -2668,6 +2668,78 @@ class PendingProductView(MasterView):
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):
base = globals()
@ -2677,6 +2749,9 @@ def defaults(config, **kwargs):
PendingProductView = kwargs.get('PendingProductView', base['PendingProductView'])
PendingProductView.defaults(config)
ProductCostView = kwargs.get('ProductCostView', base['ProductCostView'])
ProductCostView.defaults(config)
def includeme(config):
defaults(config)