From cd35213ae67b0ba074a1beb21ba7b04b04e7e05e Mon Sep 17 00:00:00 2001 From: liu <2302563948@qq.com> Date: Sat, 15 Aug 2026 00:14:19 +0800 Subject: [PATCH] =?UTF-8?q?=E5=9B=9E=E7=AD=90=E5=AF=BC=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .phpunit.result.cache | 2 +- app/Exports/ContainerReturnExport.php | 145 ++++++++++++++++++ .../Recon/ContainerReturnController.php | 63 ++++++++ database/seeders/PermissionSeeder.php | 1 + tests/Feature/ContainerReturnTest.php | 140 +++++++++++++++++ web/api/recon/containerReturn.ts | 19 +++ web/pages/recon/container-return.tsx | 92 ++++++++++- 7 files changed, 459 insertions(+), 3 deletions(-) create mode 100644 app/Exports/ContainerReturnExport.php create mode 100644 web/api/recon/containerReturn.ts diff --git a/.phpunit.result.cache b/.phpunit.result.cache index 36920d8..8ff5fba 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,"Tests\\Feature\\PurchaseEditTest::test_cell_detail_returns_items_with_order_info":8,"Tests\\Feature\\ProductCategoryTest::test_create_product_with_leaf_category_ok":7,"Tests\\Feature\\BillExportTest::test_export_filters_by_root_category":8,"Tests\\Feature\\BillExportTest::test_export_requires_export_permission":8,"Tests\\Feature\\DebugPermissionTest::test_debug_permission":8,"Tests\\Feature\\BillPaymentTest::test_order_status_progresses_via_business_chain":7,"Tests\\Feature\\BillPaymentTest::test_payment_audit_reject_does_not_accumulate":7,"Tests\\Feature\\BillPaymentTest::test_payment_detail_returns_voucher_urls":8,"Tests\\Feature\\ContainerReturnTest::test_press_containers_adds_amount_and_writes_record":7,"Tests\\Feature\\ContainerReturnTest::test_return_containers_deducts_amount":7,"Tests\\Feature\\ContainerReturnTest::test_mixed_sign_containers":7,"Tests\\Feature\\ContainerReturnTest::test_zero_containers_writes_no_record":7,"Tests\\Feature\\StoreOrderTest::test_order_list_outputs_status_name_and_item_preview":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.006,"Tests\\Feature\\ProductPriceTest::test_store_without_level_is_rejected":0.007,"Tests\\Feature\\ProductPriceTest::test_batch_price_updates_and_notifies_affected_stores":0.021,"Tests\\Feature\\ProductPriceTest::test_price_matrix_structure":0.01,"Tests\\Feature\\StoreOrderTest::test_place_order_snapshots_level_price_and_recalculates":0.018,"Tests\\Feature\\StoreOrderTest::test_client_supplied_amount_is_ignored":0.018,"Tests\\Feature\\StoreOrderTest::test_product_without_level_price_rejected":0.008,"Tests\\Feature\\StoreOrderTest::test_cancel_only_pending_orders":0.01,"Tests\\Feature\\StoreOrderTest::test_summarized_order_cannot_be_cancelled":0.032,"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.028,"Tests\\Feature\\PurchaseGenerateTest::test_generate_without_pending_orders_fails":0.006,"Tests\\Feature\\PurchaseGenerateTest::test_generate_is_idempotent":0.031,"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.022,"Tests\\Feature\\ReconciliationTest::test_update_item_recalculates_diff_and_header":0.019,"Tests\\Feature\\ReconciliationTest::test_toggle_reconciled_flag":0.02,"Tests\\Feature\\ReconciliationTest::test_settle_creates_settlements_per_store":0.036,"Tests\\Feature\\StatementTest::test_generate_snapshots_payment_cycle":0.011,"Tests\\Feature\\StatementTest::test_statement_isolated_between_stores":0.013,"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.003,"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.006,"Tests\\Feature\\MiniAuthTest::test_mini_token_cannot_access_admin_api":0.004,"Tests\\Feature\\ExportTest::test_export_xlsx_with_encoded_chinese_filename":0.037,"Tests\\Feature\\ExportTest::test_export_category_filters_to_vegetables":0.017,"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.008,"Tests\\Feature\\CartTest::test_duplicate_add_merges_quantity":0.01,"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.007,"Tests\\Feature\\CartTest::test_unbound_user_rejected":0.007,"Tests\\Feature\\CartTest::test_store_without_level_rejected":0.007,"Tests\\Feature\\CartTest::test_list_with_prices_amounts_and_totals":0.011,"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.009,"Tests\\Feature\\CartTest::test_update_other_users_item_rejected":0.01,"Tests\\Feature\\CartTest::test_update_missing_item_rejected":0.011,"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.011,"Tests\\Feature\\CartTest::test_clear_only_own_cart":0.013,"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.006,"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.01,"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.009,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_empty_row_rejected":0.006,"Tests\\Feature\\ProductCostPriceTest::test_price_matrix_includes_cost_and_percent_columns":0.011,"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.01,"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.005,"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.003,"Tests\\Feature\\MiniStoreTest::test_info_returns_bound_store":0.004,"Tests\\Feature\\MiniStoreTest::test_update_info":0.005,"Tests\\Feature\\MiniStoreTest::test_update_info_ignores_readonly_fields":0.008,"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.012,"Tests\\Feature\\StoreOrderTest::test_container_update_allowed_in_editable_statuses":0.022,"Tests\\Feature\\StoreOrderTest::test_container_update_rejected_in_other_statuses":0.019,"Tests\\Feature\\StoreOrderTest::test_container_update_heals_legacy_product_amount":0.02,"Tests\\Feature\\StoreOrderTest::test_container_update_validates_negative_numbers":0.014,"Tests\\Feature\\PurchaseGenerateTest::test_generate_without_accepted_orders_fails":0.018,"Tests\\Feature\\PurchaseGenerateTest::test_generate_with_selected_order_ids":0.037,"Tests\\Feature\\StoreOrderItemTest::test_place_order_snapshots_full_product_info":0.013,"Tests\\Feature\\StoreOrderItemTest::test_mini_detail_hides_cost_price":0.017,"Tests\\Feature\\StoreOrderItemTest::test_admin_detail_shows_cost_price_and_supplier":0.02,"Tests\\Feature\\StoreOrderItemTest::test_update_item_recalculates_order_totals":0.018,"Tests\\Feature\\StoreOrderItemTest::test_update_item_rejected_when_completed_or_cancelled":0.017,"Tests\\Feature\\StoreOrderItemTest::test_update_item_validates_negative_price":0.016,"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.016,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_rejected_when_product_deleted":0.019,"Tests\\Feature\\StoreOrderItemTest::test_order_list_search_by_product_name":0.026,"Tests\\Feature\\StoreOrderTest::test_soft_delete_only_cancelled_orders":0.021,"Tests\\Feature\\StoreOrderTest::test_soft_delete_rejected_for_non_cancelled_orders":0.056,"Tests\\Feature\\StoreOrderTest::test_soft_delete_twice_rejected":0.016,"Tests\\Feature\\PurchaseEditTest::test_detail_returns_store_matrix":0.022,"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.019,"Tests\\Feature\\PurchaseEditTest::test_update_row_weight_distributed_by_quantity":0.032,"Tests\\Feature\\PurchaseEditTest::test_update_row_requires_at_least_one_field":0.019,"Tests\\Feature\\PurchaseEditTest::test_update_cell_rejects_negative_quantity":0.02,"Tests\\Feature\\PurchaseGenerateTest::test_generate_aggregates_order_items_into_header":0.043,"Tests\\Feature\\ReconciliationTest::test_build_excludes_unfinished_orders":0.016,"Tests\\Feature\\PurchaseEditTest::test_update_row_syncs_product_attributes":0.022,"Tests\\Feature\\PurchaseEditTest::test_update_row_syncs_to_product_catalog":0.023,"Tests\\Feature\\PurchaseEditTest::test_cell_detail_returns_items_with_order_info":0.038,"Tests\\Feature\\PurchaseEditTest::test_cell_detail_rejects_missing_params":0.029,"Tests\\Feature\\PurchaseEditTest::test_update_cell_weight_cascades_totals":0.03,"Tests\\Feature\\PurchaseEditTest::test_cell_sync_refreshes_snapshot_and_cascades":0.024,"Tests\\Feature\\PurchaseEditTest::test_edits_rejected_when_purchase_completed":0.034,"Tests\\Feature\\PurchaseEditTest::test_update_row_does_not_touch_manual_weight":0.019,"Tests\\Feature\\PurchaseEditTest::test_store_summary_returns_aggregated_items":0.024,"Tests\\Feature\\PurchaseEditTest::test_store_summary_aggregates_multiple_orders_with_weighted_price":0.019,"Tests\\Feature\\PurchaseEditTest::test_store_summary_rejects_missing_or_invalid_params":0.028,"Tests\\Feature\\ExportTest::test_export_invalid_type_rejected":0.016,"Tests\\Feature\\ExportTest::test_export_missing_purchase_rejected":0.006,"Tests\\Feature\\PurchaseEditTest::test_detail_returns_container_summary":0.022,"Tests\\Feature\\PurchaseEditTest::test_update_container_syncs_order_amounts":0.018,"Tests\\Feature\\PurchaseEditTest::test_update_container_rejected_when_purchase_completed":0.018,"Tests\\Feature\\PurchaseEditTest::test_update_container_rejects_incomplete_or_foreign_orders":0.026,"Tests\\Feature\\PurchaseEditTest::test_update_container_validates_negative_and_duplicate_orders":0.018,"Tests\\Feature\\ProductCategoryTest::test_create_child_category_under_top_level":0.006,"Tests\\Feature\\ProductCategoryTest::test_create_category_under_second_level_rejected":0.019,"Tests\\Feature\\ProductCategoryTest::test_update_second_level_move_to_another_top":0.007,"Tests\\Feature\\ProductCategoryTest::test_update_parent_with_children_cannot_move_under_category":0.013,"Tests\\Feature\\ProductCategoryTest::test_update_with_missing_parent_rejected":0.009,"Tests\\Feature\\ProductCategoryTest::test_create_product_with_parent_category_rejected":0.007,"Tests\\Feature\\ProductCategoryTest::test_create_product_with_leaf_category_ok":0.007,"Tests\\Feature\\ProductCategoryTest::test_mini_categories_returns_all_enabled_categories":0.005,"Tests\\Feature\\MiniBillTest::test_bill_list_derives_pay_state_and_settlement_date":0.008,"Tests\\Feature\\MiniBillTest::test_bill_list_summary_aggregates_unpaid":0.007,"Tests\\Feature\\MiniBillTest::test_bill_list_payable_filter":0.007,"Tests\\Feature\\MiniBillTest::test_bill_list_filters_by_bill_date_range":0.012,"Tests\\Feature\\MiniBillTest::test_bill_data_isolated_between_stores":0.008,"Tests\\Feature\\MiniBillTest::test_bill_detail_outputs_bill_items_and_orders":0.009,"Tests\\Feature\\StoreOrderTest::test_order_list_outputs_status_name_and_item_preview":0.012,"Tests\\Feature\\StoreOrderTest::test_order_list_filters_by_order_date_range":0.018,"Tests\\Feature\\StoreOrderTest::test_order_list_rejects_invalid_filters":0.009,"Tests\\Feature\\BillExportTest::test_export_merges_items_across_bills":0.057,"Tests\\Feature\\BillExportTest::test_export_filters_by_root_category":0.012,"Tests\\Feature\\BillExportTest::test_export_empty_category_rejected":0.028,"Tests\\Feature\\BillExportTest::test_export_requires_export_permission":0.008,"Tests\\Feature\\BillExportTest::test_export_invalid_ids_rejected":0.009,"Tests\\Feature\\DebugPermissionTest::test_debug_permission":0.098,"Tests\\Feature\\BillExportTest::test_mini_export_scoped_to_bound_store":0.014,"Tests\\Feature\\BillExportTest::test_mini_export_requires_store_binding":0.006,"Tests\\Feature\\MiniNoticeTest::test_notice_list_includes_personal_and_broadcast":0.006,"Tests\\Feature\\MiniNoticeTest::test_broadcast_read_creates_personal_copy":0.008,"Tests\\Feature\\MiniNoticeTest::test_personal_notice_read_marks_directly":0.005,"Tests\\Feature\\MiniNoticeTest::test_other_users_notice_not_found":0.006,"Tests\\Feature\\StoreOrderTest::test_manual_status_flow_only_accept_and_cancel":0.02,"Tests\\Feature\\StoreOrderTest::test_manual_status_flow_rejects_delivery_and_completion":0.037,"Tests\\Feature\\StoreOrderTest::test_batch_status_only_accept_and_cancel":0.031,"Tests\\Feature\\BillPaymentTest::test_order_status_progresses_via_business_chain":0.023,"Tests\\Feature\\BillPaymentTest::test_manual_pay_accumulates_store_total_purchase_amount":0.027,"Tests\\Feature\\BillPaymentTest::test_payment_audit_pass_accumulates_store_total_purchase_amount":0.012,"Tests\\Feature\\BillPaymentTest::test_payment_audit_reject_does_not_accumulate":0.008,"Tests\\Feature\\MiniProductTest::test_product_detail_guest_sees_null_price":0.008,"Tests\\Feature\\MiniProductTest::test_product_detail_store_user_sees_level_price":0.006,"Tests\\Feature\\MiniProductTest::test_product_detail_percent_price_calculated_from_cost":0.006,"Tests\\Feature\\MiniProductTest::test_product_detail_off_shelf_or_missing_rejected":0.006,"Tests\\Feature\\MiniProductTest::test_product_list_store_user_sees_level_price":0.006,"Tests\\Feature\\MiniProductTest::test_product_list_guest_sees_null_price":0.005,"Tests\\Feature\\BillPaymentTest::test_admin_order_list_and_detail_show_bill_info":0.031,"Tests\\Feature\\BillPaymentTest::test_payment_detail_returns_voucher_urls":0.014,"Tests\\Feature\\StoreOrderTest::test_order_list_preview_includes_image_spec_and_unit":0.019,"Tests\\Feature\\ContainerReturnTest::test_press_containers_adds_amount_and_writes_record":0.021,"Tests\\Feature\\ContainerReturnTest::test_return_containers_deducts_amount":0.02,"Tests\\Feature\\ContainerReturnTest::test_mixed_sign_containers":0.018,"Tests\\Feature\\ContainerReturnTest::test_zero_containers_writes_no_record":0.018,"Tests\\Feature\\ContainerReturnTest::test_non_integer_containers_rejected":0.008,"Tests\\Feature\\ContainerReturnTest::test_manual_create_and_delete_routes_removed":0.006}} \ 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,"Tests\\Feature\\PurchaseEditTest::test_cell_detail_returns_items_with_order_info":8,"Tests\\Feature\\ProductCategoryTest::test_create_product_with_leaf_category_ok":7,"Tests\\Feature\\BillExportTest::test_export_filters_by_root_category":8,"Tests\\Feature\\BillExportTest::test_export_requires_export_permission":8,"Tests\\Feature\\DebugPermissionTest::test_debug_permission":8,"Tests\\Feature\\BillPaymentTest::test_order_status_progresses_via_business_chain":7,"Tests\\Feature\\BillPaymentTest::test_payment_audit_reject_does_not_accumulate":7,"Tests\\Feature\\BillPaymentTest::test_payment_detail_returns_voucher_urls":8,"Tests\\Feature\\ContainerReturnTest::test_press_containers_adds_amount_and_writes_record":7,"Tests\\Feature\\ContainerReturnTest::test_return_containers_deducts_amount":7,"Tests\\Feature\\ContainerReturnTest::test_mixed_sign_containers":7,"Tests\\Feature\\ContainerReturnTest::test_zero_containers_writes_no_record":7,"Tests\\Feature\\StoreOrderTest::test_order_list_outputs_status_name_and_item_preview":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.006,"Tests\\Feature\\ProductPriceTest::test_store_without_level_is_rejected":0.007,"Tests\\Feature\\ProductPriceTest::test_batch_price_updates_and_notifies_affected_stores":0.021,"Tests\\Feature\\ProductPriceTest::test_price_matrix_structure":0.01,"Tests\\Feature\\StoreOrderTest::test_place_order_snapshots_level_price_and_recalculates":0.018,"Tests\\Feature\\StoreOrderTest::test_client_supplied_amount_is_ignored":0.018,"Tests\\Feature\\StoreOrderTest::test_product_without_level_price_rejected":0.008,"Tests\\Feature\\StoreOrderTest::test_cancel_only_pending_orders":0.01,"Tests\\Feature\\StoreOrderTest::test_summarized_order_cannot_be_cancelled":0.032,"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.028,"Tests\\Feature\\PurchaseGenerateTest::test_generate_without_pending_orders_fails":0.006,"Tests\\Feature\\PurchaseGenerateTest::test_generate_is_idempotent":0.031,"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.022,"Tests\\Feature\\ReconciliationTest::test_update_item_recalculates_diff_and_header":0.019,"Tests\\Feature\\ReconciliationTest::test_toggle_reconciled_flag":0.02,"Tests\\Feature\\ReconciliationTest::test_settle_creates_settlements_per_store":0.036,"Tests\\Feature\\StatementTest::test_generate_snapshots_payment_cycle":0.011,"Tests\\Feature\\StatementTest::test_statement_isolated_between_stores":0.013,"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.003,"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.006,"Tests\\Feature\\MiniAuthTest::test_mini_token_cannot_access_admin_api":0.004,"Tests\\Feature\\ExportTest::test_export_xlsx_with_encoded_chinese_filename":0.037,"Tests\\Feature\\ExportTest::test_export_category_filters_to_vegetables":0.017,"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.008,"Tests\\Feature\\CartTest::test_duplicate_add_merges_quantity":0.01,"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.007,"Tests\\Feature\\CartTest::test_unbound_user_rejected":0.007,"Tests\\Feature\\CartTest::test_store_without_level_rejected":0.007,"Tests\\Feature\\CartTest::test_list_with_prices_amounts_and_totals":0.011,"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.009,"Tests\\Feature\\CartTest::test_update_other_users_item_rejected":0.01,"Tests\\Feature\\CartTest::test_update_missing_item_rejected":0.011,"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.011,"Tests\\Feature\\CartTest::test_clear_only_own_cart":0.013,"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.006,"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.01,"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.009,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_empty_row_rejected":0.006,"Tests\\Feature\\ProductCostPriceTest::test_price_matrix_includes_cost_and_percent_columns":0.011,"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.01,"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.005,"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.003,"Tests\\Feature\\MiniStoreTest::test_info_returns_bound_store":0.004,"Tests\\Feature\\MiniStoreTest::test_update_info":0.005,"Tests\\Feature\\MiniStoreTest::test_update_info_ignores_readonly_fields":0.008,"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.012,"Tests\\Feature\\StoreOrderTest::test_container_update_allowed_in_editable_statuses":0.022,"Tests\\Feature\\StoreOrderTest::test_container_update_rejected_in_other_statuses":0.019,"Tests\\Feature\\StoreOrderTest::test_container_update_heals_legacy_product_amount":0.02,"Tests\\Feature\\StoreOrderTest::test_container_update_validates_negative_numbers":0.014,"Tests\\Feature\\PurchaseGenerateTest::test_generate_without_accepted_orders_fails":0.018,"Tests\\Feature\\PurchaseGenerateTest::test_generate_with_selected_order_ids":0.037,"Tests\\Feature\\StoreOrderItemTest::test_place_order_snapshots_full_product_info":0.013,"Tests\\Feature\\StoreOrderItemTest::test_mini_detail_hides_cost_price":0.017,"Tests\\Feature\\StoreOrderItemTest::test_admin_detail_shows_cost_price_and_supplier":0.02,"Tests\\Feature\\StoreOrderItemTest::test_update_item_recalculates_order_totals":0.018,"Tests\\Feature\\StoreOrderItemTest::test_update_item_rejected_when_completed_or_cancelled":0.017,"Tests\\Feature\\StoreOrderItemTest::test_update_item_validates_negative_price":0.016,"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.016,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_rejected_when_product_deleted":0.019,"Tests\\Feature\\StoreOrderItemTest::test_order_list_search_by_product_name":0.026,"Tests\\Feature\\StoreOrderTest::test_soft_delete_only_cancelled_orders":0.021,"Tests\\Feature\\StoreOrderTest::test_soft_delete_rejected_for_non_cancelled_orders":0.056,"Tests\\Feature\\StoreOrderTest::test_soft_delete_twice_rejected":0.016,"Tests\\Feature\\PurchaseEditTest::test_detail_returns_store_matrix":0.022,"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.019,"Tests\\Feature\\PurchaseEditTest::test_update_row_weight_distributed_by_quantity":0.032,"Tests\\Feature\\PurchaseEditTest::test_update_row_requires_at_least_one_field":0.019,"Tests\\Feature\\PurchaseEditTest::test_update_cell_rejects_negative_quantity":0.02,"Tests\\Feature\\PurchaseGenerateTest::test_generate_aggregates_order_items_into_header":0.043,"Tests\\Feature\\ReconciliationTest::test_build_excludes_unfinished_orders":0.016,"Tests\\Feature\\PurchaseEditTest::test_update_row_syncs_product_attributes":0.022,"Tests\\Feature\\PurchaseEditTest::test_update_row_syncs_to_product_catalog":0.023,"Tests\\Feature\\PurchaseEditTest::test_cell_detail_returns_items_with_order_info":0.038,"Tests\\Feature\\PurchaseEditTest::test_cell_detail_rejects_missing_params":0.029,"Tests\\Feature\\PurchaseEditTest::test_update_cell_weight_cascades_totals":0.03,"Tests\\Feature\\PurchaseEditTest::test_cell_sync_refreshes_snapshot_and_cascades":0.024,"Tests\\Feature\\PurchaseEditTest::test_edits_rejected_when_purchase_completed":0.034,"Tests\\Feature\\PurchaseEditTest::test_update_row_does_not_touch_manual_weight":0.019,"Tests\\Feature\\PurchaseEditTest::test_store_summary_returns_aggregated_items":0.024,"Tests\\Feature\\PurchaseEditTest::test_store_summary_aggregates_multiple_orders_with_weighted_price":0.019,"Tests\\Feature\\PurchaseEditTest::test_store_summary_rejects_missing_or_invalid_params":0.028,"Tests\\Feature\\ExportTest::test_export_invalid_type_rejected":0.016,"Tests\\Feature\\ExportTest::test_export_missing_purchase_rejected":0.006,"Tests\\Feature\\PurchaseEditTest::test_detail_returns_container_summary":0.022,"Tests\\Feature\\PurchaseEditTest::test_update_container_syncs_order_amounts":0.018,"Tests\\Feature\\PurchaseEditTest::test_update_container_rejected_when_purchase_completed":0.018,"Tests\\Feature\\PurchaseEditTest::test_update_container_rejects_incomplete_or_foreign_orders":0.026,"Tests\\Feature\\PurchaseEditTest::test_update_container_validates_negative_and_duplicate_orders":0.018,"Tests\\Feature\\ProductCategoryTest::test_create_child_category_under_top_level":0.006,"Tests\\Feature\\ProductCategoryTest::test_create_category_under_second_level_rejected":0.019,"Tests\\Feature\\ProductCategoryTest::test_update_second_level_move_to_another_top":0.007,"Tests\\Feature\\ProductCategoryTest::test_update_parent_with_children_cannot_move_under_category":0.013,"Tests\\Feature\\ProductCategoryTest::test_update_with_missing_parent_rejected":0.009,"Tests\\Feature\\ProductCategoryTest::test_create_product_with_parent_category_rejected":0.007,"Tests\\Feature\\ProductCategoryTest::test_create_product_with_leaf_category_ok":0.007,"Tests\\Feature\\ProductCategoryTest::test_mini_categories_returns_all_enabled_categories":0.005,"Tests\\Feature\\MiniBillTest::test_bill_list_derives_pay_state_and_settlement_date":0.008,"Tests\\Feature\\MiniBillTest::test_bill_list_summary_aggregates_unpaid":0.007,"Tests\\Feature\\MiniBillTest::test_bill_list_payable_filter":0.007,"Tests\\Feature\\MiniBillTest::test_bill_list_filters_by_bill_date_range":0.012,"Tests\\Feature\\MiniBillTest::test_bill_data_isolated_between_stores":0.008,"Tests\\Feature\\MiniBillTest::test_bill_detail_outputs_bill_items_and_orders":0.009,"Tests\\Feature\\StoreOrderTest::test_order_list_outputs_status_name_and_item_preview":0.012,"Tests\\Feature\\StoreOrderTest::test_order_list_filters_by_order_date_range":0.018,"Tests\\Feature\\StoreOrderTest::test_order_list_rejects_invalid_filters":0.009,"Tests\\Feature\\BillExportTest::test_export_merges_items_across_bills":0.057,"Tests\\Feature\\BillExportTest::test_export_filters_by_root_category":0.012,"Tests\\Feature\\BillExportTest::test_export_empty_category_rejected":0.028,"Tests\\Feature\\BillExportTest::test_export_requires_export_permission":0.008,"Tests\\Feature\\BillExportTest::test_export_invalid_ids_rejected":0.009,"Tests\\Feature\\DebugPermissionTest::test_debug_permission":0.098,"Tests\\Feature\\BillExportTest::test_mini_export_scoped_to_bound_store":0.014,"Tests\\Feature\\BillExportTest::test_mini_export_requires_store_binding":0.006,"Tests\\Feature\\MiniNoticeTest::test_notice_list_includes_personal_and_broadcast":0.006,"Tests\\Feature\\MiniNoticeTest::test_broadcast_read_creates_personal_copy":0.008,"Tests\\Feature\\MiniNoticeTest::test_personal_notice_read_marks_directly":0.005,"Tests\\Feature\\MiniNoticeTest::test_other_users_notice_not_found":0.006,"Tests\\Feature\\StoreOrderTest::test_manual_status_flow_only_accept_and_cancel":0.02,"Tests\\Feature\\StoreOrderTest::test_manual_status_flow_rejects_delivery_and_completion":0.037,"Tests\\Feature\\StoreOrderTest::test_batch_status_only_accept_and_cancel":0.031,"Tests\\Feature\\BillPaymentTest::test_order_status_progresses_via_business_chain":0.023,"Tests\\Feature\\BillPaymentTest::test_manual_pay_accumulates_store_total_purchase_amount":0.027,"Tests\\Feature\\BillPaymentTest::test_payment_audit_pass_accumulates_store_total_purchase_amount":0.012,"Tests\\Feature\\BillPaymentTest::test_payment_audit_reject_does_not_accumulate":0.008,"Tests\\Feature\\MiniProductTest::test_product_detail_guest_sees_null_price":0.008,"Tests\\Feature\\MiniProductTest::test_product_detail_store_user_sees_level_price":0.006,"Tests\\Feature\\MiniProductTest::test_product_detail_percent_price_calculated_from_cost":0.006,"Tests\\Feature\\MiniProductTest::test_product_detail_off_shelf_or_missing_rejected":0.006,"Tests\\Feature\\MiniProductTest::test_product_list_store_user_sees_level_price":0.006,"Tests\\Feature\\MiniProductTest::test_product_list_guest_sees_null_price":0.005,"Tests\\Feature\\BillPaymentTest::test_admin_order_list_and_detail_show_bill_info":0.031,"Tests\\Feature\\BillPaymentTest::test_payment_detail_returns_voucher_urls":0.014,"Tests\\Feature\\StoreOrderTest::test_order_list_preview_includes_image_spec_and_unit":0.019,"Tests\\Feature\\ContainerReturnTest::test_press_containers_adds_amount_and_writes_record":0.065,"Tests\\Feature\\ContainerReturnTest::test_return_containers_deducts_amount":0.017,"Tests\\Feature\\ContainerReturnTest::test_mixed_sign_containers":0.018,"Tests\\Feature\\ContainerReturnTest::test_zero_containers_writes_no_record":0.017,"Tests\\Feature\\ContainerReturnTest::test_non_integer_containers_rejected":0.009,"Tests\\Feature\\ContainerReturnTest::test_manual_create_and_delete_routes_removed":0.008,"Tests\\Feature\\ContainerReturnTest::test_export_matrix_merges_same_day_and_zero_fills":0.012,"Tests\\Feature\\ContainerReturnTest::test_export_filters_by_selected_stores":0.007,"Tests\\Feature\\ContainerReturnTest::test_export_invalid_params_rejected":0.023,"Tests\\Feature\\ContainerReturnTest::test_export_empty_range_rejected":0.006,"Tests\\Feature\\ContainerReturnTest::test_export_requires_export_permission":0.006}} \ No newline at end of file diff --git a/app/Exports/ContainerReturnExport.php b/app/Exports/ContainerReturnExport.php new file mode 100644 index 0000000..9653745 --- /dev/null +++ b/app/Exports/ContainerReturnExport.php @@ -0,0 +1,145 @@ + 特殊行索引(1 起)=> 类型(title/scope/header/total) */ + private array $specialRows = []; + + /** 列头所在行索引 */ + private int $headerRow = 1; + + /** + * @param Collection $records 区间内压回筐记录 + * @param array $storeNames 门店ID => 名称(导出列,升序) + * @param string $startDate 开始日期 Y-m-d + * @param string $endDate 结束日期 Y-m-d + */ + public function __construct( + private readonly Collection $records, + private readonly array $storeNames, + private readonly string $startDate, + private readonly string $endDate, + ) { + } + + /** + * 导出行:标题/范围/列头/日期矩阵/合计全部手工构建(行位置不固定,不用 WithHeadings) + */ + public function collection(): Collection + { + if ($this->rows !== null) { + return $this->rows; + } + + // 同日期同门店多条记录合并:金额代数和(正=压筐附加,负=回筐抵扣) + $amounts = []; + foreach ($this->records as $record) { + $date = $record->created_at->format('Y-m-d'); + $amounts[$date][(int) $record->store_id] = bcadd( + $amounts[$date][(int) $record->store_id] ?? '0', + (string) $record->amount, + 2 + ); + } + + $rows = []; + $rowIndex = 0; + + // 标题行 + $rows[] = ['压回筐记录汇总(' . $this->startDate . ' ~ ' . $this->endDate . ')']; + $this->specialRows[++$rowIndex] = 'title'; + + // 范围行:门店 / 导出时间 + $rows[] = ['门店:' . implode('、', array_values($this->storeNames)) . ' 导出时间:' . now()->format('Y-m-d H:i:s')]; + $this->specialRows[++$rowIndex] = 'scope'; + + // 空行 + $rows[] = ['']; + $rowIndex++; + + // 列头:日期 + 门店列 + 行合计 + $rows[] = array_merge(['日期'], array_values($this->storeNames), ['合计']); + $this->specialRows[++$rowIndex] = 'header'; + $this->headerRow = $rowIndex; + + // 日期矩阵:区间内逐日一行,当天门店无记录填 0 + $storeIds = array_map(intval(...), array_keys($this->storeNames)); + $columnTotals = array_fill_keys($storeIds, '0'); + $grandTotal = '0'; + foreach (CarbonPeriod::create($this->startDate, $this->endDate) as $date) { + $dateKey = $date->format('Y-m-d'); + $row = [$dateKey]; + $rowTotal = '0'; + foreach ($storeIds as $storeId) { + $amount = $amounts[$dateKey][$storeId] ?? '0.00'; + $row[] = (float) $amount; + $rowTotal = bcadd($rowTotal, $amount, 2); + $columnTotals[$storeId] = bcadd($columnTotals[$storeId], $amount, 2); + } + $grandTotal = bcadd($grandTotal, $rowTotal, 2); + $row[] = (float) $rowTotal; + $rows[] = $row; + $rowIndex++; + } + + // 合计行:各门店列合计 + 总计 + $totalRow = ['合计']; + foreach ($storeIds as $storeId) { + $totalRow[] = (float) $columnTotals[$storeId]; + } + $totalRow[] = (float) $grandTotal; + $rows[] = $totalRow; + $this->specialRows[++$rowIndex] = 'total'; + + return $this->rows = collect($rows); + } + + /** + * 标题/列头/合计行加粗,冻结列头行 + */ + public function styles(Worksheet $sheet): array + { + $this->collection(); + + $sheet->freezePane('A' . ($this->headerRow + 1)); + $sheet->getColumnDimension('A')->setWidth(12); + $columnCount = count($this->storeNames) + 2; + for ($column = 2; $column <= $columnCount; $column++) { + $sheet->getColumnDimensionByColumn($column)->setWidth(12); + } + + $styles = []; + foreach ($this->specialRows as $row => $type) { + $style = match ($type) { + 'title' => ['font' => ['bold' => true, 'size' => 14]], + 'header', 'total' => ['font' => ['bold' => true]], + default => [], + }; + if ($style !== []) { + $styles[$row] = $style; + } + } + + return $styles; + } +} diff --git a/app/Http/Controllers/Recon/ContainerReturnController.php b/app/Http/Controllers/Recon/ContainerReturnController.php index 3f11b56..1545e09 100644 --- a/app/Http/Controllers/Recon/ContainerReturnController.php +++ b/app/Http/Controllers/Recon/ContainerReturnController.php @@ -2,12 +2,18 @@ namespace App\Http\Controllers\Recon; +use App\Exceptions\RepositoryException; +use App\Exports\ContainerReturnExport; use App\Models\ContainerReturnModel; +use App\Models\StoreModel; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; +use Illuminate\Support\Carbon; +use Maatwebsite\Excel\Facades\Excel; use Modules\AnnoRoute\Attribute\GetRoute; use Modules\AnnoRoute\Attribute\RequestAttribute; use Modules\Common\Http\Controllers\BaseController; +use Symfony\Component\HttpFoundation\Response; /** * 压回筐记录(周转筐/托盘跟账单走,生成账单时自动写入完整快照,只读) @@ -39,4 +45,61 @@ class ContainerReturnController extends BaseController ->toArray(); return $this->success($data); } + + /** + * 汇总导出:按日期区间 + 门店导出抵扣(附加)金额矩阵, + * 行=日期(同日记录合并),列=门店,含行合计/列合计,当天门店无记录填 0 + */ + #[GetRoute(route: '/export', authorize: 'export')] + public function export(Request $request): Response + { + $data = $request->validate([ + 'start_date' => 'required|date_format:Y-m-d', + 'end_date' => 'required|date_format:Y-m-d|after_or_equal:start_date', + 'store_ids' => 'nullable|string', + ], [ + 'start_date.required' => '请选择开始日期', + 'start_date.date_format' => '开始日期格式为 Y-m-d', + 'end_date.required' => '请选择结束日期', + 'end_date.date_format' => '结束日期格式为 Y-m-d', + 'end_date.after_or_equal' => '结束日期不能早于开始日期', + ]); + + $startDate = $data['start_date']; + $endDate = $data['end_date']; + if (Carbon::parse($startDate)->diffInDays(Carbon::parse($endDate)) > 366) { + throw new RepositoryException('日期区间不能超过 366 天'); + } + + $storeIds = array_values(array_filter( + array_map(intval(...), explode(',', (string) ($data['store_ids'] ?? ''))) + )); + + $records = ContainerReturnModel::query() + ->whereBetween('created_at', [$startDate . ' 00:00:00', $endDate . ' 23:59:59']) + ->when($storeIds !== [], static fn ($query) => $query->whereIn('store_id', $storeIds)) + ->get(['id', 'store_id', 'amount', 'created_at']); + + // 已选门店:列取所选门店(无记录填 0);未选门店:列取区间内有记录的门店 + $columnStoreIds = $storeIds !== [] + ? $storeIds + : $records->pluck('store_id')->unique()->map(static fn ($id): int => (int) $id)->all(); + if ($columnStoreIds === []) { + throw new RepositoryException('所选日期区间内无压回筐记录'); + } + sort($columnStoreIds); + + $names = StoreModel::withTrashed() + ->whereIn('id', $columnStoreIds) + ->pluck('name', 'id'); + $storeNames = []; + foreach ($columnStoreIds as $storeId) { + $storeNames[$storeId] = $names->get($storeId, '门店#' . $storeId); + } + + return Excel::download( + new ContainerReturnExport($records, $storeNames, $startDate, $endDate), + '回筐记录_' . $startDate . '_' . $endDate . '.xlsx' + ); + } } diff --git a/database/seeders/PermissionSeeder.php b/database/seeders/PermissionSeeder.php index d67b31c..f2a7b32 100644 --- a/database/seeders/PermissionSeeder.php +++ b/database/seeders/PermissionSeeder.php @@ -223,6 +223,7 @@ class PermissionSeeder extends Seeder 'path' => '/recon/container-return', 'children' => [ ['type' => 'rule', 'key' => 'recon.containerReturn.query', 'name' => '查询'], + ['type' => 'rule', 'key' => 'recon.containerReturn.export', 'name' => '导出'], ], ], [ diff --git a/tests/Feature/ContainerReturnTest.php b/tests/Feature/ContainerReturnTest.php index 7b10fb7..361cf2d 100644 --- a/tests/Feature/ContainerReturnTest.php +++ b/tests/Feature/ContainerReturnTest.php @@ -2,6 +2,7 @@ namespace Tests\Feature; +use App\Exports\ContainerReturnExport; use App\Models\BillModel; use App\Models\ContainerReturnModel; use App\Models\CustomerLevelModel; @@ -12,6 +13,7 @@ use App\Models\StoreModel; use App\Models\StoreOrderModel; use App\Models\UserModel; use Illuminate\Support\Facades\DB; +use Maatwebsite\Excel\Facades\Excel; use Modules\SystemTool\Services\SysSiteConfigService; /** @@ -182,4 +184,142 @@ class ContainerReturnTest extends ProcurementTestCase ])->assertJsonPath('success', false); $this->deleteJson('/recon/container-return/1')->assertJsonPath('success', false); } + + /** 造一条压回筐记录(直接入库并指定记录时间,用于导出区间/合并断言) */ + private function makeRecord(StoreModel $store, string $amount, string $createdAt): void + { + DB::table('container_return')->insert([ + 'store_id' => $store->id, + 'bill_id' => 0, + 'box_num' => 0, + 'tray_num' => 0, + 'box_price' => '5.00', + 'tray_price' => '20.00', + 'amount' => $amount, + 'operator_id' => 0, + 'created_at' => $createdAt, + 'updated_at' => $createdAt, + ]); + } + + /** 汇总导出:行=日期(区间内逐日),列=门店,同日记录合并,无记录填 0,含行/列合计 */ + public function test_export_matrix_merges_same_day_and_zero_fills(): void + { + $storeA = StoreModel::factory()->create(); + $storeB = StoreModel::factory()->create(); + + // 08-01:A 两条同日记录合并(+55 压筐、-10 回筐 = 45),B 一条 -20 + $this->makeRecord($storeA, '55.00', '2026-08-01 09:00:00'); + $this->makeRecord($storeA, '-10.00', '2026-08-01 15:30:00'); + $this->makeRecord($storeB, '-20.00', '2026-08-01 10:00:00'); + // 08-02:仅 A +5,B 无记录填 0 + $this->makeRecord($storeA, '5.00', '2026-08-02 08:00:00'); + // 区间外记录不参与 + $this->makeRecord($storeB, '30.00', '2026-08-04 08:00:00'); + + $this->actingAsSysUser(); + Excel::fake(); + $this->get('/recon/container-return/export?start_date=2026-08-01&end_date=2026-08-03')->assertOk(); + + Excel::assertDownloaded( + '回筐记录_2026-08-01_2026-08-03.xlsx', + static function (ContainerReturnExport $export) use ($storeA, $storeB): bool { + $rows = $export->collection()->values(); + // 标题1 + 范围1 + 空行1 + 列头1 + 日期3 + 合计1 = 8 行 + if ($rows->count() !== 8) { + return false; + } + // 列头:日期 + 门店(未选门店时取区间内有记录的门店,ID 升序)+ 合计 + if ($rows[3] !== ['日期', $storeA->name, $storeB->name, '合计']) { + return false; + } + // 08-01:同日合并 55-10=45,B -20,行合计 25 + if ($rows[4] !== ['2026-08-01', 45.0, -20.0, 25.0]) { + return false; + } + // 08-02:A 5、B 无记录填 0,行合计 5 + if ($rows[5] !== ['2026-08-02', 5.0, 0.0, 5.0]) { + return false; + } + // 08-03:当日全部无记录,整行填 0 + if ($rows[6] !== ['2026-08-03', 0.0, 0.0, 0.0]) { + return false; + } + // 合计行:A 列 50、B 列 -20、总计 30 + return $rows[7] === ['合计', 50.0, -20.0, 30.0]; + } + ); + } + + /** 指定门店导出:仅所选门店成列,未选门店记录忽略;所选门店无记录整列填 0 */ + public function test_export_filters_by_selected_stores(): void + { + $storeA = StoreModel::factory()->create(); + $storeB = StoreModel::factory()->create(); + $storeC = StoreModel::factory()->create(); + + $this->makeRecord($storeA, '55.00', '2026-08-01 09:00:00'); + $this->makeRecord($storeB, '-20.00', '2026-08-01 10:00:00'); + // C 区间内无记录,但被选中 → 整列填 0 + + $this->actingAsSysUser(); + Excel::fake(); + $this->get('/recon/container-return/export?start_date=2026-08-01&end_date=2026-08-02&store_ids=' . $storeA->id . ',' . $storeC->id) + ->assertOk(); + + Excel::assertDownloaded( + '回筐记录_2026-08-01_2026-08-02.xlsx', + static function (ContainerReturnExport $export) use ($storeA, $storeC): bool { + $rows = $export->collection()->values(); + // 列头仅含所选门店(B 的记录被忽略) + if ($rows[3] !== ['日期', $storeA->name, $storeC->name, '合计']) { + return false; + } + // 08-01:A 55、C 填 0;08-02 全 0 + if ($rows[4] !== ['2026-08-01', 55.0, 0.0, 55.0]) { + return false; + } + if ($rows[5] !== ['2026-08-02', 0.0, 0.0, 0.0]) { + return false; + } + + return $rows[6] === ['合计', 55.0, 0.0, 55.0]; + } + ); + } + + /** 参数校验:缺少日期 / 结束早于开始 / 区间超 366 天 → 拒绝 */ + public function test_export_invalid_params_rejected(): void + { + $this->actingAsSysUser(); + + $this->get('/recon/container-return/export') + ->assertJsonPath('success', false); + $this->get('/recon/container-return/export?start_date=2026-08-10&end_date=2026-08-01') + ->assertJsonPath('success', false); + $this->get('/recon/container-return/export?start_date=2026-01-01&end_date=2027-06-30') + ->assertJsonPath('success', false); + } + + /** 区间内无记录且未选门店 → 拒绝 */ + public function test_export_empty_range_rejected(): void + { + $this->actingAsSysUser(); + + $this->get('/recon/container-return/export?start_date=2026-08-01&end_date=2026-08-03') + ->assertJsonPath('success', false); + } + + /** 无 recon.containerReturn.export 权限点 → 拦截 */ + public function test_export_requires_export_permission(): void + { + // 先建一个占位用户:每个测试方法内首个系统用户自增 id=1, + // SysAccessToken::can() 对 tokenable_id==1(超管)放行全部权限,会绕过 abilities 校验 + $this->actingAsSysUser(); + // 仅持有查询权限的用户 + $this->actingAsSysUser(['recon.containerReturn.query']); + + $response = $this->get('/recon/container-return/export?start_date=2026-08-01&end_date=2026-08-03'); + $this->assertFalse($response->json('success'), '缺少权限点应被拦截'); + } } diff --git a/web/api/recon/containerReturn.ts b/web/api/recon/containerReturn.ts new file mode 100644 index 0000000..53238db --- /dev/null +++ b/web/api/recon/containerReturn.ts @@ -0,0 +1,19 @@ +import { downloadBlob } from '@/api/common/download.ts'; + +/** + * 汇总导出回筐记录:行=日期(同日记录合并),列=门店, + * 单元格为抵扣(附加)金额,含行合计/列合计,当天门店无记录填 0 + * + * @param storeIds 门店ID列表,空数组=全部门店 + */ +export async function exportContainerReturns(startDate: string, endDate: string, storeIds: number[]) { + return downloadBlob( + '/recon/container-return/export', + { + start_date: startDate, + end_date: endDate, + ...(storeIds.length > 0 ? { store_ids: storeIds.join(',') } : {}), + }, + '回筐记录.xlsx' + ); +} diff --git a/web/pages/recon/container-return.tsx b/web/pages/recon/container-return.tsx index 7aa0b26..64f2541 100644 --- a/web/pages/recon/container-return.tsx +++ b/web/pages/recon/container-return.tsx @@ -1,12 +1,23 @@ import React, { useEffect, useState } from 'react'; -import { Typography } from 'antd'; +import { Button, DatePicker, Form, Modal, Select, Typography } from 'antd'; +import { DownloadOutlined } from '@ant-design/icons'; +import dayjs from 'dayjs'; import XinTable from '@/components/XinTable'; import type { XinTableColumn, XinTableProps } from '@/components/XinTable/typings.ts'; import type IContainerReturn from '@/domain/iContainerReturn.ts'; import { getStoreOptions } from '@/api/customer/store.ts'; +import { exportContainerReturns } from '@/api/recon/containerReturn.ts'; import type IStore from '@/domain/iStore.ts'; +import AuthButton from '@/components/AuthButton'; const { Title, Text } = Typography; +const { RangePicker } = DatePicker; + +/** 汇总导出表单值 */ +interface ExportFormValues { + date_range: [dayjs.Dayjs, dayjs.Dayjs]; + store_ids?: number[]; +} /** 有符号数量展示:正数=压筐(橙色 +N),负数=回筐(绿色 N),0 置灰 */ const SignedNum: React.FC<{ value?: number }> = ({ value }) => { @@ -39,10 +50,31 @@ const SignedAmount: React.FC<{ value?: string }> = ({ value }) => { const ContainerReturnPage: React.FC = () => { const [stores, setStores] = useState([]); + // 汇总导出(日期区间 + 门店:行=日期,列=门店,单元格为抵扣/附加金额) + const [exportOpen, setExportOpen] = useState(false); + const [exporting, setExporting] = useState(false); + const [exportForm] = Form.useForm(); + useEffect(() => { getStoreOptions().then((res) => setStores(res.data.data ?? [])); }, []); + /** 提交汇总导出:下载失败时 downloadBlob 内部已提示 */ + const handleExport = async (values: ExportFormValues) => { + const [start, end] = values.date_range; + setExporting(true); + try { + await exportContainerReturns( + start.format('YYYY-MM-DD'), + end.format('YYYY-MM-DD'), + values.store_ids ?? [] + ); + setExportOpen(false); + } finally { + setExporting(false); + } + }; + const columns: XinTableColumn[] = [ { title: '门店', @@ -143,6 +175,17 @@ const ContainerReturnPage: React.FC = () => { editShow: false, deleteShow: false, formProps: false, + toolBarRender: (dom) => [ + + + , + dom.columnSetting, + dom.hideBorder, + dom.reload, + dom.columnHeight, + ], }; return ( @@ -150,10 +193,55 @@ const ContainerReturnPage: React.FC = () => {
回筐记录 - 周转筐/托盘跟账单走:生成账单时按填写数量自动写入;正数=压筐(附加金额),负数=回筐(抵扣金额),数量为 0 不生成记录。 + 周转筐/托盘跟账单走:生成账单时按填写数量自动写入;正数=压筐(附加金额),负数=回筐(抵扣金额),数量为 0 不生成记录; + 可按日期区间与门店汇总导出(行=日期,列=门店,含行列合计)。
{...tableProps} /> + + {/* 汇总导出:日期区间 + 门店,同日记录合并,无记录填 0 */} + setExportOpen(false)} + onOk={() => exportForm.submit()} + confirmLoading={exporting} + okText="导出" + destroyOnHidden + > +
+ 按日期区间与门店导出抵扣(附加)金额汇总表:行=日期(同日记录合并),列=门店, + 当天门店无记录填 0,含行合计与列合计。门店不选默认导出全部门店。 +
+
+ + + + +