diff --git a/tailbone/templates/people/configure.mako b/tailbone/templates/people/configure.mako index d821d898..7d7a5618 100644 --- a/tailbone/templates/people/configure.mako +++ b/tailbone/templates/people/configure.mako @@ -33,6 +33,20 @@ + +

Profile View

+
+ + + + Show tab for Customer POS Transactions + + + +
diff --git a/tailbone/templates/people/view_profile.mako b/tailbone/templates/people/view_profile.mako index 1eac6a2f..9d9ab37d 100644 --- a/tailbone/templates/people/view_profile.mako +++ b/tailbone/templates/people/view_profile.mako @@ -1656,6 +1656,34 @@ +% if expose_transactions: + + <%def name="render_transactions_tab_template()"> + + + + <%def name="render_transactions_tab()"> + <${b}-tab-item label="Transactions" + value="transactions" + % if not request.use_oruga: + icon-pack="fas" + % endif + icon="bars"> + + + + +% endif + + <%def name="render_user_tab_template()"> +% if expose_transactions: + + <%def name="declare_transactions_tab_vars()"> + + + + <%def name="make_transactions_tab_component()"> + ${self.declare_transactions_tab_vars()} + + + +% endif + <%def name="declare_user_tab_vars()"> + ${self.make_transactions_tab_component()} + % endif + ${self.make_user_tab_component()} ${self.make_profile_info_component()} diff --git a/tailbone/views/people.py b/tailbone/views/people.py index 08e32c3c..2cabf1ec 100644 --- a/tailbone/views/people.py +++ b/tailbone/views/people.py @@ -40,6 +40,7 @@ import colander from webhelpers2.html import HTML, tags from tailbone import forms, grids +from tailbone.db import TrainwreckSession from tailbone.views import MasterView from tailbone.util import raw_datetime @@ -487,13 +488,101 @@ class PersonView(MasterView): 'expose_customer_shoppers': self.customers_should_expose_shoppers(), 'max_one_member': app.get_membership_handler().max_one_per_person(), 'use_preferred_first_name': self.people_handler.should_use_preferred_first_name(), + 'expose_transactions': self.should_expose_profile_transactions(), } + if context['expose_transactions']: + context['transactions_grid'] = self.profile_transactions_grid(person, empty=True) + if self.request.has_perm('people_profile.view_versions'): context['revisions_grid'] = self.profile_revisions_grid(person) return self.render_to_response('view_profile', context) + def should_expose_profile_transactions(self): + return self.rattail_config.get_bool('tailbone.people.profile.expose_transactions', + default=False) + + def profile_transactions_grid(self, person, empty=False): + app = self.get_rattail_app() + trainwreck = app.get_trainwreck_handler() + model = trainwreck.get_model() + route_prefix = self.get_route_prefix() + if empty: + # TODO: surely there is a better way to have empty data..? but so + # much logic depends on a query, can't just pass empty list here + data = TrainwreckSession.query(model.Transaction)\ + .filter(model.Transaction.uuid == 'bogus') + else: + data = self.profile_transactions_query(person) + factory = self.get_grid_factory() + g = factory( + f'{route_prefix}.profile.transactions.{person.uuid}', + data, + request=self.request, + model_class=model.Transaction, + ajax_data_url=self.get_action_url('view_profile_transactions', person), + columns=[ + 'start_time', + 'end_time', + 'system', + 'terminal_id', + 'receipt_number', + 'cashier_name', + 'customer_id', + 'customer_name', + 'total', + ], + labels={ + 'terminal_id': "Terminal", + 'customer_id': "Customer " + app.get_customer_key_label(), + }, + filterable=True, + sortable=True, + pageable=True, + default_sortkey='end_time', + default_sortdir='desc', + component='transactions-grid', + ) + if self.request.has_perm('trainwreck.transactions.view'): + url = lambda row, i: self.request.route_url('trainwreck.transactions.view', + uuid=row.uuid) + g.main_actions.append(grids.GridAction('view', icon='eye', url=url)) + g.load_settings() + + g.set_enum('system', self.enum.TRAINWRECK_SYSTEM) + g.set_type('total', 'currency') + + return g + + def profile_transactions_query(self, person): + """ + Method which must return the base query for the profile's POS + Transactions grid data. + """ + app = self.get_rattail_app() + customer = app.get_customer(person) + + key_field = app.get_customer_key_field() + customer_key = getattr(customer, key_field) + if customer_key is not None: + customer_key = str(customer_key) + + trainwreck = app.get_trainwreck_handler() + model = trainwreck.get_model() + query = TrainwreckSession.query(model.Transaction)\ + .filter(model.Transaction.customer_id == customer_key) + return query + + def profile_transactions_data(self): + """ + AJAX view to return new sorted, filtered data for transactions + grid within profile view. + """ + person = self.get_instance() + grid = self.profile_transactions_grid(person) + return grid.get_table_data() + def get_context_tabchecks(self, person): app = self.get_rattail_app() membership = app.get_membership_handler() @@ -1605,6 +1694,11 @@ class PersonView(MasterView): {'section': 'rattail', 'option': 'people.handler'}, + + # Profile View + {'section': 'tailbone', + 'option': 'people.profile.expose_transactions', + 'type': bool}, ] @classmethod @@ -1873,6 +1967,15 @@ class PersonView(MasterView): permission='people_profile.delete_note', renderer='json') + # profile - transactions data + config.add_route(f'{route_prefix}.view_profile_transactions', + f'{instance_url_prefix}/profile/transactions', + request_method='GET') + config.add_view(cls, attr='profile_transactions_data', + route_name=f'{route_prefix}.view_profile_transactions', + permission=f'{permission_prefix}.view_profile', + renderer='json') + # make user for person config.add_route('{}.make_user'.format(route_prefix), '{}/make-user'.format(url_prefix), request_method='POST')