315 lines
10 KiB
Python
315 lines
10 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Copyright 2024-2026 Nexa Systems Inc.
|
|
# License OPL-1 (Odoo Proprietary License v1.0)
|
|
|
|
from dateutil.relativedelta import relativedelta
|
|
|
|
from odoo import models, fields, api, _
|
|
|
|
|
|
class FusionLTCFacility(models.Model):
|
|
_name = 'fusion.ltc.facility'
|
|
_description = 'LTC Facility'
|
|
_inherit = ['mail.thread', 'mail.activity.mixin']
|
|
_order = 'name'
|
|
|
|
name = fields.Char(
|
|
string='Facility Name',
|
|
required=True,
|
|
tracking=True,
|
|
)
|
|
code = fields.Char(
|
|
string='Code',
|
|
copy=False,
|
|
readonly=True,
|
|
default=lambda self: _('New'),
|
|
)
|
|
active = fields.Boolean(default=True)
|
|
image_1920 = fields.Image(string='Image', max_width=1920, max_height=1920)
|
|
|
|
partner_id = fields.Many2one(
|
|
'res.partner',
|
|
string='Contact Record',
|
|
help='The facility as a contact in the system',
|
|
tracking=True,
|
|
)
|
|
|
|
# Address
|
|
street = fields.Char(string='Street')
|
|
street2 = fields.Char(string='Street2')
|
|
city = fields.Char(string='City')
|
|
state_id = fields.Many2one(
|
|
'res.country.state',
|
|
string='Province',
|
|
domain="[('country_id', '=', country_id)]",
|
|
)
|
|
zip = fields.Char(string='Postal Code')
|
|
country_id = fields.Many2one('res.country', string='Country')
|
|
phone = fields.Char(string='Phone')
|
|
email = fields.Char(string='Email')
|
|
website = fields.Char(string='Website')
|
|
|
|
# Key contacts
|
|
director_of_care_id = fields.Many2one(
|
|
'res.partner',
|
|
string='Director of Care',
|
|
tracking=True,
|
|
)
|
|
service_supervisor_id = fields.Many2one(
|
|
'res.partner',
|
|
string='Service Supervisor',
|
|
tracking=True,
|
|
)
|
|
physiotherapist_ids = fields.Many2many(
|
|
'res.partner',
|
|
'ltc_facility_physiotherapist_rel',
|
|
'facility_id',
|
|
'partner_id',
|
|
string='Physiotherapists',
|
|
help='Primary contacts for equipment recommendations and communication',
|
|
)
|
|
|
|
# Structure
|
|
number_of_floors = fields.Integer(string='Number of Floors')
|
|
floor_ids = fields.One2many(
|
|
'fusion.ltc.floor',
|
|
'facility_id',
|
|
string='Floors',
|
|
)
|
|
|
|
# Contract
|
|
contract_start_date = fields.Date(string='Contract Start Date', tracking=True)
|
|
contract_end_date = fields.Date(string='Contract End Date', tracking=True)
|
|
contract_file = fields.Binary(
|
|
string='Contract Document',
|
|
attachment=True,
|
|
)
|
|
contract_file_filename = fields.Char(string='Contract Filename')
|
|
contract_notes = fields.Text(string='Contract Notes')
|
|
|
|
# Cleanup scheduling
|
|
cleanup_frequency = fields.Selection([
|
|
('quarterly', 'Quarterly (Every 3 Months)'),
|
|
('semi_annual', 'Semi-Annual (Every 6 Months)'),
|
|
('annual', 'Annual (Yearly)'),
|
|
('custom', 'Custom Interval'),
|
|
], string='Cleanup Frequency', default='quarterly')
|
|
cleanup_interval_days = fields.Integer(
|
|
string='Custom Interval (Days)',
|
|
help='Number of days between cleanups when using custom interval',
|
|
)
|
|
next_cleanup_date = fields.Date(
|
|
string='Next Cleanup Date',
|
|
compute='_compute_next_cleanup_date',
|
|
store=True,
|
|
readonly=False,
|
|
tracking=True,
|
|
)
|
|
|
|
# Related records
|
|
repair_ids = fields.One2many('fusion.ltc.repair', 'facility_id', string='Repairs')
|
|
cleanup_ids = fields.One2many('fusion.ltc.cleanup', 'facility_id', string='Cleanups')
|
|
|
|
# Computed counts
|
|
repair_count = fields.Integer(compute='_compute_repair_count', string='Total Repairs')
|
|
active_repair_count = fields.Integer(compute='_compute_repair_count', string='Active Repairs')
|
|
cleanup_count = fields.Integer(compute='_compute_cleanup_count', string='Cleanups')
|
|
|
|
notes = fields.Html(string='Notes')
|
|
|
|
@api.model_create_multi
|
|
def create(self, vals_list):
|
|
for vals in vals_list:
|
|
if vals.get('code', _('New')) == _('New'):
|
|
vals['code'] = self.env['ir.sequence'].next_by_code('fusion.ltc.facility') or _('New')
|
|
return super().create(vals_list)
|
|
|
|
@api.depends('contract_start_date', 'cleanup_frequency', 'cleanup_interval_days')
|
|
def _compute_next_cleanup_date(self):
|
|
today = fields.Date.context_today(self)
|
|
for facility in self:
|
|
start = facility.contract_start_date
|
|
freq = facility.cleanup_frequency
|
|
if not start or not freq:
|
|
if not facility.next_cleanup_date:
|
|
facility.next_cleanup_date = False
|
|
continue
|
|
|
|
interval = facility._get_cleanup_interval_days()
|
|
delta = relativedelta(days=interval)
|
|
|
|
candidate = start + delta
|
|
while candidate < today:
|
|
candidate += delta
|
|
|
|
facility.next_cleanup_date = candidate
|
|
|
|
def action_preview_contract(self):
|
|
self.ensure_one()
|
|
if not self.contract_file:
|
|
return {
|
|
'type': 'ir.actions.client',
|
|
'tag': 'display_notification',
|
|
'params': {
|
|
'title': _('No Document'),
|
|
'message': _('No contract document has been uploaded yet.'),
|
|
'type': 'warning',
|
|
'sticky': False,
|
|
},
|
|
}
|
|
|
|
attachment = self.env['ir.attachment'].search([
|
|
('res_model', '=', self._name),
|
|
('res_id', '=', self.id),
|
|
('res_field', '=', 'contract_file'),
|
|
], limit=1)
|
|
|
|
if not attachment:
|
|
attachment = self.env['ir.attachment'].search([
|
|
('res_model', '=', self._name),
|
|
('res_id', '=', self.id),
|
|
('name', '=', self.contract_file_filename or 'contract_file'),
|
|
], limit=1, order='id desc')
|
|
|
|
if attachment:
|
|
return {
|
|
'type': 'ir.actions.client',
|
|
'tag': 'fusion_claims.preview_document',
|
|
'params': {
|
|
'attachment_id': attachment.id,
|
|
'title': _('Contract - %s', self.name),
|
|
},
|
|
}
|
|
|
|
return {
|
|
'type': 'ir.actions.client',
|
|
'tag': 'display_notification',
|
|
'params': {
|
|
'title': _('Error'),
|
|
'message': _('Could not load contract document.'),
|
|
'type': 'danger',
|
|
'sticky': False,
|
|
},
|
|
}
|
|
|
|
def _compute_repair_count(self):
|
|
for facility in self:
|
|
repairs = facility.repair_ids
|
|
facility.repair_count = len(repairs)
|
|
facility.active_repair_count = len(repairs.filtered(
|
|
lambda r: r.stage_id and not r.stage_id.fold
|
|
))
|
|
|
|
def _compute_cleanup_count(self):
|
|
for facility in self:
|
|
facility.cleanup_count = len(facility.cleanup_ids)
|
|
|
|
def action_view_repairs(self):
|
|
self.ensure_one()
|
|
return {
|
|
'name': _('Repairs - %s', self.name),
|
|
'type': 'ir.actions.act_window',
|
|
'res_model': 'fusion.ltc.repair',
|
|
'view_mode': 'kanban,list,form',
|
|
'domain': [('facility_id', '=', self.id)],
|
|
'context': {'default_facility_id': self.id},
|
|
}
|
|
|
|
def action_view_cleanups(self):
|
|
self.ensure_one()
|
|
return {
|
|
'name': _('Cleanups - %s', self.name),
|
|
'type': 'ir.actions.act_window',
|
|
'res_model': 'fusion.ltc.cleanup',
|
|
'view_mode': 'list,kanban,form',
|
|
'domain': [('facility_id', '=', self.id)],
|
|
'context': {'default_facility_id': self.id},
|
|
}
|
|
|
|
def _get_cleanup_interval_days(self):
|
|
mapping = {
|
|
'quarterly': 90,
|
|
'semi_annual': 180,
|
|
'annual': 365,
|
|
}
|
|
if self.cleanup_frequency == 'custom':
|
|
return self.cleanup_interval_days or 90
|
|
return mapping.get(self.cleanup_frequency, 90)
|
|
|
|
|
|
class FusionLTCFloor(models.Model):
|
|
_name = 'fusion.ltc.floor'
|
|
_description = 'LTC Facility Floor'
|
|
_order = 'sequence, name'
|
|
|
|
facility_id = fields.Many2one(
|
|
'fusion.ltc.facility',
|
|
string='Facility',
|
|
required=True,
|
|
ondelete='cascade',
|
|
)
|
|
name = fields.Char(string='Floor Name', required=True)
|
|
sequence = fields.Integer(string='Sequence', default=10)
|
|
station_ids = fields.One2many(
|
|
'fusion.ltc.station',
|
|
'floor_id',
|
|
string='Nursing Stations',
|
|
)
|
|
head_nurse_id = fields.Many2one(
|
|
'res.partner',
|
|
string='Head Nurse',
|
|
)
|
|
physiotherapist_id = fields.Many2one(
|
|
'res.partner',
|
|
string='Physiotherapist',
|
|
help='Floor-level physiotherapist if different from facility level',
|
|
)
|
|
|
|
|
|
class FusionLTCStation(models.Model):
|
|
_name = 'fusion.ltc.station'
|
|
_description = 'LTC Nursing Station'
|
|
_order = 'sequence, name'
|
|
|
|
floor_id = fields.Many2one(
|
|
'fusion.ltc.floor',
|
|
string='Floor',
|
|
required=True,
|
|
ondelete='cascade',
|
|
)
|
|
name = fields.Char(string='Station Name', required=True)
|
|
sequence = fields.Integer(string='Sequence', default=10)
|
|
head_nurse_id = fields.Many2one(
|
|
'res.partner',
|
|
string='Head Nurse',
|
|
)
|
|
phone = fields.Char(string='Phone')
|
|
|
|
|
|
class FusionLTCFamilyContact(models.Model):
|
|
_name = 'fusion.ltc.family.contact'
|
|
_description = 'LTC Resident Family Contact'
|
|
_order = 'is_poa desc, name'
|
|
|
|
partner_id = fields.Many2one(
|
|
'res.partner',
|
|
string='Resident',
|
|
required=True,
|
|
ondelete='cascade',
|
|
)
|
|
name = fields.Char(string='Contact Name', required=True)
|
|
relationship = fields.Selection([
|
|
('spouse', 'Spouse'),
|
|
('child', 'Child'),
|
|
('sibling', 'Sibling'),
|
|
('parent', 'Parent'),
|
|
('guardian', 'Guardian'),
|
|
('poa', 'Power of Attorney'),
|
|
('other', 'Other'),
|
|
], string='Relationship')
|
|
phone = fields.Char(string='Phone')
|
|
phone2 = fields.Char(string='Phone 2')
|
|
email = fields.Char(string='Email')
|
|
is_poa = fields.Boolean(string='Is POA', help='Is this person the Power of Attorney?')
|
|
notes = fields.Char(string='Notes')
|