updates
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
{
|
{
|
||||||
'name': 'Fusion Authorizer & Sales Portal',
|
'name': 'Fusion Authorizer & Sales Portal',
|
||||||
'version': '19.0.2.0.9',
|
'version': '19.0.2.2.0',
|
||||||
'category': 'Sales/Portal',
|
'category': 'Sales/Portal',
|
||||||
'summary': 'Portal for Authorizers (OTs) and Sales Reps with Assessment Forms',
|
'summary': 'Portal for Authorizers (OTs) and Sales Reps with Assessment Forms',
|
||||||
'description': """
|
'description': """
|
||||||
@@ -66,6 +66,7 @@ This module provides external portal access for:
|
|||||||
'views/res_partner_views.xml',
|
'views/res_partner_views.xml',
|
||||||
'views/sale_order_views.xml',
|
'views/sale_order_views.xml',
|
||||||
'views/assessment_views.xml',
|
'views/assessment_views.xml',
|
||||||
|
'views/loaner_checkout_views.xml',
|
||||||
'views/pdf_template_views.xml',
|
'views/pdf_template_views.xml',
|
||||||
# Portal Templates
|
# Portal Templates
|
||||||
'views/portal_templates.xml',
|
'views/portal_templates.xml',
|
||||||
@@ -75,6 +76,7 @@ This module provides external portal access for:
|
|||||||
'views/portal_accessibility_forms.xml',
|
'views/portal_accessibility_forms.xml',
|
||||||
'views/portal_technician_templates.xml',
|
'views/portal_technician_templates.xml',
|
||||||
'views/portal_book_assessment.xml',
|
'views/portal_book_assessment.xml',
|
||||||
|
'views/portal_repair_form.xml',
|
||||||
],
|
],
|
||||||
'assets': {
|
'assets': {
|
||||||
'web.assets_backend': [
|
'web.assets_backend': [
|
||||||
|
|||||||
@@ -3,3 +3,4 @@
|
|||||||
from . import portal_main
|
from . import portal_main
|
||||||
from . import portal_assessment
|
from . import portal_assessment
|
||||||
from . import pdf_editor
|
from . import pdf_editor
|
||||||
|
from . import portal_repair
|
||||||
182
fusion_authorizer_portal/controllers/portal_repair.py
Normal file
182
fusion_authorizer_portal/controllers/portal_repair.py
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2024-2026 Nexa Systems Inc.
|
||||||
|
# License OPL-1 (Odoo Proprietary License v1.0)
|
||||||
|
|
||||||
|
from odoo import http, _, fields
|
||||||
|
from odoo.http import request
|
||||||
|
import base64
|
||||||
|
import logging
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class LTCRepairPortal(http.Controller):
|
||||||
|
|
||||||
|
def _is_password_required(self):
|
||||||
|
password = request.env['ir.config_parameter'].sudo().get_param(
|
||||||
|
'fusion_claims.ltc_form_password', ''
|
||||||
|
)
|
||||||
|
return bool(password and password.strip())
|
||||||
|
|
||||||
|
def _process_photos(self, file_list, repair):
|
||||||
|
attachment_ids = []
|
||||||
|
for photo in file_list:
|
||||||
|
if photo and photo.filename:
|
||||||
|
data = photo.read()
|
||||||
|
if data:
|
||||||
|
attachment = request.env['ir.attachment'].sudo().create({
|
||||||
|
'name': photo.filename,
|
||||||
|
'datas': base64.b64encode(data),
|
||||||
|
'res_model': 'fusion.ltc.repair',
|
||||||
|
'res_id': repair.id,
|
||||||
|
})
|
||||||
|
attachment_ids.append(attachment.id)
|
||||||
|
return attachment_ids
|
||||||
|
|
||||||
|
def _is_authenticated(self):
|
||||||
|
if not request.env.user._is_public():
|
||||||
|
return True
|
||||||
|
if not self._is_password_required():
|
||||||
|
return True
|
||||||
|
return request.session.get('ltc_form_authenticated', False)
|
||||||
|
|
||||||
|
@http.route('/repair-form', type='http', auth='public', website=True,
|
||||||
|
sitemap=False)
|
||||||
|
def repair_form(self, **kw):
|
||||||
|
if not self._is_authenticated():
|
||||||
|
return request.render(
|
||||||
|
'fusion_authorizer_portal.portal_ltc_repair_password',
|
||||||
|
{'error': kw.get('auth_error', False)}
|
||||||
|
)
|
||||||
|
|
||||||
|
facilities = request.env['fusion.ltc.facility'].sudo().search(
|
||||||
|
[('active', '=', True)], order='name'
|
||||||
|
)
|
||||||
|
is_technician = not request.env.user._is_public() and request.env.user.has_group(
|
||||||
|
'base.group_user'
|
||||||
|
)
|
||||||
|
return request.render(
|
||||||
|
'fusion_authorizer_portal.portal_ltc_repair_form',
|
||||||
|
{
|
||||||
|
'facilities': facilities,
|
||||||
|
'today': fields.Date.today(),
|
||||||
|
'is_technician': is_technician,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
@http.route('/repair-form/auth', type='http', auth='public',
|
||||||
|
website=True, methods=['POST'], csrf=True)
|
||||||
|
def repair_form_auth(self, **kw):
|
||||||
|
stored_password = request.env['ir.config_parameter'].sudo().get_param(
|
||||||
|
'fusion_claims.ltc_form_password', ''
|
||||||
|
).strip()
|
||||||
|
|
||||||
|
entered_password = (kw.get('password', '') or '').strip()
|
||||||
|
|
||||||
|
if stored_password and entered_password == stored_password:
|
||||||
|
request.session['ltc_form_authenticated'] = True
|
||||||
|
return request.redirect('/repair-form')
|
||||||
|
|
||||||
|
return request.render(
|
||||||
|
'fusion_authorizer_portal.portal_ltc_repair_password',
|
||||||
|
{'error': True}
|
||||||
|
)
|
||||||
|
|
||||||
|
@http.route('/repair-form/submit', type='http', auth='public',
|
||||||
|
website=True, methods=['POST'], csrf=True)
|
||||||
|
def repair_form_submit(self, **kw):
|
||||||
|
if not self._is_authenticated():
|
||||||
|
return request.redirect('/repair-form')
|
||||||
|
|
||||||
|
try:
|
||||||
|
facility_id = int(kw.get('facility_id', 0))
|
||||||
|
if not facility_id:
|
||||||
|
return request.redirect('/repair-form?error=facility')
|
||||||
|
|
||||||
|
vals = {
|
||||||
|
'facility_id': facility_id,
|
||||||
|
'client_name': kw.get('client_name', '').strip(),
|
||||||
|
'room_number': kw.get('room_number', '').strip(),
|
||||||
|
'product_serial': kw.get('product_serial', '').strip(),
|
||||||
|
'issue_description': kw.get('issue_description', '').strip(),
|
||||||
|
'issue_reported_date': kw.get('issue_reported_date') or fields.Date.today(),
|
||||||
|
'is_emergency': kw.get('is_emergency') == 'on',
|
||||||
|
'poa_name': kw.get('poa_name', '').strip() or False,
|
||||||
|
'poa_phone': kw.get('poa_phone', '').strip() or False,
|
||||||
|
'source': 'portal_form',
|
||||||
|
}
|
||||||
|
|
||||||
|
if not vals['client_name']:
|
||||||
|
return request.redirect('/repair-form?error=name')
|
||||||
|
if not vals['issue_description']:
|
||||||
|
return request.redirect('/repair-form?error=description')
|
||||||
|
|
||||||
|
before_files = request.httprequest.files.getlist('before_photos')
|
||||||
|
has_before = any(f and f.filename for f in before_files)
|
||||||
|
if not has_before:
|
||||||
|
return request.redirect('/repair-form?error=photos')
|
||||||
|
|
||||||
|
repair = request.env['fusion.ltc.repair'].sudo().create(vals)
|
||||||
|
|
||||||
|
before_ids = self._process_photos(before_files, repair)
|
||||||
|
if before_ids:
|
||||||
|
repair.sudo().write({
|
||||||
|
'before_photo_ids': [(6, 0, before_ids)],
|
||||||
|
})
|
||||||
|
|
||||||
|
after_files = request.httprequest.files.getlist('after_photos')
|
||||||
|
after_ids = self._process_photos(after_files, repair)
|
||||||
|
if after_ids:
|
||||||
|
repair.sudo().write({
|
||||||
|
'after_photo_ids': [(6, 0, after_ids)],
|
||||||
|
})
|
||||||
|
|
||||||
|
resolved = kw.get('resolved') == 'yes'
|
||||||
|
if resolved:
|
||||||
|
resolution = kw.get('resolution_description', '').strip()
|
||||||
|
if resolution:
|
||||||
|
repair.sudo().write({
|
||||||
|
'resolution_description': resolution,
|
||||||
|
'issue_fixed_date': fields.Date.today(),
|
||||||
|
})
|
||||||
|
|
||||||
|
repair.sudo().activity_schedule(
|
||||||
|
'mail.mail_activity_data_todo',
|
||||||
|
summary=_('New repair request from portal: %s', repair.display_client_name),
|
||||||
|
note=_(
|
||||||
|
'Repair request submitted via portal form for %s at %s (Room %s).',
|
||||||
|
repair.display_client_name,
|
||||||
|
repair.facility_id.name,
|
||||||
|
repair.room_number or 'N/A',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
ip_address = request.httprequest.headers.get(
|
||||||
|
'X-Forwarded-For', request.httprequest.remote_addr
|
||||||
|
)
|
||||||
|
if ip_address and ',' in ip_address:
|
||||||
|
ip_address = ip_address.split(',')[0].strip()
|
||||||
|
|
||||||
|
try:
|
||||||
|
request.env['fusion.ltc.form.submission'].sudo().create({
|
||||||
|
'form_type': 'repair',
|
||||||
|
'repair_id': repair.id,
|
||||||
|
'facility_id': facility_id,
|
||||||
|
'client_name': vals['client_name'],
|
||||||
|
'room_number': vals['room_number'],
|
||||||
|
'product_serial': vals['product_serial'],
|
||||||
|
'is_emergency': vals['is_emergency'],
|
||||||
|
'ip_address': ip_address or '',
|
||||||
|
'status': 'processed',
|
||||||
|
})
|
||||||
|
except Exception:
|
||||||
|
_logger.warning('Failed to log form submission', exc_info=True)
|
||||||
|
|
||||||
|
return request.render(
|
||||||
|
'fusion_authorizer_portal.portal_ltc_repair_thank_you',
|
||||||
|
{'repair': repair}
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
_logger.exception('Error submitting LTC repair form')
|
||||||
|
return request.redirect('/repair-form?error=server')
|
||||||
@@ -7,4 +7,5 @@ from . import adp_document
|
|||||||
from . import assessment
|
from . import assessment
|
||||||
from . import accessibility_assessment
|
from . import accessibility_assessment
|
||||||
from . import sale_order
|
from . import sale_order
|
||||||
|
from . import loaner_checkout
|
||||||
from . import pdf_template
|
from . import pdf_template
|
||||||
27
fusion_authorizer_portal/models/loaner_checkout.py
Normal file
27
fusion_authorizer_portal/models/loaner_checkout.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from odoo import api, fields, models, _
|
||||||
|
|
||||||
|
|
||||||
|
class FusionLoanerCheckoutAssessment(models.Model):
|
||||||
|
_inherit = 'fusion.loaner.checkout'
|
||||||
|
|
||||||
|
assessment_id = fields.Many2one(
|
||||||
|
'fusion.assessment',
|
||||||
|
string='Assessment',
|
||||||
|
ondelete='set null',
|
||||||
|
tracking=True,
|
||||||
|
help='Assessment during which this loaner was issued',
|
||||||
|
)
|
||||||
|
|
||||||
|
def action_view_assessment(self):
|
||||||
|
self.ensure_one()
|
||||||
|
if not self.assessment_id:
|
||||||
|
return
|
||||||
|
return {
|
||||||
|
'name': self.assessment_id.display_name,
|
||||||
|
'type': 'ir.actions.act_window',
|
||||||
|
'res_model': 'fusion.assessment',
|
||||||
|
'view_mode': 'form',
|
||||||
|
'res_id': self.assessment_id.id,
|
||||||
|
}
|
||||||
@@ -6,15 +6,26 @@
|
|||||||
<field name="name">fusion.assessment.tree</field>
|
<field name="name">fusion.assessment.tree</field>
|
||||||
<field name="model">fusion.assessment</field>
|
<field name="model">fusion.assessment</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<list string="Assessments" decoration-info="state == 'draft'" decoration-warning="state == 'pending_signature'" decoration-success="state == 'completed'" decoration-muted="state == 'cancelled'">
|
<list string="Assessments" default_order="assessment_date desc, id desc"
|
||||||
|
decoration-info="state == 'draft'"
|
||||||
|
decoration-warning="state == 'pending_signature'"
|
||||||
|
decoration-success="state == 'completed'"
|
||||||
|
decoration-muted="state == 'cancelled'">
|
||||||
<field name="reference"/>
|
<field name="reference"/>
|
||||||
<field name="client_name"/>
|
<field name="client_name"/>
|
||||||
|
<field name="equipment_type" optional="show"/>
|
||||||
|
<field name="client_type" optional="show"/>
|
||||||
<field name="assessment_date"/>
|
<field name="assessment_date"/>
|
||||||
<field name="sales_rep_id"/>
|
<field name="sales_rep_id"/>
|
||||||
<field name="authorizer_id"/>
|
<field name="authorizer_id"/>
|
||||||
<field name="state" widget="badge" decoration-info="state == 'draft'" decoration-warning="state == 'pending_signature'" decoration-success="state == 'completed'" decoration-danger="state == 'cancelled'"/>
|
<field name="reason_for_application" optional="hide"/>
|
||||||
<field name="signatures_complete" widget="boolean"/>
|
<field name="state" widget="badge"
|
||||||
<field name="sale_order_id"/>
|
decoration-info="state == 'draft'"
|
||||||
|
decoration-warning="state == 'pending_signature'"
|
||||||
|
decoration-success="state == 'completed'"
|
||||||
|
decoration-danger="state == 'cancelled'"/>
|
||||||
|
<field name="signatures_complete" widget="boolean" optional="show"/>
|
||||||
|
<field name="sale_order_id" optional="show"/>
|
||||||
</list>
|
</list>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
@@ -26,124 +37,310 @@
|
|||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<form string="Assessment">
|
<form string="Assessment">
|
||||||
<header>
|
<header>
|
||||||
<button name="action_mark_pending_signature" type="object" string="Mark Pending Signature" class="btn-primary" invisible="state != 'draft'"/>
|
<button name="action_mark_pending_signature" type="object"
|
||||||
<button name="action_complete" type="object" string="Complete Assessment" class="btn-success" invisible="state not in ['draft', 'pending_signature']"/>
|
string="Mark Pending Signature" class="btn-primary"
|
||||||
<button name="action_cancel" type="object" string="Cancel" invisible="state in ['completed', 'cancelled']"/>
|
invisible="state != 'draft'"/>
|
||||||
<button name="action_reset_draft" type="object" string="Reset to Draft" invisible="state != 'cancelled'"/>
|
<button name="action_complete" type="object"
|
||||||
<field name="state" widget="statusbar" statusbar_visible="draft,pending_signature,completed"/>
|
string="Complete Assessment" class="btn-success"
|
||||||
|
invisible="state not in ['draft', 'pending_signature']"/>
|
||||||
|
<button name="action_complete_express" type="object"
|
||||||
|
string="Express Complete" class="btn-warning"
|
||||||
|
invisible="state not in ['draft', 'pending_signature']"
|
||||||
|
confirm="This will complete the assessment without requiring signatures. Continue?"/>
|
||||||
|
<button name="action_cancel" type="object"
|
||||||
|
string="Cancel"
|
||||||
|
invisible="state in ['completed', 'cancelled']"/>
|
||||||
|
<button name="action_reset_draft" type="object"
|
||||||
|
string="Reset to Draft"
|
||||||
|
invisible="state != 'cancelled'"/>
|
||||||
|
<field name="state" widget="statusbar"
|
||||||
|
statusbar_visible="draft,pending_signature,completed"/>
|
||||||
</header>
|
</header>
|
||||||
<sheet>
|
<sheet>
|
||||||
<div class="oe_button_box" name="button_box">
|
<div class="oe_button_box" name="button_box">
|
||||||
<button name="action_view_documents" type="object" class="oe_stat_button" icon="fa-file-pdf-o">
|
<button name="action_view_documents" type="object"
|
||||||
|
class="oe_stat_button" icon="fa-file-pdf-o">
|
||||||
<field name="document_count" string="Documents" widget="statinfo"/>
|
<field name="document_count" string="Documents" widget="statinfo"/>
|
||||||
</button>
|
</button>
|
||||||
<button name="action_view_sale_order" type="object" class="oe_stat_button" icon="fa-shopping-cart" invisible="not sale_order_id">
|
<button name="action_view_sale_order" type="object"
|
||||||
|
class="oe_stat_button" icon="fa-shopping-cart"
|
||||||
|
invisible="not sale_order_id">
|
||||||
<span class="o_stat_text">Sale Order</span>
|
<span class="o_stat_text">Sale Order</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="oe_title">
|
<widget name="web_ribbon" title="Completed" bg_color="text-bg-success"
|
||||||
|
invisible="state != 'completed'"/>
|
||||||
|
<widget name="web_ribbon" title="Cancelled" bg_color="text-bg-danger"
|
||||||
|
invisible="state != 'cancelled'"/>
|
||||||
|
|
||||||
|
<div class="oe_title mb-3">
|
||||||
<h1>
|
<h1>
|
||||||
<field name="reference" readonly="1"/>
|
<field name="reference" readonly="1" class="me-3"/>
|
||||||
</h1>
|
</h1>
|
||||||
|
<h2 class="text-muted" invisible="not client_name">
|
||||||
|
<field name="client_name" readonly="1"/>
|
||||||
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- ============ TOP SUMMARY ============ -->
|
||||||
<group>
|
<group>
|
||||||
<group string="Client Information">
|
<group string="Equipment">
|
||||||
<field name="client_name"/>
|
<field name="equipment_type"/>
|
||||||
<field name="client_first_name"/>
|
<field name="rollator_type" invisible="equipment_type != 'rollator'"/>
|
||||||
<field name="client_last_name"/>
|
<field name="wheelchair_type" invisible="equipment_type != 'wheelchair'"/>
|
||||||
<field name="client_phone"/>
|
<field name="powerchair_type" invisible="equipment_type != 'powerchair'"/>
|
||||||
<field name="client_mobile"/>
|
<field name="client_type"/>
|
||||||
<field name="client_email"/>
|
<field name="reason_for_application"/>
|
||||||
<field name="client_dob"/>
|
<field name="previous_funding_date"
|
||||||
<field name="client_health_card"/>
|
invisible="reason_for_application not in ['replace_status','replace_size','replace_worn','replace_lost','replace_stolen','replace_damaged','replace_no_longer_meets']"/>
|
||||||
</group>
|
</group>
|
||||||
<group string="Address">
|
<group string="Assessment Info">
|
||||||
<field name="client_street"/>
|
|
||||||
<field name="client_city"/>
|
|
||||||
<field name="client_state"/>
|
|
||||||
<field name="client_postal_code"/>
|
|
||||||
<field name="client_country_id"/>
|
|
||||||
</group>
|
|
||||||
</group>
|
|
||||||
|
|
||||||
<group>
|
|
||||||
<group string="Assessment Details">
|
|
||||||
<field name="assessment_date"/>
|
<field name="assessment_date"/>
|
||||||
<field name="assessment_location"/>
|
<field name="assessment_location"/>
|
||||||
<field name="assessment_location_notes"/>
|
<field name="assessment_location_notes"/>
|
||||||
</group>
|
|
||||||
<group string="Participants">
|
|
||||||
<field name="sales_rep_id"/>
|
<field name="sales_rep_id"/>
|
||||||
<field name="authorizer_id"/>
|
<field name="authorizer_id"/>
|
||||||
<field name="partner_id"/>
|
<field name="sale_order_id" readonly="1"/>
|
||||||
<field name="create_new_partner"/>
|
|
||||||
</group>
|
|
||||||
</group>
|
|
||||||
|
|
||||||
<group>
|
|
||||||
<group string="Client References">
|
|
||||||
<field name="client_reference_1"/>
|
|
||||||
<field name="client_reference_2"/>
|
|
||||||
</group>
|
</group>
|
||||||
</group>
|
</group>
|
||||||
|
|
||||||
<notebook>
|
<notebook>
|
||||||
<page string="Wheelchair Specifications" name="specs">
|
|
||||||
|
<!-- ============ CLIENT INFORMATION ============ -->
|
||||||
|
<page string="Client" name="client_info">
|
||||||
<group>
|
<group>
|
||||||
<group string="Seat Measurements">
|
<group string="Personal Details">
|
||||||
|
<field name="client_first_name"/>
|
||||||
|
<field name="client_middle_name"/>
|
||||||
|
<field name="client_last_name"/>
|
||||||
|
<field name="client_dob"/>
|
||||||
|
<field name="client_phone"/>
|
||||||
|
<field name="client_mobile"/>
|
||||||
|
<field name="client_email" widget="email"/>
|
||||||
|
</group>
|
||||||
|
<group string="Health Card">
|
||||||
|
<field name="client_health_card"/>
|
||||||
|
<field name="client_health_card_version"/>
|
||||||
|
<field name="client_weight"/>
|
||||||
|
<field name="client_height"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<group string="Address">
|
||||||
|
<field name="client_street"/>
|
||||||
|
<field name="client_unit"/>
|
||||||
|
<field name="client_city"/>
|
||||||
|
<field name="client_state"/>
|
||||||
|
<field name="client_postal_code"/>
|
||||||
|
<field name="client_country_id"/>
|
||||||
|
</group>
|
||||||
|
<group string="References & Linking">
|
||||||
|
<field name="client_reference_1"/>
|
||||||
|
<field name="client_reference_2"/>
|
||||||
|
<field name="partner_id"/>
|
||||||
|
<field name="create_new_partner"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
</page>
|
||||||
|
|
||||||
|
<!-- ============ MEASUREMENTS & SPECS ============ -->
|
||||||
|
<page string="Measurements" name="measurements">
|
||||||
|
|
||||||
|
<!-- Rollator Measurements -->
|
||||||
|
<group string="Rollator Measurements"
|
||||||
|
invisible="equipment_type != 'rollator'">
|
||||||
|
<group>
|
||||||
|
<field name="rollator_handle_height"/>
|
||||||
|
<field name="rollator_seat_height"/>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<field name="rollator_addons" placeholder="e.g. Basket, Tray, Backrest pad..."/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
|
||||||
|
<!-- Wheelchair / Powerchair Measurements -->
|
||||||
|
<group string="Seat Measurements"
|
||||||
|
invisible="equipment_type not in ['wheelchair', 'powerchair']">
|
||||||
|
<group>
|
||||||
<field name="seat_width"/>
|
<field name="seat_width"/>
|
||||||
<field name="seat_depth"/>
|
<field name="seat_depth"/>
|
||||||
<field name="seat_to_floor_height"/>
|
<field name="seat_to_floor_height"/>
|
||||||
<field name="seat_angle"/>
|
<field name="seat_angle"/>
|
||||||
</group>
|
</group>
|
||||||
<group string="Back & Arms">
|
<group>
|
||||||
<field name="back_height"/>
|
<field name="back_height"/>
|
||||||
<field name="back_angle"/>
|
<field name="back_angle"/>
|
||||||
<field name="armrest_height"/>
|
<field name="armrest_height"/>
|
||||||
<field name="footrest_length"/>
|
|
||||||
</group>
|
</group>
|
||||||
</group>
|
</group>
|
||||||
<group>
|
|
||||||
<group string="Overall Dimensions">
|
<group string="Leg & Foot"
|
||||||
|
invisible="equipment_type not in ['wheelchair', 'powerchair']">
|
||||||
|
<group>
|
||||||
|
<field name="footrest_length"/>
|
||||||
|
<field name="legrest_length"/>
|
||||||
|
<field name="cane_height"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
|
||||||
|
<group string="Overall Dimensions"
|
||||||
|
invisible="equipment_type not in ['wheelchair', 'powerchair']">
|
||||||
|
<group>
|
||||||
<field name="overall_width"/>
|
<field name="overall_width"/>
|
||||||
<field name="overall_length"/>
|
<field name="overall_length"/>
|
||||||
<field name="overall_height"/>
|
<field name="overall_height"/>
|
||||||
</group>
|
</group>
|
||||||
<group string="Client Measurements">
|
</group>
|
||||||
<field name="client_weight"/>
|
|
||||||
<field name="client_height"/>
|
</page>
|
||||||
|
|
||||||
|
<!-- ============ OPTIONS & ACCESSORIES ============ -->
|
||||||
|
<page string="Options" name="options" invisible="equipment_type not in ['wheelchair', 'powerchair']">
|
||||||
|
|
||||||
|
<group invisible="equipment_type != 'wheelchair'">
|
||||||
|
<group string="Frame Options">
|
||||||
|
<field name="frame_options" nolabel="1"
|
||||||
|
placeholder="e.g. Recliner Option, Dynamic Tilt Frame, Titanium Frame"/>
|
||||||
|
</group>
|
||||||
|
<group string="Wheel Options">
|
||||||
|
<field name="wheel_options" nolabel="1"
|
||||||
|
placeholder="e.g. Quick Release Axle, Mag Wheels, Anti-Tip..."/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
|
||||||
|
<group invisible="equipment_type != 'wheelchair'">
|
||||||
|
<group string="Legrest Accessories">
|
||||||
|
<field name="legrest_options" nolabel="1"
|
||||||
|
placeholder="e.g. Elevating Legrest, Swing Away..."/>
|
||||||
|
</group>
|
||||||
|
<group string="Additional ADP Options">
|
||||||
|
<field name="additional_adp_options" nolabel="1"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
|
||||||
|
<group invisible="equipment_type != 'powerchair'">
|
||||||
|
<group string="Powerchair Options">
|
||||||
|
<field name="powerchair_options" nolabel="1"/>
|
||||||
|
</group>
|
||||||
|
<group string="Specialty Controls">
|
||||||
|
<field name="specialty_controls" nolabel="1"
|
||||||
|
placeholder="Rationale required for specialty components"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
|
||||||
|
<group>
|
||||||
|
<group string="Seating">
|
||||||
|
<field name="seatbelt_type"/>
|
||||||
|
<field name="cushion_info"/>
|
||||||
|
<field name="backrest_info"/>
|
||||||
|
</group>
|
||||||
|
<group string="Additional Customization">
|
||||||
|
<field name="additional_customization" nolabel="1"
|
||||||
|
placeholder="Free-form notes for any customization..."/>
|
||||||
</group>
|
</group>
|
||||||
</group>
|
</group>
|
||||||
</page>
|
</page>
|
||||||
|
|
||||||
|
<!-- ============ PRODUCT TYPES ============ -->
|
||||||
<page string="Product Types" name="products">
|
<page string="Product Types" name="products">
|
||||||
<group>
|
<group>
|
||||||
<group>
|
<group string="Cushion">
|
||||||
<field name="cushion_type"/>
|
<field name="cushion_type"/>
|
||||||
<field name="cushion_notes"/>
|
<field name="cushion_notes" placeholder="Cushion details..."
|
||||||
|
invisible="not cushion_type"/>
|
||||||
|
</group>
|
||||||
|
<group string="Backrest">
|
||||||
<field name="backrest_type"/>
|
<field name="backrest_type"/>
|
||||||
<field name="backrest_notes"/>
|
<field name="backrest_notes" placeholder="Backrest details..."
|
||||||
|
invisible="not backrest_type"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<group string="Frame">
|
||||||
|
<field name="frame_type"/>
|
||||||
|
<field name="frame_notes" placeholder="Frame details..."
|
||||||
|
invisible="not frame_type"/>
|
||||||
|
</group>
|
||||||
|
<group string="Wheels">
|
||||||
|
<field name="wheel_type"/>
|
||||||
|
<field name="wheel_notes" placeholder="Wheel details..."
|
||||||
|
invisible="not wheel_type"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
</page>
|
||||||
|
|
||||||
|
<!-- ============ CLINICAL NOTES ============ -->
|
||||||
|
<page string="Clinical Notes" name="needs">
|
||||||
|
<group>
|
||||||
|
<group>
|
||||||
|
<field name="diagnosis" placeholder="Relevant medical diagnosis or conditions..."/>
|
||||||
</group>
|
</group>
|
||||||
<group>
|
<group>
|
||||||
<field name="frame_type"/>
|
<field name="mobility_notes" placeholder="Document mobility needs and challenges..."/>
|
||||||
<field name="frame_notes"/>
|
</group>
|
||||||
<field name="wheel_type"/>
|
</group>
|
||||||
<field name="wheel_notes"/>
|
<group>
|
||||||
|
<group>
|
||||||
|
<field name="accessibility_notes" placeholder="Accessibility requirements and home environment..."/>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<field name="special_requirements" placeholder="Any special requirements or customizations..."/>
|
||||||
</group>
|
</group>
|
||||||
</group>
|
</group>
|
||||||
</page>
|
</page>
|
||||||
|
|
||||||
<page string="Needs & Requirements" name="needs">
|
<!-- ============ KEY DATES ============ -->
|
||||||
|
<page string="Dates" name="dates">
|
||||||
<group>
|
<group>
|
||||||
<field name="diagnosis"/>
|
<group string="Assessment Period">
|
||||||
<field name="mobility_notes"/>
|
<field name="assessment_start_date"/>
|
||||||
<field name="accessibility_notes"/>
|
<field name="assessment_end_date"/>
|
||||||
<field name="special_requirements"/>
|
</group>
|
||||||
|
<group string="Authorization">
|
||||||
|
<field name="claim_authorization_date"/>
|
||||||
|
</group>
|
||||||
</group>
|
</group>
|
||||||
</page>
|
</page>
|
||||||
|
|
||||||
|
<!-- ============ CONSENT & DECLARATION (PAGE 11) ============ -->
|
||||||
|
<page string="Consent & Declaration" name="consent">
|
||||||
|
<group>
|
||||||
|
<group string="Consent Details">
|
||||||
|
<field name="consent_signed_by"/>
|
||||||
|
<field name="consent_declaration_accepted"/>
|
||||||
|
<field name="consent_date"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
|
||||||
|
<group string="Agent Details"
|
||||||
|
invisible="consent_signed_by != 'agent'">
|
||||||
|
<group>
|
||||||
|
<field name="agent_relationship"/>
|
||||||
|
<field name="agent_first_name"/>
|
||||||
|
<field name="agent_middle_initial"/>
|
||||||
|
<field name="agent_last_name"/>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<field name="agent_street_number"/>
|
||||||
|
<field name="agent_street_name"/>
|
||||||
|
<field name="agent_unit"/>
|
||||||
|
<field name="agent_city"/>
|
||||||
|
<field name="agent_province"/>
|
||||||
|
<field name="agent_postal_code"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
<group string="Agent Contact"
|
||||||
|
invisible="consent_signed_by != 'agent'">
|
||||||
|
<group>
|
||||||
|
<field name="agent_home_phone"/>
|
||||||
|
<field name="agent_business_phone"/>
|
||||||
|
<field name="agent_phone_ext"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
</page>
|
||||||
|
|
||||||
|
<!-- ============ SIGNATURES ============ -->
|
||||||
<page string="Signatures" name="signatures">
|
<page string="Signatures" name="signatures">
|
||||||
<group>
|
<group>
|
||||||
<group string="Page 11 - Authorizer Signature">
|
<group string="Page 11 - Authorizer Signature">
|
||||||
@@ -158,13 +355,17 @@
|
|||||||
</group>
|
</group>
|
||||||
</group>
|
</group>
|
||||||
<group>
|
<group>
|
||||||
<field name="signatures_complete"/>
|
<field name="signatures_complete" readonly="1"/>
|
||||||
|
<field name="signed_page_11_pdf" filename="signed_page_11_pdf_filename"
|
||||||
|
invisible="not signed_page_11_pdf"/>
|
||||||
|
<field name="signed_page_11_pdf_filename" invisible="1"/>
|
||||||
</group>
|
</group>
|
||||||
</page>
|
</page>
|
||||||
|
|
||||||
|
<!-- ============ DOCUMENTS ============ -->
|
||||||
<page string="Documents" name="documents">
|
<page string="Documents" name="documents">
|
||||||
<field name="document_ids">
|
<field name="document_ids">
|
||||||
<list string="Documents">
|
<list string="Documents" editable="bottom">
|
||||||
<field name="document_type"/>
|
<field name="document_type"/>
|
||||||
<field name="filename"/>
|
<field name="filename"/>
|
||||||
<field name="revision"/>
|
<field name="revision"/>
|
||||||
@@ -174,26 +375,20 @@
|
|||||||
</field>
|
</field>
|
||||||
</page>
|
</page>
|
||||||
|
|
||||||
|
<!-- ============ COMMENTS ============ -->
|
||||||
<page string="Comments" name="comments">
|
<page string="Comments" name="comments">
|
||||||
<field name="comment_ids">
|
<field name="comment_ids">
|
||||||
<list string="Comments">
|
<list string="Comments" editable="bottom">
|
||||||
<field name="create_date"/>
|
<field name="create_date" string="Date"/>
|
||||||
<field name="author_id"/>
|
<field name="author_id"/>
|
||||||
<field name="comment"/>
|
<field name="comment"/>
|
||||||
</list>
|
</list>
|
||||||
</field>
|
</field>
|
||||||
</page>
|
</page>
|
||||||
</notebook>
|
|
||||||
|
|
||||||
<group invisible="not sale_order_id">
|
</notebook>
|
||||||
<field name="sale_order_id"/>
|
|
||||||
</group>
|
|
||||||
</sheet>
|
</sheet>
|
||||||
<div class="oe_chatter">
|
<chatter/>
|
||||||
<field name="message_follower_ids"/>
|
|
||||||
<field name="activity_ids"/>
|
|
||||||
<field name="message_ids"/>
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
@@ -207,8 +402,10 @@
|
|||||||
<field name="reference"/>
|
<field name="reference"/>
|
||||||
<field name="client_name"/>
|
<field name="client_name"/>
|
||||||
<field name="client_email"/>
|
<field name="client_email"/>
|
||||||
|
<field name="client_health_card"/>
|
||||||
<field name="sales_rep_id"/>
|
<field name="sales_rep_id"/>
|
||||||
<field name="authorizer_id"/>
|
<field name="authorizer_id"/>
|
||||||
|
<field name="sale_order_id"/>
|
||||||
<separator/>
|
<separator/>
|
||||||
<filter string="In Progress" name="draft" domain="[('state', '=', 'draft')]"/>
|
<filter string="In Progress" name="draft" domain="[('state', '=', 'draft')]"/>
|
||||||
<filter string="Pending Signature" name="pending" domain="[('state', '=', 'pending_signature')]"/>
|
<filter string="Pending Signature" name="pending" domain="[('state', '=', 'pending_signature')]"/>
|
||||||
@@ -216,11 +413,19 @@
|
|||||||
<filter string="Cancelled" name="cancelled" domain="[('state', '=', 'cancelled')]"/>
|
<filter string="Cancelled" name="cancelled" domain="[('state', '=', 'cancelled')]"/>
|
||||||
<separator/>
|
<separator/>
|
||||||
<filter string="My Assessments" name="my_assessments" domain="[('sales_rep_id', '=', uid)]"/>
|
<filter string="My Assessments" name="my_assessments" domain="[('sales_rep_id', '=', uid)]"/>
|
||||||
|
<filter string="Has Sale Order" name="has_so" domain="[('sale_order_id', '!=', False)]"/>
|
||||||
|
<filter string="Signatures Pending" name="sigs_pending" domain="[('signatures_complete', '=', False), ('state', '!=', 'cancelled')]"/>
|
||||||
|
<separator/>
|
||||||
|
<filter string="Wheelchair" name="filter_wheelchair" domain="[('equipment_type', '=', 'wheelchair')]"/>
|
||||||
|
<filter string="Powerchair" name="filter_powerchair" domain="[('equipment_type', '=', 'powerchair')]"/>
|
||||||
|
<filter string="Rollator" name="filter_rollator" domain="[('equipment_type', '=', 'rollator')]"/>
|
||||||
<separator/>
|
<separator/>
|
||||||
<filter string="Status" name="group_state" context="{'group_by': 'state'}"/>
|
<filter string="Status" name="group_state" context="{'group_by': 'state'}"/>
|
||||||
|
<filter string="Equipment Type" name="group_equipment" context="{'group_by': 'equipment_type'}"/>
|
||||||
|
<filter string="Client Type" name="group_client_type" context="{'group_by': 'client_type'}"/>
|
||||||
<filter string="Sales Rep" name="group_sales_rep" context="{'group_by': 'sales_rep_id'}"/>
|
<filter string="Sales Rep" name="group_sales_rep" context="{'group_by': 'sales_rep_id'}"/>
|
||||||
<filter string="Authorizer" name="group_authorizer" context="{'group_by': 'authorizer_id'}"/>
|
<filter string="Authorizer" name="group_authorizer" context="{'group_by': 'authorizer_id'}"/>
|
||||||
<filter string="Date" name="group_date" context="{'group_by': 'assessment_date:month'}"/>
|
<filter string="Month" name="group_date" context="{'group_by': 'assessment_date:month'}"/>
|
||||||
</search>
|
</search>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
@@ -237,8 +442,8 @@
|
|||||||
Create your first assessment
|
Create your first assessment
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Assessments are used to record wheelchair specifications and client needs.
|
Assessments record wheelchair, powerchair, and rollator specifications
|
||||||
Once completed, they will create a draft sale order for review.
|
along with client needs. Once completed, a draft sale order is created.
|
||||||
</p>
|
</p>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|||||||
25
fusion_authorizer_portal/views/loaner_checkout_views.xml
Normal file
25
fusion_authorizer_portal/views/loaner_checkout_views.xml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<!-- Add Assessment button and field to Loaner Checkout form -->
|
||||||
|
<record id="view_fusion_loaner_checkout_form_assessment" model="ir.ui.view">
|
||||||
|
<field name="name">fusion.loaner.checkout.form.assessment</field>
|
||||||
|
<field name="model">fusion.loaner.checkout</field>
|
||||||
|
<field name="inherit_id" ref="fusion_claims.view_fusion_loaner_checkout_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//button[@name='action_view_partner']" position="before">
|
||||||
|
<button name="action_view_assessment" type="object"
|
||||||
|
class="oe_stat_button" icon="fa-clipboard"
|
||||||
|
invisible="not assessment_id">
|
||||||
|
<div class="o_field_widget o_stat_info">
|
||||||
|
<span class="o_stat_text">Assessment</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//field[@name='sale_order_id']" position="after">
|
||||||
|
<field name="assessment_id"/>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
320
fusion_authorizer_portal/views/portal_repair_form.xml
Normal file
320
fusion_authorizer_portal/views/portal_repair_form.xml
Normal file
@@ -0,0 +1,320 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<template id="portal_ltc_repair_form"
|
||||||
|
name="LTC Repair Form">
|
||||||
|
<t t-call="website.layout">
|
||||||
|
<div id="wrap" class="oe_structure">
|
||||||
|
<section class="container py-5">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<div class="text-center mb-4">
|
||||||
|
<h1>LTC Repairs Request</h1>
|
||||||
|
<p class="lead text-muted">
|
||||||
|
Submit a repair request for medical equipment at your facility.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<t t-if="request.params.get('error') == 'facility'">
|
||||||
|
<div class="alert alert-danger">Please select a facility.</div>
|
||||||
|
</t>
|
||||||
|
<t t-if="request.params.get('error') == 'name'">
|
||||||
|
<div class="alert alert-danger">Patient name is required.</div>
|
||||||
|
</t>
|
||||||
|
<t t-if="request.params.get('error') == 'description'">
|
||||||
|
<div class="alert alert-danger">Issue description is required.</div>
|
||||||
|
</t>
|
||||||
|
<t t-if="request.params.get('error') == 'photos'">
|
||||||
|
<div class="alert alert-danger">At least one before photo is required.</div>
|
||||||
|
</t>
|
||||||
|
<t t-if="request.params.get('error') == 'server'">
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
An error occurred. Please try again or contact us.
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
|
||||||
|
<form action="/repair-form/submit" method="POST"
|
||||||
|
enctype="multipart/form-data"
|
||||||
|
class="card shadow-sm">
|
||||||
|
<input type="hidden" name="csrf_token"
|
||||||
|
t-att-value="request.csrf_token()"/>
|
||||||
|
<div class="card-body p-4">
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<div class="form-check">
|
||||||
|
<input type="checkbox" class="form-check-input"
|
||||||
|
id="is_emergency" name="is_emergency"/>
|
||||||
|
<label class="form-check-label fw-bold text-danger"
|
||||||
|
for="is_emergency">
|
||||||
|
Is this an Emergency Repair Request?
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<small class="text-muted">
|
||||||
|
Emergency visits may be chargeable at an extra rate.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="facility_id" class="form-label fw-bold">
|
||||||
|
Facility Location *
|
||||||
|
</label>
|
||||||
|
<select name="facility_id" id="facility_id"
|
||||||
|
class="form-select" required="required">
|
||||||
|
<option value="">-- Select Facility --</option>
|
||||||
|
<t t-foreach="facilities" t-as="fac">
|
||||||
|
<option t-att-value="fac.id">
|
||||||
|
<t t-esc="fac.name"/>
|
||||||
|
</option>
|
||||||
|
</t>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="client_name" class="form-label fw-bold">
|
||||||
|
Patient Name *
|
||||||
|
</label>
|
||||||
|
<input type="text" name="client_name" id="client_name"
|
||||||
|
class="form-control" required="required"
|
||||||
|
placeholder="Enter patient name"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="room_number" class="form-label fw-bold">
|
||||||
|
Room Number *
|
||||||
|
</label>
|
||||||
|
<input type="text" name="room_number" id="room_number"
|
||||||
|
class="form-control" required="required"
|
||||||
|
placeholder="e.g. 305"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="issue_description" class="form-label fw-bold">
|
||||||
|
Describe the Issue *
|
||||||
|
</label>
|
||||||
|
<textarea name="issue_description" id="issue_description"
|
||||||
|
class="form-control" rows="4"
|
||||||
|
required="required"
|
||||||
|
placeholder="Please provide as much detail as possible about the issue."/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="issue_reported_date" class="form-label fw-bold">
|
||||||
|
Issue Reported Date *
|
||||||
|
</label>
|
||||||
|
<input type="date" name="issue_reported_date"
|
||||||
|
id="issue_reported_date"
|
||||||
|
class="form-control" required="required"
|
||||||
|
t-att-value="today"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="product_serial" class="form-label fw-bold">
|
||||||
|
Product Serial # *
|
||||||
|
</label>
|
||||||
|
<input type="text" name="product_serial"
|
||||||
|
id="product_serial"
|
||||||
|
class="form-control" required="required"
|
||||||
|
placeholder="Serial number is required for repairs"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="before_photos" class="form-label fw-bold">
|
||||||
|
Before Photos (Reported Condition) *
|
||||||
|
</label>
|
||||||
|
<input type="file" name="before_photos" id="before_photos"
|
||||||
|
class="form-control" multiple="multiple"
|
||||||
|
accept="image/*" required="required"/>
|
||||||
|
<small class="text-muted">
|
||||||
|
At least 1 photo required. Up to 4 photos (max 10MB each).
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<h5>Family / POA Contact</h5>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label for="poa_name" class="form-label">
|
||||||
|
Relative/POA Name
|
||||||
|
</label>
|
||||||
|
<input type="text" name="poa_name" id="poa_name"
|
||||||
|
class="form-control"
|
||||||
|
placeholder="Contact name"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label for="poa_phone" class="form-label">
|
||||||
|
Relative/POA Phone
|
||||||
|
</label>
|
||||||
|
<input type="tel" name="poa_phone" id="poa_phone"
|
||||||
|
class="form-control"
|
||||||
|
placeholder="Phone number"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<t t-if="is_technician">
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<div class="bg-light p-3 rounded mb-3">
|
||||||
|
<p class="fw-bold text-muted mb-2">
|
||||||
|
FOR TECHNICIAN USE ONLY
|
||||||
|
</p>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">
|
||||||
|
Has the issue been resolved?
|
||||||
|
</label>
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<input type="radio" name="resolved" value="yes"
|
||||||
|
class="form-check-input" id="resolved_yes"/>
|
||||||
|
<label class="form-check-label"
|
||||||
|
for="resolved_yes">Yes</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<input type="radio" name="resolved" value="no"
|
||||||
|
class="form-check-input" id="resolved_no"
|
||||||
|
checked="checked"/>
|
||||||
|
<label class="form-check-label"
|
||||||
|
for="resolved_no">No</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3" id="resolution_section"
|
||||||
|
style="display: none;">
|
||||||
|
<label for="resolution_description"
|
||||||
|
class="form-label">
|
||||||
|
Describe the Solution
|
||||||
|
</label>
|
||||||
|
<textarea name="resolution_description"
|
||||||
|
id="resolution_description"
|
||||||
|
class="form-control" rows="3"
|
||||||
|
placeholder="How was the issue resolved?"/>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="after_photos" class="form-label fw-bold">
|
||||||
|
After Photos (Completed Repair)
|
||||||
|
</label>
|
||||||
|
<input type="file" name="after_photos" id="after_photos"
|
||||||
|
class="form-control" multiple="multiple"
|
||||||
|
accept="image/*"/>
|
||||||
|
<small class="text-muted">
|
||||||
|
Optional. Attach after repair is completed. Up to 4 photos (max 10MB each).
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
|
||||||
|
<div class="text-center mt-4">
|
||||||
|
<button type="submit" class="btn btn-primary btn-lg px-5">
|
||||||
|
Submit Repair Request
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
var section = document.getElementById('resolution_section');
|
||||||
|
if (!section) return;
|
||||||
|
var radios = document.querySelectorAll('input[name="resolved"]');
|
||||||
|
radios.forEach(function(r) {
|
||||||
|
r.addEventListener('change', function() {
|
||||||
|
section.style.display = this.value === 'yes' ? 'block' : 'none';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</t>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template id="portal_ltc_repair_thank_you"
|
||||||
|
name="Repair Request Submitted">
|
||||||
|
<t t-call="website.layout">
|
||||||
|
<div id="wrap" class="oe_structure">
|
||||||
|
<section class="container py-5">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-lg-6 text-center">
|
||||||
|
<div class="mb-4">
|
||||||
|
<i class="fa fa-check-circle text-success"
|
||||||
|
style="font-size: 4rem;"/>
|
||||||
|
</div>
|
||||||
|
<h2>Thank You!</h2>
|
||||||
|
<p class="lead text-muted">
|
||||||
|
Your repair request has been submitted successfully.
|
||||||
|
</p>
|
||||||
|
<div class="card mt-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<p><strong>Reference:</strong>
|
||||||
|
<t t-esc="repair.name"/></p>
|
||||||
|
<p><strong>Facility:</strong>
|
||||||
|
<t t-esc="repair.facility_id.name"/></p>
|
||||||
|
<p><strong>Patient:</strong>
|
||||||
|
<t t-esc="repair.display_client_name"/></p>
|
||||||
|
<p><strong>Room:</strong>
|
||||||
|
<t t-esc="repair.room_number"/></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<a href="/repair-form" class="btn btn-outline-primary mt-4">
|
||||||
|
Submit Another Request
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template id="portal_ltc_repair_password"
|
||||||
|
name="LTC Repair Form - Password">
|
||||||
|
<t t-call="website.layout">
|
||||||
|
<div id="wrap" class="oe_structure">
|
||||||
|
<section class="container py-5">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-lg-5">
|
||||||
|
<div class="card shadow-sm">
|
||||||
|
<div class="card-body p-4 text-center">
|
||||||
|
<div class="mb-3">
|
||||||
|
<i class="fa fa-lock text-primary"
|
||||||
|
style="font-size: 3rem;"/>
|
||||||
|
</div>
|
||||||
|
<h3>LTC Repairs Request</h3>
|
||||||
|
<p class="text-muted">
|
||||||
|
Please enter the access password to continue.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<t t-if="error">
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
Incorrect password. Please try again.
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
|
||||||
|
<form action="/repair-form/auth" method="POST"
|
||||||
|
class="mt-3">
|
||||||
|
<input type="hidden" name="csrf_token"
|
||||||
|
t-att-value="request.csrf_token()"/>
|
||||||
|
<div class="mb-3">
|
||||||
|
<input type="password" name="password"
|
||||||
|
class="form-control form-control-lg text-center"
|
||||||
|
placeholder="Enter password"
|
||||||
|
minlength="4" required="required"
|
||||||
|
autofocus="autofocus"/>
|
||||||
|
</div>
|
||||||
|
<button type="submit"
|
||||||
|
class="btn btn-primary btn-lg w-100">
|
||||||
|
Access Form
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
@@ -171,12 +171,12 @@
|
|||||||
|
|
||||||
<!-- Quick Links -->
|
<!-- Quick Links -->
|
||||||
<div class="row g-2 mb-4">
|
<div class="row g-2 mb-4">
|
||||||
<div class="col-6">
|
<div class="col-4">
|
||||||
<a href="/my/technician/tasks" class="btn btn-outline-primary w-100 py-3">
|
<a href="/my/technician/tasks" class="btn btn-outline-primary w-100 py-3">
|
||||||
<i class="fa fa-list me-1"/>All Tasks
|
<i class="fa fa-list me-1"/>All Tasks
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6">
|
<div class="col-4">
|
||||||
<a href="/my/technician/tomorrow" class="btn btn-outline-secondary w-100 py-3">
|
<a href="/my/technician/tomorrow" class="btn btn-outline-secondary w-100 py-3">
|
||||||
<i class="fa fa-calendar me-1"/>Tomorrow
|
<i class="fa fa-calendar me-1"/>Tomorrow
|
||||||
<t t-if="tomorrow_count">
|
<t t-if="tomorrow_count">
|
||||||
@@ -184,6 +184,11 @@
|
|||||||
</t>
|
</t>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-4">
|
||||||
|
<a href="/repair-form" class="btn btn-outline-warning w-100 py-3">
|
||||||
|
<i class="fa fa-wrench me-1"/>Repair Form
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- My Start Location -->
|
<!-- My Start Location -->
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
{
|
{
|
||||||
'name': 'Fusion Claims',
|
'name': 'Fusion Claims',
|
||||||
'version': '19.0.5.1.0',
|
'version': '19.0.6.0.0',
|
||||||
'category': 'Sales',
|
'category': 'Sales',
|
||||||
'summary': 'Complete ADP Claims Management with Dashboard, Sales Integration, Billing Automation, and Two-Stage Verification.',
|
'summary': 'Complete ADP Claims Management with Dashboard, Sales Integration, Billing Automation, and Two-Stage Verification.',
|
||||||
'description': """
|
'description': """
|
||||||
@@ -127,11 +127,16 @@
|
|||||||
'wizard/odsp_submit_to_odsp_wizard_views.xml',
|
'wizard/odsp_submit_to_odsp_wizard_views.xml',
|
||||||
'wizard/odsp_pre_approved_wizard_views.xml',
|
'wizard/odsp_pre_approved_wizard_views.xml',
|
||||||
'wizard/odsp_ready_delivery_wizard_views.xml',
|
'wizard/odsp_ready_delivery_wizard_views.xml',
|
||||||
|
'wizard/ltc_repair_create_so_wizard_views.xml',
|
||||||
'views/res_partner_views.xml',
|
'views/res_partner_views.xml',
|
||||||
'views/pdf_template_inherit_views.xml',
|
'views/pdf_template_inherit_views.xml',
|
||||||
'views/dashboard_views.xml',
|
'views/dashboard_views.xml',
|
||||||
'views/client_profile_views.xml',
|
'views/client_profile_views.xml',
|
||||||
'wizard/xml_import_wizard_views.xml',
|
'wizard/xml_import_wizard_views.xml',
|
||||||
|
'views/ltc_facility_views.xml',
|
||||||
|
'views/ltc_repair_views.xml',
|
||||||
|
'views/ltc_cleanup_views.xml',
|
||||||
|
'views/ltc_form_submission_views.xml',
|
||||||
'views/adp_claims_views.xml',
|
'views/adp_claims_views.xml',
|
||||||
'views/submission_history_views.xml',
|
'views/submission_history_views.xml',
|
||||||
'views/fusion_loaner_views.xml',
|
'views/fusion_loaner_views.xml',
|
||||||
@@ -142,6 +147,7 @@
|
|||||||
'report/report_templates.xml',
|
'report/report_templates.xml',
|
||||||
'report/sale_report_portrait.xml',
|
'report/sale_report_portrait.xml',
|
||||||
'report/sale_report_landscape.xml',
|
'report/sale_report_landscape.xml',
|
||||||
|
'report/sale_report_ltc_repair.xml',
|
||||||
'report/invoice_report_portrait.xml',
|
'report/invoice_report_portrait.xml',
|
||||||
'report/invoice_report_landscape.xml',
|
'report/invoice_report_landscape.xml',
|
||||||
'report/report_proof_of_delivery.xml',
|
'report/report_proof_of_delivery.xml',
|
||||||
@@ -152,6 +158,9 @@
|
|||||||
'report/report_accessibility_contract.xml',
|
'report/report_accessibility_contract.xml',
|
||||||
'report/report_mod_quotation.xml',
|
'report/report_mod_quotation.xml',
|
||||||
'report/report_mod_invoice.xml',
|
'report/report_mod_invoice.xml',
|
||||||
|
'data/ltc_data.xml',
|
||||||
|
'report/report_ltc_nursing_station.xml',
|
||||||
|
'data/ltc_report_data.xml',
|
||||||
'data/mail_template_data.xml',
|
'data/mail_template_data.xml',
|
||||||
'data/ai_agent_data.xml',
|
'data/ai_agent_data.xml',
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -153,5 +153,11 @@
|
|||||||
<field name="value"></field>
|
<field name="value"></field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
<!-- LTC Portal Form Password -->
|
||||||
|
<record id="config_ltc_form_password" model="ir.config_parameter">
|
||||||
|
<field name="key">fusion_claims.ltc_form_password</field>
|
||||||
|
<field name="value"></field>
|
||||||
|
</record>
|
||||||
|
|
||||||
</data>
|
</data>
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|||||||
103
fusion_claims/data/ltc_data.xml
Normal file
103
fusion_claims/data/ltc_data.xml
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<data noupdate="1">
|
||||||
|
|
||||||
|
<!-- ================================================================== -->
|
||||||
|
<!-- SEQUENCES -->
|
||||||
|
<!-- ================================================================== -->
|
||||||
|
<record id="seq_ltc_facility" model="ir.sequence">
|
||||||
|
<field name="name">LTC Facility</field>
|
||||||
|
<field name="code">fusion.ltc.facility</field>
|
||||||
|
<field name="prefix">LTC-</field>
|
||||||
|
<field name="padding">4</field>
|
||||||
|
<field name="company_id" eval="False"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="seq_ltc_repair" model="ir.sequence">
|
||||||
|
<field name="name">LTC Repair</field>
|
||||||
|
<field name="code">fusion.ltc.repair</field>
|
||||||
|
<field name="prefix">LTC-RPR-</field>
|
||||||
|
<field name="padding">5</field>
|
||||||
|
<field name="company_id" eval="False"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="seq_ltc_cleanup" model="ir.sequence">
|
||||||
|
<field name="name">LTC Cleanup</field>
|
||||||
|
<field name="code">fusion.ltc.cleanup</field>
|
||||||
|
<field name="prefix">LTC-CLN-</field>
|
||||||
|
<field name="padding">5</field>
|
||||||
|
<field name="company_id" eval="False"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- ================================================================== -->
|
||||||
|
<!-- DEFAULT LTC REPAIR SERVICE PRODUCT -->
|
||||||
|
<!-- ================================================================== -->
|
||||||
|
<record id="product_ltc_repair_service" model="product.template">
|
||||||
|
<field name="name">REPAIRS AT LTC HOME</field>
|
||||||
|
<field name="default_code">LTC-REPAIR</field>
|
||||||
|
<field name="type">service</field>
|
||||||
|
<field name="list_price">0.00</field>
|
||||||
|
<field name="sale_ok" eval="True"/>
|
||||||
|
<field name="purchase_ok" eval="False"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- ================================================================== -->
|
||||||
|
<!-- DEFAULT REPAIR STAGES -->
|
||||||
|
<!-- ================================================================== -->
|
||||||
|
<record id="ltc_repair_stage_new" model="fusion.ltc.repair.stage">
|
||||||
|
<field name="name">New</field>
|
||||||
|
<field name="sequence">1</field>
|
||||||
|
<field name="fold">False</field>
|
||||||
|
<field name="description">Newly submitted repair requests awaiting review.</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="ltc_repair_stage_in_review" model="fusion.ltc.repair.stage">
|
||||||
|
<field name="name">In Review</field>
|
||||||
|
<field name="sequence">2</field>
|
||||||
|
<field name="fold">False</field>
|
||||||
|
<field name="description">Under review by office staff before assignment.</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="ltc_repair_stage_in_progress" model="fusion.ltc.repair.stage">
|
||||||
|
<field name="name">In Progress</field>
|
||||||
|
<field name="sequence">3</field>
|
||||||
|
<field name="fold">False</field>
|
||||||
|
<field name="description">Technician has been assigned and repair is underway.</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="ltc_repair_stage_completed" model="fusion.ltc.repair.stage">
|
||||||
|
<field name="name">Completed</field>
|
||||||
|
<field name="sequence">4</field>
|
||||||
|
<field name="fold">True</field>
|
||||||
|
<field name="description">Repair has been completed by the technician.</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="ltc_repair_stage_invoiced" model="fusion.ltc.repair.stage">
|
||||||
|
<field name="name">Invoiced</field>
|
||||||
|
<field name="sequence">5</field>
|
||||||
|
<field name="fold">True</field>
|
||||||
|
<field name="description">Sale order created and invoiced for this repair.</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="ltc_repair_stage_declined" model="fusion.ltc.repair.stage">
|
||||||
|
<field name="name">Declined/No Response</field>
|
||||||
|
<field name="sequence">6</field>
|
||||||
|
<field name="fold">True</field>
|
||||||
|
<field name="description">Repair was declined or no response was received.</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- ================================================================== -->
|
||||||
|
<!-- CRON: Cleanup scheduling -->
|
||||||
|
<!-- ================================================================== -->
|
||||||
|
<record id="ir_cron_ltc_cleanup_schedule" model="ir.cron">
|
||||||
|
<field name="name">LTC: Auto-Schedule Cleanups</field>
|
||||||
|
<field name="model_id" ref="model_fusion_ltc_cleanup"/>
|
||||||
|
<field name="state">code</field>
|
||||||
|
<field name="code">model._cron_schedule_cleanups()</field>
|
||||||
|
<field name="interval_number">1</field>
|
||||||
|
<field name="interval_type">days</field>
|
||||||
|
<field name="active">True</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
42
fusion_claims/data/ltc_report_data.xml
Normal file
42
fusion_claims/data/ltc_report_data.xml
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<!-- Landscape paper format for nursing station report -->
|
||||||
|
<record id="paperformat_ltc_nursing_station" model="report.paperformat">
|
||||||
|
<field name="name">LTC Nursing Station (Landscape)</field>
|
||||||
|
<field name="default">False</field>
|
||||||
|
<field name="format">Letter</field>
|
||||||
|
<field name="orientation">Landscape</field>
|
||||||
|
<field name="margin_top">20</field>
|
||||||
|
<field name="margin_bottom">15</field>
|
||||||
|
<field name="margin_left">10</field>
|
||||||
|
<field name="margin_right">10</field>
|
||||||
|
<field name="header_line">False</field>
|
||||||
|
<field name="header_spacing">10</field>
|
||||||
|
<field name="dpi">90</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Nursing Station Report action -->
|
||||||
|
<record id="action_report_ltc_nursing_station" model="ir.actions.report">
|
||||||
|
<field name="name">Nursing Station Repair Log</field>
|
||||||
|
<field name="model">fusion.ltc.facility</field>
|
||||||
|
<field name="report_type">qweb-pdf</field>
|
||||||
|
<field name="report_name">fusion_claims.report_ltc_nursing_station_document</field>
|
||||||
|
<field name="report_file">fusion_claims.report_ltc_nursing_station_document</field>
|
||||||
|
<field name="binding_model_id" ref="model_fusion_ltc_facility"/>
|
||||||
|
<field name="binding_type">report</field>
|
||||||
|
<field name="paperformat_id" ref="paperformat_ltc_nursing_station"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Repair Summary Report action -->
|
||||||
|
<record id="action_report_ltc_repairs_summary" model="ir.actions.report">
|
||||||
|
<field name="name">Repair Summary</field>
|
||||||
|
<field name="model">fusion.ltc.facility</field>
|
||||||
|
<field name="report_type">qweb-pdf</field>
|
||||||
|
<field name="report_name">fusion_claims.report_ltc_repairs_summary_document</field>
|
||||||
|
<field name="report_file">fusion_claims.report_ltc_repairs_summary_document</field>
|
||||||
|
<field name="binding_model_id" ref="model_fusion_ltc_facility"/>
|
||||||
|
<field name="binding_type">report</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
@@ -32,3 +32,7 @@ from . import technician_task
|
|||||||
from . import task_sync
|
from . import task_sync
|
||||||
from . import technician_location
|
from . import technician_location
|
||||||
from . import push_subscription
|
from . import push_subscription
|
||||||
|
from . import ltc_facility
|
||||||
|
from . import ltc_repair
|
||||||
|
from . import ltc_cleanup
|
||||||
|
from . import ltc_form_submission
|
||||||
@@ -458,6 +458,30 @@ class FusionLoanerCheckout(models.Model):
|
|||||||
'context': {'default_checkout_id': self.id},
|
'context': {'default_checkout_id': self.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_partner(self):
|
||||||
|
self.ensure_one()
|
||||||
|
if not self.partner_id:
|
||||||
|
return
|
||||||
|
return {
|
||||||
|
'name': self.partner_id.name,
|
||||||
|
'type': 'ir.actions.act_window',
|
||||||
|
'res_model': 'res.partner',
|
||||||
|
'view_mode': 'form',
|
||||||
|
'res_id': self.partner_id.id,
|
||||||
|
}
|
||||||
|
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
# STOCK MOVES
|
# STOCK MOVES
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
|
|||||||
167
fusion_claims/models/ltc_cleanup.py
Normal file
167
fusion_claims/models/ltc_cleanup.py
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2024-2026 Nexa Systems Inc.
|
||||||
|
# License OPL-1 (Odoo Proprietary License v1.0)
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from odoo import models, fields, api, _
|
||||||
|
|
||||||
|
|
||||||
|
class FusionLTCCleanup(models.Model):
|
||||||
|
_name = 'fusion.ltc.cleanup'
|
||||||
|
_description = 'LTC Cleanup Schedule'
|
||||||
|
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||||
|
_order = 'scheduled_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,
|
||||||
|
)
|
||||||
|
scheduled_date = fields.Date(
|
||||||
|
string='Scheduled Date',
|
||||||
|
required=True,
|
||||||
|
tracking=True,
|
||||||
|
)
|
||||||
|
completed_date = fields.Date(
|
||||||
|
string='Completed Date',
|
||||||
|
tracking=True,
|
||||||
|
)
|
||||||
|
state = fields.Selection([
|
||||||
|
('scheduled', 'Scheduled'),
|
||||||
|
('in_progress', 'In Progress'),
|
||||||
|
('completed', 'Completed'),
|
||||||
|
('cancelled', 'Cancelled'),
|
||||||
|
('rescheduled', 'Rescheduled'),
|
||||||
|
], string='Status', default='scheduled', required=True, tracking=True)
|
||||||
|
|
||||||
|
technician_id = fields.Many2one(
|
||||||
|
'res.users',
|
||||||
|
string='Technician',
|
||||||
|
tracking=True,
|
||||||
|
)
|
||||||
|
task_id = fields.Many2one(
|
||||||
|
'fusion.technician.task',
|
||||||
|
string='Field Service Task',
|
||||||
|
)
|
||||||
|
notes = fields.Text(string='Notes')
|
||||||
|
items_cleaned = fields.Integer(string='Items Cleaned')
|
||||||
|
photo_ids = fields.Many2many(
|
||||||
|
'ir.attachment',
|
||||||
|
'ltc_cleanup_photo_rel',
|
||||||
|
'cleanup_id',
|
||||||
|
'attachment_id',
|
||||||
|
string='Photos',
|
||||||
|
)
|
||||||
|
|
||||||
|
@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.cleanup') or _('New')
|
||||||
|
return super().create(vals_list)
|
||||||
|
|
||||||
|
def action_start(self):
|
||||||
|
self.write({'state': 'in_progress'})
|
||||||
|
|
||||||
|
def action_complete(self):
|
||||||
|
self.write({
|
||||||
|
'state': 'completed',
|
||||||
|
'completed_date': fields.Date.context_today(self),
|
||||||
|
})
|
||||||
|
for record in self:
|
||||||
|
record._schedule_next_cleanup()
|
||||||
|
record.message_post(
|
||||||
|
body=_("Cleanup completed. Items cleaned: %s", record.items_cleaned or 0),
|
||||||
|
message_type='comment',
|
||||||
|
)
|
||||||
|
|
||||||
|
def action_cancel(self):
|
||||||
|
self.write({'state': 'cancelled'})
|
||||||
|
|
||||||
|
def action_reschedule(self):
|
||||||
|
self.write({'state': 'rescheduled'})
|
||||||
|
|
||||||
|
def action_reset(self):
|
||||||
|
self.write({'state': 'scheduled'})
|
||||||
|
|
||||||
|
def _schedule_next_cleanup(self):
|
||||||
|
facility = self.facility_id
|
||||||
|
interval = facility._get_cleanup_interval_days()
|
||||||
|
next_date = (self.completed_date or fields.Date.context_today(self)) + timedelta(days=interval)
|
||||||
|
facility.next_cleanup_date = next_date
|
||||||
|
|
||||||
|
next_cleanup = self.env['fusion.ltc.cleanup'].create({
|
||||||
|
'facility_id': facility.id,
|
||||||
|
'scheduled_date': next_date,
|
||||||
|
'technician_id': self.technician_id.id if self.technician_id else False,
|
||||||
|
})
|
||||||
|
|
||||||
|
self.activity_schedule(
|
||||||
|
'mail.mail_activity_data_todo',
|
||||||
|
date_deadline=next_date - timedelta(days=7),
|
||||||
|
summary=_('Upcoming cleanup at %s', facility.name),
|
||||||
|
note=_('Next cleanup is scheduled for %s at %s.', next_date, facility.name),
|
||||||
|
)
|
||||||
|
return next_cleanup
|
||||||
|
|
||||||
|
def action_create_task(self):
|
||||||
|
self.ensure_one()
|
||||||
|
if self.task_id:
|
||||||
|
return {
|
||||||
|
'type': 'ir.actions.act_window',
|
||||||
|
'res_model': 'fusion.technician.task',
|
||||||
|
'view_mode': 'form',
|
||||||
|
'res_id': self.task_id.id,
|
||||||
|
}
|
||||||
|
task = self.env['fusion.technician.task'].create({
|
||||||
|
'task_type': 'ltc_visit',
|
||||||
|
'facility_id': self.facility_id.id,
|
||||||
|
'scheduled_date': self.scheduled_date,
|
||||||
|
'technician_id': self.technician_id.id if self.technician_id else False,
|
||||||
|
'description': _('Cleanup visit at %s', self.facility_id.name),
|
||||||
|
})
|
||||||
|
self.task_id = task.id
|
||||||
|
return {
|
||||||
|
'type': 'ir.actions.act_window',
|
||||||
|
'res_model': 'fusion.technician.task',
|
||||||
|
'view_mode': 'form',
|
||||||
|
'res_id': task.id,
|
||||||
|
}
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _cron_schedule_cleanups(self):
|
||||||
|
today = fields.Date.context_today(self)
|
||||||
|
week_ahead = today + timedelta(days=7)
|
||||||
|
facilities = self.env['fusion.ltc.facility'].search([
|
||||||
|
('active', '=', True),
|
||||||
|
('cleanup_frequency', '!=', False),
|
||||||
|
('next_cleanup_date', '<=', week_ahead),
|
||||||
|
('next_cleanup_date', '>=', today),
|
||||||
|
])
|
||||||
|
for facility in facilities:
|
||||||
|
existing = self.search([
|
||||||
|
('facility_id', '=', facility.id),
|
||||||
|
('scheduled_date', '=', facility.next_cleanup_date),
|
||||||
|
('state', 'not in', ['cancelled', 'rescheduled']),
|
||||||
|
], limit=1)
|
||||||
|
if not existing:
|
||||||
|
cleanup = self.create({
|
||||||
|
'facility_id': facility.id,
|
||||||
|
'scheduled_date': facility.next_cleanup_date,
|
||||||
|
})
|
||||||
|
cleanup.activity_schedule(
|
||||||
|
'mail.mail_activity_data_todo',
|
||||||
|
date_deadline=facility.next_cleanup_date - timedelta(days=3),
|
||||||
|
summary=_('Cleanup scheduled at %s', facility.name),
|
||||||
|
note=_('Cleanup is scheduled for %s.', facility.next_cleanup_date),
|
||||||
|
)
|
||||||
314
fusion_claims/models/ltc_facility.py
Normal file
314
fusion_claims/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')
|
||||||
68
fusion_claims/models/ltc_form_submission.py
Normal file
68
fusion_claims/models/ltc_form_submission.py
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2024-2026 Nexa Systems Inc.
|
||||||
|
# License OPL-1 (Odoo Proprietary License v1.0)
|
||||||
|
|
||||||
|
from odoo import models, fields, api, _
|
||||||
|
|
||||||
|
|
||||||
|
class FusionLTCFormSubmission(models.Model):
|
||||||
|
_name = 'fusion.ltc.form.submission'
|
||||||
|
_description = 'LTC Form Submission'
|
||||||
|
_order = 'submitted_date desc, id desc'
|
||||||
|
|
||||||
|
name = fields.Char(
|
||||||
|
string='Reference',
|
||||||
|
required=True,
|
||||||
|
copy=False,
|
||||||
|
readonly=True,
|
||||||
|
default=lambda self: _('New'),
|
||||||
|
)
|
||||||
|
form_type = fields.Selection([
|
||||||
|
('repair', 'Repair Request'),
|
||||||
|
], string='Form Type', default='repair', required=True, index=True)
|
||||||
|
repair_id = fields.Many2one(
|
||||||
|
'fusion.ltc.repair',
|
||||||
|
string='Repair Request',
|
||||||
|
ondelete='set null',
|
||||||
|
index=True,
|
||||||
|
)
|
||||||
|
facility_id = fields.Many2one(
|
||||||
|
'fusion.ltc.facility',
|
||||||
|
string='Facility',
|
||||||
|
index=True,
|
||||||
|
)
|
||||||
|
client_name = fields.Char(string='Client Name')
|
||||||
|
room_number = fields.Char(string='Room Number')
|
||||||
|
product_serial = fields.Char(string='Product Serial #')
|
||||||
|
is_emergency = fields.Boolean(string='Emergency')
|
||||||
|
submitted_date = fields.Datetime(
|
||||||
|
string='Submitted Date',
|
||||||
|
default=fields.Datetime.now,
|
||||||
|
readonly=True,
|
||||||
|
)
|
||||||
|
ip_address = fields.Char(string='IP Address', readonly=True)
|
||||||
|
status = fields.Selection([
|
||||||
|
('submitted', 'Submitted'),
|
||||||
|
('processed', 'Processed'),
|
||||||
|
('rejected', 'Rejected'),
|
||||||
|
], string='Status', default='submitted', tracking=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.form.submission'
|
||||||
|
) or _('New')
|
||||||
|
return super().create(vals_list)
|
||||||
|
|
||||||
|
def action_view_repair(self):
|
||||||
|
self.ensure_one()
|
||||||
|
if not self.repair_id:
|
||||||
|
return
|
||||||
|
return {
|
||||||
|
'type': 'ir.actions.act_window',
|
||||||
|
'res_model': 'fusion.ltc.repair',
|
||||||
|
'view_mode': 'form',
|
||||||
|
'res_id': self.repair_id.id,
|
||||||
|
}
|
||||||
376
fusion_claims/models/ltc_repair.py
Normal file
376
fusion_claims/models/ltc_repair.py
Normal file
@@ -0,0 +1,376 @@
|
|||||||
|
# -*- 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_claims.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,
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from odoo import models, fields, api
|
from odoo import models, fields, api
|
||||||
|
from odoo.exceptions import ValidationError
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -476,6 +477,16 @@ class ResConfigSettings(models.TransientModel):
|
|||||||
help='Default ODSP office contact for new ODSP cases',
|
help='Default ODSP office contact for new ODSP cases',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# =========================================================================
|
||||||
|
# PORTAL FORMS
|
||||||
|
# =========================================================================
|
||||||
|
|
||||||
|
fc_ltc_form_password = fields.Char(
|
||||||
|
string='LTC Form Access Password',
|
||||||
|
config_parameter='fusion_claims.ltc_form_password',
|
||||||
|
help='Minimum 4 characters. Share with facility staff to access the repair form.',
|
||||||
|
)
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def get_values(self):
|
def get_values(self):
|
||||||
res = super().get_values()
|
res = super().get_values()
|
||||||
@@ -571,6 +582,13 @@ class ResConfigSettings(models.TransientModel):
|
|||||||
# Office notification recipients are stored via related field on res.company
|
# Office notification recipients are stored via related field on res.company
|
||||||
# No need to store in ir.config_parameter
|
# No need to store in ir.config_parameter
|
||||||
|
|
||||||
|
# Validate LTC form password length
|
||||||
|
form_pw = self.fc_ltc_form_password or ''
|
||||||
|
if form_pw and len(form_pw.strip()) < 4:
|
||||||
|
raise ValidationError(
|
||||||
|
'LTC Form Access Password must be at least 4 characters.'
|
||||||
|
)
|
||||||
|
|
||||||
# Store designated vendor signer (Many2one - manual handling)
|
# Store designated vendor signer (Many2one - manual handling)
|
||||||
if self.fc_designated_vendor_signer:
|
if self.fc_designated_vendor_signer:
|
||||||
ICP.set_param('fusion_claims.designated_vendor_signer',
|
ICP.set_param('fusion_claims.designated_vendor_signer',
|
||||||
|
|||||||
@@ -76,6 +76,25 @@ class ResPartner(models.Model):
|
|||||||
store=True,
|
store=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# ==========================================================================
|
||||||
|
# LTC FIELDS
|
||||||
|
# ==========================================================================
|
||||||
|
x_fc_ltc_facility_id = fields.Many2one(
|
||||||
|
'fusion.ltc.facility',
|
||||||
|
string='LTC Home',
|
||||||
|
tracking=True,
|
||||||
|
help='Long-Term Care Home this resident belongs to',
|
||||||
|
)
|
||||||
|
x_fc_ltc_room_number = fields.Char(
|
||||||
|
string='Room Number',
|
||||||
|
tracking=True,
|
||||||
|
)
|
||||||
|
x_fc_ltc_family_contact_ids = fields.One2many(
|
||||||
|
'fusion.ltc.family.contact',
|
||||||
|
'partner_id',
|
||||||
|
string='Family Contacts',
|
||||||
|
)
|
||||||
|
|
||||||
@api.depends('x_fc_contact_type')
|
@api.depends('x_fc_contact_type')
|
||||||
def _compute_is_odsp_office(self):
|
def _compute_is_odsp_office(self):
|
||||||
for partner in self:
|
for partner in self:
|
||||||
|
|||||||
@@ -35,6 +35,22 @@ class SaleOrder(models.Model):
|
|||||||
help='True only for ADP or ADP/ODSP sale types',
|
help='True only for ADP or ADP/ODSP sale types',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# ==========================================================================
|
||||||
|
# LTC REPAIR LINK
|
||||||
|
# ==========================================================================
|
||||||
|
x_fc_ltc_repair_id = fields.Many2one(
|
||||||
|
'fusion.ltc.repair',
|
||||||
|
string='LTC Repair',
|
||||||
|
tracking=True,
|
||||||
|
ondelete='set null',
|
||||||
|
index=True,
|
||||||
|
)
|
||||||
|
x_fc_is_ltc_repair_sale = fields.Boolean(
|
||||||
|
compute='_compute_is_ltc_repair_sale',
|
||||||
|
store=True,
|
||||||
|
string='Is LTC Repair Sale',
|
||||||
|
)
|
||||||
|
|
||||||
# ==========================================================================
|
# ==========================================================================
|
||||||
# INVOICE COUNT FIELDS (Separate ADP and Client invoices)
|
# INVOICE COUNT FIELDS (Separate ADP and Client invoices)
|
||||||
# ==========================================================================
|
# ==========================================================================
|
||||||
@@ -402,6 +418,11 @@ class SaleOrder(models.Model):
|
|||||||
for order in self:
|
for order in self:
|
||||||
order.x_fc_is_adp_sale = order._is_adp_sale()
|
order.x_fc_is_adp_sale = order._is_adp_sale()
|
||||||
|
|
||||||
|
@api.depends('x_fc_ltc_repair_id')
|
||||||
|
def _compute_is_ltc_repair_sale(self):
|
||||||
|
for order in self:
|
||||||
|
order.x_fc_is_ltc_repair_sale = bool(order.x_fc_ltc_repair_id)
|
||||||
|
|
||||||
# ==========================================================================
|
# ==========================================================================
|
||||||
# SALE TYPE AND CLIENT TYPE FIELDS
|
# SALE TYPE AND CLIENT TYPE FIELDS
|
||||||
# ==========================================================================
|
# ==========================================================================
|
||||||
|
|||||||
@@ -153,9 +153,17 @@ class FusionTechnicianTask(models.Model):
|
|||||||
('assessment', 'Assessment'),
|
('assessment', 'Assessment'),
|
||||||
('installation', 'Installation'),
|
('installation', 'Installation'),
|
||||||
('maintenance', 'Maintenance'),
|
('maintenance', 'Maintenance'),
|
||||||
|
('ltc_visit', 'LTC Visit'),
|
||||||
('other', 'Other'),
|
('other', 'Other'),
|
||||||
], string='Task Type', required=True, default='delivery', tracking=True)
|
], string='Task Type', required=True, default='delivery', tracking=True)
|
||||||
|
|
||||||
|
facility_id = fields.Many2one(
|
||||||
|
'fusion.ltc.facility',
|
||||||
|
string='LTC Facility',
|
||||||
|
tracking=True,
|
||||||
|
help='LTC Home for this visit',
|
||||||
|
)
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# SCHEDULING
|
# SCHEDULING
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
@@ -219,6 +227,7 @@ class FusionTechnicianTask(models.Model):
|
|||||||
'assessment': 1.5,
|
'assessment': 1.5,
|
||||||
'installation': 2.0,
|
'installation': 2.0,
|
||||||
'maintenance': 1.5,
|
'maintenance': 1.5,
|
||||||
|
'ltc_visit': 3.0,
|
||||||
'other': 1.0,
|
'other': 1.0,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -919,6 +928,26 @@ class FusionTechnicianTask(models.Model):
|
|||||||
addr = order.dest_address_id or order.partner_id
|
addr = order.dest_address_id or order.partner_id
|
||||||
self._fill_address_from_partner(addr)
|
self._fill_address_from_partner(addr)
|
||||||
|
|
||||||
|
@api.onchange('facility_id')
|
||||||
|
def _onchange_facility_id(self):
|
||||||
|
"""Auto-fill address from the LTC facility."""
|
||||||
|
if self.facility_id and self.task_type == 'ltc_visit':
|
||||||
|
fac = self.facility_id
|
||||||
|
self.address_street = fac.street or ''
|
||||||
|
self.address_street2 = fac.street2 or ''
|
||||||
|
self.address_city = fac.city or ''
|
||||||
|
self.address_state_id = fac.state_id.id if fac.state_id else False
|
||||||
|
self.address_zip = fac.zip or ''
|
||||||
|
self.description = self.description or _(
|
||||||
|
'LTC Visit at %s', fac.name
|
||||||
|
)
|
||||||
|
|
||||||
|
@api.onchange('task_type')
|
||||||
|
def _onchange_task_type_ltc(self):
|
||||||
|
if self.task_type == 'ltc_visit':
|
||||||
|
self.sale_order_id = False
|
||||||
|
self.purchase_order_id = False
|
||||||
|
|
||||||
def _fill_address_from_partner(self, addr):
|
def _fill_address_from_partner(self, addr):
|
||||||
"""Populate address fields from a partner record."""
|
"""Populate address fields from a partner record."""
|
||||||
if not addr:
|
if not addr:
|
||||||
@@ -941,6 +970,8 @@ class FusionTechnicianTask(models.Model):
|
|||||||
for task in self:
|
for task in self:
|
||||||
if task.x_fc_sync_source:
|
if task.x_fc_sync_source:
|
||||||
continue
|
continue
|
||||||
|
if task.task_type == 'ltc_visit':
|
||||||
|
continue
|
||||||
if not task.sale_order_id and not task.purchase_order_id:
|
if not task.sale_order_id and not task.purchase_order_id:
|
||||||
raise ValidationError(_(
|
raise ValidationError(_(
|
||||||
"A task must be linked to either a Sale Order (Case) or a Purchase Order."
|
"A task must be linked to either a Sale Order (Case) or a Purchase Order."
|
||||||
|
|||||||
@@ -45,6 +45,21 @@
|
|||||||
<field name="paperformat_id" ref="paperformat_a4_landscape"/>
|
<field name="paperformat_id" ref="paperformat_a4_landscape"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
<!-- =============================================================== -->
|
||||||
|
<!-- LTC Repair Order / Quotation Report (Landscape) -->
|
||||||
|
<!-- =============================================================== -->
|
||||||
|
<record id="action_report_saleorder_ltc_repair" model="ir.actions.report">
|
||||||
|
<field name="name">LTC Repair Order / Quotation</field>
|
||||||
|
<field name="model">sale.order</field>
|
||||||
|
<field name="report_type">qweb-pdf</field>
|
||||||
|
<field name="report_name">fusion_claims.report_saleorder_ltc_repair</field>
|
||||||
|
<field name="report_file">fusion_claims.report_saleorder_ltc_repair</field>
|
||||||
|
<field name="print_report_name">'LTC Repair - %s - %s' % (object.name, object.partner_id.name)</field>
|
||||||
|
<field name="binding_model_id" ref="sale.model_sale_order"/>
|
||||||
|
<field name="binding_type">report</field>
|
||||||
|
<field name="paperformat_id" ref="paperformat_a4_landscape"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
<!-- Invoice Reports -->
|
<!-- Invoice Reports -->
|
||||||
<record id="action_report_invoice_portrait" model="ir.actions.report">
|
<record id="action_report_invoice_portrait" model="ir.actions.report">
|
||||||
<field name="name">Invoice (Portrait)</field>
|
<field name="name">Invoice (Portrait)</field>
|
||||||
|
|||||||
139
fusion_claims/report/report_ltc_nursing_station.xml
Normal file
139
fusion_claims/report/report_ltc_nursing_station.xml
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<template id="report_ltc_nursing_station_document">
|
||||||
|
<t t-call="web.html_container">
|
||||||
|
<t t-foreach="docs" t-as="facility">
|
||||||
|
<t t-call="web.external_layout">
|
||||||
|
<div class="page" style="font-size: 11px;">
|
||||||
|
<h3 class="text-center mb-3">
|
||||||
|
Equipment Repair Log - <t t-esc="facility.name"/>
|
||||||
|
</h3>
|
||||||
|
<p class="text-center text-muted mb-4">
|
||||||
|
Generated: <t t-esc="context_timestamp(datetime.datetime.now()).strftime('%B %d, %Y')"/>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<table class="table table-bordered" style="border: 1px solid #000;">
|
||||||
|
<thead>
|
||||||
|
<tr style="background-color: #f0f0f0; height: 20px;">
|
||||||
|
<th style="border: 1px solid #000; width: 5%; text-align: center;">S/N</th>
|
||||||
|
<th style="border: 1px solid #000; width: 18%;">Client Name</th>
|
||||||
|
<th style="border: 1px solid #000; width: 8%; text-align: center;">Room #</th>
|
||||||
|
<th style="border: 1px solid #000; width: 27%;">Issue Description</th>
|
||||||
|
<th style="border: 1px solid #000; width: 12%; text-align: center;">Reported Date</th>
|
||||||
|
<th style="border: 1px solid #000; width: 20%;">Resolution</th>
|
||||||
|
<th style="border: 1px solid #000; width: 10%; text-align: center;">Fixed Date</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<t t-set="counter" t-value="0"/>
|
||||||
|
<t t-foreach="facility.repair_ids.sorted(key=lambda r: r.issue_reported_date or '', reverse=True)"
|
||||||
|
t-as="repair">
|
||||||
|
<t t-set="counter" t-value="counter + 1"/>
|
||||||
|
<tr style="height: 20px;">
|
||||||
|
<td style="border: 1px solid #000; text-align: center;">
|
||||||
|
<t t-esc="counter"/>
|
||||||
|
</td>
|
||||||
|
<td style="border: 1px solid #000;">
|
||||||
|
<t t-esc="repair.display_client_name"/>
|
||||||
|
</td>
|
||||||
|
<td style="border: 1px solid #000; text-align: center;">
|
||||||
|
<t t-esc="repair.room_number"/>
|
||||||
|
</td>
|
||||||
|
<td style="border: 1px solid #000; font-size: 10px;">
|
||||||
|
<t t-esc="repair.issue_description"/>
|
||||||
|
</td>
|
||||||
|
<td style="border: 1px solid #000; text-align: center;">
|
||||||
|
<t t-if="repair.issue_reported_date">
|
||||||
|
<t t-esc="repair.issue_reported_date" t-options='{"widget": "date"}'/>
|
||||||
|
</t>
|
||||||
|
</td>
|
||||||
|
<td style="border: 1px solid #000; font-size: 10px;">
|
||||||
|
<t t-esc="repair.resolution_description or ''"/>
|
||||||
|
</td>
|
||||||
|
<td style="border: 1px solid #000; text-align: center;">
|
||||||
|
<t t-if="repair.issue_fixed_date">
|
||||||
|
<t t-esc="repair.issue_fixed_date" t-options='{"widget": "date"}'/>
|
||||||
|
</t>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</t>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
</t>
|
||||||
|
</t>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template id="report_ltc_repairs_summary_document">
|
||||||
|
<t t-call="web.html_container">
|
||||||
|
<t t-foreach="docs" t-as="facility">
|
||||||
|
<t t-call="web.external_layout">
|
||||||
|
<div class="page">
|
||||||
|
<h3 class="text-center mb-3">
|
||||||
|
Repair Summary - <t t-esc="facility.name"/>
|
||||||
|
</h3>
|
||||||
|
<p class="text-center text-muted mb-4">
|
||||||
|
Total Repairs: <t t-esc="len(facility.repair_ids)"/>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h5>Repairs by Stage</h5>
|
||||||
|
<table class="table table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Stage</th>
|
||||||
|
<th class="text-end">Count</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<t t-set="stages" t-value="{}"/>
|
||||||
|
<t t-foreach="facility.repair_ids" t-as="r">
|
||||||
|
<t t-set="sname" t-value="r.stage_id.name or 'No Stage'"/>
|
||||||
|
<t t-if="sname not in stages">
|
||||||
|
<t t-set="_" t-value="stages.__setitem__(sname, 0)"/>
|
||||||
|
</t>
|
||||||
|
<t t-set="_" t-value="stages.__setitem__(sname, stages[sname] + 1)"/>
|
||||||
|
</t>
|
||||||
|
<t t-foreach="stages" t-as="stage_name">
|
||||||
|
<tr>
|
||||||
|
<td><t t-esc="stage_name"/></td>
|
||||||
|
<td class="text-end"><t t-esc="stages[stage_name]"/></td>
|
||||||
|
</tr>
|
||||||
|
</t>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h5 class="mt-4">Recent Repairs</h5>
|
||||||
|
<table class="table table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Reference</th>
|
||||||
|
<th>Client</th>
|
||||||
|
<th>Room</th>
|
||||||
|
<th>Reported</th>
|
||||||
|
<th>Fixed</th>
|
||||||
|
<th>Stage</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<t t-foreach="facility.repair_ids.sorted(key=lambda r: r.issue_reported_date or '', reverse=True)[:50]"
|
||||||
|
t-as="repair">
|
||||||
|
<tr>
|
||||||
|
<td><t t-esc="repair.name"/></td>
|
||||||
|
<td><t t-esc="repair.display_client_name"/></td>
|
||||||
|
<td><t t-esc="repair.room_number"/></td>
|
||||||
|
<td><t t-esc="repair.issue_reported_date" t-options='{"widget": "date"}'/></td>
|
||||||
|
<td><t t-esc="repair.issue_fixed_date" t-options='{"widget": "date"}'/></td>
|
||||||
|
<td><t t-esc="repair.stage_id.name"/></td>
|
||||||
|
</tr>
|
||||||
|
</t>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
</t>
|
||||||
|
</t>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
280
fusion_claims/report/sale_report_ltc_repair.xml
Normal file
280
fusion_claims/report/sale_report_ltc_repair.xml
Normal file
@@ -0,0 +1,280 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright 2024-2026 Nexa Systems Inc.
|
||||||
|
License OPL-1 (Odoo Proprietary License v1.0)
|
||||||
|
Part of the Fusion Claim Assistant product family.
|
||||||
|
Landscape LTC Repair Order / Quotation Report Template
|
||||||
|
-->
|
||||||
|
<odoo>
|
||||||
|
<template id="report_saleorder_ltc_repair">
|
||||||
|
<t t-call="web.html_container">
|
||||||
|
<t t-foreach="docs" t-as="doc">
|
||||||
|
<t t-call="web.external_layout">
|
||||||
|
<t t-set="doc" t-value="doc.with_context(lang=doc.partner_id.lang)"/>
|
||||||
|
<t t-set="repair" t-value="doc.x_fc_ltc_repair_id"/>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.fc-ltc { font-family: Arial, sans-serif; font-size: 11pt; }
|
||||||
|
.fc-ltc table { width: 100%; border-collapse: collapse; margin-bottom: 12px; }
|
||||||
|
.fc-ltc table.bordered, .fc-ltc table.bordered th, .fc-ltc table.bordered td { border: 1px solid #000; }
|
||||||
|
.fc-ltc th { background-color: #0066a1; color: white; padding: 8px 10px; font-weight: bold; font-size: 10pt; }
|
||||||
|
.fc-ltc td { padding: 6px 8px; vertical-align: top; font-size: 10pt; }
|
||||||
|
.fc-ltc .text-center { text-align: center; }
|
||||||
|
.fc-ltc .text-end { text-align: right; }
|
||||||
|
.fc-ltc .text-start { text-align: left; }
|
||||||
|
.fc-ltc .repair-bg { background-color: #e8f5e9; }
|
||||||
|
.fc-ltc .section-row { background-color: #f0f0f0; font-weight: bold; }
|
||||||
|
.fc-ltc .note-row { font-style: italic; }
|
||||||
|
.fc-ltc h2 { color: #0066a1; margin: 10px 0; font-size: 18pt; }
|
||||||
|
.fc-ltc .info-table td { padding: 8px 12px; font-size: 11pt; }
|
||||||
|
.fc-ltc .info-table th { background-color: #f5f5f5; color: #333; font-size: 10pt; padding: 6px 12px; }
|
||||||
|
.fc-ltc .totals-table { border: 1px solid #000; }
|
||||||
|
.fc-ltc .totals-table td { border: 1px solid #000; padding: 8px 12px; font-size: 11pt; }
|
||||||
|
.fc-ltc .photo-grid { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 8px; }
|
||||||
|
.fc-ltc .photo-grid img { max-width: 220px; max-height: 180px; border: 1px solid #ccc; }
|
||||||
|
.fc-ltc .photo-section { margin-top: 20px; page-break-inside: avoid; }
|
||||||
|
.fc-ltc .photo-section h3 { color: #0066a1; font-size: 14pt; border-bottom: 2px solid #0066a1; padding-bottom: 4px; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="fc-ltc">
|
||||||
|
<div class="page">
|
||||||
|
|
||||||
|
<!-- Document Title -->
|
||||||
|
<h2 style="text-align: left;">
|
||||||
|
<span t-if="doc.state in ['draft','sent']">LTC Repair Quotation </span>
|
||||||
|
<span t-else="">LTC Repair Order </span>
|
||||||
|
<span t-field="doc.name"/>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<!-- Address Table -->
|
||||||
|
<table class="bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width: 50%;">BILLING ADDRESS</th>
|
||||||
|
<th style="width: 50%;">DELIVERY ADDRESS</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style="height: 70px; font-size: 12pt;">
|
||||||
|
<div t-field="doc.partner_invoice_id"
|
||||||
|
t-options="{'widget': 'contact', 'fields': ['name', 'address'], 'no_marker': True}"/>
|
||||||
|
</td>
|
||||||
|
<td style="height: 70px; font-size: 12pt;">
|
||||||
|
<div t-field="doc.partner_shipping_id"
|
||||||
|
t-options="{'widget': 'contact', 'fields': ['name', 'address'], 'no_marker': True}"/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<!-- Order Info Table -->
|
||||||
|
<table class="bordered info-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ORDER DATE</th>
|
||||||
|
<th>SALES REP</th>
|
||||||
|
<th>VALIDITY</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="text-center">
|
||||||
|
<span t-field="doc.date_order" t-options="{'widget': 'date'}"/>
|
||||||
|
</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<span t-field="doc.user_id"/>
|
||||||
|
</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<span t-field="doc.validity_date"/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<!-- LTC Repair Info Table -->
|
||||||
|
<t t-if="repair">
|
||||||
|
<table class="bordered info-table">
|
||||||
|
<thead>
|
||||||
|
<tr class="repair-bg">
|
||||||
|
<th style="background-color: #e8f5e9; color: #333;">REPAIR REF</th>
|
||||||
|
<th style="background-color: #e8f5e9; color: #333;">TECHNICIAN</th>
|
||||||
|
<th style="background-color: #e8f5e9; color: #333;">REPORTED DATE</th>
|
||||||
|
<th style="background-color: #e8f5e9; color: #333;">SERIAL #</th>
|
||||||
|
<th style="background-color: #e8f5e9; color: #333;">LTC LOCATION</th>
|
||||||
|
<th style="background-color: #e8f5e9; color: #333;">ROOM #</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr class="repair-bg">
|
||||||
|
<td class="text-center">
|
||||||
|
<span t-esc="repair.name or '-'"/>
|
||||||
|
</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<span t-if="repair.assigned_technician_id"
|
||||||
|
t-esc="repair.assigned_technician_id.name"/>
|
||||||
|
<span t-else="">-</span>
|
||||||
|
</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<t t-if="repair.issue_reported_date">
|
||||||
|
<span t-field="repair.issue_reported_date"/>
|
||||||
|
</t>
|
||||||
|
<t t-else="">-</t>
|
||||||
|
</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<span t-esc="repair.product_serial or '-'"/>
|
||||||
|
</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<span t-if="repair.facility_id"
|
||||||
|
t-esc="repair.facility_id.name"/>
|
||||||
|
<span t-else="">-</span>
|
||||||
|
</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<span t-esc="repair.room_number or '-'"/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</t>
|
||||||
|
|
||||||
|
<!-- Order Lines Table -->
|
||||||
|
<table class="bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="text-start" style="width: 40%;">DESCRIPTION</th>
|
||||||
|
<th class="text-center" style="width: 10%;">QTY</th>
|
||||||
|
<th class="text-center" style="width: 15%;">UNIT PRICE</th>
|
||||||
|
<th class="text-center" style="width: 15%;">TAX</th>
|
||||||
|
<th class="text-center" style="width: 20%;">TOTAL</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<t t-foreach="doc.order_line" t-as="line">
|
||||||
|
<!-- Section Header -->
|
||||||
|
<t t-if="line.display_type == 'line_section'">
|
||||||
|
<tr class="section-row">
|
||||||
|
<td colspan="5">
|
||||||
|
<strong><span t-field="line.name"/></strong>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</t>
|
||||||
|
<!-- Note Line -->
|
||||||
|
<t t-elif="line.display_type == 'line_note'">
|
||||||
|
<tr class="note-row">
|
||||||
|
<td colspan="5">
|
||||||
|
<span t-field="line.name"/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</t>
|
||||||
|
<!-- Product Line -->
|
||||||
|
<t t-elif="not line.display_type">
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<t t-if="line.name">
|
||||||
|
<t t-set="clean_name" t-value="line.name"/>
|
||||||
|
<t t-if="'] ' in line.name">
|
||||||
|
<t t-set="clean_name" t-value="line.name.split('] ', 1)[1]"/>
|
||||||
|
</t>
|
||||||
|
<t t-esc="clean_name"/>
|
||||||
|
</t>
|
||||||
|
</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<span t-esc="int(line.product_uom_qty) if line.product_uom_qty == int(line.product_uom_qty) else line.product_uom_qty"/>
|
||||||
|
</td>
|
||||||
|
<td class="text-end">
|
||||||
|
<span t-field="line.price_unit" t-options='{"widget": "monetary", "display_currency": doc.currency_id}'/>
|
||||||
|
</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<t t-esc="', '.join([(tax.invoice_label or tax.name) for tax in line.tax_ids]) or 'NO TAX SALE'"/>
|
||||||
|
</td>
|
||||||
|
<td class="text-end">
|
||||||
|
<span t-field="line.price_subtotal" t-options='{"widget": "monetary", "display_currency": doc.currency_id}'/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</t>
|
||||||
|
</t>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<!-- Payment Terms and Totals Row -->
|
||||||
|
<div class="row" style="margin-top: 15px;">
|
||||||
|
<div class="col-7">
|
||||||
|
<t t-if="doc.payment_term_id.note">
|
||||||
|
<strong>Payment Terms:</strong><br/>
|
||||||
|
<span t-field="doc.payment_term_id.note"/>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
<div class="col-5" style="text-align: right;">
|
||||||
|
<table class="totals-table" style="width: auto; margin-left: auto;">
|
||||||
|
<tr>
|
||||||
|
<td style="min-width: 200px;">Subtotal</td>
|
||||||
|
<td class="text-end" style="min-width: 150px;">
|
||||||
|
<span t-field="doc.amount_untaxed" t-options='{"widget": "monetary", "display_currency": doc.currency_id}'/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Taxes</td>
|
||||||
|
<td class="text-end">
|
||||||
|
<span t-field="doc.amount_tax" t-options='{"widget": "monetary", "display_currency": doc.currency_id}'/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Grand Total</strong></td>
|
||||||
|
<td class="text-end"><strong>
|
||||||
|
<span t-field="doc.amount_total" t-options='{"widget": "monetary", "display_currency": doc.currency_id}'/>
|
||||||
|
</strong></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Terms and Conditions -->
|
||||||
|
<t t-if="doc.note">
|
||||||
|
<div style="margin-top: 15px;">
|
||||||
|
<strong>Terms and Conditions:</strong>
|
||||||
|
<div t-field="doc.note"/>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
|
||||||
|
<!-- Before Photos -->
|
||||||
|
<t t-if="repair and repair.before_photo_ids">
|
||||||
|
<div class="photo-section">
|
||||||
|
<h3>Before Photos (Reported Condition)</h3>
|
||||||
|
<div class="photo-grid">
|
||||||
|
<t t-foreach="repair.before_photo_ids" t-as="photo">
|
||||||
|
<img t-att-src="image_data_uri(photo.datas)"
|
||||||
|
t-att-alt="photo.name"/>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
|
||||||
|
<!-- After Photos -->
|
||||||
|
<t t-if="repair and repair.after_photo_ids">
|
||||||
|
<div class="photo-section">
|
||||||
|
<h3>After Photos (Completed Repair)</h3>
|
||||||
|
<div class="photo-grid">
|
||||||
|
<t t-foreach="repair.after_photo_ids" t-as="photo">
|
||||||
|
<img t-att-src="image_data_uri(photo.datas)"
|
||||||
|
t-att-alt="photo.name"/>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
|
||||||
|
<!-- Signature -->
|
||||||
|
<t t-if="doc.signature">
|
||||||
|
<div style="margin-top: 20px; text-align: right;">
|
||||||
|
<strong>Signature</strong><br/>
|
||||||
|
<img t-att-src="image_data_uri(doc.signature)" style="max-height: 4cm; max-width: 8cm;"/><br/>
|
||||||
|
<span t-field="doc.signed_by"/>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
</t>
|
||||||
|
</t>
|
||||||
|
</template>
|
||||||
|
</odoo>
|
||||||
@@ -73,3 +73,21 @@ access_fusion_submit_to_odsp_wizard_user,fusion_claims.submit.to.odsp.wizard.use
|
|||||||
access_fusion_submit_to_odsp_wizard_manager,fusion_claims.submit.to.odsp.wizard.manager,model_fusion_claims_submit_to_odsp_wizard,sales_team.group_sale_manager,1,1,1,1
|
access_fusion_submit_to_odsp_wizard_manager,fusion_claims.submit.to.odsp.wizard.manager,model_fusion_claims_submit_to_odsp_wizard,sales_team.group_sale_manager,1,1,1,1
|
||||||
access_fusion_task_sync_config_manager,fusion.task.sync.config.manager,model_fusion_task_sync_config,sales_team.group_sale_manager,1,1,1,1
|
access_fusion_task_sync_config_manager,fusion.task.sync.config.manager,model_fusion_task_sync_config,sales_team.group_sale_manager,1,1,1,1
|
||||||
access_fusion_task_sync_config_user,fusion.task.sync.config.user,model_fusion_task_sync_config,sales_team.group_sale_salesman,1,0,0,0
|
access_fusion_task_sync_config_user,fusion.task.sync.config.user,model_fusion_task_sync_config,sales_team.group_sale_salesman,1,0,0,0
|
||||||
|
access_fusion_ltc_facility_user,fusion.ltc.facility.user,model_fusion_ltc_facility,sales_team.group_sale_salesman,1,1,1,0
|
||||||
|
access_fusion_ltc_facility_manager,fusion.ltc.facility.manager,model_fusion_ltc_facility,sales_team.group_sale_manager,1,1,1,1
|
||||||
|
access_fusion_ltc_floor_user,fusion.ltc.floor.user,model_fusion_ltc_floor,sales_team.group_sale_salesman,1,1,1,0
|
||||||
|
access_fusion_ltc_floor_manager,fusion.ltc.floor.manager,model_fusion_ltc_floor,sales_team.group_sale_manager,1,1,1,1
|
||||||
|
access_fusion_ltc_station_user,fusion.ltc.station.user,model_fusion_ltc_station,sales_team.group_sale_salesman,1,1,1,0
|
||||||
|
access_fusion_ltc_station_manager,fusion.ltc.station.manager,model_fusion_ltc_station,sales_team.group_sale_manager,1,1,1,1
|
||||||
|
access_fusion_ltc_repair_user,fusion.ltc.repair.user,model_fusion_ltc_repair,sales_team.group_sale_salesman,1,1,1,0
|
||||||
|
access_fusion_ltc_repair_manager,fusion.ltc.repair.manager,model_fusion_ltc_repair,sales_team.group_sale_manager,1,1,1,1
|
||||||
|
access_fusion_ltc_repair_stage_user,fusion.ltc.repair.stage.user,model_fusion_ltc_repair_stage,sales_team.group_sale_salesman,1,0,0,0
|
||||||
|
access_fusion_ltc_repair_stage_manager,fusion.ltc.repair.stage.manager,model_fusion_ltc_repair_stage,sales_team.group_sale_manager,1,1,1,1
|
||||||
|
access_fusion_ltc_cleanup_user,fusion.ltc.cleanup.user,model_fusion_ltc_cleanup,sales_team.group_sale_salesman,1,1,1,0
|
||||||
|
access_fusion_ltc_cleanup_manager,fusion.ltc.cleanup.manager,model_fusion_ltc_cleanup,sales_team.group_sale_manager,1,1,1,1
|
||||||
|
access_fusion_ltc_family_contact_user,fusion.ltc.family.contact.user,model_fusion_ltc_family_contact,sales_team.group_sale_salesman,1,1,1,0
|
||||||
|
access_fusion_ltc_family_contact_manager,fusion.ltc.family.contact.manager,model_fusion_ltc_family_contact,sales_team.group_sale_manager,1,1,1,1
|
||||||
|
access_fusion_ltc_form_submission_user,fusion.ltc.form.submission.user,model_fusion_ltc_form_submission,sales_team.group_sale_salesman,1,1,0,0
|
||||||
|
access_fusion_ltc_form_submission_manager,fusion.ltc.form.submission.manager,model_fusion_ltc_form_submission,sales_team.group_sale_manager,1,1,1,1
|
||||||
|
access_fusion_ltc_repair_create_so_wizard_user,fusion.ltc.repair.create.so.wizard.user,model_fusion_ltc_repair_create_so_wizard,sales_team.group_sale_salesman,1,1,1,1
|
||||||
|
access_fusion_ltc_repair_create_so_wizard_manager,fusion.ltc.repair.create.so.wizard.manager,model_fusion_ltc_repair_create_so_wizard,sales_team.group_sale_manager,1,1,1,1
|
||||||
|
@@ -1048,6 +1048,332 @@ async function setupSimpleAddressFields(el, orm) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup autocomplete for LTC Facility form.
|
||||||
|
* Attaches establishment search on the name field and address search on street.
|
||||||
|
*/
|
||||||
|
async function setupFacilityAutocomplete(el, model, orm) {
|
||||||
|
globalOrm = orm;
|
||||||
|
|
||||||
|
const apiKey = await getGoogleMapsApiKey(orm);
|
||||||
|
if (!apiKey) return;
|
||||||
|
try { await loadGoogleMapsApi(apiKey); } catch (e) { return; }
|
||||||
|
|
||||||
|
// --- Name field: establishment autocomplete ---
|
||||||
|
const nameSelectors = [
|
||||||
|
'.oe_title [name="name"] input',
|
||||||
|
'div[name="name"] input',
|
||||||
|
'.o_field_widget[name="name"] input',
|
||||||
|
'[name="name"] input',
|
||||||
|
];
|
||||||
|
|
||||||
|
let nameInput = null;
|
||||||
|
for (const sel of nameSelectors) {
|
||||||
|
nameInput = el.querySelector(sel);
|
||||||
|
if (nameInput) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nameInput && !autocompleteInstances.has('facility_name_' + (nameInput.id || 'default'))) {
|
||||||
|
_attachFacilityNameAutocomplete(nameInput, el, model);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Street field: address autocomplete ---
|
||||||
|
const streetSelectors = [
|
||||||
|
'div[name="street"] input',
|
||||||
|
'.o_field_widget[name="street"] input',
|
||||||
|
'[name="street"] input',
|
||||||
|
];
|
||||||
|
|
||||||
|
let streetInput = null;
|
||||||
|
for (const sel of streetSelectors) {
|
||||||
|
streetInput = el.querySelector(sel);
|
||||||
|
if (streetInput) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (streetInput && !autocompleteInstances.has(streetInput)) {
|
||||||
|
_attachFacilityAddressAutocomplete(streetInput, el, model);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attach establishment (business) autocomplete on facility name field.
|
||||||
|
* Selecting a business fills name, address, phone, email, and website.
|
||||||
|
*/
|
||||||
|
function _attachFacilityNameAutocomplete(input, el, model) {
|
||||||
|
if (!input || !window.google?.maps?.places) return;
|
||||||
|
|
||||||
|
const instanceKey = 'facility_name_' + (input.id || 'default');
|
||||||
|
if (autocompleteInstances.has(instanceKey)) return;
|
||||||
|
|
||||||
|
const autocomplete = new google.maps.places.Autocomplete(input, {
|
||||||
|
componentRestrictions: { country: 'ca' },
|
||||||
|
types: ['establishment'],
|
||||||
|
fields: [
|
||||||
|
'place_id', 'name', 'address_components', 'formatted_address',
|
||||||
|
'formatted_phone_number', 'international_phone_number', 'website',
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
autocomplete.addListener('place_changed', async () => {
|
||||||
|
let place = autocomplete.getPlace();
|
||||||
|
if (!place.name && !place.place_id) return;
|
||||||
|
|
||||||
|
if (place.place_id && !place.formatted_phone_number && !place.website) {
|
||||||
|
try {
|
||||||
|
const service = new google.maps.places.PlacesService(document.createElement('div'));
|
||||||
|
const details = await new Promise((resolve, reject) => {
|
||||||
|
service.getDetails(
|
||||||
|
{
|
||||||
|
placeId: place.place_id,
|
||||||
|
fields: ['formatted_phone_number', 'international_phone_number', 'website'],
|
||||||
|
},
|
||||||
|
(result, status) => {
|
||||||
|
if (status === google.maps.places.PlacesServiceStatus.OK) resolve(result);
|
||||||
|
else reject(new Error(status));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
if (details.formatted_phone_number) place.formatted_phone_number = details.formatted_phone_number;
|
||||||
|
if (details.international_phone_number) place.international_phone_number = details.international_phone_number;
|
||||||
|
if (details.website) place.website = details.website;
|
||||||
|
} catch (_) { /* ignore */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
let streetNumber = '', streetName = '', unitNumber = '';
|
||||||
|
let city = '', province = '', postalCode = '', countryCode = '';
|
||||||
|
|
||||||
|
if (place.address_components) {
|
||||||
|
for (const c of place.address_components) {
|
||||||
|
const t = c.types;
|
||||||
|
if (t.includes('street_number')) streetNumber = c.long_name;
|
||||||
|
else if (t.includes('route')) streetName = c.long_name;
|
||||||
|
else if (t.includes('subpremise')) unitNumber = c.long_name;
|
||||||
|
else if (t.includes('floor') && !unitNumber) unitNumber = 'Floor ' + c.long_name;
|
||||||
|
else if (t.includes('locality')) city = c.long_name;
|
||||||
|
else if (t.includes('sublocality_level_1') && !city) city = c.long_name;
|
||||||
|
else if (t.includes('administrative_area_level_1')) province = c.short_name;
|
||||||
|
else if (t.includes('postal_code')) postalCode = c.long_name;
|
||||||
|
else if (t.includes('country')) countryCode = c.short_name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const street = streetNumber ? `${streetNumber} ${streetName}` : streetName;
|
||||||
|
const phone = place.formatted_phone_number || place.international_phone_number || '';
|
||||||
|
|
||||||
|
if (!model?.root) return;
|
||||||
|
const record = model.root;
|
||||||
|
|
||||||
|
let countryId = null, stateId = null;
|
||||||
|
if (globalOrm && countryCode) {
|
||||||
|
try {
|
||||||
|
const [countries, states] = await Promise.all([
|
||||||
|
globalOrm.searchRead('res.country', [['code', '=', countryCode]], ['id'], { limit: 1 }),
|
||||||
|
province
|
||||||
|
? globalOrm.searchRead('res.country.state', [['code', '=', province], ['country_id.code', '=', countryCode]], ['id'], { limit: 1 })
|
||||||
|
: Promise.resolve([]),
|
||||||
|
]);
|
||||||
|
if (countries.length) countryId = countries[0].id;
|
||||||
|
if (states.length) stateId = states[0].id;
|
||||||
|
} catch (_) { /* ignore */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (record.resId && globalOrm) {
|
||||||
|
try {
|
||||||
|
const firstWrite = {};
|
||||||
|
if (place.name) firstWrite.name = place.name;
|
||||||
|
if (street) firstWrite.street = street;
|
||||||
|
if (unitNumber) firstWrite.street2 = unitNumber;
|
||||||
|
if (city) firstWrite.city = city;
|
||||||
|
if (postalCode) firstWrite.zip = postalCode;
|
||||||
|
if (phone) firstWrite.phone = phone;
|
||||||
|
if (place.website) firstWrite.website = place.website;
|
||||||
|
if (countryId) firstWrite.country_id = countryId;
|
||||||
|
|
||||||
|
await globalOrm.write('fusion.ltc.facility', [record.resId], firstWrite);
|
||||||
|
|
||||||
|
if (stateId) {
|
||||||
|
await new Promise(r => setTimeout(r, 100));
|
||||||
|
await globalOrm.write('fusion.ltc.facility', [record.resId], { state_id: stateId });
|
||||||
|
}
|
||||||
|
|
||||||
|
await record.load();
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[GooglePlaces Facility] Name autocomplete ORM write failed:', err);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
const textUpdate = {};
|
||||||
|
if (place.name) textUpdate.name = place.name;
|
||||||
|
if (street) textUpdate.street = street;
|
||||||
|
if (unitNumber) textUpdate.street2 = unitNumber;
|
||||||
|
if (city) textUpdate.city = city;
|
||||||
|
if (postalCode) textUpdate.zip = postalCode;
|
||||||
|
if (phone) textUpdate.phone = phone;
|
||||||
|
if (place.website) textUpdate.website = place.website;
|
||||||
|
|
||||||
|
await record.update(textUpdate);
|
||||||
|
|
||||||
|
if (countryId && globalOrm) {
|
||||||
|
const formEl = input.closest('.o_form_view') || input.closest('.o_content') || document.body;
|
||||||
|
const [countryData, stateData] = await Promise.all([
|
||||||
|
globalOrm.read('res.country', [countryId], ['display_name']),
|
||||||
|
stateId ? globalOrm.read('res.country.state', [stateId], ['display_name']) : Promise.resolve([]),
|
||||||
|
]);
|
||||||
|
|
||||||
|
await simulateMany2OneSelection(formEl, 'country_id', countryId, countryData[0]?.display_name || 'Canada');
|
||||||
|
await new Promise(r => setTimeout(r, 300));
|
||||||
|
|
||||||
|
if (stateId && stateData.length) {
|
||||||
|
await simulateMany2OneSelection(formEl, 'state_id', stateId, stateData[0]?.display_name || province);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[GooglePlaces Facility] Name autocomplete update failed:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
autocompleteInstances.set(instanceKey, autocomplete);
|
||||||
|
|
||||||
|
input.style.backgroundImage = 'url("data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 24 24\' fill=\'%232196F3\'%3E%3Cpath d=\'M12 7V3H2v18h20V7H12zM6 19H4v-2h2v2zm0-4H4v-2h2v2zm0-4H4V9h2v2zm0-4H4V5h2v2zm4 12H8v-2h2v2zm0-4H8v-2h2v2zm0-4H8V9h2v2zm0-4H8V5h2v2zm10 12h-8v-2h2v-2h-2v-2h2v-2h-2V9h8v10zm-2-8h-2v2h2v-2zm0 4h-2v2h2v-2z\'/%3E%3C/svg%3E")';
|
||||||
|
input.style.backgroundRepeat = 'no-repeat';
|
||||||
|
input.style.backgroundPosition = 'right 8px center';
|
||||||
|
input.style.backgroundSize = '20px';
|
||||||
|
input.style.paddingRight = '35px';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attach address autocomplete on facility street field.
|
||||||
|
* Fills street, street2, city, state, zip, and country.
|
||||||
|
*/
|
||||||
|
function _attachFacilityAddressAutocomplete(input, el, model) {
|
||||||
|
if (!input || !window.google?.maps?.places) return;
|
||||||
|
if (autocompleteInstances.has(input)) return;
|
||||||
|
|
||||||
|
const autocomplete = new google.maps.places.Autocomplete(input, {
|
||||||
|
componentRestrictions: { country: 'ca' },
|
||||||
|
types: ['address'],
|
||||||
|
fields: ['address_components', 'formatted_address'],
|
||||||
|
});
|
||||||
|
|
||||||
|
autocomplete.addListener('place_changed', async () => {
|
||||||
|
const place = autocomplete.getPlace();
|
||||||
|
if (!place.address_components) return;
|
||||||
|
|
||||||
|
let streetNumber = '', streetName = '', unitNumber = '';
|
||||||
|
let city = '', province = '', postalCode = '', countryCode = '';
|
||||||
|
|
||||||
|
for (const c of place.address_components) {
|
||||||
|
const t = c.types;
|
||||||
|
if (t.includes('street_number')) streetNumber = c.long_name;
|
||||||
|
else if (t.includes('route')) streetName = c.long_name;
|
||||||
|
else if (t.includes('subpremise')) unitNumber = c.long_name;
|
||||||
|
else if (t.includes('floor') && !unitNumber) unitNumber = 'Floor ' + c.long_name;
|
||||||
|
else if (t.includes('locality')) city = c.long_name;
|
||||||
|
else if (t.includes('sublocality_level_1') && !city) city = c.long_name;
|
||||||
|
else if (t.includes('administrative_area_level_1')) province = c.short_name;
|
||||||
|
else if (t.includes('postal_code')) postalCode = c.long_name;
|
||||||
|
else if (t.includes('country')) countryCode = c.short_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
const street = streetNumber ? `${streetNumber} ${streetName}` : streetName;
|
||||||
|
|
||||||
|
if (!model?.root) return;
|
||||||
|
const record = model.root;
|
||||||
|
|
||||||
|
let countryId = null, stateId = null;
|
||||||
|
if (globalOrm && countryCode) {
|
||||||
|
try {
|
||||||
|
const [countries, states] = await Promise.all([
|
||||||
|
globalOrm.searchRead('res.country', [['code', '=', countryCode]], ['id'], { limit: 1 }),
|
||||||
|
province
|
||||||
|
? globalOrm.searchRead('res.country.state', [['code', '=', province], ['country_id.code', '=', countryCode]], ['id'], { limit: 1 })
|
||||||
|
: Promise.resolve([]),
|
||||||
|
]);
|
||||||
|
if (countries.length) countryId = countries[0].id;
|
||||||
|
if (states.length) stateId = states[0].id;
|
||||||
|
} catch (_) { /* ignore */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (record.resId && globalOrm) {
|
||||||
|
try {
|
||||||
|
const firstWrite = {};
|
||||||
|
if (street) firstWrite.street = street;
|
||||||
|
if (unitNumber) firstWrite.street2 = unitNumber;
|
||||||
|
if (city) firstWrite.city = city;
|
||||||
|
if (postalCode) firstWrite.zip = postalCode;
|
||||||
|
if (countryId) firstWrite.country_id = countryId;
|
||||||
|
|
||||||
|
await globalOrm.write('fusion.ltc.facility', [record.resId], firstWrite);
|
||||||
|
|
||||||
|
if (stateId) {
|
||||||
|
await new Promise(r => setTimeout(r, 100));
|
||||||
|
await globalOrm.write('fusion.ltc.facility', [record.resId], { state_id: stateId });
|
||||||
|
}
|
||||||
|
|
||||||
|
await record.load();
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[GooglePlaces Facility] Address ORM write failed:', err);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
const textUpdate = {};
|
||||||
|
if (street) textUpdate.street = street;
|
||||||
|
if (unitNumber) textUpdate.street2 = unitNumber;
|
||||||
|
if (city) textUpdate.city = city;
|
||||||
|
if (postalCode) textUpdate.zip = postalCode;
|
||||||
|
|
||||||
|
await record.update(textUpdate);
|
||||||
|
|
||||||
|
if (countryId && globalOrm) {
|
||||||
|
const formEl = input.closest('.o_form_view') || input.closest('.o_content') || document.body;
|
||||||
|
const [countryData, stateData] = await Promise.all([
|
||||||
|
globalOrm.read('res.country', [countryId], ['display_name']),
|
||||||
|
stateId ? globalOrm.read('res.country.state', [stateId], ['display_name']) : Promise.resolve([]),
|
||||||
|
]);
|
||||||
|
|
||||||
|
await simulateMany2OneSelection(formEl, 'country_id', countryId, countryData[0]?.display_name || 'Canada');
|
||||||
|
await new Promise(r => setTimeout(r, 300));
|
||||||
|
|
||||||
|
if (stateId && stateData.length) {
|
||||||
|
await simulateMany2OneSelection(formEl, 'state_id', stateId, stateData[0]?.display_name || province);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[GooglePlaces Facility] Address autocomplete update failed:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => { _reattachFacilityAutocomplete(el, model); }, 400);
|
||||||
|
});
|
||||||
|
|
||||||
|
autocompleteInstances.set(input, autocomplete);
|
||||||
|
|
||||||
|
input.style.backgroundImage = 'url("data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 24 24\' fill=\'%234CAF50\'%3E%3Cpath d=\'M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z\'/%3E%3C/svg%3E")';
|
||||||
|
input.style.backgroundRepeat = 'no-repeat';
|
||||||
|
input.style.backgroundPosition = 'right 8px center';
|
||||||
|
input.style.backgroundSize = '20px';
|
||||||
|
input.style.paddingRight = '35px';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Re-attach facility autocomplete after OWL re-renders inputs.
|
||||||
|
*/
|
||||||
|
function _reattachFacilityAutocomplete(el, model) {
|
||||||
|
const streetSelectors = [
|
||||||
|
'div[name="street"] input',
|
||||||
|
'.o_field_widget[name="street"] input',
|
||||||
|
'[name="street"] input',
|
||||||
|
];
|
||||||
|
for (const sel of streetSelectors) {
|
||||||
|
const inp = el.querySelector(sel);
|
||||||
|
if (inp && !autocompleteInstances.has(inp)) {
|
||||||
|
_attachFacilityAddressAutocomplete(inp, el, model);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Patch FormController to add Google autocomplete for partner forms and dialog detection
|
* Patch FormController to add Google autocomplete for partner forms and dialog detection
|
||||||
*/
|
*/
|
||||||
@@ -1164,6 +1490,35 @@ patch(FormController.prototype, {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LTC Facility form
|
||||||
|
if (this.props.resModel === 'fusion.ltc.facility') {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (this.rootRef && this.rootRef.el) {
|
||||||
|
setupFacilityAutocomplete(this.rootRef.el, this.model, this.orm);
|
||||||
|
}
|
||||||
|
}, 800);
|
||||||
|
|
||||||
|
if (this.rootRef && this.rootRef.el) {
|
||||||
|
this._facilityAddrObserver = new MutationObserver((mutations) => {
|
||||||
|
const hasNewInputs = mutations.some(m =>
|
||||||
|
m.addedNodes.length > 0 &&
|
||||||
|
Array.from(m.addedNodes).some(n =>
|
||||||
|
n.nodeType === 1 && (n.tagName === 'INPUT' || n.querySelector?.('input'))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if (hasNewInputs) {
|
||||||
|
setTimeout(() => {
|
||||||
|
setupFacilityAutocomplete(this.rootRef.el, this.model, this.orm);
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this._facilityAddrObserver.observe(this.rootRef.el, {
|
||||||
|
childList: true,
|
||||||
|
subtree: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Simple address autocomplete: res.partner, res.users, res.config.settings
|
// Simple address autocomplete: res.partner, res.users, res.config.settings
|
||||||
if (this.props.resModel === 'res.partner' || this.props.resModel === 'res.users' || this.props.resModel === 'res.config.settings') {
|
if (this.props.resModel === 'res.partner' || this.props.resModel === 'res.users' || this.props.resModel === 'res.config.settings') {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -1201,6 +1556,9 @@ patch(FormController.prototype, {
|
|||||||
if (this._taskAddressObserver) {
|
if (this._taskAddressObserver) {
|
||||||
this._taskAddressObserver.disconnect();
|
this._taskAddressObserver.disconnect();
|
||||||
}
|
}
|
||||||
|
if (this._facilityAddrObserver) {
|
||||||
|
this._facilityAddrObserver.disconnect();
|
||||||
|
}
|
||||||
if (this._simpleAddrObserver) {
|
if (this._simpleAddrObserver) {
|
||||||
this._simpleAddrObserver.disconnect();
|
this._simpleAddrObserver.disconnect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -869,4 +869,62 @@ html.dark, .o_dark {
|
|||||||
z-index: 100000 !important;
|
z-index: 100000 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// LTC REPAIR KANBAN - Stage & priority color coding
|
||||||
|
// Uses data-* attributes on <main> + CSS :has() to style the outer card.
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
.o_kanban_view .o_kanban_record {
|
||||||
|
|
||||||
|
// --- Stage left border (on the full card) ---
|
||||||
|
&:has(main[data-stage="info"]) {
|
||||||
|
border-left: 3px solid #0dcaf0 !important;
|
||||||
|
background-color: rgba(13, 202, 240, 0.04) !important;
|
||||||
|
}
|
||||||
|
&:has(main[data-stage="warning"]) {
|
||||||
|
border-left: 3px solid #ffc107 !important;
|
||||||
|
background-color: rgba(255, 193, 7, 0.04) !important;
|
||||||
|
}
|
||||||
|
&:has(main[data-stage="success"]) {
|
||||||
|
border-left: 3px solid #198754 !important;
|
||||||
|
background-color: rgba(25, 135, 84, 0.04) !important;
|
||||||
|
}
|
||||||
|
&:has(main[data-stage="danger"]) {
|
||||||
|
border-left: 3px solid #dc3545 !important;
|
||||||
|
background-color: rgba(220, 53, 69, 0.04) !important;
|
||||||
|
}
|
||||||
|
&:has(main[data-stage="secondary"]) {
|
||||||
|
border-left: 3px solid #adb5bd !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Priority high: warm amber bottom accent ---
|
||||||
|
&:has(main[data-priority="1"]) {
|
||||||
|
box-shadow: inset 0 -2px 0 0 rgba(255, 152, 0, 0.4) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Emergency: override with stronger red ---
|
||||||
|
&:has(main[data-emergency="1"]) {
|
||||||
|
border-left: 4px solid #dc3545 !important;
|
||||||
|
background-color: rgba(220, 53, 69, 0.06) !important;
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(220, 53, 69, 0.15) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emergency + priority combined
|
||||||
|
&:has(main[data-emergency="1"][data-priority="1"]) {
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(220, 53, 69, 0.15),
|
||||||
|
inset 0 -2px 0 0 rgba(255, 152, 0, 0.4) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dark mode: slightly stronger tints
|
||||||
|
html.dark, .o_dark {
|
||||||
|
.o_kanban_view .o_kanban_record {
|
||||||
|
&:has(main[data-stage="info"]) { background-color: rgba(13, 202, 240, 0.07) !important; }
|
||||||
|
&:has(main[data-stage="warning"]) { background-color: rgba(255, 193, 7, 0.07) !important; }
|
||||||
|
&:has(main[data-stage="success"]) { background-color: rgba(25, 135, 84, 0.07) !important; }
|
||||||
|
&:has(main[data-stage="danger"]) { background-color: rgba(220, 53, 69, 0.07) !important; }
|
||||||
|
&:has(main[data-emergency="1"]) { background-color: rgba(220, 53, 69, 0.1) !important; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -612,13 +612,131 @@
|
|||||||
<field name="help" type="html"><p class="o_view_nocontent_smiling_face">No ACSD cases yet</p></field>
|
<field name="help" type="html"><p class="o_view_nocontent_smiling_face">No ACSD cases yet</p></field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
<!-- ===================================================================== -->
|
||||||
|
<!-- ODSP: LIST VIEW -->
|
||||||
|
<!-- ===================================================================== -->
|
||||||
|
<record id="view_sale_order_list_odsp" model="ir.ui.view">
|
||||||
|
<field name="name">sale.order.list.odsp</field>
|
||||||
|
<field name="model">sale.order</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<list string="ODSP Cases" default_order="date_order desc, id desc">
|
||||||
|
<!-- Always visible -->
|
||||||
|
<field name="name" string="Order"/>
|
||||||
|
<field name="date_order" string="Date" optional="show"/>
|
||||||
|
<field name="partner_id" string="Client"/>
|
||||||
|
<field name="x_fc_odsp_member_id" string="Member ID" optional="show"/>
|
||||||
|
<field name="x_fc_odsp_division" optional="show"/>
|
||||||
|
|
||||||
|
<!-- Division-specific status columns -->
|
||||||
|
<field name="x_fc_odsp_std_status" widget="badge" string="ODSP Status"
|
||||||
|
decoration-info="x_fc_odsp_std_status in ('quotation','submitted_to_odsp')"
|
||||||
|
decoration-warning="x_fc_odsp_std_status in ('pre_approved','on_hold')"
|
||||||
|
decoration-success="x_fc_odsp_std_status in ('ready_delivery','delivered','pod_submitted','payment_received','case_closed')"
|
||||||
|
decoration-danger="x_fc_odsp_std_status in ('denied','cancelled')"
|
||||||
|
optional="show"/>
|
||||||
|
<field name="x_fc_sa_status" widget="badge" string="SA Status"
|
||||||
|
decoration-info="x_fc_sa_status in ('quotation','form_ready','submitted_to_sa')"
|
||||||
|
decoration-warning="x_fc_sa_status in ('pre_approved','on_hold')"
|
||||||
|
decoration-success="x_fc_sa_status in ('ready_delivery','delivered','pod_submitted','payment_received','case_closed')"
|
||||||
|
decoration-danger="x_fc_sa_status in ('denied','cancelled')"
|
||||||
|
optional="hide"/>
|
||||||
|
<field name="x_fc_ow_status" widget="badge" string="OW Status"
|
||||||
|
decoration-info="x_fc_ow_status in ('quotation','documents_ready','submitted_to_ow')"
|
||||||
|
decoration-warning="x_fc_ow_status in ('on_hold')"
|
||||||
|
decoration-success="x_fc_ow_status in ('payment_received','ready_delivery','delivered','case_closed')"
|
||||||
|
decoration-danger="x_fc_ow_status in ('denied','cancelled')"
|
||||||
|
optional="hide"/>
|
||||||
|
|
||||||
|
<!-- ODSP contacts -->
|
||||||
|
<field name="x_fc_odsp_office_id" optional="show"/>
|
||||||
|
<field name="x_fc_odsp_case_worker_name" optional="show"/>
|
||||||
|
<field name="user_id" string="Sales Rep" optional="hide"/>
|
||||||
|
<field name="x_fc_authorizer_id" optional="hide"/>
|
||||||
|
|
||||||
|
<!-- Amounts -->
|
||||||
|
<field name="amount_total" widget="monetary" sum="Grand Total" optional="show"/>
|
||||||
|
|
||||||
|
<!-- Misc -->
|
||||||
|
<field name="x_fc_on_hold_date" optional="hide"/>
|
||||||
|
<field name="x_fc_case_locked" optional="hide"/>
|
||||||
|
<field name="state" widget="badge" decoration-success="state == 'sale'"
|
||||||
|
decoration-info="state == 'draft'" optional="hide"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- ===================================================================== -->
|
||||||
|
<!-- ODSP: SEARCH VIEW -->
|
||||||
|
<!-- ===================================================================== -->
|
||||||
|
<record id="view_sale_order_search_odsp" model="ir.ui.view">
|
||||||
|
<field name="name">sale.order.search.odsp</field>
|
||||||
|
<field name="model">sale.order</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<search string="Search ODSP Cases">
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="partner_id"/>
|
||||||
|
<field name="x_fc_odsp_member_id"/>
|
||||||
|
<field name="x_fc_odsp_office_id"/>
|
||||||
|
<field name="x_fc_odsp_case_worker_name"/>
|
||||||
|
<separator/>
|
||||||
|
<!-- ODSP Standard Status -->
|
||||||
|
<filter string="Quotation" name="filter_quotation"
|
||||||
|
domain="[('x_fc_odsp_std_status', '=', 'quotation')]"/>
|
||||||
|
<filter string="Submitted to ODSP" name="filter_submitted_odsp"
|
||||||
|
domain="[('x_fc_odsp_std_status', '=', 'submitted_to_odsp')]"/>
|
||||||
|
<filter string="Pre-Approved" name="filter_pre_approved"
|
||||||
|
domain="[('x_fc_odsp_std_status', '=', 'pre_approved')]"/>
|
||||||
|
<filter string="Ready for Delivery" name="filter_ready_delivery"
|
||||||
|
domain="[('x_fc_odsp_std_status', '=', 'ready_delivery')]"/>
|
||||||
|
<filter string="Delivered" name="filter_delivered"
|
||||||
|
domain="[('x_fc_odsp_std_status', '=', 'delivered')]"/>
|
||||||
|
<filter string="POD Submitted" name="filter_pod_submitted"
|
||||||
|
domain="[('x_fc_odsp_std_status', '=', 'pod_submitted')]"/>
|
||||||
|
<filter string="Payment Received" name="filter_payment_received"
|
||||||
|
domain="[('x_fc_odsp_std_status', '=', 'payment_received')]"/>
|
||||||
|
<filter string="Case Closed" name="filter_case_closed"
|
||||||
|
domain="[('x_fc_odsp_std_status', '=', 'case_closed')]"/>
|
||||||
|
<separator/>
|
||||||
|
<!-- Special Status -->
|
||||||
|
<filter string="On Hold" name="filter_on_hold"
|
||||||
|
domain="[('x_fc_odsp_std_status', '=', 'on_hold')]"/>
|
||||||
|
<filter string="Denied" name="filter_denied"
|
||||||
|
domain="[('x_fc_odsp_std_status', '=', 'denied')]"/>
|
||||||
|
<filter string="Cancelled" name="filter_cancelled"
|
||||||
|
domain="[('x_fc_odsp_std_status', '=', 'cancelled')]"/>
|
||||||
|
<separator/>
|
||||||
|
<!-- Division Filters -->
|
||||||
|
<filter string="ODSP Standard" name="filter_division_standard"
|
||||||
|
domain="[('x_fc_odsp_division', '=', 'standard')]"/>
|
||||||
|
<filter string="SA Mobility" name="filter_division_sa"
|
||||||
|
domain="[('x_fc_odsp_division', '=', 'sa_mobility')]"/>
|
||||||
|
<filter string="Ontario Works" name="filter_division_ow"
|
||||||
|
domain="[('x_fc_odsp_division', '=', 'ontario_works')]"/>
|
||||||
|
<separator/>
|
||||||
|
<!-- Group By -->
|
||||||
|
<filter string="ODSP Division" name="group_division" context="{'group_by': 'x_fc_odsp_division'}"/>
|
||||||
|
<filter string="ODSP Status" name="group_odsp_status" context="{'group_by': 'x_fc_odsp_std_status'}"/>
|
||||||
|
<filter string="SA Status" name="group_sa_status" context="{'group_by': 'x_fc_sa_status'}"/>
|
||||||
|
<filter string="OW Status" name="group_ow_status" context="{'group_by': 'x_fc_ow_status'}"/>
|
||||||
|
<filter string="ODSP Office" name="group_odsp_office" context="{'group_by': 'x_fc_odsp_office_id'}"/>
|
||||||
|
<filter string="Salesperson" name="group_salesperson" context="{'group_by': 'user_id'}"/>
|
||||||
|
<filter string="Create Month" name="group_create_month" context="{'group_by': 'create_date:month'}"/>
|
||||||
|
<filter string="Create Quarter" name="group_create_quarter" context="{'group_by': 'create_date:quarter'}"/>
|
||||||
|
<filter string="Create Year" name="group_create_year" context="{'group_by': 'create_date:year'}"/>
|
||||||
|
</search>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- ===================================================================== -->
|
||||||
|
<!-- ODSP: ACTIONS -->
|
||||||
|
<!-- ===================================================================== -->
|
||||||
<record id="action_fc_odsp_orders" model="ir.actions.act_window">
|
<record id="action_fc_odsp_orders" model="ir.actions.act_window">
|
||||||
<field name="name">All ODSP Cases</field>
|
<field name="name">All ODSP Cases</field>
|
||||||
<field name="res_model">sale.order</field>
|
<field name="res_model">sale.order</field>
|
||||||
<field name="view_mode">list,form,kanban</field>
|
<field name="view_mode">list,form,kanban</field>
|
||||||
<field name="view_ids" eval="[(5, 0, 0),
|
<field name="view_ids" eval="[(5, 0, 0),
|
||||||
(0, 0, {'view_mode': 'list', 'view_id': ref('view_sale_order_list_adp')})]"/>
|
(0, 0, {'view_mode': 'list', 'view_id': ref('view_sale_order_list_odsp')})]"/>
|
||||||
<field name="search_view_id" ref="view_sale_order_search_adp"/>
|
<field name="search_view_id" ref="view_sale_order_search_odsp"/>
|
||||||
<field name="domain">[('x_fc_sale_type', 'in', ['odsp', 'adp_odsp'])]</field>
|
<field name="domain">[('x_fc_sale_type', 'in', ['odsp', 'adp_odsp'])]</field>
|
||||||
<field name="context">{'default_x_fc_sale_type': 'odsp'}</field>
|
<field name="context">{'default_x_fc_sale_type': 'odsp'}</field>
|
||||||
<field name="help" type="html"><p class="o_view_nocontent_smiling_face">No ODSP cases yet</p></field>
|
<field name="help" type="html"><p class="o_view_nocontent_smiling_face">No ODSP cases yet</p></field>
|
||||||
@@ -629,8 +747,8 @@
|
|||||||
<field name="res_model">sale.order</field>
|
<field name="res_model">sale.order</field>
|
||||||
<field name="view_mode">list,form,kanban</field>
|
<field name="view_mode">list,form,kanban</field>
|
||||||
<field name="view_ids" eval="[(5, 0, 0),
|
<field name="view_ids" eval="[(5, 0, 0),
|
||||||
(0, 0, {'view_mode': 'list', 'view_id': ref('view_sale_order_list_adp')})]"/>
|
(0, 0, {'view_mode': 'list', 'view_id': ref('view_sale_order_list_odsp')})]"/>
|
||||||
<field name="search_view_id" ref="view_sale_order_search_adp"/>
|
<field name="search_view_id" ref="view_sale_order_search_odsp"/>
|
||||||
<field name="domain">[('x_fc_sale_type', 'in', ['odsp', 'adp_odsp']), ('x_fc_odsp_division', '=', 'standard')]</field>
|
<field name="domain">[('x_fc_sale_type', 'in', ['odsp', 'adp_odsp']), ('x_fc_odsp_division', '=', 'standard')]</field>
|
||||||
<field name="context">{'default_x_fc_sale_type': 'odsp', 'default_x_fc_odsp_division': 'standard'}</field>
|
<field name="context">{'default_x_fc_sale_type': 'odsp', 'default_x_fc_odsp_division': 'standard'}</field>
|
||||||
<field name="help" type="html"><p class="o_view_nocontent_smiling_face">No ODSP Standard cases yet</p></field>
|
<field name="help" type="html"><p class="o_view_nocontent_smiling_face">No ODSP Standard cases yet</p></field>
|
||||||
@@ -641,8 +759,8 @@
|
|||||||
<field name="res_model">sale.order</field>
|
<field name="res_model">sale.order</field>
|
||||||
<field name="view_mode">list,form,kanban</field>
|
<field name="view_mode">list,form,kanban</field>
|
||||||
<field name="view_ids" eval="[(5, 0, 0),
|
<field name="view_ids" eval="[(5, 0, 0),
|
||||||
(0, 0, {'view_mode': 'list', 'view_id': ref('view_sale_order_list_adp')})]"/>
|
(0, 0, {'view_mode': 'list', 'view_id': ref('view_sale_order_list_odsp')})]"/>
|
||||||
<field name="search_view_id" ref="view_sale_order_search_adp"/>
|
<field name="search_view_id" ref="view_sale_order_search_odsp"/>
|
||||||
<field name="domain">[('x_fc_sale_type', 'in', ['odsp', 'adp_odsp']), ('x_fc_odsp_division', '=', 'sa_mobility')]</field>
|
<field name="domain">[('x_fc_sale_type', 'in', ['odsp', 'adp_odsp']), ('x_fc_odsp_division', '=', 'sa_mobility')]</field>
|
||||||
<field name="context">{'default_x_fc_sale_type': 'odsp', 'default_x_fc_odsp_division': 'sa_mobility'}</field>
|
<field name="context">{'default_x_fc_sale_type': 'odsp', 'default_x_fc_odsp_division': 'sa_mobility'}</field>
|
||||||
<field name="help" type="html"><p class="o_view_nocontent_smiling_face">No SA Mobility cases yet</p></field>
|
<field name="help" type="html"><p class="o_view_nocontent_smiling_face">No SA Mobility cases yet</p></field>
|
||||||
@@ -653,8 +771,8 @@
|
|||||||
<field name="res_model">sale.order</field>
|
<field name="res_model">sale.order</field>
|
||||||
<field name="view_mode">list,form,kanban</field>
|
<field name="view_mode">list,form,kanban</field>
|
||||||
<field name="view_ids" eval="[(5, 0, 0),
|
<field name="view_ids" eval="[(5, 0, 0),
|
||||||
(0, 0, {'view_mode': 'list', 'view_id': ref('view_sale_order_list_adp')})]"/>
|
(0, 0, {'view_mode': 'list', 'view_id': ref('view_sale_order_list_odsp')})]"/>
|
||||||
<field name="search_view_id" ref="view_sale_order_search_adp"/>
|
<field name="search_view_id" ref="view_sale_order_search_odsp"/>
|
||||||
<field name="domain">[('x_fc_sale_type', 'in', ['odsp', 'adp_odsp']), ('x_fc_odsp_division', '=', 'ontario_works')]</field>
|
<field name="domain">[('x_fc_sale_type', 'in', ['odsp', 'adp_odsp']), ('x_fc_odsp_division', '=', 'ontario_works')]</field>
|
||||||
<field name="context">{'default_x_fc_sale_type': 'odsp', 'default_x_fc_odsp_division': 'ontario_works'}</field>
|
<field name="context">{'default_x_fc_sale_type': 'odsp', 'default_x_fc_odsp_division': 'ontario_works'}</field>
|
||||||
<field name="help" type="html"><p class="o_view_nocontent_smiling_face">No Ontario Works cases yet</p></field>
|
<field name="help" type="html"><p class="o_view_nocontent_smiling_face">No Ontario Works cases yet</p></field>
|
||||||
@@ -986,12 +1104,54 @@ else:
|
|||||||
sequence="30"
|
sequence="30"
|
||||||
groups="group_fusion_claims_user,group_field_technician"/>
|
groups="group_fusion_claims_user,group_field_technician"/>
|
||||||
|
|
||||||
<!-- ===== DASHBOARD ===== -->
|
<!-- ===== LTC MANAGEMENT ===== -->
|
||||||
<menuitem id="menu_fc_dashboard"
|
<menuitem id="menu_fc_ltc"
|
||||||
name="Dashboard"
|
name="LTC Management"
|
||||||
parent="menu_adp_claims_root"
|
parent="menu_adp_claims_root"
|
||||||
action="action_fusion_claims_dashboard"
|
|
||||||
sequence="5"/>
|
sequence="5"/>
|
||||||
|
<menuitem id="menu_ltc_overview"
|
||||||
|
name="Overview"
|
||||||
|
parent="menu_fc_ltc"
|
||||||
|
action="action_ltc_repairs_kanban"
|
||||||
|
sequence="1"/>
|
||||||
|
<menuitem id="menu_ltc_repairs"
|
||||||
|
name="Repair Requests"
|
||||||
|
parent="menu_fc_ltc"
|
||||||
|
sequence="10"/>
|
||||||
|
<menuitem id="menu_ltc_repairs_all"
|
||||||
|
name="All Repairs"
|
||||||
|
parent="menu_ltc_repairs"
|
||||||
|
action="action_ltc_repairs_all"
|
||||||
|
sequence="1"/>
|
||||||
|
<menuitem id="menu_ltc_repairs_new"
|
||||||
|
name="New / Pending"
|
||||||
|
parent="menu_ltc_repairs"
|
||||||
|
action="action_ltc_repairs_new"
|
||||||
|
sequence="2"/>
|
||||||
|
<menuitem id="menu_ltc_repairs_progress"
|
||||||
|
name="In Progress"
|
||||||
|
parent="menu_ltc_repairs"
|
||||||
|
action="action_ltc_repairs_in_progress"
|
||||||
|
sequence="3"/>
|
||||||
|
<menuitem id="menu_ltc_repairs_completed"
|
||||||
|
name="Completed"
|
||||||
|
parent="menu_ltc_repairs"
|
||||||
|
action="action_ltc_repairs_completed"
|
||||||
|
sequence="4"/>
|
||||||
|
<menuitem id="menu_ltc_cleanup"
|
||||||
|
name="Cleanup Schedule"
|
||||||
|
parent="menu_fc_ltc"
|
||||||
|
action="action_ltc_cleanups"
|
||||||
|
sequence="20"/>
|
||||||
|
<menuitem id="menu_ltc_locations"
|
||||||
|
name="Locations"
|
||||||
|
parent="menu_fc_ltc"
|
||||||
|
sequence="30"/>
|
||||||
|
<menuitem id="menu_ltc_facilities"
|
||||||
|
name="Facilities"
|
||||||
|
parent="menu_ltc_locations"
|
||||||
|
action="action_ltc_facilities"
|
||||||
|
sequence="1"/>
|
||||||
|
|
||||||
<!-- ===== ADP SUBMENU (full workflow) ===== -->
|
<!-- ===== ADP SUBMENU (full workflow) ===== -->
|
||||||
<menuitem id="menu_fc_adp"
|
<menuitem id="menu_fc_adp"
|
||||||
@@ -1156,6 +1316,22 @@ else:
|
|||||||
action="action_device_import_wizard" sequence="20"/>
|
action="action_device_import_wizard" sequence="20"/>
|
||||||
<menuitem id="menu_import_xml_files" name="Import XML Files" parent="menu_adp_config"
|
<menuitem id="menu_import_xml_files" name="Import XML Files" parent="menu_adp_config"
|
||||||
action="action_xml_import_wizard" sequence="30"/>
|
action="action_xml_import_wizard" sequence="30"/>
|
||||||
|
<menuitem id="menu_ltc_repair_stages" name="LTC Repair Stages" parent="menu_adp_config"
|
||||||
|
action="action_ltc_repair_stages" sequence="40"/>
|
||||||
|
<menuitem id="menu_forms_management"
|
||||||
|
name="Forms Management"
|
||||||
|
parent="menu_adp_config"
|
||||||
|
sequence="50"/>
|
||||||
|
<menuitem id="menu_form_submissions"
|
||||||
|
name="Form Submissions"
|
||||||
|
parent="menu_forms_management"
|
||||||
|
action="action_ltc_form_submissions"
|
||||||
|
sequence="1"/>
|
||||||
|
<menuitem id="menu_forms_settings"
|
||||||
|
name="Forms Settings"
|
||||||
|
parent="menu_forms_management"
|
||||||
|
action="action_fusion_claims_settings"
|
||||||
|
sequence="2"/>
|
||||||
<menuitem id="menu_fusion_claims_settings" name="Settings" parent="menu_adp_config"
|
<menuitem id="menu_fusion_claims_settings" name="Settings" parent="menu_adp_config"
|
||||||
action="action_fusion_claims_settings" sequence="90"/>
|
action="action_fusion_claims_settings" sequence="90"/>
|
||||||
|
|
||||||
|
|||||||
@@ -15,15 +15,27 @@
|
|||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<list decoration-danger="state == 'overdue'"
|
<list decoration-danger="state == 'overdue'"
|
||||||
decoration-warning="state == 'rental_pending'"
|
decoration-warning="state == 'rental_pending'"
|
||||||
decoration-muted="state in ('returned', 'lost')">
|
decoration-muted="state in ('returned', 'lost')"
|
||||||
|
default_order="checkout_date desc, id desc">
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
<field name="partner_id"/>
|
<field name="partner_id"/>
|
||||||
<field name="product_id"/>
|
<field name="product_id"/>
|
||||||
<field name="lot_id" optional="show"/>
|
<field name="lot_id" optional="show"/>
|
||||||
<field name="checkout_date"/>
|
<field name="checkout_date"/>
|
||||||
<field name="expected_return_date"/>
|
<field name="expected_return_date"/>
|
||||||
|
<field name="actual_return_date" optional="hide"/>
|
||||||
<field name="days_out"/>
|
<field name="days_out"/>
|
||||||
<field name="state" widget="badge"/>
|
<field name="days_overdue" optional="hide"/>
|
||||||
|
<field name="sales_rep_id" optional="hide"/>
|
||||||
|
<field name="checkout_condition" optional="hide"/>
|
||||||
|
<field name="return_condition" optional="hide"/>
|
||||||
|
<field name="sale_order_id" optional="hide"/>
|
||||||
|
<field name="state" widget="badge"
|
||||||
|
decoration-info="state == 'draft'"
|
||||||
|
decoration-success="state == 'checked_out'"
|
||||||
|
decoration-danger="state in ('overdue', 'lost')"
|
||||||
|
decoration-warning="state == 'rental_pending'"
|
||||||
|
decoration-muted="state in ('returned', 'converted_rental')"/>
|
||||||
</list>
|
</list>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
@@ -43,6 +55,21 @@
|
|||||||
statusbar_visible="draft,checked_out,returned"/>
|
statusbar_visible="draft,checked_out,returned"/>
|
||||||
</header>
|
</header>
|
||||||
<sheet>
|
<sheet>
|
||||||
|
<div class="oe_button_box" name="button_box">
|
||||||
|
<button name="action_view_sale_order" type="object"
|
||||||
|
class="oe_stat_button" icon="fa-file-text-o"
|
||||||
|
invisible="not sale_order_id">
|
||||||
|
<div class="o_field_widget o_stat_info">
|
||||||
|
<span class="o_stat_text">Sale Order</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
<button name="action_view_partner" type="object"
|
||||||
|
class="oe_stat_button" icon="fa-user">
|
||||||
|
<div class="o_field_widget o_stat_info">
|
||||||
|
<span class="o_stat_text">Contact</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div class="oe_title">
|
<div class="oe_title">
|
||||||
<h1>
|
<h1>
|
||||||
<field name="name" readonly="1"/>
|
<field name="name" readonly="1"/>
|
||||||
@@ -83,10 +110,61 @@
|
|||||||
<field name="name">fusion.loaner.checkout.search</field>
|
<field name="name">fusion.loaner.checkout.search</field>
|
||||||
<field name="model">fusion.loaner.checkout</field>
|
<field name="model">fusion.loaner.checkout</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<search>
|
<search string="Search Loaners">
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
<field name="partner_id"/>
|
<field name="partner_id"/>
|
||||||
<field name="product_id"/>
|
<field name="product_id"/>
|
||||||
|
<field name="lot_id" string="Serial Number"/>
|
||||||
|
<field name="sale_order_id"/>
|
||||||
|
<field name="sales_rep_id"/>
|
||||||
|
<separator/>
|
||||||
|
<!-- Status Filters -->
|
||||||
|
<filter string="Draft" name="filter_draft"
|
||||||
|
domain="[('state', '=', 'draft')]"/>
|
||||||
|
<filter string="Checked Out" name="filter_checked_out"
|
||||||
|
domain="[('state', '=', 'checked_out')]"/>
|
||||||
|
<filter string="Overdue" name="filter_overdue"
|
||||||
|
domain="[('state', '=', 'overdue')]"/>
|
||||||
|
<filter string="Rental Pending" name="filter_rental_pending"
|
||||||
|
domain="[('state', '=', 'rental_pending')]"/>
|
||||||
|
<filter string="Returned" name="filter_returned"
|
||||||
|
domain="[('state', '=', 'returned')]"/>
|
||||||
|
<filter string="Converted to Rental" name="filter_converted"
|
||||||
|
domain="[('state', '=', 'converted_rental')]"/>
|
||||||
|
<filter string="Lost" name="filter_lost"
|
||||||
|
domain="[('state', '=', 'lost')]"/>
|
||||||
|
<separator/>
|
||||||
|
<!-- Quick Filters -->
|
||||||
|
<filter string="Active Loaners" name="filter_active"
|
||||||
|
domain="[('state', 'in', ['checked_out', 'overdue', 'rental_pending'])]"/>
|
||||||
|
<filter string="Needs Attention" name="filter_attention"
|
||||||
|
domain="[('state', 'in', ['overdue', 'rental_pending'])]"/>
|
||||||
|
<filter string="My Loaners" name="filter_my_loaners"
|
||||||
|
domain="[('sales_rep_id', '=', uid)]"/>
|
||||||
|
<separator/>
|
||||||
|
<!-- Condition Filters -->
|
||||||
|
<filter string="Needs Repair (Checkout)" name="filter_checkout_repair"
|
||||||
|
domain="[('checkout_condition', '=', 'needs_repair')]"/>
|
||||||
|
<filter string="Damaged (Return)" name="filter_return_damaged"
|
||||||
|
domain="[('return_condition', 'in', ['needs_repair', 'damaged'])]"/>
|
||||||
|
<separator/>
|
||||||
|
<!-- Group By -->
|
||||||
|
<filter string="Status" name="group_state"
|
||||||
|
context="{'group_by': 'state'}"/>
|
||||||
|
<filter string="Client" name="group_client"
|
||||||
|
context="{'group_by': 'partner_id'}"/>
|
||||||
|
<filter string="Product" name="group_product"
|
||||||
|
context="{'group_by': 'product_id'}"/>
|
||||||
|
<filter string="Sales Rep" name="group_sales_rep"
|
||||||
|
context="{'group_by': 'sales_rep_id'}"/>
|
||||||
|
<filter string="Checkout Condition" name="group_checkout_condition"
|
||||||
|
context="{'group_by': 'checkout_condition'}"/>
|
||||||
|
<filter string="Return Condition" name="group_return_condition"
|
||||||
|
context="{'group_by': 'return_condition'}"/>
|
||||||
|
<filter string="Checkout Month" name="group_checkout_month"
|
||||||
|
context="{'group_by': 'checkout_date:month'}"/>
|
||||||
|
<filter string="Return Month" name="group_return_month"
|
||||||
|
context="{'group_by': 'actual_return_date:month'}"/>
|
||||||
</search>
|
</search>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
@@ -99,16 +177,64 @@
|
|||||||
<field name="name">fusion.loaner.history.list</field>
|
<field name="name">fusion.loaner.history.list</field>
|
||||||
<field name="model">fusion.loaner.history</field>
|
<field name="model">fusion.loaner.history</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<list>
|
<list default_order="action_date desc, id desc">
|
||||||
<field name="action_date"/>
|
<field name="action_date"/>
|
||||||
<field name="checkout_id"/>
|
<field name="checkout_id"/>
|
||||||
<field name="action" widget="badge"/>
|
<field name="partner_id" optional="show"/>
|
||||||
|
<field name="product_id" optional="show"/>
|
||||||
|
<field name="lot_id" optional="hide"/>
|
||||||
|
<field name="action" widget="badge"
|
||||||
|
decoration-info="action in ('create', 'note')"
|
||||||
|
decoration-success="action in ('checkout', 'return')"
|
||||||
|
decoration-warning="action in ('reminder_sent', 'overdue', 'rental_pending')"
|
||||||
|
decoration-danger="action in ('lost', 'condition_update')"/>
|
||||||
<field name="user_id"/>
|
<field name="user_id"/>
|
||||||
<field name="notes"/>
|
<field name="notes" optional="show"/>
|
||||||
</list>
|
</list>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
<!-- History Search View -->
|
||||||
|
<record id="view_fusion_loaner_history_search" model="ir.ui.view">
|
||||||
|
<field name="name">fusion.loaner.history.search</field>
|
||||||
|
<field name="model">fusion.loaner.history</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<search string="Search Loaner History">
|
||||||
|
<field name="checkout_id"/>
|
||||||
|
<field name="partner_id"/>
|
||||||
|
<field name="product_id"/>
|
||||||
|
<field name="lot_id" string="Serial Number"/>
|
||||||
|
<field name="user_id"/>
|
||||||
|
<separator/>
|
||||||
|
<!-- Action Type Filters -->
|
||||||
|
<filter string="Checkouts" name="filter_checkout"
|
||||||
|
domain="[('action', '=', 'checkout')]"/>
|
||||||
|
<filter string="Returns" name="filter_return"
|
||||||
|
domain="[('action', '=', 'return')]"/>
|
||||||
|
<filter string="Reminders" name="filter_reminders"
|
||||||
|
domain="[('action', '=', 'reminder_sent')]"/>
|
||||||
|
<filter string="Overdue" name="filter_overdue"
|
||||||
|
domain="[('action', '=', 'overdue')]"/>
|
||||||
|
<filter string="Rental Conversions" name="filter_rental"
|
||||||
|
domain="[('action', 'in', ['rental_pending', 'rental_converted'])]"/>
|
||||||
|
<filter string="Lost" name="filter_lost"
|
||||||
|
domain="[('action', '=', 'lost')]"/>
|
||||||
|
<separator/>
|
||||||
|
<!-- Group By -->
|
||||||
|
<filter string="Action Type" name="group_action"
|
||||||
|
context="{'group_by': 'action'}"/>
|
||||||
|
<filter string="Client" name="group_client"
|
||||||
|
context="{'group_by': 'partner_id'}"/>
|
||||||
|
<filter string="Product" name="group_product"
|
||||||
|
context="{'group_by': 'product_id'}"/>
|
||||||
|
<filter string="User" name="group_user"
|
||||||
|
context="{'group_by': 'user_id'}"/>
|
||||||
|
<filter string="Month" name="group_month"
|
||||||
|
context="{'group_by': 'action_date:month'}"/>
|
||||||
|
</search>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
<!-- ===================================================================== -->
|
<!-- ===================================================================== -->
|
||||||
<!-- WIZARD VIEWS -->
|
<!-- WIZARD VIEWS -->
|
||||||
<!-- ===================================================================== -->
|
<!-- ===================================================================== -->
|
||||||
@@ -164,12 +290,45 @@
|
|||||||
<field name="name">Loaner Equipment</field>
|
<field name="name">Loaner Equipment</field>
|
||||||
<field name="res_model">fusion.loaner.checkout</field>
|
<field name="res_model">fusion.loaner.checkout</field>
|
||||||
<field name="view_mode">list,form</field>
|
<field name="view_mode">list,form</field>
|
||||||
|
<field name="search_view_id" ref="view_fusion_loaner_checkout_search"/>
|
||||||
|
<field name="context">{'search_default_filter_active': 1}</field>
|
||||||
|
<field name="help" type="html">
|
||||||
|
<p class="o_view_nocontent_smiling_face">No loaner checkouts yet</p>
|
||||||
|
<p>Track loaner equipment issued to clients during assessments or trials.</p>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="action_fusion_loaner_all" model="ir.actions.act_window">
|
||||||
|
<field name="name">All Loaners</field>
|
||||||
|
<field name="res_model">fusion.loaner.checkout</field>
|
||||||
|
<field name="view_mode">list,form</field>
|
||||||
|
<field name="search_view_id" ref="view_fusion_loaner_checkout_search"/>
|
||||||
|
<field name="help" type="html">
|
||||||
|
<p class="o_view_nocontent_smiling_face">No loaner checkouts yet</p>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="action_fusion_loaner_overdue" model="ir.actions.act_window">
|
||||||
|
<field name="name">Overdue Loaners</field>
|
||||||
|
<field name="res_model">fusion.loaner.checkout</field>
|
||||||
|
<field name="view_mode">list,form</field>
|
||||||
|
<field name="search_view_id" ref="view_fusion_loaner_checkout_search"/>
|
||||||
|
<field name="context">{'search_default_filter_attention': 1}</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="action_fusion_loaner_returned" model="ir.actions.act_window">
|
||||||
|
<field name="name">Returned Loaners</field>
|
||||||
|
<field name="res_model">fusion.loaner.checkout</field>
|
||||||
|
<field name="view_mode">list,form</field>
|
||||||
|
<field name="search_view_id" ref="view_fusion_loaner_checkout_search"/>
|
||||||
|
<field name="context">{'search_default_filter_returned': 1}</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="action_fusion_loaner_history" model="ir.actions.act_window">
|
<record id="action_fusion_loaner_history" model="ir.actions.act_window">
|
||||||
<field name="name">Loaner History</field>
|
<field name="name">Loaner History</field>
|
||||||
<field name="res_model">fusion.loaner.history</field>
|
<field name="res_model">fusion.loaner.history</field>
|
||||||
<field name="view_mode">list</field>
|
<field name="view_mode">list</field>
|
||||||
|
<field name="search_view_id" ref="view_fusion_loaner_history_search"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<!-- Action: Loaner Products (products that can be loaned) -->
|
<!-- Action: Loaner Products (products that can be loaned) -->
|
||||||
@@ -204,6 +363,24 @@
|
|||||||
action="action_fusion_loaner_checkout"
|
action="action_fusion_loaner_checkout"
|
||||||
sequence="10"/>
|
sequence="10"/>
|
||||||
|
|
||||||
|
<menuitem id="menu_loaner_all"
|
||||||
|
name="All Loaners"
|
||||||
|
parent="menu_loaner_root"
|
||||||
|
action="action_fusion_loaner_all"
|
||||||
|
sequence="15"/>
|
||||||
|
|
||||||
|
<menuitem id="menu_loaner_overdue"
|
||||||
|
name="Overdue / Attention"
|
||||||
|
parent="menu_loaner_root"
|
||||||
|
action="action_fusion_loaner_overdue"
|
||||||
|
sequence="18"/>
|
||||||
|
|
||||||
|
<menuitem id="menu_loaner_returned"
|
||||||
|
name="Returned"
|
||||||
|
parent="menu_loaner_root"
|
||||||
|
action="action_fusion_loaner_returned"
|
||||||
|
sequence="19"/>
|
||||||
|
|
||||||
<menuitem id="menu_loaner_history"
|
<menuitem id="menu_loaner_history"
|
||||||
name="Loaner History"
|
name="Loaner History"
|
||||||
parent="menu_loaner_root"
|
parent="menu_loaner_root"
|
||||||
|
|||||||
180
fusion_claims/views/ltc_cleanup_views.xml
Normal file
180
fusion_claims/views/ltc_cleanup_views.xml
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<!-- ================================================================== -->
|
||||||
|
<!-- CLEANUP - FORM VIEW -->
|
||||||
|
<!-- ================================================================== -->
|
||||||
|
<record id="view_ltc_cleanup_form" model="ir.ui.view">
|
||||||
|
<field name="name">fusion.ltc.cleanup.form</field>
|
||||||
|
<field name="model">fusion.ltc.cleanup</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="Cleanup Schedule">
|
||||||
|
<header>
|
||||||
|
<button name="action_start" type="object" string="Start"
|
||||||
|
class="btn-primary"
|
||||||
|
invisible="state != 'scheduled'"/>
|
||||||
|
<button name="action_complete" type="object" string="Complete"
|
||||||
|
class="btn-primary"
|
||||||
|
invisible="state != 'in_progress'"/>
|
||||||
|
<button name="action_cancel" type="object" string="Cancel"
|
||||||
|
invisible="state in ('completed', 'cancelled')"/>
|
||||||
|
<button name="action_reset" type="object" string="Reset to Scheduled"
|
||||||
|
invisible="state not in ('cancelled', 'rescheduled')"/>
|
||||||
|
<field name="state" widget="statusbar"
|
||||||
|
statusbar_visible="scheduled,in_progress,completed"/>
|
||||||
|
</header>
|
||||||
|
<sheet>
|
||||||
|
<div class="oe_button_box" name="button_box">
|
||||||
|
<button name="action_create_task" type="object"
|
||||||
|
class="oe_stat_button" icon="fa-tasks">
|
||||||
|
<div class="o_field_widget o_stat_info">
|
||||||
|
<span class="o_stat_text">Task</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="oe_title">
|
||||||
|
<label for="name"/>
|
||||||
|
<h1>
|
||||||
|
<field name="name" readonly="1"/>
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<group>
|
||||||
|
<group string="Schedule">
|
||||||
|
<field name="facility_id"/>
|
||||||
|
<field name="scheduled_date"/>
|
||||||
|
<field name="completed_date"/>
|
||||||
|
<field name="technician_id"/>
|
||||||
|
</group>
|
||||||
|
<group string="Details">
|
||||||
|
<field name="items_cleaned"/>
|
||||||
|
<field name="task_id" readonly="1"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
|
||||||
|
<notebook>
|
||||||
|
<page string="Notes" name="notes">
|
||||||
|
<field name="notes" placeholder="Cleanup notes..."/>
|
||||||
|
</page>
|
||||||
|
<page string="Photos" name="photos">
|
||||||
|
<field name="photo_ids" widget="many2many_binary"/>
|
||||||
|
</page>
|
||||||
|
</notebook>
|
||||||
|
</sheet>
|
||||||
|
<chatter/>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- ================================================================== -->
|
||||||
|
<!-- CLEANUP - LIST VIEW -->
|
||||||
|
<!-- ================================================================== -->
|
||||||
|
<record id="view_ltc_cleanup_list" model="ir.ui.view">
|
||||||
|
<field name="name">fusion.ltc.cleanup.list</field>
|
||||||
|
<field name="model">fusion.ltc.cleanup</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<list string="Cleanup Schedule" default_order="scheduled_date desc"
|
||||||
|
decoration-success="state == 'completed'"
|
||||||
|
decoration-muted="state in ('cancelled', 'rescheduled')">
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="facility_id"/>
|
||||||
|
<field name="scheduled_date"/>
|
||||||
|
<field name="completed_date" optional="show"/>
|
||||||
|
<field name="technician_id" widget="many2one_avatar_user" optional="show"/>
|
||||||
|
<field name="items_cleaned" optional="show"/>
|
||||||
|
<field name="state" widget="badge"
|
||||||
|
decoration-info="state == 'scheduled'"
|
||||||
|
decoration-warning="state == 'in_progress'"
|
||||||
|
decoration-success="state == 'completed'"
|
||||||
|
decoration-danger="state == 'cancelled'"
|
||||||
|
decoration-muted="state == 'rescheduled'"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- ================================================================== -->
|
||||||
|
<!-- CLEANUP - KANBAN VIEW -->
|
||||||
|
<!-- ================================================================== -->
|
||||||
|
<record id="view_ltc_cleanup_kanban" model="ir.ui.view">
|
||||||
|
<field name="name">fusion.ltc.cleanup.kanban</field>
|
||||||
|
<field name="model">fusion.ltc.cleanup</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<kanban default_group_by="state" class="o_kanban_small_column">
|
||||||
|
<templates>
|
||||||
|
<t t-name="card">
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<field name="name" class="fw-bold"/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<field name="facility_id" class="fw-bold"/>
|
||||||
|
</div>
|
||||||
|
<div class="text-muted">
|
||||||
|
Scheduled: <field name="scheduled_date"/>
|
||||||
|
</div>
|
||||||
|
<div t-if="record.completed_date.raw_value" class="text-muted">
|
||||||
|
Completed: <field name="completed_date"/>
|
||||||
|
</div>
|
||||||
|
<footer class="mt-2">
|
||||||
|
<field name="technician_id" widget="many2one_avatar_user"/>
|
||||||
|
</footer>
|
||||||
|
</t>
|
||||||
|
</templates>
|
||||||
|
</kanban>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- ================================================================== -->
|
||||||
|
<!-- CLEANUP - SEARCH VIEW -->
|
||||||
|
<!-- ================================================================== -->
|
||||||
|
<record id="view_ltc_cleanup_search" model="ir.ui.view">
|
||||||
|
<field name="name">fusion.ltc.cleanup.search</field>
|
||||||
|
<field name="model">fusion.ltc.cleanup</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<search string="Search Cleanups">
|
||||||
|
<field name="facility_id"/>
|
||||||
|
<field name="technician_id"/>
|
||||||
|
<separator/>
|
||||||
|
<filter string="Scheduled" name="filter_scheduled"
|
||||||
|
domain="[('state', '=', 'scheduled')]"/>
|
||||||
|
<filter string="In Progress" name="filter_in_progress"
|
||||||
|
domain="[('state', '=', 'in_progress')]"/>
|
||||||
|
<filter string="Completed" name="filter_completed"
|
||||||
|
domain="[('state', '=', 'completed')]"/>
|
||||||
|
<separator/>
|
||||||
|
<filter string="Upcoming (7 Days)" name="filter_upcoming"
|
||||||
|
domain="[('scheduled_date', '<=', (context_today() + datetime.timedelta(days=7)).strftime('%Y-%m-%d')),
|
||||||
|
('scheduled_date', '>=', context_today().strftime('%Y-%m-%d')),
|
||||||
|
('state', '=', 'scheduled')]"/>
|
||||||
|
<filter string="Overdue" name="filter_overdue"
|
||||||
|
domain="[('scheduled_date', '<', context_today().strftime('%Y-%m-%d')),
|
||||||
|
('state', '=', 'scheduled')]"/>
|
||||||
|
<separator/>
|
||||||
|
<filter string="Facility" name="group_facility"
|
||||||
|
context="{'group_by': 'facility_id'}"/>
|
||||||
|
<filter string="Status" name="group_state"
|
||||||
|
context="{'group_by': 'state'}"/>
|
||||||
|
<filter string="Technician" name="group_technician"
|
||||||
|
context="{'group_by': 'technician_id'}"/>
|
||||||
|
<filter string="Scheduled Month" name="group_month"
|
||||||
|
context="{'group_by': 'scheduled_date:month'}"/>
|
||||||
|
</search>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- ================================================================== -->
|
||||||
|
<!-- ACTIONS -->
|
||||||
|
<!-- ================================================================== -->
|
||||||
|
<record id="action_ltc_cleanups" model="ir.actions.act_window">
|
||||||
|
<field name="name">Cleanup Schedule</field>
|
||||||
|
<field name="res_model">fusion.ltc.cleanup</field>
|
||||||
|
<field name="view_mode">list,kanban,form</field>
|
||||||
|
<field name="search_view_id" ref="view_ltc_cleanup_search"/>
|
||||||
|
<field name="help" type="html">
|
||||||
|
<p class="o_view_nocontent_smiling_face">
|
||||||
|
No cleanups scheduled yet
|
||||||
|
</p>
|
||||||
|
<p>Cleanups are auto-scheduled based on facility settings, or can be created manually.</p>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
299
fusion_claims/views/ltc_facility_views.xml
Normal file
299
fusion_claims/views/ltc_facility_views.xml
Normal file
@@ -0,0 +1,299 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<!-- ================================================================== -->
|
||||||
|
<!-- FACILITY - FORM VIEW -->
|
||||||
|
<!-- ================================================================== -->
|
||||||
|
<record id="view_ltc_facility_form" model="ir.ui.view">
|
||||||
|
<field name="name">fusion.ltc.facility.form</field>
|
||||||
|
<field name="model">fusion.ltc.facility</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="LTC Facility">
|
||||||
|
<header/>
|
||||||
|
<sheet>
|
||||||
|
<widget name="web_ribbon" text="Archived" bg_color="text-bg-danger"
|
||||||
|
invisible="active"/>
|
||||||
|
<field name="active" invisible="1"/>
|
||||||
|
<div class="oe_button_box" name="button_box">
|
||||||
|
<button name="action_view_repairs" type="object"
|
||||||
|
class="oe_stat_button" icon="fa-wrench">
|
||||||
|
<div class="o_field_widget o_stat_info">
|
||||||
|
<span class="o_stat_value">
|
||||||
|
<field name="active_repair_count"/>
|
||||||
|
</span>
|
||||||
|
<span class="o_stat_text">Active Repairs</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
<button name="action_view_repairs" type="object"
|
||||||
|
class="oe_stat_button" icon="fa-list">
|
||||||
|
<div class="o_field_widget o_stat_info">
|
||||||
|
<span class="o_stat_value">
|
||||||
|
<field name="repair_count"/>
|
||||||
|
</span>
|
||||||
|
<span class="o_stat_text">Total Repairs</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
<button name="action_view_cleanups" type="object"
|
||||||
|
class="oe_stat_button" icon="fa-refresh">
|
||||||
|
<div class="o_field_widget o_stat_info">
|
||||||
|
<span class="o_stat_value">
|
||||||
|
<field name="cleanup_count"/>
|
||||||
|
</span>
|
||||||
|
<span class="o_stat_text">Cleanups</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<field name="image_1920" widget="image" class="oe_avatar"
|
||||||
|
options="{'preview_image': 'image_128'}"/>
|
||||||
|
<div class="oe_title">
|
||||||
|
<label for="name"/>
|
||||||
|
<h1>
|
||||||
|
<field name="name" placeholder="Facility Name"/>
|
||||||
|
</h1>
|
||||||
|
<field name="code" readonly="1"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<group>
|
||||||
|
<group string="Facility Details">
|
||||||
|
<field name="partner_id"/>
|
||||||
|
<field name="phone" widget="phone"/>
|
||||||
|
<field name="email" widget="email"/>
|
||||||
|
<field name="website" widget="url"/>
|
||||||
|
</group>
|
||||||
|
<group string="Address">
|
||||||
|
<field name="street"/>
|
||||||
|
<field name="street2"/>
|
||||||
|
<field name="city"/>
|
||||||
|
<field name="state_id"/>
|
||||||
|
<field name="zip"/>
|
||||||
|
<field name="country_id"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
|
||||||
|
<notebook>
|
||||||
|
<page string="Key Contacts" name="contacts">
|
||||||
|
<group>
|
||||||
|
<group>
|
||||||
|
<field name="director_of_care_id"/>
|
||||||
|
<field name="service_supervisor_id"/>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<field name="physiotherapist_ids" widget="many2many_tags"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
</page>
|
||||||
|
|
||||||
|
<page string="Floors & Stations" name="structure">
|
||||||
|
<group>
|
||||||
|
<field name="number_of_floors"/>
|
||||||
|
</group>
|
||||||
|
<field name="floor_ids">
|
||||||
|
<list editable="bottom">
|
||||||
|
<field name="sequence" widget="handle"/>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="head_nurse_id"/>
|
||||||
|
<field name="physiotherapist_id"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</page>
|
||||||
|
|
||||||
|
<page string="Nursing Stations" name="stations">
|
||||||
|
<field name="floor_ids" mode="list">
|
||||||
|
<list>
|
||||||
|
<field name="name" string="Floor"/>
|
||||||
|
<field name="station_ids" widget="many2many_tags" string="Stations"/>
|
||||||
|
<field name="head_nurse_id"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</page>
|
||||||
|
|
||||||
|
<page string="Contract" name="contract">
|
||||||
|
<group>
|
||||||
|
<group>
|
||||||
|
<field name="contract_start_date"/>
|
||||||
|
<field name="contract_end_date"/>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<field name="cleanup_frequency"/>
|
||||||
|
<field name="cleanup_interval_days"
|
||||||
|
invisible="cleanup_frequency != 'custom'"/>
|
||||||
|
<field name="next_cleanup_date"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
|
||||||
|
<separator string="Contract Document"/>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="d-flex align-items-center gap-2">
|
||||||
|
<field name="contract_file"
|
||||||
|
filename="contract_file_filename"
|
||||||
|
widget="binary" nolabel="1"/>
|
||||||
|
<field name="contract_file_filename" invisible="1"/>
|
||||||
|
<button name="action_preview_contract" type="object"
|
||||||
|
class="btn btn-secondary"
|
||||||
|
icon="fa-eye"
|
||||||
|
string="Preview"
|
||||||
|
invisible="not contract_file"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<field name="contract_notes" placeholder="Contract details and notes..."/>
|
||||||
|
</page>
|
||||||
|
|
||||||
|
<page string="Notes" name="notes">
|
||||||
|
<field name="notes"/>
|
||||||
|
</page>
|
||||||
|
</notebook>
|
||||||
|
</sheet>
|
||||||
|
<chatter/>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- ================================================================== -->
|
||||||
|
<!-- FACILITY - LIST VIEW -->
|
||||||
|
<!-- ================================================================== -->
|
||||||
|
<record id="view_ltc_facility_list" model="ir.ui.view">
|
||||||
|
<field name="name">fusion.ltc.facility.list</field>
|
||||||
|
<field name="model">fusion.ltc.facility</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<list default_order="name">
|
||||||
|
<field name="code" optional="show"/>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="city"/>
|
||||||
|
<field name="phone" optional="show"/>
|
||||||
|
<field name="director_of_care_id" optional="show"/>
|
||||||
|
<field name="service_supervisor_id" optional="hide"/>
|
||||||
|
<field name="number_of_floors" optional="hide"/>
|
||||||
|
<field name="contract_start_date" optional="hide"/>
|
||||||
|
<field name="contract_end_date" optional="hide"/>
|
||||||
|
<field name="cleanup_frequency" optional="show"/>
|
||||||
|
<field name="next_cleanup_date" optional="show"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- ================================================================== -->
|
||||||
|
<!-- FACILITY - SEARCH VIEW -->
|
||||||
|
<!-- ================================================================== -->
|
||||||
|
<record id="view_ltc_facility_search" model="ir.ui.view">
|
||||||
|
<field name="name">fusion.ltc.facility.search</field>
|
||||||
|
<field name="model">fusion.ltc.facility</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<search string="Search Facilities">
|
||||||
|
<field name="name" string="Facility"/>
|
||||||
|
<field name="code"/>
|
||||||
|
<field name="city"/>
|
||||||
|
<field name="director_of_care_id"/>
|
||||||
|
<separator/>
|
||||||
|
<filter string="Active" name="filter_active"
|
||||||
|
domain="[('active', '=', True)]"/>
|
||||||
|
<filter string="Archived" name="filter_archived"
|
||||||
|
domain="[('active', '=', False)]"/>
|
||||||
|
<separator/>
|
||||||
|
<filter string="Has Repairs" name="filter_has_repairs"
|
||||||
|
domain="[('repair_ids', '!=', False)]"/>
|
||||||
|
<filter string="Cleanup Due" name="filter_cleanup_due"
|
||||||
|
domain="[('next_cleanup_date', '<=', (context_today() + datetime.timedelta(days=7)).strftime('%Y-%m-%d')),
|
||||||
|
('next_cleanup_date', '!=', False)]"/>
|
||||||
|
<separator/>
|
||||||
|
<filter string="City" name="group_city" context="{'group_by': 'city'}"/>
|
||||||
|
<filter string="Cleanup Frequency" name="group_frequency"
|
||||||
|
context="{'group_by': 'cleanup_frequency'}"/>
|
||||||
|
</search>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- ================================================================== -->
|
||||||
|
<!-- STATION - FORM VIEW (for inline editing within floors) -->
|
||||||
|
<!-- ================================================================== -->
|
||||||
|
<record id="view_ltc_station_form" model="ir.ui.view">
|
||||||
|
<field name="name">fusion.ltc.station.form</field>
|
||||||
|
<field name="model">fusion.ltc.station</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="Nursing Station">
|
||||||
|
<sheet>
|
||||||
|
<group>
|
||||||
|
<group>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="sequence"/>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<field name="head_nurse_id"/>
|
||||||
|
<field name="phone" widget="phone"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
</sheet>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- ================================================================== -->
|
||||||
|
<!-- STATION - LIST VIEW -->
|
||||||
|
<!-- ================================================================== -->
|
||||||
|
<record id="view_ltc_station_list" model="ir.ui.view">
|
||||||
|
<field name="name">fusion.ltc.station.list</field>
|
||||||
|
<field name="model">fusion.ltc.station</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<list string="Nursing Stations" editable="bottom">
|
||||||
|
<field name="sequence" widget="handle"/>
|
||||||
|
<field name="floor_id"/>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="head_nurse_id"/>
|
||||||
|
<field name="phone"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- ================================================================== -->
|
||||||
|
<!-- FLOOR - FORM VIEW (with embedded stations) -->
|
||||||
|
<!-- ================================================================== -->
|
||||||
|
<record id="view_ltc_floor_form" model="ir.ui.view">
|
||||||
|
<field name="name">fusion.ltc.floor.form</field>
|
||||||
|
<field name="model">fusion.ltc.floor</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="Floor">
|
||||||
|
<sheet>
|
||||||
|
<group>
|
||||||
|
<group>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="sequence"/>
|
||||||
|
<field name="facility_id" invisible="1"/>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<field name="head_nurse_id"/>
|
||||||
|
<field name="physiotherapist_id"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
<field name="station_ids">
|
||||||
|
<list editable="bottom">
|
||||||
|
<field name="sequence" widget="handle"/>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="head_nurse_id"/>
|
||||||
|
<field name="phone"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</sheet>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- ================================================================== -->
|
||||||
|
<!-- ACTIONS -->
|
||||||
|
<!-- ================================================================== -->
|
||||||
|
<record id="action_ltc_facilities" model="ir.actions.act_window">
|
||||||
|
<field name="name">LTC Facilities</field>
|
||||||
|
<field name="res_model">fusion.ltc.facility</field>
|
||||||
|
<field name="view_mode">list,form</field>
|
||||||
|
<field name="search_view_id" ref="view_ltc_facility_search"/>
|
||||||
|
<field name="context">{'search_default_filter_active': 1}</field>
|
||||||
|
<field name="help" type="html">
|
||||||
|
<p class="o_view_nocontent_smiling_face">
|
||||||
|
Add your first LTC Facility
|
||||||
|
</p>
|
||||||
|
<p>Create a facility record to start managing repairs and cleanups.</p>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
148
fusion_claims/views/ltc_form_submission_views.xml
Normal file
148
fusion_claims/views/ltc_form_submission_views.xml
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<!-- ================================================================== -->
|
||||||
|
<!-- FORM SUBMISSION - LIST VIEW -->
|
||||||
|
<!-- ================================================================== -->
|
||||||
|
<record id="view_ltc_form_submission_list" model="ir.ui.view">
|
||||||
|
<field name="name">fusion.ltc.form.submission.list</field>
|
||||||
|
<field name="model">fusion.ltc.form.submission</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<list string="Form Submissions" default_order="submitted_date desc"
|
||||||
|
decoration-danger="is_emergency"
|
||||||
|
decoration-info="status == 'submitted'"
|
||||||
|
decoration-success="status == 'processed'">
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="form_type"/>
|
||||||
|
<field name="facility_id"/>
|
||||||
|
<field name="client_name"/>
|
||||||
|
<field name="room_number"/>
|
||||||
|
<field name="product_serial" optional="show"/>
|
||||||
|
<field name="is_emergency"/>
|
||||||
|
<field name="submitted_date"/>
|
||||||
|
<field name="ip_address" optional="hide"/>
|
||||||
|
<field name="repair_id" optional="show"/>
|
||||||
|
<field name="status" widget="badge"
|
||||||
|
decoration-info="status == 'submitted'"
|
||||||
|
decoration-success="status == 'processed'"
|
||||||
|
decoration-danger="status == 'rejected'"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- ================================================================== -->
|
||||||
|
<!-- FORM SUBMISSION - FORM VIEW -->
|
||||||
|
<!-- ================================================================== -->
|
||||||
|
<record id="view_ltc_form_submission_form" model="ir.ui.view">
|
||||||
|
<field name="name">fusion.ltc.form.submission.form</field>
|
||||||
|
<field name="model">fusion.ltc.form.submission</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="Form Submission">
|
||||||
|
<sheet>
|
||||||
|
<div class="oe_button_box" name="button_box">
|
||||||
|
<button name="action_view_repair" type="object"
|
||||||
|
class="oe_stat_button" icon="fa-wrench"
|
||||||
|
invisible="not repair_id">
|
||||||
|
<div class="o_field_widget o_stat_info">
|
||||||
|
<span class="o_stat_text">Repair</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="oe_title">
|
||||||
|
<label for="name"/>
|
||||||
|
<h1>
|
||||||
|
<field name="name" readonly="1"/>
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
<group>
|
||||||
|
<group>
|
||||||
|
<field name="form_type"/>
|
||||||
|
<field name="facility_id"/>
|
||||||
|
<field name="client_name"/>
|
||||||
|
<field name="room_number"/>
|
||||||
|
<field name="product_serial"/>
|
||||||
|
<field name="is_emergency"/>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<field name="status"/>
|
||||||
|
<field name="submitted_date"/>
|
||||||
|
<field name="ip_address"/>
|
||||||
|
<field name="repair_id"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
</sheet>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- ================================================================== -->
|
||||||
|
<!-- FORM SUBMISSION - SEARCH VIEW -->
|
||||||
|
<!-- ================================================================== -->
|
||||||
|
<record id="view_ltc_form_submission_search" model="ir.ui.view">
|
||||||
|
<field name="name">fusion.ltc.form.submission.search</field>
|
||||||
|
<field name="model">fusion.ltc.form.submission</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<search string="Search Submissions">
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="client_name"/>
|
||||||
|
<field name="facility_id"/>
|
||||||
|
<field name="room_number"/>
|
||||||
|
<separator/>
|
||||||
|
<filter string="Emergency" name="filter_emergency"
|
||||||
|
domain="[('is_emergency', '=', True)]"/>
|
||||||
|
<separator/>
|
||||||
|
<filter string="Submitted" name="filter_submitted"
|
||||||
|
domain="[('status', '=', 'submitted')]"/>
|
||||||
|
<filter string="Processed" name="filter_processed"
|
||||||
|
domain="[('status', '=', 'processed')]"/>
|
||||||
|
<filter string="Rejected" name="filter_rejected"
|
||||||
|
domain="[('status', '=', 'rejected')]"/>
|
||||||
|
<separator/>
|
||||||
|
<filter string="Today" name="filter_today"
|
||||||
|
domain="[('submitted_date', '>=', (context_today()).strftime('%Y-%m-%d'))]"/>
|
||||||
|
<filter string="This Week" name="filter_week"
|
||||||
|
domain="[('submitted_date', '>=', (context_today() - datetime.timedelta(days=7)).strftime('%Y-%m-%d'))]"/>
|
||||||
|
<filter string="This Month" name="filter_month"
|
||||||
|
domain="[('submitted_date', '>=', (context_today()).strftime('%Y-%m-01'))]"/>
|
||||||
|
<separator/>
|
||||||
|
<filter string="Facility" name="group_facility"
|
||||||
|
context="{'group_by': 'facility_id'}"/>
|
||||||
|
<filter string="Form Type" name="group_type"
|
||||||
|
context="{'group_by': 'form_type'}"/>
|
||||||
|
<filter string="Status" name="group_status"
|
||||||
|
context="{'group_by': 'status'}"/>
|
||||||
|
<filter string="Submitted Date" name="group_date"
|
||||||
|
context="{'group_by': 'submitted_date:day'}"/>
|
||||||
|
</search>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- ================================================================== -->
|
||||||
|
<!-- ACTIONS -->
|
||||||
|
<!-- ================================================================== -->
|
||||||
|
<record id="action_ltc_form_submissions" model="ir.actions.act_window">
|
||||||
|
<field name="name">Form Submissions</field>
|
||||||
|
<field name="res_model">fusion.ltc.form.submission</field>
|
||||||
|
<field name="view_mode">list,form</field>
|
||||||
|
<field name="search_view_id" ref="view_ltc_form_submission_search"/>
|
||||||
|
<field name="help" type="html">
|
||||||
|
<p class="o_view_nocontent_smiling_face">
|
||||||
|
No form submissions yet
|
||||||
|
</p>
|
||||||
|
<p>Submissions from portal repair forms will appear here.</p>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- SEQUENCE -->
|
||||||
|
<data noupdate="1">
|
||||||
|
<record id="seq_ltc_form_submission" model="ir.sequence">
|
||||||
|
<field name="name">LTC Form Submission</field>
|
||||||
|
<field name="code">fusion.ltc.form.submission</field>
|
||||||
|
<field name="prefix">LTC-SUB-</field>
|
||||||
|
<field name="padding">5</field>
|
||||||
|
<field name="number_next">1</field>
|
||||||
|
<field name="number_increment">1</field>
|
||||||
|
</record>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
375
fusion_claims/views/ltc_repair_views.xml
Normal file
375
fusion_claims/views/ltc_repair_views.xml
Normal file
@@ -0,0 +1,375 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<!-- ================================================================== -->
|
||||||
|
<!-- REPAIR - FORM VIEW -->
|
||||||
|
<!-- ================================================================== -->
|
||||||
|
<record id="view_ltc_repair_form" model="ir.ui.view">
|
||||||
|
<field name="name">fusion.ltc.repair.form</field>
|
||||||
|
<field name="model">fusion.ltc.repair</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="Repair Request">
|
||||||
|
<header>
|
||||||
|
<field name="stage_id" widget="statusbar"
|
||||||
|
options="{'clickable': '1'}"/>
|
||||||
|
</header>
|
||||||
|
<sheet>
|
||||||
|
<div class="oe_button_box" name="button_box">
|
||||||
|
<button name="action_view_sale_order" type="object"
|
||||||
|
class="oe_stat_button" icon="fa-shopping-cart"
|
||||||
|
invisible="not sale_order_id">
|
||||||
|
<div class="o_field_widget o_stat_info">
|
||||||
|
<span class="o_stat_value">
|
||||||
|
<field name="sale_order_name"/>
|
||||||
|
</span>
|
||||||
|
<span class="o_stat_text">Sale Order</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
<button name="action_view_task" type="object"
|
||||||
|
class="oe_stat_button" icon="fa-tasks"
|
||||||
|
invisible="not task_id">
|
||||||
|
<div class="o_field_widget o_stat_info">
|
||||||
|
<span class="o_stat_text">Task</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<widget name="web_ribbon" text="Emergency" bg_color="text-bg-danger"
|
||||||
|
invisible="not is_emergency"/>
|
||||||
|
<div class="oe_title">
|
||||||
|
<label for="name"/>
|
||||||
|
<h1>
|
||||||
|
<field name="name" readonly="1"/>
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<group>
|
||||||
|
<group string="Repair Details">
|
||||||
|
<field name="facility_id"/>
|
||||||
|
<field name="client_id"/>
|
||||||
|
<field name="client_name" invisible="client_id"/>
|
||||||
|
<field name="room_number"/>
|
||||||
|
<field name="is_emergency"/>
|
||||||
|
<field name="priority" widget="priority"/>
|
||||||
|
</group>
|
||||||
|
<group string="Product & Assignment">
|
||||||
|
<field name="product_serial"/>
|
||||||
|
<field name="product_id"/>
|
||||||
|
<field name="assigned_technician_id"/>
|
||||||
|
<field name="source"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
|
||||||
|
<notebook>
|
||||||
|
<page string="Issue" name="issue">
|
||||||
|
<group>
|
||||||
|
<group>
|
||||||
|
<field name="issue_reported_date"/>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<field name="issue_fixed_date"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
<label for="issue_description"/>
|
||||||
|
<field name="issue_description"
|
||||||
|
placeholder="Describe the issue in detail..."/>
|
||||||
|
<label for="resolution_description"/>
|
||||||
|
<field name="resolution_description"
|
||||||
|
placeholder="How was the issue resolved?"/>
|
||||||
|
</page>
|
||||||
|
|
||||||
|
<page string="Family/POA" name="poa">
|
||||||
|
<group>
|
||||||
|
<group>
|
||||||
|
<field name="poa_name"/>
|
||||||
|
<field name="poa_phone" widget="phone"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
</page>
|
||||||
|
|
||||||
|
<page string="Financial" name="financial">
|
||||||
|
<group>
|
||||||
|
<group>
|
||||||
|
<field name="sale_order_id"/>
|
||||||
|
<field name="task_id"/>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<field name="currency_id" invisible="1"/>
|
||||||
|
<field name="company_id" invisible="1"/>
|
||||||
|
<field name="repair_value"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
<button name="action_create_sale_order" type="object"
|
||||||
|
string="Create Sale Order" class="btn-primary"
|
||||||
|
invisible="sale_order_id"/>
|
||||||
|
</page>
|
||||||
|
|
||||||
|
<page string="Photos & Notes" name="photos">
|
||||||
|
<div class="fc-gallery-content">
|
||||||
|
<separator string="Before Photos (Reported Condition)"/>
|
||||||
|
<field name="before_photo_ids" widget="many2many_binary"/>
|
||||||
|
<separator string="After Photos (Completed Repair)"/>
|
||||||
|
<field name="after_photo_ids" widget="many2many_binary"/>
|
||||||
|
</div>
|
||||||
|
<field name="photo_ids" invisible="1"/>
|
||||||
|
<separator string="Internal Notes"/>
|
||||||
|
<field name="notes" placeholder="Internal notes..."/>
|
||||||
|
</page>
|
||||||
|
</notebook>
|
||||||
|
</sheet>
|
||||||
|
<chatter/>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- ================================================================== -->
|
||||||
|
<!-- REPAIR - KANBAN VIEW -->
|
||||||
|
<!-- ================================================================== -->
|
||||||
|
<record id="view_ltc_repair_kanban" model="ir.ui.view">
|
||||||
|
<field name="name">fusion.ltc.repair.kanban</field>
|
||||||
|
<field name="model">fusion.ltc.repair</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<kanban default_group_by="stage_id"
|
||||||
|
default_order="is_emergency desc, issue_reported_date desc"
|
||||||
|
highlight_color="color"
|
||||||
|
class="o_kanban_small_column">
|
||||||
|
<field name="color"/>
|
||||||
|
<field name="is_emergency"/>
|
||||||
|
<field name="stage_id"/>
|
||||||
|
<field name="kanban_state"/>
|
||||||
|
<field name="stage_color"/>
|
||||||
|
<progressbar field="kanban_state"
|
||||||
|
colors='{"normal": "200", "done": "success", "blocked": "danger"}'/>
|
||||||
|
<templates>
|
||||||
|
<t t-name="menu">
|
||||||
|
<a t-if="widget.editable" role="menuitem" type="open"
|
||||||
|
class="dropdown-item">Edit</a>
|
||||||
|
<a t-if="widget.deletable" role="menuitem" type="delete"
|
||||||
|
class="dropdown-item">Delete</a>
|
||||||
|
<field widget="kanban_color_picker" name="color"/>
|
||||||
|
</t>
|
||||||
|
<t t-name="card" class="flex-row">
|
||||||
|
<main t-att-data-stage="record.stage_color.raw_value || 'secondary'"
|
||||||
|
t-att-data-emergency="record.is_emergency.raw_value ? '1' : '0'"
|
||||||
|
t-att-data-priority="record.priority.raw_value">
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<field name="name" class="fw-bold text-primary"/>
|
||||||
|
<span t-attf-style="#{record.priority.raw_value == '1' ? 'background: rgba(255,152,0,0.1); border-radius: 4px; padding: 1px 5px;' : ''}">
|
||||||
|
<field name="priority" widget="priority"/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="mt-1">
|
||||||
|
<span class="fw-bold">
|
||||||
|
<field name="display_client_name"/>
|
||||||
|
</span>
|
||||||
|
<span t-if="record.room_number.raw_value"
|
||||||
|
class="ms-1 badge text-bg-light">
|
||||||
|
Rm <field name="room_number"/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="text-muted small">
|
||||||
|
<field name="facility_id"/>
|
||||||
|
</div>
|
||||||
|
<div t-if="record.is_emergency.raw_value"
|
||||||
|
class="mt-1">
|
||||||
|
<span class="badge text-bg-danger">
|
||||||
|
<i class="fa fa-exclamation-triangle me-1"/>Emergency
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div t-if="record.product_serial.raw_value"
|
||||||
|
class="text-muted small mt-1">
|
||||||
|
<i class="fa fa-barcode me-1"/>S/N: <field name="product_serial"/>
|
||||||
|
</div>
|
||||||
|
<footer class="mt-2 pt-1 border-top">
|
||||||
|
<span class="text-muted small">
|
||||||
|
<i class="fa fa-calendar me-1"/><field name="issue_reported_date"/>
|
||||||
|
</span>
|
||||||
|
<div class="ms-auto d-flex align-items-center">
|
||||||
|
<field name="kanban_state" widget="state_selection"/>
|
||||||
|
<field name="assigned_technician_id"
|
||||||
|
widget="many2one_avatar_user"
|
||||||
|
class="ms-1"/>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</main>
|
||||||
|
</t>
|
||||||
|
</templates>
|
||||||
|
</kanban>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- ================================================================== -->
|
||||||
|
<!-- REPAIR - LIST VIEW -->
|
||||||
|
<!-- ================================================================== -->
|
||||||
|
<record id="view_ltc_repair_list" model="ir.ui.view">
|
||||||
|
<field name="name">fusion.ltc.repair.list</field>
|
||||||
|
<field name="model">fusion.ltc.repair</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<list string="Repair Requests" default_order="issue_reported_date desc"
|
||||||
|
decoration-danger="is_emergency">
|
||||||
|
<field name="stage_color" column_invisible="1"/>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="facility_id"/>
|
||||||
|
<field name="display_client_name" string="Client"/>
|
||||||
|
<field name="room_number"/>
|
||||||
|
<field name="product_serial" optional="show"/>
|
||||||
|
<field name="issue_reported_date"/>
|
||||||
|
<field name="issue_fixed_date" optional="show"/>
|
||||||
|
<field name="stage_id" widget="badge"
|
||||||
|
decoration-info="stage_color == 'info'"
|
||||||
|
decoration-warning="stage_color == 'warning'"
|
||||||
|
decoration-success="stage_color == 'success'"
|
||||||
|
decoration-danger="stage_color == 'danger'"
|
||||||
|
optional="show"/>
|
||||||
|
<field name="is_emergency" string="Emergency" optional="show"
|
||||||
|
widget="boolean_toggle"/>
|
||||||
|
<field name="assigned_technician_id" widget="many2one_avatar_user"
|
||||||
|
optional="show"/>
|
||||||
|
<field name="sale_order_id" optional="hide"/>
|
||||||
|
<field name="repair_value" optional="hide"/>
|
||||||
|
<field name="source" optional="hide"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- ================================================================== -->
|
||||||
|
<!-- REPAIR - SEARCH VIEW -->
|
||||||
|
<!-- ================================================================== -->
|
||||||
|
<record id="view_ltc_repair_search" model="ir.ui.view">
|
||||||
|
<field name="name">fusion.ltc.repair.search</field>
|
||||||
|
<field name="model">fusion.ltc.repair</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<search string="Search Repairs">
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="display_client_name" string="Client"/>
|
||||||
|
<field name="room_number"/>
|
||||||
|
<field name="product_serial"/>
|
||||||
|
<field name="facility_id"/>
|
||||||
|
<field name="assigned_technician_id"/>
|
||||||
|
<separator/>
|
||||||
|
<filter string="Emergency" name="filter_emergency"
|
||||||
|
domain="[('is_emergency', '=', True)]"/>
|
||||||
|
<separator/>
|
||||||
|
<filter string="New" name="filter_new"
|
||||||
|
domain="[('stage_id.sequence', '=', 1)]"/>
|
||||||
|
<filter string="In Progress" name="filter_in_progress"
|
||||||
|
domain="[('stage_id.fold', '=', False), ('stage_id.sequence', '>', 1)]"/>
|
||||||
|
<filter string="Completed" name="filter_completed"
|
||||||
|
domain="[('stage_id.fold', '=', True)]"/>
|
||||||
|
<separator/>
|
||||||
|
<filter string="Has Sale Order" name="filter_has_so"
|
||||||
|
domain="[('sale_order_id', '!=', False)]"/>
|
||||||
|
<filter string="No Sale Order" name="filter_no_so"
|
||||||
|
domain="[('sale_order_id', '=', False)]"/>
|
||||||
|
<separator/>
|
||||||
|
<filter string="This Month" name="filter_this_month"
|
||||||
|
domain="[('issue_reported_date', '>=', (context_today()).strftime('%Y-%m-01'))]"/>
|
||||||
|
<filter string="Last 30 Days" name="filter_30d"
|
||||||
|
domain="[('issue_reported_date', '>=', (context_today() - datetime.timedelta(days=30)).strftime('%Y-%m-%d'))]"/>
|
||||||
|
<filter string="This Year" name="filter_this_year"
|
||||||
|
domain="[('issue_reported_date', '>=', (context_today()).strftime('%Y-01-01'))]"/>
|
||||||
|
<separator/>
|
||||||
|
<filter string="Facility" name="group_facility"
|
||||||
|
context="{'group_by': 'facility_id'}"/>
|
||||||
|
<filter string="Stage" name="group_stage"
|
||||||
|
context="{'group_by': 'stage_id'}"/>
|
||||||
|
<filter string="Technician" name="group_technician"
|
||||||
|
context="{'group_by': 'assigned_technician_id'}"/>
|
||||||
|
<filter string="Source" name="group_source"
|
||||||
|
context="{'group_by': 'source'}"/>
|
||||||
|
<filter string="Reported Month" name="group_month"
|
||||||
|
context="{'group_by': 'issue_reported_date:month'}"/>
|
||||||
|
<filter string="Fixed Month" name="group_fixed_month"
|
||||||
|
context="{'group_by': 'issue_fixed_date:month'}"/>
|
||||||
|
</search>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- ================================================================== -->
|
||||||
|
<!-- REPAIR STAGE - LIST + FORM (Configuration) -->
|
||||||
|
<!-- ================================================================== -->
|
||||||
|
<record id="view_ltc_repair_stage_list" model="ir.ui.view">
|
||||||
|
<field name="name">fusion.ltc.repair.stage.list</field>
|
||||||
|
<field name="model">fusion.ltc.repair.stage</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<list string="Repair Stages" editable="bottom">
|
||||||
|
<field name="sequence" widget="handle"/>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="color"/>
|
||||||
|
<field name="fold"/>
|
||||||
|
<field name="description" optional="hide"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="view_ltc_repair_stage_form" model="ir.ui.view">
|
||||||
|
<field name="name">fusion.ltc.repair.stage.form</field>
|
||||||
|
<field name="model">fusion.ltc.repair.stage</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="Repair Stage">
|
||||||
|
<sheet>
|
||||||
|
<group>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="sequence"/>
|
||||||
|
<field name="color"/>
|
||||||
|
<field name="fold"/>
|
||||||
|
<field name="description"/>
|
||||||
|
</group>
|
||||||
|
</sheet>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- ================================================================== -->
|
||||||
|
<!-- ACTIONS -->
|
||||||
|
<!-- ================================================================== -->
|
||||||
|
<record id="action_ltc_repairs_kanban" model="ir.actions.act_window">
|
||||||
|
<field name="name">Repair Overview</field>
|
||||||
|
<field name="res_model">fusion.ltc.repair</field>
|
||||||
|
<field name="view_mode">kanban,list,form</field>
|
||||||
|
<field name="search_view_id" ref="view_ltc_repair_search"/>
|
||||||
|
<field name="help" type="html">
|
||||||
|
<p class="o_view_nocontent_smiling_face">
|
||||||
|
No repair requests yet
|
||||||
|
</p>
|
||||||
|
<p>Repair requests submitted via the portal form or manually will appear here.</p>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="action_ltc_repairs_all" model="ir.actions.act_window">
|
||||||
|
<field name="name">All Repairs</field>
|
||||||
|
<field name="res_model">fusion.ltc.repair</field>
|
||||||
|
<field name="view_mode">kanban,list,form</field>
|
||||||
|
<field name="search_view_id" ref="view_ltc_repair_search"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="action_ltc_repairs_new" model="ir.actions.act_window">
|
||||||
|
<field name="name">New / Pending</field>
|
||||||
|
<field name="res_model">fusion.ltc.repair</field>
|
||||||
|
<field name="view_mode">kanban,list,form</field>
|
||||||
|
<field name="search_view_id" ref="view_ltc_repair_search"/>
|
||||||
|
<field name="domain">[('stage_id.sequence', '<=', 2)]</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="action_ltc_repairs_in_progress" model="ir.actions.act_window">
|
||||||
|
<field name="name">In Progress</field>
|
||||||
|
<field name="res_model">fusion.ltc.repair</field>
|
||||||
|
<field name="view_mode">kanban,list,form</field>
|
||||||
|
<field name="search_view_id" ref="view_ltc_repair_search"/>
|
||||||
|
<field name="domain">[('stage_id.fold', '=', False), ('stage_id.sequence', '>', 2)]</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="action_ltc_repairs_completed" model="ir.actions.act_window">
|
||||||
|
<field name="name">Completed</field>
|
||||||
|
<field name="res_model">fusion.ltc.repair</field>
|
||||||
|
<field name="view_mode">kanban,list,form</field>
|
||||||
|
<field name="search_view_id" ref="view_ltc_repair_search"/>
|
||||||
|
<field name="domain">[('stage_id.fold', '=', True)]</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="action_ltc_repair_stages" model="ir.actions.act_window">
|
||||||
|
<field name="name">Repair Stages</field>
|
||||||
|
<field name="res_model">fusion.ltc.repair.stage</field>
|
||||||
|
<field name="view_mode">list,form</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
@@ -500,6 +500,25 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- ===== PORTAL FORMS ===== -->
|
||||||
|
<h2>Portal Forms</h2>
|
||||||
|
<div class="row mt16 o_settings_container">
|
||||||
|
<div class="col-12 col-lg-6 o_setting_box">
|
||||||
|
<div class="o_setting_right_pane">
|
||||||
|
<span class="o_form_label">LTC Form Access Password</span>
|
||||||
|
<div class="text-muted">
|
||||||
|
Set a password to protect the public LTC repair form.
|
||||||
|
Share this with facility staff so they can submit repair requests.
|
||||||
|
Minimum 4 characters. Leave empty to allow unrestricted access.
|
||||||
|
</div>
|
||||||
|
<div class="mt-2">
|
||||||
|
<field name="fc_ltc_form_password"
|
||||||
|
placeholder="e.g. 1234"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Hidden fields for field mappings (still needed for ir.config_parameter storage) -->
|
<!-- Hidden fields for field mappings (still needed for ir.config_parameter storage) -->
|
||||||
<div class="d-none">
|
<div class="d-none">
|
||||||
<field name="fc_field_sale_type"/>
|
<field name="fc_field_sale_type"/>
|
||||||
|
|||||||
@@ -16,6 +16,32 @@
|
|||||||
<field name="x_fc_contact_type" placeholder="Select contact type..."/>
|
<field name="x_fc_contact_type" placeholder="Select contact type..."/>
|
||||||
</xpath>
|
</xpath>
|
||||||
|
|
||||||
|
<!-- LTC section in notebook -->
|
||||||
|
<xpath expr="//notebook" position="inside">
|
||||||
|
<page string="LTC Home" name="ltc_info"
|
||||||
|
invisible="x_fc_contact_type not in ('long_term_care_home',)">
|
||||||
|
<group string="LTC Facility Information">
|
||||||
|
<group>
|
||||||
|
<field name="x_fc_ltc_facility_id"/>
|
||||||
|
<field name="x_fc_ltc_room_number"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
<group string="Family Contacts">
|
||||||
|
<field name="x_fc_ltc_family_contact_ids" nolabel="1">
|
||||||
|
<list editable="bottom">
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="relationship"/>
|
||||||
|
<field name="phone"/>
|
||||||
|
<field name="phone2"/>
|
||||||
|
<field name="email"/>
|
||||||
|
<field name="is_poa"/>
|
||||||
|
<field name="notes" optional="hide"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</group>
|
||||||
|
</page>
|
||||||
|
</xpath>
|
||||||
|
|
||||||
<!-- ODSP section in notebook -->
|
<!-- ODSP section in notebook -->
|
||||||
<xpath expr="//notebook" position="inside">
|
<xpath expr="//notebook" position="inside">
|
||||||
<page string="ODSP" name="odsp_info"
|
<page string="ODSP" name="odsp_info"
|
||||||
@@ -71,6 +97,8 @@
|
|||||||
domain="[('x_fc_contact_type', 'in', ['odsp_customer', 'adp_odsp_customer'])]"/>
|
domain="[('x_fc_contact_type', 'in', ['odsp_customer', 'adp_odsp_customer'])]"/>
|
||||||
<filter name="filter_odsp_office" string="ODSP Offices"
|
<filter name="filter_odsp_office" string="ODSP Offices"
|
||||||
domain="[('x_fc_contact_type', '=', 'odsp_office')]"/>
|
domain="[('x_fc_contact_type', '=', 'odsp_office')]"/>
|
||||||
|
<filter name="filter_ltc_home" string="LTC Homes"
|
||||||
|
domain="[('x_fc_contact_type', '=', 'long_term_care_home')]"/>
|
||||||
</xpath>
|
</xpath>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|||||||
@@ -163,8 +163,12 @@
|
|||||||
domain="[('x_fc_is_field_staff', '=', True)]"/>
|
domain="[('x_fc_is_field_staff', '=', True)]"/>
|
||||||
<field name="task_type"/>
|
<field name="task_type"/>
|
||||||
<field name="priority" widget="priority"/>
|
<field name="priority" widget="priority"/>
|
||||||
<field name="sale_order_id"/>
|
<field name="facility_id"
|
||||||
<field name="purchase_order_id"/>
|
invisible="task_type != 'ltc_visit'"/>
|
||||||
|
<field name="sale_order_id"
|
||||||
|
invisible="task_type == 'ltc_visit'"/>
|
||||||
|
<field name="purchase_order_id"
|
||||||
|
invisible="task_type == 'ltc_visit'"/>
|
||||||
</group>
|
</group>
|
||||||
<group string="Schedule">
|
<group string="Schedule">
|
||||||
<field name="scheduled_date"/>
|
<field name="scheduled_date"/>
|
||||||
|
|||||||
@@ -30,3 +30,4 @@ from . import odsp_discretionary_wizard
|
|||||||
from . import odsp_pre_approved_wizard
|
from . import odsp_pre_approved_wizard
|
||||||
from . import odsp_ready_delivery_wizard
|
from . import odsp_ready_delivery_wizard
|
||||||
from . import odsp_submit_to_odsp_wizard
|
from . import odsp_submit_to_odsp_wizard
|
||||||
|
from . import ltc_repair_create_so_wizard
|
||||||
48
fusion_claims/wizard/ltc_repair_create_so_wizard.py
Normal file
48
fusion_claims/wizard/ltc_repair_create_so_wizard.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
# -*- 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 LTCRepairCreateSOWizard(models.TransientModel):
|
||||||
|
_name = 'fusion.ltc.repair.create.so.wizard'
|
||||||
|
_description = 'LTC Repair - Link Contact & Create Sale Order'
|
||||||
|
|
||||||
|
repair_id = fields.Many2one(
|
||||||
|
'fusion.ltc.repair',
|
||||||
|
string='Repair Request',
|
||||||
|
required=True,
|
||||||
|
readonly=True,
|
||||||
|
)
|
||||||
|
client_name = fields.Char(
|
||||||
|
string='Client Name',
|
||||||
|
readonly=True,
|
||||||
|
)
|
||||||
|
action_type = fields.Selection([
|
||||||
|
('create_new', 'Create New Contact'),
|
||||||
|
('link_existing', 'Link to Existing Contact'),
|
||||||
|
], string='Action', default='create_new', required=True)
|
||||||
|
partner_id = fields.Many2one(
|
||||||
|
'res.partner',
|
||||||
|
string='Existing Contact',
|
||||||
|
)
|
||||||
|
|
||||||
|
def action_confirm(self):
|
||||||
|
self.ensure_one()
|
||||||
|
repair = self.repair_id
|
||||||
|
|
||||||
|
if self.action_type == 'create_new':
|
||||||
|
if not self.client_name:
|
||||||
|
raise UserError(_('Client name is required to create a new contact.'))
|
||||||
|
partner = self.env['res.partner'].create({
|
||||||
|
'name': self.client_name,
|
||||||
|
})
|
||||||
|
repair.client_id = partner
|
||||||
|
elif self.action_type == 'link_existing':
|
||||||
|
if not self.partner_id:
|
||||||
|
raise UserError(_('Please select an existing contact.'))
|
||||||
|
repair.client_id = self.partner_id
|
||||||
|
|
||||||
|
return repair._create_linked_sale_order()
|
||||||
28
fusion_claims/wizard/ltc_repair_create_so_wizard_views.xml
Normal file
28
fusion_claims/wizard/ltc_repair_create_so_wizard_views.xml
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<record id="view_ltc_repair_create_so_wizard_form" model="ir.ui.view">
|
||||||
|
<field name="name">fusion.ltc.repair.create.so.wizard.form</field>
|
||||||
|
<field name="model">fusion.ltc.repair.create.so.wizard</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="Link Contact">
|
||||||
|
<group>
|
||||||
|
<field name="repair_id" invisible="1"/>
|
||||||
|
<field name="client_name"/>
|
||||||
|
<field name="action_type" widget="radio"/>
|
||||||
|
<field name="partner_id"
|
||||||
|
invisible="action_type != 'link_existing'"
|
||||||
|
required="action_type == 'link_existing'"/>
|
||||||
|
</group>
|
||||||
|
<footer>
|
||||||
|
<button name="action_confirm" type="object"
|
||||||
|
string="Confirm & Create Sale Order"
|
||||||
|
class="btn-primary"/>
|
||||||
|
<button string="Cancel" class="btn-secondary"
|
||||||
|
special="cancel"/>
|
||||||
|
</footer>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
Reference in New Issue
Block a user