263 lines
8.9 KiB
Python
263 lines
8.9 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Copyright 2024-2025 Nexa Systems Inc.
|
|
# License OPL-1 (Odoo Proprietary License v1.0)
|
|
# Part of the Fusion Claim Assistant product family.
|
|
|
|
import logging
|
|
from datetime import date, timedelta
|
|
from odoo import models, api
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
class ADPPostingScheduleMixin(models.AbstractModel):
|
|
"""Mixin providing ADP posting schedule calculation methods.
|
|
|
|
This mixin can be inherited by any model that needs to calculate
|
|
ADP posting dates and deadlines.
|
|
|
|
Posting Schedule Logic:
|
|
- Posting days occur every N days (default 14) from a base date
|
|
- Submission deadline: Wednesday 6 PM before posting day
|
|
- Delivery reminder: Tuesday of posting week
|
|
- Billing reminder: Monday of posting week
|
|
- Payment processed: Posting day + 7 days
|
|
- Payment received: Posting day + 10 days
|
|
"""
|
|
_name = 'fusion_claims.adp.posting.schedule.mixin'
|
|
_description = 'ADP Posting Schedule Mixin'
|
|
|
|
@api.model
|
|
def _get_adp_posting_base_date(self):
|
|
"""Get the configured base posting date from settings."""
|
|
ICP = self.env['ir.config_parameter'].sudo()
|
|
base_date_str = ICP.get_param('fusion_claims.adp_posting_base_date', '2026-01-23')
|
|
try:
|
|
return date.fromisoformat(base_date_str)
|
|
except (ValueError, TypeError):
|
|
return date(2026, 1, 23)
|
|
|
|
@api.model
|
|
def _get_adp_posting_frequency(self):
|
|
"""Get the configured posting frequency in days from settings."""
|
|
ICP = self.env['ir.config_parameter'].sudo()
|
|
frequency = ICP.get_param('fusion_claims.adp_posting_frequency_days', '14')
|
|
try:
|
|
return int(frequency)
|
|
except (ValueError, TypeError):
|
|
return 14
|
|
|
|
@api.model
|
|
def _get_next_posting_date(self, from_date=None):
|
|
"""Calculate the next ADP posting date from a given date.
|
|
|
|
Args:
|
|
from_date: The date to calculate from (default: today)
|
|
|
|
Returns:
|
|
date: The next posting date
|
|
"""
|
|
if from_date is None:
|
|
from_date = date.today()
|
|
elif hasattr(from_date, 'date'):
|
|
from_date = from_date.date()
|
|
|
|
base_date = self._get_adp_posting_base_date()
|
|
frequency = self._get_adp_posting_frequency()
|
|
|
|
if frequency <= 0:
|
|
frequency = 14
|
|
|
|
# Calculate days since base date
|
|
days_diff = (from_date - base_date).days
|
|
|
|
if days_diff < 0:
|
|
# from_date is before base_date, so base_date is the next posting date
|
|
return base_date
|
|
|
|
# Calculate how many complete cycles have passed
|
|
cycles_passed = days_diff // frequency
|
|
|
|
# The next posting date is (cycles_passed + 1) * frequency days from base
|
|
next_posting = base_date + timedelta(days=(cycles_passed + 1) * frequency)
|
|
|
|
# If from_date equals a posting date, return the next one
|
|
if days_diff % frequency == 0:
|
|
return next_posting
|
|
|
|
return next_posting
|
|
|
|
@api.model
|
|
def _get_current_posting_date(self, from_date=None):
|
|
"""Get the posting date for the current cycle (may be in the past).
|
|
|
|
Args:
|
|
from_date: The date to calculate from (default: today)
|
|
|
|
Returns:
|
|
date: The current cycle's posting date
|
|
"""
|
|
if from_date is None:
|
|
from_date = date.today()
|
|
elif hasattr(from_date, 'date'):
|
|
from_date = from_date.date()
|
|
|
|
base_date = self._get_adp_posting_base_date()
|
|
frequency = self._get_adp_posting_frequency()
|
|
|
|
if frequency <= 0:
|
|
frequency = 14
|
|
|
|
days_diff = (from_date - base_date).days
|
|
|
|
if days_diff < 0:
|
|
return base_date
|
|
|
|
cycles_passed = days_diff // frequency
|
|
return base_date + timedelta(days=cycles_passed * frequency)
|
|
|
|
@api.model
|
|
def _get_posting_week_wednesday(self, posting_date):
|
|
"""Get the Wednesday before the posting date (submission deadline day).
|
|
|
|
The submission deadline is Wednesday 6 PM of the posting week.
|
|
Posting day is typically Friday, so Wednesday is 2 days before.
|
|
|
|
Args:
|
|
posting_date: The posting date
|
|
|
|
Returns:
|
|
date: The Wednesday before posting date
|
|
"""
|
|
if hasattr(posting_date, 'date'):
|
|
posting_date = posting_date.date()
|
|
|
|
# Find the Wednesday of the same week
|
|
# weekday(): Monday=0, Tuesday=1, Wednesday=2, Thursday=3, Friday=4
|
|
days_since_wednesday = (posting_date.weekday() - 2) % 7
|
|
if days_since_wednesday == 0 and posting_date.weekday() != 2:
|
|
days_since_wednesday = 7
|
|
|
|
return posting_date - timedelta(days=days_since_wednesday)
|
|
|
|
@api.model
|
|
def _get_posting_week_tuesday(self, posting_date):
|
|
"""Get the Tuesday of the posting week (delivery reminder date).
|
|
|
|
Args:
|
|
posting_date: The posting date
|
|
|
|
Returns:
|
|
date: The Tuesday of posting week
|
|
"""
|
|
if hasattr(posting_date, 'date'):
|
|
posting_date = posting_date.date()
|
|
|
|
# Find the Tuesday of the same week
|
|
days_since_tuesday = (posting_date.weekday() - 1) % 7
|
|
if days_since_tuesday == 0 and posting_date.weekday() != 1:
|
|
days_since_tuesday = 7
|
|
|
|
return posting_date - timedelta(days=days_since_tuesday)
|
|
|
|
@api.model
|
|
def _get_posting_week_monday(self, posting_date):
|
|
"""Get the Monday of the posting week (billing reminder date).
|
|
|
|
Args:
|
|
posting_date: The posting date
|
|
|
|
Returns:
|
|
date: The Monday of posting week
|
|
"""
|
|
if hasattr(posting_date, 'date'):
|
|
posting_date = posting_date.date()
|
|
|
|
# Find the Monday of the same week
|
|
days_since_monday = posting_date.weekday() # Monday=0
|
|
return posting_date - timedelta(days=days_since_monday)
|
|
|
|
@api.model
|
|
def _get_expected_payment_date(self, posting_date):
|
|
"""Get the expected payment received date (posting + 10 days).
|
|
|
|
Args:
|
|
posting_date: The posting date
|
|
|
|
Returns:
|
|
date: The expected payment received date
|
|
"""
|
|
if hasattr(posting_date, 'date'):
|
|
posting_date = posting_date.date()
|
|
|
|
return posting_date + timedelta(days=10)
|
|
|
|
@api.model
|
|
def _get_payment_processed_date(self, posting_date):
|
|
"""Get the payment processed date (posting + 7 days).
|
|
|
|
Args:
|
|
posting_date: The posting date
|
|
|
|
Returns:
|
|
date: The payment processed date
|
|
"""
|
|
if hasattr(posting_date, 'date'):
|
|
posting_date = posting_date.date()
|
|
|
|
return posting_date + timedelta(days=7)
|
|
|
|
@api.model
|
|
def _get_adp_billing_reminder_user(self):
|
|
"""Get the configured billing reminder user from settings."""
|
|
ICP = self.env['ir.config_parameter'].sudo()
|
|
user_id_str = ICP.get_param('fusion_claims.adp_billing_reminder_user_id', '')
|
|
if user_id_str:
|
|
try:
|
|
user_id = int(user_id_str)
|
|
return self.env['res.users'].browse(user_id).exists()
|
|
except (ValueError, TypeError):
|
|
pass
|
|
return self.env['res.users']
|
|
|
|
@api.model
|
|
def _get_adp_correction_reminder_users(self):
|
|
"""Get the configured correction reminder users from settings."""
|
|
ICP = self.env['ir.config_parameter'].sudo()
|
|
user_ids_str = ICP.get_param('fusion_claims.adp_correction_reminder_user_ids', '')
|
|
if user_ids_str:
|
|
try:
|
|
user_ids = [int(x.strip()) for x in user_ids_str.split(',') if x.strip()]
|
|
return self.env['res.users'].browse(user_ids).exists()
|
|
except (ValueError, TypeError):
|
|
pass
|
|
return self.env['res.users']
|
|
|
|
@api.model
|
|
def _is_past_submission_deadline(self, posting_date=None, check_time=True):
|
|
"""Check if we're past the submission deadline for a posting cycle.
|
|
|
|
Args:
|
|
posting_date: The posting date to check (default: next posting date)
|
|
check_time: If True, checks if past 6 PM on Wednesday
|
|
|
|
Returns:
|
|
bool: True if past deadline
|
|
"""
|
|
from datetime import datetime
|
|
|
|
if posting_date is None:
|
|
posting_date = self._get_next_posting_date()
|
|
|
|
wednesday = self._get_posting_week_wednesday(posting_date)
|
|
today = date.today()
|
|
|
|
if today > wednesday:
|
|
return True
|
|
elif today == wednesday and check_time:
|
|
# Check if past 6 PM (18:00)
|
|
now = datetime.now()
|
|
return now.hour >= 18
|
|
|
|
return False
|