Nachfolgend zeige ich dir, wie du das anhand einer einfachen Allergen-Logik vom Rohstoff über die Stücklisten bis zum Etikett für das Halbfabrikat abbilden kannst.
So berechnest du die Lebensmittel-Allergene in Odoo
(1) Datenmodell für die Allergene anlegen
Schalte den Entwicklermodus ein und gehe zu Einstellungen / Technisch / Datenbankstruktur / Modelle. Lege ein neues Modell an.
- Name: Produkt Allergen
- Bezeichnung: x_product_allergen
- Zugriffsrechte: Name & Gruppe: Fertigung / Benutzer: Lesezugriff, Schreibzugriff, Zugriff anlegen

(2) Produktfeld für Allergene anlegen
Gehe zu Einstellungen / Technisch / Datenbankstruktur / Felder. Lege ein neues Feld an.
- Feldname: x_allergens
- Feldbezeichnung: Allergene
- Modell: Produktvariante
- Feldtyp: Many2Many
- Zugehöriges Modell: x_product_allergen
- Auftragsverfolgung aktivieren: Optional kannst du dieses Feld auf "1" setzen. Dann wird jede Änderung an den Allergenen im Produkt-Chatter protokolliert.

(3) Allergen-Feld in der Ansicht hinzufügen
Gehe zu Einstellungen / Technisch / Benutzerschnittstelle / Ansichten. Lege eine neue Ansicht an.
- Bezeichnung: Product Variant Form View Inherit, Add Food Infos
- Ansichtstyp: Formular
- Modell der Ansicht: Produktvariante
- Vererbte Ansicht: product.product.form
- Vererbungsmodus für Ansicht: Erweiterungsübersicht
- Architektur:
<xpath expr="//page[@name='sales_price']" position="after">
<page name="food_infos" string="Food-Infos">
<group>
<field name="x_allergens" widget="many2many_tags"/>
</group>
</page>
</xpath>

Nun kannst du unter Verkauf -> Produkte -> Produktvarianten ein Produkt suchen und im Tab "Food-Infos" die Allergene erstellen und hinzufügen.
(4) Automatisierungsregel zur Berechnung der Allergene hinzufügen
Gehe zu Einstellungen / Technisch / Automatisierung / Automatisierungsregeln. Lege eine neue Regel an. (Hinweis: Eventuell musst du noch das technische Modul "base_automation" installieren. Das findest du im Odoo App Store, wenn du den Filter "Apps" entfernst.)
- Bezeichnung: Calculate Allergens
- Modell: Stückliste
- Auslöser: Bei Erstellung und Bearbeitung
- Bei der Aktualisierung: Aktiv, Stücklistenpositionen, Produkt, Produktvariante
- Folgeaktionen: Code Ausführen
- Code:
# Business rule:
# - Variant-specific active BoM wins over template-level active BoM.
# - On activation, archive sibling active BoMs for the same target.
# - On archive, keep current allergens unless another active BoM is activated later.
for bom in records:
if not bom:
continue
# Resolve the target variant.
if bom.product_id:
target_product = bom.product_id
elif bom.product_tmpl_id and len(bom.product_tmpl_id.product_variant_ids) == 1:
target_product = bom.product_tmpl_id.product_variant_ids[:1]
else:
log(
"BoM %s skipped for allergen sync: no unique target variant." % bom.display_name,
level='warning',
)
continue
# If this BoM is activated, archive sibling active BoMs for the same target.
if bom.active:
if bom.product_id:
sibling_domain = [
('id', '!=', bom.id),
('active', '=', True),
('product_id', '=', bom.product_id.id),
]
else:
sibling_domain = [
('id', '!=', bom.id),
('active', '=', True),
('product_id', '=', False),
('product_tmpl_id', '=', bom.product_tmpl_id.id),
]
siblings = env['mrp.bom'].search(sibling_domain)
if siblings:
siblings.write({'active': False})
# Find the currently effective active BoM for the target product.
current_bom = env['mrp.bom'].search([
('active', '=', True),
('product_id', '=', target_product.id),
], order='id desc', limit=1)
if not current_bom:
current_bom = env['mrp.bom'].search([
('active', '=', True),
('product_id', '=', False),
('product_tmpl_id', '=', target_product.product_tmpl_id.id),
], order='id desc', limit=1)
# If there is no active BoM anymore, keep current allergens as-is.
if not current_bom:
continue
allergen_ids = set()
bom_stack = [current_bom]
seen_bom_ids = set()
# Walk through the BoM recursively via sub-BoMs.
while bom_stack:
stack_bom = bom_stack.pop()
if stack_bom.id in seen_bom_ids:
continue
seen_bom_ids.add(stack_bom.id)
for line in stack_bom.bom_line_ids:
component = line.product_id
if not component:
continue
allergen_ids.update(component.x_allergens.ids)
child_bom = env['mrp.bom'].search([
('active', '=', True),
('product_id', '=', component.id),
], order='id desc', limit=1)
if not child_bom:
child_bom = env['mrp.bom'].search([
('active', '=', True),
('product_id', '=', False),
('product_tmpl_id', '=', component.product_tmpl_id.id),
], order='id desc', limit=1)
if child_bom and child_bom.id not in seen_bom_ids:
bom_stack.append(child_bom)
new_ids = sorted(allergen_ids)
current_ids = sorted(target_product.x_allergens.ids)
if current_ids != new_ids:
target_product.write({
'x_allergens': [(6, 0, new_ids)],
})

Mit dieser Automatisierungsregel werden die Allergene eines Produkts automatisch aus der aktiven Stückliste berechnet.
Hinweis 1: Alle anderen Stücklisten werden von der Automatisierungsregel archiviert. Es kann nur eine aktive Stückliste geben, damit die Berechnung korrekt funktioniert.
Hinweis 2: Wenn die Allergene auf dem Rohstoff aktualisiert werden, wird die Automatisierung nicht neu ausgelöst. Das kann durch eine zweite Automatisierungsregel auf product.product erreicht werden.
(5) Allergen-Feld in Produktlabels hinzufügen
Gehe zu Einstellungen / Technisch / Benutzerschnittstelle / Ansichten. Lege eine neue Ansicht an.
- Bezeichnung: report_lot_label Inherit, Add Allergens
- Ansichtstyp: QWeb
- Vererbte Ansicht: report_lot_label
- Vererbungsmodus: Erweiterungsübersicht
- Architektur:
<data>
<xpath expr="//div[@name='lot_name']/.." position="attributes">
<attribute name="t-att-style">'position:relative; overflow:hidden; width:43mm; height:20mm; border: 1px solid %s;' % (o.env.user.company_id.primary_color or 'black')</attribute>
</xpath>
<xpath expr="//span[@t-field='o.name']" position="attributes">
<attribute name="t-options">{'widget': 'barcode', 'img_style': 'width:100%; height:28%'}</attribute>
</xpath>
<xpath expr="//div[@name='lot_name']" position="after">
<t t-if="o.product_id.x_allergens">
<div name="allergen_line" class="o_label_4x12" t-att-style="'width:22mm; line-height:1.1;' if final_barcode else 'line-height:1.1;'">
<t t-translation="on">Allergene</t><t t-translation="off">: </t>
<span style="text-decoration: underline;" t-out="', '.join(o.product_id.x_allergens.mapped('display_name'))"/>
</div>
</t>
</xpath>
</data>

Fertig! Nun kannst du Allergene automatisch berechnen und auf deine Produktlabels drucken.
Wenn du Fragen zu Odoo in der Lebensmittelindustrie hast, kontaktiere mich gerne oder schreibe einen Kommentar.
Nate