Refactor purchasing batch views to use master3

This commit is contained in:
Lance Edgar 2018-01-27 11:59:52 -06:00
parent d20601c359
commit eac59ba5c8
6 changed files with 233 additions and 154 deletions

View file

@ -54,18 +54,9 @@
</ul> </ul>
<div class="form-wrapper"> <div class="form-wrapper">
## TODO: clean this up or fix etc..? ${form.render()|n}
## % if master.edit_with_rows:
## ${form.render(buttons=capture(buttons))|n}
## % else:
${form.render()|n}
## % endif
</div> </div>
% if master.edit_with_rows:
${rows_grid.render_complete(allow_save_defaults=False, tools=capture(self.grid_tools))|n}
% endif
<div id="execution-options-dialog" style="display: none;"> <div id="execution-options-dialog" style="display: none;">
${h.form(url('{}.execute'.format(route_prefix), uuid=batch.uuid), name='batch-execution')} ${h.form(url('{}.execute'.format(route_prefix), uuid=batch.uuid), name='batch-execution')}

View file

@ -51,7 +51,6 @@
} }
}); });
// show_mode(${form.fieldset.model.mode or enum.PURCHASE_BATCH_MODE_ORDERING});
show_mode(${enum.PURCHASE_BATCH_MODE_ORDERING}); show_mode(${enum.PURCHASE_BATCH_MODE_ORDERING});
}); });

View file

@ -65,7 +65,6 @@ class BatchMasterView(MasterView):
rows_downloadable_csv = True rows_downloadable_csv = True
refreshable = True refreshable = True
refresh_after_create = False refresh_after_create = False
edit_with_rows = False
cloneable = False cloneable = False
executable = True executable = True
results_executable = False results_executable = False
@ -311,53 +310,14 @@ class BatchMasterView(MasterView):
else: else:
return self.redirect(self.get_action_url('view', batch, mobile=mobile)) return self.redirect(self.get_action_url('view', batch, mobile=mobile))
# TODO: some of this at least can go to master now right? def template_kwargs_edit(self, **kwargs):
def edit(self): batch = kwargs['instance']
""" kwargs['batch'] = batch
Don't allow editing a batch which has already been executed. kwargs['execute_title'] = self.get_execute_title(batch)
""" kwargs['execute_enabled'] = self.instance_executable(batch)
self.editing = True if kwargs['execute_enabled'] and self.has_execution_options(batch):
batch = self.get_instance() kwargs['rendered_execution_options'] = self.render_execution_options(batch)
if not self.editable_instance(batch): return kwargs
return self.redirect(self.get_action_url('view', batch))
if self.edit_with_rows:
grid = self.make_row_grid(batch=batch)
# If user just refreshed the page with a reset instruction, issue a
# redirect in order to clear out the query string.
if self.request.GET.get('reset-to-default-filters') == 'true':
return self.redirect(self.request.current_route_url(_query=None))
if self.request.params.get('partial'):
self.request.response.content_type = b'text/html'
self.request.response.text = grid.render_grid()
return self.request.response
form = self.make_form(batch)
if self.request.method == 'POST':
if self.validate_form(form):
self.save_edit_form(form)
self.request.session.flash("{} has been updated: {}".format(
self.get_model_title(), self.get_instance_title(batch)))
return self.redirect_after_edit(batch)
context = {
'instance': batch,
'instance_title': self.get_instance_title(batch),
'instance_deletable': self.deletable_instance(batch),
'form': form,
'batch': batch,
'execute_title': self.get_execute_title(batch),
'execute_enabled': self.instance_executable(batch),
}
if self.edit_with_rows:
context['rows_grid'] = grid
if context['execute_enabled'] and self.has_execution_options(batch):
context['rendered_execution_options'] = self.render_execution_options(batch)
return self.render_to_response('edit', context)
def mobile_mark_complete(self): def mobile_mark_complete(self):
batch = self.get_instance() batch = self.get_instance()
@ -429,8 +389,6 @@ class BatchMasterView(MasterView):
""" """
if self.request.params.get('refresh') == 'true': if self.request.params.get('refresh') == 'true':
return self.redirect(self.get_action_url('refresh', batch)) return self.redirect(self.get_action_url('refresh', batch))
if self.edit_with_rows:
return self.redirect(self.get_action_url('edit', batch))
return self.redirect(self.get_action_url('view', batch)) return self.redirect(self.get_action_url('view', batch))
def delete_instance(self, batch): def delete_instance(self, batch):

