- 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>
151 lines
5.4 KiB
Python
151 lines
5.4 KiB
Python
"""Тех карти (bober_bbq/admin/tech_cards.py) — kitchen-only instructions +
|
||
plating photo per product. Access follows the same admin/manager-toggle
|
||
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, Order, OrderItem, Product, Setting, TechCard
|
||
|
||
|
||
def _make_category(app):
|
||
with app.app_context():
|
||
cat = Category(name="Тестова категорія", slug="test-tech-cards-cat", sort_order=999)
|
||
db.session.add(cat)
|
||
db.session.commit()
|
||
return cat.id
|
||
|
||
|
||
def _make_product(app):
|
||
with app.app_context():
|
||
cat_id = _make_category(app)
|
||
product = Product(category_id=cat_id, name="Шашлик класичний", price=150, is_active=True)
|
||
db.session.add(product)
|
||
db.session.commit()
|
||
return product.id
|
||
|
||
|
||
def _admin_login(client, app):
|
||
with app.app_context():
|
||
username, password = config.FLASK_ADMIN_USERNAME, config.FLASK_ADMIN_PASSWORD
|
||
return client.post("/admin/login", data={"username": username, "password": password}, follow_redirects=True)
|
||
|
||
|
||
def _manager_login(client, app):
|
||
with app.app_context():
|
||
manager = AdminUser(username="test-manager", role="manager")
|
||
manager.set_password("manager-pass-123")
|
||
db.session.add(manager)
|
||
db.session.commit()
|
||
return client.post(
|
||
"/admin/login", data={"username": "test-manager", "password": "manager-pass-123"}, follow_redirects=True
|
||
)
|
||
|
||
|
||
def test_manager_blocked_from_tech_cards_by_default(app):
|
||
client = app.test_client()
|
||
_manager_login(client, app)
|
||
|
||
resp = client.get("/admin/tech-cards", follow_redirects=True)
|
||
assert resp.status_code == 200
|
||
assert "Доступ до тех карт вимкнено" in resp.get_data(as_text=True)
|
||
|
||
|
||
def test_manager_allowed_when_admin_enables_access(app):
|
||
with app.app_context():
|
||
Setting.set("tech_cards_manager_access", "1")
|
||
db.session.commit()
|
||
|
||
client = app.test_client()
|
||
_manager_login(client, app)
|
||
|
||
resp = client.get("/admin/tech-cards", follow_redirects=True)
|
||
assert resp.status_code == 200
|
||
assert "Доступ до тех карт вимкнено" not in resp.get_data(as_text=True)
|
||
|
||
|
||
def test_admin_always_has_tech_cards_access(app):
|
||
client = app.test_client()
|
||
_admin_login(client, app)
|
||
|
||
resp = client.get("/admin/tech-cards", follow_redirects=True)
|
||
assert resp.status_code == 200
|
||
assert "Тех карти" in resp.get_data(as_text=True)
|
||
|
||
|
||
def test_tech_card_create_then_edit_roundtrip(app):
|
||
product_id = _make_product(app)
|
||
client = app.test_client()
|
||
_admin_login(client, app)
|
||
|
||
resp = client.post(
|
||
f"/admin/tech-cards/{product_id}",
|
||
data={"instructions": "1. Замаринувати\n2. Обсмажити", "cook_time_min": "25"},
|
||
follow_redirects=True,
|
||
)
|
||
assert resp.status_code == 200
|
||
assert "Тех карту збережено" in resp.get_data(as_text=True)
|
||
|
||
with app.app_context():
|
||
card = TechCard.query.filter_by(product_id=product_id).one()
|
||
assert card.instructions == "1. Замаринувати\n2. Обсмажити"
|
||
assert card.cook_time_min == 25
|
||
|
||
resp = client.get(f"/admin/tech-cards/{product_id}")
|
||
body = resp.get_data(as_text=True)
|
||
assert "Замаринувати" in body
|
||
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():
|
||
db.session.add(TechCard(product_id=product_id, instructions="старий текст"))
|
||
db.session.commit()
|
||
|
||
client = app.test_client()
|
||
_admin_login(client, app)
|
||
resp = client.post(f"/admin/tech-cards/{product_id}/delete", follow_redirects=True)
|
||
assert resp.status_code == 200
|
||
assert "Тех карту видалено" in resp.get_data(as_text=True)
|
||
|
||
with app.app_context():
|
||
assert TechCard.query.filter_by(product_id=product_id).first() is None
|