Jul 10 - Update codes to catch up with the single tenant project (Low)

This commit is contained in:
2026-07-10 13:10:08 -04:00
parent aa749107c7
commit 5bd30aeaa0
4 changed files with 110 additions and 6 deletions
+9
View File
@@ -8,6 +8,7 @@ app/templates/billing/email/.
import logging import logging
import threading import threading
from urllib.parse import urlparse
from flask import current_app, render_template from flask import current_app, render_template
from flask_mail import Message from flask_mail import Message
from app import mail from app import mail
@@ -91,6 +92,13 @@ def send_billing_email(to_addr: str, event_type: str, context_dict: dict):
app = current_app._get_current_object() app = current_app._get_current_object()
# Derive noreply sender from APP_BASE_URL so billing emails match the tenant domain.
try:
_netloc = urlparse(app.config.get('APP_BASE_URL', '')).netloc
_sender = f'noreply@{_netloc}' if _netloc else app.config.get('MAIL_DEFAULT_SENDER', '')
except Exception:
_sender = app.config.get('MAIL_DEFAULT_SENDER', '')
def _send(): def _send():
try: try:
with app.app_context(): with app.app_context():
@@ -105,6 +113,7 @@ def send_billing_email(to_addr: str, event_type: str, context_dict: dict):
msg = Message( msg = Message(
subject=subject, subject=subject,
recipients=[to_addr], recipients=[to_addr],
sender=_sender or None,
body=plain_body, body=plain_body,
html=html_body, html=html_body,
) )
+13
View File
@@ -12,6 +12,7 @@ from app import db
from app.models.inspection import Inspection, InspectionTemplate from app.models.inspection import Inspection, InspectionTemplate
from app.models.facility import Facility, Area from app.models.facility import Facility, Area
from app.models.issue import Issue from app.models.issue import Issue
from app.models.project import Project
from app.models.user import User from app.models.user import User
from app.utils.decorators import supervisor_required from app.utils.decorators import supervisor_required
from app.utils.scope import get_customer_scope from app.utils.scope import get_customer_scope
@@ -1105,8 +1106,13 @@ def issues_aging():
if customer_facility_ids is not None: if customer_facility_ids is not None:
facilities = (Facility.query.filter(Facility.id.in_(customer_facility_ids), Facility.active == True) facilities = (Facility.query.filter(Facility.id.in_(customer_facility_ids), Facility.active == True)
.order_by(Facility.name).all()) if customer_facility_ids else [] .order_by(Facility.name).all()) if customer_facility_ids else []
projects = (Project.query
.join(Facility, Project.id == Facility.project_id)
.filter(Facility.id.in_(customer_facility_ids), Project.active == True)
.distinct().order_by(Project.name).all()) if customer_facility_ids else []
else: else:
facilities = Facility.query.filter_by(active=True).order_by(Facility.name).all() facilities = Facility.query.filter_by(active=True).order_by(Facility.name).all()
projects = Project.query.filter_by(active=True).order_by(Project.name).all()
return render_template('reports/issues_aging.html', return render_template('reports/issues_aging.html',
now=now, now=now,
@@ -1118,6 +1124,7 @@ def issues_aging():
severity_filter=severity_filter, severity_filter=severity_filter,
facility_id_filter=facility_id_filter, facility_id_filter=facility_id_filter,
facilities=facilities, facilities=facilities,
projects=projects,
) )
@@ -1282,8 +1289,13 @@ def sla_compliance():
if customer_facility_ids is not None: if customer_facility_ids is not None:
facilities = (Facility.query.filter(Facility.id.in_(customer_facility_ids), Facility.active == True) facilities = (Facility.query.filter(Facility.id.in_(customer_facility_ids), Facility.active == True)
.order_by(Facility.name).all()) if customer_facility_ids else [] .order_by(Facility.name).all()) if customer_facility_ids else []
projects = (Project.query
.join(Facility, Project.id == Facility.project_id)
.filter(Facility.id.in_(customer_facility_ids), Project.active == True)
.distinct().order_by(Project.name).all()) if customer_facility_ids else []
else: else:
facilities = Facility.query.filter_by(active=True).order_by(Facility.name).all() facilities = Facility.query.filter_by(active=True).order_by(Facility.name).all()
projects = Project.query.filter_by(active=True).order_by(Project.name).all()
return render_template('reports/sla_compliance.html', return render_template('reports/sla_compliance.html',
start=start, end=end, start=start, end=end,
@@ -1292,6 +1304,7 @@ def sla_compliance():
by_facility=by_facility, by_facility=by_facility,
facility_id_filter=facility_id_filter, facility_id_filter=facility_id_filter,
facilities=facilities, facilities=facilities,
projects=projects,
) )
+43 -2
View File
@@ -29,9 +29,20 @@
{% endfor %} {% endfor %}
</select> </select>
</div> </div>
<div class="col-md-4"> {% if projects %}
<div class="col-md-2">
<label class="form-label small mb-1">Contract</label>
<select id="aging_contract_sel" class="form-select form-select-sm">
<option value="">All Contracts</option>
{% for p in projects %}
<option value="{{ p.id }}">{{ p.name }}</option>
{% endfor %}
</select>
</div>
{% endif %}
<div class="col-md-3">
<label class="form-label small mb-1">Facility</label> <label class="form-label small mb-1">Facility</label>
<select name="facility_id" class="form-select form-select-sm"> <select id="aging_facility_sel" name="facility_id" class="form-select form-select-sm">
<option value="">All Facilities</option> <option value="">All Facilities</option>
{% for f in facilities %} {% for f in facilities %}
<option value="{{ f.id }}" {{ 'selected' if facility_id_filter == f.id }}>{{ f.name }}</option> <option value="{{ f.id }}" {{ 'selected' if facility_id_filter == f.id }}>{{ f.name }}</option>
@@ -46,6 +57,36 @@
</div> </div>
</div> </div>
{% if projects %}
<script>
(function () {
var contractSel = document.getElementById('aging_contract_sel');
var facSel = document.getElementById('aging_facility_sel');
if (!contractSel || !facSel) return;
var allOpts = Array.from(facSel.options).map(function (o) { return {v: o.value, t: o.text}; });
contractSel.addEventListener('change', function () {
var pid = this.value;
facSel.innerHTML = '<option value="">All Facilities</option>';
if (!pid) {
allOpts.slice(1).forEach(function (o) {
var opt = document.createElement('option'); opt.value = o.v; opt.text = o.t;
facSel.appendChild(opt);
});
return;
}
fetch('/inspections/facilities_for_project/' + pid)
.then(function (r) { return r.json(); })
.then(function (data) {
data.forEach(function (f) {
var opt = document.createElement('option'); opt.value = f.id; opt.text = f.name;
facSel.appendChild(opt);
});
});
});
}());
</script>
{% endif %}
{# ── KPI row ── #} {# ── KPI row ── #}
<div class="row g-3 mb-4"> <div class="row g-3 mb-4">
<div class="col-6 col-md-4"> <div class="col-6 col-md-4">
+45 -4
View File
@@ -25,17 +25,28 @@
<div class="card shadow-sm mb-4"> <div class="card shadow-sm mb-4">
<div class="card-body py-2"> <div class="card-body py-2">
<form method="get" class="row g-2 align-items-end"> <form method="get" class="row g-2 align-items-end">
<div class="col-md-3"> <div class="col-md-2">
<label class="form-label small mb-1">From</label> <label class="form-label small mb-1">From</label>
<input type="date" name="start" class="form-control form-control-sm" value="{{ start.strftime('%Y-%m-%d') }}"> <input type="date" name="start" class="form-control form-control-sm" value="{{ start.strftime('%Y-%m-%d') }}">
</div> </div>
<div class="col-md-3"> <div class="col-md-2">
<label class="form-label small mb-1">To</label> <label class="form-label small mb-1">To</label>
<input type="date" name="end" class="form-control form-control-sm" value="{{ end.strftime('%Y-%m-%d') }}"> <input type="date" name="end" class="form-control form-control-sm" value="{{ end.strftime('%Y-%m-%d') }}">
</div> </div>
<div class="col-md-4"> {% if projects %}
<div class="col-md-2">
<label class="form-label small mb-1">Contract</label>
<select id="sla_contract_sel" class="form-select form-select-sm">
<option value="">All Contracts</option>
{% for p in projects %}
<option value="{{ p.id }}">{{ p.name }}</option>
{% endfor %}
</select>
</div>
{% endif %}
<div class="col-md-3">
<label class="form-label small mb-1">Facility</label> <label class="form-label small mb-1">Facility</label>
<select name="facility_id" class="form-select form-select-sm"> <select id="sla_facility_sel" name="facility_id" class="form-select form-select-sm">
<option value="">All Facilities</option> <option value="">All Facilities</option>
{% for f in facilities %} {% for f in facilities %}
<option value="{{ f.id }}" {{ 'selected' if facility_id_filter == f.id }}>{{ f.name }}</option> <option value="{{ f.id }}" {{ 'selected' if facility_id_filter == f.id }}>{{ f.name }}</option>
@@ -50,6 +61,36 @@
</div> </div>
</div> </div>
{% if projects %}
<script>
(function () {
var contractSel = document.getElementById('sla_contract_sel');
var facSel = document.getElementById('sla_facility_sel');
if (!contractSel || !facSel) return;
var allOpts = Array.from(facSel.options).map(function (o) { return {v: o.value, t: o.text}; });
contractSel.addEventListener('change', function () {
var pid = this.value;
facSel.innerHTML = '<option value="">All Facilities</option>';
if (!pid) {
allOpts.slice(1).forEach(function (o) {
var opt = document.createElement('option'); opt.value = o.v; opt.text = o.t;
facSel.appendChild(opt);
});
return;
}
fetch('/inspections/facilities_for_project/' + pid)
.then(function (r) { return r.json(); })
.then(function (data) {
data.forEach(function (f) {
var opt = document.createElement('option'); opt.value = f.id; opt.text = f.name;
facSel.appendChild(opt);
});
});
});
}());
</script>
{% endif %}
{% if total == 0 %} {% if total == 0 %}
<div class="alert alert-info"> <div class="alert alert-info">
<i class="bi bi-info-circle me-2"></i>No resolved issues found for this period and filter. <i class="bi bi-info-circle me-2"></i>No resolved issues found for this period and filter.