{% extends "layouts/base.twig" %}
{% block title %}{{ recipe.name }} — {{ translations.calculator_subtitle }}{% endblock %}
{% block head %}
{% endblock %}
{% block page_heading %}
{{ recipe.name }}
{% if recipe.is_archived %}
{{ translations.archived }}
{% endif %}
{{ translations.calculator_subtitle }}
{% endblock %}
{% block content %}
{# ===== TILE 1: PRODUKT ===== #}
{{ translations.select_product_label }}
{{ translations.recipe_select_product }}
{% for p in products %}
{{ p.name }}{% if p.sku %} ({{ p.sku }}){% endif %}
{% endfor %}
{{ translations.recipe_product_hint }}
{# Karta podglądu produktu #}
{{ recipe.product_name ? (recipe.product_name|slice(0, 2)|upper) : '--' }}
{{ recipe.product_name ?? '' }}
{{ translations.product_preview_sku }}:
{{ recipe.product_sku ?? translations.product_no_sku }}
{{ recipe.product_is_active ? translations.product_preview_status_active : translations.product_preview_status_inactive }}
{# #}
{# ===== TILE 2: SUROWCE ===== #}
{# Formularz dodawania składnika #}
{# Tabela składników #}
{{ translations.ingredient_name }}
{{ translations.ingredients_qty_header }}
{{ translations.ingredients_price_header }}
%
{{ translations.ingredients_cost_header }}
{% if ingredients is empty %}
{{ translations.no_results }}
{% else %}
{% set tw = summary.total_weight ?? 0 %}
{% for ing in ingredients %}
{{ ing.raw_material_name_snapshot }}
{% if ing.raw_material_is_archived %}
{% endif %}
{% set _noDecUnits = ['g', 'ml', 'szt'] %}
{% if ing.unit_symbol_snapshot in _noDecUnits %}
{{ ing.quantity|number_format(0, '.', '') }}
{% else %}
{{ ing.quantity|number_format(2, '.', '') }}
{% endif %}
{{ ing.unit_symbol_snapshot }}
{% if ing.price_net_snapshot is not null %}
{{ ing.price_net_snapshot|number_format(2, '.', '') }}
{% else %}
—
{% endif %}
{% if tw > 0 %}
{{ (ing.quantity / tw * 100)|number_format(2, '.', '') }}%
{% else %}
—
{% endif %}
{% if ing.price_net_snapshot is not null %}
{{ (ing.quantity * ing.price_net_snapshot)|number_format(2, '.', '') }}
{% else %}
—
{% endif %}
{% endfor %}
{% endif %}
{% if ingredients is not empty %}
{{ ingredients|length }} {{ translations.summary_ingredients }}
· {% set _twUnit = ingredients[0].unit_symbol_snapshot ?? '' %}{% if _twUnit in ['g','ml','szt'] %}{{ (summary.total_weight ?? 0)|number_format(0, '.', '') }}{% else %}{{ (summary.total_weight ?? 0)|number_format(2, '.', '') }}{% endif %}
{{ _twUnit }} {{ translations.summary_total_weight|lower }}
100%
{% if summary.total_cost is not null %}
{{ summary.total_cost|number_format(2, '.', '') }} {{ currency }}
{% else %}
—
{% endif %}
{% endif %}
{# Totals row (mockup-style) #}
{% if ingredients is not empty %}
{{ ingredients|length }} {{ translations.summary_ingredients }}
· {% set _twUnit2 = ingredients[0].unit_symbol_snapshot ?? '' %}{% if _twUnit2 in ['g','ml','szt'] %}{{ (summary.total_weight ?? 0)|number_format(0, '.', '') }}{% else %}{{ (summary.total_weight ?? 0)|number_format(2, '.', '') }}{% endif %} {{ _twUnit2 }}
{{ translations.total_raw_materials }}
{% if summary.total_cost is not null %}
{{ summary.total_cost|number_format(2, '.', '') }} {{ currency }}
{% else %}
—
{% endif %}
{% endif %}
{# Alergeny #}
{{ translations.allergens_section }} —
{{ translations.allergens_hint }}
{% if summary.allergens is empty %}
{{ translations.no_allergens }}
{% else %}
{% set _seenAllergens = [] %}
{% for a in summary.allergens %}
{% set _allergenKey = a.code ?? a.name ?? '' %}
{% if _allergenKey not in _seenAllergens %}
{% set _seenAllergens = _seenAllergens|merge([_allergenKey]) %}
{% set _allergenLabel = translations['allergen_' ~ _allergenKey] ?? a.name ?? _allergenKey %}
{{ _allergenLabel }}
{% endif %}
{% endfor %}
{% endif %}
{# Notatki technologiczne #}
{{ translations.technical_notes }}
{{ translations.save_technical_notes }}
{{ translations.technical_notes_hint }}
{# ===== SUMMARY AFTER TILE 2 ===== #}
{% if ingredients is not empty %}
{% set _sum_g = 0 %}
{% set _sum_ml = 0 %}
{% set _sum_szt = 0 %}
{% for _si in ingredients %}
{% if _si.unit_symbol_snapshot == 'g' %}
{% set _sum_g = _sum_g + _si.quantity %}
{% elseif _si.unit_symbol_snapshot == 'ml' %}
{% set _sum_ml = _sum_ml + _si.quantity %}
{% elseif _si.unit_symbol_snapshot == 'szt' %}
{% set _sum_szt = _sum_szt + _si.quantity %}
{% endif %}
{% endfor %}
{{ translations.summary_step2_title }}
{{ translations.summary_recipe_intro }}
{% set _sum_parts = [] %}
{% if _sum_g > 0 %}
{% set _sum_parts = _sum_parts|merge(['' ~ _sum_g|number_format(0, '.', '') ~ ' g ']) %}
{% endif %}
{% if _sum_ml > 0 %}
{% set _sum_parts = _sum_parts|merge(['' ~ _sum_ml|number_format(0, '.', '') ~ ' ml ']) %}
{% endif %}
{% if _sum_szt > 0 %}
{% set _sum_parts = _sum_parts|merge(['' ~ _sum_szt|number_format(0, '.', '') ~ ' szt ']) %}
{% endif %}
{% if _sum_parts is empty %}
{{ (summary.total_weight ?? 0)|number_format(2, '.', '') }} {{ ingredients[0].unit_symbol_snapshot ?? '' }}
{% else %}
{% for _part in _sum_parts %}
{{ _part|raw }}{% if not loop.last %} {{ translations.summary_and }} {% endif %}
{% endfor %}
{% endif %}
{% if summary.total_cost is not null %}
— {{ translations.summary_cost_intro }}
{{ summary.total_cost|number_format(2, '.', '') }} {{ currency }} .
{% endif %}
{% endif %}
{# ===== TILE 3: SALES UNIT ===== #}
{% set su = recipe.sales_unit %}
{# Default portion_unit is resolved in PHP (RecipeController::resolveDefaultPortionUnit) #}
{% set _portionUnit = defaultPortionUnit %}
{{ translations.sales_unit_portion_size }}
g
ml
szt
kg
l
{{ translations.sales_unit_portion_size_hint }}
{{ translations.sales_unit_portions_per_carton }}
{% if su.portions_per_carton is not null %}{{ su.portions_per_carton }}{% else %}— {% endif %}
{{ translations.sales_unit_packaging_weight }}
{% if su.packaging_size is not null %}
{% set _puUnit = su.portion_unit ?? _portionUnit %}
{% if _puUnit in ['g','ml','szt'] %}{{ su.packaging_size|number_format(0, '.', '') }}{% else %}{{ su.packaging_size|number_format(2, '.', '') }}{% endif %} {{ _puUnit }}
{% else %}— {% endif %}
{{ translations.sales_unit_carton_weight }}
{% if su.carton_weight_kg is not null %}
{{ su.carton_weight_kg|number_format(2, '.', '') }} kg
{% elseif su.carton_size is not null %}
{% set _puUnit2 = su.portion_unit ?? _portionUnit %}
{% if _puUnit2 in ['g','ml','szt'] %}{{ su.carton_size|number_format(0, '.', '') }}{% else %}{{ su.carton_size|number_format(2, '.', '') }}{% endif %} {{ _puUnit2 }}
{% else %}— {% endif %}
{{ translations.sales_unit_hierarchy_hint }}
{# ===== TILE 4: LABOR ===== #}
{{ translations.labor_time_per_unit }}
{{ translations.labor_unit_min }}
{{ translations.labor_time_hint }}
{{ translations.labor_hourly_rate }}
{{ currency }}/h
{{ translations.labor_rate_hint }}
{{ translations.labor_cost_per_unit }}
{% if recipe.labor_cost_per_unit is not null %}
{{ recipe.labor_cost_per_unit|number_format(2, '.', '') }} {{ currency }}
{% else %}
—
{% endif %}
{{ translations.labor_cost_hint }}
{# ===== LABOR SUMMARY ===== #}
{% if recipe.labor_time_per_unit_min is not null or recipe.labor_cost_per_unit is not null %}
{{ translations.labor_summary_title }}
{{ translations.labor_summary_intro }}
{% if recipe.labor_time_per_unit_min is not null %}
{{ recipe.labor_time_per_unit_min|number_format(1, '.', '') }} {{ translations.labor_unit_min }}
{% else %}
—
{% endif %}
{{ translations.labor_summary_cost_intro }}
{% if recipe.labor_cost_per_unit is not null %}
{{ recipe.labor_cost_per_unit|number_format(2, '.', '') }} {{ currency }} .
{% else %}
— .
{% endif %}
{% endif %}
{# ===== TILE 5: OVERHEAD ===== #}
{% set oh = recipe.overhead %}
{{ translations.overhead_forecast_cartons }}
/ {{ translations.month_short }}
{{ translations.overhead_forecast_hint }}
{{ translations.overhead_pool }}
{{ currency }}
{{ translations.overhead_pool_hint }}
{{ translations.overhead_per_portion }}
{% if oh.overhead_per_portion is not null %}
{{ oh.overhead_per_portion|number_format(2, '.', '') }} {{ currency }}
{% else %}— {% endif %}
{{ translations.overhead_per_portion_hint }}
{{ translations.overhead_recalculate }}
{% if recipe.sales_unit.portions_per_carton is not null %}
{{ translations.overhead_based_on|replace({'{n}': recipe.sales_unit.portions_per_carton}) }}
{% else %}
{{ translations.overhead_missing_sales_unit }}
{% endif %}
{# ===== TILE 6: PACKAGING COST ===== #}
{% set pack = recipe.packaging %}
{{ translations.packaging_cost_per_packaging }}
{{ currency }}
{{ translations.packaging_cost_hint }}
{{ translations.packaging_cost_per_portion }}
{% if pack.cost_per_packaging is not null and recipe.sales_unit.portions_per_packaging is not null and recipe.sales_unit.portions_per_packaging > 0 %}
{{ (pack.cost_per_packaging / recipe.sales_unit.portions_per_packaging)|number_format(2, '.', '') }} {{ currency }}
{% else %}
—
{% endif %}
{{ translations.packaging_cost_per_portion_hint }}
{# ===== TILE 7: VIDANGES ===== #}
{% set vid = recipe.vidanges %}
{{ translations.vidanges_deposit_per_carton }}
{{ currency }}
{{ translations.vidanges_deposit_hint }}
{{ translations.vidanges_deposit_per_packaging }}
{% if vid.deposit_per_packaging is not null %}
{{ vid.deposit_per_packaging|number_format(2, '.', '') }} {{ currency }}
{% else %}— {% endif %}
{{ translations.vidanges_deposit_per_packaging_hint }}
{{ translations.vidanges_deposit_per_portion }}
{% if vid.deposit_per_portion is not null %}
{{ vid.deposit_per_portion|number_format(2, '.', '') }} {{ currency }}
{% else %}— {% endif %}
{{ translations.vidanges_deposit_per_portion_hint }}
{{ translations.vidanges_passthrough_note }}
{% if recipe.sales_unit.packagings_per_carton is null or recipe.sales_unit.portions_per_packaging is null %}
{{ translations.vidanges_missing_sales_unit }}
{% endif %}
{# ===== TILE 8: P&L SUMMARY ===== #}
{% set pnl = recipe.pnl %}
{% set _pnl_ppp = pnl.portions_per_packaging %}
{{ translations.pnl_computed_per_packaging }}:
{% if pnl.portions_per_packaging is not null %}
{{ pnl.portions_per_packaging }} {{ translations.pnl_portions }}
{% else %}
{{ translations.pnl_missing_portions }}
{% endif %}
{# ===== PANEL: PER PACKAGING ===== #}
{{ translations.pnl_raw_materials }}
{% if pnl.raw_materials_per_packaging is not null %}
{{ pnl.raw_materials_per_packaging|number_format(2, '.', '') }} {{ currency }}
{% else %}— {% endif %}
{{ translations.pnl_labor }}
{% if pnl.labor_per_packaging is not null %}
{{ pnl.labor_per_packaging|number_format(2, '.', '') }} {{ currency }}
{% else %}— {% endif %}
{{ translations.pnl_overhead }}
{% if pnl.overhead_per_packaging is not null %}
{{ pnl.overhead_per_packaging|number_format(2, '.', '') }} {{ currency }}
{% else %}— {% endif %}
{{ translations.pnl_packaging }}
{% if pnl.packaging_per_packaging is not null %}
{{ pnl.packaging_per_packaging|number_format(2, '.', '') }} {{ currency }}
{% else %}{{ translations.pnl_packaging_not_set }} {% endif %}
{{ translations.pnl_total_cost }}
{% if pnl.total_cost_per_packaging is not null %}
{{ pnl.total_cost_per_packaging|number_format(2, '.', '') }} {{ currency }}
{% else %}— {% endif %}
+
{% if pnl.vidanges_per_packaging is not null %}
{{ pnl.vidanges_per_packaging|number_format(2, '.', '') }} {{ currency }}
{% else %}—{% endif %}
{{ translations.pnl_vidanges_passthrough }}
{{ translations.pnl_recommended_price }}
{% if pnl.recommended_packaging_price is not null %}
{{ pnl.recommended_packaging_price|number_format(2, '.', '') }} {{ currency }}
{% else %}— {% endif %}
{{ translations.pnl_per_portion_label }}
{% if pnl.target_margin is not null %}({{ translations.pnl_margin }} {{ pnl.target_margin|number_format(2, '.', '') }}%){% endif %}
{% if pnl.per_portion_price is not null %}
{{ pnl.per_portion_price|number_format(2, '.', '') }} {{ currency }}
{% else %}— {% endif %}
{# ===== PANEL: PER PORTION ===== #}
{{ translations.pnl_raw_materials }}
{% if _pnl_ppp and pnl.raw_materials_per_packaging is not null %}
{{ (pnl.raw_materials_per_packaging / _pnl_ppp)|number_format(2, '.', '') }} {{ currency }}
{% else %}— {% endif %}
{{ translations.pnl_labor }}
{% if _pnl_ppp and pnl.labor_per_packaging is not null %}
{{ (pnl.labor_per_packaging / _pnl_ppp)|number_format(2, '.', '') }} {{ currency }}
{% else %}— {% endif %}
{{ translations.pnl_overhead }}
{% if _pnl_ppp and pnl.overhead_per_packaging is not null %}
{{ (pnl.overhead_per_packaging / _pnl_ppp)|number_format(2, '.', '') }} {{ currency }}
{% else %}— {% endif %}
{{ translations.pnl_packaging }}
{% if _pnl_ppp and pnl.packaging_per_packaging is not null %}
{{ (pnl.packaging_per_packaging / _pnl_ppp)|number_format(2, '.', '') }} {{ currency }}
{% else %}{{ translations.pnl_packaging_not_set }} {% endif %}
{{ translations.pnl_total_cost }}
{% if _pnl_ppp and pnl.total_cost_per_packaging is not null %}
{{ (pnl.total_cost_per_packaging / _pnl_ppp)|number_format(2, '.', '') }} {{ currency }}
{% else %}— {% endif %}
+
{% if _pnl_ppp and pnl.vidanges_per_packaging is not null %}
{{ (pnl.vidanges_per_packaging / _pnl_ppp)|number_format(2, '.', '') }} {{ currency }}
{% else %}—{% endif %}
{{ translations.pnl_vidanges_passthrough }}
{{ translations.pnl_recommended_price }}
{% if pnl.per_portion_price is not null %}
{{ pnl.per_portion_price|number_format(2, '.', '') }} {{ currency }}
{% else %}— {% endif %}
{# Margin slider — wspólny dla obu widoków #}
{{ translations.pnl_incomplete_warning }}
{% if pnl.portions_per_packaging is null %}
{{ translations.recipe_missing_sales_unit }}
{% endif %}
{% if pnl.raw_materials_per_packaging is null %}
{{ translations.recipe_missing_raw_materials }}
{% endif %}
{% if pnl.labor_per_packaging is null %}
{{ translations.recipe_missing_labor }}
{% endif %}
{% if pnl.overhead_per_packaging is null %}
{{ translations.recipe_missing_overhead }}
{% endif %}
{# ===== MODAL: Quick-add raw material ===== #}
{# ===== MODAL: Add raw material (full) ===== #}
{# ===== MODAL: Manage raw material categories (from quick-add) ===== #}
{# ===== MODAL: Labor defaults ===== #}
{{ translations.labor_defaults_hint }}
{# ===== AKCJE RECEPTURY ===== #}
{# {% if recipe.is_archived %}
{{ translations.restore }}
{% else %}
{{ translations.archive }}
{% endif %} #}
{{ translations.delete }}
{{ translations.back_to_recipes }}
{# /.calc-page-wrap #}
{% endblock %}
{% block scripts %}
{% endblock %}