celerp 1.2.7__tar.gz → 1.2.8__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-1.2.7 → celerp-1.2.8}/PKG-INFO +1 -1
- {celerp-1.2.7 → celerp-1.2.8}/celerp/connectors/base.py +17 -7
- {celerp-1.2.7 → celerp-1.2.8}/celerp/connectors/daily_scheduler.py +38 -9
- {celerp-1.2.7 → celerp-1.2.8}/celerp/connectors/http.py +17 -1
- {celerp-1.2.7 → celerp-1.2.8}/celerp/connectors/quickbooks.py +13 -21
- {celerp-1.2.7 → celerp-1.2.8}/celerp/connectors/shopify.py +14 -62
- {celerp-1.2.7 → celerp-1.2.8}/celerp/connectors/sync_runner.py +97 -26
- {celerp-1.2.7 → celerp-1.2.8}/celerp/connectors/upsert.py +16 -9
- {celerp-1.2.7 → celerp-1.2.8}/celerp/connectors/webhooks.py +9 -15
- {celerp-1.2.7 → celerp-1.2.8}/celerp/connectors/woocommerce.py +123 -116
- {celerp-1.2.7 → celerp-1.2.8}/celerp/connectors/xero.py +13 -21
- {celerp-1.2.7 → celerp-1.2.8}/celerp/events/engine.py +65 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/events/schemas.py +17 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/gateway/client.py +42 -9
- celerp-1.2.8/celerp/migrations/versions/c2d3e4f5a6b7_add_is_sync_to_shopify.py +35 -0
- celerp-1.2.8/celerp/migrations/versions/e4f5a6b7c8d9_backfill_connector_idempotency_key.py +98 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/models/__init__.py +1 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/models/projections.py +1 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/models/sync_run.py +1 -1
- {celerp-1.2.7 → celerp-1.2.8}/celerp/projections/engine.py +2 -0
- celerp-1.2.8/celerp/projections/handlers/shopify.py +23 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/routers/system.py +1 -1
- {celerp-1.2.7 → celerp-1.2.8}/celerp.egg-info/PKG-INFO +1 -1
- {celerp-1.2.7 → celerp-1.2.8}/celerp.egg-info/SOURCES.txt +3 -1
- {celerp-1.2.7 → celerp-1.2.8}/celerp.egg-info/scm_file_list.json +14 -1
- celerp-1.2.8/celerp.egg-info/scm_version.json +8 -0
- {celerp-1.2.7 → celerp-1.2.8}/conftest.py +8 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-connectors/__init__.py +27 -4
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-connectors/celerp_connectors/routes.py +5 -12
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-connectors/tests/conftest.py +7 -7
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-connectors/tests/test_compliance.py +1 -5
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-connectors/tests/test_connector_correctness.py +5 -98
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-connectors/tests/test_connectors.py +15 -262
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-connectors/tests/test_outbound.py +3 -3
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-connectors/tests/test_shopify.py +1 -1
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-connectors/tests/test_wiring.py +25 -3
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-connectors/tests/test_woocommerce.py +25 -98
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-connectors/tests/test_woocommerce_webhooks.py +2 -2
- celerp-1.2.8/default_modules/celerp-contacts/celerp_contacts/services.py +126 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-docs/celerp_docs/doc_projections.py +7 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-docs/celerp_docs/doc_service.py +92 -89
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-inventory/celerp_inventory/projections.py +20 -2
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-inventory/celerp_inventory/routes.py +112 -12
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-inventory/celerp_inventory/services.py +47 -54
- {celerp-1.2.7 → celerp-1.2.8}/ui/api_client.py +5 -0
- {celerp-1.2.7 → celerp-1.2.8}/ui/components/table.py +5 -1
- {celerp-1.2.7 → celerp-1.2.8}/ui/locales/en.json +23 -4
- {celerp-1.2.7 → celerp-1.2.8}/ui/routes/auth.py +9 -0
- {celerp-1.2.7 → celerp-1.2.8}/ui/routes/inventory.py +79 -33
- {celerp-1.2.7 → celerp-1.2.8}/ui/routes/settings_connectors.py +404 -112
- {celerp-1.2.7 → celerp-1.2.8}/ui/static/app.css +1 -0
- celerp-1.2.7/celerp.egg-info/scm_version.json +0 -8
- celerp-1.2.7/default_modules/celerp-contacts/celerp_contacts/services.py +0 -181
- celerp-1.2.7/memory/fix-receive-goods-bugs.md +0 -1
- {celerp-1.2.7 → celerp-1.2.8}/CLA.md +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/CONTRIBUTING.md +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/LICENSE +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/LICENSING.md +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/MANIFEST.in +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/NOTICE +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/README.md +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/alembic.ini +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/__init__.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/__main__.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/ai/__init__.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/ai/batch.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/ai/cleanup.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/ai/commands.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/ai/conversations.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/ai/files.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/ai/intent.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/ai/llm.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/ai/memory.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/ai/models.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/ai/page_count.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/ai/quota.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/ai/service.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/ai/tools.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/alembic.ini +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/alembic_config.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/cli.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/compute/__init__.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/compute/aggregations.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/compute/valuation.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/config.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/connectors/__init__.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/connectors/images.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/connectors/registry.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/connectors/relay_token.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/connectors/util.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/constants.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/db.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/events/__init__.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/events/types.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/gateway/__init__.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/gateway/state.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/importers/__init__.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/importers/importer.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/importers/schema.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/main.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/middleware.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/migrations/README.md +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/migrations/_auto_stamp.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/migrations/_data_reconcile.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/migrations/_json_compat.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/migrations/env.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/migrations/script.py.mako +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/migrations/versions/a0b1c2d3e4f5_seed_landed_cost_clearing_accounts.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/migrations/versions/a1b2c3d4e5f6_add_doc_share_tokens.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/migrations/versions/b1c2d3e4f5a6_add_supporter_badges.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/migrations/versions/b2c3d4e5f6a7_add_import_batches.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/migrations/versions/c1d2e3f4a5b6_add_marketplace.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/migrations/versions/d2e3f4a5b6c7_add_password_reset_token.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/migrations/versions/e3f4a5b6c7d8_add_company_is_active.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/migrations/versions/f1a2b3c4d5e6_move_accounts_marketplace_to_modules.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/migrations/versions/fd5de461e14e_initial_schema.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/migrations/versions/g1h2i3j4k5l6_add_consignment_flag.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/migrations/versions/h2i3j4k5l6m7_add_bank_accounts_and_reconciliation.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/migrations/versions/j4k5l6m7n8o9_labels_db_persistence.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/migrations/versions/k5l6m7n8o9p0_add_reconciliation_v2.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/migrations/versions/l6m7n8o9p0q1_add_sync_runs_table.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/migrations/versions/m7n8o9p0q1r2_add_connector_configs_and_outbound_queue.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/migrations/versions/n8o9p0q1r2s3_backfill_payment_date.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/migrations/versions/o9p0q1r2s3t4_backfill_item_price_fields.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/migrations/versions/p1q2r3s4t5u6_add_session_registry_and_user_auth_state.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/migrations/versions/q2r3s4t5u6v7_add_system_runtime_state.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/migrations/versions/r3s4t5u6v7w8_add_projections_company_entity_type_index.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/migrations/versions/s4t5u6v7w8x9_drop_user_company_id_role.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/migrations/versions/t5u6v7w8x9y0_seed_inventory_sub_accounts.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/migrations/versions/u6v7w8x9y0z1_backfill_item_prices_from_attributes.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/migrations/versions/v7w8x9y0z1a2_backfill_purchase_unit_defaults.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/migrations/versions/w8x9y0z1a2b3_backfill_category_unit_defaults.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/migrations/versions/x7y8z9a0b1c2_add_projection_created_at.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/migrations/versions/y8z9a0b1c2d3_per_company_ledger_idempotency.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/migrations/versions/z9a0b1c2d3e4_work_centers.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/models/accounting.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/models/ai.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/models/auth.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/models/base.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/models/company.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/models/connector_config.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/models/import_batch.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/models/ledger.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/models/marketplace.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/models/notification.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/models/share.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/models/supporter.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/modules/__init__.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/modules/api.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/modules/audit.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/modules/license.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/modules/loader.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/modules/references.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/modules/registry.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/modules/slots.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/notifications/__init__.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/notifications/service.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/notifications/sse.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/output/__init__.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/output/pdf.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/output/share_render.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/projections/__init__.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/projections/handlers/__init__.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/projections/handlers/marketplace.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/projections/handlers/scanning.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/projections/handlers/system.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/routers/__init__.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/routers/auth.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/routers/companies.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/routers/debug.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/routers/doctor.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/routers/events.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/routers/health.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/routers/ledger.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/routers/notifications.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/routers/stars.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/schemas/__init__.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/services/__init__.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/services/activity_redaction.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/services/attachments.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/services/auth.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/services/auto_je.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/services/backup.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/services/backup_export.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/services/backup_import.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/services/backup_repo.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/services/backup_scheduler.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/services/backup_state.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/services/demo.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/services/dev_release_guard.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/services/email.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/services/field_schema.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/services/fulfill.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/services/je_keys.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/services/landed_cost.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/services/line_measures.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/services/list_behavior.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/services/money.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/services/pick.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/services/runtime_state.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/services/session_tracker.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/services/star_cta.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/services/supporter_badge.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/services/system_health.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/services/units.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/session_gate.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp/tax_regimes.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp.egg-info/dependency_links.txt +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp.egg-info/entry_points.txt +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp.egg-info/requires.txt +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/celerp.egg-info/top_level.txt +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/__init__.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-accounting/LICENSE +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-accounting/__init__.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-accounting/celerp_accounting/__init__.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-accounting/celerp_accounting/api_setup.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-accounting/celerp_accounting/csv_parser.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-accounting/celerp_accounting/matcher.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-accounting/celerp_accounting/models.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-accounting/celerp_accounting/projections.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-accounting/celerp_accounting/routes.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-accounting/celerp_accounting/ui_routes.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-accounting/tests/test_accounting_banking.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-accounting/tests/test_accounting_banking_routers.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-accounting/tests/test_accounting_gaps.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-accounting/tests/test_accounting_import_idempotency.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-accounting/tests/test_reconciliation_v2.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-admin/__init__.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-admin/celerp_admin/__init__.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-admin/celerp_admin/routes.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-admin/celerp_admin/setup.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-ai/__init__.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-ai/celerp_ai/__init__.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-ai/celerp_ai/routes.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-ai/celerp_ai/setup.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-ai/celerp_ai/ui_routes.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-ai/tests/conftest.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-ai/tests/test_batch.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-ai/tests/test_cleanup.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-ai/tests/test_conversations.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-ai/tests/test_coverage_gaps.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-ai/tests/test_intent.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-ai/tests/test_llm.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-ai/tests/test_memory.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-ai/tests/test_models.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-ai/tests/test_page_count.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-ai/tests/test_quota.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-ai/tests/test_router.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-ai/tests/test_routes_coverage.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-ai/tests/test_service.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-ai/tests/test_tools.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-backup/__init__.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-backup/celerp_backup/__init__.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-backup/celerp_backup/routes.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-backup/celerp_backup/setup.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-connectors/celerp_connectors/__init__.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-connectors/celerp_connectors/models.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-connectors/tests/__init__.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-connectors/tests/test_images.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-connectors/tests/test_quickbooks.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-connectors/tests/test_rate_limiting.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-connectors/tests/test_xero.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-contacts/LICENSE +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-contacts/__init__.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-contacts/celerp_contacts/__init__.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-contacts/celerp_contacts/migrations.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-contacts/celerp_contacts/projections.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-contacts/celerp_contacts/routes.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-contacts/celerp_contacts/ui_routes.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-dashboard/LICENSE +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-dashboard/__init__.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-dashboard/celerp_dashboard/__init__.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-dashboard/celerp_dashboard/routes.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-dashboard/celerp_dashboard/setup.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-docs/LICENSE +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-docs/__init__.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-docs/celerp_docs/__init__.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-docs/celerp_docs/api_setup.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-docs/celerp_docs/doc_constants.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-docs/celerp_docs/models_share.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-docs/celerp_docs/routes.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-docs/celerp_docs/routes_share.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-docs/celerp_docs/sequences.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-docs/celerp_docs/taxes.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-docs/celerp_docs/ui_routes.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-docs/tests/conftest.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-docs/tests/test_audits.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-docs/tests/test_freight_je.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-docs/tests/test_freight_lines.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-docs/tests/test_fulfill_no_split.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-docs/tests/test_fulfillment_activity.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-docs/tests/test_inbound_je_lifecycle.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-docs/tests/test_inventory_account_reconciliation.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-docs/tests/test_landed_receive.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-docs/tests/test_line_delete_persist.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-docs/tests/test_payment_void_activity.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-docs/tests/test_send_visibility.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-docs/tests/test_service_lines.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-inventory/LICENSE +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-inventory/__init__.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-inventory/celerp_inventory/__init__.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-inventory/celerp_inventory/models_import_batch.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-inventory/celerp_inventory/routes_attachments.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-inventory/celerp_inventory/routes_scanning.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-inventory/celerp_inventory/ui_routes.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-inventory/tests/test_item_recipe_projection.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-inventory/tests/test_item_workflow_projection.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-inventory/tests/test_landed_cost_projection.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-labels/LICENSE +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-labels/__init__.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-labels/celerp_labels/__init__.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-labels/celerp_labels/migrations/__init__.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-labels/celerp_labels/migrations/labels_001_create_label_templates.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-labels/celerp_labels/models.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-labels/celerp_labels/presets.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-labels/celerp_labels/routes.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-labels/celerp_labels/service.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-labels/celerp_labels/ui_routes.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-labels/requirements.txt +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-labels/tests/test_labels.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-labels/tests/test_labels_module.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-manufacturing/LICENSE +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-manufacturing/__init__.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-manufacturing/celerp_manufacturing/__init__.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-manufacturing/celerp_manufacturing/costing.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-manufacturing/celerp_manufacturing/expansion.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-manufacturing/celerp_manufacturing/labor.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-manufacturing/celerp_manufacturing/migrations.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-manufacturing/celerp_manufacturing/projection_handler.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-manufacturing/celerp_manufacturing/routes.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-manufacturing/celerp_manufacturing/ui_routes.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-manufacturing/requirements.txt +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-manufacturing/tests/test_bom_removed.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-manufacturing/tests/test_build_api.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-manufacturing/tests/test_item_recipe_api.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-manufacturing/tests/test_item_workflow_api.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-manufacturing/tests/test_labor_providers.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-manufacturing/tests/test_manufacture_from_doc.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-manufacturing/tests/test_manufacturing.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-manufacturing/tests/test_manufacturing_import_idempotency.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-manufacturing/tests/test_manufacturing_module.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-manufacturing/tests/test_mfg_events.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-manufacturing/tests/test_phase2_manufacturing_validations.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-manufacturing/tests/test_production_order.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-manufacturing/tests/test_recipe_costing.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-manufacturing/tests/test_recipe_expansion.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-manufacturing/tests/test_recost_api.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-manufacturing/tests/test_to_make_allocation.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-manufacturing/tests/test_where_used.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-manufacturing/tests/test_work_centers_and_settings.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-manufacturing/tests/test_workflow_time.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-reports/LICENSE +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-reports/__init__.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-reports/celerp_reports/__init__.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-reports/celerp_reports/api_setup.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-reports/celerp_reports/routes.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-reports/celerp_reports/ui_routes.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-reports/tests/test_reports_full.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-reports/tests/test_reports_unit.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-subscriptions/LICENSE +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-subscriptions/__init__.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-subscriptions/celerp_subscriptions/__init__.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-subscriptions/celerp_subscriptions/routes.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-subscriptions/celerp_subscriptions/ui_routes.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-subscriptions/tests/test_subscriptions.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-subscriptions/tests/test_subscriptions_ui.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/LICENSE +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/__init__.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/__init__.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/accessory_fashion.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/activewear.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/art_photography.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/audio_equipment.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/bag_handbag.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/banknote.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/beauty_tool.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/bed_bedroom.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/beer.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/beverage_nonalc.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/body_part_auto.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/book.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/bottoms.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/brake_suspension.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/bullion_coin.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/camera.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/colored_stone.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/comic.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/commercial_space.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/component_part.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/confectionery.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/consulting_service.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/decorative_art.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/diamond.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/drawing.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/dress_jumpsuit.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/electrical_auto.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/electrical_component.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/emerald.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/engine_part.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/fastener.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/fertilizer_chemical.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/film_video.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/fine_writing_instrument.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/fluid_lubricant.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/footwear.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/fragrance.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/fresh_food.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/fresh_produce.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/frozen_food.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/gaming_console.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/gold_bullion.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/grain_cereal.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/haircare.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/hand_tool.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/ingredient_bulk.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/interior_part.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/jewelry.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/kitchen_dining.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/laptop.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/lighting.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/livestock_feed.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/makeup.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/measuring_instrument.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/medal_token.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/mineral_specimen.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/mobile_phone.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/music_cd.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/music_vinyl.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/nail.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/numismatic_coin.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/outdoor_furniture.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/outerwear.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/packaged_food.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/painting.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/parking_bay.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/pearl.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/personal_care.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/pipe_plumbing.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/platinum_bullion.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/power_tool.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/print_edition.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/residential_unit.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/rough_gemstone.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/ruby.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/saas_plan.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/safety_ppe.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/sapphire.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/sculpture.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/seating.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/seeds.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/silver_bullion.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/skincare.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/soft_furnishing.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/software_addon.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/software_license.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/spirit.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/storage_furniture.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/swimwear.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/table_desk.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/tablet.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/tire_wheel.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/tops.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/trading_card.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/tv_display.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/video_game.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/watch.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/watch_strap.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/categories/wine.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/presets/agricultural.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/presets/artwork.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/presets/automotive.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/presets/blank.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/presets/books_media.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/presets/coins_precious_metals.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/presets/consulting.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/presets/cosmetics.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/presets/electronics.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/presets/fashion.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/presets/food_beverage.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/presets/furniture.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/presets/gemstones.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/presets/hardware.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/presets/property_rental.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/presets/saas.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/presets/watches.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/presets/wine_spirits.json +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/routes.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/celerp_verticals/ui_routes.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/tests/test_verticals.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/default_modules/celerp-verticals/tests/test_verticals_routers.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/legal/OFFICIAL-COMPONENTS.md +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/legal/TRADEMARK.md +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/pyproject.toml +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/setup.cfg +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/test_helpers.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/ui/__init__.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/ui/app.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/ui/components/__init__.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/ui/components/activity.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/ui/components/backup.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/ui/components/cloud_gate.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/ui/components/currency.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/ui/components/files.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/ui/components/notes.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/ui/components/phone.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/ui/components/shell.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/ui/config.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/ui/i18n.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/ui/locales/ar.json +4 -4
- {celerp-1.2.7 → celerp-1.2.8}/ui/locales/de.json +4 -4
- {celerp-1.2.7 → celerp-1.2.8}/ui/locales/es.json +4 -4
- {celerp-1.2.7 → celerp-1.2.8}/ui/locales/fr.json +4 -4
- {celerp-1.2.7 → celerp-1.2.8}/ui/locales/id.json +4 -4
- {celerp-1.2.7 → celerp-1.2.8}/ui/locales/it.json +4 -4
- {celerp-1.2.7 → celerp-1.2.8}/ui/locales/ja.json +4 -4
- {celerp-1.2.7 → celerp-1.2.8}/ui/locales/pt.json +4 -4
- {celerp-1.2.7 → celerp-1.2.8}/ui/locales/th.json +4 -4
- {celerp-1.2.7 → celerp-1.2.8}/ui/locales/vi.json +4 -4
- {celerp-1.2.7 → celerp-1.2.8}/ui/routes/__init__.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/ui/routes/accounting.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/ui/routes/accounting_import.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/ui/routes/contacts.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/ui/routes/csv_import.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/ui/routes/dashboard.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/ui/routes/docs_import.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/ui/routes/documents.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/ui/routes/events.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/ui/routes/lists_import.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/ui/routes/manufacturing.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/ui/routes/notifications.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/ui/routes/reconciliation.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/ui/routes/reports.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/ui/routes/search.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/ui/routes/settings.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/ui/routes/settings_accounting.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/ui/routes/settings_cloud.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/ui/routes/settings_contacts.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/ui/routes/settings_general.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/ui/routes/settings_import.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/ui/routes/settings_inventory.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/ui/routes/settings_manufacturing.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/ui/routes/settings_purchasing.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/ui/routes/settings_sales.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/ui/routes/setup.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/ui/routes/stars.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/ui/routes/subscriptions.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/ui/routes/subscriptions_import.py +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/ui/static/htmx.min.js +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/ui/static/icon.png +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/ui/static/logo.png +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/ui/static/vendor/intl-tel-input/img/flags.webp +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/ui/static/vendor/intl-tel-input/img/flags@2x.webp +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/ui/static/vendor/intl-tel-input/intlTelInput.min.css +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/ui/static/vendor/intl-tel-input/intlTelInput.min.js +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/ui/static/vendor/intl-tel-input/intlTelInputWithUtils.min.js +0 -0
- {celerp-1.2.7 → celerp-1.2.8}/ui/static/vendor/intl-tel-input/utils.js +0 -0
|
@@ -9,7 +9,7 @@ CelERP relay service and injected at call time via ConnectorContext.
|
|
|
9
9
|
|
|
10
10
|
Sync direction:
|
|
11
11
|
- INBOUND: external platform -> Celerp (pull products, orders, contacts)
|
|
12
|
-
- OUTBOUND: Celerp -> external platform (push invoices, inventory
|
|
12
|
+
- OUTBOUND: Celerp -> external platform (push items, invoices, inventory)
|
|
13
13
|
- BOTH: bidirectional
|
|
14
14
|
"""
|
|
15
15
|
from __future__ import annotations
|
|
@@ -49,7 +49,8 @@ class SyncFrequency(str, Enum):
|
|
|
49
49
|
DAILY = "daily" # once per day at configured hour
|
|
50
50
|
|
|
51
51
|
|
|
52
|
-
# Entity classification for direction filtering
|
|
52
|
+
# Entity classification for direction filtering. Inbound entities are pulled from
|
|
53
|
+
# the platform; the *_out entities are pushed from Celerp to the platform.
|
|
53
54
|
_INBOUND_ENTITIES = {"products", "orders", "contacts", "inventory"}
|
|
54
55
|
_OUTBOUND_ENTITIES = {"products_out", "invoices_out", "inventory_out"}
|
|
55
56
|
|
|
@@ -77,7 +78,7 @@ class ConnectorContext:
|
|
|
77
78
|
@dataclass
|
|
78
79
|
class SyncResult:
|
|
79
80
|
entity: SyncEntity
|
|
80
|
-
direction: SyncDirection
|
|
81
|
+
direction: SyncDirection = SyncDirection.INBOUND
|
|
81
82
|
created: int = 0
|
|
82
83
|
updated: int = 0
|
|
83
84
|
skipped: int = 0
|
|
@@ -87,9 +88,18 @@ class SyncResult:
|
|
|
87
88
|
def ok(self) -> bool:
|
|
88
89
|
return not self.errors
|
|
89
90
|
|
|
91
|
+
def record(self, outcome: str) -> None:
|
|
92
|
+
"""Tally a `connector_upsert`/push outcome ("created" | "updated" | "noop")."""
|
|
93
|
+
if outcome == "created":
|
|
94
|
+
self.created += 1
|
|
95
|
+
elif outcome == "updated":
|
|
96
|
+
self.updated += 1
|
|
97
|
+
else:
|
|
98
|
+
self.skipped += 1
|
|
99
|
+
|
|
90
100
|
|
|
91
101
|
class ConnectorBase(ABC):
|
|
92
|
-
"""Abstract base for all platform connectors."""
|
|
102
|
+
"""Abstract base for all platform connectors (inbound + outbound)."""
|
|
93
103
|
|
|
94
104
|
name: str
|
|
95
105
|
display_name: str
|
|
@@ -109,8 +119,8 @@ class ConnectorBase(ABC):
|
|
|
109
119
|
...
|
|
110
120
|
|
|
111
121
|
async def sync_inventory(self, ctx: ConnectorContext, since: datetime | None = None) -> SyncResult:
|
|
112
|
-
"""
|
|
113
|
-
raise NotImplementedError(f"{self.name} does not support inventory
|
|
122
|
+
"""Pull inventory levels from platform -> Celerp. Override if supported."""
|
|
123
|
+
raise NotImplementedError(f"{self.name} does not support inventory sync")
|
|
114
124
|
|
|
115
125
|
async def sync_contacts(self, ctx: ConnectorContext, since: datetime | None = None) -> SyncResult:
|
|
116
126
|
"""Pull customers/vendors from platform -> Celerp CRM. Override if supported."""
|
|
@@ -130,7 +140,7 @@ class ConnectorBase(ABC):
|
|
|
130
140
|
|
|
131
141
|
# -- Webhook lifecycle (override for e-commerce connectors) ----------------
|
|
132
142
|
|
|
133
|
-
async def register_webhooks(self, ctx: ConnectorContext, webhook_url: str) -> list[str]:
|
|
143
|
+
async def register_webhooks(self, ctx: ConnectorContext, webhook_url: str, secret: str | None = None) -> list[str]:
|
|
134
144
|
"""Register platform webhooks. Returns list of webhook IDs. Override if supported."""
|
|
135
145
|
return []
|
|
136
146
|
|
|
@@ -16,11 +16,28 @@ from typing import Awaitable, Callable
|
|
|
16
16
|
|
|
17
17
|
import sqlalchemy as sa
|
|
18
18
|
|
|
19
|
-
from celerp.connectors.base import
|
|
19
|
+
from celerp.connectors.base import SyncDirection
|
|
20
20
|
from celerp.models.connector_config import ConnectorConfig
|
|
21
21
|
|
|
22
22
|
log = logging.getLogger(__name__)
|
|
23
23
|
|
|
24
|
+
# Outbound entity -> connector method. A connector "supports" an outbound entity when it
|
|
25
|
+
# overrides the base (which raises NotImplementedError); we only dispatch those, so the
|
|
26
|
+
# scheduler never emits a failed run for a push a connector doesn't implement.
|
|
27
|
+
_OUTBOUND_ENTITY_METHODS = {
|
|
28
|
+
"products_out": "sync_products_out",
|
|
29
|
+
"invoices_out": "sync_invoices_out",
|
|
30
|
+
"inventory_out": "sync_inventory_out",
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _supported_outbound(connector) -> list[str]:
|
|
35
|
+
from celerp.connectors.base import ConnectorBase
|
|
36
|
+
return [
|
|
37
|
+
entity for entity, method in _OUTBOUND_ENTITY_METHODS.items()
|
|
38
|
+
if getattr(type(connector), method, None) is not getattr(ConnectorBase, method, None)
|
|
39
|
+
]
|
|
40
|
+
|
|
24
41
|
_CHECK_INTERVAL_SECONDS = 3600 # check every hour
|
|
25
42
|
_MIN_HOURS_BETWEEN_SYNCS = 23
|
|
26
43
|
|
|
@@ -81,8 +98,7 @@ async def check_and_run_daily_syncs(
|
|
|
81
98
|
)
|
|
82
99
|
continue
|
|
83
100
|
|
|
84
|
-
|
|
85
|
-
log.info("daily_scheduler: running %s (direction=%s)", config.connector, direction.value)
|
|
101
|
+
log.info("daily_scheduler: running %s", config.connector)
|
|
86
102
|
|
|
87
103
|
try:
|
|
88
104
|
ctx = await token_fetcher(company_id, config.connector)
|
|
@@ -90,16 +106,29 @@ async def check_and_run_daily_syncs(
|
|
|
90
106
|
log.warning("daily_scheduler: token fetch failed for %s: %s", config.connector, exc)
|
|
91
107
|
continue
|
|
92
108
|
|
|
93
|
-
#
|
|
94
|
-
|
|
109
|
+
# Sync every supported entity, honouring the configured direction: inbound
|
|
110
|
+
# entities pull from the platform; outbound (*_out) entities push back the ones
|
|
111
|
+
# the connector actually implements. run_sync enforces the direction gate too.
|
|
112
|
+
direction = SyncDirection(config.direction)
|
|
113
|
+
entities = [e.value for e in connector.supported_entities]
|
|
114
|
+
if direction in (SyncDirection.BOTH, SyncDirection.OUTBOUND):
|
|
115
|
+
entities += _supported_outbound(connector)
|
|
116
|
+
entity_results = []
|
|
117
|
+
for entity in entities:
|
|
95
118
|
try:
|
|
96
|
-
await run_sync(connector, ctx,
|
|
119
|
+
entity_results.append(await run_sync(connector, ctx, entity, direction=direction))
|
|
97
120
|
except Exception as exc:
|
|
98
|
-
log.error("daily_scheduler: sync error %s/%s: %s", config.connector,
|
|
121
|
+
log.error("daily_scheduler: sync error %s/%s: %s", config.connector, entity, exc)
|
|
122
|
+
|
|
123
|
+
# Only mark the connector synced (advancing the daily clock) if at least one
|
|
124
|
+
# entity made progress. On a total failure (e.g. a transient outage) we leave
|
|
125
|
+
# last_daily_sync_at unset, so the connector stays "due" and retries at the next
|
|
126
|
+
# tick that lands on its configured hour (the next day, or sooner on app restart).
|
|
127
|
+
if not any((r.created or r.updated or not r.errors) for r in entity_results):
|
|
128
|
+
log.warning("daily_scheduler: all entities failed for %s — will retry when next due", config.connector)
|
|
129
|
+
continue
|
|
99
130
|
|
|
100
131
|
synced.append(config.connector)
|
|
101
|
-
|
|
102
|
-
# Update last_daily_sync_at only after actually running
|
|
103
132
|
async with get_session_ctx() as session:
|
|
104
133
|
await session.execute(
|
|
105
134
|
sa.update(ConnectorConfig)
|
|
@@ -45,9 +45,25 @@ class RateLimitedClient:
|
|
|
45
45
|
async def delete(self, url: str, **kwargs) -> httpx.Response:
|
|
46
46
|
return await self._request("DELETE", url, **kwargs)
|
|
47
47
|
|
|
48
|
+
# Transient transport failures (flaky network, mid-pagination drops) — retried
|
|
49
|
+
# with the same backoff as 429/503 rather than aborting the whole sync page.
|
|
50
|
+
_RETRY_EXC = (
|
|
51
|
+
httpx.ConnectError, httpx.ReadTimeout, httpx.WriteTimeout,
|
|
52
|
+
httpx.PoolTimeout, httpx.RemoteProtocolError,
|
|
53
|
+
)
|
|
54
|
+
|
|
48
55
|
async def _request(self, method: str, url: str, **kwargs) -> httpx.Response:
|
|
49
56
|
for attempt in range(self._max_retries + 1):
|
|
50
|
-
|
|
57
|
+
try:
|
|
58
|
+
resp = await self._client.request(method, url, **kwargs)
|
|
59
|
+
except self._RETRY_EXC as exc:
|
|
60
|
+
if attempt == self._max_retries:
|
|
61
|
+
raise
|
|
62
|
+
delay = self._backoff_base ** attempt
|
|
63
|
+
log.info("Transport error (%s), retry %d/%d in %.1fs",
|
|
64
|
+
type(exc).__name__, attempt + 1, self._max_retries, delay)
|
|
65
|
+
await asyncio.sleep(delay)
|
|
66
|
+
continue
|
|
51
67
|
if resp.status_code not in (429, 503):
|
|
52
68
|
return resp
|
|
53
69
|
if attempt == self._max_retries:
|
|
@@ -100,14 +100,13 @@ async def _query(ctx: ConnectorContext, sql: str) -> list[dict[str, Any]]:
|
|
|
100
100
|
class QuickBooksConnector(ConnectorBase):
|
|
101
101
|
name = "quickbooks"
|
|
102
102
|
display_name = "QuickBooks"
|
|
103
|
-
supported_entities = [SyncEntity.PRODUCTS, SyncEntity.ORDERS, SyncEntity.CONTACTS
|
|
104
|
-
direction = SyncDirection.BOTH
|
|
103
|
+
supported_entities = [SyncEntity.PRODUCTS, SyncEntity.ORDERS, SyncEntity.CONTACTS]
|
|
105
104
|
category = ConnectorCategory.ACCOUNTING
|
|
105
|
+
direction = SyncDirection.BOTH
|
|
106
106
|
conflict_strategy = {
|
|
107
107
|
SyncEntity.PRODUCTS: "newest",
|
|
108
108
|
SyncEntity.ORDERS: "platform",
|
|
109
109
|
SyncEntity.CONTACTS: "merge",
|
|
110
|
-
SyncEntity.INVOICES: "platform",
|
|
111
110
|
}
|
|
112
111
|
|
|
113
112
|
# -- Products (Items in QB) ------------------------------------------------
|
|
@@ -124,7 +123,7 @@ class QuickBooksConnector(ConnectorBase):
|
|
|
124
123
|
Item.PurchaseCost -> item.cost_price
|
|
125
124
|
Item.Id -> idempotency_key
|
|
126
125
|
"""
|
|
127
|
-
result = SyncResult(entity=SyncEntity.PRODUCTS
|
|
126
|
+
result = SyncResult(entity=SyncEntity.PRODUCTS)
|
|
128
127
|
errors: list[str] = []
|
|
129
128
|
|
|
130
129
|
try:
|
|
@@ -159,11 +158,7 @@ class QuickBooksConnector(ConnectorBase):
|
|
|
159
158
|
cost_price=money(qb_item.get("PurchaseCost")),
|
|
160
159
|
idempotency_key=idempotency_key,
|
|
161
160
|
)
|
|
162
|
-
|
|
163
|
-
if created:
|
|
164
|
-
result.created += 1
|
|
165
|
-
else:
|
|
166
|
-
result.skipped += 1
|
|
161
|
+
result.record(await _upsert.upsert_item(ctx.company_id, item))
|
|
167
162
|
except Exception as exc:
|
|
168
163
|
errors.append(f"SKU {sku}: {exc}")
|
|
169
164
|
|
|
@@ -187,7 +182,7 @@ class QuickBooksConnector(ConnectorBase):
|
|
|
187
182
|
Invoice.Balance -> used to determine status
|
|
188
183
|
Invoice.Id -> idempotency_key
|
|
189
184
|
"""
|
|
190
|
-
result = SyncResult(entity=SyncEntity.ORDERS
|
|
185
|
+
result = SyncResult(entity=SyncEntity.ORDERS)
|
|
191
186
|
errors: list[str] = []
|
|
192
187
|
|
|
193
188
|
try:
|
|
@@ -199,11 +194,7 @@ class QuickBooksConnector(ConnectorBase):
|
|
|
199
194
|
|
|
200
195
|
for inv in invoices:
|
|
201
196
|
try:
|
|
202
|
-
|
|
203
|
-
if created:
|
|
204
|
-
result.created += 1
|
|
205
|
-
else:
|
|
206
|
-
result.skipped += 1
|
|
197
|
+
result.record(await _upsert.upsert_invoice_from_quickbooks(ctx.company_id, inv))
|
|
207
198
|
except Exception as exc:
|
|
208
199
|
errors.append(f"Invoice {inv.get('DocNumber')}: {exc}")
|
|
209
200
|
|
|
@@ -218,7 +209,7 @@ class QuickBooksConnector(ConnectorBase):
|
|
|
218
209
|
|
|
219
210
|
async def sync_contacts(self, ctx: ConnectorContext, since: datetime | None = None) -> SyncResult:
|
|
220
211
|
"""Pull QuickBooks Customers -> Celerp CRM contacts."""
|
|
221
|
-
result = SyncResult(entity=SyncEntity.CONTACTS
|
|
212
|
+
result = SyncResult(entity=SyncEntity.CONTACTS)
|
|
222
213
|
errors: list[str] = []
|
|
223
214
|
|
|
224
215
|
try:
|
|
@@ -230,11 +221,7 @@ class QuickBooksConnector(ConnectorBase):
|
|
|
230
221
|
|
|
231
222
|
for customer in customers:
|
|
232
223
|
try:
|
|
233
|
-
|
|
234
|
-
if created:
|
|
235
|
-
result.created += 1
|
|
236
|
-
else:
|
|
237
|
-
result.skipped += 1
|
|
224
|
+
result.record(await _upsert.upsert_contact_from_quickbooks(ctx.company_id, customer))
|
|
238
225
|
except Exception as exc:
|
|
239
226
|
errors.append(f"Customer {customer.get('Id')}: {exc}")
|
|
240
227
|
|
|
@@ -286,6 +273,11 @@ class QuickBooksConnector(ConnectorBase):
|
|
|
286
273
|
json=payload,
|
|
287
274
|
)
|
|
288
275
|
resp.raise_for_status()
|
|
276
|
+
# Write-back: stamp the returned QB invoice id so the next run's
|
|
277
|
+
# list_unsynced_invoices skips this doc — never created in QB twice.
|
|
278
|
+
qb_id = ((resp.json() or {}).get("Invoice") or {}).get("Id")
|
|
279
|
+
if qb_id and inv.get("entity_id"):
|
|
280
|
+
await _upsert.mark_doc_pushed(ctx.company_id, inv["entity_id"], "quickbooks", qb_id)
|
|
289
281
|
result.created += 1
|
|
290
282
|
except Exception as exc:
|
|
291
283
|
errors.append(f"Invoice {inv.get('ref_id')}: {exc}")
|
|
@@ -58,8 +58,8 @@ class ShopifyConnector(ConnectorBase):
|
|
|
58
58
|
name = "shopify"
|
|
59
59
|
display_name = "Shopify"
|
|
60
60
|
supported_entities = [SyncEntity.PRODUCTS, SyncEntity.ORDERS, SyncEntity.CONTACTS]
|
|
61
|
-
direction = SyncDirection.BOTH
|
|
62
61
|
category = ConnectorCategory.WEBSITE
|
|
62
|
+
direction = SyncDirection.BOTH
|
|
63
63
|
conflict_strategy = {
|
|
64
64
|
SyncEntity.PRODUCTS: "newest",
|
|
65
65
|
SyncEntity.ORDERS: "platform",
|
|
@@ -99,7 +99,7 @@ class ShopifyConnector(ConnectorBase):
|
|
|
99
99
|
"""
|
|
100
100
|
from celerp_inventory.routes import ItemCreate
|
|
101
101
|
|
|
102
|
-
result = SyncResult(entity=SyncEntity.PRODUCTS
|
|
102
|
+
result = SyncResult(entity=SyncEntity.PRODUCTS)
|
|
103
103
|
errors: list[str] = []
|
|
104
104
|
|
|
105
105
|
try:
|
|
@@ -132,11 +132,7 @@ class ShopifyConnector(ConnectorBase):
|
|
|
132
132
|
)
|
|
133
133
|
|
|
134
134
|
try:
|
|
135
|
-
|
|
136
|
-
if created:
|
|
137
|
-
result.created += 1
|
|
138
|
-
else:
|
|
139
|
-
result.skipped += 1
|
|
135
|
+
result.record(await _upsert.upsert_item(ctx.company_id, item))
|
|
140
136
|
except Exception as exc:
|
|
141
137
|
errors.append(f"SKU {sku}: {exc}")
|
|
142
138
|
continue
|
|
@@ -203,7 +199,7 @@ class ShopifyConnector(ConnectorBase):
|
|
|
203
199
|
financial_status -> doc status (paid -> closed, pending -> open)
|
|
204
200
|
order.id -> idempotency_key
|
|
205
201
|
"""
|
|
206
|
-
result = SyncResult(entity=SyncEntity.ORDERS
|
|
202
|
+
result = SyncResult(entity=SyncEntity.ORDERS)
|
|
207
203
|
errors: list[str] = []
|
|
208
204
|
|
|
209
205
|
try:
|
|
@@ -217,11 +213,7 @@ class ShopifyConnector(ConnectorBase):
|
|
|
217
213
|
|
|
218
214
|
for order in orders:
|
|
219
215
|
try:
|
|
220
|
-
|
|
221
|
-
if created:
|
|
222
|
-
result.created += 1
|
|
223
|
-
else:
|
|
224
|
-
result.skipped += 1
|
|
216
|
+
result.record(await _upsert.upsert_order_from_shopify(ctx.company_id, order))
|
|
225
217
|
except Exception as exc:
|
|
226
218
|
msg = f"Order {order.get('name')}: {exc}"
|
|
227
219
|
log.warning("shopify.sync_orders error: %s", msg)
|
|
@@ -238,7 +230,7 @@ class ShopifyConnector(ConnectorBase):
|
|
|
238
230
|
|
|
239
231
|
async def sync_contacts(self, ctx: ConnectorContext, since: datetime | None = None) -> SyncResult:
|
|
240
232
|
"""Pull Shopify customers -> Celerp CRM contacts."""
|
|
241
|
-
result = SyncResult(entity=SyncEntity.CONTACTS
|
|
233
|
+
result = SyncResult(entity=SyncEntity.CONTACTS)
|
|
242
234
|
errors: list[str] = []
|
|
243
235
|
|
|
244
236
|
try:
|
|
@@ -249,11 +241,7 @@ class ShopifyConnector(ConnectorBase):
|
|
|
249
241
|
|
|
250
242
|
for customer in customers:
|
|
251
243
|
try:
|
|
252
|
-
|
|
253
|
-
if created:
|
|
254
|
-
result.created += 1
|
|
255
|
-
else:
|
|
256
|
-
result.skipped += 1
|
|
244
|
+
result.record(await _upsert.upsert_contact_from_shopify(ctx.company_id, customer))
|
|
257
245
|
except Exception as exc:
|
|
258
246
|
errors.append(f"Customer {customer.get('id')}: {exc}")
|
|
259
247
|
|
|
@@ -262,8 +250,13 @@ class ShopifyConnector(ConnectorBase):
|
|
|
262
250
|
|
|
263
251
|
# -- Outbound: Inventory push ----------------------------------------------
|
|
264
252
|
|
|
265
|
-
async def
|
|
266
|
-
"""Push Celerp stock levels -> Shopify inventory levels.
|
|
253
|
+
async def sync_inventory_out(self, ctx: ConnectorContext) -> SyncResult:
|
|
254
|
+
"""Push Celerp stock levels -> Shopify inventory levels (outbound).
|
|
255
|
+
|
|
256
|
+
Named *_out so it is classified as an outbound entity and actually gets
|
|
257
|
+
dispatched by the scheduler; as `sync_inventory` it was mis-typed as the
|
|
258
|
+
inbound 'inventory' entity and never ran (Shopify has no inbound inventory
|
|
259
|
+
pull — stock arrives via product variants)."""
|
|
267
260
|
result = SyncResult(entity=SyncEntity.INVENTORY, direction=SyncDirection.OUTBOUND)
|
|
268
261
|
errors: list[str] = []
|
|
269
262
|
|
|
@@ -376,47 +369,6 @@ class ShopifyConnector(ConnectorBase):
|
|
|
376
369
|
)
|
|
377
370
|
return result
|
|
378
371
|
|
|
379
|
-
# -- Webhook lifecycle -----------------------------------------------------
|
|
380
|
-
|
|
381
|
-
_INBOUND_TOPICS = [
|
|
382
|
-
"products/create", "products/update", "products/delete",
|
|
383
|
-
"orders/create", "orders/updated",
|
|
384
|
-
"inventory_levels/update",
|
|
385
|
-
"customers/create", "customers/update",
|
|
386
|
-
]
|
|
387
|
-
|
|
388
|
-
def webhook_topics_for_direction(self, direction: SyncDirection) -> list[str]:
|
|
389
|
-
if direction == SyncDirection.OUTBOUND:
|
|
390
|
-
return []
|
|
391
|
-
return self._INBOUND_TOPICS
|
|
392
|
-
|
|
393
|
-
async def register_webhooks(self, ctx: ConnectorContext, webhook_url: str) -> list[str]:
|
|
394
|
-
"""Register Shopify webhooks. Returns list of webhook IDs."""
|
|
395
|
-
base = _base_url(ctx)
|
|
396
|
-
ids: list[str] = []
|
|
397
|
-
topics = self.webhook_topics_for_direction(self.direction)
|
|
398
|
-
async with RateLimitedClient() as client:
|
|
399
|
-
for topic in topics:
|
|
400
|
-
resp = await client.post(
|
|
401
|
-
f"{base}/webhooks.json",
|
|
402
|
-
headers=_headers(ctx),
|
|
403
|
-
json={"webhook": {"topic": topic, "address": webhook_url, "format": "json"}},
|
|
404
|
-
)
|
|
405
|
-
if resp.status_code in (200, 201):
|
|
406
|
-
wh = resp.json().get("webhook", {})
|
|
407
|
-
ids.append(str(wh.get("id", "")))
|
|
408
|
-
else:
|
|
409
|
-
log.warning("shopify.register_webhook topic=%s status=%d", topic, resp.status_code)
|
|
410
|
-
return ids
|
|
411
|
-
|
|
412
|
-
async def deregister_webhooks(self, ctx: ConnectorContext, webhook_ids: list[str]) -> None:
|
|
413
|
-
base = _base_url(ctx)
|
|
414
|
-
async with RateLimitedClient() as client:
|
|
415
|
-
for wid in webhook_ids:
|
|
416
|
-
resp = await client.delete(f"{base}/webhooks/{wid}.json", headers=_headers(ctx))
|
|
417
|
-
if resp.status_code not in (200, 204):
|
|
418
|
-
log.warning("shopify.deregister_webhook id=%s status=%d", wid, resp.status_code)
|
|
419
|
-
|
|
420
372
|
|
|
421
373
|
# -- Pagination helper ---------------------------------------------------------
|
|
422
374
|
|
|
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
|
|
6
6
|
import json
|
|
7
7
|
import logging
|
|
8
|
-
from datetime import datetime, timezone
|
|
8
|
+
from datetime import datetime, timedelta, timezone
|
|
9
9
|
|
|
10
10
|
from celerp.connectors.base import (
|
|
11
11
|
ConnectorBase,
|
|
@@ -53,10 +53,90 @@ async def _last_success_watermark(company_id: str, connector: str, entity: str):
|
|
|
53
53
|
SyncRun.status == "success",
|
|
54
54
|
)
|
|
55
55
|
)
|
|
56
|
-
except Exception:
|
|
56
|
+
except Exception as exc:
|
|
57
|
+
# A read failure degrades to a full re-pull (safe, dup-safe via idempotency
|
|
58
|
+
# keys) — but say so rather than silently widening every sync.
|
|
59
|
+
log.warning("Could not read sync watermark for %s.%s: %s — full pull", connector, entity, exc)
|
|
60
|
+
return None
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# Concurrency guard: a still-unfinished run older than this is treated as dead (the
|
|
64
|
+
# process likely crashed mid-sync), so a new run is allowed to supersede it.
|
|
65
|
+
_STALE_AFTER = timedelta(minutes=15)
|
|
66
|
+
_BUSY = "busy"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
async def _begin_run(company_id: str, connector: str, entity: str, direction_str: str, started_at):
|
|
70
|
+
"""Insert an in-progress SyncRun (status=running, finished_at=None) so the UI can
|
|
71
|
+
observe an active sync, and return its id. Returns _BUSY if a recent unfinished run
|
|
72
|
+
for this (company, connector, entity) already exists (the concurrency guard). Returns
|
|
73
|
+
None if the row could not be written, in which case the sync still runs and a single
|
|
74
|
+
final row is written by _finish_run."""
|
|
75
|
+
import sqlalchemy as sa
|
|
76
|
+
|
|
77
|
+
from celerp.db import get_session_ctx
|
|
78
|
+
|
|
79
|
+
try:
|
|
80
|
+
async with get_session_ctx() as session:
|
|
81
|
+
existing = await session.scalar(
|
|
82
|
+
sa.select(SyncRun.id)
|
|
83
|
+
.where(
|
|
84
|
+
SyncRun.company_id == company_id,
|
|
85
|
+
SyncRun.connector == connector,
|
|
86
|
+
SyncRun.entity == entity,
|
|
87
|
+
SyncRun.finished_at.is_(None),
|
|
88
|
+
SyncRun.started_at >= started_at - _STALE_AFTER,
|
|
89
|
+
)
|
|
90
|
+
.limit(1)
|
|
91
|
+
)
|
|
92
|
+
if existing is not None:
|
|
93
|
+
return _BUSY
|
|
94
|
+
run = SyncRun(
|
|
95
|
+
company_id=company_id, connector=connector, entity=entity,
|
|
96
|
+
direction=direction_str, started_at=started_at, finished_at=None,
|
|
97
|
+
created_count=0, updated_count=0, skipped_count=0,
|
|
98
|
+
errors_json=None, status="running",
|
|
99
|
+
)
|
|
100
|
+
session.add(run)
|
|
101
|
+
await session.commit()
|
|
102
|
+
await session.refresh(run)
|
|
103
|
+
return run.id
|
|
104
|
+
except Exception as exc:
|
|
105
|
+
log.warning("Failed to record in-progress SyncRun: %s", exc)
|
|
57
106
|
return None
|
|
58
107
|
|
|
59
108
|
|
|
109
|
+
async def _finish_run(run_id, company_id, connector, entity, result, started_at, finished_at, status):
|
|
110
|
+
"""Update the in-progress row with the final outcome, or insert a final row if the
|
|
111
|
+
in-progress write failed (run_id is None)."""
|
|
112
|
+
import sqlalchemy as sa
|
|
113
|
+
|
|
114
|
+
from celerp.db import get_session_ctx
|
|
115
|
+
|
|
116
|
+
direction_str = result.direction.value if hasattr(result.direction, "value") else str(result.direction)
|
|
117
|
+
errors_json = json.dumps(result.errors) if result.errors else None
|
|
118
|
+
try:
|
|
119
|
+
async with get_session_ctx() as session:
|
|
120
|
+
if run_id is not None:
|
|
121
|
+
await session.execute(
|
|
122
|
+
sa.update(SyncRun).where(SyncRun.id == run_id).values(
|
|
123
|
+
direction=direction_str, finished_at=finished_at,
|
|
124
|
+
created_count=result.created, updated_count=result.updated,
|
|
125
|
+
skipped_count=result.skipped, errors_json=errors_json, status=status,
|
|
126
|
+
)
|
|
127
|
+
)
|
|
128
|
+
else:
|
|
129
|
+
session.add(SyncRun(
|
|
130
|
+
company_id=company_id, connector=connector, entity=entity,
|
|
131
|
+
direction=direction_str, started_at=started_at, finished_at=finished_at,
|
|
132
|
+
created_count=result.created, updated_count=result.updated,
|
|
133
|
+
skipped_count=result.skipped, errors_json=errors_json, status=status,
|
|
134
|
+
))
|
|
135
|
+
await session.commit()
|
|
136
|
+
except Exception as exc:
|
|
137
|
+
log.warning("Failed to record SyncRun: %s", exc)
|
|
138
|
+
|
|
139
|
+
|
|
60
140
|
async def run_sync(
|
|
61
141
|
connector: ConnectorBase,
|
|
62
142
|
ctx: ConnectorContext,
|
|
@@ -69,8 +149,6 @@ async def run_sync(
|
|
|
69
149
|
If ``direction`` is provided, checks whether ``entity`` is allowed
|
|
70
150
|
for that direction before running. Returns a failed SyncResult if blocked.
|
|
71
151
|
"""
|
|
72
|
-
from celerp.db import get_session_ctx
|
|
73
|
-
|
|
74
152
|
# Direction gate
|
|
75
153
|
if direction and not entity_allowed(entity, direction):
|
|
76
154
|
try:
|
|
@@ -92,13 +170,25 @@ async def run_sync(
|
|
|
92
170
|
if sync_method is None:
|
|
93
171
|
raise ValueError(f"{connector.name} has no method {method_name}")
|
|
94
172
|
|
|
173
|
+
started_at = datetime.now(timezone.utc)
|
|
174
|
+
intended_direction = direction if isinstance(direction, SyncDirection) else connector.direction
|
|
175
|
+
direction_str = intended_direction.value if hasattr(intended_direction, "value") else str(intended_direction)
|
|
176
|
+
|
|
177
|
+
# Mark this entity's sync as in progress (and refuse to start a second concurrent
|
|
178
|
+
# run for the same entity). The UI polls these rows for live status.
|
|
179
|
+
run_id = await _begin_run(ctx.company_id, connector.name, entity, direction_str, started_at)
|
|
180
|
+
if run_id == _BUSY:
|
|
181
|
+
return SyncResult(
|
|
182
|
+
entity=entity,
|
|
183
|
+
direction=intended_direction,
|
|
184
|
+
errors=[f"{entity} sync already in progress"],
|
|
185
|
+
)
|
|
186
|
+
|
|
95
187
|
# Incremental by default: pull only what changed since the last successful run
|
|
96
188
|
# for this entity. Idempotency keys make any overlap dup-safe.
|
|
97
189
|
if since is None and entity not in _OUTBOUND_ENTITIES:
|
|
98
190
|
since = await _last_success_watermark(ctx.company_id, connector.name, entity)
|
|
99
191
|
|
|
100
|
-
started_at = datetime.now(timezone.utc)
|
|
101
|
-
|
|
102
192
|
try:
|
|
103
193
|
if entity in _OUTBOUND_ENTITIES:
|
|
104
194
|
result = await sync_method(ctx)
|
|
@@ -126,26 +216,7 @@ async def run_sync(
|
|
|
126
216
|
else:
|
|
127
217
|
status = "success"
|
|
128
218
|
|
|
129
|
-
|
|
130
|
-
company_id=ctx.company_id,
|
|
131
|
-
connector=connector.name,
|
|
132
|
-
entity=entity,
|
|
133
|
-
direction=result.direction.value if hasattr(result.direction, "value") else str(result.direction),
|
|
134
|
-
started_at=started_at,
|
|
135
|
-
finished_at=finished_at,
|
|
136
|
-
created_count=result.created,
|
|
137
|
-
updated_count=result.updated,
|
|
138
|
-
skipped_count=result.skipped,
|
|
139
|
-
errors_json=json.dumps(result.errors) if result.errors else None,
|
|
140
|
-
status=status,
|
|
141
|
-
)
|
|
142
|
-
|
|
143
|
-
try:
|
|
144
|
-
async with get_session_ctx() as session:
|
|
145
|
-
session.add(run)
|
|
146
|
-
await session.commit()
|
|
147
|
-
except Exception as exc:
|
|
148
|
-
log.warning("Failed to record SyncRun: %s", exc)
|
|
219
|
+
await _finish_run(run_id, ctx.company_id, connector.name, entity, result, started_at, finished_at, status)
|
|
149
220
|
|
|
150
221
|
log.info(
|
|
151
222
|
"sync_run %s.%s company=%s status=%s created=%d updated=%d skipped=%d errors=%d",
|
|
@@ -9,51 +9,53 @@ light and to respect default-module load order.
|
|
|
9
9
|
from __future__ import annotations
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
async def upsert_item(company_id: str, item) ->
|
|
12
|
+
async def upsert_item(company_id: str, item) -> str:
|
|
13
13
|
from celerp_inventory import services as items_svc
|
|
14
14
|
return await items_svc.upsert_from_connector(company_id, item)
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
async def upsert_order_from_shopify(company_id: str, order: dict) ->
|
|
17
|
+
async def upsert_order_from_shopify(company_id: str, order: dict) -> str:
|
|
18
18
|
from celerp_docs import doc_service as docs_svc
|
|
19
19
|
return await docs_svc.upsert_order_from_shopify(company_id, order)
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
async def upsert_invoice_from_quickbooks(company_id: str, invoice: dict) ->
|
|
22
|
+
async def upsert_invoice_from_quickbooks(company_id: str, invoice: dict) -> str:
|
|
23
23
|
from celerp_docs import doc_service as docs_svc
|
|
24
24
|
return await docs_svc.upsert_invoice_from_quickbooks(company_id, invoice)
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
async def upsert_invoice_from_xero(company_id: str, invoice: dict) ->
|
|
27
|
+
async def upsert_invoice_from_xero(company_id: str, invoice: dict) -> str:
|
|
28
28
|
from celerp_docs import doc_service as docs_svc
|
|
29
29
|
return await docs_svc.upsert_invoice_from_xero(company_id, invoice)
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
async def upsert_order_from_woocommerce(company_id: str, order: dict) ->
|
|
32
|
+
async def upsert_order_from_woocommerce(company_id: str, order: dict) -> str:
|
|
33
33
|
from celerp_docs import doc_service as docs_svc
|
|
34
34
|
return await docs_svc.upsert_order_from_woocommerce(company_id, order)
|
|
35
35
|
|
|
36
36
|
|
|
37
|
-
async def upsert_contact_from_shopify(company_id: str, customer: dict) ->
|
|
37
|
+
async def upsert_contact_from_shopify(company_id: str, customer: dict) -> str:
|
|
38
38
|
from celerp_contacts import services as contacts_svc
|
|
39
39
|
return await contacts_svc.upsert_contact_from_shopify(company_id, customer)
|
|
40
40
|
|
|
41
41
|
|
|
42
|
-
async def upsert_contact_from_quickbooks(company_id: str, customer: dict) ->
|
|
42
|
+
async def upsert_contact_from_quickbooks(company_id: str, customer: dict) -> str:
|
|
43
43
|
from celerp_contacts import services as contacts_svc
|
|
44
44
|
return await contacts_svc.upsert_contact_from_quickbooks(company_id, customer)
|
|
45
45
|
|
|
46
46
|
|
|
47
|
-
async def upsert_contact_from_xero(company_id: str, contact: dict) ->
|
|
47
|
+
async def upsert_contact_from_xero(company_id: str, contact: dict) -> str:
|
|
48
48
|
from celerp_contacts import services as contacts_svc
|
|
49
49
|
return await contacts_svc.upsert_contact_from_xero(company_id, contact)
|
|
50
50
|
|
|
51
51
|
|
|
52
|
-
async def upsert_contact_from_woocommerce(company_id: str, customer: dict) ->
|
|
52
|
+
async def upsert_contact_from_woocommerce(company_id: str, customer: dict) -> str:
|
|
53
53
|
from celerp_contacts import services as contacts_svc
|
|
54
54
|
return await contacts_svc.upsert_contact_from_woocommerce(company_id, customer)
|
|
55
55
|
|
|
56
56
|
|
|
57
|
+
# -- Outbound: read the Celerp-side list of records to push ---------------------
|
|
58
|
+
|
|
57
59
|
async def list_items_with_external_id(company_id: str, platform: str) -> list:
|
|
58
60
|
from celerp_inventory import services as items_svc
|
|
59
61
|
return await items_svc.list_items_with_external_id(company_id, platform=platform)
|
|
@@ -67,3 +69,8 @@ async def list_items_modified_since_last_sync(company_id: str, platform: str) ->
|
|
|
67
69
|
async def list_unsynced_invoices(company_id: str, platform: str) -> list:
|
|
68
70
|
from celerp_docs import doc_service as docs_svc
|
|
69
71
|
return await docs_svc.list_unsynced_invoices(company_id, platform=platform)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
async def mark_doc_pushed(company_id: str, entity_id: str, platform: str, external_id: str, entity: str = "invoice") -> None:
|
|
75
|
+
from celerp_docs import doc_service as docs_svc
|
|
76
|
+
return await docs_svc.mark_doc_pushed(company_id, entity_id, platform, external_id, entity=entity)
|