app

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

commit f08c543163702fb3836873f4d3626e7e8d9f2e96
parent e0aac9e8afc0dfdfb95f765b86b38bbdacd3ca52
Author: triesap <tyson@radroots.org>
Date:   Thu,  4 Jun 2026 17:41:19 -0700

app: remove stale local order mutations

- delete direct pack and complete runtime and store mutation APIs
- route private order action handlers through publish-oriented names
- rewrite stale mutation tests as pending-sync and projection fixtures
- keep fulfillment state changes behind signed lifecycle publication

Diffstat:
Mcrates/desktop/src/runtime.rs | 209+++++++++++++++++++++++++++++++------------------------------------------------
Mcrates/desktop/src/window.rs | 12++++++------
Mcrates/store/src/lib.rs | 18------------------
Mcrates/store/src/repo/orders.rs | 131-------------------------------------------------------------------------------
4 files changed, 89 insertions(+), 281 deletions(-)

diff --git a/crates/desktop/src/runtime.rs b/crates/desktop/src/runtime.rs @@ -718,14 +718,6 @@ impl DesktopAppRuntime { self.lock_state_mut().open_order_detail(order_id) } - pub fn mark_order_packed(&self, order_id: OrderId) -> Result<bool, AppSqliteError> { - self.lock_state_mut().mark_order_packed(order_id) - } - - pub fn mark_order_completed(&self, order_id: OrderId) -> Result<bool, AppSqliteError> { - self.lock_state_mut().mark_order_completed(order_id) - } - pub fn prepare_order_accept( &self, order_id: OrderId, @@ -2335,66 +2327,6 @@ impl DesktopAppRuntimeState { Ok(detail_changed || section_changed || editor_changed) } - fn mark_order_packed(&mut self, order_id: OrderId) -> Result<bool, AppSqliteError> { - let Some(sqlite_store) = self.sqlite_store.as_ref() else { - return Ok(false); - }; - let Some(farm_id) = self.selected_farm_id() else { - return Ok(false); - }; - - let updated = sqlite_store.mark_order_packed(farm_id, order_id)?; - if !updated { - return Ok(false); - } - - let continuity_state = - self.continuity_state_with_order_detail(self.selected_order_detail_id()); - let selected_account_context = load_selected_account_context( - sqlite_store, - self.state_store.identity_projection(), - &continuity_state, - )?; - let context_changed = self.apply_selected_account_context(&selected_account_context); - let pending_changed = - self.enqueue_selected_account_sync_operations(vec![pending_sync_upsert( - SyncAggregateRef::Order(order_id), - order_sync_payload(order_id, farm_id, "mark_order_packed", Some("packed")), - )])?; - - Ok(updated || context_changed || pending_changed) - } - - fn mark_order_completed(&mut self, order_id: OrderId) -> Result<bool, AppSqliteError> { - let Some(sqlite_store) = self.sqlite_store.as_ref() else { - return Ok(false); - }; - let Some(farm_id) = self.selected_farm_id() else { - return Ok(false); - }; - - let updated = sqlite_store.mark_order_completed(farm_id, order_id)?; - if !updated { - return Ok(false); - } - - let continuity_state = - self.continuity_state_with_order_detail(self.selected_order_detail_id()); - let selected_account_context = load_selected_account_context( - sqlite_store, - self.state_store.identity_projection(), - &continuity_state, - )?; - let context_changed = self.apply_selected_account_context(&selected_account_context); - let pending_changed = - self.enqueue_selected_account_sync_operations(vec![pending_sync_upsert( - SyncAggregateRef::Order(order_id), - order_sync_payload(order_id, farm_id, "mark_order_completed", Some("completed")), - )])?; - - Ok(updated || context_changed || pending_changed) - } - fn prepare_seller_order_decision( &mut self, order_id: OrderId, @@ -9588,22 +9520,6 @@ fn farm_sync_payload( .to_string() } -fn order_sync_payload( - order_id: OrderId, - farm_id: FarmId, - source: &str, - status: Option<&str>, -) -> String { - json!({ - "aggregate_kind": "order", - "order_id": order_id.to_string(), - "farm_id": farm_id.to_string(), - "status": status, - "source": source, - }) - .to_string() -} - fn signed_order_request_evidence_record_is_usable(record: &LocalEventRecord) -> bool { if record.status != LocalRecordStatus::Published || matches!( @@ -17191,25 +17107,21 @@ mod tests { let runtime = memory_runtime(); let (_, farm_id) = provision_ready_farmer_account(&runtime); let (_, order_id) = seed_order_workspace(&runtime, farm_id); - let sql = format!( - "update orders - set status = 'scheduled', updated_at = '2026-04-17T12:00:00Z' - where id = '{order_id}' and farm_id = '{farm_id}'" - ); - runtime - .lock_state() - .sqlite_store - .as_ref() - .expect("sqlite store") - .connection() - .execute_batch(&sql) - .expect("order should update to scheduled"); assert!(runtime.open_orders().expect("orders should open")); assert!( runtime - .mark_order_packed(order_id) - .expect("mark packed should succeed") + .lock_state_mut() + .enqueue_selected_account_sync_operations(vec![pending_sync_upsert( + SyncAggregateRef::Order(order_id), + json!({ + "aggregate_kind": "order", + "order_id": order_id.to_string(), + "source": "test_pending_order_sync", + }) + .to_string(), + )]) + .expect("pending order sync should enqueue") ); let summary = runtime.summary(); @@ -17574,15 +17486,51 @@ mod tests { } #[test] - fn runtime_order_actions_refresh_repository_backed_orders_projection() { + fn runtime_order_filters_refresh_repository_backed_orders_projection() { let runtime = memory_runtime(); let (_, farm_id) = provision_ready_farmer_account(&runtime); - let (_, order_id) = seed_order_workspace(&runtime, farm_id); + let (fulfillment_window_id, scheduled_order_id) = seed_order_workspace(&runtime, farm_id); + let packed_order_id = OrderId::new(); + let completed_order_id = OrderId::new(); let sql = format!( "update orders set status = 'scheduled', updated_at = '2026-04-17T12:00:00Z' - where id = '{order_id}' and farm_id = '{farm_id}'" + where id = '{scheduled_order_id}' and farm_id = '{farm_id}'; + insert into orders ( + id, + farm_id, + fulfillment_window_id, + order_number, + customer_display_name, + status, + updated_at + ) values ( + '{packed_order_id}', + '{farm_id}', + '{fulfillment_window_id}', + 'R-101', + 'Taylor', + 'packed', + '2026-04-17T12:30:00Z' + ); + insert into orders ( + id, + farm_id, + fulfillment_window_id, + order_number, + customer_display_name, + status, + updated_at + ) values ( + '{completed_order_id}', + '{farm_id}', + '{fulfillment_window_id}', + 'R-102', + 'Morgan', + 'completed', + '2026-04-17T13:00:00Z' + )" ); runtime .lock_state() @@ -17606,35 +17554,33 @@ mod tests { assert!( runtime - .open_order_detail(order_id) + .open_order_detail(scheduled_order_id) .expect("order detail should open") ); - assert!( - runtime - .mark_order_packed(order_id) - .expect("order should mark packed") - ); - let packed_summary = runtime.summary(); + let scheduled_detail_summary = runtime.summary(); assert_eq!( - packed_summary + scheduled_detail_summary .orders_projection .detail .as_ref() - .expect("packed detail") + .expect("scheduled detail") .status, - OrderStatus::Packed + OrderStatus::Scheduled ); - assert_eq!(packed_summary.orders_projection.list.rows.len(), 0); assert_eq!( - packed_summary + scheduled_detail_summary .orders_projection .list .summary .scheduled_orders, - 0 + 1 ); assert_eq!( - packed_summary.orders_projection.list.summary.packed_orders, + scheduled_detail_summary + .orders_projection + .list + .summary + .packed_orders, 1 ); @@ -17651,23 +17597,18 @@ mod tests { assert!( runtime - .open_order_detail(order_id) + .open_order_detail(packed_order_id) .expect("packed detail should open") ); - assert!( - runtime - .mark_order_completed(order_id) - .expect("order should mark completed") - ); - let completed_summary = runtime.summary(); + let packed_detail_summary = runtime.summary(); assert_eq!( - completed_summary + packed_detail_summary .orders_projection .detail .as_ref() - .expect("completed detail") + .expect("packed detail") .status, - OrderStatus::Completed + OrderStatus::Packed ); assert!( @@ -17680,6 +17621,22 @@ mod tests { runtime.summary().orders_projection.list.rows[0].status, OrderStatus::Completed ); + + assert!( + runtime + .open_order_detail(completed_order_id) + .expect("completed detail should open") + ); + assert_eq!( + runtime + .summary() + .orders_projection + .detail + .as_ref() + .expect("completed detail") + .status, + OrderStatus::Completed + ); } #[test] diff --git a/crates/desktop/src/window.rs b/crates/desktop/src/window.rs @@ -2099,7 +2099,7 @@ impl HomeView { } } - fn mark_order_packed(&mut self, order_id: OrderId, cx: &mut Context<Self>) { + fn publish_order_ready_for_pickup(&mut self, order_id: OrderId, cx: &mut Context<Self>) { match self.runtime.publish_order_ready_for_pickup(order_id) { Ok(true) => cx.notify(), Ok(false) => {} @@ -2115,7 +2115,7 @@ impl HomeView { } } - fn mark_order_completed(&mut self, order_id: OrderId, cx: &mut Context<Self>) { + fn publish_order_delivered(&mut self, order_id: OrderId, cx: &mut Context<Self>) { match self.runtime.publish_order_delivered(order_id) { Ok(true) => cx.notify(), Ok(false) => {} @@ -4180,7 +4180,7 @@ impl HomeView { app_shared_text(AppTextKey::OrdersActionMarkPacked), cx.listener({ let order_id = detail.order_id; - move |this, _, _, cx| this.mark_order_packed(order_id, cx) + move |this, _, _, cx| this.publish_order_ready_for_pickup(order_id, cx) }), cx, ) @@ -4192,7 +4192,7 @@ impl HomeView { app_shared_text(AppTextKey::OrdersActionMarkCompleted), cx.listener({ let order_id = detail.order_id; - move |this, _, _, cx| this.mark_order_completed(order_id, cx) + move |this, _, _, cx| this.publish_order_delivered(order_id, cx) }), cx, ) @@ -4555,11 +4555,11 @@ impl HomeView { }), cx.listener({ let order_id = row.order_id; - move |this, _, _, cx| this.mark_order_packed(order_id, cx) + move |this, _, _, cx| this.publish_order_ready_for_pickup(order_id, cx) }), cx.listener({ let order_id = row.order_id; - move |this, _, _, cx| this.mark_order_completed(order_id, cx) + move |this, _, _, cx| this.publish_order_delivered(order_id, cx) }), cx, ); diff --git a/crates/store/src/lib.rs b/crates/store/src/lib.rs @@ -258,24 +258,6 @@ impl AppSqliteStore { .load_pack_day_output_source(farm_id, fulfillment_window_id) } - pub fn mark_order_packed( - &self, - farm_id: FarmId, - order_id: OrderId, - ) -> Result<bool, AppSqliteError> { - self.orders_repository() - .mark_order_packed(farm_id, order_id) - } - - pub fn mark_order_completed( - &self, - farm_id: FarmId, - order_id: OrderId, - ) -> Result<bool, AppSqliteError> { - self.orders_repository() - .mark_order_completed(farm_id, order_id) - } - pub fn load_reminder_schedule( &self, account_id: &str, diff --git a/crates/store/src/repo/orders.rs b/crates/store/src/repo/orders.rs @@ -279,34 +279,6 @@ impl<'a> AppOrdersRepository<'a> { })) } - pub fn mark_order_packed( - &self, - farm_id: FarmId, - order_id: OrderId, - ) -> Result<bool, AppSqliteError> { - self.transition_order_status( - farm_id, - order_id, - OrderStatus::Scheduled, - OrderStatus::Packed, - "mark order packed", - ) - } - - pub fn mark_order_completed( - &self, - farm_id: FarmId, - order_id: OrderId, - ) -> Result<bool, AppSqliteError> { - self.transition_order_status( - farm_id, - order_id, - OrderStatus::Packed, - OrderStatus::Completed, - "mark order completed", - ) - } - fn load_order_records( &self, farm_id: FarmId, @@ -1152,36 +1124,6 @@ impl<'a> AppOrdersRepository<'a> { Ok(roster) } - - fn transition_order_status( - &self, - farm_id: FarmId, - order_id: OrderId, - current_status: OrderStatus, - next_status: OrderStatus, - operation: &'static str, - ) -> Result<bool, AppSqliteError> { - let updated_rows = self - .connection - .execute( - "update orders - set - status = ?3, - updated_at = strftime('%Y-%m-%dT%H:%M:%SZ', 'now') - where farm_id = ?1 - and id = ?2 - and status = ?4", - params![ - farm_id.to_string(), - order_id.to_string(), - next_status.storage_key(), - current_status.storage_key(), - ], - ) - .map_err(|source| AppSqliteError::Query { operation, source })?; - - Ok(updated_rows > 0) - } } #[derive(Clone, Debug, Eq, PartialEq)] @@ -2067,67 +2009,6 @@ mod tests { } #[test] - fn order_status_transitions_are_guarded() { - let store = AppSqliteStore::open(DatabaseTarget::InMemory).expect("store should open"); - let connection = store.connection(); - let farm_id = FarmId::new(); - let scheduled_order_id = OrderId::new(); - let packed_order_id = OrderId::new(); - - insert_farm( - connection, - farm_id, - "Willow farm", - "ready", - "2026-04-17T08:00:00Z", - ); - insert_order( - connection, - scheduled_order_id, - farm_id, - None, - "R-100", - "Casey", - "scheduled", - "2026-04-17T10:00:00Z", - ); - insert_order( - connection, - packed_order_id, - farm_id, - None, - "R-101", - "Taylor", - "packed", - "2026-04-17T11:00:00Z", - ); - - assert!( - store - .mark_order_packed(farm_id, scheduled_order_id) - .expect("scheduled order should mark packed") - ); - assert!( - !store - .mark_order_packed(farm_id, packed_order_id) - .expect("packed order should not mark packed twice") - ); - assert!( - store - .mark_order_completed(farm_id, packed_order_id) - .expect("packed order should mark completed") - ); - assert_eq!( - read_order_status(connection, scheduled_order_id), - OrderStatus::Packed - ); - assert_eq!( - read_order_status(connection, packed_order_id), - OrderStatus::Completed - ); - } - - #[test] fn orders_list_stays_aligned_with_today_needs_action_order_boundary() { let store = AppSqliteStore::open(DatabaseTarget::InMemory).expect("store should open"); let connection = store.connection(); @@ -2451,16 +2332,4 @@ mod tests { ) .expect("order line insert should succeed"); } - - fn read_order_status(connection: &Connection, order_id: OrderId) -> OrderStatus { - let status = connection - .query_row( - "select status from orders where id = ?1 limit 1", - params![order_id.to_string()], - |row| row.get::<_, String>(0), - ) - .expect("order status should load"); - - super::parse_order_status("orders.status", status).expect("order status should decode") - } }