Compare commits

...

22 Commits

Author SHA1 Message Date
b7e06a3408 feat: Se añade workflow a pending_task 2024-02-29 10:52:44 -05:00
9096983ffd feat(call): Se añade dominio de window usuario actual 2024-02-28 22:34:07 -05:00
660ac06c1d fix: Se agregan rutas relativas en importaciones 2024-01-31 18:29:35 +00:00
7b0bafa05f feat: Se añaden archivos __init__.py 2024-01-31 18:09:01 +00:00
4d88f9423d feat: Se agregan traducciones a configuración de operador 2024-01-30 13:16:04 -05:00
e0f3846bb5 chore: Se arregla formato 2024-01-28 17:13:10 -05:00
ef8f45267f chore: Se remueve código duplicado usando asignador genérico 2024-01-28 17:10:27 -05:00
5cc0277db9 chore: Se remueve código muerto 2024-01-28 16:56:00 -05:00
3f7b989781 Merge pull request 'Implementar asignación automática de prospecto al operador que lo creó' (#81) from automaticAssignOperatorWhoCreatedProspect#3 into 6.8
Reviewed-on: #81
2024-01-28 16:46:22 -05:00
6c31dfb64f chore(sale_opportunity): Se remueve código muerto 2024-01-28 16:41:19 -05:00
b1c3b21a9c feat(Asignación de prospectos): Auto-asignar prospectos al operador que lo creó 2024-01-28 16:36:34 -05:00
99de45dd32 Refactor(Asignación de prospectos): Se generaliza método para implementarlo en reassign_by_operator 2024-01-28 15:01:16 -05:00
950b9350cc refactor(Asignación de prospectos a operarios): Se extrae clase 2024-01-28 14:00:01 -05:00
6f69709823 refactor(Asignación de prospectos): se extrae método 2024-01-28 13:54:26 -05:00
cd0564c95f refactor: Se extraen archivos 2024-01-26 22:21:23 -05:00
4a3d178a92 WIP: Se agrega escenario de asignación de prospecto a usuario actual 2024-01-26 20:35:01 -05:00
4826687b3b Merge pull request 'fix(Entorno de desarrollo): Se ajusta versión de sao' (#80) from errorNoMuestraProspectos into 6.8
Reviewed-on: #80
2024-01-23 15:16:23 -05:00
bd62abb43f fix(Entorno de desarrollo): Se ajusta versión de sao 2024-01-23 15:12:37 -05:00
18d06374fe Merge pull request 'fix(Entorno de desarrollo): Se ajusta versión de proteus' (#79) from errorChangingContactMethod into 6.8
Reviewed-on: #79
2024-01-23 14:47:26 -05:00
d5ca112bff fix(Entorno de desarrollo): Se ajusta versión de proteus 2024-01-23 14:42:35 -05:00
d1ad40203b Merge pull request 'fix: al crear mecanismo de contacto desde prospecto no se actualiza correctamente en seguimiento de prospecto' (#77) from NoSeActualizaContactMethodEnProspectTrace_#76 into 6.8
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Se replica error en las pruebas y se soluciona con un on_change
2023-10-20 16:19:03 -05:00
9c368809f9 fix: al crear mecanismo de contacto desde prospecto no se actualiza correctamente en seguimiento de prospecto
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline was successful
2023-10-20 15:27:51 -05:00
33 changed files with 729 additions and 571 deletions

View File

@ -7,7 +7,7 @@
[ ! -d "$SRC" ] && die "no se ubica ruta en SRC"
# dependencias minimas
pip3 install psycopg2 proteus inotify honcho
pip3 install psycopg2 proteus==6.8 inotify honcho
# instalar dependencias de tryton desde paquete
python3 setup.py install

View File

@ -1,4 +1,3 @@
#
# variables que puedo usar? https://woodpecker-ci.org/docs/0.15/usage/environment#built-in-environment-variables
steps:
@ -33,7 +32,7 @@ steps:
<p>${CI_COMMIT_AUTHOR}: <a href="${CI_BUILD_LINK}">Task ${CI_BUILD_NUMBER}</a> <font color="green"><b>TODO BIEN EN ${CI_REPO}</b></font></p>
<pre><a href="${CI_COMMIT_LINK}">${CI_COMMIT_MESSAGE}</a></pre>
when:
status: [ success ]
status: [success]
notify-failure:
image: plugins/matrix
@ -49,7 +48,7 @@ steps:
<p>${CI_COMMIT_AUTHOR}: <a href="${CI_BUILD_LINK}">Task ${CI_BUILD_NUMBER}</a> <font color="red"><b>!!!TODO MAL EN ${CI_REPO} </b></font></p>
<pre><a href="${CI_COMMIT_LINK}">${CI_COMMIT_MESSAGE}</a></pre>
when:
status: [ failure ]
status: [failure]
services:
postgres:

View File

@ -3,7 +3,7 @@ FROM node as builder-node
ENV SERIES 6.8
RUN npm install -g bower
RUN curl https://downloads.tryton.org/${SERIES}/tryton-sao-last.tgz | tar zxf - -C /
RUN curl https://downloads.tryton.org/${SERIES}/tryton-sao-6.8.4.tgz | tar zxf - -C /
RUN cd /package && bower install --allow-root
FROM python:3.9-bullseye

View File

@ -1,9 +1,31 @@
from trytond.pool import Pool
from . import prospect
from . import prospect_trace
from . import call
from . import pending_call
from . import user
# Prospect Core
from .core.Prospect.models.prospect import Prospect
from .core.Prospect.models.contact_method import ContactMethod
from .core.Prospect.wizards.assign_operator \
import AssignOperator, AssignOperatorStart
from .core.Prospect.wizards.reassign_prospect_by_prospect \
import ReasignProspectByProspect, ReassignProspectByProspectStart
from .core.Prospect.wizards.reassign_prospect_by_operator \
import ReassignProspectByOperator, ReassignProspectByOperatorStart
# Prospect Trace Core
from .core.ProspectTrace.wizards.make_call \
import MakeCall, MakeCallAsk, MakeCallAskTask, MakeCallStart
from .core.ProspectTrace.wizards.schedule_call \
import ScheduleCall, ScheduleCallStart
from .core.ProspectTrace.models.prospect_trace \
import ProspectTrace
# Call Core
from .core.Call.models.call import Call
from .core.Call.models.pending_call import PendingCall
from .core.Call.models.pending_task import PendingTask
# Role core
from .core.Role.models.user import User
from .locations import city
from .locations import department
@ -12,29 +34,29 @@ __all__ = ['register']
def register():
Pool.register(
user.User,
pending_call.PendingCall,
call.Call,
call.PendingTask,
User,
PendingCall,
Call,
PendingTask,
department.Department,
city.City,
prospect.ContactMethod,
prospect.Prospect,
prospect_trace.ProspectTrace,
prospect.AssignOperatorStart,
prospect_trace.ScheduleCallStart,
prospect_trace.MakeCallStart,
prospect_trace.MakeCallAsk,
prospect_trace.MakeCallAskTask,
prospect.ReassignProspectByOperatorStart,
prospect.ReassignProspectByProspectStart,
ContactMethod,
Prospect,
ProspectTrace,
AssignOperatorStart,
ScheduleCallStart,
MakeCallStart,
MakeCallAsk,
MakeCallAskTask,
ReassignProspectByOperatorStart,
ReassignProspectByProspectStart,
module='sale_opportunity_management', type_='model')
Pool.register(
prospect_trace.ScheduleCall,
prospect.AssignOperator,
prospect_trace.MakeCall,
prospect.ReassignProspectByOperator,
prospect.ReasignProspectByProspect,
ScheduleCall,
AssignOperator,
MakeCall,
ReassignProspectByOperator,
ReasignProspectByProspect,
module='sale_opportunity_management', type_='wizard')
Pool.register(
module='sale_opportunity_management', type_='report')

View File

@ -21,7 +21,9 @@ this repository contains the full copyright notices and license terms. -->
<record model="ir.action.act_window" id="act_call_tree">
<field name="name">Calls</field>
<field name="res_model">sale.call</field>
<field name="res_model">sale.call</field>
<field name="domain"
eval="[('operator_who_called', '=', Eval('_user'))]" pyson="1"/>
</record>
<record model="ir.ui.view" id="call_view_tree">
<field name="model">sale.call</field>
@ -77,7 +79,10 @@ this repository contains the full copyright notices and license terms. -->
<record model="ir.action.act_window" id="act_pending_task_tree">
<field name="name">Pending tasks</field>
<field name="res_model">sale.pending_task</field>
<field name="res_model">sale.pending_task</field>
<field name="domain"
eval="[If(Eval('context', {}).get('user_admin', None), (), ('prospect_trace.prospect_assigned_operator', '=', Eval('_user')))]"
pyson="1"/>
</record>
<record model="ir.ui.view" id="pending_task_view_tree">
<field name="model">sale.pending_task</field>

0
core/Call/__init__.py Normal file
View File

View File

@ -1,9 +1,11 @@
# 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 trytond.model import ModelSQL, ModelView, fields
from trytond.pyson import Eval
from datetime import date
from .selections.interest import Interest
from .selections.call_types import CallTypes
from .selections.call_results import CallResults
from ....selections.interest import Interest
from ....selections.call_types import CallTypes
from ....selections.call_results import CallResults
class Call(ModelSQL, ModelView):
@ -46,43 +48,3 @@ class Call(ModelSQL, ModelView):
@classmethod
def default_date(cls):
return date.today()
class PendingTask(ModelSQL, ModelView):
'Tarea a realizar a un seguimiento de prospecto'
__name__ = "sale.pending_task"
description = fields.Text(
'Description', required=True,
states={
'readonly': Eval('state') == 'done'
})
state = fields.Selection(
[('pending', 'Pending'),
('done', 'Done')],
'State')
prospect_trace = fields.Many2One(
'sale.prospect_trace', 'Prospect trace',
required=True, readonly=True)
@classmethod
def __setup__(cls):
super(PendingTask, cls).__setup__()
cls._buttons.update({
'close_task': {
'invisible': Eval('state') == 'done'
}
})
@classmethod
@ModelView.button
def close_task(cls, tasks):
for task in tasks:
task.state = 'done'
task.save()
@classmethod
def default_state(cls):
return 'pending'

View File

@ -1,5 +1,6 @@
# 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 trytond.model import ModelSQL, ModelView, fields

View File

@ -0,0 +1,47 @@
# 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 trytond.model import Workflow, ModelSQL, ModelView, fields
from trytond.pyson import Eval
class PendingTask(Workflow, ModelSQL, ModelView):
'Tarea a realizar a un seguimiento de prospecto'
__name__ = "sale.pending_task"
description = fields.Text(
'Description', required=True,
states={
'readonly': Eval('state') == 'done'
})
state = fields.Selection(
[('pending', 'Pending'),
('done', 'Done')],
'State')
prospect_trace = fields.Many2One(
'sale.prospect_trace', 'Prospect trace',
required=True, readonly=True)
@classmethod
def __setup__(cls):
super(PendingTask, cls).__setup__()
cls._transitions |= set((
('pending', 'done'),
))
cls._buttons.update({
'close_task': {
'invisible': ~Eval('state').in_(['pending']),
'depends':['state']
}
})
@classmethod
@ModelView.button
@Workflow.transition('done')
def close_task(cls, tasks):
pass
@classmethod
def default_state(cls):
return 'pending'

View File

View File

View File

@ -0,0 +1,37 @@
# 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 trytond.model import ModelSQL, ModelView, fields
class ContactMethod(ModelSQL, ModelView):
'Mecanismo de contacto'
__name__ = 'prospect.contact_method'
contact_type = fields.Selection([
('phone', 'Phone'),
('mobile', 'Mobile'),
('mail', 'Mail')
], 'Contact type', required=True)
value = fields.Char('Value', required=True)
name = fields.Char('Name')
job = fields.Char('Job')
prospect = fields.Many2One('sale.prospect', 'Prospect', required=True)
prospect_trace = fields.Many2One(
'sale.prospect_trace', 'Prospect Trace', required=False)
@classmethod
def default_contact_type(cls):
return 'mobile'
def get_rec_name(self, name):
fields = [self.name, self.job, self.value]
contact_rec_name = ''
for field in fields:
if field:
contact_rec_name += ' [' + str(field) + '] '
return contact_rec_name

View File

@ -0,0 +1,80 @@
# 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 trytond.model import ModelSQL, ModelView, fields, DeactivableMixin
from trytond.pyson import Eval, If
from trytond.transaction import Transaction
from trytond.pool import Pool
from ....core.Prospect.wizards.assign_operator import GenericAssign
class Prospect(ModelSQL, ModelView, DeactivableMixin):
'Prospecto'
__name__ = 'sale.prospect'
_rec_name = 'name'
name = fields.Char('Name', required=True)
business_unit = fields.Selection(
[('brigade', 'Brigade'),
('optics', 'Optics'),
('equipment', 'Equipment')],
'Business unit', required=True
)
contact_methods = fields.One2Many(
'prospect.contact_method',
'prospect', 'Contact methods', required=True)
department = fields.Many2One('sale.department', 'Department')
city = fields.Many2One('sale.city', 'City',
domain=[If(Eval('department'),
('parent', '=', Eval('department')))])
assigned_operator = fields.Many2One(
'res.user', "Assigned operator", readonly=True)
state = fields.Selection([
('unassigned', 'Unsassigned'),
('assigned', 'Assigned')], "State", readonly=True)
prospect_trace = fields.Many2One('sale.prospect_trace', 'Prospect trace')
rating = fields.Selection(
[(None, None),
('1', '1'),
('2', '2'),
('3', '3'),
('4', '4'),
('5', '5')], 'Rating (1-5)')
comments = fields.Text('Comments')
@classmethod
def default_state(cls):
return 'unassigned'
@fields.depends('prospect_trace', 'contact_methods')
def on_change_contact_methods(self):
for contact in self.contact_methods:
contact.prospect_trace = self.prospect_trace
@fields.depends('city', 'department')
def on_change_city(self):
if self.city:
self.department = self.city.parent
@classmethod
def create(cls, values):
user_id = Transaction().user
records = super().create(values)
cls.try_assign_to_current_operator(records, user_id)
return records
@classmethod
def try_assign_to_current_operator(cls, prospects, user_id):
User = Pool().get('res.user')
user, = User.search([('id', '=', user_id)])
if user.is_operator:
GenericAssign.assign_prospects_to_operator(prospects, user)

View File

View File

@ -0,0 +1,108 @@
# 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 trytond.wizard import Wizard, StateView, Button, StateTransition
from trytond.model import ModelView, fields
from trytond.pyson import Eval
from trytond.pool import Pool
from ....core.ProspectTrace.models.prospect_trace import ProspectTrace
class AssignOperator(Wizard):
'Asignar operador a prospecto'
__name__ = 'sale.prospect.assign'
start = StateView(
'sale.prospect.assign.start',
'sale_opportunity_management.assign_start_view_form', [
Button("Cancel", 'end', 'tryton-cancel'),
Button("Assign", 'assign', 'tryton-ok', default=True)])
assign = StateTransition()
def transition_assign(self):
_prospects = self.start.prospects
_operator = self.start.operator
GenericAssign.assign_prospects_to_operator(_prospects, _operator)
return 'end'
class AssignOperatorStart(ModelView):
'Inicio de asignación de operador'
__name__ = 'sale.prospect.assign.start'
prospects_chunk = fields.Integer(
'Prospects chunk', required=True,
states={
'readonly': ~Eval('business_unit', False)})
operator = fields.Many2One('res.user', 'Operator', required=True)
prospects = fields.One2Many(
'sale.prospect', None, 'Prospects', readonly=True)
business_unit = fields.Selection(
[('brigade', 'Brigade'),
('optics', 'Optics'),
('equipment', 'Equipment')],
'Business unit',
states={
'readonly': Eval('prospects_chunk', False)}
)
@classmethod
def default_prospects_chunk(cls):
return 0
@fields.depends('prospects_chunk', 'prospects', 'business_unit')
def on_change_prospects_chunk(self):
pool = Pool()
Prospect = pool.get('sale.prospect')
if self.prospects_chunk >= 1:
self.prospects = []
self.prospects = Prospect.search(
[('state', '=', 'unassigned'),
('business_unit', '=', self.business_unit)],
limit=self.prospects_chunk)
class GenericAssign():
@classmethod
def assign_prospects_to_operator(cls, prospects, operator):
for prospect in prospects:
prospect.assigned_operator = operator
prospect.state = 'assigned'
prospect.prospect_trace = cls._get_prosp_trace(prospect, operator)
prospect.save()
@classmethod
def _get_prosp_trace(cls, prospect, operator):
ProspectTrace = Pool().get('sale.prospect_trace')
prospect_trace = ProspectTrace.search([('prospect', '=', prospect)])
if not prospect_trace:
created_trace = cls._create_base_prospect_trace(prospect, operator)
prospect_trace.append(created_trace)
prospect_trace, = prospect_trace
prospect_trace.prospect_assigned_operator = operator
prospect_trace.save()
return prospect_trace
@staticmethod
def _create_base_prospect_trace(prospect, operator) -> ProspectTrace:
ProspectTrace = Pool().get('sale.prospect_trace')
prospect_trace = ProspectTrace(
prospect=prospect,
prospect_city=prospect.city,
prospect_business_unit=prospect.business_unit,
prospect_contacts=prospect.contact_methods
)
return prospect_trace

View File

@ -0,0 +1,53 @@
# 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 trytond.wizard import Wizard, StateView, Button, StateTransition
from trytond.model import ModelView, fields
from trytond.pool import Pool
from ....core.Prospect.wizards.assign_operator import GenericAssign
class ReassignProspectByOperatorStart(ModelView):
'Inicio de reasignación de prospecto por operario'
__name__ = 'sale.prospect.reassign_by_operator.start'
current_operator = fields.Many2One(
'res.user', "Current operator", required=True)
new_operator = fields.Many2One(
'res.user', "New operator", required=True)
prospects = fields.One2Many(
'sale.prospect', None, 'Prospects', readonly=True)
@fields.depends('current_operator', 'prospects')
def on_change_current_operator(self):
pool = Pool()
Prospect = pool.get('sale.prospect')
self.prospects = []
self.prospects = Prospect.search(
[('state', '=', 'assigned'),
('assigned_operator', '=', self.current_operator)])
class ReassignProspectByOperator(Wizard):
'Reasignar todos los prospectos de un operario, a otro operario'
__name__ = 'sale.prospect.reassign_by_operator'
start = StateView(
'sale.prospect.reassign_by_operator.start',
'sale_opportunity_management.reassign_by_operator_start_view_form',
[Button("Cancel", 'end', 'tryton-cancel'),
Button("Reassign", 'reassign_by_operator', 'tryton-ok', default=True)
])
reassign_by_operator = StateTransition()
def transition_reassign_by_operator(self):
_prospects = self.start.prospects
_operator = self.start.new_operator
GenericAssign.assign_prospects_to_operator(_prospects, _operator)
return 'end'

View File

@ -0,0 +1,40 @@
# 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 trytond.wizard import Wizard, StateView, Button, StateTransition
from trytond.model import ModelView, fields
from ....core.Prospect.wizards.assign_operator import GenericAssign
class ReassignProspectByProspectStart(ModelView):
'Inicio de reasignación de un prospecto en específico'
__name__ = 'sale.prospect.reassign_by_prospect.start'
prospect = fields.Many2One(
'sale.prospect', 'Prospect', required=True,
domain=[('assigned_operator', '!=', None)])
new_operator = fields.Many2One('res.user', "New operator", required=True)
class ReasignProspectByProspect(Wizard):
'Reasignar un prospecto en específico a un nuevo operario'
__name__ = 'sale.prospect.reassign_by_prospect'
start = StateView(
'sale.prospect.reassign_by_prospect.start',
'sale_opportunity_management.reassign_by_prospect_start_view_form',
[Button("Cancel", 'end', 'tryton-cancel'),
Button("Reassign", 'reassign_by_prospect', 'tryton-ok', default=True)
])
reassign_by_prospect = StateTransition()
def transition_reassign_by_prospect(self):
_prospect = self.start.prospect
_operator = self.start.new_operator
GenericAssign.assign_prospects_to_operator([_prospect], _operator)
return 'end'

View File

View File

View File

@ -0,0 +1,106 @@
# 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 trytond.model import ModelSQL, ModelView, fields
from trytond.pyson import Eval
from ....selections.interest import Interest
class ProspectTrace(ModelSQL, ModelView):
'Seguimiento de un prospecto'
__name__ = 'sale.prospect_trace'
_states = {'readonly': True}
prospect = fields.Many2One(
'sale.prospect', 'Prospect', required=True, states=_states)
prospect_business_unit = fields.Selection(
[('brigade', 'Brigade'),
('optics', 'Optics'),
('equipment', 'Equipment')],
'Business unit', states=_states
)
prospect_contacts = fields.One2Many(
'prospect.contact_method', 'prospect_trace',
'Prospect contacts', required=True)
prospect_city = fields.Many2One('sale.city', 'City',
states=_states)
prospect_assigned_operator = fields.Many2One(
'res.user', "Assigned operator", states=_states)
calls = fields.One2Many(
'sale.call', 'prospect_trace', 'Calls', states=_states)
pending_call = fields.Many2One(
'sale.pending_call', 'Pending call', states=_states)
current_interest = fields.Selection(
Interest.get_interest_levels(), 'Current interest',
states=_states)
state = fields.Selection([
('unassigned', 'Unassigned'),
('open', 'Open'),
('with_pending_calls', 'With pending calls'),
('closed', 'Closed')
], 'State',
states=_states)
@fields.depends('prospect_contacts', 'prospect')
def on_change_prospect_contacts(self):
for contact in self.prospect_contacts:
contact.prospect = self.prospect
@classmethod
def __setup__(cls):
super(ProspectTrace, cls).__setup__()
cls._buttons.update({
'wizard_schedule': {
'invisible': Eval('state') == 'with_pending_calls',
},
'wizard_make_call': {},
'close_trace': {
'invisible': Eval('state') == 'closed',
'depends': ['state']
},
'reopen_trace': {
'invisible': (Eval('state') == 'open')
| (Eval('state') == 'with_pending_calls'),
'depends': ['state']
}
})
@classmethod
def default_state(cls):
return 'open'
@classmethod
@ModelView.button_action(
'sale_opportunity_management.schedule_call_wizard')
def wizard_schedule(cls, prospect_traces):
pass
@classmethod
@ModelView.button_action(
'sale_opportunity_management.make_call_wizard')
def wizard_make_call(cls, prospect_traces):
pass
@classmethod
@ModelView.button
def close_trace(cls, prospect_traces):
for prospect_trace in prospect_traces:
prospect_trace.state = 'closed'
prospect_trace.save()
@classmethod
@ModelView.button
def reopen_trace(cls, prospect_traces):
for prospect_trace in prospect_traces:
prospect_trace.state = 'open'
prospect_trace.save()
def get_rec_name(self, name):
if self.prospect:
return '[' + str(self.id) + '] ' + self.prospect.name

View File

@ -1,146 +1,15 @@
# 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 trytond.wizard import Wizard, StateView, Button, StateTransition
from trytond.model import ModelSQL, ModelView, fields
from trytond.model import ModelView, fields
from trytond.pool import Pool
from trytond.pyson import Eval
from .selections.call_types import CallTypes
from .selections.interest import Interest
from ....selections.call_types import CallTypes
from ....selections.interest import Interest
from datetime import datetime
class ProspectTrace(ModelSQL, ModelView):
'Seguimiento de un prospecto'
__name__ = 'sale.prospect_trace'
_states = {'readonly': True}
prospect = fields.Many2One(
'sale.prospect', 'Prospect', required=True, states=_states)
prospect_business_unit = fields.Selection(
[('brigade', 'Brigade'),
('optics', 'Optics'),
('equipment', 'Equipment')],
'Business unit', states=_states
)
prospect_contacts = fields.One2Many(
'prospect.contact_method', 'prospect_trace',
'Prospect contacts', required=True)
prospect_city = fields.Many2One('sale.city', 'City',
states=_states)
prospect_assigned_operator = fields.Many2One(
'res.user', "Assigned operator", states=_states)
calls = fields.One2Many(
'sale.call', 'prospect_trace', 'Calls', states=_states)
pending_call = fields.Many2One(
'sale.pending_call', 'Pending call', states=_states)
current_interest = fields.Selection(
Interest.get_interest_levels(), 'Current interest',
states=_states)
state = fields.Selection([
('unassigned', 'Unassigned'),
('open', 'Open'),
('with_pending_calls', 'With pending calls'),
('closed', 'Closed')
], 'State',
states=_states)
@fields.depends('prospect_contacts', 'prospect')
def on_change_prospect_contacts(self):
for contact in self.prospect_contacts:
contact.prospect = self.prospect
@classmethod
def __setup__(cls):
super(ProspectTrace, cls).__setup__()
cls._buttons.update({
'wizard_schedule': {
'invisible': Eval('state') == 'with_pending_calls',
},
'wizard_make_call': {},
'close_trace': {
'invisible': Eval('state') == 'closed',
'depends': ['state']
},
'reopen_trace': {
'invisible': (Eval('state') == 'open')
| (Eval('state') == 'with_pending_calls'),
'depends': ['state']
}
})
@classmethod
def default_state(cls):
return 'open'
@classmethod
@ModelView.button_action(
'sale_opportunity_management.schedule_call_wizard')
def wizard_schedule(cls, prospect_traces):
pass
@classmethod
@ModelView.button_action(
'sale_opportunity_management.make_call_wizard')
def wizard_make_call(cls, prospect_traces):
pass
@classmethod
@ModelView.button
def close_trace(cls, prospect_traces):
for prospect_trace in prospect_traces:
prospect_trace.state = 'closed'
prospect_trace.save()
@classmethod
@ModelView.button
def reopen_trace(cls, prospect_traces):
for prospect_trace in prospect_traces:
prospect_trace.state = 'open'
prospect_trace.save()
def get_rec_name(self, name):
if self.prospect:
return '[' + str(self.id) + '] ' + self.prospect.name
class ScheduleCallStart(ModelView):
'Inicio agendar llamada a seguimiento de prospecto'
__name__ = 'sale.prospect_trace.schedule.start'
currency_date = fields.DateTime('Currency Date', readonly=True)
date_time = fields.DateTime('Date time', domain=[
('date_time', '>=', Eval('currency_date'))])
@classmethod
def default_currency_date(cls):
date = datetime.now()
return date
class ScheduleCall(Wizard):
'Agendar llamada a seguimiento de prospecto'
__name__ = 'sale.prospect_trace.schedule'
start = StateView(
'sale.prospect_trace.schedule.start',
'sale_opportunity_management.schedule_start_view_form', [
Button("Cancel", 'end', 'tryton-cancel'),
Button("Schedule", 'schedule', 'tryton-ok', default=True)])
schedule = StateTransition()
def transition_schedule(self):
MakeCall.create_schedule_call(self.start.date_time, self.record)
return 'end'
class MakeCallStart(ModelView):
'Inicio de creación de llamada a seguimiento de prospecto'
__name__ = 'sale.prospect_trace.make_call.start'

View File

@ -0,0 +1,40 @@
# 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 trytond.wizard import Wizard, StateView, Button, StateTransition
from trytond.model import ModelView, fields
from trytond.pyson import Eval
from datetime import datetime
from ....core.ProspectTrace.wizards.make_call import MakeCall
class ScheduleCallStart(ModelView):
'Inicio agendar llamada a seguimiento de prospecto'
__name__ = 'sale.prospect_trace.schedule.start'
currency_date = fields.DateTime('Currency Date', readonly=True)
date_time = fields.DateTime('Date time', domain=[
('date_time', '>=', Eval('currency_date'))])
@classmethod
def default_currency_date(cls):
date = datetime.now()
return date
class ScheduleCall(Wizard):
'Agendar llamada a seguimiento de prospecto'
__name__ = 'sale.prospect_trace.schedule'
start = StateView(
'sale.prospect_trace.schedule.start',
'sale_opportunity_management.schedule_start_view_form', [
Button("Cancel", 'end', 'tryton-cancel'),
Button("Schedule", 'schedule', 'tryton-ok', default=True)])
schedule = StateTransition()
def transition_schedule(self):
MakeCall.create_schedule_call(self.start.date_time, self.record)
return 'end'

0
core/Role/__init__.py Normal file
View File

View File

View File

@ -1,6 +1,6 @@
# 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 trytond.model import fields
from trytond.pool import PoolMeta
@ -10,6 +10,7 @@ class User(metaclass=PoolMeta):
__name__ = 'res.user'
user_admin = fields.Boolean('Is Admin')
is_operator = fields.Boolean('Is Operator')
@classmethod
def __setup__(cls):

0
core/__init__.py Normal file
View File

View File

@ -30,6 +30,10 @@ msgctxt "field:res.user,user_admin:"
msgid "Is Admin"
msgstr "Es Administrador"
msgctxt "field:res.user,is_operator:"
msgid "Is Operator"
msgstr "Es Operador"
msgctxt "field:sale.prospect_trace,prospect:"
msgid "Prospect"
msgstr "Prospecto"

View File

@ -1,260 +0,0 @@
# 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 trytond.wizard import Wizard, StateView, Button, StateTransition
from trytond.model import ModelSQL, ModelView, fields, DeactivableMixin
from trytond.pyson import Eval, If
from trytond.pool import Pool
class Prospect(ModelSQL, ModelView, DeactivableMixin):
'Prospecto'
__name__ = 'sale.prospect'
_rec_name = 'name'
name = fields.Char('Name', required=True)
business_unit = fields.Selection(
[('brigade', 'Brigade'),
('optics', 'Optics'),
('equipment', 'Equipment')],
'Business unit', required=True
)
contact_methods = fields.One2Many(
'prospect.contact_method',
'prospect', 'Contact methods', required=True)
department = fields.Many2One('sale.department', 'Department')
city = fields.Many2One('sale.city', 'City',
domain=[If(Eval('department'),
('parent', '=', Eval('department')))])
assigned_operator = fields.Many2One(
'res.user', "Assigned operator", readonly=True)
state = fields.Selection([
('unassigned', 'Unsassigned'),
('assigned', 'Assigned')], "State", readonly=True)
prospect_trace = fields.Many2One('sale.prospect_trace', 'Prospect trace')
rating = fields.Selection(
[(None, None),
('1', '1'),
('2', '2'),
('3', '3'),
('4', '4'),
('5', '5')], 'Rating (1-5)')
comments = fields.Text('Comments')
@classmethod
def default_state(cls):
return 'unassigned'
@fields.depends('city', 'department')
def on_change_city(self):
if self.city:
self.department = self.city.parent
class ContactMethod(ModelSQL, ModelView):
'Mecanismo de contacto'
__name__ = 'prospect.contact_method'
contact_type = fields.Selection([
('phone', 'Phone'),
('mobile', 'Mobile'),
('mail', 'Mail')
], 'Contact type', required=True)
value = fields.Char('Value', required=True)
name = fields.Char('Name')
job = fields.Char('Job')
prospect = fields.Many2One('sale.prospect', 'Prospect', required=True)
prospect_trace = fields.Many2One(
'sale.prospect_trace', 'Prospect Trace', required=False)
@classmethod
def default_contact_type(cls):
return 'mobile'
def get_rec_name(self, name):
fields = [self.name, self.job, self.value]
contact_rec_name = ''
for field in fields:
if field:
contact_rec_name += ' [' + str(field) + '] '
return contact_rec_name
class AssignOperatorStart(ModelView):
'Inicio de asignación de operador'
__name__ = 'sale.prospect.assign.start'
prospects_chunk = fields.Integer(
'Prospects chunk', required=True,
states={
'readonly': ~Eval('business_unit', False)})
operator = fields.Many2One('res.user', 'Operator', required=True)
prospects = fields.One2Many(
'sale.prospect', None, 'Prospects', readonly=True)
business_unit = fields.Selection(
[('brigade', 'Brigade'),
('optics', 'Optics'),
('equipment', 'Equipment')],
'Business unit',
states={
'readonly': Eval('prospects_chunk', False)}
)
@classmethod
def default_prospects_chunk(cls):
return 0
@fields.depends('prospects_chunk', 'prospects', 'business_unit')
def on_change_prospects_chunk(self):
pool = Pool()
Prospect = pool.get('sale.prospect')
if self.prospects_chunk >= 1:
self.prospects = []
self.prospects = Prospect.search(
[('state', '=', 'unassigned'),
('business_unit', '=', self.business_unit)],
limit=self.prospects_chunk)
class AssignOperator(Wizard):
'Asignar operador a prospecto'
__name__ = 'sale.prospect.assign'
start = StateView(
'sale.prospect.assign.start',
'sale_opportunity_management.assign_start_view_form', [
Button("Cancel", 'end', 'tryton-cancel'),
Button("Assign", 'assign', 'tryton-ok', default=True)])
assign = StateTransition()
def transition_assign(self):
pool = Pool()
ProspectTrace = pool.get('sale.prospect_trace')
for prospect in self.start.prospects:
prospect.assigned_operator = self.start.operator
prospect.state = 'assigned'
prospect.save()
prospect_trace = ProspectTrace(
prospect=prospect,
prospect_city=prospect.city,
prospect_business_unit=prospect.business_unit,
prospect_assigned_operator=prospect.assigned_operator,
prospect_contacts=prospect.contact_methods
)
prospect_trace.save()
prospect.prospect_trace = prospect_trace
prospect.save()
return 'end'
class ReassignProspectByOperatorStart(ModelView):
'Inicio de reasignación de prospecto por operario'
__name__ = 'sale.prospect.reassign_by_operator.start'
current_operator = fields.Many2One(
'res.user', "Current operator", required=True)
new_operator = fields.Many2One(
'res.user', "New operator", required=True)
prospects = fields.One2Many(
'sale.prospect', None, 'Prospects', readonly=True)
@fields.depends('current_operator', 'prospects')
def on_change_current_operator(self):
pool = Pool()
Prospect = pool.get('sale.prospect')
self.prospects = []
self.prospects = Prospect.search(
[('state', '=', 'assigned'),
('assigned_operator', '=', self.current_operator)])
class ReassignProspectByOperator(Wizard):
'Reasignar todos los prospectos de un operario, a otro operario'
__name__ = 'sale.prospect.reassign_by_operator'
start = StateView(
'sale.prospect.reassign_by_operator.start',
'sale_opportunity_management.reassign_by_operator_start_view_form',
[Button("Cancel", 'end', 'tryton-cancel'),
Button("Reassign", 'reassign_by_operator', 'tryton-ok', default=True)
])
reassign_by_operator = StateTransition()
def transition_reassign_by_operator(self):
pool = Pool()
ProspectTrace = pool.get('sale.prospect_trace')
for prospect in self.start.prospects:
prospect.assigned_operator = self.start.new_operator
if prospect.prospect_trace:
prospect_trace, = ProspectTrace.search(
[('prospect', '=', prospect)])
prospect_trace.prospect_assigned_operator =\
self.start.new_operator
prospect_trace.save()
prospect.save()
return 'end'
class ReassignProspectByProspectStart(ModelView):
'Inicio de reasignación de un prospecto en específico'
__name__ = 'sale.prospect.reassign_by_prospect.start'
prospect = fields.Many2One(
'sale.prospect', 'Prospect', required=True,
domain=[('assigned_operator', '!=', None)])
new_operator = fields.Many2One('res.user', "New operator", required=True)
class ReasignProspectByProspect(Wizard):
'Reasignar un prospecto en específico a un nuevo operario'
__name__ = 'sale.prospect.reassign_by_prospect'
start = StateView(
'sale.prospect.reassign_by_prospect.start',
'sale_opportunity_management.reassign_by_prospect_start_view_form',
[Button("Cancel", 'end', 'tryton-cancel'),
Button("Reassign", 'reassign_by_prospect', 'tryton-ok', default=True)
])
reassign_by_prospect = StateTransition()
def transition_reassign_by_prospect(self):
pool = Pool()
ProspectTrace = pool.get('sale.prospect_trace')
self.start.prospect.assigned_operator = self.start.new_operator
if self.start.prospect.prospect_trace:
prospect_trace, = ProspectTrace.search(
[('prospect', '=', self.start.prospect)])
prospect_trace.prospect_assigned_operator =\
self.start.new_operator
prospect_trace.save()
self.start.prospect.save()
return 'end'

View File

@ -3,183 +3,183 @@
this repository contains the full copyright notices and license terms. -->
<tryton>
<data>
<record model="res.group" id="group_prospect">
<field name="name">Prospects</field>
</record>
<record model="res.group" id="group_prospect_admin">
<field name="name">Prospect Administrator</field>
<field name="parent" ref="group_prospect"/>
</record>
<record model="res.user-res.group" id="user_admin_group_prospect">
<field name="user" ref="res.user_admin"/>
<field name="group" ref="group_prospect"/>
<record model="res.group" id="group_prospect">
<field name="name">Prospects</field>
</record>
<record model="res.user-res.group" id="user_admin_group_prospect_admin">
<field name="user" ref="res.user_admin"/>
<field name="group" ref="group_prospect_admin"/>
<record model="res.group" id="group_prospect_admin">
<field name="name">Prospect Administrator</field>
<field name="parent" ref="group_prospect" />
</record>
<record model="res.user-res.group" id="user_admin_group_prospect">
<field name="user" ref="res.user_admin" />
<field name="group" ref="group_prospect" />
</record>
<record model="res.user-res.group" id="user_admin_group_prospect_admin">
<field name="user" ref="res.user_admin" />
<field name="group" ref="group_prospect_admin" />
</record>
<record model="ir.action.act_window" id="act_prospect_tree">
<field name="name">Prospects</field>
<field name="name">Prospects</field>
<field name="domain"
eval="[If(Eval('context', {}).get('user_admin', None), (), ('assigned_operator', '=', Eval('_user')))]"
pyson="1"/>
<field name="res_model">sale.prospect</field>
eval="[If(Eval('context', {}).get('user_admin', None), (), ('assigned_operator', '=', Eval('_user')))]"
pyson="1" />
<field name="res_model">sale.prospect</field>
</record>
<record model="ir.ui.view" id="prospect_view_tree">
<field name="model">sale.prospect</field>
<field name="model">sale.prospect</field>
<field name="type">tree</field>
<field name="name">prospect_tree</field>
</record>
<record model="ir.ui.view" id="prospect_view_form">
<field name="model">sale.prospect</field>
<field name="model">sale.prospect</field>
<field name="type">form</field>
<field name="name">prospect_form</field>
</record>
<record model="ir.action.act_window.view" id="act_prospect_tree_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="prospect_view_tree"/>
<field name="act_window" ref="act_prospect_tree"/>
<field name="sequence" eval="10" />
<field name="view" ref="prospect_view_tree" />
<field name="act_window" ref="act_prospect_tree" />
</record>
<record model="ir.action.act_window.view" id="act_prospect_form_view1">
<field name="sequence" eval="20"/>
<field name="view" ref="prospect_view_form"/>
<field name="act_window" ref="act_prospect_tree"/>
<field name="sequence" eval="20" />
<field name="view" ref="prospect_view_form" />
<field name="act_window" ref="act_prospect_tree" />
</record>
<record model="ir.action.act_window.domain" id="act_prospect_domain_unassigned">
<field name="name">Unassigned</field>
<field name="sequence" eval="10"/>
<field name="domain" eval="[('state', '=', 'unassigned')]" pyson="1"/>
<field name="count" eval="True"/>
<field name="act_window" ref="act_prospect_tree"/>
<field name="sequence" eval="10" />
<field name="domain" eval="[('state', '=', 'unassigned')]" pyson="1" />
<field name="count" eval="True" />
<field name="act_window" ref="act_prospect_tree" />
</record>
<record model="ir.action.act_window.domain" id="act_prospect_domain_assigned">
<field name="name">Assigned</field>
<field name="sequence" eval="10"/>
<field name="domain" eval="[('state', '=', 'assigned')]" pyson="1"/>
<field name="count" eval="True"/>
<field name="act_window" ref="act_prospect_tree"/>
<field name="sequence" eval="10" />
<field name="domain" eval="[('state', '=', 'assigned')]" pyson="1" />
<field name="count" eval="True" />
<field name="act_window" ref="act_prospect_tree" />
</record>
<menuitem
parent="menu_calls"
sequence="10"
id="menu_prospects_tree"
icon="tryton-party"
action="act_prospect_tree"/>
action="act_prospect_tree" />
<record model="ir.ui.menu-res.group" id="menu_prospects_group_prospect">
<field name="menu" ref="menu_prospects_tree"/>
<field name="group" ref="group_prospect"/>
<record model="ir.ui.menu-res.group" id="menu_prospects_group_prospect">
<field name="menu" ref="menu_prospects_tree" />
<field name="group" ref="group_prospect" />
</record>
<record model="ir.action.act_window" id="act_contact_method_tree">
<field name="name">Contact method</field>
<field name="res_model">prospect.contact_method</field>
<field name="name">Contact method</field>
<field name="res_model">prospect.contact_method</field>
</record>
<record model="ir.ui.view" id="contact_method_view_tree">
<field name="model">prospect.contact_method</field>
<field name="model">prospect.contact_method</field>
<field name="type">tree</field>
<field name="name">contact_method_tree</field>
</record>
<record model="ir.ui.view" id="contact_method_view_form">
<field name="model">prospect.contact_method</field>
<field name="model">prospect.contact_method</field>
<field name="type">form</field>
<field name="name">contact_method_form</field>
</record>
<record model="ir.action.act_window.view" id="act_contact_method_tree_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="contact_method_view_tree"/>
<field name="act_window" ref="act_contact_method_tree"/>
<field name="sequence" eval="10" />
<field name="view" ref="contact_method_view_tree" />
<field name="act_window" ref="act_contact_method_tree" />
</record>
<record model="ir.action.act_window.view" id="act_contact_method_form_view1">
<field name="sequence" eval="20"/>
<field name="view" ref="contact_method_view_form"/>
<field name="act_window" ref="act_contact_method_tree"/>
<field name="sequence" eval="20" />
<field name="view" ref="contact_method_view_form" />
<field name="act_window" ref="act_contact_method_tree" />
</record>
<record model="ir.action.wizard" id="assign_operator_wizard">
<field name="name">Assign Operator</field>
<field name="wiz_name">sale.prospect.assign</field>
</record>
</record>
<record model="ir.ui.view" id="assign_start_view_form">
<field name="model">sale.prospect.assign.start</field>
<field name="type">form</field>
<field name="name">assign_start_form</field>
</record>
</record>
<record model="ir.action.keyword" id="assign_operator_wizard_keyword">
<field name="keyword">form_action</field>
<field name="model">sale.prospect, -1</field>
<field name="action" ref="assign_operator_wizard"/>
<field name="action" ref="assign_operator_wizard" />
</record>
<menuitem
parent="menu_prospects_tree"
sequence="40"
id="menu_prospects_assigned_wizard"
action="assign_operator_wizard"/>
parent="menu_prospects_tree"
sequence="40"
id="menu_prospects_assigned_wizard"
action="assign_operator_wizard" />
<record model="ir.ui.menu-res.group" id="menu_prospect_assigned_group_prospect">
<field name="menu" ref="menu_prospects_assigned_wizard"/>
<field name="group" ref="group_prospect_admin"/>
<record model="ir.ui.menu-res.group" id="menu_prospect_assigned_group_prospect">
<field name="menu" ref="menu_prospects_assigned_wizard" />
<field name="group" ref="group_prospect_admin" />
</record>
<record model="ir.action.wizard" id="reassign_by_operator_wizard">
<field name="name">Reassign by operator</field>
<field name="wiz_name">sale.prospect.reassign_by_operator</field>
</record>
</record>
<record model="ir.ui.view" id="reassign_by_operator_start_view_form">
<field name="model">sale.prospect.reassign_by_operator.start</field>
<field name="type">form</field>
<field name="name">reassign_by_operator_form</field>
</record>
</record>
<menuitem
name="Reassign"
parent="menu_prospects_tree"
sequence="50"
id="menu_reassign"
icon='tryton-refresh'/>
icon='tryton-refresh' />
<record model="ir.ui.menu-res.group" id="menu_prospect_reassigned_group_prospect">
<field name="menu" ref="menu_reassign"/>
<field name="group" ref="group_prospect_admin"/>
<record model="ir.ui.menu-res.group" id="menu_prospect_reassigned_group_prospect">
<field name="menu" ref="menu_reassign" />
<field name="group" ref="group_prospect_admin" />
</record>
<menuitem
parent="menu_reassign"
sequence="10"
id="menu_reassign_by_operator_wizard"
action="reassign_by_operator_wizard"/>
action="reassign_by_operator_wizard" />
<record model="ir.action.wizard" id="reassign_by_prospect_wizard">
<field name="name">Reassign by prospect</field>
<field name="wiz_name">sale.prospect.reassign_by_prospect</field>
</record>
</record>
<record model="ir.ui.view" id="reassign_by_prospect_start_view_form">
<field name="model">sale.prospect.reassign_by_prospect.start</field>
<field name="type">form</field>
<field name="name">reassign_by_prospect_form</field>
</record>
<menuitem
</record>
<menuitem
parent="menu_reassign"
sequence="20"
id="menu_reassign_by_prospect_wizard"
action="reassign_by_prospect_wizard"/>
action="reassign_by_prospect_wizard" />
<record model="ir.model.access" id="access_sale">
<field name="model" search="[('model', '=', 'sale.prospect')]"/>
<field name="perm_read" eval="False"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.model.access" id="access_sale_prospect">
<field name="model" search="[('model', '=', 'sale.prospect')]"/>
<field name="group" ref="group_prospect"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
<record model="ir.model.access" id="access_sale">
<field name="model" search="[('model', '=', 'sale.prospect')]" />
<field name="perm_read" eval="False" />
<field name="perm_write" eval="False" />
<field name="perm_create" eval="False" />
<field name="perm_delete" eval="False" />
</record>
</data>
</tryton>
<record model="ir.model.access" id="access_sale_prospect">
<field name="model" search="[('model', '=', 'sale.prospect')]" />
<field name="group" ref="group_prospect" />
<field name="perm_read" eval="True" />
<field name="perm_write" eval="True" />
<field name="perm_create" eval="True" />
<field name="perm_delete" eval="True" />
</record>
</data>
</tryton>

View File

@ -0,0 +1,35 @@
Importaciones::
>>> from proteus import Model, Wizard
>>> from trytond.transaction import Transaction
>>> from trytond.tests.tools import activate_modules, set_user
Activar módulos::
>>> config = activate_modules('sale_opportunity_management')
Crear operario::
>>> User = Model.get('res.user')
>>> operator, = User.find([('name', '=', 'Administrator')])
>>> operator.is_operator = True
>>> operator.save()
>>> set_user(operator.id)
Crear prospecto::
>>> Prospect = Model.get('sale.prospect')
>>> prospect = Prospect()
>>> prospect.name = 'Assignable To Me S.A.S'
>>> contact_method = prospect.contact_methods.new(value='123123123', name='Ricardo', job='Infraestructura')
>>> prospect.business_unit = 'brigade'
>>> prospect.save()
[ Se inició seguimiento, asignado al operador que lo creó ]
>>> ProspectTrace = Model.get('sale.prospect_trace')
>>> prospect_trace, = ProspectTrace.find([('prospect', '=', prospect)])
>>> assigned_operator_id = prospect_trace.prospect_assigned_operator.id
>>> assert operator.id == assigned_operator_id
>>> assert prospect.assigned_operator.id == assigned_operator_id

View File

@ -365,7 +365,14 @@ Crear un usuario de rol administrador::
>>> admin.user_admin == True
True
Agregar un nuevo método de contacto desde prospecto
>>> contact_method = prospect1.contact_methods.new(value='0000000000', name='Nuevo', job='Puesto increíble')
>>> prospect1.save()
>>> prospect1.contact_methods[-1].value
'0000000000'
>>> prospect_trace.prospect_contacts[-1].value
'0000000000'
--------
Reportes

View File

@ -2,18 +2,18 @@
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<form col="6">
<label name="operator"/>
<field name="operator"/>
<label name="business_unit"/>
<field name="business_unit"/>
<label name="prospects_chunk"/>
<field name="prospects_chunk"/>
<label name="operator" />
<field name="operator" />
<label name="business_unit" />
<field name="business_unit" />
<label name="prospects_chunk" />
<field name="prospects_chunk" />
<notebook colspan="6">
<page string="Prospects" id="prospects_to_assign" col="6">
<group col="2" id="prospects">
<field name="prospects"/>
</group>
</page>
<page string="Prospects" id="prospects_to_assign" col="6">
<group col="2" id="prospects">
<field name="prospects" />
</group>
</page>
</notebook>
</form>
</form>

View File

@ -3,7 +3,9 @@
this repository contains the full copyright notices and license terms. -->
<data>
<xpath expr="//field[@name='name']" position="after">
<label name="user_admin"/>
<field name="user_admin"/>
<label name="user_admin" />
<field name="user_admin" />
<label name="is_operator" />
<field name="is_operator" />
</xpath>
</data>
</data>