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 name="leading_buttons()"> +<%def name="leading_buttons()"> + % if master.has_worksheet and not batch.executed and request.has_perm('{}.worksheet'.format(permission_prefix)): + + % endif + <%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 name="extra_styles()"> + ${parent.extra_styles()} + + + +<%def name="worksheet_grid()"> + + +${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 name="title()">${index_title} <%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 name="content_title()"> +

Row ${instance.sequence}

+ + <%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(