Skip to main content

coil_commerce/module/
core.rs

1use crate::CommerceModelError;
2use coil_auth::Capability;
3use coil_core::{AdminContributionKind, AdminNavigationSection, AdminResourceContribution};
4use coil_data::{MigrationId, MigrationOwner, MigrationPlan, MigrationStep};
5
6pub struct CommerceModule {
7    name: String,
8    config_namespace: String,
9    admin_resources: Vec<AdminResourceContribution>,
10}
11
12impl CommerceModule {
13    pub fn new() -> Self {
14        Self {
15            name: "commerce".to_string(),
16            config_namespace: "commerce".to_string(),
17            admin_resources: vec![
18                AdminResourceContribution::new(
19                    "commerce.catalog",
20                    "/admin/commerce/catalog",
21                    "Catalog",
22                    "Catalog",
23                    AdminNavigationSection::Commerce,
24                    AdminContributionKind::ResourceIndex,
25                    Capability::CatalogProductRead,
26                ),
27                AdminResourceContribution::new(
28                    "commerce.collections",
29                    "/admin/commerce/collections",
30                    "Collections",
31                    "Collections",
32                    AdminNavigationSection::Commerce,
33                    AdminContributionKind::ResourceIndex,
34                    Capability::CatalogCollectionEdit,
35                ),
36                AdminResourceContribution::new(
37                    "commerce.checkouts",
38                    "/admin/commerce/checkouts",
39                    "Checkouts",
40                    "Checkouts",
41                    AdminNavigationSection::Commerce,
42                    AdminContributionKind::ResourceIndex,
43                    Capability::CheckoutSessionCreate,
44                ),
45                AdminResourceContribution::new(
46                    "commerce.orders",
47                    "/admin/commerce/orders",
48                    "Orders",
49                    "Orders",
50                    AdminNavigationSection::Commerce,
51                    AdminContributionKind::ResourceIndex,
52                    Capability::OrderRead,
53                ),
54            ],
55        }
56    }
57
58    pub fn name(&self) -> &str {
59        &self.name
60    }
61
62    pub fn config_namespace(&self) -> &str {
63        &self.config_namespace
64    }
65
66    pub fn admin_resources(&self) -> &[AdminResourceContribution] {
67        &self.admin_resources
68    }
69
70    pub fn migration_plan(&self) -> Result<MigrationPlan, CommerceModelError> {
71        let owner = MigrationOwner::Module(self.name.clone());
72        let mut plan = MigrationPlan::new();
73        plan.insert(
74            MigrationStep::new(
75                MigrationId::new("001_catalog_products")?,
76                owner.clone(),
77                10,
78                "create catalog products and variants tables",
79            )?
80            .with_statement(
81                "CREATE TABLE IF NOT EXISTS commerce_catalog_products (id TEXT PRIMARY KEY, slug TEXT NOT NULL, sku TEXT NOT NULL, title TEXT NOT NULL, product_type TEXT NOT NULL, status TEXT NOT NULL, price_minor BIGINT NOT NULL, currency TEXT NOT NULL, source_system TEXT, source_key TEXT UNIQUE, import_batch_id TEXT, fingerprint TEXT NOT NULL, updated_at BIGINT NOT NULL)",
82            )?
83            .with_statement(
84                "CREATE TABLE IF NOT EXISTS commerce_catalog_variants (id TEXT PRIMARY KEY, product_id TEXT NOT NULL, sku TEXT NOT NULL, title TEXT NOT NULL, status TEXT NOT NULL, price_minor BIGINT NOT NULL, currency TEXT NOT NULL, fingerprint TEXT NOT NULL, updated_at BIGINT NOT NULL)",
85            )?,
86        )?;
87        plan.insert(
88            MigrationStep::new(
89                MigrationId::new("002_collections")?,
90                owner.clone(),
91                20,
92                "create collections and product membership tables",
93            )?
94            .with_statement(
95                "CREATE TABLE IF NOT EXISTS commerce_collections (id TEXT PRIMARY KEY, handle TEXT NOT NULL, title TEXT NOT NULL, status TEXT NOT NULL, fingerprint TEXT NOT NULL, updated_at BIGINT NOT NULL)",
96            )?
97            .with_statement(
98                "CREATE TABLE IF NOT EXISTS commerce_collection_products (collection_id TEXT NOT NULL, product_id TEXT NOT NULL, position BIGINT NOT NULL, PRIMARY KEY (collection_id, product_id))",
99            )?,
100        )?;
101        plan.insert(
102            MigrationStep::new(
103                MigrationId::new("003_checkouts_orders")?,
104                owner.clone(),
105                30,
106                "create checkout, order, and pricing snapshot tables",
107            )?
108            .with_statement(
109                "CREATE TABLE IF NOT EXISTS commerce_checkouts (id TEXT PRIMARY KEY, status TEXT NOT NULL, currency TEXT NOT NULL, email TEXT, principal_id TEXT, subtotal_minor BIGINT NOT NULL, total_minor BIGINT NOT NULL, payment_reference TEXT, source_system TEXT, source_key TEXT UNIQUE, import_batch_id TEXT, fingerprint TEXT NOT NULL, updated_at BIGINT NOT NULL)",
110            )?
111            .with_statement(
112                "CREATE TABLE IF NOT EXISTS commerce_orders (id TEXT PRIMARY KEY, checkout_id TEXT, status TEXT NOT NULL, currency TEXT NOT NULL, email TEXT, principal_id TEXT, subtotal_minor BIGINT NOT NULL, total_minor BIGINT NOT NULL, payment_status TEXT NOT NULL, payment_reference TEXT, source_system TEXT, source_key TEXT UNIQUE, import_batch_id TEXT, fingerprint TEXT NOT NULL, updated_at BIGINT NOT NULL)",
113            )?
114            .with_statement(
115                "CREATE TABLE IF NOT EXISTS commerce_order_lines (id TEXT PRIMARY KEY, order_id TEXT NOT NULL, product_id TEXT, variant_id TEXT, title TEXT NOT NULL, quantity BIGINT NOT NULL, line_total_minor BIGINT NOT NULL, currency TEXT NOT NULL)",
116            )?,
117        )?;
118        plan.insert(
119            MigrationStep::new(
120                MigrationId::new("004_refunds")?,
121                owner,
122                40,
123                "create refund ledger and payment reconciliation tables",
124            )?
125            .with_statement(
126                "CREATE TABLE IF NOT EXISTS commerce_refunds (id TEXT PRIMARY KEY, order_id TEXT NOT NULL, status TEXT NOT NULL, amount_minor BIGINT NOT NULL, currency TEXT NOT NULL, reason TEXT NOT NULL, fingerprint TEXT NOT NULL, updated_at BIGINT NOT NULL)",
127            )?
128            .with_statement(
129                "CREATE TABLE IF NOT EXISTS commerce_payment_reconciliation (id TEXT PRIMARY KEY, order_id TEXT NOT NULL, provider TEXT NOT NULL, provider_reference TEXT NOT NULL, status TEXT NOT NULL, fingerprint TEXT NOT NULL, updated_at BIGINT NOT NULL)",
130            )?,
131        )?;
132        Ok(plan)
133    }
134}
135
136impl Default for CommerceModule {
137    fn default() -> Self {
138        Self::new()
139    }
140}