trytonpsk-crm/customer_service.py

621 lines
21 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)
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.customer_service_sequence:
raise CrmConfigurationError(
gettext('crm.msg_missing_sequence_customer_service'))
seq = config.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