# -*- coding: utf-8 -*- # Copyright 2024-2026 Nexa Systems Inc. # License OPL-1 (Odoo Proprietary License v1.0) from odoo import http from odoo.http import request import logging _logger = logging.getLogger(__name__) class LoanerPortalController(http.Controller): @http.route('/my/loaner/categories', type='jsonrpc', auth='user', website=True) def portal_loaner_categories(self, **kw): parent = request.env.ref( 'fusion_loaners_management.product_category_loaner', raise_if_not_found=False, ) if not parent: return [] categories = request.env['product.category'].sudo().search([ ('parent_id', '=', parent.id), ], order='name') return [{'id': c.id, 'name': c.name} for c in categories] @http.route('/my/loaner/products', type='jsonrpc', auth='user', website=True) def portal_loaner_products(self, **kw): domain = [('x_fc_can_be_loaned', '=', True)] category_id = kw.get('category_id') if category_id: domain.append(('categ_id', '=', int(category_id))) products = request.env['product.product'].sudo().search(domain) loaner_location = request.env.ref( 'fusion_loaners_management.stock_location_loaner', raise_if_not_found=False, ) result = [] for p in products: lots = [] if loaner_location: quants = request.env['stock.quant'].sudo().search([ ('product_id', '=', p.id), ('location_id', '=', loaner_location.id), ('quantity', '>', 0), ]) for q in quants: if q.lot_id: lots.append({'id': q.lot_id.id, 'name': q.lot_id.name}) result.append({ 'id': p.id, 'name': p.name, 'category_id': p.categ_id.id, 'period_days': p.product_tmpl_id.x_fc_loaner_period_days or 7, 'lots': lots, }) return result @http.route('/my/loaner/locations', type='jsonrpc', auth='user', website=True) def portal_loaner_locations(self, **kw): locations = request.env['stock.location'].sudo().search([ ('usage', '=', 'internal'), ('company_id', '=', request.env.company.id), ]) return [{'id': loc.id, 'name': loc.complete_name} for loc in locations] @http.route('/my/loaner/equipment-locations', type='jsonrpc', auth='user', website=True) def portal_equipment_locations(self, **kw): """Return current location of all loaner equipment for the portal.""" partner = request.env.user.partner_id if not (hasattr(partner, 'is_sales_rep_portal') and partner.is_sales_rep_portal) and \ not (hasattr(partner, 'is_authorizer') and partner.is_authorizer): return {'error': 'Unauthorized'} products = request.env['product.product'].sudo().search([ ('x_fc_can_be_loaned', '=', True), ]) loaner_location = request.env.ref( 'fusion_loaners_management.stock_location_loaner', raise_if_not_found=False, ) result = [] for p in products: quants = request.env['stock.quant'].sudo().search([ ('product_id', '=', p.id), ('quantity', '>', 0), ]) for q in quants: result.append({ 'product_id': p.id, 'product_name': p.name, 'lot_id': q.lot_id.id if q.lot_id else False, 'serial_number': q.lot_id.name if q.lot_id else '', 'location_id': q.location_id.id, 'location_name': q.location_id.complete_name, 'quantity': q.quantity, 'is_loaner_stock': q.location_id.id == loaner_location.id if loaner_location else False, }) return result @http.route('/my/loaner/checkout', type='jsonrpc', auth='user', website=True) def portal_loaner_checkout(self, **kw): partner = request.env.user.partner_id if not (hasattr(partner, 'is_sales_rep_portal') and partner.is_sales_rep_portal) and \ not (hasattr(partner, 'is_authorizer') and partner.is_authorizer): return {'error': 'Unauthorized'} product_id = int(kw.get('product_id', 0)) lot_id = int(kw.get('lot_id', 0)) if kw.get('lot_id') else False sale_order_id = int(kw.get('sale_order_id', 0)) if kw.get('sale_order_id') else False client_id = int(kw.get('client_id', 0)) if kw.get('client_id') else False loaner_period = int(kw.get('loaner_period_days', 7)) condition = kw.get('checkout_condition', 'good') notes = kw.get('checkout_notes', '') if not product_id: return {'error': 'Product is required'} vals = { 'product_id': product_id, 'loaner_period_days': loaner_period, 'checkout_condition': condition, 'checkout_notes': notes, 'sales_rep_id': request.env.user.id, } if lot_id: vals['lot_id'] = lot_id if sale_order_id: so = request.env['sale.order'].sudo().browse(sale_order_id) if so.exists(): vals['sale_order_id'] = so.id vals['partner_id'] = so.partner_id.id if hasattr(so, 'x_fc_authorizer_id') and so.x_fc_authorizer_id: vals['authorizer_id'] = so.x_fc_authorizer_id.id vals['delivery_address'] = so.partner_shipping_id.contact_address if so.partner_shipping_id else '' if client_id and not vals.get('partner_id'): vals['partner_id'] = client_id if not vals.get('partner_id'): return {'error': 'Client is required'} try: checkout = request.env['fusion.loaner.checkout'].sudo().create(vals) checkout.action_checkout() return { 'success': True, 'checkout_id': checkout.id, 'name': checkout.name, 'message': f'Loaner {checkout.name} checked out successfully', } except Exception as e: _logger.error(f"Loaner checkout error: {e}") return {'error': str(e)} @http.route('/my/loaner/create-product', type='jsonrpc', auth='user', website=True) def portal_loaner_create_product(self, **kw): partner = request.env.user.partner_id if not (hasattr(partner, 'is_sales_rep_portal') and partner.is_sales_rep_portal) and \ not (hasattr(partner, 'is_authorizer') and partner.is_authorizer): return {'error': 'Unauthorized'} product_name = kw.get('product_name', '').strip() serial_number = kw.get('serial_number', '').strip() if not product_name: return {'error': 'Product name is required'} if not serial_number: return {'error': 'Serial number is required'} try: category_id = kw.get('category_id') category = None if category_id: category = request.env['product.category'].sudo().browse(int(category_id)) if not category.exists(): category = None if not category: category = request.env.ref( 'fusion_loaners_management.product_category_loaner', raise_if_not_found=False, ) if not category: category = request.env['product.category'].sudo().search([ ('name', '=', 'Loaner Equipment'), ], limit=1) if not category: category = request.env['product.category'].sudo().create({ 'name': 'Loaner Equipment', }) product_tmpl = request.env['product.template'].sudo().create({ 'name': product_name, 'type': 'consu', 'tracking': 'serial', 'categ_id': category.id, 'x_fc_can_be_loaned': True, 'x_fc_loaner_period_days': 7, 'sale_ok': False, 'purchase_ok': False, }) product = product_tmpl.product_variant_id lot = request.env['stock.lot'].sudo().create({ 'name': serial_number, 'product_id': product.id, 'company_id': request.env.company.id, }) loaner_location = request.env.ref( 'fusion_loaners_management.stock_location_loaner', raise_if_not_found=False, ) if loaner_location: request.env['stock.quant'].sudo().create({ 'product_id': product.id, 'location_id': loaner_location.id, 'lot_id': lot.id, 'quantity': 1, }) return { 'success': True, 'product_id': product.id, 'product_name': product.name, 'lot_id': lot.id, 'lot_name': lot.name, } except Exception as e: _logger.error(f"Loaner product creation error: {e}") return {'error': str(e)} @http.route('/my/loaner/return', type='jsonrpc', auth='user', website=True) def portal_loaner_return(self, **kw): partner = request.env.user.partner_id if not (hasattr(partner, 'is_sales_rep_portal') and partner.is_sales_rep_portal) and \ not (hasattr(partner, 'is_authorizer') and partner.is_authorizer): return {'error': 'Unauthorized'} checkout_id = int(kw.get('checkout_id', 0)) return_condition = kw.get('return_condition', 'good') return_notes = kw.get('return_notes', '') return_location_id = int(kw.get('return_location_id', 0)) if kw.get('return_location_id') else None if not checkout_id: return {'error': 'Checkout ID is required'} try: checkout = request.env['fusion.loaner.checkout'].sudo().browse(checkout_id) if not checkout.exists(): return {'error': 'Checkout not found'} if checkout.state not in ('checked_out', 'overdue', 'rental_pending'): return {'error': 'This loaner is not currently checked out'} checkout.action_process_return( return_condition=return_condition, return_notes=return_notes, return_location_id=return_location_id, ) return { 'success': True, 'message': f'Loaner {checkout.name} returned successfully', } except Exception as e: _logger.error(f"Loaner return error: {e}") return {'error': str(e)}