238 lines
8.5 KiB
Python
238 lines
8.5 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Copyright 2024-2026 Nexa Systems Inc.
|
|
# License OPL-1 (Odoo Proprietary License v1.0)
|
|
|
|
from odoo import models, fields, api, _
|
|
from odoo.exceptions import UserError
|
|
import logging
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
class LoanerCheckoutWizard(models.TransientModel):
|
|
"""Wizard to checkout loaner equipment."""
|
|
_name = 'fusion.loaner.checkout.wizard'
|
|
_description = 'Loaner Checkout Wizard'
|
|
|
|
# =========================================================================
|
|
# CONTEXT FIELDS
|
|
# =========================================================================
|
|
|
|
sale_order_id = fields.Many2one(
|
|
'sale.order',
|
|
string='Sale Order',
|
|
readonly=True,
|
|
)
|
|
partner_id = fields.Many2one(
|
|
'res.partner',
|
|
string='Client',
|
|
required=True,
|
|
)
|
|
authorizer_id = fields.Many2one(
|
|
'res.partner',
|
|
string='Authorizer',
|
|
)
|
|
|
|
# =========================================================================
|
|
# PRODUCT SELECTION
|
|
# =========================================================================
|
|
|
|
product_id = fields.Many2one(
|
|
'product.product',
|
|
string='Product',
|
|
domain="[('x_fc_can_be_loaned', '=', True)]",
|
|
required=True,
|
|
)
|
|
lot_id = fields.Many2one(
|
|
'stock.lot',
|
|
string='Serial Number',
|
|
domain="[('product_id', '=', product_id)]",
|
|
)
|
|
available_lot_ids = fields.Many2many(
|
|
'stock.lot',
|
|
compute='_compute_available_lots',
|
|
string='Available Serial Numbers',
|
|
)
|
|
|
|
# =========================================================================
|
|
# DATES
|
|
# =========================================================================
|
|
|
|
checkout_date = fields.Date(
|
|
string='Checkout Date',
|
|
required=True,
|
|
default=fields.Date.context_today,
|
|
)
|
|
loaner_period_days = fields.Integer(
|
|
string='Loaner Period (Days)',
|
|
default=7,
|
|
)
|
|
expected_return_date = fields.Date(
|
|
string='Expected Return Date',
|
|
compute='_compute_expected_return',
|
|
)
|
|
|
|
# =========================================================================
|
|
# CONDITION
|
|
# =========================================================================
|
|
|
|
checkout_condition = fields.Selection([
|
|
('excellent', 'Excellent'),
|
|
('good', 'Good'),
|
|
('fair', 'Fair'),
|
|
('needs_repair', 'Needs Repair'),
|
|
], string='Condition', default='excellent', required=True)
|
|
checkout_notes = fields.Text(
|
|
string='Notes',
|
|
)
|
|
|
|
# =========================================================================
|
|
# PHOTOS
|
|
# =========================================================================
|
|
|
|
checkout_photo_ids = fields.Many2many(
|
|
'ir.attachment',
|
|
'loaner_checkout_wizard_photo_rel',
|
|
'wizard_id',
|
|
'attachment_id',
|
|
string='Photos',
|
|
)
|
|
|
|
# =========================================================================
|
|
# DELIVERY
|
|
# =========================================================================
|
|
|
|
delivery_address = fields.Text(
|
|
string='Delivery Address',
|
|
)
|
|
|
|
# =========================================================================
|
|
# COMPUTED
|
|
# =========================================================================
|
|
|
|
@api.depends('product_id')
|
|
def _compute_available_lots(self):
|
|
"""Get available serial numbers for the selected product."""
|
|
for wizard in self:
|
|
if wizard.product_id:
|
|
# Get loaner location
|
|
loaner_location = self.env.ref('fusion_claims.stock_location_loaner', raise_if_not_found=False)
|
|
if loaner_location:
|
|
# Find lots with stock in loaner location
|
|
quants = self.env['stock.quant'].search([
|
|
('product_id', '=', wizard.product_id.id),
|
|
('location_id', '=', loaner_location.id),
|
|
('quantity', '>', 0),
|
|
])
|
|
wizard.available_lot_ids = quants.mapped('lot_id')
|
|
else:
|
|
# Fallback: all lots for product
|
|
wizard.available_lot_ids = self.env['stock.lot'].search([
|
|
('product_id', '=', wizard.product_id.id),
|
|
])
|
|
else:
|
|
wizard.available_lot_ids = False
|
|
|
|
@api.depends('checkout_date', 'loaner_period_days')
|
|
def _compute_expected_return(self):
|
|
from datetime import timedelta
|
|
for wizard in self:
|
|
if wizard.checkout_date and wizard.loaner_period_days:
|
|
wizard.expected_return_date = wizard.checkout_date + timedelta(days=wizard.loaner_period_days)
|
|
else:
|
|
wizard.expected_return_date = False
|
|
|
|
# =========================================================================
|
|
# ONCHANGE
|
|
# =========================================================================
|
|
|
|
@api.onchange('product_id')
|
|
def _onchange_product_id(self):
|
|
if self.product_id:
|
|
self.loaner_period_days = self.product_id.x_fc_loaner_period_days or 7
|
|
self.lot_id = False
|
|
|
|
# =========================================================================
|
|
# DEFAULT GET
|
|
# =========================================================================
|
|
|
|
@api.model
|
|
def default_get(self, fields_list):
|
|
res = super().default_get(fields_list)
|
|
|
|
# Get context
|
|
active_model = self._context.get('active_model')
|
|
active_id = self._context.get('active_id')
|
|
|
|
if active_model == 'sale.order' and active_id:
|
|
order = self.env['sale.order'].browse(active_id)
|
|
res['sale_order_id'] = order.id
|
|
res['partner_id'] = order.partner_id.id
|
|
res['authorizer_id'] = order.x_fc_authorizer_id.id if order.x_fc_authorizer_id else False
|
|
if order.partner_shipping_id:
|
|
res['delivery_address'] = order.partner_shipping_id.contact_address
|
|
|
|
# Get default loaner period from settings
|
|
ICP = self.env['ir.config_parameter'].sudo()
|
|
default_period = int(ICP.get_param('fusion_claims.default_loaner_period_days', '7'))
|
|
res['loaner_period_days'] = default_period
|
|
|
|
return res
|
|
|
|
# =========================================================================
|
|
# ACTION
|
|
# =========================================================================
|
|
|
|
def action_checkout(self):
|
|
"""Create and confirm loaner checkout."""
|
|
self.ensure_one()
|
|
|
|
if not self.product_id:
|
|
raise UserError(_("Please select a product."))
|
|
|
|
# Create persistent attachments for photos
|
|
photo_ids = []
|
|
for photo in self.checkout_photo_ids:
|
|
new_attachment = self.env['ir.attachment'].create({
|
|
'name': photo.name,
|
|
'datas': photo.datas,
|
|
'res_model': 'fusion.loaner.checkout',
|
|
'res_id': 0, # Will update after checkout creation
|
|
})
|
|
photo_ids.append(new_attachment.id)
|
|
|
|
# Create checkout record
|
|
checkout_vals = {
|
|
'sale_order_id': self.sale_order_id.id if self.sale_order_id else False,
|
|
'partner_id': self.partner_id.id,
|
|
'authorizer_id': self.authorizer_id.id if self.authorizer_id else False,
|
|
'sales_rep_id': self.env.user.id,
|
|
'product_id': self.product_id.id,
|
|
'lot_id': self.lot_id.id if self.lot_id else False,
|
|
'checkout_date': self.checkout_date,
|
|
'loaner_period_days': self.loaner_period_days,
|
|
'checkout_condition': self.checkout_condition,
|
|
'checkout_notes': self.checkout_notes,
|
|
'delivery_address': self.delivery_address,
|
|
}
|
|
|
|
checkout = self.env['fusion.loaner.checkout'].create(checkout_vals)
|
|
|
|
# Update photo attachments
|
|
if photo_ids:
|
|
self.env['ir.attachment'].browse(photo_ids).write({'res_id': checkout.id})
|
|
checkout.checkout_photo_ids = [(6, 0, photo_ids)]
|
|
|
|
# Confirm checkout
|
|
checkout.action_checkout()
|
|
|
|
# Return to checkout record
|
|
return {
|
|
'type': 'ir.actions.act_window',
|
|
'name': _('Loaner Checkout'),
|
|
'res_model': 'fusion.loaner.checkout',
|
|
'res_id': checkout.id,
|
|
'view_mode': 'form',
|
|
'target': 'current',
|
|
}
|