Tech cards: remove photo without replacement, header spacing, order link

- New "Видалити фото (без заміни)" checkbox on the tech card form —
  previously the only way to clear a plating photo was uploading a
  replacement.
- Fixed missing top margin between the "← До тех карт" back-link and the
  product header on the tech card form.
- Order detail page now links each item to its tech card (when the viewer
  has tech-card access) so kitchen staff can jump straight to prep
  instructions while working an order.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
byrsapty 2026-09-14 00:19:32 +03:00
parent 3697b4eb89
commit 5501fe3094
4 changed files with 52 additions and 3 deletions

View file

@ -46,6 +46,8 @@ def tech_card_edit(product_id):
upload_dir.mkdir(parents=True, exist_ok=True)
file.save(upload_dir / secure_filename(filename))
card.photo_url = f"/static/uploads/{filename}"
elif request.form.get("remove_photo"):
card.photo_url = ""
db.session.commit()
log_action("tech_card_save", f"Оновлено тех карту «{product.name}»")

View file

@ -74,7 +74,12 @@
<tr><th>Товар</th><th>К-сть</th><th>Ціна</th><th>Сума</th></tr>
{% for item in order.items %}
<tr>
<td>{{ item.product_name }}</td>
<td>
{{ item.product_name }}
{% if tech_cards_nav_visible and item.product_id %}
<a class="muted" style="font-size:11px;margin-left:6px;white-space:nowrap;" href="{{ url_for('admin.tech_card_edit', product_id=item.product_id) }}" title="Тех карта: інструкція та фото подачі">📋 тех карта</a>
{% endif %}
</td>
<td>{{ item.quantity }}</td>
<td>{{ item.product_price }} грн</td>
<td>{{ item.line_total }} грн</td>

View file

@ -2,7 +2,7 @@
{% block page_title %}Тех карта: {{ product.name }}{% endblock %}
{% block content %}
<style>
.tc-head { display: flex; align-items: center; gap: 14px; margin-bottom: 18px; }
.tc-head { display: flex; align-items: center; gap: 14px; margin-top: 18px; margin-bottom: 18px; }
.tc-head h1 { margin: 0 0 4px; }
.tc-preview {
display: block;
@ -38,6 +38,10 @@
<label>Фото подачі <span class="muted">(як страва має виглядати на видачі)</span></label>
{% if card and card.photo_url %}
<img class="tc-preview" src="{{ card.photo_url }}" alt="">
<label style="display:flex;align-items:center;gap:8px;font-weight:400;margin-bottom:10px;">
<input type="checkbox" name="remove_photo" class="admin-checkbox">
Видалити фото (без заміни)
</label>
{% endif %}
<input type="file" name="photo" accept="image/*">

View file

@ -5,7 +5,7 @@ pattern as suppliers (see admin/authz.py's tech_cards_access_required).
from bober_bbq.config import config
from bober_bbq.extensions import db
from bober_bbq.models import AdminUser, Category, Product, Setting, TechCard
from bober_bbq.models import AdminUser, Category, Order, OrderItem, Product, Setting, TechCard
def _make_category(app):
@ -97,6 +97,44 @@ def test_tech_card_create_then_edit_roundtrip(app):
assert "Видалити тех карту" in body
def test_tech_card_remove_photo_without_replacement(app):
product_id = _make_product(app)
with app.app_context():
db.session.add(TechCard(product_id=product_id, photo_url="/static/uploads/old.jpg"))
db.session.commit()
client = app.test_client()
_admin_login(client, app)
resp = client.post(
f"/admin/tech-cards/{product_id}",
data={"instructions": "", "remove_photo": "1"},
follow_redirects=True,
)
assert resp.status_code == 200
with app.app_context():
card = TechCard.query.filter_by(product_id=product_id).one()
assert card.photo_url == ""
def test_order_detail_links_to_tech_card(app):
product_id = _make_product(app)
with app.app_context():
product = Product.query.get(product_id)
order = Order(user_id=-1, order_type="pickup", customer_name="Гість", payment_method="cash", pickup_time="19:00")
db.session.add(order)
db.session.commit()
db.session.add(OrderItem(order_id=order.id, product_id=product_id, product_name=product.name, product_price=150, quantity=1))
db.session.commit()
order_id = order.id
client = app.test_client()
_admin_login(client, app)
resp = client.get(f"/admin/orders/{order_id}")
assert resp.status_code == 200
assert f"/admin/tech-cards/{product_id}" in resp.get_data(as_text=True)
def test_tech_card_delete_removes_row(app):
product_id = _make_product(app)
with app.app_context():