270 lines
11 KiB
Python
270 lines
11 KiB
Python
# -*- 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)}
|