Add basic support for exposing Customer.shoppers
now there is a Shoppers field when viewing a Customer, unless configured otherwise also tweaked some logic for navigating Customer/Person relationships, to handle implications of Shoppers being (maybe) present
This commit is contained in:
parent
afd5c3a5fd
commit
3fde80f991
|
@ -26,12 +26,30 @@
|
|||
|
||||
</b-field>
|
||||
|
||||
<b-field message="If not set, customer chooser is an autocomplete field.">
|
||||
<b-field message="Set this to show the Shoppers field when viewing a Customer record.">
|
||||
<b-checkbox name="rattail.customers.expose_shoppers"
|
||||
v-model="simpleSettings['rattail.customers.expose_shoppers']"
|
||||
native-value="true"
|
||||
@input="settingsNeedSaved = true">
|
||||
Show the Shoppers field
|
||||
</b-checkbox>
|
||||
</b-field>
|
||||
|
||||
<b-field message="Set this to show the People field when viewing a Customer record.">
|
||||
<b-checkbox name="rattail.customers.expose_people"
|
||||
v-model="simpleSettings['rattail.customers.expose_people']"
|
||||
native-value="true"
|
||||
@input="settingsNeedSaved = true">
|
||||
Show the People field
|
||||
</b-checkbox>
|
||||
</b-field>
|
||||
|
||||
<b-field message="If not set, Customer chooser is an autocomplete field.">
|
||||
<b-checkbox name="rattail.customers.choice_uses_dropdown"
|
||||
v-model="simpleSettings['rattail.customers.choice_uses_dropdown']"
|
||||
native-value="true"
|
||||
@input="settingsNeedSaved = true">
|
||||
Show customer chooser as dropdown (select) element
|
||||
Use dropdown (select element) for Customer chooser
|
||||
</b-checkbox>
|
||||
</b-field>
|
||||
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
|
||||
<%def name="object_helpers()">
|
||||
${parent.object_helpers()}
|
||||
% if show_profiles_helper and instance.people:
|
||||
${view_profiles_helper(instance.people)}
|
||||
% if show_profiles_helper and show_profiles_people:
|
||||
${view_profiles_helper(show_profiles_people)}
|
||||
% endif
|
||||
</%def>
|
||||
|
||||
|
@ -20,7 +20,12 @@
|
|||
${parent.modify_this_page_vars()}
|
||||
<script type="text/javascript">
|
||||
|
||||
% if expose_shoppers:
|
||||
${form.component_studly}Data.shoppers = ${json.dumps(shoppers_data)|n}
|
||||
% endif
|
||||
% if expose_people:
|
||||
${form.component_studly}Data.peopleData = ${json.dumps(people_data)|n}
|
||||
% endif
|
||||
|
||||
ThisPage.methods.detachPerson = function(url) {
|
||||
## TODO: this should require POST, but we will add that once
|
||||
|
|
|
@ -24,6 +24,8 @@
|
|||
Customer Views
|
||||
"""
|
||||
|
||||
from collections import OrderedDict
|
||||
|
||||
import sqlalchemy as sa
|
||||
|
||||
import colander
|
||||
|
@ -84,6 +86,7 @@ class CustomerView(MasterView):
|
|||
'wholesale',
|
||||
'active_in_pos',
|
||||
'active_in_pos_sticky',
|
||||
'shoppers',
|
||||
'people',
|
||||
'groups',
|
||||
'members',
|
||||
|
@ -108,6 +111,16 @@ class CustomerView(MasterView):
|
|||
default=False)
|
||||
return self._expose_active_in_pos
|
||||
|
||||
def should_expose_shoppers(self):
|
||||
return self.rattail_config.getbool('rattail',
|
||||
'customers.expose_shoppers',
|
||||
default=True)
|
||||
|
||||
def should_expose_people(self):
|
||||
return self.rattail_config.getbool('rattail',
|
||||
'customers.expose_people',
|
||||
default=True)
|
||||
|
||||
def configure_grid(self, g):
|
||||
super(CustomerView, self).configure_grid(g)
|
||||
model = self.model
|
||||
|
@ -198,15 +211,17 @@ class CustomerView(MasterView):
|
|||
|
||||
raise HTTPNotFound
|
||||
|
||||
def configure_common_form(self, f):
|
||||
super(CustomerView, self).configure_common_form(f)
|
||||
def configure_form(self, f):
|
||||
super(CustomerView, self).configure_form(f)
|
||||
customer = f.model_instance
|
||||
permission_prefix = self.get_permission_prefix()
|
||||
|
||||
# default_email
|
||||
f.set_renderer('default_email', self.render_default_email)
|
||||
if not self.creating and customer.emails:
|
||||
f.set_default('default_email', customer.emails[0].address)
|
||||
|
||||
# default_phone
|
||||
f.set_renderer('default_phone', self.render_default_phone)
|
||||
if not self.creating and customer.phones:
|
||||
f.set_default('default_phone', customer.phones[0].number)
|
||||
|
@ -233,6 +248,7 @@ class CustomerView(MasterView):
|
|||
f.set_default('address_state', addr.state)
|
||||
f.set_default('address_zipcode', addr.zipcode)
|
||||
|
||||
# email_preference
|
||||
f.set_enum('email_preference', self.enum.EMAIL_PREFERENCE)
|
||||
preferences = list(self.enum.EMAIL_PREFERENCE.items())
|
||||
preferences.insert(0, ('', "(no preference)"))
|
||||
|
@ -245,9 +261,21 @@ class CustomerView(MasterView):
|
|||
f.set_readonly('person')
|
||||
f.set_renderer('person', self.form_render_person)
|
||||
|
||||
# shoppers
|
||||
if self.should_expose_shoppers():
|
||||
if self.viewing:
|
||||
f.set_renderer('shoppers', self.render_shoppers)
|
||||
else:
|
||||
f.remove('shoppers')
|
||||
else:
|
||||
f.remove('shoppers')
|
||||
|
||||
# people
|
||||
if self.viewing:
|
||||
f.set_renderer('people', self.render_people_buefy)
|
||||
if self.should_expose_people():
|
||||
if self.viewing:
|
||||
f.set_renderer('people', self.render_people_buefy)
|
||||
else:
|
||||
f.remove('people')
|
||||
else:
|
||||
f.remove('people')
|
||||
|
||||
|
@ -258,11 +286,7 @@ class CustomerView(MasterView):
|
|||
f.set_renderer('groups', self.render_groups)
|
||||
f.set_readonly('groups')
|
||||
|
||||
def configure_form(self, f):
|
||||
super(CustomerView, self).configure_form(f)
|
||||
customer = f.model_instance
|
||||
permission_prefix = self.get_permission_prefix()
|
||||
|
||||
# active_in_pos*
|
||||
if not self.get_expose_active_in_pos():
|
||||
f.remove('active_in_pos',
|
||||
'active_in_pos_sticky')
|
||||
|
@ -275,32 +299,66 @@ class CustomerView(MasterView):
|
|||
f.set_readonly('members')
|
||||
|
||||
def template_kwargs_view(self, **kwargs):
|
||||
kwargs = super(CustomerView, self).template_kwargs_view(**kwargs)
|
||||
kwargs = super().template_kwargs_view(**kwargs)
|
||||
customer = kwargs['instance']
|
||||
|
||||
kwargs['expose_shoppers'] = self.should_expose_shoppers()
|
||||
if kwargs['expose_shoppers']:
|
||||
shoppers = []
|
||||
for shopper in customer.shoppers:
|
||||
person = shopper.person
|
||||
active = None
|
||||
if shopper.active is not None:
|
||||
active = "Yes" if shopper.active else "No"
|
||||
data = {
|
||||
'uuid': shopper.uuid,
|
||||
'shopper_number': shopper.shopper_number,
|
||||
'first_name': person.first_name,
|
||||
'last_name': person.last_name,
|
||||
'full_name': person.display_name,
|
||||
'phone': person.first_phone_number(),
|
||||
'email': person.first_email_address(),
|
||||
'active': active,
|
||||
}
|
||||
shoppers.append(data)
|
||||
kwargs['shoppers_data'] = shoppers
|
||||
|
||||
kwargs['expose_people'] = self.should_expose_people()
|
||||
if kwargs['expose_people']:
|
||||
people = []
|
||||
for person in customer.people:
|
||||
data = {
|
||||
'uuid': person.uuid,
|
||||
'full_name': person.display_name,
|
||||
'first_name': person.first_name,
|
||||
'last_name': person.last_name,
|
||||
'_action_url_view': self.request.route_url('people.view',
|
||||
uuid=person.uuid),
|
||||
}
|
||||
if self.editable and self.request.has_perm('people.edit'):
|
||||
data['_action_url_edit'] = self.request.route_url(
|
||||
'people.edit',
|
||||
uuid=person.uuid)
|
||||
if self.people_detachable and self.has_perm('detach_person'):
|
||||
data['_action_url_detach'] = self.request.route_url(
|
||||
'customers.detach_person',
|
||||
uuid=customer.uuid,
|
||||
person_uuid=person.uuid)
|
||||
people.append(data)
|
||||
kwargs['people_data'] = people
|
||||
|
||||
kwargs['show_profiles_helper'] = self.show_profiles_helper
|
||||
if kwargs['show_profiles_helper']:
|
||||
people = OrderedDict()
|
||||
|
||||
customer = kwargs['instance']
|
||||
people = []
|
||||
for person in customer.people:
|
||||
data = {
|
||||
'uuid': person.uuid,
|
||||
'full_name': person.display_name,
|
||||
'first_name': person.first_name,
|
||||
'last_name': person.last_name,
|
||||
'_action_url_view': self.request.route_url('people.view',
|
||||
uuid=person.uuid),
|
||||
}
|
||||
if self.editable and self.request.has_perm('people.edit'):
|
||||
data['_action_url_edit'] = self.request.route_url(
|
||||
'people.edit',
|
||||
uuid=person.uuid)
|
||||
if self.people_detachable and self.has_perm('detach_person'):
|
||||
data['_action_url_detach'] = self.request.route_url(
|
||||
'customers.detach_person',
|
||||
uuid=customer.uuid,
|
||||
person_uuid=person.uuid)
|
||||
people.append(data)
|
||||
kwargs['people_data'] = people
|
||||
for shopper in customer.shoppers:
|
||||
person = shopper.person
|
||||
people.setdefault(person.uuid, person)
|
||||
|
||||
for person in customer.people:
|
||||
people.setdefault(person.uuid, person)
|
||||
|
||||
kwargs['show_profiles_people'] = list(people.values())
|
||||
|
||||
return kwargs
|
||||
|
||||
|
@ -375,6 +433,35 @@ class CustomerView(MasterView):
|
|||
main_actions=actions)
|
||||
return HTML.literal(g.render_grid())
|
||||
|
||||
def render_shoppers(self, customer, field):
|
||||
route_prefix = self.get_route_prefix()
|
||||
permission_prefix = self.get_permission_prefix()
|
||||
|
||||
factory = self.get_grid_factory()
|
||||
g = factory(
|
||||
key='{}.people'.format(route_prefix),
|
||||
data=[],
|
||||
columns=[
|
||||
'shopper_number',
|
||||
'first_name',
|
||||
'last_name',
|
||||
'phone',
|
||||
'email',
|
||||
'active',
|
||||
],
|
||||
sortable=True,
|
||||
sorters={'shopper_number': True,
|
||||
'first_name': True,
|
||||
'last_name': True,
|
||||
'phone': True,
|
||||
'email': True,
|
||||
'active': True},
|
||||
labels={'shopper_number': "Shopper #"},
|
||||
)
|
||||
|
||||
return HTML.literal(
|
||||
g.render_buefy_table_element(data_prop='shoppers'))
|
||||
|
||||
def render_people_buefy(self, customer, field):
|
||||
route_prefix = self.get_route_prefix()
|
||||
permission_prefix = self.get_permission_prefix()
|
||||
|
@ -492,6 +579,14 @@ class CustomerView(MasterView):
|
|||
{'section': 'rattail',
|
||||
'option': 'customers.choice_uses_dropdown',
|
||||
'type': bool},
|
||||
{'section': 'rattail',
|
||||
'option': 'customers.expose_shoppers',
|
||||
'type': bool,
|
||||
'default': True},
|
||||
{'section': 'rattail',
|
||||
'option': 'customers.expose_people',
|
||||
'type': bool,
|
||||
'default': True},
|
||||
|
||||
# POS
|
||||
{'section': 'rattail',
|
||||
|
|
|
@ -4878,7 +4878,7 @@ class MasterView(View):
|
|||
elif simple.get('type') is bool:
|
||||
value = config.getbool(simple['section'],
|
||||
simple['option'],
|
||||
default=False)
|
||||
default=simple.get('default', False))
|
||||
else:
|
||||
value = config.get(simple['section'],
|
||||
simple['option'])
|
||||
|
|
|
@ -550,13 +550,16 @@ class PersonView(MasterView):
|
|||
return context
|
||||
|
||||
def get_context_customers(self, person):
|
||||
app = self.get_rattail_app()
|
||||
clientele = app.get_clientele_handler()
|
||||
|
||||
customers = clientele.get_customers_for_account_holder(person)
|
||||
key = self.get_customer_key_field()
|
||||
data = []
|
||||
for cp in person._customers:
|
||||
customer = cp.customer
|
||||
|
||||
for customer in customers:
|
||||
data.append({
|
||||
'uuid': customer.uuid,
|
||||
'ordinal': cp.ordinal,
|
||||
'_key': getattr(customer, key),
|
||||
'id': customer.id,
|
||||
'number': customer.number,
|
||||
|
@ -568,19 +571,19 @@ class PersonView(MasterView):
|
|||
'addresses': [self.get_context_address(a)
|
||||
for a in customer.addresses],
|
||||
})
|
||||
|
||||
return data
|
||||
|
||||
def get_context_members(self, person):
|
||||
app = self.get_rattail_app()
|
||||
membership = app.get_membership_handler()
|
||||
|
||||
data = OrderedDict()
|
||||
|
||||
for member in person.members:
|
||||
members = membership.get_members_for_account_holder(person)
|
||||
for member in members:
|
||||
data[member.uuid] = self.get_context_member(member)
|
||||
|
||||
for customer in person.customers:
|
||||
for member in customer.members:
|
||||
if member.uuid not in data:
|
||||
data[member.uuid] = self.get_context_member(member)
|
||||
|
||||
return list(data.values())
|
||||
|
||||
def get_context_member(self, member):
|
||||
|
@ -1115,6 +1118,40 @@ class PersonView(MasterView):
|
|||
.filter(model.CustomerPerson.person_uuid == person.uuid)
|
||||
versions.extend(query.all())
|
||||
|
||||
# nb. this is used in some queries below
|
||||
FirstShopper = orm.aliased(model.CustomerShopper)
|
||||
|
||||
# CustomerShopper (from Customer perspective)
|
||||
cls = continuum.version_class(model.CustomerShopper)
|
||||
query = self.Session.query(cls)\
|
||||
.join(model.Customer,
|
||||
model.Customer.uuid == cls.customer_uuid)\
|
||||
.filter(model.Customer.account_holder_uuid == person.uuid)
|
||||
versions.extend(query.all())
|
||||
|
||||
# CustomerShopperHistory (from Customer perspective)
|
||||
cls = continuum.version_class(model.CustomerShopperHistory)
|
||||
query = self.Session.query(cls)\
|
||||
.join(model.CustomerShopper,
|
||||
model.CustomerShopper.uuid == cls.shopper_uuid)\
|
||||
.join(model.Customer)\
|
||||
.filter(model.Customer.account_holder_uuid == person.uuid)
|
||||
versions.extend(query.all())
|
||||
|
||||
# CustomerShopper (from Shopper perspective)
|
||||
cls = continuum.version_class(model.CustomerShopper)
|
||||
query = self.Session.query(cls)\
|
||||
.filter(cls.person_uuid == person.uuid)
|
||||
versions.extend(query.all())
|
||||
|
||||
# CustomerShopperHistory (from Shopper perspective)
|
||||
cls = continuum.version_class(model.CustomerShopperHistory)
|
||||
query = self.Session.query(cls)\
|
||||
.join(model.CustomerShopper,
|
||||
model.CustomerShopper.uuid == cls.shopper_uuid)\
|
||||
.filter(model.CustomerShopper.person_uuid == person.uuid)
|
||||
versions.extend(query.all())
|
||||
|
||||
# PersonNote
|
||||
cls = continuum.version_class(model.PersonNote)
|
||||
query = self.Session.query(cls)\
|
||||
|
|
Loading…
Reference in a new issue