celerp 2.3.1__tar.gz → 2.3.3__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.1 → celerp-2.3.3}/PKG-INFO +1 -1
- celerp-2.3.3/celerp/db.py +136 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/events/schemas.py +54 -4
- celerp-2.3.3/celerp/inventory_codes.py +87 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/main.py +20 -10
- celerp-2.3.3/celerp/migrations/versions/bc0d1e2f3a4b_barcode_unique_index.py +119 -0
- celerp-2.3.3/celerp/migrations/versions/c9d8e7f6a5b4_seed_shrinkage_writeoff_account.py +65 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/models/projections.py +16 -1
- {celerp-2.3.1 → celerp-2.3.3}/celerp/modules/migrations_runner.py +33 -29
- {celerp-2.3.1 → celerp-2.3.3}/celerp/projections/engine.py +26 -2
- {celerp-2.3.1 → celerp-2.3.3}/celerp/routers/doctor.py +8 -3
- {celerp-2.3.1 → celerp-2.3.3}/celerp/routers/ledger.py +15 -5
- {celerp-2.3.1 → celerp-2.3.3}/celerp/services/auto_je.py +2 -2
- {celerp-2.3.1 → celerp-2.3.3}/celerp/services/cost_visibility.py +17 -1
- {celerp-2.3.1 → celerp-2.3.3}/celerp/services/field_schema.py +20 -1
- {celerp-2.3.1 → celerp-2.3.3}/celerp/services/fulfill.py +22 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/services/system_health.py +19 -8
- {celerp-2.3.1 → celerp-2.3.3}/celerp/tax_regimes.py +16 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp.egg-info/PKG-INFO +1 -1
- {celerp-2.3.1 → celerp-2.3.3}/celerp.egg-info/SOURCES.txt +15 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp.egg-info/scm_file_list.json +32 -0
- celerp-2.3.3/celerp.egg-info/scm_version.json +8 -0
- {celerp-2.3.1 → celerp-2.3.3}/conftest.py +15 -1
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-accounting/celerp_accounting/routes.py +1 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-accounting/tests/report_goldens/statement.html +1 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-admin/celerp_admin/routes.py +34 -29
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-docs/celerp_docs/doc_constants.py +3 -2
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-docs/celerp_docs/doc_projections.py +11 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-docs/celerp_docs/routes.py +1014 -421
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-docs/celerp_docs/routes_payments.py +5 -5
- celerp-2.3.3/default_modules/celerp-docs/tests/test_audits.py +815 -0
- celerp-2.3.3/default_modules/celerp-docs/tests/test_bulk_payment_alloc_lock.py +502 -0
- celerp-2.3.3/default_modules/celerp-docs/tests/test_cross_lot_settlement.py +168 -0
- celerp-2.3.3/default_modules/celerp-docs/tests/test_list_concurrency.py +291 -0
- celerp-2.3.3/default_modules/celerp-docs/tests/test_list_outcome_concurrency.py +632 -0
- celerp-2.3.3/default_modules/celerp-docs/tests/test_memo_close.py +176 -0
- celerp-2.3.3/default_modules/celerp-docs/tests/test_memo_close_projection.py +26 -0
- celerp-2.3.3/default_modules/celerp-docs/tests/test_memo_close_review_fixes.py +450 -0
- celerp-2.3.3/default_modules/celerp-docs/tests/test_memo_lifecycle_concurrency.py +992 -0
- celerp-2.3.3/default_modules/celerp-docs/tests/test_memo_line_labels.py +191 -0
- celerp-2.3.3/default_modules/celerp-docs/tests/test_memo_unfulfilled_filter.py +47 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-docs/tests/test_send_visibility.py +16 -0
- celerp-2.3.3/default_modules/celerp-docs/tests/test_sold_label_ordering.py +95 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-docs/tests/test_writeoffs.py +303 -1
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-inventory/celerp_inventory/routes.py +527 -112
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-inventory/celerp_inventory/routes_scanning.py +3 -1
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-inventory/celerp_inventory/services.py +96 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-labels/celerp_labels/routes.py +5 -2
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-manufacturing/celerp_manufacturing/routes.py +25 -0
- celerp-2.3.3/default_modules/celerp-manufacturing/tests/test_completion_lock_order.py +240 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-manufacturing/tests/test_run_records.py +18 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/first_party.lock.json +6 -6
- {celerp-2.3.1 → celerp-2.3.3}/ui/api_client.py +56 -5
- {celerp-2.3.1 → celerp-2.3.3}/ui/components/shell.py +6 -2
- {celerp-2.3.1 → celerp-2.3.3}/ui/locales/am.json +27 -10
- {celerp-2.3.1 → celerp-2.3.3}/ui/locales/ar.json +27 -10
- {celerp-2.3.1 → celerp-2.3.3}/ui/locales/de.json +27 -10
- {celerp-2.3.1 → celerp-2.3.3}/ui/locales/en.json +28 -11
- {celerp-2.3.1 → celerp-2.3.3}/ui/locales/es.json +27 -10
- {celerp-2.3.1 → celerp-2.3.3}/ui/locales/fr.json +27 -10
- {celerp-2.3.1 → celerp-2.3.3}/ui/locales/id.json +27 -10
- {celerp-2.3.1 → celerp-2.3.3}/ui/locales/it.json +27 -10
- {celerp-2.3.1 → celerp-2.3.3}/ui/locales/ja.json +27 -10
- {celerp-2.3.1 → celerp-2.3.3}/ui/locales/pt.json +27 -10
- {celerp-2.3.1 → celerp-2.3.3}/ui/locales/th.json +27 -10
- {celerp-2.3.1 → celerp-2.3.3}/ui/locales/vi.json +27 -10
- {celerp-2.3.1 → celerp-2.3.3}/ui/routes/documents.py +398 -172
- {celerp-2.3.1 → celerp-2.3.3}/ui/routes/inventory.py +15 -1
- {celerp-2.3.1 → celerp-2.3.3}/ui/routes/settings_cloud.py +0 -12
- {celerp-2.3.1 → celerp-2.3.3}/ui/static/app.css +1 -1
- celerp-2.3.1/celerp/db.py +0 -61
- celerp-2.3.1/celerp.egg-info/scm_version.json +0 -8
- celerp-2.3.1/default_modules/celerp-docs/tests/test_audits.py +0 -355
- {celerp-2.3.1 → celerp-2.3.3}/CLA.md +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/CONTRIBUTING.md +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/LICENSE +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/LICENSING.md +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/MANIFEST.in +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/NOTICE +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/README.md +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/alembic.ini +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/__init__.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/__main__.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/ai/__init__.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/ai/batch.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/ai/cleanup.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/ai/commands.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/ai/conversations.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/ai/files.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/ai/intent.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/ai/llm.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/ai/memory.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/ai/models.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/ai/page_count.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/ai/quota.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/ai/service.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/ai/tools.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/alembic.ini +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/alembic_config.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/cli.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/compat.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/compute/__init__.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/compute/aggregations.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/compute/valuation.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/config.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/connectors/__init__.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/connectors/base.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/connectors/daily_scheduler.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/connectors/http.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/connectors/images.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/connectors/quickbooks.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/connectors/registry.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/connectors/relay_token.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/connectors/shopify.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/connectors/sync_runner.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/connectors/upsert.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/connectors/util.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/connectors/webhooks.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/connectors/woocommerce.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/connectors/xero.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/constants.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/embedded_pg.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/events/__init__.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/events/engine.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/events/types.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/gateway/__init__.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/gateway/client.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/gateway/state.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/importers/__init__.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/importers/importer.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/importers/schema.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/middleware.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/migrations/README.md +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/migrations/_auto_stamp.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/migrations/_data_reconcile.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/migrations/_json_compat.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/migrations/env.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/migrations/script.py.mako +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/migrations/versions/a0b1c2d3e4f5_seed_landed_cost_clearing_accounts.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/migrations/versions/a1b2c3d4e5f6_add_doc_share_tokens.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/migrations/versions/b1c2d3e4f5a6_add_supporter_badges.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/migrations/versions/b2c3d4e5f6a7_add_import_batches.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/migrations/versions/b3c4d5e6f7a8_expand_role_permissions_to_role_grants.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/migrations/versions/c1d2e3f4a5b6_add_marketplace.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/migrations/versions/c2d3e4f5a6b7_add_is_sync_to_shopify.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/migrations/versions/c4d5e6f7a8b9_account_cash_flow_category.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/migrations/versions/d0e1f2a3b4c5_add_share_token_expiry.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/migrations/versions/d2e3f4a5b6c7_add_password_reset_token.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/migrations/versions/d5e6f7a8b9c0_rename_fx_rounding_to_fx_difference.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/migrations/versions/e3f4a5b6c7d8_add_company_is_active.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/migrations/versions/e4f5a6b7c8d9_backfill_connector_idempotency_key.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/migrations/versions/e7c9a1b3d5f2_work_center_default_columns.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/migrations/versions/f1a2b3c4d5e6_move_accounts_marketplace_to_modules.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/migrations/versions/f6a7b8c9d0e1_seed_fx_rounding_account.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/migrations/versions/f8a9b0c1d2e3_work_center_default_backfill.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/migrations/versions/fd5de461e14e_initial_schema.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/migrations/versions/g1h2i3j4k5l6_add_consignment_flag.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/migrations/versions/h2i3j4k5l6m7_add_bank_accounts_and_reconciliation.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/migrations/versions/j4k5l6m7n8o9_labels_db_persistence.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/migrations/versions/k5l6m7n8o9p0_add_reconciliation_v2.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/migrations/versions/l6m7n8o9p0q1_add_sync_runs_table.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/migrations/versions/m7n8o9p0q1r2_add_connector_configs_and_outbound_queue.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/migrations/versions/n8o9p0q1r2s3_backfill_payment_date.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/migrations/versions/o9p0q1r2s3t4_backfill_item_price_fields.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/migrations/versions/p1q2r3s4t5u6_add_session_registry_and_user_auth_state.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/migrations/versions/q2r3s4t5u6v7_add_system_runtime_state.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/migrations/versions/r3s4t5u6v7w8_add_projections_company_entity_type_index.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/migrations/versions/s4t5u6v7w8x9_drop_user_company_id_role.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/migrations/versions/t5u6v7w8x9y0_seed_inventory_sub_accounts.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/migrations/versions/u6v7w8x9y0z1_backfill_item_prices_from_attributes.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/migrations/versions/v7w8x9y0z1a2_backfill_purchase_unit_defaults.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/migrations/versions/w8x9y0z1a2b3_backfill_category_unit_defaults.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/migrations/versions/x7y8z9a0b1c2_add_projection_created_at.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/migrations/versions/y8z9a0b1c2d3_per_company_ledger_idempotency.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/migrations/versions/z9a0b1c2d3e4_work_centers.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/models/__init__.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/models/accounting.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/models/ai.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/models/auth.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/models/base.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/models/company.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/models/connector_config.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/models/import_batch.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/models/ledger.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/models/marketplace.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/models/notification.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/models/share.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/models/supporter.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/models/sync_run.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/modules/__init__.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/modules/api.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/modules/audit.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/modules/demotion.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/modules/importer.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/modules/license.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/modules/loader.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/modules/meta.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/modules/references.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/modules/registry.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/modules/slots.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/notifications/__init__.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/notifications/service.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/notifications/sse.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/output/__init__.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/output/branding.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/output/doc_print.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/output/pdf.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/output/share_render.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/projections/__init__.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/projections/handlers/__init__.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/projections/handlers/marketplace.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/projections/handlers/scanning.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/projections/handlers/shopify.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/projections/handlers/system.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/routers/__init__.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/routers/auth.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/routers/companies.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/routers/debug.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/routers/events.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/routers/health.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/routers/notifications.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/routers/stars.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/routers/system.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/schemas/__init__.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/services/__init__.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/services/activity_redaction.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/services/attachments.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/services/auth.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/services/backup.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/services/backup_export.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/services/backup_import.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/services/backup_repo.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/services/backup_scheduler.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/services/backup_state.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/services/cogs_backfill.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/services/demo.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/services/dev_release_guard.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/services/email.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/services/holdings.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/services/je_keys.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/services/landed_cost.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/services/line_measures.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/services/list_behavior.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/services/money.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/services/payments.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/services/permissions.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/services/pick.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/services/pricing.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/services/relay_share.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/services/reorder.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/services/runtime_state.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/services/session_tracker.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/services/shipping.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/services/star_cta.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/services/status_doc_backfill.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/services/supporter_badge.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/services/units.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp/session_gate.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp.egg-info/dependency_links.txt +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp.egg-info/entry_points.txt +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp.egg-info/requires.txt +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/celerp.egg-info/top_level.txt +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/conftest_support.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/__init__.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-accounting/LICENSE +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-accounting/__init__.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-accounting/celerp_accounting/__init__.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-accounting/celerp_accounting/api_setup.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-accounting/celerp_accounting/csv_parser.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-accounting/celerp_accounting/matcher.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-accounting/celerp_accounting/models.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-accounting/celerp_accounting/projections.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-accounting/celerp_accounting/ui_routes.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-accounting/tests/report_goldens/balance-sheet.html +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-accounting/tests/report_goldens/cash-flow.html +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-accounting/tests/report_goldens/extended-journal-csv.csv +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-accounting/tests/report_goldens/extended-journal-print.html +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-accounting/tests/report_goldens/extended-journal.html +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-accounting/tests/report_goldens/general-ledger.html +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-accounting/tests/report_goldens/journal-csv.csv +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-accounting/tests/report_goldens/journal-print.html +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-accounting/tests/report_goldens/journal.html +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-accounting/tests/report_goldens/ledger.html +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-accounting/tests/report_goldens/pnl.html +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-accounting/tests/report_goldens/trial-balance.html +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-accounting/tests/test_accounting_banking.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-accounting/tests/test_accounting_banking_routers.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-accounting/tests/test_accounting_gaps.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-accounting/tests/test_accounting_import_idempotency.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-accounting/tests/test_cash_flow.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-accounting/tests/test_financial_reports_ui.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-accounting/tests/test_journal_gl_soa.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-accounting/tests/test_journal_gl_soa_ui.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-accounting/tests/test_reconciliation_v2.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-accounting/tests/test_reports_end_to_end.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-accounting/tests/test_route_permissions.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-admin/__init__.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-admin/celerp_admin/__init__.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-admin/celerp_admin/setup.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-ai/__init__.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-ai/celerp_ai/__init__.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-ai/celerp_ai/routes.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-ai/celerp_ai/setup.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-ai/celerp_ai/ui_routes.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-ai/tests/conftest.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-ai/tests/test_batch.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-ai/tests/test_cleanup.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-ai/tests/test_conversations.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-ai/tests/test_coverage_gaps.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-ai/tests/test_intent.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-ai/tests/test_llm.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-ai/tests/test_memory.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-ai/tests/test_models.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-ai/tests/test_page_count.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-ai/tests/test_quota.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-ai/tests/test_router.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-ai/tests/test_routes_coverage.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-ai/tests/test_service.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-ai/tests/test_tools.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-backup/__init__.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-backup/celerp_backup/__init__.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-backup/celerp_backup/routes.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-backup/celerp_backup/setup.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-connectors/__init__.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-connectors/celerp_connectors/__init__.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-connectors/celerp_connectors/models.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-connectors/celerp_connectors/routes.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-connectors/tests/__init__.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-connectors/tests/conftest.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-connectors/tests/test_compliance.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-connectors/tests/test_connector_correctness.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-connectors/tests/test_connectors.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-connectors/tests/test_credentials_routes.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-connectors/tests/test_images.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-connectors/tests/test_outbound.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-connectors/tests/test_quickbooks.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-connectors/tests/test_rate_limiting.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-connectors/tests/test_shopify.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-connectors/tests/test_wiring.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-connectors/tests/test_woocommerce.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-connectors/tests/test_woocommerce_webhooks.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-connectors/tests/test_xero.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-contacts/LICENSE +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-contacts/__init__.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-contacts/celerp_contacts/__init__.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-contacts/celerp_contacts/migrations.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-contacts/celerp_contacts/projections.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-contacts/celerp_contacts/routes.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-contacts/celerp_contacts/services.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-contacts/celerp_contacts/ui_routes.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-dashboard/LICENSE +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-dashboard/__init__.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-dashboard/celerp_dashboard/__init__.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-dashboard/celerp_dashboard/routes.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-dashboard/celerp_dashboard/setup.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-docs/LICENSE +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-docs/__init__.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-docs/celerp_docs/__init__.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-docs/celerp_docs/api_setup.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-docs/celerp_docs/doc_email.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-docs/celerp_docs/doc_service.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-docs/celerp_docs/models_share.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-docs/celerp_docs/routes_share.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-docs/celerp_docs/sequences.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-docs/celerp_docs/taxes.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-docs/celerp_docs/ui_routes.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-docs/tests/conftest.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-docs/tests/test_consolidate_lots.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-docs/tests/test_freight_je.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-docs/tests/test_freight_lines.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-docs/tests/test_fulfill_no_split.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-docs/tests/test_fulfillment_activity.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-docs/tests/test_inbound_je_lifecycle.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-docs/tests/test_inventory_account_reconciliation.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-docs/tests/test_landed_receive.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-docs/tests/test_line_delete_persist.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-docs/tests/test_line_identifier.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-docs/tests/test_payment_void_activity.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-docs/tests/test_receive_sku_batch.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-docs/tests/test_service_lines.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-docs/tests/test_void_guards.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-inventory/LICENSE +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-inventory/__init__.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-inventory/celerp_inventory/__init__.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-inventory/celerp_inventory/models_import_batch.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-inventory/celerp_inventory/projections.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-inventory/celerp_inventory/routes_attachments.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-inventory/celerp_inventory/ui_routes.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-inventory/tests/test_item_recipe_projection.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-inventory/tests/test_item_workflow_projection.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-inventory/tests/test_landed_cost_projection.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-labels/LICENSE +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-labels/__init__.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-labels/celerp_labels/__init__.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-labels/celerp_labels/locales/am.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-labels/celerp_labels/locales/ar.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-labels/celerp_labels/locales/de.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-labels/celerp_labels/locales/en.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-labels/celerp_labels/locales/es.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-labels/celerp_labels/locales/fr.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-labels/celerp_labels/locales/id.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-labels/celerp_labels/locales/it.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-labels/celerp_labels/locales/ja.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-labels/celerp_labels/locales/pt.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-labels/celerp_labels/locales/th.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-labels/celerp_labels/locales/vi.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-labels/celerp_labels/models.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-labels/celerp_labels/presets.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-labels/celerp_labels/service.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-labels/celerp_labels/ui_routes.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-labels/requirements.txt +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-labels/tests/test_labels.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-labels/tests/test_labels_module.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-manufacturing/LICENSE +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-manufacturing/__init__.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-manufacturing/celerp_manufacturing/__init__.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-manufacturing/celerp_manufacturing/costing.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-manufacturing/celerp_manufacturing/expansion.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-manufacturing/celerp_manufacturing/labor.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-manufacturing/celerp_manufacturing/migrations.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-manufacturing/celerp_manufacturing/projection_handler.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-manufacturing/celerp_manufacturing/ui_routes.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-manufacturing/requirements.txt +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-manufacturing/tests/test_auto_complete_on_finalize.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-manufacturing/tests/test_bom_removed.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-manufacturing/tests/test_build_api.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-manufacturing/tests/test_item_recipe_api.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-manufacturing/tests/test_item_workflow_api.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-manufacturing/tests/test_labor_providers.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-manufacturing/tests/test_manufacture_from_doc.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-manufacturing/tests/test_manufacturing.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-manufacturing/tests/test_manufacturing_import_idempotency.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-manufacturing/tests/test_manufacturing_module.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-manufacturing/tests/test_mfg_events.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-manufacturing/tests/test_phase2_manufacturing_validations.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-manufacturing/tests/test_production_order.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-manufacturing/tests/test_recipe_costing.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-manufacturing/tests/test_recipe_expansion.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-manufacturing/tests/test_recost_api.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-manufacturing/tests/test_to_make_allocation.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-manufacturing/tests/test_where_used.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-manufacturing/tests/test_work_centers_and_settings.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-manufacturing/tests/test_workflow_time.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-reports/LICENSE +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-reports/__init__.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-reports/celerp_reports/__init__.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-reports/celerp_reports/api_setup.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-reports/celerp_reports/routes.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-reports/celerp_reports/ui_routes.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-reports/tests/test_reports_full.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-reports/tests/test_reports_unit.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-subscriptions/LICENSE +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-subscriptions/__init__.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-subscriptions/celerp_subscriptions/__init__.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-subscriptions/celerp_subscriptions/routes.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-subscriptions/celerp_subscriptions/ui_routes.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-subscriptions/tests/test_subscriptions.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-subscriptions/tests/test_subscriptions_ui.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/LICENSE +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/__init__.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/__init__.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/accessory_fashion.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/activewear.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/art_photography.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/audio_equipment.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/bag_handbag.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/banknote.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/beauty_tool.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/bed_bedroom.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/beer.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/beverage_nonalc.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/body_part_auto.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/book.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/bottoms.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/brake_suspension.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/bullion_coin.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/camera.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/colored_stone.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/comic.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/commercial_space.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/component_part.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/confectionery.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/consulting_service.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/decorative_art.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/diamond.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/drawing.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/dress_jumpsuit.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/electrical_auto.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/electrical_component.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/emerald.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/engine_part.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/fastener.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/fertilizer_chemical.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/film_video.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/fine_writing_instrument.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/fluid_lubricant.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/footwear.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/fragrance.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/fresh_food.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/fresh_produce.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/frozen_food.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/gaming_console.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/gold_bullion.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/grain_cereal.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/haircare.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/hand_tool.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/ingredient_bulk.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/interior_part.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/jewelry.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/kitchen_dining.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/laptop.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/lighting.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/livestock_feed.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/makeup.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/measuring_instrument.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/medal_token.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/mineral_specimen.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/mobile_phone.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/music_cd.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/music_vinyl.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/nail.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/numismatic_coin.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/outdoor_furniture.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/outerwear.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/packaged_food.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/painting.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/parking_bay.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/pearl.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/personal_care.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/pipe_plumbing.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/platinum_bullion.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/power_tool.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/print_edition.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/residential_unit.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/rough_gemstone.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/ruby.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/saas_plan.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/safety_ppe.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/sapphire.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/sculpture.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/seating.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/seeds.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/silver_bullion.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/skincare.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/soft_furnishing.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/software_addon.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/software_license.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/spirit.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/storage_furniture.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/swimwear.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/table_desk.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/tablet.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/tire_wheel.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/tops.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/trading_card.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/tv_display.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/video_game.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/watch.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/watch_strap.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/categories/wine.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/presets/agricultural.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/presets/artwork.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/presets/automotive.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/presets/blank.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/presets/books_media.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/presets/coins_precious_metals.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/presets/consulting.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/presets/cosmetics.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/presets/electronics.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/presets/fashion.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/presets/food_beverage.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/presets/furniture.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/presets/gemstones.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/presets/hardware.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/presets/property_rental.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/presets/saas.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/presets/watches.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/presets/wine_spirits.json +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/routes.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/celerp_verticals/ui_routes.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/tests/test_verticals.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/default_modules/celerp-verticals/tests/test_verticals_routers.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/legal/OFFICIAL-COMPONENTS.md +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/legal/TRADEMARK.md +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/packaging/winget/Celerp.Celerp.installer.yaml +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/packaging/winget/Celerp.Celerp.locale.en-US.yaml +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/packaging/winget/Celerp.Celerp.yaml +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/packaging/winget/README.md +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/pyproject.toml +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/setup.cfg +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/test_helpers.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/__init__.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/app.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/components/__init__.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/components/activity.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/components/attrs.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/components/backup.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/components/cloud_gate.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/components/currency.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/components/files.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/components/journal.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/components/notes.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/components/phone.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/components/report_kit.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/components/table.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/config.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/i18n.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/marketplace_catalog.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/routes/__init__.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/routes/account.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/routes/accounting.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/routes/accounting_import.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/routes/auth.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/routes/contacts.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/routes/csv_import.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/routes/dashboard.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/routes/docs_import.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/routes/events.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/routes/financial_reports.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/routes/lists_import.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/routes/manufacturing.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/routes/modules_page.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/routes/notifications.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/routes/reconciliation.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/routes/reports.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/routes/search.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/routes/settings.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/routes/settings_accounting.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/routes/settings_connectors.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/routes/settings_contacts.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/routes/settings_general.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/routes/settings_import.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/routes/settings_inventory.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/routes/settings_manufacturing.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/routes/settings_payments.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/routes/settings_purchasing.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/routes/settings_sales.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/routes/setup.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/routes/stars.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/routes/subscriptions.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/routes/subscriptions_import.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/security.py +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/static/htmx.min.js +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/static/icon.png +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/static/logo.png +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/static/vendor/intl-tel-input/img/flags.webp +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/static/vendor/intl-tel-input/img/flags@2x.webp +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/static/vendor/intl-tel-input/intlTelInput.min.css +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/static/vendor/intl-tel-input/intlTelInput.min.js +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/ui/static/vendor/intl-tel-input/intlTelInputWithUtils.min.js +0 -0
- {celerp-2.3.1 → celerp-2.3.3}/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
|
|
@@ -5,7 +5,18 @@ from __future__ import annotations
|
|
|
5
5
|
|
|
6
6
|
from typing import Any
|
|
7
7
|
|
|
8
|
-
from pydantic import BaseModel, Field
|
|
8
|
+
from pydantic import BaseModel, Field, model_validator
|
|
9
|
+
|
|
10
|
+
# The SKU/barcode write-time predicates live in celerp.inventory_codes so the event
|
|
11
|
+
# boundary, the interactive routes, the allocation service, and the scanner share one
|
|
12
|
+
# source of truth. reject_comma_sku/SKU_COMMA_MESSAGE are re-exported here because
|
|
13
|
+
# celerp_inventory.routes imports them from this module.
|
|
14
|
+
from celerp.inventory_codes import ( # noqa: F401 - re-exported for celerp_inventory
|
|
15
|
+
SKU_COMMA_MESSAGE,
|
|
16
|
+
reject_comma_sku,
|
|
17
|
+
validate_barcode,
|
|
18
|
+
validate_sku,
|
|
19
|
+
)
|
|
9
20
|
|
|
10
21
|
|
|
11
22
|
# -----------------
|
|
@@ -13,7 +24,27 @@ from pydantic import BaseModel, Field
|
|
|
13
24
|
# -----------------
|
|
14
25
|
|
|
15
26
|
|
|
16
|
-
class
|
|
27
|
+
class _SkuGuard(BaseModel):
|
|
28
|
+
"""Mixin: enforce the SKU and barcode invariants on any item event that sets them.
|
|
29
|
+
|
|
30
|
+
SKU: no comma (Celerp's OR operator in the SKU/search syntax) and a bounded length.
|
|
31
|
+
Barcode: digits only and a bounded length. These are the canonical write-time
|
|
32
|
+
predicates; interactive routes wrap them to surface a friendly 422, every other
|
|
33
|
+
emitter hits them here at the event boundary (imports, connector upserts, document
|
|
34
|
+
receiving). Replay never runs these schemas (the reducer does), so enforcing here
|
|
35
|
+
can never brick a historical projection.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
@model_validator(mode="before")
|
|
39
|
+
@classmethod
|
|
40
|
+
def _guard_item_codes(cls, data: Any) -> Any:
|
|
41
|
+
if isinstance(data, dict):
|
|
42
|
+
validate_sku(data.get("sku"))
|
|
43
|
+
validate_barcode(data.get("barcode"))
|
|
44
|
+
return data
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class ItemCreated(_SkuGuard):
|
|
17
48
|
sku: str
|
|
18
49
|
name: str
|
|
19
50
|
quantity: float = 0
|
|
@@ -28,7 +59,7 @@ class ItemCreated(BaseModel):
|
|
|
28
59
|
model_config = {"extra": "allow"}
|
|
29
60
|
|
|
30
61
|
|
|
31
|
-
class ItemSnapshot(
|
|
62
|
+
class ItemSnapshot(_SkuGuard):
|
|
32
63
|
"""Snapshot of full item state - used for imports. sku/name optional (absent in some sources)."""
|
|
33
64
|
|
|
34
65
|
sku: str | None = None
|
|
@@ -53,6 +84,14 @@ class ItemSnapshot(BaseModel):
|
|
|
53
84
|
class ItemUpdated(BaseModel):
|
|
54
85
|
fields_changed: dict[str, dict[str, Any]]
|
|
55
86
|
|
|
87
|
+
@model_validator(mode="after")
|
|
88
|
+
def _guard_item_codes(self) -> "ItemUpdated":
|
|
89
|
+
if "sku" in self.fields_changed:
|
|
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"))
|
|
93
|
+
return self
|
|
94
|
+
|
|
56
95
|
|
|
57
96
|
class ItemPricingSet(BaseModel):
|
|
58
97
|
price_type: str
|
|
@@ -188,7 +227,7 @@ class ItemMerged(BaseModel):
|
|
|
188
227
|
source_entity_ids: list[str]
|
|
189
228
|
|
|
190
229
|
|
|
191
|
-
class ItemPatched(
|
|
230
|
+
class ItemPatched(_SkuGuard):
|
|
192
231
|
"""CSV upsert patch: accepts any item data fields."""
|
|
193
232
|
model_config = {"extra": "allow"}
|
|
194
233
|
|
|
@@ -507,6 +546,15 @@ class DocUnvoided(BaseModel):
|
|
|
507
546
|
restored_status: str
|
|
508
547
|
|
|
509
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
|
+
|
|
510
558
|
class DocSent(BaseModel):
|
|
511
559
|
sent_via: str | None = None
|
|
512
560
|
sent_to: str | None = None
|
|
@@ -1123,6 +1171,8 @@ EVENT_SCHEMA_MAP: dict[str, type[BaseModel]] = {
|
|
|
1123
1171
|
"doc.voided": DocVoided,
|
|
1124
1172
|
"doc.reverted_to_draft": DocRevertedToDraft,
|
|
1125
1173
|
"doc.unvoided": DocUnvoided,
|
|
1174
|
+
"doc.closed": DocClosed,
|
|
1175
|
+
"doc.reopened": DocReopened,
|
|
1126
1176
|
"doc.converted_to_bill": DocConvertedToBill,
|
|
1127
1177
|
"doc.sent": DocSent,
|
|
1128
1178
|
"doc.payment.received": DocPaymentReceived,
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Copyright (c) 2026 Noah Severs
|
|
2
|
+
# SPDX-License-Identifier: BUSL-1.1
|
|
3
|
+
|
|
4
|
+
"""Canonical inventory code limits and validation - one source of truth.
|
|
5
|
+
|
|
6
|
+
The SKU/barcode invariants live here so the event boundary (celerp.events.schemas),
|
|
7
|
+
the interactive inventory routes, the allocation service, and the scanner all share
|
|
8
|
+
the exact same rules. A comma is Celerp's OR operator in the SKU/search syntax, so a
|
|
9
|
+
SKU may never contain one (it would split into separate codes wherever SKUs are
|
|
10
|
+
matched). A barcode is a numeric physical-lot identifier and must be unique per
|
|
11
|
+
company - enforced at the event boundary (format) and by a partial unique index on
|
|
12
|
+
the projections table (uniqueness).
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
MAX_BARCODE_LEN = 64
|
|
20
|
+
MAX_SKU_LEN = 255
|
|
21
|
+
MAX_SCAN_CODE_LEN = max(MAX_BARCODE_LEN, MAX_SKU_LEN)
|
|
22
|
+
|
|
23
|
+
# Partial unique index enforcing at-most-one item per (company, barcode). Declared on
|
|
24
|
+
# the Projection model so create_all builds it for the test schema, and created on
|
|
25
|
+
# existing databases by the barcode-uniqueness migration. Named here so the applier
|
|
26
|
+
# can tell a barcode violation apart from the (company_id, entity_id) primary-key race.
|
|
27
|
+
BARCODE_UNIQUE_INDEX = "uq_projection_company_item_barcode"
|
|
28
|
+
|
|
29
|
+
SKU_COMMA_MESSAGE = "SKU cannot contain a comma"
|
|
30
|
+
SKU_TOO_LONG_MESSAGE = f"SKU cannot exceed {MAX_SKU_LEN} characters"
|
|
31
|
+
BARCODE_NOT_DIGITS_MESSAGE = "Barcode must contain only digits"
|
|
32
|
+
BARCODE_TOO_LONG_MESSAGE = f"Barcode cannot exceed {MAX_BARCODE_LEN} characters"
|
|
33
|
+
BARCODE_CONFLICT_MESSAGE = "Barcode '{barcode}' already exists"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def reject_comma_sku(sku: Any) -> Any:
|
|
37
|
+
"""Reject a comma-bearing SKU. Returns the sku for chaining."""
|
|
38
|
+
if sku is not None and "," in str(sku):
|
|
39
|
+
raise ValueError(SKU_COMMA_MESSAGE)
|
|
40
|
+
return sku
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def validate_sku(sku: Any) -> Any:
|
|
44
|
+
"""Reject a comma-bearing or over-long SKU. Returns the sku for chaining."""
|
|
45
|
+
reject_comma_sku(sku)
|
|
46
|
+
if sku is not None and len(str(sku)) > MAX_SKU_LEN:
|
|
47
|
+
raise ValueError(SKU_TOO_LONG_MESSAGE)
|
|
48
|
+
return sku
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def validate_barcode(barcode: Any) -> Any:
|
|
52
|
+
"""Reject a non-digit or over-long barcode. An absent/empty barcode is allowed."""
|
|
53
|
+
if barcode is None:
|
|
54
|
+
return barcode
|
|
55
|
+
s = str(barcode)
|
|
56
|
+
if s == "":
|
|
57
|
+
return barcode
|
|
58
|
+
if not s.isdigit():
|
|
59
|
+
raise ValueError(BARCODE_NOT_DIGITS_MESSAGE)
|
|
60
|
+
if len(s) > MAX_BARCODE_LEN:
|
|
61
|
+
raise ValueError(BARCODE_TOO_LONG_MESSAGE)
|
|
62
|
+
return barcode
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class BarcodeConflictError(Exception):
|
|
66
|
+
"""A write violated the (company, item, barcode) uniqueness invariant.
|
|
67
|
+
|
|
68
|
+
Raised by the projection applier when the DB unique index rejects an insert - the
|
|
69
|
+
final defense for any writer that bypasses the application lock (imports,
|
|
70
|
+
connectors, a future writer). The API layer maps it to 409.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
def __init__(self, barcode: str | None = None):
|
|
74
|
+
self.barcode = barcode
|
|
75
|
+
super().__init__(
|
|
76
|
+
BARCODE_CONFLICT_MESSAGE.format(barcode=barcode) if barcode else "Barcode already exists"
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def is_barcode_unique_violation(exc: Exception) -> bool:
|
|
81
|
+
"""True when an IntegrityError is the barcode unique-index violation (not the PK race)."""
|
|
82
|
+
orig = getattr(exc, "orig", None)
|
|
83
|
+
if getattr(orig, "constraint_name", None) == BARCODE_UNIQUE_INDEX:
|
|
84
|
+
return True
|
|
85
|
+
# Expression-index violations do not always populate constraint_name across drivers;
|
|
86
|
+
# the index name still appears in the server message.
|
|
87
|
+
return BARCODE_UNIQUE_INDEX in str(exc)
|
|
@@ -12,7 +12,8 @@ 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
|
+
from celerp.inventory_codes import BarcodeConflictError
|
|
16
17
|
from celerp.config import settings, assert_secure_jwt, ensure_instance_id, load_cloud_config, load_backup_config
|
|
17
18
|
load_cloud_config()
|
|
18
19
|
load_backup_config()
|
|
@@ -163,7 +164,7 @@ async def _try_auto_activate() -> None:
|
|
|
163
164
|
async def lifespan(_app: FastAPI):
|
|
164
165
|
(settings.data_dir / "static" / "attachments").mkdir(parents=True, exist_ok=True)
|
|
165
166
|
try:
|
|
166
|
-
async with
|
|
167
|
+
async with lifecycle_engine.begin() as conn:
|
|
167
168
|
await conn.run_sync(Base.metadata.create_all)
|
|
168
169
|
except Exception as exc:
|
|
169
170
|
masked_url = mask_db_credentials(settings.database_url)
|
|
@@ -204,19 +205,20 @@ async def lifespan(_app: FastAPI):
|
|
|
204
205
|
register_api_routes(_app, _loaded_modules)
|
|
205
206
|
# Module models register on Base.metadata at import time.
|
|
206
207
|
# Run create_all again so module tables are created (idempotent).
|
|
207
|
-
async with
|
|
208
|
+
async with lifecycle_engine.begin() as conn:
|
|
208
209
|
await conn.run_sync(Base.metadata.create_all)
|
|
209
210
|
# Allow modules to backfill data for existing companies (e.g. seed
|
|
210
211
|
# chart of accounts when accounting module is first enabled on an
|
|
211
212
|
# instance that already has companies).
|
|
212
213
|
from celerp.modules.slots import fire_lifecycle as _fire
|
|
213
|
-
from celerp.db import
|
|
214
|
+
from celerp.db import LifecycleSessionLocal as _LifecycleSession
|
|
214
215
|
# Best-effort, like the two sibling blocks below: a hook that fails
|
|
215
216
|
# during flush poisons the shared session, so the commit raises.
|
|
216
217
|
# Roll back and log at ERROR rather than let that crash boot - the
|
|
217
218
|
# manufacturing seed hook, for one, must never be able to take the
|
|
218
|
-
# app down.
|
|
219
|
-
|
|
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:
|
|
220
222
|
try:
|
|
221
223
|
await _fire("on_modules_ready", session=_sess)
|
|
222
224
|
await _sess.commit()
|
|
@@ -235,7 +237,7 @@ async def lifespan(_app: FastAPI):
|
|
|
235
237
|
_demoted = demoted_first_party(_enabled)
|
|
236
238
|
if _demoted:
|
|
237
239
|
from celerp.modules.demotion import notify_demoted_modules
|
|
238
|
-
async with
|
|
240
|
+
async with _LifecycleSession() as _dsess:
|
|
239
241
|
await notify_demoted_modules(_dsess, _demoted)
|
|
240
242
|
await _dsess.commit()
|
|
241
243
|
except Exception:
|
|
@@ -255,7 +257,7 @@ async def lifespan(_app: FastAPI):
|
|
|
255
257
|
# marker so it runs once per version. Non-fatal: a failure must not block
|
|
256
258
|
# boot — the marker stays unset and a later boot retries.
|
|
257
259
|
try:
|
|
258
|
-
from celerp.db import
|
|
260
|
+
from celerp.db import LifecycleSessionLocal as _GuardSession
|
|
259
261
|
from celerp.services.dev_release_guard import run_upgrade_guard
|
|
260
262
|
async with _GuardSession() as _guard_sess:
|
|
261
263
|
await run_upgrade_guard(_guard_sess)
|
|
@@ -270,7 +272,7 @@ async def lifespan(_app: FastAPI):
|
|
|
270
272
|
# or consigned in before that field shipped, so their inventory status links
|
|
271
273
|
# to its document. Marker-gated (runs once); non-fatal like the guard above.
|
|
272
274
|
try:
|
|
273
|
-
from celerp.db import
|
|
275
|
+
from celerp.db import LifecycleSessionLocal as _BackfillSession
|
|
274
276
|
from celerp.services.status_doc_backfill import run_status_doc_backfill
|
|
275
277
|
async with _BackfillSession() as _bf_sess:
|
|
276
278
|
await run_status_doc_backfill(_bf_sess)
|
|
@@ -286,7 +288,7 @@ async def lifespan(_app: FastAPI):
|
|
|
286
288
|
# (runs once; retries stragglers while any doc is locked or errored);
|
|
287
289
|
# non-fatal like the backfill above.
|
|
288
290
|
try:
|
|
289
|
-
from celerp.db import
|
|
291
|
+
from celerp.db import LifecycleSessionLocal as _CogsSession
|
|
290
292
|
from celerp.services.cogs_backfill import run_cogs_backfill
|
|
291
293
|
async with _CogsSession() as _cogs_sess:
|
|
292
294
|
await run_cogs_backfill(_cogs_sess)
|
|
@@ -447,6 +449,14 @@ async def rate_limit_handler(_request: Request, _exc: RateLimitExceeded):
|
|
|
447
449
|
return JSONResponse(status_code=429, content={"detail": "Rate limit exceeded"})
|
|
448
450
|
|
|
449
451
|
|
|
452
|
+
@app.exception_handler(BarcodeConflictError)
|
|
453
|
+
async def barcode_conflict_handler(_request: Request, exc: BarcodeConflictError):
|
|
454
|
+
# The projection applier raises this when the barcode unique index rejects a write
|
|
455
|
+
# that bypassed the allocation lock (imports, connectors). A more specific handler
|
|
456
|
+
# than the Exception catch-all, so it maps to 409 instead of a masked 500.
|
|
457
|
+
return JSONResponse(status_code=409, content={"detail": str(exc)})
|
|
458
|
+
|
|
459
|
+
|
|
450
460
|
# Kernel routes — always present regardless of module configuration
|
|
451
461
|
app.include_router(health.router, tags=["system"])
|
|
452
462
|
app.include_router(auth.router, prefix="/auth", tags=["auth"])
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Copyright (c) 2026 Noah Severs
|
|
2
|
+
# SPDX-License-Identifier: BUSL-1.1
|
|
3
|
+
|
|
4
|
+
"""Enforce at-most-one item per (company, barcode) with a partial unique index.
|
|
5
|
+
|
|
6
|
+
Revision ID: bc0d1e2f3a4b
|
|
7
|
+
Revises: b3c4d5e6f7a8
|
|
8
|
+
Create Date: 2026-08-25
|
|
9
|
+
|
|
10
|
+
Context
|
|
11
|
+
-------
|
|
12
|
+
A barcode identifies at most one physical-lot item per company. Until now that was
|
|
13
|
+
only enforced by an application check under a lock; any writer bypassing the lock
|
|
14
|
+
(imports, connectors, a future writer) could create duplicates, and legacy databases
|
|
15
|
+
may already hold some.
|
|
16
|
+
|
|
17
|
+
This migration is the final defense: a PostgreSQL partial expression unique index on
|
|
18
|
+
``(company_id, state ->> 'barcode')`` for item projections with a non-empty barcode.
|
|
19
|
+
|
|
20
|
+
It refuses to create the index while incompatible data exists rather than silently
|
|
21
|
+
renaming or clearing anything. A preflight scans for duplicate non-empty barcodes,
|
|
22
|
+
barcodes over the length limit, SKUs over the length limit, and comma-bearing SKUs,
|
|
23
|
+
and stops with an actionable report (company, value, affected entity ids) so an
|
|
24
|
+
operator can remediate and re-run.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import sqlalchemy as sa
|
|
30
|
+
from alembic import op
|
|
31
|
+
|
|
32
|
+
from celerp.inventory_codes import BARCODE_UNIQUE_INDEX, MAX_BARCODE_LEN, MAX_SKU_LEN
|
|
33
|
+
|
|
34
|
+
revision = "bc0d1e2f3a4b"
|
|
35
|
+
down_revision = "b3c4d5e6f7a8"
|
|
36
|
+
branch_labels = None
|
|
37
|
+
depends_on = None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _preflight(conn) -> list[str]:
|
|
41
|
+
"""Return a list of human-readable remediation problems; empty means clean."""
|
|
42
|
+
problems: list[str] = []
|
|
43
|
+
|
|
44
|
+
dups = conn.execute(
|
|
45
|
+
sa.text(
|
|
46
|
+
"SELECT company_id, state ->> 'barcode' AS barcode, "
|
|
47
|
+
" array_agg(entity_id ORDER BY entity_id) AS entities "
|
|
48
|
+
"FROM projections "
|
|
49
|
+
"WHERE entity_type = 'item' AND NULLIF(state ->> 'barcode', '') IS NOT NULL "
|
|
50
|
+
"GROUP BY company_id, state ->> 'barcode' "
|
|
51
|
+
"HAVING count(*) > 1"
|
|
52
|
+
)
|
|
53
|
+
).fetchall()
|
|
54
|
+
for row in dups:
|
|
55
|
+
problems.append(
|
|
56
|
+
f"duplicate barcode {row.barcode!r} in company {row.company_id}: entities {list(row.entities)}"
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
long_barcodes = conn.execute(
|
|
60
|
+
sa.text(
|
|
61
|
+
"SELECT company_id, entity_id, state ->> 'barcode' AS barcode "
|
|
62
|
+
"FROM projections "
|
|
63
|
+
"WHERE entity_type = 'item' AND length(state ->> 'barcode') > :n"
|
|
64
|
+
),
|
|
65
|
+
{"n": MAX_BARCODE_LEN},
|
|
66
|
+
).fetchall()
|
|
67
|
+
for row in long_barcodes:
|
|
68
|
+
problems.append(
|
|
69
|
+
f"barcode {row.barcode!r} over {MAX_BARCODE_LEN} chars in company {row.company_id} entity {row.entity_id}"
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
long_skus = conn.execute(
|
|
73
|
+
sa.text(
|
|
74
|
+
"SELECT company_id, entity_id "
|
|
75
|
+
"FROM projections "
|
|
76
|
+
"WHERE entity_type = 'item' AND length(state ->> 'sku') > :n"
|
|
77
|
+
),
|
|
78
|
+
{"n": MAX_SKU_LEN},
|
|
79
|
+
).fetchall()
|
|
80
|
+
for row in long_skus:
|
|
81
|
+
problems.append(
|
|
82
|
+
f"SKU over {MAX_SKU_LEN} chars in company {row.company_id} entity {row.entity_id}"
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
comma_skus = conn.execute(
|
|
86
|
+
sa.text(
|
|
87
|
+
"SELECT company_id, entity_id, state ->> 'sku' AS sku "
|
|
88
|
+
"FROM projections "
|
|
89
|
+
"WHERE entity_type = 'item' AND position(',' in state ->> 'sku') > 0"
|
|
90
|
+
)
|
|
91
|
+
).fetchall()
|
|
92
|
+
for row in comma_skus:
|
|
93
|
+
problems.append(
|
|
94
|
+
f"comma-bearing SKU {row.sku!r} in company {row.company_id} entity {row.entity_id}"
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
return problems
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def upgrade() -> None:
|
|
101
|
+
conn = op.get_bind()
|
|
102
|
+
|
|
103
|
+
problems = _preflight(conn)
|
|
104
|
+
if problems:
|
|
105
|
+
raise RuntimeError(
|
|
106
|
+
"Cannot create the barcode uniqueness index: incompatible item data exists. "
|
|
107
|
+
"Resolve each of the following (rename or clear the barcode/SKU on the affected "
|
|
108
|
+
"items), then re-run the migration:\n - " + "\n - ".join(problems)
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
op.execute(
|
|
112
|
+
f"CREATE UNIQUE INDEX IF NOT EXISTS {BARCODE_UNIQUE_INDEX} "
|
|
113
|
+
"ON projections (company_id, (state ->> 'barcode')) "
|
|
114
|
+
"WHERE entity_type = 'item' AND NULLIF(state ->> 'barcode', '') IS NOT NULL"
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def downgrade() -> None:
|
|
119
|
+
op.execute(f"DROP INDEX IF EXISTS {BARCODE_UNIQUE_INDEX}")
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Copyright (c) 2026 Noah Severs
|
|
2
|
+
# SPDX-License-Identifier: BUSL-1.1
|
|
3
|
+
|
|
4
|
+
"""seed the inventory shrinkage/write-off account 6970 for existing companies
|
|
5
|
+
|
|
6
|
+
Revision ID: c9d8e7f6a5b4
|
|
7
|
+
Revises: bc0d1e2f3a4b
|
|
8
|
+
Create Date: 2026-08-31
|
|
9
|
+
|
|
10
|
+
Inventory write-offs and audit shrinkage post to 6970. New companies get the
|
|
11
|
+
account from THAI_CHART_OF_ACCOUNTS on creation; this backfills existing ones,
|
|
12
|
+
which would otherwise be refused at posting time.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from alembic import op
|
|
18
|
+
import sqlalchemy as sa
|
|
19
|
+
|
|
20
|
+
revision = "c9d8e7f6a5b4"
|
|
21
|
+
down_revision = "bc0d1e2f3a4b"
|
|
22
|
+
branch_labels = None
|
|
23
|
+
depends_on = None
|
|
24
|
+
|
|
25
|
+
_CODE = "6970"
|
|
26
|
+
_NAME = "Inventory Shrinkage & Write-offs"
|
|
27
|
+
_TYPE = "expense"
|
|
28
|
+
_PARENT = "6000"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def upgrade() -> None:
|
|
32
|
+
conn = op.get_bind()
|
|
33
|
+
# to_regclass resolves against the connection's search_path rather than
|
|
34
|
+
# assuming public, so the guard is correct for any schema the migration is
|
|
35
|
+
# actually run in.
|
|
36
|
+
table_exists = conn.execute(sa.text("SELECT to_regclass('accounts')")).scalar()
|
|
37
|
+
if not table_exists:
|
|
38
|
+
return # Nothing to backfill on fresh install
|
|
39
|
+
|
|
40
|
+
companies = conn.execute(
|
|
41
|
+
sa.text("SELECT DISTINCT company_id FROM accounts WHERE code = :parent"),
|
|
42
|
+
{"parent": _PARENT},
|
|
43
|
+
).fetchall()
|
|
44
|
+
|
|
45
|
+
for (company_id,) in companies:
|
|
46
|
+
exists = conn.execute(
|
|
47
|
+
sa.text("SELECT 1 FROM accounts WHERE company_id = :cid AND code = :code"),
|
|
48
|
+
{"cid": company_id, "code": _CODE},
|
|
49
|
+
).fetchone()
|
|
50
|
+
if not exists:
|
|
51
|
+
conn.execute(
|
|
52
|
+
sa.text(
|
|
53
|
+
"INSERT INTO accounts (id, company_id, code, name, account_type, parent_code, is_active, created_at) "
|
|
54
|
+
"VALUES (gen_random_uuid(), :cid, :code, :name, :acc_type, :parent, TRUE, NOW())"
|
|
55
|
+
),
|
|
56
|
+
{"cid": company_id, "code": _CODE, "name": _NAME,
|
|
57
|
+
"acc_type": _TYPE, "parent": _PARENT},
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def downgrade() -> None:
|
|
62
|
+
# The upgrade only inserts 6970 when absent, so a seeded row is indistinguishable from a
|
|
63
|
+
# company's own pre-existing 6970. Deleting on downgrade would destroy user data and orphan any
|
|
64
|
+
# ledger row referencing it, so the reversal intentionally leaves the account in place.
|
|
65
|
+
pass
|
|
@@ -9,12 +9,27 @@ import sqlalchemy as sa
|
|
|
9
9
|
from sqlalchemy import BigInteger, Boolean, DateTime, ForeignKey, PrimaryKeyConstraint, String, Text
|
|
10
10
|
from sqlalchemy.orm import Mapped, mapped_column
|
|
11
11
|
|
|
12
|
+
from celerp.inventory_codes import BARCODE_UNIQUE_INDEX
|
|
12
13
|
from celerp.models.base import Base
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
class Projection(Base):
|
|
16
17
|
__tablename__ = "projections"
|
|
17
|
-
__table_args__ = (
|
|
18
|
+
__table_args__ = (
|
|
19
|
+
PrimaryKeyConstraint("company_id", "entity_id"),
|
|
20
|
+
# At most one item per (company, non-empty barcode). The final defense behind
|
|
21
|
+
# the application allocation lock: imports, connectors, or a future writer that
|
|
22
|
+
# bypasses the lock still cannot create a duplicate barcode. Declared here so
|
|
23
|
+
# create_all builds it for the test schema; created on existing databases by the
|
|
24
|
+
# barcode-uniqueness migration.
|
|
25
|
+
sa.Index(
|
|
26
|
+
BARCODE_UNIQUE_INDEX,
|
|
27
|
+
"company_id",
|
|
28
|
+
sa.text("(state ->> 'barcode')"),
|
|
29
|
+
unique=True,
|
|
30
|
+
postgresql_where=sa.text("entity_type = 'item' AND NULLIF(state ->> 'barcode', '') IS NOT NULL"),
|
|
31
|
+
),
|
|
32
|
+
)
|
|
18
33
|
|
|
19
34
|
entity_id: Mapped[str] = mapped_column(Text, nullable=False)
|
|
20
35
|
company_id: Mapped[uuid.UUID] = mapped_column(sa.Uuid(as_uuid=True), ForeignKey("companies.id"), nullable=False)
|