Skip to content

Commit

Permalink
Merge PR #559 into 16.0
Browse files Browse the repository at this point in the history
Signed-off-by pedrobaeza
  • Loading branch information
OCA-git-bot committed Mar 25, 2024
2 parents bd38a95 + b8d3eeb commit c77642b
Show file tree
Hide file tree
Showing 9 changed files with 451 additions and 164 deletions.
6 changes: 5 additions & 1 deletion crm_salesperson_planner/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Crm Salesperson Planner
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:ece9cce90a55feea9d5e85078846e339a4c3fe95400e5a7e218f7624b81201c0
!! source digest: sha256:39aa94d6627a7c39e9220ca722d7dc32fb546b850287206071a5eb873e2368d1
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
Expand Down Expand Up @@ -84,6 +84,10 @@ Contributors

* Gerardo Marin Parra <info@pesol.es>

* `Tecnativa <https://www.tecnativa.com>`_:

* Víctor Martínez

Maintainers
~~~~~~~~~~~

Expand Down
238 changes: 194 additions & 44 deletions crm_salesperson_planner/models/crm_salesperson_planner_visit_template.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,58 @@
# 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 dateutil.relativedelta import relativedelta

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 = "calendar.event"
_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",
relation="salesperson_planner_res_partner_rel",
default=False,
required=True,
)
Expand All @@ -36,18 +65,13 @@ class CrmSalespersonPlannerVisitTemplate(models.Model):
string="Company",
default=lambda self: self.env.company,
)
user_id = fields.Many2one(
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)
],
)
categ_ids = fields.Many2many(
relation="visit_category_rel",
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.",
)
alarm_ids = fields.Many2many(relation="visit_calendar_event_rel")
state = fields.Selection(
string="Status",
required=True,
Expand All @@ -70,14 +94,64 @@ class CrmSalespersonPlannerVisitTemplate(models.Model):
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,
)
last_visit_date = fields.Date(compute="_compute_last_visit_date", store=True)
final_date = fields.Date(string="Repeat Until")
allday = fields.Boolean(default=True)
recurrency = fields.Boolean(default=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 = [
(
Expand All @@ -104,12 +178,72 @@ 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:
Expand All @@ -119,12 +253,6 @@ def create(self, vals_list):
)
return super().create(vals_list)

# overwrite
# Calling _update_cron from default write funciont is not
# necessary in this case
def write(self, vals):
return super(models.Model, self).write(vals)

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"
Expand Down Expand Up @@ -162,29 +290,51 @@ def _prepare_crm_salesperson_planner_visit_vals(self, dates):
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):
return self._increase_date(self.start_date, self.count)

def _increase_date(self, date, value):
if self.rrule_type == "daily":
date += timedelta(days=value)
elif self.rrule_type == "weekly":
date += timedelta(weeks=value)
elif self.rrule_type == "monthly":
date += relativedelta(months=value)
elif self.rrule_type == "yearly":
date += relativedelta(years=value)
return date
"""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 = []
max_date = self._get_max_date()
from_date = self._increase_date(self.last_visit_date or self.start_date, 1)
if max_date > from_date:
for _x in range(items):
if from_date <= max_date:
dates.append(from_date)
from_date = self._increase_date(from_date, 1)
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):
Expand Down
4 changes: 4 additions & 0 deletions crm_salesperson_planner/readme/CONTRIBUTORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,7 @@
* `Pesol <https://www.pesol.es>`__:

* Gerardo Marin Parra <info@pesol.es>

* `Tecnativa <https://www.tecnativa.com>`_:

* Víctor Martínez
Loading

0 comments on commit c77642b

Please sign in to comment.