Initial support for mobile ordering
plus various other changes required for that
This commit is contained in:
parent
5afa832684
commit
65c63dad3e
|
@ -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
|
||||
<br />
|
||||
${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
|
||||
|
|
4
tailbone/templates/mobile/master/create_row.mako
Normal file
4
tailbone/templates/mobile/master/create_row.mako
Normal file
|
@ -0,0 +1,4 @@
|
|||
## -*- coding: utf-8; -*-
|
||||
<%inherit file="/mobile/master/create.mako" />
|
||||
|
||||
${parent.body()}
|
10
tailbone/templates/mobile/master/edit.mako
Normal file
10
tailbone/templates/mobile/master/edit.mako
Normal file
|
@ -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>
|
||||
|
||||
<div class="form-wrapper">
|
||||
${form.render()|n}
|
||||
</div><!-- form-wrapper -->
|
16
tailbone/templates/mobile/master/edit_row.mako
Normal file
16
tailbone/templates/mobile/master/edit_row.mako
Normal file
|
@ -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()">
|
||||
<br />
|
||||
${h.submit('create', form.update_label)}
|
||||
<a href="${form.cancel_url}" class="ui-btn">Cancel</a>
|
||||
</%def>
|
||||
|
||||
<div class="form-wrapper">
|
||||
${form.render(buttons=capture(self.buttons))|n}
|
||||
</div><!-- form-wrapper -->
|
|
@ -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
|
||||
|
|
22
tailbone/templates/mobile/ordering/create.mako
Normal file
22
tailbone/templates/mobile/ordering/create.mako
Normal file
|
@ -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)}
|
||||
|
||||
<div class="field-wrapper vendor">
|
||||
<div class="field autocomplete" data-url="${url('vendors.autocomplete')}">
|
||||
${h.hidden('vendor')}
|
||||
${h.text('new-purchasing-batch-vendor-text', placeholder="Vendor name", autocomplete='off', data_type='search')}
|
||||
<ul data-role="listview" data-inset="true" data-filter="true" data-input="#new-purchasing-batch-vendor-text"></ul>
|
||||
<button type="button" style="display: none;">Change Vendor</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br />
|
||||
${h.submit('submit', "Make Batch")}
|
||||
${h.end_form()}
|
6
tailbone/templates/mobile/ordering/create_row.mako
Normal file
6
tailbone/templates/mobile/ordering/create_row.mako
Normal file
|
@ -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()}
|
|
@ -40,7 +40,10 @@
|
|||
<span class="global">${index_title}</span>
|
||||
% else:
|
||||
${h.link_to(index_title, index_url, class_='global')}
|
||||
% if instance_url is not Undefined:
|
||||
% if parent_url is not Undefined:
|
||||
<span class="global">»</span>
|
||||
${h.link_to(parent_title, parent_url, class_='global')}
|
||||
% elif instance_url is not Undefined:
|
||||
<span class="global">»</span>
|
||||
${h.link_to(instance_title, instance_url, class_='global')}
|
||||
% endif
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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:
|
||||
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))
|
||||
config.add_tailbone_permission(permission_prefix, '{}.create_row'.format(permission_prefix),
|
||||
"Create new {} rows".format(model_title))
|
||||
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:
|
||||
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))
|
||||
config.add_tailbone_permission(permission_prefix, '{}.edit_row'.format(permission_prefix),
|
||||
"Edit individual {} rows".format(model_title))
|
||||
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:
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in a new issue