diff --git a/.phpunit.result.cache b/.phpunit.result.cache index dab5692..9adf4d4 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},"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.015,"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.007,"Tests\\Feature\\StoreOrderTest::test_cancel_only_pending_orders":0.014,"Tests\\Feature\\StoreOrderTest::test_summarized_order_cannot_be_cancelled":0.009,"Tests\\Feature\\StoreOrderTest::test_order_data_isolated_between_stores":0.012,"Tests\\Feature\\PurchaseGenerateTest::test_generate_aggregates_orders_by_product":0.025,"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.022,"Tests\\Feature\\AllocationTest::test_allocation_conserves_amount_with_tail_correction":0.081,"Tests\\Feature\\AllocationTest::test_allocation_follows_order_ratio":0.021,"Tests\\Feature\\AllocationTest::test_allocation_is_idempotent":0.026,"Tests\\Feature\\AllocationTest::test_allocation_rejected_without_actual_amount":0.017,"Tests\\Feature\\DebugGenerateTest::test_debug_generate":0.047,"Tests\\Feature\\ReconciliationTest::test_build_creates_reconciliation_items":0.029,"Tests\\Feature\\ReconciliationTest::test_build_filters_by_supplier":0.032,"Tests\\Feature\\ReconciliationTest::test_update_item_recalculates_diff_and_header":0.043,"Tests\\Feature\\ReconciliationTest::test_toggle_reconciled_flag":0.039,"Tests\\Feature\\ReconciliationTest::test_settle_creates_settlements_per_store":0.031,"Tests\\Feature\\StatementTest::test_generate_snapshots_payment_cycle":0.012,"Tests\\Feature\\StatementTest::test_statement_isolated_between_stores":0.018,"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.054,"Tests\\Feature\\ExportTest::test_export_category_filters_to_vegetables":0.018,"Tests\\Feature\\ExportTest::test_export_pdf":1.303,"Tests\\Feature\\ExportTest::test_export_invalid_format_rejected":0.018,"Tests\\Feature\\ExportTest::test_export_requires_export_permission":0.018,"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.008,"Tests\\Feature\\CartTest::test_duplicate_add_merges_quantity":0.008,"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.007,"Tests\\Feature\\CartTest::test_store_without_level_rejected":0.006,"Tests\\Feature\\CartTest::test_list_with_prices_amounts_and_totals":0.01,"Tests\\Feature\\CartTest::test_list_marks_off_shelf_item_unbuyable":0.012,"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.009,"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.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.009,"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.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.012,"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.009,"Tests\\Feature\\ProductCostPriceTest::test_mini_order_uses_actual_price_for_percent_row":0.011,"Tests\\Feature\\ProductCostPriceTest::test_mini_cart_uses_actual_price_for_percent_row":0.013,"Tests\\Feature\\PurchaseGenerateTest::test_generate_uses_min_actual_price_across_types":0.015,"Tests\\Feature\\ProductPriceTest::test_store_without_level_sees_products_without_price":0.01,"Tests\\Feature\\ProductPriceTest::test_unbound_user_can_browse_products_without_price_but_cannot_cart":0.012,"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.01,"Tests\\Feature\\MiniAuthTest::test_register_rejects_unknown_store":0.002,"Tests\\Feature\\MiniAuthTest::test_register_rejects_duplicate_openid":0.003,"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.013,"Tests\\Feature\\StoreOrderTest::test_container_update_recalculates_amounts":0.041,"Tests\\Feature\\StoreOrderTest::test_container_update_allowed_in_editable_statuses":0.019,"Tests\\Feature\\StoreOrderTest::test_container_update_rejected_in_other_statuses":0.02,"Tests\\Feature\\StoreOrderTest::test_container_update_heals_legacy_product_amount":0.015,"Tests\\Feature\\StoreOrderTest::test_container_update_validates_negative_numbers":0.018,"Tests\\Feature\\PurchaseGenerateTest::test_generate_without_accepted_orders_fails":0.014,"Tests\\Feature\\PurchaseGenerateTest::test_generate_with_selected_order_ids":0.025,"Tests\\Feature\\StoreOrderItemTest::test_place_order_snapshots_full_product_info":0.048,"Tests\\Feature\\StoreOrderItemTest::test_mini_detail_hides_cost_price":0.011,"Tests\\Feature\\StoreOrderItemTest::test_admin_detail_shows_cost_price_and_supplier":0.015,"Tests\\Feature\\StoreOrderItemTest::test_update_item_recalculates_order_totals":0.014,"Tests\\Feature\\StoreOrderItemTest::test_update_item_rejected_when_completed_or_cancelled":0.016,"Tests\\Feature\\StoreOrderItemTest::test_update_item_validates_negative_price":0.012,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_updates_snapshot_from_product":0.013,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_recalculates_percent_price_with_latest_cost":0.013,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_rejected_when_product_deleted":0.012,"Tests\\Feature\\StoreOrderItemTest::test_order_list_search_by_product_name":0.017}} \ 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},"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.018,"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.013,"Tests\\Feature\\StoreOrderTest::test_place_order_snapshots_level_price_and_recalculates":0.008,"Tests\\Feature\\StoreOrderTest::test_client_supplied_amount_is_ignored":0.007,"Tests\\Feature\\StoreOrderTest::test_product_without_level_price_rejected":0.01,"Tests\\Feature\\StoreOrderTest::test_cancel_only_pending_orders":0.016,"Tests\\Feature\\StoreOrderTest::test_summarized_order_cannot_be_cancelled":0.014,"Tests\\Feature\\StoreOrderTest::test_order_data_isolated_between_stores":0.011,"Tests\\Feature\\PurchaseGenerateTest::test_generate_aggregates_orders_by_product":0.028,"Tests\\Feature\\PurchaseGenerateTest::test_generate_writes_back_order_status":0.033,"Tests\\Feature\\PurchaseGenerateTest::test_generate_without_pending_orders_fails":0.006,"Tests\\Feature\\PurchaseGenerateTest::test_generate_is_idempotent":0.03,"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.045,"Tests\\Feature\\ReconciliationTest::test_build_filters_by_supplier":0.032,"Tests\\Feature\\ReconciliationTest::test_update_item_recalculates_diff_and_header":0.035,"Tests\\Feature\\ReconciliationTest::test_toggle_reconciled_flag":0.034,"Tests\\Feature\\ReconciliationTest::test_settle_creates_settlements_per_store":0.043,"Tests\\Feature\\StatementTest::test_generate_snapshots_payment_cycle":0.016,"Tests\\Feature\\StatementTest::test_statement_isolated_between_stores":0.026,"Tests\\Feature\\StatementTest::test_generate_excludes_cancelled_and_used_items":0.019,"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.003,"Tests\\Feature\\ExportTest::test_export_xlsx_with_encoded_chinese_filename":0.069,"Tests\\Feature\\ExportTest::test_export_category_filters_to_vegetables":0.019,"Tests\\Feature\\ExportTest::test_export_pdf":1.302,"Tests\\Feature\\ExportTest::test_export_invalid_format_rejected":0.019,"Tests\\Feature\\ExportTest::test_export_requires_export_permission":0.019,"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.009,"Tests\\Feature\\CartTest::test_duplicate_add_merges_quantity":0.009,"Tests\\Feature\\CartTest::test_off_shelf_product_rejected":0.007,"Tests\\Feature\\CartTest::test_soft_deleted_product_rejected":0.006,"Tests\\Feature\\CartTest::test_product_without_level_price_rejected":0.007,"Tests\\Feature\\CartTest::test_unbound_user_rejected":0.007,"Tests\\Feature\\CartTest::test_store_without_level_rejected":0.006,"Tests\\Feature\\CartTest::test_list_with_prices_amounts_and_totals":0.01,"Tests\\Feature\\CartTest::test_list_marks_off_shelf_item_unbuyable":0.014,"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.01,"Tests\\Feature\\CartTest::test_update_missing_item_rejected":0.01,"Tests\\Feature\\CartTest::test_invalid_quantity_rejected":0.007,"Tests\\Feature\\CartTest::test_delete_item":0.009,"Tests\\Feature\\CartTest::test_delete_other_users_item_rejected":0.011,"Tests\\Feature\\CartTest::test_clear_only_own_cart":0.017,"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.009,"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.01,"Tests\\Feature\\ProductCostPriceTest::test_create_percent_row_without_percent_fails":0.01,"Tests\\Feature\\ProductCostPriceTest::test_update_switches_fixed_to_percent":0.011,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_cost_only_notifies_percent_level_stores":0.02,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_percent_row_update":0.013,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_mixed_row_updates_cost_and_level":0.016,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_empty_row_rejected":0.007,"Tests\\Feature\\ProductCostPriceTest::test_price_matrix_includes_cost_and_percent_columns":0.01,"Tests\\Feature\\ProductCostPriceTest::test_mini_order_uses_actual_price_for_percent_row":0.009,"Tests\\Feature\\ProductCostPriceTest::test_mini_cart_uses_actual_price_for_percent_row":0.014,"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.009,"Tests\\Feature\\ProductPriceTest::test_unbound_user_can_browse_products_without_price_but_cannot_cart":0.014,"Tests\\Feature\\MiniAuthTest::test_login_issues_token_for_registered_user":0.022,"Tests\\Feature\\MiniAuthTest::test_login_rejects_unregistered_openid":0.002,"Tests\\Feature\\MiniAuthTest::test_register_binds_store_and_issues_token":0.039,"Tests\\Feature\\MiniAuthTest::test_register_rejects_unknown_store":0.005,"Tests\\Feature\\MiniAuthTest::test_register_rejects_duplicate_openid":0.003,"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.006,"Tests\\Feature\\MiniStoreTest::test_update_info_validates_length":0.007,"Tests\\Feature\\StoreOrderTest::test_container_update_recalculates_amounts":0.016,"Tests\\Feature\\StoreOrderTest::test_container_update_allowed_in_editable_statuses":0.018,"Tests\\Feature\\StoreOrderTest::test_container_update_rejected_in_other_statuses":0.019,"Tests\\Feature\\StoreOrderTest::test_container_update_heals_legacy_product_amount":0.014,"Tests\\Feature\\StoreOrderTest::test_container_update_validates_negative_numbers":0.012,"Tests\\Feature\\PurchaseGenerateTest::test_generate_without_accepted_orders_fails":0.016,"Tests\\Feature\\PurchaseGenerateTest::test_generate_with_selected_order_ids":0.05,"Tests\\Feature\\StoreOrderItemTest::test_place_order_snapshots_full_product_info":0.008,"Tests\\Feature\\StoreOrderItemTest::test_mini_detail_hides_cost_price":0.015,"Tests\\Feature\\StoreOrderItemTest::test_admin_detail_shows_cost_price_and_supplier":0.017,"Tests\\Feature\\StoreOrderItemTest::test_update_item_recalculates_order_totals":0.021,"Tests\\Feature\\StoreOrderItemTest::test_update_item_rejected_when_completed_or_cancelled":0.015,"Tests\\Feature\\StoreOrderItemTest::test_update_item_validates_negative_price":0.011,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_updates_snapshot_from_product":0.014,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_recalculates_percent_price_with_latest_cost":0.014,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_rejected_when_product_deleted":0.014,"Tests\\Feature\\StoreOrderItemTest::test_order_list_search_by_product_name":0.017,"Tests\\Feature\\StoreOrderTest::test_soft_delete_only_cancelled_orders":0.023,"Tests\\Feature\\StoreOrderTest::test_soft_delete_rejected_for_non_cancelled_orders":0.042,"Tests\\Feature\\StoreOrderTest::test_soft_delete_twice_rejected":0.014}} \ No newline at end of file diff --git a/app/Http/Controllers/Order/StoreOrderController.php b/app/Http/Controllers/Order/StoreOrderController.php index fae22ec..38961d2 100644 --- a/app/Http/Controllers/Order/StoreOrderController.php +++ b/app/Http/Controllers/Order/StoreOrderController.php @@ -12,6 +12,7 @@ use App\Models\UserModel; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; +use Modules\AnnoRoute\Attribute\DeleteRoute; use Modules\AnnoRoute\Attribute\GetRoute; use Modules\AnnoRoute\Attribute\PutRoute; use Modules\AnnoRoute\Attribute\RequestAttribute; @@ -400,6 +401,27 @@ class StoreOrderController extends BaseController return $this->success(['success' => $orders->count()]); } + /** + * 删除订单(软删除):仅已取消订单允许删除;删除后后台列表/详情、小程序端均不可见 + */ + #[DeleteRoute(route: '/{id}', authorize: 'delete', where: ['id' => '[0-9]+'])] + public function delete(int $id): JsonResponse + { + $order = StoreOrderModel::find($id); + if (empty($order)) { + throw new RepositoryException('订单不存在'); + } + if ($order->status !== StoreOrderModel::STATUS_CANCELLED) { + throw new RepositoryException( + '订单当前状态为「' . (self::STATUS_NAMES[$order->status] ?? $order->status) . '」,仅已取消订单可删除' + ); + } + + $order->delete(); + + return $this->success(); + } + /** * 状态流转合法路径(与迁移状态定义一致): * 待接单→已接单/已取消;采购中→配送中/已完成;配送中→已完成 diff --git a/app/Models/StoreOrderModel.php b/app/Models/StoreOrderModel.php index 7e246da..2bdbcfc 100644 --- a/app/Models/StoreOrderModel.php +++ b/app/Models/StoreOrderModel.php @@ -6,13 +6,14 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Eloquent\SoftDeletes; /** - * 门店订单模型(小程序下单,快照等级价) + * 门店订单模型(小程序下单,快照等级价;软删除,仅已取消订单可由后台删除) */ class StoreOrderModel extends Model { - use HasFactory; + use HasFactory, SoftDeletes; /** 状态:待接单(可被采购单生成归集、可取消) */ public const int STATUS_PENDING = 0; diff --git a/app/Services/PurchaseAllocateService.php b/app/Services/PurchaseAllocateService.php index b007bc5..73047b4 100644 --- a/app/Services/PurchaseAllocateService.php +++ b/app/Services/PurchaseAllocateService.php @@ -41,6 +41,7 @@ class PurchaseAllocateService $orderItems = StoreOrderItemModel::query() ->join('store_order', 'store_order.id', '=', 'store_order_item.order_id') ->where('store_order.purchase_id', $purchase->id) + ->whereNull('store_order.deleted_at') ->select('store_order_item.*') ->get() ->groupBy('product_id'); diff --git a/app/Services/StatementGenerateService.php b/app/Services/StatementGenerateService.php index 05a5435..a20d268 100644 --- a/app/Services/StatementGenerateService.php +++ b/app/Services/StatementGenerateService.php @@ -39,6 +39,7 @@ class StatementGenerateService ->whereDate('store_order.order_date', '>=', $periodStart) ->whereDate('store_order.order_date', '<=', $periodEnd) ->where('store_order.status', '<>', StoreOrderModel::STATUS_CANCELLED) + ->whereNull('store_order.deleted_at') ->select('store_order_item.*') ->get(); diff --git a/database/migrations/2026_07_23_030610_create_store_order_table.php b/database/migrations/2026_07_23_030610_create_store_order_table.php index 1749edf..2c22930 100644 --- a/database/migrations/2026_07_23_030610_create_store_order_table.php +++ b/database/migrations/2026_07_23_030610_create_store_order_table.php @@ -30,6 +30,7 @@ return new class extends Migration $table->decimal('tray_num', 10, 2)->default(0)->comment('周转托盘数量'); $table->integer('status')->default(0)->comment('订单状态(0待接单 1已接单 2采购中 3配送中 4已完成 9已取消)'); $table->string('remark', 255)->default('')->comment('订单备注'); + $table->softDeletes(); $table->timestamps(); $table->index(['store_id', 'order_date'], 'store_order_store_date_index'); $table->index(['status'], 'store_order_status_index'); diff --git a/database/seeders/PermissionSeeder.php b/database/seeders/PermissionSeeder.php index f357dff..8452bc8 100644 --- a/database/seeders/PermissionSeeder.php +++ b/database/seeders/PermissionSeeder.php @@ -173,6 +173,7 @@ class PermissionSeeder extends Seeder 'children' => [ ['type' => 'rule', 'key' => 'order.store.query', 'name' => '查询'], ['type' => 'rule', 'key' => 'order.store.update', 'name' => '状态流转'], + ['type' => 'rule', 'key' => 'order.store.delete', 'name' => '删除(仅已取消订单)'], ], ], ], diff --git a/tests/Feature/StoreOrderTest.php b/tests/Feature/StoreOrderTest.php index 5e8f3ef..18d6602 100644 --- a/tests/Feature/StoreOrderTest.php +++ b/tests/Feature/StoreOrderTest.php @@ -250,4 +250,57 @@ class StoreOrderTest extends ProcurementTestCase ->assertJsonPath('success', false) ->assertJsonPath('msg', '周转框数量不能小于 0'); } + + /** 软删除:仅已取消订单可由后台删除,删除后后台/小程序端均不可见 */ + public function test_soft_delete_only_cancelled_orders(): void + { + $order = $this->makeOrderWithStatus(StoreOrderModel::STATUS_CANCELLED); + + // 删除前小程序端可见 + $this->getJson('/mini/order')->assertOk()->assertJsonPath('data.total', 1); + + $this->actingAsSysUser(); + $this->deleteJson("/order/store/{$order->id}")->assertOk()->assertJsonPath('success', true); + + $this->assertNotNull($order->fresh()->deleted_at, '软删除应写入 deleted_at'); + $this->assertNull(StoreOrderModel::find($order->id), '默认查询不可见软删除订单'); + $this->assertNotNull(StoreOrderModel::withTrashed()->find($order->id), '数据仍保留在库中'); + + // 后台列表/详情不可见 + $this->getJson('/order/store')->assertOk()->assertJsonPath('data.total', 0); + $this->getJson("/order/store/{$order->id}")->assertJsonPath('success', false); + + // 小程序端历史订单同样不可见 + $this->actingAsMiniUser(UserModel::where('store_id', $order->store_id)->first()); + $this->getJson('/mini/order')->assertOk()->assertJsonPath('data.total', 0); + $this->getJson("/mini/order/{$order->id}")->assertJsonPath('success', false); + } + + /** 软删除:非已取消订单拒绝删除 */ + public function test_soft_delete_rejected_for_non_cancelled_orders(): void + { + foreach ([ + StoreOrderModel::STATUS_PENDING, + StoreOrderModel::STATUS_SUMMARIZED, + StoreOrderModel::STATUS_DELIVERING, + StoreOrderModel::STATUS_DISTRIBUTION, + StoreOrderModel::STATUS_COMPLETED, + ] as $status) { + $order = $this->makeOrderWithStatus($status); + $this->actingAsSysUser(); + $this->deleteJson("/order/store/{$order->id}") + ->assertOk()->assertJsonPath('success', false); + $this->assertNull($order->fresh()->deleted_at, '非已取消订单不得删除'); + } + } + + /** 软删除:重复删除返回订单不存在 */ + public function test_soft_delete_twice_rejected(): void + { + $order = $this->makeOrderWithStatus(StoreOrderModel::STATUS_CANCELLED); + $this->actingAsSysUser(); + + $this->deleteJson("/order/store/{$order->id}")->assertOk()->assertJsonPath('success', true); + $this->deleteJson("/order/store/{$order->id}")->assertJsonPath('success', false); + } } diff --git a/web/api/order/store.ts b/web/api/order/store.ts index dfec2ca..562b1c2 100644 --- a/web/api/order/store.ts +++ b/web/api/order/store.ts @@ -61,3 +61,11 @@ export async function syncOrderItem(itemId: number) { method: 'put', }); } + +/** 删除订单(软删除,仅已取消订单允许) */ +export async function deleteStoreOrder(id: number) { + return createAxios({ + url: `/order/store/${id}`, + method: 'delete', + }); +} diff --git a/web/pages/order/store.tsx b/web/pages/order/store.tsx index ab48ae1..f3d80c2 100644 --- a/web/pages/order/store.tsx +++ b/web/pages/order/store.tsx @@ -35,6 +35,7 @@ import { batchUpdateOrderStatus, updateOrderItem, syncOrderItem, + deleteStoreOrder, } from '@/api/order/store.ts'; import { generatePurchase } from '@/api/purchase/order.ts'; import { getStoreOptions } from '@/api/customer/store.ts'; @@ -42,7 +43,7 @@ import { getSupplierOptions } from '@/api/customer/supplier.ts'; import type IStore from '@/domain/iStore.ts'; import type ISupplier from '@/domain/iSupplier.ts'; import AuthButton from '@/components/AuthButton'; -import { EditOutlined, SettingOutlined, SyncOutlined, UnorderedListOutlined } from '@ant-design/icons'; +import { DeleteOutlined, EditOutlined, SettingOutlined, SyncOutlined, UnorderedListOutlined } from '@ant-design/icons'; import TextArea from "antd/es/input/TextArea"; const { Title, Text } = Typography; @@ -224,6 +225,14 @@ const StoreOrderPage: React.FC = () => { await tableRef.current?.reload(); }; + /** 删除订单(软删除,仅已取消订单可删;删除后后台/小程序端均不可见) */ + const handleDelete = async (id: number) => { + await deleteStoreOrder(id); + message.success('订单已删除'); + setDetailOpen(false); + await tableRef.current?.reload(); + }; + // 弹窗内实时预览:附加金额 = 框×单价 + 托盘×单价;订单总金额 = 商品金额 + 附加金额 const watchBoxNum = Number(Form.useWatch('box_num', containerForm) ?? 0); const watchTrayNum = Number(Form.useWatch('tray_num', containerForm) ?? 0); @@ -338,7 +347,7 @@ const StoreOrderPage: React.FC = () => { align: 'center', }, { - title: '订单信息', + title: '附加信息', hideInForm: true, dataIndex: 'status', hideInSearch: true, @@ -361,7 +370,7 @@ const StoreOrderPage: React.FC = () => { ) }, { - title: '附加信息', + title: '订单金额', hideInForm: true, dataIndex: 'box_num', hideInSearch: true, @@ -455,6 +464,17 @@ const StoreOrderPage: React.FC = () => { )} + { record.status === 9 && ( + + handleDelete(record.id!)} + > + + + + )} ) : null }