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
+
+
+
+
%def>
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 @@
${b}-tab-item>
%def>
+% if expose_transactions:
+
+ <%def name="render_transactions_tab_template()">
+
+ %def>
+
+ <%def name="render_transactions_tab()">
+ <${b}-tab-item label="Transactions"
+ value="transactions"
+ % if not request.use_oruga:
+ icon-pack="fas"
+ % endif
+ icon="bars">
+
+ ${b}-tab-item>
+ %def>
+
+% endif
+
+
<%def name="render_user_tab_template()">
%def>
+% if expose_transactions:
+
+ <%def name="declare_transactions_tab_vars()">
+
+ %def>
+
+ <%def name="make_transactions_tab_component()">
+ ${self.declare_transactions_tab_vars()}
+
+ %def>
+
+% endif
+
<%def name="declare_user_tab_vars()">
+ ${self.make_transactions_tab_component()}
+ % endif
+
${self.make_user_tab_component()}
${self.make_profile_info_component()}
%def>
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')