changes
This commit is contained in:
@@ -4,6 +4,8 @@ import uuid
|
||||
from datetime import timedelta
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
|
||||
@@ -70,7 +72,19 @@ class SaleOrder(models.Model):
|
||||
string="Original Duration (Days)",
|
||||
compute='_compute_rental_original_duration',
|
||||
store=True,
|
||||
help="Original rental duration in days, used for renewal period calculation.",
|
||||
help="Current rental period length in days, used for reminder/marketing calculations.",
|
||||
)
|
||||
rental_renewal_period = fields.Selection(
|
||||
[
|
||||
('daily', 'Daily'),
|
||||
('weekly', 'Weekly'),
|
||||
('monthly', 'Monthly'),
|
||||
('yearly', 'Yearly'),
|
||||
],
|
||||
string="Renewal Period",
|
||||
default='monthly',
|
||||
help="How often the rental auto-renews. Monthly uses calendar months "
|
||||
"so the renewal date stays on the same day each month.",
|
||||
)
|
||||
rental_renewal_log_ids = fields.One2many(
|
||||
'rental.renewal.log',
|
||||
@@ -452,10 +466,11 @@ class SaleOrder(models.Model):
|
||||
)
|
||||
|
||||
def _get_rental_duration_days(self):
|
||||
"""Return the rental duration to use for renewal.
|
||||
"""Return the current rental period length in days.
|
||||
|
||||
Uses the stored original duration so renewals keep the same
|
||||
period length even if dates were manually adjusted.
|
||||
Used for percentage-based calculations (reminders, marketing)
|
||||
and short-term detection. NOT used for computing the next
|
||||
renewal date -- see ``_get_renewal_delta`` for that.
|
||||
"""
|
||||
self.ensure_one()
|
||||
if self.rental_original_duration:
|
||||
@@ -464,6 +479,23 @@ class SaleOrder(models.Model):
|
||||
return max((self.rental_return_date - self.rental_start_date).days, 1)
|
||||
return 30
|
||||
|
||||
def _get_renewal_delta(self):
|
||||
"""Return the relativedelta/timedelta to add for the next renewal.
|
||||
|
||||
Monthly and yearly periods use ``relativedelta`` so the renewal
|
||||
date always lands on the same calendar day (e.g. the 13th).
|
||||
Daily and weekly periods use a fixed ``timedelta``.
|
||||
"""
|
||||
self.ensure_one()
|
||||
period = self.rental_renewal_period or 'monthly'
|
||||
if period == 'monthly':
|
||||
return relativedelta(months=1)
|
||||
if period == 'yearly':
|
||||
return relativedelta(years=1)
|
||||
if period == 'weekly':
|
||||
return timedelta(weeks=1)
|
||||
return timedelta(days=max(self._get_rental_duration_days(), 1))
|
||||
|
||||
def _get_marketing_target_date(self):
|
||||
"""When to send the purchase marketing email (percentage of period after start)."""
|
||||
self.ensure_one()
|
||||
@@ -592,12 +624,19 @@ class SaleOrder(models.Model):
|
||||
and logs the renewal event.
|
||||
"""
|
||||
self.ensure_one()
|
||||
duration = self._get_rental_duration_days()
|
||||
rental_lines = self._get_rental_only_lines()
|
||||
if not rental_lines:
|
||||
_logger.warning(
|
||||
"Skipping auto-renewal for %s: no rental lines to invoice.",
|
||||
self.name,
|
||||
)
|
||||
return
|
||||
|
||||
old_start = self.rental_start_date
|
||||
old_return = self.rental_return_date
|
||||
|
||||
new_start = old_return
|
||||
new_return = new_start + timedelta(days=duration)
|
||||
new_return = new_start + self._get_renewal_delta()
|
||||
|
||||
self.write({
|
||||
'rental_start_date': new_start,
|
||||
@@ -624,8 +663,6 @@ class SaleOrder(models.Model):
|
||||
payment_ok = False
|
||||
if invoice and self.rental_payment_token_id:
|
||||
payment_ok = self._collect_renewal_payment(invoice, renewal_log)
|
||||
if payment_ok:
|
||||
self._send_invoice_with_receipt(invoice, 'renewal')
|
||||
|
||||
if invoice and not self.rental_payment_token_id:
|
||||
self._notify_staff_manual_payment(invoice)
|
||||
@@ -697,7 +734,7 @@ class SaleOrder(models.Model):
|
||||
self._notify_staff_manual_payment(invoice)
|
||||
return False
|
||||
|
||||
except (UserError, ValidationError) as e:
|
||||
except Exception as e:
|
||||
_logger.error("Auto-payment failed for rental %s: %s", self.name, e)
|
||||
renewal_log.write({
|
||||
'payment_status': 'failed',
|
||||
@@ -724,7 +761,7 @@ class SaleOrder(models.Model):
|
||||
)
|
||||
|
||||
def _send_renewal_confirmation_email(self, renewal_log, payment_ok):
|
||||
"""Send renewal confirmation email with invoice + receipt attached."""
|
||||
"""Send a single renewal email with invoice PDF + Poynt receipt attached."""
|
||||
self.ensure_one()
|
||||
template = self.env.ref(
|
||||
'fusion_rental.mail_template_rental_renewed',
|
||||
@@ -743,17 +780,20 @@ class SaleOrder(models.Model):
|
||||
attachment_ids = self._generate_invoice_attachments(
|
||||
invoice, 'Renewal',
|
||||
)
|
||||
receipt_ids = self._find_poynt_receipt_attachments(invoice)
|
||||
attachment_ids.extend(receipt_ids)
|
||||
|
||||
try:
|
||||
template.with_context(
|
||||
payment_ok=payment_ok,
|
||||
renewal_log=renewal_log,
|
||||
renewal_invoice=invoice,
|
||||
).send_mail(
|
||||
self.id,
|
||||
force_send=True,
|
||||
email_values={'attachment_ids': attachment_ids} if attachment_ids else {},
|
||||
)
|
||||
_logger.warning("Renewal confirmation email sent for %s", self.name)
|
||||
_logger.info("Renewal confirmation email sent for %s", self.name)
|
||||
except Exception as e:
|
||||
_logger.error("Failed to send renewal confirmation email for %s: %s", self.name, e)
|
||||
|
||||
@@ -919,16 +959,16 @@ class SaleOrder(models.Model):
|
||||
continue
|
||||
|
||||
try:
|
||||
order._process_auto_renewal()
|
||||
with self.env.cr.savepoint():
|
||||
order._process_auto_renewal()
|
||||
except Exception as e:
|
||||
_logger.error("Auto-renewal failed for %s: %s", order.name, e)
|
||||
|
||||
def action_manual_renewal(self):
|
||||
"""Open the manual renewal wizard."""
|
||||
self.ensure_one()
|
||||
duration = self._get_rental_duration_days()
|
||||
new_start = self.rental_return_date
|
||||
new_return = new_start + timedelta(days=duration) if new_start else False
|
||||
new_return = new_start + self._get_renewal_delta() if new_start else False
|
||||
|
||||
return {
|
||||
'name': _("Renew Rental"),
|
||||
|
||||
Reference in New Issue
Block a user