commit ae793686fc4136d3432ca0b496c24f7cdf035e49
parent 6b51c5964ee79ab4e016f6194eb7da7814c27765
Author: triesap <tyson@radroots.org>
Date: Thu, 30 Apr 2026 08:38:02 +0000
order: expose status revision state
Diffstat:
2 files changed, 91 insertions(+), 2 deletions(-)
diff --git a/src/domain/runtime.rs b/src/domain/runtime.rs
@@ -1658,6 +1658,8 @@ pub struct OrderStatusView {
#[serde(skip_serializing_if = "Option::is_none")]
pub last_event_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
+ pub revision: Option<OrderStatusRevisionView>,
+ #[serde(skip_serializing_if = "Option::is_none")]
pub inventory: Option<OrderInventoryView>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fulfillment: Option<OrderStatusFulfillmentView>,
@@ -1684,6 +1686,25 @@ pub struct OrderStatusView {
}
#[derive(Debug, Clone, Serialize)]
+pub struct OrderStatusRevisionView {
+ pub state: String,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub revision_id: Option<String>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub proposal_event_id: Option<String>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub decision_event_id: Option<String>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub root_event_id: Option<String>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub prev_event_id: Option<String>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub agreement_event_id: Option<String>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub reason: Option<String>,
+}
+
+#[derive(Debug, Clone, Serialize)]
pub struct OrderStatusFulfillmentView {
pub state: String,
#[serde(skip_serializing_if = "Option::is_none")]
diff --git a/src/runtime/order.rs b/src/runtime/order.rs
@@ -74,8 +74,8 @@ use crate::domain::runtime::{
OrderInventoryView, OrderIssueView, OrderListView, OrderNewView, OrderReceiptView,
OrderRevisionDecisionView, OrderRevisionProposalView, OrderStatusFulfillmentView,
OrderStatusLifecycleCancellationView, OrderStatusLifecycleReceiptView,
- OrderStatusLifecycleView, OrderStatusView, OrderSubmitView, OrderSummaryView, OrderWatchView,
- RelayFailureView,
+ OrderStatusLifecycleView, OrderStatusRevisionView, OrderStatusView, OrderSubmitView,
+ OrderSummaryView, OrderWatchView, RelayFailureView,
};
use crate::runtime::RuntimeError;
use crate::runtime::accounts;
@@ -1497,6 +1497,7 @@ pub fn status(
seller_pubkey: None,
economics: None,
last_event_id: None,
+ revision: None,
inventory: None,
fulfillment: None,
lifecycle: None,
@@ -1533,6 +1534,7 @@ pub fn status(
seller_pubkey: None,
economics: None,
last_event_id: None,
+ revision: None,
inventory: None,
fulfillment: None,
lifecycle: None,
@@ -1707,6 +1709,8 @@ fn order_status_reduction_from_receipt_with_context(
});
let order_id = context.order_id;
+ let revision_proposal_records = revision_proposals.clone();
+ let revision_decision_records = revision_decisions.clone();
let fulfillment_records = fulfillments.clone();
let cancellation_records = cancellations.clone();
let receipt_records = receipts.clone();
@@ -1828,6 +1832,12 @@ fn order_status_reduction_from_receipt_with_context(
}),
reducer_issues.as_slice(),
);
+ let revision = order_status_revision_view(
+ projection.last_event_id.as_deref(),
+ projection.agreement_event_id.as_deref(),
+ &revision_proposal_records,
+ &revision_decision_records,
+ );
let view = OrderStatusView {
state,
@@ -1842,6 +1852,7 @@ fn order_status_reduction_from_receipt_with_context(
seller_pubkey: projection.seller_pubkey,
economics: projection.economics,
last_event_id: projection.last_event_id,
+ revision,
inventory,
fulfillment,
lifecycle: Some(lifecycle),
@@ -2683,6 +2694,63 @@ fn order_status_lifecycle_view(
}
}
+fn order_status_revision_view(
+ last_event_id: Option<&str>,
+ agreement_event_id: Option<&str>,
+ proposals: &[RadrootsActiveOrderRevisionProposalRecord],
+ decisions: &[RadrootsActiveOrderRevisionDecisionRecord],
+) -> Option<OrderStatusRevisionView> {
+ if let Some(proposal) = last_event_id
+ .and_then(|event_id| proposals.iter().find(|record| record.event_id == event_id))
+ {
+ return Some(OrderStatusRevisionView {
+ state: "pending".to_owned(),
+ revision_id: Some(proposal.payload.revision_id.clone()),
+ proposal_event_id: Some(proposal.event_id.clone()),
+ decision_event_id: None,
+ root_event_id: Some(proposal.root_event_id.clone()),
+ prev_event_id: Some(proposal.prev_event_id.clone()),
+ agreement_event_id: None,
+ reason: Some(proposal.payload.reason.clone()),
+ });
+ }
+
+ if let Some(decision) = last_event_id
+ .and_then(|event_id| decisions.iter().find(|record| record.event_id == event_id))
+ {
+ return Some(order_status_revision_view_from_decision(
+ decision,
+ agreement_event_id,
+ ));
+ }
+
+ agreement_event_id
+ .and_then(|event_id| decisions.iter().find(|record| record.event_id == event_id))
+ .map(|decision| order_status_revision_view_from_decision(decision, agreement_event_id))
+}
+
+fn order_status_revision_view_from_decision(
+ decision: &RadrootsActiveOrderRevisionDecisionRecord,
+ agreement_event_id: Option<&str>,
+) -> OrderStatusRevisionView {
+ let (state, reason) = match &decision.payload.decision {
+ RadrootsTradeOrderRevisionDecision::Accepted => ("accepted", None),
+ RadrootsTradeOrderRevisionDecision::Declined { reason } => {
+ ("declined", Some(reason.clone()))
+ }
+ };
+ OrderStatusRevisionView {
+ state: state.to_owned(),
+ revision_id: Some(decision.payload.revision_id.clone()),
+ proposal_event_id: Some(decision.prev_event_id.clone()),
+ decision_event_id: Some(decision.event_id.clone()),
+ root_event_id: Some(decision.root_event_id.clone()),
+ prev_event_id: Some(decision.prev_event_id.clone()),
+ agreement_event_id: agreement_event_id.map(str::to_owned),
+ reason,
+ }
+}
+
fn order_status_lifecycle_phase(
status: &RadrootsActiveOrderStatus,
fulfillment_status: Option<RadrootsActiveTradeFulfillmentState>,