View file

@ -76,6 +76,7 @@ class BatchMasterView3(MasterView3, BatchMasterView2):
f.set_label('rowcount', "Row Count") f.set_label('rowcount', "Row Count")
# status_code # status_code
f.set_readonly('status_code')
f.set_renderer('status_code', self.make_status_renderer(self.model_class.STATUS)) f.set_renderer('status_code', self.make_status_renderer(self.model_class.STATUS))
f.set_label('status_code', "Status") f.set_label('status_code', "Status")
@ -141,7 +142,7 @@ class BatchMasterView3(MasterView3, BatchMasterView2):
return batch return batch
def make_status_renderer(self, enum): def make_status_renderer(self, enum):
def render_status(self, batch, field): def render_status(batch, field):
value = batch.status_code value = batch.status_code
if value is None: if value is None:
return "" return ""

View file

@ -2,7 +2,7 @@
################################################################################ ################################################################################
# #
# Rattail -- Retail Software Framework # Rattail -- Retail Software Framework
# Copyright © 2010-2017 Lance Edgar # Copyright © 2010-2018 Lance Edgar
# #
# This file is part of Rattail. # This file is part of Rattail.
# #
@ -26,17 +26,18 @@ Base views for purchasing batches
from __future__ import unicode_literals, absolute_import from __future__ import unicode_literals, absolute_import
# from sqlalchemy import orm import six
from rattail.db import model, api from rattail.db import model, api
# from rattail.gpc import GPC
from rattail.time import localtime from rattail.time import localtime
import formalchemy as fa import colander
from deform import widget as dfwidget
from pyramid import httpexceptions from pyramid import httpexceptions
from webhelpers2.html import tags
from tailbone import forms from tailbone import forms, forms2
from tailbone.views.batch import BatchMasterView2 as BatchMasterView from tailbone.views.batch import BatchMasterView3 as BatchMasterView
class PurchasingBatchView(BatchMasterView): class PurchasingBatchView(BatchMasterView):
@ -76,6 +77,33 @@ class PurchasingBatchView(BatchMasterView):
# 'status_code', # 'status_code',
# ] # ]
form_fields = [
'id',
'store',
'buyer',
'vendor',
'department',
'purchase',
'vendor_email',
'vendor_fax',
'vendor_contact',
'vendor_phone',
'date_ordered',
'date_received',
'po_number',
'po_total',
'invoice_date',
'invoice_number',
'invoice_total',
'notes',
'created',
'created_by',
'status_code',
'complete',
'executed',
'executed_by',
]
@property @property
def batch_mode(self): def batch_mode(self):
raise NotImplementedError("Please define `batch_mode` for your purchasing batch view") raise NotImplementedError("Please define `batch_mode` for your purchasing batch view")
@ -118,29 +146,189 @@ class PurchasingBatchView(BatchMasterView):
# form = super(PurchasingBatchView, self).make_form(batch, **kwargs) # form = super(PurchasingBatchView, self).make_form(batch, **kwargs)
# return form # return form
def _preconfigure_fieldset(self, fs): def configure_form(self, f):
super(PurchasingBatchView, self)._preconfigure_fieldset(fs) super(PurchasingBatchView, self).configure_form(f)
fs.mode.set(renderer=forms.renderers.EnumFieldRenderer(self.enum.PURCHASE_BATCH_MODE)) batch = f.model_instance
fs.store.set(renderer=forms.renderers.StoreFieldRenderer) today = localtime(self.rattail_config).date()
fs.purchase.set(renderer=forms.renderers.PurchaseFieldRenderer, options=[])
fs.vendor.set(renderer=forms.renderers.VendorFieldRenderer,
attrs={'selected': 'vendor_selected',
'cleared': 'vendor_cleared'})
fs.department.set(renderer=forms.renderers.DepartmentFieldRenderer,
options=self.get_department_options())
fs.buyer.set(renderer=forms.renderers.EmployeeFieldRenderer)
fs.po_number.set(label="PO Number")
fs.po_total.set(label="PO Total", readonly=True, renderer=forms.renderers.CurrencyFieldRenderer)
fs.invoice_total.set(readonly=True, renderer=forms.renderers.CurrencyFieldRenderer)
fs.append(fa.Field('vendor_email', readonly=True, # mode
value=lambda b: b.vendor.email.address if b.vendor.email else None)) f.set_enum('mode', self.enum.PURCHASE_BATCH_MODE)
fs.append(fa.Field('vendor_fax', readonly=True,
value=self.get_vendor_fax_number)) # TODO: this hardly seems complete...
fs.append(fa.Field('vendor_contact', readonly=True, # store
value=lambda b: b.vendor.contact or None)) if not self.creating:
fs.append(fa.Field('vendor_phone', readonly=True, f.set_readonly('store')
value=self.get_vendor_phone_number)) f.set_renderer('store', self.render_store)
# purchase
f.set_renderer('purchase', self.render_purchase)
if self.editing:
f.set_readonly('purchase')
# vendor
# fs.vendor.set(renderer=forms.renderers.VendorFieldRenderer,
# attrs={'selected': 'vendor_selected',
# 'cleared': 'vendor_cleared'})
f.set_renderer('vendor', self.render_vendor)
if self.creating:
f.replace('vendor', 'vendor_uuid')
f.set_node('vendor_uuid', colander.String())
vendor_display = ""
if self.request.method == 'POST':
if self.request.POST.get('vendor_uuid'):
vendor = self.Session.query(model.Vendor).get(self.request.POST['vendor_uuid'])
if vendor:
vendor_display = six.text_type(vendor)
vendors_url = self.request.route_url('vendors.autocomplete')
f.set_widget('vendor_uuid', forms2.widgets.JQueryAutocompleteWidget(
field_display=vendor_display, service_url=vendors_url))
f.set_label('vendor_uuid', "Vendor")
elif self.editing:
f.set_readonly('vendor')
# department
f.set_renderer('department', self.render_department)
if self.creating:
f.replace('department', 'department_uuid')
f.set_node('department_uuid', colander.String())
dept_options = self.get_department_options()
dept_values = [(v, k) for k, v in dept_options]
f.set_widget('department_uuid', dfwidget.SelectWidget(values=dept_values))
f.set_label('department_uuid', "Department")
else:
f.set_readonly('department')
# buyer
f.set_renderer('buyer', self.render_buyer)
if self.creating or self.editing:
f.replace('buyer', 'buyer_uuid')
f.set_node('buyer_uuid', colander.String(), missing=colander.null)
buyer_display = ""
if self.request.method == 'POST':
if self.request.POST.get('buyer_uuid'):
buyer = self.Session.query(model.Employee).get(self.request.POST['buyer_uuid'])
if buyer:
buyer_display = six.text_type(buyer)
elif self.creating:
buyer = self.request.user.employee
buyer_display = six.text_type(buyer)
f.set_default('buyer_uuid', buyer.uuid)
elif self.editing:
buyer_display = six.text_type(batch.buyer or '')
buyers_url = self.request.route_url('employees.autocomplete')
f.set_widget('buyer_uuid', forms2.widgets.JQueryAutocompleteWidget(
field_display=buyer_display, service_url=buyers_url))
f.set_label('buyer_uuid', "Buyer")
# date_ordered
f.set_type('date_ordered', 'date_jquery')
if self.creating:
f.set_default('date_ordered', today)
# date_received
f.set_type('date_received', 'date_jquery')
if self.creating:
f.set_default('date_received', today)
# invoice_date
f.set_type('invoice_date', 'date_jquery')
# po_number
f.set_label('po_number', "PO Number")
# po_total
f.set_readonly('po_total')
f.set_type('po_total', 'currency')
f.set_label('po_total', "PO Total")
# invoice_total
f.set_readonly('invoice_total')
f.set_type('invoice_total', 'currency')
# vendor_email
f.set_readonly('vendor_email')
f.set_renderer('vendor_email', self.render_vendor_email)
# vendor_fax
f.set_readonly('vendor_fax')
f.set_renderer('vendor_fax', self.render_vendor_fax)
# vendor_contact
f.set_readonly('vendor_contact')
f.set_renderer('vendor_contact', self.render_vendor_contact)
# vendor_phone
f.set_readonly('vendor_phone')
f.set_renderer('vendor_phone', self.render_vendor_phone)
if self.creating:
f.remove_fields('po_total',
'invoice_total',
'complete',
'vendor_email',
'vendor_fax',
'vendor_phone',
'vendor_contact',
'status_code')
def render_store(self, batch, field):
store = batch.store
if not store:
return ""
text = "({}) {}".format(store.id, store.name)
url = self.request.route_url('stores.view', uuid=store.uuid)
return tags.link_to(text, url)
def render_purchase(self, batch, field):
purchase = batch.purchase
if not purchase:
return ""
text = six.text_type(purchase)
url = self.request.route_url('purchases.view', uuid=purchase.uuid)
return tags.link_to(text, url)
def render_vendor(self, batch, field):
vendor = batch.vendor
if not vendor:
return ""
text = "({}) {}".format(vendor.id, vendor.name)
url = self.request.route_url('vendors.view', uuid=vendor.uuid)
return tags.link_to(text, url)
def render_vendor_email(self, batch, field):
if batch.vendor.email:
return batch.vendor.email.address
def render_vendor_fax(self, batch, field):
return self.get_vendor_fax_number(batch)
def render_vendor_contact(self, batch, field):
if batch.vendor.contact:
return six.text_type(batch.vendor.contact)
def render_vendor_phone(self, batch, field):
return self.get_vendor_phone_number(batch)
def render_department(self, batch, field):
department = batch.department
if not department:
return ""
if department.number:
text = "({}) {}".format(department.number, department.name)
else:
text = department.name
url = self.request.route_url('departments.view', uuid=department.uuid)
return tags.link_to(text, url)
def render_buyer(self, batch, field):
employee = batch.buyer
if not employee:
return ""
text = six.text_type(employee)
if self.request.has_perm('employees.view'):
url = self.request.route_url('employees.view', uuid=employee.uuid)
return tags.link_to(text, url)
return text
def get_department_options(self): def get_department_options(self):
departments = self.Session.query(model.Department).order_by(model.Department.number) departments = self.Session.query(model.Department).order_by(model.Department.number)
@ -156,70 +344,6 @@ class PurchasingBatchView(BatchMasterView):
if phone.type == 'Fax': if phone.type == 'Fax':
return phone.number return phone.number
def configure_fieldset(self, fs):
fs.configure(
include=[
fs.id,
fs.store,
fs.buyer,
fs.vendor,
fs.department,
fs.purchase,
fs.vendor_email,
fs.vendor_fax,
fs.vendor_contact,
fs.vendor_phone,
fs.date_ordered,
fs.date_received,
fs.po_number,
fs.po_total,
fs.invoice_date,
fs.invoice_number,
fs.invoice_total,
fs.notes,
fs.created,
fs.created_by,
fs.status_code,
fs.complete,
fs.executed,
fs.executed_by,
])
if self.creating:
del fs.po_total
del fs.invoice_total
del fs.complete
del fs.vendor_email
del fs.vendor_fax
del fs.vendor_phone
del fs.vendor_contact
del fs.status_code
# default store may be configured
store = self.rattail_config.get('rattail', 'store')
if store:
store = api.get_store(self.Session(), store)
if store:
fs.model.store = store
# default buyer is current user
if self.request.method != 'POST':
buyer = self.request.user.employee
if buyer:
fs.model.buyer = buyer
# TODO: something tells me this isn't quite safe..
# all dates have today as default
today = localtime(self.rattail_config).date()
fs.model.date_ordered = today
fs.model.date_received = today
elif self.editing:
fs.store.set(readonly=True)
fs.vendor.set(readonly=True)
fs.department.set(readonly=True)
fs.purchase.set(readonly=True)
def eligible_purchases(self, vendor_uuid=None, mode=None): def eligible_purchases(self, vendor_uuid=None, mode=None):
if not vendor_uuid: if not vendor_uuid:
vendor_uuid = self.request.GET.get('vendor_uuid') vendor_uuid = self.request.GET.get('vendor_uuid')

View file

@ -86,6 +86,12 @@ class OrderingBatchView(PurchasingBatchView):
def batch_mode(self): def batch_mode(self):
return self.enum.PURCHASE_BATCH_MODE_ORDERING return self.enum.PURCHASE_BATCH_MODE_ORDERING
def configure_form(self, f):
super(OrderingBatchView, self).configure_form(f)
# purchase
f.remove_field('purchase')
def order_form(self): def order_form(self):
""" """
View for editing batch row data as an order form. View for editing batch row data as an order form.