Merge pull request 'pankaj-dev' (#27) from pankaj-dev into main
Reviewed-on: #27
This commit was merged in pull request #27.
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -11,8 +11,11 @@ app/static/uploads/
|
|||||||
# Ignore env files
|
# Ignore env files
|
||||||
venv
|
venv
|
||||||
|
|
||||||
# Ignore Log files ss
|
# Ignore Log files
|
||||||
logs/
|
logs/
|
||||||
|
logs/app.log
|
||||||
|
logs/debug.log
|
||||||
|
logs/error.log
|
||||||
*.log
|
*.log
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
20
app/models/client_rate_model.py
Normal file
20
app/models/client_rate_model.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
from app import db
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
class ClientRate(db.Model):
|
||||||
|
__tablename__ = "client_rates"
|
||||||
|
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
category = db.Column(db.String(50), nullable=False)
|
||||||
|
item_code = db.Column(db.String(50), nullable=False)
|
||||||
|
item_name = db.Column(db.String(200), nullable=False)
|
||||||
|
unit = db.Column(db.String(20))
|
||||||
|
rate = db.Column(db.Numeric(12,2), nullable=False)
|
||||||
|
effective_from = db.Column(db.Date, nullable=False)
|
||||||
|
effective_to = db.Column(db.Date)
|
||||||
|
status = db.Column(db.String(20), default="Active")
|
||||||
|
created_at = db.Column(db.DateTime, default=datetime.now)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"< Client Rate {self.item_name}>"
|
||||||
|
|
||||||
@@ -6,10 +6,11 @@ from flask import (
|
|||||||
url_for,
|
url_for,
|
||||||
flash, jsonify
|
flash, jsonify
|
||||||
)
|
)
|
||||||
|
from app.constants.messages import SuccessMessage, ErrorMessage
|
||||||
|
|
||||||
from app.models.subcontractor_model import Subcontractor
|
from app.models.subcontractor_model import Subcontractor
|
||||||
from app.services.subcontractor_rate_service import SubcontractorRateService
|
from app.services.subcontractor_rate_service import SubcontractorRateService
|
||||||
from app.constants.messages import SuccessMessage, ErrorMessage
|
from app.services.client_rate_service import ClientRateService
|
||||||
|
|
||||||
engi_bp = Blueprint(
|
engi_bp = Blueprint(
|
||||||
"engineering",
|
"engineering",
|
||||||
@@ -27,17 +28,60 @@ def engineering_master():
|
|||||||
title="Engineering Masters"
|
title="Engineering Masters"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Client rate model
|
#---------------------- Client rate model -------------------------------
|
||||||
@engi_bp.route("/client-rate")
|
@engi_bp.route("/client-rate", methods=["GET", "POST"])
|
||||||
def client_rates():
|
def client_rate_master():
|
||||||
|
|
||||||
|
if request.method == "POST":
|
||||||
|
result = ClientRateService.save_or_update(request.form)
|
||||||
|
if result["success"]:
|
||||||
|
flash(result["message"], "success")
|
||||||
|
return redirect(url_for("engineering.client_rate_master"))
|
||||||
|
else:
|
||||||
|
flash(result["message"], "danger")
|
||||||
|
|
||||||
|
rates = ClientRateService.get_all_rates()
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"engineering/client_rate.html",
|
"engineering/client_rate.html",
|
||||||
title="Client Rate Master"
|
title="Client Rate Master",
|
||||||
|
rates=rates
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@engi_bp.route("/client-rate/edit/<int:rate_id>", methods=["GET", "POST"])
|
||||||
|
def client_edit_rate(rate_id):
|
||||||
|
|
||||||
|
if request.method == "POST":
|
||||||
|
|
||||||
|
result = ClientRateService.save_or_update(request.form)
|
||||||
|
|
||||||
|
if result["success"]:
|
||||||
|
flash(result["message"], "success")
|
||||||
|
return redirect(url_for("engineering.client-rate"))
|
||||||
|
|
||||||
|
flash(result["message"], "danger")
|
||||||
|
|
||||||
|
rate = ClientRateService.get_rate(rate_id)
|
||||||
|
|
||||||
|
rates = ClientRateService.get_all_rates()
|
||||||
|
|
||||||
|
return render_template(
|
||||||
|
"engineering/client_rate.html",
|
||||||
|
rate=rate,
|
||||||
|
rates=rates
|
||||||
|
)
|
||||||
|
|
||||||
|
@engi_bp.route("/client-rate/delete/<int:rate_id>")
|
||||||
|
def client_delete_rate(rate_id):
|
||||||
|
ClientRateService.delete_rate(rate_id)
|
||||||
|
flash("Rate deleted successfully.", "success")
|
||||||
|
return redirect(url_for("engineering.client-rate"))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# -------------------- Sub-Contractor -------------------------------------
|
||||||
@engi_bp.route("/subcontractor-rate", methods=["GET", "POST"])
|
@engi_bp.route("/subcontractor-rate", methods=["GET", "POST"])
|
||||||
def add_subcontractor_rates():
|
def subcontractor_rate_master():
|
||||||
|
|
||||||
subcontractors = Subcontractor.query.filter_by(status="Active").all()
|
subcontractors = Subcontractor.query.filter_by(status="Active").all()
|
||||||
|
|
||||||
@@ -45,7 +89,7 @@ def add_subcontractor_rates():
|
|||||||
result = SubcontractorRateService.save_or_update(request.form)
|
result = SubcontractorRateService.save_or_update(request.form)
|
||||||
if result["success"]:
|
if result["success"]:
|
||||||
flash(result["message"], "success")
|
flash(result["message"], "success")
|
||||||
return redirect(url_for("engineering.add_subcontractor_rates"))
|
return redirect(url_for("engineering.subcontractor_rate_master"))
|
||||||
else:
|
else:
|
||||||
flash(result["message"], "danger")
|
flash(result["message"], "danger")
|
||||||
|
|
||||||
@@ -59,7 +103,7 @@ def add_subcontractor_rates():
|
|||||||
)
|
)
|
||||||
|
|
||||||
@engi_bp.route("/subcontractor-rate/edit/<int:rate_id>", methods=["GET", "POST"])
|
@engi_bp.route("/subcontractor-rate/edit/<int:rate_id>", methods=["GET", "POST"])
|
||||||
def edit_rate(rate_id):
|
def subcontractor_edit_rate(rate_id):
|
||||||
|
|
||||||
subcontractors = Subcontractor.query.filter_by(status="Active").all()
|
subcontractors = Subcontractor.query.filter_by(status="Active").all()
|
||||||
|
|
||||||
@@ -69,7 +113,7 @@ def edit_rate(rate_id):
|
|||||||
|
|
||||||
if result["success"]:
|
if result["success"]:
|
||||||
flash(result["message"], "success")
|
flash(result["message"], "success")
|
||||||
return redirect(url_for("engineering.add_subcontractor_rates"))
|
return redirect(url_for("engineering.subcontractor_rate_master"))
|
||||||
|
|
||||||
flash(result["message"], "danger")
|
flash(result["message"], "danger")
|
||||||
|
|
||||||
@@ -85,10 +129,10 @@ def edit_rate(rate_id):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@engi_bp.route("/subcontractor-rate/delete/<int:rate_id>")
|
@engi_bp.route("/subcontractor-rate/delete/<int:rate_id>")
|
||||||
def delete_rate(rate_id):
|
def subcontractor_delete_rate(rate_id):
|
||||||
SubcontractorRateService.delete_rate(rate_id)
|
SubcontractorRateService.delete_rate(rate_id)
|
||||||
flash("Rate deleted successfully.", "success")
|
flash("Rate deleted successfully.", "success")
|
||||||
return redirect(url_for("engineering.add_subcontractor_rates"))
|
return redirect(url_for("engineering.subcontractor_rate_master"))
|
||||||
|
|
||||||
|
|
||||||
@engi_bp.route("/check-rate")
|
@engi_bp.route("/check-rate")
|
||||||
|
|||||||
97
app/services/client_rate_service.py
Normal file
97
app/services/client_rate_service.py
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
from app.services.db_service import db
|
||||||
|
from app.models.client_rate_model import ClientRate
|
||||||
|
from sqlalchemy import func
|
||||||
|
|
||||||
|
|
||||||
|
class ClientRateService:
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def save_or_update(form):
|
||||||
|
|
||||||
|
rate_id = form.get("id")
|
||||||
|
category = form.get("category")
|
||||||
|
item_name = form.get("item_name").strip()
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# Duplicate Validation
|
||||||
|
# -----------------------------
|
||||||
|
duplicate = (
|
||||||
|
ClientRate.query
|
||||||
|
.filter(
|
||||||
|
ClientRate.category == category,
|
||||||
|
func.lower(ClientRate.item_name) == item_name.lower()
|
||||||
|
)
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
|
||||||
|
# Ignore current record while editing
|
||||||
|
if duplicate and (not rate_id or duplicate.id != int(rate_id)):
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"message": "Category and Rate already exists for this Client."
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# Insert / Update
|
||||||
|
# -----------------------------
|
||||||
|
if rate_id:
|
||||||
|
rate = ClientRate.query.get_or_404(rate_id)
|
||||||
|
else:
|
||||||
|
rate = ClientRate()
|
||||||
|
|
||||||
|
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
|
||||||
|
def get_all_rates():
|
||||||
|
|
||||||
|
return (
|
||||||
|
ClientRate.query
|
||||||
|
.order_by(ClientRate.created_at.desc())
|
||||||
|
.all()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_rate(rate_id):
|
||||||
|
|
||||||
|
return ClientRate.query.get_or_404(rate_id)
|
||||||
|
|
||||||
|
def delete_rate(rate_id):
|
||||||
|
rate = ClientRate.query.get_or_404(rate_id)
|
||||||
|
db.session.delete(rate)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def check_duplicate(subcontractor_id, category, item_name, rate_id=None):
|
||||||
|
|
||||||
|
query = ClientRate.query.filter(
|
||||||
|
ClientRate.category == category,
|
||||||
|
func.lower(ClientRate.item_name) == item_name.strip().lower()
|
||||||
|
)
|
||||||
|
|
||||||
|
if rate_id:
|
||||||
|
query = query.filter(ClientRate.id != int(rate_id))
|
||||||
|
|
||||||
|
return query.first() is not None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -14,7 +14,353 @@
|
|||||||
|
|
||||||
</div>
|
</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">
|
||||||
|
|
||||||
|
<!-- 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>
|
</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.client_rate_master') }}" 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>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.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.client_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.client_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 %}
|
{% endblock %}
|
||||||
@@ -4,14 +4,12 @@
|
|||||||
<div class="container-fluid mt-4">
|
<div class="container-fluid mt-4">
|
||||||
|
|
||||||
<div class="card shadow">
|
<div class="card shadow">
|
||||||
|
<!-- Heading -->
|
||||||
<div class="card-header bg-primary text-white d-flex justify-content-between align-items-center">
|
<div class="card-header bg-primary text-white d-flex justify-content-between align-items-center">
|
||||||
|
|
||||||
<h4 class="mb-0">
|
<h4 class="mb-0">
|
||||||
<i class="bi bi-currency-rupee"></i>
|
<i class="bi bi-currency-rupee"></i>
|
||||||
Subcontractor Rate Master
|
Subcontractor Rate Master
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
@@ -67,23 +65,6 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</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>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -95,7 +76,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Item Name -->
|
<!-- Item Name -->
|
||||||
<div class="col-md-5 mb-3">
|
<div class="col-md-4 mb-3">
|
||||||
<label class="form-label fw-bold"> Item Name </label>
|
<label class="form-label fw-bold"> Item Name </label>
|
||||||
<input type="text"
|
<input type="text"
|
||||||
id="item_name"
|
id="item_name"
|
||||||
@@ -116,6 +97,7 @@
|
|||||||
<div class="col-md-2 mb-3">
|
<div class="col-md-2 mb-3">
|
||||||
<label class="form-label fw-bold">Unit</label>
|
<label class="form-label fw-bold">Unit</label>
|
||||||
<select name="unit" class="form-select">
|
<select name="unit" class="form-select">
|
||||||
|
<option value=""> -- Select Unit -- </option>
|
||||||
<option value="Cum"
|
<option value="Cum"
|
||||||
{% if not rate or rate.unit=="Cum" %}selected{% endif %}>
|
{% if not rate or rate.unit=="Cum" %}selected{% endif %}>
|
||||||
Cum
|
Cum
|
||||||
@@ -136,28 +118,46 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Rate -->
|
<!-- Rate -->
|
||||||
<div class="col-md-2 mb-3">
|
<div class="col-md-3 mb-3">
|
||||||
<label class="form-label fw-bold">Rate</label>
|
<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>
|
<input type="number" step="0.01" name="rate" class="form-control" value="{{ rate.rate if rate else '' }}" required>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
<!-- Effective From -->
|
||||||
<div class="col-md-3 mb-3">
|
<div class="col-md-3 mb-3">
|
||||||
<label class="form-label fw-bold"> Effective From </label>
|
<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>
|
<input type="date" name="effective_from" class="form-control" value="{{ rate.effective_from if rate else '' }}" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Effective To -->
|
||||||
<div class="col-md-3 mb-3">
|
<div class="col-md-3 mb-3">
|
||||||
<label class="form-label fw-bold"> Effective To </label>
|
<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 '' }}">
|
<input type="date" name="effective_to" class="form-control" value="{{ rate.effective_to if rate else '' }}">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Status -->
|
||||||
|
<div class="col-md-3 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>
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<div class="text-end">
|
<div class="text-end">
|
||||||
<a href="{{ url_for('engineering.add_subcontractor_rates') }}" class="btn btn-secondary">
|
<a href="{{ url_for('engineering.subcontractor_rate_master') }}" class="btn btn-secondary">
|
||||||
<i class="bi bi-arrow-clockwise"></i> Reset
|
<i class="bi bi-arrow-clockwise"></i> Reset
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
@@ -222,11 +222,11 @@
|
|||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ url_for('engineering.edit_rate', rate_id=row.id) }}" class="btn btn-warning btn-sm">
|
<a href="{{ url_for('engineering.subcontractor_edit_rate', rate_id=row.id) }}" class="btn btn-warning btn-sm">
|
||||||
<i class="bi bi-pencil-square"></i>
|
<i class="bi bi-pencil-square"></i>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="{{ url_for('engineering.delete_rate', rate_id=row.id) }}"
|
<a href="{{ url_for('engineering.subcontractor_delete_rate', rate_id=row.id) }}"
|
||||||
class="btn btn-danger btn-sm" onclick="return confirm('Delete this Item:{{row.item_name}} & Rate:{{row.rate}} ?')">
|
class="btn btn-danger btn-sm" onclick="return confirm('Delete this Item:{{row.item_name}} & Rate:{{row.rate}} ?')">
|
||||||
<i class="bi bi-trash"></i>
|
<i class="bi bi-trash"></i>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
Manage subcontractor-wise rates.
|
Manage subcontractor-wise rates.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<a href="{{ url_for('engineering.add_subcontractor_rates') }}"
|
<a href="{{ url_for('engineering.subcontractor_rate_master') }}"
|
||||||
class="btn btn-success">
|
class="btn btn-success">
|
||||||
|
|
||||||
<i class="bi bi-arrow-right-circle"></i>
|
<i class="bi bi-arrow-right-circle"></i>
|
||||||
@@ -62,7 +62,7 @@
|
|||||||
Manage client standard rates.
|
Manage client standard rates.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<a href="{{ url_for('engineering.client_rates') }}"
|
<a href="{{ url_for('engineering.client_rate_master') }}"
|
||||||
class="btn btn-primary">
|
class="btn btn-primary">
|
||||||
|
|
||||||
<i class="bi bi-arrow-right-circle"></i>
|
<i class="bi bi-arrow-right-circle"></i>
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
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=- | - | - | ======================================================================
|
|
||||||
|
|||||||
Reference in New Issue
Block a user