mirror of
https://github.com/bringout/oca-technical.git
synced 2026-04-26 08:12:06 +02:00
Initial commit: OCA Technical packages (595 packages)
This commit is contained in:
commit
2cc02aac6e
24950 changed files with 2318079 additions and 0 deletions
|
|
@ -0,0 +1,9 @@
|
|||
# Copyright 2021 Sygel - Valentin Vinagre
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
|
||||
|
||||
from . import crm_salesperson_planner_visit
|
||||
from . import crm_salesperson_planner_visit_template
|
||||
from . import crm_salesperson_planner_visit_close_reason
|
||||
from . import res_partner
|
||||
from . import crm_lead
|
||||
from . import calendar_event
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
# Copyright 2021 Sygel - Manuel Regidor
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
|
||||
|
||||
from odoo import _, fields, models
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class CalendarEvent(models.Model):
|
||||
_inherit = "calendar.event"
|
||||
|
||||
salesperson_planner_visit_ids = fields.One2many(
|
||||
string="Salesperson Visits",
|
||||
comodel_name="crm.salesperson.planner.visit",
|
||||
inverse_name="calendar_event_id",
|
||||
)
|
||||
|
||||
def write(self, values):
|
||||
if values.get("start") or values.get("user_id"):
|
||||
salesperson_visit_events = self.filtered(
|
||||
lambda a: a.res_model == "crm.salesperson.planner.visit"
|
||||
)
|
||||
if salesperson_visit_events:
|
||||
new_vals = {}
|
||||
if values.get("start"):
|
||||
new_vals["date"] = values.get("start")
|
||||
if values.get("user_id"):
|
||||
new_vals["user_id"] = values.get("user_id")
|
||||
user_id = self.env["res.users"].browse(values.get("user_id"))
|
||||
if user_id:
|
||||
partner_ids = self.partner_ids.filtered(
|
||||
lambda a: a != self.user_id.partner_id
|
||||
).ids
|
||||
partner_ids.append(user_id.partner_id.id)
|
||||
values["partner_ids"] = [(6, 0, partner_ids)]
|
||||
salesperson_visit_events.mapped(
|
||||
"salesperson_planner_visit_ids"
|
||||
).with_context(bypass_update_event=True).write(new_vals)
|
||||
return super().write(values)
|
||||
|
||||
def unlink(self):
|
||||
if not self.env.context.get("bypass_cancel_visit"):
|
||||
salesperson_visit_events = self.filtered(
|
||||
lambda a: a.res_model == "crm.salesperson.planner.visit"
|
||||
and a.salesperson_planner_visit_ids
|
||||
)
|
||||
if salesperson_visit_events:
|
||||
error_msg = ""
|
||||
for event in salesperson_visit_events:
|
||||
error_msg += _(
|
||||
"Event %(event_name)s is related to salesperson visit "
|
||||
"%(partner_name)s. Cancel it to delete this event.\n"
|
||||
) % {
|
||||
"event_name": event.name,
|
||||
"partner_name": fields.first(
|
||||
event.salesperson_planner_visit_ids
|
||||
).name,
|
||||
}
|
||||
raise ValidationError(error_msg)
|
||||
return super().unlink()
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
# Copyright 2021 Sygel - Valentin Vinagre
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class CrmLead(models.Model):
|
||||
_inherit = "crm.lead"
|
||||
|
||||
crm_salesperson_planner_visit_ids = fields.Many2many(
|
||||
comodel_name="crm.salesperson.planner.visit",
|
||||
relation="crm_salesperson_planner_visit_crm_lead_rel",
|
||||
string="Visits",
|
||||
copy=False,
|
||||
domain="[('partner_id', 'child_of', partner_id)]",
|
||||
)
|
||||
|
|
@ -0,0 +1,198 @@
|
|||
# Copyright 2021 Sygel - Valentin Vinagre
|
||||
# Copyright 2021 Sygel - Manuel Regidor
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class CrmSalespersonPlannerVisit(models.Model):
|
||||
_name = "crm.salesperson.planner.visit"
|
||||
_description = "Salesperson Planner Visit"
|
||||
_order = "date desc,sequence"
|
||||
_inherit = ["mail.thread", "mail.activity.mixin"]
|
||||
|
||||
name = fields.Char(
|
||||
string="Visit Number",
|
||||
required=True,
|
||||
default="/",
|
||||
readonly=True,
|
||||
copy=False,
|
||||
)
|
||||
partner_id = fields.Many2one(
|
||||
comodel_name="res.partner",
|
||||
string="Customer",
|
||||
required=True,
|
||||
)
|
||||
partner_phone = fields.Char(string="Phone", related="partner_id.phone")
|
||||
partner_mobile = fields.Char(string="Mobile", related="partner_id.mobile")
|
||||
date = fields.Date(
|
||||
default=fields.Date.context_today,
|
||||
required=True,
|
||||
)
|
||||
sequence = fields.Integer(
|
||||
help="Used to order Visits in the different views",
|
||||
default=20,
|
||||
)
|
||||
company_id = fields.Many2one(
|
||||
comodel_name="res.company",
|
||||
string="Company",
|
||||
default=lambda self: self.env.company,
|
||||
)
|
||||
user_id = fields.Many2one(
|
||||
comodel_name="res.users",
|
||||
string="Salesperson",
|
||||
index=True,
|
||||
tracking=True,
|
||||
default=lambda self: self.env.user,
|
||||
domain=lambda self: [
|
||||
("groups_id", "in", self.env.ref("sales_team.group_sale_salesman").id)
|
||||
],
|
||||
)
|
||||
opportunity_ids = fields.Many2many(
|
||||
comodel_name="crm.lead",
|
||||
relation="crm_salesperson_planner_visit_crm_lead_rel",
|
||||
string="Opportunities",
|
||||
copy=False,
|
||||
domain="[('type', '=', 'opportunity'), ('partner_id', 'child_of', partner_id)]",
|
||||
)
|
||||
description = fields.Html()
|
||||
state = fields.Selection(
|
||||
string="Status",
|
||||
required=True,
|
||||
readonly=True,
|
||||
copy=False,
|
||||
tracking=True,
|
||||
selection=[
|
||||
("draft", "Draft"),
|
||||
("confirm", "Validated"),
|
||||
("done", "Visited"),
|
||||
("cancel", "Cancelled"),
|
||||
("incident", "Incident"),
|
||||
],
|
||||
default="draft",
|
||||
)
|
||||
close_reason_id = fields.Many2one(
|
||||
comodel_name="crm.salesperson.planner.visit.close.reason", string="Close Reason"
|
||||
)
|
||||
close_reason_image = fields.Image(max_width=1024, max_height=1024, attachment=True)
|
||||
close_reason_notes = fields.Text()
|
||||
visit_template_id = fields.Many2one(
|
||||
comodel_name="crm.salesperson.planner.visit.template", string="Visit Template"
|
||||
)
|
||||
calendar_event_id = fields.Many2one(
|
||||
comodel_name="calendar.event", string="Calendar Event"
|
||||
)
|
||||
|
||||
_sql_constraints = [
|
||||
(
|
||||
"crm_salesperson_planner_visit_name",
|
||||
"UNIQUE (name)",
|
||||
"The visit number must be unique!",
|
||||
),
|
||||
]
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
for vals in vals_list:
|
||||
if vals.get("name", "/") == "/":
|
||||
vals["name"] = self.env["ir.sequence"].next_by_code(
|
||||
"salesperson.planner.visit"
|
||||
)
|
||||
return super().create(vals_list)
|
||||
|
||||
def action_draft(self):
|
||||
if self.state not in ["cancel", "incident", "done"]:
|
||||
raise ValidationError(
|
||||
_("The visit must be in cancelled, incident or visited state")
|
||||
)
|
||||
if self.calendar_event_id:
|
||||
self.calendar_event_id.with_context(bypass_cancel_visit=True).unlink()
|
||||
self.write({"state": "draft"})
|
||||
|
||||
def action_confirm(self):
|
||||
if self.filtered(lambda a: not a.state == "draft"):
|
||||
raise ValidationError(_("The visit must be in draft state"))
|
||||
events = self.create_calendar_event()
|
||||
if events:
|
||||
self.browse(events.mapped("res_id")).write({"state": "confirm"})
|
||||
|
||||
def action_done(self):
|
||||
if not self.state == "confirm":
|
||||
raise ValidationError(_("The visit must be in confirmed state"))
|
||||
self.write({"state": "done"})
|
||||
|
||||
def action_cancel(self, reason_id, image=None, notes=None):
|
||||
if self.state not in ["draft", "confirm"]:
|
||||
raise ValidationError(_("The visit must be in draft or validated state"))
|
||||
if self.calendar_event_id:
|
||||
self.calendar_event_id.with_context(bypass_cancel_visit=True).unlink()
|
||||
self.write(
|
||||
{
|
||||
"state": "cancel",
|
||||
"close_reason_id": reason_id.id,
|
||||
"close_reason_image": image,
|
||||
"close_reason_notes": notes,
|
||||
}
|
||||
)
|
||||
|
||||
def _prepare_calendar_event_vals(self):
|
||||
return {
|
||||
"name": self.name,
|
||||
"partner_ids": [(6, 0, [self.partner_id.id, self.user_id.partner_id.id])],
|
||||
"user_id": self.user_id.id,
|
||||
"start_date": self.date,
|
||||
"stop_date": self.date,
|
||||
"start": self.date,
|
||||
"stop": self.date,
|
||||
"allday": True,
|
||||
"res_model": self._name,
|
||||
"res_model_id": self.env.ref(
|
||||
"crm_salesperson_planner.model_crm_salesperson_planner_visit"
|
||||
).id,
|
||||
"res_id": self.id,
|
||||
}
|
||||
|
||||
def create_calendar_event(self):
|
||||
events = self.env["calendar.event"]
|
||||
for item in self:
|
||||
event = self.env["calendar.event"].create(
|
||||
item._prepare_calendar_event_vals()
|
||||
)
|
||||
if event:
|
||||
event.activity_ids.unlink()
|
||||
item.calendar_event_id = event
|
||||
events += event
|
||||
return events
|
||||
|
||||
def action_incident(self, reason_id, image=None, notes=None):
|
||||
if self.state not in ["draft", "confirm"]:
|
||||
raise ValidationError(_("The visit must be in draft or validated state"))
|
||||
self.write(
|
||||
{
|
||||
"state": "incident",
|
||||
"close_reason_id": reason_id.id,
|
||||
"close_reason_image": image,
|
||||
"close_reason_notes": notes,
|
||||
}
|
||||
)
|
||||
|
||||
def unlink(self):
|
||||
if any(sel.state not in ["draft", "cancel"] for sel in self):
|
||||
raise ValidationError(_("Visits must be in cancelled state"))
|
||||
return super().unlink()
|
||||
|
||||
def write(self, values):
|
||||
ret_val = super().write(values)
|
||||
if (values.get("date") or values.get("user_id")) and not self.env.context.get(
|
||||
"bypass_update_event"
|
||||
):
|
||||
new_vals = {}
|
||||
for item in self.filtered(lambda a: a.calendar_event_id):
|
||||
if values.get("date"):
|
||||
new_vals["start"] = values.get("date")
|
||||
new_vals["stop"] = values.get("date")
|
||||
if values.get("user_id"):
|
||||
new_vals["user_id"] = values.get("user_id")
|
||||
item.calendar_event_id.write(new_vals)
|
||||
return ret_val
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
# Copyright 2021 Sygel - Valentin Vinagre
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class CrmSalespersonPlannerVisitCloseReason(models.Model):
|
||||
_name = "crm.salesperson.planner.visit.close.reason"
|
||||
_description = "SalesPerson Planner Visit Close Reason"
|
||||
|
||||
name = fields.Char(string="Description", required=True, translate=True)
|
||||
close_type = fields.Selection(
|
||||
selection=[("cancel", "Cancel"), ("incident", "Incident")],
|
||||
string="Type",
|
||||
required=True,
|
||||
default="cancel",
|
||||
)
|
||||
require_image = fields.Boolean(default=False)
|
||||
reschedule = fields.Boolean(default=False)
|
||||
|
|
@ -0,0 +1,357 @@
|
|||
# Copyright 2021 Sygel - Valentin Vinagre
|
||||
# Copyright 2021 Sygel - Manuel Regidor
|
||||
# Copyright 2024 Tecnativa - Víctor Martínez
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
from odoo.addons.base.models.res_partner import _tz_get
|
||||
from odoo.addons.calendar.models.calendar_recurrence import (
|
||||
BYDAY_SELECTION,
|
||||
END_TYPE_SELECTION,
|
||||
MONTH_BY_SELECTION,
|
||||
RRULE_TYPE_SELECTION,
|
||||
WEEKDAY_SELECTION,
|
||||
)
|
||||
|
||||
|
||||
class CrmSalespersonPlannerVisitTemplate(models.Model):
|
||||
_name = "crm.salesperson.planner.visit.template"
|
||||
_description = "Crm Salesperson Planner Visit Template"
|
||||
_inherit = ["mail.thread"]
|
||||
|
||||
# We cannot inherit from calendar.event for several reasons:
|
||||
# 1- There are many compute recursion fields that would not allow to change them.
|
||||
# 2- Recurrence is only created correctly if the model is calendar.event
|
||||
# 3- We want to generate visits ("events") manually when we want and only the ones
|
||||
# we want.
|
||||
name = fields.Char(
|
||||
string="Visit Template Number",
|
||||
default="/",
|
||||
readonly=True,
|
||||
copy=False,
|
||||
)
|
||||
description = fields.Html()
|
||||
user_id = fields.Many2one(
|
||||
comodel_name="res.users",
|
||||
string="Salesperson",
|
||||
tracking=True,
|
||||
default=lambda self: self.env.user,
|
||||
domain=lambda self: [
|
||||
("groups_id", "in", self.env.ref("sales_team.group_sale_salesman").id)
|
||||
],
|
||||
)
|
||||
partner_id = fields.Many2one(
|
||||
comodel_name="res.partner",
|
||||
string="Scheduled by",
|
||||
related="user_id.partner_id",
|
||||
readonly=True,
|
||||
)
|
||||
partner_ids = fields.Many2many(
|
||||
comodel_name="res.partner",
|
||||
string="Customer",
|
||||
default=False,
|
||||
required=True,
|
||||
)
|
||||
sequence = fields.Integer(
|
||||
help="Used to order Visits in the different views",
|
||||
default=20,
|
||||
)
|
||||
company_id = fields.Many2one(
|
||||
comodel_name="res.company",
|
||||
string="Company",
|
||||
default=lambda self: self.env.company,
|
||||
)
|
||||
categ_ids = fields.Many2many(comodel_name="calendar.event.type", string="Tags")
|
||||
alarm_ids = fields.Many2many(
|
||||
comodel_name="calendar.alarm",
|
||||
string="Reminders",
|
||||
ondelete="restrict",
|
||||
help="Notifications sent to all attendees to remind of the meeting.",
|
||||
)
|
||||
state = fields.Selection(
|
||||
string="Status",
|
||||
required=True,
|
||||
copy=False,
|
||||
tracking=True,
|
||||
selection=[
|
||||
("draft", "Draft"),
|
||||
("in-progress", "In Progress"),
|
||||
("done", "Done"),
|
||||
("cancel", "Cancelled"),
|
||||
],
|
||||
default="draft",
|
||||
)
|
||||
visit_ids = fields.One2many(
|
||||
comodel_name="crm.salesperson.planner.visit",
|
||||
inverse_name="visit_template_id",
|
||||
string="Visit Template",
|
||||
)
|
||||
visit_ids_count = fields.Integer(
|
||||
string="Number of Sales Person Visits", compute="_compute_visit_ids_count"
|
||||
)
|
||||
auto_validate = fields.Boolean(default=True)
|
||||
last_visit_date = fields.Date(compute="_compute_last_visit_date", store=True)
|
||||
final_date = fields.Date(string="Repeat Until")
|
||||
start = fields.Datetime(
|
||||
required=True,
|
||||
tracking=True,
|
||||
default=fields.Date.today,
|
||||
help="Start date of an event, without time for full days events",
|
||||
)
|
||||
stop = fields.Datetime(
|
||||
required=True,
|
||||
tracking=True,
|
||||
default=lambda self: fields.Datetime.today() + timedelta(hours=1),
|
||||
compute="_compute_stop",
|
||||
readonly=False,
|
||||
store=True,
|
||||
help="Stop date of an event, without time for full days events",
|
||||
)
|
||||
allday = fields.Boolean(string="All Day", default=True)
|
||||
start_date = fields.Date(
|
||||
store=True,
|
||||
tracking=True,
|
||||
compute="_compute_dates",
|
||||
inverse="_inverse_dates",
|
||||
)
|
||||
stop_date = fields.Date(
|
||||
string="End Date",
|
||||
store=True,
|
||||
tracking=True,
|
||||
compute="_compute_dates",
|
||||
inverse="_inverse_dates",
|
||||
)
|
||||
duration = fields.Float(compute="_compute_duration", store=True, readonly=False)
|
||||
rrule = fields.Char(string="Recurrent Rule")
|
||||
rrule_type = fields.Selection(
|
||||
RRULE_TYPE_SELECTION,
|
||||
string="Recurrence",
|
||||
help="Let the event automatically repeat at that interval",
|
||||
default="daily",
|
||||
required=True,
|
||||
)
|
||||
event_tz = fields.Selection(_tz_get, string="Timezone")
|
||||
end_type = fields.Selection(END_TYPE_SELECTION, string="Recurrence Termination")
|
||||
interval = fields.Integer(
|
||||
string="Repeat Every", help="Repeat every (Days/Week/Month/Year)"
|
||||
)
|
||||
count = fields.Integer(string="Repeat", help="Repeat x times")
|
||||
mon = fields.Boolean()
|
||||
tue = fields.Boolean()
|
||||
wed = fields.Boolean()
|
||||
thu = fields.Boolean()
|
||||
fri = fields.Boolean()
|
||||
sat = fields.Boolean()
|
||||
sun = fields.Boolean()
|
||||
month_by = fields.Selection(MONTH_BY_SELECTION, string="Option")
|
||||
day = fields.Integer(string="Date of month")
|
||||
weekday = fields.Selection(WEEKDAY_SELECTION)
|
||||
byday = fields.Selection(BYDAY_SELECTION)
|
||||
until = fields.Date()
|
||||
|
||||
_sql_constraints = [
|
||||
(
|
||||
"crm_salesperson_planner_visit_template_name",
|
||||
"UNIQUE (name)",
|
||||
"The visit template number must be unique!",
|
||||
),
|
||||
]
|
||||
|
||||
def _compute_visit_ids_count(self):
|
||||
visit_data = self.env["crm.salesperson.planner.visit"].read_group(
|
||||
[("visit_template_id", "in", self.ids)],
|
||||
["visit_template_id"],
|
||||
["visit_template_id"],
|
||||
)
|
||||
mapped_data = {
|
||||
m["visit_template_id"][0]: m["visit_template_id_count"] for m in visit_data
|
||||
}
|
||||
for sel in self:
|
||||
sel.visit_ids_count = mapped_data.get(sel.id, 0)
|
||||
|
||||
@api.depends("visit_ids.date")
|
||||
def _compute_last_visit_date(self):
|
||||
for sel in self.filtered(lambda x: x.visit_ids):
|
||||
sel.last_visit_date = sel.visit_ids.sorted(lambda x: x.date)[-1].date
|
||||
|
||||
@api.depends("start", "duration")
|
||||
def _compute_stop(self):
|
||||
"""Same method as in calendar.event."""
|
||||
for item in self:
|
||||
item.stop = item.start and item.start + timedelta(
|
||||
minutes=round((item.duration or 1.0) * 60)
|
||||
)
|
||||
if item.allday:
|
||||
item.stop -= timedelta(seconds=1)
|
||||
|
||||
@api.depends("allday", "start", "stop")
|
||||
def _compute_dates(self):
|
||||
"""Same method as in calendar.event."""
|
||||
for item in self:
|
||||
if item.allday and item.start and item.stop:
|
||||
item.start_date = item.start.date()
|
||||
item.stop_date = item.stop.date()
|
||||
else:
|
||||
item.start_date = False
|
||||
item.stop_date = False
|
||||
|
||||
@api.depends("stop", "start")
|
||||
def _compute_duration(self):
|
||||
"""Same method as in calendar.event."""
|
||||
for item in self:
|
||||
item.duration = self._get_duration(item.start, item.stop)
|
||||
|
||||
def _get_duration(self, start, stop):
|
||||
"""Same method as in calendar.event."""
|
||||
if not start or not stop:
|
||||
return 0
|
||||
duration = (stop - start).total_seconds() / 3600
|
||||
return round(duration, 2)
|
||||
|
||||
def _inverse_dates(self):
|
||||
"""Same method as in calendar.event."""
|
||||
for item in self:
|
||||
if item.allday:
|
||||
enddate = fields.Datetime.from_string(item.stop_date)
|
||||
enddate = enddate.replace(hour=18)
|
||||
startdate = fields.Datetime.from_string(item.start_date)
|
||||
startdate = startdate.replace(hour=8)
|
||||
item.write(
|
||||
{
|
||||
"start": startdate.replace(tzinfo=None),
|
||||
"stop": enddate.replace(tzinfo=None),
|
||||
}
|
||||
)
|
||||
|
||||
@api.constrains("partner_ids")
|
||||
def _constrains_partner_ids(self):
|
||||
for item in self:
|
||||
if len(item.partner_ids) > 1:
|
||||
raise ValidationError(_("Only one customer is allowed"))
|
||||
|
||||
@api.onchange("end_type")
|
||||
def _onchange_end_type(self):
|
||||
"""Avoid inconsistent data if you switch from one thing to another."""
|
||||
if self.end_type == "count":
|
||||
self.until = False
|
||||
elif self.end_type == "end_date":
|
||||
self.count = 0
|
||||
elif self.end_type == "forever":
|
||||
self.count = 0
|
||||
self.until = False
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
for vals in vals_list:
|
||||
if vals.get("name", "/") == "/":
|
||||
vals["name"] = self.env["ir.sequence"].next_by_code(
|
||||
"salesperson.planner.visit.template"
|
||||
)
|
||||
return super().create(vals_list)
|
||||
|
||||
def action_view_salesperson_planner_visit(self):
|
||||
action = self.env["ir.actions.act_window"]._for_xml_id(
|
||||
"crm_salesperson_planner.all_crm_salesperson_planner_visit_action"
|
||||
)
|
||||
action["domain"] = [("id", "=", self.visit_ids.ids)]
|
||||
action["context"] = {
|
||||
"default_partner_id": self.partner_id.id,
|
||||
"default_visit_template_id": self.id,
|
||||
"default_description": self.description,
|
||||
}
|
||||
return action
|
||||
|
||||
def action_validate(self):
|
||||
self.write({"state": "in-progress"})
|
||||
|
||||
def action_cancel(self):
|
||||
self.write({"state": "cancel"})
|
||||
|
||||
def action_draft(self):
|
||||
self.write({"state": "draft"})
|
||||
|
||||
def _prepare_crm_salesperson_planner_visit_vals(self, dates):
|
||||
return [
|
||||
{
|
||||
"partner_id": (
|
||||
fields.first(self.partner_ids).id if self.partner_ids else False
|
||||
),
|
||||
"date": date,
|
||||
"sequence": self.sequence,
|
||||
"user_id": self.user_id.id,
|
||||
"description": self.description,
|
||||
"company_id": self.company_id.id,
|
||||
"visit_template_id": self.id,
|
||||
}
|
||||
for date in dates
|
||||
]
|
||||
|
||||
# Get the date range from calendar.recurrence, that way the values obtained will
|
||||
# be correct (except for incompatible cases).
|
||||
def _get_start_range_dates(self):
|
||||
"""Method to get all dates (sorted) in the range."""
|
||||
duration = self.stop - self.start
|
||||
ranges = (
|
||||
self.env["calendar.recurrence"]
|
||||
.new(
|
||||
{
|
||||
"rrule_type": self.rrule_type,
|
||||
"interval": self.interval,
|
||||
"month_by": self.month_by,
|
||||
"weekday": self.weekday,
|
||||
"byday": self.byday,
|
||||
"count": self.count,
|
||||
"end_type": self.end_type,
|
||||
"until": self.until,
|
||||
"mon": self.mon,
|
||||
"tue": self.tue,
|
||||
"wed": self.wed,
|
||||
"thu": self.thu,
|
||||
"fri": self.fri,
|
||||
"sat": self.sat,
|
||||
"sun": self.sun,
|
||||
}
|
||||
)
|
||||
._range_calculation(self, duration)
|
||||
)
|
||||
start_dates = []
|
||||
for start, _stop in ranges:
|
||||
start_dates.append(start.date())
|
||||
return sorted(start_dates)
|
||||
|
||||
def _get_max_date(self):
|
||||
"""The maximum date will be the last of the range."""
|
||||
return self._get_start_range_dates()[-1]
|
||||
|
||||
def _get_recurrence_dates(self, items):
|
||||
"""For the n items, get only those that are not already generated."""
|
||||
start_dates = self._get_start_range_dates()
|
||||
dates = []
|
||||
visit_dates = self.visit_ids.mapped("date")
|
||||
for _date in start_dates[:items]:
|
||||
if _date not in visit_dates:
|
||||
dates.append(_date)
|
||||
return dates
|
||||
|
||||
def _create_visits(self, days=7):
|
||||
return self._prepare_crm_salesperson_planner_visit_vals(
|
||||
self._get_recurrence_dates(days)
|
||||
)
|
||||
|
||||
def create_visits(self, days=7):
|
||||
for item in self:
|
||||
visits = self.env["crm.salesperson.planner.visit"].create(
|
||||
item._create_visits(days)
|
||||
)
|
||||
if visits and item.auto_validate:
|
||||
visits.action_confirm()
|
||||
if item.last_visit_date >= item._get_max_date():
|
||||
item.state = "done"
|
||||
|
||||
def _cron_create_visits(self, days=7):
|
||||
templates = self.search([("state", "=", "in-progress")])
|
||||
templates.create_visits(days)
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
# Copyright 2021 Sygel - Valentin Vinagre
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class ResPartner(models.Model):
|
||||
_inherit = "res.partner"
|
||||
|
||||
salesperson_planner_visit_count = fields.Integer(
|
||||
string="Number of Salesperson Visits",
|
||||
compute="_compute_salesperson_planner_visit_count",
|
||||
)
|
||||
|
||||
def _compute_salesperson_planner_visit_count(self):
|
||||
partners = self | self.mapped("child_ids")
|
||||
partner_data = self.env["crm.salesperson.planner.visit"].read_group(
|
||||
[("partner_id", "in", partners.ids)], ["partner_id"], ["partner_id"]
|
||||
)
|
||||
mapped_data = {m["partner_id"][0]: m["partner_id_count"] for m in partner_data}
|
||||
for partner in self:
|
||||
visit_count = mapped_data.get(partner.id, 0)
|
||||
for child in partner.child_ids:
|
||||
visit_count += mapped_data.get(child.id, 0)
|
||||
partner.salesperson_planner_visit_count = visit_count
|
||||
|
||||
def action_view_salesperson_planner_visit(self):
|
||||
action = self.env["ir.actions.act_window"]._for_xml_id(
|
||||
"crm_salesperson_planner.all_crm_salesperson_planner_visit_action"
|
||||
)
|
||||
operator = "child_of" if self.is_company else "="
|
||||
action["domain"] = [("partner_id", operator, self.id)]
|
||||
return action
|
||||
Loading…
Add table
Add a link
Reference in a new issue