celerp 2.3.5__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.5 → celerp-2.4.0}/PKG-INFO +1 -1
- {celerp-2.3.5 → 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.3.5 → celerp-2.4.0}/celerp/events/engine.py +44 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/events/schemas.py +61 -12
- celerp-2.4.0/celerp/gateway/bootstrap.py +101 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/gateway/client.py +207 -78
- 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.4.0/celerp/inventory_codes.py +185 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/main.py +21 -31
- {celerp-2.3.5 → celerp-2.4.0}/celerp/middleware.py +53 -53
- celerp-2.4.0/celerp/migrations/versions/d1e2f3a4b5c6_rfid_epc_unique_index.py +44 -0
- celerp-2.4.0/celerp/migrations/versions/f3a4b5c6d7e8_purge_session_registry.py +41 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/models/projections.py +12 -1
- {celerp-2.3.5 → celerp-2.4.0}/celerp/modules/api.py +8 -2
- {celerp-2.3.5 → celerp-2.4.0}/celerp/notifications/sse.py +1 -36
- {celerp-2.3.5 → celerp-2.4.0}/celerp/output/doc_print.py +3 -1
- {celerp-2.3.5 → celerp-2.4.0}/celerp/projections/engine.py +16 -6
- celerp-2.4.0/celerp/routers/attachments.py +42 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/routers/auth.py +104 -129
- {celerp-2.3.5 → celerp-2.4.0}/celerp/routers/companies.py +90 -27
- celerp-2.4.0/celerp/routers/events.py +134 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/routers/health.py +179 -23
- {celerp-2.3.5 → celerp-2.4.0}/celerp/routers/notifications.py +1 -36
- {celerp-2.3.5 → celerp-2.4.0}/celerp/services/attachments.py +37 -9
- celerp-2.4.0/celerp/services/auth.py +339 -0
- celerp-2.4.0/celerp/services/document_lines.py +102 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/services/field_schema.py +2 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/services/session_tracker.py +122 -96
- {celerp-2.3.5 → celerp-2.4.0}/celerp/services/units.py +12 -3
- {celerp-2.3.5 → celerp-2.4.0}/celerp/session_gate.py +10 -3
- {celerp-2.3.5 → celerp-2.4.0}/celerp.egg-info/PKG-INFO +1 -1
- {celerp-2.3.5 → celerp-2.4.0}/celerp.egg-info/SOURCES.txt +12 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp.egg-info/scm_file_list.json +58 -0
- celerp-2.4.0/celerp.egg-info/scm_version.json +8 -0
- {celerp-2.3.5 → celerp-2.4.0}/conftest.py +24 -5
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-accounting/tests/test_accounting_import_idempotency.py +2 -2
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-ai/celerp_ai/routes.py +13 -5
- {celerp-2.3.5 → 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.5 → celerp-2.4.0}/default_modules/celerp-docs/celerp_docs/routes.py +186 -20
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_audits.py +5 -2
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_line_page_patch_truncate.py +35 -17
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_list_page_no_full_state.py +4 -1
- celerp-2.4.0/default_modules/celerp-docs/tests/test_list_quantity_integrity.py +370 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_list_query_pushdown.py +21 -10
- celerp-2.4.0/default_modules/celerp-docs/tests/test_list_sell_by_and_reference.py +103 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-inventory/celerp_inventory/projections.py +2 -1
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-inventory/celerp_inventory/routes.py +241 -82
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-inventory/celerp_inventory/routes_scanning.py +1 -1
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-inventory/celerp_inventory/services.py +70 -18
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-manufacturing/tests/test_manufacturing_import_idempotency.py +2 -2
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-manufacturing/tests/test_work_centers_and_settings.py +28 -9
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/first_party.lock.json +5 -5
- {celerp-2.3.5 → celerp-2.4.0}/test_helpers.py +16 -0
- {celerp-2.3.5 → celerp-2.4.0}/ui/api_client.py +50 -7
- {celerp-2.3.5 → celerp-2.4.0}/ui/app.py +64 -32
- celerp-2.4.0/ui/components/cloud_gate.py +236 -0
- {celerp-2.3.5 → celerp-2.4.0}/ui/config.py +40 -8
- {celerp-2.3.5 → celerp-2.4.0}/ui/locales/am.json +49 -3
- {celerp-2.3.5 → celerp-2.4.0}/ui/locales/ar.json +49 -3
- {celerp-2.3.5 → celerp-2.4.0}/ui/locales/de.json +49 -3
- {celerp-2.3.5 → celerp-2.4.0}/ui/locales/en.json +49 -3
- {celerp-2.3.5 → celerp-2.4.0}/ui/locales/es.json +49 -3
- {celerp-2.3.5 → celerp-2.4.0}/ui/locales/fr.json +49 -3
- {celerp-2.3.5 → celerp-2.4.0}/ui/locales/id.json +49 -3
- {celerp-2.3.5 → celerp-2.4.0}/ui/locales/it.json +49 -3
- {celerp-2.3.5 → celerp-2.4.0}/ui/locales/ja.json +49 -3
- {celerp-2.3.5 → celerp-2.4.0}/ui/locales/pt.json +49 -3
- {celerp-2.3.5 → celerp-2.4.0}/ui/locales/th.json +49 -3
- {celerp-2.3.5 → celerp-2.4.0}/ui/locales/vi.json +49 -3
- {celerp-2.3.5 → celerp-2.4.0}/ui/routes/account.py +18 -8
- {celerp-2.3.5 → celerp-2.4.0}/ui/routes/auth.py +58 -60
- celerp-2.4.0/ui/routes/commercial.py +144 -0
- {celerp-2.3.5 → celerp-2.4.0}/ui/routes/contacts.py +2 -2
- {celerp-2.3.5 → celerp-2.4.0}/ui/routes/dashboard.py +1 -1
- {celerp-2.3.5 → celerp-2.4.0}/ui/routes/documents.py +142 -23
- {celerp-2.3.5 → celerp-2.4.0}/ui/routes/inventory.py +1 -1
- {celerp-2.3.5 → celerp-2.4.0}/ui/routes/modules_page.py +3 -4
- {celerp-2.3.5 → celerp-2.4.0}/ui/routes/notifications.py +1 -44
- {celerp-2.3.5 → celerp-2.4.0}/ui/routes/settings.py +69 -32
- celerp-2.4.0/ui/routes/settings_cloud.py +1535 -0
- {celerp-2.3.5 → celerp-2.4.0}/ui/routes/settings_connectors.py +6 -7
- {celerp-2.3.5 → celerp-2.4.0}/ui/routes/settings_payments.py +9 -6
- {celerp-2.3.5 → celerp-2.4.0}/ui/routes/setup.py +26 -17
- {celerp-2.3.5 → celerp-2.4.0}/ui/routes/subscriptions.py +1 -1
- {celerp-2.3.5 → celerp-2.4.0}/ui/static/app.css +1 -0
- celerp-2.3.5/celerp/gateway/state.py +0 -178
- celerp-2.3.5/celerp/inventory_codes.py +0 -87
- celerp-2.3.5/celerp/routers/events.py +0 -123
- celerp-2.3.5/celerp/services/auth.py +0 -174
- celerp-2.3.5/celerp.egg-info/scm_version.json +0 -8
- celerp-2.3.5/ui/components/cloud_gate.py +0 -127
- celerp-2.3.5/ui/routes/settings_cloud.py +0 -751
- {celerp-2.3.5 → celerp-2.4.0}/CLA.md +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/CONTRIBUTING.md +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/LICENSE +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/LICENSING.md +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/MANIFEST.in +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/NOTICE +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/README.md +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/alembic.ini +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/__init__.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/__main__.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/ai/__init__.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/ai/batch.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/ai/cleanup.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/ai/commands.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/ai/conversations.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/ai/files.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/ai/intent.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/ai/llm.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/ai/memory.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/ai/models.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/ai/page_count.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/ai/quota.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/ai/service.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/ai/tools.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/alembic.ini +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/alembic_config.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/capacity.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/cli.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/compat.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/compute/__init__.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/compute/aggregations.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/compute/valuation.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/connectors/__init__.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/connectors/base.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/connectors/daily_scheduler.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/connectors/http.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/connectors/images.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/connectors/quickbooks.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/connectors/registry.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/connectors/relay_token.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/connectors/shopify.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/connectors/sync_runner.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/connectors/upsert.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/connectors/util.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/connectors/webhooks.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/connectors/woocommerce.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/connectors/xero.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/constants.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/db.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/embedded_pg.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/events/__init__.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/events/types.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/gateway/__init__.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/importers/__init__.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/importers/importer.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/importers/schema.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/migrations/README.md +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/migrations/_auto_stamp.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/migrations/_data_reconcile.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/migrations/_json_compat.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/migrations/env.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/migrations/script.py.mako +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/migrations/versions/a0b1c2d3e4f5_seed_landed_cost_clearing_accounts.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/migrations/versions/a1b2c3d4e5f6_add_doc_share_tokens.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/migrations/versions/b1c2d3e4f5a6_add_supporter_badges.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/migrations/versions/b2c3d4e5f6a7_add_import_batches.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/migrations/versions/b3c4d5e6f7a8_expand_role_permissions_to_role_grants.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/migrations/versions/bc0d1e2f3a4b_barcode_unique_index.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/migrations/versions/c1d2e3f4a5b6_add_marketplace.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/migrations/versions/c2d3e4f5a6b7_add_is_sync_to_shopify.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/migrations/versions/c4d5e6f7a8b9_account_cash_flow_category.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/migrations/versions/c9d8e7f6a5b4_seed_shrinkage_writeoff_account.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/migrations/versions/d0e1f2a3b4c5_add_share_token_expiry.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/migrations/versions/d2e3f4a5b6c7_add_password_reset_token.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/migrations/versions/d5e6f7a8b9c0_rename_fx_rounding_to_fx_difference.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/migrations/versions/e3f4a5b6c7d8_add_company_is_active.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/migrations/versions/e4f5a6b7c8d9_backfill_connector_idempotency_key.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/migrations/versions/e7c9a1b3d5f2_work_center_default_columns.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/migrations/versions/f1a2b3c4d5e6_move_accounts_marketplace_to_modules.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/migrations/versions/f6a7b8c9d0e1_seed_fx_rounding_account.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/migrations/versions/f8a9b0c1d2e3_work_center_default_backfill.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/migrations/versions/fd5de461e14e_initial_schema.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/migrations/versions/g1h2i3j4k5l6_add_consignment_flag.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/migrations/versions/h2i3j4k5l6m7_add_bank_accounts_and_reconciliation.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/migrations/versions/j4k5l6m7n8o9_labels_db_persistence.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/migrations/versions/k5l6m7n8o9p0_add_reconciliation_v2.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/migrations/versions/l6m7n8o9p0q1_add_sync_runs_table.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/migrations/versions/m7n8o9p0q1r2_add_connector_configs_and_outbound_queue.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/migrations/versions/n8o9p0q1r2s3_backfill_payment_date.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/migrations/versions/o9p0q1r2s3t4_backfill_item_price_fields.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/migrations/versions/p1q2r3s4t5u6_add_session_registry_and_user_auth_state.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/migrations/versions/q2r3s4t5u6v7_add_system_runtime_state.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/migrations/versions/r3s4t5u6v7w8_add_projections_company_entity_type_index.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/migrations/versions/s4t5u6v7w8x9_drop_user_company_id_role.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/migrations/versions/t5u6v7w8x9y0_seed_inventory_sub_accounts.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/migrations/versions/u6v7w8x9y0z1_backfill_item_prices_from_attributes.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/migrations/versions/v7w8x9y0z1a2_backfill_purchase_unit_defaults.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/migrations/versions/w8x9y0z1a2b3_backfill_category_unit_defaults.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/migrations/versions/x7y8z9a0b1c2_add_projection_created_at.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/migrations/versions/y8z9a0b1c2d3_per_company_ledger_idempotency.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/migrations/versions/z9a0b1c2d3e4_work_centers.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/models/__init__.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/models/accounting.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/models/ai.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/models/auth.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/models/base.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/models/company.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/models/connector_config.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/models/import_batch.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/models/ledger.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/models/marketplace.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/models/notification.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/models/share.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/models/supporter.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/models/sync_run.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/modules/__init__.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/modules/audit.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/modules/demotion.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/modules/importer.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/modules/license.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/modules/loader.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/modules/meta.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/modules/migrations_runner.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/modules/references.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/modules/registry.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/modules/slots.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/notifications/__init__.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/notifications/service.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/output/__init__.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/output/branding.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/output/pdf.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/output/share_render.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/projections/__init__.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/projections/handlers/__init__.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/projections/handlers/marketplace.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/projections/handlers/scanning.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/projections/handlers/shopify.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/projections/handlers/system.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/routers/__init__.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/routers/debug.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/routers/doctor.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/routers/ledger.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/routers/search.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/routers/stars.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/routers/system.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/schemas/__init__.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/services/__init__.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/services/activity_redaction.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/services/auto_je.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/services/backup.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/services/backup_export.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/services/backup_import.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/services/backup_repo.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/services/backup_scheduler.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/services/backup_state.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/services/cogs_backfill.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/services/cost_visibility.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/services/demo.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/services/dev_release_guard.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/services/email.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/services/fulfill.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/services/holdings.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/services/je_keys.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/services/landed_cost.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/services/line_measures.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/services/list_behavior.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/services/money.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/services/payments.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/services/permissions.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/services/pick.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/services/pricing.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/services/relay_share.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/services/reorder.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/services/runtime_state.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/services/shipping.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/services/star_cta.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/services/status_doc_backfill.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/services/supporter_badge.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/services/system_health.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp/tax_regimes.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp.egg-info/dependency_links.txt +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp.egg-info/entry_points.txt +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp.egg-info/requires.txt +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/celerp.egg-info/top_level.txt +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/conftest_support.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/__init__.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-accounting/LICENSE +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-accounting/__init__.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-accounting/celerp_accounting/__init__.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-accounting/celerp_accounting/api_setup.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-accounting/celerp_accounting/csv_parser.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-accounting/celerp_accounting/matcher.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-accounting/celerp_accounting/models.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-accounting/celerp_accounting/projections.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-accounting/celerp_accounting/routes.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-accounting/celerp_accounting/search.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-accounting/celerp_accounting/ui_routes.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-accounting/tests/report_goldens/balance-sheet.html +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-accounting/tests/report_goldens/cash-flow.html +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-accounting/tests/report_goldens/extended-journal-csv.csv +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-accounting/tests/report_goldens/extended-journal-print.html +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-accounting/tests/report_goldens/extended-journal.html +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-accounting/tests/report_goldens/general-ledger.html +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-accounting/tests/report_goldens/journal-csv.csv +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-accounting/tests/report_goldens/journal-print.html +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-accounting/tests/report_goldens/journal.html +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-accounting/tests/report_goldens/ledger.html +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-accounting/tests/report_goldens/pnl.html +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-accounting/tests/report_goldens/statement.html +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-accounting/tests/report_goldens/trial-balance.html +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-accounting/tests/test_accounting_banking.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-accounting/tests/test_accounting_banking_routers.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-accounting/tests/test_accounting_gaps.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-accounting/tests/test_cash_flow.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-accounting/tests/test_financial_reports_ui.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-accounting/tests/test_journal_gl_soa.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-accounting/tests/test_journal_gl_soa_ui.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-accounting/tests/test_reconciliation_v2.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-accounting/tests/test_reports_end_to_end.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-accounting/tests/test_route_permissions.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-admin/__init__.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-admin/celerp_admin/__init__.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-admin/celerp_admin/routes.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-admin/celerp_admin/setup.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-ai/__init__.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-ai/celerp_ai/__init__.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-ai/celerp_ai/setup.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-ai/tests/conftest.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-ai/tests/test_batch.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-ai/tests/test_cleanup.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-ai/tests/test_conversations.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-ai/tests/test_coverage_gaps.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-ai/tests/test_intent.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-ai/tests/test_llm.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-ai/tests/test_memory.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-ai/tests/test_models.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-ai/tests/test_page_count.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-ai/tests/test_quota.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-ai/tests/test_router.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-ai/tests/test_routes_coverage.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-ai/tests/test_service.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-ai/tests/test_tools.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-backup/__init__.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-backup/celerp_backup/__init__.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-backup/celerp_backup/routes.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-backup/celerp_backup/setup.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-connectors/__init__.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-connectors/celerp_connectors/__init__.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-connectors/celerp_connectors/models.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-connectors/celerp_connectors/routes.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-connectors/tests/__init__.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-connectors/tests/conftest.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-connectors/tests/test_compliance.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-connectors/tests/test_connector_correctness.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-connectors/tests/test_connectors.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-connectors/tests/test_credentials_routes.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-connectors/tests/test_images.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-connectors/tests/test_outbound.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-connectors/tests/test_quickbooks.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-connectors/tests/test_rate_limiting.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-connectors/tests/test_shopify.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-connectors/tests/test_wiring.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-connectors/tests/test_woocommerce.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-connectors/tests/test_woocommerce_webhooks.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-connectors/tests/test_xero.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-contacts/LICENSE +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-contacts/__init__.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-contacts/celerp_contacts/__init__.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-contacts/celerp_contacts/migrations.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-contacts/celerp_contacts/projections.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-contacts/celerp_contacts/routes.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-contacts/celerp_contacts/search.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-contacts/celerp_contacts/services.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-contacts/celerp_contacts/ui_routes.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-dashboard/LICENSE +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-dashboard/__init__.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-dashboard/celerp_dashboard/__init__.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-dashboard/celerp_dashboard/routes.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-dashboard/celerp_dashboard/setup.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-docs/LICENSE +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-docs/__init__.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-docs/celerp_docs/__init__.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-docs/celerp_docs/api_setup.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-docs/celerp_docs/doc_constants.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-docs/celerp_docs/doc_email.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-docs/celerp_docs/doc_projections.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-docs/celerp_docs/doc_service.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-docs/celerp_docs/models_share.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-docs/celerp_docs/routes_payments.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-docs/celerp_docs/routes_share.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-docs/celerp_docs/search.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-docs/celerp_docs/sequences.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-docs/celerp_docs/taxes.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-docs/celerp_docs/ui_routes.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-docs/tests/conftest.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_bulk_payment_alloc_lock.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_consolidate_lots.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_cross_lot_settlement.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_freight_je.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_freight_lines.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_fulfill_no_split.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_fulfillment_activity.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_inbound_je_lifecycle.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_inventory_account_reconciliation.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_landed_receive.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_line_delete_persist.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_line_identifier.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_list_concurrency.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_list_outcome_concurrency.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_memo_close.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_memo_close_projection.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_memo_close_review_fixes.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_memo_lifecycle_concurrency.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_memo_line_labels.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_memo_unfulfilled_filter.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_payment_void_activity.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_receive_sku_batch.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_send_visibility.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_service_lines.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_sold_label_ordering.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_void_guards.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-docs/tests/test_writeoffs.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-inventory/LICENSE +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-inventory/__init__.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-inventory/celerp_inventory/__init__.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-inventory/celerp_inventory/models_import_batch.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-inventory/celerp_inventory/routes_attachments.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-inventory/celerp_inventory/search.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-inventory/celerp_inventory/ui_routes.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-inventory/tests/test_item_recipe_projection.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-inventory/tests/test_item_workflow_projection.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-inventory/tests/test_landed_cost_projection.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-labels/LICENSE +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-labels/__init__.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-labels/celerp_labels/__init__.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-labels/celerp_labels/locales/am.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-labels/celerp_labels/locales/ar.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-labels/celerp_labels/locales/de.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-labels/celerp_labels/locales/en.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-labels/celerp_labels/locales/es.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-labels/celerp_labels/locales/fr.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-labels/celerp_labels/locales/id.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-labels/celerp_labels/locales/it.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-labels/celerp_labels/locales/ja.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-labels/celerp_labels/locales/pt.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-labels/celerp_labels/locales/th.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-labels/celerp_labels/locales/vi.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-labels/celerp_labels/models.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-labels/celerp_labels/presets.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-labels/celerp_labels/routes.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-labels/celerp_labels/service.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-labels/celerp_labels/ui_routes.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-labels/requirements.txt +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-labels/tests/test_labels.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-labels/tests/test_labels_module.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-manufacturing/LICENSE +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-manufacturing/__init__.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-manufacturing/celerp_manufacturing/__init__.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-manufacturing/celerp_manufacturing/costing.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-manufacturing/celerp_manufacturing/expansion.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-manufacturing/celerp_manufacturing/labor.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-manufacturing/celerp_manufacturing/migrations.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-manufacturing/celerp_manufacturing/projection_handler.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-manufacturing/celerp_manufacturing/routes.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-manufacturing/celerp_manufacturing/search.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-manufacturing/celerp_manufacturing/ui_routes.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-manufacturing/requirements.txt +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-manufacturing/tests/test_auto_complete_on_finalize.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-manufacturing/tests/test_bom_removed.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-manufacturing/tests/test_build_api.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-manufacturing/tests/test_completion_lock_order.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-manufacturing/tests/test_item_recipe_api.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-manufacturing/tests/test_item_workflow_api.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-manufacturing/tests/test_labor_providers.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-manufacturing/tests/test_manufacture_from_doc.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-manufacturing/tests/test_manufacturing.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-manufacturing/tests/test_manufacturing_module.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-manufacturing/tests/test_mfg_events.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-manufacturing/tests/test_phase2_manufacturing_validations.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-manufacturing/tests/test_production_order.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-manufacturing/tests/test_recipe_costing.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-manufacturing/tests/test_recipe_expansion.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-manufacturing/tests/test_recost_api.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-manufacturing/tests/test_run_records.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-manufacturing/tests/test_to_make_allocation.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-manufacturing/tests/test_where_used.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-manufacturing/tests/test_workflow_time.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-reports/LICENSE +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-reports/__init__.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-reports/celerp_reports/__init__.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-reports/celerp_reports/api_setup.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-reports/celerp_reports/routes.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-reports/celerp_reports/ui_routes.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-reports/tests/test_reports_full.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-reports/tests/test_reports_unit.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-subscriptions/LICENSE +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-subscriptions/__init__.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-subscriptions/celerp_subscriptions/__init__.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-subscriptions/celerp_subscriptions/routes.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-subscriptions/celerp_subscriptions/search.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-subscriptions/celerp_subscriptions/ui_routes.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-subscriptions/tests/test_subscriptions.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-subscriptions/tests/test_subscriptions_ui.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/LICENSE +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/__init__.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/__init__.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/accessory_fashion.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/activewear.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/art_photography.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/audio_equipment.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/bag_handbag.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/banknote.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/beauty_tool.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/bed_bedroom.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/beer.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/beverage_nonalc.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/body_part_auto.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/book.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/bottoms.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/brake_suspension.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/bullion_coin.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/camera.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/colored_stone.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/comic.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/commercial_space.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/component_part.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/confectionery.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/consulting_service.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/decorative_art.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/diamond.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/drawing.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/dress_jumpsuit.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/electrical_auto.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/electrical_component.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/emerald.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/engine_part.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/fastener.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/fertilizer_chemical.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/film_video.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/fine_writing_instrument.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/fluid_lubricant.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/footwear.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/fragrance.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/fresh_food.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/fresh_produce.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/frozen_food.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/gaming_console.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/gold_bullion.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/grain_cereal.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/haircare.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/hand_tool.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/ingredient_bulk.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/interior_part.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/jewelry.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/kitchen_dining.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/laptop.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/lighting.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/livestock_feed.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/makeup.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/measuring_instrument.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/medal_token.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/mineral_specimen.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/mobile_phone.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/music_cd.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/music_vinyl.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/nail.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/numismatic_coin.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/outdoor_furniture.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/outerwear.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/packaged_food.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/painting.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/parking_bay.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/pearl.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/personal_care.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/pipe_plumbing.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/platinum_bullion.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/power_tool.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/print_edition.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/residential_unit.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/rough_gemstone.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/ruby.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/saas_plan.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/safety_ppe.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/sapphire.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/sculpture.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/seating.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/seeds.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/silver_bullion.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/skincare.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/soft_furnishing.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/software_addon.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/software_license.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/spirit.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/storage_furniture.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/swimwear.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/table_desk.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/tablet.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/tire_wheel.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/tops.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/trading_card.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/tv_display.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/video_game.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/watch.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/watch_strap.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/categories/wine.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/presets/agricultural.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/presets/artwork.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/presets/automotive.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/presets/blank.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/presets/books_media.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/presets/coins_precious_metals.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/presets/consulting.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/presets/cosmetics.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/presets/electronics.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/presets/fashion.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/presets/food_beverage.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/presets/furniture.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/presets/gemstones.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/presets/hardware.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/presets/property_rental.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/presets/saas.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/presets/watches.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/presets/wine_spirits.json +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/routes.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/celerp_verticals/ui_routes.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/tests/test_verticals.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/default_modules/celerp-verticals/tests/test_verticals_routers.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/legal/OFFICIAL-COMPONENTS.md +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/legal/TRADEMARK.md +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/packaging/winget/Celerp.Celerp.installer.yaml +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/packaging/winget/Celerp.Celerp.locale.en-US.yaml +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/packaging/winget/Celerp.Celerp.yaml +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/packaging/winget/README.md +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/pyproject.toml +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/setup.cfg +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/ui/__init__.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/ui/components/__init__.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/ui/components/activity.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/ui/components/attrs.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/ui/components/backup.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/ui/components/currency.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/ui/components/files.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/ui/components/journal.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/ui/components/notes.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/ui/components/phone.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/ui/components/report_kit.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/ui/components/shell.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/ui/components/table.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/ui/i18n.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/ui/marketplace_catalog.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/ui/routes/__init__.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/ui/routes/accounting.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/ui/routes/accounting_import.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/ui/routes/csv_import.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/ui/routes/docs_import.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/ui/routes/events.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/ui/routes/financial_reports.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/ui/routes/lists_import.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/ui/routes/manufacturing.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/ui/routes/reconciliation.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/ui/routes/reports.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/ui/routes/search.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/ui/routes/settings_accounting.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/ui/routes/settings_contacts.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/ui/routes/settings_general.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/ui/routes/settings_import.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/ui/routes/settings_inventory.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/ui/routes/settings_manufacturing.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/ui/routes/settings_purchasing.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/ui/routes/settings_sales.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/ui/routes/stars.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/ui/routes/subscriptions_import.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/ui/security.py +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/ui/static/htmx.min.js +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/ui/static/icon.png +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/ui/static/logo.png +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/ui/static/vendor/intl-tel-input/img/flags.webp +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/ui/static/vendor/intl-tel-input/img/flags@2x.webp +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/ui/static/vendor/intl-tel-input/intlTelInput.min.css +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/ui/static/vendor/intl-tel-input/intlTelInput.min.js +0 -0
- {celerp-2.3.5 → celerp-2.4.0}/ui/static/vendor/intl-tel-input/intlTelInputWithUtils.min.js +0 -0
- {celerp-2.3.5 → 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()
|
|
@@ -11,7 +11,9 @@ from sqlalchemy.exc import IntegrityError
|
|
|
11
11
|
|
|
12
12
|
from celerp.events.schemas import EVENT_SCHEMA_MAP
|
|
13
13
|
from celerp.models.ledger import LedgerEntry
|
|
14
|
+
from celerp.models.projections import Projection
|
|
14
15
|
from celerp.projections.engine import ProjectionEngine
|
|
16
|
+
from celerp.services.document_lines import assert_document_item_uniqueness
|
|
15
17
|
|
|
16
18
|
|
|
17
19
|
def apply_event(state: dict, event: LedgerEntry) -> dict:
|
|
@@ -126,9 +128,51 @@ async def emit_event(session, **kwargs) -> LedgerEntry:
|
|
|
126
128
|
|
|
127
129
|
schema(**kwargs["data"])
|
|
128
130
|
|
|
131
|
+
# The schema validates a Pydantic-made copy, so any code canonicalization it applies
|
|
132
|
+
# (rfid_epc -> trimmed upper-case, gtin -> validated form) never reaches the raw dict
|
|
133
|
+
# persisted below. Apply the same normalization to the dict that is actually stored, so
|
|
134
|
+
# the stored value equals what the availability check and the partial unique index
|
|
135
|
+
# compare against - otherwise a case-variant identifier is stored raw and never collides.
|
|
136
|
+
_normalize = getattr(schema, "normalize_for_storage", None)
|
|
137
|
+
if _normalize is not None:
|
|
138
|
+
_normalize(kwargs["data"])
|
|
139
|
+
|
|
129
140
|
# Enforce period lock
|
|
130
141
|
await _check_period_lock(session, kwargs.get("company_id"), kwargs.get("data", {}))
|
|
131
142
|
|
|
143
|
+
# Enforce physical-item uniqueness on new OUTBOUND doc writes (invoice, memo).
|
|
144
|
+
# Extract the post-change line set by DATA SHAPE so every doc writer (create,
|
|
145
|
+
# patch, shared_import, update, conversion, import) is covered by one rule,
|
|
146
|
+
# keyed on entity_type == "doc" rather than an event list. The doc-type scope
|
|
147
|
+
# lives in assert_document_item_uniqueness beside the invariant; this boundary
|
|
148
|
+
# only resolves the doc_type to hand it. Rebuild/replay applies events via
|
|
149
|
+
# apply_event, never emit_event, so historical events are never re-validated.
|
|
150
|
+
if kwargs.get("entity_type") == "doc":
|
|
151
|
+
data = kwargs.get("data") or {}
|
|
152
|
+
line_set = None
|
|
153
|
+
if isinstance(data.get("line_items"), list):
|
|
154
|
+
line_set = data["line_items"]
|
|
155
|
+
elif isinstance(data.get("fields_changed"), dict):
|
|
156
|
+
changed = data["fields_changed"].get("line_items")
|
|
157
|
+
if isinstance(changed, dict):
|
|
158
|
+
line_set = changed.get("new")
|
|
159
|
+
if line_set is not None:
|
|
160
|
+
# Prefer the event's own doc_type (present on doc.created and any update
|
|
161
|
+
# that carries it - zero query). Otherwise resolve it from the persisted
|
|
162
|
+
# projection, reading state["doc_type"] only when that projection is a doc
|
|
163
|
+
# (the Projection PK is (company_id, entity_id) with no type discriminator,
|
|
164
|
+
# so the entity_type check guards against a same-id non-doc projection).
|
|
165
|
+
doc_type = data.get("doc_type")
|
|
166
|
+
if doc_type is None:
|
|
167
|
+
proj = await session.get(
|
|
168
|
+
Projection, (kwargs.get("company_id"), kwargs.get("entity_id"))
|
|
169
|
+
)
|
|
170
|
+
if proj is not None and proj.entity_type == "doc":
|
|
171
|
+
doc_type = (proj.state or {}).get("doc_type")
|
|
172
|
+
await assert_document_item_uniqueness(
|
|
173
|
+
session, kwargs.get("company_id"), doc_type, line_set
|
|
174
|
+
)
|
|
175
|
+
|
|
132
176
|
entry = LedgerEntry(**kwargs)
|
|
133
177
|
|
|
134
178
|
try:
|