305 lines
12 KiB
Python
305 lines
12 KiB
Python
# -*- coding: utf-8 -*-
|
|
from odoo import models, fields, api, _
|
|
from odoo.exceptions import UserError
|
|
from odoo.fields import Command
|
|
from markupsafe import Markup
|
|
import logging
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
class ModPcaReceivedWizard(models.TransientModel):
|
|
_name = 'fusion_claims.mod.pca.received.wizard'
|
|
_description = 'MOD - Record PCA Receipt and Create Invoice(s)'
|
|
|
|
sale_order_id = fields.Many2one('sale.order', required=True, readonly=True)
|
|
currency_id = fields.Many2one('res.currency', related='sale_order_id.currency_id')
|
|
order_total = fields.Monetary(
|
|
related='sale_order_id.amount_untaxed', string='Order Subtotal', readonly=True)
|
|
|
|
# PCA Document
|
|
pca_document = fields.Binary(string='PCA Document', required=True)
|
|
pca_filename = fields.Char(string='PCA Filename')
|
|
|
|
# Case details (pre-filled from order, editable)
|
|
hvmp_reference = fields.Char(string='HVMP Reference #')
|
|
case_worker_id = fields.Many2one(
|
|
'res.partner', string='Case Worker',
|
|
help='March of Dimes case worker assigned to this case',
|
|
)
|
|
|
|
# Approval details
|
|
approval_type = fields.Selection([
|
|
('full', 'Full Approval'),
|
|
('partial', 'Partial Approval'),
|
|
], string='Approval Type', required=True, default='full')
|
|
|
|
approved_amount = fields.Monetary(
|
|
string='MOD Approved Amount',
|
|
currency_field='currency_id',
|
|
help='Total amount approved by March of Dimes (before taxes)',
|
|
)
|
|
|
|
# Preview lines (computed when partial)
|
|
preview_line_ids = fields.One2many(
|
|
'fusion_claims.mod.funding.approved.wizard.line', 'wizard_id',
|
|
string='Line Split Preview',
|
|
)
|
|
|
|
@api.model
|
|
def default_get(self, fields_list):
|
|
res = super().default_get(fields_list)
|
|
if self.env.context.get('active_id'):
|
|
order = self.env['sale.order'].browse(self.env.context['active_id'])
|
|
res['sale_order_id'] = order.id
|
|
if order.x_fc_case_reference:
|
|
res['hvmp_reference'] = order.x_fc_case_reference
|
|
if order.x_fc_case_worker:
|
|
res['case_worker_id'] = order.x_fc_case_worker.id
|
|
if order.x_fc_mod_pca_document:
|
|
res['pca_document'] = order.x_fc_mod_pca_document
|
|
res['pca_filename'] = order.x_fc_mod_pca_filename
|
|
if order.x_fc_mod_approved_amount:
|
|
res['approved_amount'] = order.x_fc_mod_approved_amount
|
|
res['approval_type'] = order.x_fc_mod_approval_type or 'full'
|
|
else:
|
|
res['approved_amount'] = order.amount_untaxed
|
|
return res
|
|
|
|
@api.onchange('approval_type', 'approved_amount')
|
|
def _onchange_compute_preview(self):
|
|
"""Compute the proportional split preview when partial approval."""
|
|
order = self.sale_order_id
|
|
if not order:
|
|
return
|
|
|
|
lines = []
|
|
product_lines = order.order_line.filtered(
|
|
lambda l: not l.display_type and l.product_uom_qty > 0 and l.product_id)
|
|
|
|
if self.approval_type == 'partial' and self.approved_amount and self.approved_amount > 0:
|
|
subtotal = order.amount_untaxed
|
|
if subtotal <= 0:
|
|
return
|
|
for line in product_lines:
|
|
ratio = line.price_subtotal / subtotal if subtotal else 0
|
|
mod_amount = round(self.approved_amount * ratio, 2)
|
|
client_amount = round(line.price_subtotal - mod_amount, 2)
|
|
lines.append((0, 0, {
|
|
'product_name': line.product_id.display_name,
|
|
'quantity': line.product_uom_qty,
|
|
'line_total': line.price_subtotal,
|
|
'mod_amount': mod_amount,
|
|
'client_amount': client_amount,
|
|
}))
|
|
elif self.approval_type == 'full':
|
|
for line in product_lines:
|
|
lines.append((0, 0, {
|
|
'product_name': line.product_id.display_name,
|
|
'quantity': line.product_uom_qty,
|
|
'line_total': line.price_subtotal,
|
|
'mod_amount': line.price_subtotal,
|
|
'client_amount': 0,
|
|
}))
|
|
|
|
self.preview_line_ids = [(5, 0, 0)] + lines
|
|
|
|
def action_confirm(self):
|
|
"""Record PCA, set approval amounts, and create invoice(s)."""
|
|
self.ensure_one()
|
|
order = self.sale_order_id
|
|
|
|
if not self.pca_document:
|
|
raise UserError(_("Please attach the PCA document."))
|
|
|
|
if self.approval_type == 'partial':
|
|
if not self.approved_amount or self.approved_amount <= 0:
|
|
raise UserError(_("Please enter the approved amount for partial approval."))
|
|
if self.approved_amount >= order.amount_untaxed:
|
|
raise UserError(_(
|
|
"Approved amount is equal to or greater than the order subtotal. "
|
|
"Use 'Full Approval' instead."))
|
|
|
|
client_name = order.partner_id.name or 'Client'
|
|
|
|
# Update sale order
|
|
vals = {
|
|
'x_fc_mod_status': 'contract_received',
|
|
'x_fc_mod_pca_received_date': fields.Date.today(),
|
|
'x_fc_mod_pca_document': self.pca_document,
|
|
'x_fc_mod_pca_filename': self.pca_filename or f'PCA - {client_name}.pdf',
|
|
'x_fc_mod_approval_type': self.approval_type,
|
|
}
|
|
if self.approval_type == 'partial':
|
|
vals['x_fc_mod_approved_amount'] = self.approved_amount
|
|
vals['x_fc_mod_payment_commitment'] = self.approved_amount
|
|
else:
|
|
vals['x_fc_mod_approved_amount'] = order.amount_untaxed
|
|
vals['x_fc_mod_payment_commitment'] = order.amount_total
|
|
if self.case_worker_id:
|
|
vals['x_fc_case_worker'] = self.case_worker_id.id
|
|
if self.hvmp_reference:
|
|
vals['x_fc_case_reference'] = self.hvmp_reference
|
|
order.write(vals)
|
|
|
|
# Create invoices
|
|
mod_partner = order._get_mod_partner()
|
|
client = order.partner_id
|
|
|
|
if self.approval_type == 'full':
|
|
mod_invoice = self._create_full_invoice(order, mod_partner)
|
|
self._log_pca_and_invoices(order, mod_invoice)
|
|
return self._open_invoice(mod_invoice)
|
|
else:
|
|
mod_invoice = self._create_split_mod_invoice(order, mod_partner)
|
|
client_invoice = self._create_split_client_invoice(order, client)
|
|
self._log_pca_and_invoices(order, mod_invoice, client_invoice)
|
|
return self._open_invoice(mod_invoice)
|
|
|
|
# ------------------------------------------------------------------
|
|
# Invoice creation helpers
|
|
# ------------------------------------------------------------------
|
|
|
|
def _create_full_invoice(self, order, mod_partner):
|
|
"""Create a single MOD invoice for the full order amount."""
|
|
line_vals = []
|
|
for line in order.order_line:
|
|
if line.display_type in ('line_section', 'line_note'):
|
|
line_vals.append(Command.create({
|
|
'display_type': line.display_type,
|
|
'name': line.name,
|
|
'sequence': line.sequence,
|
|
}))
|
|
elif not line.display_type and line.product_uom_qty > 0:
|
|
inv_line = line._prepare_invoice_line()
|
|
inv_line['quantity'] = line.product_uom_qty
|
|
inv_line['sequence'] = line.sequence
|
|
line_vals.append(Command.create(inv_line))
|
|
|
|
return order._create_mod_invoice(
|
|
partner_id=mod_partner.id,
|
|
invoice_lines=line_vals,
|
|
portion_type='full',
|
|
label=' (March of Dimes - Full)',
|
|
)
|
|
|
|
def _create_split_mod_invoice(self, order, mod_partner):
|
|
"""Create MOD invoice with proportionally reduced amounts."""
|
|
subtotal = order.amount_untaxed
|
|
approved = self.approved_amount
|
|
line_vals = []
|
|
|
|
for line in order.order_line:
|
|
if line.display_type in ('line_section', 'line_note'):
|
|
line_vals.append(Command.create({
|
|
'display_type': line.display_type,
|
|
'name': line.name,
|
|
'sequence': line.sequence,
|
|
}))
|
|
elif not line.display_type and line.product_uom_qty > 0:
|
|
ratio = line.price_subtotal / subtotal if subtotal else 0
|
|
mod_line_amount = round(approved * ratio, 2)
|
|
mod_price_unit = round(
|
|
mod_line_amount / line.product_uom_qty, 2
|
|
) if line.product_uom_qty else 0
|
|
|
|
inv_line = line._prepare_invoice_line()
|
|
inv_line['quantity'] = line.product_uom_qty
|
|
inv_line['price_unit'] = mod_price_unit
|
|
inv_line['sequence'] = line.sequence
|
|
line_vals.append(Command.create(inv_line))
|
|
|
|
return order._create_mod_invoice(
|
|
partner_id=mod_partner.id,
|
|
invoice_lines=line_vals,
|
|
portion_type='adp',
|
|
label=' (March of Dimes Portion)',
|
|
)
|
|
|
|
def _create_split_client_invoice(self, order, client):
|
|
"""Create Client invoice with the difference amounts."""
|
|
subtotal = order.amount_untaxed
|
|
approved = self.approved_amount
|
|
line_vals = []
|
|
|
|
for line in order.order_line:
|
|
if line.display_type in ('line_section', 'line_note'):
|
|
line_vals.append(Command.create({
|
|
'display_type': line.display_type,
|
|
'name': line.name,
|
|
'sequence': line.sequence,
|
|
}))
|
|
elif not line.display_type and line.product_uom_qty > 0:
|
|
ratio = line.price_subtotal / subtotal if subtotal else 0
|
|
mod_line_amount = round(approved * ratio, 2)
|
|
client_line_amount = round(line.price_subtotal - mod_line_amount, 2)
|
|
client_price_unit = round(
|
|
client_line_amount / line.product_uom_qty, 2
|
|
) if line.product_uom_qty else 0
|
|
|
|
inv_line = line._prepare_invoice_line()
|
|
inv_line['quantity'] = line.product_uom_qty
|
|
inv_line['price_unit'] = client_price_unit
|
|
inv_line['sequence'] = line.sequence
|
|
|
|
if client_line_amount <= 0:
|
|
inv_line['price_unit'] = 0
|
|
inv_line['name'] = f'{line.name}\n[Covered by March of Dimes]'
|
|
|
|
line_vals.append(Command.create(inv_line))
|
|
|
|
return order._create_mod_invoice(
|
|
partner_id=client.id,
|
|
invoice_lines=line_vals,
|
|
portion_type='client',
|
|
label=' (Client Portion)',
|
|
)
|
|
|
|
# ------------------------------------------------------------------
|
|
# Logging and navigation
|
|
# ------------------------------------------------------------------
|
|
|
|
def _log_pca_and_invoices(self, order, mod_invoice, client_invoice=None):
|
|
"""Log PCA receipt and invoice creation to chatter."""
|
|
parts = [f'<strong>PCA Received and Invoice(s) Created</strong>']
|
|
parts.append(f'Date: {fields.Date.today().strftime("%B %d, %Y")}')
|
|
if self.hvmp_reference:
|
|
parts.append(f'HVMP Reference: {self.hvmp_reference}')
|
|
if self.case_worker_id:
|
|
parts.append(f'Case Worker: {self.case_worker_id.name}')
|
|
|
|
type_label = 'Full Approval' if self.approval_type == 'full' else 'Partial Approval'
|
|
parts.append(f'Approval Type: {type_label}')
|
|
|
|
if self.approval_type == 'partial':
|
|
parts.append(f'MOD Approved: ${self.approved_amount:,.2f}')
|
|
parts.append(
|
|
f'Client Portion: ${order.amount_untaxed - self.approved_amount:,.2f}')
|
|
|
|
inv_links = [
|
|
f'MOD Invoice: <a href="/web#id={mod_invoice.id}'
|
|
f'&model=account.move&view_type=form">'
|
|
f'{mod_invoice.name or "Draft"}</a>'
|
|
]
|
|
if client_invoice:
|
|
inv_links.append(
|
|
f'Client Invoice: <a href="/web#id={client_invoice.id}'
|
|
f'&model=account.move&view_type=form">'
|
|
f'{client_invoice.name or "Draft"}</a>'
|
|
)
|
|
parts.extend(inv_links)
|
|
|
|
order.message_post(
|
|
body=Markup('<div class="alert alert-success">' + '<br/>'.join(parts) + '</div>'),
|
|
message_type='notification', subtype_xmlid='mail.mt_note',
|
|
)
|
|
|
|
def _open_invoice(self, invoice):
|
|
return {
|
|
'type': 'ir.actions.act_window',
|
|
'name': 'Invoice',
|
|
'res_model': 'account.move',
|
|
'view_mode': 'form',
|
|
'res_id': invoice.id,
|
|
}
|