Fix TOU rate-window mismatch and enforce fallback-split validation

Rate windows were matched by substring (label.includes(key)), so
overlapping labels like "Day"/"Weekday" could silently cross-match to
the wrong rate. Switched to an exact match.

The "split must total 100%" check was advisory-only in the UI and
never validated server-side, letting a tariff save with a split that
doesn't sum to 100% and permanently mis-cost that meter's usage.
Enforced on both the API (POST/PATCH /api/tariffs) and the Save
button.

Also aligned the app's portal category to 'Hotel' (already correct in
auth/src/db.js) here and in stack-init/install-stack.sh, which had
both drifted to 'Operations'.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
jtricerolph 2026-07-28 12:59:19 +00:00
parent 7ab048931f
commit f1ce0f06d8
4 changed files with 38 additions and 3 deletions

View file

@ -93,6 +93,10 @@ export default function Tariffs() {
async function save() {
if (!form) return
if (!form.name.trim() || !form.category_id) { setError('Name and category are required'); return }
if (form.is_time_of_use && form.windows.length > 1) {
const total = form.windows.reduce((s, w) => s + (Number(w.split_pct) || 0), 0)
if (Math.round(total) !== 100) { setError(`Fallback split must total 100% (currently ${total}%)`); return }
}
setSaving(true)
setError(null)
try {
@ -271,7 +275,13 @@ export default function Tariffs() {
<div className="modal-actions">
<button className="btn" onClick={() => setForm(null)}>Cancel</button>
<button className="btn btn-primary" onClick={save} disabled={saving}>{saving ? 'Saving…' : 'Save'}</button>
<button
className="btn btn-primary"
onClick={save}
disabled={saving || (form.is_time_of_use && form.windows.length > 1 && Math.round(splitTotal) !== 100)}
>
{saving ? 'Saving…' : 'Save'}
</button>
</div>
</div>
</div>