Initial commit
This commit is contained in:
202
Fusion Accounting/models/res_partner_followup.py
Normal file
202
Fusion Accounting/models/res_partner_followup.py
Normal file
@@ -0,0 +1,202 @@
|
||||
# Part of Fusion Accounting. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class FusionPartnerFollowup(models.Model):
|
||||
"""Extends the partner model with payment follow-up capabilities.
|
||||
|
||||
Adds fields and methods for tracking overdue invoices, determining
|
||||
the appropriate follow-up level, and launching the follow-up
|
||||
workflow directly from the partner form.
|
||||
"""
|
||||
|
||||
_inherit = 'res.partner'
|
||||
|
||||
# ---- Follow-up Fields ----
|
||||
followup_level_id = fields.Many2one(
|
||||
comodel_name='fusion.followup.level',
|
||||
string="Follow-up Level",
|
||||
company_dependent=True,
|
||||
tracking=True,
|
||||
help="Current follow-up escalation level for this partner.",
|
||||
)
|
||||
followup_next_action_date = fields.Date(
|
||||
string="Next Follow-up Date",
|
||||
company_dependent=True,
|
||||
tracking=True,
|
||||
help="Date on which the next follow-up action should be performed.",
|
||||
)
|
||||
followup_responsible_id = fields.Many2one(
|
||||
comodel_name='res.users',
|
||||
string="Follow-up Responsible",
|
||||
company_dependent=True,
|
||||
tracking=True,
|
||||
help="User responsible for managing payment collection for this partner.",
|
||||
)
|
||||
|
||||
# ---- Computed Indicators ----
|
||||
fusion_overdue_amount = fields.Monetary(
|
||||
string="Total Overdue Amount",
|
||||
compute='_compute_fusion_overdue_amount',
|
||||
currency_field='currency_id',
|
||||
help="Total amount of overdue receivables for the current company.",
|
||||
)
|
||||
fusion_overdue_count = fields.Integer(
|
||||
string="Overdue Invoice Count",
|
||||
compute='_compute_fusion_overdue_amount',
|
||||
help="Number of overdue invoices for the current company.",
|
||||
)
|
||||
|
||||
# --------------------------------------------------
|
||||
# Computed Fields
|
||||
# --------------------------------------------------
|
||||
|
||||
@api.depends('credit')
|
||||
def _compute_fusion_overdue_amount(self):
|
||||
"""Compute the total overdue receivable amount and invoice count.
|
||||
|
||||
Uses the receivable move lines that are posted, unreconciled,
|
||||
and past their maturity date.
|
||||
"""
|
||||
today = fields.Date.context_today(self)
|
||||
for partner in self:
|
||||
overdue_data = partner.get_overdue_invoices()
|
||||
partner.fusion_overdue_amount = sum(
|
||||
line.amount_residual for line in overdue_data
|
||||
)
|
||||
partner.fusion_overdue_count = len(overdue_data.mapped('move_id'))
|
||||
|
||||
# --------------------------------------------------
|
||||
# Public Methods
|
||||
# --------------------------------------------------
|
||||
|
||||
def get_overdue_invoices(self):
|
||||
"""Return unpaid receivable move lines that are past due.
|
||||
|
||||
Searches for posted, unreconciled journal items on receivable
|
||||
accounts where the maturity date is earlier than today.
|
||||
|
||||
:returns: An ``account.move.line`` recordset.
|
||||
"""
|
||||
self.ensure_one()
|
||||
today = fields.Date.context_today(self)
|
||||
return self.env['account.move.line'].search([
|
||||
('partner_id', '=', self.commercial_partner_id.id),
|
||||
('company_id', '=', self.env.company.id),
|
||||
('account_id.account_type', '=', 'asset_receivable'),
|
||||
('parent_state', '=', 'posted'),
|
||||
('reconciled', '=', False),
|
||||
('date_maturity', '<', today),
|
||||
])
|
||||
|
||||
def get_overdue_amount(self):
|
||||
"""Return the total overdue receivable amount for this partner.
|
||||
|
||||
:returns: A float representing the overdue monetary amount.
|
||||
"""
|
||||
self.ensure_one()
|
||||
overdue_lines = self.get_overdue_invoices()
|
||||
return sum(overdue_lines.mapped('amount_residual'))
|
||||
|
||||
def action_open_followup(self):
|
||||
"""Open the follow-up form view for this partner.
|
||||
|
||||
Locates or creates the ``fusion.followup.line`` record for the
|
||||
current partner and company, then opens the form view for it.
|
||||
|
||||
:returns: An ``ir.actions.act_window`` dictionary.
|
||||
"""
|
||||
self.ensure_one()
|
||||
followup_line = self.env['fusion.followup.line'].search([
|
||||
('partner_id', '=', self.id),
|
||||
('company_id', '=', self.env.company.id),
|
||||
], limit=1)
|
||||
|
||||
if not followup_line:
|
||||
# Assign first level automatically
|
||||
first_level = self.env['fusion.followup.level'].search([
|
||||
('company_id', '=', self.env.company.id),
|
||||
], order='sequence, id', limit=1)
|
||||
followup_line = self.env['fusion.followup.line'].create({
|
||||
'partner_id': self.id,
|
||||
'company_id': self.env.company.id,
|
||||
'followup_level_id': first_level.id if first_level else False,
|
||||
})
|
||||
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _("Payment Follow-up: %s", self.display_name),
|
||||
'res_model': 'fusion.followup.line',
|
||||
'res_id': followup_line.id,
|
||||
'view_mode': 'form',
|
||||
'target': 'current',
|
||||
}
|
||||
|
||||
# --------------------------------------------------
|
||||
# Scheduled Actions
|
||||
# --------------------------------------------------
|
||||
|
||||
@api.model
|
||||
def compute_partners_needing_followup(self):
|
||||
"""Scheduled action: find partners with overdue invoices and create
|
||||
or update their follow-up tracking records.
|
||||
|
||||
This method is called daily by ``ir.cron``. It scans for partners
|
||||
that have at least one overdue receivable and ensures each one has
|
||||
a ``fusion.followup.line`` record. Existing records are refreshed
|
||||
so their computed fields stay current.
|
||||
|
||||
:returns: ``True``
|
||||
"""
|
||||
today = fields.Date.context_today(self)
|
||||
companies = self.env['res.company'].search([])
|
||||
|
||||
for company in companies:
|
||||
# Find partners with overdue receivables in this company
|
||||
overdue_lines = self.env['account.move.line'].search([
|
||||
('company_id', '=', company.id),
|
||||
('account_id.account_type', '=', 'asset_receivable'),
|
||||
('parent_state', '=', 'posted'),
|
||||
('reconciled', '=', False),
|
||||
('date_maturity', '<', today),
|
||||
('amount_residual', '>', 0),
|
||||
])
|
||||
|
||||
partner_ids = overdue_lines.mapped(
|
||||
'partner_id.commercial_partner_id'
|
||||
).ids
|
||||
|
||||
if not partner_ids:
|
||||
continue
|
||||
|
||||
# Fetch existing tracking records for these partners
|
||||
existing_lines = self.env['fusion.followup.line'].search([
|
||||
('partner_id', 'in', partner_ids),
|
||||
('company_id', '=', company.id),
|
||||
])
|
||||
existing_partner_ids = existing_lines.mapped('partner_id').ids
|
||||
|
||||
# Determine the first follow-up level for new records
|
||||
first_level = self.env['fusion.followup.level'].search([
|
||||
('company_id', '=', company.id),
|
||||
], order='sequence, id', limit=1)
|
||||
|
||||
# Create tracking records for partners that don't have one yet
|
||||
new_partner_ids = set(partner_ids) - set(existing_partner_ids)
|
||||
if new_partner_ids:
|
||||
self.env['fusion.followup.line'].create([{
|
||||
'partner_id': pid,
|
||||
'company_id': company.id,
|
||||
'followup_level_id': first_level.id if first_level else False,
|
||||
} for pid in new_partner_ids])
|
||||
|
||||
# Refresh computed fields on all relevant records
|
||||
all_lines = self.env['fusion.followup.line'].search([
|
||||
('partner_id', 'in', partner_ids),
|
||||
('company_id', '=', company.id),
|
||||
])
|
||||
all_lines.compute_followup_status()
|
||||
|
||||
return True
|
||||
Reference in New Issue
Block a user