celerp 2.3.2__tar.gz → 2.3.4__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.2 → celerp-2.3.4}/PKG-INFO +1 -1
- celerp-2.3.4/celerp/db.py +136 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/events/schemas.py +11 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/gateway/client.py +225 -70
- {celerp-2.3.2 → celerp-2.3.4}/celerp/main.py +11 -10
- celerp-2.3.4/celerp/migrations/versions/c9d8e7f6a5b4_seed_shrinkage_writeoff_account.py +65 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/modules/migrations_runner.py +33 -29
- {celerp-2.3.2 → celerp-2.3.4}/celerp/routers/doctor.py +8 -3
- {celerp-2.3.2 → celerp-2.3.4}/celerp/routers/ledger.py +15 -5
- {celerp-2.3.2 → celerp-2.3.4}/celerp/services/auto_je.py +2 -2
- {celerp-2.3.2 → celerp-2.3.4}/celerp/services/cost_visibility.py +17 -1
- {celerp-2.3.2 → celerp-2.3.4}/celerp/services/field_schema.py +20 -1
- {celerp-2.3.2 → celerp-2.3.4}/celerp/services/system_health.py +19 -8
- {celerp-2.3.2 → celerp-2.3.4}/celerp.egg-info/PKG-INFO +1 -1
- {celerp-2.3.2 → celerp-2.3.4}/celerp.egg-info/SOURCES.txt +13 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp.egg-info/scm_file_list.json +36 -0
- celerp-2.3.4/celerp.egg-info/scm_version.json +8 -0
- {celerp-2.3.2 → celerp-2.3.4}/conftest.py +15 -1
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-accounting/celerp_accounting/routes.py +1 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-accounting/tests/report_goldens/statement.html +1 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-admin/celerp_admin/routes.py +34 -29
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-docs/celerp_docs/doc_constants.py +3 -2
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-docs/celerp_docs/doc_projections.py +22 -2
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-docs/celerp_docs/routes.py +1147 -392
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-docs/celerp_docs/routes_payments.py +5 -5
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-docs/tests/test_audits.py +150 -3
- celerp-2.3.4/default_modules/celerp-docs/tests/test_bulk_payment_alloc_lock.py +502 -0
- celerp-2.3.4/default_modules/celerp-docs/tests/test_cross_lot_settlement.py +168 -0
- celerp-2.3.4/default_modules/celerp-docs/tests/test_line_page_patch_truncate.py +245 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-docs/tests/test_list_concurrency.py +115 -2
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-docs/tests/test_list_outcome_concurrency.py +268 -0
- celerp-2.3.4/default_modules/celerp-docs/tests/test_list_page_no_full_state.py +151 -0
- celerp-2.3.4/default_modules/celerp-docs/tests/test_list_query_pushdown.py +399 -0
- celerp-2.3.4/default_modules/celerp-docs/tests/test_memo_close.py +176 -0
- celerp-2.3.4/default_modules/celerp-docs/tests/test_memo_close_projection.py +26 -0
- celerp-2.3.4/default_modules/celerp-docs/tests/test_memo_close_review_fixes.py +450 -0
- celerp-2.3.4/default_modules/celerp-docs/tests/test_memo_lifecycle_concurrency.py +992 -0
- celerp-2.3.4/default_modules/celerp-docs/tests/test_memo_line_labels.py +191 -0
- celerp-2.3.4/default_modules/celerp-docs/tests/test_memo_unfulfilled_filter.py +47 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-docs/tests/test_send_visibility.py +16 -0
- celerp-2.3.4/default_modules/celerp-docs/tests/test_sold_label_ordering.py +95 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-docs/tests/test_writeoffs.py +303 -1
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-inventory/celerp_inventory/routes.py +390 -49
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-labels/celerp_labels/routes.py +5 -2
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/first_party.lock.json +5 -5
- {celerp-2.3.2 → celerp-2.3.4}/ui/api_client.py +199 -8
- {celerp-2.3.2 → celerp-2.3.4}/ui/app.py +8 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/components/shell.py +93 -13
- {celerp-2.3.2 → celerp-2.3.4}/ui/locales/am.json +18 -7
- {celerp-2.3.2 → celerp-2.3.4}/ui/locales/ar.json +18 -7
- {celerp-2.3.2 → celerp-2.3.4}/ui/locales/de.json +18 -7
- {celerp-2.3.2 → celerp-2.3.4}/ui/locales/en.json +19 -8
- {celerp-2.3.2 → celerp-2.3.4}/ui/locales/es.json +18 -7
- {celerp-2.3.2 → celerp-2.3.4}/ui/locales/fr.json +18 -7
- {celerp-2.3.2 → celerp-2.3.4}/ui/locales/id.json +18 -7
- {celerp-2.3.2 → celerp-2.3.4}/ui/locales/it.json +18 -7
- {celerp-2.3.2 → celerp-2.3.4}/ui/locales/ja.json +18 -7
- {celerp-2.3.2 → celerp-2.3.4}/ui/locales/pt.json +18 -7
- {celerp-2.3.2 → celerp-2.3.4}/ui/locales/th.json +18 -7
- {celerp-2.3.2 → celerp-2.3.4}/ui/locales/vi.json +18 -7
- {celerp-2.3.2 → celerp-2.3.4}/ui/routes/documents.py +403 -169
- {celerp-2.3.2 → celerp-2.3.4}/ui/routes/settings.py +6 -2
- {celerp-2.3.2 → celerp-2.3.4}/ui/routes/settings_cloud.py +0 -12
- {celerp-2.3.2 → celerp-2.3.4}/ui/static/app.css +1 -1
- celerp-2.3.2/celerp/db.py +0 -61
- celerp-2.3.2/celerp.egg-info/scm_version.json +0 -8
- {celerp-2.3.2 → celerp-2.3.4}/CLA.md +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/CONTRIBUTING.md +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/LICENSE +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/LICENSING.md +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/MANIFEST.in +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/NOTICE +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/README.md +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/alembic.ini +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/__init__.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/__main__.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/ai/__init__.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/ai/batch.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/ai/cleanup.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/ai/commands.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/ai/conversations.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/ai/files.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/ai/intent.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/ai/llm.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/ai/memory.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/ai/models.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/ai/page_count.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/ai/quota.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/ai/service.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/ai/tools.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/alembic.ini +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/alembic_config.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/cli.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/compat.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/compute/__init__.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/compute/aggregations.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/compute/valuation.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/config.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/connectors/__init__.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/connectors/base.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/connectors/daily_scheduler.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/connectors/http.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/connectors/images.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/connectors/quickbooks.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/connectors/registry.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/connectors/relay_token.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/connectors/shopify.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/connectors/sync_runner.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/connectors/upsert.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/connectors/util.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/connectors/webhooks.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/connectors/woocommerce.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/connectors/xero.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/constants.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/embedded_pg.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/events/__init__.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/events/engine.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/events/types.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/gateway/__init__.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/gateway/state.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/importers/__init__.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/importers/importer.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/importers/schema.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/inventory_codes.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/middleware.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/migrations/README.md +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/migrations/_auto_stamp.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/migrations/_data_reconcile.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/migrations/_json_compat.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/migrations/env.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/migrations/script.py.mako +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/migrations/versions/a0b1c2d3e4f5_seed_landed_cost_clearing_accounts.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/migrations/versions/a1b2c3d4e5f6_add_doc_share_tokens.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/migrations/versions/b1c2d3e4f5a6_add_supporter_badges.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/migrations/versions/b2c3d4e5f6a7_add_import_batches.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/migrations/versions/b3c4d5e6f7a8_expand_role_permissions_to_role_grants.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/migrations/versions/bc0d1e2f3a4b_barcode_unique_index.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/migrations/versions/c1d2e3f4a5b6_add_marketplace.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/migrations/versions/c2d3e4f5a6b7_add_is_sync_to_shopify.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/migrations/versions/c4d5e6f7a8b9_account_cash_flow_category.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/migrations/versions/d0e1f2a3b4c5_add_share_token_expiry.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/migrations/versions/d2e3f4a5b6c7_add_password_reset_token.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/migrations/versions/d5e6f7a8b9c0_rename_fx_rounding_to_fx_difference.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/migrations/versions/e3f4a5b6c7d8_add_company_is_active.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/migrations/versions/e4f5a6b7c8d9_backfill_connector_idempotency_key.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/migrations/versions/e7c9a1b3d5f2_work_center_default_columns.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/migrations/versions/f1a2b3c4d5e6_move_accounts_marketplace_to_modules.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/migrations/versions/f6a7b8c9d0e1_seed_fx_rounding_account.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/migrations/versions/f8a9b0c1d2e3_work_center_default_backfill.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/migrations/versions/fd5de461e14e_initial_schema.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/migrations/versions/g1h2i3j4k5l6_add_consignment_flag.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/migrations/versions/h2i3j4k5l6m7_add_bank_accounts_and_reconciliation.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/migrations/versions/j4k5l6m7n8o9_labels_db_persistence.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/migrations/versions/k5l6m7n8o9p0_add_reconciliation_v2.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/migrations/versions/l6m7n8o9p0q1_add_sync_runs_table.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/migrations/versions/m7n8o9p0q1r2_add_connector_configs_and_outbound_queue.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/migrations/versions/n8o9p0q1r2s3_backfill_payment_date.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/migrations/versions/o9p0q1r2s3t4_backfill_item_price_fields.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/migrations/versions/p1q2r3s4t5u6_add_session_registry_and_user_auth_state.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/migrations/versions/q2r3s4t5u6v7_add_system_runtime_state.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/migrations/versions/r3s4t5u6v7w8_add_projections_company_entity_type_index.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/migrations/versions/s4t5u6v7w8x9_drop_user_company_id_role.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/migrations/versions/t5u6v7w8x9y0_seed_inventory_sub_accounts.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/migrations/versions/u6v7w8x9y0z1_backfill_item_prices_from_attributes.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/migrations/versions/v7w8x9y0z1a2_backfill_purchase_unit_defaults.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/migrations/versions/w8x9y0z1a2b3_backfill_category_unit_defaults.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/migrations/versions/x7y8z9a0b1c2_add_projection_created_at.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/migrations/versions/y8z9a0b1c2d3_per_company_ledger_idempotency.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/migrations/versions/z9a0b1c2d3e4_work_centers.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/models/__init__.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/models/accounting.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/models/ai.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/models/auth.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/models/base.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/models/company.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/models/connector_config.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/models/import_batch.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/models/ledger.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/models/marketplace.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/models/notification.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/models/projections.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/models/share.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/models/supporter.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/models/sync_run.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/modules/__init__.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/modules/api.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/modules/audit.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/modules/demotion.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/modules/importer.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/modules/license.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/modules/loader.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/modules/meta.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/modules/references.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/modules/registry.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/modules/slots.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/notifications/__init__.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/notifications/service.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/notifications/sse.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/output/__init__.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/output/branding.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/output/doc_print.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/output/pdf.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/output/share_render.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/projections/__init__.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/projections/engine.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/projections/handlers/__init__.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/projections/handlers/marketplace.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/projections/handlers/scanning.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/projections/handlers/shopify.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/projections/handlers/system.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/routers/__init__.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/routers/auth.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/routers/companies.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/routers/debug.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/routers/events.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/routers/health.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/routers/notifications.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/routers/stars.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/routers/system.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/schemas/__init__.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/services/__init__.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/services/activity_redaction.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/services/attachments.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/services/auth.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/services/backup.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/services/backup_export.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/services/backup_import.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/services/backup_repo.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/services/backup_scheduler.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/services/backup_state.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/services/cogs_backfill.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/services/demo.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/services/dev_release_guard.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/services/email.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/services/fulfill.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/services/holdings.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/services/je_keys.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/services/landed_cost.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/services/line_measures.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/services/list_behavior.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/services/money.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/services/payments.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/services/permissions.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/services/pick.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/services/pricing.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/services/relay_share.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/services/reorder.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/services/runtime_state.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/services/session_tracker.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/services/shipping.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/services/star_cta.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/services/status_doc_backfill.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/services/supporter_badge.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/services/units.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/session_gate.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp/tax_regimes.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp.egg-info/dependency_links.txt +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp.egg-info/entry_points.txt +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp.egg-info/requires.txt +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/celerp.egg-info/top_level.txt +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/conftest_support.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/__init__.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-accounting/LICENSE +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-accounting/__init__.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-accounting/celerp_accounting/__init__.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-accounting/celerp_accounting/api_setup.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-accounting/celerp_accounting/csv_parser.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-accounting/celerp_accounting/matcher.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-accounting/celerp_accounting/models.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-accounting/celerp_accounting/projections.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-accounting/celerp_accounting/ui_routes.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-accounting/tests/report_goldens/balance-sheet.html +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-accounting/tests/report_goldens/cash-flow.html +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-accounting/tests/report_goldens/extended-journal-csv.csv +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-accounting/tests/report_goldens/extended-journal-print.html +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-accounting/tests/report_goldens/extended-journal.html +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-accounting/tests/report_goldens/general-ledger.html +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-accounting/tests/report_goldens/journal-csv.csv +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-accounting/tests/report_goldens/journal-print.html +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-accounting/tests/report_goldens/journal.html +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-accounting/tests/report_goldens/ledger.html +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-accounting/tests/report_goldens/pnl.html +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-accounting/tests/report_goldens/trial-balance.html +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-accounting/tests/test_accounting_banking.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-accounting/tests/test_accounting_banking_routers.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-accounting/tests/test_accounting_gaps.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-accounting/tests/test_accounting_import_idempotency.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-accounting/tests/test_cash_flow.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-accounting/tests/test_financial_reports_ui.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-accounting/tests/test_journal_gl_soa.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-accounting/tests/test_journal_gl_soa_ui.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-accounting/tests/test_reconciliation_v2.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-accounting/tests/test_reports_end_to_end.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-accounting/tests/test_route_permissions.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-admin/__init__.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-admin/celerp_admin/__init__.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-admin/celerp_admin/setup.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-ai/__init__.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-ai/celerp_ai/__init__.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-ai/celerp_ai/routes.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-ai/celerp_ai/setup.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-ai/celerp_ai/ui_routes.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-ai/tests/conftest.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-ai/tests/test_batch.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-ai/tests/test_cleanup.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-ai/tests/test_conversations.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-ai/tests/test_coverage_gaps.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-ai/tests/test_intent.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-ai/tests/test_llm.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-ai/tests/test_memory.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-ai/tests/test_models.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-ai/tests/test_page_count.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-ai/tests/test_quota.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-ai/tests/test_router.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-ai/tests/test_routes_coverage.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-ai/tests/test_service.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-ai/tests/test_tools.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-backup/__init__.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-backup/celerp_backup/__init__.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-backup/celerp_backup/routes.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-backup/celerp_backup/setup.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-connectors/__init__.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-connectors/celerp_connectors/__init__.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-connectors/celerp_connectors/models.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-connectors/celerp_connectors/routes.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-connectors/tests/__init__.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-connectors/tests/conftest.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-connectors/tests/test_compliance.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-connectors/tests/test_connector_correctness.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-connectors/tests/test_connectors.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-connectors/tests/test_credentials_routes.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-connectors/tests/test_images.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-connectors/tests/test_outbound.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-connectors/tests/test_quickbooks.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-connectors/tests/test_rate_limiting.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-connectors/tests/test_shopify.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-connectors/tests/test_wiring.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-connectors/tests/test_woocommerce.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-connectors/tests/test_woocommerce_webhooks.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-connectors/tests/test_xero.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-contacts/LICENSE +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-contacts/__init__.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-contacts/celerp_contacts/__init__.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-contacts/celerp_contacts/migrations.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-contacts/celerp_contacts/projections.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-contacts/celerp_contacts/routes.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-contacts/celerp_contacts/services.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-contacts/celerp_contacts/ui_routes.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-dashboard/LICENSE +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-dashboard/__init__.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-dashboard/celerp_dashboard/__init__.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-dashboard/celerp_dashboard/routes.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-dashboard/celerp_dashboard/setup.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-docs/LICENSE +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-docs/__init__.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-docs/celerp_docs/__init__.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-docs/celerp_docs/api_setup.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-docs/celerp_docs/doc_email.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-docs/celerp_docs/doc_service.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-docs/celerp_docs/models_share.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-docs/celerp_docs/routes_share.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-docs/celerp_docs/sequences.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-docs/celerp_docs/taxes.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-docs/celerp_docs/ui_routes.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-docs/tests/conftest.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-docs/tests/test_consolidate_lots.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-docs/tests/test_freight_je.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-docs/tests/test_freight_lines.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-docs/tests/test_fulfill_no_split.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-docs/tests/test_fulfillment_activity.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-docs/tests/test_inbound_je_lifecycle.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-docs/tests/test_inventory_account_reconciliation.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-docs/tests/test_landed_receive.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-docs/tests/test_line_delete_persist.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-docs/tests/test_line_identifier.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-docs/tests/test_payment_void_activity.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-docs/tests/test_receive_sku_batch.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-docs/tests/test_service_lines.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-docs/tests/test_void_guards.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-inventory/LICENSE +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-inventory/__init__.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-inventory/celerp_inventory/__init__.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-inventory/celerp_inventory/models_import_batch.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-inventory/celerp_inventory/projections.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-inventory/celerp_inventory/routes_attachments.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-inventory/celerp_inventory/routes_scanning.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-inventory/celerp_inventory/services.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-inventory/celerp_inventory/ui_routes.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-inventory/tests/test_item_recipe_projection.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-inventory/tests/test_item_workflow_projection.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-inventory/tests/test_landed_cost_projection.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-labels/LICENSE +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-labels/__init__.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-labels/celerp_labels/__init__.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-labels/celerp_labels/locales/am.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-labels/celerp_labels/locales/ar.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-labels/celerp_labels/locales/de.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-labels/celerp_labels/locales/en.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-labels/celerp_labels/locales/es.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-labels/celerp_labels/locales/fr.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-labels/celerp_labels/locales/id.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-labels/celerp_labels/locales/it.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-labels/celerp_labels/locales/ja.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-labels/celerp_labels/locales/pt.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-labels/celerp_labels/locales/th.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-labels/celerp_labels/locales/vi.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-labels/celerp_labels/models.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-labels/celerp_labels/presets.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-labels/celerp_labels/service.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-labels/celerp_labels/ui_routes.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-labels/requirements.txt +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-labels/tests/test_labels.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-labels/tests/test_labels_module.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-manufacturing/LICENSE +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-manufacturing/__init__.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-manufacturing/celerp_manufacturing/__init__.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-manufacturing/celerp_manufacturing/costing.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-manufacturing/celerp_manufacturing/expansion.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-manufacturing/celerp_manufacturing/labor.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-manufacturing/celerp_manufacturing/migrations.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-manufacturing/celerp_manufacturing/projection_handler.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-manufacturing/celerp_manufacturing/routes.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-manufacturing/celerp_manufacturing/ui_routes.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-manufacturing/requirements.txt +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-manufacturing/tests/test_auto_complete_on_finalize.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-manufacturing/tests/test_bom_removed.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-manufacturing/tests/test_build_api.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-manufacturing/tests/test_completion_lock_order.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-manufacturing/tests/test_item_recipe_api.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-manufacturing/tests/test_item_workflow_api.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-manufacturing/tests/test_labor_providers.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-manufacturing/tests/test_manufacture_from_doc.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-manufacturing/tests/test_manufacturing.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-manufacturing/tests/test_manufacturing_import_idempotency.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-manufacturing/tests/test_manufacturing_module.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-manufacturing/tests/test_mfg_events.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-manufacturing/tests/test_phase2_manufacturing_validations.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-manufacturing/tests/test_production_order.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-manufacturing/tests/test_recipe_costing.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-manufacturing/tests/test_recipe_expansion.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-manufacturing/tests/test_recost_api.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-manufacturing/tests/test_run_records.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-manufacturing/tests/test_to_make_allocation.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-manufacturing/tests/test_where_used.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-manufacturing/tests/test_work_centers_and_settings.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-manufacturing/tests/test_workflow_time.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-reports/LICENSE +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-reports/__init__.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-reports/celerp_reports/__init__.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-reports/celerp_reports/api_setup.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-reports/celerp_reports/routes.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-reports/celerp_reports/ui_routes.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-reports/tests/test_reports_full.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-reports/tests/test_reports_unit.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-subscriptions/LICENSE +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-subscriptions/__init__.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-subscriptions/celerp_subscriptions/__init__.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-subscriptions/celerp_subscriptions/routes.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-subscriptions/celerp_subscriptions/ui_routes.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-subscriptions/tests/test_subscriptions.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-subscriptions/tests/test_subscriptions_ui.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/LICENSE +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/__init__.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/__init__.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/accessory_fashion.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/activewear.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/art_photography.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/audio_equipment.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/bag_handbag.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/banknote.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/beauty_tool.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/bed_bedroom.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/beer.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/beverage_nonalc.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/body_part_auto.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/book.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/bottoms.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/brake_suspension.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/bullion_coin.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/camera.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/colored_stone.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/comic.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/commercial_space.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/component_part.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/confectionery.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/consulting_service.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/decorative_art.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/diamond.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/drawing.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/dress_jumpsuit.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/electrical_auto.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/electrical_component.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/emerald.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/engine_part.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/fastener.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/fertilizer_chemical.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/film_video.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/fine_writing_instrument.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/fluid_lubricant.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/footwear.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/fragrance.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/fresh_food.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/fresh_produce.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/frozen_food.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/gaming_console.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/gold_bullion.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/grain_cereal.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/haircare.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/hand_tool.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/ingredient_bulk.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/interior_part.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/jewelry.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/kitchen_dining.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/laptop.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/lighting.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/livestock_feed.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/makeup.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/measuring_instrument.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/medal_token.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/mineral_specimen.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/mobile_phone.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/music_cd.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/music_vinyl.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/nail.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/numismatic_coin.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/outdoor_furniture.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/outerwear.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/packaged_food.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/painting.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/parking_bay.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/pearl.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/personal_care.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/pipe_plumbing.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/platinum_bullion.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/power_tool.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/print_edition.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/residential_unit.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/rough_gemstone.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/ruby.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/saas_plan.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/safety_ppe.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/sapphire.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/sculpture.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/seating.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/seeds.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/silver_bullion.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/skincare.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/soft_furnishing.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/software_addon.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/software_license.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/spirit.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/storage_furniture.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/swimwear.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/table_desk.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/tablet.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/tire_wheel.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/tops.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/trading_card.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/tv_display.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/video_game.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/watch.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/watch_strap.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/categories/wine.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/presets/agricultural.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/presets/artwork.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/presets/automotive.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/presets/blank.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/presets/books_media.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/presets/coins_precious_metals.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/presets/consulting.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/presets/cosmetics.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/presets/electronics.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/presets/fashion.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/presets/food_beverage.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/presets/furniture.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/presets/gemstones.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/presets/hardware.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/presets/property_rental.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/presets/saas.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/presets/watches.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/presets/wine_spirits.json +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/routes.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/celerp_verticals/ui_routes.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/tests/test_verticals.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/default_modules/celerp-verticals/tests/test_verticals_routers.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/legal/OFFICIAL-COMPONENTS.md +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/legal/TRADEMARK.md +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/packaging/winget/Celerp.Celerp.installer.yaml +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/packaging/winget/Celerp.Celerp.locale.en-US.yaml +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/packaging/winget/Celerp.Celerp.yaml +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/packaging/winget/README.md +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/pyproject.toml +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/setup.cfg +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/test_helpers.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/__init__.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/components/__init__.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/components/activity.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/components/attrs.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/components/backup.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/components/cloud_gate.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/components/currency.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/components/files.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/components/journal.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/components/notes.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/components/phone.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/components/report_kit.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/components/table.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/config.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/i18n.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/marketplace_catalog.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/routes/__init__.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/routes/account.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/routes/accounting.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/routes/accounting_import.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/routes/auth.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/routes/contacts.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/routes/csv_import.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/routes/dashboard.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/routes/docs_import.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/routes/events.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/routes/financial_reports.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/routes/inventory.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/routes/lists_import.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/routes/manufacturing.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/routes/modules_page.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/routes/notifications.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/routes/reconciliation.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/routes/reports.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/routes/search.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/routes/settings_accounting.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/routes/settings_connectors.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/routes/settings_contacts.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/routes/settings_general.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/routes/settings_import.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/routes/settings_inventory.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/routes/settings_manufacturing.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/routes/settings_payments.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/routes/settings_purchasing.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/routes/settings_sales.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/routes/setup.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/routes/stars.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/routes/subscriptions.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/routes/subscriptions_import.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/security.py +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/static/htmx.min.js +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/static/icon.png +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/static/logo.png +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/static/vendor/intl-tel-input/img/flags.webp +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/static/vendor/intl-tel-input/img/flags@2x.webp +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/static/vendor/intl-tel-input/intlTelInput.min.css +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/static/vendor/intl-tel-input/intlTelInput.min.js +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/static/vendor/intl-tel-input/intlTelInputWithUtils.min.js +0 -0
- {celerp-2.3.2 → celerp-2.3.4}/ui/static/vendor/intl-tel-input/utils.js +0 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# Copyright (c) 2026 Noah Severs
|
|
2
|
+
# SPDX-License-Identifier: BUSL-1.1
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import re
|
|
6
|
+
from contextlib import asynccontextmanager
|
|
7
|
+
|
|
8
|
+
from sqlalchemy import text as _sql_text
|
|
9
|
+
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
|
|
10
|
+
from sqlalchemy.pool import NullPool
|
|
11
|
+
|
|
12
|
+
from celerp.config import settings
|
|
13
|
+
|
|
14
|
+
# Shared advisory-lock key that serialises schema migrations across every process
|
|
15
|
+
# that might run them (the CLI upgrade command and the API startup phase). One
|
|
16
|
+
# key, one source of truth, so two boots can never migrate the same database at
|
|
17
|
+
# once.
|
|
18
|
+
_MIGRATION_LOCK_KEY = 4207320001
|
|
19
|
+
|
|
20
|
+
# Redacts the password in any Postgres URL: "://user:secret@host" -> "://user:***@host".
|
|
21
|
+
# One authoritative masker for logs and surfaced errors so a connection string
|
|
22
|
+
# never leaks a password.
|
|
23
|
+
_DB_CREDENTIALS_RE = re.compile(r"(://[^:]+:)[^@]+(@)")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def mask_db_credentials(text: str) -> str:
|
|
27
|
+
"""Return text with any database URL password replaced by ``***``."""
|
|
28
|
+
return _DB_CREDENTIALS_RE.sub(r"\1***\2", text)
|
|
29
|
+
|
|
30
|
+
# Request-connection timeouts (ms). One source of truth: the engine sets them in
|
|
31
|
+
# server_settings, and lifecycle_timeouts_disabled restores exactly these values
|
|
32
|
+
# after clearing them for startup work.
|
|
33
|
+
_REQUEST_LOCK_TIMEOUT_MS = "3000"
|
|
34
|
+
_REQUEST_STATEMENT_TIMEOUT_MS = "30000"
|
|
35
|
+
|
|
36
|
+
# Pool budget: total_possible = (api_workers * (pool_size + max_overflow))
|
|
37
|
+
# + (gui_workers * (gui_pool_size + gui_max_overflow))
|
|
38
|
+
# Default workers 2 API + 1 GUI → 2*(10+5) + 1*(5+5) = 40 connections max.
|
|
39
|
+
# Postgres default max_connections=100 leaves 60 for migrations, admin tools, etc.
|
|
40
|
+
# If you increase worker counts, recalculate this budget before deploying.
|
|
41
|
+
if os.environ.get("CELERP_TEST_NULLPOOL"):
|
|
42
|
+
# Test mode: a fresh connection per use that closes on return, so a failed
|
|
43
|
+
# test can't leave a poisoned/locked connection lingering in a pooled
|
|
44
|
+
# connection and block the next test's TRUNCATE. Only ever set by the test
|
|
45
|
+
# harness; no effect in production.
|
|
46
|
+
engine = create_async_engine(settings.database_url, future=True, poolclass=NullPool)
|
|
47
|
+
else:
|
|
48
|
+
engine = create_async_engine(
|
|
49
|
+
settings.database_url,
|
|
50
|
+
future=True,
|
|
51
|
+
pool_pre_ping=True,
|
|
52
|
+
pool_size=10,
|
|
53
|
+
max_overflow=5,
|
|
54
|
+
# Bound how long any query may wait on a lock or run, so a stuck query is
|
|
55
|
+
# cancelled instead of pinning one of the few pooled connections for the
|
|
56
|
+
# full request lifetime (lock_timeout 3s, statement_timeout 30s, in ms).
|
|
57
|
+
# This is a per-connection default, so it applies to every request AND
|
|
58
|
+
# every pooled background job (connector syncs, the gateway, the daily
|
|
59
|
+
# scheduler) - all of which must stay bounded. Startup reconciliation and
|
|
60
|
+
# the projection rebuild are the only work that legitimately runs longer;
|
|
61
|
+
# they use lifecycle_engine below instead of this pool, and the migration
|
|
62
|
+
# advisory-lock wait self-exempts via lifecycle_timeouts_disabled.
|
|
63
|
+
connect_args={
|
|
64
|
+
"server_settings": {
|
|
65
|
+
"lock_timeout": _REQUEST_LOCK_TIMEOUT_MS,
|
|
66
|
+
"statement_timeout": _REQUEST_STATEMENT_TIMEOUT_MS,
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
)
|
|
70
|
+
SessionLocal = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
|
|
71
|
+
|
|
72
|
+
# Lifecycle/maintenance engine: a separate NullPool engine carrying NO statement
|
|
73
|
+
# or lock timeout. Startup reconciliation (create_all, module on_modules_ready
|
|
74
|
+
# seed hooks, the develop->release projection rebuild, the one-time status-doc and
|
|
75
|
+
# COGS backfills) and the on-demand projection rebuild routes run whole-table
|
|
76
|
+
# DELETEs and full-ledger replays that can legitimately exceed the request
|
|
77
|
+
# statement_timeout on a mature database. Running them on the bounded request pool
|
|
78
|
+
# would cancel them at 30s - permanently blocking boot's upgrade guard and the
|
|
79
|
+
# /ledger/rebuild recovery route. They run on this engine instead. NullPool: these
|
|
80
|
+
# operations are rare and brief, so no pooled slot is reserved and the request pool
|
|
81
|
+
# budget above is unaffected (at most one transient connection per concurrent
|
|
82
|
+
# lifecycle op).
|
|
83
|
+
lifecycle_engine = create_async_engine(settings.database_url, future=True, poolclass=NullPool)
|
|
84
|
+
LifecycleSessionLocal = async_sessionmaker(
|
|
85
|
+
lifecycle_engine, class_=AsyncSession, expire_on_commit=False
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@asynccontextmanager
|
|
90
|
+
async def lifecycle_timeouts_disabled(conn):
|
|
91
|
+
"""Clear the request statement/lock timeouts on the migration lock connection.
|
|
92
|
+
|
|
93
|
+
The migration advisory-lock wait runs on a connection drawn from the bounded
|
|
94
|
+
request engine (so a single engine serialises boots), but a second worker's
|
|
95
|
+
wait can legitimately exceed the 30s statement_timeout while the first worker
|
|
96
|
+
migrates, and a cancelled wait aborts the boot. This sets both timeouts to 0
|
|
97
|
+
(unbounded) for the block and restores the request defaults on exit, so the
|
|
98
|
+
connection returned to the pool still carries them. Other long-running startup
|
|
99
|
+
work uses lifecycle_engine (no timeout) rather than this in-place exemption.
|
|
100
|
+
Postgres only; a no-op on other dialects, which have no such settings.
|
|
101
|
+
"""
|
|
102
|
+
if conn.dialect.name != "postgresql":
|
|
103
|
+
yield
|
|
104
|
+
return
|
|
105
|
+
await conn.execute(_sql_text("SET statement_timeout = 0"))
|
|
106
|
+
await conn.execute(_sql_text("SET lock_timeout = 0"))
|
|
107
|
+
try:
|
|
108
|
+
yield
|
|
109
|
+
finally:
|
|
110
|
+
await conn.execute(_sql_text(f"SET statement_timeout = {_REQUEST_STATEMENT_TIMEOUT_MS}"))
|
|
111
|
+
await conn.execute(_sql_text(f"SET lock_timeout = {_REQUEST_LOCK_TIMEOUT_MS}"))
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
async def get_session() -> AsyncSession:
|
|
115
|
+
async with SessionLocal() as session:
|
|
116
|
+
yield session
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@asynccontextmanager
|
|
120
|
+
async def get_session_ctx():
|
|
121
|
+
"""Standalone async context manager for use outside FastAPI request handlers."""
|
|
122
|
+
async with SessionLocal() as session:
|
|
123
|
+
yield session
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@asynccontextmanager
|
|
127
|
+
async def get_lifecycle_session_ctx():
|
|
128
|
+
"""Session on the unbounded lifecycle_engine, for known long-running work.
|
|
129
|
+
|
|
130
|
+
Use for the projection rebuild and startup backfills: a full rebuild's
|
|
131
|
+
whole-table DELETE and full-ledger replay can exceed the request
|
|
132
|
+
statement_timeout on a mature database, and must not be cancelled. Ordinary
|
|
133
|
+
request and background work uses get_session/get_session_ctx (bounded).
|
|
134
|
+
"""
|
|
135
|
+
async with LifecycleSessionLocal() as session:
|
|
136
|
+
yield session
|
|
@@ -546,6 +546,15 @@ class DocUnvoided(BaseModel):
|
|
|
546
546
|
restored_status: str
|
|
547
547
|
|
|
548
548
|
|
|
549
|
+
class DocClosed(BaseModel):
|
|
550
|
+
reason: str | None = None
|
|
551
|
+
pre_close_status: str | None = None
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
class DocReopened(BaseModel):
|
|
555
|
+
restored_status: str
|
|
556
|
+
|
|
557
|
+
|
|
549
558
|
class DocSent(BaseModel):
|
|
550
559
|
sent_via: str | None = None
|
|
551
560
|
sent_to: str | None = None
|
|
@@ -1162,6 +1171,8 @@ EVENT_SCHEMA_MAP: dict[str, type[BaseModel]] = {
|
|
|
1162
1171
|
"doc.voided": DocVoided,
|
|
1163
1172
|
"doc.reverted_to_draft": DocRevertedToDraft,
|
|
1164
1173
|
"doc.unvoided": DocUnvoided,
|
|
1174
|
+
"doc.closed": DocClosed,
|
|
1175
|
+
"doc.reopened": DocReopened,
|
|
1165
1176
|
"doc.converted_to_bill": DocConvertedToBill,
|
|
1166
1177
|
"doc.sent": DocSent,
|
|
1167
1178
|
"doc.payment.received": DocPaymentReceived,
|
|
@@ -26,7 +26,6 @@ from websockets.exceptions import ConnectionClosed
|
|
|
26
26
|
|
|
27
27
|
log = logging.getLogger(__name__)
|
|
28
28
|
|
|
29
|
-
_PING_INTERVAL = 30 # seconds
|
|
30
29
|
_BACKOFF_MAX = 60 # seconds
|
|
31
30
|
# Consecutive auth rejections before the client stops retrying: a rejected
|
|
32
31
|
# credential never heals on its own, so beyond a small allowance for a
|
|
@@ -37,6 +36,10 @@ _AUTH_FAILED_MAX = 3
|
|
|
37
36
|
# a revoke-then-reshare so it does not flap connect/disconnect).
|
|
38
37
|
_REAP_INTERVAL = 15 * 60 # seconds
|
|
39
38
|
_REAP_GRACE = 5 * 60 # seconds
|
|
39
|
+
# A single websocket send that cannot be handed to the transport within this long means the
|
|
40
|
+
# socket is wedged (a stalled or backpressured peer). Bound every send so it surfaces as an
|
|
41
|
+
# error instead of pinning the proxy task holding a relay slot forever.
|
|
42
|
+
_SEND_DEADLINE = 30 # seconds
|
|
40
43
|
|
|
41
44
|
|
|
42
45
|
def _shop_key(handle: str | None) -> str:
|
|
@@ -85,6 +88,22 @@ class GatewayClient:
|
|
|
85
88
|
# Hold strong refs to fire-and-forget tasks so the loop can't GC them mid-run
|
|
86
89
|
# (a dropped task = a lost proxy response or webhook sync).
|
|
87
90
|
self._bg_tasks: set = set()
|
|
91
|
+
# In-flight proxy tasks keyed by relay request id so an http.cancel frame can
|
|
92
|
+
# abort exactly the one request it names, and no other.
|
|
93
|
+
self._inflight: dict[str, asyncio.Task] = {}
|
|
94
|
+
# Connection generation: bumped on every teardown so a response produced by a
|
|
95
|
+
# request that outlived its socket can be recognised as stale and dropped
|
|
96
|
+
# rather than sent on the freshly rebuilt connection.
|
|
97
|
+
self._generation: int = 0
|
|
98
|
+
# One shared httpx client for all proxied requests, built lazily with bounded
|
|
99
|
+
# connection limits. A fresh client per request opened an unbounded number of
|
|
100
|
+
# pools under load; one bounded client caps the concurrency into the local app.
|
|
101
|
+
self._http_client: Any = None
|
|
102
|
+
# Observability counters for the proxy path, surfaced when the connection ends.
|
|
103
|
+
self._proxied_count = 0
|
|
104
|
+
self._cancelled_count = 0
|
|
105
|
+
self._timeout_count = 0
|
|
106
|
+
self._stale_dropped_count = 0
|
|
88
107
|
# Resolve local server ports for proxy routing.
|
|
89
108
|
# In Electron builds the ports are dynamic; Electron passes them via
|
|
90
109
|
# CELERP_API_PORT / CELERP_UI_PORT env vars so we don't rely on the
|
|
@@ -122,6 +141,22 @@ class GatewayClient:
|
|
|
122
141
|
self._bg_tasks.add(task)
|
|
123
142
|
task.add_done_callback(self._bg_tasks.discard)
|
|
124
143
|
|
|
144
|
+
def _spawn_proxy(self, payload: dict) -> None:
|
|
145
|
+
"""Run a proxied request fire-and-forget, keyed by its relay request id so an
|
|
146
|
+
http.cancel frame can abort exactly that task. The key is cleared when the task
|
|
147
|
+
finishes (a cancel arriving after completion is then a harmless no-op)."""
|
|
148
|
+
request_id = payload.get("id", "")
|
|
149
|
+
task = asyncio.create_task(self._handle_proxy_request(payload))
|
|
150
|
+
self._bg_tasks.add(task)
|
|
151
|
+
self._inflight[request_id] = task
|
|
152
|
+
|
|
153
|
+
def _done(t: asyncio.Task) -> None:
|
|
154
|
+
self._bg_tasks.discard(t)
|
|
155
|
+
if self._inflight.get(request_id) is t:
|
|
156
|
+
del self._inflight[request_id]
|
|
157
|
+
|
|
158
|
+
task.add_done_callback(_done)
|
|
159
|
+
|
|
125
160
|
# ── Public API ─────────────────────────────────────────────────────
|
|
126
161
|
|
|
127
162
|
async def run(self) -> None:
|
|
@@ -198,6 +233,7 @@ class GatewayClient:
|
|
|
198
233
|
await ws.close()
|
|
199
234
|
except Exception:
|
|
200
235
|
pass
|
|
236
|
+
await self._close_http_client()
|
|
201
237
|
|
|
202
238
|
async def _should_reap(self) -> bool:
|
|
203
239
|
"""A free instance with no live share and no recent proxied request should
|
|
@@ -293,40 +329,61 @@ class GatewayClient:
|
|
|
293
329
|
import ssl as _ssl
|
|
294
330
|
_ssl_ctx = _ssl.create_default_context()
|
|
295
331
|
_ssl_ctx.set_alpn_protocols(["http/1.1"])
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
"
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
332
|
+
try:
|
|
333
|
+
async with websockets.connect(
|
|
334
|
+
self._url, ssl=_ssl_ctx, ping_interval=20, ping_timeout=20, max_size=160 * 1024 * 1024
|
|
335
|
+
) as ws:
|
|
336
|
+
self._ws = ws
|
|
337
|
+
# Read current TOS version from config
|
|
338
|
+
from celerp.config import read_config
|
|
339
|
+
cfg = read_config() or {}
|
|
340
|
+
tos_version = cfg.get("cloud", {}).get("tos_version", "")
|
|
341
|
+
# App version: the Electron wrapper passes the release version via
|
|
342
|
+
# CELERP_APP_VERSION; fall back to the package version. Sent on every
|
|
343
|
+
# connect so the handshake reflects the build that is actually running.
|
|
344
|
+
from celerp import __version__ as _pkg_version
|
|
345
|
+
app_version = os.environ.get("CELERP_APP_VERSION") or _pkg_version
|
|
346
|
+
# Send hello handshake
|
|
347
|
+
await self._send(ws, {
|
|
348
|
+
"type": "hello",
|
|
349
|
+
"id": str(uuid.uuid4()),
|
|
350
|
+
"payload": {
|
|
351
|
+
"gateway_token": self._token,
|
|
352
|
+
"instance_id": self._instance_id,
|
|
353
|
+
"tos_version": tos_version,
|
|
354
|
+
"version": app_version,
|
|
355
|
+
},
|
|
356
|
+
})
|
|
357
|
+
# Message dispatch loop
|
|
358
|
+
async for raw in ws:
|
|
359
|
+
try:
|
|
360
|
+
msg = json.loads(raw)
|
|
361
|
+
except json.JSONDecodeError:
|
|
362
|
+
log.warning("Gateway sent non-JSON frame: %r", raw)
|
|
363
|
+
continue
|
|
364
|
+
await self._dispatch(msg)
|
|
365
|
+
finally:
|
|
366
|
+
# Teardown runs on every exit - clean end of the message stream OR an
|
|
367
|
+
# abnormal disconnect raised mid-serve. Kept in a finally so an exception
|
|
368
|
+
# from the async-for can't leave a dangling socket, an unfenced generation,
|
|
369
|
+
# and a stuck status behind.
|
|
370
|
+
self._ws = None
|
|
371
|
+
# A new generation for the next connection: any proxy response still in
|
|
372
|
+
# flight from this socket is now stale and must not be sent on the rebuilt
|
|
373
|
+
# one. Cancel the in-flight proxy tasks that belong to the dead connection -
|
|
374
|
+
# their responses can never be delivered on a socket that no longer exists.
|
|
375
|
+
self._generation += 1
|
|
376
|
+
for task in list(self._inflight.values()):
|
|
377
|
+
if not task.done():
|
|
378
|
+
task.cancel()
|
|
379
|
+
self._inflight.clear()
|
|
380
|
+
log.debug(
|
|
381
|
+
"Gateway proxy activity this connection: proxied=%d cancelled=%d "
|
|
382
|
+
"timed_out=%d stale_dropped=%d",
|
|
383
|
+
self._proxied_count, self._cancelled_count,
|
|
384
|
+
self._timeout_count, self._stale_dropped_count,
|
|
385
|
+
)
|
|
386
|
+
self._set_status("inactive")
|
|
330
387
|
|
|
331
388
|
async def _dispatch(self, msg: dict) -> None:
|
|
332
389
|
msg_type = msg.get("type", "")
|
|
@@ -414,7 +471,13 @@ class GatewayClient:
|
|
|
414
471
|
set_subscription_state(tier, status)
|
|
415
472
|
|
|
416
473
|
elif msg_type == "http.request":
|
|
417
|
-
self.
|
|
474
|
+
self._spawn_proxy(payload)
|
|
475
|
+
|
|
476
|
+
elif msg_type == "http.cancel":
|
|
477
|
+
request_id = payload.get("id", "")
|
|
478
|
+
task = self._inflight.get(request_id)
|
|
479
|
+
if task is not None and not task.done():
|
|
480
|
+
task.cancel()
|
|
418
481
|
|
|
419
482
|
elif msg_type == "shopify.webhook":
|
|
420
483
|
self._spawn(self._handle_shopify_webhook(payload))
|
|
@@ -533,6 +596,31 @@ class GatewayClient:
|
|
|
533
596
|
except Exception as exc:
|
|
534
597
|
log.warning("invoice.payment handling failed (entity=%s): %s", entity_id, exc)
|
|
535
598
|
|
|
599
|
+
def _get_http_client(self):
|
|
600
|
+
"""Return the one shared httpx client used for every proxied request, building
|
|
601
|
+
it lazily on first use. A bounded connection pool caps how many concurrent
|
|
602
|
+
requests this client can drive into the local app, so a burst of relayed
|
|
603
|
+
requests can no longer open an unbounded number of pools. The default timeout
|
|
604
|
+
is the fixed cap; a per-request timeout_ms tightens it further."""
|
|
605
|
+
import httpx
|
|
606
|
+
|
|
607
|
+
if self._http_client is None:
|
|
608
|
+
self._http_client = httpx.AsyncClient(
|
|
609
|
+
timeout=180.0,
|
|
610
|
+
limits=httpx.Limits(max_connections=32, max_keepalive_connections=8),
|
|
611
|
+
)
|
|
612
|
+
return self._http_client
|
|
613
|
+
|
|
614
|
+
async def _close_http_client(self) -> None:
|
|
615
|
+
"""Close the shared httpx client if one was built. Safe to call when none was."""
|
|
616
|
+
client = self._http_client
|
|
617
|
+
self._http_client = None
|
|
618
|
+
if client is not None:
|
|
619
|
+
try:
|
|
620
|
+
await client.aclose()
|
|
621
|
+
except Exception:
|
|
622
|
+
pass
|
|
623
|
+
|
|
536
624
|
async def _handle_proxy_request(self, payload: dict) -> None:
|
|
537
625
|
"""Handle a proxied HTTP request from the relay.
|
|
538
626
|
|
|
@@ -545,10 +633,14 @@ class GatewayClient:
|
|
|
545
633
|
local access.
|
|
546
634
|
"""
|
|
547
635
|
import base64
|
|
548
|
-
import httpx
|
|
549
636
|
|
|
550
637
|
# Activity stamp for the reaper's grace window: any proxied request counts.
|
|
551
638
|
self._last_request_monotonic = time.monotonic()
|
|
639
|
+
self._proxied_count += 1
|
|
640
|
+
# The generation this request belongs to: if the socket is rebuilt before the
|
|
641
|
+
# response is ready, self._generation advances past this and the response is
|
|
642
|
+
# dropped rather than sent on a connection that never issued the request.
|
|
643
|
+
generation = self._generation
|
|
552
644
|
|
|
553
645
|
request_id = payload.get("id", "")
|
|
554
646
|
method = payload.get("method", "GET")
|
|
@@ -557,6 +649,13 @@ class GatewayClient:
|
|
|
557
649
|
headers = payload.get("headers", {})
|
|
558
650
|
body_b64 = payload.get("body_b64", "")
|
|
559
651
|
body = base64.b64decode(body_b64) if body_b64 else None
|
|
652
|
+
# Additive per-request deadline: a numeric timeout_ms bounds this request below
|
|
653
|
+
# the fixed cap so a single slow local handler can't pin a relay slot for 180s.
|
|
654
|
+
# Absent or non-numeric leaves the default cap in force.
|
|
655
|
+
timeout_ms = payload.get("timeout_ms")
|
|
656
|
+
deadline_s: float | None = None
|
|
657
|
+
if isinstance(timeout_ms, (int, float)) and not isinstance(timeout_ms, bool) and timeout_ms > 0:
|
|
658
|
+
deadline_s = timeout_ms / 1000.0
|
|
560
659
|
|
|
561
660
|
# SSE / long-poll paths cannot be proxied over the WS request/response
|
|
562
661
|
# protocol. Return an empty stream so the browser doesn't 500.
|
|
@@ -599,47 +698,98 @@ class GatewayClient:
|
|
|
599
698
|
if query:
|
|
600
699
|
url = f"{url}?{query}"
|
|
601
700
|
|
|
701
|
+
client = self._get_http_client()
|
|
602
702
|
try:
|
|
603
|
-
|
|
703
|
+
if deadline_s is not None:
|
|
704
|
+
# asyncio.wait_for, not asyncio.timeout: the latter is 3.11+, and this package
|
|
705
|
+
# supports Python >=3.10 where it does not exist. wait_for cancels the request and
|
|
706
|
+
# raises TimeoutError on the deadline, which the handler below turns into an error
|
|
707
|
+
# response.
|
|
708
|
+
resp = await asyncio.wait_for(
|
|
709
|
+
client.request(
|
|
710
|
+
method=method,
|
|
711
|
+
url=url,
|
|
712
|
+
headers=headers,
|
|
713
|
+
content=body,
|
|
714
|
+
),
|
|
715
|
+
deadline_s,
|
|
716
|
+
)
|
|
717
|
+
else:
|
|
604
718
|
resp = await client.request(
|
|
605
719
|
method=method,
|
|
606
720
|
url=url,
|
|
607
721
|
headers=headers,
|
|
608
722
|
content=body,
|
|
609
723
|
)
|
|
610
|
-
|
|
611
|
-
#
|
|
612
|
-
#
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
724
|
+
except asyncio.CancelledError:
|
|
725
|
+
# http.cancel aborted this request: emit nothing and let the cancellation
|
|
726
|
+
# propagate so the task is recorded cancelled.
|
|
727
|
+
self._cancelled_count += 1
|
|
728
|
+
raise
|
|
729
|
+
except TimeoutError:
|
|
730
|
+
# The per-request deadline elapsed before the local handler answered. Surface
|
|
731
|
+
# it as an error response instead of hanging to the fixed cap.
|
|
732
|
+
self._timeout_count += 1
|
|
733
|
+
log.warning("Proxy request timed out after %sms for %s %s", timeout_ms, method, path)
|
|
734
|
+
if generation == self._generation:
|
|
735
|
+
await self._send(self._ws, {
|
|
736
|
+
"type": "http.response",
|
|
737
|
+
"payload": {
|
|
738
|
+
"id": request_id,
|
|
739
|
+
"status": 504,
|
|
740
|
+
"headers": [["content-type", "text/plain"]],
|
|
741
|
+
"body_b64": base64.b64encode(b"Local app timed out").decode(),
|
|
742
|
+
},
|
|
743
|
+
})
|
|
744
|
+
else:
|
|
745
|
+
self._stale_dropped_count += 1
|
|
746
|
+
return
|
|
630
747
|
except Exception as exc:
|
|
631
748
|
log.warning("Proxy request failed for %s %s: %s", method, path, exc)
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
"
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
749
|
+
if generation == self._generation:
|
|
750
|
+
await self._send(self._ws, {
|
|
751
|
+
"type": "http.response",
|
|
752
|
+
"payload": {
|
|
753
|
+
"id": request_id,
|
|
754
|
+
"status": 502,
|
|
755
|
+
"headers": [["content-type", "text/plain"]],
|
|
756
|
+
"body_b64": base64.b64encode(
|
|
757
|
+
f"Local app error: {type(exc).__name__}: {exc}".encode()
|
|
758
|
+
).decode(),
|
|
759
|
+
},
|
|
760
|
+
})
|
|
761
|
+
else:
|
|
762
|
+
self._stale_dropped_count += 1
|
|
763
|
+
return
|
|
764
|
+
|
|
765
|
+
# The socket that carried this request may have been rebuilt while it was in
|
|
766
|
+
# flight. A response from a superseded generation belongs to a connection the
|
|
767
|
+
# relay no longer has; sending it on the new socket would answer the wrong
|
|
768
|
+
# request, so it is dropped.
|
|
769
|
+
if generation != self._generation:
|
|
770
|
+
self._stale_dropped_count += 1
|
|
771
|
+
return
|
|
772
|
+
|
|
773
|
+
resp_body_b64 = base64.b64encode(resp.content).decode() if resp.content else ""
|
|
774
|
+
# Serialize as an ORDERED LIST of pairs (not a dict): a response can carry several
|
|
775
|
+
# Set-Cookie headers (login/refresh emit access_token + refresh_token), and a dict would
|
|
776
|
+
# collapse them into one comma-joined value the browser can't parse, so the refresh
|
|
777
|
+
# cookie never reaches the browser and the session dies at the access token's hard cap.
|
|
778
|
+
# multi_items() keeps each header separate. Drop content-length; the relay recomputes it.
|
|
779
|
+
_skip = {"transfer-encoding", "connection", "keep-alive", "content-length"}
|
|
780
|
+
resp_headers = [
|
|
781
|
+
[k, v] for k, v in resp.headers.multi_items()
|
|
782
|
+
if k.lower() not in _skip
|
|
783
|
+
]
|
|
784
|
+
await self._send(self._ws, {
|
|
785
|
+
"type": "http.response",
|
|
786
|
+
"payload": {
|
|
787
|
+
"id": request_id,
|
|
788
|
+
"status": resp.status_code,
|
|
789
|
+
"headers": resp_headers,
|
|
790
|
+
"body_b64": resp_body_b64,
|
|
791
|
+
},
|
|
792
|
+
})
|
|
643
793
|
|
|
644
794
|
async def send_message(self, msg_type: str, **payload) -> None:
|
|
645
795
|
"""Send a JSON message over the active WS connection.
|
|
@@ -652,7 +802,12 @@ class GatewayClient:
|
|
|
652
802
|
|
|
653
803
|
@staticmethod
|
|
654
804
|
async def _send(ws, message: dict) -> None:
|
|
655
|
-
|
|
805
|
+
# Bound the send: a websocket send awaits until the frame is handed to the transport, and a
|
|
806
|
+
# stalled or backpressured peer would otherwise block it forever, pinning the proxy task and
|
|
807
|
+
# its relay slot. On the deadline the send is cancelled and TimeoutError surfaces to the
|
|
808
|
+
# caller, which lets the wedged socket be torn down and rebuilt. asyncio.wait_for, not
|
|
809
|
+
# asyncio.timeout: the latter is 3.11+ and this package supports Python >=3.10.
|
|
810
|
+
await asyncio.wait_for(ws.send(json.dumps(message)), _SEND_DEADLINE)
|
|
656
811
|
|
|
657
812
|
|
|
658
813
|
# Module-level singleton — set by main.py lifespan
|
|
@@ -12,7 +12,7 @@ from slowapi import Limiter, _rate_limit_exceeded_handler
|
|
|
12
12
|
from slowapi.errors import RateLimitExceeded
|
|
13
13
|
from slowapi.util import get_remote_address
|
|
14
14
|
|
|
15
|
-
from celerp.db import engine, mask_db_credentials
|
|
15
|
+
from celerp.db import engine, lifecycle_engine, mask_db_credentials
|
|
16
16
|
from celerp.inventory_codes import BarcodeConflictError
|
|
17
17
|
from celerp.config import settings, assert_secure_jwt, ensure_instance_id, load_cloud_config, load_backup_config
|
|
18
18
|
load_cloud_config()
|
|
@@ -164,7 +164,7 @@ async def _try_auto_activate() -> None:
|
|
|
164
164
|
async def lifespan(_app: FastAPI):
|
|
165
165
|
(settings.data_dir / "static" / "attachments").mkdir(parents=True, exist_ok=True)
|
|
166
166
|
try:
|
|
167
|
-
async with
|
|
167
|
+
async with lifecycle_engine.begin() as conn:
|
|
168
168
|
await conn.run_sync(Base.metadata.create_all)
|
|
169
169
|
except Exception as exc:
|
|
170
170
|
masked_url = mask_db_credentials(settings.database_url)
|
|
@@ -205,19 +205,20 @@ async def lifespan(_app: FastAPI):
|
|
|
205
205
|
register_api_routes(_app, _loaded_modules)
|
|
206
206
|
# Module models register on Base.metadata at import time.
|
|
207
207
|
# Run create_all again so module tables are created (idempotent).
|
|
208
|
-
async with
|
|
208
|
+
async with lifecycle_engine.begin() as conn:
|
|
209
209
|
await conn.run_sync(Base.metadata.create_all)
|
|
210
210
|
# Allow modules to backfill data for existing companies (e.g. seed
|
|
211
211
|
# chart of accounts when accounting module is first enabled on an
|
|
212
212
|
# instance that already has companies).
|
|
213
213
|
from celerp.modules.slots import fire_lifecycle as _fire
|
|
214
|
-
from celerp.db import
|
|
214
|
+
from celerp.db import LifecycleSessionLocal as _LifecycleSession
|
|
215
215
|
# Best-effort, like the two sibling blocks below: a hook that fails
|
|
216
216
|
# during flush poisons the shared session, so the commit raises.
|
|
217
217
|
# Roll back and log at ERROR rather than let that crash boot - the
|
|
218
218
|
# manufacturing seed hook, for one, must never be able to take the
|
|
219
|
-
# app down.
|
|
220
|
-
|
|
219
|
+
# app down. Seed hooks can replay large ledgers, so they run on the
|
|
220
|
+
# unbounded lifecycle engine, not the timeout-bounded request pool.
|
|
221
|
+
async with _LifecycleSession() as _sess:
|
|
221
222
|
try:
|
|
222
223
|
await _fire("on_modules_ready", session=_sess)
|
|
223
224
|
await _sess.commit()
|
|
@@ -236,7 +237,7 @@ async def lifespan(_app: FastAPI):
|
|
|
236
237
|
_demoted = demoted_first_party(_enabled)
|
|
237
238
|
if _demoted:
|
|
238
239
|
from celerp.modules.demotion import notify_demoted_modules
|
|
239
|
-
async with
|
|
240
|
+
async with _LifecycleSession() as _dsess:
|
|
240
241
|
await notify_demoted_modules(_dsess, _demoted)
|
|
241
242
|
await _dsess.commit()
|
|
242
243
|
except Exception:
|
|
@@ -256,7 +257,7 @@ async def lifespan(_app: FastAPI):
|
|
|
256
257
|
# marker so it runs once per version. Non-fatal: a failure must not block
|
|
257
258
|
# boot — the marker stays unset and a later boot retries.
|
|
258
259
|
try:
|
|
259
|
-
from celerp.db import
|
|
260
|
+
from celerp.db import LifecycleSessionLocal as _GuardSession
|
|
260
261
|
from celerp.services.dev_release_guard import run_upgrade_guard
|
|
261
262
|
async with _GuardSession() as _guard_sess:
|
|
262
263
|
await run_upgrade_guard(_guard_sess)
|
|
@@ -271,7 +272,7 @@ async def lifespan(_app: FastAPI):
|
|
|
271
272
|
# or consigned in before that field shipped, so their inventory status links
|
|
272
273
|
# to its document. Marker-gated (runs once); non-fatal like the guard above.
|
|
273
274
|
try:
|
|
274
|
-
from celerp.db import
|
|
275
|
+
from celerp.db import LifecycleSessionLocal as _BackfillSession
|
|
275
276
|
from celerp.services.status_doc_backfill import run_status_doc_backfill
|
|
276
277
|
async with _BackfillSession() as _bf_sess:
|
|
277
278
|
await run_status_doc_backfill(_bf_sess)
|
|
@@ -287,7 +288,7 @@ async def lifespan(_app: FastAPI):
|
|
|
287
288
|
# (runs once; retries stragglers while any doc is locked or errored);
|
|
288
289
|
# non-fatal like the backfill above.
|
|
289
290
|
try:
|
|
290
|
-
from celerp.db import
|
|
291
|
+
from celerp.db import LifecycleSessionLocal as _CogsSession
|
|
291
292
|
from celerp.services.cogs_backfill import run_cogs_backfill
|
|
292
293
|
async with _CogsSession() as _cogs_sess:
|
|
293
294
|
await run_cogs_backfill(_cogs_sess)
|