feat: separate fusion field service and LTC into standalone modules, update core modules
- fusion_claims: separated field service logic, updated controllers/views - fusion_tasks: updated task views and map integration - fusion_authorizer_portal: added page 11 signing, schedule booking, migrations - fusion_shipping: new standalone shipping module (Canada Post, FedEx, DHL, Purolator) - fusion_ltc_management: new standalone LTC management module
This commit is contained in:
314
fusion_ltc_management/models/ltc_facility.py
Normal file
314
fusion_ltc_management/models/ltc_facility.py
Normal file
@@ -0,0 +1,314 @@
|
||||
# -*- 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')
|
||||
Reference in New Issue
Block a user