diff --git a/fusion_authorizer_portal/__manifest__.py b/fusion_authorizer_portal/__manifest__.py index 283339b..b977607 100644 --- a/fusion_authorizer_portal/__manifest__.py +++ b/fusion_authorizer_portal/__manifest__.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- { 'name': 'Fusion Authorizer & Sales Portal', - 'version': '19.0.2.0.9', + 'version': '19.0.2.2.0', 'category': 'Sales/Portal', 'summary': 'Portal for Authorizers (OTs) and Sales Reps with Assessment Forms', 'description': """ @@ -66,6 +66,7 @@ This module provides external portal access for: 'views/res_partner_views.xml', 'views/sale_order_views.xml', 'views/assessment_views.xml', + 'views/loaner_checkout_views.xml', 'views/pdf_template_views.xml', # Portal Templates 'views/portal_templates.xml', @@ -75,6 +76,7 @@ This module provides external portal access for: 'views/portal_accessibility_forms.xml', 'views/portal_technician_templates.xml', 'views/portal_book_assessment.xml', + 'views/portal_repair_form.xml', ], 'assets': { 'web.assets_backend': [ diff --git a/fusion_authorizer_portal/controllers/__init__.py b/fusion_authorizer_portal/controllers/__init__.py index 81b165c..25941d4 100644 --- a/fusion_authorizer_portal/controllers/__init__.py +++ b/fusion_authorizer_portal/controllers/__init__.py @@ -2,4 +2,5 @@ from . import portal_main from . import portal_assessment -from . import pdf_editor \ No newline at end of file +from . import pdf_editor +from . import portal_repair \ No newline at end of file diff --git a/fusion_authorizer_portal/controllers/portal_repair.py b/fusion_authorizer_portal/controllers/portal_repair.py new file mode 100644 index 0000000..213e442 --- /dev/null +++ b/fusion_authorizer_portal/controllers/portal_repair.py @@ -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') diff --git a/fusion_authorizer_portal/models/__init__.py b/fusion_authorizer_portal/models/__init__.py index 297f041..4109891 100644 --- a/fusion_authorizer_portal/models/__init__.py +++ b/fusion_authorizer_portal/models/__init__.py @@ -7,4 +7,5 @@ from . import adp_document from . import assessment from . import accessibility_assessment from . import sale_order +from . import loaner_checkout from . import pdf_template \ No newline at end of file diff --git a/fusion_authorizer_portal/models/loaner_checkout.py b/fusion_authorizer_portal/models/loaner_checkout.py new file mode 100644 index 0000000..6330b6b --- /dev/null +++ b/fusion_authorizer_portal/models/loaner_checkout.py @@ -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, + } diff --git a/fusion_authorizer_portal/views/assessment_views.xml b/fusion_authorizer_portal/views/assessment_views.xml index afcf13f..0d76272 100644 --- a/fusion_authorizer_portal/views/assessment_views.xml +++ b/fusion_authorizer_portal/views/assessment_views.xml @@ -6,15 +6,26 @@ fusion.assessment.tree fusion.assessment - + + + - - - + + + + @@ -26,124 +37,310 @@
-
- -
- -
+ + + + +

- +

+

+ +

- + + - - - - - - - - - + + + + + + + + - - - - - - - - - - - + - - - - + - - - - - - - - + - + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + - + - + + + - + + + + + + + + + + + + + + + + + + + + - - - - + + + + + + + + + - - + + + - - - - + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -158,13 +355,17 @@ - + + + - + + - + @@ -173,27 +374,21 @@ - + + - - + + + - - - - -
- - - -
+ @@ -207,8 +402,10 @@ + + @@ -216,11 +413,19 @@ + + + + + + + + - + @@ -237,8 +442,8 @@ Create your first assessment

- Assessments are used to record wheelchair specifications and client needs. - Once completed, they will create a draft sale order for review. + Assessments record wheelchair, powerchair, and rollator specifications + along with client needs. Once completed, a draft sale order is created.

@@ -248,7 +453,7 @@ name="Assessments" parent="fusion_claims.menu_adp_claims_root" sequence="42"/> - + + + + + + fusion.loaner.checkout.form.assessment + fusion.loaner.checkout + + + + + + + + + + + + diff --git a/fusion_authorizer_portal/views/portal_repair_form.xml b/fusion_authorizer_portal/views/portal_repair_form.xml new file mode 100644 index 0000000..165e077 --- /dev/null +++ b/fusion_authorizer_portal/views/portal_repair_form.xml @@ -0,0 +1,320 @@ + + + +