diff --git a/.cargo-templates/dev.toml b/.cargo-templates/dev.toml index 733c3813..8a24a01d 100644 --- a/.cargo-templates/dev.toml +++ b/.cargo-templates/dev.toml @@ -3,25 +3,25 @@ # # To enable # - install clang -# - install mold https://github.com/rui314/mold into /usr/local/bin/mold -# - add a symbolic link from .cargo -> .cargo-dev +# - install mold https://github.com/rui314/mold into /usr/bin/mold +# - add a symbolic link from .cargo -> .cargo-dev # via `mkdir -p .cargo && ln -s ../.cargo-templates/dev.toml $_/config.toml`. # # If there is an issue, reverting is as simple as deleting .cargo. [target.x86_64-unknown-linux-gnu] linker = "/usr/bin/clang" -rustflags = ["-C", "link-arg=-fuse-ld=/usr/local/bin/mold"] +rustflags = ["-C", "link-arg=-fuse-ld=mold"] [target.aarch64-unknown-linux-gnu] linker = "clang" -rustflags = ["-C", "link-arg=-fuse-ld=/usr/local/bin/mold"] +rustflags = ["-C", "link-arg=-fuse-ld=mold"] [profile.release] incremental = true -[profile.fast] -inherits = "release" +[profile.dev] +incremental = true opt-level = 0 lto = "off" codegen-units = 256 diff --git a/Cargo.toml b/Cargo.toml index 171b6572..06228a45 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -195,3 +195,4 @@ metering = { path = "modules/metering" } diesel-models = { path = "modules/meteroid/crates/diesel-models" } meteroid-store = { path = "modules/meteroid/crates/meteroid-store" } meteroid-invoicing = { path = "modules/meteroid/crates/meteroid-invoicing" } + diff --git a/docker/develop/data/seed.sql b/docker/develop/data/seed.sql index 9d6bff25..99c5eec4 100644 --- a/docker/develop/data/seed.sql +++ b/docker/develop/data/seed.sql @@ -1,554 +1,554 @@ DO $$ - DECLARE - var_org_id UUID := '018c2c82-3def-7fa0-bf6f-a5f8fe341549'; - var_user_id UUID := 'ae35bbb9-65da-477d-b856-7dbd87546441'; - var_tenant_id UUID := '018c2c82-3df1-7e84-9e05-6e141d0e751a'; - var_invoicing_entity_id UUID := 'cf144094-ab72-441c-8c8a-54e18bfba0ef'; - var_product_family_id UUID := '018c2c82-3df2-71a4-b45c-86cb8604b75c'; + DECLARE + var_org_id UUID := '018c2c82-3def-7fa0-bf6f-a5f8fe341549'; + var_user_id UUID := 'ae35bbb9-65da-477d-b856-7dbd87546441'; + var_tenant_id UUID := '018c2c82-3df1-7e84-9e05-6e141d0e751a'; + var_invoicing_entity_id UUID := 'cf144094-ab72-441c-8c8a-54e18bfba0ef'; + var_product_family_id UUID := '018c2c82-3df2-71a4-b45c-86cb8604b75c'; - -- customers + -- customers - var_cust_spotify_id UUID := '018c345f-7324-7cd2-a692-78e5ab9158e0'; - var_cust_uber_id UUID := '018c345f-dff1-7857-b988-6c792ed6fa3f'; - var_cust_comodo_id UUID := '018c3463-05f3-7c1f-92b1-ddb1f70905a2'; + var_cust_spotify_id UUID := '018c345f-7324-7cd2-a692-78e5ab9158e0'; + var_cust_uber_id UUID := '018c345f-dff1-7857-b988-6c792ed6fa3f'; + var_cust_comodo_id UUID := '018c3463-05f3-7c1f-92b1-ddb1f70905a2'; - -- metrics + -- metrics - var_metric_database_size UUID := '018c3452-129f-702c-93f4-9c15095b0ef4'; - var_metric_bandwidth UUID := '018c3453-1f11-76a8-8d69-f74921b2646d'; + var_metric_database_size UUID := '018c3452-129f-702c-93f4-9c15095b0ef4'; + var_metric_bandwidth UUID := '018c3453-1f11-76a8-8d69-f74921b2646d'; - -- plans - var_plan_leetcode_id UUID := '018c344a-78a8-79bc-aefd-09113eaf5cb3'; - var_plan_notion_id UUID := '018c344b-da85-70dc-ae6f-5b919847dbbf'; - var_plan_supabase_id UUID := '018c344d-5957-72cf-816b-938dea2f5c76'; + -- plans + var_plan_leetcode_id UUID := '018c344a-78a8-79bc-aefd-09113eaf5cb3'; + var_plan_notion_id UUID := '018c344b-da85-70dc-ae6f-5b919847dbbf'; + var_plan_supabase_id UUID := '018c344d-5957-72cf-816b-938dea2f5c76'; - -- plan_versions - var_plan_version_1_leetcode_id UUID := '018c344a-78a9-7e2b-af90-5748672711f8'; - var_plan_version_2_leetcode_id UUID := '018c344a-78a9-7e2b-af90-5748672711f9'; - var_plan_version_notion_id UUID := '018c344b-da87-7392-bbae-c5c8780adb1b'; - var_plan_version_supabase_id UUID := '018c35cc-3f41-7551-b7b6-f8bbcd62b784'; + -- plan_versions + var_plan_version_1_leetcode_id UUID := '018c344a-78a9-7e2b-af90-5748672711f8'; + var_plan_version_2_leetcode_id UUID := '018c344a-78a9-7e2b-af90-5748672711f9'; + var_plan_version_notion_id UUID := '018c344b-da87-7392-bbae-c5c8780adb1b'; + var_plan_version_supabase_id UUID := '018c35cc-3f41-7551-b7b6-f8bbcd62b784'; - -- components + -- components - var_comp_leetcode_rate_id UUID := '018c344b-6050-7ec8-bd8c-d2e9c41ab711'; - var_comp_notion_seats_id UUID := '018c344c-9ec9-7608-b115-1537b6985e73'; - var_comp_supabase_org_slots_id UUID := '3b083801-c77c-4488-848e-a185f0f0a8be'; - var_comp_supabase_bandwidth_id UUID := '705265c8-6069-4b84-a815-73bc7bd773bd'; - var_comp_supabase_db_size_id UUID := '331810d4-05b1-4d8e-bf9b-d61cedaec117'; + var_comp_leetcode_rate_id UUID := '018c344b-6050-7ec8-bd8c-d2e9c41ab711'; + var_comp_notion_seats_id UUID := '018c344c-9ec9-7608-b115-1537b6985e73'; + var_comp_supabase_org_slots_id UUID := '3b083801-c77c-4488-848e-a185f0f0a8be'; + var_comp_supabase_bandwidth_id UUID := '705265c8-6069-4b84-a815-73bc7bd773bd'; + var_comp_supabase_db_size_id UUID := '331810d4-05b1-4d8e-bf9b-d61cedaec117'; - -- subscriptions + -- subscriptions - var_sub_spotify_notion_id UUID := '018c3475-bdc5-77dd-9e26-e9a7fdd60426'; - var_sub_spotify_supabase_id UUID := '018c3762-d554-7339-b13d-6fff8c9b76a0'; - var_sub_uber_notion_id UUID := '018c3477-2274-7029-9743-b3a4eb779399'; - var_sub_uber_leetcode_id UUID := '018c3479-fa9d-713f-b74f-6d9cc22cf110'; - var_sub_comodo_leetcode_id UUID := '018c347a-b42b-709f-8e70-b0b63029aa35'; - var_sub_comodo_supabase_id UUID := '018c3763-070e-709d-8413-f42828e71943'; + var_sub_spotify_notion_id UUID := '018c3475-bdc5-77dd-9e26-e9a7fdd60426'; + var_sub_spotify_supabase_id UUID := '018c3762-d554-7339-b13d-6fff8c9b76a0'; + var_sub_uber_notion_id UUID := '018c3477-2274-7029-9743-b3a4eb779399'; + var_sub_uber_leetcode_id UUID := '018c3479-fa9d-713f-b74f-6d9cc22cf110'; + var_sub_comodo_leetcode_id UUID := '018c347a-b42b-709f-8e70-b0b63029aa35'; + var_sub_comodo_supabase_id UUID := '018c3763-070e-709d-8413-f42828e71943'; - BEGIN + BEGIN - INSERT INTO public.organization - (id, trade_name, slug, created_at, archived_at, invite_link_hash, default_country) - VALUES (var_org_id, 'Local Org', 'TESTORG', '2023-12-02 21:49:42.255', NULL, - 'fake-invite-link', 'FR'); + INSERT INTO public.organization + (id, trade_name, slug, created_at, archived_at, invite_link_hash, default_country) + VALUES (var_org_id, 'Local Org', 'TESTORG', '2023-12-02 21:49:42.255', NULL, + 'fake-invite-link', 'FR'); - INSERT INTO public."user" - (id, email, created_at, archived_at, password_hash, onboarded, first_name, last_name, department) - VALUES (var_user_id, 'demo-user@meteroid.dev', '2023-12-02 21:49:08.805', NULL, - '$argon2id$v=19$m=19456,t=2,p=1$dawIX5+sybNHqfFoNvHFhw$uhtWJd50wiFDV8nR10RNZI4OCrOAJ1kiNZQF0OUSoGE', - true, - 'John', 'Doe', 'Engineering'); + INSERT INTO public."user" + (id, email, created_at, archived_at, password_hash, onboarded, first_name, last_name, department) + VALUES (var_user_id, 'demo-user@meteroid.dev', '2023-12-02 21:49:08.805', NULL, + '$argon2id$v=19$m=19456,t=2,p=1$dawIX5+sybNHqfFoNvHFhw$uhtWJd50wiFDV8nR10RNZI4OCrOAJ1kiNZQF0OUSoGE', + true, + 'John', 'Doe', 'Engineering'); - INSERT INTO public.organization_member - (user_id, organization_id, role) - VALUES (var_user_id, var_org_id, 'ADMIN'); + INSERT INTO public.organization_member + (user_id, organization_id, role) + VALUES (var_user_id, var_org_id, 'ADMIN'); - -- -- + -- -- -- -- Data for Name: tenant; Type: TABLE DATA; Schema: public; Owner: meteroidbilling -- -- - INSERT INTO public.tenant - (id, name, slug, created_at, updated_at, archived_at, organization_id, currency, environment) - VALUES (var_tenant_id, 'Sandbox', 'testslug', '2023-12-02 21:49:42.255', NULL, - NULL, - var_org_id, 'EUR', 'DEVELOPMENT'::"TenantEnvironmentEnum"); - -- + INSERT INTO public.tenant + (id, name, slug, created_at, updated_at, archived_at, organization_id, currency, environment) + VALUES (var_tenant_id, 'Sandbox', 'testslug', '2023-12-02 21:49:42.255', NULL, + NULL, + var_org_id, 'EUR', 'DEVELOPMENT'::"TenantEnvironmentEnum"); + -- -- -- -- -- -- Data for Name: api_token; Type: TABLE DATA; Schema: public; Owner: meteroidbilling -- -- - INSERT INTO public.api_token - (id, name, created_at, created_by, tenant_id, hash, hint) - VALUES ('018ce957-b628-7355-a460-f0d71e01335e', 'token-pD_', '2024-01-08 13:51:29.151', - var_user_id, var_tenant_id, - '$argon2id$v=19$m=19456,t=2,p=1$98CkbdqB8KNdlqryCBIx+g$nhTanF/4QsVnpPFvPHzshLPOGd7btYxXfq2UWB0xkiU', - 'pv_sand_9XzH...AbBG'); + INSERT INTO public.api_token + (id, name, created_at, created_by, tenant_id, hash, hint) + VALUES ('018ce957-b628-7355-a460-f0d71e01335e', 'token-pD_', '2024-01-08 13:51:29.151', + var_user_id, var_tenant_id, + '$argon2id$v=19$m=19456,t=2,p=1$98CkbdqB8KNdlqryCBIx+g$nhTanF/4QsVnpPFvPHzshLPOGd7btYxXfq2UWB0xkiU', + 'pv_sand_9XzH...AbBG'); - INSERT INTO public.invoicing_entity - (id, local_id, is_default, legal_name, invoice_number_pattern, next_invoice_number, next_credit_note_number, - grace_period_hours, net_terms, invoice_footer_info, invoice_footer_legal, logo_attachment_id, brand_color, - address_line1, address_line2, zip_code, state, city, vat_number, country, accounting_currency, tenant_id) - VALUES (var_invoicing_entity_id, 'ive_O0sddA9FDlfeq', true, 'ACME_UK', 'INV-{number}', 1, 1, 23, 30, 'hello', - 'world', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'FE', 'EUR', var_tenant_id); + INSERT INTO public.invoicing_entity + (id, local_id, is_default, legal_name, invoice_number_pattern, next_invoice_number, next_credit_note_number, + grace_period_hours, net_terms, invoice_footer_info, invoice_footer_legal, logo_attachment_id, brand_color, + address_line1, address_line2, zip_code, state, city, vat_number, country, accounting_currency, tenant_id) + VALUES (var_invoicing_entity_id, 'ive_O0sddA9FDlfeq', true, 'ACME_UK', 'INV-{number}', 1, 1, 23, 30, 'hello', + 'world', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'FE', 'EUR', var_tenant_id); - -- -- + -- -- -- -- Data for Name: historical_rates_from_usd; Type: TABLE DATA; Schema: public; Owner: meteroidbilling -- -- A default historical value that will be used as fallback until rates gets updated -- -- - INSERT INTO public.historical_rates_from_usd - (id, date, rates) - VALUES ('018df083-46df-7326-a3ca-fb98888e1196', '2010-01-01', '{ - "AUD": 1.108609, - "BRL": 1.741616, - "CAD": 1.048367, - "CHF": 1.0338, - "CNY": 6.828759, - "COP": 2044.171135, - "EUR": 0.697253, - "GBP": 0.618224, - "HKD": 7.754729, - "JPY": 92.910732, - "KRW": 1160.640163, - "MXN": 13.108757, - "NZD": 1.377768, - "SEK": 7.138645, - "USD": 1 - }'), - ('018df083-46df-7f80-86da-f8c878b120f9', '2023-01-01', '{ - "AUD": 1.466361, - "BRL": 5.286471, - "CAD": 1.35339, - "CHF": 0.924587, - "CNY": 6.89814, - "COP": 4837.794852, - "EUR": 0.934096, - "GBP": 0.826651, - "HKD": 7.80261, - "JPY": 130.926, - "KRW": 1261.764305, - "MXN": 19.497266, - "NZD": 1.573642, - "SEK": 10.421755, - "USD": 1 - }'), - ('018df083-b921-7e28-8824-3a7a6ae2733e', '2024-01-01', '{ - "AUD": 1.468645, - "BRL": 4.8539, - "CAD": 1.324436, - "CHF": 0.841915, - "CNY": 7.0786, - "COP": 3887.87175, - "EUR": 0.906074, - "GBP": 0.78569, - "HKD": 7.81035, - "JPY": 141.115, - "KRW": 1280.64, - "MXN": 16.9664, - "NZD": 1.583713, - "SEK": 10.074633, - "USD": 1 - }'); - - - -- + INSERT INTO public.historical_rates_from_usd + (id, date, rates) + VALUES ('018df083-46df-7326-a3ca-fb98888e1196', '2010-01-01', '{ + "AUD": 1.108609, + "BRL": 1.741616, + "CAD": 1.048367, + "CHF": 1.0338, + "CNY": 6.828759, + "COP": 2044.171135, + "EUR": 0.697253, + "GBP": 0.618224, + "HKD": 7.754729, + "JPY": 92.910732, + "KRW": 1160.640163, + "MXN": 13.108757, + "NZD": 1.377768, + "SEK": 7.138645, + "USD": 1 + }'), + ('018df083-46df-7f80-86da-f8c878b120f9', '2023-01-01', '{ + "AUD": 1.466361, + "BRL": 5.286471, + "CAD": 1.35339, + "CHF": 0.924587, + "CNY": 6.89814, + "COP": 4837.794852, + "EUR": 0.934096, + "GBP": 0.826651, + "HKD": 7.80261, + "JPY": 130.926, + "KRW": 1261.764305, + "MXN": 19.497266, + "NZD": 1.573642, + "SEK": 10.421755, + "USD": 1 + }'), + ('018df083-b921-7e28-8824-3a7a6ae2733e', '2024-01-01', '{ + "AUD": 1.468645, + "BRL": 4.8539, + "CAD": 1.324436, + "CHF": 0.841915, + "CNY": 7.0786, + "COP": 3887.87175, + "EUR": 0.906074, + "GBP": 0.78569, + "HKD": 7.81035, + "JPY": 141.115, + "KRW": 1280.64, + "MXN": 16.9664, + "NZD": 1.583713, + "SEK": 10.074633, + "USD": 1 + }'); + + + -- -- Data for Name: customer; Type: TABLE DATA; Schema: public; Owner: meteroidbilling -- - INSERT INTO public.customer - (id, name, created_at, created_by, updated_at, updated_by, archived_at, tenant_id, - billing_config, alias, email, invoicing_email, phone, balance_value_cents, currency, - billing_address, shipping_address, invoicing_entity_id) - VALUES (var_cust_spotify_id, 'Sportify', '2023-12-04 10:28:39.845', - var_user_id, NULL, NULL, NULL, var_tenant_id, '{ - "Stripe": { - "customer_id": "spotify", - "collection_method": 0 - } - }', 'spotify', NULL, NULL, NULL, 0, 'EUR', NULL, NULL, var_invoicing_entity_id), - (var_cust_uber_id, 'Uber', '2023-12-04 10:29:07.699', - var_user_id, NULL, NULL, NULL, var_tenant_id, '{ - "Stripe": { - "customer_id": "uber", - "collection_method": 0 - } - }', 'uber', NULL, NULL, NULL, 0, 'EUR', NULL, NULL, var_invoicing_entity_id), - (var_cust_comodo_id, 'Comodo', '2023-12-04 10:32:34.036', - var_user_id, NULL, NULL, NULL, var_tenant_id, '{ - "Stripe": { - "customer_id": "comodo", - "collection_method": 0 - } - }', 'comodo', NULL, NULL, NULL, 0, 'EUR', NULL, NULL, var_invoicing_entity_id); - - - -- + INSERT INTO public.customer + (id, name, created_at, created_by, updated_at, updated_by, archived_at, tenant_id, + billing_config, alias, email, invoicing_email, phone, balance_value_cents, currency, + billing_address, shipping_address, invoicing_entity_id) + VALUES (var_cust_spotify_id, 'Sportify', '2023-12-04 10:28:39.845', + var_user_id, NULL, NULL, NULL, var_tenant_id, '{ + "Stripe": { + "customer_id": "spotify", + "collection_method": 0 + } + }', 'spotify', NULL, NULL, NULL, 0, 'EUR', NULL, NULL, var_invoicing_entity_id), + (var_cust_uber_id, 'Uber', '2023-12-04 10:29:07.699', + var_user_id, NULL, NULL, NULL, var_tenant_id, '{ + "Stripe": { + "customer_id": "uber", + "collection_method": 0 + } + }', 'uber', NULL, NULL, NULL, 0, 'EUR', NULL, NULL, var_invoicing_entity_id), + (var_cust_comodo_id, 'Comodo', '2023-12-04 10:32:34.036', + var_user_id, NULL, NULL, NULL, var_tenant_id, '{ + "Stripe": { + "customer_id": "comodo", + "collection_method": 0 + } + }', 'comodo', NULL, NULL, NULL, 0, 'EUR', NULL, NULL, var_invoicing_entity_id); + + + -- -- Data for Name: product_family; Type: TABLE DATA; Schema: public; Owner: meteroidbilling -- - INSERT INTO public.product_family - (id, name, external_id, created_at, updated_at, archived_at, tenant_id) - VALUES (var_product_family_id, 'Default', 'default', '2023-12-02 21:49:42.255', NULL, NULL, var_tenant_id); + INSERT INTO public.product_family + (id, name, local_id, created_at, updated_at, archived_at, tenant_id) + VALUES (var_product_family_id, 'Default', 'default', '2023-12-02 21:49:42.255', NULL, NULL, var_tenant_id); - -- + -- -- Data for Name: billable_metric; Type: TABLE DATA; Schema: public; Owner: meteroidbilling -- - INSERT INTO public.billable_metric - (id, name, description, code, aggregation_type, aggregation_key, - unit_conversion_factor, unit_conversion_rounding, segmentation_matrix, - usage_group_key, created_at, created_by, updated_at, archived_at, tenant_id, - product_family_id) - VALUES (var_metric_database_size, 'Database size (GB)', '', 'db_size', 'LATEST', 'size_gb', 1, - NULL, '{ - "matrix": null - }', '', '2023-12-04 10:14:03.168', var_user_id, NULL, NULL, - var_tenant_id, var_product_family_id), - (var_metric_bandwidth, 'Bandwidth (GB)', '', 'bandwidth', 'SUM', 'value', 1, NULL, '{ - "matrix": null - }', NULL, '2023-12-04 10:15:11.89', var_user_id, NULL, NULL, - var_tenant_id, var_product_family_id); - - - INSERT INTO public.plan - (id, name, description, created_at, created_by, updated_at, archived_at, tenant_id, - product_family_id, external_id, plan_type, status) - VALUES (var_plan_leetcode_id, 'LeetCode', '', '2023-12-04 10:05:45', - var_user_id, NULL, NULL, var_tenant_id, - var_product_family_id, 'default_leet-code', 'STANDARD', 'ACTIVE'); - - INSERT INTO public.plan_version - VALUES (var_plan_version_1_leetcode_id, false, var_plan_leetcode_id, 1, NULL, NULL, - var_tenant_id, NULL, 0, 'EUR', NULL, '2023-12-04 10:05:45', - var_user_id, '{MONTHLY,ANNUAL}'); - - INSERT INTO public.plan_version - VALUES ('018c344a-78a9-7e2b-af90-5748672711f9', true, var_plan_leetcode_id, 2, NULL, NULL, - var_tenant_id, NULL, 0, 'EUR', NULL, '2023-12-04 10:05:45', - var_user_id, '{MONTHLY,ANNUAL}'); - - INSERT INTO public.price_component - VALUES (var_comp_leetcode_rate_id, 'Subscription Rate', '{ - "rate": { - "pricing": { - "term_based": { - "rates": [ - { - "term": 0, - "price": { - "value": "35.00" - } - }, - { - "term": 2, - "price": { - "value": "159.00" - } - } - ] + INSERT INTO public.billable_metric + (id, name, description, code, aggregation_type, aggregation_key, + unit_conversion_factor, unit_conversion_rounding, segmentation_matrix, + usage_group_key, created_at, created_by, updated_at, archived_at, tenant_id, + product_family_id) + VALUES (var_metric_database_size, 'Database size (GB)', '', 'db_size', 'LATEST', 'size_gb', 1, + NULL, '{ + "matrix": null + }', '', '2023-12-04 10:14:03.168', var_user_id, NULL, NULL, + var_tenant_id, var_product_family_id), + (var_metric_bandwidth, 'Bandwidth (GB)', '', 'bandwidth', 'SUM', 'value', 1, NULL, '{ + "matrix": null + }', NULL, '2023-12-04 10:15:11.89', var_user_id, NULL, NULL, + var_tenant_id, var_product_family_id); + + + INSERT INTO public.plan + (id, name, description, created_at, created_by, updated_at, archived_at, tenant_id, + product_family_id, local_id, plan_type, status) + VALUES (var_plan_leetcode_id, 'LeetCode', '', '2023-12-04 10:05:45', + var_user_id, NULL, NULL, var_tenant_id, + var_product_family_id, 'default_leet-code', 'STANDARD', 'ACTIVE'); + + INSERT INTO public.plan_version + VALUES (var_plan_version_1_leetcode_id, false, var_plan_leetcode_id, 1, NULL, NULL, + var_tenant_id, NULL, 0, 'EUR', NULL, '2023-12-04 10:05:45', + var_user_id, '{MONTHLY,ANNUAL}'); + + INSERT INTO public.plan_version + VALUES ('018c344a-78a9-7e2b-af90-5748672711f9', true, var_plan_leetcode_id, 2, NULL, NULL, + var_tenant_id, NULL, 0, 'EUR', NULL, '2023-12-04 10:05:45', + var_user_id, '{MONTHLY,ANNUAL}'); + + INSERT INTO public.price_component + VALUES (var_comp_leetcode_rate_id, 'Subscription Rate', '{ + "rate": { + "pricing": { + "term_based": { + "rates": [ + { + "term": 0, + "price": { + "value": "35.00" + } + }, + { + "term": 2, + "price": { + "value": "159.00" + } } - } + ] } - }', var_plan_version_1_leetcode_id, NULL); + } + } + }', var_plan_version_1_leetcode_id, NULL); - ------------------------------------------------------------ + ------------------------------------------------------------ -- -- Notion plan -- Seat based (monthly :10, annual : 96) -- - INSERT INTO public.plan - VALUES (var_plan_notion_id, 'Notion', '', '2023-12-04 10:07:15.589', - var_user_id, NULL, NULL, var_tenant_id, - var_product_family_id, 'default_notion', 'STANDARD', 'ACTIVE'); - - INSERT INTO public.plan_version - VALUES (var_plan_version_notion_id, false, var_plan_notion_id, 1, NULL, NULL, - var_tenant_id, NULL, 0, 'EUR', NULL, '2023-12-04 10:07:15.589', - var_user_id, '{MONTHLY,ANNUAL}'); - - INSERT INTO public.price_component - VALUES (var_comp_notion_seats_id, 'Seats', '{ - "slot_based": { - "quota": null, - "pricing": { - "term_based": { - "rates": [ - { - "term": 0, - "price": { - "value": "10.00" - } - }, - { - "term": 2, - "price": { - "value": "96.00" - } - } - ] + INSERT INTO public.plan + VALUES (var_plan_notion_id, 'Notion', '', '2023-12-04 10:07:15.589', + var_user_id, NULL, NULL, var_tenant_id, + var_product_family_id, 'default_notion', 'STANDARD', 'ACTIVE'); + + INSERT INTO public.plan_version + VALUES (var_plan_version_notion_id, false, var_plan_notion_id, 1, NULL, NULL, + var_tenant_id, NULL, 0, 'EUR', NULL, '2023-12-04 10:07:15.589', + var_user_id, '{MONTHLY,ANNUAL}'); + + INSERT INTO public.price_component + VALUES (var_comp_notion_seats_id, 'Seats', '{ + "slot_based": { + "quota": null, + "pricing": { + "term_based": { + "rates": [ + { + "term": 0, + "price": { + "value": "10.00" + } + }, + { + "term": 2, + "price": { + "value": "96.00" + } } - }, - "slot_unit": { - "id": null, - "name": "Seats" - }, - "minimum_count": 1, - "upgrade_policy": 0, - "downgrade_policy": 0 + ] } - }', var_plan_version_notion_id, NULL); - - ------------------------------------------------------------ + }, + "slot_unit": { + "id": null, + "name": "Seats" + }, + "minimum_count": 1, + "upgrade_policy": 0, + "downgrade_policy": 0 + } + }', var_plan_version_notion_id, NULL); + + ------------------------------------------------------------ -- -- Supabase plan -- Usage based (DB size, bandwith) + Slot-based (Organizations, monthly) -- - INSERT INTO public.plan - VALUES (var_plan_supabase_id, 'Supabase', '', '2023-12-04 10:08:53.591', - var_user_id, NULL, NULL, var_tenant_id, - var_product_family_id, 'default_supabase', 'STANDARD', 'ACTIVE'); - - INSERT INTO public.plan_version - VALUES (var_plan_version_supabase_id, false, var_plan_supabase_id, 3, NULL, NULL, - var_tenant_id, NULL, 0, 'EUR', NULL, '2023-12-04 17:07:07.2', - var_user_id, '{}'); - - - INSERT INTO public.price_component - VALUES (var_comp_supabase_org_slots_id, 'Organization Slots', '{ - "slot_based": { - "quota": null, - "pricing": { - "single": { - "price": { - "value": "25.00" - }, - "cadence": 0 - } + INSERT INTO public.plan + VALUES (var_plan_supabase_id, 'Supabase', '', '2023-12-04 10:08:53.591', + var_user_id, NULL, NULL, var_tenant_id, + var_product_family_id, 'default_supabase', 'STANDARD', 'ACTIVE'); + + INSERT INTO public.plan_version + VALUES (var_plan_version_supabase_id, false, var_plan_supabase_id, 3, NULL, NULL, + var_tenant_id, NULL, 0, 'EUR', NULL, '2023-12-04 17:07:07.2', + var_user_id, '{}'); + + + INSERT INTO public.price_component + VALUES (var_comp_supabase_org_slots_id, 'Organization Slots', '{ + "slot_based": { + "quota": null, + "pricing": { + "single": { + "price": { + "value": "25.00" }, - "slot_unit": { - "id": null, - "name": "Organization" - }, - "minimum_count": 1, - "upgrade_policy": 0, - "downgrade_policy": 0 + "cadence": 0 } - }', var_plan_version_supabase_id, NULL); - INSERT INTO public.price_component - VALUES (var_comp_supabase_bandwidth_id, 'Bandwidth (GB)', '{ - "usage_based": { - "model": { - "tiered": { - "rows": [ - { - "flat_cap": null, - "flat_fee": null, - "last_unit": 250, - "first_unit": 0, - "unit_price": { - "value": "0.00" - } - }, - { - "flat_cap": null, - "flat_fee": null, - "last_unit": null, - "first_unit": 250, - "unit_price": { - "value": "0.09" - } - } - ], - "block_size": null + }, + "slot_unit": { + "id": null, + "name": "Organization" + }, + "minimum_count": 1, + "upgrade_policy": 0, + "downgrade_policy": 0 + } + }', var_plan_version_supabase_id, NULL); + INSERT INTO public.price_component + VALUES (var_comp_supabase_bandwidth_id, 'Bandwidth (GB)', '{ + "usage_based": { + "model": { + "tiered": { + "rows": [ + { + "flat_cap": null, + "flat_fee": null, + "last_unit": 250, + "first_unit": 0, + "unit_price": { + "value": "0.00" + } + }, + { + "flat_cap": null, + "flat_fee": null, + "last_unit": null, + "first_unit": 250, + "unit_price": { + "value": "0.09" + } } - }, - "metric": { - "id": "018c3453-1f11-76a8-8d69-f74921b2646d", - "name": "N/A" - } + ], + "block_size": null } - }', var_plan_version_supabase_id, NULL); - INSERT INTO public.price_component - VALUES (var_comp_supabase_db_size_id, 'Database size (GB)', '{ - "usage_based": { - "model": { - "tiered": { - "rows": [ - { - "flat_cap": null, - "flat_fee": null, - "last_unit": 8, - "first_unit": 0, - "unit_price": { - "value": "0.00" - } - }, - { - "flat_cap": null, - "flat_fee": null, - "last_unit": null, - "first_unit": 8, - "unit_price": { - "value": "0.125" - } - } - ], - "block_size": null + }, + "metric": { + "id": "018c3453-1f11-76a8-8d69-f74921b2646d", + "name": "N/A" + } + } + }', var_plan_version_supabase_id, NULL); + INSERT INTO public.price_component + VALUES (var_comp_supabase_db_size_id, 'Database size (GB)', '{ + "usage_based": { + "model": { + "tiered": { + "rows": [ + { + "flat_cap": null, + "flat_fee": null, + "last_unit": 8, + "first_unit": 0, + "unit_price": { + "value": "0.00" + } + }, + { + "flat_cap": null, + "flat_fee": null, + "last_unit": null, + "first_unit": 8, + "unit_price": { + "value": "0.125" + } } - }, - "metric": { - "id": "018c3452-129f-702c-93f4-9c15095b0ef4", - "name": "N/A" - } + ], + "block_size": null } - }', var_plan_version_supabase_id, NULL); + }, + "metric": { + "id": "018c3452-129f-702c-93f4-9c15095b0ef4", + "name": "N/A" + } + } + }', var_plan_version_supabase_id, NULL); - -- + -- -- Data for Name: subscription; Type: TABLE DATA; Schema: public; Owner: meteroidbilling -- - INSERT INTO public.subscription (id, customer_id, billing_day, tenant_id, trial_start_date, billing_start_date, - billing_end_date, plan_version_id, created_at, created_by, net_terms, - invoice_memo, - invoice_threshold, activated_at, canceled_at, cancellation_reason, currency, - mrr_cents, - period) - VALUES (var_sub_spotify_notion_id, var_cust_spotify_id, 1, - var_tenant_id, NULL, '2023-11-04', NULL, - var_plan_version_notion_id, - '2023-12-04 10:53:00.742', var_user_id, 0, null, null, null, null, null, - 'EUR', 0, - 'MONTHLY'); - - - INSERT INTO public.subscription_component - (id, name, subscription_id, price_component_id, product_item_id, period, fee) - VALUES ('018f0a4f-7bb5-78f4-b239-dece81ee4585', 'Seats', var_sub_spotify_notion_id, - var_comp_notion_seats_id, null, 'MONTHLY', '{ - "Slot": { - "unit": "Seats", - "max_slots": null, - "min_slots": 1, - "unit_rate": "10.00", - "initial_slots": 12 - } - }'); - - - INSERT INTO public.subscription - VALUES (var_sub_comodo_leetcode_id, var_cust_comodo_id, 31, - var_tenant_id, NULL, '2023-11-04', NULL, - var_plan_version_1_leetcode_id, - '2023-12-04 10:58:25.964', var_user_id, 30, null, null, null, null, null, - 'EUR', 0, - 'MONTHLY'); - - INSERT INTO public.subscription_component - (id, name, subscription_id, price_component_id, product_item_id, period, fee) - VALUES ('018f0a4f-9f81-7b70-871f-8efcf61f284c', 'Seats', var_sub_comodo_leetcode_id, - var_comp_leetcode_rate_id, null, 'MONTHLY', '{ - "Rate": { - "rate": "35.00" - } - }'); - - - INSERT INTO public.subscription (id, customer_id, billing_day, tenant_id, trial_start_date, billing_start_date, - billing_end_date, plan_version_id, created_at, created_by, net_terms, - invoice_memo, - invoice_threshold, activated_at, canceled_at, cancellation_reason, currency, - mrr_cents, - period) - VALUES (var_sub_uber_notion_id, var_cust_uber_id, 1, - var_tenant_id, NULL, '2023-11-04', NULL, - var_plan_version_notion_id, - '2023-12-04 10:54:32.056', var_user_id, 0, null, null, null, null, null, - 'EUR', 0, - 'ANNUAL'); - - INSERT INTO public.subscription_component - (id, name, subscription_id, price_component_id, product_item_id, period, fee) - VALUES ('018f0a50-0053-7c41-bd4b-f7bdcca609e7', 'Seats', var_sub_uber_notion_id, - var_comp_notion_seats_id, null, 'ANNUAL', '{ - "Slot": { - "unit": "Seats", - "max_slots": null, - "min_slots": 1, - "unit_rate": "96.00", - "initial_slots": 25 - } - }'); - - - INSERT INTO public.subscription - VALUES (var_sub_uber_leetcode_id, var_cust_uber_id, 15, - var_tenant_id, NULL, '2023-11-04', NULL, - var_plan_version_1_leetcode_id, - '2023-12-04 10:57:38.462', var_user_id, 30, null, null, null, null, null, - 'EUR', 0, - 'ANNUAL'); - - - INSERT INTO public.subscription_component - (id, name, subscription_id, price_component_id, product_item_id, period, fee) - VALUES ('018f0a50-3a67-7448-8235-6ca5a4c75b41', 'Seats', var_sub_uber_leetcode_id, - var_comp_leetcode_rate_id, null, 'ANNUAL', '{ - "Rate": { - "rate": "159.00" - } - }'); - - INSERT INTO public.subscription - VALUES (var_sub_spotify_supabase_id, var_cust_spotify_id, 1, - var_tenant_id, NULL, '2023-11-04', NULL, - var_plan_version_supabase_id, - '2023-12-05 00:31:13.237', var_user_id, 0, null, null, null, null, null, - 'EUR', 0, - 'MONTHLY'); - - INSERT INTO public.subscription_component - (id, name, subscription_id, price_component_id, product_item_id, period, fee) - VALUES ('018f0a50-50f7-779e-9255-cbbad34f5a88', 'Organization Slots', var_sub_spotify_supabase_id, - var_comp_supabase_org_slots_id, null, 'MONTHLY', '{ - "Slot": { - "unit": "Organization", - "max_slots": null, - "min_slots": 1, - "unit_rate": "96.00", - "initial_slots": 15 - } - }'); - - - INSERT INTO public.subscription - VALUES (var_sub_comodo_supabase_id, var_cust_comodo_id, 1, - var_tenant_id, NULL, '2023-11-04', NULL, - var_plan_version_supabase_id, - '2023-12-05 00:31:25.967', var_user_id, 0, null, null, null, null, null, - 'EUR', 0, - 'MONTHLY'); - - INSERT INTO public.subscription_component - (id, name, subscription_id, price_component_id, product_item_id, period, fee) - VALUES ('018f0a50-9bcc-73c8-a3ca-25e2439c1dbd', 'Organization Slots', var_sub_comodo_supabase_id, - var_comp_supabase_org_slots_id, null, 'MONTHLY', '{ - "Slot": { - "unit": "Organization", - "max_slots": null, - "min_slots": 1, - "unit_rate": "96.00", - "initial_slots": 3 - } - }'); - - - END + INSERT INTO public.subscription (id, customer_id, billing_day, tenant_id, trial_start_date, billing_start_date, + billing_end_date, plan_version_id, created_at, created_by, net_terms, + invoice_memo, + invoice_threshold, activated_at, canceled_at, cancellation_reason, currency, + mrr_cents, + period) + VALUES (var_sub_spotify_notion_id, var_cust_spotify_id, 1, + var_tenant_id, NULL, '2023-11-04', NULL, + var_plan_version_notion_id, + '2023-12-04 10:53:00.742', var_user_id, 0, null, null, null, null, null, + 'EUR', 0, + 'MONTHLY'); + + + INSERT INTO public.subscription_component + (id, name, subscription_id, price_component_id, product_id, period, fee) + VALUES ('018f0a4f-7bb5-78f4-b239-dece81ee4585', 'Seats', var_sub_spotify_notion_id, + var_comp_notion_seats_id, null, 'MONTHLY', '{ + "Slot": { + "unit": "Seats", + "max_slots": null, + "min_slots": 1, + "unit_rate": "10.00", + "initial_slots": 12 + } + }'); + + + INSERT INTO public.subscription + VALUES (var_sub_comodo_leetcode_id, var_cust_comodo_id, 31, + var_tenant_id, NULL, '2023-11-04', NULL, + var_plan_version_1_leetcode_id, + '2023-12-04 10:58:25.964', var_user_id, 30, null, null, null, null, null, + 'EUR', 0, + 'MONTHLY'); + + INSERT INTO public.subscription_component + (id, name, subscription_id, price_component_id, product_id, period, fee) + VALUES ('018f0a4f-9f81-7b70-871f-8efcf61f284c', 'Seats', var_sub_comodo_leetcode_id, + var_comp_leetcode_rate_id, null, 'MONTHLY', '{ + "Rate": { + "rate": "35.00" + } + }'); + + + INSERT INTO public.subscription (id, customer_id, billing_day, tenant_id, trial_start_date, billing_start_date, + billing_end_date, plan_version_id, created_at, created_by, net_terms, + invoice_memo, + invoice_threshold, activated_at, canceled_at, cancellation_reason, currency, + mrr_cents, + period) + VALUES (var_sub_uber_notion_id, var_cust_uber_id, 1, + var_tenant_id, NULL, '2023-11-04', NULL, + var_plan_version_notion_id, + '2023-12-04 10:54:32.056', var_user_id, 0, null, null, null, null, null, + 'EUR', 0, + 'ANNUAL'); + + INSERT INTO public.subscription_component + (id, name, subscription_id, price_component_id, product_id, period, fee) + VALUES ('018f0a50-0053-7c41-bd4b-f7bdcca609e7', 'Seats', var_sub_uber_notion_id, + var_comp_notion_seats_id, null, 'ANNUAL', '{ + "Slot": { + "unit": "Seats", + "max_slots": null, + "min_slots": 1, + "unit_rate": "96.00", + "initial_slots": 25 + } + }'); + + + INSERT INTO public.subscription + VALUES (var_sub_uber_leetcode_id, var_cust_uber_id, 15, + var_tenant_id, NULL, '2023-11-04', NULL, + var_plan_version_1_leetcode_id, + '2023-12-04 10:57:38.462', var_user_id, 30, null, null, null, null, null, + 'EUR', 0, + 'ANNUAL'); + + + INSERT INTO public.subscription_component + (id, name, subscription_id, price_component_id, product_id, period, fee) + VALUES ('018f0a50-3a67-7448-8235-6ca5a4c75b41', 'Seats', var_sub_uber_leetcode_id, + var_comp_leetcode_rate_id, null, 'ANNUAL', '{ + "Rate": { + "rate": "159.00" + } + }'); + + INSERT INTO public.subscription + VALUES (var_sub_spotify_supabase_id, var_cust_spotify_id, 1, + var_tenant_id, NULL, '2023-11-04', NULL, + var_plan_version_supabase_id, + '2023-12-05 00:31:13.237', var_user_id, 0, null, null, null, null, null, + 'EUR', 0, + 'MONTHLY'); + + INSERT INTO public.subscription_component + (id, name, subscription_id, price_component_id, product_id, period, fee) + VALUES ('018f0a50-50f7-779e-9255-cbbad34f5a88', 'Organization Slots', var_sub_spotify_supabase_id, + var_comp_supabase_org_slots_id, null, 'MONTHLY', '{ + "Slot": { + "unit": "Organization", + "max_slots": null, + "min_slots": 1, + "unit_rate": "96.00", + "initial_slots": 15 + } + }'); + + + INSERT INTO public.subscription + VALUES (var_sub_comodo_supabase_id, var_cust_comodo_id, 1, + var_tenant_id, NULL, '2023-11-04', NULL, + var_plan_version_supabase_id, + '2023-12-05 00:31:25.967', var_user_id, 0, null, null, null, null, null, + 'EUR', 0, + 'MONTHLY'); + + INSERT INTO public.subscription_component + (id, name, subscription_id, price_component_id, product_id, period, fee) + VALUES ('018f0a50-9bcc-73c8-a3ca-25e2439c1dbd', 'Organization Slots', var_sub_comodo_supabase_id, + var_comp_supabase_org_slots_id, null, 'MONTHLY', '{ + "Slot": { + "unit": "Organization", + "max_slots": null, + "min_slots": 1, + "unit_rate": "96.00", + "initial_slots": 3 + } + }'); + + + END $$; diff --git a/extra/generator/seed.yaml b/extra/generator/seed.yaml index ac21b2a2..bf7c498b 100644 --- a/extra/generator/seed.yaml +++ b/extra/generator/seed.yaml @@ -5,12 +5,12 @@ events_per_second: 200 limit: 5000 events: - event_name: api_request - customer_ids: ["spotify", "uber", "comodo"] + customer_aliases: [ "spotify", "uber", "comodo" ] properties: app_id: type: string endpoint: type: pick - values: ["/api/v1/auth", "/api/v1/checkout", "/api/v3/ride"] + values: [ "/api/v1/auth", "/api/v1/checkout", "/api/v3/ride" ] success: type: bool diff --git a/extra/generator/src/domain.rs b/extra/generator/src/domain.rs index 7402a042..b78f69e0 100644 --- a/extra/generator/src/domain.rs +++ b/extra/generator/src/domain.rs @@ -20,7 +20,7 @@ pub struct Connect { #[derive(Debug, Serialize, Deserialize)] pub struct Schema { pub event_name: String, - pub customer_ids: Vec, + pub customer_aliases: Vec, pub properties: std::collections::HashMap, pub weight: Option, } diff --git a/extra/generator/src/generate.rs b/extra/generator/src/generate.rs index 5efe3f60..7713da21 100644 --- a/extra/generator/src/generate.rs +++ b/extra/generator/src/generate.rs @@ -170,8 +170,8 @@ fn generate_random_data(schema: &Schema) -> Event { Event { event_id: Uuid::new_v4().to_string(), event_name: schema.event_name.clone(), - customer_id: Some(CustomerId::ExternalCustomerId( - schema.customer_ids[fastrand::usize(0..schema.customer_ids.len())].clone(), + customer_id: Some(CustomerId::ExternalCustomerAlias( + schema.customer_aliases[fastrand::usize(0..schema.customer_aliases.len())].clone(), )), timestamp: now.to_rfc3339(), properties, diff --git a/modules/adapters/openstack/src/events.rs b/modules/adapters/openstack/src/events.rs index dc3173a4..d8d53836 100644 --- a/modules/adapters/openstack/src/events.rs +++ b/modules/adapters/openstack/src/events.rs @@ -157,7 +157,7 @@ impl EventHandler { Ok(Some(server::Event { event_id: sample.message_id.clone(), event_name: format!("openstack.{}", sample.counter_name), - customer_id: Some(server::event::CustomerId::ExternalCustomerId( + customer_id: Some(server::event::CustomerId::ExternalCustomerAlias( sample.project_id.clone(), )), timestamp: sample.timestamp.clone(), @@ -215,7 +215,7 @@ impl EventHandler { Ok(Some(server::Event { event_id: event.message_id.clone(), event_name: format!("openstack.{}", event.event_type), - customer_id: Some(server::event::CustomerId::ExternalCustomerId( + customer_id: Some(server::event::CustomerId::ExternalCustomerAlias( project_id.to_string(), )), timestamp: timestamp.clone(), diff --git a/modules/adapters/slurm-collector/src/main.rs b/modules/adapters/slurm-collector/src/main.rs index 7ef1c76f..eccbf455 100644 --- a/modules/adapters/slurm-collector/src/main.rs +++ b/modules/adapters/slurm-collector/src/main.rs @@ -246,7 +246,7 @@ async fn send_batch_to_api(client: &mut GrpcClient, batch: &[SacctData]) -> Resu event_id: data.id.clone(), event_name: "slurm_job".to_string(), customer_id: Some( - metering_grpc::meteroid::metering::v1::event::CustomerId::ExternalCustomerId(data.account.clone()) + metering_grpc::meteroid::metering::v1::event::CustomerId::ExternalCustomerAlias(data.account.clone()) ), timestamp: data.start_time.to_rfc3339(), properties, diff --git a/modules/metering/proto/meters.proto b/modules/metering/proto/meters.proto index b31ce016..1b039257 100644 --- a/modules/metering/proto/meters.proto +++ b/modules/metering/proto/meters.proto @@ -17,7 +17,8 @@ message RegisterMeterResponse { } message UnregisterMeterRequest { - ResourceIdentifier meter = 1; + string id = 1; + string tenant_id = 2; } message UnregisterMeterResponse {} diff --git a/modules/metering/proto/models.proto b/modules/metering/proto/models.proto index c649e211..5f1f32aa 100644 --- a/modules/metering/proto/models.proto +++ b/modules/metering/proto/models.proto @@ -2,14 +2,12 @@ syntax = "proto3"; package meteroid.metering.v1; -import "google/protobuf/timestamp.proto"; - message Event { string event_id = 1; string event_name = 2; oneof customer_id { string meteroid_customer_id = 3; - string external_customer_id = 4; + string external_customer_alias = 4; // TODO we can allow external_subscription_id as well if a resource is linked to a specific subscription (and if we don't have the customer_id) } // rfc3339 string @@ -25,7 +23,7 @@ message Metadata { } message Meter { - string meter_slug = 1; + string id = 1; // id by default is local_id. We could at some point support metric alias for external implementations of metering, & use this field string event_name = 3; // TODO used ? (can we store metadata in clickhouse ? if yes do we want some metadata field instead) @@ -56,8 +54,7 @@ message Meter { // segmentation matrix (or that's just the group_by_dimensions ?) } -message ResourceIdentifier { - string meteroid_id = 1; - // the identifier of the resource within the customer's system (associated with the Meteroid's resource) - string external_id = 2; +message CustomerIdentifier { + string local_id = 1; + optional string alias = 2; } diff --git a/modules/metering/proto/queries.proto b/modules/metering/proto/queries.proto index 966d9b10..1745deb4 100644 --- a/modules/metering/proto/queries.proto +++ b/modules/metering/proto/queries.proto @@ -2,10 +2,8 @@ syntax = "proto3"; package meteroid.metering.v1; -import "common/v1/date.proto"; import "common/v1/decimal.proto"; import "google/protobuf/timestamp.proto"; -import "google/protobuf/wrappers.proto"; import "models.proto"; message Filter { @@ -18,7 +16,7 @@ message QueryMeterRequest { string tenant_id = 1; string meter_slug = 2; Meter.AggregationType meter_aggregation_type = 3; - repeated ResourceIdentifier customers = 4; + repeated CustomerIdentifier customers = 4; google.protobuf.Timestamp from = 5; google.protobuf.Timestamp to = 6; // If null, default to WindowSize.AGGREGATE_ALL diff --git a/modules/metering/src/cache.rs b/modules/metering/src/cache.rs index 9617bdb8..3a741109 100644 --- a/modules/metering/src/cache.rs +++ b/modules/metering/src/cache.rs @@ -5,7 +5,8 @@ use std::sync::Arc; // type IdentifierCache = Lazy>>; // pub static CUSTOMER_ID_CACHE: IdentifierCache = Lazy::new(|| RwLock::new(SizedCache::with_size(10000))); -type IdentifierCache = Lazy>>; +type TenantAliasTuple = (String, String); +type IdentifierCache = Lazy>>; pub static CUSTOMER_ID_CACHE: IdentifierCache = Lazy::new(|| Arc::new(Cache::new(10000))); // TODO add an optional redis on top diff --git a/modules/metering/src/connectors/clickhouse/sql/create_meter.rs b/modules/metering/src/connectors/clickhouse/sql/create_meter.rs index 276374c4..e4c1f4d1 100644 --- a/modules/metering/src/connectors/clickhouse/sql/create_meter.rs +++ b/modules/metering/src/connectors/clickhouse/sql/create_meter.rs @@ -85,7 +85,7 @@ fn create_meter_view_to_select_sql(meter: Meter) -> String { } pub fn create_meter_view(meter: Meter, populate: bool) -> String { - let view_name = get_meter_view_name(&meter.namespace, &meter.meter_slug); + let view_name = get_meter_view_name(&meter.namespace, &meter.id); let mut columns = vec![ Column { name: "customer_id".to_string(), @@ -159,7 +159,7 @@ mod tests { fn test_create_meter_view_count() { let meter = Meter { namespace: "test_namespace".to_string(), - meter_slug: "test_slug".to_string(), + id: "test_slug".to_string(), event_name: "test_event".to_string(), aggregation: MeterAggregation::Count, group_by: vec!["test_group1".to_string(), "test_group2".to_string()], diff --git a/modules/metering/src/connectors/clickhouse/sql/query_meter.rs b/modules/metering/src/connectors/clickhouse/sql/query_meter.rs index 4177fbce..836348d8 100644 --- a/modules/metering/src/connectors/clickhouse/sql/query_meter.rs +++ b/modules/metering/src/connectors/clickhouse/sql/query_meter.rs @@ -96,7 +96,7 @@ pub fn query_meter_view_sql(params: QueryMeterParams) -> Result let subjects_condition = params .customers .iter() - .map(|customer| format!("customer_id = '{}'", customer.id)) // TODO config for id/ext/custom field + .map(|customer| format!("customer_id = '{}'", customer.local_id)) // TODO config for id/ext/custom field .collect::>() .join(" OR "); where_clauses.push(format!("({})", subjects_condition)); diff --git a/modules/metering/src/domain.rs b/modules/metering/src/domain.rs index 2b704d68..b281387b 100644 --- a/modules/metering/src/domain.rs +++ b/modules/metering/src/domain.rs @@ -38,7 +38,7 @@ pub enum WindowSize { pub struct Meter { pub aggregation: MeterAggregation, pub namespace: String, - pub meter_slug: String, + pub id: String, pub event_name: String, pub value_property: Option, pub group_by: Vec, @@ -46,8 +46,8 @@ pub struct Meter { #[derive(Debug, Clone)] pub struct Customer { - pub id: String, - pub external_id: String, + pub local_id: String, + pub alias: Option, // pub custom_fields: HashMap, } diff --git a/modules/metering/src/ingest/service.rs b/modules/metering/src/ingest/service.rs index 985879ef..7f245d4b 100644 --- a/modules/metering/src/ingest/service.rs +++ b/modules/metering/src/ingest/service.rs @@ -14,7 +14,7 @@ use crate::ingest::domain::{FailedEvent, ProcessedEvent}; use crate::ingest::sinks::Sink; use common_grpc::middleware::server::auth::RequestExt; use meteroid_grpc::meteroid::internal::v1::internal_service_client::InternalServiceClient; -use meteroid_grpc::meteroid::internal::v1::ResolveCustomerExternalIdsRequest; +use meteroid_grpc::meteroid::internal::v1::ResolveCustomerAliasesRequest; #[derive(Clone)] pub struct EventsService { @@ -62,19 +62,18 @@ impl EventsServiceGrpc for EventsService { // - get the customer_id from external_customer_id as necessary let mut resolved = vec![]; let mut unresolved = vec![]; - let mut unresolved_ids = vec![]; + let mut unresolved_aliases = vec![]; let now = chrono::Utc::now(); for event in events { match validate_event(&event, &now, allow_backfilling) { Ok((id, ts)) => match id { - CustomerId::MeteroidCustomerId(meteroid_id) => resolved.push( - to_processed_event(event, meteroid_id, tenant_id.clone(), ts), - ), - CustomerId::ExternalCustomerId(external_id) => { - let from_cache = - CUSTOMER_ID_CACHE.get(&(tenant_id.clone(), external_id.clone())); + CustomerId::MeteroidCustomerId(local_id) => { + resolved.push(to_processed_event(event, local_id, tenant_id.clone(), ts)) + } + CustomerId::ExternalCustomerAlias(alias) => { + let from_cache = CUSTOMER_ID_CACHE.get(&(tenant_id.clone(), alias.clone())); match from_cache { Some(meteroid_id) => resolved.push(to_processed_event( event, @@ -83,8 +82,8 @@ impl EventsServiceGrpc for EventsService { ts, )), None => { - unresolved_ids.push(external_id.clone()); - unresolved.push((event, external_id.clone(), ts)) + unresolved_aliases.push(alias.clone()); + unresolved.push((event, alias.clone(), ts)) } } } @@ -98,15 +97,15 @@ impl EventsServiceGrpc for EventsService { }; } - if !unresolved_ids.is_empty() { + if !unresolved_aliases.is_empty() { // we call the api to resolve customers by external id & tenant let mut client = self.internal_client.clone(); let res = client - .resolve_customer_external_ids(ResolveCustomerExternalIdsRequest { + .resolve_customer_aliases(ResolveCustomerAliasesRequest { tenant_id: tenant_id.clone(), - external_ids: unresolved_ids, + aliases: unresolved_aliases, }) .await .map_err(|e| { @@ -117,31 +116,33 @@ impl EventsServiceGrpc for EventsService { let res = res.into_inner(); - res.unresolved_ids.into_iter().for_each(|external_id| { - failed_events.push(FailedEvent { - event: unresolved - .iter() - .find(|(_, id, _)| id == &external_id) - .unwrap() - .0 - .clone(), - reason: "Unable to resolve external id".to_string(), - }) - }); + res.unresolved_aliases + .into_iter() + .for_each(|unresolved_alias| { + failed_events.push(FailedEvent { + event: unresolved + .iter() + .find(|(_, alias, _)| alias == &unresolved_alias) + .unwrap() + .0 + .clone(), + reason: "Unable to resolve unresolved alias".to_string(), + }) + }); res.customers.into_iter().for_each(|customer| { CUSTOMER_ID_CACHE.insert( - (tenant_id.clone(), customer.external_id.clone()), - customer.meteroid_id.clone(), + (tenant_id.clone(), customer.alias.clone()), + customer.local_id.clone(), ); let (event, _, ts) = unresolved .iter() - .find(|(_, id, _)| id == &customer.external_id) + .find(|(_, alias, _)| alias == &customer.alias) .unwrap(); resolved.push(to_processed_event( event.clone(), - customer.meteroid_id, + customer.local_id, tenant_id.clone(), *ts, )) diff --git a/modules/metering/src/meters/service.rs b/modules/metering/src/meters/service.rs index 15cd2769..a86589f3 100644 --- a/modules/metering/src/meters/service.rs +++ b/modules/metering/src/meters/service.rs @@ -44,7 +44,7 @@ impl MetersServiceGrpc for MetersService { let meter = Meter { aggregation: meter_aggregation, namespace: req.tenant_id, - meter_slug: meter.meter_slug, + id: meter.id, event_name: meter.event_name, value_property: meter.aggregation_key, group_by: meter.dimensions, diff --git a/modules/metering/src/query/service.rs b/modules/metering/src/query/service.rs index 80b8b278..cca6515c 100644 --- a/modules/metering/src/query/service.rs +++ b/modules/metering/src/query/service.rs @@ -63,8 +63,8 @@ impl UsageQueryServiceGrpc for UsageQueryService { .customers .iter() .map(|c| Customer { - id: c.meteroid_id.clone(), - external_id: c.external_id.clone(), + alias: c.alias.clone(), + local_id: c.local_id.clone(), }) .collect(), group_by: req.group_by_properties, diff --git a/modules/meteroid/crates/diesel-models/src/add_ons.rs b/modules/meteroid/crates/diesel-models/src/add_ons.rs index 28f05bcb..6b4d2e73 100644 --- a/modules/meteroid/crates/diesel-models/src/add_ons.rs +++ b/modules/meteroid/crates/diesel-models/src/add_ons.rs @@ -12,6 +12,7 @@ pub struct AddOnRow { pub tenant_id: Uuid, pub created_at: NaiveDateTime, pub updated_at: NaiveDateTime, + pub local_id: String, } #[derive(Debug, Default, Insertable)] @@ -22,6 +23,7 @@ pub struct AddOnRowNew { pub name: String, pub fee: serde_json::Value, pub tenant_id: Uuid, + pub local_id: String, } #[derive(AsChangeset)] diff --git a/modules/meteroid/crates/diesel-models/src/billable_metrics.rs b/modules/meteroid/crates/diesel-models/src/billable_metrics.rs index b953dd38..08a76b4f 100644 --- a/modules/meteroid/crates/diesel-models/src/billable_metrics.rs +++ b/modules/meteroid/crates/diesel-models/src/billable_metrics.rs @@ -25,6 +25,8 @@ pub struct BillableMetricRow { pub archived_at: Option, pub tenant_id: Uuid, pub product_family_id: Uuid, + pub product_id: Option, + pub local_id: String, } #[derive(Debug, Clone, Insertable)] @@ -44,6 +46,8 @@ pub struct BillableMetricRowNew { pub created_by: Uuid, pub tenant_id: Uuid, pub product_family_id: Uuid, + pub product_id: Option, + pub local_id: String, } #[derive(Debug, Identifiable, Queryable, Selectable)] @@ -51,6 +55,7 @@ pub struct BillableMetricRowNew { #[diesel(check_for_backend(diesel::pg::Pg))] pub struct BillableMetricMetaRow { pub id: Uuid, + pub local_id: String, pub name: String, pub code: String, pub aggregation_type: BillingMetricAggregateEnum, diff --git a/modules/meteroid/crates/diesel-models/src/coupons.rs b/modules/meteroid/crates/diesel-models/src/coupons.rs index f5139b9f..f1ae8c09 100644 --- a/modules/meteroid/crates/diesel-models/src/coupons.rs +++ b/modules/meteroid/crates/diesel-models/src/coupons.rs @@ -18,9 +18,10 @@ pub struct CouponRow { pub created_at: NaiveDateTime, pub updated_at: NaiveDateTime, pub redemption_count: i32, - pub disabled: bool, pub last_redemption_at: Option, + pub disabled: bool, pub archived_at: Option, + pub local_id: String, } #[derive(Debug, Default, Insertable)] @@ -28,6 +29,7 @@ pub struct CouponRow { #[diesel(check_for_backend(diesel::pg::Pg))] pub struct CouponRowNew { pub id: Uuid, + pub local_id: String, pub code: String, pub description: String, pub tenant_id: Uuid, diff --git a/modules/meteroid/crates/diesel-models/src/credit_notes.rs b/modules/meteroid/crates/diesel-models/src/credit_notes.rs index 71c948d2..5fe06041 100644 --- a/modules/meteroid/crates/diesel-models/src/credit_notes.rs +++ b/modules/meteroid/crates/diesel-models/src/credit_notes.rs @@ -20,4 +20,5 @@ pub struct CreditNoteRow { pub tenant_id: Uuid, pub customer_id: Uuid, pub status: CreditNoteStatus, + pub local_id: String, } diff --git a/modules/meteroid/crates/diesel-models/src/customers.rs b/modules/meteroid/crates/diesel-models/src/customers.rs index 5ae51ef3..6b6fee90 100644 --- a/modules/meteroid/crates/diesel-models/src/customers.rs +++ b/modules/meteroid/crates/diesel-models/src/customers.rs @@ -25,6 +25,7 @@ pub struct CustomerRow { pub billing_address: Option, pub shipping_address: Option, pub invoicing_entity_id: Uuid, + pub local_id: String, } #[derive(Clone, Debug, Queryable, Selectable)] @@ -32,6 +33,7 @@ pub struct CustomerRow { #[diesel(check_for_backend(diesel::pg::Pg))] pub struct CustomerBriefRow { pub id: Uuid, + pub local_id: String, pub name: String, pub alias: Option, } @@ -41,6 +43,7 @@ pub struct CustomerBriefRow { #[diesel(check_for_backend(diesel::pg::Pg))] pub struct CustomerRowNew { pub id: Uuid, + pub local_id: String, pub name: String, pub created_by: Uuid, pub tenant_id: Uuid, @@ -75,21 +78,7 @@ pub struct CustomerRowPatch { pub invoicing_entity_id: Option, } -#[derive(AsChangeset, Debug)] -#[diesel(table_name = crate::schema::customer)] -pub struct CustomerRowAsChangeset { - pub name: String, - pub billing_config: Option, - pub alias: Option, - pub email: Option, - pub invoicing_email: Option, - pub phone: Option, - pub balance_value_cents: i32, - pub currency: String, - pub billing_address: Option, - pub shipping_address: Option, -} - +// TODO unused #[derive(Debug)] pub enum CustomerUpdate { UpdateDetails { diff --git a/modules/meteroid/crates/diesel-models/src/invoices.rs b/modules/meteroid/crates/diesel-models/src/invoices.rs index fd95274b..849e565e 100644 --- a/modules/meteroid/crates/diesel-models/src/invoices.rs +++ b/modules/meteroid/crates/diesel-models/src/invoices.rs @@ -5,7 +5,7 @@ use chrono::NaiveDate; use chrono::NaiveDateTime; use crate::customers::CustomerRow; -use crate::plan_versions::PlanVersionRowLatest; +use crate::plan_versions::PlanVersionRowOverview; use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable}; use uuid::Uuid; @@ -124,5 +124,5 @@ pub struct DetailedInvoiceRow { #[diesel(embed)] pub customer: CustomerRow, #[diesel(embed)] - pub plan: Option, + pub plan: Option, } diff --git a/modules/meteroid/crates/diesel-models/src/lib.rs b/modules/meteroid/crates/diesel-models/src/lib.rs index 20414a89..7053c4a1 100644 --- a/modules/meteroid/crates/diesel-models/src/lib.rs +++ b/modules/meteroid/crates/diesel-models/src/lib.rs @@ -45,3 +45,5 @@ use diesel_async::AsyncPgConnection; pub type DbResult = Result; pub type PgConn = Object; + +pub mod aliases {} diff --git a/modules/meteroid/crates/diesel-models/src/plan_versions.rs b/modules/meteroid/crates/diesel-models/src/plan_versions.rs index 0b801354..591ffed8 100644 --- a/modules/meteroid/crates/diesel-models/src/plan_versions.rs +++ b/modules/meteroid/crates/diesel-models/src/plan_versions.rs @@ -1,7 +1,7 @@ use chrono::NaiveDateTime; use uuid::Uuid; -use crate::enums::{ActionAfterTrialEnum, BillingPeriodEnum}; +use crate::enums::ActionAfterTrialEnum; use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable}; @@ -18,11 +18,11 @@ pub struct PlanVersionRow { pub tenant_id: Uuid, pub period_start_day: Option, pub net_terms: i32, + // TODO is this used ? or always the tenant currency ? pub currency: String, pub billing_cycles: Option, pub created_at: NaiveDateTime, pub created_by: Uuid, - pub billing_periods: Vec>, pub trialing_plan_id: Option, pub action_after_trial: Option, pub trial_is_free: bool, @@ -44,7 +44,6 @@ pub struct PlanVersionRowNew { pub currency: String, pub billing_cycles: Option, pub created_by: Uuid, - pub billing_periods: Vec, pub trialing_plan_id: Option, pub action_after_trial: Option, pub trial_is_free: bool, @@ -53,15 +52,15 @@ pub struct PlanVersionRowNew { #[derive(Debug, Queryable, Identifiable, Selectable)] #[diesel(table_name = crate::schema::plan_version)] #[diesel(check_for_backend(diesel::pg::Pg))] -pub struct PlanVersionRowLatest { +pub struct PlanVersionRowOverview { pub id: Uuid, pub plan_id: Uuid, #[diesel(select_expression = crate::schema::plan::name)] #[diesel(select_expression_type = crate::schema::plan::name)] pub plan_name: String, - #[diesel(select_expression = crate::schema::plan::external_id)] - #[diesel(select_expression_type = crate::schema::plan::external_id)] - pub external_id: String, + #[diesel(select_expression = crate::schema::plan::local_id)] + #[diesel(select_expression_type = crate::schema::plan::local_id)] + pub local_id: String, pub version: i32, pub created_by: Uuid, pub trial_duration_days: Option, @@ -89,7 +88,6 @@ pub struct PlanVersionRowPatch { pub tenant_id: Uuid, pub currency: Option, pub net_terms: Option, - pub billing_periods: Option>, } #[derive(Debug, AsChangeset)] @@ -105,3 +103,10 @@ pub struct PlanVersionTrialRowPatch { pub trial_duration_days: Option>, pub downgrade_plan_id: Option>, } + +#[derive(Debug, Clone)] +pub enum PlanVersionFilter { + Draft, + Active, + Version(i32), +} diff --git a/modules/meteroid/crates/diesel-models/src/plans.rs b/modules/meteroid/crates/diesel-models/src/plans.rs index 3ddf22aa..6e578bf5 100644 --- a/modules/meteroid/crates/diesel-models/src/plans.rs +++ b/modules/meteroid/crates/diesel-models/src/plans.rs @@ -18,9 +18,11 @@ pub struct PlanRow { pub archived_at: Option, pub tenant_id: Uuid, pub product_family_id: Uuid, - pub external_id: String, + pub local_id: String, pub plan_type: PlanTypeEnum, pub status: PlanStatusEnum, + pub active_version_id: Option, + pub draft_version_id: Option, } #[derive(Debug, Default, Insertable)] @@ -33,31 +35,30 @@ pub struct PlanRowNew { pub created_by: Uuid, pub tenant_id: Uuid, pub product_family_id: Uuid, - pub external_id: String, - + pub local_id: String, pub plan_type: PlanTypeEnum, pub status: PlanStatusEnum, } -#[derive(Queryable, Debug, Identifiable, Selectable)] -#[diesel(table_name = crate::schema::plan)] -#[diesel(check_for_backend(diesel::pg::Pg))] -pub struct PlanRowForList { +#[derive(Debug, Queryable)] +pub struct PlanRowOverview { pub id: Uuid, pub name: String, pub description: Option, pub created_at: NaiveDateTime, - pub created_by: Uuid, - pub updated_at: Option, - pub archived_at: Option, - pub tenant_id: Uuid, - pub product_family_id: Uuid, - pub external_id: String, + pub local_id: String, pub plan_type: PlanTypeEnum, pub status: PlanStatusEnum, - #[diesel(select_expression = crate::schema::product_family::name)] - #[diesel(select_expression_type = crate::schema::product_family::name)] pub product_family_name: String, + pub active_version: Option, + pub draft_version: Option, + pub subscription_count: Option, +} + +#[derive(Debug, Queryable)] +pub struct PlanVersionRowInfo { + pub version: i32, + pub trial_duration_days: Option, } #[derive(Debug, Queryable, Selectable)] @@ -66,7 +67,7 @@ pub struct PlanWithVersionRow { #[diesel(embed)] pub plan: PlanRow, #[diesel(embed)] - pub version: PlanVersionRow, + pub version: Option, } #[derive(Debug, AsChangeset)] @@ -78,6 +79,8 @@ pub struct PlanRowPatch { pub tenant_id: Uuid, pub name: Option, pub description: Option>, + pub active_version_id: Option>, + pub draft_version_id: Option>, } pub struct PlanFilters { diff --git a/modules/meteroid/crates/diesel-models/src/price_components.rs b/modules/meteroid/crates/diesel-models/src/price_components.rs index 6c439d83..f4da5819 100644 --- a/modules/meteroid/crates/diesel-models/src/price_components.rs +++ b/modules/meteroid/crates/diesel-models/src/price_components.rs @@ -10,8 +10,9 @@ pub struct PriceComponentRow { pub name: String, pub fee: serde_json::Value, pub plan_version_id: Uuid, - pub product_item_id: Option, + pub product_id: Option, pub billable_metric_id: Option, + pub local_id: String, } #[derive(Debug, Default, Insertable)] @@ -19,10 +20,11 @@ pub struct PriceComponentRow { #[diesel(check_for_backend(diesel::pg::Pg))] pub struct PriceComponentRowNew { pub id: Uuid, + pub local_id: String, pub name: String, pub fee: serde_json::Value, pub plan_version_id: Uuid, - pub product_item_id: Option, + pub product_id: Option, pub billable_metric_id: Option, } diff --git a/modules/meteroid/crates/diesel-models/src/product_families.rs b/modules/meteroid/crates/diesel-models/src/product_families.rs index f21ee9a1..18111dd4 100644 --- a/modules/meteroid/crates/diesel-models/src/product_families.rs +++ b/modules/meteroid/crates/diesel-models/src/product_families.rs @@ -9,7 +9,7 @@ use diesel::{Identifiable, Insertable, Queryable}; pub struct ProductFamilyRow { pub id: Uuid, pub name: String, - pub external_id: String, + pub local_id: String, pub created_at: NaiveDateTime, pub updated_at: Option, pub archived_at: Option, @@ -22,6 +22,6 @@ pub struct ProductFamilyRow { pub struct ProductFamilyRowNew { pub id: Uuid, pub name: String, - pub external_id: String, + pub local_id: String, pub tenant_id: Uuid, } diff --git a/modules/meteroid/crates/diesel-models/src/products.rs b/modules/meteroid/crates/diesel-models/src/products.rs index 1a665f03..da659509 100644 --- a/modules/meteroid/crates/diesel-models/src/products.rs +++ b/modules/meteroid/crates/diesel-models/src/products.rs @@ -16,6 +16,7 @@ pub struct ProductRow { pub archived_at: Option, pub tenant_id: Uuid, pub product_family_id: Uuid, + pub local_id: String, } #[derive(Debug, Insertable)] @@ -23,6 +24,7 @@ pub struct ProductRow { #[diesel(check_for_backend(diesel::pg::Pg))] pub struct ProductRowNew { pub id: Uuid, + pub local_id: String, pub name: String, pub description: Option, pub created_by: Uuid, diff --git a/modules/meteroid/crates/diesel-models/src/query/billable_metrics.rs b/modules/meteroid/crates/diesel-models/src/query/billable_metrics.rs index ca82ee30..a0464bfd 100644 --- a/modules/meteroid/crates/diesel-models/src/query/billable_metrics.rs +++ b/modules/meteroid/crates/diesel-models/src/query/billable_metrics.rs @@ -67,7 +67,7 @@ impl BillableMetricRow { conn: &mut PgConn, param_tenant_id: uuid::Uuid, pagination: PaginationRequest, - param_product_family_external_id: String, + param_product_family_local_id: String, ) -> DbResult> { use crate::schema::billable_metric::dsl as bm_dsl; use crate::schema::product_family::dsl as pf_dsl; @@ -75,7 +75,7 @@ impl BillableMetricRow { let query = bm_dsl::billable_metric .inner_join(pf_dsl::product_family.on(bm_dsl::product_family_id.eq(pf_dsl::id))) .filter(bm_dsl::tenant_id.eq(param_tenant_id)) - .filter(pf_dsl::external_id.eq(param_product_family_external_id)) + .filter(pf_dsl::local_id.eq(param_product_family_local_id)) .order(bm_dsl::created_at.asc()) .select(BillableMetricMetaRow::as_select()); diff --git a/modules/meteroid/crates/diesel-models/src/query/plan_versions.rs b/modules/meteroid/crates/diesel-models/src/query/plan_versions.rs index 43f853c0..431e3cf9 100644 --- a/modules/meteroid/crates/diesel-models/src/query/plan_versions.rs +++ b/modules/meteroid/crates/diesel-models/src/query/plan_versions.rs @@ -1,6 +1,6 @@ use crate::errors::IntoDbResult; use crate::plan_versions::{ - PlanVersionRow, PlanVersionRowLatest, PlanVersionRowNew, PlanVersionRowPatch, + PlanVersionRow, PlanVersionRowNew, PlanVersionRowOverview, PlanVersionRowPatch, PlanVersionTrialRowPatch, }; @@ -204,11 +204,11 @@ impl PlanVersionRow { } } -impl PlanVersionRowLatest { +impl PlanVersionRowOverview { pub async fn list( conn: &mut PgConn, tenant_id: uuid::Uuid, - ) -> DbResult> { + ) -> DbResult> { use crate::schema::plan::dsl as p_dsl; use crate::schema::plan_version::dsl as pv_dsl; use crate::schema::product_family::dsl as pf_dsl; @@ -225,7 +225,7 @@ impl PlanVersionRowLatest { pv_dsl::created_at.desc(), )) .distinct_on(pv_dsl::plan_id) - .select(PlanVersionRowLatest::as_select()); + .select(PlanVersionRowOverview::as_select()); log::debug!("{}", debug_query::(&query).to_string()); diff --git a/modules/meteroid/crates/diesel-models/src/query/plans.rs b/modules/meteroid/crates/diesel-models/src/query/plans.rs index bdf80cd4..967b3949 100644 --- a/modules/meteroid/crates/diesel-models/src/query/plans.rs +++ b/modules/meteroid/crates/diesel-models/src/query/plans.rs @@ -1,6 +1,7 @@ use crate::errors::IntoDbResult; +use crate::plan_versions::PlanVersionFilter; use crate::plans::{ - PlanFilters, PlanRow, PlanRowForList, PlanRowNew, PlanRowPatch, PlanWithVersionRow, + PlanFilters, PlanRow, PlanRowNew, PlanRowOverview, PlanRowPatch, PlanWithVersionRow, }; use std::collections::HashMap; @@ -9,9 +10,11 @@ use crate::{DbResult, PgConn}; use crate::enums::PlanStatusEnum; use crate::extend::order::OrderByRequest; use crate::extend::pagination::{Paginate, PaginatedVec, PaginationRequest}; + +use diesel::NullableExpressionMethods; use diesel::{ - debug_query, BoolExpressionMethods, ExpressionMethods, JoinOnDsl, PgTextExpressionMethods, - QueryDsl, SelectableHelper, + alias, debug_query, BoolExpressionMethods, ExpressionMethods, JoinOnDsl, + PgTextExpressionMethods, QueryDsl, SelectableHelper, }; use error_stack::ResultExt; use uuid::Uuid; @@ -34,48 +37,6 @@ impl PlanRowNew { } impl PlanRow { - pub async fn get_by_external_id_and_tenant_id( - conn: &mut PgConn, - external_id: &str, - tenant_id: Uuid, - ) -> DbResult { - use crate::schema::plan::dsl as p_dsl; - use diesel_async::RunQueryDsl; - - let query = p_dsl::plan - .filter(p_dsl::external_id.eq(external_id)) - .filter(p_dsl::tenant_id.eq(tenant_id)); - - log::debug!("{}", debug_query::(&query).to_string()); - - query - .first(conn) - .await - .attach_printable("Error while getting plan") - .into_db_result() - } - - pub async fn get_by_id_and_tenant_id( - conn: &mut PgConn, - id: Uuid, - tenant_id: Uuid, - ) -> DbResult { - use crate::schema::plan::dsl as p_dsl; - use diesel_async::RunQueryDsl; - - let query = p_dsl::plan - .filter(p_dsl::id.eq(id)) - .filter(p_dsl::tenant_id.eq(tenant_id)); - - log::debug!("{}", debug_query::(&query).to_string()); - - query - .first(conn) - .await - .attach_printable("Error while getting plan") - .into_db_result() - } - pub async fn activate(conn: &mut PgConn, id: Uuid, tenant_id: Uuid) -> DbResult { use crate::schema::plan::dsl as p_dsl; use diesel_async::RunQueryDsl; @@ -146,19 +107,97 @@ impl PlanRow { .into_db_result() } - pub async fn get_with_version_by_external_id( + pub async fn get_overview_by_local_id( conn: &mut PgConn, - external_id: &str, + local_id: &str, tenant_id: Uuid, - ) -> DbResult { + ) -> DbResult { use crate::schema::plan::dsl as p_dsl; + use crate::schema::plan_version; use crate::schema::plan_version::dsl as pv_dsl; + use crate::schema::product_family::dsl as pf_dsl; use diesel_async::RunQueryDsl; + let (active_version_alias, draft_version_alias) = alias!( + plan_version as active_version_alias, + plan_version as draft_version_alias + ); + let query = p_dsl::plan - .inner_join(pv_dsl::plan_version.on(p_dsl::id.eq(pv_dsl::plan_id))) - .filter(p_dsl::external_id.eq(external_id)) + .inner_join(pf_dsl::product_family.on(p_dsl::product_family_id.eq(pf_dsl::id))) .filter(p_dsl::tenant_id.eq(tenant_id)) + .filter(p_dsl::local_id.eq(local_id)) + .left_join( + active_version_alias.on(active_version_alias + .field(plan_version::id) + .nullable() + .eq(p_dsl::active_version_id)), + ) + .left_join( + draft_version_alias.on(draft_version_alias + .field(plan_version::id) + .nullable() + .eq(p_dsl::draft_version_id)), + ) + .select(( + p_dsl::id, + p_dsl::name, + p_dsl::description, + p_dsl::created_at, + p_dsl::local_id, + p_dsl::plan_type, + p_dsl::status, + pf_dsl::name, + active_version_alias + .fields((pv_dsl::version, pv_dsl::trial_duration_days)) + .nullable(), + draft_version_alias.field(pv_dsl::id).nullable(), + diesel::dsl::sql::>("null"), + )); + + log::debug!("{}", debug_query::(&query).to_string()); + + query + .get_result(conn) + .await + .attach_printable("Error while getting plan with version") + .into_db_result() + } + + pub async fn get_with_version_by_local_id( + conn: &mut PgConn, + local_id: &str, + tenant_id: Uuid, + version_filter: PlanVersionFilter, + ) -> DbResult { + use crate::schema::plan::dsl as p_dsl; + use crate::schema::plan_version::dsl as pv_dsl; + use diesel_async::RunQueryDsl; + + let mut query = p_dsl::plan + .left_join(pv_dsl::plan_version.on(p_dsl::id.eq(pv_dsl::plan_id))) + .filter(p_dsl::local_id.eq(local_id)) + .filter(p_dsl::tenant_id.eq(tenant_id)) + .into_boxed(); + + match version_filter { + PlanVersionFilter::Draft => { + query = query + .filter(p_dsl::draft_version_id.is_not_null()) + .filter(pv_dsl::id.nullable().eq(p_dsl::draft_version_id)); + } + PlanVersionFilter::Active => { + query = query + .filter(p_dsl::active_version_id.is_not_null()) + .filter(pv_dsl::id.nullable().eq(p_dsl::active_version_id)); + } + PlanVersionFilter::Version(v) => { + query = query.filter(pv_dsl::version.eq(v)); + } + } + + // Finalize the query + let query = query .order(pv_dsl::version.desc()) .select(PlanWithVersionRow::as_select()); @@ -167,31 +206,78 @@ impl PlanRow { query .first(conn) .await - .attach_printable("Error while getting plan with version by external_id") + .attach_printable("Error while getting plan with version by local_id") .into_db_result() } } -impl PlanRowForList { +impl PlanRowOverview { pub async fn list( conn: &mut PgConn, tenant_id: Uuid, - product_family_external_id: Option, + product_family_local_id: Option, filters: PlanFilters, pagination: PaginationRequest, order_by: OrderByRequest, - ) -> DbResult> { + ) -> DbResult> { use crate::schema::plan::dsl as p_dsl; + use crate::schema::plan_version; + use crate::schema::plan_version::dsl as pv_dsl; use crate::schema::product_family::dsl as pf_dsl; + use crate::schema::subscription::dsl as s_dsl; + use diesel::dsl::today; + + let (active_version_alias, draft_version_alias) = alias!( + plan_version as active_version_alias, + plan_version as draft_version_alias + ); + + let active_subscriptions_count_subselect = s_dsl::subscription + .inner_join(pv_dsl::plan_version.on(s_dsl::plan_version_id.eq(pv_dsl::id))) + .filter(pv_dsl::plan_id.eq(p_dsl::id)) + .filter(s_dsl::billing_start_date.le(today)) + .filter( + s_dsl::billing_end_date + .is_null() + .or(s_dsl::billing_end_date.nullable().ge(today)), + ) + .count() + .single_value(); // single_value transforms the query in subquery let mut query = p_dsl::plan .inner_join(pf_dsl::product_family.on(p_dsl::product_family_id.eq(pf_dsl::id))) .filter(p_dsl::tenant_id.eq(tenant_id)) - .select(PlanRowForList::as_select()) + .left_join( + active_version_alias.on(active_version_alias + .field(plan_version::id) + .nullable() + .eq(p_dsl::active_version_id)), + ) + .left_join( + draft_version_alias.on(draft_version_alias + .field(plan_version::id) + .nullable() + .eq(p_dsl::draft_version_id)), + ) + .select(( + p_dsl::id, + p_dsl::name, + p_dsl::description, + p_dsl::created_at, + p_dsl::local_id, + p_dsl::plan_type, + p_dsl::status, + pf_dsl::name, + active_version_alias + .fields((pv_dsl::version, pv_dsl::trial_duration_days)) + .nullable(), + draft_version_alias.field(pv_dsl::id).nullable(), + active_subscriptions_count_subselect, + )) .into_boxed(); - if let Some(product_family_external_id) = product_family_external_id { - query = query.filter(pf_dsl::external_id.eq(product_family_external_id)) + if let Some(product_family_local_id) = product_family_local_id { + query = query.filter(pf_dsl::local_id.eq(product_family_local_id)) } if !filters.filter_status.is_empty() { @@ -206,7 +292,7 @@ impl PlanRowForList { query = query.filter( p_dsl::name .ilike(format!("%{}%", search)) - .or(p_dsl::external_id.ilike(format!("%{}%", search))), + .or(p_dsl::local_id.ilike(format!("%{}%", search))), ); } diff --git a/modules/meteroid/crates/diesel-models/src/query/price_components.rs b/modules/meteroid/crates/diesel-models/src/query/price_components.rs index 0ad7cd74..51c9ae7d 100644 --- a/modules/meteroid/crates/diesel-models/src/query/price_components.rs +++ b/modules/meteroid/crates/diesel-models/src/query/price_components.rs @@ -223,7 +223,7 @@ impl PriceComponentRow { diesel::dsl::sql::( format!("'{}'", dst_plan_version_id).as_str(), ), - pc_dsl::product_item_id, + pc_dsl::product_id, pc_dsl::billable_metric_id, )) .insert_into(pc_dsl::price_component) @@ -232,7 +232,7 @@ impl PriceComponentRow { pc_dsl::name, pc_dsl::fee, pc_dsl::plan_version_id, - pc_dsl::product_item_id, + pc_dsl::product_id, pc_dsl::billable_metric_id, )); diff --git a/modules/meteroid/crates/diesel-models/src/query/product_families.rs b/modules/meteroid/crates/diesel-models/src/query/product_families.rs index a21fc034..21256e25 100644 --- a/modules/meteroid/crates/diesel-models/src/query/product_families.rs +++ b/modules/meteroid/crates/diesel-models/src/query/product_families.rs @@ -40,16 +40,16 @@ impl ProductFamilyRow { .into_db_result() } - pub async fn find_by_external_id_and_tenant_id( + pub async fn find_by_local_id_and_tenant_id( conn: &mut PgConn, - external_id: &str, + local_id: &str, tenant_id: Uuid, ) -> DbResult { use crate::schema::product_family::dsl as pf_dsl; use diesel_async::RunQueryDsl; let query = pf_dsl::product_family - .filter(pf_dsl::external_id.eq(external_id)) + .filter(pf_dsl::local_id.eq(local_id)) .filter(pf_dsl::tenant_id.eq(tenant_id)); log::debug!("{}", debug_query::(&query).to_string()); @@ -57,7 +57,7 @@ impl ProductFamilyRow { query .first(conn) .await - .attach_printable("Error while finding product family by external_id and tenant_id") + .attach_printable("Error while finding product family by local_id and tenant_id") .into_db_result() } } diff --git a/modules/meteroid/crates/diesel-models/src/query/products.rs b/modules/meteroid/crates/diesel-models/src/query/products.rs index 7128a169..6346ae8d 100644 --- a/modules/meteroid/crates/diesel-models/src/query/products.rs +++ b/modules/meteroid/crates/diesel-models/src/query/products.rs @@ -53,7 +53,7 @@ impl ProductRow { pub async fn list( conn: &mut PgConn, tenant_id: Uuid, - family_external_id: &str, + family_local_id: &str, pagination: PaginationRequest, order_by: OrderByRequest, ) -> DbResult> { @@ -63,7 +63,7 @@ impl ProductRow { let mut query = p_dsl::product .inner_join(pf_dsl::product_family.on(p_dsl::product_family_id.eq(pf_dsl::id))) .filter(p_dsl::tenant_id.eq(tenant_id)) - .filter(pf_dsl::external_id.eq(family_external_id)) + .filter(pf_dsl::local_id.eq(family_local_id)) .select(ProductRow::as_select()) .into_boxed(); @@ -93,7 +93,7 @@ impl ProductRow { pub async fn search( conn: &mut PgConn, tenant_id: Uuid, - family_external_id: &str, + family_local_id: &str, query: &str, pagination: PaginationRequest, order_by: OrderByRequest, @@ -104,7 +104,7 @@ impl ProductRow { let mut query = p_dsl::product .inner_join(pf_dsl::product_family.on(p_dsl::product_family_id.eq(pf_dsl::id))) .filter(p_dsl::tenant_id.eq(tenant_id)) - .filter(pf_dsl::external_id.eq(family_external_id)) + .filter(pf_dsl::local_id.eq(family_local_id)) .filter(p_dsl::name.ilike(format!("%{}%", query))) .select(ProductRow::as_select()) .into_boxed(); diff --git a/modules/meteroid/crates/diesel-models/src/schema.rs b/modules/meteroid/crates/diesel-models/src/schema.rs index ed5ca5fc..e2ef03b2 100644 --- a/modules/meteroid/crates/diesel-models/src/schema.rs +++ b/modules/meteroid/crates/diesel-models/src/schema.rs @@ -86,6 +86,7 @@ diesel::table! { tenant_id -> Uuid, created_at -> Timestamp, updated_at -> Timestamp, + local_id -> Text, } } @@ -207,6 +208,8 @@ diesel::table! { archived_at -> Nullable, tenant_id -> Uuid, product_family_id -> Uuid, + product_id -> Nullable, + local_id -> Text, } } @@ -224,9 +227,10 @@ diesel::table! { created_at -> Timestamp, updated_at -> Timestamp, redemption_count -> Int4, - disabled -> Bool, last_redemption_at -> Nullable, + disabled -> Bool, archived_at -> Nullable, + local_id -> Text, } } @@ -247,6 +251,7 @@ diesel::table! { tenant_id -> Uuid, customer_id -> Uuid, status -> CreditNoteStatus, + local_id -> Text, } } @@ -270,6 +275,7 @@ diesel::table! { billing_address -> Nullable, shipping_address -> Nullable, invoicing_entity_id -> Uuid, + local_id -> Text, } } @@ -486,15 +492,16 @@ diesel::table! { archived_at -> Nullable, tenant_id -> Uuid, product_family_id -> Uuid, - external_id -> Text, + local_id -> Text, plan_type -> PlanTypeEnum, status -> PlanStatusEnum, + active_version_id -> Nullable, + draft_version_id -> Nullable, } } diesel::table! { use diesel::sql_types::*; - use super::sql_types::BillingPeriodEnum; use super::sql_types::ActionAfterTrialEnum; plan_version (id) { @@ -511,7 +518,6 @@ diesel::table! { billing_cycles -> Nullable, created_at -> Timestamp, created_by -> Uuid, - billing_periods -> Array>, trialing_plan_id -> Nullable, action_after_trial -> Nullable, trial_is_free -> Bool, @@ -524,8 +530,9 @@ diesel::table! { name -> Text, fee -> Jsonb, plan_version_id -> Uuid, - product_item_id -> Nullable, + product_id -> Nullable, billable_metric_id -> Nullable, + local_id -> Text, } } @@ -540,6 +547,7 @@ diesel::table! { archived_at -> Nullable, tenant_id -> Uuid, product_family_id -> Uuid, + local_id -> Text, } } @@ -547,7 +555,7 @@ diesel::table! { product_family (id) { id -> Uuid, name -> Text, - external_id -> Text, + local_id -> Text, created_at -> Timestamp, updated_at -> Nullable, archived_at -> Nullable, @@ -619,6 +627,7 @@ diesel::table! { currency -> Varchar, mrr_cents -> Int8, period -> BillingPeriodEnum, + local_id -> Text, } } @@ -646,7 +655,7 @@ diesel::table! { name -> Text, subscription_id -> Uuid, price_component_id -> Nullable, - product_item_id -> Nullable, + product_id -> Nullable, period -> SubscriptionFeeBillingPeriod, fee -> Jsonb, } @@ -755,6 +764,7 @@ diesel::joinable!(bi_mrr_movement_log -> invoice (invoice_id)); diesel::joinable!(bi_mrr_movement_log -> plan_version (plan_version_id)); diesel::joinable!(bi_mrr_movement_log -> tenant (tenant_id)); diesel::joinable!(bi_revenue_daily -> historical_rates_from_usd (historical_rate_id)); +diesel::joinable!(billable_metric -> product (product_id)); diesel::joinable!(billable_metric -> product_family (product_family_id)); diesel::joinable!(billable_metric -> tenant (tenant_id)); diesel::joinable!(coupon -> tenant (tenant_id)); @@ -783,7 +793,7 @@ diesel::joinable!(plan -> product_family (product_family_id)); diesel::joinable!(plan -> tenant (tenant_id)); diesel::joinable!(price_component -> billable_metric (billable_metric_id)); diesel::joinable!(price_component -> plan_version (plan_version_id)); -diesel::joinable!(price_component -> product (product_item_id)); +diesel::joinable!(price_component -> product (product_id)); diesel::joinable!(product -> product_family (product_family_id)); diesel::joinable!(product -> tenant (tenant_id)); diesel::joinable!(product_family -> tenant (tenant_id)); @@ -796,7 +806,7 @@ diesel::joinable!(subscription -> tenant (tenant_id)); diesel::joinable!(subscription_add_on -> add_on (add_on_id)); diesel::joinable!(subscription_add_on -> subscription (subscription_id)); diesel::joinable!(subscription_component -> price_component (price_component_id)); -diesel::joinable!(subscription_component -> product (product_item_id)); +diesel::joinable!(subscription_component -> product (product_id)); diesel::joinable!(subscription_component -> subscription (subscription_id)); diesel::joinable!(subscription_event -> bi_mrr_movement_log (bi_mrr_movement_log_id)); diesel::joinable!(subscription_event -> subscription (subscription_id)); diff --git a/modules/meteroid/crates/diesel-models/src/subscription_components.rs b/modules/meteroid/crates/diesel-models/src/subscription_components.rs index c416c983..f87b5abd 100644 --- a/modules/meteroid/crates/diesel-models/src/subscription_components.rs +++ b/modules/meteroid/crates/diesel-models/src/subscription_components.rs @@ -11,7 +11,7 @@ pub struct SubscriptionComponentRow { pub name: String, pub subscription_id: Uuid, pub price_component_id: Option, - pub product_item_id: Option, + pub product_id: Option, pub period: SubscriptionFeeBillingPeriod, // pub mrr_value: Option, pub fee: serde_json::Value, @@ -24,7 +24,7 @@ pub struct SubscriptionComponentRowNew { pub name: String, pub subscription_id: Uuid, pub price_component_id: Option, - pub product_item_id: Option, + pub product_id: Option, pub period: SubscriptionFeeBillingPeriod, // pub mrr_value: Option, pub fee: serde_json::Value, diff --git a/modules/meteroid/crates/diesel-models/src/subscriptions.rs b/modules/meteroid/crates/diesel-models/src/subscriptions.rs index 84634731..a26a78f7 100644 --- a/modules/meteroid/crates/diesel-models/src/subscriptions.rs +++ b/modules/meteroid/crates/diesel-models/src/subscriptions.rs @@ -32,12 +32,14 @@ pub struct SubscriptionRow { pub currency: String, pub mrr_cents: i64, pub period: BillingPeriodEnum, + pub local_id: String, } #[derive(Insertable, Debug)] #[diesel(table_name = crate::schema::subscription)] pub struct SubscriptionRowNew { pub id: Uuid, + pub local_id: String, pub customer_id: Uuid, pub billing_day: i16, pub tenant_id: Uuid, @@ -68,9 +70,12 @@ pub struct CancelSubscriptionParams { pub struct SubscriptionForDisplayRow { #[diesel(embed)] pub subscription: SubscriptionRow, + #[diesel(select_expression = customer::local_id)] + #[diesel(select_expression_type = customer::local_id)] + pub customer_local_id: String, #[diesel(select_expression = customer::alias)] #[diesel(select_expression_type = customer::alias)] - pub customer_external_id: Option, + pub customer_alias: Option, #[diesel(select_expression = customer::name)] #[diesel(select_expression_type = customer::name)] pub customer_name: String, @@ -116,6 +121,7 @@ mod subscription_invoice_candidate { #[diesel(check_for_backend(diesel::pg::Pg))] pub struct SubscriptionEmbedRow { pub id: Uuid, + pub local_id: String, pub tenant_id: Uuid, pub customer_id: Uuid, pub plan_version_id: Uuid, @@ -138,5 +144,8 @@ mod subscription_invoice_candidate { #[diesel(select_expression = crate::schema::plan::name)] #[diesel(select_expression_type = crate::schema::plan::name)] pub plan_name: String, + #[diesel(select_expression = crate::schema::plan::local_id)] + #[diesel(select_expression_type = crate::schema::plan::local_id)] + pub plan_local_id: String, } } diff --git a/modules/meteroid/crates/meteroid-store/src/compute/clients/slots.rs b/modules/meteroid/crates/meteroid-store/src/compute/clients/slots.rs index d840206f..841d7e07 100644 --- a/modules/meteroid/crates/meteroid-store/src/compute/clients/slots.rs +++ b/modules/meteroid/crates/meteroid-store/src/compute/clients/slots.rs @@ -5,6 +5,7 @@ use uuid::Uuid; use crate::compute::errors::ComputeError; use crate::repositories::subscriptions::SubscriptionSlotsInterface; +use error_stack::{Result, ResultExt}; #[async_trait::async_trait] pub trait SlotClient { @@ -35,7 +36,7 @@ impl SlotClient for crate::Store { invoice_date.clone().and_hms_opt(0, 0, 0), ) .await - .map_err(|_e| ComputeError::InternalError)?; + .change_context(ComputeError::InternalError)?; Ok(res) } diff --git a/modules/meteroid/crates/meteroid-store/src/compute/clients/usage.rs b/modules/meteroid/crates/meteroid-store/src/compute/clients/usage.rs index 028b59e0..5b0f4563 100644 --- a/modules/meteroid/crates/meteroid-store/src/compute/clients/usage.rs +++ b/modules/meteroid/crates/meteroid-store/src/compute/clients/usage.rs @@ -49,8 +49,8 @@ pub trait UsageClient: Send + Sync { async fn fetch_usage( &self, tenant_id: &Uuid, - customer_id: &Uuid, - customer_external_id: &Option, + customer_local_id: &str, + customer_alias: &Option, metric: &BillableMetric, period: Period, ) -> Result; @@ -79,8 +79,8 @@ impl UsageClient for MockUsageClient { async fn fetch_usage( &self, _tenant_id: &Uuid, - _customer_id: &Uuid, - _customer_external_id: &Option, + _customer_local_id: &str, + _customer_alias: &Option, metric: &BillableMetric, period: Period, ) -> Result { diff --git a/modules/meteroid/crates/meteroid-store/src/compute/engine/component.rs b/modules/meteroid/crates/meteroid-store/src/compute/engine/component.rs index a229a052..e43369eb 100644 --- a/modules/meteroid/crates/meteroid-store/src/compute/engine/component.rs +++ b/modules/meteroid/crates/meteroid-store/src/compute/engine/component.rs @@ -18,6 +18,7 @@ use crate::utils::decimals::ToSubunit; use super::super::clients::usage::UsageClient; use super::super::errors::ComputeError; use super::fees; +use error_stack::{Report, Result}; pub struct ComponentEngine { usage_client: Arc, @@ -111,7 +112,7 @@ impl ComponentEngine { invoice_date, &component .price_component_id() - .ok_or(ComputeError::InternalError)?, + .ok_or(Report::new(ComputeError::InternalError))?, ) // TODO we need unit instead. That would allow for subscription components not linked to a plan. It'd also match Sequence model .await? .max(min_slots.unwrap_or(0) as u64) @@ -210,7 +211,7 @@ impl ComponentEngine { let price_cents = only_positive( price_total .to_subunit_opt(precision) - .ok_or(ComputeError::ConversionError)?, + .ok_or(Report::new(ComputeError::ConversionError))?, ); if price_cents > 0 { @@ -331,7 +332,7 @@ impl ComponentEngine { sub_lines: line.sublines, is_prorated: line.is_prorated, price_component_id: component.price_component_id(), - product_id: component.product_item_id(), + product_id: component.product_id(), metric_id: component.fee_ref().metric_id(), subtotal: line.total as i64, // TODO description: None, @@ -355,8 +356,8 @@ impl ComponentEngine { .usage_client .fetch_usage( &self.subscription_details.tenant_id, - &self.subscription_details.customer_id, - &self.subscription_details.customer_external_id, + &self.subscription_details.customer_local_id, + &self.subscription_details.customer_alias, metric, period, ) diff --git a/modules/meteroid/crates/meteroid-store/src/compute/engine/invoice.rs b/modules/meteroid/crates/meteroid-store/src/compute/engine/invoice.rs index 097c84cb..e2141320 100644 --- a/modules/meteroid/crates/meteroid-store/src/compute/engine/invoice.rs +++ b/modules/meteroid/crates/meteroid-store/src/compute/engine/invoice.rs @@ -10,6 +10,8 @@ use crate::Store; use chrono::NaiveDate; use itertools::Itertools; +use error_stack::{Report, Result, ResultExt}; + #[async_trait::async_trait] pub trait InvoiceLineInterface { async fn compute_dated_invoice_lines( @@ -30,13 +32,13 @@ impl InvoiceLineInterface for Store { subscription_details: &SubscriptionDetails, ) -> Result, ComputeError> { if *invoice_date < subscription_details.billing_start_date { - return Err(ComputeError::InvalidInvoiceDate); + return Err(Report::new(ComputeError::InvalidInvoiceDate)); } let currency = self .get_reporting_currency_by_tenant_id(subscription_details.tenant_id) .await - .map_err(|_| ComputeError::InternalError)?; + .change_context(ComputeError::InternalError)?; let billing_start_date = subscription_details.billing_start_date; let billing_day = subscription_details.billing_day; diff --git a/modules/meteroid/crates/meteroid-store/src/domain/add_ons.rs b/modules/meteroid/crates/meteroid-store/src/domain/add_ons.rs index a264318d..3a29b9fa 100644 --- a/modules/meteroid/crates/meteroid-store/src/domain/add_ons.rs +++ b/modules/meteroid/crates/meteroid-store/src/domain/add_ons.rs @@ -1,5 +1,6 @@ use crate::domain::FeeType; use crate::errors::StoreError; +use crate::utils::local_id::{IdType, LocalId}; use chrono::NaiveDateTime; use diesel_models::add_ons::{AddOnRow, AddOnRowNew, AddOnRowPatch}; use error_stack::Report; @@ -8,6 +9,7 @@ use uuid::Uuid; #[derive(Debug, Clone)] pub struct AddOn { pub id: Uuid, + pub local_id: String, pub name: String, pub fee: FeeType, pub tenant_id: Uuid, @@ -26,6 +28,7 @@ impl TryInto for AddOnRow { Ok(AddOn { id: self.id, name: self.name, + local_id: self.local_id, fee, tenant_id: self.tenant_id, created_at: self.created_at, @@ -51,6 +54,7 @@ impl TryInto for AddOnNew { Ok(AddOnRowNew { id: Uuid::now_v7(), + local_id: LocalId::generate_for(IdType::AddOn), tenant_id: self.tenant_id, name: self.name, fee: json_fee, diff --git a/modules/meteroid/crates/meteroid-store/src/domain/billable_metrics.rs b/modules/meteroid/crates/meteroid-store/src/domain/billable_metrics.rs index 23a54d30..63d5580c 100644 --- a/modules/meteroid/crates/meteroid-store/src/domain/billable_metrics.rs +++ b/modules/meteroid/crates/meteroid-store/src/domain/billable_metrics.rs @@ -12,6 +12,7 @@ use uuid::Uuid; #[try_map_owned(BillableMetricRow, StoreErrorReport)] pub struct BillableMetric { pub id: Uuid, + pub local_id: String, pub name: String, pub description: Option, pub code: String, @@ -41,6 +42,7 @@ pub struct BillableMetric { pub archived_at: Option, pub tenant_id: Uuid, pub product_family_id: Uuid, + pub product_id: Option, } #[derive(Clone, Debug, Serialize, Deserialize)] @@ -76,7 +78,8 @@ pub struct BillableMetricNew { pub usage_group_key: Option, pub created_by: Uuid, pub tenant_id: Uuid, - pub family_external_id: String, + pub family_local_id: String, + pub product_id: Option, } #[derive(Clone, Debug, o2o)] @@ -84,6 +87,7 @@ pub struct BillableMetricNew { #[owned_into(BillableMetricMetaRow)] pub struct BillableMetricMeta { pub id: Uuid, + pub local_id: String, pub name: String, pub code: String, #[map(~.into())] diff --git a/modules/meteroid/crates/meteroid-store/src/domain/coupons.rs b/modules/meteroid/crates/meteroid-store/src/domain/coupons.rs index a633924f..246884e6 100644 --- a/modules/meteroid/crates/meteroid-store/src/domain/coupons.rs +++ b/modules/meteroid/crates/meteroid-store/src/domain/coupons.rs @@ -1,4 +1,5 @@ use crate::errors::StoreError; +use crate::utils::local_id::{IdType, LocalId}; use chrono::NaiveDateTime; use diesel_models::coupons::{CouponRow, CouponRowNew, CouponRowPatch}; use error_stack::Report; @@ -9,6 +10,7 @@ use uuid::Uuid; #[derive(Debug, Clone)] pub struct Coupon { pub id: Uuid, + pub local_id: String, pub code: String, pub description: String, pub tenant_id: Uuid, @@ -65,6 +67,7 @@ impl TryInto for CouponRow { Ok(Coupon { id: self.id, + local_id: self.local_id, code: self.code, description: self.description, tenant_id: self.tenant_id, @@ -102,6 +105,7 @@ impl TryInto for CouponNew { Ok(CouponRowNew { id: Uuid::now_v7(), + local_id: LocalId::generate_for(IdType::Coupon), code: self.code, description: self.description, tenant_id: self.tenant_id, diff --git a/modules/meteroid/crates/meteroid-store/src/domain/customers.rs b/modules/meteroid/crates/meteroid-store/src/domain/customers.rs index 943dfaca..e4a5797b 100644 --- a/modules/meteroid/crates/meteroid-store/src/domain/customers.rs +++ b/modules/meteroid/crates/meteroid-store/src/domain/customers.rs @@ -8,10 +8,12 @@ use serde_json::Value; use uuid::Uuid; use crate::errors::StoreError; +use crate::utils::local_id::{IdType, LocalId}; #[derive(Clone, Debug, PartialEq, Eq)] pub struct Customer { pub id: Uuid, + pub local_id: String, pub name: String, pub created_at: NaiveDateTime, pub created_by: Uuid, @@ -37,6 +39,7 @@ impl TryFrom for Customer { fn try_from(value: CustomerRow) -> Result { Ok(Customer { id: value.id, + local_id: value.local_id, name: value.name, created_at: value.created_at, created_by: value.created_by, @@ -64,6 +67,7 @@ impl TryInto for Customer { fn try_into(self) -> Result { Ok(CustomerRow { id: self.id, + local_id: self.local_id, name: self.name, created_at: self.created_at, created_by: self.created_by, @@ -90,6 +94,7 @@ impl TryInto for Customer { #[owned_into(CustomerBriefRow)] pub struct CustomerBrief { pub id: Uuid, + pub local_id: String, pub name: String, pub alias: Option, } @@ -126,6 +131,7 @@ impl TryInto for CustomerNewWrapper { fn try_into(self) -> Result { Ok(CustomerRowNew { id: Uuid::now_v7(), + local_id: LocalId::generate_for(IdType::Customer), name: self.inner.name, created_by: self.inner.created_by, tenant_id: self.tenant_id, diff --git a/modules/meteroid/crates/meteroid-store/src/domain/enums.rs b/modules/meteroid/crates/meteroid-store/src/domain/enums.rs index 1a109cfc..a4210609 100644 --- a/modules/meteroid/crates/meteroid-store/src/domain/enums.rs +++ b/modules/meteroid/crates/meteroid-store/src/domain/enums.rs @@ -143,7 +143,7 @@ pub enum OutboxStatus { Failed, } -#[derive(o2o, Serialize, Deserialize, Debug, Default, Clone)] +#[derive(o2o, Serialize, Deserialize, Debug, Default, Clone, PartialEq)] #[map_owned(diesel_enums::PlanStatusEnum)] pub enum PlanStatusEnum { #[default] diff --git a/modules/meteroid/crates/meteroid-store/src/domain/invoices.rs b/modules/meteroid/crates/meteroid-store/src/domain/invoices.rs index 2b42fb5c..a427cb5b 100644 --- a/modules/meteroid/crates/meteroid-store/src/domain/invoices.rs +++ b/modules/meteroid/crates/meteroid-store/src/domain/invoices.rs @@ -3,9 +3,10 @@ use super::enums::{ }; use crate::domain::coupons::CouponDiscount; use crate::domain::invoice_lines::LineItem; -use crate::domain::{Address, AppliedCouponDetailed, Customer, PlanVersionLatest}; +use crate::domain::{Address, AppliedCouponDetailed, Customer, PlanVersionOverview}; use crate::errors::{StoreError, StoreErrorReport}; use crate::utils::decimals::ToSubunit; +use crate::utils::local_id::{IdType, LocalId}; use chrono::{NaiveDate, NaiveDateTime}; use diesel_models::invoices::DetailedInvoiceRow; use diesel_models::invoices::InvoiceRow; @@ -83,6 +84,7 @@ pub struct Invoice { #[owned_try_into(InvoiceRowNew, StoreErrorReport)] #[ghosts( id: {uuid::Uuid::now_v7()}, + local_id: {LocalId::generate_for(IdType::Invoice)}, )] pub struct InvoiceNew { #[into(~.into())] @@ -120,7 +122,6 @@ pub struct InvoiceNew { pub net_terms: i32, pub reference: Option, pub memo: Option, - pub local_id: String, pub due_at: Option, // TODO due_date pub plan_name: Option, #[into(serde_json::to_value(& ~).map_err(| e | { @@ -216,11 +217,11 @@ impl TryFrom for InvoiceWithCustomer { } } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq)] pub struct DetailedInvoice { pub invoice: Invoice, pub customer: Customer, - pub plan: Option, + pub plan: Option, } impl TryFrom for DetailedInvoice { diff --git a/modules/meteroid/crates/meteroid-store/src/domain/plans.rs b/modules/meteroid/crates/meteroid-store/src/domain/plans.rs index d4e5a8e3..940e405b 100644 --- a/modules/meteroid/crates/meteroid-store/src/domain/plans.rs +++ b/modules/meteroid/crates/meteroid-store/src/domain/plans.rs @@ -1,29 +1,49 @@ use chrono::NaiveDateTime; use diesel_models::plan_versions::PlanVersionRow; -use diesel_models::plan_versions::PlanVersionRowLatest; use diesel_models::plan_versions::PlanVersionRowNew; +use diesel_models::plan_versions::PlanVersionRowOverview; use diesel_models::plan_versions::PlanVersionRowPatch; use diesel_models::plans::PlanFilters as PlanFiltersDb; use diesel_models::plans::PlanRow; -use diesel_models::plans::PlanRowForList; use diesel_models::plans::PlanRowNew; +use diesel_models::plans::PlanRowOverview; use diesel_models::plans::PlanRowPatch; +use diesel_models::plans::PlanVersionRowInfo; use diesel_models::plans::PlanWithVersionRow; + use o2o::o2o; use uuid::Uuid; // TODO duplicate as well -use super::enums::{ActionAfterTrialEnum, BillingPeriodEnum, PlanStatusEnum, PlanTypeEnum}; +use super::enums::{ActionAfterTrialEnum, PlanStatusEnum, PlanTypeEnum}; use crate::domain::price_components::{PriceComponent, PriceComponentNewInternal}; +#[derive(Debug, Clone)] +pub enum PlanVersionFilter { + Draft, + Active, + Version(i32), +} +impl From for diesel_models::plan_versions::PlanVersionFilter { + fn from(val: PlanVersionFilter) -> Self { + match val { + PlanVersionFilter::Draft => diesel_models::plan_versions::PlanVersionFilter::Draft, + PlanVersionFilter::Active => diesel_models::plan_versions::PlanVersionFilter::Active, + PlanVersionFilter::Version(v) => { + diesel_models::plan_versions::PlanVersionFilter::Version(v) + } + } + } +} + #[derive(Debug, Clone)] pub struct PlanNew { pub name: String, pub description: Option, pub created_by: Uuid, pub tenant_id: Uuid, - pub product_family_external_id: String, - pub external_id: String, + pub product_family_local_id: String, + pub local_id: String, pub plan_type: PlanTypeEnum, pub status: PlanStatusEnum, } @@ -37,7 +57,7 @@ impl PlanNew { created_by: self.created_by, tenant_id: self.tenant_id, product_family_id, - external_id: self.external_id, + local_id: self.local_id, plan_type: self.plan_type.into(), status: self.status.into(), } @@ -57,7 +77,6 @@ pub struct PlanVersionNewInternal { pub net_terms: i32, pub currency: Option, pub billing_cycles: Option, - pub billing_periods: Vec, pub trial: Option, } @@ -117,17 +136,11 @@ impl PlanVersionNew { net_terms: self.internal.net_terms, currency: self.internal.currency.unwrap_or(tenant_currency), billing_cycles: self.internal.billing_cycles, - billing_periods: self - .internal - .billing_periods - .into_iter() - .map(|v| v.into()) - .collect::>(), } } } -#[derive(Debug, o2o)] +#[derive(Debug, Clone, PartialEq, o2o)] #[from_owned(PlanRow)] pub struct Plan { pub id: Uuid, @@ -137,14 +150,16 @@ pub struct Plan { pub created_at: NaiveDateTime, pub tenant_id: Uuid, pub product_family_id: Uuid, - pub external_id: String, + pub local_id: String, #[from(~.into())] pub plan_type: PlanTypeEnum, #[from(~.into())] pub status: PlanStatusEnum, + pub active_version_id: Option, + pub draft_version_id: Option, } -#[derive(Debug, o2o)] +#[derive(Debug, Clone, PartialEq, o2o)] #[from_owned(PlanVersionRow)] pub struct PlanVersion { pub id: Uuid, @@ -158,8 +173,6 @@ pub struct PlanVersion { pub billing_cycles: Option, pub created_at: NaiveDateTime, pub created_by: Uuid, - #[from(~.into_iter().filter_map(| v | v.map(| v | v.into())).collect::< Vec < _ >> ())] - pub billing_periods: Vec, pub trialing_plan_id: Option, #[from(~.map(| v | v.into()))] pub action_after_trial: Option, @@ -175,32 +188,33 @@ pub struct FullPlan { } #[derive(Debug, o2o)] -#[from_owned(PlanRowForList)] -pub struct PlanForList { +#[from_owned(PlanRowOverview)] +pub struct PlanOverview { pub id: Uuid, pub name: String, pub description: Option, pub created_at: NaiveDateTime, - pub created_by: Uuid, - pub updated_at: Option, - pub archived_at: Option, - pub tenant_id: Uuid, - pub product_family_id: Uuid, - pub external_id: String, + pub local_id: String, #[from(~.into())] pub plan_type: PlanTypeEnum, #[from(~.into())] pub status: PlanStatusEnum, pub product_family_name: String, + #[from(~.map(| v | v.into()))] + pub active_version: Option, + // pub draft_version: Option, + #[from(draft_version, ~.is_some())] + pub has_draft_version: bool, + pub subscription_count: Option, } #[derive(Debug, Clone, PartialEq, Eq, o2o)] -#[from_owned(PlanVersionRowLatest)] -pub struct PlanVersionLatest { +#[from_owned(PlanVersionRowOverview)] +pub struct PlanVersionOverview { pub id: Uuid, pub plan_id: Uuid, pub plan_name: String, - pub external_id: String, + pub local_id: String, pub version: i32, pub created_by: Uuid, pub period_start_day: Option, @@ -216,6 +230,14 @@ pub struct PlanVersionLatest { pub trial_duration_days: Option, } +#[derive(Debug, o2o)] +#[from_owned(PlanVersionRowInfo)] +pub struct PlanVersionInfo { + pub version: i32, + pub trial_duration_days: Option, + // add currency(-ies) ? +} + #[derive(Debug, o2o)] #[owned_into(PlanVersionRowPatch)] pub struct PlanVersionPatch { @@ -223,8 +245,6 @@ pub struct PlanVersionPatch { pub tenant_id: Uuid, pub currency: Option, pub net_terms: Option, - #[into(~.map(| x | x.into_iter().map(| v | v.into()).collect::< Vec < _ >> ()))] - pub billing_periods: Option>, } pub struct PlanAndVersionPatch { @@ -235,20 +255,22 @@ pub struct PlanAndVersionPatch { #[derive(Debug, o2o)] #[owned_into(PlanRowPatch)] +#[ghosts(draft_version_id: {None})] pub struct PlanPatch { pub id: Uuid, pub tenant_id: Uuid, pub name: Option, pub description: Option>, + pub active_version_id: Option>, } -#[derive(Debug, o2o)] +#[derive(Debug, Clone, PartialEq, o2o)] #[from_owned(PlanWithVersionRow)] pub struct PlanWithVersion { #[from(~.into())] pub plan: Plan, - #[from(~.into())] - pub version: PlanVersion, + #[from(~.map(| v | v.into()))] + pub version: Option, } pub struct TrialPatch { diff --git a/modules/meteroid/crates/meteroid-store/src/domain/price_components.rs b/modules/meteroid/crates/meteroid-store/src/domain/price_components.rs index c6cc1158..2e290086 100644 --- a/modules/meteroid/crates/meteroid-store/src/domain/price_components.rs +++ b/modules/meteroid/crates/meteroid-store/src/domain/price_components.rs @@ -6,15 +6,17 @@ use super::enums::{BillingPeriodEnum, BillingType, SubscriptionFeeBillingPeriod} use crate::domain::SubscriptionFee; use crate::errors::StoreError; +use crate::utils::local_id::{IdType, LocalId}; use diesel_models::price_components::{PriceComponentRow, PriceComponentRowNew}; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone)] pub struct PriceComponent { pub id: Uuid, + pub local_id: String, pub name: String, pub fee: FeeType, - pub product_item_id: Option, + pub product_id: Option, } impl TryInto for PriceComponentRow { @@ -29,8 +31,9 @@ impl TryInto for PriceComponentRow { Ok(PriceComponent { id: self.id, name: self.name, + local_id: self.local_id, fee, - product_item_id: self.product_item_id, + product_id: self.product_id, }) } } @@ -39,7 +42,7 @@ impl TryInto for PriceComponentRow { pub struct PriceComponentNew { pub name: String, pub fee: FeeType, - pub product_item_id: Option, + pub product_id: Option, pub plan_version_id: Uuid, } @@ -47,7 +50,7 @@ pub struct PriceComponentNew { pub struct PriceComponentNewInternal { pub name: String, pub fee: FeeType, - pub product_item_id: Option, + pub product_id: Option, } impl TryInto for PriceComponentNew { @@ -60,10 +63,11 @@ impl TryInto for PriceComponentNew { Ok(PriceComponentRowNew { id: Uuid::now_v7(), + local_id: LocalId::generate_for(IdType::PriceComponent), plan_version_id: self.plan_version_id, name: self.name, fee: json_fee, - product_item_id: self.product_item_id, + product_id: self.product_id, billable_metric_id: self.fee.metric_id(), }) } diff --git a/modules/meteroid/crates/meteroid-store/src/domain/product_families.rs b/modules/meteroid/crates/meteroid-store/src/domain/product_families.rs index b050882a..800b2a67 100644 --- a/modules/meteroid/crates/meteroid-store/src/domain/product_families.rs +++ b/modules/meteroid/crates/meteroid-store/src/domain/product_families.rs @@ -11,7 +11,7 @@ use diesel_models::product_families::ProductFamilyRowNew; pub struct ProductFamily { pub id: Uuid, pub name: String, - pub external_id: String, + pub local_id: String, pub created_at: NaiveDateTime, pub updated_at: Option, pub archived_at: Option, @@ -23,6 +23,6 @@ pub struct ProductFamily { #[ghosts(id: {uuid::Uuid::now_v7()})] pub struct ProductFamilyNew { pub name: String, - pub external_id: String, + pub local_id: String, pub tenant_id: Uuid, } diff --git a/modules/meteroid/crates/meteroid-store/src/domain/products.rs b/modules/meteroid/crates/meteroid-store/src/domain/products.rs index dc76e20c..d16460f2 100644 --- a/modules/meteroid/crates/meteroid-store/src/domain/products.rs +++ b/modules/meteroid/crates/meteroid-store/src/domain/products.rs @@ -8,6 +8,7 @@ use uuid::Uuid; #[owned_into(ProductRow)] pub struct Product { pub id: Uuid, + pub local_id: String, pub name: String, pub description: Option, pub created_at: NaiveDateTime, @@ -24,5 +25,5 @@ pub struct ProductNew { pub description: Option, pub created_by: Uuid, pub tenant_id: Uuid, - pub family_external_id: String, + pub family_local_id: String, } diff --git a/modules/meteroid/crates/meteroid-store/src/domain/subscription_add_ons.rs b/modules/meteroid/crates/meteroid-store/src/domain/subscription_add_ons.rs index f515ef64..81f23344 100644 --- a/modules/meteroid/crates/meteroid-store/src/domain/subscription_add_ons.rs +++ b/modules/meteroid/crates/meteroid-store/src/domain/subscription_add_ons.rs @@ -24,7 +24,7 @@ impl SubscriptionFeeInterface for SubscriptionAddOn { } #[inline] - fn product_item_id(&self) -> Option { + fn product_id(&self) -> Option { None } diff --git a/modules/meteroid/crates/meteroid-store/src/domain/subscription_components.rs b/modules/meteroid/crates/meteroid-store/src/domain/subscription_components.rs index 1702250f..d97a7faa 100644 --- a/modules/meteroid/crates/meteroid-store/src/domain/subscription_components.rs +++ b/modules/meteroid/crates/meteroid-store/src/domain/subscription_components.rs @@ -10,7 +10,7 @@ use crate::errors::StoreError; pub trait SubscriptionFeeInterface { fn price_component_id(&self) -> Option; - fn product_item_id(&self) -> Option; + fn product_id(&self) -> Option; fn subscription_id(&self) -> Uuid; fn name_ref(&self) -> &String; fn period_ref(&self) -> &SubscriptionFeeBillingPeriod; @@ -21,7 +21,7 @@ pub trait SubscriptionFeeInterface { pub struct SubscriptionComponent { pub id: Uuid, pub price_component_id: Option, - pub product_item_id: Option, + pub product_id: Option, pub subscription_id: Uuid, pub name: String, pub period: SubscriptionFeeBillingPeriod, @@ -35,8 +35,8 @@ impl SubscriptionFeeInterface for SubscriptionComponent { } #[inline] - fn product_item_id(&self) -> Option { - self.product_item_id + fn product_id(&self) -> Option { + self.product_id } #[inline] @@ -70,7 +70,7 @@ impl TryInto for SubscriptionComponentRow { Ok(SubscriptionComponent { id: self.id, price_component_id: self.price_component_id, - product_item_id: self.product_item_id, + product_id: self.product_id, subscription_id: self.subscription_id, name: self.name, period: self.period.into(), @@ -106,7 +106,7 @@ impl TryInto for SubscriptionComponentNew { id: Uuid::now_v7(), subscription_id: self.subscription_id, price_component_id: self.internal.price_component_id, - product_item_id: self.internal.product_item_id, + product_id: self.internal.product_id, name: self.internal.name, period: self.internal.period.into(), fee, @@ -149,7 +149,7 @@ pub struct ExtraComponent { #[derive(Debug, Clone)] pub struct SubscriptionComponentNewInternal { pub price_component_id: Option, - pub product_item_id: Option, + pub product_id: Option, pub name: String, pub period: SubscriptionFeeBillingPeriod, // pub mrr_value: Option, // TODO diff --git a/modules/meteroid/crates/meteroid-store/src/domain/subscriptions.rs b/modules/meteroid/crates/meteroid-store/src/domain/subscriptions.rs index b84d1b00..04e8538c 100644 --- a/modules/meteroid/crates/meteroid-store/src/domain/subscriptions.rs +++ b/modules/meteroid/crates/meteroid-store/src/domain/subscriptions.rs @@ -9,6 +9,7 @@ use crate::domain::{ AppliedCouponDetailed, BillableMetric, CreateSubscriptionComponents, CreateSubscriptionCoupons, Schedule, SubscriptionComponent, }; +use crate::utils::local_id::{IdType, LocalId}; use diesel_models::subscriptions::SubscriptionRowNew; use diesel_models::subscriptions::{ SubscriptionForDisplayRow, SubscriptionInvoiceCandidateRow, SubscriptionRow, @@ -18,6 +19,7 @@ use diesel_models::subscriptions::{ #[from_owned(SubscriptionRow)] pub struct CreatedSubscription { pub id: Uuid, + pub local_id: String, pub customer_id: Uuid, pub billing_day: i16, pub tenant_id: Uuid, @@ -42,9 +44,11 @@ pub struct CreatedSubscription { #[derive(Debug, Clone)] pub struct Subscription { pub id: Uuid, + pub local_id: String, pub customer_id: Uuid, - pub customer_name: String, + pub customer_local_id: String, pub customer_alias: Option, + pub customer_name: String, pub billing_day: i16, pub tenant_id: Uuid, pub currency: String, @@ -72,9 +76,11 @@ impl From for Subscription { fn from(val: SubscriptionForDisplayRow) -> Self { Subscription { id: val.subscription.id, + local_id: val.subscription.local_id, customer_id: val.subscription.customer_id, + customer_local_id: val.customer_local_id, customer_name: val.customer_name, - customer_alias: val.customer_external_id, + customer_alias: val.customer_alias, billing_day: val.subscription.billing_day, tenant_id: val.subscription.tenant_id, currency: val.subscription.currency, @@ -123,7 +129,8 @@ impl SubscriptionNew { tenant_id: Uuid, ) -> SubscriptionRowNew { SubscriptionRowNew { - id: uuid::Uuid::now_v7(), + id: Uuid::now_v7(), + local_id: LocalId::generate_for(IdType::Subscription), customer_id: self.customer_id, billing_day: self.billing_day, tenant_id, @@ -158,10 +165,12 @@ pub struct CreateSubscription { #[derive(Debug, Clone)] pub struct SubscriptionDetails { pub id: uuid::Uuid, + pub local_id: String, pub tenant_id: uuid::Uuid, pub customer_id: uuid::Uuid, + pub customer_local_id: String, + pub customer_alias: Option, pub plan_version_id: uuid::Uuid, - pub customer_external_id: Option, pub billing_start_date: chrono::NaiveDate, pub billing_end_date: Option, pub billing_day: i16, diff --git a/modules/meteroid/crates/meteroid-store/src/errors.rs b/modules/meteroid/crates/meteroid-store/src/errors.rs index d29511f3..9014d468 100644 --- a/modules/meteroid/crates/meteroid-store/src/errors.rs +++ b/modules/meteroid/crates/meteroid-store/src/errors.rs @@ -28,8 +28,8 @@ pub enum StoreError { InsertError, #[error("Transaction error: {0:?}")] TransactionStoreError(error_stack::Report), - #[error("Failed to compute invoice lines: {0:?}")] - InvoiceComputationError(#[source] ComputeError), + #[error("Failed to compute invoice lines")] + InvoiceComputationError, #[error("Failed to process price components: {0}")] InvalidPriceComponents(String), #[error("Failed to serialize/deserialize data: {0}")] @@ -49,18 +49,6 @@ pub enum StoreError { // used in some o2o macros failing to compile, https://github.com/meteroid-oss/meteroid/actions/runs/10921372280/job/30313299862 pub(crate) type StoreErrorReport = error_stack::Report; -impl From for StoreError { - fn from(err: ComputeError) -> Self { - StoreError::InvoiceComputationError(err) - } -} - -impl From for error_stack::Report { - fn from(err: ComputeError) -> Self { - error_stack::Report::from(StoreError::InvoiceComputationError(err)) - } -} - impl From for StoreError { fn from(err: DatabaseError) -> Self { match err { diff --git a/modules/meteroid/crates/meteroid-store/src/repositories/billable_metrics.rs b/modules/meteroid/crates/meteroid-store/src/repositories/billable_metrics.rs index d4383951..d6422c7c 100644 --- a/modules/meteroid/crates/meteroid-store/src/repositories/billable_metrics.rs +++ b/modules/meteroid/crates/meteroid-store/src/repositories/billable_metrics.rs @@ -10,6 +10,7 @@ use crate::domain::{ BillableMetric, BillableMetricMeta, BillableMetricNew, PaginatedVec, PaginationRequest, }; use crate::errors::StoreError; +use crate::utils::local_id::{IdType, LocalId}; use crate::{domain, Store, StoreResult}; #[async_trait::async_trait] @@ -24,7 +25,7 @@ pub trait BillableMetricInterface { &self, tenant_id: Uuid, pagination: PaginationRequest, - product_family_external_id: String, + product_family_local_id: String, ) -> StoreResult>; async fn insert_billable_metric( @@ -52,7 +53,7 @@ impl BillableMetricInterface for Store { &self, tenant_id: Uuid, pagination: PaginationRequest, - product_family_external_id: String, + product_family_local_id: String, ) -> StoreResult> { let mut conn = self.get_conn().await?; @@ -60,7 +61,7 @@ impl BillableMetricInterface for Store { &mut conn, tenant_id, pagination.into(), - product_family_external_id, + product_family_local_id, ) .await .map_err(Into::>::into)?; @@ -80,16 +81,19 @@ impl BillableMetricInterface for Store { ) -> StoreResult { let mut conn = self.get_conn().await?; - let family = ProductFamilyRow::find_by_external_id_and_tenant_id( + let family = ProductFamilyRow::find_by_local_id_and_tenant_id( &mut conn, - &billable_metric.family_external_id, + &billable_metric.family_local_id, billable_metric.tenant_id, ) .await .map_err(Into::>::into)?; + // TODO create product if None ? + let insertable_entity = BillableMetricRowNew { id: Uuid::now_v7(), + local_id: LocalId::generate_for(IdType::BillableMetric), name: billable_metric.name, description: billable_metric.description, code: billable_metric.code, @@ -112,6 +116,7 @@ impl BillableMetricInterface for Store { created_by: billable_metric.created_by, tenant_id: billable_metric.tenant_id, product_family_id: family.id, + product_id: billable_metric.product_id, }; let res: BillableMetric = self diff --git a/modules/meteroid/crates/meteroid-store/src/repositories/customers.rs b/modules/meteroid/crates/meteroid-store/src/repositories/customers.rs index 88cd4573..1694f0d6 100644 --- a/modules/meteroid/crates/meteroid-store/src/repositories/customers.rs +++ b/modules/meteroid/crates/meteroid-store/src/repositories/customers.rs @@ -398,7 +398,6 @@ impl CustomersInterface for Store { subtotal_recurring: totals.subtotal_recurring, tax_rate: 0, // TODO tax_amount: totals.tax_amount, - local_id: LocalId::generate_for(IdType::Invoice), customer_details: InlineCustomer { billing_address: None, // TODO id: req.customer_id, diff --git a/modules/meteroid/crates/meteroid-store/src/repositories/invoices.rs b/modules/meteroid/crates/meteroid-store/src/repositories/invoices.rs index da8347ca..7e6ea605 100644 --- a/modules/meteroid/crates/meteroid-store/src/repositories/invoices.rs +++ b/modules/meteroid/crates/meteroid-store/src/repositories/invoices.rs @@ -6,7 +6,7 @@ use chrono::NaiveDateTime; use diesel_async::scoped_futures::ScopedFutureExt; use diesel_models::enums::{MrrMovementType, SubscriptionEventType}; use diesel_models::{DbResult, PgConn}; -use error_stack::Report; +use error_stack::{Report, ResultExt}; use crate::compute::InvoiceLineInterface; use crate::domain::{ @@ -572,7 +572,8 @@ async fn compute_invoice_patch( .await?; let lines = store .compute_dated_invoice_lines(&invoice.invoice.invoice_date, &subscription_details) - .await?; + .await + .change_context(StoreError::InvoiceComputationError)?; Ok(InvoiceLinesPatch::new( &invoice, diff --git a/modules/meteroid/crates/meteroid-store/src/repositories/plans.rs b/modules/meteroid/crates/meteroid-store/src/repositories/plans.rs index 60b1b968..8e19a96c 100644 --- a/modules/meteroid/crates/meteroid-store/src/repositories/plans.rs +++ b/modules/meteroid/crates/meteroid-store/src/repositories/plans.rs @@ -3,17 +3,16 @@ use crate::StoreResult; use crate::domain::{ FullPlan, FullPlanNew, OrderByRequest, PaginatedVec, PaginationRequest, Plan, - PlanAndVersionPatch, PlanFilters, PlanForList, PlanPatch, PlanVersion, PlanVersionLatest, + PlanAndVersionPatch, PlanFilters, PlanOverview, PlanPatch, PlanVersion, PlanVersionFilter, PlanVersionNew, PlanWithVersion, PriceComponent, PriceComponentNew, TrialPatch, }; use common_eventbus::Event; use diesel_async::scoped_futures::ScopedFutureExt; use diesel_async::AsyncConnection; use diesel_models::plan_versions::{ - PlanVersionRow, PlanVersionRowLatest, PlanVersionRowNew, PlanVersionRowPatch, - PlanVersionTrialRowPatch, + PlanVersionRow, PlanVersionRowNew, PlanVersionRowPatch, PlanVersionTrialRowPatch, }; -use diesel_models::plans::{PlanRow, PlanRowForList, PlanRowNew, PlanRowPatch}; +use diesel_models::plans::{PlanRow, PlanRowNew, PlanRowOverview, PlanRowPatch}; use diesel_models::price_components::PriceComponentRow; use diesel_models::product_families::ProductFamilyRow; use diesel_models::tenants::TenantRow; @@ -26,49 +25,54 @@ use crate::errors::StoreError; pub trait PlansInterface { async fn insert_plan(&self, plan: FullPlanNew) -> StoreResult; - async fn get_plan_by_external_id( + async fn get_plan( &self, - external_id: &str, + local_id: &str, auth_tenant_id: Uuid, + version_filter: PlanVersionFilter, ) -> StoreResult; - async fn get_plan_by_id( + /** + * Details of a plan irrespective of version + */ + async fn get_plan_overview( &self, - plan_id: Uuid, + local_id: &str, auth_tenant_id: Uuid, - ) -> StoreResult; + ) -> StoreResult; - async fn find_plan_by_external_id_and_status( + /** + * Find a plan by local id and version, including pricing components + */ + async fn get_detailed_plan( &self, - external_id: &str, + local_id: &str, auth_tenant_id: Uuid, - is_draft: Option, - ) -> StoreResult>; + version_filter: PlanVersionFilter, + ) -> StoreResult; async fn list_plans( &self, auth_tenant_id: Uuid, - product_family_external_id: Option, + product_family_local_id: Option, filters: PlanFilters, pagination: PaginationRequest, order_by: OrderByRequest, - ) -> StoreResult>; + ) -> StoreResult>; - async fn list_latest_published_plan_versions( - &self, - auth_tenant_id: Uuid, - ) -> StoreResult>; async fn get_plan_version_by_id( &self, id: Uuid, auth_tenant_id: Uuid, ) -> StoreResult; + async fn list_plan_versions( &self, plan_id: Uuid, auth_tenant_id: Uuid, pagination: PaginationRequest, ) -> StoreResult>; + async fn copy_plan_version_to_draft( &self, plan_version_id: Uuid, @@ -83,12 +87,6 @@ pub trait PlansInterface { auth_actor: Uuid, ) -> StoreResult; - async fn get_last_published_plan_version( - &self, - plan_id: Uuid, - auth_tenant_id: Uuid, - ) -> StoreResult>; - async fn discard_draft_plan_version( &self, plan_version_id: Uuid, @@ -96,13 +94,7 @@ pub trait PlansInterface { auth_actor: Uuid, ) -> StoreResult<()>; - async fn patch_published_plan(&self, patch: PlanPatch) -> StoreResult; - - async fn get_plan_with_version_by_external_id( - &self, - external_id: &str, - auth_tenant_id: Uuid, - ) -> StoreResult; + async fn patch_published_plan(&self, patch: PlanPatch) -> StoreResult; async fn patch_draft_plan(&self, patch: PlanAndVersionPatch) -> StoreResult; @@ -120,9 +112,9 @@ impl PlansInterface for Store { price_components, } = full_plan; - let product_family = ProductFamilyRow::find_by_external_id_and_tenant_id( + let product_family = ProductFamilyRow::find_by_local_id_and_tenant_id( &mut conn, - plan.product_family_external_id.as_str(), + plan.product_family_local_id.as_str(), plan.tenant_id, ) .await @@ -157,6 +149,25 @@ impl PlansInterface for Store { .map(Into::into) .map_err(|err| StoreError::DatabaseError(err.error))?; + let (active_version_id, draft_version_id) = + match inserted_plan_version_new.is_draft_version { + true => (None, Some(Some(inserted_plan_version_new.id))), + false => (Some(Some(inserted_plan_version_new.id)), None), + }; + + let updated: Plan = PlanRowPatch { + id: inserted.id, + tenant_id: inserted.tenant_id, + name: None, + description: None, + active_version_id, + draft_version_id, + } + .update(conn) + .await + .map(Into::into) + .map_err(|err| StoreError::DatabaseError(err.error))?; + // insert price component as batch, etc let inserted_price_components = PriceComponentRow::insert_batch( conn, @@ -166,7 +177,7 @@ impl PlansInterface for Store { PriceComponentNew { plan_version_id: inserted_plan_version_new.id, name: p.name, - product_item_id: p.product_item_id, + product_id: p.product_id, fee: p.fee, } .try_into() @@ -182,7 +193,7 @@ impl PlansInterface for Store { Ok::<_, StoreError>(FullPlan { price_components: inserted_price_components, - plan: inserted, + plan: updated, version: inserted_plan_version_new, }) } @@ -203,73 +214,57 @@ impl PlansInterface for Store { Ok(res) } - async fn get_plan_by_external_id( + async fn get_plan( &self, - external_id: &str, + local_id: &str, auth_tenant_id: Uuid, + version_filter: PlanVersionFilter, ) -> StoreResult { let mut conn = self.get_conn().await?; - let plan: Plan = - PlanRow::get_by_external_id_and_tenant_id(&mut conn, external_id, auth_tenant_id) - .await - .map(Into::into) - .map_err(|err| StoreError::DatabaseError(err.error))?; - - let version: PlanVersion = - PlanVersionRow::get_latest_by_plan_id_and_tenant_id(&mut conn, plan.id, auth_tenant_id) - .await - .map(Into::into) - .map_err(|err| StoreError::DatabaseError(err.error))?; - - Ok(PlanWithVersion { plan, version }) + PlanRow::get_with_version_by_local_id( + &mut conn, + local_id, + auth_tenant_id, + version_filter.into(), + ) + .await + .map_err(Into::into) + .map(Into::into) } - async fn get_plan_by_id( + + async fn get_plan_overview( &self, - plan_id: Uuid, + local_id: &str, auth_tenant_id: Uuid, - ) -> StoreResult { + ) -> StoreResult { let mut conn = self.get_conn().await?; - let plan: Plan = PlanRow::get_by_id_and_tenant_id(&mut conn, plan_id, auth_tenant_id) + PlanRow::get_overview_by_local_id(&mut conn, local_id, auth_tenant_id) .await + .map_err(Into::into) .map(Into::into) - .map_err(|err| StoreError::DatabaseError(err.error))?; - - let version: PlanVersion = - PlanVersionRow::get_latest_by_plan_id_and_tenant_id(&mut conn, plan.id, auth_tenant_id) - .await - .map(Into::into) - .map_err(|err| StoreError::DatabaseError(err.error))?; - - Ok(PlanWithVersion { plan, version }) } - async fn find_plan_by_external_id_and_status( + async fn get_detailed_plan( &self, - external_id: &str, + local_id: &str, auth_tenant_id: Uuid, - is_draft: Option, - ) -> StoreResult> { + version_filter: PlanVersionFilter, + ) -> StoreResult { let mut conn = self.get_conn().await?; - let plan: Plan = - PlanRow::get_by_external_id_and_tenant_id(&mut conn, external_id, auth_tenant_id) - .await - .map(Into::into) - .map_err(|err| StoreError::DatabaseError(err.error))?; - - let version: Option = PlanVersionRow::find_latest_by_plan_id_and_tenant_id( + let plan_with_version: PlanWithVersion = PlanRow::get_with_version_by_local_id( &mut conn, - plan.id, + local_id, auth_tenant_id, - is_draft, + version_filter.into(), ) .await - .map(|opt| opt.map(Into::into)) + .map(Into::into) .map_err(|err| StoreError::DatabaseError(err.error))?; - match version { + match plan_with_version.version { Some(version) => { let price_components: Vec = PriceComponentRow::list_by_plan_version_id( @@ -283,30 +278,32 @@ impl PlansInterface for Store { .map(TryInto::try_into) .collect::, _>>()?; - Ok(Some(FullPlan { - plan, + Ok(FullPlan { + plan: plan_with_version.plan, version, price_components, - })) + }) + } + None => { + Err(StoreError::ValueNotFound("Plan version was not resolved".to_string()).into()) } - None => Ok(None), } } async fn list_plans( &self, auth_tenant_id: Uuid, - product_family_external_id: Option, + product_family_local_id: Option, filters: PlanFilters, pagination: PaginationRequest, order_by: OrderByRequest, - ) -> StoreResult> { + ) -> StoreResult> { let mut conn = self.get_conn().await?; - let rows = PlanRowForList::list( + let rows = PlanRowOverview::list( &mut conn, auth_tenant_id, - product_family_external_id, + product_family_local_id, filters.into(), pagination.into(), order_by.into(), @@ -314,7 +311,7 @@ impl PlansInterface for Store { .await .map_err(Into::>::into)?; - let res: PaginatedVec = PaginatedVec { + let res: PaginatedVec = PaginatedVec { items: rows.items.into_iter().map(Into::into).collect(), total_pages: rows.total_pages, total_results: rows.total_results, @@ -323,18 +320,6 @@ impl PlansInterface for Store { Ok(res) } - async fn list_latest_published_plan_versions( - &self, - auth_tenant_id: Uuid, - ) -> StoreResult> { - let mut conn = self.get_conn().await?; - - PlanVersionRowLatest::list(&mut conn, auth_tenant_id) - .await - .map_err(Into::into) - .map(|x| x.into_iter().map(Into::into).collect()) - } - async fn get_plan_version_by_id( &self, id: Uuid, @@ -411,7 +396,6 @@ impl PlansInterface for Store { currency: original.currency, billing_cycles: original.billing_cycles, created_by: auth_actor, - billing_periods: original.billing_periods.into_iter().flatten().collect(), } .insert(conn) .await @@ -425,6 +409,18 @@ impl PlansInterface for Store { .await .map_err(Into::>::into)?; + PlanRowPatch { + id: original.plan_id, + tenant_id: original.tenant_id, + name: None, + description: None, + active_version_id: None, + draft_version_id: Some(Some(new.id)), + } + .update(conn) + .await + .map_err(Into::>::into)?; + Ok(new.into()) } .scope_boxed() @@ -451,6 +447,18 @@ impl PlansInterface for Store { .await .map_err(Into::>::into)?; + PlanRowPatch { + id: published.plan_id, + tenant_id: published.tenant_id, + name: None, + description: None, + active_version_id: Some(Some(published.id)), + draft_version_id: Some(None), + } + .update(conn) + .await + .map_err(Into::>::into)?; + Ok(published.into()) } .scope_boxed() @@ -469,23 +477,6 @@ impl PlansInterface for Store { Ok(res) } - async fn get_last_published_plan_version( - &self, - plan_id: Uuid, - auth_tenant_id: Uuid, - ) -> StoreResult> { - let mut conn = self.get_conn().await?; - PlanVersionRow::find_latest_by_plan_id_and_tenant_id( - &mut conn, - plan_id, - auth_tenant_id, - Some(false), - ) - .await - .map(|opt| opt.map(Into::into)) - .map_err(Into::into) - } - async fn discard_draft_plan_version( &self, plan_version_id: Uuid, @@ -503,10 +494,23 @@ impl PlansInterface for Store { .await .map_err(Into::>::into)?; + PlanRowPatch { + id: original.plan_id, + tenant_id: original.tenant_id, + name: None, + description: None, + active_version_id: None, + draft_version_id: Some(None), + } + .update(conn) + .await + .map_err(Into::>::into)?; + PlanVersionRow::delete_draft(conn, plan_version_id, auth_tenant_id) .await .map_err(Into::>::into)?; + // only deletes if no versions left PlanRow::delete(conn, original.plan_id, auth_tenant_id) .await .map_err(Into::>::into)?; @@ -529,7 +533,7 @@ impl PlansInterface for Store { Ok(res) } - async fn patch_published_plan(&self, patch: PlanPatch) -> StoreResult { + async fn patch_published_plan(&self, patch: PlanPatch) -> StoreResult { let mut conn = self.get_conn().await?; let patch: PlanRowPatch = patch.into(); @@ -539,24 +543,7 @@ impl PlansInterface for Store { .await .map_err(Into::>::into)?; - PlanRow::get_with_version_by_external_id( - &mut conn, - plan.external_id.as_str(), - plan.tenant_id, - ) - .await - .map_err(Into::into) - .map(Into::into) - } - - async fn get_plan_with_version_by_external_id( - &self, - external_id: &str, - auth_tenant_id: Uuid, - ) -> StoreResult { - let mut conn = self.get_conn().await?; - - PlanRow::get_with_version_by_external_id(&mut conn, external_id, auth_tenant_id) + PlanRow::get_overview_by_local_id(&mut conn, plan.local_id.as_str(), plan.tenant_id) .await .map_err(Into::into) .map(Into::into) @@ -580,6 +567,7 @@ impl PlansInterface for Store { tenant_id: patched_version.tenant_id, name: patch.name, description: patch.description, + active_version_id: None, } .into(); diff --git a/modules/meteroid/crates/meteroid-store/src/repositories/price_components.rs b/modules/meteroid/crates/meteroid-store/src/repositories/price_components.rs index 05b8ecf5..d1bfde93 100644 --- a/modules/meteroid/crates/meteroid-store/src/repositories/price_components.rs +++ b/modules/meteroid/crates/meteroid-store/src/repositories/price_components.rs @@ -7,6 +7,7 @@ use diesel_models::price_components::PriceComponentRow; use uuid::Uuid; use crate::errors::StoreError; +use crate::utils::local_id::{IdType, LocalId}; #[async_trait::async_trait] pub trait PriceComponentInterface { @@ -119,9 +120,10 @@ impl PriceComponentInterface for Store { let mut conn = self.get_conn().await?; let price_component: PriceComponentRow = PriceComponentRow { id: price_component.id, + local_id: LocalId::generate_for(IdType::PriceComponent), plan_version_id, name: price_component.name, - product_item_id: price_component.product_item_id, + product_id: price_component.product_id, fee: json_fee, billable_metric_id: price_component.fee.metric_id(), }; diff --git a/modules/meteroid/crates/meteroid-store/src/repositories/product_families.rs b/modules/meteroid/crates/meteroid-store/src/repositories/product_families.rs index e2090c98..83172778 100644 --- a/modules/meteroid/crates/meteroid-store/src/repositories/product_families.rs +++ b/modules/meteroid/crates/meteroid-store/src/repositories/product_families.rs @@ -17,9 +17,9 @@ pub trait ProductFamilyInterface { auth_tenant_id: Uuid, ) -> StoreResult>; - async fn find_product_family_by_external_id( + async fn find_product_family_by_local_id( &self, - external_id: &str, + local_id: &str, auth_tenant_id: Uuid, ) -> StoreResult; } @@ -74,14 +74,14 @@ impl ProductFamilyInterface for Store { .map(|x| x.into_iter().map(Into::into).collect()) } - async fn find_product_family_by_external_id( + async fn find_product_family_by_local_id( &self, - external_id: &str, + local_id: &str, auth_tenant_id: Uuid, ) -> StoreResult { let mut conn = self.get_conn().await?; - ProductFamilyRow::find_by_external_id_and_tenant_id(&mut conn, external_id, auth_tenant_id) + ProductFamilyRow::find_by_local_id_and_tenant_id(&mut conn, local_id, auth_tenant_id) .await .map_err(Into::into) .map(Into::into) diff --git a/modules/meteroid/crates/meteroid-store/src/repositories/products.rs b/modules/meteroid/crates/meteroid-store/src/repositories/products.rs index 8fa84c93..84ee7f3b 100644 --- a/modules/meteroid/crates/meteroid-store/src/repositories/products.rs +++ b/modules/meteroid/crates/meteroid-store/src/repositories/products.rs @@ -1,6 +1,7 @@ use crate::domain::{OrderByRequest, PaginatedVec, PaginationRequest, Product, ProductNew}; use crate::errors::StoreError; use crate::store::Store; +use crate::utils::local_id::{IdType, LocalId}; use crate::StoreResult; use diesel_models::product_families::ProductFamilyRow; use diesel_models::products::{ProductRow, ProductRowNew}; @@ -14,14 +15,14 @@ pub trait ProductInterface { async fn list_products( &self, auth_tenant_id: Uuid, - family_external_id: &str, + family_local_id: &str, pagination: PaginationRequest, order_by: OrderByRequest, ) -> StoreResult>; async fn search_products( &self, auth_tenant_id: Uuid, - family_external_id: &str, + family_local_id: &str, query: &str, pagination: PaginationRequest, order_by: OrderByRequest, @@ -33,9 +34,9 @@ impl ProductInterface for Store { async fn create_product(&self, product: ProductNew) -> StoreResult { let mut conn = self.get_conn().await?; - let family = ProductFamilyRow::find_by_external_id_and_tenant_id( + let family = ProductFamilyRow::find_by_local_id_and_tenant_id( &mut conn, - product.family_external_id.as_str(), + product.family_local_id.as_str(), product.tenant_id, ) .await @@ -43,6 +44,7 @@ impl ProductInterface for Store { let insertable = ProductRowNew { id: Uuid::now_v7(), + local_id: LocalId::generate_for(IdType::Product), name: product.name, description: product.description, created_by: product.created_by, @@ -69,7 +71,7 @@ impl ProductInterface for Store { async fn list_products( &self, auth_tenant_id: Uuid, - family_external_id: &str, + family_local_id: &str, pagination: PaginationRequest, order_by: OrderByRequest, ) -> StoreResult> { @@ -78,7 +80,7 @@ impl ProductInterface for Store { let rows = ProductRow::list( &mut conn, auth_tenant_id, - family_external_id, + family_local_id, pagination.into(), order_by.into(), ) @@ -97,7 +99,7 @@ impl ProductInterface for Store { async fn search_products( &self, auth_tenant_id: Uuid, - family_external_id: &str, + family_local_id: &str, query: &str, pagination: PaginationRequest, order_by: OrderByRequest, @@ -107,7 +109,7 @@ impl ProductInterface for Store { let rows = ProductRow::search( &mut conn, auth_tenant_id, - family_external_id, + family_local_id, query, pagination.into(), order_by.into(), diff --git a/modules/meteroid/crates/meteroid-store/src/repositories/subscriptions.rs b/modules/meteroid/crates/meteroid-store/src/repositories/subscriptions.rs index 26aaf97a..b85c75d1 100644 --- a/modules/meteroid/crates/meteroid-store/src/repositories/subscriptions.rs +++ b/modules/meteroid/crates/meteroid-store/src/repositories/subscriptions.rs @@ -31,7 +31,6 @@ use crate::domain::subscription_add_ons::SubscriptionAddOn; use crate::repositories::historical_rates::HistoricalRatesInterface; use crate::repositories::invoicing_entities::InvoicingEntityInterface; use crate::repositories::{CustomersInterface, InvoiceInterface}; -use crate::utils::local_id::{IdType, LocalId}; use common_eventbus::Event; use diesel_models::add_ons::AddOnRow; use diesel_models::applied_coupons::{ @@ -641,10 +640,12 @@ impl SubscriptionInterface for Store { Ok(SubscriptionDetails { id: subscription.id, + local_id: subscription.local_id, tenant_id: subscription.tenant_id, customer_id: subscription.customer_id, plan_version_id: subscription.plan_version_id, - customer_external_id: subscription.customer_alias, + customer_local_id: subscription.customer_local_id, + customer_alias: subscription.customer_alias, billing_start_date: subscription.billing_start_date, billing_end_date: subscription.billing_end_date, billing_day: subscription.billing_day, @@ -1190,7 +1191,7 @@ fn process_create_subscription_components( )?; processed_components.push(SubscriptionComponentNewInternal { price_component_id: Some(c.id), - product_item_id: c.product_item_id, + product_id: c.product_id, name: c.name.clone(), period, fee, @@ -1221,7 +1222,7 @@ fn process_create_subscription_components( // If the component is not in any of the lists, add it as is processed_components.push(SubscriptionComponentNewInternal { price_component_id: Some(c.id), - product_item_id: c.product_item_id, + product_id: c.product_id, name: c.name.clone(), period, fee, @@ -1322,7 +1323,6 @@ pub fn subscription_to_draft( net_terms: subscription.net_terms, reference: None, memo: None, - local_id: LocalId::generate_for(IdType::Invoice), due_at: Some(due_date), plan_name: None, // TODO invoice_number: invoice_number.to_string(), diff --git a/modules/meteroid/crates/meteroid-store/src/repositories/tenants.rs b/modules/meteroid/crates/meteroid-store/src/repositories/tenants.rs index f63eb0d4..b4d974eb 100644 --- a/modules/meteroid/crates/meteroid-store/src/repositories/tenants.rs +++ b/modules/meteroid/crates/meteroid-store/src/repositories/tenants.rs @@ -205,7 +205,7 @@ impl StoreInternal { conn, domain::ProductFamilyNew { name: "Default".to_string(), - external_id: "default".to_string(), + local_id: "default".to_string(), tenant_id: inserted.id, }, ) diff --git a/modules/meteroid/crates/meteroid-store/src/utils/local_id.rs b/modules/meteroid/crates/meteroid-store/src/utils/local_id.rs index e81eb911..3040d58e 100644 --- a/modules/meteroid/crates/meteroid-store/src/utils/local_id.rs +++ b/modules/meteroid/crates/meteroid-store/src/utils/local_id.rs @@ -2,25 +2,34 @@ use nanoid::nanoid; #[derive(Debug)] pub enum IdType { - Organization, - Tenant, - InvoicingEntity, + AddOn, + BillableMetric, + Coupon, Customer, Invoice, - Subscription, - Plan, + InvoicingEntity, Other, + Plan, + PriceComponent, + Product, + Subscription, + Tenant, } impl IdType { fn prefix(&self) -> &'static str { match self { - IdType::Organization => "org_", - IdType::Tenant => "", - IdType::InvoicingEntity => "ive_", + IdType::AddOn => "add_", + IdType::BillableMetric => "bm_", + IdType::Coupon => "cou_", IdType::Customer => "cus_", IdType::Invoice => "inv_", + IdType::InvoicingEntity => "ive_", + IdType::Plan => "plan_", + IdType::PriceComponent => "price_", + IdType::Product => "prd_", IdType::Subscription => "sub_", + IdType::Tenant => "", _ => "", } } diff --git a/modules/meteroid/migrations/diesel/2024-10-24-155813_products/down.sql b/modules/meteroid/migrations/diesel/2024-10-24-155813_products/down.sql new file mode 100644 index 00000000..67b81dfd --- /dev/null +++ b/modules/meteroid/migrations/diesel/2024-10-24-155813_products/down.sql @@ -0,0 +1,56 @@ +-- This file should undo anything in `up.sql` + + +ALTER TABLE "billable_metric" + DROP COLUMN "product_id"; + +ALTER TABLE "plan" + DROP COLUMN "active_version_id"; + +ALTER TABLE "plan" + DROP COLUMN "draft_version_id"; + +ALTER TABLE "plan_version" + ADD COLUMN "billing_periods" "BillingPeriodEnum"[] NOT NULL DEFAULT '{}'; + +ALTER TABLE "price_component" + RENAME COLUMN "product_id" TO "product_item_id"; + +ALTER TABLE "subscription_component" + RENAME COLUMN "product_id" TO "product_item_id"; + +ALTER TABLE "add_on" + DROP COLUMN "local_id"; + +ALTER TABLE "billable_metric" + DROP COLUMN "local_id"; + +ALTER TABLE "coupon" + DROP COLUMN "local_id"; + +ALTER TABLE "credit_note" + DROP COLUMN "local_id"; + +ALTER TABLE "customer" + DROP COLUMN "local_id"; +-- ALTER TABLE "customer" +-- RENAME COLUMN "external_id" TO "alias"; + +ALTER TABLE "plan" + RENAME COLUMN "local_id" TO "external_id"; + +ALTER INDEX "plan_tenant_id_local_id_key" RENAME TO "plan_tenant_id_external_id_key"; + + +ALTER TABLE "price_component" + DROP COLUMN "local_id"; + +ALTER TABLE "product" + DROP COLUMN "local_id"; + +ALTER INDEX "product_family_tenant_id_local_id_key" RENAME TO "product_family_external_id_tenant_id_key"; +ALTER TABLE "product_family" + RENAME COLUMN "local_id" TO "external_id"; + +ALTER TABLE "subscription" + DROP COLUMN "local_id"; diff --git a/modules/meteroid/migrations/diesel/2024-10-24-155813_products/up.sql b/modules/meteroid/migrations/diesel/2024-10-24-155813_products/up.sql new file mode 100644 index 00000000..fb935380 --- /dev/null +++ b/modules/meteroid/migrations/diesel/2024-10-24-155813_products/up.sql @@ -0,0 +1,66 @@ +-- Your SQL goes here + + +ALTER TABLE "billable_metric" + ADD COLUMN "product_id" UUID REFERENCES "product" ("id"); + +ALTER TABLE "plan" + ADD COLUMN "active_version_id" UUID REFERENCES "plan_version" ("id"); +ALTER TABLE "plan" + ADD COLUMN "draft_version_id" UUID REFERENCES "plan_version" ("id"); + +ALTER TABLE "plan_version" + DROP COLUMN "billing_periods"; + +ALTER TABLE "price_component" + RENAME COLUMN "product_item_id" TO "product_id"; + +ALTER TABLE "subscription_component" + RENAME COLUMN "product_item_id" TO "product_id"; + +ALTER TABLE "add_on" + ADD COLUMN "local_id" TEXT NOT NULL DEFAULT gen_random_uuid()::TEXT, + ADD UNIQUE ("tenant_id", "local_id"); + + +ALTER TABLE "billable_metric" + ADD COLUMN "local_id" TEXT NOT NULL DEFAULT gen_random_uuid()::TEXT, + ADD UNIQUE ("tenant_id", "local_id"); + + +ALTER TABLE "coupon" + ADD COLUMN "local_id" TEXT NOT NULL DEFAULT gen_random_uuid()::TEXT, + ADD UNIQUE ("tenant_id", "local_id"); + +ALTER TABLE "credit_note" + ADD COLUMN "local_id" TEXT NOT NULL DEFAULT gen_random_uuid()::TEXT, + ADD UNIQUE ("tenant_id", "local_id"); + +-- ALTER TABLE "customer" +-- RENAME COLUMN "alias" TO "external_id"; +ALTER TABLE "customer" + ADD COLUMN "local_id" TEXT NOT NULL DEFAULT gen_random_uuid()::TEXT, + ADD UNIQUE ("tenant_id", "local_id"); + +ALTER TABLE "plan" + RENAME COLUMN "external_id" TO "local_id"; +ALTER INDEX "plan_tenant_id_external_id_key" RENAME TO "plan_tenant_id_local_id_key"; + +ALTER TABLE "price_component" + ADD COLUMN "local_id" TEXT NOT NULL DEFAULT gen_random_uuid()::TEXT, + ADD UNIQUE ("plan_version_id", "local_id"); + +ALTER TABLE "product" + ADD COLUMN "local_id" TEXT NOT NULL DEFAULT gen_random_uuid()::TEXT, + ADD UNIQUE ("tenant_id", "local_id"); + +ALTER TABLE "product_family" + RENAME COLUMN "external_id" TO "local_id"; +ALTER INDEX "product_family_external_id_tenant_id_key" RENAME TO "product_family_tenant_id_local_id_key"; +DROP INDEX if exists "product_family_api_name_tenant_id_key"; + +ALTER TABLE "subscription" + ADD COLUMN "local_id" TEXT NOT NULL DEFAULT gen_random_uuid()::TEXT, + ADD UNIQUE ("tenant_id", "local_id"); + + diff --git a/modules/meteroid/proto/api/addons/v1/models.proto b/modules/meteroid/proto/api/addons/v1/models.proto index 5cf54ff2..ad3fae70 100644 --- a/modules/meteroid/proto/api/addons/v1/models.proto +++ b/modules/meteroid/proto/api/addons/v1/models.proto @@ -9,4 +9,5 @@ message AddOn { string name = 2; // todo extract in a separate package? meteroid.api.components.v1.Fee fee = 3; + string local_id = 4; } diff --git a/modules/meteroid/proto/api/billablemetrics/v1/billablemetrics.proto b/modules/meteroid/proto/api/billablemetrics/v1/billablemetrics.proto index 1a44ceba..858fc5c0 100644 --- a/modules/meteroid/proto/api/billablemetrics/v1/billablemetrics.proto +++ b/modules/meteroid/proto/api/billablemetrics/v1/billablemetrics.proto @@ -12,7 +12,8 @@ message CreateBillableMetricRequest { Aggregation aggregation = 4; SegmentationMatrix segmentation_matrix = 5; optional string usage_group_key = 6; - string family_external_id = 7; + string family_local_id = 7; + optional string product_id = 8; } message CreateBillableMetricResponse { @@ -20,7 +21,7 @@ message CreateBillableMetricResponse { } message ListBillableMetricsRequest { - string family_external_id = 1; + string family_local_id = 1; meteroid.common.v1.Pagination pagination = 2; } diff --git a/modules/meteroid/proto/api/billablemetrics/v1/models.proto b/modules/meteroid/proto/api/billablemetrics/v1/models.proto index 274a19c2..29cd6ff9 100644 --- a/modules/meteroid/proto/api/billablemetrics/v1/models.proto +++ b/modules/meteroid/proto/api/billablemetrics/v1/models.proto @@ -75,6 +75,8 @@ message BillableMetric { optional string usage_group_key = 7; google.protobuf.Timestamp created_at = 8; google.protobuf.Timestamp archived_at = 9; + optional string product_id = 10; + string local_id = 11; } message BillableMetricMeta { diff --git a/modules/meteroid/proto/api/coupons/v1/models.proto b/modules/meteroid/proto/api/coupons/v1/models.proto index 48041547..8a40a7ac 100644 --- a/modules/meteroid/proto/api/coupons/v1/models.proto +++ b/modules/meteroid/proto/api/coupons/v1/models.proto @@ -11,6 +11,7 @@ message Coupon { CouponDiscount discount = 4; optional google.protobuf.Timestamp expires_at = 5; optional int32 redemption_limit = 6; + string local_id = 7; } message CouponDiscount { diff --git a/modules/meteroid/proto/api/customers/v1/models.proto b/modules/meteroid/proto/api/customers/v1/models.proto index 04778a91..80dffc42 100644 --- a/modules/meteroid/proto/api/customers/v1/models.proto +++ b/modules/meteroid/proto/api/customers/v1/models.proto @@ -30,6 +30,7 @@ message CustomerBrief { optional string alias = 4; optional string country = 5; string created_at = 6; + string local_id = 7; } message Address { @@ -61,6 +62,7 @@ message Customer { optional Address billing_address = 12; optional ShippingAddress shipping_address = 13; string invoicing_entity_id = 14; + string local_id = 15; } message CustomerNew { diff --git a/modules/meteroid/proto/api/plans/v1/models.proto b/modules/meteroid/proto/api/plans/v1/models.proto index b54c043d..b0c4c2ff 100644 --- a/modules/meteroid/proto/api/plans/v1/models.proto +++ b/modules/meteroid/proto/api/plans/v1/models.proto @@ -2,8 +2,6 @@ syntax = "proto3"; package meteroid.api.plans.v1; -import "api/shared/v1/shared.proto"; - enum PlanStatus { DRAFT = 0; ACTIVE = 1; @@ -25,7 +23,6 @@ message PlanFilters { } message PlanBillingConfiguration { - repeated meteroid.api.shared.v1.BillingPeriod billing_periods = 1; uint32 net_terms = 2; oneof service_period_start { @@ -78,6 +75,8 @@ message PlanVersion { TrialConfig trial_config = 4; PlanBillingConfiguration billing_config = 5; string currency = 6; + optional int32 period_start_day = 8; + int32 net_terms = 9; } message ListPlanVersion { @@ -85,20 +84,7 @@ message ListPlanVersion { bool is_draft = 2; uint32 version = 3; string currency = 6; -} - -message ListSubscribablePlanVersion { - string plan_id = 1; - string id = 2; - string plan_name = 3; - int32 version = 4; - string created_by = 5; - optional int32 period_start_day = 8; - int32 net_terms = 9; - string currency = 10; - string product_family_id = 11; - string product_family_name = 12; - TrialConfig trial_config = 13; + string created_at = 7; } message Metadata { @@ -109,44 +95,53 @@ message Metadata { message Plan { string id = 1; string name = 2; - string external_id = 3; + string local_id = 3; optional string description = 4; PlanType plan_type = 5; PlanStatus plan_status = 6; // string currency = 7; - // PlanVersion current_version = 8; - // repeated Metadata metadata = 9; + // PlanVersion version = 8; + optional string active_version_id = 11; + optional string draft_version_id = 12; } -message ListPlan { +message PlanOverview { string id = 1; string name = 2; - string external_id = 3; + string local_id = 3; optional string description = 4; PlanType plan_type = 5; PlanStatus plan_status = 6; - string product_family_id = 7; - string product_family_name = 8; + string product_family_name = 7; + ActiveVersionInfo active_version = 8; + bool has_draft_version = 9; + uint32 subscription_count = 10; + + message ActiveVersionInfo { + uint32 version = 1; + optional uint32 trial_duration_days = 2; + } } -message PlanDetails { +message PlanWithVersion { Plan plan = 1; - PlanVersion current_version = 2; - repeated Metadata metadata = 9; + PlanVersion version = 2; } -message PlanOverview { - string plan_id = 1; - string plan_version_id = 2; - string name = 3; - uint32 version = 4; - optional string description = 5; - string currency = 6; - uint32 net_terms = 7; - repeated meteroid.api.shared.v1.BillingPeriod billing_periods = 8; - bool is_draft = 9; - PlanType plan_type = 10; -} +//message PlanOverview { +// string plan_id = 1; +// string plan_version_id = 2; +// string name = 3; +// uint32 version = 4; +// optional string description = 5; +// string currency = 6; +// uint32 net_terms = 7; +// string local_id = 8; +// PlanType plan_type = 9; +// bool is_draft = 10; +// bool has_draft = 11; +// optional string default_version_id = 12; // TODO we should get the version number here +//} message PlanParameter { oneof param { @@ -164,3 +159,5 @@ message PlanParameter { string component_id = 1; } } + + diff --git a/modules/meteroid/proto/api/plans/v1/plans.proto b/modules/meteroid/proto/api/plans/v1/plans.proto index f53893e9..c5a32cb4 100644 --- a/modules/meteroid/proto/api/plans/v1/plans.proto +++ b/modules/meteroid/proto/api/plans/v1/plans.proto @@ -6,26 +6,18 @@ import "api/plans/v1/models.proto"; import "api/shared/v1/shared.proto"; import "common/v1/pagination.proto"; +import "google/protobuf/empty.proto"; + // Request and response messages for CreateDraftPlan RPC message CreateDraftPlanRequest { string name = 1; - string external_id = 2; optional string description = 3; - string product_family_external_id = 4; + string product_family_local_id = 4; PlanType plan_type = 5; } message CreateDraftPlanResponse { - PlanDetails plan = 1; -} - -// Request and response messages for GetPlanByExternalId RPC -message GetPlanByExternalIdRequest { - string external_id = 1; -} - -message GetPlanByExternalIdResponse { - PlanDetails plan_details = 1; + PlanWithVersion plan = 1; } // Request and response messages for ListPlans RPC @@ -36,26 +28,17 @@ message ListPlansRequest { NAME_DESC = 2; NAME_ASC = 3; } - optional string product_family_external_id = 1; + optional string product_family_local_id = 1; PlanFilters filters = 2; SortBy sort_by = 3; meteroid.common.v1.Pagination pagination = 4; } message ListPlansResponse { - repeated ListPlan plans = 1; + repeated PlanOverview plans = 1; meteroid.common.v1.PaginationResponse pagination_meta = 2; } -// Request and response messages for GetPlanVersionById RPC -message GetPlanVersionByIdRequest { - string plan_version_id = 1; -} - -message GetPlanVersionByIdResponse { - PlanVersion plan_version = 1; -} - // Request and response messages for ListPlanVersionById RPC message ListPlanVersionByIdRequest { string plan_id = 1; @@ -67,12 +50,6 @@ message ListPlanVersionByIdResponse { meteroid.common.v1.PaginationResponse pagination_meta = 2; } -// Request and response messages for ListSubscribablePlansVersions RPC -message ListSubscribablePlanVersionRequest {} - -message ListSubscribablePlanVersionResponse { - repeated ListSubscribablePlanVersion plan_versions = 1; -} // Request and response messages for CopyVersionToDraft RPC message CopyVersionToDraftRequest { @@ -94,14 +71,6 @@ message PublishPlanVersionResponse { PlanVersion plan_version = 1; } -// Request and response messages for GetLastPublishedPlanVersion RPC -message GetLastPublishedPlanVersionRequest { - string plan_id = 1; -} - -message GetLastPublishedPlanVersionResponse { - PlanVersion version = 1; -} // Request and response messages for DiscardDraftVersion RPC message DiscardDraftVersionRequest { @@ -119,11 +88,10 @@ message UpdateDraftPlanOverviewRequest { optional string description = 4; string currency = 5; uint32 net_terms = 6; - repeated meteroid.api.shared.v1.BillingPeriod billing_periods = 7; } message UpdateDraftPlanOverviewResponse { - PlanOverview plan_overview = 1; + PlanWithVersion plan = 1; } // Request and response messages for UpdatePublishedPlanOverview RPC @@ -138,14 +106,6 @@ message UpdatePublishedPlanOverviewResponse { PlanOverview plan_overview = 1; } -// Request and response messages for GetPlanOverviewByExternalId RPC -message GetPlanOverviewByExternalIdRequest { - string external_id = 1; -} - -message GetPlanOverviewByExternalIdResponse { - PlanOverview plan_overview = 1; -} message GetPlanParametersRequest { string plan_version_id = 1; @@ -161,40 +121,49 @@ message UpdatePlanTrialRequest { } message UpdatePlanTrialResponse { - PlanOverview plan_overview = 1; + PlanWithVersion plan = 1; } -message GetPlanByIdRequest { - string plan_id = 1; +message GetPlanWithVersionRequest { + string local_id = 1; + + oneof filter { + uint32 version = 2; + google.protobuf.Empty draft = 3; + google.protobuf.Empty active = 4; + } +} + +message GetPlanWithVersionResponse { + PlanWithVersion plan = 1; } -message GetPlanByIdResponse { - PlanDetails plan_details = 1; +message GetPlanOverviewRequest { + string local_id = 1; +} + +message GetPlanOverviewResponse { + PlanOverview plan_overview = 1; } // Response message for all RPCs returning EmptyResponse message EmptyResponse {} service PlansService { - rpc CreateDraftPlan(CreateDraftPlanRequest) returns (CreateDraftPlanResponse) {} - rpc GetPlanByExternalId(GetPlanByExternalIdRequest) returns (GetPlanByExternalIdResponse) {} - rpc GetPlanById(GetPlanByIdRequest) returns (GetPlanByIdResponse) {} + rpc GetPlanOverview(GetPlanOverviewRequest) returns (GetPlanOverviewResponse) {} + rpc GetPlanWithVersion(GetPlanWithVersionRequest) returns (GetPlanWithVersionResponse) {} + rpc ListPlans(ListPlansRequest) returns (ListPlansResponse) {} - rpc GetPlanVersionById(GetPlanVersionByIdRequest) returns (GetPlanVersionByIdResponse) {} + rpc CreateDraftPlan(CreateDraftPlanRequest) returns (CreateDraftPlanResponse) {} + rpc UpdateDraftPlanOverview(UpdateDraftPlanOverviewRequest) returns (UpdateDraftPlanOverviewResponse) {} + rpc UpdatePublishedPlanOverview(UpdatePublishedPlanOverviewRequest) returns (UpdatePublishedPlanOverviewResponse) {} + rpc UpdatePlanTrial(UpdatePlanTrialRequest) returns (UpdatePlanTrialResponse) {} + rpc ListPlanVersionById(ListPlanVersionByIdRequest) returns (ListPlanVersionByIdResponse) {} rpc CopyVersionToDraft(CopyVersionToDraftRequest) returns (CopyVersionToDraftResponse) {} rpc PublishPlanVersion(PublishPlanVersionRequest) returns (PublishPlanVersionResponse) {} - rpc ListSubscribablePlanVersion(ListSubscribablePlanVersionRequest) returns (ListSubscribablePlanVersionResponse) {} - - rpc GetLastPublishedPlanVersion(GetLastPublishedPlanVersionRequest) returns (GetLastPublishedPlanVersionResponse) {} rpc DiscardDraftVersion(DiscardDraftVersionRequest) returns (DiscardDraftVersionResponse) {} - rpc UpdateDraftPlanOverview(UpdateDraftPlanOverviewRequest) returns (UpdateDraftPlanOverviewResponse) {} - rpc UpdatePublishedPlanOverview(UpdatePublishedPlanOverviewRequest) returns (UpdatePublishedPlanOverviewResponse) {} - rpc GetPlanOverviewByExternalId(GetPlanOverviewByExternalIdRequest) returns (GetPlanOverviewByExternalIdResponse) {} - - rpc UpdatePlanTrial(UpdatePlanTrialRequest) returns (UpdatePlanTrialResponse) {} - rpc GetPlanParameters(GetPlanParametersRequest) returns (GetPlanParametersResponse) {} } diff --git a/modules/meteroid/proto/api/pricecomponents/v1/models.proto b/modules/meteroid/proto/api/pricecomponents/v1/models.proto index d42b791c..15ee5f4d 100644 --- a/modules/meteroid/proto/api/pricecomponents/v1/models.proto +++ b/modules/meteroid/proto/api/pricecomponents/v1/models.proto @@ -116,5 +116,6 @@ message PriceComponent { string id = 1; string name = 2; Fee fee = 3; - optional string product_item_id = 4; + optional string product_id = 4; + string local_id = 5; } diff --git a/modules/meteroid/proto/api/pricecomponents/v1/pricecomponents.proto b/modules/meteroid/proto/api/pricecomponents/v1/pricecomponents.proto index 716ec5da..27e1d30b 100644 --- a/modules/meteroid/proto/api/pricecomponents/v1/pricecomponents.proto +++ b/modules/meteroid/proto/api/pricecomponents/v1/pricecomponents.proto @@ -17,7 +17,7 @@ message CreatePriceComponentRequest { string plan_version_id = 1; string name = 2; Fee fee = 3; - optional string product_item_id = 4; + optional string product_id = 4; } message CreatePriceComponentResponse { diff --git a/modules/meteroid/proto/api/productfamilies/v1/models.proto b/modules/meteroid/proto/api/productfamilies/v1/models.proto index 1df96849..379fbc22 100644 --- a/modules/meteroid/proto/api/productfamilies/v1/models.proto +++ b/modules/meteroid/proto/api/productfamilies/v1/models.proto @@ -5,5 +5,5 @@ package meteroid.api.productfamilies.v1; message ProductFamily { string id = 1; string name = 2; - string external_id = 3; -} \ No newline at end of file + string local_id = 3; +} diff --git a/modules/meteroid/proto/api/productfamilies/v1/productfamilies.proto b/modules/meteroid/proto/api/productfamilies/v1/productfamilies.proto index ca3b0b25..09d8184a 100644 --- a/modules/meteroid/proto/api/productfamilies/v1/productfamilies.proto +++ b/modules/meteroid/proto/api/productfamilies/v1/productfamilies.proto @@ -12,23 +12,23 @@ message ListProductFamiliesResponse { message CreateProductFamilyRequest { string name = 1; - string external_id = 2; + string local_id = 2; } message CreateProductFamilyResponse { ProductFamily product_family = 1; } -message GetProductFamilyByExternalIdRequest { - string external_id = 1; +message GetProductFamilyByLocalIdRequest { + string local_id = 1; } -message GetProductFamilyByExternalIdResponse { +message GetProductFamilyByLocalIdResponse { ProductFamily product_family = 1; } service ProductFamiliesService { rpc ListProductFamilies(ListProductFamiliesRequest) returns (ListProductFamiliesResponse) {} rpc CreateProductFamily(CreateProductFamilyRequest) returns (CreateProductFamilyResponse) {} - rpc GetProductFamilyByExternalId(GetProductFamilyByExternalIdRequest) returns (GetProductFamilyByExternalIdResponse) {} + rpc GetProductFamilyByLocalId(GetProductFamilyByLocalIdRequest) returns (GetProductFamilyByLocalIdResponse) {} } diff --git a/modules/meteroid/proto/api/products/v1/models.proto b/modules/meteroid/proto/api/products/v1/models.proto index 1bd6be92..8d6a404b 100644 --- a/modules/meteroid/proto/api/products/v1/models.proto +++ b/modules/meteroid/proto/api/products/v1/models.proto @@ -9,9 +9,11 @@ message Product { string name = 2; optional string description = 3; google.protobuf.Timestamp created_at = 4; + string local_id = 5; } message ProductMeta { string id = 1; string name = 2; + string local_id = 3; } diff --git a/modules/meteroid/proto/api/products/v1/products.proto b/modules/meteroid/proto/api/products/v1/products.proto index fc0442c3..905616be 100644 --- a/modules/meteroid/proto/api/products/v1/products.proto +++ b/modules/meteroid/proto/api/products/v1/products.proto @@ -8,7 +8,7 @@ import "api/products/v1/models.proto"; message CreateProductRequest { string name = 1; optional string description = 2; - string family_external_id = 3; + string family_local_id = 3; // tax, custom fields etc } @@ -17,7 +17,7 @@ message CreateProductResponse { } message ListProductsRequest { - string family_external_id = 1; + string family_local_id = 1; meteroid.common.v1.Pagination pagination = 2; } @@ -27,7 +27,7 @@ message ListProductsResponse { } message SearchProductsRequest { - string family_external_id = 1; + string family_local_id = 1; optional string query = 2; meteroid.common.v1.Pagination pagination = 3; } diff --git a/modules/meteroid/proto/api/stats/v1/models.proto b/modules/meteroid/proto/api/stats/v1/models.proto index bd57cc41..7c65e2c1 100644 --- a/modules/meteroid/proto/api/stats/v1/models.proto +++ b/modules/meteroid/proto/api/stats/v1/models.proto @@ -3,7 +3,6 @@ syntax = "proto3"; package meteroid.api.stats.v1; import "google/protobuf/timestamp.proto"; -import "google/protobuf/wrappers.proto"; import "common/v1/date.proto"; message BreakdownStat { @@ -125,4 +124,4 @@ message MrrLogEntry { MRRMovementType mrr_type = 7; common.v1.Date applies_to = 8; -} \ No newline at end of file +} diff --git a/modules/meteroid/proto/api/subscriptions/v1/models.proto b/modules/meteroid/proto/api/subscriptions/v1/models.proto index 044e5c1a..7dcfb590 100644 --- a/modules/meteroid/proto/api/subscriptions/v1/models.proto +++ b/modules/meteroid/proto/api/subscriptions/v1/models.proto @@ -42,6 +42,7 @@ message Subscription { uint64 mrr_cents = 23; SubscriptionStatus status = 24; // TODO accrued (total up until now ? ) , due (next billing cycle) , last X months of revenue for a graph ? + string local_id = 25; } message SubscriptionDetails { @@ -71,6 +72,7 @@ message CreatedSubscription { optional string invoice_threshold = 14; optional string activated_at = 15; uint64 mrr_cents = 18; + string local_id = 19; } message CreateSubscription { @@ -164,7 +166,7 @@ message BillableMetric { message SubscriptionComponent { string id = 1; optional string price_component_id = 2; - optional string product_item_id = 3; + optional string product_id = 3; string subscription_id = 4; string name = 5; SubscriptionFeeBillingPeriod period = 6; @@ -175,7 +177,7 @@ message SubscriptionComponent { message SubscriptionComponentNewInternal { optional string price_component_id = 1; - optional string product_item_id = 2; + optional string product_id = 2; string name = 3; SubscriptionFeeBillingPeriod period = 4; SubscriptionFee fee = 5; diff --git a/modules/meteroid/proto/api/webhooksout/v1/webhooksout.proto b/modules/meteroid/proto/api/webhooksout/v1/webhooksout.proto index c55feab6..674faa74 100644 --- a/modules/meteroid/proto/api/webhooksout/v1/webhooksout.proto +++ b/modules/meteroid/proto/api/webhooksout/v1/webhooksout.proto @@ -3,8 +3,6 @@ syntax = "proto3"; package meteroid.api.webhooks.out.v1; import "api/webhooksout/v1/models.proto"; -import "google/protobuf/timestamp.proto"; -import "api/invoices/v1/models.proto"; import "common/v1/pagination.proto"; message CreateWebhookEndpointRequest { diff --git a/modules/meteroid/proto/internal/v1/internal.proto b/modules/meteroid/proto/internal/v1/internal.proto index 21313a59..b0c08020 100644 --- a/modules/meteroid/proto/internal/v1/internal.proto +++ b/modules/meteroid/proto/internal/v1/internal.proto @@ -3,18 +3,18 @@ syntax = "proto3"; package meteroid.internal.v1; message ResolvedId { - string external_id = 1; - string meteroid_id = 2; + string alias = 1; + string local_id = 2; } -message ResolveCustomerExternalIdsRequest { +message ResolveCustomerAliasesRequest { string tenant_id = 1; - repeated string external_ids = 2; + repeated string aliases = 2; } -message ResolveCustomerExternalIdsResponse { +message ResolveCustomerAliasesResponse { repeated ResolvedId customers = 1; - repeated string unresolved_ids = 2; + repeated string unresolved_aliases = 2; } message ResolveApiKeyRequest { @@ -28,6 +28,6 @@ message ResolveApiKeyResponse { } service InternalService { - rpc ResolveCustomerExternalIds(ResolveCustomerExternalIdsRequest) returns (ResolveCustomerExternalIdsResponse) {} + rpc ResolveCustomerAliases(ResolveCustomerAliasesRequest) returns (ResolveCustomerAliasesResponse) {} rpc ResolveApiKey(ResolveApiKeyRequest) returns (ResolveApiKeyResponse) {} } diff --git a/modules/meteroid/src/api/addons/mapping.rs b/modules/meteroid/src/api/addons/mapping.rs index 849b5a41..970aa641 100644 --- a/modules/meteroid/src/api/addons/mapping.rs +++ b/modules/meteroid/src/api/addons/mapping.rs @@ -7,6 +7,7 @@ pub mod addons { fn from(value: domain::add_ons::AddOn) -> Self { Self(server::AddOn { id: value.id.to_string(), + local_id: value.local_id, name: value.name, fee: Some( crate::api::pricecomponents::mapping::components::map_fee_domain_to_api( diff --git a/modules/meteroid/src/api/billablemetrics/mapping.rs b/modules/meteroid/src/api/billablemetrics/mapping.rs index ea074e76..898cfc44 100644 --- a/modules/meteroid/src/api/billablemetrics/mapping.rs +++ b/modules/meteroid/src/api/billablemetrics/mapping.rs @@ -95,14 +95,14 @@ pub mod metric { use meteroid_grpc::meteroid::api::billablemetrics::v1 as server; use std::collections::HashMap; + use crate::api::shared::conversions::AsProtoOpt; + use crate::api::shared::mapping::datetime::chrono_to_timestamp; use metering_grpc::meteroid::metering::v1 as metering; use meteroid_grpc::meteroid::api::billablemetrics::v1::segmentation_matrix::Matrix; use meteroid_store::domain; use meteroid_store::domain::billable_metrics::{Dimension, SegmentationMatrix}; use meteroid_store::errors::StoreError; - use crate::api::shared::mapping::datetime::chrono_to_timestamp; - pub struct ServerBillableMetricWrapper(pub server::BillableMetric); impl TryFrom for ServerBillableMetricWrapper { @@ -111,6 +111,7 @@ pub mod metric { fn try_from(value: domain::BillableMetric) -> Result { Ok(ServerBillableMetricWrapper(server::BillableMetric { id: value.id.to_string(), + local_id: value.local_id, name: value.name, code: value.code, description: value.description, @@ -133,6 +134,7 @@ pub mod metric { archived_at: value.archived_at.map(chrono_to_timestamp), created_at: Some(chrono_to_timestamp(value.created_at)), usage_group_key: value.usage_group_key, + product_id: value.product_id.as_proto(), })) } } @@ -267,7 +269,7 @@ pub mod metric { .unwrap_or_default(); metering::Meter { - meter_slug: metric.id.to_string(), // TODO slug would make it easier for external metering + id: metric.local_id, // we could allow optional external_id if the metric is defined externally event_name: metric.code.to_string(), aggregation_key: metric.aggregation_key, aggregation: super::aggregation_type::domain_to_metering(metric.aggregation_type) diff --git a/modules/meteroid/src/api/billablemetrics/service.rs b/modules/meteroid/src/api/billablemetrics/service.rs index 41b74ef9..3dfc7cd2 100644 --- a/modules/meteroid/src/api/billablemetrics/service.rs +++ b/modules/meteroid/src/api/billablemetrics/service.rs @@ -1,7 +1,5 @@ -use error_stack::Report; -use tonic::{Request, Response, Status}; - use common_grpc::middleware::server::auth::RequestExt; +use error_stack::Report; use meteroid_grpc::meteroid::api::billablemetrics::v1::{ billable_metrics_service_server::BillableMetricsService, BillableMetricMeta, CreateBillableMetricRequest, CreateBillableMetricResponse, GetBillableMetricRequest, @@ -11,11 +9,14 @@ use meteroid_store::domain; use meteroid_store::domain::BillableMetric; use meteroid_store::errors::StoreError; use meteroid_store::repositories::billable_metrics::BillableMetricInterface; +use tonic::{Request, Response, Status}; +use uuid::Uuid; use crate::api::billablemetrics::error::BillableMetricApiError; use crate::api::billablemetrics::mapping::metric::{ ServerBillableMetricMetaWrapper, ServerBillableMetricWrapper, }; +use crate::api::shared::conversions::FromProtoOpt; use crate::api::utils::{parse_uuid, PaginationExt}; use super::{mapping, BillableMetricsComponents}; @@ -62,7 +63,8 @@ impl BillableMetricsService for BillableMetricsComponents { usage_group_key: inner.usage_group_key, created_by: actor, tenant_id, - family_external_id: inner.family_external_id, + family_local_id: inner.family_local_id, + product_id: Uuid::from_proto_opt(inner.product_id)?, }) .await .map_err(Into::::into)?; @@ -92,7 +94,7 @@ impl BillableMetricsService for BillableMetricsComponents { let res = self .store - .list_billable_metrics(tenant_id, pagination_req, inner.family_external_id) + .list_billable_metrics(tenant_id, pagination_req, inner.family_local_id) .await .map_err(Into::::into)?; diff --git a/modules/meteroid/src/api/coupons/mapping.rs b/modules/meteroid/src/api/coupons/mapping.rs index 86a78a98..cf6209e9 100644 --- a/modules/meteroid/src/api/coupons/mapping.rs +++ b/modules/meteroid/src/api/coupons/mapping.rs @@ -8,6 +8,7 @@ pub mod coupons { fn from(value: domain::coupons::Coupon) -> Self { Self(server::Coupon { id: value.id.to_string(), + local_id: value.local_id, description: value.description, code: value.code, discount: Some(discount::to_server(&value.discount)), diff --git a/modules/meteroid/src/api/customers/mapping.rs b/modules/meteroid/src/api/customers/mapping.rs index b6426f6a..cc79da93 100644 --- a/modules/meteroid/src/api/customers/mapping.rs +++ b/modules/meteroid/src/api/customers/mapping.rs @@ -142,6 +142,7 @@ pub mod customer { fn try_from(value: domain::Customer) -> Result { Ok(ServerCustomerWrapper(server::Customer { id: value.id.as_proto(), + local_id: value.local_id, billing_config: Some(ServerBillingConfigWrapper::try_from(value.billing_config)?.0), invoicing_entity_id: value.invoicing_entity_id.as_proto(), name: value.name, @@ -175,6 +176,7 @@ pub mod customer { fn try_from(value: domain::Customer) -> Result { Ok(ServerCustomerBriefWrapper(server::CustomerBrief { id: value.id.to_string(), + local_id: value.local_id, name: value.name, alias: value.alias, country: value diff --git a/modules/meteroid/src/api/internal/service.rs b/modules/meteroid/src/api/internal/service.rs index 05ea9f04..aa840970 100644 --- a/modules/meteroid/src/api/internal/service.rs +++ b/modules/meteroid/src/api/internal/service.rs @@ -4,8 +4,8 @@ use tonic::{Request, Response, Status}; use meteroid_grpc::meteroid::internal::v1::internal_service_server::InternalService; use meteroid_grpc::meteroid::internal::v1::{ - ResolveApiKeyRequest, ResolveApiKeyResponse, ResolveCustomerExternalIdsRequest, - ResolveCustomerExternalIdsResponse, ResolvedId, + ResolveApiKeyRequest, ResolveApiKeyResponse, ResolveCustomerAliasesRequest, + ResolveCustomerAliasesResponse, ResolvedId, }; use meteroid_store::repositories::api_tokens::ApiTokensInterface; @@ -17,17 +17,17 @@ use meteroid_store::repositories::customers::CustomersInterface; #[tonic::async_trait] impl InternalService for InternalServiceComponents { #[tracing::instrument(skip_all)] - async fn resolve_customer_external_ids( + async fn resolve_customer_aliases( &self, - request: Request, - ) -> Result, Status> { + request: Request, + ) -> Result, Status> { let inner = request.into_inner(); let tenant_id = parse_uuid!(inner.tenant_id)?; let res = self .store - .find_customer_ids_by_aliases(tenant_id, inner.external_ids.clone()) + .find_customer_ids_by_aliases(tenant_id, inner.aliases.clone()) .await .map_err(Into::::into)?; @@ -35,22 +35,22 @@ impl InternalService for InternalServiceComponents { for record in &res { customers.push(ResolvedId { - external_id: record.alias.clone().unwrap(), - meteroid_id: record.id.to_string(), + alias: record.alias.clone().unwrap(), + local_id: record.local_id.to_string(), }); } let resolved_aliases: HashSet = res.into_iter().map(|x| x.alias.unwrap()).collect(); - let unresolved_ids: Vec = inner - .external_ids + let unresolved_aliases: Vec = inner + .aliases .into_iter() .filter(|id| !resolved_aliases.contains(id)) .collect(); - Ok(Response::new(ResolveCustomerExternalIdsResponse { + Ok(Response::new(ResolveCustomerAliasesResponse { customers, - unresolved_ids, + unresolved_aliases, })) } diff --git a/modules/meteroid/src/api/plans/mapping.rs b/modules/meteroid/src/api/plans/mapping.rs index 6be52c4d..1ead2110 100644 --- a/modules/meteroid/src/api/plans/mapping.rs +++ b/modules/meteroid/src/api/plans/mapping.rs @@ -1,18 +1,17 @@ pub mod plans { - use crate::api::domain_mapping::billing_period::to_proto; + use crate::api::shared::conversions::{AsProtoOpt, ProtoConv}; + use meteroid_grpc::meteroid::api::plans::v1::plan_overview::ActiveVersionInfo; use meteroid_grpc::meteroid::api::plans::v1::{ plan_billing_configuration as billing_config_grpc, ListPlanVersion, PlanOverview, }; use meteroid_grpc::meteroid::api::plans::v1::{ - trial_config::ActionAfterTrial, ListPlan, ListSubscribablePlanVersion, Plan, - PlanBillingConfiguration, PlanDetails, PlanStatus, PlanType, PlanVersion, TrialConfig, + trial_config::ActionAfterTrial, Plan, PlanBillingConfiguration, PlanStatus, PlanType, + PlanVersion, PlanWithVersion, TrialConfig, }; - - use crate::api::shared::conversions::AsProtoOpt; use meteroid_store::domain; use meteroid_store::domain::enums::{ActionAfterTrialEnum, PlanStatusEnum, PlanTypeEnum}; - pub struct PlanDetailsWrapper(pub PlanDetails); + pub struct PlanWithVersionWrapper(pub PlanWithVersion); pub struct PlanVersionWrapper(pub PlanVersion); @@ -22,10 +21,6 @@ pub mod plans { pub struct PlanStatusWrapper(pub PlanStatus); - pub struct ListPlanWrapper(pub ListPlan); - - pub struct ListSubscribablePlanVersionWrapper(pub ListSubscribablePlanVersion); - pub struct ListPlanVersionWrapper(pub ListPlanVersion); pub struct PlanOverviewWrapper(pub PlanOverview); @@ -37,6 +32,7 @@ pub mod plans { is_draft: value.is_draft_version, version: value.version as u32, currency: value.currency, + created_at: value.created_at.as_proto(), }) } } @@ -63,12 +59,6 @@ pub mod plans { fn billing_config(version: &domain::PlanVersion) -> Option { Some(PlanBillingConfiguration { - billing_periods: version - .billing_periods - .clone() - .into_iter() - .map(|freq| to_proto(freq) as i32) - .collect(), billing_cycles: Some(match version.billing_cycles { Some(count) => { billing_config_grpc::BillingCycles::Fixed(billing_config_grpc::Fixed { @@ -99,40 +89,44 @@ pub mod plans { trial_config: trial_config(&value), billing_config: billing_config(&value), currency: value.currency, + net_terms: value.net_terms, + period_start_day: value.period_start_day.map(|x| x as i32), }) } } - impl From for PlanDetailsWrapper { + impl From for PlanWithVersionWrapper { fn from(value: domain::FullPlan) -> Self { - Self(PlanDetails { + Self(PlanWithVersion { plan: Some(Plan { id: value.plan.id.to_string(), - external_id: value.plan.external_id, + local_id: value.plan.local_id, name: value.plan.name, description: value.plan.description, plan_type: PlanTypeWrapper::from(value.plan.plan_type).0 as i32, plan_status: PlanStatusWrapper::from(value.plan.status).0 as i32, + active_version_id: value.plan.active_version_id.as_proto(), + draft_version_id: value.plan.draft_version_id.as_proto(), }), - current_version: Some(PlanVersionWrapper::from(value.version).0), - metadata: vec![], + version: Some(PlanVersionWrapper::from(value.version).0), }) } } - impl From for PlanDetailsWrapper { + impl From for PlanWithVersionWrapper { fn from(value: domain::PlanWithVersion) -> Self { - Self(PlanDetails { + Self(PlanWithVersion { plan: Some(Plan { id: value.plan.id.to_string(), - external_id: value.plan.external_id, + local_id: value.plan.local_id, name: value.plan.name, description: value.plan.description, plan_type: PlanTypeWrapper::from(value.plan.plan_type).0 as i32, plan_status: PlanStatusWrapper::from(value.plan.status).0 as i32, + active_version_id: value.plan.active_version_id.as_proto(), + draft_version_id: value.plan.draft_version_id.as_proto(), }), - current_version: Some(PlanVersionWrapper::from(value.version).0), - metadata: vec![], + version: value.version.map(|v| (PlanVersionWrapper::from(v).0)), }) } } @@ -199,70 +193,22 @@ pub mod plans { } } - impl From for ListPlanWrapper { - fn from(value: domain::PlanForList) -> Self { - Self(ListPlan { + impl From for PlanOverviewWrapper { + fn from(value: domain::PlanOverview) -> Self { + Self(PlanOverview { id: value.id.to_string(), name: value.name, - external_id: value.external_id, + local_id: value.local_id, description: value.description, plan_type: PlanTypeWrapper::from(value.plan_type).0 as i32, plan_status: PlanStatusWrapper::from(value.status).0 as i32, - product_family_id: value.product_family_id.to_string(), product_family_name: value.product_family_name, - }) - } - } - - impl From for ListSubscribablePlanVersionWrapper { - fn from(value: domain::PlanVersionLatest) -> Self { - Self(ListSubscribablePlanVersion { - id: value.id.to_string(), - plan_id: value.plan_id.to_string(), - plan_name: value.plan_name, - version: value.version, - created_by: value.created_by.to_string(), - trial_config: match value.trial_duration_days { - Some(days) if days > 0 => Some(TrialConfig { - trialing_plan_id: value.trialing_plan_id.as_proto(), - downgrade_plan_id: value.downgrade_plan_id.as_proto(), - action_after_trial: value - .action_after_trial - .map(|a| ActionAfterTrialWrapper::from(a).0) - .unwrap_or(ActionAfterTrial::Block) - .into(), - duration_days: days as u32, - trial_is_free: value.trial_is_free, - }), - _ => None, - }, - period_start_day: value.period_start_day.map(|x| x as i32), - net_terms: value.net_terms, - currency: value.currency, - product_family_id: value.product_family_id.to_string(), - product_family_name: value.product_family_name, - }) - } - } - - impl From for PlanOverviewWrapper { - fn from(value: domain::PlanWithVersion) -> Self { - Self(PlanOverview { - plan_id: value.plan.id.to_string(), - plan_version_id: value.version.id.to_string(), - name: value.plan.name, - version: value.version.version as u32, - description: value.plan.description, - currency: value.version.currency, - net_terms: value.version.net_terms as u32, - billing_periods: value - .version - .billing_periods - .into_iter() - .map(|freq| to_proto(freq) as i32) - .collect(), - is_draft: value.version.is_draft_version, - plan_type: PlanTypeWrapper::from(value.plan.plan_type).0 as i32, + has_draft_version: value.has_draft_version, + active_version: value.active_version.map(|v| ActiveVersionInfo { + version: v.version as u32, + trial_duration_days: v.trial_duration_days.map(|x| x as u32), + }), + subscription_count: value.subscription_count.map(|x| x as u32).unwrap_or(0), }) } } diff --git a/modules/meteroid/src/api/plans/service.rs b/modules/meteroid/src/api/plans/service.rs index 64475ebb..4e9c0aae 100644 --- a/modules/meteroid/src/api/plans/service.rs +++ b/modules/meteroid/src/api/plans/service.rs @@ -1,41 +1,35 @@ use tonic::{Request, Response, Status}; use uuid::Uuid; -use common_grpc::middleware::server::auth::RequestExt; -use meteroid_grpc::meteroid::api::plans::v1::{ - list_plans_request::SortBy, plans_service_server::PlansService, CopyVersionToDraftRequest, - CopyVersionToDraftResponse, CreateDraftPlanRequest, CreateDraftPlanResponse, - DiscardDraftVersionRequest, DiscardDraftVersionResponse, GetLastPublishedPlanVersionRequest, - GetLastPublishedPlanVersionResponse, GetPlanByExternalIdRequest, GetPlanByExternalIdResponse, - GetPlanByIdRequest, GetPlanByIdResponse, GetPlanOverviewByExternalIdRequest, - GetPlanOverviewByExternalIdResponse, GetPlanParametersRequest, GetPlanParametersResponse, - GetPlanVersionByIdRequest, GetPlanVersionByIdResponse, ListPlanVersionByIdRequest, - ListPlanVersionByIdResponse, ListPlansRequest, ListPlansResponse, - ListSubscribablePlanVersionRequest, ListSubscribablePlanVersionResponse, - PublishPlanVersionRequest, PublishPlanVersionResponse, UpdateDraftPlanOverviewRequest, - UpdateDraftPlanOverviewResponse, UpdatePlanTrialRequest, UpdatePlanTrialResponse, - UpdatePublishedPlanOverviewRequest, UpdatePublishedPlanOverviewResponse, -}; -use meteroid_grpc::meteroid::api::shared::v1::BillingPeriod; - +use super::PlanServiceComponents; use crate::api::plans::error::PlanApiError; - -use crate::api::domain_mapping::billing_period; use crate::api::plans::mapping::plans::{ - ActionAfterTrialWrapper, ListPlanVersionWrapper, ListPlanWrapper, - ListSubscribablePlanVersionWrapper, PlanDetailsWrapper, PlanOverviewWrapper, PlanStatusWrapper, - PlanTypeWrapper, PlanVersionWrapper, + ActionAfterTrialWrapper, ListPlanVersionWrapper, PlanOverviewWrapper, PlanStatusWrapper, + PlanTypeWrapper, PlanVersionWrapper, PlanWithVersionWrapper, }; -use crate::api::shared::conversions::{FromProtoOpt, ProtoConv}; +use crate::api::shared::conversions::FromProtoOpt; use crate::api::utils::PaginationExt; use crate::{api::utils::parse_uuid, parse_uuid}; +use common_grpc::middleware::server::auth::RequestExt; +use meteroid_grpc::meteroid::api::plans::v1::get_plan_with_version_request::Filter; +use meteroid_grpc::meteroid::api::plans::v1::{ + list_plans_request::SortBy, plans_service_server::PlansService, CopyVersionToDraftRequest, + CopyVersionToDraftResponse, CreateDraftPlanRequest, CreateDraftPlanResponse, + DiscardDraftVersionRequest, DiscardDraftVersionResponse, GetPlanOverviewRequest, + GetPlanOverviewResponse, GetPlanParametersRequest, GetPlanParametersResponse, + GetPlanWithVersionRequest, GetPlanWithVersionResponse, ListPlanVersionByIdRequest, + ListPlanVersionByIdResponse, ListPlansRequest, ListPlansResponse, PublishPlanVersionRequest, + PublishPlanVersionResponse, UpdateDraftPlanOverviewRequest, UpdateDraftPlanOverviewResponse, + UpdatePlanTrialRequest, UpdatePlanTrialResponse, UpdatePublishedPlanOverviewRequest, + UpdatePublishedPlanOverviewResponse, +}; use meteroid_store::domain; use meteroid_store::domain::{ - OrderByRequest, PlanAndVersionPatch, PlanFilters, PlanPatch, PlanVersionPatch, TrialPatch, + OrderByRequest, PlanAndVersionPatch, PlanFilters, PlanPatch, PlanVersionFilter, + PlanVersionPatch, TrialPatch, }; use meteroid_store::repositories::PlansInterface; - -use super::PlanServiceComponents; +use meteroid_store::utils::local_id::{IdType, LocalId}; #[tonic::async_trait] impl PlansService for PlanServiceComponents { @@ -57,8 +51,8 @@ impl PlansService for PlanServiceComponents { description: req.description, created_by, tenant_id, - external_id: req.external_id, - product_family_external_id: req.product_family_external_id, + local_id: LocalId::generate_for(IdType::Plan), + product_family_local_id: req.product_family_local_id, status: domain::enums::PlanStatusEnum::Draft, plan_type, }, @@ -69,7 +63,6 @@ impl PlansService for PlanServiceComponents { net_terms: 0, currency: None, billing_cycles: None, - billing_periods: vec![], }, price_components: vec![], }; @@ -78,7 +71,7 @@ impl PlansService for PlanServiceComponents { .store .insert_plan(plan_new) .await - .map(|x| PlanDetailsWrapper::from(x).0) + .map(|x| PlanWithVersionWrapper::from(x).0) .map_err(Into::::into)?; Ok(Response::new(CreateDraftPlanResponse { @@ -86,27 +79,6 @@ impl PlansService for PlanServiceComponents { })) } - #[tracing::instrument(skip_all)] - async fn get_plan_by_external_id( - &self, - request: Request, - ) -> Result, Status> { - let tenant_id = request.tenant()?; - - let req = request.into_inner(); - - let plan_details = self - .store - .get_plan_by_external_id(req.external_id.as_str(), tenant_id) - .await - .map(|x| PlanDetailsWrapper::from(x).0) - .map_err(Into::::into)?; - - Ok(Response::new(GetPlanByExternalIdResponse { - plan_details: Some(plan_details), - })) - } - #[tracing::instrument(skip_all)] async fn list_plans( &self, @@ -152,7 +124,7 @@ impl PlansService for PlanServiceComponents { .store .list_plans( tenant_id, - req.product_family_external_id, + req.product_family_local_id, plan_filters, pagination_req, order_by, @@ -165,61 +137,13 @@ impl PlansService for PlanServiceComponents { plans: res .items .into_iter() - .map(|l| ListPlanWrapper::from(l).0) + .map(|l| PlanOverviewWrapper::from(l).0) .collect::>(), }; Ok(Response::new(response)) } - #[tracing::instrument(skip_all)] - async fn list_subscribable_plan_version( - &self, - request: Request, - ) -> Result, Status> { - let tenant_id = request.tenant()?; - - let plan_versions = self - .store - .list_latest_published_plan_versions(tenant_id) - .await - .map_err(Into::::into)?; - - let response = ListSubscribablePlanVersionResponse { - plan_versions: plan_versions - .into_iter() - .map(|x| ListSubscribablePlanVersionWrapper::from(x).0) - .collect(), - }; - - Ok(Response::new(response)) - } - - #[tracing::instrument(skip_all)] - async fn get_plan_version_by_id( - &self, - request: Request, - ) -> Result, Status> { - let tenant_id = request.tenant()?; - - let req = request.into_inner(); - - let id = parse_uuid!(&req.plan_version_id)?; - - let version = self - .store - .get_plan_version_by_id(id, tenant_id) - .await - .map_err(Into::::into) - .map(|x| PlanVersionWrapper::from(x).0)?; - - let response = GetPlanVersionByIdResponse { - plan_version: Some(version), - }; - - Ok(Response::new(response)) - } - #[tracing::instrument(skip_all)] async fn list_plan_version_by_id( &self, @@ -299,27 +223,6 @@ impl PlansService for PlanServiceComponents { })) } - #[tracing::instrument(skip_all)] - async fn get_last_published_plan_version( - &self, - request: Request, - ) -> Result, Status> { - let tenant_id = request.tenant()?; - let req = request.into_inner(); - let plan_id = parse_uuid!(&req.plan_id)?; - - let res = self - .store - .get_last_published_plan_version(plan_id, tenant_id) - .await - .map_err(Into::::into) - .map(|x| x.map(|x| PlanVersionWrapper::from(x).0))?; - - Ok(Response::new(GetLastPublishedPlanVersionResponse { - version: res, - })) - } - #[tracing::instrument(skip_all)] async fn discard_draft_version( &self, @@ -349,16 +252,6 @@ impl PlansService for PlanServiceComponents { let plan_version_id = parse_uuid!(&req.plan_version_id)?; - let frequencies = req - .billing_periods - .iter() - .map(|f| { - BillingPeriod::try_from(*f) - .map_err(|_| PlanApiError::InvalidArgument("billing period".to_string())) - .map(billing_period::from_proto) - }) - .collect::, PlanApiError>>()?; - let res = self .store .patch_draft_plan(PlanAndVersionPatch { @@ -367,17 +260,16 @@ impl PlansService for PlanServiceComponents { tenant_id, currency: Some(req.currency), net_terms: Some(req.net_terms as i32), - billing_periods: Some(frequencies), }, name: Some(req.name), description: Some(req.description), }) .await - .map_err(Into::::into) - .map(|x| PlanOverviewWrapper::from(x).0)?; + .map(|x| PlanWithVersionWrapper::from(x).0) + .map_err(Into::::into)?; Ok(Response::new(UpdateDraftPlanOverviewResponse { - plan_overview: Some(res), + plan: Some(res), })) } @@ -397,38 +289,17 @@ impl PlansService for PlanServiceComponents { tenant_id, name: Some(req.name), description: Some(req.description), + active_version_id: None, }) .await - .map_err(Into::::into) - .map(|x| PlanOverviewWrapper::from(x).0)?; + .map(|x| PlanOverviewWrapper::from(x).0) + .map_err(Into::::into)?; Ok(Response::new(UpdatePublishedPlanOverviewResponse { plan_overview: Some(res), })) } - #[tracing::instrument(skip_all)] - async fn get_plan_overview_by_external_id( - &self, - request: Request, - ) -> Result, Status> { - let tenant_id = request.tenant()?; - let req = request.into_inner(); - - let res = self - .store - .get_plan_with_version_by_external_id(&req.external_id, tenant_id) - .await - .map_err(Into::::into) - .map(|x| PlanOverviewWrapper::from(x).0)?; - - let response = GetPlanOverviewByExternalIdResponse { - plan_overview: Some(res), - }; - - Ok(Response::new(response)) - } - #[tracing::instrument(skip_all)] async fn get_plan_parameters( &self, @@ -468,32 +339,58 @@ impl PlansService for PlanServiceComponents { .transpose()?, }) .await - .map_err(Into::::into) - .map(|x| PlanOverviewWrapper::from(x).0)?; + .map(|x| PlanWithVersionWrapper::from(x).0) + .map_err(Into::::into)?; - Ok(Response::new(UpdatePlanTrialResponse { + Ok(Response::new(UpdatePlanTrialResponse { plan: Some(res) })) + } + + #[tracing::instrument(skip_all)] + async fn get_plan_overview( + &self, + request: Request, + ) -> Result, Status> { + let tenant_id = request.tenant()?; + let req = request.into_inner(); + + let res = self + .store + .get_plan_overview(&req.local_id, tenant_id) + .await + .map(|x| PlanOverviewWrapper::from(x).0) + .map_err(Into::::into)?; + + Ok(Response::new(GetPlanOverviewResponse { plan_overview: Some(res), })) } #[tracing::instrument(skip_all)] - async fn get_plan_by_id( + async fn get_plan_with_version( &self, - request: Request, - ) -> Result, Status> { + request: Request, + ) -> Result, Status> { let tenant_id = request.tenant()?; - let req = request.into_inner(); - let plan_details = self + let filter = match req.filter { + None => PlanVersionFilter::Active, + Some(c) => match c { + Filter::Version(v) => PlanVersionFilter::Version(v as i32), + Filter::Draft(_) => PlanVersionFilter::Draft, + Filter::Active(_) => PlanVersionFilter::Active, + }, + }; + + let res = self .store - .get_plan_by_id(Uuid::from_proto(req.plan_id)?, tenant_id) + .get_plan(&req.local_id, tenant_id, filter) .await - .map(|x| PlanDetailsWrapper::from(x).0) + .map(|x| PlanWithVersionWrapper::from(x).0) .map_err(Into::::into)?; - Ok(Response::new(GetPlanByIdResponse { - plan_details: Some(plan_details), + Ok(Response::new(GetPlanWithVersionResponse { + plan: Some(res), })) } diff --git a/modules/meteroid/src/api/pricecomponents/mapping.rs b/modules/meteroid/src/api/pricecomponents/mapping.rs index 55737d31..036a9126 100644 --- a/modules/meteroid/src/api/pricecomponents/mapping.rs +++ b/modules/meteroid/src/api/pricecomponents/mapping.rs @@ -22,7 +22,7 @@ pub mod components { Ok(domain::PriceComponentNew { name: comp.name, fee: map_fee_to_domain(comp.fee)?, - product_item_id: Uuid::from_proto_opt(comp.product_item_id)?, + product_id: Uuid::from_proto_opt(comp.product_id)?, plan_version_id: Uuid::from_proto_ref(&comp.plan_version_id)?, }) } @@ -37,8 +37,9 @@ pub mod components { Ok(domain::PriceComponent { name: component.name, fee: map_fee_to_domain(component.fee)?, - product_item_id: Uuid::from_proto_opt(component.product_item_id)?, + product_id: Uuid::from_proto_opt(component.product_id)?, id: Uuid::from_proto_ref(&component.id)?, + local_id: component.local_id, }) } @@ -124,9 +125,10 @@ pub mod components { pub fn domain_to_api(comp: domain::PriceComponent) -> api::PriceComponent { api::PriceComponent { id: comp.id.to_string(), + local_id: comp.local_id, name: comp.name.to_string(), fee: Some(map_fee_domain_to_api(comp.fee)), - product_item_id: comp.product_item_id.as_proto(), + product_id: comp.product_id.as_proto(), } } diff --git a/modules/meteroid/src/api/productfamilies/mapping.rs b/modules/meteroid/src/api/productfamilies/mapping.rs index 4b927f9f..bb71d178 100644 --- a/modules/meteroid/src/api/productfamilies/mapping.rs +++ b/modules/meteroid/src/api/productfamilies/mapping.rs @@ -9,7 +9,7 @@ pub mod product_family { ProductFamilyWrapper(ProductFamily { id: domain_family.id.to_string(), name: domain_family.name, - external_id: domain_family.external_id, + local_id: domain_family.local_id, }) } } diff --git a/modules/meteroid/src/api/productfamilies/service.rs b/modules/meteroid/src/api/productfamilies/service.rs index 6fec0164..795c0f4c 100644 --- a/modules/meteroid/src/api/productfamilies/service.rs +++ b/modules/meteroid/src/api/productfamilies/service.rs @@ -3,8 +3,8 @@ use tonic::{Request, Response, Status}; use common_grpc::middleware::server::auth::RequestExt; use meteroid_grpc::meteroid::api::productfamilies::v1::{ product_families_service_server::ProductFamiliesService, CreateProductFamilyRequest, - CreateProductFamilyResponse, GetProductFamilyByExternalIdRequest, - GetProductFamilyByExternalIdResponse, ListProductFamiliesRequest, ListProductFamiliesResponse, + CreateProductFamilyResponse, GetProductFamilyByLocalIdRequest, + GetProductFamilyByLocalIdResponse, ListProductFamiliesRequest, ListProductFamiliesResponse, }; use meteroid_store::domain; use meteroid_store::repositories::ProductFamilyInterface; @@ -51,7 +51,7 @@ impl ProductFamiliesService for ProductFamilyServiceComponents { .insert_product_family( domain::ProductFamilyNew { name: req.name, - external_id: req.external_id, + local_id: req.local_id, tenant_id, }, Some(actor), @@ -66,21 +66,21 @@ impl ProductFamiliesService for ProductFamilyServiceComponents { } #[tracing::instrument(skip_all)] - async fn get_product_family_by_external_id( + async fn get_product_family_by_local_id( &self, - request: Request, - ) -> Result, Status> { + request: Request, + ) -> Result, Status> { let tenant_id = request.tenant()?; let req = request.into_inner(); let rs = self .store - .find_product_family_by_external_id(req.external_id.as_str(), tenant_id) + .find_product_family_by_local_id(req.local_id.as_str(), tenant_id) .await .map_err(Into::::into) .map(|x| ProductFamilyWrapper::from(x).0)?; - Ok(Response::new(GetProductFamilyByExternalIdResponse { + Ok(Response::new(GetProductFamilyByLocalIdResponse { product_family: Some(rs), })) } diff --git a/modules/meteroid/src/api/productitems/mapping.rs b/modules/meteroid/src/api/productitems/mapping.rs index 7d91499a..cf646015 100644 --- a/modules/meteroid/src/api/productitems/mapping.rs +++ b/modules/meteroid/src/api/productitems/mapping.rs @@ -9,6 +9,7 @@ pub mod products { fn from(product: domain::Product) -> Self { ProductWrapper(Product { id: product.id.to_string(), + local_id: product.local_id, name: product.name, description: product.description, created_at: Some(chrono_to_timestamp(product.created_at)), @@ -21,6 +22,7 @@ pub mod products { fn from(product: domain::Product) -> Self { ProductMetaWrapper(ProductMeta { id: product.id.to_string(), + local_id: product.local_id, name: product.name, }) } diff --git a/modules/meteroid/src/api/productitems/service.rs b/modules/meteroid/src/api/productitems/service.rs index 80d44db5..a52a7fec 100644 --- a/modules/meteroid/src/api/productitems/service.rs +++ b/modules/meteroid/src/api/productitems/service.rs @@ -36,7 +36,7 @@ impl ProductsService for ProductServiceComponents { description: req.description, created_by: actor, tenant_id, - family_external_id: req.family_external_id, + family_local_id: req.family_local_id, }) .await .map_err(Into::::into) @@ -64,7 +64,7 @@ impl ProductsService for ProductServiceComponents { .store .list_products( tenant_id, - req.family_external_id.as_str(), + req.family_local_id.as_str(), pagination_req, order_by, ) @@ -101,7 +101,7 @@ impl ProductsService for ProductServiceComponents { .store .search_products( tenant_id, - req.family_external_id.as_str(), + req.family_local_id.as_str(), req.query.unwrap_or("".to_string()).as_str(), // todo add some validation on the query pagination_req, order_by, diff --git a/modules/meteroid/src/api/subscriptions/mapping.rs b/modules/meteroid/src/api/subscriptions/mapping.rs index e694805a..f1302d5b 100644 --- a/modules/meteroid/src/api/subscriptions/mapping.rs +++ b/modules/meteroid/src/api/subscriptions/mapping.rs @@ -16,6 +16,7 @@ pub mod subscriptions { Ok(proto2::Subscription { id: s.id.as_proto(), + local_id: s.local_id, tenant_id: s.tenant_id.as_proto(), customer_id: s.customer_id.as_proto(), plan_id: s.plan_id.as_proto(), @@ -86,6 +87,7 @@ pub mod subscriptions { ) -> Result { Ok(proto2::CreatedSubscription { id: sub.id.as_proto(), + local_id: sub.local_id, customer_id: sub.customer_id.as_proto(), billing_day: sub.billing_day as u32, tenant_id: sub.tenant_id.as_proto(), @@ -111,6 +113,7 @@ pub mod subscriptions { Ok(proto2::SubscriptionDetails { subscription: Some(proto2::Subscription { id: sub.id.as_proto(), + local_id: sub.local_id, tenant_id: sub.tenant_id.as_proto(), customer_id: sub.customer_id.as_proto(), plan_id: sub.plan_id.as_proto(), @@ -125,7 +128,7 @@ pub mod subscriptions { billing_end_date: sub.billing_end_date.as_proto(), billing_start_date: sub.billing_start_date.as_proto(), customer_name: sub.customer_name, - customer_alias: sub.customer_external_id, + customer_alias: sub.customer_alias, canceled_at: sub.canceled_at.as_proto(), cancellation_reason: sub.cancellation_reason, billing_day: sub.billing_day as u32, @@ -282,8 +285,8 @@ mod price_components { .price_component_id .map(|id| Uuid::from_proto_ref(&id)) .transpose()?, - product_item_id: component - .product_item_id + product_id: component + .product_id .map(|id| Uuid::from_proto_ref(&id)) .transpose()?, name: component.name.clone(), @@ -298,7 +301,7 @@ mod price_components { api::SubscriptionComponent { id: component.id.to_string(), price_component_id: component.price_component_id.map(|id| id.to_string()), - product_item_id: component.product_item_id.map(|id| id.to_string()), + product_id: component.product_id.map(|id| id.to_string()), subscription_id: component.subscription_id.to_string(), name: component.name.clone(), period: subscription_fee_billing_period_to_grpc(component.period.clone()).into(), diff --git a/modules/meteroid/src/bin/seeder.rs b/modules/meteroid/src/bin/seeder.rs index 58b54f78..181cea36 100644 --- a/modules/meteroid/src/bin/seeder.rs +++ b/modules/meteroid/src/bin/seeder.rs @@ -91,7 +91,6 @@ async fn main() -> error_stack::Result<(), SeederError> { period_start_day: None, currency: tenant_currency.clone(), billing_cycles: None, - billing_periods: vec![], net_terms: 0, }, components: vec![], @@ -108,12 +107,11 @@ async fn main() -> error_stack::Result<(), SeederError> { period_start_day: None, currency: tenant_currency.clone(), billing_cycles: None, - billing_periods: vec![BillingPeriodEnum::Monthly, BillingPeriodEnum::Annual], net_terms: 0, }, components: vec![meteroid_store::domain::PriceComponentNewInternal { name: "Rate".to_string(), - product_item_id: None, + product_id: None, fee: meteroid_store::domain::FeeType::Rate { rates: vec![ meteroid_store::domain::TermRate { @@ -140,12 +138,11 @@ async fn main() -> error_stack::Result<(), SeederError> { period_start_day: None, currency: tenant_currency.clone(), billing_cycles: None, - billing_periods: vec![BillingPeriodEnum::Annual], net_terms: 90, }, components: vec![meteroid_store::domain::PriceComponentNewInternal { name: "Seats".to_string(), - product_item_id: None, + product_id: None, fee: meteroid_store::domain::FeeType::Slot { quota: None, rates: vec![ diff --git a/modules/meteroid/src/clients/usage.rs b/modules/meteroid/src/clients/usage.rs index c58bf2c1..19507b67 100644 --- a/modules/meteroid/src/clients/usage.rs +++ b/modules/meteroid/src/clients/usage.rs @@ -10,7 +10,7 @@ use metering_grpc::meteroid::metering::v1::meters_service_client::MetersServiceC use metering_grpc::meteroid::metering::v1::query_meter_request::QueryWindowSize; use metering_grpc::meteroid::metering::v1::usage_query_service_client::UsageQueryServiceClient; use metering_grpc::meteroid::metering::v1::{ - Filter, QueryMeterRequest, QueryMeterResponse, RegisterMeterRequest, ResourceIdentifier, + CustomerIdentifier, Filter, QueryMeterRequest, QueryMeterResponse, RegisterMeterRequest, }; use meteroid_store::compute::clients::usage::*; use meteroid_store::compute::ComputeError; @@ -74,8 +74,8 @@ impl UsageClient for MeteringUsageClient { async fn fetch_usage( &self, tenant_id: &Uuid, - customer_id: &Uuid, - customer_external_id: &Option, + customer_local_id: &str, + customer_alias: &Option, metric: &BillableMetric, period: Period, ) -> Result { @@ -143,11 +143,9 @@ impl UsageClient for MeteringUsageClient { meter_slug: metric.id.to_string(), event_name: metric.code.clone(), meter_aggregation_type: aggregation_type, - customers: vec![ResourceIdentifier { - meteroid_id: customer_id.to_string(), - external_id: customer_external_id - .clone() - .unwrap_or(customer_id.to_string()), // TODO make mandatory in db, or optional in metering + customers: vec![CustomerIdentifier { + local_id: customer_local_id.to_string(), + alias: customer_alias.clone(), }], from: Some(date_to_timestamp(period.start)), to: Some(date_to_timestamp(period.end)), // exclusive TODO check diff --git a/modules/meteroid/src/seeder/domain.rs b/modules/meteroid/src/seeder/domain.rs index f34eebc2..10f225a8 100644 --- a/modules/meteroid/src/seeder/domain.rs +++ b/modules/meteroid/src/seeder/domain.rs @@ -1,5 +1,5 @@ use chrono::NaiveDate; -use meteroid_store::domain::enums::{BillingPeriodEnum, PlanTypeEnum}; +use meteroid_store::domain::enums::PlanTypeEnum; use meteroid_store::domain::PlanTrial; // pub enum PlanType { @@ -23,7 +23,6 @@ pub struct PlanVersion { pub period_start_day: Option, pub currency: String, pub billing_cycles: Option, - pub billing_periods: Vec, pub net_terms: i32, pub trial: Option, } diff --git a/modules/meteroid/src/seeder/runner.rs b/modules/meteroid/src/seeder/runner.rs index b01f7c2c..99f142d7 100644 --- a/modules/meteroid/src/seeder/runner.rs +++ b/modules/meteroid/src/seeder/runner.rs @@ -34,7 +34,6 @@ use meteroid_store::domain::{ use meteroid_store::repositories::billable_metrics::BillableMetricInterface; use meteroid_store::repositories::invoicing_entities::InvoicingEntityInterface; use meteroid_store::repositories::subscriptions::CancellationEffectiveAt; -use meteroid_store::utils::local_id::{IdType, LocalId}; pub async fn run( store: Store, @@ -93,7 +92,8 @@ pub async fn run( usage_group_key: None, description: None, created_by: user_id, - family_external_id: product_family.external_id.clone(), + family_local_id: product_family.local_id.clone(), + product_id: None, // TODO }) .await .change_context(SeederError::TempError)?; @@ -106,12 +106,12 @@ pub async fn run( let created = store .insert_plan(store_domain::FullPlanNew { plan: store_domain::PlanNew { - external_id: slugify(&plan.name), + local_id: slugify(&plan.name), name: plan.name, plan_type: plan.plan_type, status: PlanStatusEnum::Active, tenant_id: tenant.id, - product_family_external_id: product_family.external_id.clone(), + product_family_local_id: product_family.local_id.clone(), description: plan.description, created_by: user_id, }, @@ -122,7 +122,6 @@ pub async fn run( net_terms: plan.version_details.net_terms, currency: Some(plan.version_details.currency), billing_cycles: plan.version_details.billing_cycles, - billing_periods: plan.version_details.billing_periods, }, price_components: plan .components @@ -130,7 +129,7 @@ pub async fn run( .map(|component| store_domain::PriceComponentNewInternal { name: component.name.clone(), fee: component.fee.clone(), - product_item_id: component.product_item_id, + product_id: component.product_id, }) .collect::>(), }) @@ -485,7 +484,6 @@ pub async fn run( net_terms: 30, reference: None, memo: None, - local_id: LocalId::generate_for(IdType::Invoice), due_at: Some((invoice_date + chrono::Duration::days(30)).and_time(NaiveTime::MIN)), plan_name: Some(subscription_details.plan_name.clone()), invoice_number: format!("{}-{:0>8}", invoice_number_prefix, i.to_string()), diff --git a/modules/meteroid/tests/data/1_meters.sql b/modules/meteroid/tests/data/1_meters.sql index aff2cc96..f8d37700 100644 --- a/modules/meteroid/tests/data/1_meters.sql +++ b/modules/meteroid/tests/data/1_meters.sql @@ -1,43 +1,43 @@ DO $$ - DECLARE - var_user_id UUID := 'ae35bbb9-65da-477d-b856-7dbd87546441'; - var_tenant_id UUID := '018c2c82-3df1-7e84-9e05-6e141d0e751a'; - var_product_family_id UUID := '018c2c82-3df2-71a4-b45c-86cb8604b75c'; - var_metric_database_size UUID := '018c3452-129f-702c-93f4-9c15095b0ef4'; - var_metric_bandwidth UUID := '018c3453-1f11-76a8-8d69-f74921b2646d'; - BEGIN + DECLARE + var_user_id UUID := 'ae35bbb9-65da-477d-b856-7dbd87546441'; + var_tenant_id UUID := '018c2c82-3df1-7e84-9e05-6e141d0e751a'; + var_product_family_id UUID := '018c2c82-3df2-71a4-b45c-86cb8604b75c'; + var_metric_database_size UUID := '018c3452-129f-702c-93f4-9c15095b0ef4'; + var_metric_bandwidth UUID := '018c3453-1f11-76a8-8d69-f74921b2646d'; + BEGIN - -- + -- -- Data for Name: product_family; Type: TABLE DATA; Schema: public; Owner: meteroidbilling -- - INSERT INTO public.product_family - (id, name, external_id, created_at, updated_at, archived_at, tenant_id) - VALUES (var_product_family_id, 'Default', 'default', '2023-12-02 21:49:42.255', NULL, NULL, var_tenant_id); + INSERT INTO public.product_family + (id, name, local_id, created_at, updated_at, archived_at, tenant_id) + VALUES (var_product_family_id, 'Default', 'default', '2023-12-02 21:49:42.255', NULL, NULL, var_tenant_id); - -- + -- -- Data for Name: billable_metric; Type: TABLE DATA; Schema: public; Owner: meteroidbilling -- - INSERT INTO public.billable_metric - (id, name, description, code, aggregation_type, aggregation_key, - unit_conversion_factor, unit_conversion_rounding, segmentation_matrix, - usage_group_key, created_at, created_by, updated_at, archived_at, tenant_id, - product_family_id) - VALUES (var_metric_database_size, 'Database size (GB)', '', 'db_size', 'LATEST', 'size_gb', 1, - NULL, '{ - "matrix": null - }', '', '2023-12-04 10:14:03.168', var_user_id, NULL, NULL, - var_tenant_id, var_product_family_id), - (var_metric_bandwidth, 'Bandwidth (GB)', '', 'bandwidth', 'SUM', 'value', 1, NULL, '{ - "matrix": null - }', NULL, '2023-12-04 10:15:11.89', var_user_id, NULL, NULL, - var_tenant_id, var_product_family_id); - - - COMMIT; - END + INSERT INTO public.billable_metric + (id, name, description, code, aggregation_type, aggregation_key, + unit_conversion_factor, unit_conversion_rounding, segmentation_matrix, + usage_group_key, created_at, created_by, updated_at, archived_at, tenant_id, + product_family_id) + VALUES (var_metric_database_size, 'Database size (GB)', '', 'db_size', 'LATEST', 'size_gb', 1, + NULL, '{ + "matrix": null + }', '', '2023-12-04 10:14:03.168', var_user_id, NULL, NULL, + var_tenant_id, var_product_family_id), + (var_metric_bandwidth, 'Bandwidth (GB)', '', 'bandwidth', 'SUM', 'value', 1, NULL, '{ + "matrix": null + }', NULL, '2023-12-04 10:15:11.89', var_user_id, NULL, NULL, + var_tenant_id, var_product_family_id); + + + COMMIT; + END $$; diff --git a/modules/meteroid/tests/data/2_plans.sql b/modules/meteroid/tests/data/2_plans.sql index 9d44ee05..c1eae152 100644 --- a/modules/meteroid/tests/data/2_plans.sql +++ b/modules/meteroid/tests/data/2_plans.sql @@ -1,233 +1,245 @@ DO $$ - DECLARE - var_user_id UUID := 'ae35bbb9-65da-477d-b856-7dbd87546441'; - var_tenant_id UUID := '018c2c82-3df1-7e84-9e05-6e141d0e751a'; - var_product_family_id UUID := '018c2c82-3df2-71a4-b45c-86cb8604b75c'; + DECLARE + var_user_id UUID := 'ae35bbb9-65da-477d-b856-7dbd87546441'; + var_tenant_id UUID := '018c2c82-3df1-7e84-9e05-6e141d0e751a'; + var_product_family_id UUID := '018c2c82-3df2-71a4-b45c-86cb8604b75c'; - -- plans - var_plan_leetcode_id UUID := '018c344a-78a8-79bc-aefd-09113eaf5cb3'; - var_plan_notion_id UUID := '018c344b-da85-70dc-ae6f-5b919847dbbf'; - var_plan_supabase_id UUID := '018c344d-5957-72cf-816b-938dea2f5c76'; + -- plans + var_plan_leetcode_id UUID := '018c344a-78a8-79bc-aefd-09113eaf5cb3'; + var_plan_notion_id UUID := '018c344b-da85-70dc-ae6f-5b919847dbbf'; + var_plan_supabase_id UUID := '018c344d-5957-72cf-816b-938dea2f5c76'; - -- plan_versions - var_plan_version_1_leetcode_id UUID := '018c344a-78a9-7e2b-af90-5748672711f8'; - var_plan_version_notion_id UUID := '018c344b-da87-7392-bbae-c5c8780adb1b'; - var_plan_version_supabase_id UUID := '018c35cc-3f41-7551-b7b6-f8bbcd62b784'; + -- plan_versions + var_plan_version_1_leetcode_id UUID := '018c344a-78a9-7e2b-af90-5748672711f8'; + var_plan_version_notion_id UUID := '018c344b-da87-7392-bbae-c5c8780adb1b'; + var_plan_version_supabase_id UUID := '018c35cc-3f41-7551-b7b6-f8bbcd62b784'; - -- components + -- components - var_comp_leetcode_rate_id UUID := '018c344b-6050-7ec8-bd8c-d2e9c41ab711'; - var_comp_notion_seats_id UUID := '018c344c-9ec9-7608-b115-1537b6985e73'; - var_comp_supabase_org_slots_id UUID := '3b083801-c77c-4488-848e-a185f0f0a8be'; - var_comp_supabase_bandwidth_id UUID := '705265c8-6069-4b84-a815-73bc7bd773bd'; - var_comp_supabase_db_size_id UUID := '331810d4-05b1-4d8e-bf9b-d61cedaec117'; + var_comp_leetcode_rate_id UUID := '018c344b-6050-7ec8-bd8c-d2e9c41ab711'; + var_comp_notion_seats_id UUID := '018c344c-9ec9-7608-b115-1537b6985e73'; + var_comp_supabase_org_slots_id UUID := '3b083801-c77c-4488-848e-a185f0f0a8be'; + var_comp_supabase_bandwidth_id UUID := '705265c8-6069-4b84-a815-73bc7bd773bd'; + var_comp_supabase_db_size_id UUID := '331810d4-05b1-4d8e-bf9b-d61cedaec117'; - BEGIN + BEGIN - -- + -- -- LeetCode plan -- Rate only (monthly, annual) -- - INSERT INTO public.plan - (id, name, description, created_at, created_by, updated_at, archived_at, tenant_id, - product_family_id, external_id, plan_type, status) - VALUES (var_plan_leetcode_id, 'LeetCode', '', '2023-12-04 10:05:45', - var_user_id, NULL, NULL, var_tenant_id, - var_product_family_id, 'default_leet-code', 'STANDARD', 'ACTIVE'); - - INSERT INTO public.plan_version - VALUES (var_plan_version_1_leetcode_id, false, var_plan_leetcode_id, 1, NULL, NULL, - var_tenant_id, NULL, 0, 'EUR', NULL, '2023-12-04 10:05:45', - var_user_id, '{MONTHLY,ANNUAL}'); - - INSERT INTO public.plan_version - VALUES ('018c344a-78a9-7e2b-af90-5748672711f9', true, var_plan_leetcode_id, 2, NULL, NULL, - var_tenant_id, NULL, 0, 'EUR', NULL, '2023-12-04 10:05:45', - var_user_id, '{MONTHLY,ANNUAL}'); - - INSERT INTO public.price_component - VALUES (var_comp_leetcode_rate_id, 'Subscription Rate', '{ - "rate": { - "pricing": { - "term_based": { - "rates": [ - { - "term": 0, - "price": { - "value": "35.00" - } - }, - { - "term": 2, - "price": { - "value": "159.00" - } - } - ] + INSERT INTO public.plan + (id, name, description, created_at, created_by, updated_at, archived_at, tenant_id, + product_family_id, local_id, plan_type, status, active_version_id, draft_version_id) + VALUES (var_plan_leetcode_id, 'LeetCode', '', '2023-12-04 10:05:45', + var_user_id, NULL, NULL, var_tenant_id, + var_product_family_id, 'default_leet-code', 'STANDARD', 'ACTIVE', NULL, NULL); + + INSERT INTO public.plan_version + VALUES (var_plan_version_1_leetcode_id, false, var_plan_leetcode_id, 1, NULL, NULL, + var_tenant_id, NULL, 0, 'EUR', NULL, '2023-12-04 10:05:45', + var_user_id); + + INSERT INTO public.plan_version + VALUES ('018c344a-78a9-7e2b-af90-5748672711f9', true, var_plan_leetcode_id, 2, NULL, NULL, + var_tenant_id, NULL, 0, 'EUR', NULL, '2023-12-04 10:05:45', + var_user_id); + + UPDATE public.plan + SET active_version_id = var_plan_version_1_leetcode_id, + draft_version_id = '018c344a-78a9-7e2b-af90-5748672711f9' + WHERE id = var_plan_leetcode_id; + + INSERT INTO public.price_component + VALUES (var_comp_leetcode_rate_id, 'Subscription Rate', '{ + "rate": { + "pricing": { + "term_based": { + "rates": [ + { + "term": 0, + "price": { + "value": "35.00" + } + }, + { + "term": 2, + "price": { + "value": "159.00" + } } - } + ] } - }', var_plan_version_1_leetcode_id, NULL); + } + } + }', var_plan_version_1_leetcode_id, NULL); - ------------------------------------------------------------ + ------------------------------------------------------------ -- -- Notion plan -- Seat based (monthly :10, annual : 96) -- - INSERT INTO public.plan - VALUES (var_plan_notion_id, 'Notion', '', '2023-12-04 10:07:15.589', - var_user_id, NULL, NULL, var_tenant_id, - var_product_family_id, 'default_notion', 'STANDARD', 'ACTIVE'); - - INSERT INTO public.plan_version - VALUES (var_plan_version_notion_id, false, var_plan_notion_id, 1, NULL, NULL, - var_tenant_id, NULL, 0, 'EUR', NULL, '2023-12-04 10:07:15.589', - var_user_id, '{MONTHLY,ANNUAL}'); - - INSERT INTO public.price_component - VALUES (var_comp_notion_seats_id, 'Seats', '{ - "slot_based": { - "quota": null, - "pricing": { - "term_based": { - "rates": [ - { - "term": 0, - "price": { - "value": "10.00" - } - }, - { - "term": 2, - "price": { - "value": "96.00" - } - } - ] + INSERT INTO public.plan + VALUES (var_plan_notion_id, 'Notion', '', '2023-12-04 10:07:15.589', + var_user_id, NULL, NULL, var_tenant_id, + var_product_family_id, 'default_notion', 'STANDARD', 'ACTIVE'); + + INSERT INTO public.plan_version + VALUES (var_plan_version_notion_id, false, var_plan_notion_id, 1, NULL, NULL, + var_tenant_id, NULL, 0, 'EUR', NULL, '2023-12-04 10:07:15.589', + var_user_id); + + UPDATE public.plan + SET active_version_id = var_plan_version_notion_id + WHERE id = var_plan_notion_id; + + INSERT INTO public.price_component + VALUES (var_comp_notion_seats_id, 'Seats', '{ + "slot_based": { + "quota": null, + "pricing": { + "term_based": { + "rates": [ + { + "term": 0, + "price": { + "value": "10.00" + } + }, + { + "term": 2, + "price": { + "value": "96.00" + } } - }, - "slot_unit": { - "id": null, - "name": "Seats" - }, - "minimum_count": 1, - "upgrade_policy": 0, - "downgrade_policy": 0 + ] } - }', var_plan_version_notion_id, NULL); - - ------------------------------------------------------------ + }, + "slot_unit": { + "id": null, + "name": "Seats" + }, + "minimum_count": 1, + "upgrade_policy": 0, + "downgrade_policy": 0 + } + }', var_plan_version_notion_id, NULL); + + ------------------------------------------------------------ -- -- Supabase plan -- Usage based (DB size, bandwith) + Slot-based (Organizations, monthly) -- - INSERT INTO public.plan - VALUES (var_plan_supabase_id, 'Supabase', '', '2023-12-04 10:08:53.591', - var_user_id, NULL, NULL, var_tenant_id, - var_product_family_id, 'default_supabase', 'STANDARD', 'ACTIVE'); - - INSERT INTO public.plan_version - VALUES (var_plan_version_supabase_id, false, var_plan_supabase_id, 3, NULL, NULL, - var_tenant_id, NULL, 0, 'EUR', NULL, '2023-12-04 17:07:07.2', - var_user_id, '{}'); - - - INSERT INTO public.price_component - VALUES (var_comp_supabase_org_slots_id, 'Organization Slots', '{ - "slot_based": { - "quota": null, - "pricing": { - "single": { - "price": { - "value": "25.00" - }, - "cadence": 0 - } - }, - "slot_unit": { - "id": null, - "name": "Organization" + INSERT INTO public.plan + VALUES (var_plan_supabase_id, 'Supabase', '', '2023-12-04 10:08:53.591', + var_user_id, NULL, NULL, var_tenant_id, + var_product_family_id, 'default_supabase', 'STANDARD', 'ACTIVE'); + + INSERT INTO public.plan_version + VALUES (var_plan_version_supabase_id, false, var_plan_supabase_id, 3, NULL, NULL, + var_tenant_id, NULL, 0, 'EUR', NULL, '2023-12-04 17:07:07.2', + var_user_id); + + UPDATE public.plan + SET active_version_id = var_plan_version_supabase_id + WHERE id = var_plan_supabase_id; + + INSERT INTO public.price_component + VALUES (var_comp_supabase_org_slots_id, 'Organization Slots', '{ + "slot_based": { + "quota": null, + "pricing": { + "single": { + "price": { + "value": "25.00" }, - "minimum_count": 1, - "upgrade_policy": 0, - "downgrade_policy": 0 + "cadence": 0 } - }', var_plan_version_supabase_id, NULL); - INSERT INTO public.price_component - VALUES (var_comp_supabase_bandwidth_id, 'Bandwidth (GB)', '{ - "usage_based": { - "model": { - "tiered": { - "rows": [ - { - "flat_cap": null, - "flat_fee": null, - "last_unit": 250, - "first_unit": 0, - "unit_price": { - "value": "0.00" - } - }, - { - "flat_cap": null, - "flat_fee": null, - "last_unit": null, - "first_unit": 250, - "unit_price": { - "value": "0.09" - } - } - ], - "block_size": null + }, + "slot_unit": { + "id": null, + "name": "Organization" + }, + "minimum_count": 1, + "upgrade_policy": 0, + "downgrade_policy": 0 + } + }', var_plan_version_supabase_id, NULL); + INSERT INTO public.price_component + VALUES (var_comp_supabase_bandwidth_id, 'Bandwidth (GB)', '{ + "usage_based": { + "model": { + "tiered": { + "rows": [ + { + "flat_cap": null, + "flat_fee": null, + "last_unit": 250, + "first_unit": 0, + "unit_price": { + "value": "0.00" + } + }, + { + "flat_cap": null, + "flat_fee": null, + "last_unit": null, + "first_unit": 250, + "unit_price": { + "value": "0.09" + } } - }, - "metric": { - "id": "018c3453-1f11-76a8-8d69-f74921b2646d", - "name": "N/A" - } + ], + "block_size": null } - }', var_plan_version_supabase_id, NULL); - INSERT INTO public.price_component - VALUES (var_comp_supabase_db_size_id, 'Database size (GB)', '{ - "usage_based": { - "model": { - "tiered": { - "rows": [ - { - "flat_cap": null, - "flat_fee": null, - "last_unit": 8, - "first_unit": 0, - "unit_price": { - "value": "0.00" - } - }, - { - "flat_cap": null, - "flat_fee": null, - "last_unit": null, - "first_unit": 8, - "unit_price": { - "value": "0.125" - } - } - ], - "block_size": null + }, + "metric": { + "id": "018c3453-1f11-76a8-8d69-f74921b2646d", + "name": "N/A" + } + } + }', var_plan_version_supabase_id, NULL); + INSERT INTO public.price_component + VALUES (var_comp_supabase_db_size_id, 'Database size (GB)', '{ + "usage_based": { + "model": { + "tiered": { + "rows": [ + { + "flat_cap": null, + "flat_fee": null, + "last_unit": 8, + "first_unit": 0, + "unit_price": { + "value": "0.00" + } + }, + { + "flat_cap": null, + "flat_fee": null, + "last_unit": null, + "first_unit": 8, + "unit_price": { + "value": "0.125" + } } - }, - "metric": { - "id": "018c3452-129f-702c-93f4-9c15095b0ef4", - "name": "N/A" - } + ], + "block_size": null } - }', var_plan_version_supabase_id, NULL); - - END + }, + "metric": { + "id": "018c3452-129f-702c-93f4-9c15095b0ef4", + "name": "N/A" + } + } + }', var_plan_version_supabase_id, NULL); + + END $$; diff --git a/modules/meteroid/tests/data/3_subscriptions.sql b/modules/meteroid/tests/data/3_subscriptions.sql index ac323c25..87939155 100644 --- a/modules/meteroid/tests/data/3_subscriptions.sql +++ b/modules/meteroid/tests/data/3_subscriptions.sql @@ -1,178 +1,178 @@ DO $$ - DECLARE + DECLARE - var_user_id UUID := 'ae35bbb9-65da-477d-b856-7dbd87546441'; - var_tenant_id UUID := '018c2c82-3df1-7e84-9e05-6e141d0e751a'; + var_user_id UUID := 'ae35bbb9-65da-477d-b856-7dbd87546441'; + var_tenant_id UUID := '018c2c82-3df1-7e84-9e05-6e141d0e751a'; - -- customers + -- customers - var_cust_spotify_id UUID := '018c345f-7324-7cd2-a692-78e5ab9158e0'; - var_cust_uber_id UUID := '018c345f-dff1-7857-b988-6c792ed6fa3f'; - var_cust_comodo_id UUID := '018c3463-05f3-7c1f-92b1-ddb1f70905a2'; + var_cust_spotify_id UUID := '018c345f-7324-7cd2-a692-78e5ab9158e0'; + var_cust_uber_id UUID := '018c345f-dff1-7857-b988-6c792ed6fa3f'; + var_cust_comodo_id UUID := '018c3463-05f3-7c1f-92b1-ddb1f70905a2'; - -- plan_versions + -- plan_versions - var_plan_version_1_leetcode_id UUID := '018c344a-78a9-7e2b-af90-5748672711f8'; - var_plan_version_notion_id UUID := '018c344b-da87-7392-bbae-c5c8780adb1b'; - var_plan_version_supabase_id UUID := '018c35cc-3f41-7551-b7b6-f8bbcd62b784'; + var_plan_version_1_leetcode_id UUID := '018c344a-78a9-7e2b-af90-5748672711f8'; + var_plan_version_notion_id UUID := '018c344b-da87-7392-bbae-c5c8780adb1b'; + var_plan_version_supabase_id UUID := '018c35cc-3f41-7551-b7b6-f8bbcd62b784'; - -- components + -- components - var_comp_leetcode_rate_id UUID := '018c344b-6050-7ec8-bd8c-d2e9c41ab711'; - var_comp_notion_seats_id UUID := '018c344c-9ec9-7608-b115-1537b6985e73'; - var_comp_supabase_org_slots_id UUID := '3b083801-c77c-4488-848e-a185f0f0a8be'; + var_comp_leetcode_rate_id UUID := '018c344b-6050-7ec8-bd8c-d2e9c41ab711'; + var_comp_notion_seats_id UUID := '018c344c-9ec9-7608-b115-1537b6985e73'; + var_comp_supabase_org_slots_id UUID := '3b083801-c77c-4488-848e-a185f0f0a8be'; - -- subscriptions + -- subscriptions - var_sub_spotify_notion_id UUID := '018c3475-bdc5-77dd-9e26-e9a7fdd60426'; - var_sub_spotify_supabase_id UUID := '018c3762-d554-7339-b13d-6fff8c9b76a0'; - var_sub_uber_notion_id UUID := '018c3477-2274-7029-9743-b3a4eb779399'; - var_sub_uber_leetcode_id UUID := '018c3479-fa9d-713f-b74f-6d9cc22cf110'; - var_sub_comodo_leetcode_id UUID := '018c347a-b42b-709f-8e70-b0b63029aa35'; - var_sub_comodo_supabase_id UUID := '018c3763-070e-709d-8413-f42828e71943'; + var_sub_spotify_notion_id UUID := '018c3475-bdc5-77dd-9e26-e9a7fdd60426'; + var_sub_spotify_supabase_id UUID := '018c3762-d554-7339-b13d-6fff8c9b76a0'; + var_sub_uber_notion_id UUID := '018c3477-2274-7029-9743-b3a4eb779399'; + var_sub_uber_leetcode_id UUID := '018c3479-fa9d-713f-b74f-6d9cc22cf110'; + var_sub_comodo_leetcode_id UUID := '018c347a-b42b-709f-8e70-b0b63029aa35'; + var_sub_comodo_supabase_id UUID := '018c3763-070e-709d-8413-f42828e71943'; - BEGIN + BEGIN - -- + -- -- Data for Name: subscription; Type: TABLE DATA; Schema: public; Owner: meteroidbilling -- - INSERT INTO public.subscription (id, customer_id, billing_day, tenant_id, trial_start_date, billing_start_date, - billing_end_date, plan_version_id, created_at, created_by, net_terms, - invoice_memo, - invoice_threshold, activated_at, canceled_at, cancellation_reason, currency, - mrr_cents, - period) - VALUES (var_sub_spotify_notion_id, var_cust_spotify_id, 1, - var_tenant_id, NULL, '2023-11-04', NULL, - var_plan_version_notion_id, - '2023-12-04 10:53:00.742', var_user_id, 0, null, null, null, null, null, - 'EUR', 0, - 'MONTHLY'); - - - INSERT INTO public.subscription_component - (id, name, subscription_id, price_component_id, product_item_id, period, fee) - VALUES ('018f0a4f-7bb5-78f4-b239-dece81ee4585', 'Seats', var_sub_spotify_notion_id, - var_comp_notion_seats_id, null, 'MONTHLY', '{ - "Slot": { - "unit": "Seats", - "max_slots": null, - "min_slots": 1, - "unit_rate": "10.00", - "initial_slots": 12 - } - }'); - - - INSERT INTO public.subscription - VALUES (var_sub_comodo_leetcode_id, var_cust_comodo_id, 31, - var_tenant_id, NULL, '2023-11-04', NULL, - var_plan_version_1_leetcode_id, - '2023-12-04 10:58:25.964', var_user_id, 30, null, null, null, null, null, - 'EUR', 0, - 'MONTHLY'); - - INSERT INTO public.subscription_component - (id, name, subscription_id, price_component_id, product_item_id, period, fee) - VALUES ('018f0a4f-9f81-7b70-871f-8efcf61f284c', 'Seats', var_sub_comodo_leetcode_id, - var_comp_leetcode_rate_id, null, 'MONTHLY', '{ - "Rate": { - "rate": "35.00" - } - }'); - - - INSERT INTO public.subscription (id, customer_id, billing_day, tenant_id, trial_start_date, billing_start_date, - billing_end_date, plan_version_id, created_at, created_by, net_terms, - invoice_memo, - invoice_threshold, activated_at, canceled_at, cancellation_reason, currency, - mrr_cents, - period) - VALUES (var_sub_uber_notion_id, var_cust_uber_id, 1, - var_tenant_id, NULL, '2023-11-04', NULL, - var_plan_version_notion_id, - '2023-12-04 10:54:32.056', var_user_id, 0, null, null, null, null, null, - 'EUR', 0, - 'ANNUAL'); - - INSERT INTO public.subscription_component - (id, name, subscription_id, price_component_id, product_item_id, period, fee) - VALUES ('018f0a50-0053-7c41-bd4b-f7bdcca609e7', 'Seats', var_sub_uber_notion_id, - var_comp_notion_seats_id, null, 'ANNUAL', '{ - "Slot": { - "unit": "Seats", - "max_slots": null, - "min_slots": 1, - "unit_rate": "96.00", - "initial_slots": 25 - } - }'); - - - INSERT INTO public.subscription - VALUES (var_sub_uber_leetcode_id, var_cust_uber_id, 15, - var_tenant_id, NULL, '2023-11-04', NULL, - var_plan_version_1_leetcode_id, - '2023-12-04 10:57:38.462', var_user_id, 30, null, null, null, null, null, - 'EUR', 0, - 'ANNUAL'); - - - INSERT INTO public.subscription_component - (id, name, subscription_id, price_component_id, product_item_id, period, fee) - VALUES ('018f0a50-3a67-7448-8235-6ca5a4c75b41', 'Seats', var_sub_uber_leetcode_id, - var_comp_leetcode_rate_id, null, 'ANNUAL', '{ - "Rate": { - "rate": "159.00" - } - }'); - - INSERT INTO public.subscription - VALUES (var_sub_spotify_supabase_id, var_cust_spotify_id, 1, - var_tenant_id, NULL, '2023-11-04', NULL, - var_plan_version_supabase_id, - '2023-12-05 00:31:13.237', var_user_id, 0, null, null, null, null, null, - 'EUR', 0, - 'MONTHLY'); - - INSERT INTO public.subscription_component - (id, name, subscription_id, price_component_id, product_item_id, period, fee) - VALUES ('018f0a50-50f7-779e-9255-cbbad34f5a88', 'Organization Slots', var_sub_spotify_supabase_id, - var_comp_supabase_org_slots_id, null, 'MONTHLY', '{ - "Slot": { - "unit": "Organization", - "max_slots": null, - "min_slots": 1, - "unit_rate": "96.00", - "initial_slots": 15 - } - }'); - - - INSERT INTO public.subscription - VALUES (var_sub_comodo_supabase_id, var_cust_comodo_id, 1, - var_tenant_id, NULL, '2023-11-04', NULL, - var_plan_version_supabase_id, - '2023-12-05 00:31:25.967', var_user_id, 0, null, null, null, null, null, - 'EUR', 0, - 'MONTHLY'); - - INSERT INTO public.subscription_component - (id, name, subscription_id, price_component_id, product_item_id, period, fee) - VALUES ('018f0a50-9bcc-73c8-a3ca-25e2439c1dbd', 'Organization Slots', var_sub_comodo_supabase_id, - var_comp_supabase_org_slots_id, null, 'MONTHLY', '{ - "Slot": { - "unit": "Organization", - "max_slots": null, - "min_slots": 1, - "unit_rate": "96.00", - "initial_slots": 3 - } - }'); - - - END + INSERT INTO public.subscription (id, customer_id, billing_day, tenant_id, trial_start_date, billing_start_date, + billing_end_date, plan_version_id, created_at, created_by, net_terms, + invoice_memo, + invoice_threshold, activated_at, canceled_at, cancellation_reason, currency, + mrr_cents, + period) + VALUES (var_sub_spotify_notion_id, var_cust_spotify_id, 1, + var_tenant_id, NULL, '2023-11-04', NULL, + var_plan_version_notion_id, + '2023-12-04 10:53:00.742', var_user_id, 0, null, null, null, null, null, + 'EUR', 0, + 'MONTHLY'); + + + INSERT INTO public.subscription_component + (id, name, subscription_id, price_component_id, product_id, period, fee) + VALUES ('018f0a4f-7bb5-78f4-b239-dece81ee4585', 'Seats', var_sub_spotify_notion_id, + var_comp_notion_seats_id, null, 'MONTHLY', '{ + "Slot": { + "unit": "Seats", + "max_slots": null, + "min_slots": 1, + "unit_rate": "10.00", + "initial_slots": 12 + } + }'); + + + INSERT INTO public.subscription + VALUES (var_sub_comodo_leetcode_id, var_cust_comodo_id, 31, + var_tenant_id, NULL, '2023-11-04', NULL, + var_plan_version_1_leetcode_id, + '2023-12-04 10:58:25.964', var_user_id, 30, null, null, null, null, null, + 'EUR', 0, + 'MONTHLY'); + + INSERT INTO public.subscription_component + (id, name, subscription_id, price_component_id, product_id, period, fee) + VALUES ('018f0a4f-9f81-7b70-871f-8efcf61f284c', 'Seats', var_sub_comodo_leetcode_id, + var_comp_leetcode_rate_id, null, 'MONTHLY', '{ + "Rate": { + "rate": "35.00" + } + }'); + + + INSERT INTO public.subscription (id, customer_id, billing_day, tenant_id, trial_start_date, billing_start_date, + billing_end_date, plan_version_id, created_at, created_by, net_terms, + invoice_memo, + invoice_threshold, activated_at, canceled_at, cancellation_reason, currency, + mrr_cents, + period) + VALUES (var_sub_uber_notion_id, var_cust_uber_id, 1, + var_tenant_id, NULL, '2023-11-04', NULL, + var_plan_version_notion_id, + '2023-12-04 10:54:32.056', var_user_id, 0, null, null, null, null, null, + 'EUR', 0, + 'ANNUAL'); + + INSERT INTO public.subscription_component + (id, name, subscription_id, price_component_id, product_id, period, fee) + VALUES ('018f0a50-0053-7c41-bd4b-f7bdcca609e7', 'Seats', var_sub_uber_notion_id, + var_comp_notion_seats_id, null, 'ANNUAL', '{ + "Slot": { + "unit": "Seats", + "max_slots": null, + "min_slots": 1, + "unit_rate": "96.00", + "initial_slots": 25 + } + }'); + + + INSERT INTO public.subscription + VALUES (var_sub_uber_leetcode_id, var_cust_uber_id, 15, + var_tenant_id, NULL, '2023-11-04', NULL, + var_plan_version_1_leetcode_id, + '2023-12-04 10:57:38.462', var_user_id, 30, null, null, null, null, null, + 'EUR', 0, + 'ANNUAL'); + + + INSERT INTO public.subscription_component + (id, name, subscription_id, price_component_id, product_id, period, fee) + VALUES ('018f0a50-3a67-7448-8235-6ca5a4c75b41', 'Seats', var_sub_uber_leetcode_id, + var_comp_leetcode_rate_id, null, 'ANNUAL', '{ + "Rate": { + "rate": "159.00" + } + }'); + + INSERT INTO public.subscription + VALUES (var_sub_spotify_supabase_id, var_cust_spotify_id, 1, + var_tenant_id, NULL, '2023-11-04', NULL, + var_plan_version_supabase_id, + '2023-12-05 00:31:13.237', var_user_id, 0, null, null, null, null, null, + 'EUR', 0, + 'MONTHLY'); + + INSERT INTO public.subscription_component + (id, name, subscription_id, price_component_id, product_id, period, fee) + VALUES ('018f0a50-50f7-779e-9255-cbbad34f5a88', 'Organization Slots', var_sub_spotify_supabase_id, + var_comp_supabase_org_slots_id, null, 'MONTHLY', '{ + "Slot": { + "unit": "Organization", + "max_slots": null, + "min_slots": 1, + "unit_rate": "96.00", + "initial_slots": 15 + } + }'); + + + INSERT INTO public.subscription + VALUES (var_sub_comodo_supabase_id, var_cust_comodo_id, 1, + var_tenant_id, NULL, '2023-11-04', NULL, + var_plan_version_supabase_id, + '2023-12-05 00:31:25.967', var_user_id, 0, null, null, null, null, null, + 'EUR', 0, + 'MONTHLY'); + + INSERT INTO public.subscription_component + (id, name, subscription_id, price_component_id, product_id, period, fee) + VALUES ('018f0a50-9bcc-73c8-a3ca-25e2439c1dbd', 'Organization Slots', var_sub_comodo_supabase_id, + var_comp_supabase_org_slots_id, null, 'MONTHLY', '{ + "Slot": { + "unit": "Organization", + "max_slots": null, + "min_slots": 1, + "unit_rate": "96.00", + "initial_slots": 3 + } + }'); + + + END $$; diff --git a/modules/meteroid/tests/integration/e2e.rs b/modules/meteroid/tests/integration/e2e.rs index d6e5a40a..3b9836d3 100644 --- a/modules/meteroid/tests/integration/e2e.rs +++ b/modules/meteroid/tests/integration/e2e.rs @@ -27,7 +27,6 @@ use meteroid_store::domain::{ PaginationRequest, }; use meteroid_store::repositories::InvoiceInterface; -use meteroid_store::utils::local_id::LocalId; use meteroid_store::Store; use crate::metering_it; @@ -295,7 +294,8 @@ async fn test_metering_e2e() { })), }), usage_group_key: None, - family_external_id: "default".to_string(), + family_local_id: "default".to_string(), + product_id: None, })) .await .expect("Could not create meter"); @@ -344,9 +344,8 @@ async fn test_metering_e2e() { .create_draft_plan(Request::new( meteroid_grpc::meteroid::api::plans::v1::CreateDraftPlanRequest { name: "Meteroid AI".to_string(), - external_id: "meteroid_ai".to_string(), description: None, - product_family_external_id: "default".to_string(), + product_family_local_id: "default".to_string(), plan_type: PlanType::Standard as i32, }, )) @@ -354,7 +353,7 @@ async fn test_metering_e2e() { .unwrap(); let plan = plan.into_inner().plan.unwrap(); - let plan_version = plan.current_version.unwrap(); + let plan_version = plan.version.unwrap(); let plan = plan.plan.unwrap(); let plan_version_id = plan_version.id; @@ -386,7 +385,7 @@ async fn test_metering_e2e() { )), }), - product_item_id: None, + product_id: None, }, )) .await @@ -482,7 +481,6 @@ async fn test_metering_e2e() { subtotal_recurring: 100, tax_rate: 0, tax_amount: 0, - local_id: LocalId::no_prefix(), customer_details: InlineCustomer { billing_address: None, id: Uuid::from_str(&customer_1).unwrap(), diff --git a/modules/meteroid/tests/integration/test_basic.rs b/modules/meteroid/tests/integration/test_basic.rs index dc40f097..97f77958 100644 --- a/modules/meteroid/tests/integration/test_basic.rs +++ b/modules/meteroid/tests/integration/test_basic.rs @@ -57,7 +57,7 @@ async fn test_main() { .create_product_family(tonic::Request::new( api::productfamilies::v1::CreateProductFamilyRequest { name: "Test - usage".to_string(), - external_id: "test-usage".to_string(), + local_id: "test-usage".to_string(), }, )) .await @@ -72,9 +72,8 @@ async fn test_main() { .create_draft_plan(tonic::Request::new( api::plans::v1::CreateDraftPlanRequest { name: "Test - usage".to_string(), - external_id: "test-usage-plan".to_string(), description: None, - product_family_external_id: product_family.external_id, + product_family_local_id: product_family.local_id, plan_type: PlanType::Standard as i32, }, )) @@ -84,7 +83,7 @@ async fn test_main() { .plan .unwrap(); - let plan_version = plan.current_version.unwrap(); + let plan_version = plan.version.unwrap(); let _price_component = clients .price_components @@ -101,7 +100,7 @@ async fn test_main() { }, )), }), - product_item_id: None, + product_id: None, }, )) .await diff --git a/modules/meteroid/tests/integration/test_billable_metric.rs b/modules/meteroid/tests/integration/test_billable_metric.rs index 11966a12..19d3fd13 100644 --- a/modules/meteroid/tests/integration/test_billable_metric.rs +++ b/modules/meteroid/tests/integration/test_billable_metric.rs @@ -32,7 +32,7 @@ async fn test_billable_metrics_basic() { .clone() .create_product_family(api::productfamilies::v1::CreateProductFamilyRequest { name: "product_family_name".into(), - external_id: "product_family_external_id".into(), + local_id: "product_family_local_id".into(), }) .await .unwrap() @@ -58,7 +58,8 @@ async fn test_billable_metrics_basic() { }), segmentation_matrix: None, // todo add usage_group_key: Some("usage".to_string()), - family_external_id: "product_family_external_id".to_string(), + family_local_id: "product_family_local_id".to_string(), + product_id: None, }) .await .unwrap() diff --git a/modules/meteroid/tests/integration/test_plan.rs b/modules/meteroid/tests/integration/test_plan.rs index 7f41a85e..097f2d41 100644 --- a/modules/meteroid/tests/integration/test_plan.rs +++ b/modules/meteroid/tests/integration/test_plan.rs @@ -34,8 +34,7 @@ async fn test_plans_basic() { .clone() .create_draft_plan(api::plans::v1::CreateDraftPlanRequest { name: "plan_name".into(), - external_id: "plan_external_id".into(), - product_family_external_id: "default".into(), + product_family_local_id: "default".into(), description: Some("plan_description".into()), plan_type: api::plans::v1::PlanType::Standard as i32, }) @@ -46,11 +45,15 @@ async fn test_plans_basic() { .unwrap(); let created_plan = created_plan_details.plan.clone().unwrap(); - let created_version = created_plan_details.current_version.clone().unwrap(); - let created_metadata = created_plan_details.metadata.clone(); + let created_version = created_plan_details.version.clone().unwrap(); + println!("{:?}", created_plan); + + assert_eq!( + created_plan.draft_version_id.clone().unwrap(), + created_version.id.clone() + ); assert_eq!(created_plan.name.as_str(), "plan_name"); - assert_eq!(created_plan.external_id.as_str(), "plan_external_id"); assert_eq!( created_plan.description, Some("plan_description".to_string()) @@ -68,7 +71,6 @@ async fn test_plans_basic() { assert_eq!( created_version.billing_config, Some(PlanBillingConfiguration { - billing_periods: vec![], net_terms: 0, service_period_start: Some(ServicePeriodStart::SubscriptionAnniversary( SubscriptionAnniversary {} @@ -77,19 +79,18 @@ async fn test_plans_basic() { }) ); - assert_eq!(created_metadata.len(), 0); - - // get plan by external_id + // get plan by local_id let plan_details = clients .plans .clone() - .get_plan_by_external_id(api::plans::v1::GetPlanByExternalIdRequest { - external_id: "plan_external_id".into(), + .get_plan_with_version(api::plans::v1::GetPlanWithVersionRequest { + filter: Some(api::plans::v1::get_plan_with_version_request::Filter::Draft(())), + local_id: created_plan.local_id.clone(), }) .await .unwrap() .into_inner() - .plan_details + .plan .unwrap(); assert_eq!(&plan_details, &created_plan_details); @@ -99,7 +100,7 @@ async fn test_plans_basic() { .plans .clone() .list_plans(api::plans::v1::ListPlansRequest { - product_family_external_id: None, + product_family_local_id: None, sort_by: 0, pagination: None, filters: None, @@ -112,36 +113,30 @@ async fn test_plans_basic() { assert_eq!(plans.len(), 1); let plan_list = plans.first().unwrap(); assert_eq!(plan_list.name.as_str(), "plan_name"); - assert_eq!(plan_list.external_id.as_str(), "plan_external_id"); + assert_eq!(plan_list.local_id.as_str(), created_plan.local_id); assert_eq!(plan_list.description, Some("plan_description".to_string())); assert_eq!(plan_list.plan_status(), api::plans::v1::PlanStatus::Draft); assert_eq!(plan_list.plan_type(), api::plans::v1::PlanType::Standard); - let plan_versions = clients - .plans - .clone() - .list_subscribable_plan_version(api::plans::v1::ListSubscribablePlanVersionRequest {}) - .await - .unwrap() - .into_inner() - .plan_versions; - - assert_eq!(plan_versions.len(), 0); - - // get_plan_version_by_id + // get_plan_with_version let plan_version = clients .plans .clone() - .get_plan_version_by_id(api::plans::v1::GetPlanVersionByIdRequest { - plan_version_id: created_version.id.clone(), + .get_plan_with_version(api::plans::v1::GetPlanWithVersionRequest { + local_id: created_plan.local_id.clone(), + filter: Some( + api::plans::v1::get_plan_with_version_request::Filter::Version( + created_version.version, + ), + ), }) .await .unwrap() .into_inner() - .plan_version + .plan .unwrap(); - assert_eq!(&plan_version, &created_version); + assert_eq!(&plan_version.version.unwrap(), &created_version); // list_plan_version_by_id let plan_versions = clients @@ -179,18 +174,6 @@ async fn test_plans_basic() { assert_eq!(&published_version.is_draft, &false); - // ListSubscribablePlanVersion - let plan_versions = clients - .plans - .clone() - .list_subscribable_plan_version(api::plans::v1::ListSubscribablePlanVersionRequest {}) - .await - .unwrap() - .into_inner() - .plan_versions; - - assert_eq!(plan_versions.len(), 1); - // copy version to draft let copied_draft_version = clients .plans @@ -213,19 +196,22 @@ async fn test_plans_basic() { let last_published_version = clients .plans .clone() - .get_last_published_plan_version(api::plans::v1::GetLastPublishedPlanVersionRequest { - plan_id: created_plan.id.clone(), + .get_plan_with_version(api::plans::v1::GetPlanWithVersionRequest { + local_id: created_plan.local_id.clone(), + filter: Some(api::plans::v1::get_plan_with_version_request::Filter::Active(())), }) .await .unwrap() .into_inner() + .plan + .unwrap() .version .unwrap(); assert_eq!(&last_published_version, &published_version); // update draft plan - let plan_overview = clients + let plan_with_version = clients .plans .clone() .update_draft_plan_overview(api::plans::v1::UpdateDraftPlanOverviewRequest { @@ -235,27 +221,22 @@ async fn test_plans_basic() { description: Some("new-plan-desc".to_string()), currency: "AUD".to_string(), net_terms: 5, - billing_periods: vec![api::shared::v1::BillingPeriod::Quarterly as i32], }) .await .unwrap() .into_inner() - .plan_overview + .plan .unwrap(); - assert_eq!(&plan_overview.plan_id, &created_plan.id); - assert_eq!(&plan_overview.plan_version_id, &copied_draft_version.id); - assert_eq!(&plan_overview.name, "new-plan-name"); - assert_eq!( - &plan_overview.description, - &Some("new-plan-desc".to_string()) - ); - assert_eq!(&plan_overview.currency, "AUD"); - assert_eq!(&plan_overview.net_terms, &5); - assert_eq!( - &plan_overview.billing_periods, - &vec![api::shared::v1::BillingPeriod::Quarterly as i32] - ); + let plan = plan_with_version.plan.unwrap(); + let version = plan_with_version.version.unwrap(); + + assert_eq!(&plan.id, &created_plan.id); + assert_eq!(&version.id, &copied_draft_version.id); + assert_eq!(&plan.name, "new-plan-name"); + assert_eq!(&plan.description, &Some("new-plan-desc".to_string())); + assert_eq!(&version.currency, "AUD"); + assert_eq!(&version.net_terms, &5); // discard plan version clients @@ -283,12 +264,12 @@ async fn test_plans_basic() { assert_eq!(plan_versions.len(), 1); - // get plan overview by external_id + // get plan overview by local_id let plan_overview = clients .plans .clone() - .get_plan_overview_by_external_id(api::plans::v1::GetPlanOverviewByExternalIdRequest { - external_id: created_plan.external_id, + .get_plan_overview(api::plans::v1::GetPlanOverviewRequest { + local_id: created_plan.local_id, }) .await .unwrap() @@ -296,10 +277,13 @@ async fn test_plans_basic() { .plan_overview .unwrap(); - assert_eq!(&plan_overview.plan_id, &created_plan.id); - assert_eq!(&plan_overview.plan_version_id, &created_version.id); + assert_eq!(&plan_overview.id, &created_plan.id); + assert_eq!( + &plan_overview.active_version.map(|a| a.version), + &Some(created_version.version) + ); - // update publised plan + // update published plan let plan_overview = clients .plans .clone() @@ -315,8 +299,7 @@ async fn test_plans_basic() { .plan_overview .unwrap(); - assert_eq!(&plan_overview.plan_id, &created_plan.id); - assert_eq!(&plan_overview.plan_version_id, &created_version.id); + assert_eq!(&plan_overview.id, &created_plan.id); assert_eq!(&plan_overview.name, "new-plan-name"); assert_eq!( &plan_overview.description, diff --git a/modules/meteroid/tests/integration/test_product.rs b/modules/meteroid/tests/integration/test_product.rs index c262a933..3ee532f1 100644 --- a/modules/meteroid/tests/integration/test_product.rs +++ b/modules/meteroid/tests/integration/test_product.rs @@ -27,7 +27,7 @@ async fn test_products_basic() { .clone() .create_product_family(api::productfamilies::v1::CreateProductFamilyRequest { name: "product_family_name".into(), - external_id: "product_family_external_id".into(), + local_id: "product_family_local_id".into(), }) .await .unwrap() @@ -42,7 +42,7 @@ async fn test_products_basic() { .create_product(api::products::v1::CreateProductRequest { name: "product_name".into(), description: Some("product_description".into()), - family_external_id: family.external_id.clone(), + family_local_id: family.local_id.clone(), }) .await .unwrap() @@ -53,7 +53,7 @@ async fn test_products_basic() { assert_eq!(created.name.as_str(), "product_name"); assert_eq!(created.description, Some("product_description".to_string())); - // product family by external_id + // product family by local_id let by_id = clients .products .clone() @@ -73,7 +73,7 @@ async fn test_products_basic() { .products .clone() .list_products(api::products::v1::ListProductsRequest { - family_external_id: family.external_id.clone(), + family_local_id: family.local_id.clone(), pagination: None, }) .await @@ -89,7 +89,7 @@ async fn test_products_basic() { .products .clone() .search_products(api::products::v1::SearchProductsRequest { - family_external_id: family.external_id.clone(), + family_local_id: family.local_id.clone(), query: Some("_nAm".to_string()), pagination: None, }) diff --git a/modules/meteroid/tests/integration/test_product_family.rs b/modules/meteroid/tests/integration/test_product_family.rs index 8261a21a..4a790c12 100644 --- a/modules/meteroid/tests/integration/test_product_family.rs +++ b/modules/meteroid/tests/integration/test_product_family.rs @@ -28,7 +28,7 @@ async fn test_product_families_basic() { .clone() .create_product_family(api::productfamilies::v1::CreateProductFamilyRequest { name: "product_family_name".into(), - external_id: "product_family_external_id".into(), + local_id: "product_family_local_id".into(), }) .await .unwrap() @@ -37,15 +37,15 @@ async fn test_product_families_basic() { .unwrap(); assert_eq!(created.name.as_str(), "product_family_name"); - assert_eq!(created.external_id.as_str(), "product_family_external_id"); + assert_eq!(created.local_id.as_str(), "product_family_local_id"); - // product family by external_id - let by_external_id = clients + // product family by local_id + let by_local_id = clients .product_families .clone() - .get_product_family_by_external_id( - api::productfamilies::v1::GetProductFamilyByExternalIdRequest { - external_id: "product_family_external_id".into(), + .get_product_family_by_local_id( + api::productfamilies::v1::GetProductFamilyByLocalIdRequest { + local_id: "product_family_local_id".into(), }, ) .await @@ -54,7 +54,7 @@ async fn test_product_families_basic() { .product_family .unwrap(); - assert_eq!(&by_external_id, &created); + assert_eq!(&by_local_id, &created); // list product families let listed = clients diff --git a/modules/web/packages/ui/src/components/ui/badge.tsx b/modules/web/packages/ui/src/components/ui/badge.tsx index d47f44c8..9fae505e 100644 --- a/modules/web/packages/ui/src/components/ui/badge.tsx +++ b/modules/web/packages/ui/src/components/ui/badge.tsx @@ -4,7 +4,7 @@ import * as React from 'react' import { cn } from '@ui/lib' const badgeVariants = cva( - 'inline-flex items-center rounded-md border px-2.5 py-0 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2', + 'inline-flex items-center rounded-md border text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2', { variants: { variant: { @@ -19,9 +19,13 @@ const badgeVariants = cva( 'border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80', outline: 'text-foreground', }, + size: { + md: 'px-2.5 py-1', + }, }, defaultVariants: { variant: 'default', + size: 'md', }, } ) @@ -30,8 +34,8 @@ export interface BadgeProps extends React.HTMLAttributes, VariantProps {} -function Badge({ className, variant, ...props }: BadgeProps) { - return
+function Badge({ className, variant, size, ...props }: BadgeProps) { + return
} export { Badge, badgeVariants } diff --git a/modules/web/web-app/components/FamilyPicker/FamilyPicker.tsx b/modules/web/web-app/components/FamilyPicker/FamilyPicker.tsx index 8d3e1cfd..d9e76536 100644 --- a/modules/web/web-app/components/FamilyPicker/FamilyPicker.tsx +++ b/modules/web/web-app/components/FamilyPicker/FamilyPicker.tsx @@ -22,8 +22,8 @@ const FamilyPicker: FunctionComponent = () => { const families = familiesQuery.data?.productFamilies ?? [] // TODO query params ? - const { familyExternalId } = useTypedParams<{ familyExternalId: string }>() - const selected = families.find(fam => fam.externalId === familyExternalId) || families[0] + const { familyLocalId } = useTypedParams<{ familyLocalId: string }>() + const selected = families.find(fam => fam.localId === familyLocalId) || families[0] return ( @@ -48,7 +48,7 @@ const FamilyPicker: FunctionComponent = () => { {families .sort((a, b) => a.name.localeCompare(b.name)) .map(family => ( - + {family.name} ))} diff --git a/modules/web/web-app/components/LocalId.tsx b/modules/web/web-app/components/LocalId.tsx new file mode 100644 index 00000000..b528cf7c --- /dev/null +++ b/modules/web/web-app/components/LocalId.tsx @@ -0,0 +1,41 @@ +import { Button } from '@ui/components' +import { cn } from '@ui/lib' +import { CopyIcon } from 'lucide-react' +import { toast } from 'sonner' + +import { copyToClipboard } from '@/lib/helpers' + +export const LocalId = ({ + localId, + buttonClassName, + className, +}: { + localId: string + buttonClassName?: string + className?: string +}) => { + return ( + + ) +} diff --git a/modules/web/web-app/components/table/CustomTable/CustomTable.tsx b/modules/web/web-app/components/table/CustomTable/CustomTable.tsx index b339b03f..f43d66d9 100644 --- a/modules/web/web-app/components/table/CustomTable/CustomTable.tsx +++ b/modules/web/web-app/components/table/CustomTable/CustomTable.tsx @@ -1,6 +1,6 @@ import { spaces } from '@md/foundation' -import { ChevronUpIcon, ChevronDownIcon } from '@md/icons' -import { Skeleton, Table, TableRow, TableCell, TableHeader, TableHead, TableBody } from '@md/ui' +import { ChevronDownIcon, ChevronUpIcon } from '@md/icons' +import { Skeleton, Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@md/ui' import { ColumnDef, OnChangeFn, @@ -18,14 +18,7 @@ import { Flex } from '@ui/components/legacy' import { AlertCircleIcon } from 'lucide-react' import { ReactNode, useMemo, useState } from 'react' -import { - SortableDefaultIndicator, - SortableIndicatorContainer, - // SortableTh, - // StyledTable, - // StyledTd, - // StyledTh, -} from './CustomTable.styled' +import { SortableDefaultIndicator, SortableIndicatorContainer } from './CustomTable.styled' import Pagination from './components/Pagination' interface CustomTableProps { @@ -61,6 +54,7 @@ export const CustomTable = ({ const defaultData = useMemo(() => [], []) const [sorting, setSorting] = useState([]) + const table = useReactTable({ data: data ?? defaultData, columns, diff --git a/modules/web/web-app/components/table/StandardTable.tsx b/modules/web/web-app/components/table/StandardTable.tsx index 0822ecae..9b01128a 100644 --- a/modules/web/web-app/components/table/StandardTable.tsx +++ b/modules/web/web-app/components/table/StandardTable.tsx @@ -1,6 +1,7 @@ import { TableCell, TableRow } from '@md/ui' import { ColumnDef, OnChangeFn, PaginationState, Row, flexRender } from '@tanstack/react-table' import { ReactNode } from 'react' +import { Link } from 'react-router-dom' import { CustomTable } from '@/components/table/CustomTable' @@ -13,6 +14,7 @@ interface StandardTableProps { totalCount: number emptyMessage?: string | ReactNode isLoading?: boolean + rowLink?: (row: Row) => string } export const StandardTable = ({ columns, @@ -23,6 +25,7 @@ export const StandardTable = ({ totalCount, emptyMessage = 'No data to display', isLoading, + rowLink, }: StandardTableProps) => { return ( ({ setPagination={setPagination} totalCount={totalCount} emptyMessage={emptyMessage} - rowRenderer={standardRowRenderer} + rowRenderer={row => standardRowRenderer(row, rowLink)} isLoading={isLoading} /> ) } -const standardRowRenderer = (row: Row) => { - return ( - - {row.getVisibleCells().map(cell => { - return ( - - {flexRender(cell.column.columnDef.cell, cell.getContext())} - - ) - })} - - ) +const standardRowRenderer = (row: Row, rowLink?: (row: Row) => string) => { + const cells = row + .getVisibleCells() + .map(cell => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + )) + + if (rowLink) { + return ( + + + {cells} + + + ) + } + + return {cells} } diff --git a/modules/web/web-app/features/billing/plans/ListPlanVersion.tsx b/modules/web/web-app/features/billing/plans/ListPlanVersion.tsx new file mode 100644 index 00000000..f4cc7e06 --- /dev/null +++ b/modules/web/web-app/features/billing/plans/ListPlanVersion.tsx @@ -0,0 +1,64 @@ +import { disableQuery } from '@connectrpc/connect-query' +import { ColumnDef, PaginationState, Row } from '@tanstack/react-table' +import { useMemo, useState } from 'react' + +import { StandardTable } from '@/components/table/StandardTable' +import { usePlanOverview } from '@/features/billing/plans/hooks/usePlan' +import { useQuery } from '@/lib/connectrpc' +import { ListPlanVersion } from '@/rpc/api/plans/v1/models_pb' +import { listPlanVersionById } from '@/rpc/api/plans/v1/plans-PlansService_connectquery' + +export const ListPlanVersionTab = () => { + const overview = usePlanOverview() + + const [pagination, setPagination] = useState({ + pageIndex: 0, + pageSize: 15, + }) + + const planVersions = useQuery( + listPlanVersionById, + overview + ? { + planId: overview.id, + pagination: { + limit: pagination.pageSize, + offset: pagination.pageIndex * pagination.pageSize, + }, + } + : disableQuery + ) + + const data = planVersions.data?.planVersions ?? [] + const count = Number(planVersions.data?.paginationMeta?.total ?? 0) + const isLoading = planVersions.isLoading + + const columns = useMemo[]>( + () => [ + { + header: 'Version', + accessorKey: 'version', + }, + + { + header: 'Status', + cell: ({ row }: { row: Row }) => + row.original.isDraft ? 'Draft' : 'Active', + }, + ], + + [] + ) + + return ( + + ) +} diff --git a/modules/web/web-app/features/billing/plans/PlanActions.tsx b/modules/web/web-app/features/billing/plans/PlanActions.tsx index 6a78c95c..3f5f61d9 100644 --- a/modules/web/web-app/features/billing/plans/PlanActions.tsx +++ b/modules/web/web-app/features/billing/plans/PlanActions.tsx @@ -1,4 +1,4 @@ -import { disableQuery, useMutation } from '@connectrpc/connect-query' +import { useMutation } from '@connectrpc/connect-query' import { Button, Modal } from '@md/ui' import { useQueryClient } from '@tanstack/react-query' import { useAtom, useSetAtom } from 'jotai' @@ -7,18 +7,19 @@ import { useNavigate } from 'react-router-dom' import ConfirmationModal from '@/components/ConfirmationModal' import { - addedComponentsAtom, - editedComponentsAtom, useIsDraftVersion, usePlanOverview, + usePlanWithVersion, +} from '@/features/billing/plans/hooks/usePlan' +import { + addedComponentsAtom, + editedComponentsAtom, } from '@/features/billing/plans/pricecomponents/utils' -import { useQuery } from '@/lib/connectrpc' import { copyVersionToDraft, discardDraftVersion, - getLastPublishedPlanVersion, - publishPlanVersion, listPlans, + publishPlanVersion, } from '@/rpc/api/plans/v1/plans-PlansService_connectquery' export const PlanActions = () => { @@ -34,15 +35,7 @@ export const PlanActions = () => { const isDraft = useIsDraftVersion() - const { data: lastPublishedVersion } = useQuery( - getLastPublishedPlanVersion, - overview?.planId - ? { - planId: overview.planId, - } - : disableQuery, - { enabled: isDraft } - ) + const planWithVersion = usePlanWithVersion() const navigate = useNavigate() @@ -70,16 +63,16 @@ export const PlanActions = () => { } const discardDraft = async () => { - if (!overview) return + if (!overview || !planWithVersion.plan || !planWithVersion.version) return setIsBusy(true) await discardDraftMutation.mutateAsync({ - planId: overview.planId, - planVersionId: overview.planVersionId, + planId: planWithVersion.plan.id, + planVersionId: planWithVersion.version.id, }) resetAtoms() - if (!lastPublishedVersion?.version) { + if (!overview?.activeVersion) { navigate('../') } @@ -95,11 +88,11 @@ export const PlanActions = () => { const publishPlan = async () => { setIsBusy(true) - if (!overview) return + if (!overview || !planWithVersion.plan || !planWithVersion.version) return await publishPlanMutation.mutateAsync({ - planId: overview.planId, - planVersionId: overview.planVersionId, + planId: planWithVersion.plan.id, + planVersionId: planWithVersion.version.id, }) setIsBusy(false) } @@ -111,10 +104,10 @@ export const PlanActions = () => { }) const copyToDraft = async () => { - if (!overview) return + if (!overview || !planWithVersion.plan || !planWithVersion.version) return await copyToDraftMutation.mutateAsync({ - planId: overview.planId, - planVersionId: overview.planVersionId, + planId: planWithVersion.plan.id, + planVersionId: planWithVersion.version.id, }) } diff --git a/modules/web/web-app/features/billing/plans/PlanBuilder.tsx b/modules/web/web-app/features/billing/plans/PlanBuilder.tsx index 6829ebc5..d2009518 100644 --- a/modules/web/web-app/features/billing/plans/PlanBuilder.tsx +++ b/modules/web/web-app/features/billing/plans/PlanBuilder.tsx @@ -10,15 +10,18 @@ import { toast } from 'sonner' import { Loading } from '@/components/Loading' import { PageSection } from '@/components/layouts/shared/PageSection' import { SimpleTable } from '@/components/table/SimpleTable' +import { ListPlanVersionTab } from '@/features/billing/plans/ListPlanVersion' import { PlanActions } from '@/features/billing/plans/PlanActions' import { PlanOverview } from '@/features/billing/plans/details/PlanDetails' -import { usePlan } from '@/features/billing/plans/hooks/usePlan' +import { + useIsDraftVersion, + usePlanOverview, + usePlanWithVersion, +} from '@/features/billing/plans/hooks/usePlan' import { PriceComponentSection } from '@/features/billing/plans/pricecomponents/PriceComponentSection' import { addedComponentsAtom, editedComponentsAtom, - useIsDraftVersion, - usePlanOverview, } from '@/features/billing/plans/pricecomponents/utils' import { PlanTrial } from '@/features/billing/plans/trial/PlanTrial' import { SubscriptionsTable } from '@/features/subscriptions' @@ -59,7 +62,7 @@ export const PlanBuilder: React.FC = ({ children }) => { Details Subscriptions Alerts - Versions + History @@ -71,7 +74,7 @@ export const PlanBuilder: React.FC = ({ children }) => { <>Alerts are not implemented yet - <>No UI yet + @@ -94,7 +97,7 @@ const SubscriptionsTab = () => { listSubscriptions, overview ? { - planId: overview.planId, + planId: overview.id, pagination: { perPage: pagination.pageSize, page: pagination.pageIndex, @@ -128,9 +131,9 @@ const SubscriptionsTab = () => { } const PlanBody = () => { - const { data: planData, isLoading } = usePlan() + const planData = usePlanWithVersion() - if (isLoading) { + if (planData.isLoading) { return ( <> @@ -138,12 +141,12 @@ const PlanBody = () => { ) } - if (!planData?.planDetails?.plan || !planData.planDetails.currentVersion) { + if (!planData?.plan || !planData.version) { return <>Failed to load plan } - const plan = planData.planDetails.plan - const current = planData.planDetails.currentVersion + const plan = planData.plan + const current = planData.version return ( <> @@ -163,7 +166,6 @@ const PlanBody = () => { config={current?.trialConfig} currentPlanId={plan.id} currentPlanVersionId={current.id} - currentPlanExternalId={plan.externalId} /> diff --git a/modules/web/web-app/features/billing/plans/PlansTable.tsx b/modules/web/web-app/features/billing/plans/PlansTable.tsx index 0ffdb719..87d9b96b 100644 --- a/modules/web/web-app/features/billing/plans/PlansTable.tsx +++ b/modules/web/web-app/features/billing/plans/PlansTable.tsx @@ -1,15 +1,17 @@ import { ColumnDef, PaginationState } from '@tanstack/react-table' -import { MoreVerticalIcon } from 'lucide-react' import { useMemo, useState } from 'react' import { useNavigate } from 'react-router-dom' +import { LocalId } from '@/components/LocalId' import { StandardTable } from '@/components/table/StandardTable' +import { displayPlanStatus, displayPlanType, printPlanStatus } from '@/features/billing/plans/utils' import { useQuery } from '@/lib/connectrpc' -import { Plan } from '@/rpc/api/plans/v1/models_pb' +import { PlanOverview } from '@/rpc/api/plans/v1/models_pb' import { listPlans } from '@/rpc/api/plans/v1/plans-PlansService_connectquery' import { ListPlansRequest_SortBy } from '@/rpc/api/plans/v1/plans_pb' import { useTypedParams } from '@/utils/params' + import type { FunctionComponent } from 'react' export const PlansTable: FunctionComponent<{ search: string | undefined }> = ({ search }) => { @@ -18,10 +20,10 @@ export const PlansTable: FunctionComponent<{ search: string | undefined }> = ({ pageSize: 20, }) - const { familyExternalId } = useTypedParams<{ familyExternalId: string }>() + const { familyLocalId } = useTypedParams<{ familyLocalId: string }>() const plansQuery = useQuery(listPlans, { - productFamilyExternalId: familyExternalId!, + productFamilyLocalId: familyLocalId!, pagination: { limit: pagination.pageSize, offset: pagination.pageIndex * pagination.pageSize, @@ -39,38 +41,70 @@ export const PlansTable: FunctionComponent<{ search: string | undefined }> = ({ const navigate = useNavigate() - const columns = useMemo[]>( + const columns = useMemo[]>( () => [ { header: 'Name', accessorKey: 'name', cell: ({ row }) => ( - navigate(row.original.externalId)} - > + {row.original.name} ), + enableSorting: false, + }, + { + header: 'Version', + cell: ({ row }) => ( + + {row.original.activeVersion && {row.original.activeVersion.version}} + + ), }, { - header: 'Active subscriptions', - accessorFn: () => '-', + header: 'Status', + cell: ({ row }) => ( + + {displayPlanStatus(row.original.planStatus)} + + ), }, { - header: 'Api Name', - accessorKey: 'externalId', + header: 'Type', + id: 'planType', + cell: ({ row }) => <>{displayPlanType(row.original.planType)}, }, + { header: 'Description', accessorKey: 'description', + enableSorting: false, + }, + { + header: 'Trial', + id: 'trial', + cell: ({ row }) => { + return ( + <> + {row.original.activeVersion?.trialDurationDays + ? `${row.original.activeVersion?.trialDurationDays} days` + : '-'} + + ) + }, }, + + { + header: 'Subscriptions', + accessorFn: c => c.subscriptionCount, + enableSorting: false, + }, + { - accessorKey: 'id', - header: '', - maxSize: 0.1, - cell: () => , + header: 'API Handle', + id: 'localId', + cell: ({ row }) => , }, ], [navigate] @@ -85,6 +119,7 @@ export const PlansTable: FunctionComponent<{ search: string | undefined }> = ({ setPagination={setPagination} totalCount={totalCount} isLoading={isLoading} + rowLink={row => `${row.original.localId}`} /> ) } diff --git a/modules/web/web-app/features/billing/plans/SubscribablePlanVersionSelect.tsx b/modules/web/web-app/features/billing/plans/SubscribablePlanVersionSelect.tsx index 6aa0999d..a7a54462 100644 --- a/modules/web/web-app/features/billing/plans/SubscribablePlanVersionSelect.tsx +++ b/modules/web/web-app/features/billing/plans/SubscribablePlanVersionSelect.tsx @@ -1,16 +1,17 @@ import { A, D, F, pipe } from '@mobily/ts-belt' import { + Select, SelectContent, SelectGroup, SelectItem, SelectLabel, - Select, SelectTrigger, } from '@ui/components' import { useQuery } from '@/lib/connectrpc' -import { ListSubscribablePlanVersion, PlanVersion } from '@/rpc/api/plans/v1/models_pb' -import { listSubscribablePlanVersion } from '@/rpc/api/plans/v1/plans-PlansService_connectquery' +import { PlanOverview, PlanStatus, PlanVersion } from '@/rpc/api/plans/v1/models_pb' +import { listPlans } from '@/rpc/api/plans/v1/plans-PlansService_connectquery' +import { ListPlansRequest_SortBy } from '@/rpc/api/plans/v1/plans_pb' interface Props { value?: PlanVersion['id'] @@ -18,15 +19,21 @@ interface Props { } export const SubscribablePlanVersionSelect = ({ value, onChange }: Props) => { - const getPlanQuery = useQuery(listSubscribablePlanVersion) + const plansQuery = useQuery(listPlans, { + sortBy: ListPlansRequest_SortBy.DATE_DESC, + filters: { + statuses: [PlanStatus.ACTIVE], + types: [], + }, + }) const plansByFamily = pipe( - getPlanQuery.data?.planVersions, - F.defaultTo([] as ListSubscribablePlanVersion[]), + plansQuery.data?.plans, + F.defaultTo([] as PlanOverview[]), A.groupBy(p => p.productFamilyName) ) - const selectedPlan = getPlanQuery.data?.planVersions.find(p => p.id === value)?.planName + const selectedPlan = plansQuery.data?.plans.find(p => p.id === value)?.name return ( diff --git a/modules/web/web-app/features/dashboard/sections/MrrSection.tsx b/modules/web/web-app/features/dashboard/sections/MrrSection.tsx index 7c554404..0430e1eb 100644 --- a/modules/web/web-app/features/dashboard/sections/MrrSection.tsx +++ b/modules/web/web-app/features/dashboard/sections/MrrSection.tsx @@ -46,14 +46,14 @@ export const MrrSection = () => { productFamily ? { sortBy: ListPlansRequest_SortBy.NAME_ASC, - productFamilyExternalId: productFamily, + productFamilyLocalId: productFamily, } : disableQuery ) React.useEffect(() => { - if (productFamilies.data?.productFamilies[0]?.externalId && productFamily !== ALL) { - setProductFamily(productFamilies.data.productFamilies[0].externalId) + if (productFamilies.data?.productFamilies[0]?.localId && productFamily !== ALL) { + setProductFamily(productFamilies.data.productFamilies[0].localId) } }, [productFamilies.data?.productFamilies, productFamily]) @@ -71,7 +71,7 @@ export const MrrSection = () => { {productFamilies.data?.productFamilies.map(pf => ( - + {pf.name} ))} @@ -84,7 +84,7 @@ export const MrrSection = () => { {plans.data?.plans.map(p => ( - + {p.name} ))} diff --git a/modules/web/web-app/features/productCatalog/addons/AddonCreatePanel.tsx b/modules/web/web-app/features/productCatalog/addons/AddonCreatePanel.tsx index 36c4e5aa..5078408f 100644 --- a/modules/web/web-app/features/productCatalog/addons/AddonCreatePanel.tsx +++ b/modules/web/web-app/features/productCatalog/addons/AddonCreatePanel.tsx @@ -14,7 +14,7 @@ export const AddonCreatePanel: FunctionComponent = () => { return <>Not implemented // const queryClient = useQueryClient() // const navigate = useNavigate() - // const { familyExternalId } = useTypedParams<{ familyExternalId: string }>() + // const { familyLocalId } = useTypedParams<{ familyLocalId: string }>() // const createAddonMut = useMutation(createAddOn, { // onSuccess: async () => { @@ -36,7 +36,7 @@ export const AddonCreatePanel: FunctionComponent = () => { // createAddonMut // .mutateAsync({ // name: a.name, - // // productFamilyExternalId: familyExternalId, + // // productFamilyLocalId: familyLocalId, // // fee TODO // }) // .then(() => void 0) diff --git a/modules/web/web-app/features/productCatalog/addons/AddonsPage.tsx b/modules/web/web-app/features/productCatalog/addons/AddonsPage.tsx index eb39614f..8914480c 100644 --- a/modules/web/web-app/features/productCatalog/addons/AddonsPage.tsx +++ b/modules/web/web-app/features/productCatalog/addons/AddonsPage.tsx @@ -2,7 +2,7 @@ // import { Flex } from '@ui/components/legacy' import { FunctionComponent } from 'react' -// import { ProductItemsTable } from '@/features/productCatalog/items/ProductItemsTable' +// import { ProductsTable } from '@/features/productCatalog/items/ProductsTable' // import { useQuery } from '@/lib/connectrpc' // import { listAddOns } from '@/rpc/api/addons/v1/addons-AddOnsService_connectquery' // // import { ListAddOnRequest_SortBy } from '@/rpc/api/addons/v1/addons_pb' @@ -41,7 +41,7 @@ export const AddonsPage: FunctionComponent = () => { // refetch={query.refetch} // setSearch={onSearch} // /> - // { const paginationState = useQueryRecordState({ pageIndex: 0, @@ -18,7 +16,7 @@ export const useCatalogPageProps = () => { const search = useDebounceValue(_search, 300) - const { familyExternalId } = useTypedParams<{ familyExternalId: string }>() + const { familyLocalId } = useTypedParams<{ familyLocalId: string }>() const paginationQuery = { limit: pagination.pageSize, @@ -26,9 +24,9 @@ export const useCatalogPageProps = () => { } return { - baseQuery: familyExternalId + baseQuery: familyLocalId ? { - productFamilyExternalId: familyExternalId, + productFamilyLocalId: familyLocalId, pagination: paginationQuery, search: search, } diff --git a/modules/web/web-app/features/productCatalog/items/ProductItemsHeader.tsx b/modules/web/web-app/features/productCatalog/items/ProductItemsHeader.tsx index 8871e76c..1313fd04 100644 --- a/modules/web/web-app/features/productCatalog/items/ProductItemsHeader.tsx +++ b/modules/web/web-app/features/productCatalog/items/ProductItemsHeader.tsx @@ -2,7 +2,7 @@ import { FunctionComponent } from 'react' import { CatalogHeader } from '@/features/productCatalog/generic/CatalogHeader' -interface ProductItemsHeaderProps { +interface ProductsHeaderProps { heading: string isLoading: boolean refetch: () => void @@ -10,6 +10,6 @@ interface ProductItemsHeaderProps { setSearch: (value: string | undefined) => void } -export const ProductItemsHeader: FunctionComponent = props => { +export const ProductsHeader: FunctionComponent = props => { return } diff --git a/modules/web/web-app/features/productCatalog/items/ProductItemsTable.tsx b/modules/web/web-app/features/productCatalog/items/ProductItemsTable.tsx index 2c08c0bc..5ddeebde 100644 --- a/modules/web/web-app/features/productCatalog/items/ProductItemsTable.tsx +++ b/modules/web/web-app/features/productCatalog/items/ProductItemsTable.tsx @@ -8,27 +8,23 @@ import { Product } from '@/rpc/api/products/v1/models_pb' import type { OnChangeFn } from '@tanstack/react-table' -type ProductItem = Product & { - isExpandable?: boolean | undefined -} - -interface ProductItemsTableProps { - data: ProductItem[] +interface ProductsTableProps { + data: Product[] pagination: PaginationState setPagination: OnChangeFn totalCount: number isLoading?: boolean } -export const ProductItemsTable: FC = ({ +export const ProductsTable: FC = ({ data, pagination, setPagination, totalCount, isLoading, }) => { - const columns = useMemo[]>( + const columns = useMemo[]>( () => [ - expandColumn as ColumnDef, + expandColumn as ColumnDef, { header: 'Name', accessorKey: 'name', @@ -98,7 +94,7 @@ export const ProductItemsTable: FC = ({
= ({ ) } -// const SubComponent = ({ item }: { item: ProductItem }) => { +// const SubComponent = ({ item }: { item: Product }) => { // const details = trpc.catalog.products.details.useQuery({ // id: item.id, // }) diff --git a/modules/web/web-app/features/productCatalog/metrics/ProductMetricsEditPanel.tsx b/modules/web/web-app/features/productCatalog/metrics/ProductMetricsEditPanel.tsx index a5af64fc..d777dd58 100644 --- a/modules/web/web-app/features/productCatalog/metrics/ProductMetricsEditPanel.tsx +++ b/modules/web/web-app/features/productCatalog/metrics/ProductMetricsEditPanel.tsx @@ -1,11 +1,11 @@ import { createConnectQueryKey, useMutation } from '@connectrpc/connect-query' import { spaces } from '@md/foundation' import { - ScrollArea, Button, Form, FormDescription, InputFormField, + ScrollArea, Separator, Sheet, SheetContent, @@ -52,7 +52,7 @@ export const ProductMetricsEditPanel = () => { await queryClient.invalidateQueries({ queryKey: createConnectQueryKey(listBillableMetrics) }) }, }) - const { familyExternalId } = useTypedParams<{ familyExternalId: string }>() + const { familyLocalId } = useTypedParams<{ familyLocalId: string }>() const navigate = useNavigate() @@ -144,7 +144,7 @@ export const ProductMetricsEditPanel = () => { : undefined), }, usageGroupKey: input.usageGroupKey ?? undefined, - familyExternalId, + familyLocalId, }) res.billableMetric?.id && toast.success('Metric created') diff --git a/modules/web/web-app/features/reports/charts/MrrReport.tsx b/modules/web/web-app/features/reports/charts/MrrReport.tsx index a665f42c..97ed3fc5 100644 --- a/modules/web/web-app/features/reports/charts/MrrReport.tsx +++ b/modules/web/web-app/features/reports/charts/MrrReport.tsx @@ -48,14 +48,14 @@ export const MrrReport = () => { productFamily ? { sortBy: ListPlansRequest_SortBy.NAME_ASC, - productFamilyExternalId: productFamily, + productFamilyLocalId: productFamily, } : disableQuery ) React.useEffect(() => { - if (productFamilies.data?.productFamilies[0]?.externalId && productFamily !== ALL) { - setProductFamily(productFamilies.data.productFamilies[0].externalId) + if (productFamilies.data?.productFamilies[0]?.localId && productFamily !== ALL) { + setProductFamily(productFamilies.data.productFamilies[0].localId) } }, [productFamilies.data?.productFamilies, productFamily]) @@ -83,7 +83,7 @@ export const MrrReport = () => { {productFamilies.data?.productFamilies.map(pf => ( - + {pf.name} ))} @@ -96,7 +96,7 @@ export const MrrReport = () => { {plans.data?.plans.map(p => ( - + {p.name} ))} diff --git a/modules/web/web-app/hooks/useProductFamily.tsx b/modules/web/web-app/hooks/useProductFamily.tsx index a9cdbcce..a37ac181 100644 --- a/modules/web/web-app/hooks/useProductFamily.tsx +++ b/modules/web/web-app/hooks/useProductFamily.tsx @@ -2,14 +2,14 @@ import { disableQuery } from '@connectrpc/connect-query' import { useQuery } from '@/lib/connectrpc' import { useTypedParams } from '@/lib/utils/params' -import { getProductFamilyByExternalId } from '@/rpc/api/productfamilies/v1/productfamilies-ProductFamiliesService_connectquery' +import { getProductFamilyByLocalId } from '@/rpc/api/productfamilies/v1/productfamilies-ProductFamiliesService_connectquery' export const useProductFamily = () => { - const { familyExternalId } = useTypedParams() + const { familyLocalId } = useTypedParams() const productFamily = useQuery( - getProductFamilyByExternalId, - familyExternalId ? { externalId: familyExternalId! } : disableQuery + getProductFamilyByLocalId, + familyLocalId ? { localId: familyLocalId! } : disableQuery ) return { diff --git a/modules/web/web-app/lib/mapping/feesFromGrpc.ts b/modules/web/web-app/lib/mapping/feesFromGrpc.ts index 23364860..84cb0b20 100644 --- a/modules/web/web-app/lib/mapping/feesFromGrpc.ts +++ b/modules/web/web-app/lib/mapping/feesFromGrpc.ts @@ -179,7 +179,8 @@ export const mapPriceComponent = (priceComponent: grpc.PriceComponent): api.Pric return { id: priceComponent.id, name: priceComponent.name, + localId: priceComponent.localId, fee: mapFeeType(priceComponent.fee.feeType), - productItemId: priceComponent.productItemId, + productId: priceComponent.productId, } } diff --git a/modules/web/web-app/lib/mapping/feesToGrpc.ts b/modules/web/web-app/lib/mapping/feesToGrpc.ts index cc1944c2..cf4f9576 100644 --- a/modules/web/web-app/lib/mapping/feesToGrpc.ts +++ b/modules/web/web-app/lib/mapping/feesToGrpc.ts @@ -182,13 +182,13 @@ export const mapFee = (feeType: api.FeeType): grpc.Fee => { return new grpc.Fee(fee) } -export const mapPriceComponent = ( - priceComponent: api.PriceComponent -): PlainMessage => { - return { - id: priceComponent.id, - name: priceComponent.name, - fee: mapFee(priceComponent.fee), - productItemId: priceComponent.productItemId, - } -} +// export const mapPriceComponent = ( +// priceComponent: api.PriceComponent +// ): PlainMessage => { +// return { +// id: priceComponent.id, +// name: priceComponent.name, +// fee: mapFee(priceComponent.fee), +// productId: priceComponent.productId, +// } +// } diff --git a/modules/web/web-app/lib/schemas/common.ts b/modules/web/web-app/lib/schemas/common.ts index a98efe5c..c4a23c2f 100644 --- a/modules/web/web-app/lib/schemas/common.ts +++ b/modules/web/web-app/lib/schemas/common.ts @@ -9,8 +9,8 @@ export const byIdSchema = z.object({ export const bySlugSchema = z.object({ slug: z.string(), }) -export const ByExternalIdSchema = z.object({ - externalId: z.string(), +export const ByLocalIdSchema = z.object({ + localId: z.string(), }) export const paginatedCursorSchema = z.object({ diff --git a/modules/web/web-app/lib/schemas/customers.ts b/modules/web/web-app/lib/schemas/customers.ts index cc4fda39..435f1d29 100644 --- a/modules/web/web-app/lib/schemas/customers.ts +++ b/modules/web/web-app/lib/schemas/customers.ts @@ -4,6 +4,6 @@ export const createCustomerSchema = z.object({ companyName: z.string().min(3), // wrapped to simplify form handling primaryEmail: z.string().optional(), - externalId: z.string().optional(), + alias: z.string().optional(), stripeCustomerId: z.string().optional(), }) diff --git a/modules/web/web-app/lib/schemas/plans.ts b/modules/web/web-app/lib/schemas/plans.ts index a9d1065e..faa42006 100644 --- a/modules/web/web-app/lib/schemas/plans.ts +++ b/modules/web/web-app/lib/schemas/plans.ts @@ -3,7 +3,7 @@ import { z } from 'zod' export const createPlanSchema = z.object({ planName: z.string().nonempty('Name is required').max(256), description: z.string().max(2048).optional(), - externalId: z + localId: z .string() .nonempty('API Name is required') .min(3) @@ -214,13 +214,14 @@ export type FeeType = z.infer export const PriceComponentSchema = z.object({ id: z.string(), name: z.string(), + localId: z.string(), fee: FeeTypeSchema, - productItemId: z.string().optional(), + productId: z.string().optional(), }) export type PriceComponent = z.infer export const byPlanVersionSchema = z.object({ - externalId: z.string(), + localId: z.string(), version: z.number().int().optional(), }) @@ -236,7 +237,7 @@ export const addPriceComponentSchema = z.object({ planVersionId: z.string(), name: z.string(), fee: FeeTypeSchema, - productItemId: z.string().optional(), + productId: z.string().optional(), }) export type AddPriceComponent = z.infer diff --git a/modules/web/web-app/lib/schemas/pricePoints.ts b/modules/web/web-app/lib/schemas/pricePoints.ts index 2a7eb9b3..f8813df9 100644 --- a/modules/web/web-app/lib/schemas/pricePoints.ts +++ b/modules/web/web-app/lib/schemas/pricePoints.ts @@ -44,7 +44,7 @@ export type PriceRampSchema = z.infer export const createPricePointSchema = z.object({ pricePointName: z.string().nonempty('Name is required').max(256), - planExternalId: z.string(), + planLocalId: z.string(), currency: z.string(), cycle: z.enum(['FOREVER', 'FIXED']), frequency: z.enum(['ANNUAL', 'MONTHLY', 'QUARTERLY', 'SEMI_ANNUAL', 'SEMI_MONTHLY']), @@ -55,5 +55,5 @@ export const createPricePointSchema = z.object({ }) export const listPricePointsSchema = z.object({ - planExternalId: z.string(), + planLocalId: z.string(), }) diff --git a/modules/web/web-app/lib/schemas/products.ts b/modules/web/web-app/lib/schemas/products.ts index aa8c6024..b17be94e 100644 --- a/modules/web/web-app/lib/schemas/products.ts +++ b/modules/web/web-app/lib/schemas/products.ts @@ -7,14 +7,14 @@ export const createProductSchema = z.object({ export const createProductFamily = z.object({ name: z.string().min(3), - externalId: z.string().min(3), + localId: z.string().min(3), description: z.string().optional(), }) -export const getByExternalId = z.object({ - externalId: z.string(), +export const getByLocalId = z.object({ + localId: z.string(), }) -export const listByPlanExternalId = z.object({ - planExternalId: z.string(), +export const listByPlanLocalId = z.object({ + planLocalId: z.string(), }) diff --git a/modules/web/web-app/lib/utils/params.ts b/modules/web/web-app/lib/utils/params.ts index 261f0ed8..ea974bbf 100644 --- a/modules/web/web-app/lib/utils/params.ts +++ b/modules/web/web-app/lib/utils/params.ts @@ -5,8 +5,8 @@ import { useParams } from 'react-router-dom' export const ParamsSlugs = { tenantSlug: ':tenantSlug', organizationSlug: ':organizationSlug', - familyExternalId: ':familyExternalId', - planExternalId: ':planExternalId', + familyLocalId: ':familyLocalId', + planLocalId: ':planLocalId', planVersion: ':planVersion', feeType: ':feeType', invoiceId: ':invoiceId', diff --git a/modules/web/web-app/pages/tenants/billing/index.tsx b/modules/web/web-app/pages/tenants/billing/index.tsx index 69f608d0..6d89f346 100644 --- a/modules/web/web-app/pages/tenants/billing/index.tsx +++ b/modules/web/web-app/pages/tenants/billing/index.tsx @@ -1,11 +1,11 @@ -import {createConnectQueryKey, useMutation} from '@connectrpc/connect-query' -import {Dot} from '@md/ui' -import {useQueryClient} from '@tanstack/react-query' -import {FunctionComponent} from 'react' -import {Navigate, Outlet} from 'react-router-dom' +import { createConnectQueryKey, useMutation } from '@connectrpc/connect-query' +import { Dot } from '@md/ui' +import { useQueryClient } from '@tanstack/react-query' +import { FunctionComponent } from 'react' +import { Navigate, Outlet } from 'react-router-dom' import SidebarMenu from '@/components/SidebarMenu' -import {TenantPageLayout} from '@/components/layouts' +import { TenantPageLayout } from '@/components/layouts' import ProductEmptyState from '@/features/productCatalog/ProductEmptyState' import { createProductFamily, @@ -13,7 +13,7 @@ import { } from '@/rpc/api/productfamilies/v1/productfamilies-ProductFamiliesService_connectquery' export const Billing: FunctionComponent = () => { - return + return } export const BillingOutlet: FunctionComponent = () => { @@ -33,7 +33,7 @@ export const BillingOutlet: FunctionComponent = () => { { label: ( - + <>Trials ), @@ -42,7 +42,7 @@ export const BillingOutlet: FunctionComponent = () => { { label: ( - + <>At risk ), @@ -99,7 +99,7 @@ export const BillingOutlet: FunctionComponent = () => { /> } > - + ) } @@ -109,12 +109,12 @@ export const FamilyCreationModalPage = () => { const createDefaultMutation = useMutation(createProductFamily, { onSuccess: async () => { - await queryClient.invalidateQueries({queryKey: createConnectQueryKey(listProductFamilies)}) + await queryClient.invalidateQueries({ queryKey: createConnectQueryKey(listProductFamilies) }) }, }) const createDefault = () => - createDefaultMutation.mutateAsync({name: 'Default', externalId: 'default'}) + createDefaultMutation.mutateAsync({ name: 'Default', localId: 'default' }) return ( diff --git a/modules/web/web-app/pages/tenants/catalog/index.tsx b/modules/web/web-app/pages/tenants/catalog/index.tsx index f2c86e4e..fc3f713c 100644 --- a/modules/web/web-app/pages/tenants/catalog/index.tsx +++ b/modules/web/web-app/pages/tenants/catalog/index.tsx @@ -13,7 +13,7 @@ export const Catalog: FunctionComponent = () => { if (families.isLoading) return if (!families.data?.productFamilies?.length) return - return + return } export const CatalogOutlet: FunctionComponent = () => { diff --git a/modules/web/web-app/pages/tenants/catalog/productItems.tsx b/modules/web/web-app/pages/tenants/catalog/productItems.tsx index 303d2559..3278b370 100644 --- a/modules/web/web-app/pages/tenants/catalog/productItems.tsx +++ b/modules/web/web-app/pages/tenants/catalog/productItems.tsx @@ -5,27 +5,24 @@ import { Fragment, FunctionComponent, useState } from 'react' import { toast } from 'sonner' import { ProductEditPanel } from '@/features/productCatalog/items/ProductEditPanel' -import { ProductItemsHeader } from '@/features/productCatalog/items/ProductItemsHeader' -import { ProductItemsTable } from '@/features/productCatalog/items/ProductItemsTable' +import { ProductsHeader } from '@/features/productCatalog/items/ProductItemsHeader' +import { ProductsTable } from '@/features/productCatalog/items/ProductItemsTable' import { useQuery } from '@/lib/connectrpc' import { listProducts } from '@/rpc/api/products/v1/products-ProductsService_connectquery' import { useTypedParams } from '@/utils/params' import type { PaginationState } from '@tanstack/react-table' -export const ProductItems: FunctionComponent = () => { +export const Products: FunctionComponent = () => { const [editPanelVisible, setEditPanelVisible] = useState(false) const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 20, }) - const { familyExternalId } = useTypedParams<{ familyExternalId: string }>() + const { familyLocalId } = useTypedParams<{ familyLocalId: string }>() - const productsQuery = useQuery( - listProducts, - familyExternalId ? { familyExternalId } : disableQuery - ) + const productsQuery = useQuery(listProducts, familyLocalId ? { familyLocalId } : disableQuery) const data = productsQuery.data?.products ?? [] const isLoading = productsQuery.isLoading const totalCount = productsQuery.data?.products?.length ?? 0 // no server pagination ? TODO @@ -37,7 +34,7 @@ export const ProductItems: FunctionComponent = () => { return ( - { toast.error('Search not implemented') }} /> - { const navigate = useNavigate() const [pagination, setPagination] = useState({ @@ -19,11 +18,11 @@ export const ProductMetrics: FunctionComponent = () => { pageSize: 20, }) - const { familyExternalId } = useTypedParams<{ familyExternalId: string }>() + const { familyLocalId } = useTypedParams<{ familyLocalId: string }>() // TODO pagination (manual ?) const productMetricsQuery = useQuery( listBillableMetrics, - familyExternalId ? { familyExternalId } : disableQuery + familyLocalId ? { familyLocalId } : disableQuery ) const totalCount = productMetricsQuery?.data?.billableMetrics?.length ?? 0 diff --git a/modules/web/web-app/router/tenant/catalog.tsx b/modules/web/web-app/router/tenant/catalog.tsx index b702b1b9..464b2f21 100644 --- a/modules/web/web-app/router/tenant/catalog.tsx +++ b/modules/web/web-app/router/tenant/catalog.tsx @@ -9,7 +9,7 @@ import { PlanEdit } from '@/pages/tenants/billing/plans/edit' import { PlanOnboardingComponent } from '@/pages/tenants/billing/plans/onboarding' import { Catalog, CatalogOutlet } from '@/pages/tenants/catalog' import { CreateBillableMetric } from '@/pages/tenants/catalog/createBillableMetric' -import { ProductItems } from '@/pages/tenants/catalog/productItems' +import { Products } from '@/pages/tenants/catalog/productItems' import { ProductMetrics } from '@/pages/tenants/catalog/productMetrics' export const productCatalogRoutes: RouteObject = { @@ -20,7 +20,7 @@ export const productCatalogRoutes: RouteObject = { element: , }, { - path: ':familyExternalId', + path: ':familyLocalId', element: , children: [ { @@ -29,7 +29,7 @@ export const productCatalogRoutes: RouteObject = { }, { path: 'items', - element: , + element: , }, { path: 'metrics', @@ -49,7 +49,7 @@ export const productCatalogRoutes: RouteObject = { index: true, }, { - path: ':planExternalId', + path: ':planLocalId/:planVersion?', element: , children: [ { diff --git a/modules/web/web-app/styles/main.scss b/modules/web/web-app/styles/main.scss index da84c555..d10a624b 100644 --- a/modules/web/web-app/styles/main.scss +++ b/modules/web/web-app/styles/main.scss @@ -24,7 +24,7 @@ --destructive-foreground: 0 0% 98%; --success: 142.1 76.2% 36.3%; - --success-foreground: 240 10% 3.9%; + --success-foreground: 0 0% 97%; --warning: 38 92% 50%; --warning-foreground: 240 10% 3.9%;