Initial commit
This commit is contained in:
304
fusion_claims/wizard/mod_pca_received_wizard.py
Normal file
304
fusion_claims/wizard/mod_pca_received_wizard.py
Normal file
@@ -0,0 +1,304 @@
|
||||
# -*- 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,
|
||||
}
|
||||
Reference in New Issue
Block a user