diff --git a/.woodpecker.yml b/.woodpecker.yml index 8f88ad2..32a7d87 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -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:

${CI_COMMIT_AUTHOR}: Task ${CI_BUILD_NUMBER} TODO BIEN EN ${CI_REPO}

${CI_COMMIT_MESSAGE}
when: - status: [ success ] + status: [success] notify-failure: image: plugins/matrix @@ -49,7 +48,7 @@ steps:

${CI_COMMIT_AUTHOR}: Task ${CI_BUILD_NUMBER} !!!TODO MAL EN ${CI_REPO}

${CI_COMMIT_MESSAGE}
when: - status: [ failure ] + status: [failure] services: postgres: diff --git a/__init__.py b/__init__.py index f5066d6..d14612d 100644 --- a/__init__.py +++ b/__init__.py @@ -1,40 +1,62 @@ from trytond.pool import Pool -from . import prospect -from . import prospect_trace -from . import call -from . import pending_call -from . import user -from .locations import city -from .locations import department + +# 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 __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') diff --git a/call.py b/core/Call/models/call.py similarity index 52% rename from call.py rename to core/Call/models/call.py index f8f36f7..335fa20 100644 --- a/call.py +++ b/core/Call/models/call.py @@ -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' diff --git a/pending_call.py b/core/Call/models/pending_call.py similarity index 99% rename from pending_call.py rename to core/Call/models/pending_call.py index fe78bc3..55a1aff 100644 --- a/pending_call.py +++ b/core/Call/models/pending_call.py @@ -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 diff --git a/core/Call/models/pending_task.py b/core/Call/models/pending_task.py new file mode 100644 index 0000000..fe1d267 --- /dev/null +++ b/core/Call/models/pending_task.py @@ -0,0 +1,45 @@ +# 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 + + +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' diff --git a/core/Prospect/models/__init__.py b/core/Prospect/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/Prospect/models/contact_method.py b/core/Prospect/models/contact_method.py new file mode 100644 index 0000000..0ed70ef --- /dev/null +++ b/core/Prospect/models/contact_method.py @@ -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 diff --git a/core/Prospect/models/prospect.py b/core/Prospect/models/prospect.py new file mode 100644 index 0000000..947ef18 --- /dev/null +++ b/core/Prospect/models/prospect.py @@ -0,0 +1,82 @@ +# 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) + # Transaction().atexit( + # lambda: cls.try_assign_to_current_operator(prospects, 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) diff --git a/core/Prospect/wizards/__init__.py b/core/Prospect/wizards/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/Prospect/wizards/assign_operator.py b/core/Prospect/wizards/assign_operator.py new file mode 100644 index 0000000..b841887 --- /dev/null +++ b/core/Prospect/wizards/assign_operator.py @@ -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 diff --git a/core/Prospect/wizards/reassign_prospect_by_operator.py b/core/Prospect/wizards/reassign_prospect_by_operator.py new file mode 100644 index 0000000..b2a29a5 --- /dev/null +++ b/core/Prospect/wizards/reassign_prospect_by_operator.py @@ -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' diff --git a/core/Prospect/wizards/reassign_prospect_by_prospect.py b/core/Prospect/wizards/reassign_prospect_by_prospect.py new file mode 100644 index 0000000..9af57a4 --- /dev/null +++ b/core/Prospect/wizards/reassign_prospect_by_prospect.py @@ -0,0 +1,48 @@ +# 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 + + +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' diff --git a/core/ProspectTrace/models/__init__.py b/core/ProspectTrace/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/ProspectTrace/models/prospect_trace.py b/core/ProspectTrace/models/prospect_trace.py new file mode 100644 index 0000000..2b3e302 --- /dev/null +++ b/core/ProspectTrace/models/prospect_trace.py @@ -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 diff --git a/prospect_trace.py b/core/ProspectTrace/wizards/make_call.py similarity index 53% rename from prospect_trace.py rename to core/ProspectTrace/wizards/make_call.py index 2ba4d60..636ee68 100644 --- a/prospect_trace.py +++ b/core/ProspectTrace/wizards/make_call.py @@ -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' diff --git a/core/ProspectTrace/wizards/schedule_call.py b/core/ProspectTrace/wizards/schedule_call.py new file mode 100644 index 0000000..ea8d32e --- /dev/null +++ b/core/ProspectTrace/wizards/schedule_call.py @@ -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' diff --git a/core/Role/models/__init__.py b/core/Role/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/user.py b/core/Role/models/user.py similarity index 90% rename from user.py rename to core/Role/models/user.py index d5d3201..8a9ea0f 100644 --- a/user.py +++ b/core/Role/models/user.py @@ -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): diff --git a/prospect.py b/prospect.py deleted file mode 100644 index e06a180..0000000 --- a/prospect.py +++ /dev/null @@ -1,265 +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('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 - - -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' diff --git a/prospect.xml b/prospect.xml index ebfebe1..9103f13 100644 --- a/prospect.xml +++ b/prospect.xml @@ -3,183 +3,183 @@ this repository contains the full copyright notices and license terms. --> - - Prospects - - - Prospect Administrator - - - - - + + Prospects - - - + + Prospect Administrator + + + + + + + + + - Prospects + Prospects - sale.prospect + eval="[If(Eval('context', {}).get('user_admin', None), (), ('assigned_operator', '=', Eval('_user')))]" + pyson="1" /> + sale.prospect - sale.prospect + sale.prospect tree prospect_tree - sale.prospect + sale.prospect form prospect_form - - - + + + - - - + + + Unassigned - - - - + + + + Assigned - - - - + + + + + action="act_prospect_tree" /> - - - + + + - Contact method - prospect.contact_method + Contact method + prospect.contact_method - prospect.contact_method + prospect.contact_method tree contact_method_tree - prospect.contact_method + prospect.contact_method form contact_method_form - - - + + + - - - + + + Assign Operator sale.prospect.assign - + sale.prospect.assign.start form assign_start_form - + form_action sale.prospect, -1 - + + parent="menu_prospects_tree" + sequence="40" + id="menu_prospects_assigned_wizard" + action="assign_operator_wizard" /> - - - + + + Reassign by operator sale.prospect.reassign_by_operator - + sale.prospect.reassign_by_operator.start form reassign_by_operator_form - + + icon='tryton-refresh' /> - - - + + + + action="reassign_by_operator_wizard" /> Reassign by prospect sale.prospect.reassign_by_prospect - + sale.prospect.reassign_by_prospect.start form reassign_by_prospect_form - - + + action="reassign_by_prospect_wizard" /> - - - - - - - - - - - - - - - + + + + + + + - - + + + + + + + + + + \ No newline at end of file diff --git a/tests/scenario_assign_prospect_to_me.rst b/tests/scenario_assign_prospect_to_me.rst new file mode 100644 index 0000000..7fffb53 --- /dev/null +++ b/tests/scenario_assign_prospect_to_me.rst @@ -0,0 +1,36 @@ +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 + diff --git a/view/assign_start_form.xml b/view/assign_start_form.xml index b08bab5..25cdda3 100644 --- a/view/assign_start_form.xml +++ b/view/assign_start_form.xml @@ -2,18 +2,18 @@
-