mirror of
https://bitbucket.org/presik/trytonpsk-crm.git
synced 2023-12-14 05:22:56 +01:00
637 lines
22 KiB
Python
637 lines
22 KiB
Python
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
|
# this repository contains the full copyright notices and license terms.
|
|
from __future__ import with_statement
|
|
|
|
from datetime import datetime, time
|
|
from trytond.model import Workflow, ModelView, ModelSQL, fields
|
|
from trytond.pyson import Eval, If, In, Get
|
|
from trytond.transaction import Transaction
|
|
from trytond.pool import Pool
|
|
from trytond.wizard import Wizard, StateView, Button, StateReport, StateTransition
|
|
from trytond.report import Report
|
|
from .exceptions import CustomerServiceError, CrmConfigurationError
|
|
from trytond.i18n import gettext
|
|
|
|
STATES = {
|
|
'readonly': (Eval('state') != 'draft'),
|
|
}
|
|
|
|
STATES_OPEN = {
|
|
'readonly': (Eval('state') != 'open'),
|
|
'required': (Eval('state') == 'close'),
|
|
}
|
|
|
|
STATES_CLOSE = {
|
|
'readonly': (Eval('state') != 'open'),
|
|
'required': (Eval('state') == 'close'),
|
|
}
|
|
|
|
OPTION_SELECT = [
|
|
('', ''),
|
|
('N.A', 'N.A'),
|
|
('yes', 'Yes'),
|
|
('no', 'No'),
|
|
]
|
|
|
|
SEXUAL_DIVERSITY = [
|
|
('N.A', 'N.A'),
|
|
('', 'HETEROSEXUAL'),
|
|
('', 'GAY'),
|
|
('', 'LESBIANA'),
|
|
('', 'TRAVESTI'),
|
|
('', 'BISEXUAL')
|
|
]
|
|
|
|
ETHNICAL_GROUP = [
|
|
('N.A', 'N.A'),
|
|
('afrocolombiano', 'AFROCOLOMBIANO'),
|
|
('palenquero', 'PALENQUERO'),
|
|
('indigena', 'INDÍGENA'),
|
|
('raizal', 'RAIZAL'),
|
|
('rom_gitano', 'ROM O GITANO'),
|
|
('mulato', 'MULATO'),
|
|
]
|
|
|
|
|
|
class CustomerService(Workflow, ModelSQL, ModelView):
|
|
'Customer Service'
|
|
__name__ = 'crm.customer_service'
|
|
_rec_name = 'number'
|
|
number = fields.Char('Number', readonly=True)
|
|
reference = fields.Char('Reference', states=STATES)
|
|
party = fields.Many2One('party.party', 'Party',
|
|
states={
|
|
'readonly': (Eval('state') != 'draft'),
|
|
# 'required': (Eval('state') == 'open'),
|
|
}, select=True)
|
|
route = fields.Char('Route', states=STATES, select=True)
|
|
customer = fields.Char('Customer', states=STATES,
|
|
required=True, select=True)
|
|
address = fields.Char('Address', states=STATES, select=True,
|
|
depends=['party'])
|
|
phone = fields.Char('Phone', states=STATES, select=True,
|
|
depends=['party'])
|
|
city = fields.Char('City', states=STATES, select=True,
|
|
depends=['party'])
|
|
case = fields.Many2One('crm.case', 'Case', states=STATES,
|
|
required=True, domain=[
|
|
('parent', '!=', None),
|
|
])
|
|
description = fields.Text('Description', required=True, states=STATES)
|
|
diagnose = fields.Text('Diagnose', states=STATES_CLOSE)
|
|
response = fields.Text('Response', states=STATES_CLOSE)
|
|
cs_date = fields.DateTime('Date', states={'readonly': True})
|
|
effective_date = fields.DateTime('Effective Datetime',
|
|
states=STATES_CLOSE, depends=['state'])
|
|
responsible_employee = fields.Many2One('company.employee',
|
|
'Responsible', states=STATES_CLOSE)
|
|
company = fields.Many2One('company.company', 'Company', required=True,
|
|
states=STATES, domain=[
|
|
('id', If(In('company',
|
|
Eval('context', {})), '=', '!='), Get(Eval('context', {}),
|
|
'company', 0)), ])
|
|
satisfaction = fields.Selection(OPTION_SELECT, 'Satisfaction')
|
|
satisfaction_string = satisfaction.translated('satisfaction')
|
|
notes = fields.Text('Notes', states=STATES_CLOSE)
|
|
efficacy = fields.Function(fields.Selection([
|
|
('on_time', 'On Time'),
|
|
('delay', 'Delay'),
|
|
], 'Efficacy', states=STATES_CLOSE), 'get_efficacy')
|
|
efficacy_string = efficacy.translated('efficacy')
|
|
department = fields.Many2One('company.department', 'Department')
|
|
category_customer = fields.Many2One('party.category', 'Category',
|
|
required=False, states=STATES)
|
|
state = fields.Selection([
|
|
('draft', 'Draft'),
|
|
('open', 'Open'),
|
|
('close', 'Close'),
|
|
('cancelled', 'Cancelled'),
|
|
], 'State', readonly=True, required=True)
|
|
state_string = state.translated('state')
|
|
media = fields.Selection([
|
|
('', ''),
|
|
('phone', 'Phone'),
|
|
('fax', 'Fax'),
|
|
('mail', 'Mail'),
|
|
('chat', 'Chat'),
|
|
('direct', 'Direct'),
|
|
('web', 'Web'),
|
|
('survey', 'Survey'),
|
|
('other', 'Other'),
|
|
], 'Media', states=STATES)
|
|
action_plan = fields.Text('Action Plan', states=STATES_CLOSE)
|
|
detailed_especific_1= fields.Char('Detailed Especific 1',
|
|
states={'required': True})
|
|
detailed_especific_2= fields.Char('Detailed Especific 2')
|
|
detailed_especific_3= fields.Char('Detailed Especific 3')
|
|
ethnical_group = fields.Selection(ETHNICAL_GROUP, 'Ethnical Group',
|
|
states={'required': True})
|
|
sexual_diversity = fields.Selection(SEXUAL_DIVERSITY, 'Sexual Diversity',
|
|
states={'required': True})
|
|
displaced = fields.Selection(OPTION_SELECT, 'Displaced', states={'required': True})
|
|
victim_of_violence = fields.Selection(OPTION_SELECT, 'Victim Of Violence',
|
|
states={'required': True})
|
|
disabled_person = fields.Selection(OPTION_SELECT, 'Disabled Person',
|
|
states={'required': True})
|
|
pregnant_woman = fields.Selection(OPTION_SELECT, 'Pregnan Woman',
|
|
states={'required': True})
|
|
homeless = fields.Selection(OPTION_SELECT, 'Homeless', states={'required': True})
|
|
deprived_of_freedom = fields.Selection(OPTION_SELECT, 'Deprived Of Freedom',
|
|
states={'required': True})
|
|
age = fields.Function(fields.Char('Age'), 'get_age')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(CustomerService, cls).__setup__()
|
|
cls._order.insert(0, ('create_date', 'DESC'))
|
|
cls._order.insert(1, ('id', 'DESC'))
|
|
cls._transitions |= set((
|
|
('draft', 'open'),
|
|
('open', 'draft'),
|
|
('open', 'close'),
|
|
('open', 'cancelled'),
|
|
('close', 'cancelled'),
|
|
('cancelled', 'draft'),
|
|
))
|
|
cls._buttons.update({
|
|
'draft': {
|
|
'invisible': Eval('state').in_(['draft','close'])
|
|
},
|
|
'cancel': {
|
|
'invisible': Eval('state') == 'cancelled',
|
|
},
|
|
'open': {
|
|
'invisible': Eval('state') != 'draft',
|
|
},
|
|
'close': {
|
|
'invisible': Eval('state') != 'open',
|
|
},
|
|
})
|
|
|
|
@staticmethod
|
|
def default_company():
|
|
return Transaction().context.get('company') or False
|
|
|
|
@staticmethod
|
|
def default_state():
|
|
return 'draft'
|
|
|
|
@staticmethod
|
|
def default_cs_date():
|
|
return datetime.now()
|
|
|
|
@staticmethod
|
|
def default_efficacy():
|
|
return 'on_time'
|
|
|
|
def get_age(self, name=None):
|
|
if self.party:
|
|
self.age = self.party.age
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
@Workflow.transition('open')
|
|
def open(cls, services):
|
|
for service in services:
|
|
service.set_number()
|
|
return {}
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
@Workflow.transition('cancelled')
|
|
def cancel(cls, services):
|
|
pass
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
@Workflow.transition('draft')
|
|
def draft(cls, services):
|
|
pass
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
@Workflow.transition('close')
|
|
def close(cls, services):
|
|
for service in services:
|
|
pass
|
|
|
|
@classmethod
|
|
def delete(cls, records):
|
|
for record in records:
|
|
if record.number:
|
|
raise CustomerServiceError(
|
|
gettext('crm.msg_delete_numbered', s=record.number)
|
|
)
|
|
super(CustomerService, cls).delete(records)
|
|
|
|
@fields.depends('party', 'address', 'phone')
|
|
def on_change_party(self):
|
|
if self.party:
|
|
if hasattr(self.party, 'street') and hasattr(self.party, 'city_name'):
|
|
self.address = self.party.street
|
|
self.city = self.party.city_name
|
|
self.phone = self.party.phone or self.party.mobile
|
|
self.customer = self.party.name
|
|
|
|
def get_efficacy(self, name):
|
|
pool = Pool()
|
|
config = pool.get('crm.configuration')(1)
|
|
now = datetime.now()
|
|
if not config.efficay_hour_limit:
|
|
raise CrmConfigurationError(
|
|
gettext('crm.msg_missing_configuration_limit_hour'))
|
|
val_hr = config.efficay_hour_limit
|
|
if self.effective_date:
|
|
lapse = self.effective_date - self.create_date
|
|
else:
|
|
lapse = now - self.create_date
|
|
lapse_hours = (lapse.days * 24) + (lapse.seconds / 3600)
|
|
if lapse_hours >= val_hr:
|
|
return 'delay'
|
|
else:
|
|
return 'on_time'
|
|
|
|
def set_number(self):
|
|
'''
|
|
Set sequence number
|
|
'''
|
|
pool = Pool()
|
|
config = pool.get('crm.configuration')(1)
|
|
if not config.crm_customer_service_sequence:
|
|
raise CrmConfigurationError(
|
|
gettext('crm.msg_missing_sequence_customer_service'))
|
|
seq = config.crm_customer_service_sequence.get()
|
|
self.write([self], {'number': seq})
|
|
|
|
|
|
class PrintCustomerService(Wizard):
|
|
'Print Customer Service Report'
|
|
__name__ = 'crm.customer_service.print'
|
|
start = StateTransition()
|
|
print_ = StateReport('crm.customer_service')
|
|
|
|
def transition_start(self):
|
|
self.services_ids = Transaction().context['active_ids'][:1]
|
|
return 'print_'
|
|
|
|
def do_print_(self, action):
|
|
data = {}
|
|
data['id'] = self.services_ids.pop()
|
|
data['ids'] = [data['id']]
|
|
Service = Pool().get('crm.customer_service')
|
|
service = Service(data['id'])
|
|
try:
|
|
action['email'] = eval(action['email'])
|
|
except:
|
|
action['email'] = {}
|
|
if service and service.party.email:
|
|
action['email'].update({"to": service.party.email})
|
|
return action, data
|
|
|
|
def transition_print_(self):
|
|
if self.services_ids:
|
|
return 'print_'
|
|
return 'end'
|
|
|
|
|
|
class CustomerServiceReport(Report):
|
|
__name__ = 'crm.customer_service'
|
|
|
|
@classmethod
|
|
def get_context(cls, records, header, data):
|
|
report_context = super().get_context(records, header, data)
|
|
Company = Pool().get('company.company')
|
|
report_context['company'] = Company(Transaction().context.get('company'))
|
|
return report_context
|
|
|
|
|
|
class CustomerServiceIndicatorsStart(ModelView):
|
|
'Customer Service Indicators Start'
|
|
__name__ = 'crm.customer_service_indicators.start'
|
|
company = fields.Many2One('company.company', 'Company', required=True)
|
|
fiscalyear = fields.Many2One('account.fiscalyear', 'Fiscal Year',
|
|
required=True)
|
|
department = fields.Many2One('company.department', 'Department')
|
|
case = fields.Many2One('crm.case', 'Case', domain=[
|
|
('parent', '!=', None),
|
|
])
|
|
|
|
@staticmethod
|
|
def default_company():
|
|
return Transaction().context.get('company')
|
|
|
|
@staticmethod
|
|
def default_fiscalyear():
|
|
FiscalYear = Pool().get('account.fiscalyear')
|
|
return FiscalYear.find(
|
|
Transaction().context.get('company'), exception=False)
|
|
|
|
|
|
class CustomerServiceIndicators(Wizard):
|
|
'Customer Service Indicators'
|
|
__name__ = 'crm.customer_service_indicators'
|
|
start = StateView('crm.customer_service_indicators.start',
|
|
'crm.customer_service_indicators_start_view_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Print', 'print_', 'tryton-ok', default=True),
|
|
])
|
|
print_ = StateReport('crm.customer_service_indicators_report')
|
|
|
|
def do_print_(self, action):
|
|
department_id = None
|
|
if self.start.department:
|
|
department_id = self.start.department.id
|
|
|
|
data = {
|
|
'company': self.start.company.id,
|
|
'department': department_id,
|
|
'fiscalyear': self.start.fiscalyear.id,
|
|
}
|
|
return action, data
|
|
|
|
def transition_print_(self):
|
|
return 'end'
|
|
|
|
|
|
class CustomerServiceIndicatorsReport(Report):
|
|
'Customer Service Indicators Report'
|
|
__name__ = 'crm.customer_service_indicators_report'
|
|
|
|
@classmethod
|
|
def get_context(cls, records, header, data):
|
|
report_context = super().get_context(records, header, data)
|
|
pool = Pool()
|
|
Company = pool.get('company.company')
|
|
Fiscalyear = pool.get('account.fiscalyear')
|
|
Service = pool.get('crm.customer_service')
|
|
Department = pool.get('company.department')
|
|
fiscalyear = Fiscalyear(data['fiscalyear'])
|
|
company = Company(data['company'])
|
|
department = None
|
|
records = {}
|
|
|
|
def _get_months():
|
|
return {'month' + str(i + 1): [] for i in range(12)}
|
|
|
|
start = datetime.combine(fiscalyear.start_date, time(0, 0))
|
|
end = datetime.combine(fiscalyear.end_date, time(0, 0))
|
|
dom_service = [
|
|
('cs_date', '>=', start),
|
|
('cs_date', '<=', end),
|
|
('state', '!=', 'cancelled'),
|
|
]
|
|
|
|
if data['department']:
|
|
dom_service.append(
|
|
('department', '=', data['department']),
|
|
)
|
|
department = Department(data['department'])
|
|
|
|
services = Service.search(dom_service)
|
|
|
|
months = {(i): [] for i in range(1, 13)}
|
|
annual_req = []
|
|
|
|
for service in services:
|
|
if service.case.id not in records.keys():
|
|
records[service.case.id] = {
|
|
'name': service.case.name,
|
|
'total': [],
|
|
'rate_total': [],
|
|
}
|
|
records[service.case.id].update(_get_months())
|
|
month_number = service.cs_date.date().month
|
|
months[month_number].append(1)
|
|
annual_req.append(1)
|
|
records[service.case.id]['month' + str(month_number)].append(1)
|
|
records[service.case.id]['total'].append(1)
|
|
|
|
annual_req = sum(annual_req)
|
|
|
|
for value in records.values():
|
|
total_req = 0
|
|
for i in range(1, 13):
|
|
if sum(months[i]) > 0:
|
|
value['rate' + str(i)] = sum(value['month' + str(i)]) / sum(months[i]) * 100
|
|
total_req += sum(value['month' + str(i)])
|
|
else:
|
|
value['rate' + str(i)] = None
|
|
if annual_req > 0:
|
|
value['rate_total'] = (total_req / annual_req) * 100
|
|
|
|
report_context['department'] = department
|
|
report_context['records'] = records.values()
|
|
report_context['fiscalyear'] = Fiscalyear(data['fiscalyear'])
|
|
report_context['company'] = company.party.name
|
|
return report_context
|
|
|
|
|
|
class EfficacyMonthStart(ModelView):
|
|
'Efficacy Month Start'
|
|
__name__ = 'crm.efficacy_month.start'
|
|
company = fields.Many2One('company.company', 'Company', required=True)
|
|
fiscalyear = fields.Many2One('account.fiscalyear', 'Fiscal Year',
|
|
required=True)
|
|
month = fields.Many2One('account.period', 'Month',
|
|
depends=['fiscalyear'], required=True, domain=[
|
|
('fiscalyear', '=', Eval('fiscalyear')),
|
|
])
|
|
|
|
@staticmethod
|
|
def default_company():
|
|
return Transaction().context.get('company')
|
|
|
|
@staticmethod
|
|
def default_fiscalyear():
|
|
FiscalYear = Pool().get('account.fiscalyear')
|
|
return FiscalYear.find(
|
|
Transaction().context.get('company'), exception=False)
|
|
|
|
|
|
class EfficacyMonth(Wizard):
|
|
'Efficacy Month'
|
|
__name__ = 'crm.efficacy_month'
|
|
start = StateView('crm.efficacy_month.start',
|
|
'crm.efficacy_month_start_view_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Print', 'print_', 'tryton-ok', default=True),
|
|
])
|
|
print_ = StateReport('crm.efficacy_month_report')
|
|
|
|
def do_print_(self, action):
|
|
|
|
data = {
|
|
'company': self.start.company.id,
|
|
'fiscalyear': self.start.fiscalyear.id,
|
|
'period': self.start.month.id,
|
|
}
|
|
return action, data
|
|
|
|
def transition_print_(self):
|
|
return 'end'
|
|
|
|
|
|
class EfficacyMonthReport(Report):
|
|
'Efficacy Month Report'
|
|
__name__ = 'crm.efficacy_month_report'
|
|
|
|
@classmethod
|
|
def _get_records_months(cls, services):
|
|
records_months = {}
|
|
|
|
def _get_months():
|
|
return {'month' + str(i + 1): {'num_req': 0, 'efficacy': 0, 'rate': None}
|
|
for i in range(12)}
|
|
|
|
total_months = {}
|
|
|
|
for i in range(12):
|
|
total_months['total_month' + str(i + 1)] = []
|
|
|
|
for service in services:
|
|
if service.department.id not in records_months.keys():
|
|
records_months[service.department.id] = {
|
|
'name': service.department.name,
|
|
'total': [],
|
|
}
|
|
records_months[service.department.id].update(_get_months())
|
|
|
|
month = 'month' + str(service.cs_date.date().month)
|
|
records_months[service.department.id][month]['num_req'] += 1
|
|
|
|
if service.efficacy == 'on_time':
|
|
records_months[service.department.id][month]['efficacy'] += 1
|
|
|
|
for department in records_months.values():
|
|
total_efficacy = []
|
|
total_num_req = []
|
|
for i in range(12):
|
|
department_month = department['month' + str(i + 1)]
|
|
if department_month['num_req'] > 0:
|
|
rate_efficacy = (department_month['efficacy'] / department_month['num_req']) * 100
|
|
department['month' + str(i + 1)] = rate_efficacy
|
|
total_months['total_month' + str(i + 1)].append({'efficacy': department_month['efficacy'], 'num_req': department_month['num_req']})
|
|
total_efficacy.append(department_month['efficacy'])
|
|
total_num_req.append(department_month['num_req'])
|
|
else:
|
|
department['month' + str(i + 1)] = None
|
|
if sum(total_num_req) > 0:
|
|
department['total'] = sum(total_efficacy) / sum(total_num_req) * 100
|
|
|
|
annual_efficacy = []
|
|
effective_months = 0
|
|
for k, v in total_months.items():
|
|
if v == []:
|
|
total_months[k] = None
|
|
else:
|
|
effective_months += 1
|
|
efficacy = [j['efficacy'] for j in v]
|
|
num_req = [j['num_req'] for j in v]
|
|
total_months[k] = (sum(efficacy) / sum(num_req)) * 100
|
|
annual_efficacy.append(total_months[k])
|
|
|
|
res_annual_efficacy = None
|
|
if effective_months > 0:
|
|
res_annual_efficacy = sum(annual_efficacy) / effective_months
|
|
total_months['annual_efficacy'] = res_annual_efficacy
|
|
return records_months, total_months
|
|
|
|
@classmethod
|
|
def _get_departments(cls, services):
|
|
departments = {}
|
|
|
|
for service in services:
|
|
if service.department.id not in departments.keys():
|
|
departments[service.department.id] = {
|
|
'name': service.department.name,
|
|
'num_req': [],
|
|
'efficacy': [],
|
|
'satisfaction': [],
|
|
}
|
|
|
|
departments[service.department.id]['num_req'].append(1)
|
|
if service.efficacy == 'on_time':
|
|
departments[service.department.id]['efficacy'].append(1)
|
|
|
|
if service.satisfaction == 'yes':
|
|
departments[service.department.id]['satisfaction'].append(1)
|
|
|
|
total_num_req = 0
|
|
total_efficacy = 0
|
|
total_rate = 0
|
|
total_satisfaction = 0
|
|
rate_total_satisfaction = 0
|
|
|
|
for k, v in departments.items():
|
|
v['num_req'] = sum(v['num_req'])
|
|
v['efficacy'] = sum(v['efficacy'])
|
|
v['satisfaction'] = sum(v['satisfaction'])
|
|
rate_satisfaction = 0
|
|
if v['satisfaction'] > 0:
|
|
rate_satisfaction = (v['satisfaction'] / v['num_req']) * 100
|
|
v['rate_satisfaction'] = rate_satisfaction
|
|
rate = 0
|
|
if v['num_req'] > 0:
|
|
rate = (v['efficacy'] / v['num_req']) * 100
|
|
v['rate'] = rate
|
|
total_num_req += v['num_req']
|
|
total_efficacy += v['efficacy']
|
|
total_satisfaction += v['satisfaction']
|
|
if total_satisfaction > 0:
|
|
rate_total_satisfaction += (total_satisfaction / total_num_req) * 100
|
|
if total_num_req > 0:
|
|
total_rate += (total_efficacy / total_num_req) * 100
|
|
|
|
values = {
|
|
'total_num_req': total_num_req,
|
|
'total_efficacy': total_efficacy,
|
|
'total_rate': total_rate,
|
|
'rate_total_satisfaction': rate_total_satisfaction,
|
|
'total_satisfaction': total_satisfaction,
|
|
}
|
|
return departments, values
|
|
|
|
@classmethod
|
|
def get_context(cls, records, header, data):
|
|
report_context = super().get_context(records, header, data)
|
|
pool = Pool()
|
|
Fiscalyear = pool.get('account.fiscalyear')
|
|
Company = pool.get('company.company')
|
|
Period = pool.get('account.period')
|
|
Service = pool.get('crm.customer_service')
|
|
company = Company(data['company'])
|
|
period = Period(data['period'])
|
|
records = {}
|
|
start = datetime.combine(period.start_date, time(0,0))
|
|
end = datetime.combine(period.end_date, time(23,59))
|
|
|
|
dom_service = [
|
|
('cs_date', '>=', start),
|
|
('cs_date', '<=', end),
|
|
('state', '!=', 'cancelled'),
|
|
]
|
|
|
|
services = Service.search(dom_service)
|
|
departments, values = cls._get_departments(services)
|
|
records_months, total_months = cls._get_records_months(services)
|
|
|
|
report_context.update(values)
|
|
report_context.update(total_months)
|
|
report_context['records'] = departments.values()
|
|
report_context['records_months'] = records_months.values()
|
|
report_context['fiscalyear'] = Fiscalyear(data['fiscalyear'])
|
|
report_context['month'] = period.name
|
|
report_context['company'] = company.party.name
|
|
return report_context
|
|
|
|
|
|
class MonitoringReport(Report):
|
|
'Monitoring Report'
|
|
__name__ = 'crm.monitoring_report'
|
|
|
|
@classmethod
|
|
def get_context(cls, records, header, data):
|
|
report_context = super().get_context(records, header, data)
|
|
user = Pool().get('res.user')(Transaction().user)
|
|
report_context['company'] = user.company
|
|
return report_context
|