Order source ownership

Shop resolves order ownership at its shared persistence boundary, before export hooks. OrderingRouteState is Unresolved (0), Local (1), or External (2). External orders have an OrderingSourceKey and a pinned OrderingIntegrationInstanceId. Resolution uses local source membership and exactly one enabled integration; it performs no external requests. Clients cannot assign these fields.

Draft routes can follow context changes. Placed routes remain immutable across account, delivery-date and cutover changes. Product eligibility for placed orders uses the pinned provider. A disabled, missing or ambiguous integration blocks handling rather than redirecting the order. Synthetic payment-card registration orders have no fulfillment destination. Saving a card alongside a real purchase does not make that purchase synthetic.

PCD normalization, export, read enrichment and synchronization require persisted PCD ownership. Existing remote PCD orders can be imported using a server-only typed import context; ordinary requests cannot use this exception. PCD's existing transport remains synchronous for its own orders. Source modules can implement IOrderSourceCommitmentProvider for transactional local-only placement evidence; internal SetPlaced pins the route before status effects as well as normal persistence. External dispatch after payment capture is owned by the destination module.

Deployment and legacy classification

Deploy Core, Shop and PCD together and run normal Platform migrations. Shop requires product schema 1.76 or a later minor within major 1: the dependency must permit newer installed minors while ordering the synthetic-payment columns before the backfill. order-1.22.14 classifies strong legacy card-registration evidence: registration intent, zero value, no delivery/usage dates, no product history (including deleted lines), and a direct payment-subscription relation containing a remote subscription ID. Failed or abandoned registrations without that evidence remain unresolved. order-1.22.15 contains only the routing-column/index ALTER; order-1.22.16 assigns synthetic references to Local. DML and DDL use separate migration checkpoints because MySQL DDL implicitly commits.

pcd-1.14.14 depends on order-1.22.16. Actual remote person/department week-order IDs establish PCD ownership; person/department IDs alone do not. Backfill requires one enabled PCD integration and excludes source-offer conflicts. Synthetic orders with remote PCD order evidence are quarantined as unresolved. Neither migration guesses ownership from current cutover settings. Large sites should allow for the indexed schema change and bulk backfill in their deployment window.

No property changes or manual SQL are required for normal deployment. Before deploying, preview the decisive evidence on the target copy with aggregate-only reads:

SELECT COUNT(*) AS legacy_registration_candidates
FROM orders o
WHERE o.is_synthetic_payment_order = 0
  AND o.is_payment_method_subscription_desired = 1
  AND o.total_base_value = 0
  AND o.desired_delivery_time IS NULL
  AND o.expected_delivery_time IS NULL AND o.usage_time IS NULL
  AND NOT EXISTS (SELECT 1 FROM order_product p WHERE p.order_id = o.id)
  AND EXISTS (SELECT 1 FROM payment_method_subscriptions s
    WHERE s.local_order_id = o.id AND s.remote_subscription_id IS NOT NULL
      AND s.remote_subscription_id <> '');

SELECT COUNT(*) AS synthetic_pcd_conflicts
FROM orders o JOIN pcd_week_order_infos p ON p.id = o.id
WHERE o.is_synthetic_payment_order = 1
  AND (p.pcd_person_week_order_id > 0 OR p.pcd_department_week_order_id > 0);

After migrations, inspect counts by route without exposing customer data:

SELECT ordering_route_state, ordering_source_key,
       ordering_integration_instance_id, COUNT(*) AS orders_count
FROM orders
GROUP BY ordering_route_state, ordering_source_key, ordering_integration_instance_id;

Unresolved historical orders cannot export. Resolve only explicitly reviewed IDs using independent evidence; never bulk-assign them from today's program, account mapping or cutover. For example, after confirming a particular order is a non-synthetic PCD order, substitute its reviewed ID below. Run while Platform is stopped, inspect the affected-row count, and commit only the intended row:

START TRANSACTION;
SET @reviewed_order_id = 0; -- Replace with one independently verified order ID.
UPDATE orders o
JOIN (SELECT MIN(id) AS id FROM integration_instances
      WHERE provider_key = 'pcd' AND is_enabled = 1
        AND COALESCE(is_deleted, 0) = 0 HAVING COUNT(*) = 1) i
SET o.ordering_route_state = 2, o.ordering_source_key = 'pcd',
    o.ordering_integration_instance_id = i.id
WHERE o.id = @reviewed_order_id AND o.ordering_route_state = 0
  AND o.is_synthetic_payment_order = 0;
SELECT ROW_COUNT() AS reviewed_orders_assigned;
ROLLBACK; -- Replace with COMMIT only after reviewing the result.

Changing an already resolved placed route or clearing a synthetic conflict requires a separate recovery decision. The example deliberately cannot do either.

Migration regression data in Piranya.Platform.Server.Modules.Shop.Test/Fixtures/Ordering/route-backfill-setup.sql uses session temporary tables only. Apply both actual migrations in that connection and verify synthetic, ambiguous, conflicting and proven PCD cases, including multiple enabled integrations. Also repeat the final Shop data step before the PCD backfill to cover an already-upgraded copy whose earlier PCD migration has run. Never run fixture setup against a shared persistent fixture schema.

All three routing fields are persisted scalar properties in the embedded OrderInfo.hbm.xml mapping. Both full saves and placement's partial updates depend on that mapping, independently of the SQL columns.