- 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
377 lines
12 KiB
Python
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,
|
|
}
|