05/06 Phase 2: fixed issues 3rd
This commit is contained in:
@@ -886,6 +886,42 @@ MySQL backup credentials stored in `/etc/mysql/backup.cnf` (mode 600, owned by `
|
||||
|
||||
---
|
||||
|
||||
## Development Rules
|
||||
|
||||
The following rules are mandatory for all development on this codebase. Violations have caused production 500 errors.
|
||||
|
||||
**Rule 1 — Never assume, always read first.**
|
||||
Before fixing any error or touching any file, read the actual file content on disk. Do not assume the file matches what was previously written — the server may have a different version. Use `cat`, `grep`, or `sed -n` to read the exact content before making any changes.
|
||||
|
||||
**Rule 2 — No temporary fixes.**
|
||||
All fixes must address the root cause. Workarounds that mask a problem without solving it are not permitted. If the root cause is unclear, investigate further before writing any code.
|
||||
|
||||
**Rule 3 — Never remove or change existing functionality unless explicitly instructed.**
|
||||
All changes must be additive or corrective. Existing routes, function names, variable names, and model fields must be preserved unless a change is explicitly requested.
|
||||
|
||||
**Rule 4 — Always audit the full extends chain in every template you create or modify.**
|
||||
Every `{% extends "..." %}` path must resolve relative to the Flask app's `template_folder` root. Before delivering any template, verify the parent template exists at that exact path. Correct convention for this project: `admin/layouts/base.html` for admin templates, `tenant/layouts/base.html` for tenant templates.
|
||||
|
||||
**Rule 5 — Never leave `url_for()` calls pointing at unregistered or stub-only endpoints.**
|
||||
If a blueprint is registered as a stub (no routes yet), all `url_for()` references to its endpoints in templates must be replaced with `'#'` or guarded with `{% if %}` until the route is implemented. A `BuildError` from an unregistered endpoint is a 500 error in production.
|
||||
|
||||
**Rule 6 — Always add logging for create, edit, and delete actions.**
|
||||
Every route that creates, edits, deletes, or changes the status of a record must call `logger.info()` or `logger.warning()` with the action, relevant IDs, and actor context.
|
||||
|
||||
**Rule 7 — All audit log values must be JSON-serialisable.**
|
||||
When passing data to `AuditLog.log()` or any JSON column, always convert `datetime`/`date` to ISO-8601 strings via `.isoformat()` and `Decimal` to `float`. Use `model_to_dict()` from `app/admin/utils.py` — never pass raw ORM objects.
|
||||
|
||||
**Rule 8 — Use absolute paths for `template_folder` and `static_folder` in app factories.**
|
||||
Never use relative paths (`"../templates"`) in Flask app factories. Flask resolves relative paths against the package directory, not the project root, which differs depending on where Gunicorn is started. Always use `os.path.dirname(os.path.abspath(__file__))` as the anchor.
|
||||
|
||||
**Rule 9 — Test all fixes before delivering them.**
|
||||
Every fix must be validated by a programmatic test (import test, render test, or unit test) before being packaged for delivery. Do not ship code that has not been executed in the container.
|
||||
|
||||
**Rule 10 — Pack files when more than 5 files are changed.**
|
||||
When a fix touches more than 5 files, compress them into a ZIP for delivery. Always include only the changed files — not the entire project.
|
||||
|
||||
---
|
||||
|
||||
## Phase 1 — Implementation Notes
|
||||
|
||||
The following decisions and resolutions were made during Phase 1 scaffold implementation.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{% extends "layouts/base.html" %}
|
||||
{% extends "tenant/layouts/base.html" %}
|
||||
{% block title %}Account Suspended{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{% extends "tenant/base.html" %}
|
||||
{% extends "tenant/layouts/base.html" %}
|
||||
{% block title %}Account Cancelled{% endblock %}
|
||||
{% block content %}
|
||||
<div class="row justify-content-center mt-5">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{% extends "layouts/base.html" %}
|
||||
{% extends "tenant/layouts/base.html" %}
|
||||
{% block title %}Sign In{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
@@ -44,9 +44,7 @@
|
||||
<a href="{{ url_for('tenant_auth.password_reset_request') }}" class="text-muted small d-block mt-2">
|
||||
Forgot password?
|
||||
</a>
|
||||
<a href="{{ url_for('tenant_auth.demo_login') }}" class="text-muted small d-block mt-1">
|
||||
View demo account
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{% extends "layouts/base.html" %}
|
||||
{% extends "tenant/layouts/base.html" %}
|
||||
{% block title %}Set New Password{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{% extends "layouts/base.html" %}
|
||||
{% extends "tenant/layouts/base.html" %}
|
||||
{% block title %}Reset Password{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{% extends "tenant/base.html" %}
|
||||
{% extends "tenant/layouts/base.html" %}
|
||||
{% block title %}Account Suspended{% endblock %}
|
||||
{% block content %}
|
||||
<div class="row justify-content-center mt-5">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{% extends "tenant/base.html" %}
|
||||
{% extends "tenant/layouts/base.html" %}
|
||||
{% block title %}Dashboard{% endblock %}
|
||||
{% block content %}
|
||||
<h4 class="mb-4">Dashboard
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{% extends "layouts/base.html" %}
|
||||
{% extends "tenant/layouts/base.html" %}
|
||||
{% block title %}Feature Unavailable{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
{% for loc in locations %}
|
||||
<li>
|
||||
<a class="dropdown-item {% if g.location and g.location.id == loc.id %}active{% endif %}"
|
||||
href="{{ url_for('locations.switch', location_id=loc.id) }}">
|
||||
href="{{ '#' }}">
|
||||
{{ loc.name }}
|
||||
{% if loc.is_primary %}<span class="badge bg-secondary ms-1">Primary</span>{% endif %}
|
||||
</a>
|
||||
@@ -86,74 +86,74 @@
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.endpoint and 'appointments' in request.endpoint %}active fw-bold{% endif %}"
|
||||
href="{{ url_for('appointments.index') }}">
|
||||
href="{{ '#' }}">
|
||||
<i class="bi bi-calendar3 me-2"></i>Appointments
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.endpoint and 'pos' in request.endpoint %}active fw-bold{% endif %}"
|
||||
href="{{ url_for('pos.checkout') }}">
|
||||
href="{{ '#' }}">
|
||||
<i class="bi bi-cash-register me-2"></i>POS / Checkout
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.endpoint and 'customers' in request.endpoint %}active fw-bold{% endif %}"
|
||||
href="{{ url_for('customers.index') }}">
|
||||
href="{{ '#' }}">
|
||||
<i class="bi bi-people me-2"></i>Customers
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.endpoint and 'staff' in request.endpoint %}active fw-bold{% endif %}"
|
||||
href="{{ url_for('staff.index') }}">
|
||||
href="{{ '#' }}">
|
||||
<i class="bi bi-person-badge me-2"></i>Staff
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.endpoint and 'services' in request.endpoint %}active fw-bold{% endif %}"
|
||||
href="{{ url_for('services.index') }}">
|
||||
href="{{ '#' }}">
|
||||
<i class="bi bi-card-list me-2"></i>Services & Products
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.endpoint and 'inventory' in request.endpoint %}active fw-bold{% endif %}"
|
||||
href="{{ url_for('inventory.index') }}">
|
||||
href="{{ '#' }}">
|
||||
<i class="bi bi-box-seam me-2"></i>Inventory
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.endpoint and 'gift_cards' in request.endpoint %}active fw-bold{% endif %}"
|
||||
href="{{ url_for('gift_cards.index') }}">
|
||||
href="{{ '#' }}">
|
||||
<i class="bi bi-gift me-2"></i>Gift Cards
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.endpoint and 'reconciliation' in request.endpoint %}active fw-bold{% endif %}"
|
||||
href="{{ url_for('reconciliation.index') }}">
|
||||
href="{{ '#' }}">
|
||||
<i class="bi bi-calculator me-2"></i>Reconciliation
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.endpoint and 'reports' in request.endpoint %}active fw-bold{% endif %}"
|
||||
href="{{ url_for('reports.index') }}">
|
||||
href="{{ '#' }}">
|
||||
<i class="bi bi-bar-chart-line me-2"></i>Reports
|
||||
</a>
|
||||
</li>
|
||||
{% if current_user.role == 'tenant_admin' %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.endpoint and 'marketing' in request.endpoint %}active fw-bold{% endif %}"
|
||||
href="{{ url_for('marketing.index') }}">
|
||||
href="{{ '#' }}">
|
||||
<i class="bi bi-megaphone me-2"></i>Marketing
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.endpoint and 'locations' in request.endpoint %}active fw-bold{% endif %}"
|
||||
href="{{ url_for('locations.index') }}">
|
||||
href="{{ '#' }}">
|
||||
<i class="bi bi-geo-alt me-2"></i>Locations
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.endpoint and 'settings' in request.endpoint %}active fw-bold{% endif %}"
|
||||
href="{{ url_for('settings.index') }}">
|
||||
href="{{ '#' }}">
|
||||
<i class="bi bi-gear me-2"></i>Settings
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{% extends "tenant/base.html" %}
|
||||
{% extends "tenant/layouts/base.html" %}
|
||||
{% block title %}Staff Login{% endblock %}
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{% extends "layouts/base.html" %}
|
||||
{% extends "tenant/layouts/base.html" %}
|
||||
{% block title %}Staff Login{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{% extends "layouts/base.html" %}
|
||||
{% extends "tenant/layouts/base.html" %}
|
||||
{% block title %}Account Suspended{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{% extends "layouts/base.html" %}
|
||||
{% extends "tenant/layouts/base.html" %}
|
||||
{% block title %}Sign In{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
@@ -44,9 +44,7 @@
|
||||
<a href="{{ url_for('tenant_auth.password_reset_request') }}" class="text-muted small d-block mt-2">
|
||||
Forgot password?
|
||||
</a>
|
||||
<a href="{{ url_for('tenant_auth.demo_login') }}" class="text-muted small d-block mt-1">
|
||||
View demo account
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{% extends "layouts/base.html" %}
|
||||
{% extends "tenant/layouts/base.html" %}
|
||||
{% block title %}Set New Password{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{% extends "layouts/base.html" %}
|
||||
{% extends "tenant/layouts/base.html" %}
|
||||
{% block title %}Reset Password{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{% extends "layouts/base.html" %}
|
||||
{% extends "tenant/layouts/base.html" %}
|
||||
{% block title %}Feature Unavailable{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
{% for loc in locations %}
|
||||
<li>
|
||||
<a class="dropdown-item {% if g.location and g.location.id == loc.id %}active{% endif %}"
|
||||
href="{{ url_for('locations.switch', location_id=loc.id) }}">
|
||||
href="{{ '#' }}">
|
||||
{{ loc.name }}
|
||||
{% if loc.is_primary %}<span class="badge bg-secondary ms-1">Primary</span>{% endif %}
|
||||
</a>
|
||||
@@ -86,74 +86,74 @@
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.endpoint and 'appointments' in request.endpoint %}active fw-bold{% endif %}"
|
||||
href="{{ url_for('appointments.index') }}">
|
||||
href="{{ '#' }}">
|
||||
<i class="bi bi-calendar3 me-2"></i>Appointments
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.endpoint and 'pos' in request.endpoint %}active fw-bold{% endif %}"
|
||||
href="{{ url_for('pos.checkout') }}">
|
||||
href="{{ '#' }}">
|
||||
<i class="bi bi-cash-register me-2"></i>POS / Checkout
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.endpoint and 'customers' in request.endpoint %}active fw-bold{% endif %}"
|
||||
href="{{ url_for('customers.index') }}">
|
||||
href="{{ '#' }}">
|
||||
<i class="bi bi-people me-2"></i>Customers
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.endpoint and 'staff' in request.endpoint %}active fw-bold{% endif %}"
|
||||
href="{{ url_for('staff.index') }}">
|
||||
href="{{ '#' }}">
|
||||
<i class="bi bi-person-badge me-2"></i>Staff
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.endpoint and 'services' in request.endpoint %}active fw-bold{% endif %}"
|
||||
href="{{ url_for('services.index') }}">
|
||||
href="{{ '#' }}">
|
||||
<i class="bi bi-card-list me-2"></i>Services & Products
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.endpoint and 'inventory' in request.endpoint %}active fw-bold{% endif %}"
|
||||
href="{{ url_for('inventory.index') }}">
|
||||
href="{{ '#' }}">
|
||||
<i class="bi bi-box-seam me-2"></i>Inventory
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.endpoint and 'gift_cards' in request.endpoint %}active fw-bold{% endif %}"
|
||||
href="{{ url_for('gift_cards.index') }}">
|
||||
href="{{ '#' }}">
|
||||
<i class="bi bi-gift me-2"></i>Gift Cards
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.endpoint and 'reconciliation' in request.endpoint %}active fw-bold{% endif %}"
|
||||
href="{{ url_for('reconciliation.index') }}">
|
||||
href="{{ '#' }}">
|
||||
<i class="bi bi-calculator me-2"></i>Reconciliation
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.endpoint and 'reports' in request.endpoint %}active fw-bold{% endif %}"
|
||||
href="{{ url_for('reports.index') }}">
|
||||
href="{{ '#' }}">
|
||||
<i class="bi bi-bar-chart-line me-2"></i>Reports
|
||||
</a>
|
||||
</li>
|
||||
{% if current_user.role == 'tenant_admin' %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.endpoint and 'marketing' in request.endpoint %}active fw-bold{% endif %}"
|
||||
href="{{ url_for('marketing.index') }}">
|
||||
href="{{ '#' }}">
|
||||
<i class="bi bi-megaphone me-2"></i>Marketing
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.endpoint and 'locations' in request.endpoint %}active fw-bold{% endif %}"
|
||||
href="{{ url_for('locations.index') }}">
|
||||
href="{{ '#' }}">
|
||||
<i class="bi bi-geo-alt me-2"></i>Locations
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.endpoint and 'settings' in request.endpoint %}active fw-bold{% endif %}"
|
||||
href="{{ url_for('settings.index') }}">
|
||||
href="{{ '#' }}">
|
||||
<i class="bi bi-gear me-2"></i>Settings
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{% extends "layouts/base.html" %}
|
||||
{% extends "tenant/layouts/base.html" %}
|
||||
{% block title %}Staff Login{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
Reference in New Issue
Block a user