diff --git a/crates/ogar-class-view/src/lib.rs b/crates/ogar-class-view/src/lib.rs index c49f283..b40c0f8 100644 --- a/crates/ogar-class-view/src/lib.rs +++ b/crates/ogar-class-view/src/lib.rs @@ -64,67 +64,19 @@ use lance_graph_contract::{ ontology::{DisplayTemplate, FieldRef, ObjectView}, }; use ogar_vocab::{ - Class, - accounting_account, // 0x0CXX — automation (HIRO MARS CMDB + DO-arm actuators) - action_applicability, - action_handler, - anatomical_structure, - auth_ory_keto, - auth_store, - auth_zanzibar, - auth_zitadel, - automation_trigger, - billable_work_entry, - billing_party, - bone, - canonical_concept_id, - commercial_document, - commercial_line_item, - currency_policy, - diagnosis, - joint, - knowledge_item, - lab_value, - mars_application, - mars_machine, - mars_node_template, - mars_resource, - mars_software, - medication, - patient, - payment_record, - priority, - product, - project, - project_actor, - project_attachment, - project_changeset, - project_comment, - project_custom_field, - project_custom_value, - project_enabled_module, - project_forum, - project_journal, - project_member_role, - project_membership, - project_message, - project_news, - project_query, - project_relation, - project_repository, - project_role, - project_status, - project_type, - project_version, - project_watcher, - project_wiki_page, - project_work_item, - skeleton, - tax_policy, - treatment, - visit, - vital_sign, + action_applicability, Class, + accounting_account, action_handler, anatomical_structure, auth_ory_keto, auth_store, + auth_zanzibar, auth_zitadel, automation_trigger, billable_work_entry, billing_party, bone, + canonical_concept_id, commercial_document, commercial_line_item, currency_policy, diagnosis, + joint, knowledge_item, lab_value, mars_application, mars_machine, mars_node_template, + mars_resource, mars_software, medication, patient, payment_record, pricelist, pricelist_rule, + priority, product, project, project_actor, project_attachment, project_changeset, + project_comment, project_custom_field, project_custom_value, project_enabled_module, + project_forum, project_journal, project_member_role, project_membership, project_message, + project_news, project_query, project_relation, project_repository, project_role, + project_status, project_type, project_version, project_watcher, project_wiki_page, + project_work_item, skeleton, tax_policy, treatment, unit_of_measure, visit, vital_sign, }; /// All promoted canonical concepts: `(canonical_concept_name, Class)`. @@ -171,6 +123,9 @@ fn all_canonical_classes() -> Vec<(&'static str, Class)> { ("currency_policy", currency_policy()), ("product", product()), ("accounting_account", accounting_account()), + ("pricelist", pricelist()), + ("pricelist_rule", pricelist_rule()), + ("unit_of_measure", unit_of_measure()), // ── 0x09XX — health (OGIT Healthcare) ── ("patient", patient()), ("diagnosis", diagnosis()), diff --git a/crates/ogar-vocab/src/lib.rs b/crates/ogar-vocab/src/lib.rs index ac50233..04f967b 100644 --- a/crates/ogar-vocab/src/lib.rs +++ b/crates/ogar-vocab/src/lib.rs @@ -1156,6 +1156,12 @@ const CODEBOOK: &[(&str, u16)] = &[ // PR description for the queue. ("product", 0x0207), ("accounting_account", 0x0208), + // ProductCatalog cluster — closes 3 more of the 11-gap. All stay in 0x02XX + // (no new ConceptDomain needed). HR cluster (hr.*) remains queued; needs + // a new 0x0DXX concept domain (keystone-style §7 review). + ("pricelist", 0x0209), + ("pricelist_rule", 0x020A), + ("unit_of_measure", 0x020B), // ── 0x09XX — Health domain (clinical / patient / care) ── // medcare-rs Healthcare-namespace promotion (Northstar T9). The 7 // entities the OGIT `NTO/Healthcare/entities/` TTL ships, projected @@ -1533,6 +1539,16 @@ pub mod class_ids { /// SMBAccounting (0x62); this id is the OGAR-side identity that closes /// the same axis. pub const ACCOUNTING_ACCOUNT: u16 = 0x0208; + /// `pricelist` (`0x0209`) — price-specification base. OSB `Pricelist`, + /// Odoo `product.pricelist` (`schema:PriceSpecification`). Phase-3 + /// ProductCatalog cluster. + pub const PRICELIST: u16 = 0x0209; + /// `pricelist_rule` (`0x020A`) — per-tier unit-price rule. OSB + /// `PricelistTier`, Odoo `product.pricelist.item`. + pub const PRICELIST_RULE: u16 = 0x020A; + /// `unit_of_measure` (`0x020B`) — measurement unit. OSB `UoM`, Odoo + /// `uom.uom` (`qudt:Unit`). + pub const UNIT_OF_MEASURE: u16 = 0x020B; // ── 0x09XX — health domain (medcare-rs Healthcare namespace) ── @@ -1670,6 +1686,9 @@ pub mod class_ids { ("currency_policy", CURRENCY_POLICY), ("product", PRODUCT), ("accounting_account", ACCOUNTING_ACCOUNT), + ("pricelist", PRICELIST), + ("pricelist_rule", PRICELIST_RULE), + ("unit_of_measure", UNIT_OF_MEASURE), // 0x09XX — health ("patient", PATIENT), ("diagnosis", DIAGNOSIS), @@ -2538,6 +2557,9 @@ pub fn all_promoted_classes() -> Vec { currency_policy(), product(), accounting_account(), + pricelist(), + pricelist_rule(), + unit_of_measure(), // 0x09XX — health arm (7 OGIT Healthcare concepts), in // class_ids::ALL order. patient(), @@ -3418,6 +3440,58 @@ pub fn accounting_account() -> Class { c } +/// `pricelist` (`0x0209`) — price-specification base. +pub fn pricelist() -> Class { + let mut c = Class::new("Pricelist"); + c.language = Language::Unknown; + c.canonical_concept = Some("pricelist".to_string()); + c.associations = Vec::new(); + let mut name = Attribute::new("name"); + name.type_name = Some("string".to_string()); + let mut currency = Attribute::new("currency"); + currency.type_name = Some("string".to_string()); + let mut active = Attribute::new("active"); + active.type_name = Some("bool".to_string()); + c.attributes = vec![name, currency, active]; + c +} + +/// `pricelist_rule` (`0x020A`) — per-tier unit-price rule. +pub fn pricelist_rule() -> Class { + let mut c = Class::new("PricelistRule"); + c.language = Language::Unknown; + c.canonical_concept = Some("pricelist_rule".to_string()); + c.associations = Vec::new(); + let mut price = Attribute::new("price"); + price.type_name = Some("decimal".to_string()); + let mut min_quantity = Attribute::new("min_quantity"); + min_quantity.type_name = Some("decimal".to_string()); + let mut max_quantity = Attribute::new("max_quantity"); + max_quantity.type_name = Some("decimal".to_string()); + let mut pricelist_ref = Attribute::new("pricelist_ref"); + pricelist_ref.type_name = Some("string".to_string()); + c.attributes = vec![price, min_quantity, max_quantity, pricelist_ref]; + c +} + +/// `unit_of_measure` (`0x020B`) — measurement unit. +pub fn unit_of_measure() -> Class { + let mut c = Class::new("UnitOfMeasure"); + c.language = Language::Unknown; + c.canonical_concept = Some("unit_of_measure".to_string()); + c.associations = Vec::new(); + let mut name = Attribute::new("name"); + name.type_name = Some("string".to_string()); + let mut symbol = Attribute::new("symbol"); + symbol.type_name = Some("string".to_string()); + let mut factor = Attribute::new("factor"); + factor.type_name = Some("decimal".to_string()); + let mut uom_type = Attribute::new("uom_type"); + uom_type.type_name = Some("string".to_string()); + c.attributes = vec![name, symbol, factor, uom_type]; + c +} + // ───────────────────────────────────────────────────────────────────── // 0x09XX — Health domain (OGIT Healthcare). The reusable Active-Record // shape for the clinical concepts. `diagnosis` (0x0902) is the worked @@ -4488,6 +4562,9 @@ mod tests { "currency_policy", "product", "accounting_account", + "pricelist", + "pricelist_rule", + "unit_of_measure", ] { let id = canonical_concept_id(commerce_concept) .unwrap_or_else(|| panic!("{commerce_concept} missing from codebook")); @@ -4633,7 +4710,7 @@ mod tests { } // Counts line up with the codebook blocks. assert_eq!(concepts_in_domain(ConceptDomain::Health).count(), 7); - assert_eq!(concepts_in_domain(ConceptDomain::Commerce).count(), 8); + assert_eq!(concepts_in_domain(ConceptDomain::Commerce).count(), 11); assert_eq!(concepts_in_domain(ConceptDomain::ProjectMgmt).count(), 26); assert_eq!(concepts_in_domain(ConceptDomain::Anatomy).count(), 4); assert_eq!(concepts_in_domain(ConceptDomain::Auth).count(), 4); diff --git a/crates/ogar-vocab/src/ports.rs b/crates/ogar-vocab/src/ports.rs index 89dd0e6..74d7775 100644 --- a/crates/ogar-vocab/src/ports.rs +++ b/crates/ogar-vocab/src/ports.rs @@ -496,6 +496,11 @@ pub const ODOO_ALIASES: &[(&str, u16)] = &[ // same `accounting_account` id. Phase-3 mint per odoo-rs PR #14 + #16. ("account.account", class_ids::ACCOUNTING_ACCOUNT), ("account.account.template", class_ids::ACCOUNTING_ACCOUNT), + // ProductCatalog cluster — pricing structure + UoM, Phase-3 per + // odoo-rs PR #14. + ("product.pricelist", class_ids::PRICELIST), + ("product.pricelist.item", class_ids::PRICELIST_RULE), + ("uom.uom", class_ids::UNIT_OF_MEASURE), // Cross-arm bridge: the timesheet / cost line converges on the // project-arm `billable_work_entry` (0x0103) — the SAME id // OpenProject `TimeEntry` and Redmine `TimeEntry` resolve to. @@ -1000,13 +1005,13 @@ mod tests { // 9 Odoo model aliases = 8 commerce-arm (account.move, // sale.order, account.move.line, sale.order.line, account.tax, // res.partner, account.payment, res.currency) + 4 product/accounting - // master-record aliases (product.template, product.product, + // master-record aliases + 3 ProductCatalog cluster aliases (product.template, product.product, // account.account, account.account.template — Phase-3 mints per // odoo-rs PR #14 + #16) + 1 cross-arm bridge // (account.analytic.line → billable_work_entry). Re-count on drift. assert_eq!( OdooPort::aliases().len(), - 13, + 16, "Odoo alias count drift — re-count the ODOO_ALIASES table", ); }