From 0f6b68cf5d69426a64abcdbb794a6e48f6c83c7f Mon Sep 17 00:00:00 2001
From: "Troll (Hermes Agent)"
Date: Sat, 1 Aug 2026 21:10:57 +0000
Subject: [PATCH] Lock customer page after choice, archive files on revision,
add file checkboxes for delivery
---
app.py | 83 ++++++++++++++++++++++++------------
models.py | 1 +
templates/admin/request.html | 50 +++++++++++++++++++++-
templates/player.html | 58 ++++++++++++++++---------
4 files changed, 144 insertions(+), 48 deletions(-)
diff --git a/app.py b/app.py
index 1778556..519ca30 100644
--- a/app.py
+++ b/app.py
@@ -234,11 +234,29 @@ def revise(token):
Customer asked for changes. Store the note and reset status to 'songs_uploaded'
so the operator sees it in the dashboard queue.
"""
- req = get_request_by_token(token)
- if not req:
- abort(404)
note = request.form.get('revision_note', '').strip()
- update_request(req['id'], revision_note=note, status='revisions_requested')
+ # Increment revision counter and archive current files before new versions are uploaded.
+ req = get_request_by_token(token)
+ if req:
+ new_count = (req.get('revision_count') or 0) + 1
+ upload_dir = Path(current_app.config['UPLOAD_FOLDER']) / str(req['id'])
+ if upload_dir.exists():
+ for field, version in (('song_a_path', 'A'), ('song_b_path', 'B')):
+ path = req.get(field)
+ if path and Path(path).exists():
+ old = Path(path)
+ archived = upload_dir / f"Rev{new_count}-{old.name}"
+ try:
+ old.rename(archived)
+ # Keep path pointing to the archived file; operator will upload new files.
+ req[field] = str(archived)
+ except OSError:
+ pass
+ update_request(req['id'], revision_note=note, status='revisions_requested',
+ song_a_path=req.get('song_a_path'), song_b_path=req.get('song_b_path'),
+ customer_approved='none', revision_count=new_count)
+ else:
+ abort(404)
# NOTE: No operator email is sent; the dashboard is the single queue.
flash('Your feedback has been saved. We will regenerate and update you.', 'success')
@@ -323,6 +341,16 @@ def admin_request(rid):
def basename(path):
return Path(path).name if path else ''
+ # Collect any extra MP3 files in the request folder (archived revisions).
+ upload_dir = Path(current_app.config['UPLOAD_FOLDER']) / str(rid)
+ current_paths = {req['song_a_path'], req['song_b_path']}
+ extra_files = []
+ if upload_dir.exists():
+ for f in upload_dir.iterdir():
+ if f.is_file() and f.suffix.lower() == '.mp3' and str(f) not in current_paths:
+ extra_files.append(str(f))
+ extra_files.sort()
+
if request.method == 'POST':
action = request.form.get('action')
@@ -366,34 +394,35 @@ def admin_request(rid):
elif action == 'mark_paid_deliver':
# Finalize: record Square payment ref, attach approved MP3s, email customer.
- if req['customer_approved'] == 'none':
- flash('Customer has not approved a version yet.', 'error')
- else:
- payment_ref = request.form.get('square_payment_ref', '').strip()
- if not payment_ref:
- flash('Square payment reference is required.', 'error')
- return redirect(url_for('admin_request', rid=rid))
+ payment_ref = request.form.get('square_payment_ref', '').strip()
+ if not payment_ref:
+ flash('Square payment reference is required.', 'error')
+ return redirect(url_for('admin_request', rid=rid))
- attachments = []
- if req['customer_approved'] in ('a', 'both') and req['song_a_path']:
- a_name = Path(req['song_a_path']).name
- attachments.append((req['song_a_path'], a_name))
- if req['customer_approved'] in ('b', 'both') and req['song_b_path']:
- b_name = Path(req['song_b_path']).name
- attachments.append((req['song_b_path'], b_name))
+ # Build list of selected files from checkboxes.
+ selected = request.form.getlist('deliver_file')
+ if not selected:
+ flash('Select at least one file to deliver.', 'error')
+ return redirect(url_for('admin_request', rid=rid))
- player_link = f"{current_app.config['PUBLIC_BASE_URL']}/play/{req['player_token']}"
- body = f"Hi {req['name']},\n\nThanks for your payment! Your approved song is attached to this email.\n\nIf you selected both versions, you'll find two MP3 files.\n\nYou can also keep streaming them here: {player_link}\n\nEnjoy!\n\nā {current_app.config['BOOTH_NAME']}"
- try:
- send_email(req['email'], 'Your theme song files are here!', body, attachments=attachments)
- update_request(rid, square_payment_ref=payment_ref, delivery_sent_at=now_utc(), status='delivered')
- flash('Delivery email sent with MP3 attachments.', 'success')
- except Exception as e:
- flash(f'Failed to send delivery email: {e}', 'error')
+ attachments = []
+ for path in selected:
+ p = Path(path)
+ if p.exists():
+ attachments.append((str(p), p.name))
+
+ player_link = f"{current_app.config['PUBLIC_BASE_URL']}/play/{req['player_token']}"
+ body = f"Hi {req['name']},\n\nThanks for your payment! Your selected song(s) are attached to this email.\n\nYou can also keep streaming them here: {player_link}\n\nEnjoy!\n\nā {current_app.config['BOOTH_NAME']}"
+ try:
+ send_email(req['email'], 'Your theme song files are here!', body, attachments=attachments)
+ update_request(rid, square_payment_ref=payment_ref, delivery_sent_at=now_utc(), status='delivered')
+ flash('Delivery email sent with MP3 attachments.', 'success')
+ except Exception as e:
+ flash(f'Failed to send delivery email: {e}', 'error')
return redirect(url_for('admin_request', rid=rid))
- return render_template('admin/request.html', req=req, statuses=STATUS_LABELS, file_exists=file_exists, basename=basename)
+ return render_template('admin/request.html', req=req, statuses=STATUS_LABELS, file_exists=file_exists, basename=basename, extra_files=extra_files)
@app.route('/admin/request//delete', methods=['POST'])
diff --git a/models.py b/models.py
index 8ca712f..bf651fc 100644
--- a/models.py
+++ b/models.py
@@ -41,6 +41,7 @@ CREATE TABLE IF NOT EXISTS requests (
square_payment_ref TEXT,
admin_alert_email TEXT,
player_token TEXT NOT NULL UNIQUE,
+ revision_count INTEGER DEFAULT 0,
revision_note TEXT
);
diff --git a/templates/admin/request.html b/templates/admin/request.html
index dbc24ee..f6752c0 100644
--- a/templates/admin/request.html
+++ b/templates/admin/request.html
@@ -97,6 +97,26 @@
margin-top:0;
color:#f87171;
}
+ .file-select{
+ background:#111827;
+ padding:.6rem;
+ border-radius:.5rem;
+ margin:.3rem 0;
+ }
+ .file-select input{
+ width:auto;
+ margin-right:.5rem;
+ }
+ .file-select label{
+ display:inline;
+ margin:0;
+ font-weight:400;
+ }
+ .old-rev{
+ font-size:.85rem;
+ color:#9ca3af;
+ margin-left:1.8rem;
+ }
@@ -125,7 +145,7 @@
{% if req.revision_note %}
-
š Revisions Requested
+
š Revisions Requested{% if req.revision_count %}Revision #{{ req.revision_count }}{% endif %}
{{ req.revision_note }}
{% endif %}
@@ -238,10 +258,36 @@
diff --git a/templates/player.html b/templates/player.html
index e7db9ca..ac9b6fb 100644
--- a/templates/player.html
+++ b/templates/player.html
@@ -8,7 +8,9 @@
/*
Private customer player page.
Shows two audio players for Version A and Version B,
- plus approval buttons and a revision note form.
+ plus approval buttons or a revision note form.
+ After the customer makes a choice, the controls are hidden
+ and a confirmation/waiting message is shown instead.
*/
body{
font-family:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
@@ -45,6 +47,7 @@
font-weight:700;
cursor:pointer;
}
+ button:disabled{background:#374151;color:#9ca3af;cursor:not-allowed;}
button.selected{background:#10b981;}
button.both{background:#8b5cf6;}
button.revision{background:#f59e0b;color:#000;}
@@ -66,6 +69,15 @@
margin-top:1rem;
}
.status.waiting{background:#3f3f46;}
+ .locked{
+ margin-top:1rem;
+ padding:1rem;
+ background:#111827;
+ border-radius:.5rem;
+ border:1px solid #374151;
+ }
+ .locked h2{margin-top:0;color:#fbbf24;}
+ .locked p{margin:.3rem 0;}
@@ -85,14 +97,36 @@
- {% if req.status in ['songs_uploaded','revisions_requested','awaiting_payment','paid','delivered'] %}
+ {% if req.status == 'revisions_requested' %}
+
+
+
š Revision Requested
+
You asked for changes. We will generate a new version and update this page.
+
Your note: {{ req.revision_note }}
+
+
+ {% elif req.status in ['awaiting_payment','paid','delivered'] %}
+
+
+
ā
Choice Received
+
You selected: {% if req.customer_approved == 'both' %}Both Versions{% else %}Version {{ req.customer_approved.upper() }}{% endif %}
+ {% if req.status == 'awaiting_payment' %}
+
Please return to the booth to finalize payment and collect your files.
+ {% elif req.status == 'paid' %}
+
Payment recorded. Your files are being prepared.
+ {% elif req.status == 'delivered' %}
+
Delivered! Check your email for the MP3 attachment(s).
+ {% endif %}
+
+
+ {% else %}
@@ -106,20 +140,6 @@
{% endif %}
- {% if req.status == 'awaiting_payment' %}
-
-
- Thanks for choosing {{ req.customer_approved.upper() }}! Please head to the booth to finalize payment and collect your files.
-
- {% endif %}
-
- {% if req.status == 'delivered' %}
-
-
- Delivered! ā
Check your email for the MP3 attachment(s).
-
- {% endif %}
-