celerp 2.3.6__tar.gz → 2.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {celerp-2.3.6 → celerp-2.4.0}/PKG-INFO +1 -1
- {celerp-2.3.6 → celerp-2.4.0}/celerp/config.py +119 -0
- celerp-2.4.0/celerp/config_store.py +203 -0
- celerp-2.4.0/celerp/entitlement_preflight.py +168 -0
- celerp-2.4.0/celerp/gateway/bootstrap.py +101 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/gateway/client.py +35 -25
- celerp-2.4.0/celerp/gateway/fixtures/entitlement_flags_fixture.json +43 -0
- celerp-2.4.0/celerp/gateway/state.py +893 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/main.py +14 -25
- {celerp-2.3.6 → celerp-2.4.0}/celerp/middleware.py +51 -51
- celerp-2.4.0/celerp/migrations/versions/f3a4b5c6d7e8_purge_session_registry.py +41 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/modules/api.py +8 -2
- {celerp-2.3.6 → celerp-2.4.0}/celerp/output/doc_print.py +3 -1
- celerp-2.4.0/celerp/routers/attachments.py +42 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/routers/auth.py +105 -50
- {celerp-2.3.6 → celerp-2.4.0}/celerp/routers/companies.py +90 -27
- {celerp-2.3.6 → celerp-2.4.0}/celerp/routers/events.py +38 -49
- {celerp-2.3.6 → celerp-2.4.0}/celerp/routers/health.py +179 -23
- {celerp-2.3.6 → celerp-2.4.0}/celerp/services/attachments.py +37 -9
- celerp-2.4.0/celerp/services/auth.py +339 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/services/session_tracker.py +122 -96
- {celerp-2.3.6 → celerp-2.4.0}/celerp/session_gate.py +10 -3
- {celerp-2.3.6 → celerp-2.4.0}/celerp.egg-info/PKG-INFO +1 -1
- {celerp-2.3.6 → celerp-2.4.0}/celerp.egg-info/SOURCES.txt +8 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp.egg-info/scm_file_list.json +44 -0
- celerp-2.4.0/celerp.egg-info/scm_version.json +8 -0
- {celerp-2.3.6 → celerp-2.4.0}/conftest.py +24 -5
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-accounting/tests/test_accounting_import_idempotency.py +2 -2
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-ai/celerp_ai/routes.py +13 -5
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-ai/celerp_ai/ui_routes.py +63 -38
- celerp-2.4.0/default_modules/celerp-ai/tests/test_commercial_routing.py +219 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-manufacturing/tests/test_manufacturing_import_idempotency.py +2 -2
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/first_party.lock.json +3 -3
- {celerp-2.3.6 → celerp-2.4.0}/test_helpers.py +16 -0
- {celerp-2.3.6 → celerp-2.4.0}/ui/api_client.py +45 -7
- {celerp-2.3.6 → celerp-2.4.0}/ui/app.py +64 -28
- celerp-2.4.0/ui/components/cloud_gate.py +236 -0
- {celerp-2.3.6 → celerp-2.4.0}/ui/config.py +40 -8
- {celerp-2.3.6 → celerp-2.4.0}/ui/locales/am.json +42 -1
- {celerp-2.3.6 → celerp-2.4.0}/ui/locales/ar.json +42 -1
- {celerp-2.3.6 → celerp-2.4.0}/ui/locales/de.json +42 -1
- {celerp-2.3.6 → celerp-2.4.0}/ui/locales/en.json +42 -1
- {celerp-2.3.6 → celerp-2.4.0}/ui/locales/es.json +42 -1
- {celerp-2.3.6 → celerp-2.4.0}/ui/locales/fr.json +42 -1
- {celerp-2.3.6 → celerp-2.4.0}/ui/locales/id.json +42 -1
- {celerp-2.3.6 → celerp-2.4.0}/ui/locales/it.json +42 -1
- {celerp-2.3.6 → celerp-2.4.0}/ui/locales/ja.json +42 -1
- {celerp-2.3.6 → celerp-2.4.0}/ui/locales/pt.json +42 -1
- {celerp-2.3.6 → celerp-2.4.0}/ui/locales/th.json +42 -1
- {celerp-2.3.6 → celerp-2.4.0}/ui/locales/vi.json +42 -1
- {celerp-2.3.6 → celerp-2.4.0}/ui/routes/account.py +18 -8
- {celerp-2.3.6 → celerp-2.4.0}/ui/routes/auth.py +58 -22
- celerp-2.4.0/ui/routes/commercial.py +144 -0
- {celerp-2.3.6 → celerp-2.4.0}/ui/routes/contacts.py +2 -2
- {celerp-2.3.6 → celerp-2.4.0}/ui/routes/dashboard.py +1 -1
- {celerp-2.3.6 → celerp-2.4.0}/ui/routes/documents.py +3 -3
- {celerp-2.3.6 → celerp-2.4.0}/ui/routes/inventory.py +1 -1
- {celerp-2.3.6 → celerp-2.4.0}/ui/routes/modules_page.py +3 -4
- {celerp-2.3.6 → celerp-2.4.0}/ui/routes/settings.py +69 -32
- celerp-2.4.0/ui/routes/settings_cloud.py +1535 -0
- {celerp-2.3.6 → celerp-2.4.0}/ui/routes/settings_connectors.py +6 -7
- {celerp-2.3.6 → celerp-2.4.0}/ui/routes/settings_payments.py +9 -6
- {celerp-2.3.6 → celerp-2.4.0}/ui/routes/setup.py +26 -17
- {celerp-2.3.6 → celerp-2.4.0}/ui/routes/subscriptions.py +1 -1
- {celerp-2.3.6 → celerp-2.4.0}/ui/static/app.css +1 -0
- celerp-2.3.6/celerp/gateway/state.py +0 -178
- celerp-2.3.6/celerp/services/auth.py +0 -174
- celerp-2.3.6/celerp.egg-info/scm_version.json +0 -8
- celerp-2.3.6/ui/components/cloud_gate.py +0 -127
- celerp-2.3.6/ui/routes/settings_cloud.py +0 -751
- {celerp-2.3.6 → celerp-2.4.0}/CLA.md +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/CONTRIBUTING.md +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/LICENSE +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/LICENSING.md +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/MANIFEST.in +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/NOTICE +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/README.md +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/alembic.ini +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/__init__.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/__main__.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/ai/__init__.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/ai/batch.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/ai/cleanup.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/ai/commands.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/ai/conversations.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/ai/files.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/ai/intent.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/ai/llm.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/ai/memory.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/ai/models.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/ai/page_count.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/ai/quota.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/ai/service.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/ai/tools.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/alembic.ini +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/alembic_config.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/capacity.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/cli.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/compat.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/compute/__init__.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/compute/aggregations.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/compute/valuation.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/connectors/__init__.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/connectors/base.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/connectors/daily_scheduler.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/connectors/http.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/connectors/images.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/connectors/quickbooks.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/connectors/registry.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/connectors/relay_token.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/connectors/shopify.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/connectors/sync_runner.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/connectors/upsert.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/connectors/util.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/connectors/webhooks.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/connectors/woocommerce.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/connectors/xero.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/constants.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/db.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/embedded_pg.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/events/__init__.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/events/engine.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/events/schemas.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/events/types.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/gateway/__init__.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/importers/__init__.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/importers/importer.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/importers/schema.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/inventory_codes.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/migrations/README.md +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/migrations/_auto_stamp.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/migrations/_data_reconcile.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/migrations/_json_compat.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/migrations/env.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/migrations/script.py.mako +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/migrations/versions/a0b1c2d3e4f5_seed_landed_cost_clearing_accounts.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/migrations/versions/a1b2c3d4e5f6_add_doc_share_tokens.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/migrations/versions/b1c2d3e4f5a6_add_supporter_badges.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/migrations/versions/b2c3d4e5f6a7_add_import_batches.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/migrations/versions/b3c4d5e6f7a8_expand_role_permissions_to_role_grants.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/migrations/versions/bc0d1e2f3a4b_barcode_unique_index.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/migrations/versions/c1d2e3f4a5b6_add_marketplace.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/migrations/versions/c2d3e4f5a6b7_add_is_sync_to_shopify.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/migrations/versions/c4d5e6f7a8b9_account_cash_flow_category.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/migrations/versions/c9d8e7f6a5b4_seed_shrinkage_writeoff_account.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/migrations/versions/d0e1f2a3b4c5_add_share_token_expiry.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/migrations/versions/d1e2f3a4b5c6_rfid_epc_unique_index.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/migrations/versions/d2e3f4a5b6c7_add_password_reset_token.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/migrations/versions/d5e6f7a8b9c0_rename_fx_rounding_to_fx_difference.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/migrations/versions/e3f4a5b6c7d8_add_company_is_active.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/migrations/versions/e4f5a6b7c8d9_backfill_connector_idempotency_key.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/migrations/versions/e7c9a1b3d5f2_work_center_default_columns.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/migrations/versions/f1a2b3c4d5e6_move_accounts_marketplace_to_modules.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/migrations/versions/f6a7b8c9d0e1_seed_fx_rounding_account.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/migrations/versions/f8a9b0c1d2e3_work_center_default_backfill.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/migrations/versions/fd5de461e14e_initial_schema.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/migrations/versions/g1h2i3j4k5l6_add_consignment_flag.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/migrations/versions/h2i3j4k5l6m7_add_bank_accounts_and_reconciliation.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/migrations/versions/j4k5l6m7n8o9_labels_db_persistence.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/migrations/versions/k5l6m7n8o9p0_add_reconciliation_v2.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/migrations/versions/l6m7n8o9p0q1_add_sync_runs_table.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/migrations/versions/m7n8o9p0q1r2_add_connector_configs_and_outbound_queue.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/migrations/versions/n8o9p0q1r2s3_backfill_payment_date.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/migrations/versions/o9p0q1r2s3t4_backfill_item_price_fields.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/migrations/versions/p1q2r3s4t5u6_add_session_registry_and_user_auth_state.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/migrations/versions/q2r3s4t5u6v7_add_system_runtime_state.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/migrations/versions/r3s4t5u6v7w8_add_projections_company_entity_type_index.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/migrations/versions/s4t5u6v7w8x9_drop_user_company_id_role.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/migrations/versions/t5u6v7w8x9y0_seed_inventory_sub_accounts.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/migrations/versions/u6v7w8x9y0z1_backfill_item_prices_from_attributes.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/migrations/versions/v7w8x9y0z1a2_backfill_purchase_unit_defaults.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/migrations/versions/w8x9y0z1a2b3_backfill_category_unit_defaults.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/migrations/versions/x7y8z9a0b1c2_add_projection_created_at.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/migrations/versions/y8z9a0b1c2d3_per_company_ledger_idempotency.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/migrations/versions/z9a0b1c2d3e4_work_centers.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/models/__init__.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/models/accounting.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/models/ai.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/models/auth.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/models/base.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/models/company.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/models/connector_config.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/models/import_batch.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/models/ledger.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/models/marketplace.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/models/notification.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/models/projections.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/models/share.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/models/supporter.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/models/sync_run.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/modules/__init__.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/modules/audit.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/modules/demotion.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/modules/importer.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/modules/license.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/modules/loader.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/modules/meta.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/modules/migrations_runner.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/modules/references.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/modules/registry.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/modules/slots.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/notifications/__init__.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/notifications/service.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/notifications/sse.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/output/__init__.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/output/branding.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/output/pdf.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/output/share_render.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/projections/__init__.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/projections/engine.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/projections/handlers/__init__.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/projections/handlers/marketplace.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/projections/handlers/scanning.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/projections/handlers/shopify.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/projections/handlers/system.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/routers/__init__.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/routers/debug.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/routers/doctor.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/routers/ledger.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/routers/notifications.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/routers/search.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/routers/stars.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/routers/system.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/schemas/__init__.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/services/__init__.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/services/activity_redaction.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/services/auto_je.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/services/backup.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/services/backup_export.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/services/backup_import.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/services/backup_repo.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/services/backup_scheduler.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/services/backup_state.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/services/cogs_backfill.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/services/cost_visibility.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/services/demo.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/services/dev_release_guard.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/services/document_lines.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/services/email.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/services/field_schema.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/services/fulfill.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/services/holdings.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/services/je_keys.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/services/landed_cost.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/services/line_measures.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/services/list_behavior.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/services/money.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/services/payments.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/services/permissions.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/services/pick.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/services/pricing.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/services/relay_share.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/services/reorder.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/services/runtime_state.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/services/shipping.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/services/star_cta.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/services/status_doc_backfill.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/services/supporter_badge.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/services/system_health.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/services/units.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp/tax_regimes.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp.egg-info/dependency_links.txt +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp.egg-info/entry_points.txt +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp.egg-info/requires.txt +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/celerp.egg-info/top_level.txt +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/conftest_support.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/__init__.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-accounting/LICENSE +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-accounting/__init__.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-accounting/celerp_accounting/__init__.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-accounting/celerp_accounting/api_setup.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-accounting/celerp_accounting/csv_parser.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-accounting/celerp_accounting/matcher.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-accounting/celerp_accounting/models.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-accounting/celerp_accounting/projections.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-accounting/celerp_accounting/routes.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-accounting/celerp_accounting/search.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-accounting/celerp_accounting/ui_routes.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-accounting/tests/report_goldens/balance-sheet.html +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-accounting/tests/report_goldens/cash-flow.html +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-accounting/tests/report_goldens/extended-journal-csv.csv +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-accounting/tests/report_goldens/extended-journal-print.html +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-accounting/tests/report_goldens/extended-journal.html +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-accounting/tests/report_goldens/general-ledger.html +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-accounting/tests/report_goldens/journal-csv.csv +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-accounting/tests/report_goldens/journal-print.html +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-accounting/tests/report_goldens/journal.html +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-accounting/tests/report_goldens/ledger.html +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-accounting/tests/report_goldens/pnl.html +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-accounting/tests/report_goldens/statement.html +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-accounting/tests/report_goldens/trial-balance.html +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-accounting/tests/test_accounting_banking.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-accounting/tests/test_accounting_banking_routers.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-accounting/tests/test_accounting_gaps.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-accounting/tests/test_cash_flow.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-accounting/tests/test_financial_reports_ui.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-accounting/tests/test_journal_gl_soa.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-accounting/tests/test_journal_gl_soa_ui.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-accounting/tests/test_reconciliation_v2.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-accounting/tests/test_reports_end_to_end.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-accounting/tests/test_route_permissions.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-admin/__init__.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-admin/celerp_admin/__init__.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-admin/celerp_admin/routes.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-admin/celerp_admin/setup.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-ai/__init__.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-ai/celerp_ai/__init__.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-ai/celerp_ai/setup.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-ai/tests/conftest.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-ai/tests/test_batch.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-ai/tests/test_cleanup.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-ai/tests/test_conversations.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-ai/tests/test_coverage_gaps.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-ai/tests/test_intent.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-ai/tests/test_llm.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-ai/tests/test_memory.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-ai/tests/test_models.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-ai/tests/test_page_count.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-ai/tests/test_quota.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-ai/tests/test_router.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-ai/tests/test_routes_coverage.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-ai/tests/test_service.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-ai/tests/test_tools.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-backup/__init__.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-backup/celerp_backup/__init__.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-backup/celerp_backup/routes.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-backup/celerp_backup/setup.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-connectors/__init__.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-connectors/celerp_connectors/__init__.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-connectors/celerp_connectors/models.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-connectors/celerp_connectors/routes.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-connectors/tests/__init__.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-connectors/tests/conftest.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-connectors/tests/test_compliance.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-connectors/tests/test_connector_correctness.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-connectors/tests/test_connectors.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-connectors/tests/test_credentials_routes.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-connectors/tests/test_images.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-connectors/tests/test_outbound.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-connectors/tests/test_quickbooks.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-connectors/tests/test_rate_limiting.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-connectors/tests/test_shopify.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-connectors/tests/test_wiring.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-connectors/tests/test_woocommerce.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-connectors/tests/test_woocommerce_webhooks.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-connectors/tests/test_xero.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-contacts/LICENSE +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-contacts/__init__.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-contacts/celerp_contacts/__init__.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-contacts/celerp_contacts/migrations.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-contacts/celerp_contacts/projections.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-contacts/celerp_contacts/routes.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-contacts/celerp_contacts/search.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-contacts/celerp_contacts/services.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-contacts/celerp_contacts/ui_routes.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-dashboard/LICENSE +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-dashboard/__init__.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-dashboard/celerp_dashboard/__init__.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-dashboard/celerp_dashboard/routes.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-dashboard/celerp_dashboard/setup.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-docs/LICENSE +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-docs/__init__.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-docs/celerp_docs/__init__.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-docs/celerp_docs/api_setup.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-docs/celerp_docs/doc_constants.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-docs/celerp_docs/doc_email.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-docs/celerp_docs/doc_projections.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-docs/celerp_docs/doc_service.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-docs/celerp_docs/models_share.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-docs/celerp_docs/routes.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-docs/celerp_docs/routes_payments.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-docs/celerp_docs/routes_share.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-docs/celerp_docs/search.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-docs/celerp_docs/sequences.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-docs/celerp_docs/taxes.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-docs/celerp_docs/ui_routes.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-docs/tests/conftest.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_audits.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_bulk_payment_alloc_lock.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_consolidate_lots.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_cross_lot_settlement.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_freight_je.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_freight_lines.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_fulfill_no_split.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_fulfillment_activity.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_inbound_je_lifecycle.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_inventory_account_reconciliation.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_landed_receive.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_line_delete_persist.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_line_identifier.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_line_page_patch_truncate.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_list_concurrency.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_list_outcome_concurrency.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_list_page_no_full_state.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_list_quantity_integrity.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_list_query_pushdown.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_list_sell_by_and_reference.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_memo_close.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_memo_close_projection.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_memo_close_review_fixes.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_memo_lifecycle_concurrency.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_memo_line_labels.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_memo_unfulfilled_filter.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_payment_void_activity.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_receive_sku_batch.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_send_visibility.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_service_lines.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_sold_label_ordering.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_void_guards.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_writeoffs.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-inventory/LICENSE +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-inventory/__init__.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-inventory/celerp_inventory/__init__.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-inventory/celerp_inventory/models_import_batch.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-inventory/celerp_inventory/projections.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-inventory/celerp_inventory/routes.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-inventory/celerp_inventory/routes_attachments.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-inventory/celerp_inventory/routes_scanning.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-inventory/celerp_inventory/search.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-inventory/celerp_inventory/services.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-inventory/celerp_inventory/ui_routes.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-inventory/tests/test_item_recipe_projection.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-inventory/tests/test_item_workflow_projection.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-inventory/tests/test_landed_cost_projection.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-labels/LICENSE +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-labels/__init__.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-labels/celerp_labels/__init__.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-labels/celerp_labels/locales/am.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-labels/celerp_labels/locales/ar.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-labels/celerp_labels/locales/de.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-labels/celerp_labels/locales/en.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-labels/celerp_labels/locales/es.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-labels/celerp_labels/locales/fr.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-labels/celerp_labels/locales/id.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-labels/celerp_labels/locales/it.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-labels/celerp_labels/locales/ja.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-labels/celerp_labels/locales/pt.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-labels/celerp_labels/locales/th.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-labels/celerp_labels/locales/vi.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-labels/celerp_labels/models.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-labels/celerp_labels/presets.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-labels/celerp_labels/routes.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-labels/celerp_labels/service.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-labels/celerp_labels/ui_routes.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-labels/requirements.txt +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-labels/tests/test_labels.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-labels/tests/test_labels_module.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-manufacturing/LICENSE +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-manufacturing/__init__.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-manufacturing/celerp_manufacturing/__init__.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-manufacturing/celerp_manufacturing/costing.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-manufacturing/celerp_manufacturing/expansion.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-manufacturing/celerp_manufacturing/labor.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-manufacturing/celerp_manufacturing/migrations.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-manufacturing/celerp_manufacturing/projection_handler.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-manufacturing/celerp_manufacturing/routes.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-manufacturing/celerp_manufacturing/search.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-manufacturing/celerp_manufacturing/ui_routes.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-manufacturing/requirements.txt +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-manufacturing/tests/test_auto_complete_on_finalize.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-manufacturing/tests/test_bom_removed.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-manufacturing/tests/test_build_api.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-manufacturing/tests/test_completion_lock_order.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-manufacturing/tests/test_item_recipe_api.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-manufacturing/tests/test_item_workflow_api.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-manufacturing/tests/test_labor_providers.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-manufacturing/tests/test_manufacture_from_doc.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-manufacturing/tests/test_manufacturing.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-manufacturing/tests/test_manufacturing_module.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-manufacturing/tests/test_mfg_events.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-manufacturing/tests/test_phase2_manufacturing_validations.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-manufacturing/tests/test_production_order.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-manufacturing/tests/test_recipe_costing.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-manufacturing/tests/test_recipe_expansion.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-manufacturing/tests/test_recost_api.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-manufacturing/tests/test_run_records.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-manufacturing/tests/test_to_make_allocation.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-manufacturing/tests/test_where_used.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-manufacturing/tests/test_work_centers_and_settings.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-manufacturing/tests/test_workflow_time.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-reports/LICENSE +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-reports/__init__.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-reports/celerp_reports/__init__.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-reports/celerp_reports/api_setup.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-reports/celerp_reports/routes.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-reports/celerp_reports/ui_routes.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-reports/tests/test_reports_full.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-reports/tests/test_reports_unit.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-subscriptions/LICENSE +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-subscriptions/__init__.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-subscriptions/celerp_subscriptions/__init__.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-subscriptions/celerp_subscriptions/routes.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-subscriptions/celerp_subscriptions/search.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-subscriptions/celerp_subscriptions/ui_routes.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-subscriptions/tests/test_subscriptions.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-subscriptions/tests/test_subscriptions_ui.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/LICENSE +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/__init__.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/__init__.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/accessory_fashion.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/activewear.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/art_photography.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/audio_equipment.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/bag_handbag.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/banknote.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/beauty_tool.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/bed_bedroom.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/beer.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/beverage_nonalc.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/body_part_auto.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/book.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/bottoms.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/brake_suspension.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/bullion_coin.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/camera.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/colored_stone.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/comic.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/commercial_space.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/component_part.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/confectionery.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/consulting_service.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/decorative_art.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/diamond.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/drawing.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/dress_jumpsuit.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/electrical_auto.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/electrical_component.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/emerald.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/engine_part.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/fastener.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/fertilizer_chemical.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/film_video.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/fine_writing_instrument.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/fluid_lubricant.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/footwear.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/fragrance.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/fresh_food.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/fresh_produce.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/frozen_food.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/gaming_console.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/gold_bullion.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/grain_cereal.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/haircare.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/hand_tool.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/ingredient_bulk.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/interior_part.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/jewelry.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/kitchen_dining.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/laptop.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/lighting.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/livestock_feed.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/makeup.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/measuring_instrument.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/medal_token.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/mineral_specimen.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/mobile_phone.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/music_cd.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/music_vinyl.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/nail.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/numismatic_coin.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/outdoor_furniture.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/outerwear.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/packaged_food.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/painting.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/parking_bay.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/pearl.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/personal_care.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/pipe_plumbing.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/platinum_bullion.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/power_tool.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/print_edition.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/residential_unit.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/rough_gemstone.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/ruby.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/saas_plan.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/safety_ppe.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/sapphire.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/sculpture.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/seating.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/seeds.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/silver_bullion.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/skincare.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/soft_furnishing.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/software_addon.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/software_license.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/spirit.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/storage_furniture.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/swimwear.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/table_desk.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/tablet.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/tire_wheel.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/tops.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/trading_card.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/tv_display.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/video_game.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/watch.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/watch_strap.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/wine.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/presets/agricultural.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/presets/artwork.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/presets/automotive.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/presets/blank.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/presets/books_media.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/presets/coins_precious_metals.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/presets/consulting.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/presets/cosmetics.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/presets/electronics.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/presets/fashion.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/presets/food_beverage.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/presets/furniture.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/presets/gemstones.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/presets/hardware.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/presets/property_rental.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/presets/saas.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/presets/watches.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/presets/wine_spirits.json +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/routes.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/ui_routes.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/tests/test_verticals.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/default_modules/celerp-verticals/tests/test_verticals_routers.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/legal/OFFICIAL-COMPONENTS.md +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/legal/TRADEMARK.md +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/packaging/winget/Celerp.Celerp.installer.yaml +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/packaging/winget/Celerp.Celerp.locale.en-US.yaml +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/packaging/winget/Celerp.Celerp.yaml +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/packaging/winget/README.md +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/pyproject.toml +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/setup.cfg +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/ui/__init__.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/ui/components/__init__.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/ui/components/activity.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/ui/components/attrs.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/ui/components/backup.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/ui/components/currency.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/ui/components/files.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/ui/components/journal.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/ui/components/notes.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/ui/components/phone.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/ui/components/report_kit.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/ui/components/shell.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/ui/components/table.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/ui/i18n.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/ui/marketplace_catalog.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/ui/routes/__init__.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/ui/routes/accounting.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/ui/routes/accounting_import.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/ui/routes/csv_import.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/ui/routes/docs_import.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/ui/routes/events.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/ui/routes/financial_reports.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/ui/routes/lists_import.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/ui/routes/manufacturing.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/ui/routes/notifications.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/ui/routes/reconciliation.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/ui/routes/reports.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/ui/routes/search.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/ui/routes/settings_accounting.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/ui/routes/settings_contacts.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/ui/routes/settings_general.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/ui/routes/settings_import.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/ui/routes/settings_inventory.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/ui/routes/settings_manufacturing.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/ui/routes/settings_purchasing.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/ui/routes/settings_sales.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/ui/routes/stars.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/ui/routes/subscriptions_import.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/ui/security.py +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/ui/static/htmx.min.js +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/ui/static/icon.png +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/ui/static/logo.png +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/ui/static/vendor/intl-tel-input/img/flags.webp +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/ui/static/vendor/intl-tel-input/img/flags@2x.webp +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/ui/static/vendor/intl-tel-input/intlTelInput.min.css +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/ui/static/vendor/intl-tel-input/intlTelInput.min.js +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/ui/static/vendor/intl-tel-input/intlTelInputWithUtils.min.js +0 -0
- {celerp-2.3.6 → celerp-2.4.0}/ui/static/vendor/intl-tel-input/utils.js +0 -0
|
@@ -42,6 +42,26 @@ class Settings(BaseSettings):
|
|
|
42
42
|
# No `[gateway_token]` means no gateway connection, no product telemetry, and
|
|
43
43
|
# no cloud dependency, except a startup subscription check.
|
|
44
44
|
gateway_token: str = ""
|
|
45
|
+
# Reusable partner deployment credential. A one-time registration input sent
|
|
46
|
+
# only on the first relay `hello` of a partner-provisioned install, distinct
|
|
47
|
+
# from the live-session gateway_token: it associates the instance with the
|
|
48
|
+
# partner and is removed from bootstrap state once the relay accepts it.
|
|
49
|
+
# Env CELERP_DEPLOYMENT_CREDENTIAL or [cloud] deployment_credential.
|
|
50
|
+
deployment_credential: str = Field(
|
|
51
|
+
default="",
|
|
52
|
+
validation_alias=AliasChoices("CELERP_DEPLOYMENT_CREDENTIAL", "deployment_credential"),
|
|
53
|
+
)
|
|
54
|
+
# True once the relay has accepted the deployment credential and associated
|
|
55
|
+
# this instance. Suppresses any re-send of the credential (an env-sourced
|
|
56
|
+
# credential cannot be erased from the environment). Persisted as
|
|
57
|
+
# [cloud] deployment_associated.
|
|
58
|
+
deployment_associated: bool = False
|
|
59
|
+
# Idempotency nonce for the partner deployment association. Generated once,
|
|
60
|
+
# persisted before the first associate call, and reused on every retry and
|
|
61
|
+
# restart so a lost response cannot create a duplicate association: the relay
|
|
62
|
+
# keys the relationship on (partner, nonce). Dropped once consumed on a
|
|
63
|
+
# successful association. Persisted as [cloud] deployment_nonce.
|
|
64
|
+
deployment_nonce: str = ""
|
|
45
65
|
# True after an explicit Cloud disconnect: the startup probe must not
|
|
46
66
|
# re-link the install. Cleared when the user reconnects (settings or a
|
|
47
67
|
# sign-in flow applies a fresh token). Persisted as [cloud] disconnected.
|
|
@@ -67,6 +87,19 @@ class Settings(BaseSettings):
|
|
|
67
87
|
storage_s3_bucket: str = ""
|
|
68
88
|
storage_s3_access_key: str = ""
|
|
69
89
|
storage_s3_secret_key: str = ""
|
|
90
|
+
# Self-hosted only: the operator declares that this install runs on a
|
|
91
|
+
# customer-owned external database covered by the Team subscription. Default
|
|
92
|
+
# off; set true via EXTERNAL_DB / [cloud] external_db. It gates Team-infra
|
|
93
|
+
# visibility and the post-lapse "restore backup" recovery UI, so it is an
|
|
94
|
+
# explicit, durable opt-in and is never inferred from database_url (every
|
|
95
|
+
# ordinary self-hosted install points database_url at its own Postgres, which
|
|
96
|
+
# is not the same as opting into customer-owned Team infrastructure).
|
|
97
|
+
# Packaged builds detect the external database from celerp-config.json instead
|
|
98
|
+
# and never read this field.
|
|
99
|
+
external_db: bool = Field(
|
|
100
|
+
default=False,
|
|
101
|
+
validation_alias=AliasChoices("EXTERNAL_DB", "external_db"),
|
|
102
|
+
)
|
|
70
103
|
# Data directory for runtime artifacts (uploads, caches).
|
|
71
104
|
# Accepts CELERP_DATA_DIR (Electron) or DATA_DIR (legacy). Defaults to ./data.
|
|
72
105
|
data_dir: Path = Field(
|
|
@@ -152,6 +185,51 @@ def persist_cloud_settings(**values: str) -> None:
|
|
|
152
185
|
write_config(cfg)
|
|
153
186
|
|
|
154
187
|
|
|
188
|
+
def ensure_deployment_nonce() -> str:
|
|
189
|
+
"""Return the deployment association nonce, generating and persisting one when
|
|
190
|
+
absent.
|
|
191
|
+
|
|
192
|
+
The nonce must be persisted before the association call so a lost response
|
|
193
|
+
plus a retry reuses the same value and resolves to the same association
|
|
194
|
+
(idempotency keyed on partner + nonce). A persist failure propagates so the
|
|
195
|
+
caller can abort before any network call rather than send an unpersisted
|
|
196
|
+
nonce that a later boot could not reproduce.
|
|
197
|
+
"""
|
|
198
|
+
if settings.deployment_nonce:
|
|
199
|
+
return settings.deployment_nonce
|
|
200
|
+
import uuid as _uuid
|
|
201
|
+
nonce = _uuid.uuid4().hex
|
|
202
|
+
settings.deployment_nonce = nonce
|
|
203
|
+
persist_cloud_settings(deployment_nonce=nonce)
|
|
204
|
+
return nonce
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def record_deployment_association(gateway_token: str, instance_id: str) -> None:
|
|
208
|
+
"""Record a successful partner deployment association durably in one write.
|
|
209
|
+
|
|
210
|
+
Persists the relay-issued gateway_token and instance_id, sets the sticky
|
|
211
|
+
deployment_associated marker, and removes the now-consumed deployment
|
|
212
|
+
credential and nonce from [cloud]. persist_cloud_settings never erases a key,
|
|
213
|
+
so this dedicated helper is what drops them. The disk write precedes the
|
|
214
|
+
in-memory updates: if the write raises, the caller sees the failure and no
|
|
215
|
+
live-but-unpersisted identity is carried, so the next boot's idempotent retry
|
|
216
|
+
can recover.
|
|
217
|
+
"""
|
|
218
|
+
cfg = read_config()
|
|
219
|
+
cloud = cfg.setdefault("cloud", {})
|
|
220
|
+
cloud["token"] = gateway_token
|
|
221
|
+
cloud["instance_id"] = instance_id
|
|
222
|
+
cloud["deployment_associated"] = True
|
|
223
|
+
cloud.pop("deployment_credential", None)
|
|
224
|
+
cloud.pop("deployment_nonce", None)
|
|
225
|
+
write_config(cfg)
|
|
226
|
+
settings.gateway_token = gateway_token
|
|
227
|
+
settings.gateway_instance_id = instance_id
|
|
228
|
+
settings.deployment_credential = ""
|
|
229
|
+
settings.deployment_nonce = ""
|
|
230
|
+
settings.deployment_associated = True
|
|
231
|
+
|
|
232
|
+
|
|
155
233
|
def load_cloud_config() -> None:
|
|
156
234
|
"""Load cloud settings from config.toml into the Settings object.
|
|
157
235
|
|
|
@@ -190,6 +268,21 @@ def load_cloud_config() -> None:
|
|
|
190
268
|
settings.celerp_public_url = cloud["public_url"]
|
|
191
269
|
if cloud.get("backup_encryption_key") and not settings.backup_encryption_key:
|
|
192
270
|
settings.backup_encryption_key = cloud["backup_encryption_key"]
|
|
271
|
+
# Deployment credential + association marker. The credential is only consumed
|
|
272
|
+
# on the first hello, so a config value loads unless env already supplied one;
|
|
273
|
+
# the marker is sticky (an associated install must never re-offer it).
|
|
274
|
+
if cloud.get("deployment_credential") and not settings.deployment_credential:
|
|
275
|
+
settings.deployment_credential = cloud["deployment_credential"]
|
|
276
|
+
if cloud.get("deployment_associated"):
|
|
277
|
+
settings.deployment_associated = True
|
|
278
|
+
# The association nonce is reused across boots: reload it so a retry after a
|
|
279
|
+
# lost response resolves to the same association rather than minting a new one.
|
|
280
|
+
if cloud.get("deployment_nonce") and not settings.deployment_nonce:
|
|
281
|
+
settings.deployment_nonce = cloud["deployment_nonce"]
|
|
282
|
+
# Self-hosted external-database opt-in. Durable [cloud] key so the operator's
|
|
283
|
+
# declaration survives restarts; loads unless the environment already set it.
|
|
284
|
+
if cloud.get("external_db") and not settings.external_db:
|
|
285
|
+
settings.external_db = True
|
|
193
286
|
# Auto-enable secure cookies when relay-connected (HTTPS via Caddy/Cloudflare)
|
|
194
287
|
if settings.gateway_token and not os.environ.get("COOKIE_SECURE"):
|
|
195
288
|
settings.cookie_secure = True
|
|
@@ -313,6 +406,32 @@ def write_config(cfg: dict) -> None:
|
|
|
313
406
|
# before this shipped), matching the embedded/headless idiom.
|
|
314
407
|
if cloud.get("disconnected"):
|
|
315
408
|
lines.append("disconnected = true")
|
|
409
|
+
# Self-hosted last-known-good commercial context, compact JSON of the
|
|
410
|
+
# already-validated envelope, so a partner-managed install presents its
|
|
411
|
+
# partner identity across an offline restart instead of defaulting to
|
|
412
|
+
# celerp_direct. Emitted only when set (a direct install that has never
|
|
413
|
+
# cached one carries no key); serialised through the JSON string encoder
|
|
414
|
+
# so embedded quotes survive the TOML round-trip.
|
|
415
|
+
if cloud.get("commercial_context_json"):
|
|
416
|
+
import json as _json
|
|
417
|
+
lines.append(
|
|
418
|
+
f"commercial_context_json = {_json.dumps(cloud['commercial_context_json'])}")
|
|
419
|
+
# Deployment credential survives every [cloud] write until the relay
|
|
420
|
+
# accepts it: emitted only while non-empty, and the association marker
|
|
421
|
+
# only once set. A direct install carries neither key. Without this, the
|
|
422
|
+
# fixed-key serializer would drop the credential before the first hello.
|
|
423
|
+
if cloud.get("deployment_credential"):
|
|
424
|
+
lines.append(f'deployment_credential = {_str(cloud["deployment_credential"])}')
|
|
425
|
+
# The association nonce persists across boots until an association
|
|
426
|
+
# consumes it, so a retry reuses it; emitted only while set.
|
|
427
|
+
if cloud.get("deployment_nonce"):
|
|
428
|
+
lines.append(f'deployment_nonce = {_str(cloud["deployment_nonce"])}')
|
|
429
|
+
if cloud.get("deployment_associated"):
|
|
430
|
+
lines.append("deployment_associated = true")
|
|
431
|
+
# Self-hosted external-database opt-in, emitted only when set. A direct
|
|
432
|
+
# local install carries no key and reads as off.
|
|
433
|
+
if cloud.get("external_db"):
|
|
434
|
+
lines.append("external_db = true")
|
|
316
435
|
lines.append("")
|
|
317
436
|
|
|
318
437
|
if "storage" in cfg:
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# Copyright (c) 2026 Noah Severs
|
|
2
|
+
# SPDX-License-Identifier: BUSL-1.1
|
|
3
|
+
|
|
4
|
+
"""Atomic reads and writes for Electron's packaged celerp-config.json.
|
|
5
|
+
|
|
6
|
+
A single writer that merges any number of top-level keys into the packaged
|
|
7
|
+
config in one atomic operation, so a save that touches several related keys
|
|
8
|
+
(a database URL plus its backup, a storage backend plus its credentials)
|
|
9
|
+
never leaves the file with only some of them applied. Callers outside this
|
|
10
|
+
module never open or replace celerp-config.json directly.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
import logging
|
|
17
|
+
import os
|
|
18
|
+
import time
|
|
19
|
+
import uuid
|
|
20
|
+
|
|
21
|
+
log = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
# Cross-process lock protocol for celerp-config.json, identical on both writers
|
|
24
|
+
# (this module and electron/config-writer.js): the lock is an O_EXCL-created
|
|
25
|
+
# sidecar file `celerp-config.json.lock`. A writer acquires by exclusive create,
|
|
26
|
+
# writing a unique owner token as the whole file body, retrying every
|
|
27
|
+
# _LOCK_RETRY_S for up to _LOCK_BUDGET_S; a lock whose mtime has aged past
|
|
28
|
+
# _LOCK_STALE_S is presumed abandoned. Both unlink sites are token-verified:
|
|
29
|
+
# a writer removes the lock only while the file still holds the token it wrote,
|
|
30
|
+
# so a stale-takeover cannot delete a faster successor's lock and a superseded
|
|
31
|
+
# owner's release becomes a no-op. Exactly one writer is elected. The token is
|
|
32
|
+
# read for control, so it must be written and re-read as the exact file body.
|
|
33
|
+
#
|
|
34
|
+
# Honest residual: a path-based read-then-unlink is not atomic, so a sub-operation
|
|
35
|
+
# TOCTOU window remains between the token re-read and the unlink. It is bounded by
|
|
36
|
+
# _LOCK_STALE_S (a takeover requires a >10s stall) and is far narrower than an
|
|
37
|
+
# unconditional unlink.
|
|
38
|
+
_LOCK_BUDGET_S = 5.0
|
|
39
|
+
_LOCK_RETRY_S = 0.05
|
|
40
|
+
_LOCK_STALE_S = 10.0
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _lock_token() -> bytes:
|
|
44
|
+
"""A unique owner token for one lock acquisition: pid plus a random suffix,
|
|
45
|
+
so two acquisitions (even in the same process) never collide."""
|
|
46
|
+
return f"{os.getpid()} {uuid.uuid4().hex}".encode()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _read_lock_token(lock_path: str) -> bytes | None:
|
|
50
|
+
"""Return the current lock file body, or None when it cannot be read."""
|
|
51
|
+
try:
|
|
52
|
+
with open(lock_path, "rb") as f:
|
|
53
|
+
return f.read()
|
|
54
|
+
except OSError:
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _acquire_lock(lock_path: str):
|
|
59
|
+
"""Acquire the config lock by exclusive create, returning (fd, token), or
|
|
60
|
+
None when the budget expires while another writer holds it. The token is the
|
|
61
|
+
exact bytes written into the lock; the caller passes it to _release_lock so
|
|
62
|
+
only the electing writer ever removes this lock."""
|
|
63
|
+
deadline = time.monotonic() + _LOCK_BUDGET_S
|
|
64
|
+
while True:
|
|
65
|
+
try:
|
|
66
|
+
fd = os.open(lock_path, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600)
|
|
67
|
+
token = _lock_token()
|
|
68
|
+
os.write(fd, token)
|
|
69
|
+
os.fsync(fd)
|
|
70
|
+
return fd, token
|
|
71
|
+
except FileExistsError:
|
|
72
|
+
try:
|
|
73
|
+
age = time.time() - os.path.getmtime(lock_path)
|
|
74
|
+
except OSError:
|
|
75
|
+
age = 0.0
|
|
76
|
+
if age > _LOCK_STALE_S:
|
|
77
|
+
# Abandoned lock: re-read the token immediately before removing
|
|
78
|
+
# and unlink only that same stale token, so a faster successor
|
|
79
|
+
# that already replaced the lock is never deleted. Then loop
|
|
80
|
+
# back to the exclusive create, which stays the only way to win.
|
|
81
|
+
stale_token = _read_lock_token(lock_path)
|
|
82
|
+
if stale_token is not None:
|
|
83
|
+
try:
|
|
84
|
+
if _read_lock_token(lock_path) == stale_token:
|
|
85
|
+
os.unlink(lock_path)
|
|
86
|
+
except OSError:
|
|
87
|
+
pass
|
|
88
|
+
continue
|
|
89
|
+
if time.monotonic() >= deadline:
|
|
90
|
+
return None
|
|
91
|
+
time.sleep(_LOCK_RETRY_S)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _release_lock(fd: int, lock_path: str, token: bytes) -> None:
|
|
95
|
+
"""Close the lock fd and unlink the lock file only while it still holds this
|
|
96
|
+
owner's token; a lock a stale takeover already replaced is left untouched."""
|
|
97
|
+
try:
|
|
98
|
+
os.close(fd)
|
|
99
|
+
except OSError:
|
|
100
|
+
pass
|
|
101
|
+
try:
|
|
102
|
+
if _read_lock_token(lock_path) == token:
|
|
103
|
+
os.unlink(lock_path)
|
|
104
|
+
except OSError:
|
|
105
|
+
pass
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _fsync_dir(dir_path: str) -> None:
|
|
109
|
+
"""Fsync the directory so a preceding atomic rename is durable, not only the
|
|
110
|
+
renamed file's contents. A crash after os.replace but before the directory
|
|
111
|
+
entry reaches disk could otherwise lose the rename. No-op on platforms that
|
|
112
|
+
cannot fsync a directory fd (Windows), and any OSError is swallowed because
|
|
113
|
+
the rename itself already committed the write."""
|
|
114
|
+
if os.name != "posix":
|
|
115
|
+
return
|
|
116
|
+
try:
|
|
117
|
+
dir_fd = os.open(dir_path, os.O_RDONLY | getattr(os, "O_DIRECTORY", 0))
|
|
118
|
+
except OSError:
|
|
119
|
+
return
|
|
120
|
+
try:
|
|
121
|
+
os.fsync(dir_fd)
|
|
122
|
+
except OSError:
|
|
123
|
+
pass
|
|
124
|
+
finally:
|
|
125
|
+
try:
|
|
126
|
+
os.close(dir_fd)
|
|
127
|
+
except OSError:
|
|
128
|
+
pass
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def merge_packaged_config(updates: dict) -> bool:
|
|
132
|
+
"""Merge every key in `updates` into Electron's celerp-config.json in one
|
|
133
|
+
atomic write, forcing mode 0600 so the co-resident secrets (external_db_url,
|
|
134
|
+
S3 keys) are never broadened. Returns True when the updates were persisted,
|
|
135
|
+
False when they could not be (no packaged data dir, the lock could not be
|
|
136
|
+
acquired within its budget, or a write error).
|
|
137
|
+
|
|
138
|
+
A no-op returning False in dev/server mode where CELERP_DATA_DIR is unset.
|
|
139
|
+
The whole read-merge-write runs under the cross-process lock so a concurrent
|
|
140
|
+
Electron writer never races: the existing config is re-read inside the lock,
|
|
141
|
+
every key is merged, the result is written to a unique temp file created
|
|
142
|
+
0600, fsync'd, then os.replace swaps it in (os.replace adopts the temp inode,
|
|
143
|
+
so the target's mode becomes 0600 regardless of the prior mode or the process
|
|
144
|
+
umask). Any failure logs the keys and exception (never the values), removes
|
|
145
|
+
the temp file, and leaves the prior config on disk untouched, so a multi-key
|
|
146
|
+
save either lands in full or not at all. The lock is always released.
|
|
147
|
+
"""
|
|
148
|
+
if not updates:
|
|
149
|
+
return True
|
|
150
|
+
data_dir = os.environ.get("CELERP_DATA_DIR", "")
|
|
151
|
+
if not data_dir:
|
|
152
|
+
return False
|
|
153
|
+
config_path = os.path.join(data_dir, "celerp-config.json")
|
|
154
|
+
lock_path = f"{config_path}.lock"
|
|
155
|
+
acquired = _acquire_lock(lock_path)
|
|
156
|
+
if acquired is None:
|
|
157
|
+
log.warning("Config: could not acquire lock for %s within %ss; not persisted.",
|
|
158
|
+
sorted(updates.keys()), _LOCK_BUDGET_S)
|
|
159
|
+
return False
|
|
160
|
+
lock_fd, lock_token = acquired
|
|
161
|
+
tmp_path = f"{config_path}.{uuid.uuid4().hex}.tmp"
|
|
162
|
+
try:
|
|
163
|
+
existing: dict = {}
|
|
164
|
+
if os.path.exists(config_path):
|
|
165
|
+
with open(config_path) as f:
|
|
166
|
+
loaded = json.load(f)
|
|
167
|
+
if isinstance(loaded, dict):
|
|
168
|
+
existing = loaded
|
|
169
|
+
existing.update(updates)
|
|
170
|
+
fd = os.open(tmp_path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
|
|
171
|
+
with os.fdopen(fd, "w") as f:
|
|
172
|
+
json.dump(existing, f, indent=2)
|
|
173
|
+
f.flush()
|
|
174
|
+
os.fsync(f.fileno())
|
|
175
|
+
os.replace(tmp_path, config_path)
|
|
176
|
+
_fsync_dir(data_dir)
|
|
177
|
+
log.debug("Config: %s persisted.", sorted(updates.keys()))
|
|
178
|
+
return True
|
|
179
|
+
except Exception as exc:
|
|
180
|
+
log.warning("Config: failed to persist %s: %s", sorted(updates.keys()), exc)
|
|
181
|
+
try:
|
|
182
|
+
if os.path.exists(tmp_path):
|
|
183
|
+
os.remove(tmp_path)
|
|
184
|
+
except OSError:
|
|
185
|
+
pass
|
|
186
|
+
return False
|
|
187
|
+
finally:
|
|
188
|
+
_release_lock(lock_fd, lock_path, lock_token)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def read_packaged_config() -> dict:
|
|
192
|
+
"""Return the packaged celerp-config.json as a dict, or {} when it is
|
|
193
|
+
absent, unreadable, or not an object."""
|
|
194
|
+
data_dir = os.environ.get("CELERP_DATA_DIR", "")
|
|
195
|
+
if not data_dir:
|
|
196
|
+
return {}
|
|
197
|
+
config_path = os.path.join(data_dir, "celerp-config.json")
|
|
198
|
+
try:
|
|
199
|
+
with open(config_path) as f:
|
|
200
|
+
loaded = json.load(f)
|
|
201
|
+
return loaded if isinstance(loaded, dict) else {}
|
|
202
|
+
except (OSError, ValueError):
|
|
203
|
+
return {}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# Copyright (c) 2026 Noah Severs
|
|
2
|
+
# SPDX-License-Identifier: BUSL-1.1
|
|
3
|
+
|
|
4
|
+
"""Pre-boot entitlement preflight for the packaged app.
|
|
5
|
+
|
|
6
|
+
Electron spawns this one-shot before it decides whether to open a previously
|
|
7
|
+
selected external database. It refreshes the subscription entitlement from the
|
|
8
|
+
relay and reports a tri-state exit code so the boot path never silently forks
|
|
9
|
+
data between an external and a local store:
|
|
10
|
+
|
|
11
|
+
RENEWED (0) the refreshed flags allow external; continue external.
|
|
12
|
+
EXPIRED (2) the subscription lapsed; fall back to local.
|
|
13
|
+
UNREACHABLE (3) the relay could not authoritatively answer; ask, never guess.
|
|
14
|
+
|
|
15
|
+
Only a well-formed HTTP 200 body is authoritative. Every transport error,
|
|
16
|
+
timeout, 5xx, 401/403, malformed body, or failed locked write maps to
|
|
17
|
+
UNREACHABLE, so a rotated credential or a half-refreshed config never reads as
|
|
18
|
+
an expiry and never diverts the running database.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import asyncio
|
|
24
|
+
import logging
|
|
25
|
+
import sys
|
|
26
|
+
|
|
27
|
+
from celerp.config import load_cloud_config, settings
|
|
28
|
+
from celerp.config_store import merge_packaged_config
|
|
29
|
+
|
|
30
|
+
log = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
RENEWED = 0
|
|
33
|
+
EXPIRED = 2
|
|
34
|
+
UNREACHABLE = 3
|
|
35
|
+
|
|
36
|
+
# Boot-path bounds: short per-attempt timeout, at most two attempts, well under
|
|
37
|
+
# the gateway's own (0, 5, 30) reconnect ladder, which would stall startup.
|
|
38
|
+
_PREFLIGHT_TIMEOUT_S = 5.0
|
|
39
|
+
_PREFLIGHT_ATTEMPTS = 2
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
async def _request_subscription():
|
|
43
|
+
"""Fetch the current subscription entitlement from the relay.
|
|
44
|
+
|
|
45
|
+
Exchanges the instance token for a short-lived bearer, then GETs
|
|
46
|
+
/billing/subscription, reusing one client for both. Returns the httpx
|
|
47
|
+
Response of any status on the first attempt that produced one, or None when
|
|
48
|
+
every attempt hit a transport error. Only transport failures are retried; a
|
|
49
|
+
response of any status is final and interpreted by the caller.
|
|
50
|
+
"""
|
|
51
|
+
import httpx
|
|
52
|
+
|
|
53
|
+
from celerp.gateway.state import fetch_relay_bearer, relay_http_url
|
|
54
|
+
|
|
55
|
+
url = f"{relay_http_url()}/billing/subscription"
|
|
56
|
+
for attempt in range(_PREFLIGHT_ATTEMPTS):
|
|
57
|
+
try:
|
|
58
|
+
async with httpx.AsyncClient(timeout=_PREFLIGHT_TIMEOUT_S) as client:
|
|
59
|
+
bearer = await fetch_relay_bearer(client)
|
|
60
|
+
return await client.get(
|
|
61
|
+
url, headers={"Authorization": f"Bearer {bearer}"})
|
|
62
|
+
except httpx.HTTPError:
|
|
63
|
+
if attempt + 1 >= _PREFLIGHT_ATTEMPTS:
|
|
64
|
+
return None
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# The canonical feature-flags contract: every relay entitlement body carries
|
|
69
|
+
# exactly these three boolean flags, always present, plus a grace_period_ends
|
|
70
|
+
# that is null or a tz-aware ISO-8601 string. The app consumes the same bytes
|
|
71
|
+
# the cloud emits (celerp/gateway/fixtures/entitlement_flags_fixture.json), so
|
|
72
|
+
# validation is field-by-field against this shape, never a shape-agnostic
|
|
73
|
+
# all-bool heuristic that a nested grace value would fail.
|
|
74
|
+
_REQUIRED_BOOL_FLAGS = ("payments_enabled", "external_db", "external_storage")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _valid_grace(value) -> bool:
|
|
78
|
+
"""A grace_period_ends field is authoritative only when it is None or a
|
|
79
|
+
tz-aware ISO-8601 string; a naive timestamp is a malformed body.
|
|
80
|
+
|
|
81
|
+
The relay always emits an offset-bearing timestamp; a value without a
|
|
82
|
+
timezone is ambiguous and would compare wrongly against a tz-aware now, so
|
|
83
|
+
it is rejected rather than silently assumed to be UTC.
|
|
84
|
+
"""
|
|
85
|
+
if value is None:
|
|
86
|
+
return True
|
|
87
|
+
if not isinstance(value, str):
|
|
88
|
+
return False
|
|
89
|
+
from datetime import datetime
|
|
90
|
+
try:
|
|
91
|
+
parsed = datetime.fromisoformat(value)
|
|
92
|
+
except ValueError:
|
|
93
|
+
return False
|
|
94
|
+
return parsed.tzinfo is not None
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _valid_feature_flags(flags) -> bool:
|
|
98
|
+
"""Validate the feature_flags object field-by-field against the canonical
|
|
99
|
+
contract: the three required flags each present and a real bool, and a
|
|
100
|
+
grace_period_ends that is null or a tz-aware ISO-8601 string. A missing
|
|
101
|
+
required flag, a non-bool flag, or a naive grace is a malformed body.
|
|
102
|
+
"""
|
|
103
|
+
if not isinstance(flags, dict):
|
|
104
|
+
return False
|
|
105
|
+
for key in _REQUIRED_BOOL_FLAGS:
|
|
106
|
+
value = flags.get(key)
|
|
107
|
+
if not isinstance(value, bool):
|
|
108
|
+
return False
|
|
109
|
+
return _valid_grace(flags.get("grace_period_ends"))
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def run_preflight() -> int:
|
|
113
|
+
"""Refresh the entitlement and return one of RENEWED / EXPIRED / UNREACHABLE."""
|
|
114
|
+
load_cloud_config()
|
|
115
|
+
if not settings.gateway_token:
|
|
116
|
+
# An install that never associated has no entitlement to confirm; there
|
|
117
|
+
# is nothing to refresh, so fall back to local without a relay call.
|
|
118
|
+
return EXPIRED
|
|
119
|
+
|
|
120
|
+
try:
|
|
121
|
+
resp = asyncio.run(_request_subscription())
|
|
122
|
+
except Exception:
|
|
123
|
+
return UNREACHABLE
|
|
124
|
+
if resp is None or getattr(resp, "status_code", None) != 200:
|
|
125
|
+
return UNREACHABLE
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
body = resp.json()
|
|
129
|
+
except Exception:
|
|
130
|
+
return UNREACHABLE
|
|
131
|
+
if not isinstance(body, dict):
|
|
132
|
+
return UNREACHABLE
|
|
133
|
+
|
|
134
|
+
flags = body.get("feature_flags")
|
|
135
|
+
if not _valid_feature_flags(flags):
|
|
136
|
+
return UNREACHABLE
|
|
137
|
+
|
|
138
|
+
# The nested grace inside feature_flags is the authoritative contract value.
|
|
139
|
+
# The relay also mirrors it at the top level; when both are present they must
|
|
140
|
+
# agree, else the body is internally inconsistent and cannot be trusted.
|
|
141
|
+
nested_grace = flags.get("grace_period_ends")
|
|
142
|
+
top_level_grace = body.get("grace_period_ends")
|
|
143
|
+
if not _valid_grace(top_level_grace):
|
|
144
|
+
return UNREACHABLE
|
|
145
|
+
if (nested_grace is not None and top_level_grace is not None
|
|
146
|
+
and nested_grace != top_level_grace):
|
|
147
|
+
return UNREACHABLE
|
|
148
|
+
|
|
149
|
+
# Persist the refreshed entitlement through the one locked writer. The
|
|
150
|
+
# canonical shape already carries grace_period_ends nested where every
|
|
151
|
+
# reader expects it. A failed write is UNREACHABLE, never a half-refreshed
|
|
152
|
+
# RENEWED.
|
|
153
|
+
if not merge_packaged_config({"feature_flags": dict(flags)}):
|
|
154
|
+
return UNREACHABLE
|
|
155
|
+
|
|
156
|
+
from celerp.gateway.state import grace_ends_in_future
|
|
157
|
+
|
|
158
|
+
external_allowed = bool(flags.get("external_db")) or grace_ends_in_future(nested_grace)
|
|
159
|
+
return RENEWED if external_allowed else EXPIRED
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def main() -> None:
|
|
163
|
+
logging.basicConfig(level=logging.WARNING)
|
|
164
|
+
sys.exit(run_preflight())
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
if __name__ == "__main__":
|
|
168
|
+
main()
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Copyright (c) 2026 Noah Severs
|
|
2
|
+
# SPDX-License-Identifier: LicenseRef-Proprietary
|
|
3
|
+
# celerp.gateway is a protected BSL internal - 3P modules must not import it.
|
|
4
|
+
"""Boot-time partner deployment association.
|
|
5
|
+
|
|
6
|
+
A partner-packaged install carries a one-time deployment credential. Before the
|
|
7
|
+
gateway starts, this seam exchanges that credential for the relay-created
|
|
8
|
+
identity (instance_id + api_key) through the explicit relay HTTP endpoint, then
|
|
9
|
+
persists the identity so the existing boot gate brings the tunnel up under it.
|
|
10
|
+
|
|
11
|
+
The relay gateway handshake never consumes the credential, so the association is
|
|
12
|
+
recorded only after a positive response from this dedicated call - never as a
|
|
13
|
+
side effect of an ordinary hello_ack.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import logging
|
|
19
|
+
|
|
20
|
+
from celerp.config import (
|
|
21
|
+
ensure_deployment_nonce,
|
|
22
|
+
record_deployment_association,
|
|
23
|
+
settings,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
log = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
async def associate_partner_deployment() -> bool:
|
|
30
|
+
"""Associate a partner-packaged install with its partner via the relay.
|
|
31
|
+
|
|
32
|
+
No-op (returns False) unless a deployment credential is set, the install is
|
|
33
|
+
not already associated, and Cloud is not disconnected. On a positive response
|
|
34
|
+
it persists the relay-issued identity, marks the install associated, erases
|
|
35
|
+
the credential, and returns True. Every failure preserves the credential and
|
|
36
|
+
returns False without raising, so a lifespan boot is never crashed.
|
|
37
|
+
"""
|
|
38
|
+
credential = (settings.deployment_credential or "").strip()
|
|
39
|
+
if not credential or settings.deployment_associated or settings.cloud_disconnected:
|
|
40
|
+
return False
|
|
41
|
+
|
|
42
|
+
from celerp.gateway.state import relay_http_url, relay_post_with_retry
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
# Persist the nonce before any network call: an unpersisted nonce could
|
|
46
|
+
# not be reproduced on a later boot, risking a duplicate association.
|
|
47
|
+
nonce = ensure_deployment_nonce()
|
|
48
|
+
except Exception as exc:
|
|
49
|
+
log.warning(
|
|
50
|
+
"Deployment association skipped: could not persist the nonce (%s).",
|
|
51
|
+
type(exc).__name__,
|
|
52
|
+
)
|
|
53
|
+
return False
|
|
54
|
+
|
|
55
|
+
url = f"{relay_http_url()}/partners/deployments/associate"
|
|
56
|
+
body = {"deployment_credential": credential, "deployment_nonce": nonce}
|
|
57
|
+
|
|
58
|
+
# The shared helper retries only transient transport failures and quiets
|
|
59
|
+
# httpx's own logger for the duration (the credential travels in this body).
|
|
60
|
+
resp = await relay_post_with_retry(url, body)
|
|
61
|
+
|
|
62
|
+
if resp is None:
|
|
63
|
+
log.warning("Deployment association could not reach the relay; will retry next boot.")
|
|
64
|
+
return False
|
|
65
|
+
|
|
66
|
+
if resp.status_code != 200:
|
|
67
|
+
if resp.status_code == 403:
|
|
68
|
+
log.warning("Deployment association rejected: invalid or ineligible credential.")
|
|
69
|
+
elif resp.status_code == 409:
|
|
70
|
+
log.warning("Deployment association rejected: instance already owned.")
|
|
71
|
+
else:
|
|
72
|
+
log.warning("Deployment association failed (status %s); will retry next boot.",
|
|
73
|
+
resp.status_code)
|
|
74
|
+
return False
|
|
75
|
+
|
|
76
|
+
try:
|
|
77
|
+
data = resp.json()
|
|
78
|
+
except Exception:
|
|
79
|
+
log.warning("Deployment association returned a malformed response body.")
|
|
80
|
+
return False
|
|
81
|
+
|
|
82
|
+
instance_id = (data.get("instance_id") or "").strip() if isinstance(data, dict) else ""
|
|
83
|
+
api_key = (data.get("api_key") or "").strip() if isinstance(data, dict) else ""
|
|
84
|
+
if not instance_id or not api_key:
|
|
85
|
+
log.warning("Deployment association response missing the instance identity.")
|
|
86
|
+
return False
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
record_deployment_association(gateway_token=api_key, instance_id=instance_id)
|
|
90
|
+
except Exception as exc:
|
|
91
|
+
# The durable write failed after a positive response. The credential is
|
|
92
|
+
# preserved and no live-but-unpersisted identity is carried; the persisted
|
|
93
|
+
# nonce makes the next boot's retry resolve to the same association.
|
|
94
|
+
log.warning(
|
|
95
|
+
"Deployment association could not be persisted (%s); will retry next boot.",
|
|
96
|
+
type(exc).__name__,
|
|
97
|
+
)
|
|
98
|
+
return False
|
|
99
|
+
|
|
100
|
+
log.info("Partner deployment associated (instance_id=%s).", instance_id)
|
|
101
|
+
return True
|