From 4bdaf2a219430dae6c8b7a8532356bb75585d6c1 Mon Sep 17 00:00:00 2001 From: liu <2302563948@qq.com> Date: Fri, 14 Aug 2026 22:20:13 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=B8=80=E4=BA=9B=E9=94=99?= =?UTF-8?q?=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .phpunit.result.cache | 2 +- app/Http/Controllers/Mini/OrderController.php | 32 +++-- .../Controllers/Mini/ProductController.php | 79 ++++++++++--- .../Order/StoreOrderController.php | 25 +++- app/Models/PaymentModel.php | 5 +- app/Services/BillDetailService.php | 14 ++- tests/Feature/BillPaymentTest.php | 70 +++++++++++ tests/Feature/MiniBillTest.php | 20 ++++ tests/Feature/MiniProductTest.php | 110 ++++++++++++++++++ tests/Feature/StoreOrderTest.php | 45 +++++++ web/domain/iStoreOrder.ts | 38 ++++++ web/pages/order/store.tsx | 51 +++++++- 12 files changed, 454 insertions(+), 37 deletions(-) create mode 100644 tests/Feature/MiniProductTest.php diff --git a/.phpunit.result.cache b/.phpunit.result.cache index 28244b5..5085fcc 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},"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.01,"Tests\\Feature\\ProductPriceTest::test_price_matrix_structure":0.007,"Tests\\Feature\\StoreOrderTest::test_place_order_snapshots_level_price_and_recalculates":0.012,"Tests\\Feature\\StoreOrderTest::test_client_supplied_amount_is_ignored":0.011,"Tests\\Feature\\StoreOrderTest::test_product_without_level_price_rejected":0.016,"Tests\\Feature\\StoreOrderTest::test_cancel_only_pending_orders":0.017,"Tests\\Feature\\StoreOrderTest::test_summarized_order_cannot_be_cancelled":0.017,"Tests\\Feature\\StoreOrderTest::test_order_data_isolated_between_stores":0.024,"Tests\\Feature\\PurchaseGenerateTest::test_generate_aggregates_orders_by_product":0.028,"Tests\\Feature\\PurchaseGenerateTest::test_generate_writes_back_order_status":0.032,"Tests\\Feature\\PurchaseGenerateTest::test_generate_without_pending_orders_fails":0.006,"Tests\\Feature\\PurchaseGenerateTest::test_generate_is_idempotent":0.036,"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.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.039,"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.017,"Tests\\Unit\\ExampleTest::test_that_true_is_true":0.001,"Tests\\Feature\\MiniAuthTest::test_login_fails_when_wechat_rejects_code":0.007,"Tests\\Feature\\CartTest::test_add_to_cart_succeeds":0.007,"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.008,"Tests\\Feature\\CartTest::test_store_without_level_rejected":0.006,"Tests\\Feature\\CartTest::test_list_with_prices_amounts_and_totals":0.012,"Tests\\Feature\\CartTest::test_list_marks_off_shelf_item_unbuyable":0.011,"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.008,"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.01,"Tests\\Feature\\CartTest::test_clear_only_own_cart":0.015,"Tests\\Feature\\CartTest::test_unauthenticated_returns_401":0.003,"Tests\\Feature\\CartTest::test_cart_isolated_between_users":0.01,"Tests\\Feature\\DebugCartListTest::test_dump_cart_list_response":0.057,"Tests\\Feature\\ProductCostPriceTest::test_calc_actual_price_fixed_and_percent":0.001,"Tests\\Feature\\ProductCostPriceTest::test_calc_actual_price_edge_cases":0.001,"Tests\\Feature\\ProductCostPriceTest::test_admin_list_contains_cost_price_and_actual_price":0.01,"Tests\\Feature\\ProductCostPriceTest::test_mini_list_returns_actual_price_without_cost_price":0.006,"Tests\\Feature\\ProductCostPriceTest::test_create_product_with_cost_and_percent_pricing":0.01,"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.009,"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.007,"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.007,"Tests\\Feature\\ProductCostPriceTest::test_mini_cart_uses_actual_price_for_percent_row":0.009,"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.008,"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.006,"Tests\\Feature\\MiniStoreTest::test_update_info_ignores_readonly_fields":0.006,"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.023,"Tests\\Feature\\PurchaseGenerateTest::test_generate_with_selected_order_ids":0.033,"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.015,"Tests\\Feature\\StoreOrderItemTest::test_update_item_recalculates_order_totals":0.018,"Tests\\Feature\\StoreOrderItemTest::test_update_item_rejected_when_completed_or_cancelled":0.02,"Tests\\Feature\\StoreOrderItemTest::test_update_item_validates_negative_price":0.016,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_updates_snapshot_from_product":0.022,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_recalculates_percent_price_with_latest_cost":0.017,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_rejected_when_product_deleted":0.022,"Tests\\Feature\\StoreOrderItemTest::test_order_list_search_by_product_name":0.024,"Tests\\Feature\\StoreOrderTest::test_soft_delete_only_cancelled_orders":0.022,"Tests\\Feature\\StoreOrderTest::test_soft_delete_rejected_for_non_cancelled_orders":0.054,"Tests\\Feature\\StoreOrderTest::test_soft_delete_twice_rejected":0.023,"Tests\\Feature\\PurchaseEditTest::test_detail_returns_store_matrix":0.023,"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.02,"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.037,"Tests\\Feature\\ReconciliationTest::test_build_excludes_unfinished_orders":0.016,"Tests\\Feature\\PurchaseEditTest::test_update_row_syncs_product_attributes":0.023,"Tests\\Feature\\PurchaseEditTest::test_update_row_syncs_to_product_catalog":0.021,"Tests\\Feature\\PurchaseEditTest::test_cell_detail_returns_items_with_order_info":0.021,"Tests\\Feature\\PurchaseEditTest::test_cell_detail_rejects_missing_params":0.019,"Tests\\Feature\\PurchaseEditTest::test_update_cell_weight_cascades_totals":0.02,"Tests\\Feature\\PurchaseEditTest::test_cell_sync_refreshes_snapshot_and_cascades":0.013,"Tests\\Feature\\PurchaseEditTest::test_edits_rejected_when_purchase_completed":0.02,"Tests\\Feature\\PurchaseEditTest::test_update_row_does_not_touch_manual_weight":0.019,"Tests\\Feature\\PurchaseEditTest::test_store_summary_returns_aggregated_items":0.023,"Tests\\Feature\\PurchaseEditTest::test_store_summary_aggregates_multiple_orders_with_weighted_price":0.018,"Tests\\Feature\\PurchaseEditTest::test_store_summary_rejects_missing_or_invalid_params":0.028,"Tests\\Feature\\ExportTest::test_export_invalid_type_rejected":0.016,"Tests\\Feature\\ExportTest::test_export_missing_purchase_rejected":0.009,"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.007,"Tests\\Feature\\ProductCategoryTest::test_create_category_under_second_level_rejected":0.007,"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.01,"Tests\\Feature\\ProductCategoryTest::test_update_with_missing_parent_rejected":0.007,"Tests\\Feature\\ProductCategoryTest::test_create_product_with_parent_category_rejected":0.008,"Tests\\Feature\\ProductCategoryTest::test_create_product_with_leaf_category_ok":0.007,"Tests\\Feature\\ProductCategoryTest::test_mini_categories_returns_all_enabled_categories":0.006,"Tests\\Feature\\MiniBillTest::test_bill_list_derives_pay_state_and_settlement_date":0.01,"Tests\\Feature\\MiniBillTest::test_bill_list_summary_aggregates_unpaid":0.008,"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.008,"Tests\\Feature\\StoreOrderTest::test_order_list_outputs_status_name_and_item_preview":0.023,"Tests\\Feature\\StoreOrderTest::test_order_list_filters_by_order_date_range":0.029,"Tests\\Feature\\StoreOrderTest::test_order_list_rejects_invalid_filters":0.011,"Tests\\Feature\\BillExportTest::test_export_merges_items_across_bills":0.06,"Tests\\Feature\\BillExportTest::test_export_filters_by_root_category":0.011,"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.011,"Tests\\Feature\\DebugPermissionTest::test_debug_permission":0.098,"Tests\\Feature\\BillExportTest::test_mini_export_scoped_to_bound_store":0.013,"Tests\\Feature\\BillExportTest::test_mini_export_requires_store_binding":0.005,"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.006,"Tests\\Feature\\MiniNoticeTest::test_other_users_notice_not_found":0.006,"Tests\\Feature\\StoreOrderTest::test_manual_status_flow_only_accept_and_cancel":0.027,"Tests\\Feature\\StoreOrderTest::test_manual_status_flow_rejects_delivery_and_completion":0.036,"Tests\\Feature\\StoreOrderTest::test_batch_status_only_accept_and_cancel":0.026,"Tests\\Feature\\BillPaymentTest::test_order_status_progresses_via_business_chain":0.022,"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.011,"Tests\\Feature\\BillPaymentTest::test_payment_audit_reject_does_not_accumulate":0.008}} \ 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},"times":{"Tests\\Feature\\ExampleTest::test_the_application_returns_a_successful_response":0.004,"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.01,"Tests\\Feature\\ProductPriceTest::test_price_matrix_structure":0.012,"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.009,"Tests\\Feature\\StoreOrderTest::test_cancel_only_pending_orders":0.011,"Tests\\Feature\\StoreOrderTest::test_summarized_order_cannot_be_cancelled":0.012,"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.036,"Tests\\Feature\\PurchaseGenerateTest::test_generate_without_pending_orders_fails":0.006,"Tests\\Feature\\PurchaseGenerateTest::test_generate_is_idempotent":0.034,"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.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.058,"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.018,"Tests\\Unit\\ExampleTest::test_that_true_is_true":0.001,"Tests\\Feature\\MiniAuthTest::test_login_fails_when_wechat_rejects_code":0.004,"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.007,"Tests\\Feature\\CartTest::test_soft_deleted_product_rejected":0.007,"Tests\\Feature\\CartTest::test_product_without_level_price_rejected":0.009,"Tests\\Feature\\CartTest::test_unbound_user_rejected":0.007,"Tests\\Feature\\CartTest::test_store_without_level_rejected":0.01,"Tests\\Feature\\CartTest::test_list_with_prices_amounts_and_totals":0.012,"Tests\\Feature\\CartTest::test_list_marks_off_shelf_item_unbuyable":0.011,"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.009,"Tests\\Feature\\CartTest::test_invalid_quantity_rejected":0.008,"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.014,"Tests\\Feature\\CartTest::test_unauthenticated_returns_401":0.003,"Tests\\Feature\\CartTest::test_cart_isolated_between_users":0.009,"Tests\\Feature\\DebugCartListTest::test_dump_cart_list_response":0.057,"Tests\\Feature\\ProductCostPriceTest::test_calc_actual_price_fixed_and_percent":0.001,"Tests\\Feature\\ProductCostPriceTest::test_calc_actual_price_edge_cases":0.001,"Tests\\Feature\\ProductCostPriceTest::test_admin_list_contains_cost_price_and_actual_price":0.01,"Tests\\Feature\\ProductCostPriceTest::test_mini_list_returns_actual_price_without_cost_price":0.006,"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.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.01,"Tests\\Feature\\ProductCostPriceTest::test_mini_order_uses_actual_price_for_percent_row":0.008,"Tests\\Feature\\ProductCostPriceTest::test_mini_cart_uses_actual_price_for_percent_row":0.009,"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.007,"Tests\\Feature\\ProductPriceTest::test_unbound_user_can_browse_products_without_price_but_cannot_cart":0.011,"Tests\\Feature\\MiniAuthTest::test_login_issues_token_for_registered_user":0.02,"Tests\\Feature\\MiniAuthTest::test_login_rejects_unregistered_openid":0.002,"Tests\\Feature\\MiniAuthTest::test_register_binds_store_and_issues_token":0.029,"Tests\\Feature\\MiniAuthTest::test_register_rejects_unknown_store":0.003,"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.006,"Tests\\Feature\\MiniStoreTest::test_update_info_ignores_readonly_fields":0.005,"Tests\\Feature\\MiniStoreTest::test_requires_bound_store":0.007,"Tests\\Feature\\MiniStoreTest::test_update_info_validates_length":0.005,"Tests\\Feature\\StoreOrderTest::test_container_update_recalculates_amounts":0.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.021,"Tests\\Feature\\PurchaseGenerateTest::test_generate_with_selected_order_ids":0.036,"Tests\\Feature\\StoreOrderItemTest::test_place_order_snapshots_full_product_info":0.013,"Tests\\Feature\\StoreOrderItemTest::test_mini_detail_hides_cost_price":0.015,"Tests\\Feature\\StoreOrderItemTest::test_admin_detail_shows_cost_price_and_supplier":0.012,"Tests\\Feature\\StoreOrderItemTest::test_update_item_recalculates_order_totals":0.013,"Tests\\Feature\\StoreOrderItemTest::test_update_item_rejected_when_completed_or_cancelled":0.026,"Tests\\Feature\\StoreOrderItemTest::test_update_item_validates_negative_price":0.011,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_updates_snapshot_from_product":0.012,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_recalculates_percent_price_with_latest_cost":0.01,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_rejected_when_product_deleted":0.017,"Tests\\Feature\\StoreOrderItemTest::test_order_list_search_by_product_name":0.024,"Tests\\Feature\\StoreOrderTest::test_soft_delete_only_cancelled_orders":0.033,"Tests\\Feature\\StoreOrderTest::test_soft_delete_rejected_for_non_cancelled_orders":0.061,"Tests\\Feature\\StoreOrderTest::test_soft_delete_twice_rejected":0.02,"Tests\\Feature\\PurchaseEditTest::test_detail_returns_store_matrix":0.023,"Tests\\Feature\\PurchaseEditTest::test_update_cell_syncs_order_item_and_totals":0.023,"Tests\\Feature\\PurchaseEditTest::test_update_row_cost_syncs_all_order_items":0.02,"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.036,"Tests\\Feature\\ReconciliationTest::test_build_excludes_unfinished_orders":0.016,"Tests\\Feature\\PurchaseEditTest::test_update_row_syncs_product_attributes":0.021,"Tests\\Feature\\PurchaseEditTest::test_update_row_syncs_to_product_catalog":0.026,"Tests\\Feature\\PurchaseEditTest::test_cell_detail_returns_items_with_order_info":0.02,"Tests\\Feature\\PurchaseEditTest::test_cell_detail_rejects_missing_params":0.029,"Tests\\Feature\\PurchaseEditTest::test_update_cell_weight_cascades_totals":0.023,"Tests\\Feature\\PurchaseEditTest::test_cell_sync_refreshes_snapshot_and_cascades":0.025,"Tests\\Feature\\PurchaseEditTest::test_edits_rejected_when_purchase_completed":0.042,"Tests\\Feature\\PurchaseEditTest::test_update_row_does_not_touch_manual_weight":0.02,"Tests\\Feature\\PurchaseEditTest::test_store_summary_returns_aggregated_items":0.032,"Tests\\Feature\\PurchaseEditTest::test_store_summary_aggregates_multiple_orders_with_weighted_price":0.041,"Tests\\Feature\\PurchaseEditTest::test_store_summary_rejects_missing_or_invalid_params":0.043,"Tests\\Feature\\ExportTest::test_export_invalid_type_rejected":0.016,"Tests\\Feature\\ExportTest::test_export_missing_purchase_rejected":0.007,"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.009,"Tests\\Feature\\ProductCategoryTest::test_create_category_under_second_level_rejected":0.007,"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.007,"Tests\\Feature\\ProductCategoryTest::test_update_with_missing_parent_rejected":0.009,"Tests\\Feature\\ProductCategoryTest::test_create_product_with_parent_category_rejected":0.009,"Tests\\Feature\\ProductCategoryTest::test_create_product_with_leaf_category_ok":0.007,"Tests\\Feature\\ProductCategoryTest::test_mini_categories_returns_all_enabled_categories":0.006,"Tests\\Feature\\MiniBillTest::test_bill_list_derives_pay_state_and_settlement_date":0.008,"Tests\\Feature\\MiniBillTest::test_bill_list_summary_aggregates_unpaid":0.008,"Tests\\Feature\\MiniBillTest::test_bill_list_payable_filter":0.007,"Tests\\Feature\\MiniBillTest::test_bill_list_filters_by_bill_date_range":0.011,"Tests\\Feature\\MiniBillTest::test_bill_data_isolated_between_stores":0.009,"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.019,"Tests\\Feature\\StoreOrderTest::test_order_list_filters_by_order_date_range":0.02,"Tests\\Feature\\StoreOrderTest::test_order_list_rejects_invalid_filters":0.009,"Tests\\Feature\\BillExportTest::test_export_merges_items_across_bills":0.069,"Tests\\Feature\\BillExportTest::test_export_filters_by_root_category":0.011,"Tests\\Feature\\BillExportTest::test_export_empty_category_rejected":0.057,"Tests\\Feature\\BillExportTest::test_export_requires_export_permission":0.01,"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.006,"Tests\\Feature\\MiniNoticeTest::test_other_users_notice_not_found":0.006,"Tests\\Feature\\StoreOrderTest::test_manual_status_flow_only_accept_and_cancel":0.033,"Tests\\Feature\\StoreOrderTest::test_manual_status_flow_rejects_delivery_and_completion":0.028,"Tests\\Feature\\StoreOrderTest::test_batch_status_only_accept_and_cancel":0.024,"Tests\\Feature\\BillPaymentTest::test_order_status_progresses_via_business_chain":0.025,"Tests\\Feature\\BillPaymentTest::test_manual_pay_accumulates_store_total_purchase_amount":0.032,"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.009,"Tests\\Feature\\MiniProductTest::test_product_detail_guest_sees_null_price":0.007,"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.013,"Tests\\Feature\\StoreOrderTest::test_order_list_preview_includes_image_spec_and_unit":0.026}} \ No newline at end of file diff --git a/app/Http/Controllers/Mini/OrderController.php b/app/Http/Controllers/Mini/OrderController.php index 17f381d..9c389c7 100644 --- a/app/Http/Controllers/Mini/OrderController.php +++ b/app/Http/Controllers/Mini/OrderController.php @@ -9,6 +9,7 @@ use App\Models\ProductPriceModel; use App\Models\StoreOrderItemModel; use App\Models\StoreOrderModel; use App\Services\BillNumberService; +use App\Services\ItemImageResolver; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; @@ -161,7 +162,7 @@ class OrderController extends BaseMiniController 'total_amount', 'status', 'remark', 'purchase_id', 'bill_id', 'created_at', ]) ->with(['items' => static fn ($itemsQuery) => $itemsQuery - ->select(['id', 'order_id', 'product_name', 'quantity', 'unit']) + ->select(['id', 'order_id', 'product_name', 'product_spec', 'quantity', 'unit', 'image_ids']) ->orderBy('id')]); if (isset($params['status'])) { $query->where('status', (int) $params['status']); @@ -177,6 +178,28 @@ class OrderController extends BaseMiniController ->orderBy('id', 'desc') ->paginate((int) ($params['pageSize'] ?? 10)); + // 商品预览(每单前 3 条明细):首图跨订单一次性批量解析 + $previewMap = []; + $flatItems = []; + foreach ($paginator->getCollection() as $order) { + foreach ($order->items as $item) { + $flatItems[] = [ + 'order_id' => $order->id, + 'product_name' => $item->product_name, + 'product_spec' => $item->product_spec, + 'quantity' => $item->quantity, + 'unit' => $item->unit, + 'image_ids' => (array) $item->image_ids, + ]; + } + } + app(ItemImageResolver::class)->resolve($flatItems); + foreach ($flatItems as $flatItem) { + $orderId = $flatItem['order_id']; + unset($flatItem['order_id']); + $previewMap[$orderId][] = $flatItem; + } + $paginator->getCollection()->transform( static fn (StoreOrderModel $order): array => [ 'id' => $order->id, @@ -193,12 +216,7 @@ class OrderController extends BaseMiniController 'bill_id' => $order->bill_id, 'created_at' => $order->created_at?->toDateTimeString(), 'item_count' => $order->items->count(), - 'items' => $order->items->take(3) - ->map(static fn (StoreOrderItemModel $item): array => [ - 'product_name' => $item->product_name, - 'quantity' => $item->quantity, - 'unit' => $item->unit, - ])->values()->all(), + 'items' => $previewMap[$order->id] ?? [], ] ); diff --git a/app/Http/Controllers/Mini/ProductController.php b/app/Http/Controllers/Mini/ProductController.php index 5e68b92..9c349f9 100644 --- a/app/Http/Controllers/Mini/ProductController.php +++ b/app/Http/Controllers/Mini/ProductController.php @@ -46,35 +46,76 @@ class ProductController extends BaseMiniController } $pageSize = (int) $request->input('pageSize', 10); - $data = $query->orderBy('sort') + $paginator = $query->orderBy('sort') ->orderBy('id') - ->paginate($pageSize) - ->toArray(); + ->paginate($pageSize); - foreach ($data['data'] as &$row) { - $row['price'] = null; + // 当前门店的等级价格(一次性取出,避免逐行查询) + $user = $this->optionalUser($request); + $store = $user !== null ? $this->boundStore($user) : null; + $priceRows = collect(); + if ($store !== null && $store->level_id > 0) { + $priceRows = ProductPriceModel::query() + ->where('level_id', $store->level_id) + ->whereIn('product_id', $paginator->getCollection()->pluck('id')) + ->get(['product_id', 'price', 'price_type', 'percent']) + ->keyBy('product_id'); } - // 用户 - $user = $this->optionalUser($request); - if (!$user) return $this->success($data); - // 门店 - $store = $this->boundStore($user); - if(!$store) return $this->success($data); + $paginator->getCollection()->transform( + static function (ProductModel $product) use ($priceRows): array { + $row = $product->toArray(); + // 实际价(百分比计价行按成本价上浮换算;成本价不随序列化输出) + $priceRow = $priceRows->get($product->id); + $row['price'] = $priceRow !== null + ? ProductPriceModel::calcActualPrice( + (int) $priceRow->price_type, + (string) $priceRow->price, + (string) $priceRow->percent, + (string) $product->cost_price, + ) + : null; + return $row; + } + ); - if ($store->level_id > 0) { - foreach ($data['data'] as &$row) { - $model = ProductPriceModel::where('product_id', $row['id'])->where('level_id', $store->level_id)->first(); + return $this->success($paginator->toArray()); + } + + /** + * 商品详情(免登录浏览;登录门店按等级显示换算价,未登录/未绑店/未设等级 price=null) + */ + #[GetRoute('/product/{id}', authorize: false, where: ['id' => '[0-9]+'])] + public function detail(int $id, Request $request): JsonResponse + { + $product = ProductModel::query() + ->where('status', ProductModel::STATUS_ON) + ->with('category:id,name') + ->find($id); + if ($product === null) { + throw new RepositoryException('商品不存在或已下架'); + } + + $price = null; + $user = $this->optionalUser($request); + $store = $user !== null ? $this->boundStore($user) : null; + if ($store !== null && $store->level_id > 0) { + $priceRow = ProductPriceModel::query() + ->forProductLevel($product->id, $store->level_id) + ->first(['price', 'price_type', 'percent']); + if ($priceRow !== null) { $price = ProductPriceModel::calcActualPrice( - $model->price_type, - $model->price, - $model->percent, - $row->cost_price, + (int) $priceRow->price_type, + (string) $priceRow->price, + (string) $priceRow->percent, + (string) $product->cost_price, ); - $row['price'] = $price; } } + $data = $product->toArray(); + $data['price'] = $price; + return $this->success($data); } } diff --git a/app/Http/Controllers/Order/StoreOrderController.php b/app/Http/Controllers/Order/StoreOrderController.php index 7fea0fe..8398a8a 100644 --- a/app/Http/Controllers/Order/StoreOrderController.php +++ b/app/Http/Controllers/Order/StoreOrderController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers\Order; use App\Exceptions\RepositoryException; +use App\Models\BillModel; use App\Models\NoticeModel; use App\Models\StoreOrderModel; use App\Models\UserModel; @@ -38,7 +39,8 @@ class StoreOrderController extends BaseController $query = StoreOrderModel::query()->with([ 'store:id,name,address,contact,phone', 'items:id,order_id,product_id,product_name,product_spec,unit,price,quantity,amount,image_ids', - 'purchase:id,purchase_no,purchase_date,status' + 'purchase:id,purchase_no,purchase_date,status', + 'bill:id,bill_no,bill_date,total_amount,status,payment_id,paid_at', ]); // 按包含的商品名称搜索:任一明细品名包含关键字即命中 @@ -65,6 +67,7 @@ class StoreOrderController extends BaseController // 明细封面图:从快照 image_ids 批量解析(不再关联商品档案表) foreach ($data['data'] as &$order) { app(ItemImageResolver::class)->resolve($order['items']); + $this->appendBillPayState($order); } unset($order); @@ -78,6 +81,7 @@ class StoreOrderController extends BaseController $order = StoreOrderModel::with([ 'store:id,name,address,contact,phone', 'items' => static fn ($query) => $query->with('supplier:id,name'), + 'bill', ])->find($id); if (empty($order)) { throw new RepositoryException('订单不存在'); @@ -90,6 +94,8 @@ class StoreOrderController extends BaseController // 明细首图 app(ItemImageResolver::class)->resolve($data['items']); + // 关联账单详情:补充支付进度 + $this->appendBillPayState($data); return $this->success($data); } @@ -234,4 +240,21 @@ class StoreOrderController extends BaseController ]); } } + + /** + * 订单输出数组的关联账单补充支付进度(待支付/审核中/已支付,与小程序端口径一致) + * + * @param array $order 订单数组(引用修改) + */ + private function appendBillPayState(array &$order): void + { + if (! is_array($order['bill'] ?? null)) { + return; + } + // 复用模型支付进度推导(已支付 > 审核中 > 待支付) + $bill = new BillModel($order['bill']); + $payState = $bill->payState(); + $order['bill']['pay_state'] = $payState; + $order['bill']['pay_state_name'] = BillModel::PAY_STATE_NAMES[$payState]; + } } diff --git a/app/Models/PaymentModel.php b/app/Models/PaymentModel.php index face02a..d489a22 100644 --- a/app/Models/PaymentModel.php +++ b/app/Models/PaymentModel.php @@ -95,7 +95,10 @@ class PaymentModel extends Model if ($ids === []) { return []; } - $urls = SysFileModel::query()->whereIn('id', $ids)->pluck('preview_url', 'id'); + $urls = []; + foreach (SysFileModel::query()->whereIn('id', $ids)->get() as $file) { + $urls[$file->id] = $file->preview_url; + } $result = []; foreach ($ids as $id) { if (isset($urls[$id])) { diff --git a/app/Services/BillDetailService.php b/app/Services/BillDetailService.php index f5d844b..4a8264f 100644 --- a/app/Services/BillDetailService.php +++ b/app/Services/BillDetailService.php @@ -14,9 +14,9 @@ class BillDetailService { /** * 合并后的商品明细:按商品聚合账单关联的全部订单明细 - * 单价为加权平均口径(Σ金额÷Σ数量),保证 单价×数量=金额 + * 单价为加权平均口径(Σ金额÷Σ数量),保证 单价×数量=金额;附商品首图 * - * @return array + * @return array */ public function mergedItems(BillModel $bill): array { @@ -33,7 +33,7 @@ class BillDetailService * 用于账单合并导出,单价同为加权平均口径 * * @param array $billIds 账单ID列表 - * @return array + * @return array */ public function mergedItemsOfBills(array $billIds): array { @@ -47,10 +47,10 @@ class BillDetailService /** * 按商品聚合订单明细:数量累加、重量/金额 bc 累加、单价加权平均, - * 按「分类sort → 商品sort」排序 + * 首图取明细快照 image_ids 批量解析,按「分类sort → 商品sort」排序 * * @param Collection $items - * @return array + * @return array */ private function aggregateItems(Collection $items): array { @@ -85,6 +85,7 @@ class BillDetailService 'quantity' => $quantity, 'weight' => $weight, 'amount' => $amount, + 'image_ids' => (array) $first->image_ids, 'category_sort' => (int) ($product->category->sort ?? 9999), 'product_sort' => (int) ($product->sort ?? 9999), ]; @@ -93,6 +94,9 @@ class BillDetailService [$a['category_sort'], $a['product_sort'], $a['product_id']] <=> [$b['category_sort'], $b['product_sort'], $b['product_id']]); + // 首图批量解析(写入 image、移除 image_ids) + app(ItemImageResolver::class)->resolve($rows); + return array_map(static function (array $row): array { unset($row['category_sort'], $row['product_sort']); return $row; diff --git a/tests/Feature/BillPaymentTest.php b/tests/Feature/BillPaymentTest.php index e475db9..0191923 100644 --- a/tests/Feature/BillPaymentTest.php +++ b/tests/Feature/BillPaymentTest.php @@ -11,6 +11,7 @@ use App\Models\PurchaseOrderModel; use App\Models\StoreModel; use App\Models\StoreOrderModel; use App\Models\UserModel; +use Modules\SystemTool\Models\SysFileModel; /** * 账单链路与支付:订单状态随业务链自动推进(采购单完成→配送中、生成账单→已完成); @@ -206,4 +207,73 @@ class BillPaymentTest extends ProcurementTestCase $this->assertSame(0, $bill->payment_id, '账单释放可重新付款'); $this->assertSame('0.00', (string) $store->fresh()->total_purchase_amount); } + + /** 后台门店订单:列表显示关联账单与支付进度,详情附带完整账单 */ + public function test_admin_order_list_and_detail_show_bill_info(): void + { + [$store, $order, $bill] = $this->makeBillViaChain(); + $this->actingAsSysUser(); + + // 列表:账单摘要 + 支付进度(待支付) + $rows = $this->getJson('/order/store')->assertOk()->json('data.data'); + $row = collect($rows)->firstWhere('id', $order->id); + $this->assertNotNull($row['bill'], '已出账订单列表应附账单'); + $this->assertSame($bill->bill_no, $row['bill']['bill_no']); + $this->assertSame('20.00', $row['bill']['total_amount']); + $this->assertSame(BillModel::PAY_STATE_UNPAID, $row['bill']['pay_state']); + $this->assertSame('待支付', $row['bill']['pay_state_name']); + + // 详情:完整账单字段 + 支付进度 + $detail = $this->getJson("/order/store/{$order->id}")->assertOk()->json('data'); + $this->assertSame($bill->bill_no, $detail['bill']['bill_no']); + $this->assertSame('20.00', $detail['bill']['product_amount']); + $this->assertSame(BillModel::PAY_STATE_UNPAID, $detail['bill']['pay_state']); + + // 收款后列表支付进度变为已支付 + $this->putJson("/recon/bill/{$bill->id}/pay")->assertJsonPath('success', true); + $rows = $this->getJson('/order/store')->assertOk()->json('data.data'); + $row = collect($rows)->firstWhere('id', $order->id); + $this->assertSame(BillModel::PAY_STATE_PAID, $row['bill']['pay_state']); + $this->assertSame('已支付', $row['bill']['pay_state_name']); + } + + /** 支付记录详情:凭证图片解析 preview_url(回归:preview_url 为访问器不能 pluck) */ + public function test_payment_detail_returns_voucher_urls(): void + { + $store = StoreModel::factory()->create(); + $user = UserModel::factory()->forStore($store->id)->create(); + + $makeFile = static function (string $path): int { + return (int) SysFileModel::create([ + 'group_id' => 4, + 'disk' => 'local', + 'channel' => 20, + 'file_type' => 10, + 'file_name' => basename($path), + 'file_path' => $path, + 'file_size' => 1024, + 'file_ext' => 'jpg', + 'uploader_id' => 1, + ])->id; + }; + $fileA = $makeFile('voucher/a.jpg'); + $fileB = $makeFile('voucher/b.jpg'); + + $payment = PaymentModel::create([ + 'payment_no' => 'ZF202608140003', + 'store_id' => $store->id, + 'user_id' => $user->id, + 'amount' => '100.00', + 'pay_method' => PaymentModel::METHOD_BANK, + 'voucher_ids' => [$fileA, $fileB], + 'status' => PaymentModel::STATUS_PENDING, + ]); + + $this->actingAsSysUser(); + $data = $this->getJson("/recon/payment/{$payment->id}")->assertOk()->json('data'); + + $this->assertCount(2, $data['payment']['voucher_urls']); + $this->assertStringContainsString('voucher/a.jpg', $data['payment']['voucher_urls'][0]); + $this->assertStringContainsString('voucher/b.jpg', $data['payment']['voucher_urls'][1], '凭证顺序保持提交顺序'); + } } diff --git a/tests/Feature/MiniBillTest.php b/tests/Feature/MiniBillTest.php index bb3b4c5..c990928 100644 --- a/tests/Feature/MiniBillTest.php +++ b/tests/Feature/MiniBillTest.php @@ -9,6 +9,7 @@ use App\Models\StoreModel; use App\Models\StoreOrderItemModel; use App\Models\StoreOrderModel; use App\Models\UserModel; +use Modules\SystemTool\Models\SysFileModel; /** * 小程序账单:支付进度推导(待支付/审核中/已支付)、应结算日期(回款周期)、 @@ -149,6 +150,18 @@ class MiniBillTest extends ProcurementTestCase [$store, $user] = $this->makeStoreWithUser(0); $this->actingAsMiniUser($user); + $file = SysFileModel::create([ + 'group_id' => 1, + 'disk' => 'local', + 'channel' => 10, + 'file_type' => 10, + 'file_name' => '白菜.jpg', + 'file_path' => 'product/baicai.jpg', + 'file_size' => 1024, + 'file_ext' => 'jpg', + 'uploader_id' => 1, + ]); + $bill = $this->makeBill($store); $product = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON]); $order = StoreOrderModel::factory()->create([ @@ -164,9 +177,12 @@ class MiniBillTest extends ProcurementTestCase 'store_id' => $store->id, 'product_id' => $product->id, 'product_name' => '白菜', + 'product_spec' => '30斤/筐', + 'unit' => '筐', 'price' => '5.00', 'quantity' => 20, 'amount' => '100.00', + 'image_ids' => (string) $file->id, ]); $data = $this->getJson("/mini/bill/{$bill->id}")->assertOk()->json('data'); @@ -178,9 +194,13 @@ class MiniBillTest extends ProcurementTestCase $this->assertCount(1, $data['items']); $this->assertSame('白菜', $data['items'][0]['product_name']); + $this->assertSame('30斤/筐', $data['items'][0]['product_spec'], '明细附规格'); + $this->assertSame('筐', $data['items'][0]['unit'], '明细附单位'); $this->assertSame('5.00', $data['items'][0]['price'], '合并明细单价 = Σ金额 ÷ Σ数量'); $this->assertSame(20, $data['items'][0]['quantity']); $this->assertSame('100.00', $data['items'][0]['amount']); + $this->assertStringContainsString('baicai.jpg', (string) $data['items'][0]['image'], '明细附首图URL'); + $this->assertArrayNotHasKey('image_ids', $data['items'][0], 'image_ids 解析后不输出'); $this->assertCount(1, $data['orders']); $this->assertSame($order->order_no, $data['orders'][0]['order_no']); diff --git a/tests/Feature/MiniProductTest.php b/tests/Feature/MiniProductTest.php new file mode 100644 index 0000000..4887005 --- /dev/null +++ b/tests/Feature/MiniProductTest.php @@ -0,0 +1,110 @@ +create(); + $store = StoreModel::factory()->create(['level_id' => $level->id]); + $product = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON]); + ProductPriceModel::factory()->create([ + 'product_id' => $product->id, + 'level_id' => $level->id, + 'price' => $price, + ]); + + return [$store, $product, UserModel::factory()->forStore($store->id)->create(), $level]; + } + + /** 商品详情:未登录浏览 price=null,基础字段完整 */ + public function test_product_detail_guest_sees_null_price(): void + { + [, $product] = $this->makeStoreWithProduct(); + + $data = $this->getJson("/mini/product/{$product->id}")->assertOk()->json('data'); + + $this->assertSame($product->name, $data['name']); + $this->assertSame($product->spec, $data['spec']); + $this->assertSame($product->unit, $data['unit']); + $this->assertNull($data['price'], '未登录不显示价格'); + $this->assertArrayNotHasKey('cost_price', $data, '成本价不得输出到小程序端'); + } + + /** 商品详情:登录门店按等级显示固定价 */ + public function test_product_detail_store_user_sees_level_price(): void + { + [, $product, $user] = $this->makeStoreWithProduct('6.50'); + $this->actingAsMiniUser($user); + + $data = $this->getJson("/mini/product/{$product->id}")->assertOk()->json('data'); + + $this->assertSame('6.50', $data['price']); + } + + /** 商品详情:百分比计价按最新成本价上浮换算 */ + public function test_product_detail_percent_price_calculated_from_cost(): void + { + [, $product, $user, $level] = $this->makeStoreWithProduct(); + ProductPriceModel::where('product_id', $product->id)->where('level_id', $level->id) + ->update(['price_type' => ProductPriceModel::PRICE_TYPE_PERCENT, 'percent' => '30']); + $product->update(['cost_price' => '20.00']); + $this->actingAsMiniUser($user); + + $data = $this->getJson("/mini/product/{$product->id}")->assertOk()->json('data'); + + $this->assertSame('26.00', $data['price'], '20 元上浮 30% = 26.00'); + } + + /** 商品详情:下架/不存在拒绝 */ + public function test_product_detail_off_shelf_or_missing_rejected(): void + { + [, $product] = $this->makeStoreWithProduct(); + $product->update(['status' => ProductModel::STATUS_OFF]); + + $this->getJson("/mini/product/{$product->id}")->assertJsonPath('success', false); + $this->getJson('/mini/product/99999')->assertJsonPath('success', false); + } + + /** 商品列表:登录门店显示等级价(回归:曾数组误用对象访问导致 500) */ + public function test_product_list_store_user_sees_level_price(): void + { + [, $product, $user] = $this->makeStoreWithProduct('7.00'); + $this->actingAsMiniUser($user); + + $rows = $this->getJson('/mini/product/list')->assertOk()->json('data.data'); + $row = collect($rows)->firstWhere('id', $product->id); + + $this->assertNotNull($row); + $this->assertSame('7.00', $row['price']); + $this->assertArrayNotHasKey('cost_price', $row, '成本价不得输出到小程序端'); + } + + /** 商品列表:未登录 price=null */ + public function test_product_list_guest_sees_null_price(): void + { + [, $product] = $this->makeStoreWithProduct(); + + $rows = $this->getJson('/mini/product/list')->assertOk()->json('data.data'); + $row = collect($rows)->firstWhere('id', $product->id); + + $this->assertNotNull($row); + $this->assertNull($row['price']); + } +} diff --git a/tests/Feature/StoreOrderTest.php b/tests/Feature/StoreOrderTest.php index 8e8fd99..7c946b1 100644 --- a/tests/Feature/StoreOrderTest.php +++ b/tests/Feature/StoreOrderTest.php @@ -8,6 +8,7 @@ use App\Models\ProductPriceModel; use App\Models\StoreModel; use App\Models\StoreOrderModel; use App\Models\UserModel; +use Modules\SystemTool\Models\SysFileModel; /** * 小程序下单:等级价快照、服务端重算总价、取消限制、门店数据隔离 @@ -321,4 +322,48 @@ class StoreOrderTest extends ProcurementTestCase ])->assertJsonPath('success', false); $this->assertSame(StoreOrderModel::STATUS_SUMMARIZED, $pending1->fresh()->status); } + + /** 订单列表商品预览:附带首图/规格/单位供小程序端展示 */ + public function test_order_list_preview_includes_image_spec_and_unit(): void + { + $file = SysFileModel::create([ + 'group_id' => 1, + 'disk' => 'local', + 'channel' => 10, + 'file_type' => 10, + 'file_name' => '白菜.jpg', + 'file_path' => 'product/baicai.jpg', + 'file_size' => 1024, + 'file_ext' => 'jpg', + 'uploader_id' => 1, + ]); + + $level = CustomerLevelModel::factory()->create(); + $store = StoreModel::factory()->create(['level_id' => $level->id]); + $product = ProductModel::factory()->create([ + 'status' => ProductModel::STATUS_ON, + 'spec' => '30斤/筐', + 'unit' => '筐', + 'image_ids' => (string) $file->id, + ]); + ProductPriceModel::factory()->create([ + 'product_id' => $product->id, + 'level_id' => $level->id, + 'price' => '5.00', + ]); + $user = UserModel::factory()->forStore($store->id)->create(); + + $this->actingAsMiniUser($user); + $this->postJson('/mini/order', ['items' => [['product_id' => $product->id, 'quantity' => 2]]]) + ->assertJsonPath('success', true); + + $rows = $this->getJson('/mini/order')->assertOk()->json('data.data'); + $preview = $rows[0]['items'][0]; + + $this->assertSame($product->name, $preview['product_name']); + $this->assertSame('30斤/筐', $preview['product_spec'], '预览附规格'); + $this->assertSame('筐', $preview['unit'], '预览附单位'); + $this->assertStringContainsString('baicai.jpg', (string) $preview['image'], '预览附首图URL'); + $this->assertArrayNotHasKey('image_ids', $preview, 'image_ids 解析后不输出'); + } } diff --git a/web/domain/iStoreOrder.ts b/web/domain/iStoreOrder.ts index 1e082cc..084973f 100644 --- a/web/domain/iStoreOrder.ts +++ b/web/domain/iStoreOrder.ts @@ -38,6 +38,42 @@ export interface IStoreOrderItemUpdate { remark: string; } +/** 订单关联账单(列表附带摘要、详情附带完整字段;pay_state 由后端推导) */ +export interface IStoreOrderBill { + id: number; + bill_no: string; + bill_date?: string; + /** 商品金额 */ + product_amount?: string; + /** 配送费 */ + delivery_fee?: string; + box_num?: number; + tray_num?: number; + box_price?: string; + tray_price?: string; + /** 附加金额 = 筐×筐单价 + 托盘×托盘单价 */ + added_amount?: string; + /** 账单总金额 */ + total_amount: string; + /** 支付状态:0未支付 1已支付 */ + status: number; + /** 关联支付记录ID(0=未发起支付) */ + payment_id?: number; + /** 支付进度:0待支付 1审核中 2已支付 */ + pay_state?: number; + pay_state_name?: string; + paid_at?: string | null; + pay_remark?: string; + created_at?: string; +} + +/** 账单支付进度映射(与小程序端口径一致) */ +export const BILL_PAY_STATE_MAP: Record = { + 0: { text: '待支付', color: 'warning' }, + 1: { text: '审核中', color: 'processing' }, + 2: { text: '已支付', color: 'success' }, +}; + /** 门店订单 */ export default interface IStoreOrder { id?: number; @@ -46,6 +82,8 @@ export default interface IStoreOrder { purchase_id?: number; /** 关联账单ID(采购单完成后按门店生成账单时回写) */ bill_id?: number; + /** 关联账单(生成账单后由接口附带) */ + bill?: IStoreOrderBill | null; store?: { id: number; name: string; diff --git a/web/pages/order/store.tsx b/web/pages/order/store.tsx index 9710c2d..0525758 100644 --- a/web/pages/order/store.tsx +++ b/web/pages/order/store.tsx @@ -23,7 +23,7 @@ import type { XinTableProps, } from '@/components/XinTable/typings.ts'; import type IStoreOrder from '@/domain/iStoreOrder.ts'; -import { STORE_ORDER_STATUS_MAP } from '@/domain/iStoreOrder.ts'; +import { BILL_PAY_STATE_MAP, STORE_ORDER_STATUS_MAP } from '@/domain/iStoreOrder.ts'; import { getStoreOrder, updateOrderStatus, @@ -312,10 +312,29 @@ const StoreOrderPage: React.FC = () => { ) : '-' }, { - title: '账单ID', + title: '账单信息', dataIndex: 'bill_id', - valueType: 'digit', hideInForm: true, + hideInSearch: true, + width: 240, + render: (_, record) => record.bill ? ( + +
+ 账单号: + {record.bill.bill_no} +
+
+ 支付状态: + + {record.bill.pay_state_name ?? BILL_PAY_STATE_MAP[record.bill.pay_state ?? 0]?.text} + +
+
+ 账单金额: + {record.bill.total_amount} ¥ +
+
+ ) : '-' }, { title: '操作栏', @@ -495,6 +514,32 @@ const StoreOrderPage: React.FC = () => { ) : null} + {/* 关联账单信息(生成账单后展示) */} + {detail.bill ? ( + + {detail.bill.bill_no} + {detail.bill.bill_date ?? '-'} + + + {detail.bill.pay_state_name ?? BILL_PAY_STATE_MAP[detail.bill.pay_state ?? 0]?.text} + + + ¥{detail.bill.product_amount ?? '-'} + ¥{detail.bill.delivery_fee ?? '-'} + + ¥{detail.bill.added_amount ?? '0.00'} + + (筐{detail.bill.box_num ?? 0}/托盘{detail.bill.tray_num ?? 0}) + + + + ¥{detail.bill.total_amount} + + {detail.bill.paid_at ?? '-'} + {detail.bill.pay_remark || '-'} + + ) : null} + {/* 商品明细(商城模式:首图 + 单价 × 订货量 + 金额) */} 商品明细