Reference #7
This commit is contained in:
parent
f7db2aebc9
commit
faa8f2ff3b
Binary file not shown.
Binary file not shown.
|
|
@ -1,5 +1,7 @@
|
|||
from odoo import models, fields, api
|
||||
import math
|
||||
from collections import defaultdict
|
||||
from odoo.tools.misc import format_amount
|
||||
|
||||
class Battery_Installation_Requirement(models.Model):
|
||||
_name = 'sos_proposal_boq'
|
||||
|
|
@ -100,6 +102,14 @@ class Battery_Installation_Requirement(models.Model):
|
|||
line_ids_fg_ups13 = fields.One2many('sos_proposal_boq_fg', 'ref_id', string="FG UPS 13",compute='_compute_line_ids_by_ups',store=False)
|
||||
line_ids_fg_ups14 = fields.One2many('sos_proposal_boq_fg', 'ref_id', string="FG UPS 14",compute='_compute_line_ids_by_ups',store=False)
|
||||
line_ids_fg_ups15 = fields.One2many('sos_proposal_boq_fg', 'ref_id', string="FG UPS 15",compute='_compute_line_ids_by_ups',store=False)
|
||||
merged_fg_html = fields.Html(string="FG (Merged)", compute="_compute_merged_fg_html", sanitize=False)
|
||||
merged_sfg_html = fields.Html(string="SFG (Merged)", compute="_compute_merged_sfg_html", sanitize=False)
|
||||
merged_material_html = fields.Html(string="Materials (Merged)", compute="_compute_merged_material_html", sanitize=False)
|
||||
merged_installation_kit_html = fields.Html(string="InstallationKit (Merged)", compute="_compute_merged_installation_kit_html", sanitize=False)
|
||||
merged_miscellaneous_html = fields.Html(string="Miscellaneous (Merged)", compute="_compute_merged_miscellaneous_html", sanitize=False)
|
||||
merged_spare_html = fields.Html(string="Spare (Merged)", compute="_compute_merged_spare_html", sanitize=False)
|
||||
|
||||
|
||||
|
||||
#SFG Fields
|
||||
line_ids_sfg_ups1 = fields.One2many('sos_proposal_boq_sfg', 'ref_id', string="SFG UPS 1",compute='_compute_line_ids_by_ups',store=False)
|
||||
|
|
@ -120,21 +130,21 @@ class Battery_Installation_Requirement(models.Model):
|
|||
|
||||
|
||||
#Material Fields
|
||||
line_ids_material_ups1 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 1",compute='_compute_line_ids_by_ups',store=False)
|
||||
line_ids_material_ups2 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 2",compute='_compute_line_ids_by_ups',store=False)
|
||||
line_ids_material_ups3 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 3",compute='_compute_line_ids_by_ups',store=False)
|
||||
line_ids_material_ups4 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 4",compute='_compute_line_ids_by_ups',store=False)
|
||||
line_ids_material_ups5 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 5",compute='_compute_line_ids_by_ups',store=False)
|
||||
line_ids_material_ups6 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 6",compute='_compute_line_ids_by_ups',store=False)
|
||||
line_ids_material_ups7 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 7",compute='_compute_line_ids_by_ups',store=False)
|
||||
line_ids_material_ups8 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 8",compute='_compute_line_ids_by_ups',store=False)
|
||||
line_ids_material_ups9 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 9",compute='_compute_line_ids_by_ups',store=False)
|
||||
line_ids_material_ups10 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 10",compute='_compute_line_ids_by_ups',store=False)
|
||||
line_ids_material_ups11 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 11",compute='_compute_line_ids_by_ups',store=False)
|
||||
line_ids_material_ups12 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 12",compute='_compute_line_ids_by_ups',store=False)
|
||||
line_ids_material_ups13 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 13",compute='_compute_line_ids_by_ups',store=False)
|
||||
line_ids_material_ups14 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 14",compute='_compute_line_ids_by_ups',store=False)
|
||||
line_ids_material_ups15 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 15",compute='_compute_line_ids_by_ups',store=False)
|
||||
line_ids_material_ups1 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 1",compute='_compute_line_ids_by_ups',store=False,readonly=False)
|
||||
line_ids_material_ups2 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 2",compute='_compute_line_ids_by_ups',store=False,readonly=False)
|
||||
line_ids_material_ups3 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 3",compute='_compute_line_ids_by_ups',store=False,readonly=False)
|
||||
line_ids_material_ups4 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 4",compute='_compute_line_ids_by_ups',store=False,readonly=False)
|
||||
line_ids_material_ups5 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 5",compute='_compute_line_ids_by_ups',store=False,readonly=False)
|
||||
line_ids_material_ups6 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 6",compute='_compute_line_ids_by_ups',store=False,readonly=False)
|
||||
line_ids_material_ups7 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 7",compute='_compute_line_ids_by_ups',store=False,readonly=False)
|
||||
line_ids_material_ups8 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 8",compute='_compute_line_ids_by_ups',store=False,readonly=False)
|
||||
line_ids_material_ups9 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 9",compute='_compute_line_ids_by_ups',store=False,readonly=False)
|
||||
line_ids_material_ups10 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 10",compute='_compute_line_ids_by_ups',store=False,readonly=False)
|
||||
line_ids_material_ups11 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 11",compute='_compute_line_ids_by_ups',store=False,readonly=False)
|
||||
line_ids_material_ups12 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 12",compute='_compute_line_ids_by_ups',store=False,readonly=False)
|
||||
line_ids_material_ups13 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 13",compute='_compute_line_ids_by_ups',store=False,readonly=False)
|
||||
line_ids_material_ups14 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 14",compute='_compute_line_ids_by_ups',store=False,readonly=False)
|
||||
line_ids_material_ups15 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 15",compute='_compute_line_ids_by_ups',store=False,readonly=False)
|
||||
|
||||
|
||||
#Installation Kit Fields
|
||||
|
|
@ -206,6 +216,454 @@ class Battery_Installation_Requirement(models.Model):
|
|||
line_ids_spare_ups13 = fields.One2many('sos_proposal_line_spare_ups13','ref_id', string="Spare UPS 13")
|
||||
line_ids_spare_ups14 = fields.One2many('sos_proposal_line_spare_ups14','ref_id', string="Spare UPS 14")
|
||||
line_ids_spare_ups15 = fields.One2many('sos_proposal_line_spare_ups15','ref_id', string="Spare UPS 15")
|
||||
def _compute_merged_spare_html(self):
|
||||
for rec in self:
|
||||
# Collect lines from ups1..ups15 (skip models that don't exist)
|
||||
models = [f'sos_proposal_line_spare_ups{i}' for i in range(1, 16)]
|
||||
|
||||
agg = defaultdict(float) # (component_id, uom, currency_id, unit_price) -> qty
|
||||
name_map, curr_map, price_map = {}, {}, {}
|
||||
|
||||
for model in models:
|
||||
if model not in self.env:
|
||||
continue
|
||||
for l in self.env[model].search([('ref_id', '=', rec.id)]):
|
||||
unit_price = l.unit_price or 0.0
|
||||
curr_id = l.currency_id.id if l.currency_id else False
|
||||
key = (l.component_id.id, l.uom, curr_id, unit_price)
|
||||
|
||||
agg[key] += float(l.quantity or 0)
|
||||
if key not in name_map:
|
||||
# Use part number (fallback to component name if needed)
|
||||
name_map[key] = (getattr(l.component_id, 'part_no', False) or l.component_id.name or '')
|
||||
curr_map[key] = l.currency_id if l.currency_id else False
|
||||
price_map[key] = unit_price
|
||||
|
||||
rows_sorted = sorted(agg.items(), key=lambda it: name_map[it[0]].lower())
|
||||
if not rows_sorted:
|
||||
rec.merged_spare_html = "<em>No Material items.</em>"
|
||||
continue
|
||||
|
||||
def fmt_price(amount, currency):
|
||||
return format_amount(self.env, amount, currency) if currency else f"{amount:.2f}"
|
||||
|
||||
total_sum = 0.0
|
||||
rows_html = ""
|
||||
for key, qty in rows_sorted:
|
||||
if not qty:
|
||||
continue
|
||||
total_price = qty * price_map[key]
|
||||
total_sum += total_price
|
||||
rows_html += (
|
||||
f"<tr>"
|
||||
f"<td>{name_map[key]}</td>"
|
||||
f"<td>{key[1] or ''}</td>"
|
||||
f"<td style='text-align:right'>{fmt_price(price_map[key], curr_map[key])}</td>"
|
||||
f"<td style='text-align:right'>{qty:.2f}</td>"
|
||||
f"<td style='text-align:right'>{fmt_price(total_price, curr_map[key])}</td>"
|
||||
f"</tr>"
|
||||
)
|
||||
|
||||
if not rows_html:
|
||||
rec.merged_spare_html = "<em>No Material items.</em>"
|
||||
continue
|
||||
|
||||
currency_for_total = curr_map[rows_sorted[0][0]] if rows_sorted else False
|
||||
rows_html += (
|
||||
f"<tr style='font-weight:bold;'>"
|
||||
f"<td colspan='4' style='text-align:right'>Grand Total:</td>"
|
||||
f"<td style='text-align:right'>{fmt_price(total_sum, currency_for_total)}</td>"
|
||||
f"</tr>"
|
||||
)
|
||||
|
||||
rec.merged_spare_html = f"""
|
||||
<table class="table table-sm" style="width:100%;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><b>Material Name</b></th>
|
||||
<th><b>UoM</b></th>
|
||||
<th style="text-align:right"><b>Unit Price</b></th>
|
||||
<th style="text-align:right"><b>Total Qty</b></th>
|
||||
<th style="text-align:right"><b>Total Price</b></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>{rows_html}</tbody>
|
||||
</table>
|
||||
"""
|
||||
def _compute_merged_miscellaneous_html(self):
|
||||
for rec in self:
|
||||
lines = self.env['sos_proposal_miscellaneous_items'].search([('ref_id', '=', rec.id)])
|
||||
|
||||
# Group by (component, currency, cost)
|
||||
agg = defaultdict(float)
|
||||
name_map = {}
|
||||
curr_map = {}
|
||||
price_map = {}
|
||||
|
||||
for l in lines:
|
||||
cost = l.cost or 0.0
|
||||
curr_id = l.currency_id.id if l.currency_id else False
|
||||
key = (l.name, curr_id, cost)
|
||||
|
||||
agg[key] += float(l.quantity or 0)
|
||||
if key not in name_map:
|
||||
name_map[key] = l.name or ''
|
||||
curr_map[key] = l.currency_id if l.currency_id else False
|
||||
price_map[key] = cost
|
||||
|
||||
rows_sorted = sorted(agg.items(), key=lambda it: name_map[it[0]].lower())
|
||||
if not rows_sorted:
|
||||
rec.merged_miscellaneous_html = "<em>No Miscellaneous items.</em>"
|
||||
continue
|
||||
|
||||
def fmt_price(amount, currency):
|
||||
if currency:
|
||||
return format_amount(self.env, amount, currency)
|
||||
return f"{amount:.2f}"
|
||||
|
||||
total_sum = 0.0
|
||||
rows_html = ""
|
||||
for key, qty in rows_sorted:
|
||||
if not qty: # Skip rows with qty = 0
|
||||
continue
|
||||
total_price = qty * price_map[key]
|
||||
total_sum += total_price
|
||||
rows_html += (
|
||||
f"<tr>"
|
||||
f"<td>{name_map[key]}</td>"
|
||||
f"<td style='text-align:right'>{fmt_price(price_map[key], curr_map[key])}</td>"
|
||||
f"<td style='text-align:right'>{qty:.2f}</td>"
|
||||
f"<td style='text-align:right'>{fmt_price(total_price, curr_map[key])}</td>"
|
||||
f"</tr>"
|
||||
)
|
||||
|
||||
if not rows_html: # If all rows were skipped
|
||||
rec.merged_miscellaneous_html = "<em>No Miscellaneous items.</em>"
|
||||
continue
|
||||
|
||||
# Add grand total row
|
||||
currency_for_total = curr_map[rows_sorted[0][0]] if rows_sorted else False
|
||||
rows_html += (
|
||||
f"<tr style='font-weight:bold;'>"
|
||||
f"<td colspan='3' style='text-align:right'>Grand Total:</td>"
|
||||
f"<td style='text-align:right'>{fmt_price(total_sum, currency_for_total)}</td>"
|
||||
f"</tr>"
|
||||
)
|
||||
|
||||
rec.merged_miscellaneous_html = f"""
|
||||
<table class="table table-sm" style="width:100%;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><b>Name</b></th>
|
||||
<th style="text-align:right"><b>Unit Price</b></th>
|
||||
<th style="text-align:right"><b>Total Qty</b></th>
|
||||
<th style="text-align:right"><b>Total Price</b></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>{rows_html}</tbody>
|
||||
</table>
|
||||
"""
|
||||
def _compute_merged_installation_kit_html(self):
|
||||
for rec in self:
|
||||
lines = self.env['sos_proposal_line_material_installation'].search([('ref_id', '=', rec.id)])
|
||||
|
||||
# Group by (component, uom, currency, unit_price)
|
||||
agg = defaultdict(float)
|
||||
name_map = {}
|
||||
curr_map = {}
|
||||
price_map = {}
|
||||
|
||||
for l in lines:
|
||||
unit_price = l.unit_price or 0.0
|
||||
curr_id = l.currency_id.id if l.currency_id else False
|
||||
key = (l.component_id.id, l.uom, curr_id, unit_price)
|
||||
|
||||
agg[key] += float(l.quantity or 0)
|
||||
if key not in name_map:
|
||||
name_map[key] = l.component_id.part_no or ''
|
||||
curr_map[key] = l.currency_id if l.currency_id else False
|
||||
price_map[key] = unit_price
|
||||
|
||||
rows_sorted = sorted(agg.items(), key=lambda it: name_map[it[0]].lower())
|
||||
if not rows_sorted:
|
||||
rec.merged_installation_kit_html = "<em>No Material items.</em>"
|
||||
continue
|
||||
|
||||
def fmt_price(amount, currency):
|
||||
if currency:
|
||||
return format_amount(self.env, amount, currency)
|
||||
return f"{amount:.2f}"
|
||||
|
||||
total_sum = 0.0
|
||||
rows_html = ""
|
||||
for key, qty in rows_sorted:
|
||||
if not qty: # Skip rows with qty = 0
|
||||
continue
|
||||
total_price = qty * price_map[key]
|
||||
total_sum += total_price
|
||||
rows_html += (
|
||||
f"<tr>"
|
||||
f"<td>{name_map[key]}</td>"
|
||||
f"<td>{key[1] or ''}</td>"
|
||||
f"<td style='text-align:right'>{fmt_price(price_map[key], curr_map[key])}</td>"
|
||||
f"<td style='text-align:right'>{qty:.2f}</td>"
|
||||
f"<td style='text-align:right'>{fmt_price(total_price, curr_map[key])}</td>"
|
||||
f"</tr>"
|
||||
)
|
||||
|
||||
if not rows_html: # If all rows were skipped
|
||||
rec.merged_installation_kit_html = "<em>No Material items.</em>"
|
||||
continue
|
||||
|
||||
# Add grand total row
|
||||
currency_for_total = curr_map[rows_sorted[0][0]] if rows_sorted else False
|
||||
rows_html += (
|
||||
f"<tr style='font-weight:bold;'>"
|
||||
f"<td colspan='4' style='text-align:right'>Grand Total:</td>"
|
||||
f"<td style='text-align:right'>{fmt_price(total_sum, currency_for_total)}</td>"
|
||||
f"</tr>"
|
||||
)
|
||||
|
||||
rec.merged_installation_kit_html = f"""
|
||||
<table class="table table-sm" style="width:100%;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><b>Material Name</b></th>
|
||||
<th><b>UoM</b></th>
|
||||
<th style="text-align:right"><b>Unit Price</b></th>
|
||||
<th style="text-align:right"><b>Total Qty</b></th>
|
||||
<th style="text-align:right"><b>Total Price</b></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>{rows_html}</tbody>
|
||||
</table>
|
||||
"""
|
||||
def _compute_merged_material_html(self):
|
||||
for rec in self:
|
||||
lines = self.env['sos_proposal_boq_material'].search([('ref_id', '=', rec.id)])
|
||||
|
||||
# Group by (component, uom, currency, unit_price)
|
||||
agg = defaultdict(float)
|
||||
name_map = {}
|
||||
curr_map = {}
|
||||
price_map = {}
|
||||
|
||||
for l in lines:
|
||||
unit_price = l.unit_price or 0.0
|
||||
curr_id = l.currency_id.id if l.currency_id else False
|
||||
key = (l.component_id.id, l.uom, curr_id, unit_price)
|
||||
|
||||
agg[key] += float(l.quantity or 0)
|
||||
if key not in name_map:
|
||||
name_map[key] = l.component_id.part_no or ''
|
||||
curr_map[key] = l.currency_id if l.currency_id else False
|
||||
price_map[key] = unit_price
|
||||
|
||||
rows_sorted = sorted(agg.items(), key=lambda it: name_map[it[0]].lower())
|
||||
if not rows_sorted:
|
||||
rec.merged_material_html = "<em>No Material items.</em>"
|
||||
continue
|
||||
|
||||
def fmt_price(amount, currency):
|
||||
if currency:
|
||||
return format_amount(self.env, amount, currency)
|
||||
return f"{amount:.2f}"
|
||||
|
||||
total_sum = 0.0
|
||||
rows_html = ""
|
||||
for key, qty in rows_sorted:
|
||||
if not qty: # Skip rows with qty = 0
|
||||
continue
|
||||
total_price = qty * price_map[key]
|
||||
total_sum += total_price
|
||||
rows_html += (
|
||||
f"<tr>"
|
||||
f"<td>{name_map[key]}</td>"
|
||||
f"<td>{key[1] or ''}</td>"
|
||||
f"<td style='text-align:right'>{fmt_price(price_map[key], curr_map[key])}</td>"
|
||||
f"<td style='text-align:right'>{qty:.2f}</td>"
|
||||
f"<td style='text-align:right'>{fmt_price(total_price, curr_map[key])}</td>"
|
||||
f"</tr>"
|
||||
)
|
||||
|
||||
if not rows_html: # If all rows were skipped
|
||||
rec.merged_material_html = "<em>No Material items.</em>"
|
||||
continue
|
||||
|
||||
# Add grand total row
|
||||
currency_for_total = curr_map[rows_sorted[0][0]] if rows_sorted else False
|
||||
rows_html += (
|
||||
f"<tr style='font-weight:bold;'>"
|
||||
f"<td colspan='4' style='text-align:right'>Grand Total:</td>"
|
||||
f"<td style='text-align:right'>{fmt_price(total_sum, currency_for_total)}</td>"
|
||||
f"</tr>"
|
||||
)
|
||||
|
||||
rec.merged_material_html = f"""
|
||||
<table class="table table-sm" style="width:100%;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><b>Material Name</b></th>
|
||||
<th><b>UoM</b></th>
|
||||
<th style="text-align:right"><b>Unit Price</b></th>
|
||||
<th style="text-align:right"><b>Total Qty</b></th>
|
||||
<th style="text-align:right"><b>Total Price</b></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>{rows_html}</tbody>
|
||||
</table>
|
||||
"""
|
||||
def _compute_merged_sfg_html(self):
|
||||
for rec in self:
|
||||
lines = self.env['sos_proposal_boq_sfg'].search([('ref_id', '=', rec.id)])
|
||||
|
||||
# Group by (component, uom, currency, unit_price)
|
||||
agg = defaultdict(float)
|
||||
name_map = {}
|
||||
curr_map = {}
|
||||
price_map = {}
|
||||
|
||||
for l in lines:
|
||||
unit_price = l.unit_price or 0.0
|
||||
curr_id = l.currency_id.id if l.currency_id else False
|
||||
key = (l.component_id.id, l.uom, curr_id, unit_price)
|
||||
|
||||
agg[key] += float(l.quantity or 0)
|
||||
if key not in name_map:
|
||||
name_map[key] = l.component_id.name or ''
|
||||
curr_map[key] = l.currency_id if l.currency_id else False
|
||||
price_map[key] = unit_price
|
||||
|
||||
rows_sorted = sorted(agg.items(), key=lambda it: name_map[it[0]].lower())
|
||||
if not rows_sorted:
|
||||
rec.merged_sfg_html = "<em>No SFG items.</em>"
|
||||
continue
|
||||
|
||||
def fmt_price(amount, currency):
|
||||
if currency:
|
||||
return format_amount(self.env, amount, currency)
|
||||
return f"{amount:.2f}"
|
||||
|
||||
total_sum = 0.0
|
||||
rows_html = ""
|
||||
for key, qty in rows_sorted:
|
||||
if not qty: # Skip rows with qty = 0
|
||||
continue
|
||||
total_price = qty * price_map[key]
|
||||
total_sum += total_price
|
||||
rows_html += (
|
||||
f"<tr>"
|
||||
f"<td>{name_map[key]}</td>"
|
||||
f"<td>{key[1] or ''}</td>"
|
||||
f"<td style='text-align:right'>{fmt_price(price_map[key], curr_map[key])}</td>"
|
||||
f"<td style='text-align:right'>{qty:.2f}</td>"
|
||||
f"<td style='text-align:right'>{fmt_price(total_price, curr_map[key])}</td>"
|
||||
f"</tr>"
|
||||
)
|
||||
|
||||
if not rows_html: # If all rows were skipped
|
||||
rec.merged_sfg_html = "<em>No SFG items.</em>"
|
||||
continue
|
||||
|
||||
# Add grand total row
|
||||
currency_for_total = curr_map[rows_sorted[0][0]] if rows_sorted else False
|
||||
rows_html += (
|
||||
f"<tr style='font-weight:bold;'>"
|
||||
f"<td colspan='4' style='text-align:right'>Grand Total:</td>"
|
||||
f"<td style='text-align:right'>{fmt_price(total_sum, currency_for_total)}</td>"
|
||||
f"</tr>"
|
||||
)
|
||||
|
||||
rec.merged_sfg_html = f"""
|
||||
<table class="table table-sm" style="width:100%;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><b>SFG Name</b></th>
|
||||
<th><b>UoM</b></th>
|
||||
<th style="text-align:right"><b>Unit Price</b></th>
|
||||
<th style="text-align:right"><b>Total Qty</b></th>
|
||||
<th style="text-align:right"><b>Total Price</b></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>{rows_html}</tbody>
|
||||
</table>
|
||||
"""
|
||||
def _compute_merged_fg_html(self):
|
||||
for rec in self:
|
||||
lines = self.env['sos_proposal_boq_fg'].search([('ref_id', '=', rec.id)])
|
||||
|
||||
# Group by (component, uom, currency, unit_price)
|
||||
agg = defaultdict(float)
|
||||
name_map = {}
|
||||
curr_map = {}
|
||||
price_map = {}
|
||||
|
||||
for l in lines:
|
||||
unit_price = l.unit_price or 0.0
|
||||
curr_id = l.currency_id.id if l.currency_id else False
|
||||
key = (l.component_id.id, l.uom, curr_id, unit_price)
|
||||
|
||||
agg[key] += float(l.quantity or 0)
|
||||
if key not in name_map:
|
||||
name_map[key] = l.component_id.name or ''
|
||||
curr_map[key] = l.currency_id if l.currency_id else False
|
||||
price_map[key] = unit_price
|
||||
|
||||
rows_sorted = sorted(agg.items(), key=lambda it: name_map[it[0]].lower())
|
||||
if not rows_sorted:
|
||||
rec.merged_fg_html = "<em>No FG items.</em>"
|
||||
continue
|
||||
|
||||
def fmt_price(amount, currency):
|
||||
if currency:
|
||||
return format_amount(self.env, amount, currency)
|
||||
return f"{amount:.2f}"
|
||||
|
||||
total_sum = 0.0
|
||||
rows_html = ""
|
||||
for key, qty in rows_sorted:
|
||||
if not qty: # Skip rows with qty = 0
|
||||
continue
|
||||
total_price = qty * price_map[key]
|
||||
total_sum += total_price
|
||||
rows_html += (
|
||||
f"<tr>"
|
||||
f"<td>{name_map[key]}</td>"
|
||||
f"<td>{key[1] or ''}</td>"
|
||||
f"<td style='text-align:right'>{fmt_price(price_map[key], curr_map[key])}</td>"
|
||||
f"<td style='text-align:right'>{qty:.2f}</td>"
|
||||
f"<td style='text-align:right'>{fmt_price(total_price, curr_map[key])}</td>"
|
||||
f"</tr>"
|
||||
)
|
||||
|
||||
if not rows_html: # If all rows were skipped
|
||||
rec.merged_fg_html = "<em>No FG items.</em>"
|
||||
continue
|
||||
|
||||
# Add grand total row
|
||||
currency_for_total = curr_map[rows_sorted[0][0]] if rows_sorted else False
|
||||
rows_html += (
|
||||
f"<tr style='font-weight:bold;'>"
|
||||
f"<td colspan='4' style='text-align:right'>Grand Total:</td>"
|
||||
f"<td style='text-align:right'>{fmt_price(total_sum, currency_for_total)}</td>"
|
||||
f"</tr>"
|
||||
)
|
||||
|
||||
rec.merged_fg_html = f"""
|
||||
<table class="table table-sm" style="width:100%;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><b>FG Name</b></th>
|
||||
<th><b>UoM</b></th>
|
||||
<th style="text-align:right"><b>Unit Price</b></th>
|
||||
<th style="text-align:right"><b>Total Qty</b></th>
|
||||
<th style="text-align:right"><b>Total Price</b></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>{rows_html}</tbody>
|
||||
</table>
|
||||
"""
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
res = super().create(vals)
|
||||
|
|
@ -662,10 +1120,22 @@ class Battery_Installation_Requirement(models.Model):
|
|||
record.total_fg_cost = sum(line.total_price for line in record.line_ids_fg)
|
||||
def action_ce_esign_btn(self):
|
||||
body_html = f"""
|
||||
<p>Below <b>Proposal</b> is waiting for your Updation</p>
|
||||
<p>Below <b>BOQ</b> is waiting for your updation.</p>
|
||||
<p><b>Customer Name:</b> {self.customer_name or ''}</p>
|
||||
<p><b>Location:</b> {self.location or ''}</p>
|
||||
<p><b>Number of Batteries:</b> {self.number_of_batteries or ''}</p>
|
||||
"""
|
||||
|
||||
sequence_util = self.env['sos_common_scripts']
|
||||
sequence_util.send_group_email(self.env,'sos_proposal_boq',self.id,"deenalaura.m@sosaley.in","Proposal System - BOQ Submitted",body_html,'sos_inventory.sos_finance_user')
|
||||
sequence_util.send_group_email(
|
||||
self.env,
|
||||
'sos_proposal_boq',
|
||||
self.id,
|
||||
"deenalaura.m@sosaley.in",
|
||||
f"Proposal System - BOQ Submitted for {self.customer_name}",
|
||||
body_html,
|
||||
'sos_inventory.sos_finance_user'
|
||||
)
|
||||
return sequence_util.action_assign_signature(
|
||||
self,
|
||||
'boq_submitted_by_name',
|
||||
|
|
|
|||
|
|
@ -694,16 +694,31 @@ class SOS_Sales_Achievement_Report_Brief(models.Model):
|
|||
report.write({
|
||||
new_field_billed: (getattr(report, new_field_billed, 0.0) or 0.0) + new_billed_amount
|
||||
})
|
||||
# Optionally create billing collection entry (if needed)
|
||||
if new_billed_amount > 0:
|
||||
# Optionally create billing collection entry (if needed
|
||||
domain = [
|
||||
('ref_id', '=', report.id),
|
||||
('sales_person', '=', report.sales_person.id),
|
||||
('customer_name', '=', vals.get('customer_name', rec.customer_name.id))
|
||||
]
|
||||
|
||||
existing = self.env['sos_billing_collection'].search(domain, limit=1)
|
||||
|
||||
if not existing:
|
||||
self.env['sos_billing_collection'].create({
|
||||
'ref_id': report.id,
|
||||
'customer_name': vals.get('customer_name', rec.customer_name.id),
|
||||
'sales_person': report.sales_person.id,
|
||||
'action_status': 'Billed',
|
||||
'date_of_action': new_billed_date,
|
||||
'po_no':vals.get('po_no'),
|
||||
'value': new_billed_amount
|
||||
})
|
||||
else:
|
||||
existing.write({
|
||||
'value': new_billed_amount,
|
||||
'po_no':vals.get('po_no'),
|
||||
'date_of_action': new_billed_date
|
||||
})
|
||||
|
||||
return super(SOS_Sales_Achievement_Report_Brief, self).write(vals)
|
||||
|
||||
|
|
@ -748,7 +763,8 @@ class SOS_Sales_Achievement_Report_Brief(models.Model):
|
|||
'sales_person': report.sales_person.id,
|
||||
'action_status': 'Billed',
|
||||
'date_of_action': billed_date,
|
||||
'value': billed_value
|
||||
'value': billed_value,
|
||||
'po_no':vals.get('po_no')
|
||||
})
|
||||
new_record = super(SOS_Sales_Achievement_Report_Brief, self).create(vals)
|
||||
return new_record
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@
|
|||
|
||||
<notebook>
|
||||
<!-- Page 1 -->
|
||||
|
||||
<page string="UPS 1" invisible="number_of_ups < 1">
|
||||
<h3 style="text-transform: uppercase;
|
||||
text-decoration: underline;">Finished Goods</h3>
|
||||
|
|
@ -1654,115 +1655,38 @@
|
|||
</div>
|
||||
</div>
|
||||
</page>
|
||||
<!-- <page string="Finished Goods">
|
||||
<field name="line_ids_fg">
|
||||
<tree editable="bottom">
|
||||
<field name="production_cost" widget="boolean_toggle"/>
|
||||
<field name="component_id"/>
|
||||
<field name="description"/>
|
||||
<field name="item_type"/>
|
||||
<field name="uom"/>
|
||||
<field name="unit_price" groups="sos_inventory.sos_management_user,sos_inventory.sos_finance_user,sos_inventory.sos_scg_group_user"/>
|
||||
<field name="singet_set_qty"/>
|
||||
<field name="total_set"/>
|
||||
<field name="quantity"/>
|
||||
<field name="total_price" groups="sos_inventory.sos_management_user,sos_inventory.sos_finance_user,sos_inventory.sos_scg_group_user"/>
|
||||
</tree>
|
||||
</field>
|
||||
<group style="float: right;
|
||||
font-weight: bold;
|
||||
font-size: 18px;">
|
||||
<field name="total_fg_cost"/>
|
||||
</group>
|
||||
</page> -->
|
||||
<!-- <page string="Semi Finished Goods">
|
||||
<field name="line_ids_sfg">
|
||||
<tree editable="bottom">
|
||||
<field name="production_cost" widget="boolean_toggle"/>
|
||||
<field name="component_id"/>
|
||||
<field name="description"/>
|
||||
<field name="item_type"/>
|
||||
<field name="uom"/>
|
||||
<field name="unit_price" groups="sos_inventory.sos_management_user,sos_inventory.sos_finance_user,sos_inventory.sos_scg_group_user"/>
|
||||
<field name="singet_set_qty"/>
|
||||
<field name="total_set"/>
|
||||
<field name="quantity"/>
|
||||
<field name="total_price" groups="sos_inventory.sos_management_user,sos_inventory.sos_finance_user,sos_inventory.sos_scg_group_user"/>
|
||||
</tree>
|
||||
</field>
|
||||
<group style="float: right;
|
||||
font-weight: bold;
|
||||
font-size: 18px;">
|
||||
<field name="total_sfg_cost"/>
|
||||
</group>
|
||||
</page> -->
|
||||
<!-- <page string="Materials">
|
||||
<field name="line_ids_material">
|
||||
<tree editable="bottom">
|
||||
<field name="production_cost" widget="boolean_toggle"/>
|
||||
<field name="component_id"/>
|
||||
<field name="item_type"/>
|
||||
<field name="description"/>
|
||||
<field name="uom"/>
|
||||
<field name="unit_price" groups="sos_inventory.sos_management_user,sos_inventory.sos_finance_user,sos_inventory.sos_scg_group_user"/>
|
||||
<field name="singet_set_qty"/>
|
||||
<field name="total_set"/>
|
||||
<field name="quantity"/>
|
||||
<field name="total_price" groups="sos_inventory.sos_management_user,sos_inventory.sos_finance_user,sos_inventory.sos_scg_group_user"/>
|
||||
</tree>
|
||||
</field>
|
||||
<group style="float: right;
|
||||
font-weight: bold;
|
||||
font-size: 18px;">
|
||||
<field name="total_material_cost"/>
|
||||
</group>
|
||||
</page> -->
|
||||
<!-- <page string="Installation Kit">
|
||||
<field name="line_ids_installation_kit">
|
||||
<tree editable="bottom">
|
||||
<field name="production_cost" widget="boolean_toggle"/>
|
||||
<field name="component_id"/>
|
||||
<field name="description"/>
|
||||
<field name="item_type"/>
|
||||
<field name="uom"/>
|
||||
<field name="unit_price" groups="sos_inventory.sos_management_user,sos_inventory.sos_finance_user,sos_inventory.sos_scg_group_user"/>
|
||||
<field name="singet_set_qty"/>
|
||||
<field name="total_set"/>
|
||||
<field name="quantity"/>
|
||||
<field name="total_price" groups="sos_inventory.sos_management_user,sos_inventory.sos_finance_user,sos_inventory.sos_scg_group_user"/>
|
||||
</tree>
|
||||
</field>
|
||||
<group style="float: right;
|
||||
font-weight: bold;
|
||||
font-size: 18px;">
|
||||
<field name="total_installation_material_cost"/>
|
||||
</group>
|
||||
<page string="Summary" groups="sos_inventory.sos_management_user,sos_inventory.sos_finance_user">
|
||||
<h3 style="text-transform: uppercase;
|
||||
text-decoration: underline;">Finished Goods</h3>
|
||||
<field name="merged_fg_html" nolabel="1"/>
|
||||
<h3 style="text-transform: uppercase;
|
||||
text-decoration: underline;">Semi-Finished Goods</h3>
|
||||
<field name="merged_sfg_html" nolabel="1"/>
|
||||
<h3 style="text-transform: uppercase;
|
||||
text-decoration: underline;">Materials</h3>
|
||||
<field name="merged_material_html" nolabel="1"/>
|
||||
<h3 style="text-transform: uppercase;
|
||||
text-decoration: underline;">Installation Kit</h3>
|
||||
<field name="merged_installation_kit_html" nolabel="1"/>
|
||||
<h3 style="text-transform: uppercase;
|
||||
text-decoration: underline;">Spare</h3>
|
||||
<field name="merged_spare_html" nolabel="1"/>
|
||||
|
||||
<h3 style="text-transform: uppercase;
|
||||
text-decoration: underline;">Miscellaneous</h3>
|
||||
<field name="merged_miscellaneous_html" nolabel="1"/>
|
||||
|
||||
</page>
|
||||
<page string="Miscellaneous">
|
||||
<field name="line_ids_miscellaneous">
|
||||
<tree editable="bottom">
|
||||
<field name="name"/>
|
||||
<field name="cost"/>
|
||||
<field name="quantity"/>
|
||||
<field name="total_price" groups="sos_inventory.sos_management_user,sos_inventory.sos_finance_user,sos_inventory.sos_scg_group_user"/>
|
||||
</tree>
|
||||
</field>
|
||||
<group style="float: right;
|
||||
font-weight: bold;
|
||||
font-size: 18px;">
|
||||
<field name="total_miscellaneous_cost"/>
|
||||
</group>
|
||||
</page> -->
|
||||
<page string="Installation & Commissioning" invisible="proposal_id == False">
|
||||
|
||||
<group><field name="engineers_nos"/></group>
|
||||
<group><field name="no_of_days"/></group>
|
||||
<br></br>
|
||||
<table class="table_custom" groups="sos_inventory.sos_management_user,sos_inventory.sos_finance_user">
|
||||
<thead><td></td><td><b>No of Persons</b></td><td><b>Cost</b></td></thead>
|
||||
<thead><td></td><td><b>No of Persons</b></td><td><b>Cost per Day</b></td></thead>
|
||||
<tbody>
|
||||
<tr><td><b>Man Month( 1 to 2 Yrs)</b></td><td><field name="man_month_1to2_yrs_persons"/></td><td><field name="man_month_1to2_yrs_cost"/></td></tr>
|
||||
<tr><td><b>Man Month( 2 to 3 Yrs)</b></td><td><field name="man_month_2to3_yrs_persons"/></td><td><field name="man_month_2to3_yrs_cost"/></td></tr>
|
||||
<tr><td><b>Man Month( 2 to 3 Yrs)</b></td><td><field name="man_month_1to2_yrs_persons"/></td><td><field name="man_month_1to2_yrs_cost"/></td></tr>
|
||||
<tr><td><b>Man Month( 3 to 5 Yrs)</b></td><td><field name="man_month_2to3_yrs_persons"/></td><td><field name="man_month_2to3_yrs_cost"/></td></tr>
|
||||
<tr><td><b>Man Month(Manager)</b></td><td><field name="man_month_manager_persons"/></td><td><field name="man_month_manager_cost"/></td></tr>
|
||||
<tr><td><b>Total</b></td><td colspan="2"><field name="iandc_costing"/></td></tr>
|
||||
</tbody>
|
||||
|
|
|
|||
Loading…
Reference in New Issue