celerp 2.3.6__tar.gz → 2.4.0__tar.gz

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