Compare commits
2 Commits
Laxmii-Dev
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 89bd47ed7f | |||
| 8ca8cbeab9 |
File diff suppressed because it is too large
Load Diff
@@ -57,7 +57,7 @@ def client_edit_rate(rate_id):
|
|||||||
|
|
||||||
if result["success"]:
|
if result["success"]:
|
||||||
flash(result["message"], "success")
|
flash(result["message"], "success")
|
||||||
return redirect(url_for("engineering.client-rate"))
|
return redirect(url_for("engineering.client_rate_master"))
|
||||||
|
|
||||||
flash(result["message"], "danger")
|
flash(result["message"], "danger")
|
||||||
|
|
||||||
@@ -75,7 +75,7 @@ def client_edit_rate(rate_id):
|
|||||||
def client_delete_rate(rate_id):
|
def client_delete_rate(rate_id):
|
||||||
ClientRateService.delete_rate(rate_id)
|
ClientRateService.delete_rate(rate_id)
|
||||||
flash("Rate deleted successfully.", "success")
|
flash("Rate deleted successfully.", "success")
|
||||||
return redirect(url_for("engineering.client-rate"))
|
return redirect(url_for("engineering.client_rate_master"))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import pandas as pd
|
import pandas as pd
|
||||||
import io
|
import io
|
||||||
from datetime import datetime
|
|
||||||
from flask import Blueprint, render_template, request, send_file, flash, jsonify,redirect, url_for
|
from flask import Blueprint, render_template, request, send_file, flash, jsonify,redirect, url_for
|
||||||
from app.utils.helpers import login_required
|
from app.utils.helpers import login_required
|
||||||
from app.utils.regex_utils import RegularExpression
|
from app.utils.regex_utils import RegularExpression
|
||||||
@@ -188,119 +187,6 @@ def render_table_or_empty(df, model_key, table_class, raw_fields=None):
|
|||||||
return html
|
return html
|
||||||
|
|
||||||
|
|
||||||
# ---------------- EXCEL SHEET FORMATTING (Tr.Ex / Mh.Ex / MH & DC / Pipe Laying) ----------------
|
|
||||||
|
|
||||||
SHEET_TITLES = {
|
|
||||||
"Tr.Ex": "TRENCH EXCAVATION",
|
|
||||||
"Mh.Ex": "MANHOLE EXCAVATION",
|
|
||||||
"MH & DC": "MANHOLE DOMESTIC CHAMBER",
|
|
||||||
"Pipe Laying": "PIPE LAYING",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def format_detail_sheet(workbook, worksheet, df, sheet_name, contractor_name="", ra_bill_no="", report_date=""):
|
|
||||||
"""
|
|
||||||
Writes an info line (Category / Contractor / RA Bill No / Date) at the
|
|
||||||
top, followed by a bold/bordered table with sensible column widths
|
|
||||||
and a frozen header row.
|
|
||||||
|
|
||||||
Assumes df.to_excel(..., startrow=1) already wrote the header at row 1
|
|
||||||
and the data starting at row 2 - this function overwrites those rows
|
|
||||||
with formatting and fills in row 0 with the info line.
|
|
||||||
"""
|
|
||||||
if df.empty:
|
|
||||||
return
|
|
||||||
|
|
||||||
header_format = workbook.add_format({
|
|
||||||
"bold": True,
|
|
||||||
"bg_color": "#D9EAD3",
|
|
||||||
"border": 1,
|
|
||||||
"align": "center",
|
|
||||||
"valign": "vcenter",
|
|
||||||
"text_wrap": True
|
|
||||||
})
|
|
||||||
|
|
||||||
text_format = workbook.add_format({
|
|
||||||
"border": 1,
|
|
||||||
"valign": "vcenter"
|
|
||||||
})
|
|
||||||
|
|
||||||
number_format = workbook.add_format({
|
|
||||||
"border": 1,
|
|
||||||
"valign": "vcenter",
|
|
||||||
"num_format": "#,##0.00"
|
|
||||||
})
|
|
||||||
|
|
||||||
n_rows, n_cols = df.shape
|
|
||||||
header_row = 1 # matches startrow=1 used in df.to_excel()
|
|
||||||
|
|
||||||
# -----------------------------------------------------
|
|
||||||
# INFO LINE (row 0) - separate cells, not one merged block,
|
|
||||||
# so each field (RA Bill No, Date, etc.) can be clicked/copied
|
|
||||||
# on its own. Always starts at column 0, so position is
|
|
||||||
# consistent regardless of how many table columns exist.
|
|
||||||
# -----------------------------------------------------
|
|
||||||
last_col = max(n_cols - 1, 0)
|
|
||||||
report_title = SHEET_TITLES.get(sheet_name, sheet_name.upper())
|
|
||||||
|
|
||||||
label_format = workbook.add_format({"bold": True})
|
|
||||||
value_format = workbook.add_format({})
|
|
||||||
|
|
||||||
info_fields = [("Category:", report_title)]
|
|
||||||
|
|
||||||
# Client reports don't have a single contractor (data can span
|
|
||||||
# multiple subcontractors), so this field is only shown when a
|
|
||||||
# contractor name is actually passed in (Subcontractor report).
|
|
||||||
if contractor_name:
|
|
||||||
info_fields.append(("Contractor:", contractor_name))
|
|
||||||
|
|
||||||
info_fields.append(("RA Bill No:", ra_bill_no or "All"))
|
|
||||||
info_fields.append(("Date:", report_date))
|
|
||||||
|
|
||||||
col = 0
|
|
||||||
for label, value in info_fields:
|
|
||||||
worksheet.write(0, col, label, label_format)
|
|
||||||
worksheet.write(0, col + 1, value, value_format)
|
|
||||||
col += 3 # leave one blank column as a visual gap before next field
|
|
||||||
|
|
||||||
# Fill any remaining columns on row 0 with a blank bordered cell so
|
|
||||||
# the row reads cleanly if the sheet is wider than the info fields.
|
|
||||||
if last_col > col:
|
|
||||||
worksheet.write_blank(0, col, None)
|
|
||||||
|
|
||||||
# -----------------------------------------------------
|
|
||||||
# TABLE HEADER (row 1) - re-write with formatting
|
|
||||||
# -----------------------------------------------------
|
|
||||||
for col_idx, col_name in enumerate(df.columns):
|
|
||||||
worksheet.write(header_row, col_idx, col_name, header_format)
|
|
||||||
|
|
||||||
# -----------------------------------------------------
|
|
||||||
# TABLE BODY (rows 2+) - borders / number formatting
|
|
||||||
# -----------------------------------------------------
|
|
||||||
for row_idx in range(n_rows):
|
|
||||||
for col_idx, col_name in enumerate(df.columns):
|
|
||||||
value = df.iat[row_idx, col_idx]
|
|
||||||
|
|
||||||
if pd.isna(value):
|
|
||||||
worksheet.write(header_row + 1 + row_idx, col_idx, "", text_format)
|
|
||||||
elif isinstance(value, (int, float)):
|
|
||||||
worksheet.write_number(header_row + 1 + row_idx, col_idx, float(value), number_format)
|
|
||||||
else:
|
|
||||||
worksheet.write(header_row + 1 + row_idx, col_idx, str(value), text_format)
|
|
||||||
|
|
||||||
# Auto width columns based on header + content length
|
|
||||||
for col_idx, col_name in enumerate(df.columns):
|
|
||||||
max_len = len(str(col_name))
|
|
||||||
if n_rows:
|
|
||||||
col_values = df.iloc[:, col_idx].astype(str)
|
|
||||||
content_max = col_values.map(len).max()
|
|
||||||
max_len = max(max_len, content_max)
|
|
||||||
worksheet.set_column(col_idx, col_idx, min(max_len + 4, 30))
|
|
||||||
|
|
||||||
worksheet.freeze_panes(header_row + 1, 1)
|
|
||||||
worksheet.set_row(header_row, 30)
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------- FETCH ----------------
|
# ---------------- FETCH ----------------
|
||||||
class SubcontractorBill:
|
class SubcontractorBill:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -679,16 +565,6 @@ def report_file():
|
|||||||
# DOWNLOAD EXCEL
|
# DOWNLOAD EXCEL
|
||||||
# ===================================================
|
# ===================================================
|
||||||
if action in ["excel", "excel_all"]:
|
if action in ["excel", "excel_all"]:
|
||||||
|
|
||||||
# Look up the contractor's real name once, up front, so it can
|
|
||||||
# be shown in the title block of every sheet.
|
|
||||||
sc_obj = next(
|
|
||||||
(s for s in subcontractors if str(s.id) == str(subcontractor_id)),
|
|
||||||
None
|
|
||||||
)
|
|
||||||
contractor_display_name = sc_obj.subcontractor_name if sc_obj else ""
|
|
||||||
report_date = datetime.now().strftime("%d-%b-%Y")
|
|
||||||
|
|
||||||
output = io.BytesIO()
|
output = io.BytesIO()
|
||||||
|
|
||||||
with pd.ExcelWriter(output,engine="xlsxwriter") as writer:
|
with pd.ExcelWriter(output,engine="xlsxwriter") as writer:
|
||||||
@@ -696,6 +572,7 @@ def report_file():
|
|||||||
abstract = AbstractReportService(subcontractor_id=subcontractor_id,ra_bill_no=ra_bill_no)
|
abstract = AbstractReportService(subcontractor_id=subcontractor_id,ra_bill_no=ra_bill_no)
|
||||||
abstract.generate(workbook)
|
abstract.generate(workbook)
|
||||||
|
|
||||||
|
|
||||||
sheet_map = [
|
sheet_map = [
|
||||||
(bill.df_tr, "Tr.Ex"),
|
(bill.df_tr, "Tr.Ex"),
|
||||||
(bill.df_mh, "Mh.Ex"),
|
(bill.df_mh, "Mh.Ex"),
|
||||||
@@ -704,24 +581,17 @@ def report_file():
|
|||||||
]
|
]
|
||||||
for df, sheet_name in sheet_map:
|
for df, sheet_name in sheet_map:
|
||||||
if not df.empty:
|
if not df.empty:
|
||||||
# Use a copy for the export only - bill.df_tr etc. still
|
df.to_excel(writer, sheet_name=sheet_name, index=False)
|
||||||
# need the real "Id" column later for the Edit/Delete
|
|
||||||
# buttons on the web preview table.
|
|
||||||
export_df = df.drop(columns=["Id"], errors="ignore").copy()
|
|
||||||
export_df.insert(0, "Sr No", range(1, len(export_df) + 1))
|
|
||||||
|
|
||||||
export_df.to_excel(writer, sheet_name=sheet_name, index=False, startrow=1)
|
|
||||||
worksheet = writer.sheets[sheet_name]
|
|
||||||
format_detail_sheet(
|
|
||||||
workbook, worksheet, export_df, sheet_name,
|
|
||||||
contractor_name=contractor_display_name,
|
|
||||||
ra_bill_no=ra_bill_no,
|
|
||||||
report_date=report_date
|
|
||||||
)
|
|
||||||
writer.close()
|
writer.close()
|
||||||
output.seek(0)
|
output.seek(0)
|
||||||
|
|
||||||
sc_name = re.sub(r'[^A-Za-z0-9_-]+', '_', contractor_display_name or "Subcontractor").strip('_')
|
|
||||||
|
sc_obj = next(
|
||||||
|
(s for s in subcontractors if str(s.id) == str(subcontractor_id)),
|
||||||
|
None
|
||||||
|
)
|
||||||
|
sc_name = sc_obj.subcontractor_name if sc_obj else "Subcontractor"
|
||||||
|
sc_name = re.sub(r'[^A-Za-z0-9_-]+', '_', sc_name).strip('_')
|
||||||
|
|
||||||
name_parts = [sc_name]
|
name_parts = [sc_name]
|
||||||
|
|
||||||
@@ -950,52 +820,22 @@ def client_report():
|
|||||||
# -------- DOWNLOAD --------
|
# -------- DOWNLOAD --------
|
||||||
if action == "download":
|
if action == "download":
|
||||||
|
|
||||||
report_date = datetime.now().strftime("%d-%b-%Y")
|
|
||||||
|
|
||||||
output = io.BytesIO()
|
output = io.BytesIO()
|
||||||
|
|
||||||
with pd.ExcelWriter(output, engine="xlsxwriter") as writer:
|
with pd.ExcelWriter(output, engine="xlsxwriter") as writer:
|
||||||
workbook = writer.book
|
workbook = writer.book
|
||||||
abstract_service.generate(workbook)
|
abstract_service.generate(workbook)
|
||||||
|
|
||||||
sheet_map = [
|
bill_gen.df_tr.to_excel(writer, index=False, sheet_name="Tr.Ex")
|
||||||
(bill_gen.df_tr, "Tr.Ex"),
|
bill_gen.df_mh.to_excel(writer, index=False, sheet_name="Mh.Ex")
|
||||||
(bill_gen.df_mh, "Mh.Ex"),
|
bill_gen.df_dc.to_excel(writer, index=False, sheet_name="MH & DC")
|
||||||
(bill_gen.df_dc, "MH & DC"),
|
bill_gen.df_laying.to_excel(writer, index=False, sheet_name="Pipe Laying")
|
||||||
(bill_gen.df_laying, "Pipe Laying"),
|
|
||||||
]
|
|
||||||
for df, sheet_name in sheet_map:
|
|
||||||
if not df.empty:
|
|
||||||
# Use a copy for the export only, same as the
|
|
||||||
# Subcontractor report, so the formatted sheet gets
|
|
||||||
# a clean "Sr No" column instead of the raw Id.
|
|
||||||
export_df = df.drop(columns=["Id"], errors="ignore").copy()
|
|
||||||
export_df.insert(0, "Sr No", range(1, len(export_df) + 1))
|
|
||||||
|
|
||||||
export_df.to_excel(writer, sheet_name=sheet_name, index=False, startrow=1)
|
|
||||||
worksheet = writer.sheets[sheet_name]
|
|
||||||
format_detail_sheet(
|
|
||||||
workbook, worksheet, export_df, sheet_name,
|
|
||||||
contractor_name="",
|
|
||||||
ra_bill_no=RA_Bill_No,
|
|
||||||
report_date=report_date
|
|
||||||
)
|
|
||||||
|
|
||||||
output.seek(0)
|
output.seek(0)
|
||||||
|
|
||||||
filename_parts = [f"Client_RA_{re.sub(r'[^A-Za-z0-9_-]+', '_', RA_Bill_No)}"]
|
|
||||||
|
|
||||||
if location:
|
|
||||||
filename_parts.append(re.sub(r'[^A-Za-z0-9_-]+', '_', location).strip('_'))
|
|
||||||
|
|
||||||
if category and category != "all":
|
|
||||||
filename_parts.append(category.upper())
|
|
||||||
|
|
||||||
filename = "_".join(filename_parts) + "_Report.xlsx"
|
|
||||||
|
|
||||||
return send_file(
|
return send_file(
|
||||||
output,
|
output,
|
||||||
download_name=filename,
|
download_name=f"Client_RA_{RA_Bill_No}_Report.xlsx",
|
||||||
as_attachment=True
|
as_attachment=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -142,11 +142,21 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th>Sr No</th>
|
<th>Sr No</th>
|
||||||
<th>Strata Type & Depth</th>
|
<th>Strata Type & Depth</th>
|
||||||
|
|
||||||
<th class="text-end">Client Qty</th>
|
<th class="text-end">Client Qty</th>
|
||||||
|
<th class="text-end">Client Rate</th>
|
||||||
|
<th class="text-end">Client Amount</th>
|
||||||
|
|
||||||
<th class="text-end">Sub Contractor Qty</th>
|
<th class="text-end">Sub Contractor Qty</th>
|
||||||
<th class="text-end">Difference</th>
|
<th class="text-end">Sub Rate</th>
|
||||||
|
<th class="text-end">Sub Amount</th>
|
||||||
|
|
||||||
|
<th class="text-end">Qty Difference</th>
|
||||||
|
<th class="text-end">Rate Difference</th>
|
||||||
|
<th class="text-end">Amount Difference</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody></tbody>
|
<tbody></tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@@ -162,13 +172,11 @@
|
|||||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
let barChart;
|
let barChart;
|
||||||
let raBillChoice;
|
let raBillChoice;
|
||||||
|
|
||||||
/* Load RA Bills */
|
/* Load RA Bills */
|
||||||
function loadRABills(){
|
function loadRABills(){
|
||||||
|
|
||||||
let subcontractor = document.getElementById("subcontractor").value
|
let subcontractor = document.getElementById("subcontractor").value
|
||||||
let category = document.getElementById("category").value
|
let category = document.getElementById("category").value
|
||||||
|
|
||||||
@@ -176,30 +184,21 @@
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
if (!subcontractor || !category) {
|
if (!subcontractor || !category) {
|
||||||
|
|
||||||
raBillChoice.clearStore();
|
raBillChoice.clearStore();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch(`/dashboard/api/get-ra-bills?subcontractor=${subcontractor}&category=${category}`)
|
fetch(`/dashboard/api/get-ra-bills?subcontractor=${subcontractor}&category=${category}`)
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
|
|
||||||
raBillChoice.clearStore();
|
raBillChoice.clearStore();
|
||||||
|
|
||||||
let choices = [];
|
let choices = [];
|
||||||
|
|
||||||
data.ra_bills.forEach(function(bill){
|
data.ra_bills.forEach(function(bill){
|
||||||
|
|
||||||
choices.push({
|
choices.push({
|
||||||
|
|
||||||
value: bill,
|
value: bill,
|
||||||
|
|
||||||
label: bill
|
label: bill
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
raBillChoice.setChoices(
|
raBillChoice.setChoices(
|
||||||
@@ -208,7 +207,6 @@
|
|||||||
"label",
|
"label",
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -327,38 +325,87 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* TABLE */
|
/* TABLE */
|
||||||
function drawTable(data){
|
function drawTable(data) {
|
||||||
|
let html = "";
|
||||||
|
for (let i = 0; i < data.labels.length; i++) {
|
||||||
|
|
||||||
let html='';
|
|
||||||
for(let i=0;i<data.labels.length;i++){
|
|
||||||
const clientQty = Number(data.client_qty[i] || 0);
|
const clientQty = Number(data.client_qty[i] || 0);
|
||||||
const subQty = Number(data.sub_qty[i] || 0);
|
const subQty = Number(data.sub_qty[i] || 0);
|
||||||
const diff = clientQty - subQty;
|
|
||||||
|
|
||||||
html+=`
|
const clientRate = Number(data.client_rate[i] || 0);
|
||||||
<tr>
|
const subRate = Number(data.sub_rate[i] || 0);
|
||||||
<td class="text-center">${i + 1}</td>
|
|
||||||
<td>${data.labels[i]}</td>
|
|
||||||
<td class="text-end">${data.client_qty[i]}</td>
|
|
||||||
<td class="text-end">${data.sub_qty[i]}</td>
|
|
||||||
<td class="text-end fw-bold ${diff >= 0 ? 'text-success' : 'text-danger'}">${diff.toFixed(2)}</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
|
const clientAmount = Number(data.client_amount[i] || 0);
|
||||||
|
const subAmount = Number(data.sub_amount[i] || 0);
|
||||||
|
|
||||||
|
const qtyDiff = Number(data.qty_difference[i] || 0 );
|
||||||
|
const rateDiff = Number(data.rate_difference[i] || 0 );
|
||||||
|
const amountDiff = Number(data.amount_difference[i] || 0 );
|
||||||
|
|
||||||
|
html += `
|
||||||
|
<tr>
|
||||||
|
<td class="text-center">
|
||||||
|
${i + 1}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
${data.labels[i]}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<!-- CLIENT -->
|
||||||
|
<td class="text-end">
|
||||||
|
${clientQty.toFixed(2)}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td class="text-end">
|
||||||
|
₹ ${clientRate.toFixed(2)}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td class="text-end fw-bold">
|
||||||
|
₹ ${clientAmount.toFixed(2)}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<!-- SUBCONTRACTOR -->
|
||||||
|
<td class="text-end">
|
||||||
|
${subQty.toFixed(2)}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td class="text-end">
|
||||||
|
₹ ${subRate.toFixed(2)}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td class="text-end fw-bold">
|
||||||
|
₹ ${subAmount.toFixed(2)}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<!-- DIFFERENCES -->
|
||||||
|
<td class="text-end fw-bold
|
||||||
|
${qtyDiff >= 0 ? 'text-success' : 'text-danger'}">
|
||||||
|
${qtyDiff.toFixed(2)}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td class="text-end fw-bold
|
||||||
|
${rateDiff >= 0 ? 'text-success' : 'text-danger'}">
|
||||||
|
₹ ${rateDiff.toFixed(2)}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td class="text-end fw-bold
|
||||||
|
${amountDiff >= 0 ? 'text-success' : 'text-danger'}">
|
||||||
|
₹ ${amountDiff.toFixed(2)}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
document.querySelector("#resultTable tbody").innerHTML = html;
|
document.querySelector(
|
||||||
|
"#resultTable tbody"
|
||||||
|
).innerHTML = html;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* EVENTS */
|
/* EVENTS */
|
||||||
document.getElementById("subcontractor").addEventListener("change", loadRABills);
|
document.getElementById("subcontractor").addEventListener("change", loadRABills);
|
||||||
|
|
||||||
document.getElementById("category").addEventListener("change", loadRABills);
|
document.getElementById("category").addEventListener("change", loadRABills);
|
||||||
|
|
||||||
document.getElementById("searchBtn").addEventListener("click", loadDashboard);
|
document.getElementById("searchBtn").addEventListener("click", loadDashboard);
|
||||||
|
|
||||||
document.getElementById("resetBtn").addEventListener("click", function () {location.reload();});
|
document.getElementById("resetBtn").addEventListener("click", function () {location.reload();});
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
|||||||
Reference in New Issue
Block a user