diff --git a/NTO/WorkOrder/entities/Activity.ttl b/NTO/WorkOrder/entities/Activity.ttl
new file mode 100644
index 0000000000..075b2faa7e
--- /dev/null
+++ b/NTO/WorkOrder/entities/Activity.ttl
@@ -0,0 +1,32 @@
+@prefix ogit: .
+@prefix ogit.WorkOrder: .
+@prefix owl: .
+@prefix rdfs: .
+@prefix dcterms: .
+
+ogit.WorkOrder:Activity
+ a rdfs:Class;
+ rdfs:subClassOf ogit:Entity;
+ rdfs:label "Activity";
+ dcterms:description "A logged work activity attached to a WorkOrder. Carries optional device/asset reference (geraet), free-text description, logbook flag (export to driver/work logbook), internal-only flag, and creation timestamp. One Activity per discrete on-site or remote action.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:Activity";
+ dcterms:creator "family-codec-smith";
+ ogit:scope "NTO";
+ ogit:parent ogit:Node;
+ ogit:mandatory-attributes (
+ ogit:id
+ );
+ ogit:optional-attributes (
+ ogit.WorkOrder:geraet
+ ogit.WorkOrder:beschreibung
+ ogit.WorkOrder:logbuch
+ ogit.WorkOrder:intern
+ ogit:created-at
+ );
+ ogit:indexed-attributes (
+ ogit:created-at
+ );
+ ogit:allowed (
+ [ ogit:belongs ogit.WorkOrder:Order ]
+ );
+.
diff --git a/NTO/WorkOrder/entities/Article.ttl b/NTO/WorkOrder/entities/Article.ttl
new file mode 100644
index 0000000000..294057c936
--- /dev/null
+++ b/NTO/WorkOrder/entities/Article.ttl
@@ -0,0 +1,77 @@
+@prefix ogit: .
+@prefix ogit.WorkOrder: .
+@prefix rdfs: .
+@prefix dcterms: .
+
+ogit.WorkOrder:Article
+ a rdfs:Class;
+ rdfs:subClassOf ogit:Entity;
+ rdfs:label "Article";
+ dcterms:description "Article master-data record (Artikelstamm / WaWi). Referenced by Position rows on an Order via article_id. Carries selling and purchase prices, VAT rate and supplier reference; derived margin (marge / marge_pct) is computed on demand by the Python model.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:Article";
+ ogit:scope "NTO";
+ ogit:parent ogit:Node;
+ ogit:mandatory-attributes (
+ ogit:id
+ ogit.WorkOrder:beschreibung
+ );
+ ogit:optional-attributes (
+ ogit.WorkOrder:artikelnr
+ ogit.WorkOrder:preisNetto
+ ogit.WorkOrder:ekPreis
+ ogit.WorkOrder:mwstSatz
+ ogit.WorkOrder:lieferant
+ ogit.WorkOrder:aktiv
+ );
+ ogit:indexed-attributes (
+ ogit:id
+ ogit.WorkOrder:artikelnr
+ );
+ ogit:allowed (
+ [ ogit:belongs ogit.WorkOrder:Tenant ]
+ );
+.
+
+# ── attributes ───────────────────────────────────────────────────────────
+
+ogit.WorkOrder:artikelnr
+ a rdfs:Property;
+ rdfs:label "artikelnr";
+ dcterms:description "Article number (Artikelnummer). Tenant-local short code used on invoices, lookup forms and inventory listings.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:Article.artikelnr";
+.
+
+ogit.WorkOrder:beschreibung
+ a rdfs:Property;
+ rdfs:label "beschreibung";
+ dcterms:description "Article description (Beschreibung) printed on order positions. Mandatory in the source model.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:Article.beschreibung";
+.
+
+ogit.WorkOrder:preisNetto
+ a rdfs:Property;
+ rdfs:label "preisNetto";
+ dcterms:description "Net selling price (Preis netto) in the tenant's currency. Float, default 0.0.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:Article.preis_netto";
+.
+
+ogit.WorkOrder:ekPreis
+ a rdfs:Property;
+ rdfs:label "ekPreis";
+ dcterms:description "Purchase price (Einkaufspreis) used for margin calculation against preisNetto.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:Article.ek_preis";
+.
+
+ogit.WorkOrder:mwstSatz
+ a rdfs:Property;
+ rdfs:label "mwstSatz";
+ dcterms:description "VAT rate (MwSt-Satz) in percent applied when this article appears as a position. Float, default 19.0.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:Article.mwst_satz";
+.
+
+ogit.WorkOrder:lieferant
+ a rdfs:Property;
+ rdfs:label "lieferant";
+ dcterms:description "Supplier name (Lieferant) the article is normally sourced from. Free-form string; not a relation to a separate Supplier class in the current source.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:Article.lieferant";
+.
diff --git a/NTO/WorkOrder/entities/Customer.ttl b/NTO/WorkOrder/entities/Customer.ttl
new file mode 100644
index 0000000000..b97a827c82
--- /dev/null
+++ b/NTO/WorkOrder/entities/Customer.ttl
@@ -0,0 +1,126 @@
+@prefix ogit: .
+@prefix ogit.WorkOrder: .
+@prefix rdfs: .
+@prefix dcterms: .
+
+ogit.WorkOrder:Customer
+ a rdfs:Class;
+ rdfs:subClassOf ogit:Entity;
+ rdfs:label "Customer";
+ dcterms:description "Customer (Kunde) of a WoA tenant. Holds postal address, billing parameters (Zahlungsziel, Stundensatz) and is the parent of all work orders, time sheets and password-vault entries belonging to that customer.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:Customer";
+ ogit:scope "NTO";
+ ogit:parent ogit:Node;
+ ogit:mandatory-attributes (
+ ogit:id
+ );
+ ogit:optional-attributes (
+ ogit.WorkOrder:kdnr
+ ogit.WorkOrder:firma
+ ogit.WorkOrder:vorname
+ ogit.WorkOrder:nachname
+ ogit:email
+ ogit.WorkOrder:telefon
+ ogit.WorkOrder:strasse
+ ogit.WorkOrder:plz
+ ogit.WorkOrder:ort
+ ogit.WorkOrder:iban
+ ogit.WorkOrder:taxId
+ ogit.WorkOrder:zahlungsziel
+ ogit.WorkOrder:stundensatz
+ );
+ ogit:indexed-attributes (
+ ogit:id
+ ogit.WorkOrder:kdnr
+ ogit:email
+ );
+ ogit:allowed (
+ [ ogit:belongs ogit.WorkOrder:Tenant ]
+ );
+.
+
+# ── attributes ───────────────────────────────────────────────────────────
+
+ogit.WorkOrder:kdnr
+ a rdfs:Property;
+ rdfs:label "kdnr";
+ dcterms:description "Customer number (Kundennummer). Tenant-local short code used on invoices and in correspondence.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:Customer.kdnr";
+.
+
+ogit.WorkOrder:firma
+ a rdfs:Property;
+ rdfs:label "firma";
+ dcterms:description "Company name (Firma). Empty for private customers; in that case display falls back to vorname + nachname.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:Customer.firma";
+.
+
+ogit.WorkOrder:vorname
+ a rdfs:Property;
+ rdfs:label "vorname";
+ dcterms:description "Given name (Vorname) of the contact person.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:Customer.vorname";
+.
+
+ogit.WorkOrder:nachname
+ a rdfs:Property;
+ rdfs:label "nachname";
+ dcterms:description "Family name (Nachname) of the contact person.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:Customer.nachname";
+.
+
+ogit.WorkOrder:telefon
+ a rdfs:Property;
+ rdfs:label "telefon";
+ dcterms:description "Telephone number (Telefon) of the customer. Free-form string; no E.164 normalization is enforced by the source model.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:Customer.telefon";
+.
+
+ogit.WorkOrder:strasse
+ a rdfs:Property;
+ rdfs:label "strasse";
+ dcterms:description "Street and house number (Strasse).";
+ dcterms:source "AdaWorldAPI/WoA/models.py:Customer.strasse";
+.
+
+ogit.WorkOrder:plz
+ a rdfs:Property;
+ rdfs:label "plz";
+ dcterms:description "Postal code (Postleitzahl).";
+ dcterms:source "AdaWorldAPI/WoA/models.py:Customer.plz";
+.
+
+ogit.WorkOrder:ort
+ a rdfs:Property;
+ rdfs:label "ort";
+ dcterms:description "City / town (Ort).";
+ dcterms:source "AdaWorldAPI/WoA/models.py:Customer.ort";
+.
+
+ogit.WorkOrder:iban
+ a rdfs:Property;
+ rdfs:label "iban";
+ dcterms:description "International Bank Account Number for SEPA payments. Optional; not modelled as a column in the current Python source but reserved here for the OGIT projection.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:Customer";
+.
+
+ogit.WorkOrder:taxId
+ a rdfs:Property;
+ rdfs:label "taxId";
+ dcterms:description "Tax identification number (USt-IdNr / Steuernummer). Optional; reserved attribute for the OGIT projection of customer billing data.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:Customer";
+.
+
+ogit.WorkOrder:zahlungsziel
+ a rdfs:Property;
+ rdfs:label "zahlungsziel";
+ dcterms:description "Payment term (Zahlungsziel) in days; default 14. Drives the faellig_am derivation on Order.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:Customer.zahlungsziel";
+.
+
+ogit.WorkOrder:stundensatz
+ a rdfs:Property;
+ rdfs:label "stundensatz";
+ dcterms:description "Hourly rate (Stundensatz) in the tenant's currency, used by labour line items. Float, default 60.0.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:Customer.stundensatz";
+.
diff --git a/NTO/WorkOrder/entities/CustomerPortalUser.ttl b/NTO/WorkOrder/entities/CustomerPortalUser.ttl
new file mode 100644
index 0000000000..d86fd7b8e1
--- /dev/null
+++ b/NTO/WorkOrder/entities/CustomerPortalUser.ttl
@@ -0,0 +1,42 @@
+@prefix ogit: .
+@prefix ogit.WorkOrder: .
+@prefix owl: .
+@prefix rdfs: .
+@prefix dcterms: .
+
+ogit.WorkOrder:CustomerPortalUser
+ a rdfs:Class;
+ rdfs:subClassOf ogit:Entity;
+ rdfs:label "CustomerPortalUser";
+ dcterms:description "External self-service portal login bound to exactly one Customer. Authenticates via the same werkzeug pbkdf2:sha256 scheme as the internal User (with legacy SHA256+salt fallback and transparent upgrade), but has NO admin/superadmin scope. Used by customers to view their own work orders, invoices, and pay-status. Tracks last-login for audit, and carries an active-flag for soft-disabling and a forced-password-change marker.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:CustomerPortalUser";
+ dcterms:creator "bus-compiler";
+ ogit:scope "NTO";
+ ogit:parent ogit:Node;
+ ogit:mandatory-attributes (
+ ogit:id
+ );
+ ogit:optional-attributes (
+ ogit.WorkOrder:username
+ ogit.WorkOrder:passwordHash
+ ogit.WorkOrder:aktiv
+ ogit.WorkOrder:mustChangePw
+ ogit.WorkOrder:lastLogin
+ ogit.WorkOrder:createdAt
+ );
+ ogit:indexed-attributes (
+ ogit.WorkOrder:username
+ );
+ ogit:allowed (
+ [ ogit:belongs ogit.WorkOrder:Customer ]
+ );
+.
+
+# ── attributes ───────────────────────────────────────────────────────────
+
+ogit.WorkOrder:lastLogin
+ a rdfs:Property;
+ rdfs:label "lastLogin";
+ dcterms:description "ISO-8601 UTC timestamp of the most recent successful portal login. Null on accounts that have never signed in.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:CustomerPortalUser.last_login";
+.
diff --git a/NTO/WorkOrder/entities/HistoryEntry.ttl b/NTO/WorkOrder/entities/HistoryEntry.ttl
new file mode 100644
index 0000000000..e578f61f45
--- /dev/null
+++ b/NTO/WorkOrder/entities/HistoryEntry.ttl
@@ -0,0 +1,32 @@
+@prefix ogit: .
+@prefix ogit.WorkOrder: .
+@prefix owl: .
+@prefix rdfs: .
+@prefix dcterms: .
+
+ogit.WorkOrder:HistoryEntry
+ a rdfs:Class;
+ rdfs:subClassOf ogit:Entity;
+ rdfs:label "HistoryEntry";
+ dcterms:description "An immutable audit/log row for a WorkOrder. Records an action label (aktion), free-text details, the User who performed the action, and a creation timestamp. Append-only: every state change to an Order should produce one HistoryEntry.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:HistoryEntry";
+ dcterms:creator "family-codec-smith";
+ ogit:scope "NTO";
+ ogit:parent ogit:Node;
+ ogit:mandatory-attributes (
+ ogit:id
+ );
+ ogit:optional-attributes (
+ ogit.WorkOrder:aktion
+ ogit.WorkOrder:details
+ ogit:created-at
+ );
+ ogit:indexed-attributes (
+ ogit:created-at
+ ogit.WorkOrder:aktion
+ );
+ ogit:allowed (
+ [ ogit:belongs ogit.WorkOrder:Order ]
+ [ ogit:relates ogit.WorkOrder:User ]
+ );
+.
diff --git a/NTO/WorkOrder/entities/LogbookEntry.ttl b/NTO/WorkOrder/entities/LogbookEntry.ttl
new file mode 100644
index 0000000000..24b0cc1dea
--- /dev/null
+++ b/NTO/WorkOrder/entities/LogbookEntry.ttl
@@ -0,0 +1,120 @@
+@prefix ogit: .
+@prefix ogit.WorkOrder: .
+@prefix owl: .
+@prefix rdfs: .
+@prefix dcterms: .
+
+ogit.WorkOrder:LogbookEntry
+ a rdfs:Class;
+ rdfs:subClassOf ogit:Entity;
+ rdfs:label "LogbookEntry";
+ dcterms:description "A driving-logbook (Fahrtenbuch) row capturing one trip: start/end odometer reading, departure/arrival times, route description, business purpose, vehicle, and the private-share kilometers split out for tax purposes. Optionally references a User (driver), Customer (visited), and Order (the work-order driven for).";
+ dcterms:source "AdaWorldAPI/WoA/models.py:LogbookEntry";
+ dcterms:creator "bus-compiler";
+ ogit:scope "NTO";
+ ogit:parent ogit:Node;
+ ogit:mandatory-attributes (
+ ogit:id
+ );
+ ogit:optional-attributes (
+ ogit.WorkOrder:datum
+ ogit.WorkOrder:abfahrt
+ ogit.WorkOrder:ankunft
+ ogit.WorkOrder:rueckfahrt
+ ogit.WorkOrder:zurueck
+ ogit.WorkOrder:startKm
+ ogit.WorkOrder:endeKm
+ ogit.WorkOrder:route
+ ogit.WorkOrder:zweck
+ ogit.WorkOrder:fahrzeug
+ ogit.WorkOrder:privatAnteil
+ ogit.WorkOrder:createdAt
+ );
+ ogit:indexed-attributes (
+ ogit.WorkOrder:datum
+ );
+ ogit:allowed (
+ [ ogit:relates ogit.WorkOrder:User ]
+ [ ogit:relates ogit.WorkOrder:Customer ]
+ [ ogit:relates ogit.WorkOrder:Order ]
+ );
+.
+
+# ── attributes ───────────────────────────────────────────────────────────
+
+ogit.WorkOrder:datum
+ a rdfs:Property;
+ rdfs:label "datum";
+ dcterms:description "ISO-8601 calendar date the trip took place (mandatory; defaults to today on insert).";
+ dcterms:source "AdaWorldAPI/WoA/models.py:LogbookEntry.datum";
+.
+
+ogit.WorkOrder:abfahrt
+ a rdfs:Property;
+ rdfs:label "abfahrt";
+ dcterms:description "Departure time as HH:MM string (max 5 chars).";
+ dcterms:source "AdaWorldAPI/WoA/models.py:LogbookEntry.abfahrt";
+.
+
+ogit.WorkOrder:ankunft
+ a rdfs:Property;
+ rdfs:label "ankunft";
+ dcterms:description "Arrival time at destination as HH:MM string (max 5 chars).";
+ dcterms:source "AdaWorldAPI/WoA/models.py:LogbookEntry.ankunft";
+.
+
+ogit.WorkOrder:rueckfahrt
+ a rdfs:Property;
+ rdfs:label "rueckfahrt";
+ dcterms:description "Return-trip departure time as HH:MM string (max 5 chars).";
+ dcterms:source "AdaWorldAPI/WoA/models.py:LogbookEntry.rueckfahrt";
+.
+
+ogit.WorkOrder:zurueck
+ a rdfs:Property;
+ rdfs:label "zurueck";
+ dcterms:description "Return-trip arrival time as HH:MM string (max 5 chars).";
+ dcterms:source "AdaWorldAPI/WoA/models.py:LogbookEntry.zurueck";
+.
+
+ogit.WorkOrder:startKm
+ a rdfs:Property;
+ rdfs:label "startKm";
+ dcterms:description "Odometer reading at trip start (kilometers, float).";
+ dcterms:source "AdaWorldAPI/WoA/models.py:LogbookEntry.start_km";
+.
+
+ogit.WorkOrder:endeKm
+ a rdfs:Property;
+ rdfs:label "endeKm";
+ dcterms:description "Odometer reading at trip end (kilometers, float). The trip's total distance is end_km - start_km.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:LogbookEntry.ende_km";
+.
+
+ogit.WorkOrder:route
+ a rdfs:Property;
+ rdfs:label "route";
+ dcterms:description "Free-text route description (e.g. 'Würzburg - Frankfurt - Würzburg', max 300 chars).";
+ dcterms:source "AdaWorldAPI/WoA/models.py:LogbookEntry.route";
+.
+
+ogit.WorkOrder:zweck
+ a rdfs:Property;
+ rdfs:label "zweck";
+ dcterms:description "Business purpose of the trip (max 300 chars). Required by German tax law for the Fahrtenbuch.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:LogbookEntry.zweck";
+.
+
+ogit.WorkOrder:fahrzeug
+ a rdfs:Property;
+ rdfs:label "fahrzeug";
+ dcterms:description "Vehicle identifier (license plate or fleet name, max 100 chars).";
+ dcterms:source "AdaWorldAPI/WoA/models.py:LogbookEntry.fahrzeug";
+.
+
+ogit.WorkOrder:privatAnteil
+ a rdfs:Property;
+ rdfs:label "privatAnteil";
+ dcterms:description "Kilometers attributed to private use within this trip (float). Subtracted from the total to derive km_geschaeftlich.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:LogbookEntry.privat_anteil";
+.
diff --git a/NTO/WorkOrder/entities/NumberSequence.ttl b/NTO/WorkOrder/entities/NumberSequence.ttl
new file mode 100644
index 0000000000..56d9c45cf5
--- /dev/null
+++ b/NTO/WorkOrder/entities/NumberSequence.ttl
@@ -0,0 +1,46 @@
+@prefix ogit: .
+@prefix ogit.WorkOrder: .
+@prefix owl: .
+@prefix rdfs: .
+@prefix dcterms: .
+
+ogit.WorkOrder:NumberSequence
+ a rdfs:Class;
+ rdfs:subClassOf ogit:Entity;
+ rdfs:label "NumberSequence";
+ dcterms:description "Per-tenant atomic counter used to mint sequential business document numbers (offer-, order-, invoice-, credit-numbers). Each sequence has a stable name (the sequence kind), an optional prefix, and a monotonically incrementing 'current' integer; next_val() increments the counter and concatenates prefix + current to produce the next document number.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:NumberSequence";
+ dcterms:creator "bus-compiler";
+ ogit:scope "NTO";
+ ogit:parent ogit:Node;
+ ogit:mandatory-attributes (
+ ogit:id
+ );
+ ogit:optional-attributes (
+ ogit:name
+ ogit.WorkOrder:prefix
+ ogit.WorkOrder:current
+ );
+ ogit:indexed-attributes (
+ ogit:name
+ );
+ ogit:allowed (
+ [ ogit:belongs ogit.WorkOrder:Tenant ]
+ );
+.
+
+# ── attributes ───────────────────────────────────────────────────────────
+
+ogit.WorkOrder:prefix
+ a rdfs:Property;
+ rdfs:label "prefix";
+ dcterms:description "Static string concatenated in front of the counter when minting a new number (e.g. 'AB-', 'RE-', max 10 chars).";
+ dcterms:source "AdaWorldAPI/WoA/models.py:NumberSequence.prefix";
+.
+
+ogit.WorkOrder:current
+ a rdfs:Property;
+ rdfs:label "current";
+ dcterms:description "Monotonically increasing integer holding the most-recently-issued counter value. Incremented in-place by next_val() inside a database transaction.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:NumberSequence.current";
+.
diff --git a/NTO/WorkOrder/entities/Order.ttl b/NTO/WorkOrder/entities/Order.ttl
new file mode 100644
index 0000000000..9d86a91edb
--- /dev/null
+++ b/NTO/WorkOrder/entities/Order.ttl
@@ -0,0 +1,97 @@
+@prefix ogit: .
+@prefix ogit.WorkOrder: .
+@prefix rdfs: .
+@prefix dcterms: .
+
+ogit.WorkOrder:Order
+ a rdfs:Class;
+ rdfs:subClassOf ogit:Entity;
+ rdfs:label "Order";
+ dcterms:description "Central work-order document of WoA. A single SQLAlchemy table 'workorders' represents five doc_type variants — workorder (Arbeitsauftrag), offer (Angebot), order (Auftrag), invoice (Rechnung), credit (Gutschrift). The doc_type column selects the variant; OGIT consumers should treat 'Order' as the umbrella class and filter by doc_type for variant-specific logic. Holds positions, activities, pictures and history as child collections, and exposes derived totals netto_summe / mwst_betrag / brutto_summe.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder";
+ ogit:scope "NTO";
+ ogit:parent ogit:Node;
+ ogit:mandatory-attributes (
+ ogit:id
+ ogit.WorkOrder:orderId
+ );
+ ogit:optional-attributes (
+ ogit:status
+ ogit.WorkOrder:docType
+ ogit.WorkOrder:datum
+ ogit.WorkOrder:betreff
+ ogit.WorkOrder:nettoSumme
+ ogit.WorkOrder:mwstBetrag
+ ogit.WorkOrder:bruttoSumme
+ ogit.WorkOrder:bezahlt
+ );
+ ogit:indexed-attributes (
+ ogit:id
+ ogit.WorkOrder:orderId
+ ogit:status
+ ogit.WorkOrder:datum
+ );
+ ogit:allowed (
+ [ ogit:belongs ogit.WorkOrder:Tenant ]
+ [ ogit:relates ogit.WorkOrder:Customer ]
+ [ ogit:relates ogit.WorkOrder:Article ]
+ );
+.
+
+# ── attributes ───────────────────────────────────────────────────────────
+
+ogit.WorkOrder:orderId
+ a rdfs:Property;
+ rdfs:label "orderId";
+ dcterms:description "Public document number. The Python source carries five parallel slots (angebot_nr, auftrags_nr, workorder_nr, rechnung_nr, gutschrift_nr) plus a derived beleg_nr that picks the active one. orderId is the OGIT-side projection of beleg_nr — the human-facing identifier regardless of doc_type.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.beleg_nr";
+.
+
+ogit.WorkOrder:docType
+ a rdfs:Property;
+ rdfs:label "docType";
+ dcterms:description "Discriminator for the document variant. One of: workorder, offer, order, invoice, credit. Mirrors WorkOrder.doc_type in the source.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.doc_type";
+.
+
+ogit.WorkOrder:datum
+ a rdfs:Property;
+ rdfs:label "datum";
+ dcterms:description "Document date (Datum). ISO-8601 date; defaults to creation day. Drives the dunning / due-date derivations.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.datum";
+.
+
+ogit.WorkOrder:betreff
+ a rdfs:Property;
+ rdfs:label "betreff";
+ dcterms:description "Subject / heading line (Betreff) printed at the top of the document.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.betreff";
+.
+
+ogit.WorkOrder:nettoSumme
+ a rdfs:Property;
+ rdfs:label "nettoSumme";
+ dcterms:description "Net total (Netto-Summe). Derived in Python as sum(menge × einzelpreis) over all positions; materialised on the OGIT projection.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.netto_summe";
+.
+
+ogit.WorkOrder:mwstBetrag
+ a rdfs:Property;
+ rdfs:label "mwstBetrag";
+ dcterms:description "VAT amount (MwSt-Betrag). Derived in Python as sum(menge × einzelpreis × mwst_satz / 100) over all positions.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.mwst_betrag";
+.
+
+ogit.WorkOrder:bruttoSumme
+ a rdfs:Property;
+ rdfs:label "bruttoSumme";
+ dcterms:description "Gross total (Brutto-Summe) = nettoSumme + mwstBetrag.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.brutto_summe";
+.
+
+ogit.WorkOrder:bezahlt
+ a rdfs:Property;
+ rdfs:label "bezahlt";
+ dcterms:description "Payment-received flag. Boolean; true once the order has been settled by the customer.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.bezahlt";
+.
diff --git a/NTO/WorkOrder/entities/PasswordEntry.ttl b/NTO/WorkOrder/entities/PasswordEntry.ttl
new file mode 100644
index 0000000000..695f8d6179
--- /dev/null
+++ b/NTO/WorkOrder/entities/PasswordEntry.ttl
@@ -0,0 +1,91 @@
+@prefix ogit: .
+@prefix ogit.WorkOrder: .
+@prefix owl: .
+@prefix rdfs: .
+@prefix dcterms: .
+
+ogit.WorkOrder:PasswordEntry
+ a rdfs:Class;
+ rdfs:subClassOf ogit:Entity;
+ rdfs:label "PasswordEntry";
+ dcterms:description "KeePass-style encrypted vault entry stored against a Customer (the resource being accessed) and authored by a User. Sensitive fields (passwort_enc, notizen_enc) are encrypted at rest using Fernet (AES-128-CBC + HMAC-SHA256), with the symmetric key derived from the WOA_SECRET environment variable via SHA-256. WOA_SECRET MUST be set (>= 32 chars); the application refuses to encrypt or decrypt without it. Non-secret metadata (group, title, username, url, icon) is stored in plaintext for searchability.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:PasswordEntry";
+ dcterms:creator "bus-compiler";
+ ogit:scope "NTO";
+ ogit:parent ogit:Node;
+ ogit:mandatory-attributes (
+ ogit:id
+ );
+ ogit:optional-attributes (
+ ogit.WorkOrder:gruppe
+ ogit.WorkOrder:titel
+ ogit.WorkOrder:benutzername
+ ogit.WorkOrder:passwortEnc
+ ogit.WorkOrder:url
+ ogit.WorkOrder:notizenEnc
+ ogit.WorkOrder:icon
+ ogit.WorkOrder:aktiv
+ ogit.WorkOrder:createdAt
+ ogit.WorkOrder:updatedAt
+ );
+ ogit:indexed-attributes (
+ ogit.WorkOrder:titel
+ ogit.WorkOrder:gruppe
+ );
+ ogit:allowed (
+ [ ogit:belongs ogit.WorkOrder:Customer ]
+ [ ogit:belongs ogit.WorkOrder:Tenant ]
+ [ ogit:relates ogit.WorkOrder:User ]
+ );
+.
+
+# ── attributes ───────────────────────────────────────────────────────────
+
+ogit.WorkOrder:gruppe
+ a rdfs:Property;
+ rdfs:label "gruppe";
+ dcterms:description "Vault folder/category (max 100 chars, default 'Allgemein'). Free-form grouping used to organise entries in the UI tree.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:PasswordEntry.gruppe";
+.
+
+ogit.WorkOrder:titel
+ a rdfs:Property;
+ rdfs:label "titel";
+ dcterms:description "Display title for the entry (max 200 chars). Mandatory.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:PasswordEntry.titel";
+.
+
+ogit.WorkOrder:benutzername
+ a rdfs:Property;
+ rdfs:label "benutzername";
+ dcterms:description "Plaintext account-username for the resource (max 200 chars). NOT encrypted — only the password and notes carry sensitive material.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:PasswordEntry.benutzername";
+.
+
+ogit.WorkOrder:passwortEnc
+ a rdfs:Property;
+ rdfs:label "passwortEnc";
+ dcterms:description "Fernet-encrypted ciphertext of the password (base64-urlsafe over AES-128-CBC + HMAC-SHA256). Decryption requires the WOA_SECRET-derived key; failure produces the placeholder '*** Entschluesselung fehlgeschlagen ***'.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:PasswordEntry.passwort_enc";
+.
+
+ogit.WorkOrder:url
+ a rdfs:Property;
+ rdfs:label "url";
+ dcterms:description "URL of the protected resource (max 500 chars), stored in plaintext.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:PasswordEntry.url";
+.
+
+ogit.WorkOrder:notizenEnc
+ a rdfs:Property;
+ rdfs:label "notizenEnc";
+ dcterms:description "Fernet-encrypted ciphertext of free-form notes. Same Fernet construction as passwortEnc.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:PasswordEntry.notizen_enc";
+.
+
+ogit.WorkOrder:icon
+ a rdfs:Property;
+ rdfs:label "icon";
+ dcterms:description "Icon glyph name (max 50 chars, default 'key') used by the UI to render the entry visually.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:PasswordEntry.icon";
+.
diff --git a/NTO/WorkOrder/entities/Picture.ttl b/NTO/WorkOrder/entities/Picture.ttl
new file mode 100644
index 0000000000..c54d3781de
--- /dev/null
+++ b/NTO/WorkOrder/entities/Picture.ttl
@@ -0,0 +1,32 @@
+@prefix ogit: .
+@prefix ogit.WorkOrder: .
+@prefix owl: .
+@prefix rdfs: .
+@prefix dcterms: .
+
+ogit.WorkOrder:Picture
+ a rdfs:Class;
+ rdfs:subClassOf ogit:Entity;
+ rdfs:label "Picture";
+ dcterms:description "An image/document attachment on a WorkOrder. Carries the stored filename (dateiname), an optional caption (beschreibung), a logbook flag (include in driver/work logbook export), an an_kunde flag (visible to / send to customer), and a creation timestamp.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:Picture";
+ dcterms:creator "family-codec-smith";
+ ogit:scope "NTO";
+ ogit:parent ogit:Node;
+ ogit:mandatory-attributes (
+ ogit:id
+ );
+ ogit:optional-attributes (
+ ogit.WorkOrder:dateiname
+ ogit.WorkOrder:beschreibung
+ ogit.WorkOrder:logbuch
+ ogit.WorkOrder:anKunde
+ ogit:created-at
+ );
+ ogit:indexed-attributes (
+ ogit:created-at
+ );
+ ogit:allowed (
+ [ ogit:belongs ogit.WorkOrder:Order ]
+ );
+.
diff --git a/NTO/WorkOrder/entities/Position.ttl b/NTO/WorkOrder/entities/Position.ttl
new file mode 100644
index 0000000000..042d12711d
--- /dev/null
+++ b/NTO/WorkOrder/entities/Position.ttl
@@ -0,0 +1,37 @@
+@prefix ogit: .
+@prefix ogit.WorkOrder: .
+@prefix owl: .
+@prefix rdfs: .
+@prefix dcterms: .
+
+ogit.WorkOrder:Position
+ a rdfs:Class;
+ rdfs:subClassOf ogit:Entity;
+ rdfs:label "Position";
+ dcterms:description "A line item (Position) on a WorkOrder/Order document. Represents one billable or descriptive row carrying quantity, unit, unit-price, VAT rate, optional Article reference, and ordering. Used across offer/order/invoice/credit doc types.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:Position";
+ dcterms:creator "family-codec-smith";
+ ogit:scope "NTO";
+ ogit:parent ogit:Node;
+ ogit:mandatory-attributes (
+ ogit:id
+ );
+ ogit:optional-attributes (
+ ogit.WorkOrder:sortOrder
+ ogit.WorkOrder:posTyp
+ ogit.WorkOrder:beschreibung
+ ogit.WorkOrder:menge
+ ogit.WorkOrder:einheit
+ ogit.WorkOrder:einzelpreis
+ ogit.WorkOrder:mwstSatz
+ ogit.WorkOrder:versteckt
+ );
+ ogit:indexed-attributes (
+ ogit.WorkOrder:sortOrder
+ ogit.WorkOrder:posTyp
+ );
+ ogit:allowed (
+ [ ogit:belongs ogit.WorkOrder:Order ]
+ [ ogit:relates ogit.WorkOrder:Article ]
+ );
+.
diff --git a/NTO/WorkOrder/entities/Setting.ttl b/NTO/WorkOrder/entities/Setting.ttl
new file mode 100644
index 0000000000..b62d6fc424
--- /dev/null
+++ b/NTO/WorkOrder/entities/Setting.ttl
@@ -0,0 +1,53 @@
+@prefix ogit: .
+@prefix ogit.WorkOrder: .
+@prefix owl: .
+@prefix rdfs: .
+@prefix dcterms: .
+
+ogit.WorkOrder:Setting
+ a rdfs:Class;
+ rdfs:subClassOf ogit:Entity;
+ rdfs:label "Setting";
+ dcterms:description "Per-tenant key/value configuration row. Stores arbitrary string-typed application settings (e.g. company address fields, default VAT rate, PDF footer text) outside of code. The label provides a human-readable caption used in admin UIs; the value is free-form text.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:Setting";
+ dcterms:creator "bus-compiler";
+ ogit:scope "NTO";
+ ogit:parent ogit:Node;
+ ogit:mandatory-attributes (
+ ogit:id
+ );
+ ogit:optional-attributes (
+ ogit.WorkOrder:settingKey
+ ogit.WorkOrder:settingValue
+ ogit.WorkOrder:label
+ );
+ ogit:indexed-attributes (
+ ogit.WorkOrder:settingKey
+ );
+ ogit:allowed (
+ [ ogit:belongs ogit.WorkOrder:Tenant ]
+ );
+.
+
+# ── attributes ───────────────────────────────────────────────────────────
+
+ogit.WorkOrder:settingKey
+ a rdfs:Property;
+ rdfs:label "settingKey";
+ dcterms:description "Setting identifier (max 100 chars), unique per tenant. Conventionally dot-namespaced (e.g. 'company.address', 'pdf.footer').";
+ dcterms:source "AdaWorldAPI/WoA/models.py:Setting.key";
+.
+
+ogit.WorkOrder:settingValue
+ a rdfs:Property;
+ rdfs:label "settingValue";
+ dcterms:description "Free-form text value associated with the key. Application-side code is responsible for any type coercion (int/bool/json).";
+ dcterms:source "AdaWorldAPI/WoA/models.py:Setting.value";
+.
+
+ogit.WorkOrder:label
+ a rdfs:Property;
+ rdfs:label "label";
+ dcterms:description "Human-readable caption (max 200 chars) shown next to the key in admin UIs.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:Setting.label";
+.
diff --git a/NTO/WorkOrder/entities/Tenant.ttl b/NTO/WorkOrder/entities/Tenant.ttl
new file mode 100644
index 0000000000..cc20f07ba5
--- /dev/null
+++ b/NTO/WorkOrder/entities/Tenant.ttl
@@ -0,0 +1,58 @@
+@prefix ogit: .
+@prefix ogit.WorkOrder: .
+@prefix rdfs: .
+@prefix dcterms: .
+
+ogit.WorkOrder:Tenant
+ a rdfs:Class;
+ rdfs:subClassOf ogit:Entity;
+ rdfs:label "Tenant";
+ dcterms:description "Multi-tenancy root for the WoA application. Every tenant-scoped business entity (Customer, Order, Article, etc.) carries a tenant_id foreign key referring to this class. Backed by SQLAlchemy table 'tenants'.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:Tenant";
+ ogit:scope "NTO";
+ ogit:parent ogit:Node;
+ ogit:mandatory-attributes (
+ ogit:id
+ ogit:name
+ ogit.WorkOrder:slug
+ );
+ ogit:optional-attributes (
+ ogit.WorkOrder:aktiv
+ ogit.WorkOrder:logoPath
+ ogit.WorkOrder:createdAt
+ );
+ ogit:indexed-attributes (
+ ogit:id
+ ogit.WorkOrder:slug
+ );
+.
+
+# ── attributes ───────────────────────────────────────────────────────────
+
+ogit.WorkOrder:slug
+ a rdfs:Property;
+ rdfs:label "slug";
+ dcterms:description "Unique URL-safe short identifier for the tenant (max 50 chars). Used in routing and as a stable handle independent of the numeric id.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:Tenant.slug";
+.
+
+ogit.WorkOrder:aktiv
+ a rdfs:Property;
+ rdfs:label "aktiv";
+ dcterms:description "Boolean active-flag. Inactive tenants are soft-disabled rather than deleted.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:Tenant.aktiv";
+.
+
+ogit.WorkOrder:logoPath
+ a rdfs:Property;
+ rdfs:label "logoPath";
+ dcterms:description "Filesystem or URL path to the tenant's logo image used in PDF/HTML rendering of work orders.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:Tenant.logo_path";
+.
+
+ogit.WorkOrder:createdAt
+ a rdfs:Property;
+ rdfs:label "createdAt";
+ dcterms:description "ISO-8601 UTC timestamp recording when the entity was first persisted.";
+ dcterms:source "AdaWorldAPI/WoA/models.py";
+.
diff --git a/NTO/WorkOrder/entities/TimeSheet.ttl b/NTO/WorkOrder/entities/TimeSheet.ttl
new file mode 100644
index 0000000000..d51ac6ef85
--- /dev/null
+++ b/NTO/WorkOrder/entities/TimeSheet.ttl
@@ -0,0 +1,74 @@
+@prefix ogit: .
+@prefix ogit.WorkOrder: .
+@prefix owl: .
+@prefix rdfs: .
+@prefix dcterms: .
+
+ogit.WorkOrder:TimeSheet
+ a rdfs:Class;
+ rdfs:subClassOf ogit:Entity;
+ rdfs:label "TimeSheet";
+ dcterms:description "Stundenzettel — a time-tracking record booked against a Customer (mandatory) and authored by a User (optional). Stores duration in minutes, a free-text description, and an 'abgerechnet' flag indicating whether the entry has been billed onto a downstream invoice. Provides 15-minute-rounded display values and decimal-hour conversion via instance methods.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:TimeSheet";
+ dcterms:creator "bus-compiler";
+ ogit:scope "NTO";
+ ogit:parent ogit:Node;
+ ogit:mandatory-attributes (
+ ogit:id
+ );
+ ogit:optional-attributes (
+ ogit.WorkOrder:datum
+ ogit.WorkOrder:minuten
+ ogit.WorkOrder:beschreibung
+ ogit.WorkOrder:timerStart
+ ogit.WorkOrder:abgerechnet
+ ogit.WorkOrder:createdAt
+ ogit.WorkOrder:updatedAt
+ );
+ ogit:indexed-attributes (
+ ogit.WorkOrder:datum
+ ogit.WorkOrder:abgerechnet
+ );
+ ogit:allowed (
+ [ ogit:belongs ogit.WorkOrder:Customer ]
+ [ ogit:belongs ogit.WorkOrder:Tenant ]
+ [ ogit:relates ogit.WorkOrder:User ]
+ );
+.
+
+# ── attributes ───────────────────────────────────────────────────────────
+
+ogit.WorkOrder:minuten
+ a rdfs:Property;
+ rdfs:label "minuten";
+ dcterms:description "Duration of the recorded work in minutes (integer). Display helpers round up to the nearest 15-minute increment for billing.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:TimeSheet.minuten";
+.
+
+ogit.WorkOrder:timerStart
+ a rdfs:Property;
+ rdfs:label "timerStart";
+ dcterms:description "ISO-8601 UTC timestamp marking when a live timer was started for this entry. Null when not currently running.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:TimeSheet.timer_start";
+.
+
+ogit.WorkOrder:abgerechnet
+ a rdfs:Property;
+ rdfs:label "abgerechnet";
+ dcterms:description "Boolean billed-flag. True once the time entry has been transferred onto an invoice document, preventing double-billing.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:TimeSheet.abgerechnet";
+.
+
+ogit.WorkOrder:beschreibung
+ a rdfs:Property;
+ rdfs:label "beschreibung";
+ dcterms:description "Free-text description of the work performed during this time entry.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:TimeSheet.beschreibung";
+.
+
+ogit.WorkOrder:updatedAt
+ a rdfs:Property;
+ rdfs:label "updatedAt";
+ dcterms:description "ISO-8601 UTC timestamp of the most recent persisted update to the row.";
+ dcterms:source "AdaWorldAPI/WoA/models.py";
+.
diff --git a/NTO/WorkOrder/entities/User.ttl b/NTO/WorkOrder/entities/User.ttl
new file mode 100644
index 0000000000..aed69a1908
--- /dev/null
+++ b/NTO/WorkOrder/entities/User.ttl
@@ -0,0 +1,102 @@
+@prefix ogit: .
+@prefix ogit.WorkOrder: .
+@prefix owl: .
+@prefix rdfs: .
+@prefix dcterms: .
+
+ogit.WorkOrder:User
+ a rdfs:Class;
+ rdfs:subClassOf ogit:Entity;
+ rdfs:label "User";
+ dcterms:description "Internal WoA user / operator. Authenticates against pbkdf2:sha256 password hashes (werkzeug, 600k iterations, salted) with transparent migration from a legacy SHA256+salt format. Carries optional contact metadata (firstname, lastname, email, phone), the admin/superadmin flags that gate Flask routes, and a forced-password-change marker. Each User belongs to exactly one Tenant via tenant_id.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:User";
+ dcterms:creator "bus-compiler";
+ ogit:scope "NTO";
+ ogit:parent ogit:Node;
+ ogit:mandatory-attributes (
+ ogit:id
+ );
+ ogit:optional-attributes (
+ ogit.WorkOrder:username
+ ogit.WorkOrder:passwordHash
+ ogit.WorkOrder:firstname
+ ogit.WorkOrder:lastname
+ ogit.WorkOrder:email
+ ogit.WorkOrder:phone
+ ogit.WorkOrder:isAdmin
+ ogit.WorkOrder:isSuperadmin
+ ogit.WorkOrder:mustChangePw
+ ogit.WorkOrder:createdAt
+ );
+ ogit:indexed-attributes (
+ ogit.WorkOrder:username
+ );
+ ogit:allowed (
+ [ ogit:belongs ogit.WorkOrder:Tenant ]
+ );
+.
+
+# ── attributes ───────────────────────────────────────────────────────────
+
+ogit.WorkOrder:username
+ a rdfs:Property;
+ rdfs:label "username";
+ dcterms:description "Globally unique login handle (max 120 chars). Primary credential identifier used for password authentication.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:User.username";
+.
+
+ogit.WorkOrder:passwordHash
+ a rdfs:Property;
+ rdfs:label "passwordHash";
+ dcterms:description "Werkzeug-formatted password hash (pbkdf2:sha256:$$, max 256 chars). Legacy two-part SHA256+salt hashes are accepted on first login and transparently upgraded to pbkdf2.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:User.password_hash";
+.
+
+ogit.WorkOrder:firstname
+ a rdfs:Property;
+ rdfs:label "firstname";
+ dcterms:description "User's given name (max 120 chars).";
+ dcterms:source "AdaWorldAPI/WoA/models.py:User.firstname";
+.
+
+ogit.WorkOrder:lastname
+ a rdfs:Property;
+ rdfs:label "lastname";
+ dcterms:description "User's family name (max 120 chars).";
+ dcterms:source "AdaWorldAPI/WoA/models.py:User.lastname";
+.
+
+ogit.WorkOrder:email
+ a rdfs:Property;
+ rdfs:label "email";
+ dcterms:description "User's primary email address (max 200 chars). Used for notifications and password resets.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:User.email";
+.
+
+ogit.WorkOrder:phone
+ a rdfs:Property;
+ rdfs:label "phone";
+ dcterms:description "User's contact telephone number (max 100 chars).";
+ dcterms:source "AdaWorldAPI/WoA/models.py:User.phone";
+.
+
+ogit.WorkOrder:isAdmin
+ a rdfs:Property;
+ rdfs:label "isAdmin";
+ dcterms:description "Boolean flag granting admin-level access within the User's tenant scope (manage customers, articles, settings).";
+ dcterms:source "AdaWorldAPI/WoA/models.py:User.is_admin";
+.
+
+ogit.WorkOrder:isSuperadmin
+ a rdfs:Property;
+ rdfs:label "isSuperadmin";
+ dcterms:description "Boolean flag granting cross-tenant superadmin access (manage tenants, system-wide configuration).";
+ dcterms:source "AdaWorldAPI/WoA/models.py:User.is_superadmin";
+.
+
+ogit.WorkOrder:mustChangePw
+ a rdfs:Property;
+ rdfs:label "mustChangePw";
+ dcterms:description "Boolean forced-password-change marker. When true, the next successful login redirects the user to the password-reset flow.";
+ dcterms:source "AdaWorldAPI/WoA/models.py:User.must_change_pw";
+.
diff --git a/NTO/WorkOrder/verbs/AccessesPortal.ttl b/NTO/WorkOrder/verbs/AccessesPortal.ttl
new file mode 100644
index 0000000000..1b8b2aa0da
--- /dev/null
+++ b/NTO/WorkOrder/verbs/AccessesPortal.ttl
@@ -0,0 +1,16 @@
+@prefix ogit: .
+@prefix ogit.WorkOrder: .
+@prefix rdfs: .
+@prefix dcterms: .
+
+ogit.WorkOrder:accessesPortal
+ a rdfs:Class;
+ rdfs:subClassOf ogit:Verb;
+ rdfs:label "accessesPortal";
+ dcterms:description "CustomerPortalUser accesses-portal as a specific Customer (login binding). Each portal login row binds exactly one credential to one Customer (one-to-one)." ;
+ dcterms:source "AdaWorldAPI/WoA/models.py:CustomerPortalUser.customer_id -> Customer.id" ;
+ ogit:scope "NTO";
+ ogit:from-to (
+ [ ogit:from ogit.WorkOrder:CustomerPortalUser ; ogit:to ogit.WorkOrder:Customer ]
+ ) ;
+.
diff --git a/NTO/WorkOrder/verbs/Assigned.ttl b/NTO/WorkOrder/verbs/Assigned.ttl
new file mode 100644
index 0000000000..c126eab7f9
--- /dev/null
+++ b/NTO/WorkOrder/verbs/Assigned.ttl
@@ -0,0 +1,16 @@
+@prefix ogit: .
+@prefix ogit.WorkOrder: .
+@prefix rdfs: .
+@prefix dcterms: .
+
+ogit.WorkOrder:assigned
+ a rdfs:Class;
+ rdfs:subClassOf ogit:Verb;
+ rdfs:label "assigned";
+ dcterms:description "User assigned to Order (work assignment). A User may be assigned to many Orders, and an Order may have many assigned Users over time via creation and history (many-to-many)." ;
+ dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.created_by -> User.id; HistoryEntry.user_id -> User.id" ;
+ ogit:scope "NTO";
+ ogit:from-to (
+ [ ogit:from ogit.WorkOrder:User ; ogit:to ogit.WorkOrder:Order ]
+ ) ;
+.
diff --git a/NTO/WorkOrder/verbs/BelongsToTenant.ttl b/NTO/WorkOrder/verbs/BelongsToTenant.ttl
new file mode 100644
index 0000000000..a9080a38d0
--- /dev/null
+++ b/NTO/WorkOrder/verbs/BelongsToTenant.ttl
@@ -0,0 +1,21 @@
+@prefix ogit: .
+@prefix ogit.WorkOrder: .
+@prefix rdfs: .
+@prefix dcterms: .
+
+ogit.WorkOrder:belongsToTenant
+ a rdfs:Class;
+ rdfs:subClassOf ogit:Verb;
+ rdfs:label "belongsToTenant";
+ dcterms:description "Every multi-tenant business entity belongs-to-tenant (mandatory partition for row-level isolation). Many entities of any business class belong to one Tenant (many-to-one)." ;
+ dcterms:source "AdaWorldAPI/WoA/models.py: tenant_id FK on User, Customer, Article, WorkOrder, PasswordEntry, TimeSheet, NumberSequence, Setting -> Tenant.id" ;
+ ogit:scope "NTO";
+ ogit:from-to (
+ [ ogit:from ogit.WorkOrder:User ; ogit:to ogit.WorkOrder:Tenant ]
+ [ ogit:from ogit.WorkOrder:Customer ; ogit:to ogit.WorkOrder:Tenant ]
+ [ ogit:from ogit.WorkOrder:Article ; ogit:to ogit.WorkOrder:Tenant ]
+ [ ogit:from ogit.WorkOrder:Order ; ogit:to ogit.WorkOrder:Tenant ]
+ [ ogit:from ogit.WorkOrder:PasswordEntry ; ogit:to ogit.WorkOrder:Tenant ]
+ [ ogit:from ogit.WorkOrder:TimeSheet ; ogit:to ogit.WorkOrder:Tenant ]
+ ) ;
+.
diff --git a/NTO/WorkOrder/verbs/Drives.ttl b/NTO/WorkOrder/verbs/Drives.ttl
new file mode 100644
index 0000000000..29b2469c5b
--- /dev/null
+++ b/NTO/WorkOrder/verbs/Drives.ttl
@@ -0,0 +1,16 @@
+@prefix ogit: .
+@prefix ogit.WorkOrder: .
+@prefix rdfs: .
+@prefix dcterms: .
+
+ogit.WorkOrder:drives
+ a rdfs:Class;
+ rdfs:subClassOf ogit:Verb;
+ rdfs:label "drives";
+ dcterms:description "User drives (records) a LogbookEntry (Fahrtenbuch trip with km, route, vehicle, business/private split). One User can drive many LogbookEntries (one-to-many)." ;
+ dcterms:source "AdaWorldAPI/WoA/models.py:LogbookEntry.user_id -> User.id" ;
+ ogit:scope "NTO";
+ ogit:from-to (
+ [ ogit:from ogit.WorkOrder:User ; ogit:to ogit.WorkOrder:LogbookEntry ]
+ ) ;
+.
diff --git a/NTO/WorkOrder/verbs/HasActivity.ttl b/NTO/WorkOrder/verbs/HasActivity.ttl
new file mode 100644
index 0000000000..5a228c428e
--- /dev/null
+++ b/NTO/WorkOrder/verbs/HasActivity.ttl
@@ -0,0 +1,16 @@
+@prefix ogit: .
+@prefix ogit.WorkOrder: .
+@prefix rdfs: .
+@prefix dcterms: .
+
+ogit.WorkOrder:hasActivity
+ a rdfs:Class;
+ rdfs:subClassOf ogit:Verb;
+ rdfs:label "hasActivity";
+ dcterms:description "Order has-many Activity records (work performed on devices). One Order can record many Activities (one-to-many, cascade-delete)." ;
+ dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.activities -> Activity.workorder_id" ;
+ ogit:scope "NTO";
+ ogit:from-to (
+ [ ogit:from ogit.WorkOrder:Order ; ogit:to ogit.WorkOrder:Activity ]
+ ) ;
+.
diff --git a/NTO/WorkOrder/verbs/HasHistory.ttl b/NTO/WorkOrder/verbs/HasHistory.ttl
new file mode 100644
index 0000000000..eb6062734d
--- /dev/null
+++ b/NTO/WorkOrder/verbs/HasHistory.ttl
@@ -0,0 +1,16 @@
+@prefix ogit: .
+@prefix ogit.WorkOrder: .
+@prefix rdfs: .
+@prefix dcterms: .
+
+ogit.WorkOrder:hasHistory
+ a rdfs:Class;
+ rdfs:subClassOf ogit:Verb;
+ rdfs:label "hasHistory";
+ dcterms:description "Order has-many HistoryEntry audit records (status transitions, edits). One Order accumulates many HistoryEntries (one-to-many, cascade-delete, ordered by created_at desc)." ;
+ dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.history -> HistoryEntry.workorder_id" ;
+ ogit:scope "NTO";
+ ogit:from-to (
+ [ ogit:from ogit.WorkOrder:Order ; ogit:to ogit.WorkOrder:HistoryEntry ]
+ ) ;
+.
diff --git a/NTO/WorkOrder/verbs/HasPicture.ttl b/NTO/WorkOrder/verbs/HasPicture.ttl
new file mode 100644
index 0000000000..9f0571bec8
--- /dev/null
+++ b/NTO/WorkOrder/verbs/HasPicture.ttl
@@ -0,0 +1,16 @@
+@prefix ogit: .
+@prefix ogit.WorkOrder: .
+@prefix rdfs: .
+@prefix dcterms: .
+
+ogit.WorkOrder:hasPicture
+ a rdfs:Class;
+ rdfs:subClassOf ogit:Verb;
+ rdfs:label "hasPicture";
+ dcterms:description "Order has-many Picture attachments (photos for documentation, logbook, or customer-facing). One Order can attach many Pictures (one-to-many, cascade-delete)." ;
+ dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.pictures -> Picture.workorder_id" ;
+ ogit:scope "NTO";
+ ogit:from-to (
+ [ ogit:from ogit.WorkOrder:Order ; ogit:to ogit.WorkOrder:Picture ]
+ ) ;
+.
diff --git a/NTO/WorkOrder/verbs/HasPosition.ttl b/NTO/WorkOrder/verbs/HasPosition.ttl
new file mode 100644
index 0000000000..3225a0d6f4
--- /dev/null
+++ b/NTO/WorkOrder/verbs/HasPosition.ttl
@@ -0,0 +1,16 @@
+@prefix ogit: .
+@prefix ogit.WorkOrder: .
+@prefix rdfs: .
+@prefix dcterms: .
+
+ogit.WorkOrder:hasPosition
+ a rdfs:Class;
+ rdfs:subClassOf ogit:Verb;
+ rdfs:label "hasPosition";
+ dcterms:description "Order has-many Position line items. One Order can carry many Positions (one-to-many, cascade-delete)." ;
+ dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.positionen -> Position.workorder_id" ;
+ ogit:scope "NTO";
+ ogit:from-to (
+ [ ogit:from ogit.WorkOrder:Order ; ogit:to ogit.WorkOrder:Position ]
+ ) ;
+.
diff --git a/NTO/WorkOrder/verbs/Issued.ttl b/NTO/WorkOrder/verbs/Issued.ttl
new file mode 100644
index 0000000000..0124d9b3e1
--- /dev/null
+++ b/NTO/WorkOrder/verbs/Issued.ttl
@@ -0,0 +1,16 @@
+@prefix ogit: .
+@prefix ogit.WorkOrder: .
+@prefix rdfs: .
+@prefix dcterms: .
+
+ogit.WorkOrder:issued
+ a rdfs:Class;
+ rdfs:subClassOf ogit:Verb;
+ rdfs:label "issued";
+ dcterms:description "Customer issued an Order. One Customer can issue many Orders (one-to-many)." ;
+ dcterms:source "AdaWorldAPI/WoA/models.py:WorkOrder.customer_id -> Customer.id" ;
+ ogit:scope "NTO";
+ ogit:from-to (
+ [ ogit:from ogit.WorkOrder:Customer ; ogit:to ogit.WorkOrder:Order ]
+ ) ;
+.
diff --git a/NTO/WorkOrder/verbs/LogsTime.ttl b/NTO/WorkOrder/verbs/LogsTime.ttl
new file mode 100644
index 0000000000..0ad2cda18a
--- /dev/null
+++ b/NTO/WorkOrder/verbs/LogsTime.ttl
@@ -0,0 +1,16 @@
+@prefix ogit: .
+@prefix ogit.WorkOrder: .
+@prefix rdfs: .
+@prefix dcterms: .
+
+ogit.WorkOrder:logsTime
+ a rdfs:Class;
+ rdfs:subClassOf ogit:Verb;
+ rdfs:label "logsTime";
+ dcterms:description "User logs-time on a TimeSheet entry (billable minutes per Customer/day). One User can log many TimeSheet rows (one-to-many)." ;
+ dcterms:source "AdaWorldAPI/WoA/models.py:TimeSheet.user_id -> User.id" ;
+ ogit:scope "NTO";
+ ogit:from-to (
+ [ ogit:from ogit.WorkOrder:User ; ogit:to ogit.WorkOrder:TimeSheet ]
+ ) ;
+.
diff --git a/NTO/WorkOrder/verbs/OwnsPasswords.ttl b/NTO/WorkOrder/verbs/OwnsPasswords.ttl
new file mode 100644
index 0000000000..01dcf4350c
--- /dev/null
+++ b/NTO/WorkOrder/verbs/OwnsPasswords.ttl
@@ -0,0 +1,16 @@
+@prefix ogit: .
+@prefix ogit.WorkOrder: .
+@prefix rdfs: .
+@prefix dcterms: .
+
+ogit.WorkOrder:ownsPasswords
+ a rdfs:Class;
+ rdfs:subClassOf ogit:Verb;
+ rdfs:label "ownsPasswords";
+ dcterms:description "Customer owns-passwords (Fernet-encrypted vault entries). One Customer can own many PasswordEntry records in their KeePass-style vault (one-to-many)." ;
+ dcterms:source "AdaWorldAPI/WoA/models.py:PasswordEntry.customer_id -> Customer.id" ;
+ ogit:scope "NTO";
+ ogit:from-to (
+ [ ogit:from ogit.WorkOrder:Customer ; ogit:to ogit.WorkOrder:PasswordEntry ]
+ ) ;
+.
diff --git a/NTO/WorkOrder/verbs/RefersToArticle.ttl b/NTO/WorkOrder/verbs/RefersToArticle.ttl
new file mode 100644
index 0000000000..e3b5499f61
--- /dev/null
+++ b/NTO/WorkOrder/verbs/RefersToArticle.ttl
@@ -0,0 +1,16 @@
+@prefix ogit: .
+@prefix ogit.WorkOrder: .
+@prefix rdfs: .
+@prefix dcterms: .
+
+ogit.WorkOrder:refersToArticle
+ a rdfs:Class;
+ rdfs:subClassOf ogit:Verb;
+ rdfs:label "refersToArticle";
+ dcterms:description "Position refers-to an Article in the catalogue (price, description source). Many Positions may reference the same Article (many-to-one, optional)." ;
+ dcterms:source "AdaWorldAPI/WoA/models.py:Position.article_id -> Article.id" ;
+ ogit:scope "NTO";
+ ogit:from-to (
+ [ ogit:from ogit.WorkOrder:Position ; ogit:to ogit.WorkOrder:Article ]
+ ) ;
+.