app

Local-first trade for farms and co-ops
git clone https://radroots.dev/git/app.git
Log | Files | Refs | README | LICENSE

commit 8c9c1ad86c52e90b749fd68a03493c630b7e71cf
parent 42be1a19470d6c46dec0afe51a7ba15eeffeb979
Author: triesap <tyson@radroots.org>
Date:   Fri, 19 Jun 2026 20:32:35 -0700

app: prune agreement-only order state

- remove payment, settlement, receipt, and fulfillment lifecycle app surfaces
- collapse sync, SDK, store, and view projections onto request agreement flow
- update migrations and interop tests for revision and pre-agreement cancellation
- refresh UI, i18n, and source guards after deleting obsolete workflow fields

Diffstat:
Mcrates/desktop/src/runtime.rs | 2388++++---------------------------------------------------------------------------
Mcrates/desktop/src/source_guards.rs | 52----------------------------------------------------
Mcrates/desktop/src/window.rs | 832+++++--------------------------------------------------------------------------
Mcrates/i18n/src/keys.rs | 35-----------------------------------
Mcrates/i18n/src/lib.rs | 95-------------------------------------------------------------------------------
Mcrates/runtime/src/lib.rs | 18++++++++----------
Mcrates/runtime/src/sdk.rs | 219++++---------------------------------------------------------------------------
Mcrates/state/src/lib.rs | 9++-------
Mcrates/store/migrations/0001_init.sql | 2+-
Mcrates/store/migrations/0011_reminders_and_recovery.sql | 4+---
Mcrates/store/migrations/0020_declined_order_status.sql | 2+-
Mcrates/store/migrations/0023_order_workflow_display_projection.sql | 26++------------------------
Acrates/store/migrations/0024_order_workflow_agreement_states.sql | 1+
Dcrates/store/migrations/0024_order_workflow_payment_display_states.sql | 196-------------------------------------------------------------------------------
Dcrates/store/migrations/0025_order_receipt_display_projection.sql | 204-------------------------------------------------------------------------------
Acrates/store/migrations/0025_order_workflow_agreement_projection.sql | 186+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcrates/store/src/interop.rs | 964+++++++++++--------------------------------------------------------------------
Mcrates/store/src/lib.rs | 85+++++++++++++++++++++++--------------------------------------------------------
Mcrates/store/src/migration_audit.rs | 61++++++++++---------------------------------------------------
Mcrates/store/src/migrations.rs | 4++--
Mcrates/store/src/repo/buyer.rs | 120++++++++++++++-----------------------------------------------------------------
Mcrates/store/src/repo/orders.rs | 236+++++++++----------------------------------------------------------------------
Mcrates/store/src/repo/reminders.rs | 6++----
Mcrates/store/src/repo/workflow.rs | 125++-----------------------------------------------------------------------------
Mcrates/sync/src/lib.rs | 1-
Mcrates/sync/src/publish.rs | 248+++++++------------------------------------------------------------------------
Mcrates/types/src/lib.rs | 14+-------------
Mcrates/view/src/lib.rs | 663++++++-------------------------------------------------------------------------
28 files changed, 631 insertions(+), 6165 deletions(-)

diff --git a/crates/desktop/src/runtime.rs b/crates/desktop/src/runtime.rs @@ -10,13 +10,11 @@ use radroots_app_core::{ AppBuildIdentity, AppDesktopRuntimePaths, AppRuntimeCapture, AppRuntimeMode, AppRuntimePathsError, AppRuntimeSnapshot, AppSdkConfig, AppSdkDiagnostics, AppSdkFarmPublishRequest, AppSdkLifecycleState, AppSdkListingPublishRequest, - AppSdkOrderCancellationRequest, AppSdkOrderDecisionRequest, - AppSdkOrderFulfillmentUpdateRequest, AppSdkOrderReceiptRecordRequest, - AppSdkOrderRevisionDecisionRequest, AppSdkOrderRevisionProposalRequest, - AppSdkOrderSubmitRequest, AppSdkProjectionLifecycleState, AppSdkRelayUrlPolicy, AppSdkRuntime, - AppSdkRuntimeError, AppSdkRuntimeIssue, AppSdkRuntimeStatus, AppSdkStoragePaths, - AppSdkWorkflowReceipt, AppSharedAccountsPaths, PackDayExportWriteError, - prepare_pack_day_export_bundle_at_data_root, + AppSdkOrderCancellationRequest, AppSdkOrderDecisionRequest, AppSdkOrderRevisionDecisionRequest, + AppSdkOrderRevisionProposalRequest, AppSdkOrderSubmitRequest, AppSdkProjectionLifecycleState, + AppSdkRelayUrlPolicy, AppSdkRuntime, AppSdkRuntimeError, AppSdkRuntimeIssue, + AppSdkRuntimeStatus, AppSdkStoragePaths, AppSdkWorkflowReceipt, AppSharedAccountsPaths, + PackDayExportWriteError, prepare_pack_day_export_bundle_at_data_root, shared_local_events_database_path_from_shared_accounts, write_prepared_pack_day_export_bundle, }; use radroots_app_remote_signer::{ @@ -43,7 +41,6 @@ use radroots_app_state::{ use radroots_app_sync::{ AppFarmProfilePublishPayload, AppListingPublishPayload, AppOrderCancellationPublishPayload, AppOrderDecisionInventoryCommitment, AppOrderDecisionPayload, AppOrderDecisionPublishPayload, - AppOrderFulfillmentPublishPayload, AppOrderReceiptOutcome, AppOrderReceiptPublishPayload, AppOrderRequestItemPayload, AppOrderRequestPublishPayload, AppOrderRevisionDecisionPublishPayload, AppOrderRevisionProposalPublishPayload, AppPublishContext, AppPublishPayload, AppPublishedOperationReceipt, @@ -57,8 +54,8 @@ use radroots_app_view::{ BuyerContext, BuyerOrderDetailProjection, BuyerOrderReviewDraft, BuyerOrderStatus, BuyerProductDetailProjection, FarmId, FarmOrderMethod, FarmProfileRecord, FarmReadiness, FarmRulesProjection, FarmSetupDraft, FarmSetupProjection, FarmSummary, FarmerSection, - FulfillmentWindowId, LoggedOutStartupProjection, OrderDetailProjection, OrderFulfillmentAction, - OrderId, OrderRecoveryProjection, OrderStatus, OrdersFilter, OrdersListProjection, + FulfillmentWindowId, LoggedOutStartupProjection, OrderDetailProjection, OrderId, + OrderRecoveryProjection, OrderStatus, OrdersFilter, OrdersListProjection, OrdersScreenQueryState, PackDayBatchPrintStatus, PackDayExportBundle, PackDayExportInstanceId, PackDayExportStatus, PackDayHostHandoffKind, PackDayHostHandoffStatus, PackDayPrintKind, PackDayPrintStatus, PackDayProjection, PackDayScreenQueryState, PersonalSection, @@ -80,15 +77,11 @@ use radroots_events::{ }, kinds::{ KIND_FARM, KIND_LISTING, KIND_LISTING_DRAFT, KIND_ORDER_CANCELLATION, KIND_ORDER_DECISION, - KIND_ORDER_FULFILLMENT_UPDATE, KIND_ORDER_PAYMENT_RECORD, KIND_ORDER_RECEIPT, KIND_ORDER_REQUEST, KIND_ORDER_REVISION_DECISION, KIND_ORDER_REVISION_PROPOSAL, - KIND_ORDER_SETTLEMENT_DECISION, KIND_PROFILE, + KIND_PROFILE, }, }; -use radroots_events_codec::order::{ - order_event_context_from_tags, order_payment_record_from_event, - order_settlement_decision_from_event, -}; +use radroots_events_codec::order::order_event_context_from_tags; use radroots_identity::{RadrootsIdentity, RadrootsIdentityId}; use radroots_local_events::{ BUYER_ORDER_REQUEST_ACTOR_SOURCE_RESOLVED_ACCOUNT, @@ -114,25 +107,21 @@ use radroots_sdk::protocol::listing::{ }; use radroots_sdk::protocol::order::{ RadrootsOrderCancellation, RadrootsOrderDecision, RadrootsOrderDecisionOutcome, - RadrootsOrderEconomics, RadrootsOrderFulfillmentState, RadrootsOrderFulfillmentUpdate, - RadrootsOrderInventoryCommitment, RadrootsOrderItem, RadrootsOrderReceipt, + RadrootsOrderEconomics, RadrootsOrderInventoryCommitment, RadrootsOrderItem, RadrootsOrderRequest, RadrootsOrderRevisionDecision, RadrootsOrderRevisionOutcome, RadrootsOrderRevisionProposal, }; use radroots_sdk::{ FARM_PUBLISH_OPERATION_KIND, LISTING_PUBLISH_OPERATION_KIND, ORDER_CANCELLATION_OPERATION_KIND, - ORDER_DECISION_OPERATION_KIND, ORDER_FULFILLMENT_UPDATE_OPERATION_KIND, - ORDER_RECEIPT_RECORD_OPERATION_KIND, ORDER_REVISION_DECISION_OPERATION_KIND, + ORDER_DECISION_OPERATION_KIND, ORDER_REVISION_DECISION_OPERATION_KIND, ORDER_REVISION_PROPOSAL_OPERATION_KIND, ORDER_SUBMIT_OPERATION_KIND, }; use radroots_sql_core::SqliteExecutor; use radroots_trade::listing::parse_public_listing_address; use radroots_trade::order::{ - RadrootsOrderCancellationRecord, RadrootsOrderDecisionRecord, RadrootsOrderFulfillmentRecord, - RadrootsOrderPaymentEventRecord, RadrootsOrderPaymentState, RadrootsOrderReceiptRecord, - RadrootsOrderReductionInputs, RadrootsOrderRequestRecord, RadrootsOrderRevisionDecisionRecord, - RadrootsOrderRevisionProposalRecord, RadrootsOrderSettlementRecord, RadrootsOrderStatus, - reduce_order_events, + RadrootsOrderCancellationRecord, RadrootsOrderDecisionRecord, RadrootsOrderReductionInputs, + RadrootsOrderRequestRecord, RadrootsOrderRevisionDecisionRecord, + RadrootsOrderRevisionProposalRecord, RadrootsOrderStatus, reduce_order_events, }; use serde_json::json; use thiserror::Error; @@ -181,10 +170,6 @@ const APP_DIRECT_RELAY_INGEST_KINDS: &[u16] = &[ KIND_ORDER_REVISION_PROPOSAL as u16, KIND_ORDER_REVISION_DECISION as u16, KIND_ORDER_CANCELLATION as u16, - KIND_ORDER_FULFILLMENT_UPDATE as u16, - KIND_ORDER_RECEIPT as u16, - KIND_ORDER_PAYMENT_RECORD as u16, - KIND_ORDER_SETTLEMENT_DECISION as u16, ]; #[derive(Debug, Default)] @@ -256,24 +241,15 @@ struct ResolvedAppOrderRevisionDecisionEvidence { } #[derive(Clone, Debug, Eq, PartialEq)] -struct ResolvedAppOrderFulfillmentEvidence { - event_id: String, - status: RadrootsOrderFulfillmentState, -} - -#[derive(Clone, Debug, Eq, PartialEq)] struct ResolvedAppOrderLifecycleEvidence { evidence_events: Vec<SdkRadrootsNostrEvent>, status: RadrootsOrderStatus, - payment_state: RadrootsOrderPaymentState, agreement_event_id: Option<String>, last_event_id: Option<String>, decision: Option<ResolvedAppOrderDecisionEvidence>, revision_proposals: Vec<ResolvedAppOrderRevisionProposalEvidence>, revision_decisions: Vec<ResolvedAppOrderRevisionDecisionEvidence>, - latest_fulfillment: Option<ResolvedAppOrderFulfillmentEvidence>, cancellation_event_id: Option<String>, - receipt_event_id: Option<String>, } #[derive(Clone, Debug, Default, Eq, PartialEq)] @@ -282,11 +258,7 @@ struct AppActiveOrderEvidenceBuckets { decisions: Vec<RadrootsOrderDecisionRecord>, revision_proposals: Vec<RadrootsOrderRevisionProposalRecord>, revision_decisions: Vec<RadrootsOrderRevisionDecisionRecord>, - fulfillments: Vec<RadrootsOrderFulfillmentRecord>, cancellations: Vec<RadrootsOrderCancellationRecord>, - receipts: Vec<RadrootsOrderReceiptRecord>, - payments: Vec<RadrootsOrderPaymentEventRecord>, - settlements: Vec<RadrootsOrderSettlementRecord>, } #[derive(Debug, Default)] @@ -396,8 +368,6 @@ fn publish_payload_context(publish_payload: &AppPublishPayload) -> &AppPublishCo AppPublishPayload::OrderRevisionProposal(payload) => &payload.context, AppPublishPayload::OrderRevisionDecision(payload) => &payload.context, AppPublishPayload::OrderCancellation(payload) => &payload.context, - AppPublishPayload::OrderFulfillment(payload) => &payload.context, - AppPublishPayload::OrderReceipt(payload) => &payload.context, } } @@ -866,15 +836,6 @@ impl DesktopAppRuntime { ) } - pub fn publish_order_fulfillment_update( - &self, - order_id: OrderId, - action: OrderFulfillmentAction, - ) -> Result<bool, AppSqliteError> { - self.lock_state_mut() - .publish_seller_order_fulfillment(order_id, action.fulfillment_status()) - } - pub fn publish_order_revision_proposal( &self, order_id: OrderId, @@ -907,15 +868,6 @@ impl DesktopAppRuntime { .publish_buyer_order_revision_decline(order_id) } - pub fn publish_buyer_order_receipt( - &self, - order_id: OrderId, - outcome: AppOrderReceiptOutcome, - ) -> Result<bool, AppSqliteError> { - self.lock_state_mut() - .publish_buyer_order_receipt(order_id, outcome) - } - pub fn start_order_recovery( &self, order_id: OrderId, @@ -2755,142 +2707,6 @@ impl DesktopAppRuntimeState { Ok(true) } - fn prepare_seller_order_fulfillment( - &mut self, - order_id: OrderId, - status: RadrootsOrderFulfillmentState, - ) -> Result<AppOrderFulfillmentPublishPayload, AppSqliteError> { - let _ = self.import_shared_local_events()?; - let relay_urls = normalized_app_sync_relay_urls(&self.nostr_relay_urls).map_err(|_| { - AppSqliteError::InvalidProjection { - reason: "seller order fulfillment requires valid configured relays", - } - })?; - if relay_urls.is_empty() { - return Err(AppSqliteError::InvalidProjection { - reason: "seller order fulfillment requires configured relays", - }); - } - self.refresh_configured_relay_state_before_order_lifecycle()?; - let Some(sqlite_store) = self.sqlite_store.as_ref() else { - return Err(AppSqliteError::InvalidProjection { - reason: "seller order fulfillment requires local state", - }); - }; - let Some(farm_id) = self.selected_farm_id() else { - return Err(AppSqliteError::InvalidProjection { - reason: "seller order fulfillment requires a selected farm", - }); - }; - let Some(selected_account) = self - .state_store - .identity_projection() - .selected_account - .as_ref() - else { - return Err(AppSqliteError::InvalidProjection { - reason: "seller order fulfillment requires a selected seller account", - }); - }; - let account_id = selected_account.account.account_id.clone(); - let seller_pubkey = self.local_events_owner_pubkey(selected_account).ok_or( - AppSqliteError::InvalidProjection { - reason: "seller order fulfillment requires a selected seller public key", - }, - )?; - let request = self.resolve_seller_order_request_evidence(order_id)?; - if request.payload.seller_pubkey.trim() != seller_pubkey.as_str() { - return Err(AppSqliteError::InvalidProjection { - reason: "seller order fulfillment seller account does not match order seller", - }); - } - let lifecycle = self.resolve_order_lifecycle_evidence(&request)?; - let Some(decision) = lifecycle.decision.as_ref() else { - return Err(AppSqliteError::InvalidProjection { - reason: "seller order fulfillment requires accepted order decision evidence", - }); - }; - if !matches!( - decision.payload.decision, - RadrootsOrderDecisionOutcome::Accepted { .. } - ) { - return Err(AppSqliteError::InvalidProjection { - reason: "seller order fulfillment requires accepted order decision evidence", - }); - } - if lifecycle.cancellation_event_id.is_some() || lifecycle.receipt_event_id.is_some() { - return Err(AppSqliteError::InvalidProjection { - reason: "seller order fulfillment requires an active order", - }); - } - if lifecycle - .latest_fulfillment - .as_ref() - .is_some_and(|fulfillment| { - matches!( - fulfillment.status, - RadrootsOrderFulfillmentState::Delivered - | RadrootsOrderFulfillmentState::SellerCancelled - ) - }) - { - return Err(AppSqliteError::InvalidProjection { - reason: "seller order fulfillment is already terminal", - }); - } - if sqlite_store.load_order_detail(farm_id, order_id)?.is_none() { - return Err(AppSqliteError::InvalidProjection { - reason: "seller order fulfillment requires a visible seller order", - }); - }; - if !status.is_publishable_update() { - return Err(AppSqliteError::InvalidProjection { - reason: "seller order fulfillment status must be publishable", - }); - } - let prev_event_id = match lifecycle.latest_fulfillment.as_ref() { - Some(fulfillment) => fulfillment.event_id.clone(), - None => active_order_current_parent_event_id( - &lifecycle, - "seller order fulfillment requires current lifecycle parent evidence", - )?, - }; - let payload = AppOrderFulfillmentPublishPayload { - context: AppPublishContext::new(account_id, "seller_order_fulfillment"), - app_order_id: order_id, - farm_id, - trade_order_id: request.payload.order_id.to_string(), - request_event_id: request.request_event_id, - prev_event_id, - listing_addr: request.payload.listing_addr.to_string(), - buyer_pubkey: request.payload.buyer_pubkey.to_string(), - seller_pubkey: request.payload.seller_pubkey.to_string(), - status, - }; - AppPublishPayload::OrderFulfillment(payload.clone()) - .validate() - .map_err(|_| AppSqliteError::InvalidProjection { - reason: "seller order fulfillment publish payload is invalid", - })?; - Ok(payload) - } - - fn publish_seller_order_fulfillment( - &mut self, - order_id: OrderId, - status: RadrootsOrderFulfillmentState, - ) -> Result<bool, AppSqliteError> { - let payload = self.prepare_seller_order_fulfillment(order_id, status)?; - let source_record_id = order_fulfillment_sdk_source_record_id(&payload); - self.enqueue_order_fulfillment_payload_via_sdk( - &payload, - AppSdkMigrationReceiptSourceKind::LocalOutbox, - source_record_id.as_str(), - )?; - let _ = self.refresh_selected_account_sync()?; - Ok(true) - } - fn prepare_seller_order_revision_proposal( &mut self, order_id: OrderId, @@ -2965,17 +2781,9 @@ impl DesktopAppRuntimeState { reason: "seller order revision requires accepted order decision evidence", }); } - if active_order_payment_blocks_lifecycle_write(&lifecycle) { + if lifecycle.cancellation_event_id.is_some() { return Err(AppSqliteError::InvalidProjection { - reason: "seller order revision requires no recorded or settled payment", - }); - } - if lifecycle.cancellation_event_id.is_some() - || lifecycle.receipt_event_id.is_some() - || lifecycle.latest_fulfillment.is_some() - { - return Err(AppSqliteError::InvalidProjection { - reason: "seller order revision requires an unfulfilled active order", + reason: "seller order revision requires an active order", }); } let Some(order_detail) = sqlite_store.load_order_detail(farm_id, order_id)? else { @@ -3112,12 +2920,9 @@ impl DesktopAppRuntimeState { reason: "buyer order revision requires accepted order decision evidence", }); } - if lifecycle.cancellation_event_id.is_some() - || lifecycle.receipt_event_id.is_some() - || lifecycle.latest_fulfillment.is_some() - { + if lifecycle.cancellation_event_id.is_some() { return Err(AppSqliteError::InvalidProjection { - reason: "buyer order revision requires an unfulfilled active order", + reason: "buyer order revision requires an active order", }); } let Some(proposal) = active_order_pending_revision_proposal(&lifecycle) else { @@ -3226,12 +3031,9 @@ impl DesktopAppRuntimeState { reason: "buyer order cancellation requires a visible buyer order", }); }; - if !matches!( - detail.status, - BuyerOrderStatus::Placed | BuyerOrderStatus::Scheduled - ) { + if !matches!(detail.status, BuyerOrderStatus::Placed) { return Err(AppSqliteError::InvalidProjection { - reason: "buyer order cancellation requires an open order", + reason: "buyer order cancellation requires an open pre-agreement order", }); } let request = self.resolve_seller_order_request_evidence(order_id)?; @@ -3241,33 +3043,24 @@ impl DesktopAppRuntimeState { }); } let lifecycle = self.resolve_order_lifecycle_evidence(&request)?; - if lifecycle.cancellation_event_id.is_some() - || lifecycle.receipt_event_id.is_some() - || lifecycle.latest_fulfillment.is_some() - { + if lifecycle.cancellation_event_id.is_some() { return Err(AppSqliteError::InvalidProjection { - reason: "buyer order cancellation requires an unfulfilled order", - }); - } - if active_order_payment_blocks_lifecycle_write(&lifecycle) { - return Err(AppSqliteError::InvalidProjection { - reason: "buyer order cancellation requires no recorded or settled payment", + reason: "buyer order cancellation requires an open pre-agreement order", }); } let prev_event_id = match lifecycle.status { RadrootsOrderStatus::Requested => request.request_event_id.clone(), - RadrootsOrderStatus::Accepted => active_order_current_parent_event_id( - &lifecycle, - "buyer order cancellation requires order decision evidence", - )?, + RadrootsOrderStatus::Accepted => { + return Err(AppSqliteError::InvalidProjection { + reason: "buyer order cancellation requires an open pre-agreement order", + }); + } RadrootsOrderStatus::Missing | RadrootsOrderStatus::Declined | RadrootsOrderStatus::Cancelled - | RadrootsOrderStatus::Completed - | RadrootsOrderStatus::Disputed | RadrootsOrderStatus::Invalid => { return Err(AppSqliteError::InvalidProjection { - reason: "buyer order cancellation requires an open order", + reason: "buyer order cancellation requires an open pre-agreement order", }); } }; @@ -3306,124 +3099,6 @@ impl DesktopAppRuntimeState { Ok(true) } - fn prepare_buyer_order_receipt( - &mut self, - order_id: OrderId, - outcome: AppOrderReceiptOutcome, - ) -> Result<AppOrderReceiptPublishPayload, AppSqliteError> { - let _ = self.import_shared_local_events()?; - let relay_urls = normalized_app_sync_relay_urls(&self.nostr_relay_urls).map_err(|_| { - AppSqliteError::InvalidProjection { - reason: "buyer order receipt requires valid configured relays", - } - })?; - if relay_urls.is_empty() { - return Err(AppSqliteError::InvalidProjection { - reason: "buyer order receipt requires configured relays", - }); - } - self.refresh_configured_relay_state_before_order_lifecycle()?; - let buyer_context = self.state_store.identity_projection().buyer_context(); - let BuyerContext::Account(account_id) = &buyer_context else { - return Err(AppSqliteError::InvalidProjection { - reason: "buyer order receipt requires a selected buyer account", - }); - }; - let Some(selected_account) = self.selected_buyer_account(&buyer_context) else { - return Err(AppSqliteError::InvalidProjection { - reason: "buyer order receipt requires a selected buyer account", - }); - }; - let buyer_pubkey = self.local_events_owner_pubkey(selected_account).ok_or( - AppSqliteError::InvalidProjection { - reason: "buyer order receipt requires a selected buyer public key", - }, - )?; - let Some(sqlite_store) = self.sqlite_store.as_ref() else { - return Err(AppSqliteError::InvalidProjection { - reason: "buyer order receipt requires local state", - }); - }; - let buyer_order_scope = selected_buyer_order_scope(self.state_store.identity_projection()); - let Some(detail) = - sqlite_store.load_buyer_order_detail_for_scope(&buyer_order_scope, order_id)? - else { - return Err(AppSqliteError::InvalidProjection { - reason: "buyer order receipt requires a visible buyer order", - }); - }; - let request = self.resolve_seller_order_request_evidence(order_id)?; - if request.payload.buyer_pubkey.trim() != buyer_pubkey.as_str() { - return Err(AppSqliteError::InvalidProjection { - reason: "buyer order receipt buyer account does not match order buyer", - }); - } - let lifecycle = self.resolve_order_lifecycle_evidence(&request)?; - if lifecycle.cancellation_event_id.is_some() || lifecycle.receipt_event_id.is_some() { - return Err(AppSqliteError::InvalidProjection { - reason: "buyer order receipt requires an active ready order", - }); - } - let fulfillment = - lifecycle - .latest_fulfillment - .as_ref() - .ok_or(AppSqliteError::InvalidProjection { - reason: "buyer order receipt requires fulfillment evidence", - })?; - if !matches!( - fulfillment.status, - RadrootsOrderFulfillmentState::ReadyForPickup - | RadrootsOrderFulfillmentState::Delivered - ) { - return Err(AppSqliteError::InvalidProjection { - reason: "buyer order receipt requires ready fulfillment evidence", - }); - } - let received_at = u64::try_from(current_runtime_time_seconds()?).map_err(|_| { - AppSqliteError::InvalidProjection { - reason: "buyer order receipt timestamp must be non-negative", - } - })?; - let received = outcome.received(); - let payload = AppOrderReceiptPublishPayload { - context: AppPublishContext::new(account_id.clone(), "buyer_order_receipt"), - app_order_id: order_id, - farm_id: detail.farm_id, - trade_order_id: request.payload.order_id.to_string(), - request_event_id: request.request_event_id, - prev_event_id: fulfillment.event_id.clone(), - listing_addr: request.payload.listing_addr.to_string(), - buyer_pubkey: request.payload.buyer_pubkey.to_string(), - seller_pubkey: request.payload.seller_pubkey.to_string(), - received, - issue: outcome.issue_text(), - received_at, - }; - AppPublishPayload::OrderReceipt(payload.clone()) - .validate() - .map_err(|_| AppSqliteError::InvalidProjection { - reason: "buyer order receipt publish payload is invalid", - })?; - Ok(payload) - } - - fn publish_buyer_order_receipt( - &mut self, - order_id: OrderId, - outcome: AppOrderReceiptOutcome, - ) -> Result<bool, AppSqliteError> { - let payload = self.prepare_buyer_order_receipt(order_id, outcome)?; - let source_record_id = order_receipt_sdk_source_record_id(&payload); - self.enqueue_order_receipt_payload_via_sdk( - &payload, - AppSdkMigrationReceiptSourceKind::LocalOutbox, - source_record_id.as_str(), - )?; - let _ = self.refresh_selected_account_sync()?; - Ok(true) - } - fn start_order_recovery( &mut self, order_id: OrderId, @@ -5505,123 +5180,7 @@ impl DesktopAppRuntimeState { } } - fn enqueue_order_fulfillment_payload_via_sdk( - &self, - payload: &AppOrderFulfillmentPublishPayload, - source_kind: AppSdkMigrationReceiptSourceKind, - source_record_id: &str, - ) -> Result<(), AppSqliteError> { - let operation_kind = ORDER_FULFILLMENT_UPDATE_OPERATION_KIND; - let request_evidence = self.resolve_seller_order_request_evidence(payload.app_order_id)?; - let lifecycle = self.resolve_order_lifecycle_evidence(&request_evidence)?; - let actor_pubkey = self - .local_signing_identity_for_publish_payload(&AppPublishPayload::OrderFulfillment( - payload.clone(), - )) - .and_then(|identity| { - let actor_pubkey = identity.public_key_hex(); - let target_relays = normalized_app_sync_relay_urls(&self.nostr_relay_urls)?; - let request = AppSdkOrderFulfillmentUpdateRequest { - actor_account_id: payload.context.account_id.clone(), - actor_pubkey: actor_pubkey.clone(), - signer_keys: identity.into_keys(), - evidence_events: lifecycle.evidence_events, - root_event: order_lifecycle_sdk_event_ptr( - payload.request_event_id.as_str(), - target_relays.as_slice(), - "order fulfillment requires request event id", - )?, - previous_event: order_lifecycle_sdk_event_ptr( - payload.prev_event_id.as_str(), - target_relays.as_slice(), - "order fulfillment requires previous event id", - )?, - fulfillment: order_fulfillment_publish_payload_to_sdk_fulfillment(payload)?, - relay_url_policy: sdk_relay_url_policy_for_targets(target_relays.as_slice()), - target_relays, - idempotency_key: Some(sdk_idempotency_key(source_record_id)), - }; - self.enqueue_app_sdk_order_fulfillment_update(request) - .map(|receipt| (actor_pubkey, receipt)) - .map_err(sync_transport_error_from_sdk_runtime_error) - }); - match actor_pubkey { - Ok((actor_pubkey, receipt)) => self.record_app_sdk_migration_success( - source_kind, - source_record_id, - operation_kind, - actor_pubkey.as_str(), - &receipt, - ), - Err(error) => self.record_app_sdk_migration_failure( - source_kind, - source_record_id, - operation_kind, - None, - sync_transport_error_detail_json(&error), - ), - } - } - - fn enqueue_order_receipt_payload_via_sdk( - &self, - payload: &AppOrderReceiptPublishPayload, - source_kind: AppSdkMigrationReceiptSourceKind, - source_record_id: &str, - ) -> Result<(), AppSqliteError> { - let operation_kind = ORDER_RECEIPT_RECORD_OPERATION_KIND; - let request_evidence = self.resolve_seller_order_request_evidence(payload.app_order_id)?; - let lifecycle = self.resolve_order_lifecycle_evidence(&request_evidence)?; - let actor_pubkey = self - .local_signing_identity_for_publish_payload(&AppPublishPayload::OrderReceipt( - payload.clone(), - )) - .and_then(|identity| { - let actor_pubkey = identity.public_key_hex(); - let target_relays = normalized_app_sync_relay_urls(&self.nostr_relay_urls)?; - let request = AppSdkOrderReceiptRecordRequest { - actor_account_id: payload.context.account_id.clone(), - actor_pubkey: actor_pubkey.clone(), - signer_keys: identity.into_keys(), - evidence_events: lifecycle.evidence_events, - root_event: order_lifecycle_sdk_event_ptr( - payload.request_event_id.as_str(), - target_relays.as_slice(), - "order receipt requires request event id", - )?, - previous_event: order_lifecycle_sdk_event_ptr( - payload.prev_event_id.as_str(), - target_relays.as_slice(), - "order receipt requires previous event id", - )?, - receipt: order_receipt_publish_payload_to_sdk_receipt(payload)?, - relay_url_policy: sdk_relay_url_policy_for_targets(target_relays.as_slice()), - target_relays, - idempotency_key: Some(sdk_idempotency_key(source_record_id)), - }; - self.enqueue_app_sdk_order_receipt_record(request) - .map(|receipt| (actor_pubkey, receipt)) - .map_err(sync_transport_error_from_sdk_runtime_error) - }); - match actor_pubkey { - Ok((actor_pubkey, receipt)) => self.record_app_sdk_migration_success( - source_kind, - source_record_id, - operation_kind, - actor_pubkey.as_str(), - &receipt, - ), - Err(error) => self.record_app_sdk_migration_failure( - source_kind, - source_record_id, - operation_kind, - None, - sync_transport_error_detail_json(&error), - ), - } - } - - fn local_signing_identity_for_publish_payload( + fn local_signing_identity_for_publish_payload( &self, payload: &AppPublishPayload, ) -> Result<RadrootsIdentity, AppSyncTransportError> { @@ -5680,20 +5239,6 @@ impl DesktopAppRuntimeState { self.with_app_sdk_runtime(|runtime| runtime.enqueue_order_cancellation(request)) } - fn enqueue_app_sdk_order_fulfillment_update( - &self, - request: AppSdkOrderFulfillmentUpdateRequest, - ) -> Result<AppSdkWorkflowReceipt, AppSdkRuntimeError> { - self.with_app_sdk_runtime(|runtime| runtime.enqueue_order_fulfillment_update(request)) - } - - fn enqueue_app_sdk_order_receipt_record( - &self, - request: AppSdkOrderReceiptRecordRequest, - ) -> Result<AppSdkWorkflowReceipt, AppSdkRuntimeError> { - self.with_app_sdk_runtime(|runtime| runtime.enqueue_order_receipt_record(request)) - } - fn with_app_sdk_runtime<T>( &self, command: impl FnOnce(&AppSdkRuntime) -> Result<T, AppSdkRuntimeError>, @@ -6290,73 +5835,6 @@ impl DesktopAppRuntimeState { payload: envelope.payload, }); } - KIND_ORDER_FULFILLMENT_UPDATE => { - let Ok(envelope) = - radroots_sdk::protocol::order::parse_fulfillment_update(&event) - else { - return Err(AppSqliteError::InvalidProjection { - reason: "order lifecycle evidence is invalid", - }); - }; - let context = active_order_event_record_context(&event, envelope.message_type)?; - buckets.fulfillments.push(RadrootsOrderFulfillmentRecord { - event_id, - author_pubkey, - counterparty_pubkey: context.0, - root_event_id: context.1, - prev_event_id: context.2, - payload: envelope.payload, - }); - } - KIND_ORDER_RECEIPT => { - let Ok(envelope) = radroots_sdk::protocol::order::parse_buyer_receipt(&event) - else { - return Err(AppSqliteError::InvalidProjection { - reason: "order lifecycle evidence is invalid", - }); - }; - let context = active_order_event_record_context(&event, envelope.message_type)?; - buckets.receipts.push(RadrootsOrderReceiptRecord { - event_id, - author_pubkey, - counterparty_pubkey: context.0, - root_event_id: context.1, - prev_event_id: context.2, - payload: envelope.payload, - }); - } - KIND_ORDER_PAYMENT_RECORD => { - let envelope = order_payment_record_from_event(&event).map_err(|_| { - AppSqliteError::InvalidProjection { - reason: "order lifecycle evidence is invalid", - } - })?; - let context = active_order_event_record_context(&event, envelope.message_type)?; - buckets.payments.push(RadrootsOrderPaymentEventRecord { - event_id, - author_pubkey, - counterparty_pubkey: context.0, - root_event_id: context.1, - prev_event_id: context.2, - payload: envelope.payload, - }); - } - KIND_ORDER_SETTLEMENT_DECISION => { - let envelope = order_settlement_decision_from_event(&event).map_err(|_| { - AppSqliteError::InvalidProjection { - reason: "order lifecycle evidence is invalid", - } - })?; - let context = active_order_event_record_context(&event, envelope.message_type)?; - buckets.settlements.push(RadrootsOrderSettlementRecord { - event_id, - author_pubkey, - counterparty_pubkey: context.0, - root_event_id: context.1, - prev_event_id: context.2, - payload: envelope.payload, - }); - } _ => {} } } @@ -6368,11 +5846,7 @@ impl DesktopAppRuntimeState { decisions: buckets.decisions.clone(), revision_proposals: buckets.revision_proposals.clone(), revision_decisions: buckets.revision_decisions.clone(), - fulfillments: buckets.fulfillments.clone(), cancellations: buckets.cancellations.clone(), - receipts: buckets.receipts.clone(), - payments: buckets.payments.clone(), - settlements: buckets.settlements.clone(), }, ); if !projection.issues.is_empty() || projection.status == RadrootsOrderStatus::Invalid { @@ -6403,28 +5877,9 @@ impl DesktopAppRuntimeState { }) }) .transpose()?; - let latest_fulfillment = projection - .fulfillment_event_id - .as_ref() - .map(|event_id| { - buckets - .fulfillments - .iter() - .find(|fulfillment| fulfillment.event_id == *event_id) - .map(|fulfillment| ResolvedAppOrderFulfillmentEvidence { - event_id: fulfillment.event_id.to_string(), - status: fulfillment.payload.status, - }) - .ok_or(AppSqliteError::InvalidProjection { - reason: "order lifecycle evidence is invalid", - }) - }) - .transpose()?; - Ok(ResolvedAppOrderLifecycleEvidence { evidence_events, status: projection.status, - payment_state: projection.payment.state, agreement_event_id: projection .agreement_event_id .map(|event_id| event_id.to_string()), @@ -6448,13 +5903,9 @@ impl DesktopAppRuntimeState { payload: decision.payload, }) .collect(), - latest_fulfillment, cancellation_event_id: projection .cancellation_event_id .map(|event_id| event_id.to_string()), - receipt_event_id: projection - .receipt_event_id - .map(|event_id| event_id.to_string()), }) } @@ -6468,10 +5919,6 @@ impl DesktopAppRuntimeState { KIND_ORDER_REVISION_PROPOSAL, KIND_ORDER_REVISION_DECISION, KIND_ORDER_CANCELLATION, - KIND_ORDER_FULFILLMENT_UPDATE, - KIND_ORDER_RECEIPT, - KIND_ORDER_PAYMENT_RECORD, - KIND_ORDER_SETTLEMENT_DECISION, ]; if let Some(sqlite_store) = self.sqlite_store.as_ref() { @@ -8083,18 +7530,6 @@ fn order_cancellation_sdk_source_record_id(payload: &AppOrderCancellationPublish format!("app:order_cancellation:{}", payload.app_order_id) } -fn order_fulfillment_sdk_source_record_id(payload: &AppOrderFulfillmentPublishPayload) -> String { - format!( - "app:order_fulfillment:{}:{}", - payload.app_order_id, - order_fulfillment_status_storage_key(payload.status) - ) -} - -fn order_receipt_sdk_source_record_id(payload: &AppOrderReceiptPublishPayload) -> String { - format!("app:order_receipt:{}", payload.app_order_id) -} - fn sdk_relay_url_policy_for_targets(target_relays: &[String]) -> AppSdkRelayUrlPolicy { if target_relays .iter() @@ -8471,17 +7906,6 @@ fn order_lifecycle_sdk_event_ptr( }) } -fn order_fulfillment_status_storage_key(status: RadrootsOrderFulfillmentState) -> &'static str { - match status { - RadrootsOrderFulfillmentState::AcceptedNotFulfilled => "accepted_not_fulfilled", - RadrootsOrderFulfillmentState::Preparing => "preparing", - RadrootsOrderFulfillmentState::ReadyForPickup => "ready_for_pickup", - RadrootsOrderFulfillmentState::OutForDelivery => "out_for_delivery", - RadrootsOrderFulfillmentState::Delivered => "delivered", - RadrootsOrderFulfillmentState::SellerCancelled => "seller_cancelled", - } -} - #[cfg(test)] fn selected_listing_relay( listing_relays: &[String], @@ -8791,10 +8215,6 @@ fn buyer_order_request_local_work_payload( "order_updated_at": order.updated_at, "created_at_ms": timestamp, }, - "payment_display": { - "state": "not_recorded", - "allows_payment_action": false, - }, "document": { "version": 1, "kind": BUYER_ORDER_REQUEST_DOCUMENT_KIND, @@ -9376,13 +8796,11 @@ fn load_selected_account_reminder_context_with_options( .items .iter() .filter(|item| { - !matches!( - item.kind, - ReminderKind::MissedPickupRecovery | ReminderKind::RefundRecovery - ) && matches!( - item.urgency, - ReminderUrgency::DueSoon | ReminderUrgency::Overdue | ReminderUrgency::Blocking - ) + !matches!(item.kind, ReminderKind::MissedPickupRecovery) + && matches!( + item.urgency, + ReminderUrgency::DueSoon | ReminderUrgency::Overdue | ReminderUrgency::Blocking + ) }) .count() as u32; let recovery_actions_open = recovery_queue @@ -9538,7 +8956,6 @@ fn derive_selected_account_reminder_schedule( { let kind = match record.kind { RecoveryKind::MissedPickup => ReminderKind::MissedPickupRecovery, - RecoveryKind::RefundFollowUp => ReminderKind::RefundRecovery, }; items.push(build_reminder_projection( farm_id, @@ -9852,7 +9269,6 @@ fn ordered_order_recoveries_for_detail( fn order_recovery_kind_rank(kind: RecoveryKind) -> u8 { match kind { RecoveryKind::MissedPickup => 0, - RecoveryKind::RefundFollowUp => 1, } } @@ -9865,13 +9281,6 @@ fn order_recovery_summary(kind: RecoveryKind, state: RecoveryState) -> &'static (RecoveryKind::MissedPickup, RecoveryState::Resolved) => { "Missed pickup follow-up is resolved" } - (RecoveryKind::RefundFollowUp, RecoveryState::Open) => "Payment status follow-up is open", - (RecoveryKind::RefundFollowUp, RecoveryState::InReview) => { - "Payment status follow-up is in review" - } - (RecoveryKind::RefundFollowUp, RecoveryState::Resolved) => { - "Payment status follow-up is resolved" - } } } @@ -9886,15 +9295,6 @@ fn order_recovery_note(kind: RecoveryKind, state: RecoveryState) -> &'static str (RecoveryKind::MissedPickup, RecoveryState::Resolved) => { "The seller and buyer have agreed on the next step." } - (RecoveryKind::RefundFollowUp, RecoveryState::Open) => { - "Review the order record and agree on the next step." - } - (RecoveryKind::RefundFollowUp, RecoveryState::InReview) => { - "Confirm the outcome with the order parties." - } - (RecoveryKind::RefundFollowUp, RecoveryState::Resolved) => { - "The payment status follow-up is resolved." - } } } @@ -10352,25 +9752,6 @@ fn active_order_event_record_context( Ok((context.counterparty_pubkey, root_event_id, prev_event_id)) } -fn active_order_current_parent_event_id( - lifecycle: &ResolvedAppOrderLifecycleEvidence, - reason: &'static str, -) -> Result<String, AppSqliteError> { - lifecycle - .last_event_id - .clone() - .ok_or(AppSqliteError::InvalidProjection { reason }) -} - -fn active_order_payment_blocks_lifecycle_write( - lifecycle: &ResolvedAppOrderLifecycleEvidence, -) -> bool { - !matches!( - lifecycle.payment_state, - RadrootsOrderPaymentState::NotRecorded - ) -} - fn active_order_revision_parent_event_id( lifecycle: &ResolvedAppOrderLifecycleEvidence, ) -> Option<String> { @@ -10584,18 +9965,6 @@ fn order_revision_decision_publish_payload_to_sdk_revision_decision( }) } -fn order_fulfillment_publish_payload_to_sdk_fulfillment( - payload: &AppOrderFulfillmentPublishPayload, -) -> Result<RadrootsOrderFulfillmentUpdate, AppSyncTransportError> { - Ok(RadrootsOrderFulfillmentUpdate { - order_id: publish_order_id(payload.trade_order_id.as_str())?, - listing_addr: publish_listing_addr(payload.listing_addr.as_str())?, - buyer_pubkey: publish_pubkey(payload.buyer_pubkey.as_str())?, - seller_pubkey: publish_pubkey(payload.seller_pubkey.as_str())?, - status: payload.status, - }) -} - fn order_cancellation_publish_payload_to_sdk_cancellation( payload: &AppOrderCancellationPublishPayload, ) -> Result<RadrootsOrderCancellation, AppSyncTransportError> { @@ -10608,20 +9977,6 @@ fn order_cancellation_publish_payload_to_sdk_cancellation( }) } -fn order_receipt_publish_payload_to_sdk_receipt( - payload: &AppOrderReceiptPublishPayload, -) -> Result<RadrootsOrderReceipt, AppSyncTransportError> { - Ok(RadrootsOrderReceipt { - order_id: publish_order_id(payload.trade_order_id.as_str())?, - listing_addr: publish_listing_addr(payload.listing_addr.as_str())?, - buyer_pubkey: publish_pubkey(payload.buyer_pubkey.as_str())?, - seller_pubkey: publish_pubkey(payload.seller_pubkey.as_str())?, - received: payload.received, - issue: payload.issue.clone(), - received_at: payload.received_at, - }) -} - fn pending_sync_upsert(aggregate: SyncAggregateRef, payload_json: String) -> PendingSyncOperation { let created_at = current_utc_timestamp(); @@ -10706,8 +10061,7 @@ mod tests { use radroots_app_sync::{ AppFarmProfilePublishPayload, AppListingPublishPayload, AppOrderCancellationPublishPayload, AppOrderDecisionInventoryCommitment, AppOrderDecisionPayload, - AppOrderDecisionPublishPayload, AppOrderFulfillmentPublishPayload, AppOrderReceiptOutcome, - AppOrderReceiptPublishPayload, AppOrderRequestItemPayload, AppOrderRequestPublishPayload, + AppOrderDecisionPublishPayload, AppOrderRequestItemPayload, AppOrderRequestPublishPayload, AppOrderRevisionDecisionPublishPayload, AppOrderRevisionProposalPublishPayload, AppPublishContext, AppPublishPayload, AppPublishedOperationReceipt, AppRelayIngestScopeFreshness, AppRelayIngestScopeStatus, AppSyncRequest, AppSyncResult, @@ -10723,10 +10077,10 @@ mod tests { BuyerOrderStatus, FarmId, FarmOperatingRulesRecord, FarmOrderMethod, FarmProfileRecord, FarmReadiness, FarmReadinessBlocker, FarmRulesProjection, FarmSetupDraft, FarmSetupProjection, FarmSummary, FarmerActivationProjection, FarmerSection, - FulfillmentWindowId, FulfillmentWindowRecord, LoggedOutStartupProjection, - OrderFulfillmentAction, OrderId, OrderStatus, OrdersFilter, PackDayBatchPrintArtifact, - PackDayBatchPrintFailureKind, PackDayBatchPrintStatus, PackDayExportInstanceId, - PackDayExportStatus, PackDayHostHandoffKind, PackDayHostHandoffStatus, PackDayPackListRow, + FulfillmentWindowId, FulfillmentWindowRecord, LoggedOutStartupProjection, OrderId, + OrderStatus, OrdersFilter, PackDayBatchPrintArtifact, PackDayBatchPrintFailureKind, + PackDayBatchPrintStatus, PackDayExportInstanceId, PackDayExportStatus, + PackDayHostHandoffKind, PackDayHostHandoffStatus, PackDayPackListRow, PackDayPrintFailureKind, PackDayPrintKind, PackDayPrintStatus, PackDayProductTotalRow, PackDayProjection, PackDayRosterRow, PersonalSection, PickupLocationId, PickupLocationRecord, ProductEditorDraft, ProductId, ProductPublishBlocker, ProductStatus, @@ -10739,11 +10093,8 @@ mod tests { RadrootsCoreCurrency, RadrootsCoreDecimal, RadrootsCoreMoney, RadrootsCoreUnit, }; use radroots_events::ids::{ - RadrootsEconomicsDigest, RadrootsEventId, RadrootsInventoryBinId, RadrootsListingAddress, - RadrootsOrderId, RadrootsOrderQuoteId, RadrootsOrderRevisionId, RadrootsPublicKey, - }; - use radroots_events_codec::order::{ - order_payment_record_event_build, order_settlement_decision_event_build, + RadrootsEventId, RadrootsInventoryBinId, RadrootsListingAddress, RadrootsOrderId, + RadrootsOrderQuoteId, RadrootsOrderRevisionId, RadrootsPublicKey, }; use radroots_events_codec::wire::WireEventParts; use radroots_identity::{RadrootsIdentity, RadrootsIdentityId}; @@ -10766,21 +10117,16 @@ mod tests { }; use radroots_sdk::protocol::order::{ RadrootsOrderCancellation, RadrootsOrderDecision, RadrootsOrderDecisionOutcome, - RadrootsOrderEconomicItem, RadrootsOrderEconomics, RadrootsOrderFulfillmentState, - RadrootsOrderFulfillmentUpdate, RadrootsOrderInventoryCommitment, RadrootsOrderItem, - RadrootsOrderPaymentMethod, RadrootsOrderPaymentRecord, RadrootsOrderPricingBasis, - RadrootsOrderReceipt, RadrootsOrderRequest, RadrootsOrderRevisionDecision, - RadrootsOrderRevisionOutcome, RadrootsOrderRevisionProposal, - RadrootsOrderSettlementDecision, RadrootsOrderSettlementOutcome, + RadrootsOrderEconomicItem, RadrootsOrderEconomics, RadrootsOrderInventoryCommitment, + RadrootsOrderItem, RadrootsOrderPricingBasis, RadrootsOrderRequest, + RadrootsOrderRevisionDecision, RadrootsOrderRevisionOutcome, RadrootsOrderRevisionProposal, }; use radroots_sdk::{ LISTING_PUBLISH_OPERATION_KIND, ORDER_CANCELLATION_OPERATION_KIND, - ORDER_DECISION_OPERATION_KIND, ORDER_FULFILLMENT_UPDATE_OPERATION_KIND, - ORDER_RECEIPT_RECORD_OPERATION_KIND, ORDER_REVISION_DECISION_OPERATION_KIND, + ORDER_DECISION_OPERATION_KIND, ORDER_REVISION_DECISION_OPERATION_KIND, ORDER_SUBMIT_OPERATION_KIND, }; use radroots_sql_core::{SqlExecutor, SqliteExecutor}; - use radroots_trade::order::radroots_order_economics_digest; use serde_json::json; use tokio::net::TcpListener; use tokio::sync::oneshot; @@ -10805,8 +10151,8 @@ mod tests { DesktopAppSdkDiagnosticsState, DesktopAppSyncStatusSummary, DesktopRemoteSignerPaths, SYNC_TRANSPORT_UNAVAILABLE_MESSAGE, TokioRuntimeBuilder, default_sync_transport, direct_relay_event_source_runtime, farm_sync_payload, is_hex_64, - order_decision_publish_payload_to_sdk_decision, order_fulfillment_status_storage_key, - pending_sync_upsert, signed_event_from_local_record, + order_decision_publish_payload_to_sdk_decision, pending_sync_upsert, + signed_event_from_local_record, }; use crate::pack_day_host_handoff::PackDayHostHandoffError; use crate::pack_day_print::{ @@ -10956,13 +10302,6 @@ mod tests { fn event_count(&self) -> usize { self.events.lock().expect("relay events lock").len() } - - fn push_event(&self, event: &radroots_nostr::prelude::RadrootsNostrEvent) { - self.events - .lock() - .expect("relay events lock") - .push(serde_json::to_value(event).expect("relay event json")); - } } fn relay_event_matches_filters( @@ -11209,10 +10548,6 @@ mod tests { RadrootsListingAddress::parse(value).expect("listing address") } - fn test_economics_digest(value: impl AsRef<str>) -> RadrootsEconomicsDigest { - RadrootsEconomicsDigest::parse(value).expect("economics digest") - } - fn install_recorded_sync_transport( runtime: &DesktopAppRuntime, transport: RecordedAppSyncTransport, @@ -11564,48 +10899,13 @@ mod tests { seller_pubkey: common.6.clone(), reason: "buyer cancelled order".to_owned(), }); - let fulfillment = AppPublishPayload::OrderFulfillment(AppOrderFulfillmentPublishPayload { - context: AppPublishContext::new( - seller_account_id.to_string(), - "seller_order_fulfillment", - ), - app_order_id: common.0, - farm_id: common.1, - trade_order_id: common.2.clone(), - request_event_id: common.3.clone(), - prev_event_id: test_event_id_seed("order-decision-event-1"), - listing_addr: common.4.clone(), - buyer_pubkey: common.5.clone(), - seller_pubkey: common.6.clone(), - status: RadrootsOrderFulfillmentState::ReadyForPickup, - }); - let receipt = AppPublishPayload::OrderReceipt(AppOrderReceiptPublishPayload { - context: AppPublishContext::new(buyer_account_id.to_string(), "buyer_order_receipt"), - app_order_id: common.0, - farm_id: common.1, - trade_order_id: common.2.clone(), - request_event_id: common.3.clone(), - prev_event_id: test_event_id_seed("fulfillment-event-1"), - listing_addr: common.4.clone(), - buyer_pubkey: common.5.clone(), - seller_pubkey: common.6.clone(), - received: true, - issue: None, - received_at: 1_785_000_000, - }); - let operations = [ - revision_proposal, - revision_decision, - cancellation, - fulfillment, - receipt, - ] - .into_iter() - .map(|payload| { - PendingSyncOperation::from_publish_payload(payload, "2026-05-24T12:00:00Z") - .expect("typed lifecycle publish work should serialize") - }) - .collect::<Vec<_>>(); + let operations = [revision_proposal, revision_decision, cancellation] + .into_iter() + .map(|payload| { + PendingSyncOperation::from_publish_payload(payload, "2026-05-24T12:00:00Z") + .expect("typed lifecycle publish work should serialize") + }) + .collect::<Vec<_>>(); let mut transport = ConfiguredRelayAppSyncTransport::with_relay_urls(manager, vec![relay.url().to_owned()]); @@ -16507,164 +15807,16 @@ mod tests { } #[test] - fn runtime_publishes_all_seller_fulfillment_states_and_projects_signed_evidence() { - for (label, action, expected_status) in [ - ( - "preparing", - OrderFulfillmentAction::Preparing, - RadrootsOrderFulfillmentState::Preparing, - ), - ( - "ready_for_pickup", - OrderFulfillmentAction::ReadyForPickup, - RadrootsOrderFulfillmentState::ReadyForPickup, - ), - ( - "out_for_delivery", - OrderFulfillmentAction::OutForDelivery, - RadrootsOrderFulfillmentState::OutForDelivery, - ), - ( - "delivered", - OrderFulfillmentAction::Delivered, - RadrootsOrderFulfillmentState::Delivered, - ), - ( - "seller_cancelled", - OrderFulfillmentAction::SellerCancelled, - RadrootsOrderFulfillmentState::SellerCancelled, - ), - ] { - let relay = ThreadedAckRelay::spawn(); - let runtime_label = format!("seller_order_fulfillment_publish_{label}"); - let (runtime, paths, order_id, product_id, seller_pubkey, buyer_pubkey) = - seller_order_decision_runtime(runtime_label.as_str(), 6, 2); - install_direct_relay_sync_transport(&runtime, &relay); - publish_prior_relay_seller_order_accept( - &runtime, - &relay, - order_id, - product_id, - seller_pubkey.as_str(), - buyer_pubkey.as_str(), - ); - - assert!( - runtime - .publish_order_fulfillment_update(order_id, action) - .expect("seller fulfillment update should publish") - ); - - assert_eq!(persisted_order_status(&runtime, order_id), "scheduled"); - assert_eq!(relay.event_count(), 1); - let fulfillment_events = - shared_order_events_by_kind(&paths, 3433, seller_pubkey.as_str()); - assert!(fulfillment_events.is_empty()); - assert_order_fulfillment_sdk_migration_receipt( - &runtime, - order_id, - expected_status, - AppSdkMigrationState::Enqueued, - ); - - cleanup_bootstrapped_runtime_paths(&paths); - } - } - - #[test] - fn runtime_publishes_seller_order_fulfillment_ready_from_revision_parent() { - for (label, revision_decision) in [ - ("accepted", RadrootsOrderRevisionOutcome::Accepted), - ( - "declined", - RadrootsOrderRevisionOutcome::Declined { - reason: "keep original order".to_owned(), - }, - ), - ] { - let relay = ThreadedAckRelay::spawn(); - let runtime_label = format!("seller_order_fulfillment_revision_parent_{label}"); - let (runtime, paths, order_id, product_id, seller_pubkey, buyer_pubkey) = - seller_order_decision_runtime(runtime_label.as_str(), 6, 2); - install_direct_relay_sync_transport(&runtime, &relay); - let listing_key = super::d_tag_from_uuid(product_id.as_uuid()); - let listing_addr = format!("30402:{seller_pubkey}:{listing_key}"); - let request_event_id = shared_order_request_event_id(&paths, "seller-order-decision-1"); - let request_event_id = request_event_id.as_str(); - let decision_event_id = append_signed_order_decision_record( - &paths, - "seller-order-decision-1", - request_event_id, - listing_addr.as_str(), - buyer_pubkey.as_str(), - seller_pubkey.as_str(), - 2, - ); - let proposal_key = format!("seller-order-ready-revision-{label}-proposal"); - let proposal_event_id = append_signed_order_revision_proposal_record_with_prev( - &paths, - "seller-order-decision-1", - proposal_key.as_str(), - request_event_id, - decision_event_id.as_str(), - listing_addr.as_str(), - buyer_pubkey.as_str(), - seller_pubkey.as_str(), - ); - let revision_id = format!("revision-{proposal_key}"); - let _revision_decision_event_id = - append_signed_order_revision_decision_record_with_prev( - &paths, - "seller-order-decision-1", - format!("seller-order-ready-revision-{label}-decision").as_str(), - request_event_id, - proposal_event_id.as_str(), - revision_id.as_str(), - listing_addr.as_str(), - buyer_pubkey.as_str(), - seller_pubkey.as_str(), - revision_decision, - ); - runtime - .refresh_shared_local_events() - .expect("seller revision fixture should import"); - set_persisted_order_status(&runtime, order_id, "scheduled"); - - assert!( - runtime - .publish_order_fulfillment_update( - order_id, - OrderFulfillmentAction::ReadyForPickup, - ) - .expect("seller ready fulfillment should publish from revision parent") - ); - - assert_eq!(relay.event_count(), 0); - let fulfillment_events = - shared_order_events_by_kind(&paths, 3433, seller_pubkey.as_str()); - assert!(fulfillment_events.is_empty()); - assert_order_fulfillment_sdk_migration_receipt( - &runtime, - order_id, - RadrootsOrderFulfillmentState::ReadyForPickup, - AppSdkMigrationState::Enqueued, - ); - - cleanup_bootstrapped_runtime_paths(&paths); - } - } - - #[test] - fn runtime_publishes_seller_order_fulfillment_delivered_when_coarse_status_lags() { + fn runtime_rejects_seller_order_revision_with_reducer_invalid_parent_evidence() { let relay = ThreadedAckRelay::spawn(); let (runtime, paths, order_id, product_id, seller_pubkey, buyer_pubkey) = - seller_order_decision_runtime("seller_order_fulfillment_delivery_status_lag", 6, 2); + seller_order_decision_runtime("seller_order_revision_invalid_parent", 6, 2); install_direct_relay_sync_transport(&runtime, &relay); let listing_key = super::d_tag_from_uuid(product_id.as_uuid()); let listing_addr = format!("30402:{seller_pubkey}:{listing_key}"); let request_event_id = shared_order_request_event_id(&paths, "seller-order-decision-1"); let request_event_id = request_event_id.as_str(); - let decision_event_id = append_signed_order_decision_record( + append_signed_order_decision_record( &paths, "seller-order-decision-1", request_event_id, @@ -16673,261 +15825,12 @@ mod tests { seller_pubkey.as_str(), 2, ); - let _ready_event_id = append_signed_order_fulfillment_record_with_status( + append_signed_order_revision_proposal_record_with_prev( &paths, "seller-order-decision-1", + "seller-order-decision-1-stale-revision", + request_event_id, request_event_id, - decision_event_id.as_str(), - listing_addr.as_str(), - buyer_pubkey.as_str(), - seller_pubkey.as_str(), - RadrootsOrderFulfillmentState::ReadyForPickup, - ); - runtime - .refresh_shared_local_events() - .expect("seller ready fulfillment should import"); - assert_eq!(persisted_order_status(&runtime, order_id), "packed"); - set_persisted_order_status(&runtime, order_id, "scheduled"); - - assert!( - runtime - .publish_order_fulfillment_update(order_id, OrderFulfillmentAction::Delivered) - .expect("seller delivered fulfillment should publish from workflow evidence") - ); - - assert_eq!(persisted_order_status(&runtime, order_id), "scheduled"); - assert_eq!(relay.event_count(), 0); - let fulfillment_events = shared_order_events_by_kind(&paths, 3433, seller_pubkey.as_str()); - assert_eq!(fulfillment_events.len(), 1); - assert_order_fulfillment_sdk_migration_receipt( - &runtime, - order_id, - RadrootsOrderFulfillmentState::Delivered, - AppSdkMigrationState::Enqueued, - ); - - cleanup_bootstrapped_runtime_paths(&paths); - } - - #[test] - fn runtime_publishes_seller_order_fulfillment_delivered_without_ready_evidence() { - for (label, latest_fulfillment) in [ - ("seller_order_fulfillment_delivery_missing_ready", None), - ( - "seller_order_fulfillment_delivery_preparing", - Some(RadrootsOrderFulfillmentState::Preparing), - ), - ] { - let relay = ThreadedAckRelay::spawn(); - let (runtime, paths, order_id, product_id, seller_pubkey, buyer_pubkey) = - seller_order_decision_runtime(label, 6, 2); - install_direct_relay_sync_transport(&runtime, &relay); - let listing_key = super::d_tag_from_uuid(product_id.as_uuid()); - let listing_addr = format!("30402:{seller_pubkey}:{listing_key}"); - let request_event_id = shared_order_request_event_id(&paths, "seller-order-decision-1"); - let request_event_id = request_event_id.as_str(); - let decision_event_id = append_signed_order_decision_record( - &paths, - "seller-order-decision-1", - request_event_id, - listing_addr.as_str(), - buyer_pubkey.as_str(), - seller_pubkey.as_str(), - 2, - ); - if let Some(status) = latest_fulfillment { - append_signed_order_fulfillment_record_with_status( - &paths, - "seller-order-decision-1", - request_event_id, - decision_event_id.as_str(), - listing_addr.as_str(), - buyer_pubkey.as_str(), - seller_pubkey.as_str(), - status, - ); - } - runtime - .refresh_shared_local_events() - .expect("seller fulfillment fixture should import"); - - assert!( - runtime - .publish_order_fulfillment_update(order_id, OrderFulfillmentAction::Delivered) - .expect("seller delivered fulfillment should publish") - ); - - assert_eq!(persisted_order_status(&runtime, order_id), "scheduled"); - assert_eq!(relay.event_count(), 0); - let fulfillment_events = - shared_order_events_by_kind(&paths, 3433, seller_pubkey.as_str()); - assert_eq!( - fulfillment_events.len(), - usize::from(latest_fulfillment.is_some()) - ); - assert_order_fulfillment_sdk_migration_receipt( - &runtime, - order_id, - RadrootsOrderFulfillmentState::Delivered, - AppSdkMigrationState::Enqueued, - ); - cleanup_bootstrapped_runtime_paths(&paths); - } - } - - #[test] - fn runtime_rejects_seller_order_fulfillment_delivered_with_reducer_invalid_ready_evidence() { - for label in [ - "seller_order_fulfillment_delivery_unchained_ready", - "seller_order_fulfillment_delivery_forked_ready", - ] { - let relay = ThreadedAckRelay::spawn(); - let (runtime, paths, order_id, product_id, seller_pubkey, buyer_pubkey) = - seller_order_decision_runtime(label, 6, 2); - install_direct_relay_sync_transport(&runtime, &relay); - let listing_key = super::d_tag_from_uuid(product_id.as_uuid()); - let listing_addr = format!("30402:{seller_pubkey}:{listing_key}"); - let request_event_id = shared_order_request_event_id(&paths, "seller-order-decision-1"); - let request_event_id = request_event_id.as_str(); - let decision_event_id = append_signed_order_decision_record( - &paths, - "seller-order-decision-1", - request_event_id, - listing_addr.as_str(), - buyer_pubkey.as_str(), - seller_pubkey.as_str(), - 2, - ); - if label.ends_with("unchained_ready") { - append_signed_order_fulfillment_record_with_status_and_key( - &paths, - "seller-order-decision-1", - "seller-order-decision-1-unchained-ready", - request_event_id, - request_event_id, - listing_addr.as_str(), - buyer_pubkey.as_str(), - seller_pubkey.as_str(), - RadrootsOrderFulfillmentState::ReadyForPickup, - ); - } else { - append_signed_order_fulfillment_record_with_status_and_key( - &paths, - "seller-order-decision-1", - "seller-order-decision-1-forked-preparing", - request_event_id, - decision_event_id.as_str(), - listing_addr.as_str(), - buyer_pubkey.as_str(), - seller_pubkey.as_str(), - RadrootsOrderFulfillmentState::Preparing, - ); - append_signed_order_fulfillment_record_with_status_and_key( - &paths, - "seller-order-decision-1", - "seller-order-decision-1-forked-ready", - request_event_id, - decision_event_id.as_str(), - listing_addr.as_str(), - buyer_pubkey.as_str(), - seller_pubkey.as_str(), - RadrootsOrderFulfillmentState::ReadyForPickup, - ); - } - - let error = runtime - .publish_order_fulfillment_update(order_id, OrderFulfillmentAction::Delivered) - .expect_err("seller delivered fulfillment should reject reducer-invalid evidence"); - - assert_order_lifecycle_evidence_invalid(error); - assert_eq!(relay.event_count(), 0); - cleanup_bootstrapped_runtime_paths(&paths); - } - } - - #[test] - fn runtime_rejects_seller_order_fulfillment_ready_with_invalid_terminal_evidence() { - for label in [ - "seller_order_fulfillment_invalid_cancellation", - "seller_order_fulfillment_invalid_receipt", - ] { - let relay = ThreadedAckRelay::spawn(); - let (runtime, paths, order_id, product_id, seller_pubkey, buyer_pubkey) = - seller_order_decision_runtime(label, 6, 2); - install_direct_relay_sync_transport(&runtime, &relay); - let listing_key = super::d_tag_from_uuid(product_id.as_uuid()); - let listing_addr = format!("30402:{seller_pubkey}:{listing_key}"); - let request_event_id = shared_order_request_event_id(&paths, "seller-order-decision-1"); - let request_event_id = request_event_id.as_str(); - append_signed_order_decision_record( - &paths, - "seller-order-decision-1", - request_event_id, - listing_addr.as_str(), - buyer_pubkey.as_str(), - seller_pubkey.as_str(), - 2, - ); - if label.ends_with("invalid_cancellation") { - append_signed_order_cancellation_record_with_prev( - &paths, - "seller-order-decision-1", - "seller-order-decision-1-invalid-cancellation", - request_event_id, - request_event_id, - listing_addr.as_str(), - buyer_pubkey.as_str(), - seller_pubkey.as_str(), - ); - } else { - append_signed_order_receipt_record_with_prev( - &paths, - "seller-order-decision-1", - "seller-order-decision-1-invalid-receipt", - request_event_id, - request_event_id, - listing_addr.as_str(), - buyer_pubkey.as_str(), - seller_pubkey.as_str(), - true, - ); - } - - let error = runtime - .publish_order_fulfillment_update(order_id, OrderFulfillmentAction::ReadyForPickup) - .expect_err("seller ready fulfillment should reject invalid terminal evidence"); - - assert_order_lifecycle_evidence_invalid(error); - assert_eq!(relay.event_count(), 0); - cleanup_bootstrapped_runtime_paths(&paths); - } - } - - #[test] - fn runtime_rejects_seller_order_revision_with_reducer_invalid_parent_evidence() { - let relay = ThreadedAckRelay::spawn(); - let (runtime, paths, order_id, product_id, seller_pubkey, buyer_pubkey) = - seller_order_decision_runtime("seller_order_revision_invalid_parent", 6, 2); - install_direct_relay_sync_transport(&runtime, &relay); - let listing_key = super::d_tag_from_uuid(product_id.as_uuid()); - let listing_addr = format!("30402:{seller_pubkey}:{listing_key}"); - let request_event_id = shared_order_request_event_id(&paths, "seller-order-decision-1"); - let request_event_id = request_event_id.as_str(); - append_signed_order_decision_record( - &paths, - "seller-order-decision-1", - request_event_id, - listing_addr.as_str(), - buyer_pubkey.as_str(), - seller_pubkey.as_str(), - 2, - ); - append_signed_order_revision_proposal_record_with_prev( - &paths, - "seller-order-decision-1", - "seller-order-decision-1-stale-revision", - request_event_id, - request_event_id, listing_addr.as_str(), buyer_pubkey.as_str(), seller_pubkey.as_str(), @@ -16948,200 +15851,6 @@ mod tests { } #[test] - fn runtime_rejects_seller_order_revision_after_recorded_or_settled_payment_evidence() { - for (label, settlement) in [ - ("recorded", None), - ("settled", Some(RadrootsOrderSettlementOutcome::Accepted)), - ] { - let relay = ThreadedAckRelay::spawn(); - let runtime_label = format!("seller_order_revision_payment_{label}"); - let (runtime, paths, order_id, product_id, seller_pubkey, buyer_pubkey) = - seller_order_decision_runtime(runtime_label.as_str(), 6, 2); - install_direct_relay_sync_transport(&runtime, &relay); - let listing_key = super::d_tag_from_uuid(product_id.as_uuid()); - let listing_addr = format!("30402:{seller_pubkey}:{listing_key}"); - let request_event_id = shared_order_request_event_id(&paths, "seller-order-decision-1"); - let request_event_id = request_event_id.as_str(); - let decision_event_id = append_signed_order_decision_record( - &paths, - "seller-order-decision-1", - request_event_id, - listing_addr.as_str(), - buyer_pubkey.as_str(), - seller_pubkey.as_str(), - 2, - ); - let payment_event_id = append_signed_payment_record( - &paths, - "seller-order-decision-1", - format!("seller-order-revision-payment-{label}").as_str(), - request_event_id, - decision_event_id.as_str(), - listing_addr.as_str(), - buyer_pubkey.as_str(), - seller_pubkey.as_str(), - 2, - ); - if let Some(decision) = settlement { - append_signed_settlement_decision_record( - &paths, - "seller-order-decision-1", - format!("seller-order-revision-k3436-{label}").as_str(), - request_event_id, - decision_event_id.as_str(), - payment_event_id.as_str(), - listing_addr.as_str(), - buyer_pubkey.as_str(), - seller_pubkey.as_str(), - 2, - decision, - ); - } - runtime - .refresh_shared_local_events() - .expect("seller payment evidence should import"); - set_persisted_order_status(&runtime, order_id, "scheduled"); - - let error = runtime - .publish_order_revision_proposal( - order_id, - revision_test_order_items(), - revision_test_order_economics(), - "harvest count updated", - ) - .expect_err("seller revision proposal should reject payment evidence"); - - assert!(matches!( - error, - AppSqliteError::InvalidProjection { - reason: "seller order revision requires no recorded or settled payment" - } - )); - assert_eq!(relay.event_count(), 0); - cleanup_bootstrapped_runtime_paths(&paths); - } - } - - #[test] - fn runtime_rejects_seller_order_revision_after_rejected_settlement_evidence() { - let relay = ThreadedAckRelay::spawn(); - let (runtime, paths, order_id, product_id, seller_pubkey, buyer_pubkey) = - seller_order_decision_runtime("seller_order_revision_rejected_payment", 6, 2); - install_direct_relay_sync_transport(&runtime, &relay); - let listing_key = super::d_tag_from_uuid(product_id.as_uuid()); - let listing_addr = format!("30402:{seller_pubkey}:{listing_key}"); - let request_event_id = shared_order_request_event_id(&paths, "seller-order-decision-1"); - let request_event_id = request_event_id.as_str(); - let decision_event_id = append_signed_order_decision_record( - &paths, - "seller-order-decision-1", - request_event_id, - listing_addr.as_str(), - buyer_pubkey.as_str(), - seller_pubkey.as_str(), - 2, - ); - let payment_event_id = append_signed_payment_record( - &paths, - "seller-order-decision-1", - "seller-order-revision-payment-rejected", - request_event_id, - decision_event_id.as_str(), - listing_addr.as_str(), - buyer_pubkey.as_str(), - seller_pubkey.as_str(), - 2, - ); - append_signed_settlement_decision_record( - &paths, - "seller-order-decision-1", - "seller-order-revision-k3436-rejected", - request_event_id, - decision_event_id.as_str(), - payment_event_id.as_str(), - listing_addr.as_str(), - buyer_pubkey.as_str(), - seller_pubkey.as_str(), - 2, - RadrootsOrderSettlementOutcome::Rejected, - ); - runtime - .refresh_shared_local_events() - .expect("seller rejected payment evidence should import"); - set_persisted_order_status(&runtime, order_id, "scheduled"); - - let error = runtime - .publish_order_revision_proposal( - order_id, - revision_test_order_items(), - revision_test_order_economics(), - "harvest count updated", - ) - .expect_err("seller revision proposal should reject rejected 3436 payment evidence"); - - assert!(matches!( - error, - AppSqliteError::InvalidProjection { - reason: "seller order revision requires no recorded or settled payment" - } - )); - assert_eq!(relay.event_count(), 0); - let revision_events = shared_order_events_by_kind(&paths, 3424, seller_pubkey.as_str()); - assert!(revision_events.is_empty()); - cleanup_bootstrapped_runtime_paths(&paths); - } - - #[test] - fn runtime_rejects_seller_order_revision_with_invalid_payment_evidence() { - let relay = ThreadedAckRelay::spawn(); - let (runtime, paths, order_id, product_id, seller_pubkey, buyer_pubkey) = - seller_order_decision_runtime("seller_order_revision_invalid_payment", 6, 2); - install_direct_relay_sync_transport(&runtime, &relay); - let listing_key = super::d_tag_from_uuid(product_id.as_uuid()); - let listing_addr = format!("30402:{seller_pubkey}:{listing_key}"); - let request_event_id = shared_order_request_event_id(&paths, "seller-order-decision-1"); - let request_event_id = request_event_id.as_str(); - let decision_event_id = append_signed_order_decision_record( - &paths, - "seller-order-decision-1", - request_event_id, - listing_addr.as_str(), - buyer_pubkey.as_str(), - seller_pubkey.as_str(), - 2, - ); - append_signed_payment_record_with_prev( - &paths, - "seller-order-decision-1", - "seller-order-revision-invalid-payment", - request_event_id, - request_event_id, - decision_event_id.as_str(), - listing_addr.as_str(), - buyer_pubkey.as_str(), - seller_pubkey.as_str(), - 2, - ); - runtime - .refresh_shared_local_events() - .expect("seller invalid payment evidence should import"); - set_persisted_order_status(&runtime, order_id, "scheduled"); - - let error = runtime - .publish_order_revision_proposal( - order_id, - revision_test_order_items(), - revision_test_order_economics(), - "harvest count updated", - ) - .expect_err("seller revision proposal should reject invalid payment evidence"); - - assert_order_lifecycle_evidence_invalid(error); - assert_eq!(relay.event_count(), 0); - cleanup_bootstrapped_runtime_paths(&paths); - } - - #[test] fn runtime_places_supported_buyer_order_into_shared_local_events() { let (runtime, paths) = bootstrapped_runtime("buyer_order_local_event"); assert!( @@ -17299,8 +16008,6 @@ mod tests { .as_ref() .expect("order local work payload"); assert_eq!(payload["support_status"]["state"], "supported"); - assert_eq!(payload["payment_display"]["state"], "not_recorded"); - assert_eq!(payload["payment_display"]["allows_payment_action"], false); assert_eq!(payload["currentness"]["current"], true); assert_eq!(payload["document"]["kind"], "order_draft_v1"); assert_eq!( @@ -17673,7 +16380,7 @@ mod tests { #[test] fn runtime_opens_linked_buyer_order_detail_from_selected_account_nostr_scope() { - let fixture = linked_buyer_lifecycle_runtime("linked_buyer_order_open", false); + let fixture = linked_buyer_lifecycle_runtime("linked_buyer_order_open"); let report = fixture .runtime .refresh_shared_local_events() @@ -17716,7 +16423,7 @@ mod tests { #[test] fn runtime_publishes_linked_buyer_cancellation_from_selected_account_nostr_scope() { let relay = ThreadedAckRelay::spawn(); - let fixture = linked_buyer_lifecycle_runtime("linked_buyer_order_cancel", false); + let fixture = linked_buyer_lifecycle_runtime("linked_buyer_order_cancel"); install_direct_relay_sync_transport(&fixture.runtime, &relay); fixture .runtime @@ -17754,348 +16461,56 @@ mod tests { } #[test] - fn runtime_rejects_linked_buyer_cancellation_after_recorded_or_settled_payment_evidence() { - for (label, settlement) in [ - ("recorded", None), - ("settled", Some(RadrootsOrderSettlementOutcome::Accepted)), + fn runtime_publishes_linked_buyer_cancellation_from_revision_parent() { + for (label, revision_decision) in [ + ("accepted", RadrootsOrderRevisionOutcome::Accepted), + ( + "declined", + RadrootsOrderRevisionOutcome::Declined { + reason: "keep original order".to_owned(), + }, + ), ] { let relay = ThreadedAckRelay::spawn(); - let fixture_label = format!("linked_buyer_order_cancel_payment_{label}"); - let fixture = linked_buyer_lifecycle_runtime(fixture_label.as_str(), false); - install_direct_relay_sync_transport(&fixture.runtime, &relay); - let payment_event_id = append_signed_payment_record( + let fixture_label = format!("linked_buyer_order_cancel_revision_{label}"); + let fixture = linked_buyer_lifecycle_runtime(fixture_label.as_str()); + let proposal_key = format!("linked-buyer-order-cancel-revision-{label}-proposal"); + let proposal_event_id = append_signed_order_revision_proposal_record_with_prev( &fixture.paths, fixture.trade_order_id.as_str(), - format!("linked-buyer-cancel-payment-{label}").as_str(), + proposal_key.as_str(), fixture.request_event_id.as_str(), fixture.decision_event_id.as_str(), fixture.listing_addr.as_str(), fixture.buyer_pubkey.as_str(), fixture.seller_pubkey.as_str(), - 2, ); - if let Some(decision) = settlement { - append_signed_settlement_decision_record( + let revision_id = format!("revision-{proposal_key}"); + let _revision_decision_event_id = + append_signed_order_revision_decision_record_with_prev( &fixture.paths, fixture.trade_order_id.as_str(), - format!("linked-buyer-cancel-k3436-{label}").as_str(), + format!("linked-buyer-order-cancel-revision-{label}-decision").as_str(), fixture.request_event_id.as_str(), - fixture.decision_event_id.as_str(), - payment_event_id.as_str(), + proposal_event_id.as_str(), + revision_id.as_str(), fixture.listing_addr.as_str(), fixture.buyer_pubkey.as_str(), fixture.seller_pubkey.as_str(), - 2, - decision, + revision_decision, ); - } + install_direct_relay_sync_transport(&fixture.runtime, &relay); fixture .runtime .refresh_shared_local_events() - .expect("linked buyer payment evidence should import"); + .expect("linked buyer revision events should import"); assert!( fixture .runtime .open_personal_order_detail(fixture.order_id) .expect("linked buyer order detail should open") ); - - let error = fixture - .runtime - .publish_buyer_order_cancel(fixture.order_id) - .expect_err("linked buyer cancellation should reject payment evidence"); - - assert!(matches!( - error, - AppSqliteError::InvalidProjection { - reason: "buyer order cancellation requires no recorded or settled payment" - } - )); - assert_eq!(relay.event_count(), 0); - cleanup_bootstrapped_runtime_paths(&fixture.paths); - } - } - - #[test] - fn runtime_rejects_linked_buyer_cancellation_after_rejected_settlement_evidence() { - let relay = ThreadedAckRelay::spawn(); - let fixture = - linked_buyer_lifecycle_runtime("linked_buyer_order_cancel_rejected_payment", false); - install_direct_relay_sync_transport(&fixture.runtime, &relay); - let payment_event_id = append_signed_payment_record( - &fixture.paths, - fixture.trade_order_id.as_str(), - "linked-buyer-cancel-payment-rejected", - fixture.request_event_id.as_str(), - fixture.decision_event_id.as_str(), - fixture.listing_addr.as_str(), - fixture.buyer_pubkey.as_str(), - fixture.seller_pubkey.as_str(), - 2, - ); - append_signed_settlement_decision_record( - &fixture.paths, - fixture.trade_order_id.as_str(), - "linked-buyer-cancel-k3436-rejected", - fixture.request_event_id.as_str(), - fixture.decision_event_id.as_str(), - payment_event_id.as_str(), - fixture.listing_addr.as_str(), - fixture.buyer_pubkey.as_str(), - fixture.seller_pubkey.as_str(), - 2, - RadrootsOrderSettlementOutcome::Rejected, - ); - fixture - .runtime - .refresh_shared_local_events() - .expect("linked buyer rejected payment evidence should import"); - assert!( - fixture - .runtime - .open_personal_order_detail(fixture.order_id) - .expect("linked buyer order detail should open") - ); - - let error = fixture - .runtime - .publish_buyer_order_cancel(fixture.order_id) - .expect_err("linked buyer cancellation should reject rejected 3436 payment evidence"); - - assert!(matches!( - error, - AppSqliteError::InvalidProjection { - reason: "buyer order cancellation requires no recorded or settled payment" - } - )); - assert_eq!(relay.event_count(), 0); - let cancellation_events = - shared_order_events_by_kind(&fixture.paths, 3432, fixture.buyer_pubkey.as_str()); - assert!(cancellation_events.is_empty()); - cleanup_bootstrapped_runtime_paths(&fixture.paths); - } - - #[test] - fn runtime_rejects_linked_buyer_cancellation_with_invalid_payment_evidence() { - let relay = ThreadedAckRelay::spawn(); - let fixture = - linked_buyer_lifecycle_runtime("linked_buyer_order_cancel_invalid_payment", false); - install_direct_relay_sync_transport(&fixture.runtime, &relay); - append_signed_payment_record_with_prev( - &fixture.paths, - fixture.trade_order_id.as_str(), - "linked-buyer-cancel-invalid-payment", - fixture.request_event_id.as_str(), - fixture.request_event_id.as_str(), - fixture.decision_event_id.as_str(), - fixture.listing_addr.as_str(), - fixture.buyer_pubkey.as_str(), - fixture.seller_pubkey.as_str(), - 2, - ); - fixture - .runtime - .refresh_shared_local_events() - .expect("linked buyer invalid payment evidence should import"); - assert!( - fixture - .runtime - .open_personal_order_detail(fixture.order_id) - .expect("linked buyer order detail should open") - ); - - let error = fixture - .runtime - .publish_buyer_order_cancel(fixture.order_id) - .expect_err("linked buyer cancellation should reject invalid payment evidence"); - - assert_order_lifecycle_evidence_invalid(error); - assert_eq!(relay.event_count(), 0); - cleanup_bootstrapped_runtime_paths(&fixture.paths); - } - - #[test] - fn runtime_rejects_linked_buyer_cancellation_after_relay_payment_evidence() { - let relay = ThreadedAckRelay::spawn(); - let fixture = - linked_buyer_lifecycle_runtime("linked_buyer_order_cancel_relay_payment", false); - configure_runtime_relay_ingest(&fixture.runtime, &relay); - fixture - .runtime - .refresh_shared_local_events() - .expect("linked buyer local events should import"); - assert!( - fixture - .runtime - .open_personal_order_detail(fixture.order_id) - .expect("linked buyer order detail should open") - ); - let buyer_identity = selected_account_signing_identity(&fixture.runtime); - let payment_event = signed_payment_recorded_relay_event( - &buyer_identity, - fixture.trade_order_id.as_str(), - fixture.request_event_id.as_str(), - fixture.decision_event_id.as_str(), - fixture.listing_addr.as_str(), - fixture.buyer_pubkey.as_str(), - fixture.seller_pubkey.as_str(), - 2, - ); - let payment_event_id = payment_event.id.to_hex(); - relay.push_event(&payment_event); - - let error = fixture - .runtime - .publish_buyer_order_cancel(fixture.order_id) - .expect_err("linked buyer cancellation should reject relay payment evidence"); - - assert!(matches!( - error, - AppSqliteError::InvalidProjection { - reason: "buyer order cancellation requires no recorded or settled payment" - } - )); - let payment_events = fixture - .runtime - .lock_state() - .sqlite_store - .as_ref() - .expect("sqlite store") - .load_local_interop_signed_events_by_kind(3435) - .expect("relay payment evidence should load from local interop"); - assert!( - payment_events - .iter() - .any(|event| event.id == payment_event_id) - ); - assert_eq!(relay.event_count(), 1); - cleanup_bootstrapped_runtime_paths(&fixture.paths); - } - - #[test] - fn runtime_rejects_linked_buyer_cancellation_after_relay_settlement_evidence() { - let relay = ThreadedAckRelay::spawn(); - let seller_identity = RadrootsIdentity::generate(); - let seller_pubkey = seller_identity.public_key_hex(); - let fixture = linked_buyer_lifecycle_runtime_with_seller_pubkey( - "linked_buyer_order_cancel_relay_k3436", - false, - seller_pubkey.as_str(), - ); - configure_runtime_relay_ingest(&fixture.runtime, &relay); - fixture - .runtime - .refresh_shared_local_events() - .expect("linked buyer local events should import"); - assert!( - fixture - .runtime - .open_personal_order_detail(fixture.order_id) - .expect("linked buyer order detail should open") - ); - let buyer_identity = selected_account_signing_identity(&fixture.runtime); - let payment_event = signed_payment_recorded_relay_event( - &buyer_identity, - fixture.trade_order_id.as_str(), - fixture.request_event_id.as_str(), - fixture.decision_event_id.as_str(), - fixture.listing_addr.as_str(), - fixture.buyer_pubkey.as_str(), - fixture.seller_pubkey.as_str(), - 2, - ); - let payment_event_id = payment_event.id.to_hex(); - let k3436_event = signed_settlement_decision_relay_event( - &seller_identity, - fixture.trade_order_id.as_str(), - fixture.request_event_id.as_str(), - fixture.decision_event_id.as_str(), - payment_event_id.as_str(), - fixture.listing_addr.as_str(), - fixture.buyer_pubkey.as_str(), - fixture.seller_pubkey.as_str(), - 2, - RadrootsOrderSettlementOutcome::Accepted, - ); - let k3436_event_id = k3436_event.id.to_hex(); - relay.push_event(&payment_event); - relay.push_event(&k3436_event); - - let error = fixture - .runtime - .publish_buyer_order_cancel(fixture.order_id) - .expect_err("linked buyer cancellation should reject relay k3436 evidence"); - - assert!(matches!( - error, - AppSqliteError::InvalidProjection { - reason: "buyer order cancellation requires no recorded or settled payment" - } - )); - let k3436_events = fixture - .runtime - .lock_state() - .sqlite_store - .as_ref() - .expect("sqlite store") - .load_local_interop_signed_events_by_kind(3436) - .expect("relay k3436 evidence should load from local interop"); - assert!(k3436_events.iter().any(|event| event.id == k3436_event_id)); - assert_eq!(relay.event_count(), 2); - cleanup_bootstrapped_runtime_paths(&fixture.paths); - } - - #[test] - fn runtime_publishes_linked_buyer_cancellation_from_revision_parent() { - for (label, revision_decision) in [ - ("accepted", RadrootsOrderRevisionOutcome::Accepted), - ( - "declined", - RadrootsOrderRevisionOutcome::Declined { - reason: "keep original order".to_owned(), - }, - ), - ] { - let relay = ThreadedAckRelay::spawn(); - let fixture_label = format!("linked_buyer_order_cancel_revision_{label}"); - let fixture = linked_buyer_lifecycle_runtime(fixture_label.as_str(), false); - let proposal_key = format!("linked-buyer-order-cancel-revision-{label}-proposal"); - let proposal_event_id = append_signed_order_revision_proposal_record_with_prev( - &fixture.paths, - fixture.trade_order_id.as_str(), - proposal_key.as_str(), - fixture.request_event_id.as_str(), - fixture.decision_event_id.as_str(), - fixture.listing_addr.as_str(), - fixture.buyer_pubkey.as_str(), - fixture.seller_pubkey.as_str(), - ); - let revision_id = format!("revision-{proposal_key}"); - let _revision_decision_event_id = - append_signed_order_revision_decision_record_with_prev( - &fixture.paths, - fixture.trade_order_id.as_str(), - format!("linked-buyer-order-cancel-revision-{label}-decision").as_str(), - fixture.request_event_id.as_str(), - proposal_event_id.as_str(), - revision_id.as_str(), - fixture.listing_addr.as_str(), - fixture.buyer_pubkey.as_str(), - fixture.seller_pubkey.as_str(), - revision_decision, - ); - install_direct_relay_sync_transport(&fixture.runtime, &relay); - fixture - .runtime - .refresh_shared_local_events() - .expect("linked buyer revision events should import"); - assert!( - fixture - .runtime - .open_personal_order_detail(fixture.order_id) - .expect("linked buyer order detail should open") - ); - set_persisted_order_status(&fixture.runtime, fixture.order_id, "scheduled"); + set_persisted_order_status(&fixture.runtime, fixture.order_id, "scheduled"); assert!( fixture @@ -18119,170 +16534,9 @@ mod tests { } #[test] - fn runtime_publishes_linked_buyer_receipt_from_selected_account_nostr_scope() { - let relay = ThreadedAckRelay::spawn(); - let fixture = linked_buyer_lifecycle_runtime("linked_buyer_order_receipt", true); - let _fulfillment_event_id = fixture - .fulfillment_event_id - .as_deref() - .expect("ready fixture should include fulfillment event") - .to_owned(); - install_direct_relay_sync_transport(&fixture.runtime, &relay); - fixture - .runtime - .refresh_shared_local_events() - .expect("linked buyer local events should import"); - assert!( - fixture - .runtime - .open_personal_order_detail(fixture.order_id) - .expect("linked ready buyer order detail should open") - ); - assert_eq!( - fixture - .runtime - .summary() - .personal_projection - .orders - .detail - .as_ref() - .expect("linked ready buyer detail") - .status, - BuyerOrderStatus::Ready - ); - - assert!( - fixture - .runtime - .publish_buyer_order_receipt(fixture.order_id, AppOrderReceiptOutcome::Received) - .expect("linked buyer receipt should publish") - ); - - assert_eq!( - persisted_order_status(&fixture.runtime, fixture.order_id), - "packed" - ); - assert_eq!(relay.event_count(), 0); - let receipt_events = - shared_order_events_by_kind(&fixture.paths, 3434, fixture.buyer_pubkey.as_str()); - assert!(receipt_events.is_empty()); - assert_order_receipt_sdk_migration_receipt( - &fixture.runtime, - fixture.order_id, - AppSdkMigrationState::Enqueued, - ); - - cleanup_bootstrapped_runtime_paths(&fixture.paths); - } - - #[test] - fn runtime_publishes_linked_buyer_receipt_after_direct_delivered_fulfillment() { - let relay = ThreadedAckRelay::spawn(); - let fixture = linked_buyer_lifecycle_runtime("linked_buyer_order_receipt_delivered", false); - let _fulfillment_event_id = append_signed_order_fulfillment_record_with_status( - &fixture.paths, - fixture.trade_order_id.as_str(), - fixture.request_event_id.as_str(), - fixture.decision_event_id.as_str(), - fixture.listing_addr.as_str(), - fixture.buyer_pubkey.as_str(), - fixture.seller_pubkey.as_str(), - RadrootsOrderFulfillmentState::Delivered, - ); - install_direct_relay_sync_transport(&fixture.runtime, &relay); - fixture - .runtime - .refresh_shared_local_events() - .expect("linked buyer delivered local events should import"); - assert!( - fixture - .runtime - .open_personal_order_detail(fixture.order_id) - .expect("linked delivered buyer order detail should open") - ); - - assert!( - fixture - .runtime - .publish_buyer_order_receipt(fixture.order_id, AppOrderReceiptOutcome::Received) - .expect("linked delivered buyer receipt should publish") - ); - - assert_eq!( - persisted_order_status(&fixture.runtime, fixture.order_id), - "packed" - ); - assert_eq!(relay.event_count(), 0); - let receipt_events = - shared_order_events_by_kind(&fixture.paths, 3434, fixture.buyer_pubkey.as_str()); - assert!(receipt_events.is_empty()); - assert_order_receipt_sdk_migration_receipt( - &fixture.runtime, - fixture.order_id, - AppSdkMigrationState::Enqueued, - ); - - cleanup_bootstrapped_runtime_paths(&fixture.paths); - } - - #[test] - fn runtime_publishes_linked_buyer_issue_receipt_from_selected_account_nostr_scope() { - let relay = ThreadedAckRelay::spawn(); - let fixture = linked_buyer_lifecycle_runtime("linked_buyer_order_issue_receipt", true); - install_direct_relay_sync_transport(&fixture.runtime, &relay); - fixture - .runtime - .refresh_shared_local_events() - .expect("linked buyer local events should import"); - assert!( - fixture - .runtime - .open_personal_order_detail(fixture.order_id) - .expect("linked ready buyer order detail should open") - ); - - assert!( - fixture - .runtime - .publish_buyer_order_receipt( - fixture.order_id, - AppOrderReceiptOutcome::issue("items need review") - .expect("issue receipt text should be accepted"), - ) - .expect("linked buyer issue receipt should publish") - ); - - assert_eq!( - persisted_order_status(&fixture.runtime, fixture.order_id), - "packed" - ); - assert_eq!(relay.event_count(), 0); - let receipt_events = - shared_order_events_by_kind(&fixture.paths, 3434, fixture.buyer_pubkey.as_str()); - assert!(receipt_events.is_empty()); - let buyer_detail = fixture - .runtime - .summary() - .personal_projection - .orders - .detail - .as_ref() - .expect("linked buyer issue receipt detail") - .clone(); - assert_eq!(buyer_detail.status, BuyerOrderStatus::Ready); - assert_order_receipt_sdk_migration_receipt( - &fixture.runtime, - fixture.order_id, - AppSdkMigrationState::Enqueued, - ); - - cleanup_bootstrapped_runtime_paths(&fixture.paths); - } - - #[test] fn runtime_rejects_linked_buyer_cancellation_with_reducer_invalid_evidence() { let relay = ThreadedAckRelay::spawn(); - let fixture = linked_buyer_lifecycle_runtime("linked_buyer_order_cancel_invalid", false); + let fixture = linked_buyer_lifecycle_runtime("linked_buyer_order_cancel_invalid"); install_direct_relay_sync_transport(&fixture.runtime, &relay); fixture .runtime @@ -18319,58 +16573,8 @@ mod tests { .runtime .publish_buyer_order_cancel(fixture.order_id) .expect_err("linked buyer cancellation should reject reducer-invalid evidence"); - - assert_order_lifecycle_evidence_invalid(error); - assert_eq!(relay.event_count(), 0); - cleanup_bootstrapped_runtime_paths(&fixture.paths); - } - - #[test] - fn runtime_rejects_linked_buyer_receipt_with_reducer_invalid_fulfillment_evidence() { - let relay = ThreadedAckRelay::spawn(); - let fixture = linked_buyer_lifecycle_runtime("linked_buyer_order_receipt_invalid", true); - install_direct_relay_sync_transport(&fixture.runtime, &relay); - fixture - .runtime - .refresh_shared_local_events() - .expect("linked buyer local events should import"); - assert!( - fixture - .runtime - .open_personal_order_detail(fixture.order_id) - .expect("linked buyer order detail should open") - ); - append_signed_order_fulfillment_record_with_status_and_key( - &fixture.paths, - fixture.trade_order_id.as_str(), - "linked-buyer-order-receipt-forked-ready", - fixture.request_event_id.as_str(), - fixture.decision_event_id.as_str(), - fixture.listing_addr.as_str(), - fixture.buyer_pubkey.as_str(), - fixture.seller_pubkey.as_str(), - RadrootsOrderFulfillmentState::ReadyForPickup, - ); - let resolver_error = { - let state = fixture.runtime.lock_state(); - let request = state - .resolve_seller_order_request_evidence(fixture.order_id) - .expect("linked buyer request evidence should resolve"); - state - .resolve_order_lifecycle_evidence(&request) - .expect_err("linked buyer receipt evidence should be reducer-invalid") - }; - assert_order_lifecycle_evidence_invalid(resolver_error); - - let error = fixture - .runtime - .publish_buyer_order_receipt(fixture.order_id, AppOrderReceiptOutcome::Received) - .expect_err("linked buyer receipt should reject reducer-invalid fulfillment evidence"); - - assert!( - matches!(error, AppSqliteError::InvalidProjection { .. }), - "{error:?}" - ); + + assert_order_lifecycle_evidence_invalid(error); assert_eq!(relay.event_count(), 0); cleanup_bootstrapped_runtime_paths(&fixture.paths); } @@ -18378,7 +16582,7 @@ mod tests { #[test] fn runtime_publishes_linked_buyer_revision_decision_from_reducer_valid_parent() { let relay = ThreadedAckRelay::spawn(); - let fixture = linked_buyer_lifecycle_runtime("linked_buyer_order_revision", false); + let fixture = linked_buyer_lifecycle_runtime("linked_buyer_order_revision"); let proposal_key = "linked-buyer-order-revision-proposal"; let _proposal_event_id = append_signed_order_revision_proposal_record_with_prev( &fixture.paths, @@ -21786,26 +19990,17 @@ mod tests { trade_order_id: String, request_event_id: String, decision_event_id: String, - fulfillment_event_id: Option<String>, listing_addr: String, buyer_pubkey: String, seller_pubkey: String, } - fn linked_buyer_lifecycle_runtime( - label: &str, - include_ready_fulfillment: bool, - ) -> LinkedBuyerLifecycleFixture { - linked_buyer_lifecycle_runtime_with_seller_pubkey( - label, - include_ready_fulfillment, - SDK_TEST_SELLER_PUBLIC_KEY_HEX, - ) + fn linked_buyer_lifecycle_runtime(label: &str) -> LinkedBuyerLifecycleFixture { + linked_buyer_lifecycle_runtime_with_seller_pubkey(label, SDK_TEST_SELLER_PUBLIC_KEY_HEX) } fn linked_buyer_lifecycle_runtime_with_seller_pubkey( label: &str, - include_ready_fulfillment: bool, seller_pubkey: &str, ) -> LinkedBuyerLifecycleFixture { let (runtime, paths) = bootstrapped_runtime(label); @@ -21873,20 +20068,6 @@ mod tests { seller_pubkey, 2, ); - let fulfillment_event_id = if include_ready_fulfillment { - Some(append_signed_order_fulfillment_record( - &paths, - trade_order_id.as_str(), - request_event_id.as_str(), - decision_event_id.as_str(), - listing_addr.as_str(), - buyer_pubkey.as_str(), - seller_pubkey, - )) - } else { - None - }; - LinkedBuyerLifecycleFixture { runtime, paths, @@ -21894,7 +20075,6 @@ mod tests { trade_order_id, request_event_id, decision_event_id, - fulfillment_event_id, listing_addr, buyer_pubkey, seller_pubkey: seller_pubkey.to_owned(), @@ -22468,320 +20648,6 @@ mod tests { ) } - fn append_signed_payment_record( - paths: &AppDesktopRuntimePaths, - trade_order_id: &str, - event_key: &str, - request_event_id: &str, - agreement_event_id: &str, - listing_addr: &str, - buyer_pubkey: &str, - seller_pubkey: &str, - order_quantity: u32, - ) -> String { - append_signed_payment_record_with_prev( - paths, - trade_order_id, - event_key, - request_event_id, - agreement_event_id, - agreement_event_id, - listing_addr, - buyer_pubkey, - seller_pubkey, - order_quantity, - ) - } - - fn append_signed_payment_record_with_prev( - paths: &AppDesktopRuntimePaths, - trade_order_id: &str, - event_key: &str, - request_event_id: &str, - prev_event_id: &str, - agreement_event_id: &str, - listing_addr: &str, - buyer_pubkey: &str, - seller_pubkey: &str, - order_quantity: u32, - ) -> String { - let economics = signed_order_request_economics(trade_order_id, order_quantity); - let request_event_id = test_event_id(request_event_id); - let prev_event_id = test_event_id(prev_event_id); - let agreement_event_id = test_event_id(agreement_event_id); - let payload = RadrootsOrderPaymentRecord { - order_id: test_order_id(trade_order_id), - listing_addr: test_listing_addr(listing_addr), - buyer_pubkey: test_pubkey(buyer_pubkey), - seller_pubkey: test_pubkey(seller_pubkey), - root_event_id: request_event_id.clone(), - previous_event_id: prev_event_id.clone(), - agreement_event_id, - quote_id: economics.quote_id.clone(), - quote_version: economics.quote_version, - economics_digest: test_economics_digest( - radroots_order_economics_digest(&economics).expect("payment economics digest"), - ), - amount: economics.total.amount, - currency: economics.total.currency, - method: RadrootsOrderPaymentMethod::ManualTransfer, - reference: Some(format!("memo-{event_key}")), - paid_at: Some(1_774_000_050), - }; - let parts = order_payment_record_event_build(&request_event_id, &prev_event_id, &payload) - .expect("payment recorded draft should build"); - let record_id = format!("app:signed_event:payment:{event_key}"); - append_trade_signed_event_record( - paths, - record_id.as_str(), - buyer_pubkey, - listing_addr, - parts, - ) - } - - fn append_signed_settlement_decision_record( - paths: &AppDesktopRuntimePaths, - trade_order_id: &str, - event_key: &str, - request_event_id: &str, - agreement_event_id: &str, - payment_event_id: &str, - listing_addr: &str, - buyer_pubkey: &str, - seller_pubkey: &str, - order_quantity: u32, - decision: RadrootsOrderSettlementOutcome, - ) -> String { - let economics = signed_order_request_economics(trade_order_id, order_quantity); - let request_event_id = test_event_id(request_event_id); - let payment_event_id = test_event_id(payment_event_id); - let agreement_event_id = test_event_id(agreement_event_id); - let payload = RadrootsOrderSettlementDecision { - order_id: test_order_id(trade_order_id), - listing_addr: test_listing_addr(listing_addr), - seller_pubkey: test_pubkey(seller_pubkey), - buyer_pubkey: test_pubkey(buyer_pubkey), - root_event_id: request_event_id.clone(), - previous_event_id: payment_event_id.clone(), - agreement_event_id, - payment_event_id: payment_event_id.clone(), - quote_id: economics.quote_id.clone(), - quote_version: economics.quote_version, - economics_digest: test_economics_digest( - radroots_order_economics_digest(&economics).expect("k3436 economics digest"), - ), - amount: economics.total.amount, - currency: economics.total.currency, - decision, - reason: (decision == RadrootsOrderSettlementOutcome::Rejected) - .then(|| "reference mismatch".to_owned()), - }; - let parts = - order_settlement_decision_event_build(&request_event_id, &payment_event_id, &payload) - .expect("k3436 draft should build"); - let record_id = format!("app:signed_event:k3436:{event_key}"); - append_trade_signed_event_record( - paths, - record_id.as_str(), - seller_pubkey, - listing_addr, - parts, - ) - } - - fn signed_payment_recorded_relay_event( - buyer: &RadrootsIdentity, - trade_order_id: &str, - request_event_id: &str, - agreement_event_id: &str, - listing_addr: &str, - buyer_pubkey: &str, - seller_pubkey: &str, - order_quantity: u32, - ) -> radroots_nostr::prelude::RadrootsNostrEvent { - let economics = signed_order_request_economics(trade_order_id, order_quantity); - let request_event_id = test_event_id(request_event_id); - let agreement_event_id = test_event_id(agreement_event_id); - let payload = RadrootsOrderPaymentRecord { - order_id: test_order_id(trade_order_id), - listing_addr: test_listing_addr(listing_addr), - buyer_pubkey: test_pubkey(buyer_pubkey), - seller_pubkey: test_pubkey(seller_pubkey), - root_event_id: request_event_id.clone(), - previous_event_id: agreement_event_id.clone(), - agreement_event_id: agreement_event_id.clone(), - quote_id: economics.quote_id.clone(), - quote_version: economics.quote_version, - economics_digest: test_economics_digest( - radroots_order_economics_digest(&economics) - .expect("relay payment economics digest"), - ), - amount: economics.total.amount, - currency: economics.total.currency, - method: RadrootsOrderPaymentMethod::ManualTransfer, - reference: Some("relay-memo-1".to_owned()), - paid_at: Some(1_774_000_050), - }; - let parts = - order_payment_record_event_build(&request_event_id, &agreement_event_id, &payload) - .expect("relay payment draft should build"); - - radroots_nostr_build_event(parts.kind, parts.content, parts.tags) - .expect("relay payment builder") - .sign_with_keys(buyer.keys()) - .expect("relay payment should sign") - } - - fn signed_settlement_decision_relay_event( - seller: &RadrootsIdentity, - trade_order_id: &str, - request_event_id: &str, - agreement_event_id: &str, - payment_event_id: &str, - listing_addr: &str, - buyer_pubkey: &str, - seller_pubkey: &str, - order_quantity: u32, - decision: RadrootsOrderSettlementOutcome, - ) -> radroots_nostr::prelude::RadrootsNostrEvent { - let economics = signed_order_request_economics(trade_order_id, order_quantity); - let request_event_id = test_event_id(request_event_id); - let agreement_event_id = test_event_id(agreement_event_id); - let payment_event_id = test_event_id(payment_event_id); - let payload = RadrootsOrderSettlementDecision { - order_id: test_order_id(trade_order_id), - listing_addr: test_listing_addr(listing_addr), - seller_pubkey: test_pubkey(seller_pubkey), - buyer_pubkey: test_pubkey(buyer_pubkey), - root_event_id: request_event_id.clone(), - previous_event_id: payment_event_id.clone(), - agreement_event_id, - payment_event_id: payment_event_id.clone(), - quote_id: economics.quote_id.clone(), - quote_version: economics.quote_version, - economics_digest: test_economics_digest( - radroots_order_economics_digest(&economics).expect("relay k3436 economics digest"), - ), - amount: economics.total.amount, - currency: economics.total.currency, - decision, - reason: (decision == RadrootsOrderSettlementOutcome::Rejected) - .then(|| "reference mismatch".to_owned()), - }; - let parts = - order_settlement_decision_event_build(&request_event_id, &payment_event_id, &payload) - .expect("relay k3436 draft should build"); - - radroots_nostr_build_event(parts.kind, parts.content, parts.tags) - .expect("relay k3436 builder") - .sign_with_keys(seller.keys()) - .expect("relay k3436 should sign") - } - - fn selected_account_signing_identity(runtime: &DesktopAppRuntime) -> RadrootsIdentity { - let account_id = runtime - .summary() - .settings_account_projection - .selected_account - .as_ref() - .expect("selected account") - .account - .account_id - .clone(); - let account_id = - RadrootsIdentityId::parse(account_id.as_str()).expect("selected account id"); - runtime - .lock_state() - .accounts_manager - .as_ref() - .expect("accounts manager") - .get_signing_identity(&account_id) - .expect("signer lookup should succeed") - .expect("selected account should have local signer") - } - - fn append_signed_order_fulfillment_record( - paths: &AppDesktopRuntimePaths, - trade_order_id: &str, - request_event_id: &str, - decision_event_id: &str, - listing_addr: &str, - buyer_pubkey: &str, - seller_pubkey: &str, - ) -> String { - append_signed_order_fulfillment_record_with_status( - paths, - trade_order_id, - request_event_id, - decision_event_id, - listing_addr, - buyer_pubkey, - seller_pubkey, - RadrootsOrderFulfillmentState::ReadyForPickup, - ) - } - - fn append_signed_order_fulfillment_record_with_status( - paths: &AppDesktopRuntimePaths, - trade_order_id: &str, - request_event_id: &str, - decision_event_id: &str, - listing_addr: &str, - buyer_pubkey: &str, - seller_pubkey: &str, - status: RadrootsOrderFulfillmentState, - ) -> String { - append_signed_order_fulfillment_record_with_status_and_key( - paths, - trade_order_id, - trade_order_id, - request_event_id, - decision_event_id, - listing_addr, - buyer_pubkey, - seller_pubkey, - status, - ) - } - - fn append_signed_order_fulfillment_record_with_status_and_key( - paths: &AppDesktopRuntimePaths, - trade_order_id: &str, - event_key: &str, - request_event_id: &str, - prev_event_id: &str, - listing_addr: &str, - buyer_pubkey: &str, - seller_pubkey: &str, - status: RadrootsOrderFulfillmentState, - ) -> String { - let request_event_id = test_event_id(request_event_id); - let prev_event_id = test_event_id(prev_event_id); - let payload = RadrootsOrderFulfillmentUpdate { - order_id: test_order_id(trade_order_id), - listing_addr: test_listing_addr(listing_addr), - buyer_pubkey: test_pubkey(buyer_pubkey), - seller_pubkey: test_pubkey(seller_pubkey), - status, - }; - let parts = radroots_sdk::protocol::order::build_fulfillment_update_draft( - &request_event_id, - &prev_event_id, - &payload, - ) - .expect("fulfillment update draft should build") - .into_wire_parts(); - let record_id = format!("app:signed_event:fulfillment:{event_key}"); - append_trade_signed_event_record( - paths, - record_id.as_str(), - seller_pubkey, - listing_addr, - parts, - ) - } - fn append_signed_order_cancellation_record_with_prev( paths: &AppDesktopRuntimePaths, trade_order_id: &str, @@ -22818,45 +20684,6 @@ mod tests { ) } - fn append_signed_order_receipt_record_with_prev( - paths: &AppDesktopRuntimePaths, - trade_order_id: &str, - event_key: &str, - request_event_id: &str, - prev_event_id: &str, - listing_addr: &str, - buyer_pubkey: &str, - seller_pubkey: &str, - received: bool, - ) -> String { - let request_event_id = test_event_id(request_event_id); - let prev_event_id = test_event_id(prev_event_id); - let payload = RadrootsOrderReceipt { - order_id: test_order_id(trade_order_id), - listing_addr: test_listing_addr(listing_addr), - buyer_pubkey: test_pubkey(buyer_pubkey), - seller_pubkey: test_pubkey(seller_pubkey), - received, - issue: (!received).then(|| "items need review".to_owned()), - received_at: 1_774_000_030, - }; - let parts = radroots_sdk::protocol::order::build_buyer_receipt_draft( - &request_event_id, - &prev_event_id, - &payload, - ) - .expect("buyer receipt draft should build") - .into_wire_parts(); - let record_id = format!("app:signed_event:receipt:{event_key}"); - append_trade_signed_event_record( - paths, - record_id.as_str(), - buyer_pubkey, - listing_addr, - parts, - ) - } - fn append_signed_order_revision_proposal_record_with_prev( paths: &AppDesktopRuntimePaths, trade_order_id: &str, @@ -23452,37 +21279,6 @@ mod tests { ); } - fn assert_order_fulfillment_sdk_migration_receipt( - runtime: &DesktopAppRuntime, - order_id: OrderId, - fulfillment: RadrootsOrderFulfillmentState, - expected_state: AppSdkMigrationState, - ) { - assert_order_sdk_migration_receipt( - runtime, - format!( - "app:order_fulfillment:{order_id}:{}", - order_fulfillment_status_storage_key(fulfillment) - ) - .as_str(), - ORDER_FULFILLMENT_UPDATE_OPERATION_KIND, - expected_state, - ); - } - - fn assert_order_receipt_sdk_migration_receipt( - runtime: &DesktopAppRuntime, - order_id: OrderId, - expected_state: AppSdkMigrationState, - ) { - assert_order_sdk_migration_receipt( - runtime, - format!("app:order_receipt:{order_id}").as_str(), - ORDER_RECEIPT_RECORD_OPERATION_KIND, - expected_state, - ); - } - fn assert_order_sdk_migration_receipt( runtime: &DesktopAppRuntime, source_record_id: &str, diff --git a/crates/desktop/src/source_guards.rs b/crates/desktop/src/source_guards.rs @@ -87,10 +87,7 @@ const ALLOWED_WINDOW_LITERALS: &[&str] = &[ "buyer-order-keep-current", "buyer-order-keep-order", "buyer-order-close-issue", - "buyer-order-mark-received", - "buyer-order-report-issue", "buyer-order-repeat-demand", - "buyer-order-send-issue", "buyer-orders-retry-coordination", "personal_orders", "buyer.add_to_cart_failed", @@ -101,8 +98,6 @@ const ALLOWED_WINDOW_LITERALS: &[&str] = &[ "buyer.order_open_failed", "buyer.order_cancel_failed", "buyer.order_coordination_retry_failed", - "buyer.order_issue_receipt_failed", - "buyer.order_receipt_failed", "buyer.order_revision_accept_failed", "buyer.order_revision_decline_failed", "buyer.repeat_demand_failed", @@ -138,9 +133,6 @@ const ALLOWED_WINDOW_LITERALS: &[&str] = &[ "failed to accept buyer order change", "failed to cancel buyer order", "failed to keep buyer order", - "failed to mark buyer order received", - "failed to report buyer order issue", - "failed to publish order fulfillment update", "failed to open existing product editor", "failed to open new product editor", "failed to acknowledge reminder", @@ -214,15 +206,12 @@ const ALLOWED_WINDOW_LITERALS: &[&str] = &[ "today-reminder-chip", "https://auth.example/challenge", "identity", - "items need review", "localhost", "npub1", "npub1qqqqq...qqqqqq", "npub1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", "npub1sxczr...5lkheq", "npub1sxczrq2dp4jtehcm8mtemj975u5ytf2d7mc6dpuuq3rzkjzr76ls5lkheq", - "receipt-clean", - "receipt-issue", "guest", "finder unavailable", "orders", @@ -247,27 +236,19 @@ const ALLOWED_WINDOW_LITERALS: &[&str] = &[ "pack-day-reveal-bundle", "pack_day.export_failed", "pack_day.route_failed", - "orders-detail-publish-delivered", - "orders-detail-publish-out-for-delivery", - "orders-detail-publish-preparing", - "orders-detail-publish-ready-for-pickup", - "orders-detail-publish-seller-cancelled", "orders-filter-all", "orders-filter-completed", "orders-filter-needs-action", "orders-filter-packed", - "orders-filter-refunded", "orders-filter-scheduled", "orders-recovery-open", "orders-recovery-review", "orders-recovery-reopen", "orders-recovery-resolve", - "orders-row-action-publish-fulfillment", "orders-row-action-review", "orders-row-open", "orders.detail_open_failed", "orders.filter_update_failed", - "orders.fulfillment_publish_failed", "orders.recovery_reopen_failed", "orders.recovery_resolve_failed", "orders.recovery_review_failed", @@ -668,17 +649,10 @@ const REQUIRED_WINDOW_COPY_KEYS: &[&str] = &[ "AppTextKey::PersonalOrdersDetailFulfillmentLabel", "AppTextKey::PersonalOrdersDetailTotalLabel", "AppTextKey::PersonalOrdersDetailNoteLabel", - "AppTextKey::PersonalOrdersDetailReceiptLabel", "AppTextKey::PersonalOrdersDetailItemsTitle", "AppTextKey::PersonalOrdersActionCancel", "AppTextKey::PersonalOrdersActionAcceptChange", "AppTextKey::PersonalOrdersActionKeepOrder", - "AppTextKey::PersonalOrdersActionMarkReceived", - "AppTextKey::PersonalOrdersActionReportIssue", - "AppTextKey::PersonalOrdersActionSendReceiptIssue", - "AppTextKey::PersonalOrdersActionCloseReceiptIssue", - "AppTextKey::PersonalOrdersReceiptIssueLabel", - "AppTextKey::PersonalOrdersReceiptIssuePlaceholder", "AppTextKey::PersonalOrdersRepeatDemandTitle", "AppTextKey::PersonalOrdersRepeatDemandActionEligible", "AppTextKey::PersonalOrdersRepeatDemandActionPartial", @@ -722,7 +696,6 @@ const REQUIRED_WINDOW_COPY_KEYS: &[&str] = &[ "AppTextKey::OrdersStatusScheduled", "AppTextKey::OrdersStatusInHandoff", "AppTextKey::OrdersStatusCompleted", - "AppTextKey::OrdersStatusRefunded", "AppTextKey::OrdersTableTitle", "AppTextKey::OrdersColumnOrder", "AppTextKey::OrdersColumnStatus", @@ -730,12 +703,6 @@ const REQUIRED_WINDOW_COPY_KEYS: &[&str] = &[ "AppTextKey::OrdersColumnPickup", "AppTextKey::OrdersColumnAction", "AppTextKey::OrdersActionReview", - "AppTextKey::OrdersActionPreparing", - "AppTextKey::OrdersActionReadyForPickup", - "AppTextKey::OrdersActionOutForDelivery", - "AppTextKey::OrdersActionMarkDelivered", - "AppTextKey::OrdersActionCancelFulfillment", - "AppTextKey::OrdersActionUpdateFulfillment", "AppTextKey::OrdersEmptyTitle", "AppTextKey::OrdersEmptyBody", "AppTextKey::OrdersEmptyNeedsActionTitle", @@ -756,38 +723,21 @@ const REQUIRED_WINDOW_COPY_KEYS: &[&str] = &[ "AppTextKey::TradeValidationReceiptTypeStateCheckpoint", "AppTextKey::TradeWorkflowAxisAgreement", "AppTextKey::TradeWorkflowAxisRevision", - "AppTextKey::TradeWorkflowAxisFulfillment", "AppTextKey::TradeWorkflowAxisInventory", - "AppTextKey::TradeWorkflowAxisPayment", - "AppTextKey::TradeWorkflowAxisReceipt", "AppTextKey::TradeWorkflowAxisSource", "AppTextKey::TradeWorkflowAgreementOrdered", "AppTextKey::TradeWorkflowAgreementConfirmed", "AppTextKey::TradeWorkflowAgreementDeclined", "AppTextKey::TradeWorkflowAgreementCancelled", - "AppTextKey::TradeWorkflowAgreementCompleted", "AppTextKey::TradeWorkflowAgreementNeedsReview", "AppTextKey::TradeWorkflowRevisionNone", "AppTextKey::TradeWorkflowRevisionChangeProposed", "AppTextKey::TradeWorkflowRevisionUpdated", "AppTextKey::TradeWorkflowRevisionKeptAsPlaced", - "AppTextKey::TradeWorkflowFulfillmentConfirmed", - "AppTextKey::TradeWorkflowFulfillmentPreparing", - "AppTextKey::TradeWorkflowFulfillmentReadyForPickup", - "AppTextKey::TradeWorkflowFulfillmentOutForDelivery", - "AppTextKey::TradeWorkflowFulfillmentDelivered", - "AppTextKey::TradeWorkflowFulfillmentCancelled", "AppTextKey::TradeWorkflowInventoryAvailable", "AppTextKey::TradeWorkflowInventoryReserved", "AppTextKey::TradeWorkflowInventorySoldOut", "AppTextKey::TradeWorkflowInventoryNeedsReview", - "AppTextKey::TradeWorkflowPaymentNotRecorded", - "AppTextKey::TradeWorkflowPaymentPending", - "AppTextKey::TradeWorkflowPaymentRecorded", - "AppTextKey::TradeWorkflowPaymentSettled", - "AppTextKey::TradeWorkflowPaymentNeedsReview", - "AppTextKey::TradeWorkflowReceiptReceived", - "AppTextKey::TradeWorkflowReceiptNeedsReview", "AppTextKey::TradeWorkflowProvenanceApp", "AppTextKey::TradeWorkflowProvenanceCli", "AppTextKey::TradeWorkflowProvenanceRelay", @@ -796,8 +746,6 @@ const REQUIRED_WINDOW_COPY_KEYS: &[&str] = &[ "AppTextKey::OrdersRecoverySectionTitle", "AppTextKey::OrdersRecoveryMissedPickupTitle", "AppTextKey::OrdersRecoveryMissedPickupBody", - "AppTextKey::OrdersRecoveryRefundFollowUpTitle", - "AppTextKey::OrdersRecoveryRefundFollowUpBody", "AppTextKey::OrdersRecoveryLastUpdatedLabel", "AppTextKey::OrdersRecoveryActionOpenFollowUp", "AppTextKey::OrdersRecoveryActionStartReview", diff --git a/crates/desktop/src/window.rs b/crates/desktop/src/window.rs @@ -29,8 +29,8 @@ use radroots_app_state::{ PackDayPrintRequest, derive_product_publish_blockers, }; use radroots_app_sync::{ - AppOrderReceiptOutcome, AppSyncRunStatus, SyncAggregateRef, SyncCheckpointState, SyncConflict, - SyncConflictKind, SyncConflictResolutionStatus, SyncConflictSeverity, + AppSyncRunStatus, SyncAggregateRef, SyncCheckpointState, SyncConflict, SyncConflictKind, + SyncConflictResolutionStatus, SyncConflictSeverity, }; use radroots_app_ui::{ APP_UI_THEME, AppCheckboxFieldSpec, AppFormFieldSpec, AppPillTabSpec, @@ -68,8 +68,8 @@ use radroots_app_view::{ FarmProfileRecord, FarmReadinessBlocker, FarmRulesProjection, FarmRulesReadiness, FarmSetupBlocker, FarmSetupDraft, FarmSummary, FarmTimingConflictKind, FarmerSection, FulfillmentWindowId, FulfillmentWindowRecord, FulfillmentWindowSummary, LoggedOutStartupPhase, - OrderDetailItemRow, OrderDetailProjection, OrderFulfillmentAction, OrderId, OrderListRow, - OrderPrimaryAction, OrderRecoveryProjection, OrderStatus, OrdersFilter, OrdersListRow, + OrderDetailItemRow, OrderDetailProjection, OrderId, OrderListRow, OrderPrimaryAction, + OrderRecoveryProjection, OrderStatus, OrdersFilter, OrdersListRow, PackDayBatchPrintFailureKind, PackDayBatchPrintStatus, PackDayExportBundle, PackDayExportStatus, PackDayHostHandoffKind, PackDayHostHandoffStatus, PackDayPackListRow, PackDayPrintFailureKind, PackDayPrintKind, PackDayPrintStatus, PackDayProductTotalRow, @@ -80,8 +80,7 @@ use radroots_app_view::{ ReminderLogEntryProjection, ReminderLogProjection, ReminderSurface, ReminderUrgency, RepeatDemandEligibility, RepeatDemandHandoffProjection, SettingsAccountProjection, ShellSection, TodayAgendaProjection, TodaySetupTaskKind, TradeAgreementStatus, - TradeEconomicsProjection, TradeFulfillmentStatus, TradeInventoryStatus, - TradePaymentDisplayStatus, TradeReceiptProjection, TradeRevisionStatus, + TradeEconomicsProjection, TradeInventoryStatus, TradeRevisionStatus, TradeValidationReceiptProjection, TradeValidationReceiptResult, TradeValidationReceiptType, TradeWorkflowProjection, TradeWorkflowSource, }; @@ -176,7 +175,6 @@ enum HomeFocusedView { BuyerProductDetail(PersonalSection), BuyerOrderReview, BuyerOrderDetail(OrderId), - BuyerReceiptIssue(OrderId), } #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] @@ -801,13 +799,6 @@ fn farmer_order_detail_focus_after_open( } } -fn buyer_receipt_issue_focus_after_submit( - runtime_changed: bool, - order_id: OrderId, -) -> Option<HomeFocusedView> { - runtime_changed.then_some(HomeFocusedView::BuyerOrderDetail(order_id)) -} - pub fn home_window_options(cx: &mut App) -> WindowOptions { let (launch_width_px, launch_height_px) = home_window_launch_size_px(); let (minimum_width_px, minimum_height_px) = home_window_minimum_size_px(); @@ -885,7 +876,6 @@ pub struct HomeView { farm_setup_form: Option<FarmSetupFormState>, personal_search: Option<PersonalSearchState>, buyer_order_review_form: Option<BuyerOrderReviewFormState>, - buyer_receipt_issue_form: Option<BuyerReceiptIssueFormState>, products_search: Option<ProductsSearchState>, products_stock_editor: Option<ProductsStockEditorState>, product_editor_form: Option<ProductEditorFormState>, @@ -937,7 +927,6 @@ struct HomeAutoFocusState { has_farm_setup_form: bool, has_personal_search_input: bool, has_buyer_order_review_form: bool, - has_buyer_receipt_issue_form: bool, has_products_search_input: bool, has_products_stock_editor: bool, has_product_editor_form: bool, @@ -954,7 +943,6 @@ enum HomeAutoFocusTarget { BuyerDetailBack, BuyerCartOpenOrderReview, BuyerOrderReviewNameInput, - BuyerReceiptIssueInput, BuyerOrderOpenFirst, BuyerOrderConfirmReplace, BuyerOrderRepeatDemand, @@ -973,7 +961,6 @@ enum HomeAutoFocusTarget { ProductsStockInput, ProductEditorTitleInput, OrdersRowOpenFirst, - OrdersDetailPublishFulfillmentFirst, } #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -1011,7 +998,6 @@ impl HomeView { farm_setup_form: None, personal_search: None, buyer_order_review_form: None, - buyer_receipt_issue_form: None, products_search: None, products_stock_editor: None, product_editor_form: None, @@ -1037,7 +1023,6 @@ impl HomeView { has_farm_setup_form: self.farm_setup_form.is_some(), has_personal_search_input: self.personal_search.is_some(), has_buyer_order_review_form: self.buyer_order_review_form.is_some(), - has_buyer_receipt_issue_form: self.buyer_receipt_issue_form.is_some(), has_products_search_input: self.products_search.is_some(), has_products_stock_editor: self.products_stock_editor.is_some(), has_product_editor_form: self.product_editor_form.is_some(), @@ -1110,12 +1095,6 @@ impl HomeView { .update(cx, |input, cx| input.focus(window, cx)); } } - HomeAutoFocusTarget::BuyerReceiptIssueInput => { - if let Some(form) = self.buyer_receipt_issue_form.as_ref() { - form.issue_input - .update(cx, |input, cx| input.focus(window, cx)); - } - } HomeAutoFocusTarget::BuyerOrderOpenFirst => { focus_button(window, ("buyer-order-open", 0_usize), cx); } @@ -1180,9 +1159,6 @@ impl HomeView { HomeAutoFocusTarget::OrdersRowOpenFirst => { focus_button(window, ("orders-row-open", 0_usize), cx); } - HomeAutoFocusTarget::OrdersDetailPublishFulfillmentFirst => { - focus_button(window, "orders-detail-publish-preparing", cx); - } } } @@ -1778,28 +1754,6 @@ impl HomeView { } } - fn sync_buyer_receipt_issue_form(&mut self, runtime_summary: &DesktopAppRuntimeSummary) { - let Some(form) = self.buyer_receipt_issue_form.as_ref() else { - return; - }; - - if home_stage(runtime_summary) != HomeStage::BuyerWorkspace - || selected_personal_section(runtime_summary) != PersonalSection::Orders - { - self.buyer_receipt_issue_form = None; - return; - } - - let Some(detail) = runtime_summary.personal_projection.orders.detail.as_ref() else { - self.buyer_receipt_issue_form = None; - return; - }; - - if detail.order_id != form.order_id || !buyer_receipt_actions_available(detail) { - self.buyer_receipt_issue_form = None; - } - } - fn sync_products_stock_editor(&mut self, runtime_summary: &DesktopAppRuntimeSummary) { let Some(editor) = self.products_stock_editor.as_ref() else { return; @@ -2138,25 +2092,6 @@ impl HomeView { } } - fn handle_buyer_receipt_issue_input_event( - &mut self, - state: &Entity<InputState>, - event: &InputEvent, - _: &mut Window, - cx: &mut Context<Self>, - ) { - if !matches!(event, InputEvent::Change) { - return; - } - - let Some(form) = self.buyer_receipt_issue_form.as_ref() else { - return; - }; - if form.issue_input == *state { - cx.notify(); - } - } - fn toggle_personal_search_fulfillment_method( &mut self, method: FarmOrderMethod, @@ -2418,13 +2353,6 @@ impl HomeView { ) else { return; }; - if self - .buyer_receipt_issue_form - .as_ref() - .is_some_and(|form| form.order_id != order_id) - { - self.buyer_receipt_issue_form = None; - } self.focused_view = Some(focused_view); cx.notify(); } @@ -2937,31 +2865,6 @@ impl HomeView { } } - fn publish_order_fulfillment_update( - &mut self, - order_id: OrderId, - action: OrderFulfillmentAction, - cx: &mut Context<Self>, - ) { - match self - .runtime - .publish_order_fulfillment_update(order_id, action) - { - Ok(true) => cx.notify(), - Ok(false) => {} - Err(runtime_error) => { - error!( - target: "orders", - event = "orders.fulfillment_publish_failed", - error = %runtime_error, - order_id = %order_id, - fulfillment_state = action.storage_key(), - "failed to publish order fulfillment update" - ); - } - } - } - fn cancel_buyer_order(&mut self, order_id: OrderId, cx: &mut Context<Self>) { match self.runtime.publish_buyer_order_cancel(order_id) { Ok(true) => cx.notify(), @@ -3010,98 +2913,6 @@ impl HomeView { } } - fn open_buyer_receipt_issue_form( - &mut self, - order_id: OrderId, - window: &mut Window, - cx: &mut Context<Self>, - ) { - self.buyer_receipt_issue_form = Some(BuyerReceiptIssueFormState::new(order_id, window, cx)); - self.focused_view = Some(HomeFocusedView::BuyerReceiptIssue(order_id)); - cx.notify(); - } - - fn close_buyer_receipt_issue_form(&mut self, cx: &mut Context<Self>) { - let order_id = self - .buyer_receipt_issue_form - .as_ref() - .map(|form| form.order_id); - let cleared = self.buyer_receipt_issue_form.take().is_some(); - let focus_changed = order_id - .map(|order_id| { - self.clear_focused_view_matching(HomeFocusedView::BuyerReceiptIssue(order_id)) - }) - .unwrap_or(false); - if focus_changed { - if let Some(order_id) = order_id { - self.focused_view = Some(HomeFocusedView::BuyerOrderDetail(order_id)); - } - } - if cleared || focus_changed { - cx.notify(); - } - } - - fn mark_buyer_order_received(&mut self, order_id: OrderId, cx: &mut Context<Self>) { - match self - .runtime - .publish_buyer_order_receipt(order_id, AppOrderReceiptOutcome::Received) - { - Ok(true) => { - if self - .buyer_receipt_issue_form - .as_ref() - .is_some_and(|form| form.order_id == order_id) - { - self.buyer_receipt_issue_form = None; - } - cx.notify(); - } - Ok(false) => {} - Err(runtime_error) => { - error!( - target: "personal_orders", - event = "buyer.order_receipt_failed", - error = %runtime_error, - order_id = %order_id, - "failed to mark buyer order received" - ); - } - } - } - - fn submit_buyer_order_issue_receipt(&mut self, order_id: OrderId, cx: &mut Context<Self>) { - let Some(issue) = self - .buyer_receipt_issue_form - .as_ref() - .filter(|form| form.order_id == order_id) - .and_then(|form| AppOrderReceiptOutcome::issue(form.issue_text(cx))) - else { - return; - }; - - match self.runtime.publish_buyer_order_receipt(order_id, issue) { - Ok(runtime_changed) => { - if let Some(focused_view) = - buyer_receipt_issue_focus_after_submit(runtime_changed, order_id) - { - self.buyer_receipt_issue_form = None; - self.focused_view = Some(focused_view); - cx.notify(); - } - } - Err(runtime_error) => { - error!( - target: "personal_orders", - event = "buyer.order_issue_receipt_failed", - error = %runtime_error, - order_id = %order_id, - "failed to report buyer order issue" - ); - } - } - } - fn start_order_recovery( &mut self, order_id: OrderId, @@ -3911,7 +3722,6 @@ impl HomeView { Some( buyer_order_detail_card( detail, - None, runtime .personal_projection .cart @@ -3926,19 +3736,6 @@ impl HomeView { .into_any_element(), ) } - HomeFocusedView::BuyerReceiptIssue(order_id) => { - let detail = runtime - .personal_projection - .orders - .detail - .as_ref() - .filter(|detail| detail.order_id == order_id)?; - let issue_form = self - .buyer_receipt_issue_form - .as_ref() - .filter(|form| form.order_id == order_id)?; - Some(buyer_receipt_issue_focused_view(detail, issue_form, cx)) - } HomeFocusedView::FarmSetup | HomeFocusedView::ProductEditor | HomeFocusedView::FarmerOrderDetail(_) => None, @@ -4311,8 +4108,7 @@ impl HomeView { } HomeFocusedView::BuyerProductDetail(_) | HomeFocusedView::BuyerOrderReview - | HomeFocusedView::BuyerOrderDetail(_) - | HomeFocusedView::BuyerReceiptIssue(_) => None, + | HomeFocusedView::BuyerOrderDetail(_) => None, } } @@ -4476,15 +4272,6 @@ impl HomeView { this.select_orders_filter(OrdersFilter::Completed, cx) }), cx, - )) - .child(choice_button( - "orders-filter-refunded", - app_shared_text(AppTextKey::OrdersStatusRefunded), - projection.query.filter == OrdersFilter::Refunded, - cx.listener(|this, _, _, cx| { - this.select_orders_filter(OrdersFilter::Refunded, cx) - }), - cx, )), )) .when(!projection.reminders.is_empty(), |this| { @@ -5094,33 +4881,6 @@ impl HomeView { on_close: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static, cx: &mut Context<Self>, ) -> AnyElement { - let fulfillment_actions = (!detail.fulfillment_actions.is_empty()).then(|| { - app_form_section( - app_shared_text(AppTextKey::TradeWorkflowAxisFulfillment), - app_cluster(APP_UI_THEME.foundation.spacing.tight_px).children( - detail - .fulfillment_actions - .iter() - .copied() - .map(|action| { - action_button_compact( - order_detail_fulfillment_action_id(action), - app_shared_text(order_fulfillment_action_label_key(action)), - cx.listener({ - let order_id = detail.order_id; - move |this, _, _, cx| { - this.publish_order_fulfillment_update(order_id, action, cx) - } - }), - cx, - ) - .into_any_element() - }) - .collect::<Vec<_>>(), - ), - ) - }); - app_focused_detail_view( app_shared_text(AppTextKey::OrdersDetailTitle), app_stack_v(APP_UI_THEME.shells.home_stack_gap_px) @@ -5169,10 +4929,7 @@ impl HomeView { this.child(home_body_text(app_shared_text(AppTextKey::ValueNone))) }), )) - .child(self.render_order_recovery_section(detail, cx)) - .when_some(fulfillment_actions, |this, fulfillment_actions| { - this.child(fulfillment_actions) - }), + .child(self.render_order_recovery_section(detail, cx)), text_button( "orders-detail-back", app_shared_text(AppTextKey::PersonalDetailBackAction), @@ -5204,17 +4961,6 @@ impl HomeView { .find(|record| record.kind == RecoveryKind::MissedPickup), cx, ), - ) - .child( - self.render_order_recovery_card( - detail.order_id, - RecoveryKind::RefundFollowUp, - detail - .recoveries - .iter() - .find(|record| record.kind == RecoveryKind::RefundFollowUp), - cx, - ), ), ) .into_any_element() @@ -5484,17 +5230,6 @@ impl HomeView { let order_id = row.order_id; move |this, _, _, cx| this.open_order_detail(order_id, cx) }), - cx.listener({ - let order_id = row.order_id; - let action = row - .primary_action - .and_then(OrderPrimaryAction::fulfillment_action); - move |this, _, _, cx| { - if let Some(action) = action { - this.publish_order_fulfillment_update(order_id, action, cx); - } - } - }), cx, ); @@ -5512,7 +5247,6 @@ impl Render for HomeView { self.sync_farm_setup_form(&runtime_summary, window, cx); self.sync_personal_search(&runtime_summary, window, cx); self.sync_buyer_order_review_form(&runtime_summary, window, cx); - self.sync_buyer_receipt_issue_form(&runtime_summary); self.sync_products_search(&runtime_summary, window, cx); self.sync_products_stock_editor(&runtime_summary); self.sync_product_editor_form(&runtime_summary, window, cx); @@ -5982,43 +5716,6 @@ fn sync_order_review_input( }); } -struct BuyerReceiptIssueFormState { - order_id: OrderId, - issue_input: Entity<InputState>, - _issue_subscription: Subscription, -} - -impl BuyerReceiptIssueFormState { - fn new(order_id: OrderId, window: &mut Window, cx: &mut Context<HomeView>) -> Self { - let issue_input = cx.new(|cx| { - InputState::new(window, cx) - .placeholder(app_shared_text( - AppTextKey::PersonalOrdersReceiptIssuePlaceholder, - )) - .default_value(String::new()) - }); - let issue_subscription = cx.subscribe_in( - &issue_input, - window, - HomeView::handle_buyer_receipt_issue_input_event, - ); - - Self { - order_id, - issue_input, - _issue_subscription: issue_subscription, - } - } - - fn issue_text(&self, cx: &App) -> String { - self.issue_input.read(cx).value().trim().to_owned() - } - - fn can_submit(&self, cx: &App) -> bool { - !self.issue_text(cx).is_empty() - } -} - struct ProductsSearchState { account_id: String, input: Entity<InputState>, @@ -9363,9 +9060,7 @@ fn buyer_auto_focus_target( .is_some_and(|confirmation| { confirmation.incoming_farm_display_name == detail.farm_display_name }); - if state.has_buyer_receipt_issue_form { - Some(HomeAutoFocusTarget::BuyerReceiptIssueInput) - } else if replace_confirmation { + if replace_confirmation { Some(HomeAutoFocusTarget::BuyerOrderConfirmReplace) } else if detail.repeat_demand.as_ref().is_some_and(|repeat_demand| { repeat_demand.eligibility != RepeatDemandEligibility::Unavailable @@ -9412,15 +9107,7 @@ fn farmer_auto_focus_target( } } FarmerSection::Orders if farmer_products_available(runtime) => { - if let Some(detail) = runtime.orders_projection.detail.as_ref() { - if !detail.fulfillment_actions.is_empty() { - Some(HomeAutoFocusTarget::OrdersDetailPublishFulfillmentFirst) - } else if !runtime.orders_projection.list.rows.is_empty() { - Some(HomeAutoFocusTarget::OrdersRowOpenFirst) - } else { - None - } - } else if !runtime.orders_projection.list.rows.is_empty() { + if !runtime.orders_projection.list.rows.is_empty() { Some(HomeAutoFocusTarget::OrdersRowOpenFirst) } else { None @@ -12183,27 +11870,10 @@ fn trade_workflow_detail_badge_strip(workflow: &TradeWorkflowProjection) -> AnyE ), ]; - if let Some(fulfillment) = workflow.fulfillment { - badges.push(trade_workflow_labeled_key_badge( - AppTextKey::TradeWorkflowAxisFulfillment, - trade_fulfillment_status_key(fulfillment), - )); - } - if let Some(receipt) = workflow.receipt.as_ref() { - badges.push(trade_workflow_labeled_key_badge( - AppTextKey::TradeWorkflowAxisReceipt, - buyer_receipt_status_key(receipt), - )); - } - badges.push(trade_workflow_labeled_key_badge( AppTextKey::TradeWorkflowAxisInventory, trade_inventory_status_key(workflow.inventory), )); - badges.push(trade_workflow_labeled_key_badge( - AppTextKey::TradeWorkflowAxisPayment, - trade_payment_display_status_key(workflow.payment), - )); if workflow.provenance.primary_source != TradeWorkflowSource::Unknown { badges.push(trade_workflow_labeled_key_badge( AppTextKey::TradeWorkflowAxisSource, @@ -12228,21 +11898,9 @@ fn trade_workflow_list_badge_strip(workflow: &TradeWorkflowProjection) -> AnyEle ))); } - if let Some(fulfillment) = workflow.fulfillment { - badges.push(trade_workflow_value_badge(trade_fulfillment_status_key( - fulfillment, - ))); - } - if let Some(receipt) = workflow.receipt.as_ref() { - badges.push(trade_workflow_value_badge(buyer_receipt_status_key( - receipt, - ))); - } - - badges.push(trade_workflow_labeled_key_badge( - AppTextKey::TradeWorkflowAxisPayment, - trade_payment_display_status_key(workflow.payment), - )); + badges.push(trade_workflow_value_badge(trade_inventory_status_key( + workflow.inventory, + ))); app_cluster(APP_UI_THEME.foundation.spacing.tight_px) .w_full() @@ -12256,16 +11914,9 @@ fn trade_workflow_status_stack(workflow: &TradeWorkflowProjection) -> AnyElement .child(trade_workflow_value_badge(trade_agreement_status_key( workflow.agreement, ))) - .when_some(workflow.fulfillment, |this, fulfillment| { - this.child(trade_workflow_value_badge(trade_fulfillment_status_key( - fulfillment, - ))) - }) - .when_some(workflow.receipt.as_ref(), |this, receipt| { - this.child(trade_workflow_value_badge(buyer_receipt_status_key( - receipt, - ))) - }) + .child(trade_workflow_value_badge(trade_inventory_status_key( + workflow.inventory, + ))) .into_any_element() } @@ -12284,7 +11935,6 @@ fn trade_agreement_status_key(status: TradeAgreementStatus) -> AppTextKey { TradeAgreementStatus::Confirmed => AppTextKey::TradeWorkflowAgreementConfirmed, TradeAgreementStatus::Declined => AppTextKey::TradeWorkflowAgreementDeclined, TradeAgreementStatus::Cancelled => AppTextKey::TradeWorkflowAgreementCancelled, - TradeAgreementStatus::Completed => AppTextKey::TradeWorkflowAgreementCompleted, TradeAgreementStatus::NeedsReview => AppTextKey::TradeWorkflowAgreementNeedsReview, } } @@ -12298,21 +11948,6 @@ fn trade_revision_status_key(status: TradeRevisionStatus) -> AppTextKey { } } -fn trade_fulfillment_status_key(status: TradeFulfillmentStatus) -> AppTextKey { - match status { - TradeFulfillmentStatus::Confirmed => AppTextKey::TradeWorkflowFulfillmentConfirmed, - TradeFulfillmentStatus::Preparing => AppTextKey::TradeWorkflowFulfillmentPreparing, - TradeFulfillmentStatus::ReadyForPickup => { - AppTextKey::TradeWorkflowFulfillmentReadyForPickup - } - TradeFulfillmentStatus::OutForDelivery => { - AppTextKey::TradeWorkflowFulfillmentOutForDelivery - } - TradeFulfillmentStatus::Delivered => AppTextKey::TradeWorkflowFulfillmentDelivered, - TradeFulfillmentStatus::Cancelled => AppTextKey::TradeWorkflowFulfillmentCancelled, - } -} - fn trade_inventory_status_key(status: TradeInventoryStatus) -> AppTextKey { match status { TradeInventoryStatus::Available => AppTextKey::TradeWorkflowInventoryAvailable, @@ -12322,16 +11957,6 @@ fn trade_inventory_status_key(status: TradeInventoryStatus) -> AppTextKey { } } -fn trade_payment_display_status_key(status: TradePaymentDisplayStatus) -> AppTextKey { - match status { - TradePaymentDisplayStatus::NotRecorded => AppTextKey::TradeWorkflowPaymentNotRecorded, - TradePaymentDisplayStatus::Pending => AppTextKey::TradeWorkflowPaymentPending, - TradePaymentDisplayStatus::Recorded => AppTextKey::TradeWorkflowPaymentRecorded, - TradePaymentDisplayStatus::Settled => AppTextKey::TradeWorkflowPaymentSettled, - TradePaymentDisplayStatus::NeedsReview => AppTextKey::TradeWorkflowPaymentNeedsReview, - } -} - fn trade_workflow_source_key(source: TradeWorkflowSource) -> AppTextKey { match source { TradeWorkflowSource::App => AppTextKey::TradeWorkflowProvenanceApp, @@ -12457,7 +12082,6 @@ fn buyer_orders_list_entry( fn buyer_order_detail_card( detail: &BuyerOrderDetailProjection, - issue_form: Option<&BuyerReceiptIssueFormState>, replace_confirmation: Option<&BuyerCartReplaceConfirmationProjection>, on_close: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static, cx: &mut Context<HomeView>, @@ -12490,9 +12114,6 @@ fn buyer_order_detail_card( order_optional_text(detail.order_note.as_deref()), ), ])) - .when_some(detail.workflow.receipt.as_ref(), |this, receipt| { - this.child(buyer_receipt_summary_section(receipt)) - }) .when(!detail.validation_receipts.is_empty(), |this| { this.child(validation_receipts_summary_section( &detail.validation_receipts, @@ -12548,57 +12169,16 @@ fn buyer_order_detail_card( ) }, ) - .when( - matches!( - detail.status, - BuyerOrderStatus::Placed | BuyerOrderStatus::Scheduled - ), - |this| { - this.child(action_button_compact( - "buyer-order-cancel", - app_shared_text(AppTextKey::PersonalOrdersActionCancel), - cx.listener({ - let order_id = detail.order_id; - move |this, _, _, cx| this.cancel_buyer_order(order_id, cx) - }), - cx, - )) - }, - ) - .when(buyer_receipt_actions_available(detail), |this| { - this.child( - app_stack_v(APP_UI_THEME.foundation.spacing.small_px) - .w_full() - .child( - app_cluster(APP_UI_THEME.foundation.spacing.small_px) - .w_full() - .child(action_button_primary( - "buyer-order-mark-received", - app_shared_text(AppTextKey::PersonalOrdersActionMarkReceived), - cx.listener({ - let order_id = detail.order_id; - move |this, _, _, cx| { - this.mark_buyer_order_received(order_id, cx) - } - }), - cx, - )) - .child(action_button_compact( - "buyer-order-report-issue", - app_shared_text(AppTextKey::PersonalOrdersActionReportIssue), - cx.listener({ - let order_id = detail.order_id; - move |this, _, window, cx| { - this.open_buyer_receipt_issue_form(order_id, window, cx) - } - }), - cx, - )), - ) - .when_some(issue_form, |this, form| { - this.child(buyer_receipt_issue_form_section(form, cx)) - }), - ) + .when(detail.status == BuyerOrderStatus::Placed, |this| { + this.child(action_button_compact( + "buyer-order-cancel", + app_shared_text(AppTextKey::PersonalOrdersActionCancel), + cx.listener({ + let order_id = detail.order_id; + move |this, _, _, cx| this.cancel_buyer_order(order_id, cx) + }), + cx, + )) }) .when_some(detail.repeat_demand.as_ref(), |this, repeat_demand| { this.child(app_form_section( @@ -12684,21 +12264,6 @@ fn buyer_order_detail_card( ) } -fn buyer_receipt_summary_section(receipt: &TradeReceiptProjection) -> AnyElement { - app_form_section( - app_shared_text(AppTextKey::PersonalOrdersDetailReceiptLabel), - app_stack_v(APP_UI_THEME.foundation.spacing.small_px) - .w_full() - .child(trade_workflow_value_badge(buyer_receipt_status_key( - receipt, - ))) - .when_some(receipt.issue.as_ref(), |this, issue| { - this.child(home_body_text(issue.clone())) - }), - ) - .into_any_element() -} - fn validation_receipts_summary_section( receipts: &[TradeValidationReceiptProjection], ) -> AnyElement { @@ -12740,120 +12305,6 @@ fn validation_receipt_summary_panel(receipt: &TradeValidationReceiptProjection) .into_any_element() } -fn buyer_receipt_issue_form_section( - form: &BuyerReceiptIssueFormState, - cx: &mut Context<HomeView>, -) -> AnyElement { - let order_id = form.order_id; - let submit_action = if form.can_submit(cx) { - action_button_primary( - "buyer-order-send-issue", - app_shared_text(AppTextKey::PersonalOrdersActionSendReceiptIssue), - cx.listener(move |this, _, _, cx| this.submit_buyer_order_issue_receipt(order_id, cx)), - cx, - ) - .into_any_element() - } else { - action_button_primary_disabled( - "buyer-order-send-issue", - app_shared_text(AppTextKey::PersonalOrdersActionSendReceiptIssue), - cx, - ) - .into_any_element() - }; - - app_form_section( - app_shared_text(AppTextKey::PersonalOrdersDetailReceiptLabel), - app_stack_v(APP_UI_THEME.foundation.spacing.small_px) - .w_full() - .child(app_form_input_text( - AppFormFieldSpec::new( - app_shared_text(AppTextKey::PersonalOrdersReceiptIssueLabel), - Option::<SharedString>::None, - ), - &form.issue_input, - false, - )) - .child( - app_cluster(APP_UI_THEME.foundation.spacing.small_px) - .w_full() - .child(submit_action) - .child(action_button_compact( - "buyer-order-close-issue", - app_shared_text(AppTextKey::PersonalOrdersActionCloseReceiptIssue), - cx.listener(|this, _, _, cx| this.close_buyer_receipt_issue_form(cx)), - cx, - )), - ), - ) - .into_any_element() -} - -fn buyer_receipt_issue_focused_view( - detail: &BuyerOrderDetailProjection, - form: &BuyerReceiptIssueFormState, - cx: &mut Context<HomeView>, -) -> AnyElement { - let order_id = form.order_id; - let submit_action = if form.can_submit(cx) { - action_button_primary( - "buyer-order-send-issue", - app_shared_text(AppTextKey::PersonalOrdersActionSendReceiptIssue), - cx.listener(move |this, _, _, cx| this.submit_buyer_order_issue_receipt(order_id, cx)), - cx, - ) - .into_any_element() - } else { - action_button_primary_disabled( - "buyer-order-send-issue", - app_shared_text(AppTextKey::PersonalOrdersActionSendReceiptIssue), - cx, - ) - .into_any_element() - }; - - app_focused_task_view( - app_shared_text(AppTextKey::PersonalOrdersActionReportIssue), - app_stack_v(APP_UI_THEME.shells.home_stack_gap_px) - .w_full() - .child(app_heading_section(detail.order_number.clone())) - .child(settings_badge_text(detail.farm_display_name.clone())) - .child(home_body_text(detail.fulfillment_summary.clone())) - .child(app_form_input_text( - AppFormFieldSpec::new( - app_shared_text(AppTextKey::PersonalOrdersReceiptIssueLabel), - Option::<SharedString>::None, - ), - &form.issue_input, - false, - )) - .child(submit_action), - text_button( - "buyer-order-close-issue", - app_shared_text(AppTextKey::PersonalDetailBackAction), - cx.listener(|this, _, _, cx| this.close_buyer_receipt_issue_form(cx)), - cx, - ), - ) -} - -fn buyer_receipt_actions_available(detail: &BuyerOrderDetailProjection) -> bool { - detail.workflow.receipt.is_none() - && detail.workflow.agreement == TradeAgreementStatus::Confirmed - && matches!( - detail.workflow.fulfillment, - Some(TradeFulfillmentStatus::ReadyForPickup | TradeFulfillmentStatus::Delivered) - ) -} - -fn buyer_receipt_status_key(receipt: &TradeReceiptProjection) -> AppTextKey { - if receipt.received { - AppTextKey::TradeWorkflowReceiptReceived - } else { - AppTextKey::TradeWorkflowReceiptNeedsReview - } -} - fn validation_receipt_result_key(result: TradeValidationReceiptResult) -> AppTextKey { match result { TradeValidationReceiptResult::Valid => AppTextKey::TradeValidationReceiptResultValid, @@ -12917,7 +12368,6 @@ fn buyer_orders_status_color(status: BuyerOrderStatus) -> u32 { } BuyerOrderStatus::Completed | BuyerOrderStatus::Declined - | BuyerOrderStatus::Refunded | BuyerOrderStatus::NeedsReview => APP_UI_THEME.components.app_status_indicator.offline, } } @@ -14109,11 +13559,6 @@ fn orders_table_header() -> impl IntoElement { false, )) .child(products_table_header_column( - AppTextKey::TradeWorkflowAxisPayment, - Some(128.0), - false, - )) - .child(products_table_header_column( AppTextKey::OrdersColumnWindow, Some(160.0), false, @@ -14158,9 +13603,6 @@ fn orders_table_row( .text_color(rgb(APP_UI_THEME.foundation.text.primary)) .child(trade_economics_total_text(&row.workflow.economics)), ) - .child(div().w(px(128.0)).child(trade_workflow_value_badge( - trade_payment_display_status_key(row.workflow.payment), - ))) .child( div() .w(px(160.0)) @@ -14184,7 +13626,6 @@ fn orders_table_action( index: usize, row: &OrdersListRow, on_review: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static, - on_publish_fulfillment: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static, cx: &App, ) -> AnyElement { match row.primary_action { @@ -14195,19 +13636,6 @@ fn orders_table_action( cx, ) .into_any_element(), - Some( - OrderPrimaryAction::PublishPreparing - | OrderPrimaryAction::PublishReadyForPickup - | OrderPrimaryAction::PublishOutForDelivery - | OrderPrimaryAction::PublishDelivered - | OrderPrimaryAction::PublishSellerCancelled, - ) => action_button_compact( - ("orders-row-action-publish-fulfillment", index), - app_shared_text(AppTextKey::OrdersActionUpdateFulfillment), - on_publish_fulfillment, - cx, - ) - .into_any_element(), None => div() .text_size(px(APP_UI_THEME.foundation.typography.utility_title_text_px)) .text_color(rgb(APP_UI_THEME.foundation.text.secondary)) @@ -14216,26 +13644,6 @@ fn orders_table_action( } } -fn order_detail_fulfillment_action_id(action: OrderFulfillmentAction) -> &'static str { - match action { - OrderFulfillmentAction::Preparing => "orders-detail-publish-preparing", - OrderFulfillmentAction::ReadyForPickup => "orders-detail-publish-ready-for-pickup", - OrderFulfillmentAction::OutForDelivery => "orders-detail-publish-out-for-delivery", - OrderFulfillmentAction::Delivered => "orders-detail-publish-delivered", - OrderFulfillmentAction::SellerCancelled => "orders-detail-publish-seller-cancelled", - } -} - -fn order_fulfillment_action_label_key(action: OrderFulfillmentAction) -> AppTextKey { - match action { - OrderFulfillmentAction::Preparing => AppTextKey::OrdersActionPreparing, - OrderFulfillmentAction::ReadyForPickup => AppTextKey::OrdersActionReadyForPickup, - OrderFulfillmentAction::OutForDelivery => AppTextKey::OrdersActionOutForDelivery, - OrderFulfillmentAction::Delivered => AppTextKey::OrdersActionMarkDelivered, - OrderFulfillmentAction::SellerCancelled => AppTextKey::OrdersActionCancelFulfillment, - } -} - fn orders_empty_state_card(filter: OrdersFilter) -> impl IntoElement { let (title_key, body_key) = if filter == OrdersFilter::NeedsAction { ( @@ -14255,24 +13663,21 @@ fn orders_status_color(status: OrderStatus) -> u32 { OrderStatus::Scheduled | OrderStatus::Packed => { APP_UI_THEME.components.app_status_indicator.online } - OrderStatus::Completed - | OrderStatus::Declined - | OrderStatus::Refunded - | OrderStatus::NeedsReview => APP_UI_THEME.components.app_status_indicator.offline, + OrderStatus::Completed | OrderStatus::Declined | OrderStatus::NeedsReview => { + APP_UI_THEME.components.app_status_indicator.offline + } } } fn order_recovery_title_key(kind: RecoveryKind) -> AppTextKey { match kind { RecoveryKind::MissedPickup => AppTextKey::OrdersRecoveryMissedPickupTitle, - RecoveryKind::RefundFollowUp => AppTextKey::OrdersRecoveryRefundFollowUpTitle, } } fn order_recovery_empty_body_key(kind: RecoveryKind) -> AppTextKey { match kind { RecoveryKind::MissedPickup => AppTextKey::OrdersRecoveryMissedPickupBody, - RecoveryKind::RefundFollowUp => AppTextKey::OrdersRecoveryRefundFollowUpBody, } } @@ -14304,7 +13709,6 @@ fn order_recovery_state_badge(state: RecoveryState) -> AnyElement { fn order_recovery_kind_index(kind: RecoveryKind) -> usize { match kind { RecoveryKind::MissedPickup => 0, - RecoveryKind::RefundFollowUp => 1, } } @@ -17205,8 +16609,7 @@ mod tests { about_conflict_review_body_key, about_manual_refresh_enabled, about_runtime_rows, about_status_rows, account_display_name, app_text, buyer_order_coordination_notice_forces_redraw, buyer_order_detail_focus_after_open, - buyer_orders_retry_action_visible, buyer_receipt_issue_focus_after_submit, - buyer_receipt_status_key, farm_setup_onboarding_card_spec, farmer_home_farm_state, + buyer_orders_retry_action_visible, farm_setup_onboarding_card_spec, farmer_home_farm_state, farmer_order_detail_focus_after_open, farmer_pack_day_available, home_auto_focus_target, home_content_scroll_id, home_saved_farm, home_sidebar_navigation_sections, home_stage, home_window_launch_size_px, home_window_minimum_size_px, @@ -17223,8 +16626,8 @@ mod tests { startup_issue_summary_text, startup_notice_text, startup_signer_preview_summary, startup_signer_preview_summary_for_connect_state, startup_signer_source_input_is_editable, startup_signer_status_spec, startup_signer_transport_failure_requires_notice, - trade_agreement_status_key, trade_fulfillment_status_key, trade_inventory_status_key, - trade_payment_display_status_key, trade_revision_status_key, trade_workflow_source_key, + trade_agreement_status_key, trade_inventory_status_key, trade_revision_status_key, + trade_workflow_source_key, }; use crate::runtime::{ DesktopAppRuntimeMetadataSummary, DesktopAppRuntimeSummary, DesktopAppSdkDiagnosticsState, @@ -17256,17 +16659,16 @@ mod tests { BuyerOrderStatus, BuyerOrdersListRow, FarmId, FarmOrderMethod, FarmReadiness, FarmSetupDraft, FarmSetupProjection, FarmSummary, FarmerSection, FulfillmentWindowId, FulfillmentWindowSummary, LoggedOutStartupPhase, LoggedOutStartupProjection, - OrderDetailProjection, OrderFulfillmentAction, OrderId, OrderPrimaryAction, OrderStatus, - OrdersListRow, PackDayBatchPrintArtifact, PackDayBatchPrintFailureKind, - PackDayExportArtifact, PackDayExportArtifactKind, PackDayExportBundle, - PackDayHostHandoffKind, PackDayHostHandoffStatus, PackDayPrintFailureKind, - PackDayPrintKind, PackDayPrintStatus, PackDayProductTotalRow, PackDayProjection, - PersonalSection, ProductId, ReminderDeadlineProjection, ReminderDeliveryState, ReminderId, - ReminderKind, ReminderSurface, ReminderUrgency, RepeatDemandEligibility, - RepeatDemandHandoffProjection, ShellSection, TodayAgendaProjection, TodaySetupTask, - TodaySetupTaskKind, TradeAgreementStatus, TradeEconomicsProjection, TradeFulfillmentStatus, - TradeInventoryStatus, TradePaymentDisplayStatus, TradeReceiptProjection, - TradeRevisionStatus, TradeWorkflowProjection, TradeWorkflowSource, + OrderDetailProjection, OrderId, OrderStatus, OrdersListRow, PackDayBatchPrintArtifact, + PackDayBatchPrintFailureKind, PackDayExportArtifact, PackDayExportArtifactKind, + PackDayExportBundle, PackDayHostHandoffKind, PackDayHostHandoffStatus, + PackDayPrintFailureKind, PackDayPrintKind, PackDayPrintStatus, PackDayProductTotalRow, + PackDayProjection, PersonalSection, ProductId, ReminderDeadlineProjection, + ReminderDeliveryState, ReminderId, ReminderKind, ReminderSurface, ReminderUrgency, + RepeatDemandEligibility, RepeatDemandHandoffProjection, ShellSection, + TodayAgendaProjection, TodaySetupTask, TodaySetupTaskKind, TradeAgreementStatus, + TradeEconomicsProjection, TradeInventoryStatus, TradeRevisionStatus, + TradeWorkflowProjection, TradeWorkflowSource, }; use radroots_identity::RadrootsIdentity; use std::{ @@ -17420,7 +16822,6 @@ mod tests { status: BuyerOrderStatus::Placed, items: Vec::new(), economics: TradeEconomicsProjection::default(), - payment: TradePaymentDisplayStatus::NotRecorded, workflow: TradeWorkflowProjection::from_buyer_order_status( order_id, BuyerOrderStatus::Placed, @@ -17466,11 +16867,9 @@ mod tests { pickup_location_label: None, items: Vec::new(), economics: TradeEconomicsProjection::default(), - payment: TradePaymentDisplayStatus::NotRecorded, workflow: TradeWorkflowProjection::from_order_status(order_id, OrderStatus::Scheduled), validation_receipts: Vec::new(), - primary_action: Some(OrderPrimaryAction::PublishPreparing), - fulfillment_actions: OrderFulfillmentAction::ALL.to_vec(), + primary_action: None, recoveries: Vec::new(), }); @@ -17485,20 +16884,6 @@ mod tests { } #[test] - fn buyer_receipt_issue_submit_returns_to_order_detail() { - let order_id = OrderId::new(); - - assert_eq!( - buyer_receipt_issue_focus_after_submit(true, order_id), - Some(HomeFocusedView::BuyerOrderDetail(order_id)) - ); - assert_eq!( - buyer_receipt_issue_focus_after_submit(false, order_id), - None - ); - } - - #[test] fn buyer_browse_refresh_failure_uses_typed_visible_notice() { let (mut view, paths, home_dir) = test_home_view("buyer_notice"); block_shared_local_events_database(&paths); @@ -17711,10 +17096,6 @@ mod tests { AppTextKey::TradeWorkflowAgreementCancelled, ), ( - TradeAgreementStatus::Completed, - AppTextKey::TradeWorkflowAgreementCompleted, - ), - ( TradeAgreementStatus::NeedsReview, AppTextKey::TradeWorkflowAgreementNeedsReview, ), @@ -17747,36 +17128,6 @@ mod tests { for (status, key) in [ ( - TradeFulfillmentStatus::Confirmed, - AppTextKey::TradeWorkflowFulfillmentConfirmed, - ), - ( - TradeFulfillmentStatus::Preparing, - AppTextKey::TradeWorkflowFulfillmentPreparing, - ), - ( - TradeFulfillmentStatus::ReadyForPickup, - AppTextKey::TradeWorkflowFulfillmentReadyForPickup, - ), - ( - TradeFulfillmentStatus::OutForDelivery, - AppTextKey::TradeWorkflowFulfillmentOutForDelivery, - ), - ( - TradeFulfillmentStatus::Delivered, - AppTextKey::TradeWorkflowFulfillmentDelivered, - ), - ( - TradeFulfillmentStatus::Cancelled, - AppTextKey::TradeWorkflowFulfillmentCancelled, - ), - ] { - assert_eq!(trade_fulfillment_status_key(status), key); - assert!(!app_text(key).is_empty()); - } - - for (status, key) in [ - ( TradeInventoryStatus::Available, AppTextKey::TradeWorkflowInventoryAvailable, ), @@ -17797,56 +17148,6 @@ mod tests { assert!(!app_text(key).is_empty()); } - for (status, key) in [ - ( - TradePaymentDisplayStatus::NotRecorded, - AppTextKey::TradeWorkflowPaymentNotRecorded, - ), - ( - TradePaymentDisplayStatus::Pending, - AppTextKey::TradeWorkflowPaymentPending, - ), - ( - TradePaymentDisplayStatus::Recorded, - AppTextKey::TradeWorkflowPaymentRecorded, - ), - ( - TradePaymentDisplayStatus::Settled, - AppTextKey::TradeWorkflowPaymentSettled, - ), - ( - TradePaymentDisplayStatus::NeedsReview, - AppTextKey::TradeWorkflowPaymentNeedsReview, - ), - ] { - assert_eq!(trade_payment_display_status_key(status), key); - assert!(!app_text(key).is_empty()); - } - - for (receipt, key) in [ - ( - TradeReceiptProjection { - event_id: "receipt-clean".to_owned(), - received: true, - issue: None, - received_at: 1_774_000_030, - }, - AppTextKey::TradeWorkflowReceiptReceived, - ), - ( - TradeReceiptProjection { - event_id: "receipt-issue".to_owned(), - received: false, - issue: Some("items need review".to_owned()), - received_at: 1_774_000_031, - }, - AppTextKey::TradeWorkflowReceiptNeedsReview, - ), - ] { - assert_eq!(buyer_receipt_status_key(&receipt), key); - assert!(!app_text(key).is_empty()); - } - for (source, key) in [ ( TradeWorkflowSource::App, @@ -17875,35 +17176,6 @@ mod tests { } #[test] - fn trade_payment_display_status_keys_cover_passive_states() { - for (status, key) in [ - ( - TradePaymentDisplayStatus::NotRecorded, - AppTextKey::TradeWorkflowPaymentNotRecorded, - ), - ( - TradePaymentDisplayStatus::Pending, - AppTextKey::TradeWorkflowPaymentPending, - ), - ( - TradePaymentDisplayStatus::Recorded, - AppTextKey::TradeWorkflowPaymentRecorded, - ), - ( - TradePaymentDisplayStatus::Settled, - AppTextKey::TradeWorkflowPaymentSettled, - ), - ( - TradePaymentDisplayStatus::NeedsReview, - AppTextKey::TradeWorkflowPaymentNeedsReview, - ), - ] { - assert_eq!(trade_payment_display_status_key(status), key); - assert!(!app_text(key).is_empty()); - } - } - - #[test] fn today_route_has_no_setup_onboarding_card() { assert!(farm_setup_onboarding_card_spec(HomeRoute::Today).is_none()); } @@ -18126,7 +17398,6 @@ mod tests { status: BuyerOrderStatus::Placed, items: Vec::new(), economics: TradeEconomicsProjection::default(), - payment: TradePaymentDisplayStatus::NotRecorded, workflow: TradeWorkflowProjection::from_buyer_order_status( order_id, BuyerOrderStatus::Placed, @@ -18145,16 +17416,6 @@ mod tests { home_auto_focus_target(&buyer_orders, HomeAutoFocusState::default()), Some(HomeAutoFocusTarget::BuyerOrderRepeatDemand) ); - assert_eq!( - home_auto_focus_target( - &buyer_orders, - HomeAutoFocusState { - has_buyer_receipt_issue_form: true, - ..HomeAutoFocusState::default() - }, - ), - Some(HomeAutoFocusTarget::BuyerReceiptIssueInput) - ); } #[test] @@ -18261,8 +17522,7 @@ mod tests { farmer_order_id, OrderStatus::Scheduled, ), - primary_action: Some(OrderPrimaryAction::PublishPreparing), - fulfillment_actions: OrderFulfillmentAction::ALL.to_vec(), + primary_action: None, }]; orders.orders_projection.detail = Some(OrderDetailProjection { order_id: farmer_order_id, @@ -18275,19 +17535,17 @@ mod tests { pickup_location_label: None, items: Vec::new(), economics: TradeEconomicsProjection::default(), - payment: TradePaymentDisplayStatus::NotRecorded, workflow: TradeWorkflowProjection::from_order_status( farmer_order_id, OrderStatus::Scheduled, ), validation_receipts: Vec::new(), - primary_action: Some(OrderPrimaryAction::PublishPreparing), - fulfillment_actions: OrderFulfillmentAction::ALL.to_vec(), + primary_action: None, recoveries: Vec::new(), }); assert_eq!( home_auto_focus_target(&orders, HomeAutoFocusState::default()), - Some(HomeAutoFocusTarget::OrdersDetailPublishFulfillmentFirst) + Some(HomeAutoFocusTarget::OrdersRowOpenFirst) ); } diff --git a/crates/i18n/src/keys.rs b/crates/i18n/src/keys.rs @@ -333,19 +333,11 @@ define_app_text_keys! { PersonalOrdersDetailStatusLabel => "personal.orders.detail.status.label", PersonalOrdersDetailFulfillmentLabel => "personal.orders.detail.fulfillment.label", PersonalOrdersDetailTotalLabel => "personal.orders.detail.total.label", - PersonalOrdersDetailPaymentLabel => "personal.orders.detail.payment.label", PersonalOrdersDetailNoteLabel => "personal.orders.detail.note.label", - PersonalOrdersDetailReceiptLabel => "personal.orders.detail.receipt.label", PersonalOrdersDetailItemsTitle => "personal.orders.detail.items.title", PersonalOrdersActionCancel => "personal.orders.action.cancel", PersonalOrdersActionAcceptChange => "personal.orders.action.accept_change", PersonalOrdersActionKeepOrder => "personal.orders.action.keep_order", - PersonalOrdersActionMarkReceived => "personal.orders.action.mark_received", - PersonalOrdersActionReportIssue => "personal.orders.action.report_issue", - PersonalOrdersActionSendReceiptIssue => "personal.orders.action.send_receipt_issue", - PersonalOrdersActionCloseReceiptIssue => "personal.orders.action.close_receipt_issue", - PersonalOrdersReceiptIssueLabel => "personal.orders.receipt.issue.label", - PersonalOrdersReceiptIssuePlaceholder => "personal.orders.receipt.issue.placeholder", PersonalOrdersRepeatDemandTitle => "personal.orders.repeat_demand.title", PersonalOrdersRepeatDemandActionEligible => "personal.orders.repeat_demand.action.eligible", PersonalOrdersRepeatDemandActionPartial => "personal.orders.repeat_demand.action.partial", @@ -360,7 +352,6 @@ define_app_text_keys! { PersonalOrdersStatusReady => "personal.orders.status.ready", PersonalOrdersStatusCompleted => "personal.orders.status.completed", PersonalOrdersStatusDeclined => "personal.orders.status.declined", - PersonalOrdersStatusRefunded => "personal.orders.status.refunded", PersonalOrdersStatusNeedsReview => "personal.orders.status.needs_review", PersonalCartSurfaceBody => "personal.cart.surface.body", PersonalOrderSummaryTitle => "personal.order_summary.title", @@ -398,7 +389,6 @@ define_app_text_keys! { OrdersStatusInHandoff => "orders.status.in_handoff", OrdersStatusCompleted => "orders.status.completed", OrdersStatusDeclined => "orders.status.declined", - OrdersStatusRefunded => "orders.status.refunded", OrdersStatusNeedsReview => "orders.status.needs_review", OrdersTableTitle => "orders.table.title", OrdersColumnOrder => "orders.column.order", @@ -407,12 +397,6 @@ define_app_text_keys! { OrdersColumnPickup => "orders.column.pickup", OrdersColumnAction => "orders.column.action", OrdersActionReview => "orders.action.review", - OrdersActionPreparing => "orders.action.preparing", - OrdersActionReadyForPickup => "orders.action.ready_for_pickup", - OrdersActionOutForDelivery => "orders.action.out_for_delivery", - OrdersActionMarkDelivered => "orders.action.mark_delivered", - OrdersActionCancelFulfillment => "orders.action.cancel_fulfillment", - OrdersActionUpdateFulfillment => "orders.action.update_fulfillment", OrdersEmptyTitle => "orders.empty.title", OrdersEmptyBody => "orders.empty.body", OrdersEmptyNeedsActionTitle => "orders.empty.needs_action.title", @@ -447,8 +431,6 @@ define_app_text_keys! { OrdersRecoverySectionTitle => "orders.recovery.section.title", OrdersRecoveryMissedPickupTitle => "orders.recovery.missed_pickup.title", OrdersRecoveryMissedPickupBody => "orders.recovery.missed_pickup.body", - OrdersRecoveryRefundFollowUpTitle => "orders.recovery.refund_follow_up.title", - OrdersRecoveryRefundFollowUpBody => "orders.recovery.refund_follow_up.body", OrdersRecoveryLastUpdatedLabel => "orders.recovery.last_updated.label", OrdersRecoveryActionOpenFollowUp => "orders.recovery.action.open_follow_up", OrdersRecoveryActionStartReview => "orders.recovery.action.start_review", @@ -459,38 +441,21 @@ define_app_text_keys! { OrdersRecoveryStateResolved => "orders.recovery.state.resolved", TradeWorkflowAxisAgreement => "trade.workflow.axis.agreement", TradeWorkflowAxisRevision => "trade.workflow.axis.revision", - TradeWorkflowAxisFulfillment => "trade.workflow.axis.fulfillment", TradeWorkflowAxisInventory => "trade.workflow.axis.inventory", - TradeWorkflowAxisPayment => "trade.workflow.axis.payment", - TradeWorkflowAxisReceipt => "trade.workflow.axis.receipt", TradeWorkflowAxisSource => "trade.workflow.axis.source", TradeWorkflowAgreementOrdered => "trade.workflow.agreement.ordered", TradeWorkflowAgreementConfirmed => "trade.workflow.agreement.confirmed", TradeWorkflowAgreementDeclined => "trade.workflow.agreement.declined", TradeWorkflowAgreementCancelled => "trade.workflow.agreement.cancelled", - TradeWorkflowAgreementCompleted => "trade.workflow.agreement.completed", TradeWorkflowAgreementNeedsReview => "trade.workflow.agreement.needs_review", TradeWorkflowRevisionNone => "trade.workflow.revision.none", TradeWorkflowRevisionChangeProposed => "trade.workflow.revision.change_proposed", TradeWorkflowRevisionUpdated => "trade.workflow.revision.updated", TradeWorkflowRevisionKeptAsPlaced => "trade.workflow.revision.kept_as_placed", - TradeWorkflowFulfillmentConfirmed => "trade.workflow.fulfillment.confirmed", - TradeWorkflowFulfillmentPreparing => "trade.workflow.fulfillment.preparing", - TradeWorkflowFulfillmentReadyForPickup => "trade.workflow.fulfillment.ready_for_pickup", - TradeWorkflowFulfillmentOutForDelivery => "trade.workflow.fulfillment.out_for_delivery", - TradeWorkflowFulfillmentDelivered => "trade.workflow.fulfillment.delivered", - TradeWorkflowFulfillmentCancelled => "trade.workflow.fulfillment.cancelled", TradeWorkflowInventoryAvailable => "trade.workflow.inventory.available", TradeWorkflowInventoryReserved => "trade.workflow.inventory.reserved", TradeWorkflowInventorySoldOut => "trade.workflow.inventory.sold_out", TradeWorkflowInventoryNeedsReview => "trade.workflow.inventory.needs_review", - TradeWorkflowPaymentNotRecorded => "trade.workflow.payment.not_recorded", - TradeWorkflowPaymentPending => "trade.workflow.payment.pending", - TradeWorkflowPaymentRecorded => "trade.workflow.payment.recorded", - TradeWorkflowPaymentSettled => "trade.workflow.payment.settled", - TradeWorkflowPaymentNeedsReview => "trade.workflow.payment.needs_review", - TradeWorkflowReceiptReceived => "trade.workflow.receipt.received", - TradeWorkflowReceiptNeedsReview => "trade.workflow.receipt.needs_review", TradeWorkflowProvenanceApp => "trade.workflow.provenance.app", TradeWorkflowProvenanceCli => "trade.workflow.provenance.cli", TradeWorkflowProvenanceRelay => "trade.workflow.provenance.relay", diff --git a/crates/i18n/src/lib.rs b/crates/i18n/src/lib.rs @@ -492,27 +492,6 @@ mod tests { app_text(AppTextKey::OrdersStatusNeedsReview), "Needs review" ); - assert_eq!( - app_text(AppTextKey::OrdersActionReadyForPickup), - "Ready for pickup" - ); - assert_eq!(app_text(AppTextKey::OrdersActionPreparing), "Preparing"); - assert_eq!( - app_text(AppTextKey::OrdersActionOutForDelivery), - "Out for delivery" - ); - assert_eq!( - app_text(AppTextKey::OrdersActionMarkDelivered), - "Mark delivered" - ); - assert_eq!( - app_text(AppTextKey::OrdersActionCancelFulfillment), - "Cancel fulfillment" - ); - assert_eq!( - app_text(AppTextKey::OrdersActionUpdateFulfillment), - "Update" - ); assert_eq!(app_text(AppTextKey::OrdersDetailTitle), "Order detail"); assert_eq!(app_text(AppTextKey::OrdersRecoverySectionTitle), "Recovery"); assert_eq!( @@ -520,14 +499,6 @@ mod tests { "Missed pickup" ); assert_eq!( - app_text(AppTextKey::OrdersRecoveryRefundFollowUpTitle), - "Payment status" - ); - assert_eq!( - app_text(AppTextKey::OrdersRecoveryRefundFollowUpBody), - "Track the recorded payment state for this order." - ); - assert_eq!( app_text(AppTextKey::OrdersRecoveryActionResolve), "Mark resolved" ); @@ -674,12 +645,6 @@ mod tests { app_text(AppTextKey::TradeWorkflowAxisAgreement), "Agreement" ); - assert_eq!( - app_text(AppTextKey::TradeWorkflowAxisFulfillment), - "Fulfillment" - ); - assert_eq!(app_text(AppTextKey::TradeWorkflowAxisPayment), "Payment"); - assert_eq!(app_text(AppTextKey::TradeWorkflowAxisReceipt), "Receipt"); assert_eq!(app_text(AppTextKey::TradeWorkflowAxisSource), "Source"); assert_eq!( app_text(AppTextKey::TradeWorkflowAgreementOrdered), @@ -702,31 +667,9 @@ mod tests { "Kept as placed" ); assert_eq!( - app_text(AppTextKey::TradeWorkflowFulfillmentReadyForPickup), - "Ready for pickup" - ); - assert_eq!( app_text(AppTextKey::TradeWorkflowInventoryReserved), "Reserved" ); - assert_eq!( - app_text(AppTextKey::TradeWorkflowPaymentNotRecorded), - "Not recorded" - ); - assert_eq!(app_text(AppTextKey::TradeWorkflowPaymentPending), "Pending"); - assert_eq!( - app_text(AppTextKey::TradeWorkflowPaymentRecorded), - "Recorded" - ); - assert_eq!(app_text(AppTextKey::TradeWorkflowPaymentSettled), "Settled"); - assert_eq!( - app_text(AppTextKey::TradeWorkflowReceiptReceived), - "Received" - ); - assert_eq!( - app_text(AppTextKey::TradeWorkflowReceiptNeedsReview), - "Needs review" - ); assert_eq!(app_text(AppTextKey::TradeWorkflowProvenanceCli), "CLI"); assert_eq!( app_text(AppTextKey::TradeWorkflowProvenanceLocalEvents), @@ -734,20 +677,6 @@ mod tests { ); } - #[test] - fn payment_workflow_copy_covers_passive_statuses() { - for (key, expected) in [ - (AppTextKey::TradeWorkflowPaymentNotRecorded, "Not recorded"), - (AppTextKey::TradeWorkflowPaymentPending, "Pending"), - (AppTextKey::TradeWorkflowPaymentRecorded, "Recorded"), - (AppTextKey::TradeWorkflowPaymentSettled, "Settled"), - (AppTextKey::TradeWorkflowPaymentNeedsReview, "Needs review"), - ] { - assert_eq!(app_text(key), expected); - } - } - - #[test] fn validation_receipt_copy_covers_passive_evidence() { for (key, expected) in [ (AppTextKey::TradeValidationReceiptSectionLabel, "Validation"), @@ -843,10 +772,6 @@ mod tests { "Declined" ); assert_eq!( - app_text(AppTextKey::PersonalOrdersStatusRefunded), - "Refunded" - ); - assert_eq!( app_text(AppTextKey::PersonalOrdersStatusNeedsReview), "Needs review" ); @@ -867,10 +792,6 @@ mod tests { "Order note" ); assert_eq!( - app_text(AppTextKey::PersonalOrdersDetailReceiptLabel), - "Receipt" - ); - assert_eq!( app_text(AppTextKey::PersonalOrdersActionCancel), "Cancel order" ); @@ -883,22 +804,6 @@ mod tests { "Keep order" ); assert_eq!( - app_text(AppTextKey::PersonalOrdersActionMarkReceived), - "Mark received" - ); - assert_eq!( - app_text(AppTextKey::PersonalOrdersActionReportIssue), - "Report issue" - ); - assert_eq!( - app_text(AppTextKey::PersonalOrdersActionSendReceiptIssue), - "Send update" - ); - assert_eq!( - app_text(AppTextKey::PersonalOrdersReceiptIssuePlaceholder), - "What needs review" - ); - assert_eq!( app_text(AppTextKey::PersonalOrdersRepeatDemandTitle), "Reorder" ); diff --git a/crates/runtime/src/lib.rs b/crates/runtime/src/lib.rs @@ -39,15 +39,13 @@ pub use sdk::{ APP_SDK_DEFAULT_COMMAND_QUEUE_CAPACITY, APP_SDK_STORAGE_DIR_NAME, AppSdkConfig, AppSdkDiagnostics, AppSdkEventStoreDiagnostics, AppSdkFarmPublishRequest, AppSdkIntegrityDiagnostics, AppSdkLifecycleState, AppSdkListingPublishRequest, - AppSdkOrderCancellationRequest, AppSdkOrderDecisionRequest, - AppSdkOrderFulfillmentUpdateRequest, AppSdkOrderReceiptRecordRequest, - AppSdkOrderRevisionDecisionRequest, AppSdkOrderRevisionProposalRequest, - AppSdkOrderSubmitRequest, AppSdkOutboxDiagnostics, AppSdkProjectionLifecycleState, - AppSdkProjectionLifecycleStatus, AppSdkRelayUrlPolicy, AppSdkRestorePreflightReceipt, - AppSdkRestorePreflightRequest, AppSdkRuntime, AppSdkRuntimeError, AppSdkRuntimeIssue, - AppSdkRuntimeStatus, AppSdkSqliteStoreDiagnostics, AppSdkStorageDiagnostics, - AppSdkStoragePaths, AppSdkSyncDiagnostics, AppSdkSyncEventStoreDiagnostics, - AppSdkSyncOutboxDiagnostics, AppSdkSyncRelayTargetDiagnostics, AppSdkWorkflowReceipt, - app_sdk_storage_root_from_data_root, + AppSdkOrderCancellationRequest, AppSdkOrderDecisionRequest, AppSdkOrderRevisionDecisionRequest, + AppSdkOrderRevisionProposalRequest, AppSdkOrderSubmitRequest, AppSdkOutboxDiagnostics, + AppSdkProjectionLifecycleState, AppSdkProjectionLifecycleStatus, AppSdkRelayUrlPolicy, + AppSdkRestorePreflightReceipt, AppSdkRestorePreflightRequest, AppSdkRuntime, + AppSdkRuntimeError, AppSdkRuntimeIssue, AppSdkRuntimeStatus, AppSdkSqliteStoreDiagnostics, + AppSdkStorageDiagnostics, AppSdkStoragePaths, AppSdkSyncDiagnostics, + AppSdkSyncEventStoreDiagnostics, AppSdkSyncOutboxDiagnostics, AppSdkSyncRelayTargetDiagnostics, + AppSdkWorkflowReceipt, app_sdk_storage_root_from_data_root, }; pub use startup::{AppStartupEvent, AppStartupEventMetadata, launch_startup_event}; diff --git a/crates/runtime/src/sdk.rs b/crates/runtime/src/sdk.rs @@ -17,9 +17,8 @@ use radroots_events::{ farm::RadrootsFarm, listing::RadrootsListing, order::{ - RadrootsOrderCancellation, RadrootsOrderDecision, RadrootsOrderFulfillmentUpdate, - RadrootsOrderReceipt, RadrootsOrderRequest, RadrootsOrderRevisionDecision, - RadrootsOrderRevisionProposal, + RadrootsOrderCancellation, RadrootsOrderDecision, RadrootsOrderRequest, + RadrootsOrderRevisionDecision, RadrootsOrderRevisionProposal, }, }; use radroots_nostr::prelude::RadrootsNostrKeys; @@ -27,17 +26,15 @@ use radroots_sdk::{ FARM_PUBLISH_OPERATION_KIND, FarmEnqueuePublishRequest, FarmEnqueueReceipt, IntegrityReceipt, IntegrityRequest, LISTING_PUBLISH_OPERATION_KIND, ListingEnqueuePublishRequest, ListingEnqueueReceipt, ORDER_CANCELLATION_OPERATION_KIND, ORDER_DECISION_OPERATION_KIND, - ORDER_FULFILLMENT_UPDATE_OPERATION_KIND, ORDER_RECEIPT_RECORD_OPERATION_KIND, ORDER_REVISION_DECISION_OPERATION_KIND, ORDER_REVISION_PROPOSAL_OPERATION_KIND, ORDER_SUBMIT_OPERATION_KIND, OrderCancellationEnqueueRequest, OrderCancellationReceipt, OrderDecisionEnqueueRequest, OrderDecisionReceipt, OrderEvidenceIngestRequest, - OrderFulfillmentUpdateEnqueueRequest, OrderFulfillmentUpdateReceipt, - OrderReceiptRecordEnqueueRequest, OrderReceiptRecordReceipt, OrderRequestEvidenceIngestRequest, - OrderRevisionDecisionEnqueueRequest, OrderRevisionDecisionReceipt, - OrderRevisionProposalEnqueueRequest, OrderRevisionProposalReceipt, OrderSubmitEnqueueRequest, - OrderSubmitReceipt, RadrootsSdk, RadrootsSdkError, RadrootsSdkStoragePaths, RestoreReceipt, - RestoreRequest, SdkBackupVerification, SdkRelayUrlPolicy as SdkRuntimeRelayUrlPolicy, - StorageStatusReceipt, StorageStatusRequest, SyncStatusReceipt, SyncStatusRequest, + OrderRequestEvidenceIngestRequest, OrderRevisionDecisionEnqueueRequest, + OrderRevisionDecisionReceipt, OrderRevisionProposalEnqueueRequest, + OrderRevisionProposalReceipt, OrderSubmitEnqueueRequest, OrderSubmitReceipt, RadrootsSdk, + RadrootsSdkError, RadrootsSdkStoragePaths, RestoreReceipt, RestoreRequest, + SdkBackupVerification, SdkRelayUrlPolicy as SdkRuntimeRelayUrlPolicy, StorageStatusReceipt, + StorageStatusRequest, SyncStatusReceipt, SyncStatusRequest, }; use radroots_sdk::{SdkMutationState, SdkRelayTargetPolicy}; use serde::Serialize; @@ -288,32 +285,6 @@ pub struct AppSdkOrderCancellationRequest { pub idempotency_key: Option<String>, } -pub struct AppSdkOrderFulfillmentUpdateRequest { - pub actor_account_id: String, - pub actor_pubkey: String, - pub signer_keys: RadrootsNostrKeys, - pub evidence_events: Vec<RadrootsNostrEvent>, - pub root_event: RadrootsNostrEventPtr, - pub previous_event: RadrootsNostrEventPtr, - pub fulfillment: RadrootsOrderFulfillmentUpdate, - pub target_relays: Vec<String>, - pub relay_url_policy: AppSdkRelayUrlPolicy, - pub idempotency_key: Option<String>, -} - -pub struct AppSdkOrderReceiptRecordRequest { - pub actor_account_id: String, - pub actor_pubkey: String, - pub signer_keys: RadrootsNostrKeys, - pub evidence_events: Vec<RadrootsNostrEvent>, - pub root_event: RadrootsNostrEventPtr, - pub previous_event: RadrootsNostrEventPtr, - pub receipt: RadrootsOrderReceipt, - pub target_relays: Vec<String>, - pub relay_url_policy: AppSdkRelayUrlPolicy, - pub idempotency_key: Option<String>, -} - #[derive(Clone, Debug, Eq, PartialEq)] pub struct AppSdkWorkflowReceipt { pub operation_kind: String, @@ -434,14 +405,6 @@ enum AppSdkWorkerCommand { AppSdkOrderCancellationRequest, mpsc::Sender<Result<AppSdkWorkflowReceipt, AppSdkRuntimeIssue>>, ), - EnqueueOrderFulfillmentUpdate( - AppSdkOrderFulfillmentUpdateRequest, - mpsc::Sender<Result<AppSdkWorkflowReceipt, AppSdkRuntimeIssue>>, - ), - EnqueueOrderReceiptRecord( - AppSdkOrderReceiptRecordRequest, - mpsc::Sender<Result<AppSdkWorkflowReceipt, AppSdkRuntimeIssue>>, - ), BeginProjectionRebuild( mpsc::Sender<Result<AppSdkProjectionLifecycleStatus, AppSdkRuntimeIssue>>, ), @@ -469,12 +432,6 @@ impl fmt::Debug for AppSdkWorkerCommand { formatter.write_str("EnqueueOrderRevisionDecision") } Self::EnqueueOrderCancellation(_, _) => formatter.write_str("EnqueueOrderCancellation"), - Self::EnqueueOrderFulfillmentUpdate(_, _) => { - formatter.write_str("EnqueueOrderFulfillmentUpdate") - } - Self::EnqueueOrderReceiptRecord(_, _) => { - formatter.write_str("EnqueueOrderReceiptRecord") - } Self::BeginProjectionRebuild(_) => formatter.write_str("BeginProjectionRebuild"), Self::CompleteProjectionRebuild(_) => formatter.write_str("CompleteProjectionRebuild"), } @@ -659,24 +616,6 @@ impl AppSdkRuntime { }) } - pub fn enqueue_order_fulfillment_update( - &self, - request: AppSdkOrderFulfillmentUpdateRequest, - ) -> Result<AppSdkWorkflowReceipt, AppSdkRuntimeError> { - self.run_command(|response_sender| { - AppSdkWorkerCommand::EnqueueOrderFulfillmentUpdate(request, response_sender) - }) - } - - pub fn enqueue_order_receipt_record( - &self, - request: AppSdkOrderReceiptRecordRequest, - ) -> Result<AppSdkWorkflowReceipt, AppSdkRuntimeError> { - self.run_command(|response_sender| { - AppSdkWorkerCommand::EnqueueOrderReceiptRecord(request, response_sender) - }) - } - pub fn begin_projection_rebuild( &self, ) -> Result<AppSdkProjectionLifecycleStatus, AppSdkRuntimeError> { @@ -1223,30 +1162,6 @@ fn run_app_sdk_worker( }; send_worker_result(&shared, response_sender, result); } - AppSdkWorkerCommand::EnqueueOrderFulfillmentUpdate(request, response_sender) => { - let result = if let Some(issue) = lifecycle_busy_issue(&shared) { - Err(issue) - } else { - match sdk.as_ref() { - Some(sdk) => { - enqueue_order_fulfillment_update_with_sdk(&runtime, sdk, request) - } - None => Err(runtime_unavailable_issue(&shared)), - } - }; - send_worker_result(&shared, response_sender, result); - } - AppSdkWorkerCommand::EnqueueOrderReceiptRecord(request, response_sender) => { - let result = if let Some(issue) = lifecycle_busy_issue(&shared) { - Err(issue) - } else { - match sdk.as_ref() { - Some(sdk) => enqueue_order_receipt_record_with_sdk(&runtime, sdk, request), - None => Err(runtime_unavailable_issue(&shared)), - } - }; - send_worker_result(&shared, response_sender, result); - } AppSdkWorkerCommand::BeginProjectionRebuild(response_sender) => { let result = match sdk.as_ref() { Some(_) => Ok(begin_projection_rebuild(&shared)), @@ -1363,20 +1278,6 @@ fn run_degraded_worker( Err(runtime_unavailable_issue(&shared)), ); } - AppSdkWorkerCommand::EnqueueOrderFulfillmentUpdate(_, response_sender) => { - send_worker_result( - &shared, - response_sender, - Err(runtime_unavailable_issue(&shared)), - ); - } - AppSdkWorkerCommand::EnqueueOrderReceiptRecord(_, response_sender) => { - send_worker_result( - &shared, - response_sender, - Err(runtime_unavailable_issue(&shared)), - ); - } AppSdkWorkerCommand::BeginProjectionRebuild(response_sender) => { send_worker_result( &shared, @@ -1534,7 +1435,7 @@ fn enqueue_order_submit_with_sdk( let receipt = runtime .block_on(sdk.orders().enqueue_submit(enqueue, &signer)) .map_err(|error| AppSdkRuntimeIssue::from_sdk_error(&error))?; - Ok(app_sdk_order_receipt(receipt, request.actor_pubkey)) + Ok(app_sdk_order_submit_ack(receipt, request.actor_pubkey)) } fn enqueue_order_decision_with_sdk( @@ -1679,74 +1580,6 @@ fn enqueue_order_cancellation_with_sdk( )) } -fn enqueue_order_fulfillment_update_with_sdk( - runtime: &tokio::runtime::Runtime, - sdk: &RadrootsSdk, - request: AppSdkOrderFulfillmentUpdateRequest, -) -> Result<AppSdkWorkflowReceipt, AppSdkRuntimeIssue> { - let actor = sdk_actor_context( - request.actor_pubkey.as_str(), - request.actor_account_id.as_str(), - RadrootsActorRole::Seller, - )?; - let signer = sdk_local_signer(request.signer_keys)?; - let target_relays = sdk_relay_targets(request.target_relays, request.relay_url_policy)?; - ingest_order_evidence_with_sdk(runtime, sdk, request.evidence_events)?; - let mut enqueue = OrderFulfillmentUpdateEnqueueRequest::new( - actor, - request.root_event, - request.previous_event, - request.fulfillment, - target_relays, - ); - if let Some(idempotency_key) = request.idempotency_key.as_deref() { - enqueue = enqueue - .try_with_idempotency_key(idempotency_key) - .map_err(|error| AppSdkRuntimeIssue::from_sdk_error(&error))?; - } - let receipt = runtime - .block_on(sdk.orders().enqueue_fulfillment_update(enqueue, &signer)) - .map_err(|error| AppSdkRuntimeIssue::from_sdk_error(&error))?; - Ok(app_sdk_order_fulfillment_update_receipt( - receipt, - request.actor_pubkey, - )) -} - -fn enqueue_order_receipt_record_with_sdk( - runtime: &tokio::runtime::Runtime, - sdk: &RadrootsSdk, - request: AppSdkOrderReceiptRecordRequest, -) -> Result<AppSdkWorkflowReceipt, AppSdkRuntimeIssue> { - let actor = sdk_actor_context( - request.actor_pubkey.as_str(), - request.actor_account_id.as_str(), - RadrootsActorRole::Buyer, - )?; - let signer = sdk_local_signer(request.signer_keys)?; - let target_relays = sdk_relay_targets(request.target_relays, request.relay_url_policy)?; - ingest_order_evidence_with_sdk(runtime, sdk, request.evidence_events)?; - let mut enqueue = OrderReceiptRecordEnqueueRequest::new( - actor, - request.root_event, - request.previous_event, - request.receipt, - target_relays, - ); - if let Some(idempotency_key) = request.idempotency_key.as_deref() { - enqueue = enqueue - .try_with_idempotency_key(idempotency_key) - .map_err(|error| AppSdkRuntimeIssue::from_sdk_error(&error))?; - } - let receipt = runtime - .block_on(sdk.orders().enqueue_receipt_record(enqueue, &signer)) - .map_err(|error| AppSdkRuntimeIssue::from_sdk_error(&error))?; - Ok(app_sdk_order_receipt_record_receipt( - receipt, - request.actor_pubkey, - )) -} - fn ingest_order_evidence_with_sdk( runtime: &tokio::runtime::Runtime, sdk: &RadrootsSdk, @@ -1821,7 +1654,7 @@ fn app_sdk_listing_receipt( } } -fn app_sdk_order_receipt( +fn app_sdk_order_submit_ack( receipt: OrderSubmitReceipt, actor_pubkey: String, ) -> AppSdkWorkflowReceipt { @@ -1901,38 +1734,6 @@ fn app_sdk_order_cancellation_receipt( } } -fn app_sdk_order_fulfillment_update_receipt( - receipt: OrderFulfillmentUpdateReceipt, - actor_pubkey: String, -) -> AppSdkWorkflowReceipt { - AppSdkWorkflowReceipt { - operation_kind: ORDER_FULFILLMENT_UPDATE_OPERATION_KIND.to_owned(), - expected_event_id: receipt.expected_event_id.as_str().to_owned(), - signed_event_id: receipt.signed_event_id.as_str().to_owned(), - outbox_operation_id: receipt.outbox_operation_id, - outbox_event_id: receipt.outbox_event_id, - state: sdk_mutation_state_key(receipt.state).to_owned(), - idempotency_digest_prefix: receipt.idempotency_digest_prefix, - actor_pubkey, - } -} - -fn app_sdk_order_receipt_record_receipt( - receipt: OrderReceiptRecordReceipt, - actor_pubkey: String, -) -> AppSdkWorkflowReceipt { - AppSdkWorkflowReceipt { - operation_kind: ORDER_RECEIPT_RECORD_OPERATION_KIND.to_owned(), - expected_event_id: receipt.expected_event_id.as_str().to_owned(), - signed_event_id: receipt.signed_event_id.as_str().to_owned(), - outbox_operation_id: receipt.outbox_operation_id, - outbox_event_id: receipt.outbox_event_id, - state: sdk_mutation_state_key(receipt.state).to_owned(), - idempotency_digest_prefix: receipt.idempotency_digest_prefix, - actor_pubkey, - } -} - fn sdk_mutation_state_key(state: SdkMutationState) -> &'static str { match state { SdkMutationState::StoredAndQueued => "enqueued", diff --git a/crates/state/src/lib.rs b/crates/state/src/lib.rs @@ -2273,8 +2273,7 @@ mod tests { ProductsSort, ReminderDeliveryState, ReminderFeedProjection, ReminderKind, ReminderLogEntryProjection, ReminderLogProjection, SelectedAccountProjection, SelectedSurfaceProjection, SettingsSection, ShellSection, TodayAgendaProjection, - TodaySetupTask, TodaySetupTaskKind, TradeEconomicsProjection, TradePaymentDisplayStatus, - TradeWorkflowProjection, + TodaySetupTask, TodaySetupTaskKind, TradeEconomicsProjection, TradeWorkflowProjection, }; struct FailingRepository; @@ -2506,7 +2505,6 @@ mod tests { currency_code: Some("USD".to_owned()), ..TradeEconomicsProjection::default() }; - let order_payment = TradePaymentDisplayStatus::NotRecorded; let orders_list = OrdersListProjection { summary: OrdersListSummary { total_orders: 2, @@ -2528,7 +2526,6 @@ mod tests { OrderStatus::NeedsAction, ), primary_action: Some(OrderPrimaryAction::Review), - fulfillment_actions: Vec::new(), }], }; let order_detail = OrderDetailProjection { @@ -2551,15 +2548,13 @@ mod tests { line_total_minor_units: Some(1300), }], economics: order_economics.clone(), - payment: order_payment, workflow: TradeWorkflowProjection::from_order_status( order_id, OrderStatus::NeedsAction, ) - .with_economics_and_payment(order_economics, order_payment), + .with_economics(order_economics), validation_receipts: Vec::new(), primary_action: Some(OrderPrimaryAction::Review), - fulfillment_actions: Vec::new(), recoveries: Vec::new(), }; let orders_reminders = ReminderFeedProjection { diff --git a/crates/store/migrations/0001_init.sql b/crates/store/migrations/0001_init.sql @@ -32,7 +32,7 @@ CREATE TABLE orders ( order_number TEXT NOT NULL, customer_display_name TEXT NOT NULL, status TEXT NOT NULL CHECK ( - status IN ('needs_action', 'scheduled', 'packed', 'completed', 'refunded') + status IN ('needs_action', 'scheduled', 'packed', 'completed') ), updated_at TEXT NOT NULL ); diff --git a/crates/store/migrations/0011_reminders_and_recovery.sql b/crates/store/migrations/0011_reminders_and_recovery.sql @@ -9,7 +9,6 @@ CREATE TABLE reminder_schedules ( 'fulfillment_window', 'order_action', 'missed_pickup_recovery', - 'refund_recovery', 'sync_impact' ) ), @@ -39,7 +38,6 @@ CREATE TABLE reminder_log_entries ( 'fulfillment_window', 'order_action', 'missed_pickup_recovery', - 'refund_recovery', 'sync_impact' ) ), @@ -57,7 +55,7 @@ CREATE TABLE order_recovery_records ( farm_id TEXT NOT NULL, order_id TEXT NOT NULL, recovery_kind TEXT NOT NULL CHECK ( - recovery_kind IN ('missed_pickup', 'refund_follow_up') + recovery_kind IN ('missed_pickup') ), recovery_state TEXT NOT NULL CHECK ( recovery_state IN ('open', 'in_review', 'resolved') diff --git a/crates/store/migrations/0020_declined_order_status.sql b/crates/store/migrations/0020_declined_order_status.sql @@ -16,7 +16,7 @@ CREATE TABLE orders ( order_number TEXT NOT NULL, customer_display_name TEXT NOT NULL, status TEXT NOT NULL CHECK ( - status IN ('needs_action', 'scheduled', 'packed', 'completed', 'declined', 'refunded') + status IN ('needs_action', 'scheduled', 'packed', 'completed', 'declined') ), updated_at TEXT NOT NULL, buyer_context_key TEXT, diff --git a/crates/store/migrations/0023_order_workflow_display_projection.sql b/crates/store/migrations/0023_order_workflow_display_projection.sql @@ -1,11 +1,6 @@ ALTER TABLE orders ADD COLUMN workflow_agreement TEXT NOT NULL DEFAULT 'ordered' CHECK ( - workflow_agreement IN ('ordered', 'confirmed', 'declined', 'cancelled', 'completed', 'needs_review') - ); - -ALTER TABLE orders - ADD COLUMN workflow_fulfillment TEXT CHECK ( - workflow_fulfillment IS NULL OR workflow_fulfillment IN ('confirmed', 'preparing', 'ready_for_pickup', 'out_for_delivery', 'delivered', 'cancelled') + workflow_agreement IN ('ordered', 'confirmed', 'declined', 'cancelled', 'needs_review') ); ALTER TABLE orders @@ -14,11 +9,6 @@ ALTER TABLE orders ); ALTER TABLE orders - ADD COLUMN workflow_payment TEXT NOT NULL DEFAULT 'not_recorded' CHECK ( - workflow_payment IN ('not_recorded', 'recorded', 'needs_review') - ); - -ALTER TABLE orders ADD COLUMN workflow_provenance_source TEXT NOT NULL DEFAULT 'unknown' CHECK ( workflow_provenance_source IN ('app', 'cli', 'relay', 'local_events', 'unknown') ); @@ -31,26 +21,14 @@ SET workflow_agreement = CASE status WHEN 'scheduled' THEN 'confirmed' WHEN 'packed' THEN 'confirmed' - WHEN 'completed' THEN 'completed' + WHEN 'completed' THEN 'confirmed' WHEN 'declined' THEN 'declined' - WHEN 'refunded' THEN 'needs_review' ELSE 'ordered' END, - workflow_fulfillment = CASE status - WHEN 'scheduled' THEN 'confirmed' - WHEN 'packed' THEN 'ready_for_pickup' - WHEN 'completed' THEN 'delivered' - WHEN 'declined' THEN 'cancelled' - ELSE NULL - END, workflow_inventory = CASE status WHEN 'scheduled' THEN 'reserved' WHEN 'packed' THEN 'reserved' WHEN 'completed' THEN 'reserved' WHEN 'declined' THEN 'available' ELSE 'needs_review' - END, - workflow_payment = CASE status - WHEN 'refunded' THEN 'needs_review' - ELSE 'not_recorded' END; diff --git a/crates/store/migrations/0024_order_workflow_agreement_states.sql b/crates/store/migrations/0024_order_workflow_agreement_states.sql @@ -0,0 +1 @@ +SELECT 1; diff --git a/crates/store/migrations/0024_order_workflow_payment_display_states.sql b/crates/store/migrations/0024_order_workflow_payment_display_states.sql @@ -1,196 +0,0 @@ -DROP INDEX IF EXISTS idx_order_lines_order_sort; -DROP INDEX IF EXISTS idx_buyer_order_coordination_context_state_updated_at; -DROP INDEX IF EXISTS idx_buyer_order_coordination_state_updated_at; -DROP INDEX IF EXISTS idx_orders_farm_status; -DROP INDEX IF EXISTS idx_orders_farm_window_status_updated_at; -DROP INDEX IF EXISTS idx_orders_buyer_context_updated_at; - -ALTER TABLE order_lines RENAME TO order_lines_payment_display_legacy; -ALTER TABLE buyer_order_coordination_records RENAME TO buyer_order_coordination_records_payment_display_legacy; -ALTER TABLE orders RENAME TO orders_payment_display_legacy; - -CREATE TABLE orders ( - id TEXT PRIMARY KEY NOT NULL, - farm_id TEXT NOT NULL REFERENCES farms(id) ON DELETE CASCADE, - fulfillment_window_id TEXT REFERENCES fulfillment_windows(id) ON DELETE SET NULL, - order_number TEXT NOT NULL, - customer_display_name TEXT NOT NULL, - status TEXT NOT NULL CHECK ( - status IN ('needs_action', 'scheduled', 'packed', 'completed', 'declined', 'refunded') - ), - updated_at TEXT NOT NULL, - buyer_context_key TEXT, - buyer_email TEXT NOT NULL DEFAULT '', - buyer_phone TEXT NOT NULL DEFAULT '', - buyer_order_note TEXT NOT NULL DEFAULT '', - workflow_revision TEXT NOT NULL DEFAULT 'none' CHECK ( - workflow_revision IN ('none', 'change_proposed', 'updated', 'kept_as_placed') - ), - workflow_agreement TEXT NOT NULL DEFAULT 'ordered' CHECK ( - workflow_agreement IN ('ordered', 'confirmed', 'declined', 'cancelled', 'completed', 'needs_review') - ), - workflow_fulfillment TEXT CHECK ( - workflow_fulfillment IS NULL OR workflow_fulfillment IN ('confirmed', 'preparing', 'ready_for_pickup', 'out_for_delivery', 'delivered', 'cancelled') - ), - workflow_inventory TEXT NOT NULL DEFAULT 'needs_review' CHECK ( - workflow_inventory IN ('available', 'reserved', 'sold_out', 'needs_review') - ), - workflow_payment TEXT NOT NULL DEFAULT 'not_recorded' CHECK ( - workflow_payment IN ('not_recorded', 'pending', 'recorded', 'settled', 'needs_review') - ), - workflow_provenance_source TEXT NOT NULL DEFAULT 'unknown' CHECK ( - workflow_provenance_source IN ('app', 'cli', 'relay', 'local_events', 'unknown') - ), - workflow_provenance_last_event_id TEXT -); - -INSERT INTO orders ( - id, - farm_id, - fulfillment_window_id, - order_number, - customer_display_name, - status, - updated_at, - buyer_context_key, - buyer_email, - buyer_phone, - buyer_order_note, - workflow_revision, - workflow_agreement, - workflow_fulfillment, - workflow_inventory, - workflow_payment, - workflow_provenance_source, - workflow_provenance_last_event_id -) -SELECT - id, - farm_id, - fulfillment_window_id, - order_number, - customer_display_name, - status, - updated_at, - buyer_context_key, - buyer_email, - buyer_phone, - buyer_order_note, - workflow_revision, - workflow_agreement, - workflow_fulfillment, - workflow_inventory, - workflow_payment, - workflow_provenance_source, - workflow_provenance_last_event_id -FROM orders_payment_display_legacy; - -CREATE TABLE order_lines ( - id TEXT PRIMARY KEY NOT NULL, - order_id TEXT NOT NULL REFERENCES orders(id) ON DELETE CASCADE, - title TEXT NOT NULL, - quantity_value INTEGER NOT NULL CHECK (quantity_value >= 0), - quantity_unit_label TEXT NOT NULL DEFAULT '', - quantity_display TEXT NOT NULL, - sort_index INTEGER NOT NULL DEFAULT 0, - listing_bin_id TEXT, - unit_price_minor_units INTEGER CHECK ( - unit_price_minor_units IS NULL OR unit_price_minor_units >= 0 - ), - price_currency TEXT NOT NULL DEFAULT 'USD', - farm_key TEXT, - listing_addr TEXT, - listing_event_id TEXT, - seller_pubkey TEXT, - listing_relays_json TEXT -); - -INSERT INTO order_lines ( - id, - order_id, - title, - quantity_value, - quantity_unit_label, - quantity_display, - sort_index, - listing_bin_id, - unit_price_minor_units, - price_currency, - farm_key, - listing_addr, - listing_event_id, - seller_pubkey, - listing_relays_json -) -SELECT - id, - order_id, - title, - quantity_value, - quantity_unit_label, - quantity_display, - sort_index, - listing_bin_id, - unit_price_minor_units, - price_currency, - farm_key, - listing_addr, - listing_event_id, - seller_pubkey, - listing_relays_json -FROM order_lines_payment_display_legacy; - -CREATE TABLE buyer_order_coordination_records ( - order_id TEXT PRIMARY KEY NOT NULL REFERENCES orders(id) ON DELETE CASCADE, - buyer_context_key TEXT NOT NULL, - record_id TEXT, - state TEXT NOT NULL CHECK (state IN ('pending', 'synced', 'failed')), - payload_json TEXT, - attempt_count INTEGER NOT NULL DEFAULT 0 CHECK (attempt_count >= 0), - last_error_message TEXT, - created_at TEXT NOT NULL, - updated_at TEXT NOT NULL, - synced_at TEXT -); - -INSERT INTO buyer_order_coordination_records ( - order_id, - buyer_context_key, - record_id, - state, - payload_json, - attempt_count, - last_error_message, - created_at, - updated_at, - synced_at -) -SELECT - order_id, - buyer_context_key, - record_id, - state, - payload_json, - attempt_count, - last_error_message, - created_at, - updated_at, - synced_at -FROM buyer_order_coordination_records_payment_display_legacy; - -CREATE INDEX idx_orders_farm_status ON orders(farm_id, status); -CREATE INDEX idx_orders_farm_window_status_updated_at - ON orders(farm_id, fulfillment_window_id, status, updated_at DESC, id DESC); -CREATE INDEX idx_orders_buyer_context_updated_at - ON orders(buyer_context_key, updated_at DESC, id DESC) - WHERE buyer_context_key IS NOT NULL AND trim(buyer_context_key) <> ''; -CREATE INDEX idx_order_lines_order_sort - ON order_lines(order_id, sort_index, id); -CREATE INDEX idx_buyer_order_coordination_context_state_updated_at - ON buyer_order_coordination_records(buyer_context_key, state, updated_at); -CREATE INDEX idx_buyer_order_coordination_state_updated_at - ON buyer_order_coordination_records(state, updated_at); - -DROP TABLE order_lines_payment_display_legacy; -DROP TABLE buyer_order_coordination_records_payment_display_legacy; -DROP TABLE orders_payment_display_legacy; diff --git a/crates/store/migrations/0025_order_receipt_display_projection.sql b/crates/store/migrations/0025_order_receipt_display_projection.sql @@ -1,204 +0,0 @@ -DROP INDEX IF EXISTS idx_order_lines_order_sort; -DROP INDEX IF EXISTS idx_buyer_order_coordination_context_state_updated_at; -DROP INDEX IF EXISTS idx_buyer_order_coordination_state_updated_at; -DROP INDEX IF EXISTS idx_orders_farm_status; -DROP INDEX IF EXISTS idx_orders_farm_window_status_updated_at; -DROP INDEX IF EXISTS idx_orders_buyer_context_updated_at; - -ALTER TABLE order_lines RENAME TO order_lines_receipt_display_legacy; -ALTER TABLE buyer_order_coordination_records RENAME TO buyer_order_coordination_records_receipt_display_legacy; -ALTER TABLE orders RENAME TO orders_receipt_display_legacy; - -CREATE TABLE orders ( - id TEXT PRIMARY KEY NOT NULL, - farm_id TEXT NOT NULL REFERENCES farms(id) ON DELETE CASCADE, - fulfillment_window_id TEXT REFERENCES fulfillment_windows(id) ON DELETE SET NULL, - order_number TEXT NOT NULL, - customer_display_name TEXT NOT NULL, - status TEXT NOT NULL CHECK ( - status IN ('needs_action', 'scheduled', 'packed', 'completed', 'declined', 'refunded', 'needs_review') - ), - updated_at TEXT NOT NULL, - buyer_context_key TEXT, - buyer_email TEXT NOT NULL DEFAULT '', - buyer_phone TEXT NOT NULL DEFAULT '', - buyer_order_note TEXT NOT NULL DEFAULT '', - workflow_revision TEXT NOT NULL DEFAULT 'none' CHECK ( - workflow_revision IN ('none', 'change_proposed', 'updated', 'kept_as_placed') - ), - workflow_agreement TEXT NOT NULL DEFAULT 'ordered' CHECK ( - workflow_agreement IN ('ordered', 'confirmed', 'declined', 'cancelled', 'completed', 'needs_review') - ), - workflow_fulfillment TEXT CHECK ( - workflow_fulfillment IS NULL OR workflow_fulfillment IN ('confirmed', 'preparing', 'ready_for_pickup', 'out_for_delivery', 'delivered', 'cancelled') - ), - workflow_receipt_event_id TEXT, - workflow_receipt_received INTEGER CHECK ( - workflow_receipt_received IS NULL OR workflow_receipt_received IN (0, 1) - ), - workflow_receipt_issue TEXT, - workflow_receipt_received_at INTEGER CHECK ( - workflow_receipt_received_at IS NULL OR workflow_receipt_received_at >= 0 - ), - workflow_inventory TEXT NOT NULL DEFAULT 'needs_review' CHECK ( - workflow_inventory IN ('available', 'reserved', 'sold_out', 'needs_review') - ), - workflow_payment TEXT NOT NULL DEFAULT 'not_recorded' CHECK ( - workflow_payment IN ('not_recorded', 'pending', 'recorded', 'settled', 'needs_review') - ), - workflow_provenance_source TEXT NOT NULL DEFAULT 'unknown' CHECK ( - workflow_provenance_source IN ('app', 'cli', 'relay', 'local_events', 'unknown') - ), - workflow_provenance_last_event_id TEXT -); - -INSERT INTO orders ( - id, - farm_id, - fulfillment_window_id, - order_number, - customer_display_name, - status, - updated_at, - buyer_context_key, - buyer_email, - buyer_phone, - buyer_order_note, - workflow_revision, - workflow_agreement, - workflow_fulfillment, - workflow_inventory, - workflow_payment, - workflow_provenance_source, - workflow_provenance_last_event_id -) -SELECT - id, - farm_id, - fulfillment_window_id, - order_number, - customer_display_name, - status, - updated_at, - buyer_context_key, - buyer_email, - buyer_phone, - buyer_order_note, - workflow_revision, - workflow_agreement, - workflow_fulfillment, - workflow_inventory, - workflow_payment, - workflow_provenance_source, - workflow_provenance_last_event_id -FROM orders_receipt_display_legacy; - -CREATE TABLE order_lines ( - id TEXT PRIMARY KEY NOT NULL, - order_id TEXT NOT NULL REFERENCES orders(id) ON DELETE CASCADE, - title TEXT NOT NULL, - quantity_value INTEGER NOT NULL CHECK (quantity_value >= 0), - quantity_unit_label TEXT NOT NULL DEFAULT '', - quantity_display TEXT NOT NULL, - sort_index INTEGER NOT NULL DEFAULT 0, - listing_bin_id TEXT, - unit_price_minor_units INTEGER CHECK ( - unit_price_minor_units IS NULL OR unit_price_minor_units >= 0 - ), - price_currency TEXT NOT NULL DEFAULT 'USD', - farm_key TEXT, - listing_addr TEXT, - listing_event_id TEXT, - seller_pubkey TEXT, - listing_relays_json TEXT -); - -INSERT INTO order_lines ( - id, - order_id, - title, - quantity_value, - quantity_unit_label, - quantity_display, - sort_index, - listing_bin_id, - unit_price_minor_units, - price_currency, - farm_key, - listing_addr, - listing_event_id, - seller_pubkey, - listing_relays_json -) -SELECT - id, - order_id, - title, - quantity_value, - quantity_unit_label, - quantity_display, - sort_index, - listing_bin_id, - unit_price_minor_units, - price_currency, - farm_key, - listing_addr, - listing_event_id, - seller_pubkey, - listing_relays_json -FROM order_lines_receipt_display_legacy; - -CREATE TABLE buyer_order_coordination_records ( - order_id TEXT PRIMARY KEY NOT NULL REFERENCES orders(id) ON DELETE CASCADE, - buyer_context_key TEXT NOT NULL, - record_id TEXT, - state TEXT NOT NULL CHECK (state IN ('pending', 'synced', 'failed')), - payload_json TEXT, - attempt_count INTEGER NOT NULL DEFAULT 0 CHECK (attempt_count >= 0), - last_error_message TEXT, - created_at TEXT NOT NULL, - updated_at TEXT NOT NULL, - synced_at TEXT -); - -INSERT INTO buyer_order_coordination_records ( - order_id, - buyer_context_key, - record_id, - state, - payload_json, - attempt_count, - last_error_message, - created_at, - updated_at, - synced_at -) -SELECT - order_id, - buyer_context_key, - record_id, - state, - payload_json, - attempt_count, - last_error_message, - created_at, - updated_at, - synced_at -FROM buyer_order_coordination_records_receipt_display_legacy; - -CREATE INDEX idx_orders_farm_status ON orders(farm_id, status); -CREATE INDEX idx_orders_farm_window_status_updated_at - ON orders(farm_id, fulfillment_window_id, status, updated_at DESC, id DESC); -CREATE INDEX idx_orders_buyer_context_updated_at - ON orders(buyer_context_key, updated_at DESC, id DESC) - WHERE buyer_context_key IS NOT NULL AND trim(buyer_context_key) <> ''; -CREATE INDEX idx_order_lines_order_sort - ON order_lines(order_id, sort_index, id); -CREATE INDEX idx_buyer_order_coordination_context_state_updated_at - ON buyer_order_coordination_records(buyer_context_key, state, updated_at); -CREATE INDEX idx_buyer_order_coordination_state_updated_at - ON buyer_order_coordination_records(state, updated_at); - -DROP TABLE order_lines_receipt_display_legacy; -DROP TABLE buyer_order_coordination_records_receipt_display_legacy; -DROP TABLE orders_receipt_display_legacy; diff --git a/crates/store/migrations/0025_order_workflow_agreement_projection.sql b/crates/store/migrations/0025_order_workflow_agreement_projection.sql @@ -0,0 +1,186 @@ +DROP INDEX IF EXISTS idx_order_lines_order_sort; +DROP INDEX IF EXISTS idx_buyer_order_coordination_context_state_updated_at; +DROP INDEX IF EXISTS idx_buyer_order_coordination_state_updated_at; +DROP INDEX IF EXISTS idx_orders_farm_status; +DROP INDEX IF EXISTS idx_orders_farm_window_status_updated_at; +DROP INDEX IF EXISTS idx_orders_buyer_context_updated_at; + +ALTER TABLE order_lines RENAME TO order_lines_agreement_legacy; +ALTER TABLE buyer_order_coordination_records RENAME TO buyer_order_coordination_records_agreement_legacy; +ALTER TABLE orders RENAME TO orders_agreement_legacy; + +CREATE TABLE orders ( + id TEXT PRIMARY KEY NOT NULL, + farm_id TEXT NOT NULL REFERENCES farms(id) ON DELETE CASCADE, + fulfillment_window_id TEXT REFERENCES fulfillment_windows(id) ON DELETE SET NULL, + order_number TEXT NOT NULL, + customer_display_name TEXT NOT NULL, + status TEXT NOT NULL CHECK ( + status IN ('needs_action', 'scheduled', 'packed', 'completed', 'declined', 'needs_review') + ), + updated_at TEXT NOT NULL, + buyer_context_key TEXT, + buyer_email TEXT NOT NULL DEFAULT '', + buyer_phone TEXT NOT NULL DEFAULT '', + buyer_order_note TEXT NOT NULL DEFAULT '', + workflow_revision TEXT NOT NULL DEFAULT 'none' CHECK ( + workflow_revision IN ('none', 'change_proposed', 'updated', 'kept_as_placed') + ), + workflow_agreement TEXT NOT NULL DEFAULT 'ordered' CHECK ( + workflow_agreement IN ('ordered', 'confirmed', 'declined', 'cancelled', 'needs_review') + ), + workflow_inventory TEXT NOT NULL DEFAULT 'needs_review' CHECK ( + workflow_inventory IN ('available', 'reserved', 'sold_out', 'needs_review') + ), + workflow_provenance_source TEXT NOT NULL DEFAULT 'unknown' CHECK ( + workflow_provenance_source IN ('app', 'cli', 'relay', 'local_events', 'unknown') + ), + workflow_provenance_last_event_id TEXT +); + +INSERT INTO orders ( + id, + farm_id, + fulfillment_window_id, + order_number, + customer_display_name, + status, + updated_at, + buyer_context_key, + buyer_email, + buyer_phone, + buyer_order_note, + workflow_revision, + workflow_agreement, + workflow_inventory, + workflow_provenance_source, + workflow_provenance_last_event_id +) +SELECT + id, + farm_id, + fulfillment_window_id, + order_number, + customer_display_name, + status, + updated_at, + buyer_context_key, + buyer_email, + buyer_phone, + buyer_order_note, + workflow_revision, + workflow_agreement, + workflow_inventory, + workflow_provenance_source, + workflow_provenance_last_event_id +FROM orders_agreement_legacy; + +CREATE TABLE order_lines ( + id TEXT PRIMARY KEY NOT NULL, + order_id TEXT NOT NULL REFERENCES orders(id) ON DELETE CASCADE, + title TEXT NOT NULL, + quantity_value INTEGER NOT NULL CHECK (quantity_value >= 0), + quantity_unit_label TEXT NOT NULL DEFAULT '', + quantity_display TEXT NOT NULL, + sort_index INTEGER NOT NULL DEFAULT 0, + listing_bin_id TEXT, + unit_price_minor_units INTEGER CHECK ( + unit_price_minor_units IS NULL OR unit_price_minor_units >= 0 + ), + price_currency TEXT NOT NULL DEFAULT 'USD', + farm_key TEXT, + listing_addr TEXT, + listing_event_id TEXT, + seller_pubkey TEXT, + listing_relays_json TEXT +); + +INSERT INTO order_lines ( + id, + order_id, + title, + quantity_value, + quantity_unit_label, + quantity_display, + sort_index, + listing_bin_id, + unit_price_minor_units, + price_currency, + farm_key, + listing_addr, + listing_event_id, + seller_pubkey, + listing_relays_json +) +SELECT + id, + order_id, + title, + quantity_value, + quantity_unit_label, + quantity_display, + sort_index, + listing_bin_id, + unit_price_minor_units, + price_currency, + farm_key, + listing_addr, + listing_event_id, + seller_pubkey, + listing_relays_json +FROM order_lines_agreement_legacy; + +CREATE TABLE buyer_order_coordination_records ( + order_id TEXT PRIMARY KEY NOT NULL REFERENCES orders(id) ON DELETE CASCADE, + buyer_context_key TEXT NOT NULL, + record_id TEXT, + state TEXT NOT NULL CHECK (state IN ('pending', 'synced', 'failed')), + payload_json TEXT, + attempt_count INTEGER NOT NULL DEFAULT 0 CHECK (attempt_count >= 0), + last_error_message TEXT, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL, + synced_at TEXT +); + +INSERT INTO buyer_order_coordination_records ( + order_id, + buyer_context_key, + record_id, + state, + payload_json, + attempt_count, + last_error_message, + created_at, + updated_at, + synced_at +) +SELECT + order_id, + buyer_context_key, + record_id, + state, + payload_json, + attempt_count, + last_error_message, + created_at, + updated_at, + synced_at +FROM buyer_order_coordination_records_agreement_legacy; + +CREATE INDEX idx_orders_farm_status ON orders(farm_id, status); +CREATE INDEX idx_orders_farm_window_status_updated_at + ON orders(farm_id, fulfillment_window_id, status, updated_at DESC, id DESC); +CREATE INDEX idx_orders_buyer_context_updated_at + ON orders(buyer_context_key, updated_at DESC, id DESC) + WHERE buyer_context_key IS NOT NULL AND trim(buyer_context_key) <> ''; +CREATE INDEX idx_order_lines_order_sort + ON order_lines(order_id, sort_index, id); +CREATE INDEX idx_buyer_order_coordination_context_state_updated_at + ON buyer_order_coordination_records(buyer_context_key, state, updated_at); +CREATE INDEX idx_buyer_order_coordination_state_updated_at + ON buyer_order_coordination_records(state, updated_at); + +DROP TABLE order_lines_agreement_legacy; +DROP TABLE buyer_order_coordination_records_agreement_legacy; +DROP TABLE orders_agreement_legacy; diff --git a/crates/store/src/interop.rs b/crates/store/src/interop.rs @@ -15,13 +15,9 @@ use radroots_events::{ KIND_LISTING_DRAFT as RADROOTS_KIND_LISTING_DRAFT, KIND_ORDER_CANCELLATION as RADROOTS_KIND_ORDER_CANCELLATION, KIND_ORDER_DECISION as RADROOTS_KIND_ORDER_DECISION, - KIND_ORDER_FULFILLMENT_UPDATE as RADROOTS_KIND_ORDER_FULFILLMENT_UPDATE, - KIND_ORDER_PAYMENT_RECORD as RADROOTS_KIND_ORDER_PAYMENT_RECORD, - KIND_ORDER_RECEIPT as RADROOTS_KIND_ORDER_RECEIPT, KIND_ORDER_REQUEST as RADROOTS_KIND_ORDER_REQUEST, KIND_ORDER_REVISION_DECISION as RADROOTS_KIND_ORDER_REVISION_DECISION, KIND_ORDER_REVISION_PROPOSAL as RADROOTS_KIND_ORDER_REVISION_PROPOSAL, - KIND_ORDER_SETTLEMENT_DECISION as RADROOTS_KIND_ORDER_SETTLEMENT_DECISION, KIND_TRADE_VALIDATION_RECEIPT, }, order::{ @@ -31,9 +27,8 @@ use radroots_events::{ }; use radroots_events_codec::order::{ order_cancellation_from_event, order_decision_from_event, order_event_context_from_tags, - order_fulfillment_update_from_event, order_payment_record_from_event, order_receipt_from_event, order_request_from_event, order_revision_decision_from_event, - order_revision_proposal_from_event, order_settlement_decision_from_event, + order_revision_proposal_from_event, }; use radroots_local_events::{ LocalEventRecord, LocalEventsStore, LocalRecordFamily, LocalRecordStatus, PublishOutboxStatus, @@ -41,10 +36,9 @@ use radroots_local_events::{ }; use radroots_sql_core::{SqlExecutor, SqliteExecutor}; use radroots_trade::order::{ - RadrootsOrderCancellationRecord, RadrootsOrderDecisionRecord, RadrootsOrderFulfillmentRecord, - RadrootsOrderPaymentEventRecord, RadrootsOrderProjection, RadrootsOrderReceiptRecord, + RadrootsOrderCancellationRecord, RadrootsOrderDecisionRecord, RadrootsOrderProjection, RadrootsOrderReductionInputs, RadrootsOrderRequestRecord, RadrootsOrderRevisionDecisionRecord, - RadrootsOrderRevisionProposalRecord, RadrootsOrderSettlementRecord, reduce_order_events, + RadrootsOrderRevisionProposalRecord, reduce_order_events, }; use radroots_trade::validation_receipt::{ RadrootsTradeValidationReceipt, RadrootsValidationReceiptTags, validation_receipt_from_event, @@ -66,21 +60,13 @@ const KIND_ORDER_DECISION: i64 = RADROOTS_KIND_ORDER_DECISION as i64; const KIND_ORDER_REVISION: i64 = RADROOTS_KIND_ORDER_REVISION_PROPOSAL as i64; const KIND_ORDER_REVISION_DECISION: i64 = RADROOTS_KIND_ORDER_REVISION_DECISION as i64; const KIND_ORDER_CANCEL: i64 = RADROOTS_KIND_ORDER_CANCELLATION as i64; -const KIND_ORDER_FULFILLMENT: i64 = RADROOTS_KIND_ORDER_FULFILLMENT_UPDATE as i64; -const KIND_ORDER_RECEIPT: i64 = RADROOTS_KIND_ORDER_RECEIPT as i64; -const KIND_ORDER_PAYMENT: i64 = RADROOTS_KIND_ORDER_PAYMENT_RECORD as i64; -const KIND_ORDER_SETTLEMENT: i64 = RADROOTS_KIND_ORDER_SETTLEMENT_DECISION as i64; const KIND_VALIDATION_RECEIPT: i64 = KIND_TRADE_VALIDATION_RECEIPT as i64; -const ACTIVE_ORDER_EVENT_KINDS: [i64; 9] = [ +const ACTIVE_ORDER_EVENT_KINDS: [i64; 5] = [ KIND_ORDER_REQUEST, KIND_ORDER_DECISION, KIND_ORDER_REVISION, KIND_ORDER_REVISION_DECISION, KIND_ORDER_CANCEL, - KIND_ORDER_FULFILLMENT, - KIND_ORDER_RECEIPT, - KIND_ORDER_PAYMENT, - KIND_ORDER_SETTLEMENT, ]; #[derive(Clone, Debug, Default, Eq, PartialEq)] @@ -1202,11 +1188,7 @@ impl<'a> AppLocalInteropRepository<'a> { decisions: buckets.decisions, revision_proposals: buckets.revision_proposals, revision_decisions: buckets.revision_decisions, - fulfillments: buckets.fulfillments, cancellations: buckets.cancellations, - receipts: buckets.receipts, - payments: buckets.payments, - settlements: buckets.settlements, }, ); let request_payload = projection.request_event_id.as_deref().and_then(|event_id| { @@ -1312,15 +1294,9 @@ impl<'a> AppLocalInteropRepository<'a> { SET status = ?2, workflow_revision = ?3, workflow_agreement = ?4, - workflow_fulfillment = ?5, - workflow_receipt_event_id = ?6, - workflow_receipt_received = ?7, - workflow_receipt_issue = ?8, - workflow_receipt_received_at = ?9, - workflow_inventory = ?10, - workflow_payment = ?11, - workflow_provenance_source = ?12, - workflow_provenance_last_event_id = ?13, + workflow_inventory = ?5, + workflow_provenance_source = ?6, + workflow_provenance_last_event_id = ?7, updated_at = strftime('%Y-%m-%dT%H:%M:%SZ', 'now') WHERE id = ?1", params![ @@ -1328,31 +1304,7 @@ impl<'a> AppLocalInteropRepository<'a> { status.storage_key(), workflow.revision.storage_key(), workflow.agreement.storage_key(), - workflow - .fulfillment - .map(|fulfillment| fulfillment.storage_key()), - workflow - .receipt - .as_ref() - .map(|receipt| receipt.event_id.as_str()), - workflow - .receipt - .as_ref() - .map(|receipt| if receipt.received { 1_i64 } else { 0_i64 }), - workflow - .receipt - .as_ref() - .and_then(|receipt| receipt.issue.as_deref()), - workflow - .receipt - .as_ref() - .map(|receipt| i64::try_from(receipt.received_at)) - .transpose() - .map_err(|_| AppSqliteError::InvalidProjection { - reason: "receipt timestamp must fit sqlite integer", - })?, workflow.inventory.storage_key(), - workflow.payment.storage_key(), workflow.provenance.primary_source.storage_key(), workflow.provenance.last_event_id.as_deref() ], @@ -2729,11 +2681,7 @@ enum ActiveOrderEvidence { Decision(RadrootsOrderDecisionRecord), RevisionProposal(RadrootsOrderRevisionProposalRecord), RevisionDecision(RadrootsOrderRevisionDecisionRecord), - Fulfillment(RadrootsOrderFulfillmentRecord), Cancellation(RadrootsOrderCancellationRecord), - Receipt(RadrootsOrderReceiptRecord), - Payment(RadrootsOrderPaymentEventRecord), - Settlement(RadrootsOrderSettlementRecord), } impl ActiveOrderEvidence { @@ -2743,11 +2691,7 @@ impl ActiveOrderEvidence { Self::Decision(record) => record.event_id.as_str(), Self::RevisionProposal(record) => record.event_id.as_str(), Self::RevisionDecision(record) => record.event_id.as_str(), - Self::Fulfillment(record) => record.event_id.as_str(), Self::Cancellation(record) => record.event_id.as_str(), - Self::Receipt(record) => record.event_id.as_str(), - Self::Payment(record) => record.event_id.as_str(), - Self::Settlement(record) => record.event_id.as_str(), } } @@ -2757,11 +2701,7 @@ impl ActiveOrderEvidence { Self::Decision(record) => record.payload.order_id.as_str(), Self::RevisionProposal(record) => record.payload.order_id.as_str(), Self::RevisionDecision(record) => record.payload.order_id.as_str(), - Self::Fulfillment(record) => record.payload.order_id.as_str(), Self::Cancellation(record) => record.payload.order_id.as_str(), - Self::Receipt(record) => record.payload.order_id.as_str(), - Self::Payment(record) => record.payload.order_id.as_str(), - Self::Settlement(record) => record.payload.order_id.as_str(), } } @@ -2783,26 +2723,10 @@ impl ActiveOrderEvidence { record.payload.order_id.as_str(), record.payload.buyer_pubkey.as_str(), ), - Self::Fulfillment(record) => ( - record.payload.order_id.as_str(), - record.payload.buyer_pubkey.as_str(), - ), Self::Cancellation(record) => ( record.payload.order_id.as_str(), record.payload.buyer_pubkey.as_str(), ), - Self::Receipt(record) => ( - record.payload.order_id.as_str(), - record.payload.buyer_pubkey.as_str(), - ), - Self::Payment(record) => ( - record.payload.order_id.as_str(), - record.payload.buyer_pubkey.as_str(), - ), - Self::Settlement(record) => ( - record.payload.order_id.as_str(), - record.payload.buyer_pubkey.as_str(), - ), } } } @@ -2813,11 +2737,7 @@ struct ActiveOrderEvidenceBuckets { decisions: Vec<RadrootsOrderDecisionRecord>, revision_proposals: Vec<RadrootsOrderRevisionProposalRecord>, revision_decisions: Vec<RadrootsOrderRevisionDecisionRecord>, - fulfillments: Vec<RadrootsOrderFulfillmentRecord>, cancellations: Vec<RadrootsOrderCancellationRecord>, - receipts: Vec<RadrootsOrderReceiptRecord>, - payments: Vec<RadrootsOrderPaymentEventRecord>, - settlements: Vec<RadrootsOrderSettlementRecord>, } impl ActiveOrderEvidenceBuckets { @@ -2833,11 +2753,7 @@ impl ActiveOrderEvidenceBuckets { ActiveOrderEvidence::RevisionDecision(record) => { buckets.revision_decisions.push(record); } - ActiveOrderEvidence::Fulfillment(record) => buckets.fulfillments.push(record), ActiveOrderEvidence::Cancellation(record) => buckets.cancellations.push(record), - ActiveOrderEvidence::Receipt(record) => buckets.receipts.push(record), - ActiveOrderEvidence::Payment(record) => buckets.payments.push(record), - ActiveOrderEvidence::Settlement(record) => buckets.settlements.push(record), } } buckets @@ -3064,60 +2980,6 @@ fn active_order_evidence_from_event(event: &RadrootsNostrEvent) -> Option<Active }, )) } - KIND_ORDER_FULFILLMENT => { - let envelope = order_fulfillment_update_from_event(event).ok()?; - let context = order_event_context_from_tags(envelope.message_type, &event.tags).ok()?; - Some(ActiveOrderEvidence::Fulfillment( - RadrootsOrderFulfillmentRecord { - event_id: active_event_id(event)?, - author_pubkey: active_author_pubkey(event)?, - counterparty_pubkey: context.counterparty_pubkey, - root_event_id: context.root_event_id?, - prev_event_id: context.prev_event_id?, - payload: envelope.payload, - }, - )) - } - KIND_ORDER_RECEIPT => { - let envelope = order_receipt_from_event(event).ok()?; - let context = order_event_context_from_tags(envelope.message_type, &event.tags).ok()?; - Some(ActiveOrderEvidence::Receipt(RadrootsOrderReceiptRecord { - event_id: active_event_id(event)?, - author_pubkey: active_author_pubkey(event)?, - counterparty_pubkey: context.counterparty_pubkey, - root_event_id: context.root_event_id?, - prev_event_id: context.prev_event_id?, - payload: envelope.payload, - })) - } - KIND_ORDER_PAYMENT => { - let envelope = order_payment_record_from_event(event).ok()?; - let context = order_event_context_from_tags(envelope.message_type, &event.tags).ok()?; - Some(ActiveOrderEvidence::Payment( - RadrootsOrderPaymentEventRecord { - event_id: active_event_id(event)?, - author_pubkey: active_author_pubkey(event)?, - counterparty_pubkey: context.counterparty_pubkey, - root_event_id: context.root_event_id?, - prev_event_id: context.prev_event_id?, - payload: envelope.payload, - }, - )) - } - KIND_ORDER_SETTLEMENT => { - let envelope = order_settlement_decision_from_event(event).ok()?; - let context = order_event_context_from_tags(envelope.message_type, &event.tags).ok()?; - Some(ActiveOrderEvidence::Settlement( - RadrootsOrderSettlementRecord { - event_id: active_event_id(event)?, - author_pubkey: active_author_pubkey(event)?, - counterparty_pubkey: context.counterparty_pubkey, - root_event_id: context.root_event_id?, - prev_event_id: context.prev_event_id?, - payload: envelope.payload, - }, - )) - } _ => None, } } @@ -3321,7 +3183,11 @@ fn active_order_revision_status( revision_proposals: &[RadrootsOrderRevisionProposalRecord], revision_decisions: &[RadrootsOrderRevisionDecisionRecord], ) -> TradeRevisionStatus { - let Some(mut parent_event_id) = projection.decision_event_id.clone() else { + let Some(mut parent_event_id) = projection + .decision_event_id + .clone() + .or_else(|| projection.request_event_id.clone()) + else { return TradeRevisionStatus::None; }; let mut status = TradeRevisionStatus::None; @@ -3865,11 +3731,11 @@ mod tests { use std::collections::BTreeSet; use radroots_app_view::{ - BuyerContext, BuyerOrderStatus, FarmId, FarmOrderMethod, OrderFulfillmentAction, OrderId, - OrderPrimaryAction, OrderStatus, OrdersFilter, OrdersScreenQueryState, - ProductAvailabilityState, ProductId, TradeFulfillmentStatus, TradeInventoryStatus, - TradePaymentDisplayStatus, TradeRevisionStatus, TradeValidationReceiptProofSystem, - TradeValidationReceiptResult, TradeValidationReceiptType, TradeWorkflowSource, + BuyerContext, BuyerOrderStatus, FarmId, FarmOrderMethod, OrderId, OrderStatus, + OrdersFilter, OrdersScreenQueryState, ProductAvailabilityState, ProductId, + TradeAgreementStatus, TradeInventoryStatus, TradeRevisionStatus, + TradeValidationReceiptProofSystem, TradeValidationReceiptResult, + TradeValidationReceiptType, TradeWorkflowSource, }; use radroots_core::{ RadrootsCoreCurrency, RadrootsCoreDecimal, RadrootsCoreMoney, RadrootsCoreUnit, @@ -3877,29 +3743,21 @@ mod tests { use radroots_events::{ RadrootsNostrEvent, RadrootsNostrEventPtr, ids::{ - RadrootsEconomicsDigest, RadrootsEventId, RadrootsInventoryBinId, - RadrootsListingAddress, RadrootsOrderId, RadrootsOrderQuoteId, RadrootsOrderRevisionId, - RadrootsPublicKey, + RadrootsEventId, RadrootsInventoryBinId, RadrootsListingAddress, RadrootsOrderId, + RadrootsOrderQuoteId, RadrootsOrderRevisionId, RadrootsPublicKey, }, - kinds::KIND_ORDER_RECEIPT, order::{ RadrootsOrderCancellation, RadrootsOrderDecision, RadrootsOrderDecisionOutcome, RadrootsOrderEconomicItem, RadrootsOrderEconomicLine, RadrootsOrderEconomics, - RadrootsOrderFulfillmentState, RadrootsOrderFulfillmentUpdate, - RadrootsOrderInventoryCommitment, RadrootsOrderItem, RadrootsOrderPaymentMethod, - RadrootsOrderPaymentRecord, RadrootsOrderPricingBasis, RadrootsOrderReceipt, + RadrootsOrderInventoryCommitment, RadrootsOrderItem, RadrootsOrderPricingBasis, RadrootsOrderRequest, RadrootsOrderRevisionDecision, RadrootsOrderRevisionOutcome, - RadrootsOrderRevisionProposal, RadrootsOrderSettlementDecision, - RadrootsOrderSettlementOutcome, + RadrootsOrderRevisionProposal, }, }; use radroots_events_codec::{ order::{ - order_cancellation_event_build, order_decision_event_build, - order_fulfillment_update_event_build, order_payment_record_event_build, - order_receipt_event_build, order_request_event_build, + order_cancellation_event_build, order_decision_event_build, order_request_event_build, order_revision_decision_event_build, order_revision_proposal_event_build, - order_settlement_decision_event_build, }, wire::WireEventParts, }; @@ -3908,14 +3766,11 @@ mod tests { LocalRecordStatus, PublishOutboxStatus, RelayDeliveryEvidence, SourceRuntime, }; use radroots_sql_core::SqliteExecutor; - use radroots_trade::{ - order::radroots_order_economics_digest, - validation_receipt::{ - RadrootsTradeValidationReceipt, RadrootsValidationReceiptProof, - RadrootsValidationReceiptProofSystem, RadrootsValidationReceiptResult, - RadrootsValidationReceiptStatement, RadrootsValidationReceiptType, - VALIDATION_RECEIPT_DOMAIN, VALIDATION_RECEIPT_VERSION, validation_receipt_event_build, - }, + use radroots_trade::validation_receipt::{ + RadrootsTradeValidationReceipt, RadrootsValidationReceiptProof, + RadrootsValidationReceiptProofSystem, RadrootsValidationReceiptResult, + RadrootsValidationReceiptStatement, RadrootsValidationReceiptType, + VALIDATION_RECEIPT_DOMAIN, VALIDATION_RECEIPT_VERSION, validation_receipt_event_build, }; use rusqlite::params; use serde_json::json; @@ -4418,10 +4273,6 @@ mod tests { raw.parse().expect("valid listing address") } - fn typed_economics_digest(raw: &str) -> RadrootsEconomicsDigest { - raw.parse().expect("valid economics digest") - } - fn listing_event_ptr(event_id: &str) -> RadrootsNostrEventPtr { RadrootsNostrEventPtr { id: test_event_id_seed(event_id), @@ -4560,22 +4411,6 @@ mod tests { } } - fn fulfillment_update_payload( - order_id: &str, - listing_addr: &str, - buyer_pubkey: &str, - seller_pubkey: &str, - status: RadrootsOrderFulfillmentState, - ) -> RadrootsOrderFulfillmentUpdate { - RadrootsOrderFulfillmentUpdate { - order_id: typed_order_id(order_id), - listing_addr: typed_listing_addr(listing_addr), - buyer_pubkey: typed_pubkey(buyer_pubkey), - seller_pubkey: typed_pubkey(seller_pubkey), - status, - } - } - fn order_cancel_payload( order_id: &str, listing_addr: &str, @@ -4591,38 +4426,6 @@ mod tests { } } - fn buyer_receipt_payload( - order_id: &str, - listing_addr: &str, - buyer_pubkey: &str, - seller_pubkey: &str, - received: bool, - ) -> RadrootsOrderReceipt { - RadrootsOrderReceipt { - order_id: typed_order_id(order_id), - listing_addr: typed_listing_addr(listing_addr), - buyer_pubkey: typed_pubkey(buyer_pubkey), - seller_pubkey: typed_pubkey(seller_pubkey), - received, - issue: (!received).then(|| "items need review".to_owned()), - received_at: 1_777_665_700, - } - } - - struct ActiveOrderReadyFixture { - app_store: AppSqliteStore, - events: LocalEventsStore<SqliteExecutor>, - buyer_context: BuyerContext, - seller_farm_id: FarmId, - order_id: OrderId, - order_id_raw: String, - listing_addr: String, - buyer_pubkey: String, - seller_pubkey: String, - request_event_id: String, - fulfillment_event_id: String, - } - struct ValidationReceiptOrderFixture { app_store: AppSqliteStore, events: LocalEventsStore<SqliteExecutor>, @@ -4638,145 +4441,6 @@ mod tests { decision_event_id: String, } - fn active_order_ready_fixture(label: &str) -> ActiveOrderReadyFixture { - let app_store = - AppSqliteStore::open(DatabaseTarget::InMemory).expect("open app sqlite store"); - let events = local_events_store(); - let farm_key = "DDDDDDDDDDDDDDDDDDDDDD"; - let listing_key = "AAAAAAAAAAAAAAAAAAAAAw"; - let seller_pubkey = test_pubkey(format!("{label}-seller").as_str()); - let buyer_pubkey = test_pubkey(format!("{label}-buyer").as_str()); - let order_id_raw = format!("{label}-order"); - let listing_addr = format!("30402:{seller_pubkey}:{listing_key}"); - events - .append_record(&signed_market_listing_record( - format!("{label}-listing-record").as_str(), - seller_pubkey.as_str(), - farm_key, - listing_key, - "Lifecycle Eggs", - "9", - "active", - "pickup", - "North barn pickup", - 4_102_444_800, - 4_102_531_200, - LocalRecordStatus::Published, - PublishOutboxStatus::Acknowledged, - )) - .expect("append signed listing"); - app_store - .import_shared_local_events_from_store(&events) - .expect("import signed listing"); - - let request_payload = order_request_payload( - order_id_raw.as_str(), - listing_addr.as_str(), - buyer_pubkey.as_str(), - seller_pubkey.as_str(), - ); - let request_parts = order_request_event_build( - &listing_event_ptr(format!("{label}-listing-event").as_str()), - &request_payload, - ) - .expect("build lifecycle order request"); - let request_event = event_from_parts( - format!("{label}-request-event").as_str(), - buyer_pubkey.as_str(), - request_parts, - ); - events - .append_record(&signed_order_event_record( - format!("app:signed_event:{label}:request").as_str(), - &request_event, - listing_addr.as_str(), - SourceRuntime::App, - Some("acct_lifecycle"), - )) - .expect("append lifecycle order request"); - app_store - .import_shared_local_events_from_store(&events) - .expect("import lifecycle order request"); - - let order_id = projected_order_id(order_id_raw.as_str(), buyer_pubkey.as_str()); - let buyer_context = BuyerContext::account("acct_lifecycle"); - let seller_farm_id = deterministic_farm_id(Some(seller_pubkey.as_str()), farm_key); - let decision_payload = accepted_order_decision_payload( - order_id_raw.as_str(), - listing_addr.as_str(), - buyer_pubkey.as_str(), - seller_pubkey.as_str(), - ); - let decision_parts = order_decision_event_build( - &typed_event_id(request_event.id.as_str()), - &typed_event_id(request_event.id.as_str()), - &decision_payload, - ) - .expect("build lifecycle order decision"); - let decision_event = event_from_parts( - format!("{label}-decision-event").as_str(), - seller_pubkey.as_str(), - decision_parts, - ); - events - .append_record(&signed_order_event_record( - format!("cli:signed_event:{label}:decision").as_str(), - &decision_event, - listing_addr.as_str(), - SourceRuntime::Cli, - None, - )) - .expect("append lifecycle order decision"); - app_store - .import_shared_local_events_from_store(&events) - .expect("import lifecycle order decision"); - - let fulfillment_payload = fulfillment_update_payload( - order_id_raw.as_str(), - listing_addr.as_str(), - buyer_pubkey.as_str(), - seller_pubkey.as_str(), - RadrootsOrderFulfillmentState::ReadyForPickup, - ); - let fulfillment_parts = order_fulfillment_update_event_build( - &typed_event_id(request_event.id.as_str()), - &typed_event_id(decision_event.id.as_str()), - &fulfillment_payload, - ) - .expect("build lifecycle fulfillment update"); - let fulfillment_event = event_from_parts( - format!("{label}-fulfillment-event").as_str(), - seller_pubkey.as_str(), - fulfillment_parts, - ); - events - .append_record(&signed_order_event_record( - format!("cli:signed_event:{label}:fulfillment").as_str(), - &fulfillment_event, - listing_addr.as_str(), - SourceRuntime::Cli, - None, - )) - .expect("append lifecycle fulfillment"); - app_store - .import_shared_local_events_from_store(&events) - .expect("import lifecycle fulfillment"); - - ActiveOrderReadyFixture { - app_store, - events, - buyer_context, - seller_farm_id, - order_id, - order_id_raw, - listing_addr, - buyer_pubkey, - seller_pubkey, - request_event_id: request_event.id, - fulfillment_event_id: fulfillment_event.id, - } - } - fn validation_receipt_order_fixture(label: &str) -> ValidationReceiptOrderFixture { let app_store = AppSqliteStore::open(DatabaseTarget::InMemory).expect("open app sqlite store"); @@ -4886,68 +4550,6 @@ mod tests { } } - fn payment_recorded_payload( - request: &RadrootsOrderRequest, - root_event_id: &str, - previous_event_id: &str, - agreement_event_id: &str, - ) -> RadrootsOrderPaymentRecord { - RadrootsOrderPaymentRecord { - order_id: request.order_id.clone(), - listing_addr: request.listing_addr.clone(), - buyer_pubkey: request.buyer_pubkey.clone(), - seller_pubkey: request.seller_pubkey.clone(), - root_event_id: typed_event_id(root_event_id), - previous_event_id: typed_event_id(previous_event_id), - agreement_event_id: typed_event_id(agreement_event_id), - quote_id: request.economics.quote_id.clone(), - quote_version: request.economics.quote_version, - economics_digest: typed_economics_digest( - radroots_order_economics_digest(&request.economics) - .expect("order economics digest should encode") - .as_str(), - ), - amount: request.economics.total.amount, - currency: request.economics.total.currency, - method: RadrootsOrderPaymentMethod::ManualTransfer, - reference: Some("manual reference".to_owned()), - paid_at: Some(1_777_665_800), - } - } - - fn settlement_decision_payload( - request: &RadrootsOrderRequest, - root_event_id: &str, - previous_event_id: &str, - agreement_event_id: &str, - payment_event_id: &str, - decision: RadrootsOrderSettlementOutcome, - ) -> RadrootsOrderSettlementDecision { - let reason = (decision == RadrootsOrderSettlementOutcome::Rejected) - .then(|| "reference mismatch".to_owned()); - RadrootsOrderSettlementDecision { - order_id: request.order_id.clone(), - listing_addr: request.listing_addr.clone(), - buyer_pubkey: request.buyer_pubkey.clone(), - seller_pubkey: request.seller_pubkey.clone(), - root_event_id: typed_event_id(root_event_id), - previous_event_id: typed_event_id(previous_event_id), - agreement_event_id: typed_event_id(agreement_event_id), - payment_event_id: typed_event_id(payment_event_id), - quote_id: request.economics.quote_id.clone(), - quote_version: request.economics.quote_version, - economics_digest: typed_economics_digest( - radroots_order_economics_digest(&request.economics) - .expect("order economics digest should encode") - .as_str(), - ), - amount: request.economics.total.amount, - currency: request.economics.total.currency, - decision, - reason, - } - } - fn event_from_parts(event_id: &str, author: &str, parts: WireEventParts) -> RadrootsNostrEvent { let event_id = event_id .parse::<RadrootsEventId>() @@ -5465,66 +5067,16 @@ mod tests { assert_eq!(decision_report.imported_records, 1); assert_eq!(buyer_orders.rows.len(), 1); assert_eq!(buyer_orders.rows[0].status, BuyerOrderStatus::Scheduled); - assert_eq!( - buyer_orders.rows[0].workflow.payment, - TradePaymentDisplayStatus::NotRecorded - ); assert_eq!(buyer_detail.status, BuyerOrderStatus::Scheduled); assert_eq!(seller_orders.rows[0].status, OrderStatus::Scheduled); - - let payment_payload = payment_recorded_payload( - &request_payload, - request_event.id.as_str(), - decision_event.id.as_str(), - decision_event.id.as_str(), + assert_eq!(buyer_detail.workflow, buyer_orders.rows[0].workflow); + assert_eq!( + seller_orders.rows[0].workflow, + buyer_orders.rows[0].workflow ); - let payment_parts = order_payment_record_event_build( - &typed_event_id(request_event.id.as_str()), - &typed_event_id(decision_event.id.as_str()), - &payment_payload, - ) - .expect("build payment recorded event"); - let payment_event = - event_from_parts("buyer-order-payment-event", buyer_pubkey, payment_parts); - events - .append_record(&signed_order_event_record( - "app:signed_event:order-payment:buyer", - &payment_event, - listing_addr.as_str(), - SourceRuntime::App, - Some("acct_buyer"), - )) - .expect("append payment recorded event"); - - let payment_report = app_store - .import_shared_local_events_from_store(&events) - .expect("import payment recorded event"); - let buyer_orders = app_store - .load_buyer_orders(&buyer_context) - .expect("load buyer orders after payment"); - let buyer_detail = app_store - .load_buyer_order_detail(&buyer_context, order_id) - .expect("load buyer order detail after payment") - .expect("buyer order detail after payment"); - let seller_orders = app_store - .load_orders_list( - farm_id, - &OrdersScreenQueryState { - filter: OrdersFilter::All, - fulfillment_window_id: None, - }, - ) - .expect("load seller orders after payment"); - let seller_detail = app_store - .load_order_detail(farm_id, order_id) - .expect("load seller order detail after payment") - .expect("seller order detail after payment"); - - assert_eq!(payment_report.imported_records, 1); - assert_eq!(buyer_orders.rows[0].status, BuyerOrderStatus::Scheduled); assert_eq!( - buyer_orders.rows[0].workflow.payment, - TradePaymentDisplayStatus::Pending + buyer_orders.rows[0].workflow.inventory, + TradeInventoryStatus::Reserved ); assert_eq!( buyer_orders.rows[0].workflow.provenance.primary_source, @@ -5538,78 +5090,10 @@ mod tests { .as_deref(), Some(decision_event.id.as_str()) ); - assert_eq!(buyer_detail.payment, TradePaymentDisplayStatus::Pending); - assert_eq!(buyer_detail.workflow, buyer_orders.rows[0].workflow); - assert_eq!( - seller_orders.rows[0].workflow.payment, - TradePaymentDisplayStatus::Pending - ); - assert_eq!(seller_detail.payment, TradePaymentDisplayStatus::Pending); - - let settlement_payload = settlement_decision_payload( - &request_payload, - request_event.id.as_str(), - payment_event.id.as_str(), - decision_event.id.as_str(), - payment_event.id.as_str(), - RadrootsOrderSettlementOutcome::Accepted, - ); - let settlement_parts = order_settlement_decision_event_build( - &typed_event_id(request_event.id.as_str()), - &typed_event_id(payment_event.id.as_str()), - &settlement_payload, - ) - .expect("build settlement decision event"); - let settlement_event = event_from_parts( - "buyer-order-settlement-event", - seller_pubkey, - settlement_parts, - ); - events - .append_record(&signed_order_event_record( - "cli:signed_event:order-settlement:buyer", - &settlement_event, - listing_addr.as_str(), - SourceRuntime::Cli, - None, - )) - .expect("append settlement decision event"); - - let settlement_report = app_store - .import_shared_local_events_from_store(&events) - .expect("import settlement decision event"); - let buyer_orders = app_store - .load_buyer_orders(&buyer_context) - .expect("load buyer orders after settlement"); - let buyer_detail = app_store - .load_buyer_order_detail(&buyer_context, order_id) - .expect("load buyer order detail after settlement") - .expect("buyer order detail after settlement"); - let seller_orders = app_store - .load_orders_list( - farm_id, - &OrdersScreenQueryState { - filter: OrdersFilter::All, - fulfillment_window_id: None, - }, - ) - .expect("load seller orders after settlement"); - let seller_detail = app_store - .load_order_detail(farm_id, order_id) - .expect("load seller order detail after settlement") - .expect("seller order detail after settlement"); - - assert_eq!(settlement_report.imported_records, 1); - assert_eq!( - buyer_orders.rows[0].workflow.payment, - TradePaymentDisplayStatus::Settled - ); - assert_eq!(buyer_detail.payment, TradePaymentDisplayStatus::Settled); assert_eq!( - seller_orders.rows[0].workflow.payment, - TradePaymentDisplayStatus::Settled + buyer_orders.rows[0].workflow.economics.total_minor_units, + Some(1600) ); - assert_eq!(seller_detail.payment, TradePaymentDisplayStatus::Settled); } #[test] @@ -5744,7 +5228,7 @@ mod tests { } #[test] - fn active_order_fulfillment_and_receipt_project_through_cli_reducer_state() { + fn active_order_decision_projects_agreement_state_through_cli_reducer() { let app_store = AppSqliteStore::open(DatabaseTarget::InMemory).expect("open app sqlite store"); let events = local_events_store(); @@ -5806,8 +5290,6 @@ mod tests { .import_shared_local_events_from_store(&events) .expect("import lifecycle order request"); - let order_id = projected_order_id(order_id_raw, buyer_pubkey); - let buyer_context = BuyerContext::account("acct_lifecycle"); let seller_farm_id = deterministic_farm_id(Some(seller_pubkey), farm_key); let decision_payload = accepted_order_decision_payload( order_id_raw, @@ -5849,216 +5331,10 @@ mod tests { .expect("load lifecycle seller orders after decision"); assert_eq!(seller_orders.rows[0].status, OrderStatus::Scheduled); assert_eq!( - seller_orders.rows[0].primary_action, - Some(OrderPrimaryAction::PublishPreparing) - ); - assert_eq!( - seller_orders.rows[0].fulfillment_actions, - OrderFulfillmentAction::ALL.to_vec() - ); - - let fulfillment_payload = fulfillment_update_payload( - order_id_raw, - listing_addr.as_str(), - buyer_pubkey, - seller_pubkey, - RadrootsOrderFulfillmentState::ReadyForPickup, - ); - let fulfillment_parts = order_fulfillment_update_event_build( - &typed_event_id(request_event.id.as_str()), - &typed_event_id(decision_event.id.as_str()), - &fulfillment_payload, - ) - .expect("build lifecycle fulfillment update"); - let fulfillment_event = event_from_parts( - "active-lifecycle-fulfillment-event", - seller_pubkey, - fulfillment_parts, - ); - events - .append_record(&signed_order_event_record( - "cli:signed_event:active-lifecycle:fulfillment", - &fulfillment_event, - listing_addr.as_str(), - SourceRuntime::Cli, - None, - )) - .expect("append lifecycle fulfillment"); - app_store - .import_shared_local_events_from_store(&events) - .expect("import lifecycle fulfillment"); - let buyer_orders = app_store - .load_buyer_orders(&buyer_context) - .expect("load lifecycle buyer orders after fulfillment"); - let seller_orders = app_store - .load_orders_list( - seller_farm_id, - &OrdersScreenQueryState { - filter: OrdersFilter::All, - fulfillment_window_id: None, - }, - ) - .expect("load lifecycle seller orders after fulfillment"); - assert_eq!(buyer_orders.rows[0].status, BuyerOrderStatus::Ready); - assert_eq!(seller_orders.rows[0].status, OrderStatus::Packed); - assert_eq!( - seller_orders.rows[0].workflow.fulfillment, - Some(TradeFulfillmentStatus::ReadyForPickup) - ); - assert_eq!( seller_orders.rows[0].workflow.inventory, TradeInventoryStatus::Reserved ); - assert_eq!( - seller_orders.rows[0].primary_action, - Some(OrderPrimaryAction::PublishDelivered) - ); - assert_eq!( - seller_orders.rows[0].fulfillment_actions, - OrderFulfillmentAction::ALL.to_vec() - ); - - let receipt_payload = buyer_receipt_payload( - order_id_raw, - listing_addr.as_str(), - buyer_pubkey, - seller_pubkey, - true, - ); - let receipt_parts = order_receipt_event_build( - &typed_event_id(request_event.id.as_str()), - &typed_event_id(fulfillment_event.id.as_str()), - &receipt_payload, - ) - .expect("build lifecycle buyer receipt"); - let receipt_event = event_from_parts( - "active-lifecycle-receipt-event", - buyer_pubkey, - receipt_parts, - ); - events - .append_record(&signed_order_event_record( - "app:signed_event:active-lifecycle:receipt", - &receipt_event, - listing_addr.as_str(), - SourceRuntime::App, - Some("acct_lifecycle"), - )) - .expect("append lifecycle buyer receipt"); - app_store - .import_shared_local_events_from_store(&events) - .expect("import lifecycle buyer receipt"); - let buyer_detail = app_store - .load_buyer_order_detail(&buyer_context, order_id) - .expect("load lifecycle buyer detail") - .expect("lifecycle buyer detail"); - let seller_orders = app_store - .load_orders_list( - seller_farm_id, - &OrdersScreenQueryState { - filter: OrdersFilter::All, - fulfillment_window_id: None, - }, - ) - .expect("load lifecycle seller orders after receipt"); - assert_eq!(buyer_detail.status, BuyerOrderStatus::Completed); - let buyer_receipt = buyer_detail - .workflow - .receipt - .as_ref() - .expect("buyer receipt projection"); - assert_eq!(buyer_receipt.event_id, receipt_event.id); - assert!(buyer_receipt.received); - assert!(buyer_receipt.issue.is_none()); - assert_eq!(buyer_receipt.received_at, receipt_payload.received_at); - assert_eq!(seller_orders.rows[0].status, OrderStatus::Completed); - let seller_receipt = seller_orders.rows[0] - .workflow - .receipt - .as_ref() - .expect("seller receipt projection"); - assert_eq!(seller_receipt.event_id, receipt_event.id); - assert!(seller_receipt.received); - assert!(seller_receipt.issue.is_none()); - assert_eq!(seller_receipt.received_at, receipt_payload.received_at); assert_eq!(seller_orders.rows[0].primary_action, None); - assert_eq!(seller_orders.rows[0].fulfillment_actions, Vec::new()); - } - - #[test] - fn active_order_issue_receipt_projects_through_cli_reducer_state() { - let fixture = active_order_ready_fixture("active-lifecycle-issue-receipt"); - let receipt_payload = buyer_receipt_payload( - fixture.order_id_raw.as_str(), - fixture.listing_addr.as_str(), - fixture.buyer_pubkey.as_str(), - fixture.seller_pubkey.as_str(), - false, - ); - let receipt_parts = order_receipt_event_build( - &typed_event_id(fixture.request_event_id.as_str()), - &typed_event_id(fixture.fulfillment_event_id.as_str()), - &receipt_payload, - ) - .expect("build lifecycle buyer issue receipt"); - let receipt_event = event_from_parts( - "active-lifecycle-issue-receipt-event", - fixture.buyer_pubkey.as_str(), - receipt_parts, - ); - fixture - .events - .append_record(&signed_order_event_record( - "app:signed_event:active-lifecycle:issue-receipt", - &receipt_event, - fixture.listing_addr.as_str(), - SourceRuntime::App, - Some("acct_lifecycle"), - )) - .expect("append lifecycle buyer issue receipt"); - fixture - .app_store - .import_shared_local_events_from_store(&fixture.events) - .expect("import lifecycle buyer issue receipt"); - - let buyer_detail = fixture - .app_store - .load_buyer_order_detail(&fixture.buyer_context, fixture.order_id) - .expect("load lifecycle buyer issue detail") - .expect("lifecycle buyer issue detail"); - let seller_orders = fixture - .app_store - .load_orders_list( - fixture.seller_farm_id, - &OrdersScreenQueryState { - filter: OrdersFilter::All, - fulfillment_window_id: None, - }, - ) - .expect("load lifecycle seller orders after issue receipt"); - - assert_eq!(buyer_detail.status, BuyerOrderStatus::NeedsReview); - let buyer_receipt = buyer_detail - .workflow - .receipt - .as_ref() - .expect("buyer issue receipt projection"); - assert_eq!(buyer_receipt.event_id, receipt_event.id); - assert!(!buyer_receipt.received); - assert_eq!(buyer_receipt.issue.as_deref(), Some("items need review")); - assert_eq!(buyer_receipt.received_at, receipt_payload.received_at); - assert_eq!(seller_orders.rows[0].status, OrderStatus::NeedsReview); - let seller_receipt = seller_orders.rows[0] - .workflow - .receipt - .as_ref() - .expect("seller issue receipt projection"); - assert_eq!(seller_receipt.event_id, receipt_event.id); - assert!(!seller_receipt.received); - assert_eq!(seller_receipt.issue.as_deref(), Some("items need review")); - assert_eq!(seller_receipt.received_at, receipt_payload.received_at); - assert_eq!(seller_orders.rows[0].primary_action, None); - assert_eq!(seller_orders.rows[0].fulfillment_actions, Vec::new()); } #[test] @@ -6137,24 +5413,7 @@ mod tests { assert_eq!(buyer_detail.status, BuyerOrderStatus::Scheduled); assert_eq!(seller_detail.status, OrderStatus::Scheduled); - assert!(buyer_detail.workflow.receipt.is_none()); - assert!(seller_detail.workflow.receipt.is_none()); - assert_eq!( - buyer_detail.workflow.payment, - TradePaymentDisplayStatus::NotRecorded - ); - assert_eq!( - seller_detail.workflow.payment, - TradePaymentDisplayStatus::NotRecorded - ); - assert_eq!( - seller_detail.primary_action, - Some(OrderPrimaryAction::PublishPreparing) - ); - assert_eq!( - seller_detail.fulfillment_actions, - OrderFulfillmentAction::ALL.to_vec() - ); + assert_eq!(seller_detail.primary_action, None); assert_eq!(buyer_detail.validation_receipts.len(), 2); assert_eq!( buyer_detail.validation_receipts, @@ -6335,8 +5594,6 @@ mod tests { ); assert_eq!(buyer_detail.status, BuyerOrderStatus::Placed); assert_eq!(seller_detail.status, OrderStatus::NeedsAction); - assert!(buyer_detail.workflow.receipt.is_none()); - assert!(seller_detail.workflow.receipt.is_none()); } #[test] @@ -6379,7 +5636,7 @@ mod tests { RadrootsValidationReceiptResult::Valid, 1_777_665_605, ); - buyer_kind_candidate.kind = KIND_ORDER_RECEIPT; + buyer_kind_candidate.kind = KIND_ORDER_REQUEST as u32; for (record_id, event) in [ ( @@ -6426,16 +5683,11 @@ mod tests { assert!(seller_detail.validation_receipts.is_empty()); assert_eq!(buyer_detail.status, BuyerOrderStatus::Scheduled); assert_eq!(seller_detail.status, OrderStatus::Scheduled); - assert!(buyer_detail.workflow.receipt.is_none()); - assert!(seller_detail.workflow.receipt.is_none()); - assert_eq!( - seller_detail.primary_action, - Some(OrderPrimaryAction::PublishPreparing) - ); + assert_eq!(seller_detail.primary_action, None); } #[test] - fn active_order_revision_and_cancellation_project_through_cli_reducer_state() { + fn active_order_revision_projects_through_cli_reducer_state() { let app_store = AppSqliteStore::open(DatabaseTarget::InMemory).expect("open app sqlite store"); let events = local_events_store(); @@ -6494,36 +5746,6 @@ mod tests { .import_shared_local_events_from_store(&events) .expect("import revision order request"); - let decision_payload = accepted_order_decision_payload( - order_id_raw, - listing_addr.as_str(), - buyer_pubkey, - seller_pubkey, - ); - let decision_parts = order_decision_event_build( - &typed_event_id(request_event.id.as_str()), - &typed_event_id(request_event.id.as_str()), - &decision_payload, - ) - .expect("build revision order decision"); - let decision_event = event_from_parts( - "active-revision-decision-event", - seller_pubkey, - decision_parts, - ); - events - .append_record(&signed_order_event_record( - "cli:signed_event:active-revision:decision", - &decision_event, - listing_addr.as_str(), - SourceRuntime::Cli, - None, - )) - .expect("append revision order decision"); - app_store - .import_shared_local_events_from_store(&events) - .expect("import revision order decision"); - let proposal_payload = revision_proposal_payload( "revision-1", order_id_raw, @@ -6531,11 +5753,11 @@ mod tests { buyer_pubkey, seller_pubkey, request_event.id.as_str(), - decision_event.id.as_str(), + request_event.id.as_str(), ); let proposal_parts = order_revision_proposal_event_build( &typed_event_id(request_event.id.as_str()), - &typed_event_id(decision_event.id.as_str()), + &typed_event_id(request_event.id.as_str()), &proposal_payload, ) .expect("build revision proposal"); @@ -6569,7 +5791,7 @@ mod tests { }, ) .expect("load revision seller orders after proposal"); - assert_eq!(seller_orders.rows[0].status, OrderStatus::Scheduled); + assert_eq!(seller_orders.rows[0].status, OrderStatus::NeedsAction); assert_eq!( seller_orders.rows[0].workflow.revision, TradeRevisionStatus::ChangeProposed @@ -6646,6 +5868,67 @@ mod tests { assert_eq!(seller_detail.economics.total_minor_units, Some(2400)); assert_eq!(buyer_detail.workflow.revision, TradeRevisionStatus::Updated); assert_eq!(buyer_detail.economics.total_minor_units, Some(2400)); + } + + #[test] + fn active_order_pre_agreement_cancellation_projects_through_cli_reducer_state() { + let app_store = + AppSqliteStore::open(DatabaseTarget::InMemory).expect("open app sqlite store"); + let events = local_events_store(); + let farm_key = "EEEEEEEEEEEEEEEEEEEEEE"; + let listing_key = "AAAAAAAAAAAAAAAAAAAAAx"; + let seller_pubkey = test_pubkey("seller-pubkey"); + let seller_pubkey = seller_pubkey.as_str(); + let buyer_pubkey = test_pubkey("app-buyer-pubkey"); + let buyer_pubkey = buyer_pubkey.as_str(); + let order_id_raw = "active-cancel-order-1"; + let listing_addr = format!("30402:{seller_pubkey}:{listing_key}"); + events + .append_record(&signed_market_listing_record( + "active-cancel-listing", + seller_pubkey, + farm_key, + listing_key, + "Cancellation Eggs", + "9", + "active", + "pickup", + "North barn pickup", + 4_102_444_800, + 4_102_531_200, + LocalRecordStatus::Published, + PublishOutboxStatus::Acknowledged, + )) + .expect("append cancellation listing"); + app_store + .import_shared_local_events_from_store(&events) + .expect("import cancellation listing"); + + let request_payload = order_request_payload( + order_id_raw, + listing_addr.as_str(), + buyer_pubkey, + seller_pubkey, + ); + let request_parts = order_request_event_build( + &listing_event_ptr("active-cancel-listing-event"), + &request_payload, + ) + .expect("build cancellation order request"); + let request_event = + event_from_parts("active-cancel-request-event", buyer_pubkey, request_parts); + events + .append_record(&signed_order_event_record( + "app:signed_event:active-cancel:request", + &request_event, + listing_addr.as_str(), + SourceRuntime::App, + Some("acct_cancel"), + )) + .expect("append cancellation order request"); + app_store + .import_shared_local_events_from_store(&events) + .expect("import cancellation order request"); let cancel_payload = order_cancel_payload( order_id_raw, @@ -6655,28 +5938,31 @@ mod tests { ); let cancel_parts = order_cancellation_event_build( &typed_event_id(request_event.id.as_str()), - &typed_event_id(revision_decision_event.id.as_str()), + &typed_event_id(request_event.id.as_str()), &cancel_payload, ) - .expect("build revision cancellation"); - let cancel_event = - event_from_parts("active-revision-cancel-event", buyer_pubkey, cancel_parts); + .expect("build cancellation"); + let cancel_event = event_from_parts("active-cancel-event", buyer_pubkey, cancel_parts); events .append_record(&signed_order_event_record( - "app:signed_event:active-revision:cancel", + "app:signed_event:active-cancel:cancel", &cancel_event, listing_addr.as_str(), SourceRuntime::App, - Some("acct_revision"), + Some("acct_cancel"), )) - .expect("append revision cancellation"); - app_store + .expect("append cancellation"); + let cancel_report = app_store .import_shared_local_events_from_store(&events) - .expect("import revision cancellation"); + .expect("import cancellation"); + + let seller_farm_id = deterministic_farm_id(Some(seller_pubkey), farm_key); + let order_id = projected_order_id(order_id_raw, buyer_pubkey); + let buyer_context = BuyerContext::account("acct_cancel"); let buyer_detail = app_store .load_buyer_order_detail(&buyer_context, order_id) - .expect("load revision buyer detail") - .expect("revision buyer detail"); + .expect("load cancellation buyer detail") + .expect("cancellation buyer detail"); let seller_orders = app_store .load_orders_list( seller_farm_id, @@ -6685,12 +5971,20 @@ mod tests { fulfillment_window_id: None, }, ) - .expect("load revision seller orders after cancellation"); + .expect("load cancellation seller orders"); + + assert_eq!(cancel_report.imported_records, 1); assert_eq!(buyer_detail.status, BuyerOrderStatus::Declined); - assert_eq!(buyer_detail.workflow.revision, TradeRevisionStatus::Updated); + assert_eq!( + buyer_detail.workflow.agreement, + TradeAgreementStatus::Cancelled + ); assert_eq!(seller_orders.rows[0].status, OrderStatus::Declined); + assert_eq!( + seller_orders.rows[0].workflow.agreement, + TradeAgreementStatus::Cancelled + ); assert_eq!(seller_orders.rows[0].primary_action, None); - assert_eq!(seller_orders.rows[0].fulfillment_actions, Vec::new()); } #[test] diff --git a/crates/store/src/lib.rs b/crates/store/src/lib.rs @@ -1149,9 +1149,23 @@ mod tests { } #[test] - fn workflow_payment_display_schema_accepts_pending_and_settled_states() { + fn order_workflow_schema_is_agreement_only() { let store = AppSqliteStore::open(DatabaseTarget::InMemory).expect("store should open"); let connection = store.connection(); + assert!(column_exists(connection, "orders", "workflow_agreement")); + assert!(column_exists(connection, "orders", "workflow_inventory")); + assert!(column_exists( + connection, + "orders", + "workflow_provenance_source" + )); + assert!(!column_exists(connection, "orders", "workflow_fulfillment")); + assert!(!column_exists(connection, "orders", "workflow_payment")); + assert!(!column_exists( + connection, + "orders", + "workflow_receipt_event_id" + )); connection .execute( "INSERT INTO farms (id, display_name, readiness, created_at, updated_at) @@ -1159,27 +1173,6 @@ mod tests { params!["farm_schema"], ) .expect("farm should insert"); - - for (order_id, workflow_payment) in [ - ("order_payment_pending", "pending"), - ("order_payment_settled", "settled"), - ] { - connection - .execute( - "INSERT INTO orders ( - id, - farm_id, - order_number, - customer_display_name, - status, - updated_at, - workflow_payment - ) VALUES (?1, 'farm_schema', ?2, 'Buyer', 'scheduled', '2026-01-01T00:00:00Z', ?3)", - params![order_id, order_id, workflow_payment], - ) - .expect("expanded workflow payment state should insert"); - } - connection .execute( "INSERT INTO orders ( @@ -1189,25 +1182,21 @@ mod tests { customer_display_name, status, updated_at, - workflow_receipt_event_id, - workflow_receipt_received, - workflow_receipt_issue, - workflow_receipt_received_at + workflow_agreement, + workflow_inventory ) VALUES ( - 'order_issue_receipt', + 'order_needs_review', 'farm_schema', - 'issue receipt', + 'needs review', 'Buyer', 'needs_review', '2026-01-01T00:00:00Z', - 'receipt-event-1', - 0, - 'items need review', - 1777665700 + 'needs_review', + 'needs_review' )", [], ) - .expect("receipt projection should insert"); + .expect("agreement-only workflow projection should insert"); let invalid_result = connection.execute( "INSERT INTO orders ( @@ -1217,37 +1206,11 @@ mod tests { customer_display_name, status, updated_at, - workflow_payment - ) VALUES ('order_payment_invalid', 'farm_schema', 'invalid', 'Buyer', 'scheduled', '2026-01-01T00:00:00Z', 'collect')", + workflow_agreement + ) VALUES ('order_agreement_invalid', 'farm_schema', 'invalid', 'Buyer', 'scheduled', '2026-01-01T00:00:00Z', 'complete')", [], ); assert!(invalid_result.is_err()); - - let invalid_receipt_result = connection.execute( - "INSERT INTO orders ( - id, - farm_id, - order_number, - customer_display_name, - status, - updated_at, - workflow_receipt_event_id, - workflow_receipt_received, - workflow_receipt_received_at - ) VALUES ( - 'order_receipt_invalid', - 'farm_schema', - 'invalid receipt', - 'Buyer', - 'needs_review', - '2026-01-01T00:00:00Z', - 'receipt-event-invalid', - 2, - 1777665700 - )", - [], - ); - assert!(invalid_receipt_result.is_err()); } #[test] diff --git a/crates/store/src/migration_audit.rs b/crates/store/src/migration_audit.rs @@ -3,9 +3,8 @@ use std::collections::{BTreeMap, BTreeSet}; use radroots_app_sync::{AppPublishPayload, SyncOperationKind}; use radroots_events::kinds::{ KIND_FARM, KIND_LISTING, KIND_LISTING_DRAFT, KIND_ORDER_CANCELLATION, KIND_ORDER_DECISION, - KIND_ORDER_FULFILLMENT_UPDATE, KIND_ORDER_PAYMENT_RECORD, KIND_ORDER_RECEIPT, KIND_ORDER_REQUEST, KIND_ORDER_REVISION_DECISION, KIND_ORDER_REVISION_PROPOSAL, - KIND_ORDER_SETTLEMENT_DECISION, KIND_TRADE_VALIDATION_RECEIPT, + KIND_TRADE_VALIDATION_RECEIPT, }; use radroots_local_events::{ LocalEventRecord, LocalEventsStore, LocalRecordFamily, LocalRecordStatus, PublishOutboxStatus, @@ -102,8 +101,6 @@ pub enum AppSdkMigrationAuditClassification { FailedRecord, LocalWorkDeferred, ManualReviewRequired, - PaymentDeferred, - SettlementDeferred, ValidationReceiptDeferred, Unsupported, Unknown, @@ -119,8 +116,6 @@ impl AppSdkMigrationAuditClassification { Self::FailedRecord => "failed_record", Self::LocalWorkDeferred => "local_work_deferred", Self::ManualReviewRequired => "manual_review_required", - Self::PaymentDeferred => "payment_deferred", - Self::SettlementDeferred => "settlement_deferred", Self::ValidationReceiptDeferred => "validation_receipt_deferred", Self::Unsupported => "unsupported", Self::Unknown => "unknown", @@ -686,12 +681,6 @@ fn classify_shared_signed_event( report: &mut AppSdkMigrationAuditSourceBuilder, ) -> AppSdkMigrationAuditClassification { match record.event_kind { - Some(kind) if kind == KIND_ORDER_PAYMENT_RECORD as i64 => { - AppSdkMigrationAuditClassification::PaymentDeferred - } - Some(kind) if kind == KIND_ORDER_SETTLEMENT_DECISION as i64 => { - AppSdkMigrationAuditClassification::SettlementDeferred - } Some(kind) if kind == KIND_TRADE_VALIDATION_RECEIPT as i64 => { AppSdkMigrationAuditClassification::ValidationReceiptDeferred } @@ -795,8 +784,6 @@ fn supported_signed_event_kind(kind: i64) -> bool { || value == KIND_ORDER_REVISION_PROPOSAL as i64 || value == KIND_ORDER_REVISION_DECISION as i64 || value == KIND_ORDER_CANCELLATION as i64 - || value == KIND_ORDER_FULFILLMENT_UPDATE as i64 - || value == KIND_ORDER_RECEIPT as i64 ) } @@ -810,10 +797,6 @@ fn shared_signed_event_kind(kind: i64) -> String { value if value == KIND_ORDER_REVISION_PROPOSAL as i64 => "order_revision_proposal", value if value == KIND_ORDER_REVISION_DECISION as i64 => "order_revision_decision", value if value == KIND_ORDER_CANCELLATION as i64 => "order_cancellation", - value if value == KIND_ORDER_FULFILLMENT_UPDATE as i64 => "order_fulfillment", - value if value == KIND_ORDER_RECEIPT as i64 => "order_receipt", - value if value == KIND_ORDER_PAYMENT_RECORD as i64 => "order_payment", - value if value == KIND_ORDER_SETTLEMENT_DECISION as i64 => "order_settlement", value if value == KIND_TRADE_VALIDATION_RECEIPT as i64 => "trade_validation_receipt", _ => "unsupported", }; @@ -860,10 +843,7 @@ mod tests { AppFarmProfilePublishPayload, AppPublishContext, AppPublishPayload, PendingSyncOperation, }; use radroots_app_view::{FarmId, FarmReadiness}; - use radroots_events::kinds::{ - KIND_LISTING, KIND_ORDER_PAYMENT_RECORD, KIND_ORDER_SETTLEMENT_DECISION, - KIND_TRADE_VALIDATION_RECEIPT, - }; + use radroots_events::kinds::{KIND_LISTING, KIND_ORDER_REQUEST, KIND_TRADE_VALIDATION_RECEIPT}; use radroots_local_events::{ LocalEventRecord, LocalEventRecordInput, LocalEventsStore, LocalRecordFamily, LocalRecordStatus, PublishOutboxStatus, SourceRuntime, @@ -1196,7 +1176,7 @@ mod tests { } #[test] - fn shared_local_events_audit_defers_payment_and_settlement() { + fn shared_local_events_audit_classifies_supported_events_and_validation_receipts() { let store = AppSqliteStore::open(DatabaseTarget::InMemory).expect("open app store"); let shared_events = local_events_store(); shared_events @@ -1215,18 +1195,11 @@ mod tests { .expect("append listing b"); shared_events .append_record(&signed_event_record( - "payment", - "payment-event", - KIND_ORDER_PAYMENT_RECORD as i64, - )) - .expect("append payment"); - shared_events - .append_record(&signed_event_record( - "settlement", - "settlement-event", - KIND_ORDER_SETTLEMENT_DECISION as i64, + "request", + "request-event", + KIND_ORDER_REQUEST as i64, )) - .expect("append settlement"); + .expect("append request"); shared_events .append_record(&signed_event_record( "validation-receipt", @@ -1253,28 +1226,14 @@ mod tests { .len(), before_records ); - assert_eq!(report.shared_local_events.batch_count, 5); - assert_eq!(report.shared_local_events.scanned_records, 5); + assert_eq!(report.shared_local_events.batch_count, 4); + assert_eq!(report.shared_local_events.scanned_records, 4); assert_eq!( count_named( &report.shared_local_events.classification_counts, AppSdkMigrationAuditClassification::AlreadyRepresentedCandidate.storage_key() ), - 2 - ); - assert_eq!( - count_named( - &report.shared_local_events.classification_counts, - AppSdkMigrationAuditClassification::PaymentDeferred.storage_key() - ), - 1 - ); - assert_eq!( - count_named( - &report.shared_local_events.classification_counts, - AppSdkMigrationAuditClassification::SettlementDeferred.storage_key() - ), - 1 + 3 ); assert_eq!( count_named( diff --git a/crates/store/src/migrations.rs b/crates/store/src/migrations.rs @@ -98,11 +98,11 @@ const MIGRATIONS: &[Migration] = &[ }, Migration { version: 24, - sql: include_str!("../migrations/0024_order_workflow_payment_display_states.sql"), + sql: include_str!("../migrations/0024_order_workflow_agreement_states.sql"), }, Migration { version: 25, - sql: include_str!("../migrations/0025_order_receipt_display_projection.sql"), + sql: include_str!("../migrations/0025_order_workflow_agreement_projection.sql"), }, Migration { version: 26, diff --git a/crates/store/src/repo/buyer.rs b/crates/store/src/repo/buyer.rs @@ -808,13 +808,7 @@ impl<'a> AppBuyerRepository<'a> { o.status, o.workflow_revision, o.workflow_agreement, - o.workflow_fulfillment, - o.workflow_receipt_event_id, - o.workflow_receipt_received, - o.workflow_receipt_issue, - o.workflow_receipt_received_at, o.workflow_inventory, - o.workflow_payment, o.workflow_provenance_source, o.workflow_provenance_last_event_id, f.display_name, @@ -843,19 +837,13 @@ impl<'a> AppBuyerRepository<'a> { row.get::<_, String>(3)?, row.get::<_, String>(4)?, row.get::<_, String>(5)?, - row.get::<_, Option<String>>(6)?, - row.get::<_, Option<String>>(7)?, - row.get::<_, Option<i64>>(8)?, - row.get::<_, Option<String>>(9)?, - row.get::<_, Option<i64>>(10)?, - row.get::<_, String>(11)?, - row.get::<_, String>(12)?, - row.get::<_, String>(13)?, - row.get::<_, Option<String>>(14)?, - row.get::<_, String>(15)?, - row.get::<_, Option<String>>(16)?, - row.get::<_, Option<String>>(17)?, - row.get::<_, Option<String>>(18)?, + row.get::<_, String>(6)?, + row.get::<_, String>(7)?, + row.get::<_, Option<String>>(8)?, + row.get::<_, String>(9)?, + row.get::<_, Option<String>>(10)?, + row.get::<_, Option<String>>(11)?, + row.get::<_, Option<String>>(12)?, )) }) .map_err(|source| AppSqliteError::Query { @@ -872,13 +860,7 @@ impl<'a> AppBuyerRepository<'a> { status, workflow_revision, workflow_agreement, - workflow_fulfillment, - workflow_receipt_event_id, - workflow_receipt_received, - workflow_receipt_issue, - workflow_receipt_received_at, workflow_inventory, - workflow_payment, workflow_provenance_source, workflow_provenance_last_event_id, farm_display_name, @@ -901,13 +883,7 @@ impl<'a> AppBuyerRepository<'a> { revision, economics, agreement: workflow_agreement, - fulfillment: workflow_fulfillment, - receipt_event_id: workflow_receipt_event_id, - receipt_received: workflow_receipt_received, - receipt_issue: workflow_receipt_issue, - receipt_received_at: workflow_receipt_received_at, inventory: workflow_inventory, - payment: workflow_payment, provenance_source: workflow_provenance_source, provenance_last_event_id: workflow_provenance_last_event_id, })?; @@ -968,13 +944,7 @@ impl<'a> AppBuyerRepository<'a> { o.buyer_order_note, o.workflow_revision, o.workflow_agreement, - o.workflow_fulfillment, - o.workflow_receipt_event_id, - o.workflow_receipt_received, - o.workflow_receipt_issue, - o.workflow_receipt_received_at, o.workflow_inventory, - o.workflow_payment, o.workflow_provenance_source, o.workflow_provenance_last_event_id, f.display_name, @@ -1000,19 +970,13 @@ impl<'a> AppBuyerRepository<'a> { row.get::<_, String>(4)?, row.get::<_, String>(5)?, row.get::<_, String>(6)?, - row.get::<_, Option<String>>(7)?, - row.get::<_, Option<String>>(8)?, - row.get::<_, Option<i64>>(9)?, - row.get::<_, Option<String>>(10)?, - row.get::<_, Option<i64>>(11)?, - row.get::<_, String>(12)?, - row.get::<_, String>(13)?, - row.get::<_, String>(14)?, - row.get::<_, Option<String>>(15)?, - row.get::<_, String>(16)?, - row.get::<_, Option<String>>(17)?, - row.get::<_, Option<String>>(18)?, - row.get::<_, Option<String>>(19)?, + row.get::<_, String>(7)?, + row.get::<_, String>(8)?, + row.get::<_, Option<String>>(9)?, + row.get::<_, String>(10)?, + row.get::<_, Option<String>>(11)?, + row.get::<_, Option<String>>(12)?, + row.get::<_, Option<String>>(13)?, )) }) .optional() @@ -1031,13 +995,7 @@ impl<'a> AppBuyerRepository<'a> { order_note, workflow_revision, workflow_agreement, - workflow_fulfillment, - workflow_receipt_event_id, - workflow_receipt_received, - workflow_receipt_issue, - workflow_receipt_received_at, workflow_inventory, - workflow_payment, workflow_provenance_source, workflow_provenance_last_event_id, farm_display_name, @@ -1059,17 +1017,10 @@ impl<'a> AppBuyerRepository<'a> { revision, economics: economics.clone(), agreement: workflow_agreement, - fulfillment: workflow_fulfillment, - receipt_event_id: workflow_receipt_event_id, - receipt_received: workflow_receipt_received, - receipt_issue: workflow_receipt_issue, - receipt_received_at: workflow_receipt_received_at, inventory: workflow_inventory, - payment: workflow_payment, provenance_source: workflow_provenance_source, provenance_last_event_id: workflow_provenance_last_event_id, })?; - let payment = workflow.payment; let validation_receipts = order_validation_receipts(self.connection, order_id)?; Ok(BuyerOrderDetailProjection { order_id, @@ -1084,7 +1035,6 @@ impl<'a> AppBuyerRepository<'a> { status, items, economics, - payment, workflow, validation_receipts, order_note: empty_string_to_none(order_note), @@ -2913,7 +2863,6 @@ fn parse_order_status(field: &'static str, value: String) -> Result<OrderStatus, "packed" => Ok(OrderStatus::Packed), "completed" => Ok(OrderStatus::Completed), "declined" => Ok(OrderStatus::Declined), - "refunded" => Ok(OrderStatus::Refunded), "needs_review" => Ok(OrderStatus::NeedsReview), _ => Err(AppSqliteError::DecodeEnum { field, value }), } @@ -2997,8 +2946,7 @@ mod tests { use radroots_app_view::{ BuyerContext, BuyerOrderReviewDisabledReason, BuyerOrderStatus, FarmId, FarmOrderMethod, FulfillmentWindowId, OrderId, PickupLocationId, ProductId, TradeAgreementStatus, - TradeFulfillmentStatus, TradeInventoryStatus, TradePaymentDisplayStatus, - TradeRevisionStatus, TradeWorkflowSource, + TradeInventoryStatus, TradeRevisionStatus, TradeWorkflowSource, }; use rusqlite::{Connection, params}; use serde_json::json; @@ -3503,10 +3451,6 @@ mod tests { buyer_order_detail.economics.currency_code.as_deref(), Some("USD") ); - assert_eq!( - buyer_order_detail.payment, - TradePaymentDisplayStatus::NotRecorded - ); } #[test] @@ -3880,11 +3824,9 @@ mod tests { connection, order_id, "confirmed", - Some("ready_for_pickup"), "reserved", - "recorded", "local_events", - Some("payment-event-1"), + Some("agreement-event-1"), ); let list = repository @@ -3898,24 +3840,18 @@ mod tests { assert_eq!(list.rows.len(), 1); assert_eq!(row.workflow.agreement, TradeAgreementStatus::Confirmed); - assert_eq!( - row.workflow.fulfillment, - Some(TradeFulfillmentStatus::ReadyForPickup) - ); assert_eq!(row.workflow.inventory, TradeInventoryStatus::Reserved); - assert_eq!(row.workflow.payment, TradePaymentDisplayStatus::Recorded); assert_eq!( row.workflow.provenance.primary_source, TradeWorkflowSource::LocalEvents ); assert_eq!( row.workflow.provenance.last_event_id.as_deref(), - Some("payment-event-1") + Some("agreement-event-1") ); assert_eq!(row.workflow.economics.total_minor_units, Some(1300)); assert_eq!(row.workflow.economics.currency_code.as_deref(), Some("USD")); assert_eq!(detail.workflow, row.workflow); - assert_eq!(detail.payment, TradePaymentDisplayStatus::Recorded); } #[test] @@ -3942,18 +3878,14 @@ mod tests { connection, order_id, "confirmed", - Some("ready_for_pickup"), "reserved", - "recorded", "local_events", Some("buyer-workflow-event"), ); for (column, expected_field) in [ ("workflow_agreement", "orders.workflow_agreement"), - ("workflow_fulfillment", "orders.workflow_fulfillment"), ("workflow_inventory", "orders.workflow_inventory"), - ("workflow_payment", "orders.workflow_payment"), ( "workflow_provenance_source", "orders.workflow_provenance_source", @@ -3963,9 +3895,7 @@ mod tests { connection, order_id, "confirmed", - Some("ready_for_pickup"), "reserved", - "recorded", "local_events", Some("buyer-workflow-event"), ); @@ -4253,9 +4183,7 @@ mod tests { connection: &Connection, order_id: OrderId, agreement: &str, - fulfillment: Option<&str>, inventory: &str, - payment: &str, provenance_source: &str, provenance_last_event_id: Option<&str>, ) { @@ -4263,17 +4191,13 @@ mod tests { .execute( "update orders set workflow_agreement = ?1, - workflow_fulfillment = ?2, - workflow_inventory = ?3, - workflow_payment = ?4, - workflow_provenance_source = ?5, - workflow_provenance_last_event_id = ?6 - where id = ?7", + workflow_inventory = ?2, + workflow_provenance_source = ?3, + workflow_provenance_last_event_id = ?4 + where id = ?5", params![ agreement, - fulfillment, inventory, - payment, provenance_source, provenance_last_event_id, order_id.to_string(), @@ -4344,9 +4268,7 @@ mod tests { .expect("check constraints should disable"); let statement = match column { "workflow_agreement" => "update orders set workflow_agreement = ?1 where id = ?2", - "workflow_fulfillment" => "update orders set workflow_fulfillment = ?1 where id = ?2", "workflow_inventory" => "update orders set workflow_inventory = ?1 where id = ?2", - "workflow_payment" => "update orders set workflow_payment = ?1 where id = ?2", "workflow_provenance_source" => { "update orders set workflow_provenance_source = ?1 where id = ?2" } diff --git a/crates/store/src/repo/orders.rs b/crates/store/src/repo/orders.rs @@ -2,12 +2,12 @@ use std::collections::BTreeMap; use radroots_app_view::{ FarmId, FulfillmentWindowId, FulfillmentWindowSummary, OrderDetailItemRow, - OrderDetailProjection, OrderFulfillmentAction, OrderId, OrderPrimaryAction, OrderStatus, - OrdersFilter, OrdersListProjection, OrdersListRow, OrdersListSummary, OrdersScreenQueryState, + OrderDetailProjection, OrderId, OrderPrimaryAction, OrderStatus, OrdersFilter, + OrdersListProjection, OrdersListRow, OrdersListSummary, OrdersScreenQueryState, PackDayOutputCustomerOrder, PackDayOutputOrderState, PackDayOutputPackListEntry, PackDayOutputProductTotal, PackDayOutputQuantity, PackDayOutputSource, PackDayOutputWindow, PackDayPackListRow, PackDayProductTotalRow, PackDayProjection, PackDayRosterRow, - PackDayScreenQueryState, ProductId, TradeFulfillmentStatus, TradeWorkflowProjection, + PackDayScreenQueryState, ProductId, TradeWorkflowProjection, }; use rusqlite::{Connection, OptionalExtension, params}; @@ -80,13 +80,7 @@ impl<'a> AppOrdersRepository<'a> { o.fulfillment_window_id, o.workflow_revision, o.workflow_agreement, - o.workflow_fulfillment, - o.workflow_receipt_event_id, - o.workflow_receipt_received, - o.workflow_receipt_issue, - o.workflow_receipt_received_at, o.workflow_inventory, - o.workflow_payment, o.workflow_provenance_source, o.workflow_provenance_last_event_id, fw.label, @@ -107,17 +101,11 @@ impl<'a> AppOrdersRepository<'a> { row.get::<_, Option<String>>(5)?, row.get::<_, String>(6)?, row.get::<_, String>(7)?, - row.get::<_, Option<String>>(8)?, - row.get::<_, Option<String>>(9)?, - row.get::<_, Option<i64>>(10)?, + row.get::<_, String>(8)?, + row.get::<_, String>(9)?, + row.get::<_, Option<String>>(10)?, row.get::<_, Option<String>>(11)?, - row.get::<_, Option<i64>>(12)?, - row.get::<_, String>(13)?, - row.get::<_, String>(14)?, - row.get::<_, String>(15)?, - row.get::<_, Option<String>>(16)?, - row.get::<_, Option<String>>(17)?, - row.get::<_, Option<String>>(18)?, + row.get::<_, Option<String>>(12)?, )) }, ) @@ -138,13 +126,7 @@ impl<'a> AppOrdersRepository<'a> { fulfillment_window_id, workflow_revision, workflow_agreement, - workflow_fulfillment, - workflow_receipt_event_id, - workflow_receipt_received, - workflow_receipt_issue, - workflow_receipt_received_at, workflow_inventory, - workflow_payment, workflow_provenance_source, workflow_provenance_last_event_id, fulfillment_window_label, @@ -163,17 +145,10 @@ impl<'a> AppOrdersRepository<'a> { revision, economics: economics.clone(), agreement: workflow_agreement, - fulfillment: workflow_fulfillment, - receipt_event_id: workflow_receipt_event_id, - receipt_received: workflow_receipt_received, - receipt_issue: workflow_receipt_issue, - receipt_received_at: workflow_receipt_received_at, inventory: workflow_inventory, - payment: workflow_payment, provenance_source: workflow_provenance_source, provenance_last_event_id: workflow_provenance_last_event_id, })?; - let payment = workflow.payment; let validation_receipts = order_validation_receipts(self.connection, order_id)?; Ok(OrderDetailProjection { order_id, @@ -189,10 +164,8 @@ impl<'a> AppOrdersRepository<'a> { pickup_location_label: empty_string_to_none(pickup_location_label), items, economics, - payment, validation_receipts, primary_action: primary_action_for_order(status, &workflow), - fulfillment_actions: fulfillment_actions_for_order(status, &workflow), workflow, recoveries: Vec::new(), }) @@ -315,13 +288,7 @@ impl<'a> AppOrdersRepository<'a> { o.status, o.workflow_revision, o.workflow_agreement, - o.workflow_fulfillment, - o.workflow_receipt_event_id, - o.workflow_receipt_received, - o.workflow_receipt_issue, - o.workflow_receipt_received_at, o.workflow_inventory, - o.workflow_payment, o.workflow_provenance_source, o.workflow_provenance_last_event_id, fw.label, @@ -353,17 +320,11 @@ impl<'a> AppOrdersRepository<'a> { row.get::<_, String>(5)?, row.get::<_, String>(6)?, row.get::<_, String>(7)?, - row.get::<_, Option<String>>(8)?, - row.get::<_, Option<String>>(9)?, - row.get::<_, Option<i64>>(10)?, + row.get::<_, String>(8)?, + row.get::<_, String>(9)?, + row.get::<_, Option<String>>(10)?, row.get::<_, Option<String>>(11)?, - row.get::<_, Option<i64>>(12)?, - row.get::<_, String>(13)?, - row.get::<_, String>(14)?, - row.get::<_, String>(15)?, - row.get::<_, Option<String>>(16)?, - row.get::<_, Option<String>>(17)?, - row.get::<_, Option<String>>(18)?, + row.get::<_, Option<String>>(12)?, )) }, ) @@ -383,13 +344,7 @@ impl<'a> AppOrdersRepository<'a> { status, workflow_revision, workflow_agreement, - workflow_fulfillment, - workflow_receipt_event_id, - workflow_receipt_received, - workflow_receipt_issue, - workflow_receipt_received_at, workflow_inventory, - workflow_payment, workflow_provenance_source, workflow_provenance_last_event_id, fulfillment_window_label, @@ -410,13 +365,7 @@ impl<'a> AppOrdersRepository<'a> { revision, economics, agreement: workflow_agreement, - fulfillment: workflow_fulfillment, - receipt_event_id: workflow_receipt_event_id, - receipt_received: workflow_receipt_received, - receipt_issue: workflow_receipt_issue, - receipt_received_at: workflow_receipt_received_at, inventory: workflow_inventory, - payment: workflow_payment, provenance_source: workflow_provenance_source, provenance_last_event_id: workflow_provenance_last_event_id, })?; @@ -1187,7 +1136,6 @@ impl OrderRecord { OrdersFilter::Scheduled => self.status == OrderStatus::Scheduled, OrdersFilter::Packed => self.status == OrderStatus::Packed, OrdersFilter::Completed => self.status == OrderStatus::Completed, - OrdersFilter::Refunded => self.status == OrderStatus::Refunded, } } @@ -1202,7 +1150,6 @@ impl OrderRecord { pickup_location_label: self.pickup_location_label, status: self.status, primary_action: primary_action_for_order(self.status, &self.workflow), - fulfillment_actions: fulfillment_actions_for_order(self.status, &self.workflow), workflow: self.workflow, } } @@ -1219,7 +1166,7 @@ fn summarize_orders(records: &[OrderRecord]) -> OrdersListSummary { OrderStatus::NeedsAction | OrderStatus::NeedsReview => summary.needs_action_orders += 1, OrderStatus::Scheduled => summary.scheduled_orders += 1, OrderStatus::Packed => summary.packed_orders += 1, - OrderStatus::Completed | OrderStatus::Declined | OrderStatus::Refunded => {} + OrderStatus::Completed | OrderStatus::Declined => {} } } @@ -1228,45 +1175,18 @@ fn summarize_orders(records: &[OrderRecord]) -> OrdersListSummary { fn primary_action_for_order( status: OrderStatus, - workflow: &TradeWorkflowProjection, + _workflow: &TradeWorkflowProjection, ) -> Option<OrderPrimaryAction> { match status { OrderStatus::NeedsAction => Some(OrderPrimaryAction::Review), - OrderStatus::Scheduled | OrderStatus::Packed => match workflow.fulfillment { - None | Some(TradeFulfillmentStatus::Confirmed | TradeFulfillmentStatus::Preparing) => { - Some(OrderPrimaryAction::PublishPreparing) - } - Some( - TradeFulfillmentStatus::ReadyForPickup | TradeFulfillmentStatus::OutForDelivery, - ) => Some(OrderPrimaryAction::PublishDelivered), - Some(TradeFulfillmentStatus::Delivered | TradeFulfillmentStatus::Cancelled) => None, - }, - OrderStatus::Completed + OrderStatus::Scheduled + | OrderStatus::Packed + | OrderStatus::Completed | OrderStatus::Declined - | OrderStatus::Refunded | OrderStatus::NeedsReview => None, } } -fn fulfillment_actions_for_order( - status: OrderStatus, - workflow: &TradeWorkflowProjection, -) -> Vec<OrderFulfillmentAction> { - match (status, workflow.fulfillment) { - ( - OrderStatus::Scheduled | OrderStatus::Packed, - None - | Some( - TradeFulfillmentStatus::Confirmed - | TradeFulfillmentStatus::Preparing - | TradeFulfillmentStatus::ReadyForPickup - | TradeFulfillmentStatus::OutForDelivery, - ), - ) => OrderFulfillmentAction::ALL.to_vec(), - _ => Vec::new(), - } -} - fn format_quantity_display(quantity_value: u32, quantity_unit_label: &str) -> String { if quantity_unit_label.trim().is_empty() { quantity_value.to_string() @@ -1321,7 +1241,6 @@ fn parse_order_status(field: &'static str, value: String) -> Result<OrderStatus, "packed" => Ok(OrderStatus::Packed), "completed" => Ok(OrderStatus::Completed), "declined" => Ok(OrderStatus::Declined), - "refunded" => Ok(OrderStatus::Refunded), "needs_review" => Ok(OrderStatus::NeedsReview), _ => Err(AppSqliteError::DecodeEnum { field, value }), } @@ -1351,10 +1270,9 @@ fn empty_string_to_none(value: Option<String>) -> Option<String> { #[cfg(test)] mod tests { use radroots_app_view::{ - FarmId, FulfillmentWindowId, OrderFulfillmentAction, OrderId, OrderPrimaryAction, - OrderStatus, OrdersFilter, OrdersScreenQueryState, PackDayOutputOrderState, - PackDayProductTotalRow, PackDayScreenQueryState, PickupLocationId, TradeAgreementStatus, - TradeFulfillmentStatus, TradeInventoryStatus, TradePaymentDisplayStatus, + FarmId, FulfillmentWindowId, OrderId, OrderPrimaryAction, OrderStatus, OrdersFilter, + OrdersScreenQueryState, PackDayOutputOrderState, PackDayProductTotalRow, + PackDayScreenQueryState, PickupLocationId, TradeAgreementStatus, TradeInventoryStatus, TradeRevisionStatus, TradeWorkflowSource, }; use rusqlite::{Connection, params}; @@ -1453,7 +1371,7 @@ mod tests { None, "R-104", "Alex", - "refunded", + "needs_review", "2026-04-17T14:00:00Z", ); insert_order( @@ -1573,15 +1491,7 @@ mod tests { assert_eq!(detail.items[1].quantity_display, "1 bunch"); assert_eq!(detail.economics.total_minor_units, Some(1950)); assert_eq!(detail.economics.currency_code.as_deref(), Some("USD")); - assert_eq!(detail.payment, TradePaymentDisplayStatus::NotRecorded); - assert_eq!( - detail.primary_action, - Some(OrderPrimaryAction::PublishPreparing) - ); - assert_eq!( - detail.fulfillment_actions, - OrderFulfillmentAction::ALL.to_vec() - ); + assert_eq!(detail.primary_action, None); } #[test] @@ -1693,9 +1603,7 @@ mod tests { connection, order_id, "confirmed", - Some("ready_for_pickup"), "reserved", - "recorded", "local_events", Some("seller-workflow-event"), ); @@ -1717,12 +1625,7 @@ mod tests { assert_eq!(workflow.agreement, TradeAgreementStatus::Confirmed); assert_eq!(workflow.revision, TradeRevisionStatus::Updated); - assert_eq!( - workflow.fulfillment, - Some(TradeFulfillmentStatus::ReadyForPickup) - ); assert_eq!(workflow.inventory, TradeInventoryStatus::Reserved); - assert_eq!(workflow.payment, TradePaymentDisplayStatus::Recorded); assert_eq!( workflow.provenance.primary_source, TradeWorkflowSource::LocalEvents @@ -1733,84 +1636,9 @@ mod tests { ); assert_eq!(workflow.economics.total_minor_units, Some(1300)); assert_eq!(workflow.economics.currency_code.as_deref(), Some("USD")); - assert_eq!(detail.payment, TradePaymentDisplayStatus::Recorded); assert_eq!(detail.workflow, *workflow); - assert_eq!( - list.rows[0].primary_action, - Some(OrderPrimaryAction::PublishDelivered) - ); - assert_eq!( - list.rows[0].fulfillment_actions, - OrderFulfillmentAction::ALL.to_vec() - ); - assert_eq!( - detail.primary_action, - Some(OrderPrimaryAction::PublishDelivered) - ); - assert_eq!( - detail.fulfillment_actions, - OrderFulfillmentAction::ALL.to_vec() - ); - } - - #[test] - fn seller_delivered_workflow_exposes_no_duplicate_primary_action() { - let store = AppSqliteStore::open(DatabaseTarget::InMemory).expect("store should open"); - let connection = store.connection(); - let farm_id = FarmId::new(); - let order_id = OrderId::new(); - - insert_farm( - connection, - farm_id, - "Willow farm", - "ready", - "2026-04-17T08:00:00Z", - ); - insert_order( - connection, - order_id, - farm_id, - None, - "R-101", - "Casey", - "packed", - "2026-04-17T10:00:00Z", - ); - set_order_workflow_display_projection( - connection, - order_id, - "confirmed", - Some("delivered"), - "reserved", - "not_recorded", - "local_events", - Some("seller-delivered-event"), - ); - - let list = store - .load_orders_list( - farm_id, - &OrdersScreenQueryState { - filter: OrdersFilter::Packed, - fulfillment_window_id: None, - }, - ) - .expect("seller list should load"); - let detail = store - .load_order_detail(farm_id, order_id) - .expect("seller detail should load") - .expect("seller detail should exist"); - - assert_eq!(list.rows[0].status, OrderStatus::Packed); - assert_eq!( - list.rows[0].workflow.fulfillment, - Some(TradeFulfillmentStatus::Delivered) - ); assert_eq!(list.rows[0].primary_action, None); - assert_eq!(list.rows[0].fulfillment_actions, Vec::new()); assert_eq!(detail.primary_action, None); - assert_eq!(detail.fulfillment_actions, Vec::new()); } #[test] @@ -1841,18 +1669,14 @@ mod tests { connection, order_id, "confirmed", - Some("ready_for_pickup"), "reserved", - "recorded", "local_events", Some("seller-workflow-event"), ); for (column, expected_field) in [ ("workflow_agreement", "orders.workflow_agreement"), - ("workflow_fulfillment", "orders.workflow_fulfillment"), ("workflow_inventory", "orders.workflow_inventory"), - ("workflow_payment", "orders.workflow_payment"), ( "workflow_provenance_source", "orders.workflow_provenance_source", @@ -1862,9 +1686,7 @@ mod tests { connection, order_id, "confirmed", - Some("ready_for_pickup"), "reserved", - "recorded", "local_events", Some("seller-workflow-event"), ); @@ -2389,9 +2211,7 @@ mod tests { connection: &Connection, order_id: OrderId, agreement: &str, - fulfillment: Option<&str>, inventory: &str, - payment: &str, provenance_source: &str, provenance_last_event_id: Option<&str>, ) { @@ -2399,17 +2219,13 @@ mod tests { .execute( "update orders set workflow_agreement = ?1, - workflow_fulfillment = ?2, - workflow_inventory = ?3, - workflow_payment = ?4, - workflow_provenance_source = ?5, - workflow_provenance_last_event_id = ?6 - where id = ?7", + workflow_inventory = ?2, + workflow_provenance_source = ?3, + workflow_provenance_last_event_id = ?4 + where id = ?5", params![ agreement, - fulfillment, inventory, - payment, provenance_source, provenance_last_event_id, order_id.to_string(), @@ -2429,9 +2245,7 @@ mod tests { .expect("check constraints should disable"); let statement = match column { "workflow_agreement" => "update orders set workflow_agreement = ?1 where id = ?2", - "workflow_fulfillment" => "update orders set workflow_fulfillment = ?1 where id = ?2", "workflow_inventory" => "update orders set workflow_inventory = ?1 where id = ?2", - "workflow_payment" => "update orders set workflow_payment = ?1 where id = ?2", "workflow_provenance_source" => { "update orders set workflow_provenance_source = ?1 where id = ?2" } diff --git a/crates/store/src/repo/reminders.rs b/crates/store/src/repo/reminders.rs @@ -536,7 +536,6 @@ fn parse_reminder_kind(value: String) -> Result<ReminderKind, AppSqliteError> { "fulfillment_window" => Ok(ReminderKind::FulfillmentWindow), "order_action" => Ok(ReminderKind::OrderAction), "missed_pickup_recovery" => Ok(ReminderKind::MissedPickupRecovery), - "refund_recovery" => Ok(ReminderKind::RefundRecovery), "sync_impact" => Ok(ReminderKind::SyncImpact), _ => Err(AppSqliteError::DecodeEnum { field: "reminder_schedules.reminder_kind", @@ -586,7 +585,6 @@ fn parse_reminder_delivery_state(value: String) -> Result<ReminderDeliveryState, fn parse_recovery_kind(value: String) -> Result<RecoveryKind, AppSqliteError> { match value.as_str() { "missed_pickup" => Ok(RecoveryKind::MissedPickup), - "refund_follow_up" => Ok(RecoveryKind::RefundFollowUp), _ => Err(AppSqliteError::DecodeEnum { field: "order_recovery_records.recovery_kind", value, @@ -718,8 +716,8 @@ mod tests { farm_id, &ReminderLogEntryProjection { reminder_id: second_reminder_id, - kind: ReminderKind::RefundRecovery, - title: "Refund follow-up pending".to_owned(), + kind: ReminderKind::MissedPickupRecovery, + title: "Pickup follow-up pending".to_owned(), recorded_at: "2026-04-25T13:00:00Z".to_owned(), delivery_state: ReminderDeliveryState::Acknowledged, detail: Some("Customer requested a callback.".to_owned()), diff --git a/crates/store/src/repo/workflow.rs b/crates/store/src/repo/workflow.rs @@ -1,7 +1,6 @@ use radroots_app_view::{ - OrderId, TradeAgreementStatus, TradeEconomicsProjection, TradeFulfillmentStatus, - TradeInventoryStatus, TradePaymentDisplayStatus, TradeProvenanceProjection, - TradeReceiptProjection, TradeRevisionStatus, TradeWorkflowProjection, TradeWorkflowSource, + OrderId, TradeAgreementStatus, TradeEconomicsProjection, TradeInventoryStatus, + TradeProvenanceProjection, TradeRevisionStatus, TradeWorkflowProjection, TradeWorkflowSource, }; use crate::AppSqliteError; @@ -12,13 +11,7 @@ pub(super) struct StoredTradeWorkflowSnapshot { pub revision: TradeRevisionStatus, pub economics: TradeEconomicsProjection, pub agreement: String, - pub fulfillment: Option<String>, - pub receipt_event_id: Option<String>, - pub receipt_received: Option<i64>, - pub receipt_issue: Option<String>, - pub receipt_received_at: Option<i64>, pub inventory: String, - pub payment: String, pub provenance_source: String, pub provenance_last_event_id: Option<String>, } @@ -30,19 +23,8 @@ pub(super) fn trade_workflow_projection_from_storage( order_id: snapshot.order_id, agreement: parse_trade_agreement_status("orders.workflow_agreement", snapshot.agreement)?, revision: snapshot.revision, - fulfillment: snapshot - .fulfillment - .map(|value| parse_trade_fulfillment_status("orders.workflow_fulfillment", value)) - .transpose()?, - receipt: trade_receipt_projection_from_storage( - snapshot.receipt_event_id, - snapshot.receipt_received, - snapshot.receipt_issue, - snapshot.receipt_received_at, - )?, economics: snapshot.economics, inventory: parse_trade_inventory_status("orders.workflow_inventory", snapshot.inventory)?, - payment: parse_trade_payment_display_status("orders.workflow_payment", snapshot.payment)?, provenance: TradeProvenanceProjection::from_primary_source(parse_trade_workflow_source( "orders.workflow_provenance_source", snapshot.provenance_source, @@ -51,40 +33,6 @@ pub(super) fn trade_workflow_projection_from_storage( }) } -fn trade_receipt_projection_from_storage( - event_id: Option<String>, - received: Option<i64>, - issue: Option<String>, - received_at: Option<i64>, -) -> Result<Option<TradeReceiptProjection>, AppSqliteError> { - match (event_id, received, received_at) { - (None, None, None) => Ok(None), - (Some(event_id), Some(received), Some(received_at)) => Ok(Some(TradeReceiptProjection { - event_id, - received: parse_workflow_receipt_received(received)?, - issue, - received_at: u64::try_from(received_at).map_err(|_| { - AppSqliteError::InvalidProjection { - reason: "orders.workflow_receipt_received_at must be non-negative", - } - })?, - })), - _ => Err(AppSqliteError::InvalidProjection { - reason: "orders.workflow_receipt projection is incomplete", - }), - } -} - -fn parse_workflow_receipt_received(value: i64) -> Result<bool, AppSqliteError> { - match value { - 0 => Ok(false), - 1 => Ok(true), - _ => Err(AppSqliteError::InvalidProjection { - reason: "orders.workflow_receipt_received must be 0 or 1", - }), - } -} - fn parse_trade_agreement_status( field: &'static str, value: String, @@ -94,27 +42,11 @@ fn parse_trade_agreement_status( "confirmed" => Ok(TradeAgreementStatus::Confirmed), "declined" => Ok(TradeAgreementStatus::Declined), "cancelled" => Ok(TradeAgreementStatus::Cancelled), - "completed" => Ok(TradeAgreementStatus::Completed), "needs_review" => Ok(TradeAgreementStatus::NeedsReview), _ => Err(AppSqliteError::DecodeEnum { field, value }), } } -fn parse_trade_fulfillment_status( - field: &'static str, - value: String, -) -> Result<TradeFulfillmentStatus, AppSqliteError> { - match value.as_str() { - "confirmed" => Ok(TradeFulfillmentStatus::Confirmed), - "preparing" => Ok(TradeFulfillmentStatus::Preparing), - "ready_for_pickup" => Ok(TradeFulfillmentStatus::ReadyForPickup), - "out_for_delivery" => Ok(TradeFulfillmentStatus::OutForDelivery), - "delivered" => Ok(TradeFulfillmentStatus::Delivered), - "cancelled" => Ok(TradeFulfillmentStatus::Cancelled), - _ => Err(AppSqliteError::DecodeEnum { field, value }), - } -} - fn parse_trade_inventory_status( field: &'static str, value: String, @@ -128,20 +60,6 @@ fn parse_trade_inventory_status( } } -fn parse_trade_payment_display_status( - field: &'static str, - value: String, -) -> Result<TradePaymentDisplayStatus, AppSqliteError> { - match value.as_str() { - "not_recorded" => Ok(TradePaymentDisplayStatus::NotRecorded), - "pending" => Ok(TradePaymentDisplayStatus::Pending), - "recorded" => Ok(TradePaymentDisplayStatus::Recorded), - "settled" => Ok(TradePaymentDisplayStatus::Settled), - "needs_review" => Ok(TradePaymentDisplayStatus::NeedsReview), - _ => Err(AppSqliteError::DecodeEnum { field, value }), - } -} - fn parse_trade_workflow_source( field: &'static str, value: String, @@ -155,42 +73,3 @@ fn parse_trade_workflow_source( _ => Err(AppSqliteError::DecodeEnum { field, value }), } } - -#[cfg(test)] -mod tests { - use super::parse_trade_payment_display_status; - use crate::AppSqliteError; - use radroots_app_view::TradePaymentDisplayStatus; - - #[test] - fn workflow_payment_display_parser_accepts_all_payment_states() { - for (stored, expected) in [ - ("not_recorded", TradePaymentDisplayStatus::NotRecorded), - ("pending", TradePaymentDisplayStatus::Pending), - ("recorded", TradePaymentDisplayStatus::Recorded), - ("settled", TradePaymentDisplayStatus::Settled), - ("needs_review", TradePaymentDisplayStatus::NeedsReview), - ] { - assert_eq!( - parse_trade_payment_display_status("orders.workflow_payment", stored.to_owned()) - .expect("workflow payment should parse"), - expected - ); - } - } - - #[test] - fn workflow_payment_display_parser_rejects_unknown_payment_state() { - let error = - parse_trade_payment_display_status("orders.workflow_payment", "unknown".to_owned()) - .expect_err("unknown workflow payment should reject"); - - match error { - AppSqliteError::DecodeEnum { field, value } => { - assert_eq!(field, "orders.workflow_payment"); - assert_eq!(value, "unknown"); - } - other => panic!("expected DecodeEnum error, got {other:?}"), - } - } -} diff --git a/crates/sync/src/lib.rs b/crates/sync/src/lib.rs @@ -5,7 +5,6 @@ mod publish; pub use publish::{ AppFarmProfilePublishPayload, AppListingPublishPayload, AppOrderCancellationPublishPayload, AppOrderDecisionInventoryCommitment, AppOrderDecisionPayload, AppOrderDecisionPublishPayload, - AppOrderFulfillmentPublishPayload, AppOrderReceiptOutcome, AppOrderReceiptPublishPayload, AppOrderRequestItemPayload, AppOrderRequestPublishPayload, AppOrderRevisionDecisionPublishPayload, AppOrderRevisionProposalPublishPayload, AppPublishContext, AppPublishPayload, AppPublishPayloadJsonError, AppPublishValidationFailure, diff --git a/crates/sync/src/publish.rs b/crates/sync/src/publish.rs @@ -2,13 +2,11 @@ use radroots_app_view::{ FarmId, FarmReadiness, FulfillmentWindowId, OrderId, ProductId, ProductStatus, }; use radroots_sdk::protocol::order::{ - RadrootsOrderEconomics, RadrootsOrderFulfillmentState, RadrootsOrderItem, - RadrootsOrderRevisionOutcome, + RadrootsOrderEconomics, RadrootsOrderItem, RadrootsOrderRevisionOutcome, }; use radroots_sdk::{ FARM_PUBLISH_OPERATION_KIND, LISTING_PUBLISH_OPERATION_KIND, ORDER_CANCELLATION_OPERATION_KIND, - ORDER_DECISION_OPERATION_KIND, ORDER_FULFILLMENT_UPDATE_OPERATION_KIND, - ORDER_RECEIPT_RECORD_OPERATION_KIND, ORDER_REVISION_DECISION_OPERATION_KIND, + ORDER_DECISION_OPERATION_KIND, ORDER_REVISION_DECISION_OPERATION_KIND, ORDER_REVISION_PROPOSAL_OPERATION_KIND, ORDER_SUBMIT_OPERATION_KIND, }; use serde::{Deserialize, Serialize}; @@ -26,8 +24,6 @@ pub enum AppPublishWorkKind { OrderRevisionProposal, OrderRevisionDecision, OrderCancellation, - OrderFulfillment, - OrderReceipt, } impl AppPublishWorkKind { @@ -40,8 +36,6 @@ impl AppPublishWorkKind { Self::OrderRevisionProposal => "order_revision_proposal", Self::OrderRevisionDecision => "order_revision_decision", Self::OrderCancellation => "order_cancellation", - Self::OrderFulfillment => "order_fulfillment", - Self::OrderReceipt => "order_receipt", } } @@ -54,8 +48,6 @@ impl AppPublishWorkKind { Self::OrderRevisionProposal => ORDER_REVISION_PROPOSAL_OPERATION_KIND, Self::OrderRevisionDecision => ORDER_REVISION_DECISION_OPERATION_KIND, Self::OrderCancellation => ORDER_CANCELLATION_OPERATION_KIND, - Self::OrderFulfillment => ORDER_FULFILLMENT_UPDATE_OPERATION_KIND, - Self::OrderReceipt => ORDER_RECEIPT_RECORD_OPERATION_KIND, } } } @@ -220,20 +212,6 @@ pub struct AppOrderRevisionDecisionPublishPayload { } #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] -pub struct AppOrderFulfillmentPublishPayload { - pub context: AppPublishContext, - pub app_order_id: OrderId, - pub farm_id: FarmId, - pub trade_order_id: String, - pub request_event_id: String, - pub prev_event_id: String, - pub listing_addr: String, - pub buyer_pubkey: String, - pub seller_pubkey: String, - pub status: RadrootsOrderFulfillmentState, -} - -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] pub struct AppOrderCancellationPublishPayload { pub context: AppPublishContext, pub app_order_id: OrderId, @@ -248,50 +226,6 @@ pub struct AppOrderCancellationPublishPayload { } #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] -pub struct AppOrderReceiptPublishPayload { - pub context: AppPublishContext, - pub app_order_id: OrderId, - pub farm_id: FarmId, - pub trade_order_id: String, - pub request_event_id: String, - pub prev_event_id: String, - pub listing_addr: String, - pub buyer_pubkey: String, - pub seller_pubkey: String, - pub received: bool, - pub issue: Option<String>, - pub received_at: u64, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum AppOrderReceiptOutcome { - Received, - Issue { issue: String }, -} - -impl AppOrderReceiptOutcome { - pub fn issue(issue: impl Into<String>) -> Option<Self> { - let issue = issue.into().trim().to_owned(); - if issue.is_empty() { - None - } else { - Some(Self::Issue { issue }) - } - } - - pub const fn received(&self) -> bool { - matches!(self, Self::Received) - } - - pub fn issue_text(self) -> Option<String> { - match self { - Self::Received => None, - Self::Issue { issue } => Some(issue), - } - } -} - -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[serde(tag = "publish_kind", content = "payload", rename_all = "snake_case")] pub enum AppPublishPayload { FarmProfile(AppFarmProfilePublishPayload), @@ -301,8 +235,6 @@ pub enum AppPublishPayload { OrderRevisionProposal(AppOrderRevisionProposalPublishPayload), OrderRevisionDecision(AppOrderRevisionDecisionPublishPayload), OrderCancellation(AppOrderCancellationPublishPayload), - OrderFulfillment(AppOrderFulfillmentPublishPayload), - OrderReceipt(AppOrderReceiptPublishPayload), } impl AppPublishPayload { @@ -315,8 +247,6 @@ impl AppPublishPayload { Self::OrderRevisionProposal(_) => AppPublishWorkKind::OrderRevisionProposal, Self::OrderRevisionDecision(_) => AppPublishWorkKind::OrderRevisionDecision, Self::OrderCancellation(_) => AppPublishWorkKind::OrderCancellation, - Self::OrderFulfillment(_) => AppPublishWorkKind::OrderFulfillment, - Self::OrderReceipt(_) => AppPublishWorkKind::OrderReceipt, } } @@ -333,8 +263,6 @@ impl AppPublishPayload { Self::OrderRevisionProposal(payload) => SyncAggregateRef::Order(payload.app_order_id), Self::OrderRevisionDecision(payload) => SyncAggregateRef::Order(payload.app_order_id), Self::OrderCancellation(payload) => SyncAggregateRef::Order(payload.app_order_id), - Self::OrderFulfillment(payload) => SyncAggregateRef::Order(payload.app_order_id), - Self::OrderReceipt(payload) => SyncAggregateRef::Order(payload.app_order_id), } } @@ -562,44 +490,6 @@ impl AppPublishPayload { failures.push(AppPublishValidationFailure::MissingOrderCancellationReason); } } - Self::OrderFulfillment(payload) => { - validate_lifecycle_order_fields( - &payload.context, - payload.trade_order_id.as_str(), - payload.request_event_id.as_str(), - payload.prev_event_id.as_str(), - payload.listing_addr.as_str(), - payload.buyer_pubkey.as_str(), - payload.seller_pubkey.as_str(), - &mut failures, - ); - if !payload.status.is_publishable_update() { - failures.push(AppPublishValidationFailure::InvalidOrderFulfillmentStatus); - } - } - Self::OrderReceipt(payload) => { - validate_lifecycle_order_fields( - &payload.context, - payload.trade_order_id.as_str(), - payload.request_event_id.as_str(), - payload.prev_event_id.as_str(), - payload.listing_addr.as_str(), - payload.buyer_pubkey.as_str(), - payload.seller_pubkey.as_str(), - &mut failures, - ); - if payload.received { - if payload.issue.is_some() { - failures.push(AppPublishValidationFailure::UnexpectedOrderReceiptIssue); - } - } else if payload - .issue - .as_deref() - .is_none_or(|value| value.trim().is_empty()) - { - failures.push(AppPublishValidationFailure::MissingOrderReceiptIssue); - } - } } failures @@ -689,9 +579,6 @@ pub enum AppPublishValidationFailure { MissingOrderRevisionReason, MissingOrderRevisionDecisionReason, MissingOrderCancellationReason, - InvalidOrderFulfillmentStatus, - MissingOrderReceiptIssue, - UnexpectedOrderReceiptIssue, } impl AppPublishValidationFailure { @@ -731,9 +618,6 @@ impl AppPublishValidationFailure { Self::MissingOrderRevisionReason => "missing_order_revision_reason", Self::MissingOrderRevisionDecisionReason => "missing_order_revision_decision_reason", Self::MissingOrderCancellationReason => "missing_order_cancellation_reason", - Self::InvalidOrderFulfillmentStatus => "invalid_order_fulfillment_status", - Self::MissingOrderReceiptIssue => "missing_order_receipt_issue", - Self::UnexpectedOrderReceiptIssue => "unexpected_order_receipt_issue", } } } @@ -786,13 +670,11 @@ impl PendingSyncOperation { mod tests { use super::{ AppFarmProfilePublishPayload, AppListingPublishPayload, AppOrderCancellationPublishPayload, - AppOrderDecisionPayload, AppOrderDecisionPublishPayload, AppOrderFulfillmentPublishPayload, - AppOrderReceiptOutcome, AppOrderReceiptPublishPayload, AppOrderRequestItemPayload, + AppOrderDecisionPayload, AppOrderDecisionPublishPayload, AppOrderRequestItemPayload, AppOrderRequestPublishPayload, AppOrderRevisionDecisionPublishPayload, AppOrderRevisionProposalPublishPayload, AppPublishContext, AppPublishPayload, AppPublishValidationFailure, AppPublishWorkKind, FARM_PUBLISH_OPERATION_KIND, ORDER_CANCELLATION_OPERATION_KIND, ORDER_DECISION_OPERATION_KIND, - ORDER_FULFILLMENT_UPDATE_OPERATION_KIND, ORDER_RECEIPT_RECORD_OPERATION_KIND, ORDER_REVISION_DECISION_OPERATION_KIND, ORDER_REVISION_PROPOSAL_OPERATION_KIND, }; use crate::{ @@ -800,8 +682,7 @@ mod tests { }; use radroots_app_view::{FarmId, FarmReadiness, OrderId, ProductId, ProductStatus}; use radroots_sdk::protocol::order::{ - RadrootsOrderEconomics, RadrootsOrderFulfillmentState, RadrootsOrderItem, - RadrootsOrderRevisionOutcome, + RadrootsOrderEconomics, RadrootsOrderItem, RadrootsOrderRevisionOutcome, }; use serde_json::json; @@ -840,18 +721,7 @@ mod tests { } #[test] - fn order_receipt_outcome_requires_non_empty_issue_text() { - assert_eq!(AppOrderReceiptOutcome::issue(" "), None); - assert!(AppOrderReceiptOutcome::Received.received()); - - let issue = AppOrderReceiptOutcome::issue(" items need review ") - .expect("issue text should normalize"); - assert!(!issue.received()); - assert_eq!(issue.issue_text().as_deref(), Some("items need review")); - } - - #[test] - fn publish_work_kinds_keep_payment_and_checkout_surfaces_reserved() { + fn publish_work_kinds_are_current_agreement_surface() { let work_kinds = [ AppPublishWorkKind::FarmProfile, AppPublishWorkKind::Listing, @@ -860,31 +730,16 @@ mod tests { AppPublishWorkKind::OrderRevisionProposal, AppPublishWorkKind::OrderRevisionDecision, AppPublishWorkKind::OrderCancellation, - AppPublishWorkKind::OrderFulfillment, - AppPublishWorkKind::OrderReceipt, - ]; - let reserved_publish_tokens = [ - "payment", - "settlement", - "checkout", - "refund", - "wallet", - "invoice", - "processor", - "provider", - "escrow", - "custody", ]; - assert_eq!(work_kinds.len(), 9); - for work_kind in work_kinds { - let storage_key = work_kind.storage_key(); - let sdk_operation = work_kind.sdk_operation(); - for reserved_token in reserved_publish_tokens { - assert!(!storage_key.contains(reserved_token)); - assert!(!sdk_operation.contains(reserved_token)); - } - } + assert_eq!(work_kinds.len(), 7); + assert_eq!(work_kinds[0].storage_key(), "farm_profile"); + assert_eq!(work_kinds[1].storage_key(), "listing"); + assert_eq!(work_kinds[2].storage_key(), "order_request"); + assert_eq!(work_kinds[3].storage_key(), "order_decision"); + assert_eq!(work_kinds[4].storage_key(), "order_revision_proposal"); + assert_eq!(work_kinds[5].storage_key(), "order_revision_decision"); + assert_eq!(work_kinds[6].storage_key(), "order_cancellation"); } #[test] @@ -1025,7 +880,7 @@ mod tests { } #[test] - fn lifecycle_publish_payloads_report_stable_validation_reason_codes() { + fn cancellation_publish_payload_reports_stable_validation_reason_codes() { let order_id = OrderId::new(); let farm_id = FarmId::new(); let cancellation = @@ -1041,57 +896,17 @@ mod tests { seller_pubkey: String::new(), reason: " ".to_owned(), }); - let fulfillment = AppPublishPayload::OrderFulfillment(AppOrderFulfillmentPublishPayload { - context: AppPublishContext::new("acct_local", "seller_order_fulfillment"), - app_order_id: order_id, - farm_id, - trade_order_id: "order-1".to_owned(), - request_event_id: "request-event-1".to_owned(), - prev_event_id: "decision-event-1".to_owned(), - listing_addr: "30402:seller:listing".to_owned(), - buyer_pubkey: "buyer".to_owned(), - seller_pubkey: "seller".to_owned(), - status: RadrootsOrderFulfillmentState::ReadyForPickup, - }); - let receipt = AppPublishPayload::OrderReceipt(AppOrderReceiptPublishPayload { - context: AppPublishContext::new("acct_local", "buyer_order_receipt"), - app_order_id: order_id, - farm_id, - trade_order_id: "order-1".to_owned(), - request_event_id: "request-event-1".to_owned(), - prev_event_id: "fulfillment-event-1".to_owned(), - listing_addr: "30402:seller:listing".to_owned(), - buyer_pubkey: "buyer".to_owned(), - seller_pubkey: "seller".to_owned(), - received: true, - issue: Some("late".to_owned()), - received_at: 1_785_000_000, - }); assert_eq!( cancellation.work_kind().sdk_operation(), ORDER_CANCELLATION_OPERATION_KIND ); - assert_eq!( - fulfillment.work_kind().sdk_operation(), - ORDER_FULFILLMENT_UPDATE_OPERATION_KIND - ); - assert_eq!( - receipt.work_kind().sdk_operation(), - ORDER_RECEIPT_RECORD_OPERATION_KIND - ); - assert_eq!(fulfillment.validation_failures(), Vec::new()); let cancellation_reason_codes: Vec<&str> = cancellation .validation_failures() .into_iter() .map(AppPublishValidationFailure::storage_key) .collect(); - let receipt_reason_codes: Vec<&str> = receipt - .validation_failures() - .into_iter() - .map(AppPublishValidationFailure::storage_key) - .collect(); assert_eq!( cancellation_reason_codes, @@ -1107,33 +922,10 @@ mod tests { "missing_order_cancellation_reason", ] ); - assert_eq!(receipt_reason_codes, vec!["unexpected_order_receipt_issue"]); - - let invalid_fulfillment_reason_codes: Vec<&str> = - AppPublishPayload::OrderFulfillment(AppOrderFulfillmentPublishPayload { - context: AppPublishContext::new("acct_local", "seller_order_fulfillment"), - app_order_id: order_id, - farm_id, - trade_order_id: "order-1".to_owned(), - request_event_id: "request-event-1".to_owned(), - prev_event_id: "decision-event-1".to_owned(), - listing_addr: "30402:seller:listing".to_owned(), - buyer_pubkey: "buyer".to_owned(), - seller_pubkey: "seller".to_owned(), - status: RadrootsOrderFulfillmentState::AcceptedNotFulfilled, - }) - .validation_failures() - .into_iter() - .map(AppPublishValidationFailure::storage_key) - .collect(); - assert_eq!( - invalid_fulfillment_reason_codes, - vec!["invalid_order_fulfillment_status"] - ); let operation = PendingSyncOperation::from_publish_payload( - AppPublishPayload::OrderFulfillment(AppOrderFulfillmentPublishPayload { - context: AppPublishContext::new("acct_local", "seller_order_fulfillment"), + AppPublishPayload::OrderCancellation(AppOrderCancellationPublishPayload { + context: AppPublishContext::new("acct_local", "buyer_order_cancellation"), app_order_id: order_id, farm_id, trade_order_id: "order-1".to_owned(), @@ -1142,7 +934,7 @@ mod tests { listing_addr: "30402:seller:listing".to_owned(), buyer_pubkey: "buyer".to_owned(), seller_pubkey: "seller".to_owned(), - status: RadrootsOrderFulfillmentState::Delivered, + reason: "buyer cancelled order".to_owned(), }), "2026-04-20T18:00:00Z", ) @@ -1153,8 +945,8 @@ mod tests { assert_eq!(operation.operation, SyncOperationKind::Upsert); assert_eq!( operation.publish_payload().expect("payload should parse"), - AppPublishPayload::OrderFulfillment(AppOrderFulfillmentPublishPayload { - context: AppPublishContext::new("acct_local", "seller_order_fulfillment"), + AppPublishPayload::OrderCancellation(AppOrderCancellationPublishPayload { + context: AppPublishContext::new("acct_local", "buyer_order_cancellation"), app_order_id: order_id, farm_id, trade_order_id: "order-1".to_owned(), @@ -1163,7 +955,7 @@ mod tests { listing_addr: "30402:seller:listing".to_owned(), buyer_pubkey: "buyer".to_owned(), seller_pubkey: "seller".to_owned(), - status: RadrootsOrderFulfillmentState::Delivered, + reason: "buyer cancelled order".to_owned(), }) ); } diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs @@ -341,7 +341,6 @@ pub enum OrderStatus { Packed, Completed, Declined, - Refunded, NeedsReview, } @@ -353,7 +352,6 @@ impl OrderStatus { Self::Packed => "packed", Self::Completed => "completed", Self::Declined => "declined", - Self::Refunded => "refunded", Self::NeedsReview => "needs_review", } } @@ -367,7 +365,6 @@ pub enum BuyerOrderStatus { Ready, Completed, Declined, - Refunded, NeedsReview, } @@ -379,7 +376,6 @@ impl BuyerOrderStatus { Self::Ready => "ready", Self::Completed => "completed", Self::Declined => "declined", - Self::Refunded => "refunded", Self::NeedsReview => "needs_review", } } @@ -393,7 +389,6 @@ impl From<OrderStatus> for BuyerOrderStatus { OrderStatus::Packed => Self::Ready, OrderStatus::Completed => Self::Completed, OrderStatus::Declined => Self::Declined, - OrderStatus::Refunded => Self::Refunded, OrderStatus::NeedsReview => Self::NeedsReview, } } @@ -695,10 +690,7 @@ impl PackDayOutputOrderState { OrderStatus::NeedsAction => Some(Self::NeedsAction), OrderStatus::Scheduled => Some(Self::Scheduled), OrderStatus::Packed => Some(Self::Packed), - OrderStatus::Completed - | OrderStatus::Declined - | OrderStatus::Refunded - | OrderStatus::NeedsReview => None, + OrderStatus::Completed | OrderStatus::Declined | OrderStatus::NeedsReview => None, } } } @@ -845,7 +837,6 @@ pub enum ReminderKind { FulfillmentWindow, OrderAction, MissedPickupRecovery, - RefundRecovery, SyncImpact, } @@ -855,7 +846,6 @@ impl ReminderKind { Self::FulfillmentWindow => "fulfillment_window", Self::OrderAction => "order_action", Self::MissedPickupRecovery => "missed_pickup_recovery", - Self::RefundRecovery => "refund_recovery", Self::SyncImpact => "sync_impact", } } @@ -905,14 +895,12 @@ impl ReminderDeliveryState { #[serde(rename_all = "snake_case")] pub enum RecoveryKind { MissedPickup, - RefundFollowUp, } impl RecoveryKind { pub const fn storage_key(self) -> &'static str { match self { Self::MissedPickup => "missed_pickup", - Self::RefundFollowUp => "refund_follow_up", } } } diff --git a/crates/view/src/lib.rs b/crates/view/src/lib.rs @@ -3,11 +3,8 @@ pub use radroots_app_types::*; use radroots_core::RadrootsCoreMoney; -use radroots_events::order::{RadrootsOrderEconomics, RadrootsOrderFulfillmentState}; -use radroots_trade::order::{ - RadrootsOrderPaymentProjection, RadrootsOrderPaymentState, RadrootsOrderProjection, - RadrootsOrderSettlementState, RadrootsOrderStatus, -}; +use radroots_events::order::RadrootsOrderEconomics; +use radroots_trade::order::{RadrootsOrderProjection, RadrootsOrderStatus}; use radroots_trade::validation_receipt::{ RadrootsValidationReceiptProofSystem, RadrootsValidationReceiptResult, RadrootsValidationReceiptType, @@ -1079,7 +1076,6 @@ pub enum TradeAgreementStatus { Confirmed, Declined, Cancelled, - Completed, NeedsReview, } @@ -1090,7 +1086,6 @@ impl TradeAgreementStatus { Self::Confirmed => "confirmed", Self::Declined => "declined", Self::Cancelled => "cancelled", - Self::Completed => "completed", Self::NeedsReview => "needs_review", } } @@ -1101,7 +1096,6 @@ impl TradeAgreementStatus { Self::Confirmed => "messages.trade.workflow.agreement.confirmed", Self::Declined => "messages.trade.workflow.agreement.declined", Self::Cancelled => "messages.trade.workflow.agreement.cancelled", - Self::Completed => "messages.trade.workflow.agreement.completed", Self::NeedsReview => "messages.trade.workflow.agreement.needs_review", } } @@ -1113,8 +1107,7 @@ impl TradeAgreementStatus { RadrootsOrderStatus::Accepted => Self::Confirmed, RadrootsOrderStatus::Declined => Self::Declined, RadrootsOrderStatus::Cancelled => Self::Cancelled, - RadrootsOrderStatus::Completed => Self::Completed, - RadrootsOrderStatus::Disputed | RadrootsOrderStatus::Invalid => Self::NeedsReview, + RadrootsOrderStatus::Invalid => Self::NeedsReview, } } } @@ -1188,59 +1181,6 @@ impl TradeRevisionStatus { #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] -pub enum TradeFulfillmentStatus { - #[default] - Confirmed, - Preparing, - ReadyForPickup, - OutForDelivery, - Delivered, - Cancelled, -} - -impl TradeFulfillmentStatus { - pub const fn storage_key(self) -> &'static str { - match self { - Self::Confirmed => "confirmed", - Self::Preparing => "preparing", - Self::ReadyForPickup => "ready_for_pickup", - Self::OutForDelivery => "out_for_delivery", - Self::Delivered => "delivered", - Self::Cancelled => "cancelled", - } - } - - pub const fn label_key_id(self) -> &'static str { - match self { - Self::Confirmed => "messages.trade.workflow.fulfillment.confirmed", - Self::Preparing => "messages.trade.workflow.fulfillment.preparing", - Self::ReadyForPickup => "messages.trade.workflow.fulfillment.ready_for_pickup", - Self::OutForDelivery => "messages.trade.workflow.fulfillment.out_for_delivery", - Self::Delivered => "messages.trade.workflow.fulfillment.delivered", - Self::Cancelled => "messages.trade.workflow.fulfillment.cancelled", - } - } - - pub const fn from_active_fulfillment_status(status: &RadrootsOrderFulfillmentState) -> Self { - match status { - RadrootsOrderFulfillmentState::AcceptedNotFulfilled => Self::Confirmed, - RadrootsOrderFulfillmentState::Preparing => Self::Preparing, - RadrootsOrderFulfillmentState::ReadyForPickup => Self::ReadyForPickup, - RadrootsOrderFulfillmentState::OutForDelivery => Self::OutForDelivery, - RadrootsOrderFulfillmentState::Delivered => Self::Delivered, - RadrootsOrderFulfillmentState::SellerCancelled => Self::Cancelled, - } - } -} - -impl From<&RadrootsOrderFulfillmentState> for TradeFulfillmentStatus { - fn from(status: &RadrootsOrderFulfillmentState) -> Self { - Self::from_active_fulfillment_status(status) - } -} - -#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] pub enum TradeInventoryStatus { Available, Reserved, @@ -1269,95 +1209,11 @@ impl TradeInventoryStatus { } pub fn from_active_order_projection(projection: &RadrootsOrderProjection) -> Self { - match (&projection.status, projection.fulfillment_status.as_ref()) { - (RadrootsOrderStatus::Requested, _) => Self::NeedsReview, - ( - RadrootsOrderStatus::Accepted, - Some(RadrootsOrderFulfillmentState::SellerCancelled), - ) => Self::Available, - (RadrootsOrderStatus::Accepted, _) => Self::Reserved, - (RadrootsOrderStatus::Declined | RadrootsOrderStatus::Cancelled, _) => Self::Available, - (RadrootsOrderStatus::Completed, _) => Self::Reserved, - ( - RadrootsOrderStatus::Missing - | RadrootsOrderStatus::Disputed - | RadrootsOrderStatus::Invalid, - _, - ) => Self::NeedsReview, - } - } -} - -#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum TradePaymentDisplayStatus { - #[default] - NotRecorded, - Pending, - Recorded, - Settled, - NeedsReview, -} - -impl TradePaymentDisplayStatus { - pub const fn storage_key(self) -> &'static str { - match self { - Self::NotRecorded => "not_recorded", - Self::Pending => "pending", - Self::Recorded => "recorded", - Self::Settled => "settled", - Self::NeedsReview => "needs_review", - } - } - - pub const fn label_key_id(self) -> &'static str { - match self { - Self::NotRecorded => "messages.trade.workflow.payment.not_recorded", - Self::Pending => "messages.trade.workflow.payment.pending", - Self::Recorded => "messages.trade.workflow.payment.recorded", - Self::Settled => "messages.trade.workflow.payment.settled", - Self::NeedsReview => "messages.trade.workflow.payment.needs_review", - } - } - - pub const fn allows_payment_action(self) -> bool { - false - } - - pub fn from_active_payment_state(status: &RadrootsOrderPaymentState) -> Self { - match status { - RadrootsOrderPaymentState::NotRecorded => Self::NotRecorded, - RadrootsOrderPaymentState::Recorded => Self::Recorded, - RadrootsOrderPaymentState::Settled => Self::Settled, - RadrootsOrderPaymentState::Rejected | RadrootsOrderPaymentState::Invalid => { - Self::NeedsReview - } - } - } - - pub fn from_active_payment_projection(payment: &RadrootsOrderPaymentProjection) -> Self { - match (&payment.state, &payment.settlement_state) { - (RadrootsOrderPaymentState::NotRecorded, RadrootsOrderSettlementState::NotRequired) => { - Self::NotRecorded - } - (RadrootsOrderPaymentState::NotRecorded, _) => Self::NeedsReview, - (RadrootsOrderPaymentState::Recorded, RadrootsOrderSettlementState::Pending) => { - Self::Pending - } - (RadrootsOrderPaymentState::Recorded, RadrootsOrderSettlementState::NotRequired) => { - Self::Recorded - } - (RadrootsOrderPaymentState::Recorded, RadrootsOrderSettlementState::Accepted) => { - Self::Settled - } - ( - RadrootsOrderPaymentState::Recorded, - RadrootsOrderSettlementState::Rejected | RadrootsOrderSettlementState::Invalid, - ) => Self::NeedsReview, - (RadrootsOrderPaymentState::Settled, _) => Self::Settled, - (RadrootsOrderPaymentState::Rejected | RadrootsOrderPaymentState::Invalid, _) => { - Self::NeedsReview - } + match projection.status { + RadrootsOrderStatus::Requested => Self::NeedsReview, + RadrootsOrderStatus::Accepted => Self::Reserved, + RadrootsOrderStatus::Declined | RadrootsOrderStatus::Cancelled => Self::Available, + RadrootsOrderStatus::Missing | RadrootsOrderStatus::Invalid => Self::NeedsReview, } } } @@ -1463,25 +1319,6 @@ impl Default for TradeProvenanceProjection { } } -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] -pub struct TradeReceiptProjection { - pub event_id: String, - pub received: bool, - pub issue: Option<String>, - pub received_at: u64, -} - -impl TradeReceiptProjection { - pub fn from_active_order_projection(projection: &RadrootsOrderProjection) -> Option<Self> { - Some(Self { - event_id: projection.receipt_event_id.as_ref()?.to_string(), - received: projection.receipt_received?, - issue: projection.receipt_issue.clone(), - received_at: projection.receipt_received_at?, - }) - } -} - #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum TradeValidationReceiptResult { @@ -1615,11 +1452,8 @@ pub struct TradeWorkflowProjection { pub order_id: OrderId, pub agreement: TradeAgreementStatus, pub revision: TradeRevisionStatus, - pub fulfillment: Option<TradeFulfillmentStatus>, - pub receipt: Option<TradeReceiptProjection>, pub economics: TradeEconomicsProjection, pub inventory: TradeInventoryStatus, - pub payment: TradePaymentDisplayStatus, pub provenance: TradeProvenanceProjection, } @@ -1629,11 +1463,8 @@ impl TradeWorkflowProjection { order_id, agreement, revision: TradeRevisionStatus::None, - fulfillment: None, - receipt: None, economics: TradeEconomicsProjection::default(), inventory: TradeInventoryStatus::NeedsReview, - payment: TradePaymentDisplayStatus::NotRecorded, provenance: TradeProvenanceProjection::default(), } } @@ -1649,19 +1480,12 @@ impl TradeWorkflowProjection { TradeAgreementStatus::from_active_order_status(&projection.status), ); workflow.revision = revision; - workflow.fulfillment = projection - .fulfillment_status - .as_ref() - .map(TradeFulfillmentStatus::from_active_fulfillment_status); - workflow.receipt = TradeReceiptProjection::from_active_order_projection(projection); workflow.economics = projection .economics .as_ref() .map(TradeEconomicsProjection::from_trade_order_economics) .unwrap_or_default(); workflow.inventory = TradeInventoryStatus::from_active_order_projection(projection); - workflow.payment = - TradePaymentDisplayStatus::from_active_payment_projection(&projection.payment); workflow.provenance = provenance .with_last_event_id(projection.last_event_id.as_ref().map(ToString::to_string)); workflow @@ -1672,34 +1496,26 @@ impl TradeWorkflowProjection { OrderStatus::NeedsAction => Self::new(order_id, TradeAgreementStatus::Ordered), OrderStatus::Scheduled => Self::new(order_id, TradeAgreementStatus::Confirmed), OrderStatus::Packed => Self::new(order_id, TradeAgreementStatus::Confirmed), - OrderStatus::Completed => Self::new(order_id, TradeAgreementStatus::Completed), + OrderStatus::Completed => Self::new(order_id, TradeAgreementStatus::Confirmed), OrderStatus::Declined => Self::new(order_id, TradeAgreementStatus::Declined), - OrderStatus::Refunded | OrderStatus::NeedsReview => { - Self::new(order_id, TradeAgreementStatus::NeedsReview) - } + OrderStatus::NeedsReview => Self::new(order_id, TradeAgreementStatus::NeedsReview), }; match status { OrderStatus::NeedsAction => {} OrderStatus::Scheduled => { - projection.fulfillment = Some(TradeFulfillmentStatus::Confirmed); projection.inventory = TradeInventoryStatus::Reserved; } OrderStatus::Packed => { - projection.fulfillment = Some(TradeFulfillmentStatus::ReadyForPickup); projection.inventory = TradeInventoryStatus::Reserved; } OrderStatus::Completed => { - projection.fulfillment = Some(TradeFulfillmentStatus::Delivered); projection.inventory = TradeInventoryStatus::Reserved; } OrderStatus::Declined => { - projection.fulfillment = Some(TradeFulfillmentStatus::Cancelled); projection.inventory = TradeInventoryStatus::Available; } - OrderStatus::Refunded | OrderStatus::NeedsReview => { - projection.payment = TradePaymentDisplayStatus::NeedsReview; - } + OrderStatus::NeedsReview => {} } projection @@ -1710,34 +1526,26 @@ impl TradeWorkflowProjection { BuyerOrderStatus::Placed => Self::new(order_id, TradeAgreementStatus::Ordered), BuyerOrderStatus::Scheduled => Self::new(order_id, TradeAgreementStatus::Confirmed), BuyerOrderStatus::Ready => Self::new(order_id, TradeAgreementStatus::Confirmed), - BuyerOrderStatus::Completed => Self::new(order_id, TradeAgreementStatus::Completed), + BuyerOrderStatus::Completed => Self::new(order_id, TradeAgreementStatus::Confirmed), BuyerOrderStatus::Declined => Self::new(order_id, TradeAgreementStatus::Declined), - BuyerOrderStatus::Refunded | BuyerOrderStatus::NeedsReview => { - Self::new(order_id, TradeAgreementStatus::NeedsReview) - } + BuyerOrderStatus::NeedsReview => Self::new(order_id, TradeAgreementStatus::NeedsReview), }; match status { BuyerOrderStatus::Placed => {} BuyerOrderStatus::Scheduled => { - projection.fulfillment = Some(TradeFulfillmentStatus::Confirmed); projection.inventory = TradeInventoryStatus::Reserved; } BuyerOrderStatus::Ready => { - projection.fulfillment = Some(TradeFulfillmentStatus::ReadyForPickup); projection.inventory = TradeInventoryStatus::Reserved; } BuyerOrderStatus::Completed => { - projection.fulfillment = Some(TradeFulfillmentStatus::Delivered); projection.inventory = TradeInventoryStatus::Reserved; } BuyerOrderStatus::Declined => { - projection.fulfillment = Some(TradeFulfillmentStatus::Cancelled); projection.inventory = TradeInventoryStatus::Available; } - BuyerOrderStatus::Refunded | BuyerOrderStatus::NeedsReview => { - projection.payment = TradePaymentDisplayStatus::NeedsReview; - } + BuyerOrderStatus::NeedsReview => {} } projection @@ -1748,56 +1556,23 @@ impl TradeWorkflowProjection { self } - pub fn with_payment(mut self, payment: TradePaymentDisplayStatus) -> Self { - self.payment = payment; - self - } - pub fn with_revision(mut self, revision: TradeRevisionStatus) -> Self { self.revision = revision; self } - - pub fn with_economics_and_payment( - self, - economics: TradeEconomicsProjection, - payment: TradePaymentDisplayStatus, - ) -> Self { - self.with_economics(economics).with_payment(payment) - } } pub fn order_status_from_active_order_projection( projection: &RadrootsOrderProjection, ) -> Option<OrderStatus> { - match (&projection.status, projection.fulfillment_status.as_ref()) { - (RadrootsOrderStatus::Missing, _) => None, - (RadrootsOrderStatus::Requested, _) => Some(OrderStatus::NeedsAction), - ( - RadrootsOrderStatus::Accepted, - Some( - RadrootsOrderFulfillmentState::ReadyForPickup - | RadrootsOrderFulfillmentState::OutForDelivery - | RadrootsOrderFulfillmentState::Delivered, - ), - ) => Some(OrderStatus::Packed), - (RadrootsOrderStatus::Accepted, Some(RadrootsOrderFulfillmentState::SellerCancelled)) => { - Some(OrderStatus::Declined) - } - ( - RadrootsOrderStatus::Accepted, - Some( - RadrootsOrderFulfillmentState::Preparing - | RadrootsOrderFulfillmentState::AcceptedNotFulfilled, - ) - | None, - ) => Some(OrderStatus::Scheduled), - (RadrootsOrderStatus::Declined | RadrootsOrderStatus::Cancelled, _) => { + match projection.status { + RadrootsOrderStatus::Missing => None, + RadrootsOrderStatus::Requested => Some(OrderStatus::NeedsAction), + RadrootsOrderStatus::Accepted => Some(OrderStatus::Scheduled), + RadrootsOrderStatus::Declined | RadrootsOrderStatus::Cancelled => { Some(OrderStatus::Declined) } - (RadrootsOrderStatus::Completed, _) => Some(OrderStatus::Completed), - (RadrootsOrderStatus::Disputed, _) => Some(OrderStatus::NeedsReview), - (RadrootsOrderStatus::Invalid, _) => Some(OrderStatus::NeedsAction), + RadrootsOrderStatus::Invalid => Some(OrderStatus::NeedsAction), } } @@ -1810,7 +1585,6 @@ pub enum OrdersFilter { Scheduled, Packed, Completed, - Refunded, } impl OrdersFilter { @@ -1821,7 +1595,6 @@ impl OrdersFilter { Self::Scheduled => "scheduled", Self::Packed => "packed", Self::Completed => "completed", - Self::Refunded => "refunded", } } } @@ -1834,87 +1607,14 @@ pub struct OrdersScreenQueryState { #[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] -pub enum OrderFulfillmentAction { - Preparing, - ReadyForPickup, - OutForDelivery, - Delivered, - SellerCancelled, -} - -impl OrderFulfillmentAction { - pub const ALL: &'static [Self] = &[ - Self::Preparing, - Self::ReadyForPickup, - Self::OutForDelivery, - Self::Delivered, - Self::SellerCancelled, - ]; - - pub const fn storage_key(self) -> &'static str { - match self { - Self::Preparing => "publish_preparing", - Self::ReadyForPickup => "publish_ready_for_pickup", - Self::OutForDelivery => "publish_out_for_delivery", - Self::Delivered => "publish_delivered", - Self::SellerCancelled => "publish_seller_cancelled", - } - } - - pub const fn fulfillment_status(self) -> RadrootsOrderFulfillmentState { - match self { - Self::Preparing => RadrootsOrderFulfillmentState::Preparing, - Self::ReadyForPickup => RadrootsOrderFulfillmentState::ReadyForPickup, - Self::OutForDelivery => RadrootsOrderFulfillmentState::OutForDelivery, - Self::Delivered => RadrootsOrderFulfillmentState::Delivered, - Self::SellerCancelled => RadrootsOrderFulfillmentState::SellerCancelled, - } - } -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] pub enum OrderPrimaryAction { Review, - PublishPreparing, - PublishReadyForPickup, - PublishOutForDelivery, - PublishDelivered, - PublishSellerCancelled, } impl OrderPrimaryAction { pub const fn storage_key(self) -> &'static str { match self { Self::Review => "review", - Self::PublishPreparing => "publish_preparing", - Self::PublishReadyForPickup => "publish_ready_for_pickup", - Self::PublishOutForDelivery => "publish_out_for_delivery", - Self::PublishDelivered => "publish_delivered", - Self::PublishSellerCancelled => "publish_seller_cancelled", - } - } - - pub const fn fulfillment_action(self) -> Option<OrderFulfillmentAction> { - match self { - Self::Review => None, - Self::PublishPreparing => Some(OrderFulfillmentAction::Preparing), - Self::PublishReadyForPickup => Some(OrderFulfillmentAction::ReadyForPickup), - Self::PublishOutForDelivery => Some(OrderFulfillmentAction::OutForDelivery), - Self::PublishDelivered => Some(OrderFulfillmentAction::Delivered), - Self::PublishSellerCancelled => Some(OrderFulfillmentAction::SellerCancelled), - } - } -} - -impl From<OrderFulfillmentAction> for OrderPrimaryAction { - fn from(action: OrderFulfillmentAction) -> Self { - match action { - OrderFulfillmentAction::Preparing => Self::PublishPreparing, - OrderFulfillmentAction::ReadyForPickup => Self::PublishReadyForPickup, - OrderFulfillmentAction::OutForDelivery => Self::PublishOutForDelivery, - OrderFulfillmentAction::Delivered => Self::PublishDelivered, - OrderFulfillmentAction::SellerCancelled => Self::PublishSellerCancelled, } } } @@ -1945,7 +1645,6 @@ pub struct OrdersListRow { pub status: OrderStatus, pub workflow: TradeWorkflowProjection, pub primary_action: Option<OrderPrimaryAction>, - pub fulfillment_actions: Vec<OrderFulfillmentAction>, } #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] @@ -1980,11 +1679,9 @@ pub struct OrderDetailProjection { pub pickup_location_label: Option<String>, pub items: Vec<OrderDetailItemRow>, pub economics: TradeEconomicsProjection, - pub payment: TradePaymentDisplayStatus, pub workflow: TradeWorkflowProjection, pub validation_receipts: Vec<TradeValidationReceiptProjection>, pub primary_action: Option<OrderPrimaryAction>, - pub fulfillment_actions: Vec<OrderFulfillmentAction>, pub recoveries: Vec<OrderRecoveryProjection>, } @@ -2021,7 +1718,6 @@ pub struct BuyerOrderDetailProjection { pub status: BuyerOrderStatus, pub items: Vec<OrderDetailItemRow>, pub economics: TradeEconomicsProjection, - pub payment: TradePaymentDisplayStatus, pub workflow: TradeWorkflowProjection, pub validation_receipts: Vec<TradeValidationReceiptProjection>, pub order_note: Option<String>, @@ -2441,13 +2137,9 @@ mod tests { RadrootsOrderQuoteId, RadrootsPublicKey, }; use radroots_events::order::{ - RadrootsOrderEconomicItem, RadrootsOrderEconomics, RadrootsOrderFulfillmentState, - RadrootsOrderPricingBasis, - }; - use radroots_trade::order::{ - RadrootsOrderPaymentProjection, RadrootsOrderPaymentState, RadrootsOrderProjection, - RadrootsOrderSettlementState, RadrootsOrderStatus, + RadrootsOrderEconomicItem, RadrootsOrderEconomics, RadrootsOrderPricingBasis, }; + use radroots_trade::order::{RadrootsOrderProjection, RadrootsOrderStatus}; use radroots_trade::validation_receipt::{ RadrootsValidationReceiptProofSystem, RadrootsValidationReceiptResult, RadrootsValidationReceiptType, @@ -2465,12 +2157,12 @@ mod tests { FarmSetupProjection, FarmSetupReadiness, FarmSetupSection, FarmTimingConflict, FarmTimingConflictKind, FarmerActivationProjection, FarmerSection, FulfillmentWindowId, IdentityBlockedReason, IdentityReadiness, LoggedOutStartupPhase, - LoggedOutStartupProjection, OrderDetailItemRow, OrderDetailProjection, - OrderFulfillmentAction, OrderId, OrderListRow, OrderPrimaryAction, OrderRecoveryProjection, - OrderStatus, OrdersFilter, OrdersListProjection, OrdersListRow, OrdersListSummary, - OrdersScreenQueryState, PackDayBatchPrintArtifact, PackDayBatchPrintFailureKind, - PackDayBatchPrintStatus, PackDayExportArtifact, PackDayExportArtifactKind, - PackDayExportBundle, PackDayExportInstanceId, PackDayExportStatus, PackDayHostHandoffKind, + LoggedOutStartupProjection, OrderDetailItemRow, OrderDetailProjection, OrderId, + OrderListRow, OrderPrimaryAction, OrderRecoveryProjection, OrderStatus, OrdersFilter, + OrdersListProjection, OrdersListRow, OrdersListSummary, OrdersScreenQueryState, + PackDayBatchPrintArtifact, PackDayBatchPrintFailureKind, PackDayBatchPrintStatus, + PackDayExportArtifact, PackDayExportArtifactKind, PackDayExportBundle, + PackDayExportInstanceId, PackDayExportStatus, PackDayHostHandoffKind, PackDayHostHandoffStatus, PackDayOutputCustomerOrder, PackDayOutputOrderState, PackDayOutputPackListEntry, PackDayOutputProductTotal, PackDayOutputQuantity, PackDayOutputSource, PackDayOutputWindow, PackDayPackListRow, PackDayPrintFailureKind, @@ -2488,11 +2180,10 @@ mod tests { SelectedAccountProjection, SelectedSurfaceProjection, SettingsPreference, SettingsSection, ShellSection, StartupSignerEntryProjection, StartupSignerSource, StartupSignerSourceKind, TodayAgendaProjection, TodaySetupTask, TodaySetupTaskKind, TodaySummary, - TradeAgreementStatus, TradeEconomicsProjection, TradeFulfillmentStatus, - TradeInventoryStatus, TradePaymentDisplayStatus, TradeProvenanceProjection, - TradeRevisionStatus, TradeValidationReceiptProofSystem, TradeValidationReceiptResult, - TradeValidationReceiptType, TradeWorkflowProjection, TradeWorkflowSource, - order_status_from_active_order_projection, + TradeAgreementStatus, TradeEconomicsProjection, TradeInventoryStatus, + TradeProvenanceProjection, TradeRevisionStatus, TradeValidationReceiptProofSystem, + TradeValidationReceiptResult, TradeValidationReceiptType, TradeWorkflowProjection, + TradeWorkflowSource, order_status_from_active_order_projection, }; use std::{collections::BTreeSet, str::FromStr}; use uuid::Uuid; @@ -3067,14 +2758,12 @@ mod tests { assert_eq!(OrderStatus::Packed.storage_key(), "packed"); assert_eq!(OrderStatus::Completed.storage_key(), "completed"); assert_eq!(OrderStatus::Declined.storage_key(), "declined"); - assert_eq!(OrderStatus::Refunded.storage_key(), "refunded"); assert_eq!(OrderStatus::NeedsReview.storage_key(), "needs_review"); assert_eq!(BuyerOrderStatus::Placed.storage_key(), "placed"); assert_eq!(BuyerOrderStatus::Scheduled.storage_key(), "scheduled"); assert_eq!(BuyerOrderStatus::Ready.storage_key(), "ready"); assert_eq!(BuyerOrderStatus::Completed.storage_key(), "completed"); assert_eq!(BuyerOrderStatus::Declined.storage_key(), "declined"); - assert_eq!(BuyerOrderStatus::Refunded.storage_key(), "refunded"); assert_eq!(BuyerOrderStatus::NeedsReview.storage_key(), "needs_review"); assert_eq!( BuyerOrderStatus::from(OrderStatus::NeedsAction), @@ -3099,45 +2788,8 @@ mod tests { assert_eq!(OrdersFilter::Scheduled.storage_key(), "scheduled"); assert_eq!(OrdersFilter::Packed.storage_key(), "packed"); assert_eq!(OrdersFilter::Completed.storage_key(), "completed"); - assert_eq!(OrdersFilter::Refunded.storage_key(), "refunded"); assert_eq!(OrderPrimaryAction::Review.storage_key(), "review"); - assert_eq!( - OrderFulfillmentAction::Preparing.storage_key(), - "publish_preparing" - ); - assert_eq!( - OrderFulfillmentAction::ReadyForPickup.storage_key(), - "publish_ready_for_pickup" - ); - assert_eq!( - OrderFulfillmentAction::OutForDelivery.storage_key(), - "publish_out_for_delivery" - ); - assert_eq!( - OrderFulfillmentAction::Delivered.storage_key(), - "publish_delivered" - ); - assert_eq!( - OrderFulfillmentAction::SellerCancelled.storage_key(), - "publish_seller_cancelled" - ); - assert_eq!( - OrderFulfillmentAction::Preparing.fulfillment_status(), - RadrootsOrderFulfillmentState::Preparing - ); - assert_eq!( - OrderPrimaryAction::PublishReadyForPickup.storage_key(), - "publish_ready_for_pickup" - ); - assert_eq!( - OrderPrimaryAction::PublishOutForDelivery.storage_key(), - "publish_out_for_delivery" - ); - assert_eq!( - OrderPrimaryAction::PublishDelivered.storage_key(), - "publish_delivered" - ); } fn test_decimal(raw: &str) -> RadrootsCoreDecimal { @@ -3196,26 +2848,7 @@ mod tests { } } - fn test_payment_projection(state: RadrootsOrderPaymentState) -> RadrootsOrderPaymentProjection { - let mut projection = RadrootsOrderPaymentProjection::not_recorded(); - projection.payment_event_id = (!matches!(&state, RadrootsOrderPaymentState::NotRecorded)) - .then(|| { - test_event_id("4444444444444444444444444444444444444444444444444444444444444444") - }); - projection.settlement_state = match &state { - RadrootsOrderPaymentState::Settled => RadrootsOrderSettlementState::Accepted, - RadrootsOrderPaymentState::Invalid => RadrootsOrderSettlementState::Invalid, - _ => RadrootsOrderSettlementState::NotRequired, - }; - projection.state = state; - projection - } - - fn test_active_order_projection( - status: RadrootsOrderStatus, - fulfillment_status: Option<RadrootsOrderFulfillmentState>, - payment_state: RadrootsOrderPaymentState, - ) -> RadrootsOrderProjection { + fn test_active_order_projection(status: RadrootsOrderStatus) -> RadrootsOrderProjection { RadrootsOrderProjection { order_id: test_order_id("ord_AAAAAAAAAAAAAAAAAAAAAg"), status, @@ -3225,17 +2858,8 @@ mod tests { decision_event_id: Some(test_event_id( "2222222222222222222222222222222222222222222222222222222222222222", )), - fulfillment_event_id: fulfillment_status.as_ref().map(|_| { - test_event_id("3333333333333333333333333333333333333333333333333333333333333333") - }), - fulfillment_status, cancellation_event_id: None, - receipt_event_id: None, - receipt_received: None, - receipt_issue: None, - receipt_received_at: None, lifecycle_terminal: false, - payment: test_payment_projection(payment_state), economics: Some(test_trade_economics()), agreement_event_id: Some(test_event_id( "2222222222222222222222222222222222222222222222222222222222222222", @@ -3268,38 +2892,10 @@ mod tests { TradeAgreementStatus::Confirmed ); assert_eq!( - TradeAgreementStatus::from_active_order_status(&RadrootsOrderStatus::Disputed), - TradeAgreementStatus::NeedsReview - ); - assert_eq!( TradeAgreementStatus::from_active_order_status(&RadrootsOrderStatus::Invalid), TradeAgreementStatus::NeedsReview ); assert_eq!( - TradeFulfillmentStatus::from_active_fulfillment_status( - &RadrootsOrderFulfillmentState::AcceptedNotFulfilled - ), - TradeFulfillmentStatus::Confirmed - ); - assert_eq!( - TradeFulfillmentStatus::from_active_fulfillment_status( - &RadrootsOrderFulfillmentState::SellerCancelled - ), - TradeFulfillmentStatus::Cancelled - ); - assert_eq!( - TradePaymentDisplayStatus::from_active_payment_state( - &RadrootsOrderPaymentState::Settled - ), - TradePaymentDisplayStatus::Settled - ); - assert_eq!( - TradePaymentDisplayStatus::from_active_payment_state( - &RadrootsOrderPaymentState::Rejected - ), - TradePaymentDisplayStatus::NeedsReview - ); - assert_eq!( TradeRevisionStatus::try_from_storage_key("none"), Ok(TradeRevisionStatus::None) ); @@ -3327,11 +2923,7 @@ mod tests { ); let order_id = OrderId::new(); - let active_order = test_active_order_projection( - RadrootsOrderStatus::Accepted, - Some(RadrootsOrderFulfillmentState::ReadyForPickup), - RadrootsOrderPaymentState::Recorded, - ); + let active_order = test_active_order_projection(RadrootsOrderStatus::Accepted); let projection = TradeWorkflowProjection::from_active_order_projection( order_id, &active_order, @@ -3341,15 +2933,9 @@ mod tests { assert_eq!(projection.order_id, order_id); assert_eq!(projection.agreement, TradeAgreementStatus::Confirmed); assert_eq!(projection.revision, TradeRevisionStatus::Updated); - assert_eq!( - projection.fulfillment, - Some(TradeFulfillmentStatus::ReadyForPickup) - ); assert_eq!(projection.inventory, TradeInventoryStatus::Reserved); - assert_eq!(projection.payment, TradePaymentDisplayStatus::Recorded); assert_eq!(projection.economics.total_minor_units, Some(1234)); assert_eq!(projection.economics.currency_code.as_deref(), Some("USD")); - assert!(!projection.payment.allows_payment_action()); assert_eq!( projection.provenance, TradeProvenanceProjection::from_primary_source(TradeWorkflowSource::LocalEvents) @@ -3359,14 +2945,10 @@ mod tests { ); assert_eq!( order_status_from_active_order_projection(&active_order), - Some(OrderStatus::Packed) + Some(OrderStatus::Scheduled) ); - let requested_order = test_active_order_projection( - RadrootsOrderStatus::Requested, - None, - RadrootsOrderPaymentState::NotRecorded, - ); + let requested_order = test_active_order_projection(RadrootsOrderStatus::Requested); let requested_projection = TradeWorkflowProjection::from_active_order_projection( order_id, &requested_order, @@ -3377,109 +2959,10 @@ mod tests { requested_projection.agreement, TradeAgreementStatus::Ordered ); - assert_eq!(requested_projection.fulfillment, None); - assert_eq!( - requested_projection.payment, - TradePaymentDisplayStatus::NotRecorded - ); assert_eq!( requested_projection.inventory, TradeInventoryStatus::NeedsReview ); - - let invalid_payment_order = test_active_order_projection( - RadrootsOrderStatus::Accepted, - None, - RadrootsOrderPaymentState::Invalid, - ); - let invalid_payment_projection = TradeWorkflowProjection::from_active_order_projection( - order_id, - &invalid_payment_order, - TradeRevisionStatus::None, - TradeProvenanceProjection::from_primary_source(TradeWorkflowSource::LocalEvents), - ); - assert_eq!( - invalid_payment_projection.payment, - TradePaymentDisplayStatus::NeedsReview - ); - - let mut pending_payment_order = test_active_order_projection( - RadrootsOrderStatus::Accepted, - None, - RadrootsOrderPaymentState::Recorded, - ); - pending_payment_order.payment.settlement_state = RadrootsOrderSettlementState::Pending; - let pending_payment_projection = TradeWorkflowProjection::from_active_order_projection( - order_id, - &pending_payment_order, - TradeRevisionStatus::None, - TradeProvenanceProjection::from_primary_source(TradeWorkflowSource::LocalEvents), - ); - assert_eq!( - pending_payment_projection.payment, - TradePaymentDisplayStatus::Pending - ); - - let settled_payment_order = test_active_order_projection( - RadrootsOrderStatus::Completed, - None, - RadrootsOrderPaymentState::Settled, - ); - let settled_payment_projection = TradeWorkflowProjection::from_active_order_projection( - order_id, - &settled_payment_order, - TradeRevisionStatus::None, - TradeProvenanceProjection::from_primary_source(TradeWorkflowSource::LocalEvents), - ); - assert_eq!( - settled_payment_projection.payment, - TradePaymentDisplayStatus::Settled - ); - } - - #[test] - fn trade_payment_display_statuses_do_not_enable_payment_actions() { - for status in [ - TradePaymentDisplayStatus::NotRecorded, - TradePaymentDisplayStatus::Pending, - TradePaymentDisplayStatus::Recorded, - TradePaymentDisplayStatus::Settled, - TradePaymentDisplayStatus::NeedsReview, - ] { - assert!(!status.allows_payment_action()); - } - } - - #[test] - fn trade_payment_display_projection_maps_reducer_payment_states() { - let mut pending = RadrootsOrderPaymentProjection::not_recorded(); - pending.state = RadrootsOrderPaymentState::Recorded; - pending.payment_event_id = Some(test_event_id( - "4444444444444444444444444444444444444444444444444444444444444444", - )); - pending.settlement_state = RadrootsOrderSettlementState::Pending; - assert_eq!( - TradePaymentDisplayStatus::from_active_payment_projection(&pending), - TradePaymentDisplayStatus::Pending - ); - - let mut settled = RadrootsOrderPaymentProjection::not_recorded(); - settled.state = RadrootsOrderPaymentState::Settled; - settled.payment_event_id = Some(test_event_id( - "4444444444444444444444444444444444444444444444444444444444444444", - )); - settled.settlement_state = RadrootsOrderSettlementState::Accepted; - assert_eq!( - TradePaymentDisplayStatus::from_active_payment_projection(&settled), - TradePaymentDisplayStatus::Settled - ); - - let mut inconsistent = RadrootsOrderPaymentProjection::not_recorded(); - inconsistent.settlement_state = RadrootsOrderSettlementState::Accepted; - assert_eq!( - TradePaymentDisplayStatus::from_active_payment_projection(&inconsistent), - TradePaymentDisplayStatus::NeedsReview - ); } #[test] @@ -3544,28 +3027,11 @@ mod tests { ); assert_eq!(TradeAgreementStatus::Ordered.storage_key(), "ordered"); assert_eq!( - TradeFulfillmentStatus::from_active_fulfillment_status( - &RadrootsOrderFulfillmentState::AcceptedNotFulfilled - ) - .storage_key(), - "confirmed" - ); - assert_eq!( - TradeFulfillmentStatus::ReadyForPickup.storage_key(), - "ready_for_pickup" - ); - assert_eq!( TradeRevisionStatus::KeptAsPlaced.storage_key(), "kept_as_placed" ); assert_eq!(TradeInventoryStatus::Reserved.storage_key(), "reserved"); assert_eq!( - TradePaymentDisplayStatus::NotRecorded.storage_key(), - "not_recorded" - ); - assert_eq!(TradePaymentDisplayStatus::Pending.storage_key(), "pending"); - assert_eq!(TradePaymentDisplayStatus::Settled.storage_key(), "settled"); - assert_eq!( TradeWorkflowSource::LocalEvents.storage_key(), "local_events" ); @@ -3583,26 +3049,10 @@ mod tests { "messages.trade.workflow.revision.change_proposed" ); assert_eq!( - TradeFulfillmentStatus::ReadyForPickup.label_key_id(), - "messages.trade.workflow.fulfillment.ready_for_pickup" - ); - assert_eq!( TradeInventoryStatus::SoldOut.label_key_id(), "messages.trade.workflow.inventory.sold_out" ); assert_eq!( - TradePaymentDisplayStatus::NotRecorded.label_key_id(), - "messages.trade.workflow.payment.not_recorded" - ); - assert_eq!( - TradePaymentDisplayStatus::Pending.label_key_id(), - "messages.trade.workflow.payment.pending" - ); - assert_eq!( - TradePaymentDisplayStatus::Settled.label_key_id(), - "messages.trade.workflow.payment.settled" - ); - assert_eq!( TradeWorkflowSource::Cli.label_key_id(), "messages.trade.workflow.provenance.cli" ); @@ -3844,10 +3294,6 @@ mod tests { None ); assert_eq!( - PackDayOutputOrderState::from_order_status(OrderStatus::Refunded), - None - ); - assert_eq!( PackDayOutputOrderState::from_order_status(OrderStatus::NeedsReview), None ); @@ -3944,7 +3390,6 @@ mod tests { currency_code: Some("USD".to_owned()), ..TradeEconomicsProjection::default() }; - let order_payment = TradePaymentDisplayStatus::NotRecorded; let orders_list = OrdersListProjection { summary: OrdersListSummary { total_orders: 3, @@ -3965,8 +3410,7 @@ mod tests { order_id, OrderStatus::Scheduled, ), - primary_action: Some(OrderPrimaryAction::PublishPreparing), - fulfillment_actions: OrderFulfillmentAction::ALL.to_vec(), + primary_action: None, }], }; let order_detail = OrderDetailProjection { @@ -3989,12 +3433,10 @@ mod tests { line_total_minor_units: Some(1300), }], economics: order_economics.clone(), - payment: order_payment, workflow: TradeWorkflowProjection::from_order_status(order_id, OrderStatus::Scheduled) - .with_economics_and_payment(order_economics, order_payment), + .with_economics(order_economics), validation_receipts: Vec::new(), - primary_action: Some(OrderPrimaryAction::PublishPreparing), - fulfillment_actions: OrderFulfillmentAction::ALL.to_vec(), + primary_action: None, recoveries: Vec::new(), }; let pack_day = PackDayProjection { @@ -4022,22 +3464,15 @@ mod tests { assert!(orders_list.summary.has_orders()); assert!(!orders_list.is_empty()); - assert_eq!( - orders_list.rows[0].primary_action, - Some(OrderPrimaryAction::PublishPreparing) - ); - assert_eq!( - orders_list.rows[0].fulfillment_actions, - OrderFulfillmentAction::ALL.to_vec() - ); + assert_eq!(orders_list.rows[0].primary_action, None); assert_eq!( orders_list.rows[0].workflow.agreement, TradeAgreementStatus::Confirmed ); assert_eq!(order_detail.items[0].quantity_display, "2 bags"); assert_eq!( - order_detail.workflow.fulfillment, - Some(TradeFulfillmentStatus::Confirmed) + order_detail.workflow.inventory, + TradeInventoryStatus::Reserved ); assert!(!pack_day.is_empty()); assert_eq!(pack_day.pickup_roster[0].order_number, "R-1001"); @@ -4054,7 +3489,6 @@ mod tests { currency_code: Some("USD".to_owned()), ..TradeEconomicsProjection::default() }; - let buyer_order_payment = TradePaymentDisplayStatus::NotRecorded; let listing = BuyerListingRow { product_id, farm_id, @@ -4153,12 +3587,11 @@ mod tests { line_total_minor_units: Some(1300), }], economics: buyer_order_economics.clone(), - payment: buyer_order_payment, workflow: TradeWorkflowProjection::from_buyer_order_status( order_id, BuyerOrderStatus::Scheduled, ) - .with_economics_and_payment(buyer_order_economics, buyer_order_payment), + .with_economics(buyer_order_economics), validation_receipts: Vec::new(), order_note: Some("Leave by the cooler".to_owned()), repeat_demand: None, @@ -4204,7 +3637,6 @@ mod tests { OrderStatus::Completed, ), primary_action: None, - fulfillment_actions: Vec::new(), }; assert_eq!(today.orders_needing_action.len(), 1); @@ -4294,18 +3726,15 @@ mod tests { assert_eq!(ReminderSurface::PackDay.storage_key(), "pack_day"); assert_eq!( - ReminderKind::RefundRecovery.storage_key(), - "refund_recovery" + ReminderKind::MissedPickupRecovery.storage_key(), + "missed_pickup_recovery" ); assert_eq!(ReminderUrgency::DueSoon.storage_key(), "due_soon"); assert_eq!( ReminderDeliveryState::Acknowledged.storage_key(), "acknowledged" ); - assert_eq!( - RecoveryKind::RefundFollowUp.storage_key(), - "refund_follow_up" - ); + assert_eq!(RecoveryKind::MissedPickup.storage_key(), "missed_pickup"); assert_eq!(RecoveryState::InReview.storage_key(), "in_review"); assert_eq!( RepeatDemandEligibility::Unavailable.storage_key(),