Skip to content

Commit

Permalink
Merge PR #558 into 15.0
Browse files Browse the repository at this point in the history
Signed-off-by pedrobaeza
  • Loading branch information
OCA-git-bot committed Mar 19, 2024
2 parents 6c9b9c7 + 634629f commit aa3d428
Show file tree
Hide file tree
Showing 5 changed files with 330 additions and 77 deletions.
245 changes: 184 additions & 61 deletions crm_salesperson_planner/models/crm_salesperson_planner_visit_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,51 @@
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 @@ -35,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 @@ -71,29 +96,62 @@ class CrmSalespersonPlannerVisitTemplate(models.Model):
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")
allday = fields.Boolean(default=True)
# Set all compute=_compute_recurrence fields of calendar.event as store=True.
# We want to manage the value of the fields manually and we don't want to depend
# on recurrence_id field (only possible with calendar.event).
# We don't use the recurrency field either because it is unnecessary.
rrule = fields.Char(store=True)
rrule_type = fields.Selection(store=True, default="daily", required=True)
event_tz = fields.Selection(store=True)
end_type = fields.Selection(store=True)
interval = fields.Integer(store=True)
count = fields.Integer(store=True)
mon = fields.Boolean(store=True)
tue = fields.Boolean(store=True)
wed = fields.Boolean(store=True)
thu = fields.Boolean(store=True)
fri = fields.Boolean(store=True)
sat = fields.Boolean(store=True)
sun = fields.Boolean(store=True)
month_by = fields.Selection(store=True)
day = fields.Integer(store=True)
weekday = fields.Selection(store=True)
byday = fields.Selection(store=True)
until = fields.Date(store=True)
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 = [
(
Expand All @@ -120,6 +178,55 @@ 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:
Expand All @@ -146,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 @@ -189,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.until or 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 += timedelta(months=value)
elif self.rrule_type == "yearly":
date += timedelta(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
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,23 @@

from odoo import fields
from odoo.tests import common
from odoo.tools import mute_logger


class TestCrmSalespersonPlannerVisitBase(common.TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.env = cls.env(
context=dict(
cls.env.context,
mail_create_nolog=True,
mail_create_nosubscribe=True,
mail_notrack=True,
no_reset_password=True,
tracking_disable=True,
)
)
cls.visit_model = cls.env["crm.salesperson.planner.visit"]
cls.partner_model = cls.env["res.partner"]
cls.close_model = cls.env["crm.salesperson.planner.visit.close.reason"]
Expand Down Expand Up @@ -94,6 +105,7 @@ def config_close_wiz(self, att_close_type, vals):
)
close_wiz.action_close_reason_apply()

@mute_logger("odoo.models.unlink")
def test_crm_salesperson_close_wiz_cancel(self):
self.visit1.action_confirm()
self.assertEqual(self.visit1.state, "confirm")
Expand All @@ -108,6 +120,7 @@ def test_crm_salesperson_close_wiz_cancel(self):
2,
)

@mute_logger("odoo.models.unlink")
def test_crm_salesperson_close_wiz_cancel_resch(self):
self.visit1.action_confirm()
self.assertEqual(self.visit1.state, "confirm")
Expand All @@ -132,6 +145,7 @@ def test_crm_salesperson_close_wiz_cancel_resch(self):
1,
)

@mute_logger("odoo.models.unlink")
def test_crm_salesperson_close_wiz_cancel_img(self):
self.visit1.action_confirm()
self.assertEqual(self.visit1.state, "confirm")
Expand Down
Loading

0 comments on commit aa3d428

Please sign in to comment.