Compare commits
3 Commits
Laxmi-Devs
...
production
| Author | SHA1 | Date | |
|---|---|---|---|
| 861a61c6ec | |||
| 375e899857 | |||
| 18874cc84e |
37
.env
Normal file
37
.env
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# -----------------------------
|
||||||
|
# Flask App Configuration
|
||||||
|
# -----------------------------
|
||||||
|
FLASK_ENV=development
|
||||||
|
FLASK_DEBUG=True
|
||||||
|
FLASK_HOST=0.0.0.0
|
||||||
|
FLASK_PORT=5015
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# Security
|
||||||
|
# -----------------------------
|
||||||
|
SECRET_KEY=change-this-to-strong-secret-key
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# Database Configuration
|
||||||
|
# -----------------------------
|
||||||
|
DB_DIALECT=mysql
|
||||||
|
DB_DRIVER=pymysql
|
||||||
|
DB_HOST=127.0.0.1
|
||||||
|
DB_PORT=3306
|
||||||
|
DB_NAME=comparisondb
|
||||||
|
DB_USER=root
|
||||||
|
DB_PASSWORD=root
|
||||||
|
|
||||||
|
# DATABASE_URL=mysql+pymysql://root:root@localhost/comparisondb
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# LDAP Configuration new
|
||||||
|
# -----------------------------
|
||||||
|
LDAP_SERVER=ldap://host.docker.internal
|
||||||
|
LDAP_PORT=389
|
||||||
|
LDAP_USE_SSL=False
|
||||||
|
|
||||||
|
LDAP_DOMAIN=lcepl.org
|
||||||
|
LDAP_BASE_DN=DC=lcepl,DC=org
|
||||||
|
LDAP_SEARCH_BASE=OU=Users,DC=lcepl,DC=org
|
||||||
19
.gitignore
vendored
19
.gitignore
vendored
@@ -1,22 +1,19 @@
|
|||||||
# Python
|
# Python
|
||||||
app/__pycache__/
|
app/__pycache__/
|
||||||
__pycache__/
|
|
||||||
*.pyc
|
*.pyc
|
||||||
*.pyo
|
*.pyo
|
||||||
*.pyd
|
*.pyd
|
||||||
|
*.__pycache__
|
||||||
|
|
||||||
# Ignore upload files
|
# Ingnor upload files
|
||||||
app/static/uploads/
|
app/static/uploads/
|
||||||
|
|
||||||
# Virtual environment
|
# Ignore env files
|
||||||
venv/
|
venv
|
||||||
.venv/
|
|
||||||
|
|
||||||
# Environment / secrets
|
# Ignore Log files ss
|
||||||
.env
|
|
||||||
.env.*
|
|
||||||
!.env.example
|
|
||||||
|
|
||||||
# Log files
|
|
||||||
logs/
|
logs/
|
||||||
*.log
|
*.log
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from flask import (
|
|||||||
request,
|
request,
|
||||||
redirect,
|
redirect,
|
||||||
url_for,
|
url_for,
|
||||||
flash
|
flash, jsonify
|
||||||
)
|
)
|
||||||
|
|
||||||
from app.models.subcontractor_model import Subcontractor
|
from app.models.subcontractor_model import Subcontractor
|
||||||
@@ -27,25 +27,7 @@ def engineering_master():
|
|||||||
title="Engineering Masters"
|
title="Engineering Masters"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Client rate model
|
||||||
@engi_bp.route("/subcontractor-rate")
|
|
||||||
def add_subcontractor_rates():
|
|
||||||
subcontractors = Subcontractor.query.filter_by(status="Active").all()
|
|
||||||
|
|
||||||
if request.method == "POST":
|
|
||||||
try:
|
|
||||||
SubcontractorRateService.save_rate(request.form)
|
|
||||||
flash(SuccessMessage.SAVE, "success")
|
|
||||||
return redirect(url_for("engineering.add_subcontractor_rates"))
|
|
||||||
except Exception as e:
|
|
||||||
flash(ErrorMessage.INTERNAL_SERVER_ERROR, "danger")
|
|
||||||
|
|
||||||
return render_template(
|
|
||||||
"engineering/add_rate.html",
|
|
||||||
title="Subcontractor Rate Master",
|
|
||||||
subcontractors=subcontractors
|
|
||||||
)
|
|
||||||
|
|
||||||
@engi_bp.route("/client-rate")
|
@engi_bp.route("/client-rate")
|
||||||
def client_rates():
|
def client_rates():
|
||||||
|
|
||||||
@@ -54,3 +36,72 @@ def client_rates():
|
|||||||
title="Client Rate Master"
|
title="Client Rate Master"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@engi_bp.route("/subcontractor-rate", methods=["GET", "POST"])
|
||||||
|
def add_subcontractor_rates():
|
||||||
|
|
||||||
|
subcontractors = Subcontractor.query.filter_by(status="Active").all()
|
||||||
|
|
||||||
|
if request.method == "POST":
|
||||||
|
result = SubcontractorRateService.save_or_update(request.form)
|
||||||
|
if result["success"]:
|
||||||
|
flash(result["message"], "success")
|
||||||
|
return redirect(url_for("engineering.add_subcontractor_rates"))
|
||||||
|
else:
|
||||||
|
flash(result["message"], "danger")
|
||||||
|
|
||||||
|
rates = SubcontractorRateService.get_all_rates()
|
||||||
|
|
||||||
|
return render_template(
|
||||||
|
"engineering/contractor_rate.html",
|
||||||
|
title="Subcontractor Rate Master",
|
||||||
|
subcontractors=subcontractors,
|
||||||
|
rates=rates
|
||||||
|
)
|
||||||
|
|
||||||
|
@engi_bp.route("/subcontractor-rate/edit/<int:rate_id>", methods=["GET", "POST"])
|
||||||
|
def edit_rate(rate_id):
|
||||||
|
|
||||||
|
subcontractors = Subcontractor.query.filter_by(status="Active").all()
|
||||||
|
|
||||||
|
if request.method == "POST":
|
||||||
|
|
||||||
|
result = SubcontractorRateService.save_or_update(request.form)
|
||||||
|
|
||||||
|
if result["success"]:
|
||||||
|
flash(result["message"], "success")
|
||||||
|
return redirect(url_for("engineering.add_subcontractor_rates"))
|
||||||
|
|
||||||
|
flash(result["message"], "danger")
|
||||||
|
|
||||||
|
rate = SubcontractorRateService.get_rate(rate_id)
|
||||||
|
|
||||||
|
rates = SubcontractorRateService.get_all_rates()
|
||||||
|
|
||||||
|
return render_template(
|
||||||
|
"engineering/contractor_rate.html",
|
||||||
|
subcontractors=subcontractors,
|
||||||
|
rate=rate,
|
||||||
|
rates=rates
|
||||||
|
)
|
||||||
|
|
||||||
|
@engi_bp.route("/subcontractor-rate/delete/<int:rate_id>")
|
||||||
|
def delete_rate(rate_id):
|
||||||
|
SubcontractorRateService.delete_rate(rate_id)
|
||||||
|
flash("Rate deleted successfully.", "success")
|
||||||
|
return redirect(url_for("engineering.add_subcontractor_rates"))
|
||||||
|
|
||||||
|
|
||||||
|
@engi_bp.route("/check-rate")
|
||||||
|
def check_rate():
|
||||||
|
|
||||||
|
exists = SubcontractorRateService.check_duplicate(
|
||||||
|
subcontractor_id=request.args.get("subcontractor_id"),
|
||||||
|
category=request.args.get("category"),
|
||||||
|
item_name=request.args.get("item_name"),
|
||||||
|
rate_id=request.args.get("rate_id")
|
||||||
|
)
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
"exists": exists
|
||||||
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -55,115 +54,6 @@ def add_action_columns(df, model_key):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------- 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),
|
|
||||||
("Contractor:", contractor_name or "-"),
|
|
||||||
("RA Bill No:", ra_bill_no or "All"),
|
|
||||||
("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):
|
||||||
@@ -427,16 +317,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:
|
||||||
@@ -444,6 +324,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"),
|
||||||
@@ -452,24 +333,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]
|
||||||
|
|
||||||
|
|||||||
@@ -1,28 +1,66 @@
|
|||||||
from app.services.db_service import db
|
from app.services.db_service import db
|
||||||
from app.models.subcontractor_rate_model import SubcontractorRate
|
from app.models.subcontractor_rate_model import SubcontractorRate
|
||||||
|
from sqlalchemy import func
|
||||||
|
|
||||||
|
|
||||||
class SubcontractorRateService:
|
class SubcontractorRateService:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def save_rate(form):
|
def save_or_update(form):
|
||||||
|
|
||||||
rate = SubcontractorRate(
|
rate_id = form.get("id")
|
||||||
subcontractor_id=form.get("subcontractor_id"),
|
|
||||||
category=form.get("category"),
|
subcontractor_id = form.get("subcontractor_id")
|
||||||
item_code=form.get("item_code"),
|
category = form.get("category")
|
||||||
item_name=form.get("item_name"),
|
item_name = form.get("item_name").strip()
|
||||||
unit=form.get("unit"),
|
|
||||||
rate=form.get("rate"),
|
# -----------------------------
|
||||||
effective_from=form.get("effective_from"),
|
# Duplicate Validation
|
||||||
effective_to=form.get("effective_to") or None,
|
# -----------------------------
|
||||||
status=form.get("status")
|
duplicate = (
|
||||||
|
SubcontractorRate.query
|
||||||
|
.filter(
|
||||||
|
SubcontractorRate.subcontractor_id == subcontractor_id,
|
||||||
|
SubcontractorRate.category == category,
|
||||||
|
func.lower(SubcontractorRate.item_name) == item_name.lower()
|
||||||
|
)
|
||||||
|
.first()
|
||||||
)
|
)
|
||||||
|
|
||||||
db.session.add(rate)
|
# Ignore current record while editing
|
||||||
db.session.commit()
|
if duplicate and (not rate_id or duplicate.id != int(rate_id)):
|
||||||
return rate
|
return {
|
||||||
|
"success": False,
|
||||||
|
"message": "Category and Rate already exists for this Subcontractor."
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# Insert / Update
|
||||||
|
# -----------------------------
|
||||||
|
if rate_id:
|
||||||
|
rate = SubcontractorRate.query.get_or_404(rate_id)
|
||||||
|
else:
|
||||||
|
rate = SubcontractorRate()
|
||||||
|
|
||||||
|
rate.subcontractor_id = subcontractor_id
|
||||||
|
rate.category = category
|
||||||
|
rate.item_code = form.get("item_code")
|
||||||
|
rate.item_name = item_name
|
||||||
|
rate.unit = form.get("unit")
|
||||||
|
rate.rate = form.get("rate")
|
||||||
|
rate.effective_from = form.get("effective_from")
|
||||||
|
rate.effective_to = form.get("effective_to") or None
|
||||||
|
rate.status = form.get("status")
|
||||||
|
|
||||||
|
if not rate_id:
|
||||||
|
db.session.add(rate)
|
||||||
|
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"message": "Saved Successfully."
|
||||||
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_all_rates():
|
def get_all_rates():
|
||||||
@@ -32,34 +70,33 @@ class SubcontractorRateService:
|
|||||||
.order_by(SubcontractorRate.created_at.desc())
|
.order_by(SubcontractorRate.created_at.desc())
|
||||||
.all()
|
.all()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_rate(rate_id):
|
def get_rate(rate_id):
|
||||||
|
|
||||||
return SubcontractorRate.query.get_or_404(rate_id)
|
return SubcontractorRate.query.get_or_404(rate_id)
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def update_rate(rate_id, form):
|
|
||||||
|
|
||||||
rate = SubcontractorRate.query.get_or_404(rate_id)
|
|
||||||
|
|
||||||
rate.subcontractor_id = form.get("subcontractor_id")
|
|
||||||
rate.category = form.get("category")
|
|
||||||
rate.item_code = form.get("item_code")
|
|
||||||
rate.item_name = form.get("item_name")
|
|
||||||
rate.unit = form.get("unit")
|
|
||||||
rate.rate = form.get("rate")
|
|
||||||
rate.effective_from = form.get("effective_from")
|
|
||||||
rate.effective_to = form.get("effective_to") or None
|
|
||||||
rate.status = form.get("status")
|
|
||||||
|
|
||||||
db.session.commit()
|
|
||||||
return rate
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def delete_rate(rate_id):
|
def delete_rate(rate_id):
|
||||||
|
|
||||||
rate = SubcontractorRate.query.get_or_404(rate_id)
|
rate = SubcontractorRate.query.get_or_404(rate_id)
|
||||||
|
|
||||||
db.session.delete(rate)
|
db.session.delete(rate)
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def check_duplicate(subcontractor_id, category, item_name, rate_id=None):
|
||||||
|
|
||||||
|
query = SubcontractorRate.query.filter(
|
||||||
|
SubcontractorRate.subcontractor_id == subcontractor_id,
|
||||||
|
SubcontractorRate.category == category,
|
||||||
|
func.lower(SubcontractorRate.item_name) == item_name.strip().lower()
|
||||||
|
)
|
||||||
|
|
||||||
|
if rate_id:
|
||||||
|
query = query.filter(SubcontractorRate.id != int(rate_id))
|
||||||
|
|
||||||
|
return query.first() is not None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -132,7 +132,6 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
|
||||||
<!-- Formats -->
|
<!-- Formats -->
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="/file_format">
|
<a class="nav-link" href="/file_format">
|
||||||
@@ -140,6 +139,37 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<!-- Masters -->
|
||||||
|
<li class="nav-item dropdown">
|
||||||
|
<a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="/engi">
|
||||||
|
<i class="bi bi-gear me-2"></i> Masters
|
||||||
|
</a>
|
||||||
|
<ul class="dropdown-menu dropdown-menu-dark">
|
||||||
|
<!-- Client Standard Rates -->
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/engi/subcontractor-rate">
|
||||||
|
<i class="bi bi-file-earmark-text me-1"></i> Rate Master
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<!-- Client Standard Rates -->
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/engi/client-rate">
|
||||||
|
<i class="bi bi-file-earmark-text me-1"></i> Client Standard Rates
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<!-- Formats -->
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/file_format">
|
||||||
|
<i class="bi bi-file-earmark-text me-1"></i> Formats
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
|
||||||
<!-- USER DROPDOWN -->
|
<!-- USER DROPDOWN -->
|
||||||
{% if session.get("user_id") %}
|
{% if session.get("user_id") %}
|
||||||
<li class="nav-item dropdown ms-lg-3">
|
<li class="nav-item dropdown ms-lg-3">
|
||||||
@@ -163,18 +193,28 @@
|
|||||||
<small class="text-muted">Logged in user</small>
|
<small class="text-muted">Logged in user</small>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<!-- Dashboard -->
|
||||||
<li>
|
<li>
|
||||||
<a class="dropdown-item" href="/dashboard">
|
<a class="dropdown-item text-light py-2" href="{{ url_for('dashboard.dashboard') }}">
|
||||||
<i class="bi bi-speedometer2 me-2"></i> Dashboard
|
<i class="bi bi-speedometer2 me-2"></i> Dashboard
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="dropdown-item" href="{{ url_for('activity.activity') }}">
|
<a class="dropdown-item text-light py-2" href="{{ url_for('activity.activity') }}">
|
||||||
<i class="bi bi-clock-history me-2"></i>
|
<i class="bi bi-clock-history me-2"></i> Activity Log
|
||||||
Activity Log
|
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<!-- Manage Account -->
|
||||||
|
<li>
|
||||||
|
<a class="dropdown-item py-2" href="#">
|
||||||
|
<i class="bi bi-gear me-2"></i> Manage Account
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li><hr class="dropdown-divider border-secondary m-0"></li>
|
||||||
|
|
||||||
|
<!-- Logout Account -->
|
||||||
<li>
|
<li>
|
||||||
<a class="dropdown-item text-warning" href="/logout">
|
<a class="dropdown-item text-warning" href="/logout">
|
||||||
<i class="bi bi-box-arrow-right me-2"></i> Logout
|
<i class="bi bi-box-arrow-right me-2"></i> Logout
|
||||||
|
|||||||
@@ -1,236 +0,0 @@
|
|||||||
{% extends "base.html" %}
|
|
||||||
{% block content %}
|
|
||||||
<div class="container-fluid mt-4">
|
|
||||||
|
|
||||||
<div class="card shadow">
|
|
||||||
|
|
||||||
<div class="card-header bg-primary text-white">
|
|
||||||
<h4 class="mb-0">
|
|
||||||
<i class="bi bi-currency-rupee"></i>
|
|
||||||
Subcontractor Rate Master
|
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card-body">
|
|
||||||
|
|
||||||
<form method="POST">
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
|
|
||||||
<!-- Subcontractor -->
|
|
||||||
<div class="col-md-4 mb-3">
|
|
||||||
<label class="form-label fw-bold">
|
|
||||||
Subcontractor
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<select name="subcontractor_id"
|
|
||||||
class="form-select"
|
|
||||||
required>
|
|
||||||
|
|
||||||
<option value="">
|
|
||||||
Select Subcontractor
|
|
||||||
</option>
|
|
||||||
|
|
||||||
{% for sub in subcontractors %}
|
|
||||||
<option value="{{ sub.id }}">
|
|
||||||
{{ sub.subcontractor_name }}
|
|
||||||
</option>
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Category -->
|
|
||||||
<div class="col-md-4 mb-3">
|
|
||||||
|
|
||||||
<label class="form-label fw-bold">
|
|
||||||
Category
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<select name="category"
|
|
||||||
class="form-select"
|
|
||||||
required>
|
|
||||||
|
|
||||||
<option value="">
|
|
||||||
Select Category
|
|
||||||
</option>
|
|
||||||
|
|
||||||
<option value="TR_EX">
|
|
||||||
Trench Excavation
|
|
||||||
</option>
|
|
||||||
|
|
||||||
<option value="MH_EX">
|
|
||||||
Manhole Excavation
|
|
||||||
</option>
|
|
||||||
|
|
||||||
<option value="MH_DC">
|
|
||||||
Manhole & Domestic Chamber
|
|
||||||
</option>
|
|
||||||
|
|
||||||
<option value="LAYING">
|
|
||||||
Pipe Laying
|
|
||||||
</option>
|
|
||||||
|
|
||||||
</select>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Status -->
|
|
||||||
<div class="col-md-4 mb-3">
|
|
||||||
|
|
||||||
<label class="form-label fw-bold">
|
|
||||||
Status
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<select name="status"
|
|
||||||
class="form-select">
|
|
||||||
|
|
||||||
<option value="Active">
|
|
||||||
Active
|
|
||||||
</option>
|
|
||||||
|
|
||||||
<option value="Inactive">
|
|
||||||
Inactive
|
|
||||||
</option>
|
|
||||||
|
|
||||||
</select>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
|
|
||||||
<!-- Item Code -->
|
|
||||||
<div class="col-md-3 mb-3">
|
|
||||||
|
|
||||||
<label class="form-label fw-bold">
|
|
||||||
Item Code
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<input type="text"
|
|
||||||
name="item_code"
|
|
||||||
class="form-control"
|
|
||||||
placeholder="SM01"
|
|
||||||
required>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Item Name -->
|
|
||||||
<div class="col-md-5 mb-3">
|
|
||||||
|
|
||||||
<label class="form-label fw-bold">
|
|
||||||
Item Name
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<input type="text"
|
|
||||||
name="item_name"
|
|
||||||
class="form-control"
|
|
||||||
placeholder="Soft Murum 0-1.5"
|
|
||||||
required>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Unit -->
|
|
||||||
<div class="col-md-2 mb-3">
|
|
||||||
|
|
||||||
<label class="form-label fw-bold">
|
|
||||||
Unit
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<select name="unit"
|
|
||||||
class="form-select">
|
|
||||||
|
|
||||||
<option value="Cum">Cum</option>
|
|
||||||
<option value="Nos">Nos</option>
|
|
||||||
<option value="Rmt">Rmt</option>
|
|
||||||
<option value="Sqm">Sqm</option>
|
|
||||||
|
|
||||||
</select>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Rate -->
|
|
||||||
<div class="col-md-2 mb-3">
|
|
||||||
|
|
||||||
<label class="form-label fw-bold">
|
|
||||||
Rate (₹)
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<input type="number"
|
|
||||||
step="0.01"
|
|
||||||
min="0"
|
|
||||||
name="rate"
|
|
||||||
class="form-control"
|
|
||||||
placeholder="0.00"
|
|
||||||
required>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
|
|
||||||
<!-- Effective From -->
|
|
||||||
<div class="col-md-3 mb-3">
|
|
||||||
|
|
||||||
<label class="form-label fw-bold">
|
|
||||||
Effective From
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<input type="date"
|
|
||||||
name="effective_from"
|
|
||||||
class="form-control"
|
|
||||||
required>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Effective To -->
|
|
||||||
<div class="col-md-3 mb-3">
|
|
||||||
|
|
||||||
<label class="form-label fw-bold">
|
|
||||||
Effective To
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<input type="date"
|
|
||||||
name="effective_to"
|
|
||||||
class="form-control">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<div class="text-end">
|
|
||||||
|
|
||||||
<button type="reset"
|
|
||||||
class="btn btn-secondary">
|
|
||||||
|
|
||||||
<i class="bi bi-arrow-clockwise"></i>
|
|
||||||
|
|
||||||
Reset
|
|
||||||
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button type="submit"
|
|
||||||
class="btn btn-success">
|
|
||||||
|
|
||||||
<i class="bi bi-check-circle"></i>
|
|
||||||
|
|
||||||
Save Rate
|
|
||||||
|
|
||||||
</button>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</form>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
{% endblock %}
|
|
||||||
20
app/templates/engineering/client_rate.html
Normal file
20
app/templates/engineering/client_rate.html
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<div class="container-fluid mt-4">
|
||||||
|
|
||||||
|
<div class="card shadow">
|
||||||
|
|
||||||
|
<div class="card-header bg-primary text-white d-flex justify-content-between align-items-center">
|
||||||
|
|
||||||
|
<h4 class="mb-0">
|
||||||
|
<i class="bi bi-currency-rupee"></i>
|
||||||
|
Client Rate Master
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
384
app/templates/engineering/contractor_rate.html
Normal file
384
app/templates/engineering/contractor_rate.html
Normal file
@@ -0,0 +1,384 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<div class="container-fluid mt-4">
|
||||||
|
|
||||||
|
<div class="card shadow">
|
||||||
|
|
||||||
|
<div class="card-header bg-primary text-white d-flex justify-content-between align-items-center">
|
||||||
|
|
||||||
|
<h4 class="mb-0">
|
||||||
|
<i class="bi bi-currency-rupee"></i>
|
||||||
|
Subcontractor Rate Master
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
|
|
||||||
|
<form method="POST" id="rateForm">
|
||||||
|
|
||||||
|
<!-- Hidden ID -->
|
||||||
|
<input type="hidden" id="rate_id" name="id" value="{{ rate.id if rate else '' }}">
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
|
||||||
|
<!-- Subcontractor -->
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<label class="form-label fw-bold"> Subcontractor </label>
|
||||||
|
<select id="subcontractor_id" name="subcontractor_id" class="form-select" required>
|
||||||
|
<option value="">-- Select Subcontractor --</option>
|
||||||
|
{% for sub in subcontractors %}
|
||||||
|
<option value="{{ sub.id }}"
|
||||||
|
{% if rate and rate.subcontractor_id==sub.id %}
|
||||||
|
selected
|
||||||
|
{% endif %}>
|
||||||
|
{{ sub.subcontractor_name }}
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Category -->
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<label class="form-label fw-bold"> Category </label>
|
||||||
|
<select id="category" name="category" class="form-select" required>
|
||||||
|
<option value="">-- Select Category --</option>
|
||||||
|
|
||||||
|
<option value="trench_excavation"
|
||||||
|
{% if rate and rate.category=="trench_excavation" %}selected{% endif %}>
|
||||||
|
Trench Excavation
|
||||||
|
</option>
|
||||||
|
<option value="manhole_excavation"
|
||||||
|
{% if rate and rate.category=="manhole_excavation" %}selected{% endif %}>
|
||||||
|
Manhole Excavation
|
||||||
|
</option>
|
||||||
|
|
||||||
|
<option value="Manhole_Domestic_Chamber"
|
||||||
|
{% if rate and rate.category=="Manhole_Domestic_Chamber" %}selected{% endif %}>
|
||||||
|
MH & Domestic Chamber
|
||||||
|
</option>
|
||||||
|
|
||||||
|
<option value="Laying"
|
||||||
|
{% if rate and rate.category=="Laying" %}selected{% endif %}>
|
||||||
|
Pipe Laying
|
||||||
|
</option>
|
||||||
|
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Status -->
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<label class="form-label fw-bold"> Status </label>
|
||||||
|
|
||||||
|
<select name="status" class="form-select">
|
||||||
|
<option value="Active"
|
||||||
|
{% if not rate or rate.status=="Active" %}selected{% endif %}>
|
||||||
|
Active
|
||||||
|
</option>
|
||||||
|
|
||||||
|
<option value="Inactive"
|
||||||
|
{% if rate and rate.status=="Inactive" %}selected{% endif %}>
|
||||||
|
Inactive
|
||||||
|
</option>
|
||||||
|
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
|
||||||
|
<!-- Item Code -->
|
||||||
|
<div class="col-md-3 mb-3">
|
||||||
|
<label class="form-label fw-bold"> Item Code </label>
|
||||||
|
<input type="text" name="item_code" class="form-control" value="{{ rate.item_code if rate else '' }}" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Item Name -->
|
||||||
|
<div class="col-md-5 mb-3">
|
||||||
|
<label class="form-label fw-bold"> Item Name </label>
|
||||||
|
<input type="text"
|
||||||
|
id="item_name"
|
||||||
|
name="item_name"
|
||||||
|
class="form-control"
|
||||||
|
autocomplete="off"
|
||||||
|
value="{{ rate.item_name if rate else '' }}"
|
||||||
|
required>
|
||||||
|
|
||||||
|
<small id="duplicateMessage"
|
||||||
|
class="text-danger"
|
||||||
|
style="display:none;">
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Unit -->
|
||||||
|
<div class="col-md-2 mb-3">
|
||||||
|
<label class="form-label fw-bold">Unit</label>
|
||||||
|
<select name="unit" class="form-select">
|
||||||
|
<option value="Cum"
|
||||||
|
{% if not rate or rate.unit=="Cum" %}selected{% endif %}>
|
||||||
|
Cum
|
||||||
|
</option>
|
||||||
|
<option value="Nos"
|
||||||
|
{% if rate and rate.unit=="Nos" %}selected{% endif %}>
|
||||||
|
Nos
|
||||||
|
</option>
|
||||||
|
<option value="Rmt"
|
||||||
|
{% if rate and rate.unit=="Rmt" %}selected{% endif %}>
|
||||||
|
Rmt
|
||||||
|
</option>
|
||||||
|
<option value="Sqm"
|
||||||
|
{% if rate and rate.unit=="Sqm" %}selected{% endif %}>
|
||||||
|
Sqm
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Rate -->
|
||||||
|
<div class="col-md-2 mb-3">
|
||||||
|
<label class="form-label fw-bold">Rate</label>
|
||||||
|
<input type="number" step="0.01" name="rate" class="form-control" value="{{ rate.rate if rate else '' }}" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-3 mb-3">
|
||||||
|
<label class="form-label fw-bold"> Effective From </label>
|
||||||
|
<input type="date" name="effective_from" class="form-control" value="{{ rate.effective_from if rate else '' }}" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-3 mb-3">
|
||||||
|
<label class="form-label fw-bold"> Effective To </label>
|
||||||
|
<input type="date" name="effective_to" class="form-control" value="{{ rate.effective_to if rate else '' }}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<div class="text-end">
|
||||||
|
<a href="{{ url_for('engineering.add_subcontractor_rates') }}" class="btn btn-secondary">
|
||||||
|
<i class="bi bi-arrow-clockwise"></i> Reset
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<button type="submit" id="saveBtn" class="btn btn-success">
|
||||||
|
{% if rate %}
|
||||||
|
<i class="bi bi-pencil-square"></i>
|
||||||
|
Update Rate
|
||||||
|
{% else %}
|
||||||
|
<i class="bi bi-check-circle"></i>
|
||||||
|
Save Rate
|
||||||
|
{% endif %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Table -->
|
||||||
|
<div class="card shadow mt-4">
|
||||||
|
<div class="card-header bg-dark text-white">
|
||||||
|
<h5 class="mb-0">
|
||||||
|
<i class="bi bi-table"></i>Rate List
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
|
<table class="table table-bordered table-hover table-striped" id="rateTable">
|
||||||
|
<thead class="table-primary">
|
||||||
|
<tr>
|
||||||
|
<th>#</th>
|
||||||
|
<th>Subcontractor</th>
|
||||||
|
<th>Category</th>
|
||||||
|
<th>Item Code</th>
|
||||||
|
<th>Item Name</th>
|
||||||
|
<th>Unit</th>
|
||||||
|
<th>Rate</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th width="130">Action</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
|
||||||
|
{% for row in rates %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ loop.index }}</td>
|
||||||
|
<td>{{ row.subcontractor.subcontractor_name }}</td>
|
||||||
|
<td>{{ row.category }}</td>
|
||||||
|
<td>{{ row.item_code }}</td>
|
||||||
|
<td>{{ row.item_name }}</td>
|
||||||
|
<td>{{ row.unit }}</td>
|
||||||
|
<td>{{ row.rate }}</td>
|
||||||
|
<td>
|
||||||
|
{% if row.status=="Active" %}
|
||||||
|
<span class="badge bg-success">
|
||||||
|
Active
|
||||||
|
</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="badge bg-danger">
|
||||||
|
Inactive
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<a href="{{ url_for('engineering.edit_rate', rate_id=row.id) }}" class="btn btn-warning btn-sm">
|
||||||
|
<i class="bi bi-pencil-square"></i>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="{{ url_for('engineering.delete_rate', rate_id=row.id) }}"
|
||||||
|
class="btn btn-danger btn-sm" onclick="return confirm('Delete this Item:{{row.item_name}} & Rate:{{row.rate}} ?')">
|
||||||
|
<i class="bi bi-trash"></i>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
$(document).ready(function () {
|
||||||
|
|
||||||
|
// ----------------------------
|
||||||
|
// DataTable
|
||||||
|
// ----------------------------
|
||||||
|
$("#rateTable").DataTable({
|
||||||
|
responsive: true,
|
||||||
|
pageLength: 10,
|
||||||
|
destroy: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// ----------------------------
|
||||||
|
// Validate on field change
|
||||||
|
// ----------------------------
|
||||||
|
$("#subcontractor_id, #category").on("change", function () {
|
||||||
|
clearValidation();
|
||||||
|
checkDuplicateRate();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// ----------------------------
|
||||||
|
// Validate while typing
|
||||||
|
// ----------------------------
|
||||||
|
let timer;
|
||||||
|
|
||||||
|
$("#item_name").on("keyup input", function () {
|
||||||
|
clearTimeout(timer);
|
||||||
|
timer = setTimeout(function () {
|
||||||
|
checkDuplicateRate();
|
||||||
|
}, 300);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// Edit Mode
|
||||||
|
checkDuplicateRate();
|
||||||
|
|
||||||
|
// ----------------------------
|
||||||
|
// Prevent Submit
|
||||||
|
// ----------------------------
|
||||||
|
$("#rateForm").submit(function (e) {
|
||||||
|
|
||||||
|
if ($("#saveBtn").prop("disabled")) {
|
||||||
|
e.preventDefault();
|
||||||
|
$("#item_name").focus();
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
//=========================================
|
||||||
|
// Reset Validation
|
||||||
|
//=========================================
|
||||||
|
function clearValidation() {
|
||||||
|
$("#duplicateMessage")
|
||||||
|
.hide()
|
||||||
|
.text("")
|
||||||
|
.removeClass("text-success text-danger");
|
||||||
|
|
||||||
|
$("#item_name")
|
||||||
|
.removeClass("is-valid is-invalid");
|
||||||
|
$("#saveBtn").prop("disabled", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//=========================================
|
||||||
|
// Duplicate Validation
|
||||||
|
//=========================================
|
||||||
|
function checkDuplicateRate() {
|
||||||
|
let subcontractor = $("#subcontractor_id").val();
|
||||||
|
let category = $("#category").val();
|
||||||
|
let item_name = $("#item_name").val().trim();
|
||||||
|
let rate_id = $("#rate_id").val();
|
||||||
|
|
||||||
|
// Mandatory fields
|
||||||
|
if (
|
||||||
|
subcontractor == "" ||
|
||||||
|
category == "" ||
|
||||||
|
item_name.length < 2
|
||||||
|
) {
|
||||||
|
clearValidation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
|
||||||
|
url: "{{ url_for('engineering.check_rate') }}",
|
||||||
|
type: "GET",
|
||||||
|
dataType: "json",
|
||||||
|
data: {
|
||||||
|
subcontractor_id: subcontractor,
|
||||||
|
category: category,
|
||||||
|
item_name: item_name,
|
||||||
|
rate_id: rate_id
|
||||||
|
},
|
||||||
|
|
||||||
|
success: function (response) {
|
||||||
|
if (response.exists) {
|
||||||
|
$("#duplicateMessage")
|
||||||
|
.text("This Item Name already exists for the selected Subcontractor and Category.")
|
||||||
|
.removeClass("text-success")
|
||||||
|
.addClass("text-danger")
|
||||||
|
.show();
|
||||||
|
|
||||||
|
$("#item_name")
|
||||||
|
.removeClass("is-valid")
|
||||||
|
.addClass("is-invalid");
|
||||||
|
|
||||||
|
$("#saveBtn").prop("disabled", true);
|
||||||
|
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$("#duplicateMessage")
|
||||||
|
.text("Item Name is available.")
|
||||||
|
.removeClass("text-danger")
|
||||||
|
.addClass("text-success")
|
||||||
|
.show();
|
||||||
|
|
||||||
|
$("#item_name")
|
||||||
|
.removeClass("is-invalid")
|
||||||
|
.addClass("is-valid");
|
||||||
|
|
||||||
|
$("#saveBtn").prop("disabled", false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
error: function () {
|
||||||
|
clearValidation();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
124
logs/app.log
Normal file
124
logs/app.log
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
2026-08-06 12:44:45 | INFO | User=System | IP=- | - | - | ======================================================================
|
||||||
|
2026-08-06 12:44:45 | INFO | User=System | IP=- | - | - | Application Started Successfully
|
||||||
|
2026-08-06 12:44:45 | INFO | User=System | IP=- | - | - | ======================================================================
|
||||||
|
<<<<<<< HEAD
|
||||||
|
=======
|
||||||
|
2026-08-07 11:46:57 | INFO | User=System | IP=- | - | - | ======================================================================
|
||||||
|
2026-08-07 11:46:57 | INFO | User=System | IP=- | - | - | Application Started Successfully
|
||||||
|
2026-08-07 11:46:57 | INFO | User=System | IP=- | - | - | ======================================================================
|
||||||
|
2026-08-07 11:47:12 | INFO | User=System | IP=- | - | - | ======================================================================
|
||||||
|
2026-08-07 11:47:12 | INFO | User=System | IP=- | - | - | Application Started Successfully
|
||||||
|
2026-08-07 11:47:12 | INFO | User=System | IP=- | - | - | ======================================================================
|
||||||
|
2026-08-07 11:47:14 | INFO | User=System | IP=- | - | - | ======================================================================
|
||||||
|
2026-08-07 11:47:14 | INFO | User=System | IP=- | - | - | Application Started Successfully
|
||||||
|
2026-08-07 11:47:14 | INFO | User=System | IP=- | - | - | ======================================================================
|
||||||
|
2026-08-07 11:47:18 | INFO | User=Laxmi | IP=192.168.0.142 | POST | http://192.168.0.142:5015/file/Subcontractor_report | Request Started
|
||||||
|
2026-08-07 11:47:18 | INFO | User=Laxmi | IP=192.168.0.142 | POST | http://192.168.0.142:5015/file/Subcontractor_report | Request Completed | Status=200
|
||||||
|
2026-08-07 11:47:19 | INFO | User=Laxmi | IP=192.168.0.142 | GET | http://192.168.0.142:5015/static/images/lcepl.png | Request Started
|
||||||
|
2026-08-07 11:47:19 | INFO | User=Laxmi | IP=192.168.0.142 | GET | http://192.168.0.142:5015/static/images/lcepl.png | Request Completed | Status=200
|
||||||
|
2026-08-07 11:47:22 | INFO | User=Laxmi | IP=192.168.0.142 | GET | http://192.168.0.142:5015/logout | Request Started
|
||||||
|
2026-08-07 11:47:22 | INFO | User=Anonymous | IP=192.168.0.142 | GET | http://192.168.0.142:5015/logout | Logout successful. User=Laxmi
|
||||||
|
2026-08-07 11:47:22 | INFO | User=Anonymous | IP=192.168.0.142 | GET | http://192.168.0.142:5015/logout | Request Completed | Status=302
|
||||||
|
2026-08-07 11:47:22 | INFO | User=Anonymous | IP=192.168.0.142 | GET | http://192.168.0.142:5015/login | Request Started
|
||||||
|
2026-08-07 11:47:22 | INFO | User=Anonymous | IP=192.168.0.142 | GET | http://192.168.0.142:5015/login | Request Completed | Status=200
|
||||||
|
2026-08-07 11:47:22 | INFO | User=Anonymous | IP=192.168.0.142 | GET | http://192.168.0.142:5015/static/images/lcepl.png | Request Started
|
||||||
|
2026-08-07 11:47:22 | INFO | User=Anonymous | IP=192.168.0.142 | GET | http://192.168.0.142:5015/static/images/lcepl.png | Request Completed | Status=304
|
||||||
|
2026-08-07 11:47:30 | INFO | User=Anonymous | IP=192.168.0.142 | POST | http://192.168.0.142:5015/login | Request Started
|
||||||
|
2026-08-07 11:47:30 | INFO | User=Laxmi | IP=192.168.0.142 | POST | http://192.168.0.142:5015/login | Login successful. User=Laxmi
|
||||||
|
2026-08-07 11:47:30 | INFO | User=Laxmi | IP=192.168.0.142 | POST | http://192.168.0.142:5015/login | Request Completed | Status=302
|
||||||
|
2026-08-07 11:47:30 | INFO | User=Laxmi | IP=192.168.0.142 | GET | http://192.168.0.142:5015/dashboard/ | Request Started
|
||||||
|
2026-08-07 11:47:30 | INFO | User=Laxmi | IP=192.168.0.142 | GET | http://192.168.0.142:5015/dashboard/ | Request Completed | Status=200
|
||||||
|
2026-08-07 11:47:30 | INFO | User=Laxmi | IP=192.168.0.142 | GET | http://192.168.0.142:5015/dashboard/api/live-stats | Request Started
|
||||||
|
2026-08-07 11:47:30 | INFO | User=Laxmi | IP=192.168.0.142 | GET | http://192.168.0.142:5015/dashboard/api/live-stats | Request Completed | Status=200
|
||||||
|
2026-08-07 11:47:35 | INFO | User=Laxmi | IP=192.168.0.142 | GET | http://192.168.0.142:5015/file/Subcontractor_report | Request Started
|
||||||
|
2026-08-07 11:47:35 | INFO | User=Laxmi | IP=192.168.0.142 | GET | http://192.168.0.142:5015/file/Subcontractor_report | Request Completed | Status=200
|
||||||
|
2026-08-07 11:47:40 | INFO | User=Laxmi | IP=192.168.0.142 | POST | http://192.168.0.142:5015/file/Subcontractor_report | Request Started
|
||||||
|
2026-08-07 11:47:40 | INFO | User=Laxmi | IP=192.168.0.142 | POST | http://192.168.0.142:5015/file/Subcontractor_report | Request Completed | Status=200
|
||||||
|
2026-08-07 11:47:41 | INFO | User=Laxmi | IP=192.168.0.142 | POST | http://192.168.0.142:5015/file/Subcontractor_report | Request Started
|
||||||
|
2026-08-07 11:47:42 | INFO | User=Laxmi | IP=192.168.0.142 | POST | http://192.168.0.142:5015/file/Subcontractor_report | Request Completed | Status=200
|
||||||
|
2026-08-07 11:47:44 | INFO | User=Laxmi | IP=192.168.0.142 | POST | http://192.168.0.142:5015/file/Subcontractor_report | Request Started
|
||||||
|
2026-08-07 11:47:44 | INFO | User=Laxmi | IP=192.168.0.142 | POST | http://192.168.0.142:5015/file/Subcontractor_report | Request Completed | Status=200
|
||||||
|
2026-08-07 11:47:46 | INFO | User=Laxmi | IP=192.168.0.142 | POST | http://192.168.0.142:5015/file/Subcontractor_report | Request Started
|
||||||
|
2026-08-07 11:47:46 | INFO | User=Laxmi | IP=192.168.0.142 | POST | http://192.168.0.142:5015/file/Subcontractor_report | Request Completed | Status=200
|
||||||
|
2026-08-07 11:47:49 | INFO | User=Laxmi | IP=192.168.0.142 | GET | http://192.168.0.142:5015/file/Subcontractor_report | Request Started
|
||||||
|
2026-08-07 11:47:49 | INFO | User=Laxmi | IP=192.168.0.142 | GET | http://192.168.0.142:5015/file/Subcontractor_report | Request Completed | Status=200
|
||||||
|
2026-08-07 11:47:54 | INFO | User=Laxmi | IP=192.168.0.142 | POST | http://192.168.0.142:5015/file/Subcontractor_report | Request Started
|
||||||
|
2026-08-07 11:47:54 | INFO | User=Laxmi | IP=192.168.0.142 | POST | http://192.168.0.142:5015/file/Subcontractor_report | Request Completed | Status=200
|
||||||
|
2026-08-07 11:47:57 | INFO | User=Laxmi | IP=192.168.0.142 | POST | http://192.168.0.142:5015/file/Subcontractor_report | Request Started
|
||||||
|
2026-08-07 11:47:57 | INFO | User=Laxmi | IP=192.168.0.142 | POST | http://192.168.0.142:5015/file/Subcontractor_report | Request Completed | Status=200
|
||||||
|
2026-08-07 11:48:07 | INFO | User=Laxmi | IP=192.168.0.142 | POST | http://192.168.0.142:5015/file/Subcontractor_report | Request Started
|
||||||
|
2026-08-07 11:48:07 | INFO | User=Laxmi | IP=192.168.0.142 | POST | http://192.168.0.142:5015/file/Subcontractor_report | Request Completed | Status=200
|
||||||
|
2026-08-07 11:48:11 | INFO | User=Laxmi | IP=192.168.0.142 | POST | http://192.168.0.142:5015/file/Subcontractor_report | Request Started
|
||||||
|
2026-08-07 11:48:11 | INFO | User=Laxmi | IP=192.168.0.142 | POST | http://192.168.0.142:5015/file/Subcontractor_report | Request Completed | Status=200
|
||||||
|
2026-08-07 12:02:57 | INFO | User=System | IP=- | - | - | ======================================================================
|
||||||
|
2026-08-07 12:02:57 | INFO | User=System | IP=- | - | - | Application Started Successfully
|
||||||
|
2026-08-07 12:02:57 | INFO | User=System | IP=- | - | - | ======================================================================
|
||||||
|
2026-08-07 12:03:04 | INFO | User=System | IP=- | - | - | ======================================================================
|
||||||
|
2026-08-07 12:03:04 | INFO | User=System | IP=- | - | - | Application Started Successfully
|
||||||
|
2026-08-07 12:03:04 | INFO | User=System | IP=- | - | - | ======================================================================
|
||||||
|
2026-08-07 12:04:23 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/ | Request Started
|
||||||
|
2026-08-07 12:04:23 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/ | Request Completed | Status=302
|
||||||
|
2026-08-07 12:04:23 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/login | Request Started
|
||||||
|
2026-08-07 12:04:23 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/login | User already logged in.
|
||||||
|
2026-08-07 12:04:23 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/login | Request Completed | Status=302
|
||||||
|
2026-08-07 12:04:23 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/dashboard/ | Request Started
|
||||||
|
2026-08-07 12:04:23 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/dashboard/ | Request Completed | Status=200
|
||||||
|
2026-08-07 12:04:23 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/dashboard/api/live-stats | Request Started
|
||||||
|
2026-08-07 12:04:23 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/dashboard/api/live-stats | Request Completed | Status=200
|
||||||
|
2026-08-07 12:04:33 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/dashboard/api/live-stats | Request Started
|
||||||
|
2026-08-07 12:04:33 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/dashboard/api/live-stats | Request Completed | Status=200
|
||||||
|
2026-08-07 12:04:37 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/engi/client-rate | Request Started
|
||||||
|
2026-08-07 12:04:37 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/engi/client-rate | Request Completed | Status=200
|
||||||
|
2026-08-07 12:04:39 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/file_format | Request Started
|
||||||
|
2026-08-07 12:04:39 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/file_format | Request Completed | Status=200
|
||||||
|
2026-08-07 12:04:40 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/file_format | Request Started
|
||||||
|
2026-08-07 12:04:40 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/file_format | Request Completed | Status=200
|
||||||
|
2026-08-07 12:04:41 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/file_format | Request Started
|
||||||
|
2026-08-07 12:04:41 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/file_format | Request Completed | Status=200
|
||||||
|
2026-08-07 12:04:42 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/file_format | Request Started
|
||||||
|
2026-08-07 12:04:42 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/file_format | Request Completed | Status=200
|
||||||
|
2026-08-07 12:04:42 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/file_format | Request Started
|
||||||
|
2026-08-07 12:04:42 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/file_format | Request Completed | Status=200
|
||||||
|
2026-08-07 12:04:44 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/file/import_client | Request Started
|
||||||
|
2026-08-07 12:04:44 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/file/import_client | Request Completed | Status=200
|
||||||
|
2026-08-07 12:04:46 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/file/Subcontractor_report | Request Started
|
||||||
|
2026-08-07 12:04:46 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/file/Subcontractor_report | Request Completed | Status=200
|
||||||
|
2026-08-07 12:04:49 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/file/Subcontractor_report | Request Started
|
||||||
|
2026-08-07 12:04:49 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/file/Subcontractor_report | Request Completed | Status=200
|
||||||
|
2026-08-07 12:04:52 | INFO | User=Admin | IP=192.168.0.118 | POST | http://192.168.0.118:5015/file/Subcontractor_report | Request Started
|
||||||
|
2026-08-07 12:04:52 | INFO | User=Admin | IP=192.168.0.118 | POST | http://192.168.0.118:5015/file/Subcontractor_report | Request Completed | Status=200
|
||||||
|
2026-08-07 12:04:54 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/file/Subcontractor_report | Request Started
|
||||||
|
2026-08-07 12:04:54 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/file/Subcontractor_report | Request Completed | Status=200
|
||||||
|
2026-08-07 12:05:01 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/dashboard/ | Request Started
|
||||||
|
2026-08-07 12:05:01 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/dashboard/ | Request Completed | Status=200
|
||||||
|
2026-08-07 12:05:01 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/dashboard/api/live-stats | Request Started
|
||||||
|
2026-08-07 12:05:01 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/dashboard/api/live-stats | Request Completed | Status=200
|
||||||
|
2026-08-07 12:05:02 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/activity/ | Request Started
|
||||||
|
2026-08-07 12:05:02 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/activity/ | Request Completed | Status=200
|
||||||
|
2026-08-07 12:07:06 | INFO | User=System | IP=- | - | - | ======================================================================
|
||||||
|
2026-08-07 12:07:06 | INFO | User=System | IP=- | - | - | Application Started Successfully
|
||||||
|
2026-08-07 12:07:06 | INFO | User=System | IP=- | - | - | ======================================================================
|
||||||
|
2026-08-07 12:07:07 | INFO | User=System | IP=- | - | - | ======================================================================
|
||||||
|
2026-08-07 12:07:07 | INFO | User=System | IP=- | - | - | Application Started Successfully
|
||||||
|
2026-08-07 12:07:07 | INFO | User=System | IP=- | - | - | ======================================================================
|
||||||
|
2026-08-07 12:07:09 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/activity/ | Request Started
|
||||||
|
2026-08-07 12:07:09 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/activity/ | Request Completed | Status=200
|
||||||
|
2026-08-07 12:07:10 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/engi/subcontractor-rate | Request Started
|
||||||
|
2026-08-07 12:07:11 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/engi/subcontractor-rate | Request Completed | Status=200
|
||||||
|
2026-08-07 12:07:11 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/static/images/lcepl.png | Request Started
|
||||||
|
2026-08-07 12:07:11 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/static/images/lcepl.png | Request Completed | Status=304
|
||||||
|
2026-08-07 12:07:14 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/engi/client-rate | Request Started
|
||||||
|
2026-08-07 12:07:14 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/engi/client-rate | Request Completed | Status=200
|
||||||
|
2026-08-07 12:07:16 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/file_format | Request Started
|
||||||
|
2026-08-07 12:07:16 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/file_format | Request Completed | Status=200
|
||||||
|
2026-08-07 12:07:16 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/engi/subcontractor-rate | Request Started
|
||||||
|
2026-08-07 12:07:16 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/engi/subcontractor-rate | Request Completed | Status=200
|
||||||
|
2026-08-07 12:07:18 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/engi/subcontractor-rate | Request Started
|
||||||
|
2026-08-07 12:07:18 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/engi/subcontractor-rate | Request Completed | Status=200
|
||||||
|
2026-08-07 12:07:23 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/dashboard/ | Request Started
|
||||||
|
2026-08-07 12:07:23 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/dashboard/ | Request Completed | Status=200
|
||||||
|
2026-08-07 12:07:23 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/dashboard/api/live-stats | Request Started
|
||||||
|
2026-08-07 12:07:23 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/dashboard/api/live-stats | Request Completed | Status=200
|
||||||
|
2026-08-07 12:07:24 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/engi/subcontractor-rate | Request Started
|
||||||
|
2026-08-07 12:07:24 | INFO | User=Admin | IP=192.168.0.118 | GET | http://192.168.0.118:5015/engi/subcontractor-rate | Request Completed | Status=200
|
||||||
|
>>>>>>> pankaj-dev
|
||||||
Reference in New Issue
Block a user