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 # 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,

View file

@ -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",

View file

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

View file

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

View file

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