diff --git a/tailbone/helpers.py b/tailbone/helpers.py
index 13a8747a..83c79518 100644
--- a/tailbone/helpers.py
+++ b/tailbone/helpers.py
@@ -29,6 +29,7 @@ from __future__ import unicode_literals, absolute_import
import datetime
from decimal import Decimal
+from rattail.time import localtime, make_utc
from rattail.util import pretty_quantity
from webhelpers2.html import *
diff --git a/tailbone/templates/batch/view.mako b/tailbone/templates/batch/view.mako
index e4f7f46b..a7da6e54 100644
--- a/tailbone/templates/batch/view.mako
+++ b/tailbone/templates/batch/view.mako
@@ -10,6 +10,12 @@
var has_execution_options = ${'true' if master.has_execution_options else 'false'};
$(function() {
+ % if master.has_worksheet:
+ $('.load-worksheet').click(function() {
+ $(this).button('disable').button('option', 'label', "Working, please wait...");
+ location.href = '${url('{}.worksheet'.format(route_prefix), uuid=batch.uuid)}';
+ });
+ % endif
$('#refresh-data').click(function() {
$(this)
.button('option', 'disabled', true)
@@ -50,7 +56,11 @@
%def>
-<%def name="leading_buttons()">%def>
+<%def name="leading_buttons()">
+ % if master.has_worksheet and not batch.executed and request.has_perm('{}.worksheet'.format(permission_prefix)):
+
+ % endif
+%def>
<%def name="refresh_button()">
% if master.viewing and master.batch_refreshable(batch) and request.has_perm('{}.refresh'.format(permission_prefix)):
diff --git a/tailbone/templates/batch/worksheet.mako b/tailbone/templates/batch/worksheet.mako
new file mode 100644
index 00000000..4f91ea3d
--- /dev/null
+++ b/tailbone/templates/batch/worksheet.mako
@@ -0,0 +1,45 @@
+## -*- coding: utf-8; -*-
+<%inherit file="/base.mako" />
+
+<%def name="extra_javascript()">
+ ${parent.extra_javascript()}
+
+%def>
+
+<%def name="extra_styles()">
+ ${parent.extra_styles()}
+
+%def>
+
+<%def name="worksheet_grid()">%def>
+
+
+${self.worksheet_grid()}
diff --git a/tailbone/templates/master/index.mako b/tailbone/templates/master/index.mako
index c7d1802f..e417499c 100644
--- a/tailbone/templates/master/index.mako
+++ b/tailbone/templates/master/index.mako
@@ -8,7 +8,7 @@
## ##############################################################################
<%inherit file="/base.mako" />
-<%def name="title()">${model_title_plural}%def>
+<%def name="title()">${index_title}%def>
<%def name="extra_javascript()">
${parent.extra_javascript()}
diff --git a/tailbone/templates/master/view_row.mako b/tailbone/templates/master/view_row.mako
index b03ae6e0..532f1e11 100644
--- a/tailbone/templates/master/view_row.mako
+++ b/tailbone/templates/master/view_row.mako
@@ -3,6 +3,10 @@
<%def name="title()">${model_title}%def>
+<%def name="content_title()">
+
Row ${instance.sequence}
+%def>
+
<%def name="context_menu_items()">
${h.link_to("Back to {}".format(parent_model_title), index_url)}
% if master.rows_editable and instance_editable and request.has_perm('{}.edit'.format(permission_prefix)):
diff --git a/tailbone/templates/themes/better/base.mako b/tailbone/templates/themes/better/base.mako
index 24bf20e8..1c7d55e4 100644
--- a/tailbone/templates/themes/better/base.mako
+++ b/tailbone/templates/themes/better/base.mako
@@ -37,7 +37,7 @@
% if master:
»
% if master.listing:
- ${model_title_plural}
+ ${index_title}
% else:
${h.link_to(index_title, index_url, class_='global')}
% if instance_url is not Undefined:
diff --git a/tailbone/views/batch/core.py b/tailbone/views/batch/core.py
index 97162e67..dfccc047 100644
--- a/tailbone/views/batch/core.py
+++ b/tailbone/views/batch/core.py
@@ -72,6 +72,7 @@ class BatchMasterView(MasterView):
supports_mobile = True
mobile_filterable = True
mobile_rows_viewable = True
+ has_worksheet = False
def __init__(self, request):
super(BatchMasterView, self).__init__(request)
@@ -422,6 +423,9 @@ class BatchMasterView(MasterView):
def editable_instance(self, batch):
return not bool(batch.executed)
+ def after_edit_row(self, row):
+ self.handler.refresh_row(row)
+
def executable(self, batch=None):
return self.handler.executable(batch)
@@ -954,6 +958,17 @@ class BatchMasterView(MasterView):
config.add_view(cls, attr='prefill', route_name='{}.prefill'.format(route_prefix),
permission='{}.create'.format(permission_prefix))
+ # worksheet
+ if cls.has_worksheet:
+ config.add_tailbone_permission(permission_prefix, '{}.worksheet'.format(permission_prefix),
+ "Edit {} data as worksheet".format(model_title))
+ config.add_route('{}.worksheet'.format(route_prefix), '{}/{{{}}}/worksheet'.format(url_prefix, model_key))
+ config.add_view(cls, attr='worksheet', route_name='{}.worksheet'.format(route_prefix),
+ permission='{}.worksheet'.format(permission_prefix))
+ config.add_route('{}.worksheet_update'.format(route_prefix), '{}/{{{}}}/worksheet/update'.format(url_prefix, model_key))
+ config.add_view(cls, attr='worksheet_update', route_name='{}.worksheet_update'.format(route_prefix),
+ renderer='json', permission='{}.worksheet'.format(permission_prefix))
+
# refresh batch data
config.add_route('{}.refresh'.format(route_prefix), '{}/{{uuid}}/refresh'.format(url_prefix))
config.add_view(cls, attr='refresh', route_name='{}.refresh'.format(route_prefix),
diff --git a/tailbone/views/batch/core2.py b/tailbone/views/batch/core2.py
index ab7e5464..d9a36f3f 100644
--- a/tailbone/views/batch/core2.py
+++ b/tailbone/views/batch/core2.py
@@ -103,6 +103,7 @@ class BatchMasterView2(MasterView2, BatchMasterView):
g.set_label('sequence', "Seq.")
g.set_label('status_code', "Status")
+ g.set_label('item_id', "Item ID")
def render_row_status(self, row, column):
code = row.status_code
diff --git a/tailbone/views/inventory.py b/tailbone/views/inventory.py
index 2352733f..cff5f134 100644
--- a/tailbone/views/inventory.py
+++ b/tailbone/views/inventory.py
@@ -59,10 +59,11 @@ class InventoryBatchView(BatchMasterView):
'id',
'created',
'created_by',
+ 'mode',
'rowcount',
+ 'total_cost',
'executed',
'executed_by',
- 'mode',
]
model_row_class = model.InventoryBatchRow
@@ -71,18 +72,21 @@ class InventoryBatchView(BatchMasterView):
row_grid_columns = [
'sequence',
'upc',
+ 'item_id',
'brand_name',
'description',
'size',
'cases',
'units',
'unit_cost',
+ 'total_cost',
'status_code',
]
def configure_grid(self, g):
super(InventoryBatchView, self).configure_grid(g)
g.set_enum('mode', self.enum.INVENTORY_MODE)
+ g.set_type('total_cost', 'currency')
g.set_label('mode', "Count Mode")
def render_mobile_listitem(self, batch, i):
@@ -96,6 +100,7 @@ class InventoryBatchView(BatchMasterView):
super(InventoryBatchView, self)._preconfigure_fieldset(fs)
fs.mode.set(renderer=forms.renderers.EnumFieldRenderer(self.enum.INVENTORY_MODE),
label="Count Mode")
+ fs.total_cost.set(readonly=True, renderer=forms.renderers.CurrencyFieldRenderer)
fs.append(fa.Field('handheld_batches', renderer=forms.renderers.HandheldBatchesFieldRenderer, readonly=True,
value=lambda b: b._handhelds))
@@ -114,6 +119,22 @@ class InventoryBatchView(BatchMasterView):
fs.executed_by,
])
+ def save_edit_row_form(self, form):
+ row = form.fieldset.model
+ batch = row.batch
+ if batch.total_cost is not None and row.total_cost is not None:
+ batch.total_cost -= row.total_cost
+ return super(InventoryBatchView, self).save_edit_row_form(form)
+
+ def delete_row(self):
+ row = self.Session.query(model.InventoryBatchRow).get(self.request.matchdict['uuid'])
+ if not row:
+ raise self.notfound()
+ batch = row.batch
+ if batch.total_cost is not None and row.total_cost is not None:
+ batch.total_cost -= row.total_cost
+ return super(InventoryBatchView, self).delete_row()
+
def configure_mobile_fieldset(self, fs):
fs.configure(include=[
fs.mode,
@@ -220,9 +241,15 @@ class InventoryBatchView(BatchMasterView):
def configure_row_grid(self, g):
super(InventoryBatchView, self).configure_row_grid(g)
- g.set_renderer('cases', 'quantity')
- g.set_renderer('units', 'quantity')
- g.set_renderer('unit_cost', 'currency')
+ g.set_type('cases', 'quantity')
+ g.set_type('units', 'quantity')
+ g.set_type('unit_cost', 'currency')
+ g.set_type('total_cost', 'currency')
+
+ # TODO: i guess row grids don't support this properly yet?
+ # g.set_link('upc')
+ # g.set_link('item_id')
+ # g.set_link('description')
g.set_label('upc', "UPC")
g.set_label('brand_name', "Brand")
@@ -242,10 +269,14 @@ class InventoryBatchView(BatchMasterView):
super(InventoryBatchView, self)._preconfigure_row_fieldset(fs)
fs.upc.set(readonly=True, label="UPC", renderer=forms.renderers.GPCFieldRenderer,
attrs={'link': lambda r: self.request.route_url('products.view', uuid=r.product_uuid)})
+ fs.item_id.set(readonly=True)
fs.brand_name.set(readonly=True)
fs.description.set(readonly=True)
fs.size.set(readonly=True)
- fs.unit_cost.set(renderer=forms.renderers.CurrencyFieldRenderer)
+ fs.cases.set(renderer=forms.renderers.QuantityFieldRenderer)
+ fs.units.set(renderer=forms.renderers.QuantityFieldRenderer)
+ fs.unit_cost.set(readonly=True, renderer=forms.renderers.CurrencyFieldRenderer)
+ fs.total_cost.set(readonly=True, renderer=forms.renderers.CurrencyFieldRenderer)
def configure_row_fieldset(self, fs):
fs.configure(
@@ -259,18 +290,22 @@ class InventoryBatchView(BatchMasterView):
fs.cases,
fs.units,
fs.unit_cost,
+ fs.total_cost,
])
@classmethod
def defaults(cls, config):
+ cls._inventory_defaults(config)
+ cls._batch_defaults(config)
+ cls._defaults(config)
+
+ @classmethod
+ def _inventory_defaults(cls, config):
model_key = cls.get_model_key()
route_prefix = cls.get_route_prefix()
url_prefix = cls.get_url_prefix()
row_permission_prefix = cls.get_row_permission_prefix()
- cls._batch_defaults(config)
- cls._defaults(config)
-
# mobile - make new row from UPC
config.add_route('mobile.{}.row_from_upc'.format(route_prefix), '/mobile{}/{{{}}}/row-from-upc'.format(url_prefix, model_key))
config.add_view(cls, attr='mobile_row_from_upc', route_name='mobile.{}.row_from_upc'.format(route_prefix),
diff --git a/tailbone/views/master.py b/tailbone/views/master.py
index b9f82090..d3e30b9e 100644
--- a/tailbone/views/master.py
+++ b/tailbone/views/master.py
@@ -1340,17 +1340,15 @@ class MasterView(View):
parent = self.get_parent(row)
return self.render_to_response('view_row', {
'instance': row,
- 'instance_title': self.get_row_instance_title(row),
+ 'batch': row.batch,
+ 'instance_title': self.get_instance_title(row.batch),
+ 'instance_url': self.get_action_url('view', parent),
'instance_editable': self.row_editable(row),
'instance_deletable': self.row_deletable(row),
'rows_creatable': self.rows_creatable and self.rows_creatable_for(parent),
'model_title': self.get_row_model_title(),
'model_title_plural': self.get_row_model_title_plural(),
'parent_model_title': self.get_model_title(),
- 'index_url': self.get_action_url('view', parent),
- 'index_title': '{} {}'.format(
- self.get_model_title(),
- self.get_instance_title(parent)),
'action_url': self.get_row_action_url,
'form': form})
diff --git a/tailbone/views/products.py b/tailbone/views/products.py
index 5f5b36a7..b1863c9a 100644
--- a/tailbone/views/products.py
+++ b/tailbone/views/products.py
@@ -291,6 +291,10 @@ class ProductsView(MasterView):
fs.last_sold.set(readonly=True)
fs.append(fa.Field('current_price_ends', type=fa.types.DateTime, readonly=True,
value=lambda p: p.current_price.ends if p.current_price else None))
+ fs.append(fa.Field('inventory_on_hand', readonly=True, label="On Hand",
+ value=lambda p: p.inventory.on_hand if p.inventory else None))
+ fs.append(fa.Field('inventory_on_order', readonly=True, label="On Order",
+ value=lambda p: p.inventory.on_order if p.inventory else None))
def configure_fieldset(self, fs):
fs.configure(