celerp 2.3.4__tar.gz → 2.3.6__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.4 → celerp-2.3.6}/PKG-INFO +1 -1
- celerp-2.3.6/celerp/capacity.py +22 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/db.py +10 -7
- {celerp-2.3.4 → celerp-2.3.6}/celerp/events/engine.py +44 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/events/schemas.py +61 -12
- {celerp-2.3.4 → celerp-2.3.6}/celerp/gateway/client.py +172 -53
- celerp-2.3.6/celerp/inventory_codes.py +185 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/main.py +9 -6
- {celerp-2.3.4 → celerp-2.3.6}/celerp/middleware.py +2 -2
- celerp-2.3.6/celerp/migrations/versions/d1e2f3a4b5c6_rfid_epc_unique_index.py +44 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/models/projections.py +12 -1
- {celerp-2.3.4 → celerp-2.3.6}/celerp/modules/loader.py +232 -9
- {celerp-2.3.4 → celerp-2.3.6}/celerp/modules/slots.py +27 -8
- {celerp-2.3.4 → celerp-2.3.6}/celerp/notifications/sse.py +1 -36
- {celerp-2.3.4 → celerp-2.3.6}/celerp/projections/engine.py +24 -11
- {celerp-2.3.4 → celerp-2.3.6}/celerp/routers/auth.py +0 -80
- {celerp-2.3.4 → celerp-2.3.6}/celerp/routers/events.py +29 -7
- {celerp-2.3.4 → celerp-2.3.6}/celerp/routers/notifications.py +1 -36
- celerp-2.3.6/celerp/routers/search.py +305 -0
- celerp-2.3.6/celerp/services/document_lines.py +102 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/services/field_schema.py +2 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/services/units.py +12 -3
- {celerp-2.3.4 → celerp-2.3.6}/celerp.egg-info/PKG-INFO +1 -1
- {celerp-2.3.4 → celerp-2.3.6}/celerp.egg-info/SOURCES.txt +12 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp.egg-info/scm_file_list.json +32 -0
- celerp-2.3.6/celerp.egg-info/scm_version.json +8 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-accounting/__init__.py +5 -0
- celerp-2.3.6/default_modules/celerp-accounting/celerp_accounting/search.py +30 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-contacts/__init__.py +5 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-contacts/celerp_contacts/routes.py +6 -24
- celerp-2.3.6/default_modules/celerp-contacts/celerp_contacts/search.py +75 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-docs/__init__.py +5 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-docs/celerp_docs/routes.py +190 -35
- celerp-2.3.6/default_modules/celerp-docs/celerp_docs/search.py +68 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-docs/tests/test_audits.py +5 -2
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-docs/tests/test_line_page_patch_truncate.py +35 -17
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-docs/tests/test_list_page_no_full_state.py +4 -1
- celerp-2.3.6/default_modules/celerp-docs/tests/test_list_quantity_integrity.py +370 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-docs/tests/test_list_query_pushdown.py +21 -10
- celerp-2.3.6/default_modules/celerp-docs/tests/test_list_sell_by_and_reference.py +103 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-inventory/__init__.py +5 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-inventory/celerp_inventory/projections.py +2 -1
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-inventory/celerp_inventory/routes.py +317 -185
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-inventory/celerp_inventory/routes_scanning.py +1 -1
- celerp-2.3.6/default_modules/celerp-inventory/celerp_inventory/search.py +265 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-inventory/celerp_inventory/services.py +70 -18
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-manufacturing/__init__.py +5 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-manufacturing/celerp_manufacturing/routes.py +3 -31
- celerp-2.3.6/default_modules/celerp-manufacturing/celerp_manufacturing/search.py +74 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-manufacturing/tests/test_work_centers_and_settings.py +28 -9
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-subscriptions/__init__.py +5 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-subscriptions/celerp_subscriptions/routes.py +4 -41
- celerp-2.3.6/default_modules/celerp-subscriptions/celerp_subscriptions/search.py +85 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/first_party.lock.json +6 -6
- {celerp-2.3.4 → celerp-2.3.6}/ui/api_client.py +636 -159
- {celerp-2.3.4 → celerp-2.3.6}/ui/app.py +9 -13
- {celerp-2.3.4 → celerp-2.3.6}/ui/components/shell.py +63 -18
- {celerp-2.3.4 → celerp-2.3.6}/ui/locales/am.json +10 -2
- {celerp-2.3.4 → celerp-2.3.6}/ui/locales/ar.json +10 -2
- {celerp-2.3.4 → celerp-2.3.6}/ui/locales/de.json +10 -2
- {celerp-2.3.4 → celerp-2.3.6}/ui/locales/en.json +10 -2
- {celerp-2.3.4 → celerp-2.3.6}/ui/locales/es.json +10 -2
- {celerp-2.3.4 → celerp-2.3.6}/ui/locales/fr.json +10 -2
- {celerp-2.3.4 → celerp-2.3.6}/ui/locales/id.json +10 -2
- {celerp-2.3.4 → celerp-2.3.6}/ui/locales/it.json +10 -2
- {celerp-2.3.4 → celerp-2.3.6}/ui/locales/ja.json +10 -2
- {celerp-2.3.4 → celerp-2.3.6}/ui/locales/pt.json +10 -2
- {celerp-2.3.4 → celerp-2.3.6}/ui/locales/th.json +10 -2
- {celerp-2.3.4 → celerp-2.3.6}/ui/locales/vi.json +10 -2
- {celerp-2.3.4 → celerp-2.3.6}/ui/routes/auth.py +8 -53
- {celerp-2.3.4 → celerp-2.3.6}/ui/routes/documents.py +139 -20
- {celerp-2.3.4 → celerp-2.3.6}/ui/routes/inventory.py +173 -77
- celerp-2.3.6/ui/routes/notifications.py +58 -0
- celerp-2.3.6/ui/routes/search.py +218 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/routes/settings.py +12 -25
- {celerp-2.3.4 → celerp-2.3.6}/ui/routes/stars.py +11 -16
- celerp-2.3.6/ui/security.py +44 -0
- celerp-2.3.4/celerp/inventory_codes.py +0 -87
- celerp-2.3.4/celerp.egg-info/scm_version.json +0 -8
- celerp-2.3.4/ui/routes/notifications.py +0 -113
- celerp-2.3.4/ui/routes/search.py +0 -170
- celerp-2.3.4/ui/security.py +0 -19
- {celerp-2.3.4 → celerp-2.3.6}/CLA.md +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/CONTRIBUTING.md +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/LICENSE +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/LICENSING.md +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/MANIFEST.in +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/NOTICE +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/README.md +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/alembic.ini +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/__init__.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/__main__.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/ai/__init__.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/ai/batch.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/ai/cleanup.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/ai/commands.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/ai/conversations.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/ai/files.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/ai/intent.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/ai/llm.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/ai/memory.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/ai/models.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/ai/page_count.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/ai/quota.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/ai/service.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/ai/tools.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/alembic.ini +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/alembic_config.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/cli.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/compat.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/compute/__init__.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/compute/aggregations.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/compute/valuation.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/config.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/connectors/__init__.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/connectors/base.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/connectors/daily_scheduler.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/connectors/http.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/connectors/images.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/connectors/quickbooks.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/connectors/registry.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/connectors/relay_token.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/connectors/shopify.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/connectors/sync_runner.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/connectors/upsert.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/connectors/util.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/connectors/webhooks.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/connectors/woocommerce.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/connectors/xero.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/constants.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/embedded_pg.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/events/__init__.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/events/types.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/gateway/__init__.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/gateway/state.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/importers/__init__.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/importers/importer.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/importers/schema.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/migrations/README.md +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/migrations/_auto_stamp.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/migrations/_data_reconcile.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/migrations/_json_compat.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/migrations/env.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/migrations/script.py.mako +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/migrations/versions/a0b1c2d3e4f5_seed_landed_cost_clearing_accounts.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/migrations/versions/a1b2c3d4e5f6_add_doc_share_tokens.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/migrations/versions/b1c2d3e4f5a6_add_supporter_badges.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/migrations/versions/b2c3d4e5f6a7_add_import_batches.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/migrations/versions/b3c4d5e6f7a8_expand_role_permissions_to_role_grants.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/migrations/versions/bc0d1e2f3a4b_barcode_unique_index.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/migrations/versions/c1d2e3f4a5b6_add_marketplace.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/migrations/versions/c2d3e4f5a6b7_add_is_sync_to_shopify.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/migrations/versions/c4d5e6f7a8b9_account_cash_flow_category.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/migrations/versions/c9d8e7f6a5b4_seed_shrinkage_writeoff_account.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/migrations/versions/d0e1f2a3b4c5_add_share_token_expiry.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/migrations/versions/d2e3f4a5b6c7_add_password_reset_token.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/migrations/versions/d5e6f7a8b9c0_rename_fx_rounding_to_fx_difference.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/migrations/versions/e3f4a5b6c7d8_add_company_is_active.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/migrations/versions/e4f5a6b7c8d9_backfill_connector_idempotency_key.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/migrations/versions/e7c9a1b3d5f2_work_center_default_columns.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/migrations/versions/f1a2b3c4d5e6_move_accounts_marketplace_to_modules.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/migrations/versions/f6a7b8c9d0e1_seed_fx_rounding_account.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/migrations/versions/f8a9b0c1d2e3_work_center_default_backfill.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/migrations/versions/fd5de461e14e_initial_schema.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/migrations/versions/g1h2i3j4k5l6_add_consignment_flag.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/migrations/versions/h2i3j4k5l6m7_add_bank_accounts_and_reconciliation.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/migrations/versions/j4k5l6m7n8o9_labels_db_persistence.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/migrations/versions/k5l6m7n8o9p0_add_reconciliation_v2.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/migrations/versions/l6m7n8o9p0q1_add_sync_runs_table.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/migrations/versions/m7n8o9p0q1r2_add_connector_configs_and_outbound_queue.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/migrations/versions/n8o9p0q1r2s3_backfill_payment_date.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/migrations/versions/o9p0q1r2s3t4_backfill_item_price_fields.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/migrations/versions/p1q2r3s4t5u6_add_session_registry_and_user_auth_state.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/migrations/versions/q2r3s4t5u6v7_add_system_runtime_state.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/migrations/versions/r3s4t5u6v7w8_add_projections_company_entity_type_index.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/migrations/versions/s4t5u6v7w8x9_drop_user_company_id_role.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/migrations/versions/t5u6v7w8x9y0_seed_inventory_sub_accounts.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/migrations/versions/u6v7w8x9y0z1_backfill_item_prices_from_attributes.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/migrations/versions/v7w8x9y0z1a2_backfill_purchase_unit_defaults.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/migrations/versions/w8x9y0z1a2b3_backfill_category_unit_defaults.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/migrations/versions/x7y8z9a0b1c2_add_projection_created_at.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/migrations/versions/y8z9a0b1c2d3_per_company_ledger_idempotency.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/migrations/versions/z9a0b1c2d3e4_work_centers.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/models/__init__.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/models/accounting.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/models/ai.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/models/auth.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/models/base.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/models/company.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/models/connector_config.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/models/import_batch.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/models/ledger.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/models/marketplace.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/models/notification.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/models/share.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/models/supporter.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/models/sync_run.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/modules/__init__.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/modules/api.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/modules/audit.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/modules/demotion.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/modules/importer.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/modules/license.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/modules/meta.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/modules/migrations_runner.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/modules/references.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/modules/registry.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/notifications/__init__.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/notifications/service.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/output/__init__.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/output/branding.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/output/doc_print.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/output/pdf.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/output/share_render.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/projections/__init__.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/projections/handlers/__init__.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/projections/handlers/marketplace.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/projections/handlers/scanning.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/projections/handlers/shopify.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/projections/handlers/system.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/routers/__init__.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/routers/companies.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/routers/debug.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/routers/doctor.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/routers/health.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/routers/ledger.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/routers/stars.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/routers/system.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/schemas/__init__.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/services/__init__.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/services/activity_redaction.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/services/attachments.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/services/auth.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/services/auto_je.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/services/backup.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/services/backup_export.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/services/backup_import.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/services/backup_repo.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/services/backup_scheduler.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/services/backup_state.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/services/cogs_backfill.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/services/cost_visibility.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/services/demo.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/services/dev_release_guard.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/services/email.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/services/fulfill.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/services/holdings.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/services/je_keys.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/services/landed_cost.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/services/line_measures.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/services/list_behavior.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/services/money.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/services/payments.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/services/permissions.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/services/pick.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/services/pricing.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/services/relay_share.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/services/reorder.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/services/runtime_state.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/services/session_tracker.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/services/shipping.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/services/star_cta.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/services/status_doc_backfill.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/services/supporter_badge.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/services/system_health.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/session_gate.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp/tax_regimes.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp.egg-info/dependency_links.txt +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp.egg-info/entry_points.txt +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp.egg-info/requires.txt +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/celerp.egg-info/top_level.txt +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/conftest.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/conftest_support.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/__init__.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-accounting/LICENSE +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-accounting/celerp_accounting/__init__.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-accounting/celerp_accounting/api_setup.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-accounting/celerp_accounting/csv_parser.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-accounting/celerp_accounting/matcher.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-accounting/celerp_accounting/models.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-accounting/celerp_accounting/projections.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-accounting/celerp_accounting/routes.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-accounting/celerp_accounting/ui_routes.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-accounting/tests/report_goldens/balance-sheet.html +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-accounting/tests/report_goldens/cash-flow.html +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-accounting/tests/report_goldens/extended-journal-csv.csv +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-accounting/tests/report_goldens/extended-journal-print.html +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-accounting/tests/report_goldens/extended-journal.html +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-accounting/tests/report_goldens/general-ledger.html +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-accounting/tests/report_goldens/journal-csv.csv +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-accounting/tests/report_goldens/journal-print.html +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-accounting/tests/report_goldens/journal.html +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-accounting/tests/report_goldens/ledger.html +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-accounting/tests/report_goldens/pnl.html +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-accounting/tests/report_goldens/statement.html +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-accounting/tests/report_goldens/trial-balance.html +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-accounting/tests/test_accounting_banking.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-accounting/tests/test_accounting_banking_routers.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-accounting/tests/test_accounting_gaps.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-accounting/tests/test_accounting_import_idempotency.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-accounting/tests/test_cash_flow.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-accounting/tests/test_financial_reports_ui.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-accounting/tests/test_journal_gl_soa.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-accounting/tests/test_journal_gl_soa_ui.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-accounting/tests/test_reconciliation_v2.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-accounting/tests/test_reports_end_to_end.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-accounting/tests/test_route_permissions.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-admin/__init__.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-admin/celerp_admin/__init__.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-admin/celerp_admin/routes.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-admin/celerp_admin/setup.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-ai/__init__.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-ai/celerp_ai/__init__.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-ai/celerp_ai/routes.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-ai/celerp_ai/setup.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-ai/celerp_ai/ui_routes.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-ai/tests/conftest.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-ai/tests/test_batch.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-ai/tests/test_cleanup.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-ai/tests/test_conversations.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-ai/tests/test_coverage_gaps.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-ai/tests/test_intent.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-ai/tests/test_llm.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-ai/tests/test_memory.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-ai/tests/test_models.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-ai/tests/test_page_count.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-ai/tests/test_quota.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-ai/tests/test_router.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-ai/tests/test_routes_coverage.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-ai/tests/test_service.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-ai/tests/test_tools.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-backup/__init__.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-backup/celerp_backup/__init__.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-backup/celerp_backup/routes.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-backup/celerp_backup/setup.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-connectors/__init__.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-connectors/celerp_connectors/__init__.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-connectors/celerp_connectors/models.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-connectors/celerp_connectors/routes.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-connectors/tests/__init__.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-connectors/tests/conftest.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-connectors/tests/test_compliance.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-connectors/tests/test_connector_correctness.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-connectors/tests/test_connectors.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-connectors/tests/test_credentials_routes.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-connectors/tests/test_images.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-connectors/tests/test_outbound.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-connectors/tests/test_quickbooks.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-connectors/tests/test_rate_limiting.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-connectors/tests/test_shopify.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-connectors/tests/test_wiring.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-connectors/tests/test_woocommerce.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-connectors/tests/test_woocommerce_webhooks.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-connectors/tests/test_xero.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-contacts/LICENSE +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-contacts/celerp_contacts/__init__.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-contacts/celerp_contacts/migrations.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-contacts/celerp_contacts/projections.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-contacts/celerp_contacts/services.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-contacts/celerp_contacts/ui_routes.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-dashboard/LICENSE +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-dashboard/__init__.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-dashboard/celerp_dashboard/__init__.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-dashboard/celerp_dashboard/routes.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-dashboard/celerp_dashboard/setup.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-docs/LICENSE +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-docs/celerp_docs/__init__.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-docs/celerp_docs/api_setup.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-docs/celerp_docs/doc_constants.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-docs/celerp_docs/doc_email.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-docs/celerp_docs/doc_projections.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-docs/celerp_docs/doc_service.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-docs/celerp_docs/models_share.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-docs/celerp_docs/routes_payments.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-docs/celerp_docs/routes_share.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-docs/celerp_docs/sequences.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-docs/celerp_docs/taxes.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-docs/celerp_docs/ui_routes.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-docs/tests/conftest.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-docs/tests/test_bulk_payment_alloc_lock.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-docs/tests/test_consolidate_lots.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-docs/tests/test_cross_lot_settlement.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-docs/tests/test_freight_je.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-docs/tests/test_freight_lines.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-docs/tests/test_fulfill_no_split.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-docs/tests/test_fulfillment_activity.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-docs/tests/test_inbound_je_lifecycle.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-docs/tests/test_inventory_account_reconciliation.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-docs/tests/test_landed_receive.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-docs/tests/test_line_delete_persist.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-docs/tests/test_line_identifier.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-docs/tests/test_list_concurrency.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-docs/tests/test_list_outcome_concurrency.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-docs/tests/test_memo_close.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-docs/tests/test_memo_close_projection.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-docs/tests/test_memo_close_review_fixes.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-docs/tests/test_memo_lifecycle_concurrency.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-docs/tests/test_memo_line_labels.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-docs/tests/test_memo_unfulfilled_filter.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-docs/tests/test_payment_void_activity.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-docs/tests/test_receive_sku_batch.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-docs/tests/test_send_visibility.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-docs/tests/test_service_lines.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-docs/tests/test_sold_label_ordering.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-docs/tests/test_void_guards.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-docs/tests/test_writeoffs.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-inventory/LICENSE +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-inventory/celerp_inventory/__init__.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-inventory/celerp_inventory/models_import_batch.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-inventory/celerp_inventory/routes_attachments.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-inventory/celerp_inventory/ui_routes.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-inventory/tests/test_item_recipe_projection.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-inventory/tests/test_item_workflow_projection.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-inventory/tests/test_landed_cost_projection.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-labels/LICENSE +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-labels/__init__.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-labels/celerp_labels/__init__.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-labels/celerp_labels/locales/am.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-labels/celerp_labels/locales/ar.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-labels/celerp_labels/locales/de.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-labels/celerp_labels/locales/en.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-labels/celerp_labels/locales/es.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-labels/celerp_labels/locales/fr.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-labels/celerp_labels/locales/id.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-labels/celerp_labels/locales/it.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-labels/celerp_labels/locales/ja.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-labels/celerp_labels/locales/pt.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-labels/celerp_labels/locales/th.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-labels/celerp_labels/locales/vi.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-labels/celerp_labels/models.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-labels/celerp_labels/presets.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-labels/celerp_labels/routes.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-labels/celerp_labels/service.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-labels/celerp_labels/ui_routes.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-labels/requirements.txt +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-labels/tests/test_labels.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-labels/tests/test_labels_module.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-manufacturing/LICENSE +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-manufacturing/celerp_manufacturing/__init__.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-manufacturing/celerp_manufacturing/costing.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-manufacturing/celerp_manufacturing/expansion.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-manufacturing/celerp_manufacturing/labor.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-manufacturing/celerp_manufacturing/migrations.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-manufacturing/celerp_manufacturing/projection_handler.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-manufacturing/celerp_manufacturing/ui_routes.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-manufacturing/requirements.txt +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-manufacturing/tests/test_auto_complete_on_finalize.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-manufacturing/tests/test_bom_removed.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-manufacturing/tests/test_build_api.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-manufacturing/tests/test_completion_lock_order.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-manufacturing/tests/test_item_recipe_api.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-manufacturing/tests/test_item_workflow_api.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-manufacturing/tests/test_labor_providers.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-manufacturing/tests/test_manufacture_from_doc.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-manufacturing/tests/test_manufacturing.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-manufacturing/tests/test_manufacturing_import_idempotency.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-manufacturing/tests/test_manufacturing_module.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-manufacturing/tests/test_mfg_events.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-manufacturing/tests/test_phase2_manufacturing_validations.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-manufacturing/tests/test_production_order.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-manufacturing/tests/test_recipe_costing.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-manufacturing/tests/test_recipe_expansion.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-manufacturing/tests/test_recost_api.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-manufacturing/tests/test_run_records.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-manufacturing/tests/test_to_make_allocation.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-manufacturing/tests/test_where_used.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-manufacturing/tests/test_workflow_time.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-reports/LICENSE +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-reports/__init__.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-reports/celerp_reports/__init__.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-reports/celerp_reports/api_setup.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-reports/celerp_reports/routes.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-reports/celerp_reports/ui_routes.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-reports/tests/test_reports_full.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-reports/tests/test_reports_unit.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-subscriptions/LICENSE +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-subscriptions/celerp_subscriptions/__init__.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-subscriptions/celerp_subscriptions/ui_routes.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-subscriptions/tests/test_subscriptions.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-subscriptions/tests/test_subscriptions_ui.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/LICENSE +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/__init__.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/__init__.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/accessory_fashion.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/activewear.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/art_photography.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/audio_equipment.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/bag_handbag.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/banknote.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/beauty_tool.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/bed_bedroom.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/beer.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/beverage_nonalc.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/body_part_auto.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/book.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/bottoms.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/brake_suspension.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/bullion_coin.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/camera.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/colored_stone.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/comic.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/commercial_space.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/component_part.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/confectionery.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/consulting_service.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/decorative_art.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/diamond.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/drawing.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/dress_jumpsuit.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/electrical_auto.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/electrical_component.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/emerald.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/engine_part.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/fastener.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/fertilizer_chemical.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/film_video.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/fine_writing_instrument.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/fluid_lubricant.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/footwear.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/fragrance.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/fresh_food.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/fresh_produce.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/frozen_food.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/gaming_console.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/gold_bullion.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/grain_cereal.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/haircare.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/hand_tool.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/ingredient_bulk.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/interior_part.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/jewelry.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/kitchen_dining.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/laptop.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/lighting.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/livestock_feed.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/makeup.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/measuring_instrument.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/medal_token.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/mineral_specimen.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/mobile_phone.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/music_cd.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/music_vinyl.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/nail.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/numismatic_coin.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/outdoor_furniture.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/outerwear.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/packaged_food.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/painting.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/parking_bay.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/pearl.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/personal_care.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/pipe_plumbing.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/platinum_bullion.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/power_tool.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/print_edition.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/residential_unit.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/rough_gemstone.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/ruby.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/saas_plan.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/safety_ppe.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/sapphire.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/sculpture.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/seating.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/seeds.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/silver_bullion.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/skincare.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/soft_furnishing.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/software_addon.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/software_license.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/spirit.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/storage_furniture.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/swimwear.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/table_desk.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/tablet.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/tire_wheel.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/tops.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/trading_card.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/tv_display.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/video_game.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/watch.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/watch_strap.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/categories/wine.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/presets/agricultural.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/presets/artwork.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/presets/automotive.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/presets/blank.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/presets/books_media.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/presets/coins_precious_metals.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/presets/consulting.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/presets/cosmetics.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/presets/electronics.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/presets/fashion.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/presets/food_beverage.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/presets/furniture.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/presets/gemstones.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/presets/hardware.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/presets/property_rental.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/presets/saas.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/presets/watches.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/presets/wine_spirits.json +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/routes.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/celerp_verticals/ui_routes.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/tests/test_verticals.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/default_modules/celerp-verticals/tests/test_verticals_routers.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/legal/OFFICIAL-COMPONENTS.md +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/legal/TRADEMARK.md +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/packaging/winget/Celerp.Celerp.installer.yaml +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/packaging/winget/Celerp.Celerp.locale.en-US.yaml +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/packaging/winget/Celerp.Celerp.yaml +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/packaging/winget/README.md +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/pyproject.toml +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/setup.cfg +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/test_helpers.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/__init__.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/components/__init__.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/components/activity.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/components/attrs.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/components/backup.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/components/cloud_gate.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/components/currency.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/components/files.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/components/journal.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/components/notes.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/components/phone.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/components/report_kit.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/components/table.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/config.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/i18n.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/marketplace_catalog.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/routes/__init__.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/routes/account.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/routes/accounting.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/routes/accounting_import.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/routes/contacts.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/routes/csv_import.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/routes/dashboard.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/routes/docs_import.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/routes/events.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/routes/financial_reports.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/routes/lists_import.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/routes/manufacturing.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/routes/modules_page.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/routes/reconciliation.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/routes/reports.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/routes/settings_accounting.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/routes/settings_cloud.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/routes/settings_connectors.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/routes/settings_contacts.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/routes/settings_general.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/routes/settings_import.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/routes/settings_inventory.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/routes/settings_manufacturing.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/routes/settings_payments.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/routes/settings_purchasing.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/routes/settings_sales.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/routes/setup.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/routes/subscriptions.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/routes/subscriptions_import.py +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/static/app.css +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/static/htmx.min.js +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/static/icon.png +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/static/logo.png +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/static/vendor/intl-tel-input/img/flags.webp +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/static/vendor/intl-tel-input/img/flags@2x.webp +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/static/vendor/intl-tel-input/intlTelInput.min.css +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/static/vendor/intl-tel-input/intlTelInput.min.js +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/static/vendor/intl-tel-input/intlTelInputWithUtils.min.js +0 -0
- {celerp-2.3.4 → celerp-2.3.6}/ui/static/vendor/intl-tel-input/utils.js +0 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Copyright (c) 2026 Noah Severs
|
|
2
|
+
# SPDX-License-Identifier: BUSL-1.1
|
|
3
|
+
"""Request-capacity constants shared by the API pool and the local UI transport.
|
|
4
|
+
|
|
5
|
+
The app launches one uvicorn worker (both `celerp start` and the Electron shell
|
|
6
|
+
run `uvicorn celerp.main:app` without `--workers`), so one API process owns one
|
|
7
|
+
request connection pool. These constants are the single source of truth for that
|
|
8
|
+
pool's size and for how many connections the web UI is allowed to open into that
|
|
9
|
+
process, so the two can never drift apart. The base size is the interactive
|
|
10
|
+
ceiling; the overflow is held back for background work, relay, auth, and bursts.
|
|
11
|
+
|
|
12
|
+
This module has no side effects and imports nothing from the app, so both the
|
|
13
|
+
database engine and the UI transport can read it without an import cycle.
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
# Base connections the single API request pool keeps open, and the extra it may
|
|
18
|
+
# open under burst before refusing. The web UI's interactive transport uses the
|
|
19
|
+
# base size as its own connection ceiling, so a page fan-out can never demand
|
|
20
|
+
# more connections than the pool holds for interactive work.
|
|
21
|
+
REQUEST_DB_POOL_SIZE = 10
|
|
22
|
+
REQUEST_DB_MAX_OVERFLOW = 5
|
|
@@ -9,6 +9,7 @@ from sqlalchemy import text as _sql_text
|
|
|
9
9
|
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
|
|
10
10
|
from sqlalchemy.pool import NullPool
|
|
11
11
|
|
|
12
|
+
from celerp.capacity import REQUEST_DB_MAX_OVERFLOW, REQUEST_DB_POOL_SIZE
|
|
12
13
|
from celerp.config import settings
|
|
13
14
|
|
|
14
15
|
# Shared advisory-lock key that serialises schema migrations across every process
|
|
@@ -33,11 +34,13 @@ def mask_db_credentials(text: str) -> str:
|
|
|
33
34
|
_REQUEST_LOCK_TIMEOUT_MS = "3000"
|
|
34
35
|
_REQUEST_STATEMENT_TIMEOUT_MS = "30000"
|
|
35
36
|
|
|
36
|
-
# Pool budget:
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
37
|
+
# Pool budget: the app runs one API worker (both `celerp start` and the Electron
|
|
38
|
+
# shell launch uvicorn without --workers), so this single process owns one request
|
|
39
|
+
# pool of REQUEST_DB_POOL_SIZE base connections plus REQUEST_DB_MAX_OVERFLOW under
|
|
40
|
+
# burst. The base size is also the web UI's interactive connection ceiling, kept in
|
|
41
|
+
# lockstep through celerp.capacity so the two never drift. Postgres default
|
|
42
|
+
# max_connections=100 leaves ample headroom for migrations, admin tools, and
|
|
43
|
+
# background jobs beyond this pool.
|
|
41
44
|
if os.environ.get("CELERP_TEST_NULLPOOL"):
|
|
42
45
|
# Test mode: a fresh connection per use that closes on return, so a failed
|
|
43
46
|
# test can't leave a poisoned/locked connection lingering in a pooled
|
|
@@ -49,8 +52,8 @@ else:
|
|
|
49
52
|
settings.database_url,
|
|
50
53
|
future=True,
|
|
51
54
|
pool_pre_ping=True,
|
|
52
|
-
pool_size=
|
|
53
|
-
max_overflow=
|
|
55
|
+
pool_size=REQUEST_DB_POOL_SIZE,
|
|
56
|
+
max_overflow=REQUEST_DB_MAX_OVERFLOW,
|
|
54
57
|
# Bound how long any query may wait on a lock or run, so a stuck query is
|
|
55
58
|
# cancelled instead of pinning one of the few pooled connections for the
|
|
56
59
|
# full request lifetime (lock_timeout 3s, statement_timeout 30s, in ms).
|
|
@@ -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:
|
|
@@ -15,6 +15,8 @@ from celerp.inventory_codes import ( # noqa: F401 - re-exported for celerp_inve
|
|
|
15
15
|
SKU_COMMA_MESSAGE,
|
|
16
16
|
reject_comma_sku,
|
|
17
17
|
validate_barcode,
|
|
18
|
+
validate_gtin,
|
|
19
|
+
validate_rfid_epc,
|
|
18
20
|
validate_sku,
|
|
19
21
|
)
|
|
20
22
|
|
|
@@ -24,23 +26,50 @@ from celerp.inventory_codes import ( # noqa: F401 - re-exported for celerp_inve
|
|
|
24
26
|
# -----------------
|
|
25
27
|
|
|
26
28
|
|
|
29
|
+
def _normalize_item_codes(data: dict) -> None:
|
|
30
|
+
"""Validate and canonicalize an item event's identity codes IN PLACE.
|
|
31
|
+
|
|
32
|
+
SKU: no comma (Celerp's OR operator in the SKU/search syntax) and a bounded length.
|
|
33
|
+
Barcode: digits only and a bounded length. GTIN: digits only, standard length; the
|
|
34
|
+
validated value replaces the input so the stored form is canonical. rfid_epc trims
|
|
35
|
+
and upper-cases so the stored value equals what the availability check and the
|
|
36
|
+
partial unique index compare against - a case-variant EPC then collides. This is the
|
|
37
|
+
single normalization used both by the schema validator (on the validated copy) and by
|
|
38
|
+
the event boundary (on the persisted dict), so storage, lookup, and uniqueness agree.
|
|
39
|
+
"""
|
|
40
|
+
validate_sku(data.get("sku"))
|
|
41
|
+
validate_barcode(data.get("barcode"))
|
|
42
|
+
if "gtin" in data:
|
|
43
|
+
data["gtin"] = validate_gtin(data.get("gtin"))
|
|
44
|
+
if "rfid_epc" in data:
|
|
45
|
+
data["rfid_epc"] = validate_rfid_epc(data.get("rfid_epc"))
|
|
46
|
+
|
|
47
|
+
|
|
27
48
|
class _SkuGuard(BaseModel):
|
|
28
49
|
"""Mixin: enforce the SKU and barcode invariants on any item event that sets them.
|
|
29
50
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
receiving). Replay never runs these schemas (the reducer does), so enforcing here
|
|
35
|
-
can never brick a historical projection.
|
|
51
|
+
The canonical write-time predicates live in _normalize_item_codes; interactive routes
|
|
52
|
+
wrap them to surface a friendly 422, every other emitter hits them here at the event
|
|
53
|
+
boundary (imports, connector upserts, document receiving). Replay never runs these
|
|
54
|
+
schemas (the reducer does), so enforcing here can never brick a historical projection.
|
|
36
55
|
"""
|
|
37
56
|
|
|
57
|
+
@classmethod
|
|
58
|
+
def normalize_for_storage(cls, data: dict) -> None:
|
|
59
|
+
"""Canonicalize identity codes on the dict that will be persisted (in place).
|
|
60
|
+
|
|
61
|
+
The schema validator runs against a Pydantic-made copy, so its normalization
|
|
62
|
+
never reaches the raw event data the engine stores. The engine calls this on the
|
|
63
|
+
persisted dict itself so the stored value is the canonical one.
|
|
64
|
+
"""
|
|
65
|
+
if isinstance(data, dict):
|
|
66
|
+
_normalize_item_codes(data)
|
|
67
|
+
|
|
38
68
|
@model_validator(mode="before")
|
|
39
69
|
@classmethod
|
|
40
70
|
def _guard_item_codes(cls, data: Any) -> Any:
|
|
41
71
|
if isinstance(data, dict):
|
|
42
|
-
|
|
43
|
-
validate_barcode(data.get("barcode"))
|
|
72
|
+
_normalize_item_codes(data)
|
|
44
73
|
return data
|
|
45
74
|
|
|
46
75
|
|
|
@@ -81,15 +110,35 @@ class ItemSnapshot(_SkuGuard):
|
|
|
81
110
|
model_config = {"extra": "allow"} # CIF adapters may pass source-specific fields
|
|
82
111
|
|
|
83
112
|
|
|
113
|
+
def _normalize_updated_codes(fields_changed: dict) -> None:
|
|
114
|
+
"""Validate and canonicalize the new identity-code values of an item.updated in place."""
|
|
115
|
+
if "sku" in fields_changed:
|
|
116
|
+
validate_sku((fields_changed.get("sku") or {}).get("new"))
|
|
117
|
+
if "barcode" in fields_changed:
|
|
118
|
+
validate_barcode((fields_changed.get("barcode") or {}).get("new"))
|
|
119
|
+
if "gtin" in fields_changed:
|
|
120
|
+
change = fields_changed["gtin"] or {}
|
|
121
|
+
change["new"] = validate_gtin(change.get("new"))
|
|
122
|
+
fields_changed["gtin"] = change
|
|
123
|
+
if "rfid_epc" in fields_changed:
|
|
124
|
+
change = fields_changed["rfid_epc"] or {}
|
|
125
|
+
# Canonicalize the new value so the stored/looked-up form matches.
|
|
126
|
+
change["new"] = validate_rfid_epc(change.get("new"))
|
|
127
|
+
fields_changed["rfid_epc"] = change
|
|
128
|
+
|
|
129
|
+
|
|
84
130
|
class ItemUpdated(BaseModel):
|
|
85
131
|
fields_changed: dict[str, dict[str, Any]]
|
|
86
132
|
|
|
133
|
+
@classmethod
|
|
134
|
+
def normalize_for_storage(cls, data: dict) -> None:
|
|
135
|
+
"""Canonicalize the persisted item.updated data's new identity values (in place)."""
|
|
136
|
+
if isinstance(data, dict) and isinstance(data.get("fields_changed"), dict):
|
|
137
|
+
_normalize_updated_codes(data["fields_changed"])
|
|
138
|
+
|
|
87
139
|
@model_validator(mode="after")
|
|
88
140
|
def _guard_item_codes(self) -> "ItemUpdated":
|
|
89
|
-
|
|
90
|
-
validate_sku((self.fields_changed.get("sku") or {}).get("new"))
|
|
91
|
-
if "barcode" in self.fields_changed:
|
|
92
|
-
validate_barcode((self.fields_changed.get("barcode") or {}).get("new"))
|
|
141
|
+
_normalize_updated_codes(self.fields_changed)
|
|
93
142
|
return self
|
|
94
143
|
|
|
95
144
|
|
|
@@ -20,7 +20,9 @@ import os
|
|
|
20
20
|
import time
|
|
21
21
|
import uuid
|
|
22
22
|
from typing import Any
|
|
23
|
+
from urllib.parse import unquote
|
|
23
24
|
|
|
25
|
+
import httpx
|
|
24
26
|
import websockets
|
|
25
27
|
from websockets.exceptions import ConnectionClosed
|
|
26
28
|
|
|
@@ -42,6 +44,31 @@ _REAP_GRACE = 5 * 60 # seconds
|
|
|
42
44
|
_SEND_DEADLINE = 30 # seconds
|
|
43
45
|
|
|
44
46
|
|
|
47
|
+
class _SharedProxyTransport(httpx.AsyncHTTPTransport):
|
|
48
|
+
"""A shared bounded transport reused by a fresh AsyncClient per proxied request.
|
|
49
|
+
|
|
50
|
+
Each proxied browser request gets its own cheap AsyncClient so no cookie state
|
|
51
|
+
carries between visitors, but they all drive requests through this one
|
|
52
|
+
transport, whose Limits cap how many connections the gateway opens into the
|
|
53
|
+
local app. httpx.AsyncClient.__aexit__ (and aclose) unconditionally closes its
|
|
54
|
+
transport, so a per-request client leaving its `async with` block would tear
|
|
55
|
+
the shared pool out from under a sibling request. The per-client close is
|
|
56
|
+
therefore a no-op here: the pool outlives every client and is torn down only
|
|
57
|
+
by shutdown_pool(), called once from GatewayClient.close(). This mirrors the
|
|
58
|
+
UI's own shared-transport pattern; a local copy avoids the gateway layer
|
|
59
|
+
depending on the UI layer.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
async def aclose(self) -> None:
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
async def __aexit__(self, *exc_info) -> None:
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
async def shutdown_pool(self) -> None:
|
|
69
|
+
await super().aclose()
|
|
70
|
+
|
|
71
|
+
|
|
45
72
|
def _shop_key(handle: str | None) -> str:
|
|
46
73
|
"""Normalize a Shopify store handle/shop domain for comparison (strip scheme,
|
|
47
74
|
trailing slash, and the .myshopify.com suffix), so a token's `store_handle`
|
|
@@ -95,10 +122,12 @@ class GatewayClient:
|
|
|
95
122
|
# request that outlived its socket can be recognised as stale and dropped
|
|
96
123
|
# rather than sent on the freshly rebuilt connection.
|
|
97
124
|
self._generation: int = 0
|
|
98
|
-
# One shared
|
|
99
|
-
#
|
|
100
|
-
#
|
|
101
|
-
|
|
125
|
+
# One shared bounded transport for all proxied requests, built lazily. Each
|
|
126
|
+
# proxied request opens its own cheap AsyncClient over this transport, so no
|
|
127
|
+
# cookie state carries between visitors, while the transport's Limits cap how
|
|
128
|
+
# many connections the gateway opens into the local app (a fresh full client
|
|
129
|
+
# per request opened an unbounded number of pools under load).
|
|
130
|
+
self._http_transport: _SharedProxyTransport | None = None
|
|
102
131
|
# Observability counters for the proxy path, surfaced when the connection ends.
|
|
103
132
|
self._proxied_count = 0
|
|
104
133
|
self._cancelled_count = 0
|
|
@@ -233,7 +262,7 @@ class GatewayClient:
|
|
|
233
262
|
await ws.close()
|
|
234
263
|
except Exception:
|
|
235
264
|
pass
|
|
236
|
-
await self.
|
|
265
|
+
await self._close_http_transport()
|
|
237
266
|
|
|
238
267
|
async def _should_reap(self) -> bool:
|
|
239
268
|
"""A free instance with no live share and no recent proxied request should
|
|
@@ -596,31 +625,64 @@ class GatewayClient:
|
|
|
596
625
|
except Exception as exc:
|
|
597
626
|
log.warning("invoice.payment handling failed (entity=%s): %s", entity_id, exc)
|
|
598
627
|
|
|
599
|
-
def
|
|
600
|
-
"""Return the one shared
|
|
601
|
-
it lazily on first use.
|
|
602
|
-
requests
|
|
603
|
-
requests can no longer open an unbounded number of pools. The
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
self._http_client = httpx.AsyncClient(
|
|
609
|
-
timeout=180.0,
|
|
628
|
+
def _get_http_transport(self) -> _SharedProxyTransport:
|
|
629
|
+
"""Return the one shared bounded transport that every per-request proxy client
|
|
630
|
+
drives, building it lazily on first use. The bounded connection pool caps how
|
|
631
|
+
many concurrent requests the gateway can drive into the local app, so a burst
|
|
632
|
+
of relayed requests can no longer open an unbounded number of pools. The
|
|
633
|
+
per-request AsyncClient wrapper carries the timeout (the fixed cap, tightened
|
|
634
|
+
further by a per-request timeout_ms)."""
|
|
635
|
+
if self._http_transport is None:
|
|
636
|
+
self._http_transport = _SharedProxyTransport(
|
|
610
637
|
limits=httpx.Limits(max_connections=32, max_keepalive_connections=8),
|
|
611
638
|
)
|
|
612
|
-
return self.
|
|
613
|
-
|
|
614
|
-
async def
|
|
615
|
-
"""
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
639
|
+
return self._http_transport
|
|
640
|
+
|
|
641
|
+
async def _close_http_transport(self) -> None:
|
|
642
|
+
"""Shut the shared transport's pool down if one was built. Safe to call when
|
|
643
|
+
none was; nulling first makes a second close() a no-op, so the pool is torn
|
|
644
|
+
down exactly once."""
|
|
645
|
+
transport = self._http_transport
|
|
646
|
+
self._http_transport = None
|
|
647
|
+
if transport is not None:
|
|
619
648
|
try:
|
|
620
|
-
await
|
|
649
|
+
await transport.shutdown_pool()
|
|
621
650
|
except Exception:
|
|
622
651
|
pass
|
|
623
652
|
|
|
653
|
+
@staticmethod
|
|
654
|
+
def _policy_path(raw_path: Any) -> str | None:
|
|
655
|
+
"""Return the canonical local route a relay-supplied path resolves to.
|
|
656
|
+
|
|
657
|
+
httpx removes dot segments ('/x/../settings' -> '/settings') before it puts
|
|
658
|
+
the request on the wire, the local ASGI server then splits the target on the
|
|
659
|
+
first '?', percent-decodes the path once, and routes on the result; a browser
|
|
660
|
+
never sends a fragment. Classification and the local-only guard must run on
|
|
661
|
+
that same canonical value, not the raw wire string, or an encoded
|
|
662
|
+
('/settings/%66actory-reset'), dot-segment ('/x/../settings/factory-reset'),
|
|
663
|
+
or fragmented ('/settings/factory-reset#x') variant would slip past a raw
|
|
664
|
+
match yet still route to the blocked handler locally. The canonical path is
|
|
665
|
+
derived from httpx itself so it can never diverge from what httpx transmits.
|
|
666
|
+
Returns None for any path that cannot address a local route (non-string,
|
|
667
|
+
non-absolute, carrying a fragment or control character, or not decodable);
|
|
668
|
+
the caller refuses those with a neutral 400.
|
|
669
|
+
"""
|
|
670
|
+
if not isinstance(raw_path, str) or not raw_path.startswith("/"):
|
|
671
|
+
return None
|
|
672
|
+
if "#" in raw_path or any(ord(c) < 0x20 or ord(c) == 0x7f for c in raw_path):
|
|
673
|
+
return None
|
|
674
|
+
path_part = raw_path.split("?", 1)[0]
|
|
675
|
+
try:
|
|
676
|
+
transmitted = httpx.URL("http://127.0.0.1" + path_part).raw_path.split(b"?", 1)[0]
|
|
677
|
+
decoded = unquote(transmitted.decode("ascii"), errors="strict")
|
|
678
|
+
except (UnicodeDecodeError, ValueError, httpx.InvalidURL):
|
|
679
|
+
return None
|
|
680
|
+
if not decoded.startswith("/"):
|
|
681
|
+
return None
|
|
682
|
+
if "#" in decoded or any(ord(c) < 0x20 or ord(c) == 0x7f for c in decoded):
|
|
683
|
+
return None
|
|
684
|
+
return decoded
|
|
685
|
+
|
|
624
686
|
async def _handle_proxy_request(self, payload: dict) -> None:
|
|
625
687
|
"""Handle a proxied HTTP request from the relay.
|
|
626
688
|
|
|
@@ -644,11 +706,10 @@ class GatewayClient:
|
|
|
644
706
|
|
|
645
707
|
request_id = payload.get("id", "")
|
|
646
708
|
method = payload.get("method", "GET")
|
|
647
|
-
|
|
709
|
+
raw_path = payload.get("path")
|
|
648
710
|
query = payload.get("query", "")
|
|
649
711
|
headers = payload.get("headers", {})
|
|
650
712
|
body_b64 = payload.get("body_b64", "")
|
|
651
|
-
body = base64.b64decode(body_b64) if body_b64 else None
|
|
652
713
|
# Additive per-request deadline: a numeric timeout_ms bounds this request below
|
|
653
714
|
# the fixed cap so a single slow local handler can't pin a relay slot for 180s.
|
|
654
715
|
# Absent or non-numeric leaves the default cap in force.
|
|
@@ -657,17 +718,61 @@ class GatewayClient:
|
|
|
657
718
|
if isinstance(timeout_ms, (int, float)) and not isinstance(timeout_ms, bool) and timeout_ms > 0:
|
|
658
719
|
deadline_s = timeout_ms / 1000.0
|
|
659
720
|
|
|
721
|
+
# Validate the relay-supplied path before decoding the body or any
|
|
722
|
+
# classification, port selection, or local forwarding. Classification and
|
|
723
|
+
# the local-only guard run on the CANONICAL path the local server would
|
|
724
|
+
# route to (percent-decoded once, fragment stripped), so an encoded or
|
|
725
|
+
# fragmented variant of a blocked route cannot slip past a raw string
|
|
726
|
+
# match. A path that cannot address a local route is refused with a neutral
|
|
727
|
+
# 400 that echoes nothing back to the caller.
|
|
728
|
+
policy_path = self._policy_path(raw_path)
|
|
729
|
+
if policy_path is None:
|
|
730
|
+
await self._send(self._ws, {
|
|
731
|
+
"type": "http.response",
|
|
732
|
+
"payload": {
|
|
733
|
+
"id": request_id,
|
|
734
|
+
"status": 400,
|
|
735
|
+
"headers": [["content-type", "text/plain"]],
|
|
736
|
+
"body_b64": base64.b64encode(b"Invalid request path").decode(),
|
|
737
|
+
},
|
|
738
|
+
})
|
|
739
|
+
return
|
|
740
|
+
|
|
741
|
+
# Decode the body only after the path is known good. A malformed base64
|
|
742
|
+
# payload is contained with a neutral 400 rather than raising. validate=True
|
|
743
|
+
# rejects non-alphabet characters outright: the permissive default silently
|
|
744
|
+
# drops them, turning a malformed relay payload into a different request body
|
|
745
|
+
# instead of the intended containment.
|
|
746
|
+
try:
|
|
747
|
+
body = base64.b64decode(body_b64, validate=True) if body_b64 else None
|
|
748
|
+
except (ValueError, TypeError):
|
|
749
|
+
await self._send(self._ws, {
|
|
750
|
+
"type": "http.response",
|
|
751
|
+
"payload": {
|
|
752
|
+
"id": request_id,
|
|
753
|
+
"status": 400,
|
|
754
|
+
"headers": [["content-type", "text/plain"]],
|
|
755
|
+
"body_b64": base64.b64encode(b"Invalid request body").decode(),
|
|
756
|
+
},
|
|
757
|
+
})
|
|
758
|
+
return
|
|
759
|
+
|
|
660
760
|
# SSE / long-poll paths cannot be proxied over the WS request/response
|
|
661
|
-
# protocol.
|
|
662
|
-
|
|
663
|
-
|
|
761
|
+
# protocol. Answer with an immediate stub carrying a long retry: directive
|
|
762
|
+
# so the browser's EventSource backs off instead of reconnecting in a tight
|
|
763
|
+
# loop; real-time updates only work on direct local access. Mirrors the
|
|
764
|
+
# relay's own SSE suppression stub.
|
|
765
|
+
_streaming_paths = ("/events/stream",)
|
|
766
|
+
if any(policy_path == p or policy_path.startswith(p + "/") for p in _streaming_paths):
|
|
664
767
|
await self._send(self._ws, {
|
|
665
768
|
"type": "http.response",
|
|
666
769
|
"payload": {
|
|
667
770
|
"id": request_id,
|
|
668
771
|
"status": 200,
|
|
669
772
|
"headers": [["content-type", "text/event-stream"], ["cache-control", "no-cache"]],
|
|
670
|
-
"body_b64": base64.b64encode(
|
|
773
|
+
"body_b64": base64.b64encode(
|
|
774
|
+
b"retry: 3600000\n: sse not supported over relay\n\n"
|
|
775
|
+
).decode(),
|
|
671
776
|
},
|
|
672
777
|
})
|
|
673
778
|
return
|
|
@@ -678,7 +783,7 @@ class GatewayClient:
|
|
|
678
783
|
# setup is intentionally NOT blocked here - a headless cloud instance is provisioned
|
|
679
784
|
# through this same proxy, so blocking it would break cloud onboarding.
|
|
680
785
|
_local_only_paths = ("/settings/factory-reset",)
|
|
681
|
-
if any(
|
|
786
|
+
if any(policy_path == p or policy_path.startswith(p + "/") for p in _local_only_paths):
|
|
682
787
|
await self._send(self._ws, {
|
|
683
788
|
"type": "http.response",
|
|
684
789
|
"payload": {
|
|
@@ -692,35 +797,49 @@ class GatewayClient:
|
|
|
692
797
|
})
|
|
693
798
|
return
|
|
694
799
|
|
|
695
|
-
|
|
800
|
+
# Port is chosen from the canonical path (an encoded variant must map to
|
|
801
|
+
# the same local app), but the original path is forwarded verbatim so the
|
|
802
|
+
# local server performs its own decoding.
|
|
803
|
+
port = self._local_port_for(policy_path)
|
|
696
804
|
|
|
697
|
-
url = f"http://127.0.0.1:{port}{
|
|
805
|
+
url = f"http://127.0.0.1:{port}{raw_path}"
|
|
698
806
|
if query:
|
|
699
807
|
url = f"{url}?{query}"
|
|
700
808
|
|
|
701
|
-
client = self._get_http_client()
|
|
702
809
|
try:
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
810
|
+
# A fresh client per request over the shared bounded transport: the
|
|
811
|
+
# wrapper's cookie jar exists only for this one request and is discarded
|
|
812
|
+
# on block exit, so no auth state carries between visitors, while the
|
|
813
|
+
# transport's pool stays shared and bounded. follow_redirects=False is
|
|
814
|
+
# explicit so the local app's redirect and cookies go back to the real
|
|
815
|
+
# browser rather than being followed internally. Opened inside the try so
|
|
816
|
+
# any construction error routes to the except Exception -> 502 below.
|
|
817
|
+
async with httpx.AsyncClient(
|
|
818
|
+
transport=self._get_http_transport(),
|
|
819
|
+
timeout=180.0,
|
|
820
|
+
follow_redirects=False,
|
|
821
|
+
) as client:
|
|
822
|
+
if deadline_s is not None:
|
|
823
|
+
# asyncio.wait_for, not asyncio.timeout: the latter is 3.11+, and this package
|
|
824
|
+
# supports Python >=3.10 where it does not exist. wait_for cancels the request and
|
|
825
|
+
# raises TimeoutError on the deadline, which the handler below turns into an error
|
|
826
|
+
# response.
|
|
827
|
+
resp = await asyncio.wait_for(
|
|
828
|
+
client.request(
|
|
829
|
+
method=method,
|
|
830
|
+
url=url,
|
|
831
|
+
headers=headers,
|
|
832
|
+
content=body,
|
|
833
|
+
),
|
|
834
|
+
deadline_s,
|
|
835
|
+
)
|
|
836
|
+
else:
|
|
837
|
+
resp = await client.request(
|
|
710
838
|
method=method,
|
|
711
839
|
url=url,
|
|
712
840
|
headers=headers,
|
|
713
841
|
content=body,
|
|
714
|
-
)
|
|
715
|
-
deadline_s,
|
|
716
|
-
)
|
|
717
|
-
else:
|
|
718
|
-
resp = await client.request(
|
|
719
|
-
method=method,
|
|
720
|
-
url=url,
|
|
721
|
-
headers=headers,
|
|
722
|
-
content=body,
|
|
723
|
-
)
|
|
842
|
+
)
|
|
724
843
|
except asyncio.CancelledError:
|
|
725
844
|
# http.cancel aborted this request: emit nothing and let the cancellation
|
|
726
845
|
# propagate so the task is recorded cancelled.
|
|
@@ -730,7 +849,7 @@ class GatewayClient:
|
|
|
730
849
|
# The per-request deadline elapsed before the local handler answered. Surface
|
|
731
850
|
# it as an error response instead of hanging to the fixed cap.
|
|
732
851
|
self._timeout_count += 1
|
|
733
|
-
log.warning("Proxy request timed out after %sms for %s %s", timeout_ms, method,
|
|
852
|
+
log.warning("Proxy request timed out after %sms for %s %s", timeout_ms, method, raw_path)
|
|
734
853
|
if generation == self._generation:
|
|
735
854
|
await self._send(self._ws, {
|
|
736
855
|
"type": "http.response",
|
|
@@ -745,7 +864,7 @@ class GatewayClient:
|
|
|
745
864
|
self._stale_dropped_count += 1
|
|
746
865
|
return
|
|
747
866
|
except Exception as exc:
|
|
748
|
-
log.warning("Proxy request failed for %s %s: %s", method,
|
|
867
|
+
log.warning("Proxy request failed for %s %s: %s", method, raw_path, exc)
|
|
749
868
|
if generation == self._generation:
|
|
750
869
|
await self._send(self._ws, {
|
|
751
870
|
"type": "http.response",
|