Data Model
middag-account stores domain data using WordPress Custom Post Types (CPTs) with metadata in wp_postmeta. A dual-repository system supports migration to custom tables (CCT) via a feature toggle.
Custom Post Types
Each domain maps to a CPT with the middag_ prefix. All CPTs use show_ui => false (no wp-admin UI) and register custom capabilities.
| Domain | CPT Slug | Meta Prefix |
|---|---|---|
| Organization | middag_organization | org_ |
| Collaborator | middag_collaborator | collab_ |
| Entitlement | middag_entitlement | ent_ |
| Environment | middag_environment | env_ |
| Service | middag_service | svc_ |
| Ticket | middag_ticket | sr_ |
| Quote | middag_quote | quote_ |
| Invoice | middag_invoice | invoice_ |
| TaxInvoice | middag_tax_invoice | taxinvoice_ |
| Contract | middag_contract | contract_ |
| Document | middag_document | document_ |
Adapter Domains (No Own CPT)
These domains wrap existing WooCommerce or third-party data:
| Domain | Storage | Notes |
|---|---|---|
| Order | WooCommerce orders | OrderService adapter |
| License | WC Software License | LicenseService adapter |
| Download | WooCommerce products | DownloadService adapter |
| Affiliate | SolidAffiliate records | AffiliateService adapter |
Meta Key Conventions
Domain metadata is stored in wp_postmeta with prefixed keys. Entity classes accept both API snake_case keys and CCT-prefixed keys via fromArray():
// Both are valid:
$entity = EntitlementEntity::fromArray(['code' => 'PLG-2026040001']);
$entity = EntitlementEntity::fromArray(['ent_code' => 'PLG-2026040001']);Organization Meta Keys
| Meta Key | Type | Description |
|---|---|---|
org_name | string | Display name |
org_legalname | string | Legal entity name |
org_documentnumber1 | string | CNPJ (Brazil) |
org_documentnumber2 | string | State registration |
org_email | string | Primary contact email |
org_phone | string | Phone number |
org_type | string | company or individual |
org_verification_status | string | pending, verified |
org_stripe_customer_id_br | string | Stripe customer (BR entity) |
org_stripe_customer_id_global | string | Stripe customer (US entity) |
org_hubspot_company_id_br | string | HubSpot company (BR) |
org_hubspot_company_id_global | string | HubSpot company (US) |
Entitlement Meta Keys
| Meta Key | Type | Description |
|---|---|---|
ent_code | string | Unique code (PLG-2026040001) |
ent_class | string | PLG, ENV, SVC, ORD, AFL, EDU |
ent_product_name | string | Linked product name |
ent_status | string | active, suspended, expired, cancelled |
ent_organization_id | int | Owning organization |
ent_company | string | middag_br or middag_global |
ent_expires_at | string | Expiration date (ISO 8601) |
ent_quote_id | int | Originating quote |
ent_auto_created | bool | Auto-provisioned from quote |
Relationships
Entitlements serve as the universal foreign key linking all downstream entities:
Organization (1) --> (*) Collaborator
Organization (1) --> (*) Entitlement
Entitlement (1) --> (*) License [class=PLG]
Entitlement (1) --> (*) Environment [class=ENV]
Entitlement (1) --> (*) Service [class=SVC]
Entitlement (1) --> (*) Contract
Service (1) --> (*) Ticket
Environment (1) --> (*) Ticket
Quote (1) --> (1) Order
Order (1) --> (*) Invoice
Order (1) --> (*) TaxInvoiceDual-Repository Toggle
The middag_migration_complete option switches between repository implementations:
| Option Value | Repository Used | Storage |
|---|---|---|
false | Cct{Name}Repository | JetEngine CCT tables |
true | {Name}Repository | wp_posts + meta |
Both implementations satisfy the same {Name}RepositoryInterface defined in src/Domain/. The DI container binds the active implementation at boot time based on the toggle value.
// Domain code is unaware of storage backend
interface EntitlementRepositoryInterface {
public function findById(int $id): ?EntitlementEntity;
public function listByOrganization(int $orgId, int $perPage, int $page): array;
}Related
- Architecture Overview
- Field Glossary -- Complete field listing
- Status Labels -- All status values