diff --git a/tailbone/templates/mobile/batch/view.mako b/tailbone/templates/mobile/batch/view.mako
index 9d3b0c0c..0770e703 100644
--- a/tailbone/templates/mobile/batch/view.mako
+++ b/tailbone/templates/mobile/batch/view.mako
@@ -4,6 +4,25 @@
${parent.body()}
% if master.has_rows:
+ % if master.mobile_rows_creatable and not batch.executed and not batch.complete:
+ ${h.link_to("Add Item", url('mobile.{}.create_row'.format(route_prefix), uuid=instance.uuid), class_='ui-btn ui-corner-all')}
+ % endif
${grid.render_complete()|n}
% endif
+
+% if not batch.executed and request.has_perm('{}.edit'.format(permission_prefix)):
+ % if batch.complete:
+ ${h.form(request.route_url('mobile.{}.mark_pending'.format(route_prefix), uuid=batch.uuid))}
+ ${h.csrf_token(request)}
+ ${h.hidden('mark-pending', value='true')}
+ ${h.submit('submit', "Mark Batch as Pending")}
+ ${h.end_form()}
+ % else:
+ ${h.form(request.route_url('mobile.{}.mark_complete'.format(route_prefix), uuid=batch.uuid))}
+ ${h.csrf_token(request)}
+ ${h.hidden('mark-complete', value='true')}
+ ${h.submit('submit', "Mark Batch as Complete")}
+ ${h.end_form()}
+ % endif
+% endif
diff --git a/tailbone/templates/mobile/master/create_row.mako b/tailbone/templates/mobile/master/create_row.mako
new file mode 100644
index 00000000..8a6157d2
--- /dev/null
+++ b/tailbone/templates/mobile/master/create_row.mako
@@ -0,0 +1,4 @@
+## -*- coding: utf-8; -*-
+<%inherit file="/mobile/master/create.mako" />
+
+${parent.body()}
diff --git a/tailbone/templates/mobile/master/edit.mako b/tailbone/templates/mobile/master/edit.mako
new file mode 100644
index 00000000..3c13a8e4
--- /dev/null
+++ b/tailbone/templates/mobile/master/edit.mako
@@ -0,0 +1,10 @@
+## -*- coding: utf-8; -*-
+<%inherit file="/mobile/base.mako" />
+
+<%def name="title()">${index_title} » ${instance_title} » Edit%def>
+
+<%def name="page_title()">${h.link_to(index_title, index_url)} » ${h.link_to(instance_title, instance_url)} » Edit%def>
+
+
+ ${form.render()|n}
+
diff --git a/tailbone/templates/mobile/master/edit_row.mako b/tailbone/templates/mobile/master/edit_row.mako
new file mode 100644
index 00000000..31b0156d
--- /dev/null
+++ b/tailbone/templates/mobile/master/edit_row.mako
@@ -0,0 +1,16 @@
+## -*- coding: utf-8; -*-
+<%inherit file="/mobile/master/edit.mako" />
+
+<%def name="title()">${index_title} » ${parent_title} » ${instance_title} » Edit%def>
+
+<%def name="page_title()">${h.link_to(index_title, index_url)} » ${h.link_to(parent_title, parent_url)} » ${h.link_to(instance_title, instance_url)} » Edit%def>
+
+<%def name="buttons()">
+
+ ${h.submit('create', form.update_label)}
+ Cancel
+%def>
+
+
+ ${form.render(buttons=capture(self.buttons))|n}
+
diff --git a/tailbone/templates/mobile/master/view_row.mako b/tailbone/templates/mobile/master/view_row.mako
index 74df7e9e..109ad415 100644
--- a/tailbone/templates/mobile/master/view_row.mako
+++ b/tailbone/templates/mobile/master/view_row.mako
@@ -1,4 +1,12 @@
## -*- coding: utf-8; -*-
<%inherit file="/mobile/master/view.mako" />
+<%def name="title()">${index_title} » ${parent_title} » ${instance_title}%def>
+
+<%def name="page_title()">${h.link_to(index_title, index_url)} » ${h.link_to(parent_title, parent_url)} » ${instance_title}%def>
+
${parent.body()}
+
+% if master.mobile_rows_editable and instance_editable and request.has_perm('{}.edit_row'.format(permission_prefix)):
+ ${h.link_to("Edit", url('mobile.{}.edit'.format(row_route_prefix), uuid=instance.uuid), class_='ui-btn')}
+% endif
diff --git a/tailbone/templates/mobile/ordering/create.mako b/tailbone/templates/mobile/ordering/create.mako
new file mode 100644
index 00000000..68f11737
--- /dev/null
+++ b/tailbone/templates/mobile/ordering/create.mako
@@ -0,0 +1,22 @@
+## -*- coding: utf-8; -*-
+<%inherit file="/mobile/base.mako" />
+
+<%def name="title()">${index_title} » New Batch%def>
+
+<%def name="page_title()">${h.link_to(index_title, index_url)} » New Batch%def>
+
+${h.form(request.current_route_url(), class_='ui-filterable', name='new-purchasing-batch')}
+${h.csrf_token(request)}
+
+
+
+ ${h.hidden('vendor')}
+ ${h.text('new-purchasing-batch-vendor-text', placeholder="Vendor name", autocomplete='off', data_type='search')}
+
+
+
+
+
+
+${h.submit('submit', "Make Batch")}
+${h.end_form()}
diff --git a/tailbone/templates/mobile/ordering/create_row.mako b/tailbone/templates/mobile/ordering/create_row.mako
new file mode 100644
index 00000000..d31814f8
--- /dev/null
+++ b/tailbone/templates/mobile/ordering/create_row.mako
@@ -0,0 +1,6 @@
+## -*- coding: utf-8; -*-
+<%inherit file="/mobile/master/create_row.mako" />
+
+<%def name="title()">${h.link_to(index_title, index_url)} » ${h.link_to(instance_title, instance_url)} » Add Item%def>
+
+${parent.body()}
diff --git a/tailbone/templates/themes/better/base.mako b/tailbone/templates/themes/better/base.mako
index 1c7d55e4..76710c34 100644
--- a/tailbone/templates/themes/better/base.mako
+++ b/tailbone/templates/themes/better/base.mako
@@ -40,7 +40,10 @@
${index_title}
% else:
${h.link_to(index_title, index_url, class_='global')}
- % if instance_url is not Undefined:
+ % if parent_url is not Undefined:
+ »
+ ${h.link_to(parent_title, parent_url, class_='global')}
+ % elif instance_url is not Undefined:
»
${h.link_to(instance_title, instance_url, class_='global')}
% endif
diff --git a/tailbone/views/batch/core.py b/tailbone/views/batch/core.py
index dfccc047..fc9ef4c9 100644
--- a/tailbone/views/batch/core.py
+++ b/tailbone/views/batch/core.py
@@ -354,6 +354,11 @@ class BatchMasterView(MasterView):
batch.complete = True
return self.redirect(self.get_index_url(mobile=True))
+ def mobile_mark_pending(self):
+ batch = self.get_instance()
+ batch.complete = False
+ return self.redirect(self.get_action_url('view', batch, mobile=True))
+
def rows_creatable_for(self, batch):
"""
Only allow creating new rows on a batch if it hasn't yet been executed.
@@ -370,6 +375,24 @@ class BatchMasterView(MasterView):
return self.redirect(self.get_action_url('view', batch))
return super(BatchMasterView, self).create_row()
+ def mobile_create_row(self):
+ """
+ Only allow creating a new row if the batch hasn't yet been executed.
+ """
+ batch = self.get_instance()
+ if batch.executed:
+ self.request.session.flash("You cannot add new rows to a batch which has been executed")
+ return self.redirect(self.get_action_url('view', batch, mobile=True))
+ return super(BatchMasterView, self).mobile_create_row()
+
+ def before_create_row(self, form):
+ batch = self.get_instance()
+ row = form.fieldset.model
+ self.handler.add_row(batch, row)
+
+ def after_create_row(self, row):
+ self.handler.refresh_row(row)
+
def make_default_row_grid_tools(self, batch):
if self.rows_creatable and not batch.executed:
permission_prefix = self.get_permission_prefix()
@@ -659,7 +682,8 @@ class BatchMasterView(MasterView):
"""
Batch rows are editable only until batch has been executed.
"""
- return self.rows_editable and not row.batch.executed
+ batch = row.batch
+ return self.rows_editable and not batch.executed and not batch.complete
def row_edit_action_url(self, row, i):
if self.row_editable(row):
@@ -989,6 +1013,11 @@ class BatchMasterView(MasterView):
config.add_view(cls, attr='mobile_mark_complete', route_name='mobile.{}.mark_complete'.format(route_prefix),
permission='{}.edit'.format(permission_prefix))
+ # mobile mark pending
+ config.add_route('mobile.{}.mark_pending'.format(route_prefix), '/mobile{}/{{{}}}/mark-pending'.format(url_prefix, model_key))
+ config.add_view(cls, attr='mobile_mark_pending', route_name='mobile.{}.mark_pending'.format(route_prefix),
+ permission='{}.edit'.format(permission_prefix))
+
# execute batch
config.add_route('{}.execute'.format(route_prefix), '{}/{{uuid}}/execute'.format(url_prefix))
diff --git a/tailbone/views/master.py b/tailbone/views/master.py
index d3e30b9e..3c59e2fe 100644
--- a/tailbone/views/master.py
+++ b/tailbone/views/master.py
@@ -95,8 +95,10 @@ class MasterView(View):
rows_bulk_deletable = False
rows_default_pagesize = 20
+ mobile_rows_creatable = False
mobile_rows_filterable = False
mobile_rows_viewable = False
+ mobile_rows_editable = False
@property
def Session(self):
@@ -472,7 +474,12 @@ class MasterView(View):
fieldset = self.make_fieldset(row)
self.preconfigure_mobile_row_fieldset(fieldset)
self.configure_mobile_row_fieldset(fieldset)
+ kwargs.setdefault('session', self.Session())
+ kwargs.setdefault('creating', self.creating)
+ kwargs.setdefault('editing', self.editing)
kwargs.setdefault('action_url', self.request.current_route_url(_query=None))
+ if 'cancel_url' not in kwargs:
+ kwargs['cancel_url'] = self.get_action_url('view', self.get_parent(row), mobile=True)
factory = kwargs.pop('factory', forms.AlchemyForm)
form = factory(self.request, fieldset, **kwargs)
form.readonly = self.viewing
@@ -508,11 +515,16 @@ class MasterView(View):
"""
self.viewing = True
row = self.get_row_instance()
+ parent = self.get_parent(row)
form = self.make_mobile_row_form(row)
context = {
'row': row,
+ 'parent_instance': parent,
+ 'parent_title': self.get_instance_title(parent),
+ 'parent_url': self.get_action_url('view', parent, mobile=True),
'instance': row,
'instance_title': self.get_row_instance_title(row),
+ 'instance_editable': self.row_editable(row),
'parent_model_title': self.get_model_title(),
'form': form,
}
@@ -945,6 +957,8 @@ class MasterView(View):
context.update(self.template_kwargs(**context))
if hasattr(self, 'template_kwargs_{}'.format(template)):
context.update(getattr(self, 'template_kwargs_{}'.format(template))(**context))
+ if hasattr(self, 'mobile_template_kwargs_{}'.format(template)):
+ context.update(getattr(self, 'mobile_template_kwargs_{}'.format(template))(**context))
# First try the template path most specific to the view.
if mobile:
@@ -1327,8 +1341,30 @@ class MasterView(View):
def after_create_row(self, row_object):
pass
- def redirect_after_create_row(self, row):
- return self.redirect(self.get_action_url('view', self.get_parent(row)))
+ def redirect_after_create_row(self, row, mobile=False):
+ return self.redirect(self.get_row_action_url('view', row, mobile=mobile))
+
+ def mobile_create_row(self):
+ """
+ Mobile view for creating a new row object
+ """
+ self.creating = True
+ parent = self.get_instance()
+ instance_url = self.get_action_url('view', parent, mobile=True)
+ form = self.make_mobile_row_form(self.model_row_class, cancel_url=instance_url)
+ if self.request.method == 'POST':
+ if form.validate():
+ self.before_create_row(form)
+ # let save() return alternate object if necessary
+ obj = self.save_create_row_form(form) or form.fieldset.model
+ self.after_create_row(obj)
+ return self.redirect_after_create_row(obj, mobile=True)
+ return self.render_to_response('create_row', {
+ 'instance_title': self.get_instance_title(parent),
+ 'instance_url': instance_url,
+ 'parent_object': parent,
+ 'form': form,
+ }, mobile=True)
def view_row(self):
"""
@@ -1359,6 +1395,14 @@ class MasterView(View):
"""
return True
+ def row_editable(self, row):
+ """
+ Returns boolean indicating whether or not the given row can be
+ considered "editable". Returns ``True`` by default; override as
+ necessary.
+ """
+ return True
+
def edit_row(self):
"""
View for editing an existing model record.
@@ -1376,14 +1420,39 @@ class MasterView(View):
return self.render_to_response('edit_row', {
'instance': row,
'row_parent': parent,
+ 'parent_title': self.get_instance_title(parent),
+ 'parent_url': self.get_action_url('view', parent),
+ 'parent_instance': parent,
'instance_title': self.get_row_instance_title(row),
'instance_deletable': self.row_deletable(row),
- 'index_url': self.get_action_url('view', parent),
- 'index_title': '{} {}'.format(
- self.get_model_title(),
- self.get_instance_title(parent)),
'form': form})
+ def mobile_edit_row(self):
+ """
+ Mobile view for editing a row object
+ """
+ self.editing = True
+ row = self.get_row_instance()
+ instance_url = self.get_row_action_url('view', row, mobile=True)
+ form = self.make_mobile_row_form(row)
+
+ if self.request.method == 'POST':
+ if form.validate():
+ self.save_edit_row_form(form)
+ return self.redirect_after_edit_row(row, mobile=True)
+
+ parent = self.get_parent(row)
+ return self.render_to_response('edit_row', {
+ 'instance': row,
+ 'instance_title': self.get_row_instance_title(row),
+ 'instance_url': instance_url,
+ 'instance_deletable': self.row_deletable(row),
+ 'parent_instance': parent,
+ 'parent_title': self.get_instance_title(parent),
+ 'parent_url': self.get_action_url('view', parent, mobile=True),
+ 'form': form},
+ mobile=True)
+
def save_edit_row_form(self, form):
self.save_row_form(form)
self.after_edit_row(form.fieldset.model)
@@ -1396,16 +1465,8 @@ class MasterView(View):
Event hook, called just after an existing row object is saved.
"""
- def redirect_after_edit_row(self, row):
- return self.redirect(self.get_action_url('view', self.get_parent(row)))
-
- def row_editable(self, row):
- """
- Returns boolean indicating whether or not the given row can be
- considered "editable". Returns ``True`` by default; override as
- necessary.
- """
- return True
+ def redirect_after_edit_row(self, row, mobile=False):
+ return self.redirect(self.get_row_action_url('view', row, mobile=True))
def row_deletable(self, row):
"""
@@ -1617,12 +1678,18 @@ class MasterView(View):
### sub-rows stuff follows
# create row
- if cls.has_rows and cls.rows_creatable:
- config.add_route('{}.create_row'.format(route_prefix), '{}/{{{}}}/new-row'.format(url_prefix, model_key))
- config.add_view(cls, attr='create_row', route_name='{}.create_row'.format(route_prefix),
- permission='{}.create_row'.format(permission_prefix))
- config.add_tailbone_permission(permission_prefix, '{}.create_row'.format(permission_prefix),
- "Create new {} rows".format(model_title))
+ if cls.has_rows:
+ if cls.rows_creatable or cls.mobile_rows_creatable:
+ config.add_tailbone_permission(permission_prefix, '{}.create_row'.format(permission_prefix),
+ "Create new {} rows".format(model_title))
+ if cls.rows_creatable:
+ config.add_route('{}.create_row'.format(route_prefix), '{}/{{{}}}/new-row'.format(url_prefix, model_key))
+ config.add_view(cls, attr='create_row', route_name='{}.create_row'.format(route_prefix),
+ permission='{}.create_row'.format(permission_prefix))
+ if cls.mobile_rows_creatable:
+ config.add_route('mobile.{}.create_row'.format(route_prefix), '/mobile{}/{{{}}}/new-row'.format(url_prefix, model_key))
+ config.add_view(cls, attr='mobile_create_row', route_name='mobile.{}.create_row'.format(route_prefix),
+ permission='{}.create_row'.format(permission_prefix))
# view row
if cls.has_rows:
@@ -1636,12 +1703,18 @@ class MasterView(View):
permission='{}.view'.format(permission_prefix))
# edit row
- if cls.has_rows and cls.rows_editable:
- config.add_route('{}.edit'.format(row_route_prefix), '{}/{{uuid}}/edit'.format(row_url_prefix))
- config.add_view(cls, attr='edit_row', route_name='{}.edit'.format(row_route_prefix),
- permission='{}.edit_row'.format(permission_prefix))
- config.add_tailbone_permission(permission_prefix, '{}.edit_row'.format(permission_prefix),
- "Edit individual {} rows".format(model_title))
+ if cls.has_rows:
+ if cls.rows_editable or cls.mobile_rows_editable:
+ config.add_tailbone_permission(permission_prefix, '{}.edit_row'.format(permission_prefix),
+ "Edit individual {} rows".format(model_title))
+ if cls.rows_editable:
+ config.add_route('{}.edit'.format(row_route_prefix), '{}/{{uuid}}/edit'.format(row_url_prefix))
+ config.add_view(cls, attr='edit_row', route_name='{}.edit'.format(row_route_prefix),
+ permission='{}.edit_row'.format(permission_prefix))
+ if cls.mobile_rows_editable:
+ config.add_route('mobile.{}.edit'.format(row_route_prefix), '/mobile{}/{{uuid}}/edit'.format(row_url_prefix))
+ config.add_view(cls, attr='mobile_edit_row', route_name='mobile.{}.edit'.format(row_route_prefix),
+ permission='{}.edit_row'.format(permission_prefix))
# delete row
if cls.has_rows and cls.rows_deletable:
diff --git a/tailbone/views/purchasing/batch.py b/tailbone/views/purchasing/batch.py
index ca26ea05..738e175e 100644
--- a/tailbone/views/purchasing/batch.py
+++ b/tailbone/views/purchasing/batch.py
@@ -275,6 +275,7 @@ class PurchasingBatchView(BatchMasterView):
elif batch.buyer_uuid:
kwargs['buyer_uuid'] = batch.buyer_uuid
kwargs['po_number'] = batch.po_number
+ kwargs['po_total'] = batch.po_total
# TODO: should these always get set?
if self.batch_mode == self.enum.PURCHASE_BATCH_MODE_ORDERING:
@@ -352,6 +353,7 @@ class PurchasingBatchView(BatchMasterView):
def _preconfigure_row_fieldset(self, fs):
super(PurchasingBatchView, self)._preconfigure_row_fieldset(fs)
fs.upc.set(label="UPC")
+ fs.item_id.set(label="Item ID")
fs.brand_name.set(label="Brand")
fs.case_quantity.set(renderer=forms.renderers.QuantityFieldRenderer, readonly=True)
fs.cases_ordered.set(renderer=forms.renderers.QuantityFieldRenderer)
@@ -402,6 +404,7 @@ class PurchasingBatchView(BatchMasterView):
include=[
# fs.item_lookup,
fs.upc,
+ fs.item_id,
fs.product,
fs.brand_name,
fs.description,
diff --git a/tailbone/views/purchasing/ordering.py b/tailbone/views/purchasing/ordering.py
index 7f16c9b6..514fdb62 100644
--- a/tailbone/views/purchasing/ordering.py
+++ b/tailbone/views/purchasing/ordering.py
@@ -51,6 +51,9 @@ class OrderingBatchView(PurchasingBatchView):
model_title = "Ordering Batch"
model_title_plural = "Ordering Batches"
index_title = "Ordering"
+ mobile_creatable = True
+ rows_editable = True
+ mobile_rows_editable = True
row_grid_columns = [
'sequence',
@@ -241,6 +244,69 @@ class OrderingBatchView(PurchasingBatchView):
'batch_po_total': '${:0,.2f}'.format(batch.po_total or 0),
}
+ def render_mobile_listitem(self, batch, i):
+ return "({}) {} on {} for ${:0,.2f}".format(batch.id_str, batch.vendor,
+ batch.date_ordered, batch.po_total)
+
+ def mobile_create(self):
+ """
+ Mobile view for creating a new ordering batch
+ """
+ mode = self.batch_mode
+ data = {'mode': mode}
+
+ vendor = None
+ if self.request.method == 'POST' and self.request.POST.get('vendor'):
+ vendor = self.Session.query(model.Vendor).get(self.request.POST['vendor'])
+ if vendor:
+
+ # fetch first to avoid flush below
+ store = self.rattail_config.get_store(self.Session())
+
+ batch = self.model_class()
+ batch.mode = mode
+ batch.vendor = vendor
+ batch.store = store
+ batch.buyer = self.request.user.employee
+ batch.date_ordered = localtime(self.rattail_config).date()
+ batch.created_by = self.request.user
+ batch.po_total = 0
+ kwargs = self.get_batch_kwargs(batch, mobile=True)
+ batch = self.handler.make_batch(self.Session(), **kwargs)
+ if self.handler.should_populate(batch):
+ self.handler.populate(batch)
+ return self.redirect(self.request.route_url('mobile.ordering.view', uuid=batch.uuid))
+
+ data['index_title'] = self.get_index_title()
+ data['index_url'] = self.get_index_url(mobile=True)
+ data['mode_title'] = self.enum.PURCHASE_BATCH_MODE[mode].capitalize()
+ return self.render_to_response('create', data, mobile=True)
+
+ def preconfigure_mobile_fieldset(self, fs):
+ super(OrderingBatchView, self).preconfigure_mobile_fieldset(fs)
+ fs.vendor.set(attrs={'hyperlink': False})
+
+ def configure_mobile_fieldset(self, fs):
+ fields = [
+ fs.vendor,
+ fs.department,
+ fs.date_ordered,
+ fs.po_number,
+ fs.po_total,
+ fs.created,
+ fs.created_by,
+ fs.notes,
+ fs.status_code,
+ fs.complete,
+ ]
+ batch = fs.model
+ if (self.viewing or self.deleting) and batch.executed:
+ fields.extend([
+ fs.executed,
+ fs.executed_by,
+ ])
+ fs.configure(include=fields)
+
def download_excel(self):
"""
Download ordering batch as Excel spreadsheet.