From df7c234f9d1f2f3a2dd0b7e9ab289cff15e2d586 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 27 May 2026 15:51:41 +0000 Subject: [PATCH] feat(ontology): odoo hydrator + DOLCE classifier (four-way alignment seam) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First concrete increment of the odoo → lance-graph-ontology integration. Adds the Layer-1 odoo OWL hydrator and the Layer-2 alignment seed so woa-rs odoo-extracted business models resolve into the existing financial ontology, per woa-rs/.claude/reference/four_way_alignment_seam.md. - modules/odoo/manifest.yaml + build.rs: new OGIT slot ODOO_V1=(50,1), inherits_from fibofnd, 17 entity types (u16=4300..4316, no collision). - data/ontologies/odoo/odoo-core.ttl: 17 core odoo classes as owl:Class. - data/ontologies/odoo/alignment/{odoo-to-fibo,odoo-to-skr}.ttl: owl:equivalentClass/equivalentProperty axioms (Seam decision 1 / Option B — odoo inherits FIBO/SKR slots, no new CAM family). Includes the account.move dual-nature projection (Open-question 5). - hydrators/odoo.rs: hydrate_odoo + hydrate_odoo_from, inherits_from FIBOFND, edge whitelist {rdfs:subClassOf, owl:equivalentClass, ...}. - hydrators/dolce_odoo.rs: classify_odoo + DolceCategory enum (own module per Open-question 3); suffix heuristics + product.template Endurant special-case + default Endurant. - tests: 21-row seam classifier matrix + hydrate smoke (seed + canonical). cargo test -p lance-graph-ontology: 127 passed / 0 failed. cargo test -p lance-graph-contract: 449 passed / 0 failed. https://claude.ai/code/session_016NwUSxRobQRH26KUJXvEYn --- .claude/board/AGENT_LOG.md | 33 ++++ .claude/board/LATEST_STATE.md | 28 +++ crates/lance-graph-contract/build.rs | 8 + .../src/hydrators/dolce_odoo.rs | 163 ++++++++++++++++++ .../lance-graph-ontology/src/hydrators/mod.rs | 4 + .../src/hydrators/odoo.rs | 162 +++++++++++++++++ crates/lance-graph-ontology/src/lib.rs | 16 +- .../tests/odoo_dolce_classifier.rs | 80 +++++++++ .../tests/odoo_hydrator_smoke.rs | 116 +++++++++++++ .../odoo/alignment/odoo-to-fibo.ttl | 73 ++++++++ .../ontologies/odoo/alignment/odoo-to-skr.ttl | 45 +++++ data/ontologies/odoo/odoo-core.ttl | 98 +++++++++++ modules/odoo/manifest.yaml | 29 ++++ 13 files changed, 847 insertions(+), 8 deletions(-) create mode 100644 crates/lance-graph-ontology/src/hydrators/dolce_odoo.rs create mode 100644 crates/lance-graph-ontology/src/hydrators/odoo.rs create mode 100644 crates/lance-graph-ontology/tests/odoo_dolce_classifier.rs create mode 100644 crates/lance-graph-ontology/tests/odoo_hydrator_smoke.rs create mode 100644 data/ontologies/odoo/alignment/odoo-to-fibo.ttl create mode 100644 data/ontologies/odoo/alignment/odoo-to-skr.ttl create mode 100644 data/ontologies/odoo/odoo-core.ttl create mode 100644 modules/odoo/manifest.yaml diff --git a/.claude/board/AGENT_LOG.md b/.claude/board/AGENT_LOG.md index 4b871026..a3ecb759 100644 --- a/.claude/board/AGENT_LOG.md +++ b/.claude/board/AGENT_LOG.md @@ -426,3 +426,36 @@ W11 [2026-05-14T12:29] test-plan-unification: spec at .claude/specs/sprint-10-te **Process note:** user explicitly called out my prior context-reset framing — corrected via Explore agent research before writing. All 8 docs grounded in shipped source (file:line refs throughout) or referenced plan documents. --- + +--- + +## [odoo-seam-bO] [IN PR] D-ODOO-1 odoo hydrator + DOLCE classifier (branch claude/lance-graph-att-activate-Jd2iZ) + +**D-id:** D-ODOO-1 — first concrete increment of the odoo → lance-graph-ontology integration (four-way alignment seam, Layer 1 + Layer 2 seed). Adds the odoo OWL hydrator, the odoo DOLCE suffix classifier (Seam decision 2, own module per Open-question 3), seed + alignment TTLs, and an `ODOO_V1` OGIT slot. Honors Seam decision 1 / Option B: odoo gets NO new CAM family — it inherits FIBO/SKR slots via `owl:equivalentClass` alignment axioms. + +**Worker:** general-purpose agent (Opus). Spec: `woa-rs/.claude/reference/four_way_alignment_seam.md`. + +**OGIT-slot decision: (a) — manifest YAML.** Added `modules/odoo/manifest.yaml` (`ogit_g: ODOO`, `inherits_from: fibofnd`, 17 entity_types at u16=4300..4316, no collision — highest prior code was 4204) and registered `("ODOO", 50)` in `crates/lance-graph-contract/build.rs` CANONICAL_SLOTS. Verified: `cargo build -p lance-graph-contract` regenerates `OUT_DIR/ogit_namespace.rs` with `pub const ODOO_V1: (u32, u32) = (50, 1);`. Slot 50 is fresh (prior slots: 0-6, 10-14, 20-21, 30-31, 40-42). + +**Files added:** +- `data/ontologies/odoo/odoo-core.ttl` — 17 core classes as owl:Class + rdfs:label + rdfs:subClassOf (res.partner{.Company,.Individual}, account.{move,move.line,account,tax,journal}, product.{product,template,category}, stock.{move,picking}, mail.{message,template}, hr.{employee,attendance}). Namespace `odoo: `. +- `data/ontologies/odoo/alignment/odoo-to-fibo.ttl` — owl:equivalentClass/equivalentProperty per seam worked example (res.partner.Company→fibo:LegalEntity, res.partner.Individual→vcard:Individual, account.move→fibo:FinancialTransaction + account.move.Invoice→ubl:Invoice dual-nature per Open-question 5, account.account→fibo:Account, product.template→schema:Product; name→foaf:name, vat→fibo:hasTaxIdentifier). +- `data/ontologies/odoo/alignment/odoo-to-skr.ttl` — odoo accounting → SKR03/SKR04 chart pivots (account.account→skr:Konto, account.tax→skr:Steuersatz, account.journal→skr:Journal, code→kontonummer). +- `crates/lance-graph-ontology/src/hydrators/odoo.rs` — `hydrate_odoo(registry)` (canonical seed + alignment overlays) + `hydrate_odoo_from(paths, registry)` (test/multi-file). `g: OGIT::ODOO_V1.0`, `inherits_from: Some(OGIT::FIBOFND_V1.0)`, edge whitelist {rdfs:subClassOf, owl:equivalentClass, rdfs:subPropertyOf, owl:equivalentProperty}. Doc-commented as Layer-1 odoo extraction source. +- `crates/lance-graph-ontology/src/hydrators/dolce_odoo.rs` — `pub fn classify_odoo(iri: &str) -> DolceCategory` + `pub enum DolceCategory { Endurant, Perdurant, Quality, AbstractEntity }` (doc-noted: canonical DUL renames Endurant→Object / Perdurant→Event). Suffix heuristics + product.template Endurant special-case + default Endurant per seam §"Seam decision 2". +- `crates/lance-graph-ontology/tests/odoo_hydrator_smoke.rs` — 3 tests (seed hydrate Ok + non-zero count + L1 invariants; edge whitelist; canonical-paths incl. alignment TTL parse-validation via fibo:LegalEntity interning). +- `crates/lance-graph-ontology/tests/odoo_dolce_classifier.rs` — 4 tests incl. the full 21-row seam matrix. + +**Files modified:** +- `crates/lance-graph-ontology/src/hydrators/mod.rs` — `pub mod odoo; pub mod dolce_odoo;` + re-exports. +- `crates/lance-graph-ontology/src/lib.rs` — re-export `classify_odoo, DolceCategory, hydrate_odoo, hydrate_odoo_from`. + +**Tests:** `cargo test -p lance-graph-ontology` → 127 passed / 0 failed (all binaries; +7 new odoo tests, +4 new lib unit tests). `cargo test -p lance-graph-contract` → 449 passed / 0 failed (build.rs change verified). + +**Bug caught + fixed during impl:** the seam's reference classifier snippet only lists `.move` in PERDURANT_SUFFIXES, but `account.move.line` ends with `.line` → fell through to default Endurant, contradicting the seam matrix row (`account.move.line → Perdurant`). Added explicit `.move.line` suffix (a line is a fact within the move event). Matches lance-graph-callcenter::odoo_alignment::dolce_odoo's handling. + +**Note — prior art:** `lance-graph-callcenter::odoo_alignment` already ships a parallel `dolce_odoo()` + `DolceMarker` + `ODOO_SEED` static table (Option B family bytes). This D-ODOO-1 work is the lance-graph-ONTOLOGY side (TTL hydration into the OntologyRegistry, separate crate, distinct `DolceCategory` enum per the task spec). The two are consistent (same pivots, same Option-B doctrine) but not yet unified; cross-crate dedup is a possible follow-up. + +**Outcome:** D-ODOO-1 ready for review. Workspace compiles; both touched crates green. NOT pushed (orchestrator reviews + pushes). + +--- diff --git a/.claude/board/LATEST_STATE.md b/.claude/board/LATEST_STATE.md index ce49fbc5..4c5009b7 100644 --- a/.claude/board/LATEST_STATE.md +++ b/.claude/board/LATEST_STATE.md @@ -407,3 +407,31 @@ Sprint-2 W7 → ndarray; sprint-3 W9 → ada-consciousness. Both corrected via m - `.claude/board/sprint-log-3/{SPRINT_LOG.md,agents/agent-W1..W12.md,meta-1-review.md,sprint-summary.md}` PR sequence: #360 → #361 → post-#360 substrate-sweep (this PR). + +--- + +## APPEND-ONLY annotation — D-ODOO-1 odoo hydrator (2026-05-27) + +> Per the APPEND-ONLY governance rule, this section augments — does not edit — prior content. Treat as the new top-of-state. Branch: `claude/lance-graph-att-activate-Jd2iZ`. + +### Current Contract Inventory — new entry + +- **`OGIT::ODOO_V1` = (50, 1)** — new OGIT G slot (first manifest-declared slot above SKR03BAU=42). Source: `modules/odoo/manifest.yaml` (`ogit_g: ODOO`, `inherits_from: fibofnd`, 17 entity_types u16=4300..4316). Registered in `crates/lance-graph-contract/build.rs` CANONICAL_SLOTS as `("ODOO", 50)`; build regenerates `OUT_DIR/ogit_namespace.rs` accordingly. + +### New module surface (`lance-graph-ontology`) + +- **`hydrators::odoo`** — Layer-1 odoo extraction hydrator (four-way alignment seam). `hydrate_odoo(registry)` + `hydrate_odoo_from(paths, registry)`; `inherits_from: Some(OGIT::FIBOFND_V1.0)`; edge whitelist {rdfs:subClassOf, owl:equivalentClass, rdfs:subPropertyOf, owl:equivalentProperty}. Re-exported from `lib.rs`. +- **`hydrators::dolce_odoo`** — odoo DOLCE suffix classifier (Seam decision 2, own module per Open-question 3). `pub fn classify_odoo(iri: &str) -> DolceCategory` + `pub enum DolceCategory { Endurant, Perdurant, Quality, AbstractEntity }`. Re-exported from `lib.rs`. (Doc-noted: canonical DUL renames Endurant→Object / Perdurant→Event.) + +### New data artifacts + +- `data/ontologies/odoo/odoo-core.ttl` — 17 odoo core classes (`odoo: `). +- `data/ontologies/odoo/alignment/odoo-to-fibo.ttl` + `odoo-to-skr.ttl` — Layer-2 `owl:equivalentClass`/`owl:equivalentProperty` alignment axioms (Seam decision 1 / Option B: odoo inherits existing FIBO/SKR slots, no new CAM family). + +### Tests + +`cargo test -p lance-graph-ontology` → 127 passed / 0 failed (+7 odoo integration tests across `tests/odoo_hydrator_smoke.rs` + `tests/odoo_dolce_classifier.rs`, incl. the full 21-row seam classifier matrix; +4 lib unit tests). `cargo test -p lance-graph-contract` → 449 passed / 0 failed. + +### Relationship to prior art + +`lance-graph-callcenter::odoo_alignment` already ships a parallel `dolce_odoo()` + `DolceMarker` + `ODOO_SEED` table. This is the ontology-side counterpart (TTL hydration into `OntologyRegistry`); consistent doctrine (Option B, same pivots), distinct crate + distinct `DolceCategory` enum per task spec. Cross-crate dedup is a possible follow-up, not done here. diff --git a/crates/lance-graph-contract/build.rs b/crates/lance-graph-contract/build.rs index efed2481..b0c891a7 100644 --- a/crates/lance-graph-contract/build.rs +++ b/crates/lance-graph-contract/build.rs @@ -61,6 +61,14 @@ const CANONICAL_SLOTS: &[(&str, u32)] = &[ // slot rather than the canonical SKR03_V1 slot so mixed consumers // can hold both account sets in one OntologyRegistry. ("SKR03BAU", 42), + // L1 odoo extraction source (four-way alignment seam). Odoo-extracted + // business models (res.partner, account.move, product.template, …) as + // OWL classes interned via OwlHydrator. Declares `inherits_from: fibofnd` + // and reaches the financial ontology via the `owl:equivalentClass` + // alignment axioms in data/ontologies/odoo/alignment/ (Seam decision 1 / + // Option B: odoo inherits existing FIBO/SKR slots, it does NOT get its + // own CAM codebook family). + ("ODOO", 50), ]; fn canonical_slot(token: &str) -> Option { diff --git a/crates/lance-graph-ontology/src/hydrators/dolce_odoo.rs b/crates/lance-graph-ontology/src/hydrators/dolce_odoo.rs new file mode 100644 index 00000000..05404241 --- /dev/null +++ b/crates/lance-graph-ontology/src/hydrators/dolce_odoo.rs @@ -0,0 +1,163 @@ +//! Odoo DOLCE suffix classifier — Seam decision 2, in its own module. +//! +//! Per Open-question 3 of the four-way alignment seam +//! (`woa-rs/.claude/reference/four_way_alignment_seam.md`), the odoo-specific +//! DOLCE heuristics live in a separate module rather than inline in +//! `dolce.rs`, so each extraction source (odoo, FMA, SNOMED, future ones) owns +//! its own per-source heuristic logic. +//! +//! The odoo namespace uses dotted lowercase model names (`account.move`, +//! `stock.move`, `hr.attendance`) where event semantics are encoded by +//! *suffix* (`.move`, `.message`, `.attendance`). [`classify_odoo`] maps a +//! model name onto its DOLCE upper category from those suffixes, with one +//! explicit special-case (`product.template` — odoo's "template" there means +//! the master product record, an Endurant, not a config template). +//! +//! Litmus (CLAUDE.md): this is a stateless pure function with no carrier — it +//! reads a `&str` and returns a category. That is the sanctioned shape for a +//! classifier; there is no odoo-class struct to hang it on. + +/// DOLCE upper categories used by the odoo suffix classifier. +/// +/// These are the four DOLCE-Lite-Plus top categories. **Canonical DOLCE+DUL +/// renames `Endurant` → `Object` and `Perdurant` → `Event`** per the DUL +/// ontology header (see `dolce.rs` module docs); this enum keeps the original +/// DOLCE-Lite-Plus names because that is the vocabulary the seam doc's test +/// matrix and the `lance-graph-callcenter::super_domain::DolceMarker` seed use. +/// +/// Unlike `DolceMarker` in `lance-graph-callcenter`, there is no `Unknown` +/// variant: [`classify_odoo`] always returns a concrete category (defaulting +/// to [`DolceCategory::Endurant`] for persistent stateful objects), matching +/// the seam's "Default: Endurant" rule. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum DolceCategory { + /// Persistent stateful object (DUL: `Object`). The default. + Endurant, + /// Event / occurrence that unfolds in time (DUL: `Event`). + Perdurant, + /// An attribute / rate / classification that characterises something. + Quality, + /// A reference / configuration / template — an abstract entity. + AbstractEntity, +} + +/// Name suffixes indicating a Perdurant (event / occurrence). +/// +/// `.move.line` precedes `.move` in spirit — a line within a move event is a +/// fact within that Perdurant (seam matrix: `account.move.line → Perdurant`). +/// It is listed explicitly because it does not end with `.move`. Order within +/// this list does not matter (any match returns Perdurant), but the comment +/// records why both are present. +const PERDURANT_SUFFIXES: &[&str] = &[ + ".move.line", // account.move.line — fact within the move event + ".move", // account.move, stock.move, hr.leave.allocation.move + ".message", // mail.message + ".activity", // mail.activity + ".attendance", // hr.attendance + ".transition", // workflow transitions + ".event", // calendar.event + ".log", // any .log model + ".history", // change history + ".transaction", // payment.transaction + ".picking", // stock.picking (logistic event) + ".scrap", // stock.scrap (logistic event) +]; + +/// Name suffixes indicating a Quality (attribute / classification / rate). +const QUALITY_SUFFIXES: &[&str] = &[ + ".tag", // crm.tag, account.account.tag + ".category", // product.category, res.partner.category + ".type", // account.account.type, sale.order.type + ".group", // res.groups, account.tax.group + ".tax", // account.tax (it's a rate, a quality, not an event) +]; + +/// Name suffixes indicating an AbstractEntity (reference / config / template). +const ABSTRACT_SUFFIXES: &[&str] = &[ + ".template", // mail.template, account.chart.template + ".config", // *.config.settings + ".policy", // any *.policy + ".rule", // account.reconcile.model rules + ".formula", // hr.payroll.structure.line formulas +]; + +/// Classify an odoo model IRI / name onto its DOLCE upper category. +/// +/// Accepts either a bare model name (`"res.partner"`) or a prefixed IRI +/// (`"odoo:res.partner"` / `"https://ada.world/onto/odoo#res.partner"`); the +/// prefix is stripped to the model name before matching. +/// +/// Resolution order (first match wins): +/// 1. `product.template` special-case → [`DolceCategory::Endurant`] (odoo uses +/// "template" here for the master product record, not a config template). +/// 2. Perdurant suffix → [`DolceCategory::Perdurant`]. +/// 3. Quality suffix → [`DolceCategory::Quality`]. +/// 4. Abstract suffix → [`DolceCategory::AbstractEntity`]. +/// 5. Default → [`DolceCategory::Endurant`] (persistent stateful object). +pub fn classify_odoo(iri: &str) -> DolceCategory { + let model = model_name(iri); + + // (1) The single special-case: product.template is a master record + // (Endurant), NOT an abstract config template — even though `.template` + // is an Abstract suffix below. Must be checked before the suffix lists. + if model == "product.template" { + return DolceCategory::Endurant; + } + + // (2) Perdurant — event / occurrence by suffix. + for suffix in PERDURANT_SUFFIXES { + if model.ends_with(suffix) { + return DolceCategory::Perdurant; + } + } + // (3) Quality — attribute / classification / rate by suffix. + for suffix in QUALITY_SUFFIXES { + if model.ends_with(suffix) { + return DolceCategory::Quality; + } + } + // (4) AbstractEntity — reference / config / template by suffix. + for suffix in ABSTRACT_SUFFIXES { + if model.ends_with(suffix) { + return DolceCategory::AbstractEntity; + } + } + + // (5) Default: Endurant (res.partner, res.users, res.company, + // product.product, account.account, account.journal, stock.warehouse, + // crm.lead, hr.employee, …). + DolceCategory::Endurant +} + +/// Strip a leading `odoo:` prefix or the full odoo namespace IRI, returning the +/// bare odoo model name. +fn model_name(iri: &str) -> &str { + if let Some(rest) = iri.strip_prefix("https://ada.world/onto/odoo#") { + return rest; + } + iri.trim_start_matches("odoo:") +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn strips_iri_prefixes() { + assert_eq!(model_name("odoo:res.partner"), "res.partner"); + assert_eq!( + model_name("https://ada.world/onto/odoo#account.move"), + "account.move" + ); + assert_eq!(model_name("res.partner"), "res.partner"); + } + + #[test] + fn classifier_handles_prefixed_iris() { + assert_eq!(classify_odoo("odoo:account.move"), DolceCategory::Perdurant); + assert_eq!( + classify_odoo("https://ada.world/onto/odoo#res.partner"), + DolceCategory::Endurant + ); + } +} diff --git a/crates/lance-graph-ontology/src/hydrators/mod.rs b/crates/lance-graph-ontology/src/hydrators/mod.rs index 4b465684..8d82b338 100644 --- a/crates/lance-graph-ontology/src/hydrators/mod.rs +++ b/crates/lance-graph-ontology/src/hydrators/mod.rs @@ -19,7 +19,9 @@ //! glue + one TTL artifact. pub mod dolce; +pub mod dolce_odoo; pub mod fibo; +pub mod odoo; pub mod owl; pub mod owltime; pub mod provo; @@ -33,7 +35,9 @@ pub mod xsd; pub mod zugferd; pub use dolce::{hydrate_dolce, hydrate_dolce_from, hydrate_dolce_from_many}; +pub use dolce_odoo::{classify_odoo, DolceCategory}; pub use fibo::{hydrate_fibo_be, hydrate_fibo_be_from, hydrate_fibo_fnd, hydrate_fibo_fnd_from}; +pub use odoo::{hydrate_odoo, hydrate_odoo_from}; pub use owl::{ContextBundle, EntityId, HydrateErr, MetaStructureHydrator, OntologySlot, OwlHydrator}; pub use owltime::{hydrate_owltime, hydrate_owltime_from}; pub use provo::{hydrate_provo, hydrate_provo_from}; diff --git a/crates/lance-graph-ontology/src/hydrators/odoo.rs b/crates/lance-graph-ontology/src/hydrators/odoo.rs new file mode 100644 index 00000000..f0833e35 --- /dev/null +++ b/crates/lance-graph-ontology/src/hydrators/odoo.rs @@ -0,0 +1,162 @@ +//! Odoo business-model hydration glue — Layer 1 of the four-way alignment seam. +//! +//! Per the seam spec (`woa-rs/.claude/reference/four_way_alignment_seam.md`), +//! odoo is an *extraction source*, not a domain. The Rust odoo extractor parses +//! `odoo/addons//models/*.py` ASTs and emits TTL in the `odoo:` +//! namespace (`https://ada.world/onto/odoo#`); this hydrator interns those +//! classes into a [`super::owl::ContextBundle`] keyed by `OGIT::ODOO_V1`. +//! +//! ## Where odoo lands in the L1-L4 stack +//! +//! This hydrator declares `inherits_from: Some(OGIT::FIBOFND_V1.0)` — odoo +//! reaches the financial ontology through FIBO Foundations. It does **NOT** +//! get its own CAM codebook family (Seam decision 1 / Option B): every odoo +//! class with an alignment axiom is `owl:equivalentClass`-routed into an +//! existing FIBO/SKR slot, so odoo content lands in the right domain +//! compartment by virtue of its alignment, and the type lattice stays unified. +//! The alignment axioms live in `data/ontologies/odoo/alignment/` +//! (`odoo-to-fibo.ttl`, `odoo-to-skr.ttl`) and are hydrated into the same +//! bundle so the cascade sees one transitive odoo surface. +//! +//! ## Edge whitelist +//! +//! The cascade follows `rdfs:subClassOf` (odoo's own facet subsumption, e.g. +//! `odoo:res.partner.Company ⊑ odoo:res.partner`) and `owl:equivalentClass` +//! (the Layer-2 alignment pivots that carry CAM-codebook resolution). Those +//! two are load-bearing and MUST be present; `rdfs:subPropertyOf` / +//! `owl:equivalentProperty` cover the field-level alignments. +//! +//! ## DOLCE category +//! +//! The DOLCE upper-category marker for an odoo class is NOT stored here — it is +//! computed by the suffix classifier in [`super::dolce_odoo::classify_odoo`] +//! (Seam decision 2, kept in its own module per Open-question 3). + +use std::path::{Path, PathBuf}; + +use lance_graph_contract::manifest::OGIT; + +use super::owl::{HydrateErr, OwlHydrator}; +use crate::registry::OntologyRegistry; + +/// Core odoo class seed, relative to the workspace root. +const ODOO_CORE_RELATIVE_PATH: &str = "data/ontologies/odoo/odoo-core.ttl"; + +/// Layer-2 alignment overlays hydrated into the same `OGIT::ODOO_V1` bundle so +/// the cascade sees odoo classes and their `owl:equivalentClass` pivots as one +/// transitive surface. Missing files are skipped (the seed alone still +/// hydrates), mirroring `dolce.rs`'s extension-module handling. +const ODOO_ALIGNMENT_RELATIVE_PATHS: &[&str] = &[ + "data/ontologies/odoo/alignment/odoo-to-fibo.ttl", + "data/ontologies/odoo/alignment/odoo-to-skr.ttl", +]; + +/// Cascade edge-IRI whitelist for the odoo surface. +/// +/// `rdfs:subClassOf` carries odoo's own facet subsumption; `owl:equivalentClass` +/// carries the Layer-2 alignment pivots that resolve CAM-codebook placement +/// through the FIBO/SKR slot the odoo class is equivalent to. The property +/// variants cover the field-level alignments (`odoo:res.partner.name` +/// `owl:equivalentProperty` `foaf:name`, etc.). +const ODOO_EDGE_WHITELIST: &[&str] = &[ + // odoo facet subsumption (load-bearing — REQUIRED) + "http://www.w3.org/2000/01/rdf-schema#subClassOf", + // Layer-2 alignment pivots (load-bearing — REQUIRED) + "http://www.w3.org/2002/07/owl#equivalentClass", + // Field-level alignments + "http://www.w3.org/2000/01/rdf-schema#subPropertyOf", + "http://www.w3.org/2002/07/owl#equivalentProperty", +]; + +/// Hydrate the odoo business-model surface as `OGIT::ODOO_V1` (Layer 1). +/// +/// Registers a [`super::owl::ContextBundle`] at `OGIT::ODOO_V1.0` with +/// `inherits_from: Some(OGIT::FIBOFND_V1.0)`. Hydrates the canonical +/// `data/ontologies/odoo/odoo-core.ttl` plus the available alignment overlays +/// under `data/ontologies/odoo/alignment/` into one bundle, then registers the +/// cascade edge whitelist. +pub fn hydrate_odoo(registry: &OntologyRegistry) -> Result { + let core = odoo_core_path(); + let alignments: Vec = + ODOO_ALIGNMENT_RELATIVE_PATHS.iter().map(alignment_path).collect(); + let mut paths: Vec<&Path> = Vec::with_capacity(1 + alignments.len()); + paths.push(&core); + for a in &alignments { + if a.exists() { + paths.push(a.as_path()); + } + } + hydrate_odoo_from(&paths, registry) +} + +/// Hydrate odoo from explicit paths (test-friendly + multi-file). +/// +/// Interns named IRIs across every file in `paths` into a single +/// [`super::owl::ContextBundle`] keyed by `OGIT::ODOO_V1.0`, then registers the +/// cascade edge whitelist. Used by [`hydrate_odoo`] to merge the core seed with +/// the alignment overlays, and by tests that compose a custom bundle. +pub fn hydrate_odoo_from( + paths: &[&Path], + registry: &OntologyRegistry, +) -> Result { + let hydrator = OwlHydrator { + g: OGIT::ODOO_V1.0, + version: OGIT::ODOO_V1.1, + domain_name: "odoo".to_string(), + inherits_from: Some(OGIT::FIBOFND_V1.0), + starting_entity_id: 100, + }; + hydrator.hydrate_many(paths, registry)?; + registry + .register_edge_types(OGIT::ODOO_V1.0, ODOO_EDGE_WHITELIST) + .map_err(|reason| HydrateErr::Registry { + g: OGIT::ODOO_V1.0, + reason, + })?; + Ok(OGIT::ODOO_V1.0) +} + +fn odoo_core_path() -> PathBuf { + // `CARGO_MANIFEST_DIR` for this crate is `crates/lance-graph-ontology`; + // the data file lives at `/data/ontologies/odoo/odoo-core.ttl`. + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("..") + .join("..") + .join(ODOO_CORE_RELATIVE_PATH) +} + +fn alignment_path(rel: &&str) -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("..") + .join("..") + .join(rel) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn odoo_core_path_resolves_to_workspace_data() { + let p = odoo_core_path(); + assert!( + p.ends_with("data/ontologies/odoo/odoo-core.ttl"), + "unexpected path tail: {}", + p.display() + ); + } + + #[test] + fn odoo_edge_whitelist_has_the_two_load_bearing_iris() { + assert!( + ODOO_EDGE_WHITELIST + .contains(&"http://www.w3.org/2000/01/rdf-schema#subClassOf"), + "rdfs:subClassOf is load-bearing" + ); + assert!( + ODOO_EDGE_WHITELIST + .contains(&"http://www.w3.org/2002/07/owl#equivalentClass"), + "owl:equivalentClass is load-bearing" + ); + } +} diff --git a/crates/lance-graph-ontology/src/lib.rs b/crates/lance-graph-ontology/src/lib.rs index 3061e5c1..2fd3983d 100644 --- a/crates/lance-graph-ontology/src/lib.rs +++ b/crates/lance-graph-ontology/src/lib.rs @@ -54,14 +54,14 @@ pub mod lance_cache; pub use bridge::{BridgeError, NamespaceBridge}; pub use error::Error; pub use hydrators::{ - collect_xsd_files, hydrate_dolce, hydrate_dolce_from, hydrate_dolce_from_many, - hydrate_fibo_be, hydrate_fibo_be_from, hydrate_fibo_fnd, hydrate_fibo_fnd_from, - hydrate_owltime, hydrate_owltime_from, hydrate_provo, hydrate_provo_from, hydrate_qudt, - hydrate_qudt_from, hydrate_schemaorg, hydrate_schemaorg_from, hydrate_skos, hydrate_skos_from, - hydrate_skr03, hydrate_skr03_bau, hydrate_skr03_bau_from, hydrate_skr03_from, hydrate_skr04, - hydrate_skr04_from, hydrate_zugferd, hydrate_zugferd_from, hydrate_zugferd_rules, - hydrate_zugferd_rules_from, ContextBundle, EntityId, HydrateErr, MetaStructureHydrator, - OntologySlot, OwlHydrator, SchematronHydrator, SkrHydrator, XsdHydrator, + classify_odoo, collect_xsd_files, hydrate_dolce, hydrate_dolce_from, hydrate_dolce_from_many, + hydrate_fibo_be, hydrate_fibo_be_from, hydrate_fibo_fnd, hydrate_fibo_fnd_from, hydrate_odoo, + hydrate_odoo_from, hydrate_owltime, hydrate_owltime_from, hydrate_provo, hydrate_provo_from, + hydrate_qudt, hydrate_qudt_from, hydrate_schemaorg, hydrate_schemaorg_from, hydrate_skos, + hydrate_skos_from, hydrate_skr03, hydrate_skr03_bau, hydrate_skr03_bau_from, hydrate_skr03_from, + hydrate_skr04, hydrate_skr04_from, hydrate_zugferd, hydrate_zugferd_from, hydrate_zugferd_rules, + hydrate_zugferd_rules_from, ContextBundle, DolceCategory, EntityId, HydrateErr, + MetaStructureHydrator, OntologySlot, OwlHydrator, SchematronHydrator, SkrHydrator, XsdHydrator, SKR03_BAU_IRI_PREFIX, SKR03_IRI_PREFIX, SKR04_IRI_PREFIX, }; pub use namespace::{NamespaceId, OgitUri, SchemaPtr}; diff --git a/crates/lance-graph-ontology/tests/odoo_dolce_classifier.rs b/crates/lance-graph-ontology/tests/odoo_dolce_classifier.rs new file mode 100644 index 00000000..32a033ed --- /dev/null +++ b/crates/lance-graph-ontology/tests/odoo_dolce_classifier.rs @@ -0,0 +1,80 @@ +//! Seam decision 2 — the full odoo DOLCE suffix-classifier test matrix. +//! +//! Pins every row of the matrix in +//! `woa-rs/.claude/reference/four_way_alignment_seam.md` §"Seam decision 2" +//! plus the prefixed-IRI accessor. The `product.template` special-case +//! (Endurant despite the `.template` Abstract suffix) is the single override +//! the seam calls out. + +use lance_graph_ontology::{classify_odoo, DolceCategory}; + +#[test] +fn classify_odoo_matrix_matches_seam_table() { + use DolceCategory::*; + + // (odoo model, expected category) — verbatim from the seam doc's 21-row + // table, in the same order. + let matrix: &[(&str, DolceCategory)] = &[ + ("res.partner", Endurant), // persistent stateful object + ("res.users", Endurant), // persistent stateful object + ("res.company", Endurant), // persistent stateful object + ("account.move", Perdurant), // .move — the journal-entry event + ("account.move.line", Perdurant), // child of Perdurant; fact within event + ("account.account", Endurant), // chart-of-accounts entry persists + ("account.tax", Quality), // .tax — a rate, characterises a txn + ("account.journal", Endurant), // persistent container + ("product.product", Endurant), // the product is the stateful thing + ("product.template", Endurant), // SPECIAL-CASE: master record, not config + ("product.category", Quality), // classification + ("stock.move", Perdurant), // .move — the stock event + ("stock.picking", Perdurant), // .picking — the logistic event + ("stock.warehouse", Endurant), // the physical warehouse + ("mail.message", Perdurant), // .message — communication event + ("crm.lead", Endurant), // the lead persists + ("crm.tag", Quality), // classification + ("hr.employee", Endurant), // persistent person + ("hr.attendance", Perdurant), // .attendance — an attendance event + ("mail.template", AbstractEntity), // .template — true template/config + ("account.chart.template", AbstractEntity), // .template — chart template + ]; + + for (model, expected) in matrix { + assert_eq!( + classify_odoo(model), + *expected, + "classify_odoo({model:?}) should be {expected:?}" + ); + } + + // Sanity: the matrix is the full ~20-row seam table. + assert_eq!(matrix.len(), 21, "the seam matrix has 21 rows"); +} + +#[test] +fn product_template_special_case_overrides_template_suffix() { + // product.template ends with `.template` (an Abstract suffix) but odoo uses + // it for the master product record — the seam's single special-case. + assert_eq!(classify_odoo("product.template"), DolceCategory::Endurant); + // ...while other `.template` models stay Abstract. + assert_eq!(classify_odoo("mail.template"), DolceCategory::AbstractEntity); + assert_eq!( + classify_odoo("account.chart.template"), + DolceCategory::AbstractEntity + ); +} + +#[test] +fn unknown_model_defaults_to_endurant() { + // The seam's "Default: Endurant" rule — no Unknown variant. + assert_eq!(classify_odoo("ir.cron"), DolceCategory::Endurant); + assert_eq!(classify_odoo("res.country"), DolceCategory::Endurant); +} + +#[test] +fn accepts_prefixed_iris() { + assert_eq!(classify_odoo("odoo:account.move"), DolceCategory::Perdurant); + assert_eq!( + classify_odoo("https://ada.world/onto/odoo#product.category"), + DolceCategory::Quality + ); +} diff --git a/crates/lance-graph-ontology/tests/odoo_hydrator_smoke.rs b/crates/lance-graph-ontology/tests/odoo_hydrator_smoke.rs new file mode 100644 index 00000000..35ee7c00 --- /dev/null +++ b/crates/lance-graph-ontology/tests/odoo_hydrator_smoke.rs @@ -0,0 +1,116 @@ +// Skip under Miri — hydration reads the seed TTL via std::fs, and Miri +// isolation blocks the syscalls. +#![cfg(not(miri))] + +//! Four-way-seam odoo Layer-1 smoke test: hydrate the odoo core seed and assert +//! the bundle invariants. +//! +//! - bundle exists at `OGIT::ODOO_V1.0` +//! - `inherits_from == Some(OGIT::FIBOFND_V1.0)` (odoo reaches the financial +//! ontology through FIBO Foundations — Seam decision 1 / Option B) +//! - `domain_name == "odoo"` +//! - `entity_count > 0` over the seed TTL +//! - the cascade whitelist carries the two load-bearing predicates +//! (`rdfs:subClassOf` + `owl:equivalentClass`) + +use std::path::Path; + +use lance_graph_contract::manifest::OGIT; +use lance_graph_ontology::{hydrate_odoo, hydrate_odoo_from, OntologyRegistry}; + +fn odoo_core_ttl() -> std::path::PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("..") + .join("..") + .join("data/ontologies/odoo/odoo-core.ttl") +} + +#[test] +fn odoo_hydrator_smoke_from_seed() { + let registry = OntologyRegistry::new_in_memory(); + let core = odoo_core_ttl(); + let g = hydrate_odoo_from(&[core.as_path()], ®istry).expect("odoo seed hydrates"); + assert_eq!(g, OGIT::ODOO_V1.0, "G slot must be ODOO_V1"); + + let bundle = registry + .bundle_for(OGIT::ODOO_V1.0) + .expect("ContextBundle registered at ODOO_V1"); + + assert_eq!(bundle.g, OGIT::ODOO_V1.0); + assert_eq!(bundle.version, OGIT::ODOO_V1.1); + assert_eq!(bundle.domain_name, "odoo"); + assert_eq!( + bundle.inherits_from, + Some(OGIT::FIBOFND_V1.0), + "odoo inherits from FIBO Foundations (Option B)" + ); + + let entity_count = bundle.entity_count(); + assert!( + entity_count > 0, + "expected a non-zero entity count from the odoo seed, got {entity_count}" + ); + + // A couple of seed classes must be resolvable to interned u32 ids. + assert!( + bundle + .resolve_iri("https://ada.world/onto/odoo#res.partner") + .is_some(), + "odoo:res.partner must be interned" + ); + assert!( + bundle + .resolve_iri("https://ada.world/onto/odoo#account.move") + .is_some(), + "odoo:account.move must be interned" + ); +} + +#[test] +fn odoo_edge_whitelist_registered() { + let registry = OntologyRegistry::new_in_memory(); + let core = odoo_core_ttl(); + hydrate_odoo_from(&[core.as_path()], ®istry).expect("odoo seed hydrates"); + + let edges = registry + .edge_types_for(OGIT::ODOO_V1.0) + .expect("edge whitelist registered for ODOO_V1"); + assert!( + edges + .iter() + .any(|e| e == "http://www.w3.org/2000/01/rdf-schema#subClassOf"), + "rdfs:subClassOf must be whitelisted" + ); + assert!( + edges + .iter() + .any(|e| e == "http://www.w3.org/2002/07/owl#equivalentClass"), + "owl:equivalentClass must be whitelisted" + ); +} + +#[test] +fn odoo_hydrator_smoke_canonical_paths() { + // The canonical entry point reads the seed + the alignment overlays from + // the workspace data dir. Asserts the production path is wired correctly. + let registry = OntologyRegistry::new_in_memory(); + let g = hydrate_odoo(®istry).expect("odoo hydrates from canonical paths"); + assert_eq!(g, OGIT::ODOO_V1.0); + + let bundle = registry.bundle_for(OGIT::ODOO_V1.0).expect("bundle registered"); + // Seed alone is ~20 classes; with the FIBO + SKR alignment overlays the + // interned count is strictly larger (pivot IRIs get interned too). + assert!( + bundle.entity_count() > 0, + "canonical hydration must intern a non-zero entity count" + ); + // The FIBO pivot from the alignment overlay must be interned. + assert!( + bundle + .resolve_iri( + "https://spec.edmcouncil.org/fibo/ontology/BE/LegalEntities/LegalPersons/LegalEntity" + ) + .is_some(), + "the fibo:LegalEntity alignment pivot must be interned from odoo-to-fibo.ttl" + ); +} diff --git a/data/ontologies/odoo/alignment/odoo-to-fibo.ttl b/data/ontologies/odoo/alignment/odoo-to-fibo.ttl new file mode 100644 index 00000000..e03e683c --- /dev/null +++ b/data/ontologies/odoo/alignment/odoo-to-fibo.ttl @@ -0,0 +1,73 @@ +# Odoo → FIBO/schema.org/vcard alignment — Layer-2 of the four-way seam. +# +# Hand-curated `owl:equivalentClass` / `owl:equivalentProperty` axioms per the +# seam worked example (woa-rs/.claude/reference/four_way_alignment_seam.md +# §"Worked example — res.partner end-to-end", Step 2). These axioms are the +# ONLY odoo-specific encoding the seam introduces (Seam addendum): they declare +# which FIBO/OWL pivot each odoo class resolves to. CAM-codebook placement then +# follows by inheritance through the pivot (Seam decision 1 / Option B — +# odoo authors no CAM family). +# +# Consistency: the pivots restated here match the static seed in +# lance-graph-callcenter::odoo_alignment (res.partner→fibo:LegalEntity, +# account.move→fibo:Transaction, account.move.line→fibo:JournalEntryLine, +# account.account→fibo:Account, product.template→schema:Product). + +@prefix odoo: . +@prefix owl: . +@prefix rdfs: . +@prefix fibo-be-le: . +@prefix fibo-fbc: . +@prefix fibo-fnd-acc-aeq: . +@prefix fibo-fnd-acc-cur: . +@prefix vcard: . +@prefix foaf: . +@prefix schema: . +@prefix ubl: . + +# ── res.partner facets ─────────────────────────────────────────────────────── +# A res.partner with is_company = true is a legal entity; with is_company = +# false it is a natural person. Mirrors seam Step 2. + +odoo:res.partner.Company + owl:equivalentClass fibo-be-le:LegalEntity . + +odoo:res.partner.Individual + owl:equivalentClass vcard:Individual . + +# Field-level property alignments (seam Step 2 tail). +odoo:res.partner.name owl:equivalentProperty foaf:name . +odoo:res.partner.vat owl:equivalentProperty fibo-fbc:hasTaxIdentifier . + +# ── account.move dual nature (seam Open-question 5) ────────────────────────── +# account.move is BOTH a journal entry AND an invoice depending on `move_type`. +# Two projections, both Perdurant: +# - move_type = 'entry' → fibo:Transaction +# - move_type = 'out_invoice' → ubl:Invoice +# The base class aligns to the financial-transaction reading; the invoice +# projection is the dedicated facet. (Full owl:intersectionOf + owl:hasValue +# restrictions over move_type are the extractor's job; this seed declares the +# two equivalence landings the cascade resolves through.) + +odoo:account.move + owl:equivalentClass fibo-fbc:FinancialTransaction . + +odoo:account.move.Invoice + rdfs:subClassOf odoo:account.move ; + owl:equivalentClass ubl:Invoice . + +odoo:account.move.line + owl:equivalentClass fibo-fnd-acc-aeq:JournalEntry . + +# ── account.account / journal ──────────────────────────────────────────────── + +odoo:account.account + owl:equivalentClass fibo-fnd-acc-aeq:Account . + +# ── product ────────────────────────────────────────────────────────────────── + +odoo:product.template + owl:equivalentClass schema:Product . + +odoo:product.product + owl:equivalentClass schema:Product . diff --git a/data/ontologies/odoo/alignment/odoo-to-skr.ttl b/data/ontologies/odoo/alignment/odoo-to-skr.ttl new file mode 100644 index 00000000..1150a90a --- /dev/null +++ b/data/ontologies/odoo/alignment/odoo-to-skr.ttl @@ -0,0 +1,45 @@ +# Odoo → SKR (DATEV chart-of-accounts) alignment — Layer-2 of the four-way seam. +# +# Hand-curated `owl:equivalentClass` / `owl:equivalentProperty` axioms tying +# odoo's accounting models to the German SKR03/SKR04 chart concepts hydrated by +# hydrators/skr_datev.rs. Per Seam decision 1 / Option B, odoo authors no CAM +# family: these axioms route an odoo account onto the SKR account node, which +# already carries an OGIT slot. +# +# Note (seam): odoo's `account.account.template` is the chart-of-accounts +# template concept that SKR03/SKR04 populate; an odoo deployment localised to +# DE maps each account.account onto its SKR account code. The structural +# equivalence below is the class-level pivot; per-code mappings (1200 → Bank, +# 1576 → Vorsteuer, …) are emitted by the extractor against the live SKR slot. + +@prefix odoo: . +@prefix owl: . +@prefix rdfs: . +@prefix skr03: . +@prefix skr04: . + +# ── chart-of-accounts class pivots ─────────────────────────────────────────── +# An odoo account IS an SKR account node when the deployment's chart is SKR. +# Declared against both SKR03 and SKR04 roots; the active chart is selected at +# hydration time per tenant (seam Open-question 4 — multi-tenant file select). + +odoo:account.account + owl:equivalentClass skr03:Konto , + skr04:Konto . + +# account.tax aligns to the SKR Umsatzsteuer/Vorsteuer rate concept (a Quality +# per the suffix classifier — it characterises a posting, it is not the event). +odoo:account.tax + owl:equivalentClass skr03:Steuersatz , + skr04:Steuersatz . + +# account.journal is the SKR posting journal (Endurant container). +odoo:account.journal + owl:equivalentClass skr03:Journal , + skr04:Journal . + +# ── property pivots ────────────────────────────────────────────────────────── +# The odoo account `code` field IS the SKR account number (Kontonummer). +odoo:account.account.code + owl:equivalentProperty skr03:kontonummer , + skr04:kontonummer . diff --git a/data/ontologies/odoo/odoo-core.ttl b/data/ontologies/odoo/odoo-core.ttl new file mode 100644 index 00000000..88f95abe --- /dev/null +++ b/data/ontologies/odoo/odoo-core.ttl @@ -0,0 +1,98 @@ +# Odoo core business-model ontology — Layer-1 extraction seed. +# +# The four-way alignment seam (woa-rs/.claude/reference/four_way_alignment_seam.md) +# declares odoo-extracted business models as OWL classes in the `odoo:` +# namespace. This is the hand-authored seed of the worked-example core classes; +# the full surface is produced by the Rust odoo extractor (Python AST → TTL, +# per odoo_work_steal_distillation §"Extraction methodology"). +# +# Naming convention: `odoo:res.partner`, `odoo:account.move`, etc. — the odoo +# model name verbatim. Faceted classes (`odoo:res.partner.Company`) carry the +# `owl:equivalentClass` alignment in alignment/odoo-to-fibo.ttl. +# +# DOLCE category is NOT declared inline here — it is computed by the suffix +# classifier (hydrators/dolce_odoo.rs, Seam decision 2). CAM codebook placement +# is NOT declared either — odoo inherits existing FIBO/SKR slots via the +# alignment axioms (Seam decision 1 / Option B). + +@prefix odoo: . +@prefix owl: . +@prefix rdfs: . + +# ── res.partner family ────────────────────────────────────────────────────── +# The partner master record and its company / individual facets. The two +# facets are subclasses of res.partner discriminated (in odoo) by the +# `is_company` boolean; the alignment axioms map them onto fibo:LegalEntity +# and vcard:Individual respectively. + +odoo:res.partner a owl:Class ; + rdfs:label "Contact"@en . + +odoo:res.partner.Company a owl:Class ; + rdfs:label "Company"@en ; + rdfs:subClassOf odoo:res.partner . + +odoo:res.partner.Individual a owl:Class ; + rdfs:label "Individual"@en ; + rdfs:subClassOf odoo:res.partner . + +# ── account.move family (the dual-nature journal-entry / invoice document) ─── +# account.move is BOTH a journal entry AND an invoice depending on `move_type` +# (Seam Open-question 5). The two projections are carried as alignment axioms, +# not as inline subclasses, mirroring the res.partner facet split. + +odoo:account.move a owl:Class ; + rdfs:label "Journal Entry"@en . + +odoo:account.move.line a owl:Class ; + rdfs:label "Journal Item"@en ; + rdfs:subClassOf odoo:account.move . + +odoo:account.account a owl:Class ; + rdfs:label "Account"@en . + +odoo:account.tax a owl:Class ; + rdfs:label "Tax"@en . + +odoo:account.journal a owl:Class ; + rdfs:label "Journal"@en . + +# ── product family ─────────────────────────────────────────────────────────── +# product.template is the master product record (despite the `.template` +# suffix — odoo uses "template" here for the master, not a config template); +# product.product is the sellable variant. product.category classifies them. + +odoo:product.product a owl:Class ; + rdfs:label "Product Variant"@en ; + rdfs:subClassOf odoo:product.template . + +odoo:product.template a owl:Class ; + rdfs:label "Product"@en . + +odoo:product.category a owl:Class ; + rdfs:label "Product Category"@en . + +# ── stock family (logistic events) ─────────────────────────────────────────── + +odoo:stock.move a owl:Class ; + rdfs:label "Stock Move"@en . + +odoo:stock.picking a owl:Class ; + rdfs:label "Transfer"@en . + +# ── mail family (communication event + template) ───────────────────────────── + +odoo:mail.message a owl:Class ; + rdfs:label "Message"@en . + +odoo:mail.template a owl:Class ; + rdfs:label "Email Template"@en . + +# ── hr family ───────────────────────────────────────────────────────────────── + +odoo:hr.employee a owl:Class ; + rdfs:label "Employee"@en . + +odoo:hr.attendance a owl:Class ; + rdfs:label "Attendance"@en ; + rdfs:subClassOf odoo:hr.employee . diff --git a/modules/odoo/manifest.yaml b/modules/odoo/manifest.yaml new file mode 100644 index 00000000..022246ad --- /dev/null +++ b/modules/odoo/manifest.yaml @@ -0,0 +1,29 @@ +ogit_g: ODOO +version: 1 +domain_name: odoo +inert_when_consumer_absent: true + +entity_types: + Partner: u16=4300 + PartnerCompany: u16=4301 + PartnerIndividual: u16=4302 + AccountMove: u16=4303 + AccountMoveLine: u16=4304 + Account: u16=4305 + AccountTax: u16=4306 + AccountJournal: u16=4307 + Product: u16=4308 + ProductTemplate: u16=4309 + ProductCategory: u16=4310 + StockMove: u16=4311 + StockPicking: u16=4312 + MailMessage: u16=4313 + MailTemplate: u16=4314 + HrEmployee: u16=4315 + HrAttendance: u16=4316 + +rbac_policy: ~ +stack_profile: ~ +action_capabilities: {} +actor: ~ +inherits_from: fibofnd