From 18f4b4ff5c4fae6f7f9a2e0ace9f26fe56b74b30 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Wed, 9 Aug 2017 21:41:42 -0500 Subject: [PATCH] Various changes to support a certain new app improve inventory support, plus "hiding" person data but still using it --- tailbone/views/batch/core.py | 1 + tailbone/views/batch/core2.py | 3 +++ tailbone/views/customers.py | 37 +++++++++++++++++++++++------------ tailbone/views/inventory.py | 11 ++++++++++- tailbone/views/master.py | 2 +- tailbone/views/products.py | 2 ++ tailbone/views/users.py | 26 ++++++++++++++++++++---- 7 files changed, 64 insertions(+), 18 deletions(-) diff --git a/tailbone/views/batch/core.py b/tailbone/views/batch/core.py index d0ec1245..24cd7850 100644 --- a/tailbone/views/batch/core.py +++ b/tailbone/views/batch/core.py @@ -274,6 +274,7 @@ class BatchMasterView(MasterView): kwargs['created_by'] = batch.created_by elif batch.created_by_uuid: kwargs['created_by_uuid'] = batch.created_by_uuid + kwargs['description'] = batch.description kwargs['notes'] = batch.notes if hasattr(batch, 'filename'): kwargs['filename'] = batch.filename diff --git a/tailbone/views/batch/core2.py b/tailbone/views/batch/core2.py index d9a36f3f..d2542803 100644 --- a/tailbone/views/batch/core2.py +++ b/tailbone/views/batch/core2.py @@ -80,6 +80,9 @@ class BatchMasterView2(MasterView2, BatchMasterView): g.set_renderer('id', self.render_batch_id) g.set_link('id') + g.set_link('description') + g.set_link('created') + g.set_link('executed') g.set_label('id', "Batch ID") g.set_label('created_by', "Created by") diff --git a/tailbone/views/customers.py b/tailbone/views/customers.py index 4ef932ca..0a4b8649 100644 --- a/tailbone/views/customers.py +++ b/tailbone/views/customers.py @@ -31,6 +31,7 @@ import re import sqlalchemy as sa from sqlalchemy import orm +import formalchemy as fa from pyramid.httpexceptions import HTTPNotFound from tailbone import forms @@ -127,26 +128,29 @@ class CustomersView(MasterView): fs.id.set(label="ID") fs.append(forms.fields.DefaultPhoneField('default_phone', label="Phone Number")) fs.append(forms.fields.DefaultEmailField('default_email', label="Email Address")) - fs.email_preference.set(renderer=forms.EnumFieldRenderer(self.enum.EMAIL_PREFERENCE)) + fs.email_preference.set(renderer=forms.EnumFieldRenderer(self.enum.EMAIL_PREFERENCE), + attrs={'auto-enhance': 'true'}) + fs.email_preference._null_option = ("(no preference)", '') fs.append(forms.AssociationProxyField('people', renderer=forms.renderers.PeopleFieldRenderer, readonly=True)) fs.active_in_pos.set(label="Active in POS") fs.active_in_pos_sticky.set(label="Always Active in POS") def configure_fieldset(self, fs): - fs.configure( - include=[ - fs.id, - fs.name, - # fs.phone.label("Phone Number").readonly(), - fs.default_phone, - # fs.email.label("Email Address").readonly(), - fs.default_email, - fs.email_preference, + include = [ + fs.id, + fs.name, + fs.default_phone, + fs.default_email, + fs.email_preference, + fs.active_in_pos, + fs.active_in_pos_sticky, + ] + if not self.creating: + include.extend([ fs.people, - fs.active_in_pos, - fs.active_in_pos_sticky, ]) + fs.configure(include=include) def configure_mobile_fieldset(self, fs): fs.configure( @@ -164,6 +168,15 @@ class CustomersView(MasterView): ] +def unique_id(value, field): + customer = field.parent.model + query = Session.query(model.Customer).filter(model.Customer.id == value) + if customer.uuid: + query = query.filter(model.Customer.uuid != customer.uuid) + if query.count(): + raise fa.ValidationError("Customer ID must be unique") + + class CustomerNameAutocomplete(AutocompleteView): """ Autocomplete view which operates on customer name. diff --git a/tailbone/views/inventory.py b/tailbone/views/inventory.py index cff5f134..dff7f435 100644 --- a/tailbone/views/inventory.py +++ b/tailbone/views/inventory.py @@ -59,6 +59,7 @@ class InventoryBatchView(BatchMasterView): 'id', 'created', 'created_by', + 'description', 'mode', 'rowcount', 'total_cost', @@ -76,6 +77,7 @@ class InventoryBatchView(BatchMasterView): 'brand_name', 'description', 'size', + 'previous_units_on_hand', 'cases', 'units', 'unit_cost', @@ -99,7 +101,7 @@ class InventoryBatchView(BatchMasterView): def _preconfigure_fieldset(self, fs): super(InventoryBatchView, self)._preconfigure_fieldset(fs) fs.mode.set(renderer=forms.renderers.EnumFieldRenderer(self.enum.INVENTORY_MODE), - label="Count Mode") + label="Count Mode", required=True, attrs={'auto-enhance': 'true'}) 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)) @@ -108,6 +110,7 @@ class InventoryBatchView(BatchMasterView): fs.configure( include=[ fs.id, + fs.description, fs.created, fs.created_by, fs.handheld_batches, @@ -118,6 +121,8 @@ class InventoryBatchView(BatchMasterView): fs.executed, fs.executed_by, ]) + if not self.creating: + fs.mode.set(readonly=True) def save_edit_row_form(self, form): row = form.fieldset.model @@ -241,6 +246,7 @@ class InventoryBatchView(BatchMasterView): def configure_row_grid(self, g): super(InventoryBatchView, self).configure_row_grid(g) + g.set_type('previous_units_on_hand', 'quantity') g.set_type('cases', 'quantity') g.set_type('units', 'quantity') g.set_type('unit_cost', 'currency') @@ -254,6 +260,7 @@ class InventoryBatchView(BatchMasterView): g.set_label('upc', "UPC") g.set_label('brand_name', "Brand") g.set_label('status_code', "Status") + g.set_label('previous_units_on_hand', "Prev. On Hand") def row_grid_extra_class(self, row, i): if row.status_code == row.STATUS_PRODUCT_NOT_FOUND: @@ -273,6 +280,7 @@ class InventoryBatchView(BatchMasterView): fs.brand_name.set(readonly=True) fs.description.set(readonly=True) fs.size.set(readonly=True) + fs.previous_units_on_hand.set(label="Prev. On Hand") fs.cases.set(renderer=forms.renderers.QuantityFieldRenderer) fs.units.set(renderer=forms.renderers.QuantityFieldRenderer) fs.unit_cost.set(readonly=True, renderer=forms.renderers.CurrencyFieldRenderer) @@ -287,6 +295,7 @@ class InventoryBatchView(BatchMasterView): fs.description, fs.size, fs.status_code, + fs.previous_units_on_hand, fs.cases, fs.units, fs.unit_cost, diff --git a/tailbone/views/master.py b/tailbone/views/master.py index 6fb33572..7d2c943a 100644 --- a/tailbone/views/master.py +++ b/tailbone/views/master.py @@ -1650,7 +1650,7 @@ class MasterView(View): """ def redirect_after_edit_row(self, row, mobile=False): - return self.redirect(self.get_row_action_url('view', row, mobile=True)) + return self.redirect(self.get_row_action_url('view', row, mobile=mobile)) def row_deletable(self, row): """ diff --git a/tailbone/views/products.py b/tailbone/views/products.py index 10f85361..a7bc02ae 100644 --- a/tailbone/views/products.py +++ b/tailbone/views/products.py @@ -256,6 +256,8 @@ class ProductsView(MasterView): cost = product.cost if not cost: return "" + if cost.unit_cost is None: + return "" return "${:0.2f}".format(cost.unit_cost) def render_on_hand(self, product, column): diff --git a/tailbone/views/users.py b/tailbone/views/users.py index 53649789..d17722dd 100644 --- a/tailbone/views/users.py +++ b/tailbone/views/users.py @@ -159,6 +159,7 @@ class UsersView(PrincipalMasterView): def query(self, session): return session.query(model.User)\ + .outerjoin(model.Person)\ .options(orm.joinedload(model.User.person)) def configure_grid(self, g): @@ -177,28 +178,44 @@ class UsersView(PrincipalMasterView): g.filters['password'] = g.make_filter('password', model.User.password, verbs=['is_null', 'is_not_null']) - g.sorters['person'] = lambda q, d: q.order_by(getattr(model.Person.display_name, d)()) + g.set_sorter('person', model.Person.display_name) + g.set_sorter('first_name', model.Person.first_name) + g.set_sorter('last_name', model.Person.last_name) + g.set_sorter('display_name', model.Person.display_name) g.default_sortkey = 'username' g.set_label('person', "Person's Name") g.set_link('username') g.set_link('person') + g.set_link('first_name') + g.set_link('last_name') + g.set_link('display_name') def _preconfigure_fieldset(self, fs): fs.username.set(renderer=forms.renderers.StrippedTextFieldRenderer, validate=unique_username) fs.person.set(renderer=forms.renderers.PersonFieldRenderer, options=[]) fs.append(PasswordField('password', label="Set Password")) - fs.password.attrs(autocomplete='off') fs.append(formalchemy.Field('confirm_password', renderer=PasswordFieldRenderer)) - fs.confirm_password.attrs(autocomplete='off') fs.append(RolesField('roles', renderer=RolesFieldRenderer(self.request), size=10)) + fs.append(forms.AssociationProxyField('first_name')) + fs.append(forms.AssociationProxyField('last_name')) + fs.append(forms.AssociationProxyField('display_name', label="Full Name")) + + # hm this should work according to MDN but doesn't seem to... + # https://developer.mozilla.org/en-US/docs/Web/Security/Securing_your_site/Turning_off_form_autocompletion + fs.username.attrs(autocomplete='new-password') + fs.password.attrs(autocomplete='new-password') + fs.confirm_password.attrs(autocomplete='new-password') def configure_fieldset(self, fs): fs.configure( include=[ fs.username, fs.person, + fs.first_name, + fs.last_name, + fs.display_name, fs.active, fs.active_sticky, fs.password, @@ -326,7 +343,8 @@ class UserEventsView(MasterView): return event.user.username def render_person(self, event, column): - return event.user.person.display_name + if event.user.person: + return event.user.person.display_name def includeme(config):