diff --git a/.phpunit.result.cache b/.phpunit.result.cache index f28bb4f..1231bd0 100644 --- a/.phpunit.result.cache +++ b/.phpunit.result.cache @@ -1 +1 @@ -{"version":2,"defects":{"Tests\\Feature\\ProductPriceTest::test_product_list_returns_price_for_store_level":8,"Tests\\Feature\\ProductPriceTest::test_store_without_level_is_rejected":8,"Tests\\Feature\\ProductPriceTest::test_batch_price_updates_and_notifies_affected_stores":7,"Tests\\Feature\\ProductPriceTest::test_price_matrix_structure":8,"Tests\\Feature\\StoreOrderTest::test_place_order_snapshots_level_price_and_recalculates":8,"Tests\\Feature\\StoreOrderTest::test_client_supplied_amount_is_ignored":8,"Tests\\Feature\\StoreOrderTest::test_product_without_level_price_rejected":8,"Tests\\Feature\\StoreOrderTest::test_cancel_only_pending_orders":8,"Tests\\Feature\\StoreOrderTest::test_summarized_order_cannot_be_cancelled":8,"Tests\\Feature\\StoreOrderTest::test_order_data_isolated_between_stores":8,"Tests\\Feature\\PurchaseGenerateTest::test_generate_aggregates_orders_by_product":7,"Tests\\Feature\\PurchaseGenerateTest::test_generate_writes_back_order_status":7,"Tests\\Feature\\PurchaseGenerateTest::test_generate_without_pending_orders_fails":8,"Tests\\Feature\\PurchaseGenerateTest::test_generate_is_idempotent":7,"Tests\\Feature\\AllocationTest::test_allocation_conserves_amount_with_tail_correction":7,"Tests\\Feature\\AllocationTest::test_allocation_follows_order_ratio":7,"Tests\\Feature\\AllocationTest::test_allocation_is_idempotent":7,"Tests\\Feature\\AllocationTest::test_allocation_rejected_without_actual_amount":8,"Tests\\Feature\\MiniAuthTest::test_login_creates_user_and_issues_token":7,"Tests\\Feature\\MiniAuthTest::test_phone_binding_matches_store":8,"Tests\\Feature\\MiniAuthTest::test_phone_no_match_stays_pending":8,"Tests\\Feature\\ExportTest::test_export_xlsx_with_encoded_chinese_filename":7,"Tests\\Feature\\CartTest::test_list_with_prices_amounts_and_totals":7,"Tests\\Feature\\CartTest::test_list_marks_off_shelf_item_unbuyable":7,"Tests\\Feature\\DebugCartListTest::test_dump_cart_list_response":5,"Tests\\Feature\\ProductCostPriceTest::test_create_product_with_cost_and_percent_pricing":7,"Tests\\Feature\\ProductCostPriceTest::test_create_percent_row_without_percent_fails":7,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_cost_only_notifies_percent_level_stores":7,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_empty_row_rejected":7,"Tests\\Feature\\MiniAuthTest::test_disabled_user_cannot_login":8,"Tests\\Feature\\MiniAuthTest::test_mini_token_cannot_access_admin_api":8,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_percent_row_update":7,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_mixed_row_updates_cost_and_level":7,"Tests\\Feature\\ReconciliationTest::test_build_creates_reconciliation_items":8,"Tests\\Feature\\ReconciliationTest::test_build_filters_by_supplier":8,"Tests\\Feature\\ReconciliationTest::test_update_item_recalculates_diff_and_header":8,"Tests\\Feature\\ReconciliationTest::test_toggle_reconciled_flag":8,"Tests\\Feature\\ReconciliationTest::test_settle_creates_settlements_per_store":8,"Tests\\Feature\\ProductCostPriceTest::test_mini_list_returns_actual_price_without_cost_price":8,"Tests\\Feature\\ProductCostPriceTest::test_mini_order_uses_actual_price_for_percent_row":8,"Tests\\Feature\\ProductCostPriceTest::test_mini_cart_uses_actual_price_for_percent_row":8,"Tests\\Feature\\StoreOrderTest::test_container_update_recalculates_amounts":8,"Tests\\Feature\\StoreOrderTest::test_container_update_allowed_in_editable_statuses":8,"Tests\\Feature\\StoreOrderTest::test_container_update_rejected_in_other_statuses":8,"Tests\\Feature\\StoreOrderTest::test_container_update_heals_legacy_product_amount":8,"Tests\\Feature\\StoreOrderTest::test_container_update_validates_negative_numbers":8,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_rejected_when_product_deleted":7,"Tests\\Feature\\StoreOrderItemTest::test_order_list_search_by_product_name":7,"Tests\\Feature\\PurchaseEditTest::test_detail_returns_store_matrix":7,"Tests\\Feature\\PurchaseEditTest::test_update_cell_syncs_order_item_and_totals":7,"Tests\\Feature\\PurchaseEditTest::test_update_row_cost_syncs_all_order_items":7,"Tests\\Feature\\PurchaseEditTest::test_update_row_weight_distributed_by_quantity":7,"Tests\\Feature\\PurchaseEditTest::test_update_row_syncs_product_attributes":7,"Tests\\Feature\\PurchaseGenerateTest::test_generate_aggregates_order_items_into_header":7,"Tests\\Feature\\ExportTest::test_export_category_filters_to_vegetables":7,"Tests\\Feature\\ExportTest::test_export_pdf":7,"Tests\\Feature\\PurchaseEditTest::test_update_row_does_not_touch_manual_weight":7,"Tests\\Feature\\PurchaseEditTest::test_update_row_syncs_to_product_catalog":7,"Tests\\Feature\\PurchaseEditTest::test_update_cell_weight_cascades_totals":7,"Tests\\Feature\\PurchaseEditTest::test_cell_sync_refreshes_snapshot_and_cascades":7,"Tests\\Feature\\PurchaseEditTest::test_edits_rejected_when_purchase_completed":7,"Tests\\Feature\\StoreOrderItemTest::test_update_item_recalculates_order_totals":7,"Tests\\Feature\\StoreOrderItemTest::test_update_item_validates_negative_price":7,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_updates_snapshot_from_product":7,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_recalculates_percent_price_with_latest_cost":7},"times":{"Tests\\Feature\\ExampleTest::test_the_application_returns_a_successful_response":0.003,"Tests\\Feature\\ProductPriceTest::test_product_list_returns_price_for_store_level":0.007,"Tests\\Feature\\ProductPriceTest::test_store_without_level_is_rejected":0.007,"Tests\\Feature\\ProductPriceTest::test_batch_price_updates_and_notifies_affected_stores":0.009,"Tests\\Feature\\ProductPriceTest::test_price_matrix_structure":0.007,"Tests\\Feature\\StoreOrderTest::test_place_order_snapshots_level_price_and_recalculates":0.012,"Tests\\Feature\\StoreOrderTest::test_client_supplied_amount_is_ignored":0.014,"Tests\\Feature\\StoreOrderTest::test_product_without_level_price_rejected":0.014,"Tests\\Feature\\StoreOrderTest::test_cancel_only_pending_orders":0.016,"Tests\\Feature\\StoreOrderTest::test_summarized_order_cannot_be_cancelled":0.015,"Tests\\Feature\\StoreOrderTest::test_order_data_isolated_between_stores":0.021,"Tests\\Feature\\PurchaseGenerateTest::test_generate_aggregates_orders_by_product":0.028,"Tests\\Feature\\PurchaseGenerateTest::test_generate_writes_back_order_status":0.048,"Tests\\Feature\\PurchaseGenerateTest::test_generate_without_pending_orders_fails":0.006,"Tests\\Feature\\PurchaseGenerateTest::test_generate_is_idempotent":0.041,"Tests\\Feature\\AllocationTest::test_allocation_conserves_amount_with_tail_correction":0.076,"Tests\\Feature\\AllocationTest::test_allocation_follows_order_ratio":0.022,"Tests\\Feature\\AllocationTest::test_allocation_is_idempotent":0.028,"Tests\\Feature\\AllocationTest::test_allocation_rejected_without_actual_amount":0.021,"Tests\\Feature\\DebugGenerateTest::test_debug_generate":0.047,"Tests\\Feature\\ReconciliationTest::test_build_creates_reconciliation_items":0.018,"Tests\\Feature\\ReconciliationTest::test_build_filters_by_supplier":0.021,"Tests\\Feature\\ReconciliationTest::test_update_item_recalculates_diff_and_header":0.019,"Tests\\Feature\\ReconciliationTest::test_toggle_reconciled_flag":0.098,"Tests\\Feature\\ReconciliationTest::test_settle_creates_settlements_per_store":0.076,"Tests\\Feature\\StatementTest::test_generate_snapshots_payment_cycle":0.034,"Tests\\Feature\\StatementTest::test_statement_isolated_between_stores":0.031,"Tests\\Feature\\StatementTest::test_generate_excludes_cancelled_and_used_items":0.042,"Tests\\Feature\\MiniAuthTest::test_login_creates_user_and_issues_token":0.056,"Tests\\Feature\\MiniAuthTest::test_disabled_user_cannot_login":0.002,"Tests\\Feature\\MiniAuthTest::test_phone_binding_matches_store":0.005,"Tests\\Feature\\MiniAuthTest::test_phone_no_match_stays_pending":0.001,"Tests\\Feature\\MiniAuthTest::test_sys_token_cannot_access_mini":0.005,"Tests\\Feature\\MiniAuthTest::test_mini_token_cannot_access_admin_api":0.004,"Tests\\Feature\\ExportTest::test_export_xlsx_with_encoded_chinese_filename":0.021,"Tests\\Feature\\ExportTest::test_export_category_filters_to_vegetables":0.015,"Tests\\Feature\\ExportTest::test_export_pdf":0.015,"Tests\\Feature\\ExportTest::test_export_invalid_format_rejected":0.016,"Tests\\Feature\\ExportTest::test_export_requires_export_permission":0.015,"Tests\\Unit\\ExampleTest::test_that_true_is_true":0.001,"Tests\\Feature\\MiniAuthTest::test_login_fails_when_wechat_rejects_code":0.003,"Tests\\Feature\\CartTest::test_add_to_cart_succeeds":0.045,"Tests\\Feature\\CartTest::test_duplicate_add_merges_quantity":0.009,"Tests\\Feature\\CartTest::test_off_shelf_product_rejected":0.006,"Tests\\Feature\\CartTest::test_soft_deleted_product_rejected":0.006,"Tests\\Feature\\CartTest::test_product_without_level_price_rejected":0.006,"Tests\\Feature\\CartTest::test_unbound_user_rejected":0.01,"Tests\\Feature\\CartTest::test_store_without_level_rejected":0.006,"Tests\\Feature\\CartTest::test_list_with_prices_amounts_and_totals":0.011,"Tests\\Feature\\CartTest::test_list_marks_off_shelf_item_unbuyable":0.01,"Tests\\Feature\\CartTest::test_empty_cart_returns_empty_items":0.005,"Tests\\Feature\\CartTest::test_update_quantity":0.008,"Tests\\Feature\\CartTest::test_update_other_users_item_rejected":0.009,"Tests\\Feature\\CartTest::test_update_missing_item_rejected":0.009,"Tests\\Feature\\CartTest::test_invalid_quantity_rejected":0.006,"Tests\\Feature\\CartTest::test_delete_item":0.007,"Tests\\Feature\\CartTest::test_delete_other_users_item_rejected":0.009,"Tests\\Feature\\CartTest::test_clear_only_own_cart":0.012,"Tests\\Feature\\CartTest::test_unauthenticated_returns_401":0.002,"Tests\\Feature\\CartTest::test_cart_isolated_between_users":0.008,"Tests\\Feature\\DebugCartListTest::test_dump_cart_list_response":0.057,"Tests\\Feature\\ProductCostPriceTest::test_calc_actual_price_fixed_and_percent":0.001,"Tests\\Feature\\ProductCostPriceTest::test_calc_actual_price_edge_cases":0.001,"Tests\\Feature\\ProductCostPriceTest::test_admin_list_contains_cost_price_and_actual_price":0.01,"Tests\\Feature\\ProductCostPriceTest::test_mini_list_returns_actual_price_without_cost_price":0.007,"Tests\\Feature\\ProductCostPriceTest::test_create_product_with_cost_and_percent_pricing":0.011,"Tests\\Feature\\ProductCostPriceTest::test_create_percent_row_without_percent_fails":0.007,"Tests\\Feature\\ProductCostPriceTest::test_update_switches_fixed_to_percent":0.009,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_cost_only_notifies_percent_level_stores":0.01,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_percent_row_update":0.008,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_mixed_row_updates_cost_and_level":0.008,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_empty_row_rejected":0.006,"Tests\\Feature\\ProductCostPriceTest::test_price_matrix_includes_cost_and_percent_columns":0.007,"Tests\\Feature\\ProductCostPriceTest::test_mini_order_uses_actual_price_for_percent_row":0.007,"Tests\\Feature\\ProductCostPriceTest::test_mini_cart_uses_actual_price_for_percent_row":0.008,"Tests\\Feature\\PurchaseGenerateTest::test_generate_uses_min_actual_price_across_types":0.014,"Tests\\Feature\\ProductPriceTest::test_store_without_level_sees_products_without_price":0.006,"Tests\\Feature\\ProductPriceTest::test_unbound_user_can_browse_products_without_price_but_cannot_cart":0.008,"Tests\\Feature\\MiniAuthTest::test_login_issues_token_for_registered_user":0.008,"Tests\\Feature\\MiniAuthTest::test_login_rejects_unregistered_openid":0.002,"Tests\\Feature\\MiniAuthTest::test_register_binds_store_and_issues_token":0.009,"Tests\\Feature\\MiniAuthTest::test_register_rejects_unknown_store":0.002,"Tests\\Feature\\MiniAuthTest::test_register_rejects_duplicate_openid":0.002,"Tests\\Feature\\MiniStoreTest::test_info_returns_bound_store":0.005,"Tests\\Feature\\MiniStoreTest::test_update_info":0.007,"Tests\\Feature\\MiniStoreTest::test_update_info_ignores_readonly_fields":0.005,"Tests\\Feature\\MiniStoreTest::test_requires_bound_store":0.007,"Tests\\Feature\\MiniStoreTest::test_update_info_validates_length":0.005,"Tests\\Feature\\StoreOrderTest::test_container_update_recalculates_amounts":0.022,"Tests\\Feature\\StoreOrderTest::test_container_update_allowed_in_editable_statuses":0.025,"Tests\\Feature\\StoreOrderTest::test_container_update_rejected_in_other_statuses":0.028,"Tests\\Feature\\StoreOrderTest::test_container_update_heals_legacy_product_amount":0.021,"Tests\\Feature\\StoreOrderTest::test_container_update_validates_negative_numbers":0.022,"Tests\\Feature\\PurchaseGenerateTest::test_generate_without_accepted_orders_fails":0.024,"Tests\\Feature\\PurchaseGenerateTest::test_generate_with_selected_order_ids":0.026,"Tests\\Feature\\StoreOrderItemTest::test_place_order_snapshots_full_product_info":0.012,"Tests\\Feature\\StoreOrderItemTest::test_mini_detail_hides_cost_price":0.025,"Tests\\Feature\\StoreOrderItemTest::test_admin_detail_shows_cost_price_and_supplier":0.023,"Tests\\Feature\\StoreOrderItemTest::test_update_item_recalculates_order_totals":0.023,"Tests\\Feature\\StoreOrderItemTest::test_update_item_rejected_when_completed_or_cancelled":0.035,"Tests\\Feature\\StoreOrderItemTest::test_update_item_validates_negative_price":0.023,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_updates_snapshot_from_product":0.019,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_recalculates_percent_price_with_latest_cost":0.03,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_rejected_when_product_deleted":0.022,"Tests\\Feature\\StoreOrderItemTest::test_order_list_search_by_product_name":0.032,"Tests\\Feature\\StoreOrderTest::test_soft_delete_only_cancelled_orders":0.049,"Tests\\Feature\\StoreOrderTest::test_soft_delete_rejected_for_non_cancelled_orders":0.068,"Tests\\Feature\\StoreOrderTest::test_soft_delete_twice_rejected":0.013,"Tests\\Feature\\PurchaseEditTest::test_detail_returns_store_matrix":0.024,"Tests\\Feature\\PurchaseEditTest::test_update_cell_syncs_order_item_and_totals":0.019,"Tests\\Feature\\PurchaseEditTest::test_update_row_cost_syncs_all_order_items":0.018,"Tests\\Feature\\PurchaseEditTest::test_update_row_weight_distributed_by_quantity":0.032,"Tests\\Feature\\PurchaseEditTest::test_update_row_requires_at_least_one_field":0.022,"Tests\\Feature\\PurchaseEditTest::test_update_cell_rejects_negative_quantity":0.019,"Tests\\Feature\\PurchaseGenerateTest::test_generate_aggregates_order_items_into_header":0.038,"Tests\\Feature\\ReconciliationTest::test_build_excludes_unfinished_orders":0.025,"Tests\\Feature\\PurchaseEditTest::test_update_row_syncs_product_attributes":0.022,"Tests\\Feature\\PurchaseEditTest::test_update_row_syncs_to_product_catalog":0.02,"Tests\\Feature\\PurchaseEditTest::test_cell_detail_returns_items_with_order_info":0.023,"Tests\\Feature\\PurchaseEditTest::test_cell_detail_rejects_missing_params":0.023,"Tests\\Feature\\PurchaseEditTest::test_update_cell_weight_cascades_totals":0.023,"Tests\\Feature\\PurchaseEditTest::test_cell_sync_refreshes_snapshot_and_cascades":0.02,"Tests\\Feature\\PurchaseEditTest::test_edits_rejected_when_purchase_completed":0.058,"Tests\\Feature\\PurchaseEditTest::test_update_row_does_not_touch_manual_weight":0.019,"Tests\\Feature\\PurchaseEditTest::test_store_summary_returns_aggregated_items":0.07,"Tests\\Feature\\PurchaseEditTest::test_store_summary_aggregates_multiple_orders_with_weighted_price":0.051,"Tests\\Feature\\PurchaseEditTest::test_store_summary_rejects_missing_or_invalid_params":0.057}} \ No newline at end of file +{"version":2,"defects":{"Tests\\Feature\\ProductPriceTest::test_product_list_returns_price_for_store_level":8,"Tests\\Feature\\ProductPriceTest::test_store_without_level_is_rejected":8,"Tests\\Feature\\ProductPriceTest::test_batch_price_updates_and_notifies_affected_stores":7,"Tests\\Feature\\ProductPriceTest::test_price_matrix_structure":8,"Tests\\Feature\\StoreOrderTest::test_place_order_snapshots_level_price_and_recalculates":8,"Tests\\Feature\\StoreOrderTest::test_client_supplied_amount_is_ignored":8,"Tests\\Feature\\StoreOrderTest::test_product_without_level_price_rejected":8,"Tests\\Feature\\StoreOrderTest::test_cancel_only_pending_orders":8,"Tests\\Feature\\StoreOrderTest::test_summarized_order_cannot_be_cancelled":8,"Tests\\Feature\\StoreOrderTest::test_order_data_isolated_between_stores":8,"Tests\\Feature\\PurchaseGenerateTest::test_generate_aggregates_orders_by_product":7,"Tests\\Feature\\PurchaseGenerateTest::test_generate_writes_back_order_status":7,"Tests\\Feature\\PurchaseGenerateTest::test_generate_without_pending_orders_fails":8,"Tests\\Feature\\PurchaseGenerateTest::test_generate_is_idempotent":7,"Tests\\Feature\\AllocationTest::test_allocation_conserves_amount_with_tail_correction":7,"Tests\\Feature\\AllocationTest::test_allocation_follows_order_ratio":7,"Tests\\Feature\\AllocationTest::test_allocation_is_idempotent":7,"Tests\\Feature\\AllocationTest::test_allocation_rejected_without_actual_amount":8,"Tests\\Feature\\MiniAuthTest::test_login_creates_user_and_issues_token":7,"Tests\\Feature\\MiniAuthTest::test_phone_binding_matches_store":8,"Tests\\Feature\\MiniAuthTest::test_phone_no_match_stays_pending":8,"Tests\\Feature\\ExportTest::test_export_xlsx_with_encoded_chinese_filename":7,"Tests\\Feature\\CartTest::test_list_with_prices_amounts_and_totals":7,"Tests\\Feature\\CartTest::test_list_marks_off_shelf_item_unbuyable":7,"Tests\\Feature\\DebugCartListTest::test_dump_cart_list_response":5,"Tests\\Feature\\ProductCostPriceTest::test_create_product_with_cost_and_percent_pricing":7,"Tests\\Feature\\ProductCostPriceTest::test_create_percent_row_without_percent_fails":7,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_cost_only_notifies_percent_level_stores":7,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_empty_row_rejected":7,"Tests\\Feature\\MiniAuthTest::test_disabled_user_cannot_login":8,"Tests\\Feature\\MiniAuthTest::test_mini_token_cannot_access_admin_api":8,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_percent_row_update":7,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_mixed_row_updates_cost_and_level":7,"Tests\\Feature\\ReconciliationTest::test_build_creates_reconciliation_items":8,"Tests\\Feature\\ReconciliationTest::test_build_filters_by_supplier":8,"Tests\\Feature\\ReconciliationTest::test_update_item_recalculates_diff_and_header":8,"Tests\\Feature\\ReconciliationTest::test_toggle_reconciled_flag":8,"Tests\\Feature\\ReconciliationTest::test_settle_creates_settlements_per_store":8,"Tests\\Feature\\ProductCostPriceTest::test_mini_list_returns_actual_price_without_cost_price":8,"Tests\\Feature\\ProductCostPriceTest::test_mini_order_uses_actual_price_for_percent_row":8,"Tests\\Feature\\ProductCostPriceTest::test_mini_cart_uses_actual_price_for_percent_row":8,"Tests\\Feature\\StoreOrderTest::test_container_update_recalculates_amounts":8,"Tests\\Feature\\StoreOrderTest::test_container_update_allowed_in_editable_statuses":8,"Tests\\Feature\\StoreOrderTest::test_container_update_rejected_in_other_statuses":8,"Tests\\Feature\\StoreOrderTest::test_container_update_heals_legacy_product_amount":8,"Tests\\Feature\\StoreOrderTest::test_container_update_validates_negative_numbers":8,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_rejected_when_product_deleted":7,"Tests\\Feature\\StoreOrderItemTest::test_order_list_search_by_product_name":7,"Tests\\Feature\\PurchaseEditTest::test_detail_returns_store_matrix":7,"Tests\\Feature\\PurchaseEditTest::test_update_cell_syncs_order_item_and_totals":7,"Tests\\Feature\\PurchaseEditTest::test_update_row_cost_syncs_all_order_items":7,"Tests\\Feature\\PurchaseEditTest::test_update_row_weight_distributed_by_quantity":7,"Tests\\Feature\\PurchaseEditTest::test_update_row_syncs_product_attributes":7,"Tests\\Feature\\PurchaseGenerateTest::test_generate_aggregates_order_items_into_header":7,"Tests\\Feature\\ExportTest::test_export_category_filters_to_vegetables":7,"Tests\\Feature\\ExportTest::test_export_pdf":7,"Tests\\Feature\\PurchaseEditTest::test_update_row_does_not_touch_manual_weight":7,"Tests\\Feature\\PurchaseEditTest::test_update_row_syncs_to_product_catalog":7,"Tests\\Feature\\PurchaseEditTest::test_update_cell_weight_cascades_totals":7,"Tests\\Feature\\PurchaseEditTest::test_cell_sync_refreshes_snapshot_and_cascades":7,"Tests\\Feature\\PurchaseEditTest::test_edits_rejected_when_purchase_completed":7,"Tests\\Feature\\StoreOrderItemTest::test_update_item_recalculates_order_totals":7,"Tests\\Feature\\StoreOrderItemTest::test_update_item_validates_negative_price":7,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_updates_snapshot_from_product":7,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_recalculates_percent_price_with_latest_cost":7},"times":{"Tests\\Feature\\ExampleTest::test_the_application_returns_a_successful_response":0.003,"Tests\\Feature\\ProductPriceTest::test_product_list_returns_price_for_store_level":0.01,"Tests\\Feature\\ProductPriceTest::test_store_without_level_is_rejected":0.007,"Tests\\Feature\\ProductPriceTest::test_batch_price_updates_and_notifies_affected_stores":0.01,"Tests\\Feature\\ProductPriceTest::test_price_matrix_structure":0.007,"Tests\\Feature\\StoreOrderTest::test_place_order_snapshots_level_price_and_recalculates":0.007,"Tests\\Feature\\StoreOrderTest::test_client_supplied_amount_is_ignored":0.008,"Tests\\Feature\\StoreOrderTest::test_product_without_level_price_rejected":0.011,"Tests\\Feature\\StoreOrderTest::test_cancel_only_pending_orders":0.017,"Tests\\Feature\\StoreOrderTest::test_summarized_order_cannot_be_cancelled":0.014,"Tests\\Feature\\StoreOrderTest::test_order_data_isolated_between_stores":0.012,"Tests\\Feature\\PurchaseGenerateTest::test_generate_aggregates_orders_by_product":0.028,"Tests\\Feature\\PurchaseGenerateTest::test_generate_writes_back_order_status":0.019,"Tests\\Feature\\PurchaseGenerateTest::test_generate_without_pending_orders_fails":0.006,"Tests\\Feature\\PurchaseGenerateTest::test_generate_is_idempotent":0.019,"Tests\\Feature\\AllocationTest::test_allocation_conserves_amount_with_tail_correction":0.076,"Tests\\Feature\\AllocationTest::test_allocation_follows_order_ratio":0.022,"Tests\\Feature\\AllocationTest::test_allocation_is_idempotent":0.028,"Tests\\Feature\\AllocationTest::test_allocation_rejected_without_actual_amount":0.021,"Tests\\Feature\\DebugGenerateTest::test_debug_generate":0.047,"Tests\\Feature\\ReconciliationTest::test_build_creates_reconciliation_items":0.019,"Tests\\Feature\\ReconciliationTest::test_build_filters_by_supplier":0.02,"Tests\\Feature\\ReconciliationTest::test_update_item_recalculates_diff_and_header":0.019,"Tests\\Feature\\ReconciliationTest::test_toggle_reconciled_flag":0.019,"Tests\\Feature\\ReconciliationTest::test_settle_creates_settlements_per_store":0.021,"Tests\\Feature\\StatementTest::test_generate_snapshots_payment_cycle":0.011,"Tests\\Feature\\StatementTest::test_statement_isolated_between_stores":0.016,"Tests\\Feature\\StatementTest::test_generate_excludes_cancelled_and_used_items":0.016,"Tests\\Feature\\MiniAuthTest::test_login_creates_user_and_issues_token":0.056,"Tests\\Feature\\MiniAuthTest::test_disabled_user_cannot_login":0.002,"Tests\\Feature\\MiniAuthTest::test_phone_binding_matches_store":0.005,"Tests\\Feature\\MiniAuthTest::test_phone_no_match_stays_pending":0.001,"Tests\\Feature\\MiniAuthTest::test_sys_token_cannot_access_mini":0.005,"Tests\\Feature\\MiniAuthTest::test_mini_token_cannot_access_admin_api":0.004,"Tests\\Feature\\ExportTest::test_export_xlsx_with_encoded_chinese_filename":0.063,"Tests\\Feature\\ExportTest::test_export_category_filters_to_vegetables":0.02,"Tests\\Feature\\ExportTest::test_export_pdf":0.015,"Tests\\Feature\\ExportTest::test_export_invalid_format_rejected":0.016,"Tests\\Feature\\ExportTest::test_export_requires_export_permission":0.016,"Tests\\Unit\\ExampleTest::test_that_true_is_true":0.001,"Tests\\Feature\\MiniAuthTest::test_login_fails_when_wechat_rejects_code":0.003,"Tests\\Feature\\CartTest::test_add_to_cart_succeeds":0.046,"Tests\\Feature\\CartTest::test_duplicate_add_merges_quantity":0.01,"Tests\\Feature\\CartTest::test_off_shelf_product_rejected":0.008,"Tests\\Feature\\CartTest::test_soft_deleted_product_rejected":0.006,"Tests\\Feature\\CartTest::test_product_without_level_price_rejected":0.006,"Tests\\Feature\\CartTest::test_unbound_user_rejected":0.01,"Tests\\Feature\\CartTest::test_store_without_level_rejected":0.006,"Tests\\Feature\\CartTest::test_list_with_prices_amounts_and_totals":0.011,"Tests\\Feature\\CartTest::test_list_marks_off_shelf_item_unbuyable":0.01,"Tests\\Feature\\CartTest::test_empty_cart_returns_empty_items":0.005,"Tests\\Feature\\CartTest::test_update_quantity":0.008,"Tests\\Feature\\CartTest::test_update_other_users_item_rejected":0.009,"Tests\\Feature\\CartTest::test_update_missing_item_rejected":0.008,"Tests\\Feature\\CartTest::test_invalid_quantity_rejected":0.006,"Tests\\Feature\\CartTest::test_delete_item":0.008,"Tests\\Feature\\CartTest::test_delete_other_users_item_rejected":0.01,"Tests\\Feature\\CartTest::test_clear_only_own_cart":0.015,"Tests\\Feature\\CartTest::test_unauthenticated_returns_401":0.002,"Tests\\Feature\\CartTest::test_cart_isolated_between_users":0.009,"Tests\\Feature\\DebugCartListTest::test_dump_cart_list_response":0.057,"Tests\\Feature\\ProductCostPriceTest::test_calc_actual_price_fixed_and_percent":0.001,"Tests\\Feature\\ProductCostPriceTest::test_calc_actual_price_edge_cases":0.001,"Tests\\Feature\\ProductCostPriceTest::test_admin_list_contains_cost_price_and_actual_price":0.01,"Tests\\Feature\\ProductCostPriceTest::test_mini_list_returns_actual_price_without_cost_price":0.007,"Tests\\Feature\\ProductCostPriceTest::test_create_product_with_cost_and_percent_pricing":0.009,"Tests\\Feature\\ProductCostPriceTest::test_create_percent_row_without_percent_fails":0.026,"Tests\\Feature\\ProductCostPriceTest::test_update_switches_fixed_to_percent":0.009,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_cost_only_notifies_percent_level_stores":0.01,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_percent_row_update":0.009,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_mixed_row_updates_cost_and_level":0.009,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_empty_row_rejected":0.006,"Tests\\Feature\\ProductCostPriceTest::test_price_matrix_includes_cost_and_percent_columns":0.007,"Tests\\Feature\\ProductCostPriceTest::test_mini_order_uses_actual_price_for_percent_row":0.008,"Tests\\Feature\\ProductCostPriceTest::test_mini_cart_uses_actual_price_for_percent_row":0.008,"Tests\\Feature\\PurchaseGenerateTest::test_generate_uses_min_actual_price_across_types":0.014,"Tests\\Feature\\ProductPriceTest::test_store_without_level_sees_products_without_price":0.006,"Tests\\Feature\\ProductPriceTest::test_unbound_user_can_browse_products_without_price_but_cannot_cart":0.009,"Tests\\Feature\\MiniAuthTest::test_login_issues_token_for_registered_user":0.009,"Tests\\Feature\\MiniAuthTest::test_login_rejects_unregistered_openid":0.002,"Tests\\Feature\\MiniAuthTest::test_register_binds_store_and_issues_token":0.009,"Tests\\Feature\\MiniAuthTest::test_register_rejects_unknown_store":0.002,"Tests\\Feature\\MiniAuthTest::test_register_rejects_duplicate_openid":0.002,"Tests\\Feature\\MiniStoreTest::test_info_returns_bound_store":0.005,"Tests\\Feature\\MiniStoreTest::test_update_info":0.005,"Tests\\Feature\\MiniStoreTest::test_update_info_ignores_readonly_fields":0.005,"Tests\\Feature\\MiniStoreTest::test_requires_bound_store":0.007,"Tests\\Feature\\MiniStoreTest::test_update_info_validates_length":0.005,"Tests\\Feature\\StoreOrderTest::test_container_update_recalculates_amounts":0.019,"Tests\\Feature\\StoreOrderTest::test_container_update_allowed_in_editable_statuses":0.016,"Tests\\Feature\\StoreOrderTest::test_container_update_rejected_in_other_statuses":0.018,"Tests\\Feature\\StoreOrderTest::test_container_update_heals_legacy_product_amount":0.019,"Tests\\Feature\\StoreOrderTest::test_container_update_validates_negative_numbers":0.019,"Tests\\Feature\\PurchaseGenerateTest::test_generate_without_accepted_orders_fails":0.011,"Tests\\Feature\\PurchaseGenerateTest::test_generate_with_selected_order_ids":0.021,"Tests\\Feature\\StoreOrderItemTest::test_place_order_snapshots_full_product_info":0.008,"Tests\\Feature\\StoreOrderItemTest::test_mini_detail_hides_cost_price":0.01,"Tests\\Feature\\StoreOrderItemTest::test_admin_detail_shows_cost_price_and_supplier":0.013,"Tests\\Feature\\StoreOrderItemTest::test_update_item_recalculates_order_totals":0.011,"Tests\\Feature\\StoreOrderItemTest::test_update_item_rejected_when_completed_or_cancelled":0.024,"Tests\\Feature\\StoreOrderItemTest::test_update_item_validates_negative_price":0.01,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_updates_snapshot_from_product":0.011,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_recalculates_percent_price_with_latest_cost":0.012,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_rejected_when_product_deleted":0.01,"Tests\\Feature\\StoreOrderItemTest::test_order_list_search_by_product_name":0.016,"Tests\\Feature\\StoreOrderTest::test_soft_delete_only_cancelled_orders":0.031,"Tests\\Feature\\StoreOrderTest::test_soft_delete_rejected_for_non_cancelled_orders":0.066,"Tests\\Feature\\StoreOrderTest::test_soft_delete_twice_rejected":0.012,"Tests\\Feature\\PurchaseEditTest::test_detail_returns_store_matrix":0.064,"Tests\\Feature\\PurchaseEditTest::test_update_cell_syncs_order_item_and_totals":0.021,"Tests\\Feature\\PurchaseEditTest::test_update_row_cost_syncs_all_order_items":0.018,"Tests\\Feature\\PurchaseEditTest::test_update_row_weight_distributed_by_quantity":0.032,"Tests\\Feature\\PurchaseEditTest::test_update_row_requires_at_least_one_field":0.017,"Tests\\Feature\\PurchaseEditTest::test_update_cell_rejects_negative_quantity":0.017,"Tests\\Feature\\PurchaseGenerateTest::test_generate_aggregates_order_items_into_header":0.018,"Tests\\Feature\\ReconciliationTest::test_build_excludes_unfinished_orders":0.017,"Tests\\Feature\\PurchaseEditTest::test_update_row_syncs_product_attributes":0.024,"Tests\\Feature\\PurchaseEditTest::test_update_row_syncs_to_product_catalog":0.019,"Tests\\Feature\\PurchaseEditTest::test_cell_detail_returns_items_with_order_info":0.02,"Tests\\Feature\\PurchaseEditTest::test_cell_detail_rejects_missing_params":0.018,"Tests\\Feature\\PurchaseEditTest::test_update_cell_weight_cascades_totals":0.019,"Tests\\Feature\\PurchaseEditTest::test_cell_sync_refreshes_snapshot_and_cascades":0.013,"Tests\\Feature\\PurchaseEditTest::test_edits_rejected_when_purchase_completed":0.018,"Tests\\Feature\\PurchaseEditTest::test_update_row_does_not_touch_manual_weight":0.018,"Tests\\Feature\\PurchaseEditTest::test_store_summary_returns_aggregated_items":0.019,"Tests\\Feature\\PurchaseEditTest::test_store_summary_aggregates_multiple_orders_with_weighted_price":0.018,"Tests\\Feature\\PurchaseEditTest::test_store_summary_rejects_missing_or_invalid_params":0.02,"Tests\\Feature\\ExportTest::test_export_invalid_type_rejected":0.016,"Tests\\Feature\\ExportTest::test_export_missing_purchase_rejected":0.006}} \ No newline at end of file diff --git a/app/Http/Controllers/Purchase/PurchaseOrderController.php b/app/Http/Controllers/Purchase/PurchaseOrderController.php index 289d10c..ab5de07 100644 --- a/app/Http/Controllers/Purchase/PurchaseOrderController.php +++ b/app/Http/Controllers/Purchase/PurchaseOrderController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers\Purchase; use App\Exceptions\RepositoryException; +use App\Exports\PurchaseOrderExport; use App\Http\Requests\Purchase\PurchaseCellUpdateRequest; use App\Http\Requests\Purchase\PurchaseRowUpdateRequest; use App\Models\ProductModel; @@ -15,11 +16,13 @@ use App\Services\PurchaseGenerateService; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; +use Maatwebsite\Excel\Facades\Excel; use Modules\AnnoRoute\Attribute\GetRoute; use Modules\AnnoRoute\Attribute\PostRoute; use Modules\AnnoRoute\Attribute\PutRoute; use Modules\AnnoRoute\Attribute\RequestAttribute; use Modules\Common\Http\Controllers\BaseController; +use Symfony\Component\HttpFoundation\Response; use Throwable; /** @@ -190,6 +193,27 @@ class PurchaseOrderController extends BaseController ); } + /** + * C2/C3 导出采购单(Excel 表格):?type=all 全品类 / category 仅蔬果分类 + */ + #[GetRoute(route: '/{id}/export', authorize: 'export', where: ['id' => '[0-9]+'])] + public function export(int $id, Request $request): Response + { + $purchase = PurchaseOrderModel::find($id); + if (empty($purchase)) { + throw new RepositoryException('采购单不存在'); + } + $type = (string) $request->query('type', 'all'); + if (! in_array($type, ['all', 'category'], true)) { + throw new RepositoryException('导出类型参数不正确(all 全品类 / category 蔬果分类)'); + } + + return Excel::download( + new PurchaseOrderExport($purchase, $type), + $purchase->purchase_no . '_采购单.xlsx', + ); + } + /** * 单元格下钻:采购单中某门店某商品的全部订货明细 */ diff --git a/tests/Feature/ExportTest.php b/tests/Feature/ExportTest.php index b4d8d38..be2e5a4 100644 --- a/tests/Feature/ExportTest.php +++ b/tests/Feature/ExportTest.php @@ -14,8 +14,8 @@ use App\Models\UserModel; use Maatwebsite\Excel\Facades\Excel; /** - * 采购单导出:xlsx Content-Type / 蔬果分类过滤 / PDF / format 非法拒绝 / 权限拦截 / - * 中文文件名 RFC 5987 编码 + * 采购单导出(仅 Excel 表格):xlsx Content-Type / 蔬果分类过滤 / type 非法拒绝 / + * 权限拦截 / 中文文件名 RFC 5987 编码 */ class ExportTest extends ProcurementTestCase { @@ -56,7 +56,7 @@ class ExportTest extends ProcurementTestCase $purchase = $this->buildPurchaseWithItems(); $this->actingAsSysUser(); - $response = $this->get("/purchase/order/{$purchase->id}/export?type=all&format=xlsx"); + $response = $this->get("/purchase/order/{$purchase->id}/export"); $response->assertOk(); $this->assertStringContainsString( @@ -78,7 +78,7 @@ class ExportTest extends ProcurementTestCase $this->actingAsSysUser(); Excel::fake(); - $this->get("/purchase/order/{$purchase->id}/export?type=category&format=xlsx")->assertOk(); + $this->get("/purchase/order/{$purchase->id}/export?type=category")->assertOk(); Excel::assertDownloaded( $purchase->purchase_no . '_采购单.xlsx', @@ -90,25 +90,22 @@ class ExportTest extends ProcurementTestCase ); } - /** PDF 导出:application/pdf */ - public function test_export_pdf(): void + /** type 参数非法 → 拒绝 */ + public function test_export_invalid_type_rejected(): void { $purchase = $this->buildPurchaseWithItems(); $this->actingAsSysUser(); - $response = $this->get("/purchase/order/{$purchase->id}/export?format=pdf"); - $response->assertOk(); - $this->assertStringContainsString('application/pdf', (string) $response->headers->get('Content-Type')); - $this->assertStringStartsWith('%PDF', (string) $response->getContent()); + $this->get("/purchase/order/{$purchase->id}/export?type=doc") + ->assertJsonPath('success', false); } - /** format 参数非法 → 拒绝 */ - public function test_export_invalid_format_rejected(): void + /** 采购单不存在 → 拒绝 */ + public function test_export_missing_purchase_rejected(): void { - $purchase = $this->buildPurchaseWithItems(); $this->actingAsSysUser(); - $this->get("/purchase/order/{$purchase->id}/export?format=doc") + $this->get('/purchase/order/99999/export') ->assertJsonPath('success', false); } @@ -119,7 +116,7 @@ class ExportTest extends ProcurementTestCase // 仅持有查询权限的用户 $this->actingAsSysUser(['purchase.order.query']); - $response = $this->get("/purchase/order/{$purchase->id}/export?format=xlsx"); + $response = $this->get("/purchase/order/{$purchase->id}/export"); $this->assertFalse($response->json('success'), '缺少权限点应被拦截'); } } diff --git a/web/api/purchase/order.ts b/web/api/purchase/order.ts index c23e64b..d6cddd3 100644 --- a/web/api/purchase/order.ts +++ b/web/api/purchase/order.ts @@ -1,8 +1,10 @@ import createAxios from '@/utils/request'; +import { downloadBlob } from '@/api/common/download.ts'; import type { IPurchaseCell, IPurchaseDetail, IPurchaseStoreSummary, + PurchaseExportType, } from '@/domain/iPurchaseOrder.ts'; /** 订单商品参数修改 */ @@ -74,3 +76,12 @@ export async function getPurchaseStoreSummary(purchaseId: number, storeId: numbe params: { store_id: storeId }, }); } + +/** C2/C3 导出采购单(Excel 表格):type=all 全品类 / category 仅蔬果分类 */ +export async function exportPurchase(id: number, type: PurchaseExportType = 'all') { + return downloadBlob( + `/purchase/order/${id}/export`, + { type }, + `采购单_${id}.xlsx`, + ); +} diff --git a/web/pages/purchase/order.tsx b/web/pages/purchase/order.tsx index a8eccc4..cb52553 100644 --- a/web/pages/purchase/order.tsx +++ b/web/pages/purchase/order.tsx @@ -3,6 +3,7 @@ import { Button, Descriptions, Drawer, + Dropdown, Empty, Form, Image, @@ -19,7 +20,7 @@ import { Tag, Typography, } from 'antd'; -import { CheckOutlined, EditOutlined } from '@ant-design/icons'; +import { CheckOutlined, DownloadOutlined, EditOutlined } from '@ant-design/icons'; import type { TableProps } from 'antd'; import XinTable from '@/components/XinTable'; import type { @@ -39,6 +40,7 @@ import type { import { PURCHASE_STATUS_MAP } from '@/domain/iPurchaseOrder.ts'; import { STORE_ORDER_STATUS_MAP } from '@/domain/iStoreOrder.ts'; import { + exportPurchase, getPurchaseCell, getPurchaseDetail, getPurchaseStoreSummary, @@ -485,7 +487,21 @@ const PurchaseOrderPage: React.FC = () => { const operateRender: XinTableProps['operateRender'] = (record) => [ + , + + exportPurchase(record.id!, 'all') }, + { key: 'category', label: '仅导出蔬果分类', onClick: () => exportPurchase(record.id!, 'category') }, + ], + }} + > + + + , ]; const tableProps: XinTableProps = {