diff --git a/.phpunit.result.cache b/.phpunit.result.cache index 409721b..f28bb4f 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},"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.007,"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.01,"Tests\\Feature\\StoreOrderTest::test_place_order_snapshots_level_price_and_recalculates":0.008,"Tests\\Feature\\StoreOrderTest::test_client_supplied_amount_is_ignored":0.007,"Tests\\Feature\\StoreOrderTest::test_product_without_level_price_rejected":0.009,"Tests\\Feature\\StoreOrderTest::test_cancel_only_pending_orders":0.011,"Tests\\Feature\\StoreOrderTest::test_summarized_order_cannot_be_cancelled":0.009,"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.018,"Tests\\Feature\\PurchaseGenerateTest::test_generate_without_pending_orders_fails":0.006,"Tests\\Feature\\PurchaseGenerateTest::test_generate_is_idempotent":0.022,"Tests\\Feature\\AllocationTest::test_allocation_conserves_amount_with_tail_correction":0.076,"Tests\\Feature\\AllocationTest::test_allocation_follows_order_ratio":0.022,"Tests\\Feature\\AllocationTest::test_allocation_is_idempotent":0.028,"Tests\\Feature\\AllocationTest::test_allocation_rejected_without_actual_amount":0.021,"Tests\\Feature\\DebugGenerateTest::test_debug_generate":0.047,"Tests\\Feature\\ReconciliationTest::test_build_creates_reconciliation_items":0.018,"Tests\\Feature\\ReconciliationTest::test_build_filters_by_supplier":0.021,"Tests\\Feature\\ReconciliationTest::test_update_item_recalculates_diff_and_header":0.02,"Tests\\Feature\\ReconciliationTest::test_toggle_reconciled_flag":0.02,"Tests\\Feature\\ReconciliationTest::test_settle_creates_settlements_per_store":0.022,"Tests\\Feature\\StatementTest::test_generate_snapshots_payment_cycle":0.011,"Tests\\Feature\\StatementTest::test_statement_isolated_between_stores":0.016,"Tests\\Feature\\StatementTest::test_generate_excludes_cancelled_and_used_items":0.017,"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.061,"Tests\\Feature\\ExportTest::test_export_category_filters_to_vegetables":0.018,"Tests\\Feature\\ExportTest::test_export_pdf":1.295,"Tests\\Feature\\ExportTest::test_export_invalid_format_rejected":0.017,"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.003,"Tests\\Feature\\CartTest::test_add_to_cart_succeeds":0.045,"Tests\\Feature\\CartTest::test_duplicate_add_merges_quantity":0.009,"Tests\\Feature\\CartTest::test_off_shelf_product_rejected":0.006,"Tests\\Feature\\CartTest::test_soft_deleted_product_rejected":0.006,"Tests\\Feature\\CartTest::test_product_without_level_price_rejected":0.006,"Tests\\Feature\\CartTest::test_unbound_user_rejected":0.01,"Tests\\Feature\\CartTest::test_store_without_level_rejected":0.006,"Tests\\Feature\\CartTest::test_list_with_prices_amounts_and_totals":0.01,"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.009,"Tests\\Feature\\CartTest::test_update_missing_item_rejected":0.009,"Tests\\Feature\\CartTest::test_invalid_quantity_rejected":0.009,"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.007,"Tests\\Feature\\ProductCostPriceTest::test_create_product_with_cost_and_percent_pricing":0.011,"Tests\\Feature\\ProductCostPriceTest::test_create_percent_row_without_percent_fails":0.007,"Tests\\Feature\\ProductCostPriceTest::test_update_switches_fixed_to_percent":0.009,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_cost_only_notifies_percent_level_stores":0.01,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_percent_row_update":0.008,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_mixed_row_updates_cost_and_level":0.008,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_empty_row_rejected":0.006,"Tests\\Feature\\ProductCostPriceTest::test_price_matrix_includes_cost_and_percent_columns":0.01,"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.006,"Tests\\Feature\\ProductPriceTest::test_unbound_user_can_browse_products_without_price_but_cannot_cart":0.008,"Tests\\Feature\\MiniAuthTest::test_login_issues_token_for_registered_user":0.011,"Tests\\Feature\\MiniAuthTest::test_login_rejects_unregistered_openid":0.002,"Tests\\Feature\\MiniAuthTest::test_register_binds_store_and_issues_token":0.011,"Tests\\Feature\\MiniAuthTest::test_register_rejects_unknown_store":0.002,"Tests\\Feature\\MiniAuthTest::test_register_rejects_duplicate_openid":0.003,"Tests\\Feature\\MiniStoreTest::test_info_returns_bound_store":0.005,"Tests\\Feature\\MiniStoreTest::test_update_info":0.005,"Tests\\Feature\\MiniStoreTest::test_update_info_ignores_readonly_fields":0.005,"Tests\\Feature\\MiniStoreTest::test_requires_bound_store":0.006,"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.016,"Tests\\Feature\\StoreOrderTest::test_container_update_rejected_in_other_statuses":0.017,"Tests\\Feature\\StoreOrderTest::test_container_update_heals_legacy_product_amount":0.016,"Tests\\Feature\\StoreOrderTest::test_container_update_validates_negative_numbers":0.012,"Tests\\Feature\\PurchaseGenerateTest::test_generate_without_accepted_orders_fails":0.011,"Tests\\Feature\\PurchaseGenerateTest::test_generate_with_selected_order_ids":0.021,"Tests\\Feature\\StoreOrderItemTest::test_place_order_snapshots_full_product_info":0.008,"Tests\\Feature\\StoreOrderItemTest::test_mini_detail_hides_cost_price":0.01,"Tests\\Feature\\StoreOrderItemTest::test_admin_detail_shows_cost_price_and_supplier":0.013,"Tests\\Feature\\StoreOrderItemTest::test_update_item_recalculates_order_totals":0.013,"Tests\\Feature\\StoreOrderItemTest::test_update_item_rejected_when_completed_or_cancelled":0.016,"Tests\\Feature\\StoreOrderItemTest::test_update_item_validates_negative_price":0.015,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_updates_snapshot_from_product":0.014,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_recalculates_percent_price_with_latest_cost":0.014,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_rejected_when_product_deleted":0.012,"Tests\\Feature\\StoreOrderItemTest::test_order_list_search_by_product_name":0.017,"Tests\\Feature\\StoreOrderTest::test_soft_delete_only_cancelled_orders":0.019,"Tests\\Feature\\StoreOrderTest::test_soft_delete_rejected_for_non_cancelled_orders":0.055,"Tests\\Feature\\StoreOrderTest::test_soft_delete_twice_rejected":0.012,"Tests\\Feature\\PurchaseEditTest::test_detail_returns_store_matrix":0.022,"Tests\\Feature\\PurchaseEditTest::test_update_cell_syncs_order_item_and_totals":0.022,"Tests\\Feature\\PurchaseEditTest::test_update_row_cost_syncs_all_order_items":0.026,"Tests\\Feature\\PurchaseEditTest::test_update_row_weight_distributed_by_quantity":0.032,"Tests\\Feature\\PurchaseEditTest::test_update_row_requires_at_least_one_field":0.018,"Tests\\Feature\\PurchaseEditTest::test_update_cell_rejects_negative_quantity":0.018,"Tests\\Feature\\PurchaseGenerateTest::test_generate_aggregates_order_items_into_header":0.019,"Tests\\Feature\\ReconciliationTest::test_build_excludes_unfinished_orders":0.018,"Tests\\Feature\\PurchaseEditTest::test_update_row_syncs_product_attributes":0.021,"Tests\\Feature\\PurchaseEditTest::test_update_row_syncs_to_product_catalog":0.027,"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.018,"Tests\\Feature\\PurchaseEditTest::test_edits_rejected_when_purchase_completed":0.025,"Tests\\Feature\\PurchaseEditTest::test_update_row_does_not_touch_manual_weight":0.019}} \ No newline at end of file +{"version":2,"defects":{"Tests\\Feature\\ProductPriceTest::test_product_list_returns_price_for_store_level":8,"Tests\\Feature\\ProductPriceTest::test_store_without_level_is_rejected":8,"Tests\\Feature\\ProductPriceTest::test_batch_price_updates_and_notifies_affected_stores":7,"Tests\\Feature\\ProductPriceTest::test_price_matrix_structure":8,"Tests\\Feature\\StoreOrderTest::test_place_order_snapshots_level_price_and_recalculates":8,"Tests\\Feature\\StoreOrderTest::test_client_supplied_amount_is_ignored":8,"Tests\\Feature\\StoreOrderTest::test_product_without_level_price_rejected":8,"Tests\\Feature\\StoreOrderTest::test_cancel_only_pending_orders":8,"Tests\\Feature\\StoreOrderTest::test_summarized_order_cannot_be_cancelled":8,"Tests\\Feature\\StoreOrderTest::test_order_data_isolated_between_stores":8,"Tests\\Feature\\PurchaseGenerateTest::test_generate_aggregates_orders_by_product":7,"Tests\\Feature\\PurchaseGenerateTest::test_generate_writes_back_order_status":7,"Tests\\Feature\\PurchaseGenerateTest::test_generate_without_pending_orders_fails":8,"Tests\\Feature\\PurchaseGenerateTest::test_generate_is_idempotent":7,"Tests\\Feature\\AllocationTest::test_allocation_conserves_amount_with_tail_correction":7,"Tests\\Feature\\AllocationTest::test_allocation_follows_order_ratio":7,"Tests\\Feature\\AllocationTest::test_allocation_is_idempotent":7,"Tests\\Feature\\AllocationTest::test_allocation_rejected_without_actual_amount":8,"Tests\\Feature\\MiniAuthTest::test_login_creates_user_and_issues_token":7,"Tests\\Feature\\MiniAuthTest::test_phone_binding_matches_store":8,"Tests\\Feature\\MiniAuthTest::test_phone_no_match_stays_pending":8,"Tests\\Feature\\ExportTest::test_export_xlsx_with_encoded_chinese_filename":7,"Tests\\Feature\\CartTest::test_list_with_prices_amounts_and_totals":7,"Tests\\Feature\\CartTest::test_list_marks_off_shelf_item_unbuyable":7,"Tests\\Feature\\DebugCartListTest::test_dump_cart_list_response":5,"Tests\\Feature\\ProductCostPriceTest::test_create_product_with_cost_and_percent_pricing":7,"Tests\\Feature\\ProductCostPriceTest::test_create_percent_row_without_percent_fails":7,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_cost_only_notifies_percent_level_stores":7,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_empty_row_rejected":7,"Tests\\Feature\\MiniAuthTest::test_disabled_user_cannot_login":8,"Tests\\Feature\\MiniAuthTest::test_mini_token_cannot_access_admin_api":8,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_percent_row_update":7,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_mixed_row_updates_cost_and_level":7,"Tests\\Feature\\ReconciliationTest::test_build_creates_reconciliation_items":8,"Tests\\Feature\\ReconciliationTest::test_build_filters_by_supplier":8,"Tests\\Feature\\ReconciliationTest::test_update_item_recalculates_diff_and_header":8,"Tests\\Feature\\ReconciliationTest::test_toggle_reconciled_flag":8,"Tests\\Feature\\ReconciliationTest::test_settle_creates_settlements_per_store":8,"Tests\\Feature\\ProductCostPriceTest::test_mini_list_returns_actual_price_without_cost_price":8,"Tests\\Feature\\ProductCostPriceTest::test_mini_order_uses_actual_price_for_percent_row":8,"Tests\\Feature\\ProductCostPriceTest::test_mini_cart_uses_actual_price_for_percent_row":8,"Tests\\Feature\\StoreOrderTest::test_container_update_recalculates_amounts":8,"Tests\\Feature\\StoreOrderTest::test_container_update_allowed_in_editable_statuses":8,"Tests\\Feature\\StoreOrderTest::test_container_update_rejected_in_other_statuses":8,"Tests\\Feature\\StoreOrderTest::test_container_update_heals_legacy_product_amount":8,"Tests\\Feature\\StoreOrderTest::test_container_update_validates_negative_numbers":8,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_rejected_when_product_deleted":7,"Tests\\Feature\\StoreOrderItemTest::test_order_list_search_by_product_name":7,"Tests\\Feature\\PurchaseEditTest::test_detail_returns_store_matrix":7,"Tests\\Feature\\PurchaseEditTest::test_update_cell_syncs_order_item_and_totals":7,"Tests\\Feature\\PurchaseEditTest::test_update_row_cost_syncs_all_order_items":7,"Tests\\Feature\\PurchaseEditTest::test_update_row_weight_distributed_by_quantity":7,"Tests\\Feature\\PurchaseEditTest::test_update_row_syncs_product_attributes":7,"Tests\\Feature\\PurchaseGenerateTest::test_generate_aggregates_order_items_into_header":7,"Tests\\Feature\\ExportTest::test_export_category_filters_to_vegetables":7,"Tests\\Feature\\ExportTest::test_export_pdf":7,"Tests\\Feature\\PurchaseEditTest::test_update_row_does_not_touch_manual_weight":7,"Tests\\Feature\\PurchaseEditTest::test_update_row_syncs_to_product_catalog":7,"Tests\\Feature\\PurchaseEditTest::test_update_cell_weight_cascades_totals":7,"Tests\\Feature\\PurchaseEditTest::test_cell_sync_refreshes_snapshot_and_cascades":7,"Tests\\Feature\\PurchaseEditTest::test_edits_rejected_when_purchase_completed":7,"Tests\\Feature\\StoreOrderItemTest::test_update_item_recalculates_order_totals":7,"Tests\\Feature\\StoreOrderItemTest::test_update_item_validates_negative_price":7,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_updates_snapshot_from_product":7,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_recalculates_percent_price_with_latest_cost":7},"times":{"Tests\\Feature\\ExampleTest::test_the_application_returns_a_successful_response":0.003,"Tests\\Feature\\ProductPriceTest::test_product_list_returns_price_for_store_level":0.007,"Tests\\Feature\\ProductPriceTest::test_store_without_level_is_rejected":0.007,"Tests\\Feature\\ProductPriceTest::test_batch_price_updates_and_notifies_affected_stores":0.009,"Tests\\Feature\\ProductPriceTest::test_price_matrix_structure":0.007,"Tests\\Feature\\StoreOrderTest::test_place_order_snapshots_level_price_and_recalculates":0.012,"Tests\\Feature\\StoreOrderTest::test_client_supplied_amount_is_ignored":0.014,"Tests\\Feature\\StoreOrderTest::test_product_without_level_price_rejected":0.014,"Tests\\Feature\\StoreOrderTest::test_cancel_only_pending_orders":0.016,"Tests\\Feature\\StoreOrderTest::test_summarized_order_cannot_be_cancelled":0.015,"Tests\\Feature\\StoreOrderTest::test_order_data_isolated_between_stores":0.021,"Tests\\Feature\\PurchaseGenerateTest::test_generate_aggregates_orders_by_product":0.028,"Tests\\Feature\\PurchaseGenerateTest::test_generate_writes_back_order_status":0.048,"Tests\\Feature\\PurchaseGenerateTest::test_generate_without_pending_orders_fails":0.006,"Tests\\Feature\\PurchaseGenerateTest::test_generate_is_idempotent":0.041,"Tests\\Feature\\AllocationTest::test_allocation_conserves_amount_with_tail_correction":0.076,"Tests\\Feature\\AllocationTest::test_allocation_follows_order_ratio":0.022,"Tests\\Feature\\AllocationTest::test_allocation_is_idempotent":0.028,"Tests\\Feature\\AllocationTest::test_allocation_rejected_without_actual_amount":0.021,"Tests\\Feature\\DebugGenerateTest::test_debug_generate":0.047,"Tests\\Feature\\ReconciliationTest::test_build_creates_reconciliation_items":0.018,"Tests\\Feature\\ReconciliationTest::test_build_filters_by_supplier":0.021,"Tests\\Feature\\ReconciliationTest::test_update_item_recalculates_diff_and_header":0.019,"Tests\\Feature\\ReconciliationTest::test_toggle_reconciled_flag":0.098,"Tests\\Feature\\ReconciliationTest::test_settle_creates_settlements_per_store":0.076,"Tests\\Feature\\StatementTest::test_generate_snapshots_payment_cycle":0.034,"Tests\\Feature\\StatementTest::test_statement_isolated_between_stores":0.031,"Tests\\Feature\\StatementTest::test_generate_excludes_cancelled_and_used_items":0.042,"Tests\\Feature\\MiniAuthTest::test_login_creates_user_and_issues_token":0.056,"Tests\\Feature\\MiniAuthTest::test_disabled_user_cannot_login":0.002,"Tests\\Feature\\MiniAuthTest::test_phone_binding_matches_store":0.005,"Tests\\Feature\\MiniAuthTest::test_phone_no_match_stays_pending":0.001,"Tests\\Feature\\MiniAuthTest::test_sys_token_cannot_access_mini":0.005,"Tests\\Feature\\MiniAuthTest::test_mini_token_cannot_access_admin_api":0.004,"Tests\\Feature\\ExportTest::test_export_xlsx_with_encoded_chinese_filename":0.021,"Tests\\Feature\\ExportTest::test_export_category_filters_to_vegetables":0.015,"Tests\\Feature\\ExportTest::test_export_pdf":0.015,"Tests\\Feature\\ExportTest::test_export_invalid_format_rejected":0.016,"Tests\\Feature\\ExportTest::test_export_requires_export_permission":0.015,"Tests\\Unit\\ExampleTest::test_that_true_is_true":0.001,"Tests\\Feature\\MiniAuthTest::test_login_fails_when_wechat_rejects_code":0.003,"Tests\\Feature\\CartTest::test_add_to_cart_succeeds":0.045,"Tests\\Feature\\CartTest::test_duplicate_add_merges_quantity":0.009,"Tests\\Feature\\CartTest::test_off_shelf_product_rejected":0.006,"Tests\\Feature\\CartTest::test_soft_deleted_product_rejected":0.006,"Tests\\Feature\\CartTest::test_product_without_level_price_rejected":0.006,"Tests\\Feature\\CartTest::test_unbound_user_rejected":0.01,"Tests\\Feature\\CartTest::test_store_without_level_rejected":0.006,"Tests\\Feature\\CartTest::test_list_with_prices_amounts_and_totals":0.011,"Tests\\Feature\\CartTest::test_list_marks_off_shelf_item_unbuyable":0.01,"Tests\\Feature\\CartTest::test_empty_cart_returns_empty_items":0.005,"Tests\\Feature\\CartTest::test_update_quantity":0.008,"Tests\\Feature\\CartTest::test_update_other_users_item_rejected":0.009,"Tests\\Feature\\CartTest::test_update_missing_item_rejected":0.009,"Tests\\Feature\\CartTest::test_invalid_quantity_rejected":0.006,"Tests\\Feature\\CartTest::test_delete_item":0.007,"Tests\\Feature\\CartTest::test_delete_other_users_item_rejected":0.009,"Tests\\Feature\\CartTest::test_clear_only_own_cart":0.012,"Tests\\Feature\\CartTest::test_unauthenticated_returns_401":0.002,"Tests\\Feature\\CartTest::test_cart_isolated_between_users":0.008,"Tests\\Feature\\DebugCartListTest::test_dump_cart_list_response":0.057,"Tests\\Feature\\ProductCostPriceTest::test_calc_actual_price_fixed_and_percent":0.001,"Tests\\Feature\\ProductCostPriceTest::test_calc_actual_price_edge_cases":0.001,"Tests\\Feature\\ProductCostPriceTest::test_admin_list_contains_cost_price_and_actual_price":0.01,"Tests\\Feature\\ProductCostPriceTest::test_mini_list_returns_actual_price_without_cost_price":0.007,"Tests\\Feature\\ProductCostPriceTest::test_create_product_with_cost_and_percent_pricing":0.011,"Tests\\Feature\\ProductCostPriceTest::test_create_percent_row_without_percent_fails":0.007,"Tests\\Feature\\ProductCostPriceTest::test_update_switches_fixed_to_percent":0.009,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_cost_only_notifies_percent_level_stores":0.01,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_percent_row_update":0.008,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_mixed_row_updates_cost_and_level":0.008,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_empty_row_rejected":0.006,"Tests\\Feature\\ProductCostPriceTest::test_price_matrix_includes_cost_and_percent_columns":0.007,"Tests\\Feature\\ProductCostPriceTest::test_mini_order_uses_actual_price_for_percent_row":0.007,"Tests\\Feature\\ProductCostPriceTest::test_mini_cart_uses_actual_price_for_percent_row":0.008,"Tests\\Feature\\PurchaseGenerateTest::test_generate_uses_min_actual_price_across_types":0.014,"Tests\\Feature\\ProductPriceTest::test_store_without_level_sees_products_without_price":0.006,"Tests\\Feature\\ProductPriceTest::test_unbound_user_can_browse_products_without_price_but_cannot_cart":0.008,"Tests\\Feature\\MiniAuthTest::test_login_issues_token_for_registered_user":0.008,"Tests\\Feature\\MiniAuthTest::test_login_rejects_unregistered_openid":0.002,"Tests\\Feature\\MiniAuthTest::test_register_binds_store_and_issues_token":0.009,"Tests\\Feature\\MiniAuthTest::test_register_rejects_unknown_store":0.002,"Tests\\Feature\\MiniAuthTest::test_register_rejects_duplicate_openid":0.002,"Tests\\Feature\\MiniStoreTest::test_info_returns_bound_store":0.005,"Tests\\Feature\\MiniStoreTest::test_update_info":0.007,"Tests\\Feature\\MiniStoreTest::test_update_info_ignores_readonly_fields":0.005,"Tests\\Feature\\MiniStoreTest::test_requires_bound_store":0.007,"Tests\\Feature\\MiniStoreTest::test_update_info_validates_length":0.005,"Tests\\Feature\\StoreOrderTest::test_container_update_recalculates_amounts":0.022,"Tests\\Feature\\StoreOrderTest::test_container_update_allowed_in_editable_statuses":0.025,"Tests\\Feature\\StoreOrderTest::test_container_update_rejected_in_other_statuses":0.028,"Tests\\Feature\\StoreOrderTest::test_container_update_heals_legacy_product_amount":0.021,"Tests\\Feature\\StoreOrderTest::test_container_update_validates_negative_numbers":0.022,"Tests\\Feature\\PurchaseGenerateTest::test_generate_without_accepted_orders_fails":0.024,"Tests\\Feature\\PurchaseGenerateTest::test_generate_with_selected_order_ids":0.026,"Tests\\Feature\\StoreOrderItemTest::test_place_order_snapshots_full_product_info":0.012,"Tests\\Feature\\StoreOrderItemTest::test_mini_detail_hides_cost_price":0.025,"Tests\\Feature\\StoreOrderItemTest::test_admin_detail_shows_cost_price_and_supplier":0.023,"Tests\\Feature\\StoreOrderItemTest::test_update_item_recalculates_order_totals":0.023,"Tests\\Feature\\StoreOrderItemTest::test_update_item_rejected_when_completed_or_cancelled":0.035,"Tests\\Feature\\StoreOrderItemTest::test_update_item_validates_negative_price":0.023,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_updates_snapshot_from_product":0.019,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_recalculates_percent_price_with_latest_cost":0.03,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_rejected_when_product_deleted":0.022,"Tests\\Feature\\StoreOrderItemTest::test_order_list_search_by_product_name":0.032,"Tests\\Feature\\StoreOrderTest::test_soft_delete_only_cancelled_orders":0.049,"Tests\\Feature\\StoreOrderTest::test_soft_delete_rejected_for_non_cancelled_orders":0.068,"Tests\\Feature\\StoreOrderTest::test_soft_delete_twice_rejected":0.013,"Tests\\Feature\\PurchaseEditTest::test_detail_returns_store_matrix":0.024,"Tests\\Feature\\PurchaseEditTest::test_update_cell_syncs_order_item_and_totals":0.019,"Tests\\Feature\\PurchaseEditTest::test_update_row_cost_syncs_all_order_items":0.018,"Tests\\Feature\\PurchaseEditTest::test_update_row_weight_distributed_by_quantity":0.032,"Tests\\Feature\\PurchaseEditTest::test_update_row_requires_at_least_one_field":0.022,"Tests\\Feature\\PurchaseEditTest::test_update_cell_rejects_negative_quantity":0.019,"Tests\\Feature\\PurchaseGenerateTest::test_generate_aggregates_order_items_into_header":0.038,"Tests\\Feature\\ReconciliationTest::test_build_excludes_unfinished_orders":0.025,"Tests\\Feature\\PurchaseEditTest::test_update_row_syncs_product_attributes":0.022,"Tests\\Feature\\PurchaseEditTest::test_update_row_syncs_to_product_catalog":0.02,"Tests\\Feature\\PurchaseEditTest::test_cell_detail_returns_items_with_order_info":0.023,"Tests\\Feature\\PurchaseEditTest::test_cell_detail_rejects_missing_params":0.023,"Tests\\Feature\\PurchaseEditTest::test_update_cell_weight_cascades_totals":0.023,"Tests\\Feature\\PurchaseEditTest::test_cell_sync_refreshes_snapshot_and_cascades":0.02,"Tests\\Feature\\PurchaseEditTest::test_edits_rejected_when_purchase_completed":0.058,"Tests\\Feature\\PurchaseEditTest::test_update_row_does_not_touch_manual_weight":0.019,"Tests\\Feature\\PurchaseEditTest::test_store_summary_returns_aggregated_items":0.07,"Tests\\Feature\\PurchaseEditTest::test_store_summary_aggregates_multiple_orders_with_weighted_price":0.051,"Tests\\Feature\\PurchaseEditTest::test_store_summary_rejects_missing_or_invalid_params":0.057}} \ No newline at end of file diff --git a/app/Http/Controllers/Purchase/PurchaseOrderController.php b/app/Http/Controllers/Purchase/PurchaseOrderController.php index 2ec64fc..289d10c 100644 --- a/app/Http/Controllers/Purchase/PurchaseOrderController.php +++ b/app/Http/Controllers/Purchase/PurchaseOrderController.php @@ -259,6 +259,84 @@ class PurchaseOrderController extends BaseController ]); } + /** + * 门店购买详情:采购单内指定门店的采购汇总(按商品聚合) + * 单价为加权平均口径(Σ金额÷Σ数量),保证 单价×数量=预计金额 + */ + #[GetRoute(route: '/{id}/store', authorize: 'query', where: ['id' => '[0-9]+'])] + public function storeSummary(int $id, Request $request): JsonResponse + { + $storeId = (int) $request->query('store_id', 0); + if ($storeId <= 0) { + throw new RepositoryException('缺少门店参数'); + } + $purchase = PurchaseOrderModel::find($id); + if (empty($purchase)) { + throw new RepositoryException('采购单不存在'); + } + + $items = StoreOrderItemModel::query() + ->join('store_order', 'store_order.id', '=', 'store_order_item.order_id') + ->where('store_order_item.purchase_id', $purchase->id) + ->where('store_order_item.store_id', $storeId) + ->whereNull('store_order.deleted_at') + ->select('store_order_item.*') + ->orderBy('store_order_item.id') + ->get(); + + // 排序键:分类 sort → 商品 sort(与明细矩阵同序) + $products = ProductModel::withTrashed() + ->with('category:id,sort') + ->whereIn('id', $items->pluck('product_id')->unique()) + ->get() + ->keyBy('id'); + + $rows = []; + foreach ($items->groupBy('product_id') as $productId => $group) { + $product = $products->get((int) $productId); + $first = $group->first(); + // 采购总数量(包数) + $quantity = 0; + // 预计金额 = Σ 明细 amount + $amount = '0'; + // 总重量 + $weight = '0'; + foreach ($group as $item) { + $weight = bcadd($weight, (string) $item->weight, 3); + $quantity += (int) $item->quantity; + $amount = bcadd($amount, (string) $item->amount, 2); + } + + $rows[] = [ + 'product_id' => (int) $productId, + 'product_name' => $first->product_name, + 'product_spec' => $first->product_spec, // 包规 + 'unit' => $first->unit, // 单位 + 'price' => $quantity > 0 // 加权平均单价 + ? bcdiv($amount, (string) $quantity, 2) + : (string) $first->price, + 'quantity' => $quantity, + 'amount' => $amount, + 'weight' => $weight, + 'category_sort' => (int) ($product->category->sort ?? 9999), + 'product_sort' => (int) ($product->sort ?? 9999), + ]; + } + usort($rows, static fn (array $a, array $b): int => + [$a['category_sort'], $a['product_sort'], $a['product_id']] + <=> [$b['category_sort'], $b['product_sort'], $b['product_id']]); + + $store = StoreModel::withTrashed()->find($storeId); + + return $this->success([ + 'store' => $store ? ['id' => $store->id, 'name' => $store->name] : null, + 'items' => array_map(static function (array $row): array { + unset($row['category_sort'], $row['product_sort']); + return $row; + }, $rows), + ]); + } + /** * 商品行修改:品名/供应商/包规/单位/成本 * @throws Throwable diff --git a/tests/Feature/PurchaseEditTest.php b/tests/Feature/PurchaseEditTest.php index 2724dbf..7857186 100644 --- a/tests/Feature/PurchaseEditTest.php +++ b/tests/Feature/PurchaseEditTest.php @@ -335,6 +335,87 @@ class PurchaseEditTest extends ProcurementTestCase $this->assertSame('40.00', (string) $purchase->actual_amount, '2 包 × 每包成本 20.00'); } + /** 门店购买详情:按商品聚合该门店采购汇总(数量/包规/单位/单价/预计金额),仅含该门店明细 */ + public function test_store_summary_returns_aggregated_items(): void + { + [$purchase, $product, $stores] = $this->buildPurchase(); + $this->actingAsSysUser(); + + $response = $this->getJson("/purchase/order/{$purchase->id}/store?store_id={$stores[0]->id}") + ->assertOk() + ->assertJsonPath('success', true); + + $data = $response->json('data'); + $this->assertSame($stores[0]->name, $data['store']['name']); + $this->assertCount(1, $data['items']); + + $row = $data['items'][0]; + $this->assertSame($product->id, $row['product_id']); + $this->assertSame('10斤/箱', $row['product_spec']); + $this->assertSame('斤', $row['unit']); + $this->assertSame('10.00', (string) $row['price']); + $this->assertSame(2, $row['quantity']); + $this->assertSame('20.00', (string) $row['amount']); + + // 另一门店只见自身明细 + $this->getJson("/purchase/order/{$purchase->id}/store?store_id={$stores[1]->id}") + ->assertOk() + ->assertJsonPath('data.items.0.quantity', 3) + ->assertJsonPath('data.items.0.amount', '30.00'); + } + + /** 门店购买详情:同一商品多笔订单不同单价 → 数量合计、金额求和、单价为加权平均 */ + public function test_store_summary_aggregates_multiple_orders_with_weighted_price(): void + { + $level = CustomerLevelModel::factory()->create(); + $store = StoreModel::factory()->create(['level_id' => $level->id]); + $product = ProductModel::factory()->create([ + 'status' => ProductModel::STATUS_ON, + 'cost_price' => 20, + 'spec' => '10斤/箱', + 'unit' => '斤', + ]); + ProductPriceModel::factory()->create(['product_id' => $product->id, 'level_id' => $level->id, 'price' => 10.00]); + + $this->actingAsMiniUser(UserModel::factory()->forStore($store->id)->create()); + foreach ([2, 3] as $qty) { + $this->postJson('/mini/order', ['items' => [['product_id' => $product->id, 'quantity' => $qty]]]) + ->assertJsonPath('success', true); + } + StoreOrderModel::query()->update(['status' => StoreOrderModel::STATUS_SUMMARIZED]); + + $this->actingAsSysUser(); + $this->postJson('/purchase/order/generate', ['purchase_date' => now()->toDateString()]) + ->assertJsonPath('success', true); + + $purchase = PurchaseOrderModel::first(); + // 第二笔明细单价改为 20.00,构造同商品双单价:2×10 + 3×20 + $secondItem = StoreOrderItemModel::orderBy('id')->skip(1)->first(); + $this->putJson("/purchase/order/cell/{$secondItem->id}", ['quantity' => 3, 'price' => 20]) + ->assertJsonPath('success', true); + + $this->getJson("/purchase/order/{$purchase->id}/store?store_id={$store->id}") + ->assertOk() + ->assertJsonPath('data.items.0.quantity', 5) + ->assertJsonPath('data.items.0.price', '16.00', '(20+60) ÷ 5 加权平均') + ->assertJsonPath('data.items.0.amount', '80.00'); + } + + /** 门店购买详情参数校验:缺少门店参数 / 采购单不存在均拒绝 */ + public function test_store_summary_rejects_missing_or_invalid_params(): void + { + [$purchase] = $this->buildPurchase(); + $this->actingAsSysUser(); + + $this->getJson("/purchase/order/{$purchase->id}/store") + ->assertJsonPath('success', false) + ->assertJsonPath('msg', '缺少门店参数'); + + $this->getJson('/purchase/order/99999/store?store_id=1') + ->assertJsonPath('success', false) + ->assertJsonPath('msg', '采购单不存在'); + } + /** 采购单已完成:单元格编辑/同步、行修改均拒绝,明细不变 */ public function test_edits_rejected_when_purchase_completed(): void { diff --git a/web/api/purchase/order.ts b/web/api/purchase/order.ts index 9fc8a2b..c23e64b 100644 --- a/web/api/purchase/order.ts +++ b/web/api/purchase/order.ts @@ -2,6 +2,7 @@ import createAxios from '@/utils/request'; import type { IPurchaseCell, IPurchaseDetail, + IPurchaseStoreSummary, } from '@/domain/iPurchaseOrder.ts'; /** 订单商品参数修改 */ @@ -64,3 +65,12 @@ export async function updatePurchaseCellItem(itemId: number, data: PurchaseCellU data, }); } + +/** 门店购买详情:采购单内某门店的采购汇总(按商品聚合) */ +export async function getPurchaseStoreSummary(purchaseId: number, storeId: number) { + return createAxios({ + url: `/purchase/order/${purchaseId}/store`, + method: 'get', + params: { store_id: storeId }, + }); +} diff --git a/web/domain/iPurchaseOrder.ts b/web/domain/iPurchaseOrder.ts index 10d5035..b9e4690 100644 --- a/web/domain/iPurchaseOrder.ts +++ b/web/domain/iPurchaseOrder.ts @@ -84,6 +84,28 @@ export interface IPurchaseCell { items: IPurchaseCellItem[]; } +/** 门店购买详情行(门店采购汇总,按商品聚合) */ +export interface IPurchaseStoreItem { + product_id: number; + product_name: string; + /** 规格/包规 */ + product_spec: string; + unit: string; + /** 单价(每包;多笔单价时为加权平均,保证 单价×数量=预计金额) */ + price: string; + /** 数量(包数) */ + quantity: number; + /** 预计金额 = Σ 明细 amount */ + amount: string; + weight: string; +} + +/** 门店购买详情(采购单内某门店的采购汇总) */ +export interface IPurchaseStoreSummary { + store: { id: number; name: string } | null; + items: IPurchaseStoreItem[]; +} + export const PURCHASE_STATUS_MAP: Record = { 0: { text: '进行中', color: 'processing' }, 3: { text: '已完成', color: 'success' }, diff --git a/web/pages/purchase/order.tsx b/web/pages/purchase/order.tsx index a56e94c..a8eccc4 100644 --- a/web/pages/purchase/order.tsx +++ b/web/pages/purchase/order.tsx @@ -15,6 +15,7 @@ import { Space, Spin, Table, + Tabs, Tag, Typography, } from 'antd'; @@ -32,12 +33,16 @@ import type { IPurchaseCellItem, IPurchaseDetail, IPurchaseDetailRow, + IPurchaseStoreItem, + IPurchaseStoreSummary, } from '@/domain/iPurchaseOrder.ts'; import { PURCHASE_STATUS_MAP } from '@/domain/iPurchaseOrder.ts'; import { STORE_ORDER_STATUS_MAP } from '@/domain/iStoreOrder.ts'; import { getPurchaseCell, - getPurchaseDetail, type PurchaseCellUpdateParams, type PurchaseRowUpdateParams, + getPurchaseDetail, + getPurchaseStoreSummary, + type PurchaseCellUpdateParams, type PurchaseRowUpdateParams, updatePurchaseCellItem, updatePurchaseRow, } from '@/api/purchase/order.ts'; @@ -48,10 +53,10 @@ import AuthButton from '@/components/AuthButton'; const { Title, Text } = Typography; -/** 参考单价 = 成本 / 包规数值(包规解析不出正数时按 1 处理,与后端同口径;仅展示参考,不参与金额计算) */ -const calcUnitCost = (cost: number, spec: string): number => { +/** 每单位参考价 = 整单价(成本/售价) ÷ 包规数值(包规解析不出正数时按 1 处理,与后端同口径;仅展示参考,不参与金额计算) */ +const calcUnitRefPrice = (total: number, spec: string): number => { const pack = parseFloat(spec); - return Number.isFinite(pack) && pack > 0 ? cost / pack : cost; + return Number.isFinite(pack) && pack > 0 ? total / pack : total; }; /** @@ -85,6 +90,12 @@ const PurchaseOrderPage: React.FC = () => { const [cellItemSaving, setCellItemSaving] = useState(false); const [cellItemForm] = Form.useForm(); + // 门店购买详情(按商品聚合的门店采购汇总) + const [detailTab, setDetailTab] = useState('items'); + const [storeId, setStoreId] = useState(0); + const [storeSummary, setStoreSummary] = useState(null); + const [storeLoading, setStoreLoading] = useState(false); + useEffect(() => { getSupplierOptions().then((res) => setSuppliers(res.data.data ?? [])); }, []); @@ -96,6 +107,20 @@ const PurchaseOrderPage: React.FC = () => { } }, [cellOpen, cellQuery]); + // 详情加载后默认选中第一个门店(当前选中门店仍在采购单内则保留) + useEffect(() => { + if (detail && detail.stores.length > 0) { + setStoreId((prev) => (detail.stores.some((s) => s.id === prev) ? prev : detail.stores[0].id)); + } + }, [detail]); + + // 切到「门店购买详情」页签或切换门店时加载汇总 + useEffect(() => { + if (detailOpen && detailTab === 'stores' && detail && storeId > 0) { + void loadStoreSummary(); + } + }, [detailOpen, detailTab, storeId, detail?.purchase.id]); + const loadDetail = async (id: number) => { setDetailLoading(true); try { @@ -107,10 +132,26 @@ const PurchaseOrderPage: React.FC = () => { }; const openDetail = async (id: number) => { + setDetailTab('items'); + setStoreSummary(null); setDetailOpen(true); await loadDetail(id); }; + /** 加载门店购买详情(门店采购汇总) */ + const loadStoreSummary = async () => { + if (!detail || storeId <= 0) { + return; + } + setStoreLoading(true); + try { + const res = await getPurchaseStoreSummary(detail.purchase.id!, storeId); + setStoreSummary(res.data.data ?? null); + } finally { + setStoreLoading(false); + } + }; + /** 打开单元格下钻:门店 + 商品 → 该采购单下全部订货明细 */ const openCell = (row: IPurchaseDetailRow, store: { id: number; name: string }) => { setCellQuery({ productId: row.product_id, storeId: store.id }); @@ -226,11 +267,11 @@ const PurchaseOrderPage: React.FC = () => { render: (_, row) => row.supplier?.name ?? '-', }, { - title: '参考单价', + title: '参考成本单价', key: 'unit_cost', width: 100, align: 'center', - render: (_, row) => `¥${calcUnitCost(Number(row.cost_price), row.product_spec ?? '').toFixed(2)}`, + render: (_, row) => `¥${calcUnitRefPrice(Number(row.cost_price), row.product_spec ?? '').toFixed(2)}`, }, { title: '包规', dataIndex: 'product_spec', align: 'center', width: 90, render: (v) => v || '-' }, { title: '单位', dataIndex: 'unit', width: 70, align: 'center', render: (v) => v || '-' }, @@ -317,6 +358,66 @@ const PurchaseOrderPage: React.FC = () => { ); }; + /** 门店购买详情列:商品/购买参考零售价(单价÷包规)/包规/单位/单价/数量/预计金额 */ + const storeColumns: TableProps['columns'] = [ + { title: '商品', dataIndex: 'product_name', width: 160, align: 'center' }, + { + title: '参考零售价', + key: 'retail_price', + width: 130, + align: 'center', + render: (_, row) => `¥${calcUnitRefPrice(Number(row.price), row.product_spec).toFixed(2)}`, + }, + { title: '包规', dataIndex: 'product_spec', width: 90, align: 'center', render: (v) => v || '-' }, + { title: '单位', dataIndex: 'unit', width: 90, align: 'center', render: (v) => v || '-' }, + { + title: '单价', + dataIndex: 'price', + width: 100, + align: 'center', + render: (v) => `¥${Number(v).toFixed(2)}`, + }, + { + title: '数量', + dataIndex: 'quantity', + width: 90, + align: 'center', + render: (v) => {v}, + }, + { title: '重量', dataIndex: 'weight', width: 90, align: 'center', render: (v) => `${v || '-'}斤` }, + { + title: '预计金额', + dataIndex: 'amount', + width: 110, + align: 'center', + render: (v) => ¥{Number(v).toFixed(2)}, + }, + ]; + + /** 门店购买详情合计行:总数量 + 总预计金额 */ + const renderStoreTotal = () => { + const items = storeSummary?.items ?? []; + const totalQuantity = items.reduce((sum, row) => sum + Number(row.quantity), 0); + const totalAmount = items.reduce((sum, row) => sum + Number(row.amount), 0); + const totalWeight = items.reduce((sum, row) => sum + Number(row.weight), 0); + return ( + + + 合计 + + + {totalQuantity} + + + {totalWeight.toFixed(3)}斤 + + + ¥{totalAmount.toFixed(2)} + + + ); + }; + const columns: XinTableColumn[] = [ { title: '采购单号', @@ -444,18 +545,69 @@ const PurchaseOrderPage: React.FC = () => { {detail.purchase.total_weight} - - 商品明细 - - - rowKey="product_id" - size="small" - bordered - columns={buildItemColumns()} - dataSource={detail.items} - pagination={false} - scroll={{ x: 'max-content' }} - summary={renderSummary} + + rowKey="product_id" + size="small" + bordered + columns={buildItemColumns()} + dataSource={detail.items} + pagination={false} + scroll={{ x: 'max-content' }} + summary={renderSummary} + /> + ), + }, + { + key: 'stores', + label: '门店购买详情', + children: ( + <> +
+ 选择门店: +