commit a1a994c8a21ebbe5fac5575e5cdedb8520f5bac3
parent 949007ee18b77b83b05904d63e29dfef9af6b378
Author: triesap <tyson@radroots.org>
Date: Wed, 22 Apr 2026 21:00:18 +0000
pack_day: wire print actions into the export card
- add window-owned print execution wiring for all three v1 pack day print kinds
- render a second quiet export-card print row with truthful cross-gating and typed status notes
- surface avery 5160 in the customer-label print affordance without adding a stock picker
- cover the new print row, action gating, i18n copy, and source guards in tests
Diffstat:
6 files changed, 629 insertions(+), 21 deletions(-)
diff --git a/crates/launchers/desktop/src/runtime.rs b/crates/launchers/desktop/src/runtime.rs
@@ -7478,7 +7478,7 @@ mod tests {
}
#[test]
- fn runtime_prepare_pack_day_print_uses_the_current_export_bundle_for_simple_documents() {
+ fn runtime_prepare_pack_day_print_uses_the_current_export_bundle_for_all_v1_documents() {
let (runtime, paths) = bootstrapped_runtime("pack_day_print_prepare");
let (_, farm_id) = provision_ready_farmer_account(&runtime);
@@ -7490,9 +7490,13 @@ mod tests {
.expect("pack day export should succeed")
);
- for (kind, suffix) in [
- (PackDayPrintKind::PrintPackSheet, "pack_sheet.txt"),
- (PackDayPrintKind::PrintPickupRoster, "pickup_roster.txt"),
+ for (kind, expected_exported_suffix) in [
+ (PackDayPrintKind::PrintPackSheet, Some("pack_sheet.txt")),
+ (
+ PackDayPrintKind::PrintPickupRoster,
+ Some("pickup_roster.txt"),
+ ),
+ (PackDayPrintKind::PrintCustomerLabels, None),
] {
let prepared = runtime
.prepare_pack_day_print(kind)
@@ -7519,19 +7523,55 @@ mod tests {
.expect("pack day export bundle")
.export_instance_id
);
+ assert_eq!(prepared.0.label_stock, kind.label_stock());
assert_eq!(prepared.1.kind, kind);
assert_eq!(prepared.1.command_program, "lp");
- assert!(prepared.1.target_path.ends_with(suffix));
assert_eq!(
prepared.1.command_args,
vec![prepared.1.target_path.to_string_lossy().into_owned()]
);
+ match expected_exported_suffix {
+ Some(suffix) => assert!(prepared.1.target_path.ends_with(suffix)),
+ None => {
+ let export_bundle = summary
+ .pack_day_projection
+ .export
+ .bundle
+ .as_ref()
+ .expect("pack day export bundle");
+ assert!(
+ prepared
+ .1
+ .target_path
+ .ends_with("customer_labels_avery_5160_letter_30_up.ps")
+ );
+ assert!(
+ !prepared
+ .1
+ .target_path
+ .starts_with(PathBuf::from(&export_bundle.bundle_directory))
+ );
+ assert!(
+ prepared
+ .1
+ .target_path
+ .to_string_lossy()
+ .contains(export_bundle.export_instance_id.to_string().as_str())
+ );
+ }
+ }
assert!(
runtime
.finish_pack_day_print(prepared.0, Ok(()))
.expect("print success should apply")
);
+
+ if let PackDayPrintKind::PrintCustomerLabels = kind {
+ if let Some(parent) = prepared.1.target_path.parent() {
+ let _ = fs::remove_dir_all(parent);
+ }
+ }
}
cleanup_bootstrapped_runtime_paths(&paths);
diff --git a/crates/launchers/desktop/src/source_guards.rs b/crates/launchers/desktop/src/source_guards.rs
@@ -84,7 +84,9 @@ const ALLOWED_WINDOW_LITERALS: &[&str] = &[
"failed to acknowledge reminder",
"failed to export pack day",
"failed to complete pack day host handoff",
+ "failed to complete pack day print",
"failed to prepare pack day host handoff",
+ "failed to prepare pack day print",
"failed to open order detail",
"failed to route into pack day view",
"failed to route into orders view",
@@ -153,9 +155,14 @@ const ALLOWED_WINDOW_LITERALS: &[&str] = &[
"pack-day-open-customer-labels",
"pack-day-open-pack-sheet",
"pack-day-open-pickup-roster",
+ "pack-day-print-customer-labels",
+ "pack-day-print-pack-sheet",
+ "pack-day-print-pickup-roster",
"pack_day",
"pack_day.host_handoff_failed",
"pack_day.host_handoff_prepare_failed",
+ "pack_day.print_failed",
+ "pack_day.print_prepare_failed",
"pack-day-reminders",
"pack-day-reveal-bundle",
"pack_day.export_failed",
diff --git a/crates/launchers/desktop/src/window.rs b/crates/launchers/desktop/src/window.rs
@@ -21,13 +21,14 @@ use radroots_app_models::{
OrderDetailItemRow, OrderDetailProjection, OrderId, OrderListRow, OrderPrimaryAction,
OrderRecoveryProjection, OrderStatus, OrdersFilter, OrdersListRow, PackDayExportBundle,
PackDayExportStatus, PackDayHostHandoffKind, PackDayHostHandoffStatus, PackDayPackListRow,
- PackDayProductTotalRow, PackDayRosterRow, PersonalEntryState, PersonalSection,
- PickupLocationId, PickupLocationRecord, ProductAttentionState, ProductEditorDraft, ProductId,
- ProductListRow, ProductPricePresentation, ProductPublishBlocker, ProductStatus, ProductsFilter,
- ProductsListRow, ProductsSort, RecoveryKind, RecoveryState, ReminderDeadlineProjection,
- ReminderDeliveryState, ReminderId, ReminderLogEntryProjection, ReminderLogProjection,
- ReminderSurface, ReminderUrgency, RepeatDemandEligibility, RepeatDemandHandoffProjection,
- ShellSection, TodayAgendaProjection, TodaySetupTaskKind,
+ PackDayPrintKind, PackDayPrintStatus, PackDayProductTotalRow, PackDayRosterRow,
+ PersonalEntryState, PersonalSection, PickupLocationId, PickupLocationRecord,
+ ProductAttentionState, ProductEditorDraft, ProductId, ProductListRow, ProductPricePresentation,
+ ProductPublishBlocker, ProductStatus, ProductsFilter, ProductsListRow, ProductsSort,
+ RecoveryKind, RecoveryState, ReminderDeadlineProjection, ReminderDeliveryState, ReminderId,
+ ReminderLogEntryProjection, ReminderLogProjection, ReminderSurface, ReminderUrgency,
+ RepeatDemandEligibility, RepeatDemandHandoffProjection, ShellSection, TodayAgendaProjection,
+ TodaySetupTaskKind,
};
use radroots_app_remote_signer::{
RadrootsAppRemoteSignerApprovedSession, RadrootsAppRemoteSignerPendingPollOutcome,
@@ -39,7 +40,7 @@ use radroots_app_remote_signer::{
use radroots_app_sqlite::derive_farm_rules_readiness;
use radroots_app_state::{
FarmSetupFlowStage, FarmWorkspaceStatus, HomeRoute, PackDayExportProjection,
- PackDayHostHandoffRequest, derive_product_publish_blockers,
+ PackDayHostHandoffRequest, PackDayPrintRequest, derive_product_publish_blockers,
};
use radroots_app_sync::{
AppSyncRunStatus, SyncAggregateRef, SyncCheckpointState, SyncConflict, SyncConflictKind,
@@ -76,6 +77,9 @@ use tracing::error;
use crate::pack_day_host_handoff::{
PackDayHostHandoffCommandPlan, PackDayHostHandoffError, execute_pack_day_host_handoff_plan,
};
+use crate::pack_day_print::{
+ PackDayPrintCommandPlan, PackDayPrintError, execute_pack_day_print_plan,
+};
use crate::runtime::{
DesktopAppRuntime, DesktopAppRuntimeSummary, DesktopAppSyncConflictSummary,
DesktopAppSyncStatusSummary,
@@ -1686,6 +1690,40 @@ impl HomeView {
}
}
+ fn start_pack_day_print(
+ &mut self,
+ kind: PackDayPrintKind,
+ window: &mut Window,
+ cx: &mut Context<Self>,
+ ) {
+ match self.runtime.prepare_pack_day_print(kind) {
+ Ok(Some((request, plan))) => {
+ cx.notify();
+ cx.spawn_in(window, async move |this, cx| {
+ let result = cx
+ .background_executor()
+ .spawn(run_pack_day_print(plan))
+ .await;
+ let _ = this.update(cx, |this, cx| {
+ this.finish_pack_day_print(request, result, cx);
+ });
+ })
+ .detach();
+ }
+ Ok(None) => {}
+ Err(runtime_error) => {
+ error!(
+ target: "pack_day",
+ event = "pack_day.print_prepare_failed",
+ kind = %kind.storage_key(),
+ error = %runtime_error,
+ "failed to prepare pack day print"
+ );
+ cx.notify();
+ }
+ }
+ }
+
fn finish_pack_day_host_handoff(
&mut self,
request: PackDayHostHandoffRequest,
@@ -1709,6 +1747,29 @@ impl HomeView {
}
}
+ fn finish_pack_day_print(
+ &mut self,
+ request: PackDayPrintRequest,
+ result: Result<(), PackDayPrintError>,
+ cx: &mut Context<Self>,
+ ) {
+ let kind = request.kind.storage_key();
+ match self.runtime.finish_pack_day_print(request, result) {
+ Ok(true) => cx.notify(),
+ Ok(false) => {}
+ Err(runtime_error) => {
+ error!(
+ target: "pack_day",
+ event = "pack_day.print_failed",
+ kind = %kind,
+ error = %runtime_error,
+ "failed to complete pack day print"
+ );
+ cx.notify();
+ }
+ }
+ }
+
fn open_today_next_window(
&mut self,
fulfillment_window_id: Option<FulfillmentWindowId>,
@@ -3469,6 +3530,15 @@ impl HomeView {
cx,
)
}),
+ cx.listener(|this, _, window, cx| {
+ this.start_pack_day_print(PackDayPrintKind::PrintPackSheet, window, cx)
+ }),
+ cx.listener(|this, _, window, cx| {
+ this.start_pack_day_print(PackDayPrintKind::PrintPickupRoster, window, cx)
+ }),
+ cx.listener(|this, _, window, cx| {
+ this.start_pack_day_print(PackDayPrintKind::PrintCustomerLabels, window, cx)
+ }),
cx,
))
.when(!projection.reminders.is_empty(), |this| {
@@ -9105,6 +9175,10 @@ async fn run_pack_day_host_handoff(
execute_pack_day_host_handoff_plan(&plan)
}
+async fn run_pack_day_print(plan: PackDayPrintCommandPlan) -> Result<(), PackDayPrintError> {
+ execute_pack_day_print_plan(&plan)
+}
+
async fn run_startup_signer_pending_poll(
record: radroots_app_remote_signer::RadrootsAppRemoteSignerSessionRecord,
client_secret_key_hex: String,
@@ -9929,6 +10003,19 @@ struct PackDayHostHandoffStatusPresentation {
title_key: AppTextKey,
}
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+struct PackDayPrintActionPresentation {
+ kind: PackDayPrintKind,
+ label_key: AppTextKey,
+ enabled: bool,
+}
+
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+struct PackDayPrintStatusPresentation {
+ indicator_color: u32,
+ title_key: AppTextKey,
+}
+
fn pack_day_export_card(
runtime: &DesktopAppRuntimeSummary,
on_export: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
@@ -9936,6 +10023,9 @@ fn pack_day_export_card(
on_open_pack_sheet: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
on_open_pickup_roster: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
on_open_customer_labels: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
+ on_print_pack_sheet: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
+ on_print_pickup_roster: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
+ on_print_customer_labels: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
cx: &App,
) -> impl IntoElement {
let export = &runtime.pack_day_projection.export;
@@ -9943,6 +10033,8 @@ fn pack_day_export_card(
let detail_rows = pack_day_export_detail_rows(export);
let host_handoff_actions = pack_day_host_handoff_action_presentations(runtime);
let host_handoff_status = pack_day_host_handoff_status_presentation(runtime);
+ let print_actions = pack_day_print_action_presentations(runtime);
+ let print_status = pack_day_print_status_presentation(runtime);
let host_handoff_error_message = runtime
.pack_day_projection
.host_handoff
@@ -10112,6 +10204,95 @@ fn pack_day_export_card(
))
}),
)
+ })
+ .when(!print_actions.is_empty(), |this| {
+ let on_print_pack_sheet = Arc::new(on_print_pack_sheet);
+ let on_print_pickup_roster = Arc::new(on_print_pickup_roster);
+ let on_print_customer_labels = Arc::new(on_print_customer_labels);
+ this.child(
+ app_stack_v(APP_UI_THEME.foundation.spacing.small_px)
+ .w_full()
+ .child(
+ app_cluster(APP_UI_THEME.foundation.spacing.small_px)
+ .items_center()
+ .children(print_actions.into_iter().map(move |action| {
+ match action.kind {
+ PackDayPrintKind::PrintPackSheet if action.enabled => {
+ action_button(
+ "pack-day-print-pack-sheet",
+ app_shared_text(action.label_key),
+ {
+ let on_print_pack_sheet =
+ Arc::clone(&on_print_pack_sheet);
+ move |event, window, cx| {
+ (on_print_pack_sheet)(event, window, cx)
+ }
+ },
+ cx,
+ )
+ .into_any_element()
+ }
+ PackDayPrintKind::PrintPickupRoster if action.enabled => {
+ action_button(
+ "pack-day-print-pickup-roster",
+ app_shared_text(action.label_key),
+ {
+ let on_print_pickup_roster =
+ Arc::clone(&on_print_pickup_roster);
+ move |event, window, cx| {
+ (on_print_pickup_roster)(event, window, cx)
+ }
+ },
+ cx,
+ )
+ .into_any_element()
+ }
+ PackDayPrintKind::PrintCustomerLabels if action.enabled => {
+ action_button(
+ "pack-day-print-customer-labels",
+ app_shared_text(action.label_key),
+ {
+ let on_print_customer_labels =
+ Arc::clone(&on_print_customer_labels);
+ move |event, window, cx| {
+ (on_print_customer_labels)(
+ event, window, cx,
+ )
+ }
+ },
+ cx,
+ )
+ .into_any_element()
+ }
+ PackDayPrintKind::PrintPackSheet => action_button_disabled(
+ "pack-day-print-pack-sheet",
+ app_shared_text(action.label_key),
+ cx,
+ )
+ .into_any_element(),
+ PackDayPrintKind::PrintPickupRoster => {
+ action_button_disabled(
+ "pack-day-print-pickup-roster",
+ app_shared_text(action.label_key),
+ cx,
+ )
+ .into_any_element()
+ }
+ PackDayPrintKind::PrintCustomerLabels => {
+ action_button_disabled(
+ "pack-day-print-customer-labels",
+ app_shared_text(action.label_key),
+ cx,
+ )
+ .into_any_element()
+ }
+ }
+ })),
+ )
+ .when_some(print_status, |this, status| {
+ this.child(pack_day_print_status_note(status))
+ }),
+ )
}),
)
}
@@ -10127,6 +10308,7 @@ fn pack_day_host_handoff_action_presentations(
let running_kind = (host_handoff.status == PackDayHostHandoffStatus::Running)
.then(|| host_handoff.request.as_ref().map(|request| request.kind))
.flatten();
+ let print_running = runtime.pack_day_projection.print.status == PackDayPrintStatus::Running;
PackDayHostHandoffKind::all_v1()
.into_iter()
@@ -10134,6 +10316,7 @@ fn pack_day_host_handoff_action_presentations(
kind,
label_key: pack_day_host_handoff_action_label_key(kind, running_kind),
enabled: running_kind.is_none()
+ && !print_running
&& pack_day_host_handoff_action_is_available(bundle, kind),
})
.collect()
@@ -10149,12 +10332,12 @@ fn pack_day_host_handoff_action_is_available(
.artifacts
.iter()
.find(|artifact| artifact.kind == artifact_kind)
- .and_then(|artifact| pack_day_host_handoff_target_path(bundle, &artifact.relative_path))
+ .and_then(|artifact| pack_day_export_artifact_path(bundle, &artifact.relative_path))
.is_some_and(|path| path.is_file()),
}
}
-fn pack_day_host_handoff_target_path(
+fn pack_day_export_artifact_path(
bundle: &PackDayExportBundle,
relative_path: &str,
) -> Option<PathBuf> {
@@ -10173,6 +10356,131 @@ fn pack_day_host_handoff_target_path(
Some(PathBuf::from(&bundle.bundle_directory).join(relative_path))
}
+fn pack_day_print_action_presentations(
+ runtime: &DesktopAppRuntimeSummary,
+) -> Vec<PackDayPrintActionPresentation> {
+ let Some(bundle) = pack_day_export_succeeded_bundle(runtime) else {
+ return Vec::new();
+ };
+
+ let print = &runtime.pack_day_projection.print;
+ let running_kind = (print.status == PackDayPrintStatus::Running)
+ .then(|| print.request.as_ref().map(|request| request.kind))
+ .flatten();
+ let host_handoff_running =
+ runtime.pack_day_projection.host_handoff.status == PackDayHostHandoffStatus::Running;
+
+ PackDayPrintKind::all_v1()
+ .into_iter()
+ .map(|kind| PackDayPrintActionPresentation {
+ kind,
+ label_key: pack_day_print_action_label_key(kind, running_kind),
+ enabled: running_kind.is_none()
+ && !host_handoff_running
+ && pack_day_print_action_is_available(bundle, kind),
+ })
+ .collect()
+}
+
+fn pack_day_print_action_is_available(
+ bundle: &PackDayExportBundle,
+ kind: PackDayPrintKind,
+) -> bool {
+ bundle
+ .artifacts
+ .iter()
+ .find(|artifact| artifact.kind == kind.artifact_kind())
+ .and_then(|artifact| pack_day_export_artifact_path(bundle, &artifact.relative_path))
+ .is_some_and(|path| path.is_file())
+}
+
+fn pack_day_print_action_label_key(
+ kind: PackDayPrintKind,
+ running_kind: Option<PackDayPrintKind>,
+) -> AppTextKey {
+ match (kind, running_kind == Some(kind)) {
+ (PackDayPrintKind::PrintPackSheet, true) => AppTextKey::PackDayPrintPackSheetActionRunning,
+ (PackDayPrintKind::PrintPackSheet, false) => AppTextKey::PackDayPrintPackSheetAction,
+ (PackDayPrintKind::PrintPickupRoster, true) => {
+ AppTextKey::PackDayPrintPickupRosterActionRunning
+ }
+ (PackDayPrintKind::PrintPickupRoster, false) => AppTextKey::PackDayPrintPickupRosterAction,
+ (PackDayPrintKind::PrintCustomerLabels, true) => {
+ AppTextKey::PackDayPrintCustomerLabelsActionRunning
+ }
+ (PackDayPrintKind::PrintCustomerLabels, false) => {
+ AppTextKey::PackDayPrintCustomerLabelsAction
+ }
+ }
+}
+
+fn pack_day_print_status_presentation(
+ runtime: &DesktopAppRuntimeSummary,
+) -> Option<PackDayPrintStatusPresentation> {
+ let print = &runtime.pack_day_projection.print;
+ let kind = print.request.as_ref()?.kind;
+
+ let status = match (print.status, kind) {
+ (PackDayPrintStatus::Idle, _) => return None,
+ (PackDayPrintStatus::Running, PackDayPrintKind::PrintPackSheet) => {
+ PackDayPrintStatusPresentation {
+ indicator_color: APP_UI_THEME.foundation.text.accent,
+ title_key: AppTextKey::PackDayPrintPackSheetQueuedTitle,
+ }
+ }
+ (PackDayPrintStatus::Running, PackDayPrintKind::PrintPickupRoster) => {
+ PackDayPrintStatusPresentation {
+ indicator_color: APP_UI_THEME.foundation.text.accent,
+ title_key: AppTextKey::PackDayPrintPickupRosterQueuedTitle,
+ }
+ }
+ (PackDayPrintStatus::Running, PackDayPrintKind::PrintCustomerLabels) => {
+ PackDayPrintStatusPresentation {
+ indicator_color: APP_UI_THEME.foundation.text.accent,
+ title_key: AppTextKey::PackDayPrintCustomerLabelsQueuedTitle,
+ }
+ }
+ (PackDayPrintStatus::Succeeded, PackDayPrintKind::PrintPackSheet) => {
+ PackDayPrintStatusPresentation {
+ indicator_color: APP_UI_THEME.components.app_status_indicator.online,
+ title_key: AppTextKey::PackDayPrintPackSheetSubmittedTitle,
+ }
+ }
+ (PackDayPrintStatus::Succeeded, PackDayPrintKind::PrintPickupRoster) => {
+ PackDayPrintStatusPresentation {
+ indicator_color: APP_UI_THEME.components.app_status_indicator.online,
+ title_key: AppTextKey::PackDayPrintPickupRosterSubmittedTitle,
+ }
+ }
+ (PackDayPrintStatus::Succeeded, PackDayPrintKind::PrintCustomerLabels) => {
+ PackDayPrintStatusPresentation {
+ indicator_color: APP_UI_THEME.components.app_status_indicator.online,
+ title_key: AppTextKey::PackDayPrintCustomerLabelsSubmittedTitle,
+ }
+ }
+ (PackDayPrintStatus::Failed, PackDayPrintKind::PrintPackSheet) => {
+ PackDayPrintStatusPresentation {
+ indicator_color: APP_UI_THEME.components.app_status_indicator.attention,
+ title_key: AppTextKey::PackDayPrintPackSheetFailedTitle,
+ }
+ }
+ (PackDayPrintStatus::Failed, PackDayPrintKind::PrintPickupRoster) => {
+ PackDayPrintStatusPresentation {
+ indicator_color: APP_UI_THEME.components.app_status_indicator.attention,
+ title_key: AppTextKey::PackDayPrintPickupRosterFailedTitle,
+ }
+ }
+ (PackDayPrintStatus::Failed, PackDayPrintKind::PrintCustomerLabels) => {
+ PackDayPrintStatusPresentation {
+ indicator_color: APP_UI_THEME.components.app_status_indicator.attention,
+ title_key: AppTextKey::PackDayPrintCustomerLabelsFailedTitle,
+ }
+ }
+ };
+
+ Some(status)
+}
+
fn pack_day_host_handoff_action_label_key(
kind: PackDayHostHandoffKind,
running_kind: Option<PackDayHostHandoffKind>,
@@ -10314,6 +10622,24 @@ fn pack_day_host_handoff_status_note(
})
}
+fn pack_day_print_status_note(status: PackDayPrintStatusPresentation) -> impl IntoElement {
+ app_stack_v(4.0).w_full().child(
+ div()
+ .w_full()
+ .flex()
+ .items_center()
+ .gap(px(APP_UI_THEME.shells.settings_account_status_gap_px))
+ .child(status_indicator(status.indicator_color))
+ .child(
+ div()
+ .text_size(px(APP_UI_THEME.foundation.typography.utility_title_text_px))
+ .font_weight(gpui::FontWeight::SEMIBOLD)
+ .text_color(rgb(APP_UI_THEME.foundation.text.primary))
+ .child(app_shared_text(status.title_key)),
+ ),
+ )
+}
+
fn pack_day_export_succeeded_bundle(
runtime: &DesktopAppRuntimeSummary,
) -> Option<&PackDayExportBundle> {
@@ -12259,7 +12585,8 @@ mod tests {
APP_UI_THEME, AppTextKey, FarmerHomeFarmState, HomeAutoFocusState, HomeAutoFocusTarget,
HomeStage, LabelValueRow, PackDayExportStatusPresentation,
PackDayHostHandoffActionPresentation, PackDayHostHandoffStatusPresentation,
- ReminderActionTarget, SETTINGS_FARM_PANEL_SECTIONS, SETTINGS_NAVIGATION_ORDER,
+ PackDayPrintActionPresentation, PackDayPrintStatusPresentation, ReminderActionTarget,
+ SETTINGS_FARM_PANEL_SECTIONS, SETTINGS_NAVIGATION_ORDER,
SETTINGS_OPERATIONS_PANEL_SECTIONS, SettingsAutoFocusTarget, SettingsInventorySectionSpec,
SettingsPanelViewKey, StartupHomeSurface, StartupSignerConnectState,
about_conflict_action_specs, about_conflict_aggregate_text, about_conflict_detail_rows,
@@ -12271,6 +12598,7 @@ mod tests {
pack_day_export_action_label_key, pack_day_export_artifact_names,
pack_day_export_detail_rows, pack_day_export_status_presentation,
pack_day_host_handoff_action_presentations, pack_day_host_handoff_status_presentation,
+ pack_day_print_action_presentations, pack_day_print_status_presentation,
parse_optional_product_editor_stock_input, parse_product_editor_price_input,
presented_farmer_reminder, product_display_title, reminder_action_target,
reminder_deadline_text, reminder_delivery_state_key, reminder_urgency_color,
@@ -12291,11 +12619,11 @@ mod tests {
FulfillmentWindowSummary, LoggedOutStartupPhase, LoggedOutStartupProjection,
OrderDetailProjection, OrderId, OrderPrimaryAction, OrderStatus, OrdersListRow,
PackDayExportArtifact, PackDayExportArtifactKind, PackDayExportBundle,
- PackDayHostHandoffKind, PackDayHostHandoffStatus, PackDayProductTotalRow,
- PackDayProjection, PersonalSection, ReminderDeadlineProjection, ReminderDeliveryState,
- ReminderId, ReminderKind, ReminderSurface, ReminderUrgency, RepeatDemandEligibility,
- RepeatDemandHandoffProjection, ShellSection, TodayAgendaProjection, TodaySetupTask,
- TodaySetupTaskKind,
+ PackDayHostHandoffKind, PackDayHostHandoffStatus, PackDayPrintKind, PackDayPrintStatus,
+ PackDayProductTotalRow, PackDayProjection, PersonalSection, ReminderDeadlineProjection,
+ ReminderDeliveryState, ReminderId, ReminderKind, ReminderSurface, ReminderUrgency,
+ RepeatDemandEligibility, RepeatDemandHandoffProjection, ShellSection,
+ TodayAgendaProjection, TodaySetupTask, TodaySetupTaskKind,
};
use radroots_app_remote_signer::{
RadrootsAppRemoteSignerApprovedSession, RadrootsAppRemoteSignerPendingSession,
@@ -12304,6 +12632,7 @@ mod tests {
use radroots_app_state::{
AppShellProjection, FarmWorkspaceReadinessProjection, FarmWorkspaceStatus, HomeRoute,
PackDayExportProjection, PackDayHostHandoffProjection, PackDayHostHandoffRequest,
+ PackDayPrintProjection, PackDayPrintRequest,
};
use radroots_app_sync::{
AppSyncProjection, AppSyncRunStatus, SyncAggregateRef, SyncCheckpointStatus, SyncConflict,
@@ -13232,6 +13561,202 @@ mod tests {
}
#[test]
+ fn pack_day_print_actions_only_surface_after_a_successful_export() {
+ let temp_dir = TestDirectory::new();
+ write_artifact(temp_dir.path(), "pack_sheet.txt");
+ write_artifact(temp_dir.path(), "pickup_roster.txt");
+ write_artifact(temp_dir.path(), "customer_labels.txt");
+ let bundle = sample_pack_day_bundle(temp_dir.path());
+ let fulfillment_window_id = bundle.fulfillment_window_id;
+ let mut runtime = summary(
+ HomeRoute::Today,
+ TodayAgendaProjection::default(),
+ FarmSetupProjection::default(),
+ );
+
+ assert!(pack_day_print_action_presentations(&runtime).is_empty());
+
+ runtime.pack_day_projection.export = PackDayExportProjection::succeeded(
+ radroots_app_state::PackDayExportRequest::for_fulfillment_window(fulfillment_window_id),
+ bundle,
+ );
+
+ assert_eq!(
+ pack_day_print_action_presentations(&runtime),
+ vec![
+ PackDayPrintActionPresentation {
+ kind: PackDayPrintKind::PrintPackSheet,
+ label_key: AppTextKey::PackDayPrintPackSheetAction,
+ enabled: true,
+ },
+ PackDayPrintActionPresentation {
+ kind: PackDayPrintKind::PrintPickupRoster,
+ label_key: AppTextKey::PackDayPrintPickupRosterAction,
+ enabled: true,
+ },
+ PackDayPrintActionPresentation {
+ kind: PackDayPrintKind::PrintCustomerLabels,
+ label_key: AppTextKey::PackDayPrintCustomerLabelsAction,
+ enabled: true,
+ },
+ ]
+ );
+ }
+
+ #[test]
+ fn pack_day_print_running_and_failure_postures_track_the_active_request() {
+ let temp_dir = TestDirectory::new();
+ write_artifact(temp_dir.path(), "pack_sheet.txt");
+ write_artifact(temp_dir.path(), "pickup_roster.txt");
+ write_artifact(temp_dir.path(), "customer_labels.txt");
+ let bundle = sample_pack_day_bundle(temp_dir.path());
+ let fulfillment_window_id = bundle.fulfillment_window_id;
+ let export_request =
+ radroots_app_state::PackDayExportRequest::for_fulfillment_window(fulfillment_window_id);
+ let print_request =
+ PackDayPrintRequest::for_bundle(PackDayPrintKind::PrintPackSheet, &bundle);
+ let failed_request =
+ PackDayPrintRequest::for_bundle(PackDayPrintKind::PrintCustomerLabels, &bundle);
+ let mut runtime = summary(
+ HomeRoute::Today,
+ TodayAgendaProjection::default(),
+ FarmSetupProjection::default(),
+ );
+ runtime.pack_day_projection.export =
+ PackDayExportProjection::succeeded(export_request, bundle.clone());
+
+ runtime.pack_day_projection.print = PackDayPrintProjection::running(print_request);
+ assert_eq!(
+ pack_day_print_action_presentations(&runtime),
+ vec![
+ PackDayPrintActionPresentation {
+ kind: PackDayPrintKind::PrintPackSheet,
+ label_key: AppTextKey::PackDayPrintPackSheetActionRunning,
+ enabled: false,
+ },
+ PackDayPrintActionPresentation {
+ kind: PackDayPrintKind::PrintPickupRoster,
+ label_key: AppTextKey::PackDayPrintPickupRosterAction,
+ enabled: false,
+ },
+ PackDayPrintActionPresentation {
+ kind: PackDayPrintKind::PrintCustomerLabels,
+ label_key: AppTextKey::PackDayPrintCustomerLabelsAction,
+ enabled: false,
+ },
+ ]
+ );
+ assert_eq!(
+ pack_day_print_status_presentation(&runtime),
+ Some(PackDayPrintStatusPresentation {
+ indicator_color: APP_UI_THEME.foundation.text.accent,
+ title_key: AppTextKey::PackDayPrintPackSheetQueuedTitle,
+ })
+ );
+ assert_eq!(
+ pack_day_host_handoff_action_presentations(&runtime),
+ vec![
+ PackDayHostHandoffActionPresentation {
+ kind: PackDayHostHandoffKind::RevealBundle,
+ label_key: AppTextKey::PackDayHostHandoffRevealAction,
+ enabled: false,
+ },
+ PackDayHostHandoffActionPresentation {
+ kind: PackDayHostHandoffKind::OpenPackSheet,
+ label_key: AppTextKey::PackDayHostHandoffOpenPackSheetAction,
+ enabled: false,
+ },
+ PackDayHostHandoffActionPresentation {
+ kind: PackDayHostHandoffKind::OpenPickupRoster,
+ label_key: AppTextKey::PackDayHostHandoffOpenPickupRosterAction,
+ enabled: false,
+ },
+ PackDayHostHandoffActionPresentation {
+ kind: PackDayHostHandoffKind::OpenCustomerLabels,
+ label_key: AppTextKey::PackDayHostHandoffOpenCustomerLabelsAction,
+ enabled: false,
+ },
+ ]
+ );
+
+ runtime.pack_day_projection.print = PackDayPrintProjection::failed(failed_request);
+ assert_eq!(
+ runtime.pack_day_projection.print.status,
+ PackDayPrintStatus::Failed
+ );
+ assert_eq!(
+ pack_day_print_status_presentation(&runtime),
+ Some(PackDayPrintStatusPresentation {
+ indicator_color: APP_UI_THEME.components.app_status_indicator.attention,
+ title_key: AppTextKey::PackDayPrintCustomerLabelsFailedTitle,
+ })
+ );
+ }
+
+ #[test]
+ fn pack_day_print_actions_disable_missing_artifacts_and_host_handoff_runs() {
+ let temp_dir = TestDirectory::new();
+ write_artifact(temp_dir.path(), "pack_sheet.txt");
+ let bundle = sample_pack_day_bundle(temp_dir.path());
+ let fulfillment_window_id = bundle.fulfillment_window_id;
+ let export_request =
+ radroots_app_state::PackDayExportRequest::for_fulfillment_window(fulfillment_window_id);
+ let host_handoff_request =
+ PackDayHostHandoffRequest::for_bundle(PackDayHostHandoffKind::RevealBundle, &bundle);
+ let mut runtime = summary(
+ HomeRoute::Today,
+ TodayAgendaProjection::default(),
+ FarmSetupProjection::default(),
+ );
+
+ runtime.pack_day_projection.export =
+ PackDayExportProjection::succeeded(export_request, bundle.clone());
+ assert_eq!(
+ pack_day_print_action_presentations(&runtime),
+ vec![
+ PackDayPrintActionPresentation {
+ kind: PackDayPrintKind::PrintPackSheet,
+ label_key: AppTextKey::PackDayPrintPackSheetAction,
+ enabled: true,
+ },
+ PackDayPrintActionPresentation {
+ kind: PackDayPrintKind::PrintPickupRoster,
+ label_key: AppTextKey::PackDayPrintPickupRosterAction,
+ enabled: false,
+ },
+ PackDayPrintActionPresentation {
+ kind: PackDayPrintKind::PrintCustomerLabels,
+ label_key: AppTextKey::PackDayPrintCustomerLabelsAction,
+ enabled: false,
+ },
+ ]
+ );
+
+ runtime.pack_day_projection.host_handoff =
+ PackDayHostHandoffProjection::running(host_handoff_request);
+ assert_eq!(
+ pack_day_print_action_presentations(&runtime),
+ vec![
+ PackDayPrintActionPresentation {
+ kind: PackDayPrintKind::PrintPackSheet,
+ label_key: AppTextKey::PackDayPrintPackSheetAction,
+ enabled: false,
+ },
+ PackDayPrintActionPresentation {
+ kind: PackDayPrintKind::PrintPickupRoster,
+ label_key: AppTextKey::PackDayPrintPickupRosterAction,
+ enabled: false,
+ },
+ PackDayPrintActionPresentation {
+ kind: PackDayPrintKind::PrintCustomerLabels,
+ label_key: AppTextKey::PackDayPrintCustomerLabelsAction,
+ enabled: false,
+ },
+ ]
+ );
+ }
+
+ #[test]
fn sidebar_navigation_keeps_the_active_destination_first() {
assert_eq!(
home_sidebar_navigation_sections(FarmerSection::Today, true, false),
diff --git a/crates/shared/i18n/src/keys.rs b/crates/shared/i18n/src/keys.rs
@@ -241,6 +241,12 @@ define_app_text_keys! {
PackDayExportFolderLabel => "pack_day.export.folder.label",
PackDayExportFilesLabel => "pack_day.export.files.label",
PackDayExportErrorLabel => "pack_day.export.error.label",
+ PackDayPrintPackSheetAction => "pack_day.print.pack_sheet.action",
+ PackDayPrintPackSheetActionRunning => "pack_day.print.pack_sheet.action.running",
+ PackDayPrintPickupRosterAction => "pack_day.print.pickup_roster.action",
+ PackDayPrintPickupRosterActionRunning => "pack_day.print.pickup_roster.action.running",
+ PackDayPrintCustomerLabelsAction => "pack_day.print.customer_labels.action",
+ PackDayPrintCustomerLabelsActionRunning => "pack_day.print.customer_labels.action.running",
PackDayPrintUnavailableTitle => "pack_day.print.unavailable.title",
PackDayPrintUnavailableBody => "pack_day.print.unavailable.body",
PackDayPrintPackSheetQueuedTitle => "pack_day.print.pack_sheet.queued.title",
diff --git a/crates/shared/i18n/src/lib.rs b/crates/shared/i18n/src/lib.rs
@@ -476,6 +476,30 @@ mod tests {
assert_eq!(app_text(AppTextKey::PackDayExportFilesLabel), "Files");
assert_eq!(app_text(AppTextKey::PackDayExportErrorLabel), "Error");
assert_eq!(
+ app_text(AppTextKey::PackDayPrintPackSheetAction),
+ "Print pack sheet"
+ );
+ assert_eq!(
+ app_text(AppTextKey::PackDayPrintPackSheetActionRunning),
+ "Printing pack sheet..."
+ );
+ assert_eq!(
+ app_text(AppTextKey::PackDayPrintPickupRosterAction),
+ "Print pickup roster"
+ );
+ assert_eq!(
+ app_text(AppTextKey::PackDayPrintPickupRosterActionRunning),
+ "Printing pickup roster..."
+ );
+ assert_eq!(
+ app_text(AppTextKey::PackDayPrintCustomerLabelsAction),
+ "Print customer labels (Avery 5160)"
+ );
+ assert_eq!(
+ app_text(AppTextKey::PackDayPrintCustomerLabelsActionRunning),
+ "Printing customer labels (Avery 5160)..."
+ );
+ assert_eq!(
app_text(AppTextKey::PackDayPrintUnavailableTitle),
"Print not available yet"
);
diff --git a/i18n/locales/en/messages.json b/i18n/locales/en/messages.json
@@ -221,6 +221,12 @@
"pack_day.export.folder.label": "Folder",
"pack_day.export.files.label": "Files",
"pack_day.export.error.label": "Error",
+ "pack_day.print.pack_sheet.action": "Print pack sheet",
+ "pack_day.print.pack_sheet.action.running": "Printing pack sheet...",
+ "pack_day.print.pickup_roster.action": "Print pickup roster",
+ "pack_day.print.pickup_roster.action.running": "Printing pickup roster...",
+ "pack_day.print.customer_labels.action": "Print customer labels (Avery 5160)",
+ "pack_day.print.customer_labels.action.running": "Printing customer labels (Avery 5160)...",
"pack_day.print.unavailable.title": "Print not available yet",
"pack_day.print.unavailable.body": "Print actions become available after pack day files are saved locally.",
"pack_day.print.pack_sheet.queued.title": "Queueing pack sheet",