Files
Nexa Admin 431052920e 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
2026-03-11 16:19:52 +00:00

377 lines
12 KiB
Python

# -*- coding: utf-8 -*-
# Copyright 2024-2026 Nexa Systems Inc.
# License OPL-1 (Odoo Proprietary License v1.0)
from odoo import models, fields, api, _
from odoo.exceptions import UserError
class FusionLTCRepairStage(models.Model):
_name = 'fusion.ltc.repair.stage'
_description = 'LTC Repair Stage'
_order = 'sequence, id'
name = fields.Char(string='Stage Name', required=True, translate=True)
sequence = fields.Integer(string='Sequence', default=10)
fold = fields.Boolean(
string='Folded in Kanban',
help='Folded stages are hidden by default in the kanban view',
)
color = fields.Char(
string='Stage Color',
help='CSS color class for stage badge (e.g. info, success, warning, danger)',
default='secondary',
)
description = fields.Text(string='Description')
class FusionLTCRepair(models.Model):
_name = 'fusion.ltc.repair'
_description = 'LTC Repair Request'
_inherit = ['mail.thread', 'mail.activity.mixin']
_order = 'issue_reported_date desc, id desc'
name = fields.Char(
string='Reference',
required=True,
copy=False,
readonly=True,
default=lambda self: _('New'),
)
facility_id = fields.Many2one(
'fusion.ltc.facility',
string='LTC Facility',
required=True,
tracking=True,
index=True,
)
client_id = fields.Many2one(
'res.partner',
string='Client/Resident',
tracking=True,
help='Link to the resident contact record',
)
client_name = fields.Char(
string='Client Name',
help='Quick entry name when no contact record exists',
)
display_client_name = fields.Char(
compute='_compute_display_client_name',
string='Client',
store=True,
)
room_number = fields.Char(string='Room Number')
stage_id = fields.Many2one(
'fusion.ltc.repair.stage',
string='Stage',
tracking=True,
group_expand='_read_group_stage_ids',
default=lambda self: self._default_stage_id(),
index=True,
)
kanban_state = fields.Selection([
('normal', 'In Progress'),
('done', 'Ready'),
('blocked', 'Blocked'),
], string='Kanban State', default='normal', tracking=True)
color = fields.Integer(string='Color Index')
is_emergency = fields.Boolean(
string='Emergency Repair',
tracking=True,
help='Emergency visits may be chargeable at an extra rate',
)
priority = fields.Selection([
('0', 'Normal'),
('1', 'High'),
], string='Priority', default='0')
product_serial = fields.Char(string='Product Serial #')
product_id = fields.Many2one(
'product.product',
string='Product',
help='Link to product record if applicable',
)
issue_description = fields.Text(
string='Issue Description',
required=True,
)
issue_reported_date = fields.Date(
string='Issue Reported Date',
required=True,
default=fields.Date.context_today,
tracking=True,
)
issue_fixed_date = fields.Date(
string='Issue Fixed Date',
tracking=True,
)
resolution_description = fields.Text(string='Resolution Description')
assigned_technician_id = fields.Many2one(
'res.users',
string='Assigned Technician',
tracking=True,
)
task_id = fields.Many2one(
'fusion.technician.task',
string='Field Service Task',
tracking=True,
)
sale_order_id = fields.Many2one(
'sale.order',
string='Sale Order',
tracking=True,
help='Sale order created for this repair if applicable',
)
sale_order_name = fields.Char(
related='sale_order_id.name',
string='SO Reference',
)
poa_name = fields.Char(string='Family/POA Name')
poa_phone = fields.Char(string='Family/POA Phone')
company_id = fields.Many2one(
'res.company',
string='Company',
default=lambda self: self.env.company,
)
currency_id = fields.Many2one(
'res.currency',
related='company_id.currency_id',
)
repair_value = fields.Monetary(
string='Repair Value',
currency_field='currency_id',
)
photo_ids = fields.Many2many(
'ir.attachment',
'ltc_repair_photo_rel',
'repair_id',
'attachment_id',
string='Photos (Legacy)',
)
before_photo_ids = fields.Many2many(
'ir.attachment',
'ltc_repair_before_photo_rel',
'repair_id',
'attachment_id',
string='Before Photos',
)
after_photo_ids = fields.Many2many(
'ir.attachment',
'ltc_repair_after_photo_rel',
'repair_id',
'attachment_id',
string='After Photos',
)
notes = fields.Text(string='Internal Notes')
source = fields.Selection([
('portal_form', 'Portal Form'),
('manual', 'Manual Entry'),
('phone', 'Phone Call'),
('migrated', 'Migrated'),
], string='Source', default='manual', tracking=True)
stage_color = fields.Char(
related='stage_id.color',
string='Stage Color',
store=True,
)
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
if vals.get('name', _('New')) == _('New'):
vals['name'] = self.env['ir.sequence'].next_by_code('fusion.ltc.repair') or _('New')
records = super().create(vals_list)
for record in records:
record._post_creation_message()
return records
def _post_creation_message(self):
body = _(
"Repair request submitted for <b>%(client)s</b> in Room <b>%(room)s</b>"
" at <b>%(facility)s</b>.<br/>"
"Issue: %(issue)s",
client=self.display_client_name or 'N/A',
room=self.room_number or 'N/A',
facility=self.facility_id.name,
issue=self.issue_description or '',
)
self.message_post(body=body, message_type='comment')
def _default_stage_id(self):
return self.env['fusion.ltc.repair.stage'].search([], order='sequence', limit=1).id
@api.model
def _read_group_stage_ids(self, stages, domain):
return self.env['fusion.ltc.repair.stage'].search([], order='sequence')
@api.depends('client_id', 'client_name')
def _compute_display_client_name(self):
for repair in self:
if repair.client_id:
repair.display_client_name = repair.client_id.name
else:
repair.display_client_name = repair.client_name or ''
@api.onchange('client_id')
def _onchange_client_id(self):
if self.client_id:
self.client_name = self.client_id.name
if hasattr(self.client_id, 'x_fc_ltc_room_number') and self.client_id.x_fc_ltc_room_number:
self.room_number = self.client_id.x_fc_ltc_room_number
if hasattr(self.client_id, 'x_fc_ltc_facility_id') and self.client_id.x_fc_ltc_facility_id:
self.facility_id = self.client_id.x_fc_ltc_facility_id
def action_view_sale_order(self):
self.ensure_one()
if not self.sale_order_id:
return
return {
'name': self.sale_order_id.name,
'type': 'ir.actions.act_window',
'res_model': 'sale.order',
'view_mode': 'form',
'res_id': self.sale_order_id.id,
}
def action_view_task(self):
self.ensure_one()
if not self.task_id:
return
return {
'name': self.task_id.name,
'type': 'ir.actions.act_window',
'res_model': 'fusion.technician.task',
'view_mode': 'form',
'res_id': self.task_id.id,
}
def action_create_sale_order(self):
self.ensure_one()
if self.sale_order_id:
raise UserError(_('A sale order already exists for this repair.'))
if not self.client_id and self.client_name:
return {
'name': _('Link Contact'),
'type': 'ir.actions.act_window',
'res_model': 'fusion.ltc.repair.create.so.wizard',
'view_mode': 'form',
'target': 'new',
'context': {
'default_repair_id': self.id,
'default_client_name': self.client_name,
},
}
if not self.client_id:
raise UserError(_('Please set a client before creating a sale order.'))
return self._create_linked_sale_order()
def _create_linked_sale_order(self):
self.ensure_one()
SaleOrder = self.env['sale.order']
OrderLine = self.env['sale.order.line']
so_vals = {
'partner_id': self.client_id.id,
'x_fc_ltc_repair_id': self.id,
}
sale_order = SaleOrder.create(so_vals)
seq = 10
OrderLine.create({
'order_id': sale_order.id,
'display_type': 'line_section',
'name': 'PRODUCTS & REPAIRS',
'sequence': seq,
})
seq += 10
repair_tmpl = self.env.ref(
'fusion_ltc_management.product_ltc_repair_service', raise_if_not_found=False
)
repair_product = (
repair_tmpl.product_variant_id if repair_tmpl else False
)
line_vals = {
'order_id': sale_order.id,
'sequence': seq,
'name': 'Repairs at LTC Home - %s' % (self.facility_id.name or ''),
}
if repair_product:
line_vals['product_id'] = repair_product.id
else:
line_vals['display_type'] = 'line_note'
OrderLine.create(line_vals)
seq += 10
OrderLine.create({
'order_id': sale_order.id,
'display_type': 'line_section',
'name': 'REPORTED ISSUES',
'sequence': seq,
})
seq += 10
if self.issue_description:
OrderLine.create({
'order_id': sale_order.id,
'display_type': 'line_note',
'name': self.issue_description,
'sequence': seq,
})
seq += 10
if self.issue_reported_date:
OrderLine.create({
'order_id': sale_order.id,
'display_type': 'line_note',
'name': 'Reported Date: %s' % self.issue_reported_date,
'sequence': seq,
})
seq += 10
OrderLine.create({
'order_id': sale_order.id,
'display_type': 'line_section',
'name': 'PROPOSED RESOLUTION',
'sequence': seq,
})
seq += 10
if self.resolution_description:
OrderLine.create({
'order_id': sale_order.id,
'display_type': 'line_note',
'name': self.resolution_description,
'sequence': seq,
})
seq += 10
if self.product_serial:
OrderLine.create({
'order_id': sale_order.id,
'display_type': 'line_note',
'name': 'Serial Number: %s' % self.product_serial,
'sequence': seq,
})
self.sale_order_id = sale_order.id
return {
'name': sale_order.name,
'type': 'ir.actions.act_window',
'res_model': 'sale.order',
'view_mode': 'form',
'res_id': sale_order.id,
}