diff --git a/.phpunit.result.cache b/.phpunit.result.cache index 023878e..29daa9d 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":8,"Tests\\Feature\\PurchaseEditTest::test_detail_returns_store_matrix":7,"Tests\\Feature\\PurchaseEditTest::test_update_cell_syncs_order_item_and_totals":7,"Tests\\Feature\\PurchaseEditTest::test_update_row_cost_syncs_all_order_items":7,"Tests\\Feature\\PurchaseEditTest::test_update_row_weight_distributed_by_quantity":7,"Tests\\Feature\\PurchaseEditTest::test_update_row_syncs_product_attributes":7,"Tests\\Feature\\PurchaseGenerateTest::test_generate_aggregates_order_items_into_header":7,"Tests\\Feature\\ExportTest::test_export_category_filters_to_vegetables":7,"Tests\\Feature\\ExportTest::test_export_pdf":7,"Tests\\Feature\\PurchaseEditTest::test_update_row_does_not_touch_manual_weight":7,"Tests\\Feature\\PurchaseEditTest::test_update_row_syncs_to_product_catalog":7,"Tests\\Feature\\PurchaseEditTest::test_update_cell_weight_cascades_totals":7,"Tests\\Feature\\PurchaseEditTest::test_cell_sync_refreshes_snapshot_and_cascades":7,"Tests\\Feature\\PurchaseEditTest::test_edits_rejected_when_purchase_completed":7,"Tests\\Feature\\StoreOrderItemTest::test_update_item_recalculates_order_totals":7,"Tests\\Feature\\StoreOrderItemTest::test_update_item_validates_negative_price":7,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_updates_snapshot_from_product":7,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_recalculates_percent_price_with_latest_cost":7,"Tests\\Feature\\PurchaseEditTest::test_cell_detail_returns_items_with_order_info":8,"Tests\\Feature\\ProductCategoryTest::test_create_product_with_leaf_category_ok":7,"Tests\\Feature\\BillExportTest::test_export_filters_by_root_category":8,"Tests\\Feature\\BillExportTest::test_export_requires_export_permission":8,"Tests\\Feature\\DebugPermissionTest::test_debug_permission":8,"Tests\\Feature\\BillPaymentTest::test_order_status_progresses_via_business_chain":7,"Tests\\Feature\\BillPaymentTest::test_payment_audit_reject_does_not_accumulate":7,"Tests\\Feature\\BillPaymentTest::test_payment_detail_returns_voucher_urls":8,"Tests\\Feature\\ContainerReturnTest::test_press_containers_adds_amount_and_writes_record":7,"Tests\\Feature\\ContainerReturnTest::test_return_containers_deducts_amount":7,"Tests\\Feature\\ContainerReturnTest::test_mixed_sign_containers":7,"Tests\\Feature\\ContainerReturnTest::test_zero_containers_writes_no_record":7,"Tests\\Feature\\StoreOrderTest::test_order_list_outputs_status_name_and_item_preview":7,"Tests\\Feature\\DashboardTest::test_requires_dashboard_analysis_ability":7,"Tests\\Feature\\DashboardTest::test_recon_summary_and_receivable":8,"Tests\\Feature\\DashboardDebugTest::test_debug":8,"Tests\\Feature\\StoreOrderItemTest::test_place_order_snapshots_full_product_info":8,"Tests\\Feature\\StoreOrderItemTest::test_mini_detail_hides_cost_price":8,"Tests\\Feature\\StoreOrderItemTest::test_admin_detail_shows_cost_price_and_supplier":8,"Tests\\Feature\\StoreOrderItemTest::test_update_item_rejected_when_completed_or_cancelled":8,"Tests\\Feature\\StoreOrderItemTest::test_add_store_item_uses_level_percent_price":8,"Tests\\Feature\\ExportTest::test_export_suppliers_multi_sheet":7,"Tests\\Feature\\PurchaseEditTest::test_update_row_skips_catalog_sync_when_product_deleted":7,"Tests\\Feature\\ProductImportExportTest::test_export_filters_by_category":8,"Tests\\Feature\\ProductImportExportTest::test_import_creates_products_and_auto_supplier":7,"Tests\\Feature\\ProductImportExportTest::test_import_matches_existing_supplier":7,"Tests\\Feature\\ProductImportExportTest::test_import_errors_abort_all_rows":7,"Tests\\Feature\\ProductImportExportTest::test_import_category_path_resolution":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.09,"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.006,"Tests\\Feature\\StoreOrderTest::test_place_order_snapshots_level_price_and_recalculates":0.048,"Tests\\Feature\\StoreOrderTest::test_client_supplied_amount_is_ignored":0.048,"Tests\\Feature\\StoreOrderTest::test_product_without_level_price_rejected":0.007,"Tests\\Feature\\StoreOrderTest::test_cancel_only_pending_orders":0.061,"Tests\\Feature\\StoreOrderTest::test_summarized_order_cannot_be_cancelled":0.049,"Tests\\Feature\\StoreOrderTest::test_order_data_isolated_between_stores":0.092,"Tests\\Feature\\PurchaseGenerateTest::test_generate_aggregates_orders_by_product":0.028,"Tests\\Feature\\PurchaseGenerateTest::test_generate_writes_back_order_status":0.1,"Tests\\Feature\\PurchaseGenerateTest::test_generate_without_pending_orders_fails":0.006,"Tests\\Feature\\PurchaseGenerateTest::test_generate_is_idempotent":0.102,"Tests\\Feature\\AllocationTest::test_allocation_conserves_amount_with_tail_correction":0.076,"Tests\\Feature\\AllocationTest::test_allocation_follows_order_ratio":0.022,"Tests\\Feature\\AllocationTest::test_allocation_is_idempotent":0.028,"Tests\\Feature\\AllocationTest::test_allocation_rejected_without_actual_amount":0.021,"Tests\\Feature\\DebugGenerateTest::test_debug_generate":0.047,"Tests\\Feature\\ReconciliationTest::test_build_creates_reconciliation_items":0.019,"Tests\\Feature\\ReconciliationTest::test_build_filters_by_supplier":0.022,"Tests\\Feature\\ReconciliationTest::test_update_item_recalculates_diff_and_header":0.019,"Tests\\Feature\\ReconciliationTest::test_toggle_reconciled_flag":0.02,"Tests\\Feature\\ReconciliationTest::test_settle_creates_settlements_per_store":0.036,"Tests\\Feature\\StatementTest::test_generate_snapshots_payment_cycle":0.011,"Tests\\Feature\\StatementTest::test_statement_isolated_between_stores":0.013,"Tests\\Feature\\StatementTest::test_generate_excludes_cancelled_and_used_items":0.016,"Tests\\Feature\\MiniAuthTest::test_login_creates_user_and_issues_token":0.056,"Tests\\Feature\\MiniAuthTest::test_disabled_user_cannot_login":0.003,"Tests\\Feature\\MiniAuthTest::test_phone_binding_matches_store":0.005,"Tests\\Feature\\MiniAuthTest::test_phone_no_match_stays_pending":0.001,"Tests\\Feature\\MiniAuthTest::test_sys_token_cannot_access_mini":0.006,"Tests\\Feature\\MiniAuthTest::test_mini_token_cannot_access_admin_api":0.045,"Tests\\Feature\\ExportTest::test_export_xlsx_with_encoded_chinese_filename":0.092,"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.057,"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.048,"Tests\\Feature\\CartTest::test_duplicate_add_merges_quantity":0.048,"Tests\\Feature\\CartTest::test_off_shelf_product_rejected":0.047,"Tests\\Feature\\CartTest::test_soft_deleted_product_rejected":0.008,"Tests\\Feature\\CartTest::test_product_without_level_price_rejected":0.006,"Tests\\Feature\\CartTest::test_unbound_user_rejected":0.006,"Tests\\Feature\\CartTest::test_store_without_level_rejected":0.087,"Tests\\Feature\\CartTest::test_list_with_prices_amounts_and_totals":0.049,"Tests\\Feature\\CartTest::test_list_marks_off_shelf_item_unbuyable":0.051,"Tests\\Feature\\CartTest::test_empty_cart_returns_empty_items":0.045,"Tests\\Feature\\CartTest::test_update_quantity":0.05,"Tests\\Feature\\CartTest::test_update_other_users_item_rejected":0.009,"Tests\\Feature\\CartTest::test_update_missing_item_rejected":0.048,"Tests\\Feature\\CartTest::test_invalid_quantity_rejected":0.047,"Tests\\Feature\\CartTest::test_delete_item":0.048,"Tests\\Feature\\CartTest::test_delete_other_users_item_rejected":0.009,"Tests\\Feature\\CartTest::test_clear_only_own_cart":0.094,"Tests\\Feature\\CartTest::test_unauthenticated_returns_401":0.002,"Tests\\Feature\\CartTest::test_cart_isolated_between_users":0.011,"Tests\\Feature\\DebugCartListTest::test_dump_cart_list_response":0.057,"Tests\\Feature\\ProductCostPriceTest::test_calc_actual_price_fixed_and_percent":0.001,"Tests\\Feature\\ProductCostPriceTest::test_calc_actual_price_edge_cases":0.001,"Tests\\Feature\\ProductCostPriceTest::test_admin_list_contains_cost_price_and_actual_price":0.009,"Tests\\Feature\\ProductCostPriceTest::test_mini_list_returns_actual_price_without_cost_price":0.045,"Tests\\Feature\\ProductCostPriceTest::test_create_product_with_cost_and_percent_pricing":0.008,"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.009,"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.01,"Tests\\Feature\\PurchaseGenerateTest::test_generate_uses_min_actual_price_across_types":0.014,"Tests\\Feature\\ProductPriceTest::test_store_without_level_sees_products_without_price":0.046,"Tests\\Feature\\ProductPriceTest::test_unbound_user_can_browse_products_without_price_but_cannot_cart":0.009,"Tests\\Feature\\MiniAuthTest::test_login_issues_token_for_registered_user":0.01,"Tests\\Feature\\MiniAuthTest::test_login_rejects_unregistered_openid":0.002,"Tests\\Feature\\MiniAuthTest::test_register_binds_store_and_issues_token":0.009,"Tests\\Feature\\MiniAuthTest::test_register_rejects_unknown_store":0.002,"Tests\\Feature\\MiniAuthTest::test_register_rejects_duplicate_openid":0.003,"Tests\\Feature\\MiniStoreTest::test_info_returns_bound_store":0.005,"Tests\\Feature\\MiniStoreTest::test_update_info":0.045,"Tests\\Feature\\MiniStoreTest::test_update_info_ignores_readonly_fields":0.046,"Tests\\Feature\\MiniStoreTest::test_requires_bound_store":0.007,"Tests\\Feature\\MiniStoreTest::test_update_info_validates_length":0.046,"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.051,"Tests\\Feature\\PurchaseGenerateTest::test_generate_with_selected_order_ids":0.101,"Tests\\Feature\\StoreOrderItemTest::test_place_order_snapshots_full_product_info":0.048,"Tests\\Feature\\StoreOrderItemTest::test_mini_detail_hides_cost_price":0.051,"Tests\\Feature\\StoreOrderItemTest::test_admin_detail_shows_cost_price_and_supplier":0.053,"Tests\\Feature\\StoreOrderItemTest::test_update_item_recalculates_order_totals":0.011,"Tests\\Feature\\StoreOrderItemTest::test_update_item_rejected_when_completed_or_cancelled":0.011,"Tests\\Feature\\StoreOrderItemTest::test_update_item_validates_negative_price":0.012,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_updates_snapshot_from_product":0.013,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_recalculates_percent_price_with_latest_cost":0.01,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_rejected_when_product_deleted":0.01,"Tests\\Feature\\StoreOrderItemTest::test_order_list_search_by_product_name":0.057,"Tests\\Feature\\StoreOrderTest::test_soft_delete_only_cancelled_orders":0.061,"Tests\\Feature\\StoreOrderTest::test_soft_delete_rejected_for_non_cancelled_orders":0.245,"Tests\\Feature\\StoreOrderTest::test_soft_delete_twice_rejected":0.054,"Tests\\Feature\\PurchaseEditTest::test_detail_returns_store_matrix":0.105,"Tests\\Feature\\PurchaseEditTest::test_update_cell_syncs_order_item_and_totals":0.102,"Tests\\Feature\\PurchaseEditTest::test_update_row_cost_syncs_all_order_items":0.105,"Tests\\Feature\\PurchaseEditTest::test_update_row_weight_distributed_by_quantity":0.032,"Tests\\Feature\\PurchaseEditTest::test_update_row_requires_at_least_one_field":0.102,"Tests\\Feature\\PurchaseEditTest::test_update_cell_rejects_negative_quantity":0.1,"Tests\\Feature\\PurchaseGenerateTest::test_generate_aggregates_order_items_into_header":0.101,"Tests\\Feature\\ReconciliationTest::test_build_excludes_unfinished_orders":0.016,"Tests\\Feature\\PurchaseEditTest::test_update_row_syncs_product_attributes":0.104,"Tests\\Feature\\PurchaseEditTest::test_update_row_syncs_to_product_catalog":0.019,"Tests\\Feature\\PurchaseEditTest::test_cell_detail_returns_items_with_order_info":0.104,"Tests\\Feature\\PurchaseEditTest::test_cell_detail_rejects_missing_params":0.1,"Tests\\Feature\\PurchaseEditTest::test_update_cell_weight_cascades_totals":0.102,"Tests\\Feature\\PurchaseEditTest::test_cell_sync_refreshes_snapshot_and_cascades":0.012,"Tests\\Feature\\PurchaseEditTest::test_edits_rejected_when_purchase_completed":0.124,"Tests\\Feature\\PurchaseEditTest::test_update_row_does_not_touch_manual_weight":0.108,"Tests\\Feature\\PurchaseEditTest::test_store_summary_returns_aggregated_items":0.103,"Tests\\Feature\\PurchaseEditTest::test_store_summary_aggregates_multiple_orders_with_weighted_price":0.061,"Tests\\Feature\\PurchaseEditTest::test_store_summary_rejects_missing_or_invalid_params":0.103,"Tests\\Feature\\ExportTest::test_export_invalid_type_rejected":0.016,"Tests\\Feature\\ExportTest::test_export_missing_purchase_rejected":0.006,"Tests\\Feature\\PurchaseEditTest::test_detail_returns_container_summary":0.022,"Tests\\Feature\\PurchaseEditTest::test_update_container_syncs_order_amounts":0.018,"Tests\\Feature\\PurchaseEditTest::test_update_container_rejected_when_purchase_completed":0.018,"Tests\\Feature\\PurchaseEditTest::test_update_container_rejects_incomplete_or_foreign_orders":0.026,"Tests\\Feature\\PurchaseEditTest::test_update_container_validates_negative_and_duplicate_orders":0.018,"Tests\\Feature\\ProductCategoryTest::test_create_child_category_under_top_level":0.007,"Tests\\Feature\\ProductCategoryTest::test_create_category_under_second_level_rejected":0.006,"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.008,"Tests\\Feature\\ProductCategoryTest::test_update_with_missing_parent_rejected":0.006,"Tests\\Feature\\ProductCategoryTest::test_create_product_with_parent_category_rejected":0.007,"Tests\\Feature\\ProductCategoryTest::test_create_product_with_leaf_category_ok":0.007,"Tests\\Feature\\ProductCategoryTest::test_mini_categories_returns_all_enabled_categories":0.05,"Tests\\Feature\\MiniBillTest::test_bill_list_derives_pay_state_and_settlement_date":0.049,"Tests\\Feature\\MiniBillTest::test_bill_list_summary_aggregates_unpaid":0.047,"Tests\\Feature\\MiniBillTest::test_bill_list_payable_filter":0.047,"Tests\\Feature\\MiniBillTest::test_bill_list_filters_by_bill_date_range":0.053,"Tests\\Feature\\MiniBillTest::test_bill_data_isolated_between_stores":0.089,"Tests\\Feature\\MiniBillTest::test_bill_detail_outputs_bill_items_and_orders":0.051,"Tests\\Feature\\StoreOrderTest::test_order_list_outputs_status_name_and_item_preview":0.054,"Tests\\Feature\\StoreOrderTest::test_order_list_filters_by_order_date_range":0.06,"Tests\\Feature\\StoreOrderTest::test_order_list_rejects_invalid_filters":0.049,"Tests\\Feature\\BillExportTest::test_export_merges_items_across_bills":0.097,"Tests\\Feature\\BillExportTest::test_export_filters_by_root_category":0.053,"Tests\\Feature\\BillExportTest::test_export_empty_category_rejected":0.069,"Tests\\Feature\\BillExportTest::test_export_requires_export_permission":0.052,"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.014,"Tests\\Feature\\BillExportTest::test_mini_export_requires_store_binding":0.005,"Tests\\Feature\\MiniNoticeTest::test_notice_list_includes_personal_and_broadcast":0.048,"Tests\\Feature\\MiniNoticeTest::test_broadcast_read_creates_personal_copy":0.049,"Tests\\Feature\\MiniNoticeTest::test_personal_notice_read_marks_directly":0.046,"Tests\\Feature\\MiniNoticeTest::test_other_users_notice_not_found":0.007,"Tests\\Feature\\StoreOrderTest::test_manual_status_flow_only_accept_and_cancel":0.101,"Tests\\Feature\\StoreOrderTest::test_manual_status_flow_rejects_delivery_and_completion":0.15,"Tests\\Feature\\StoreOrderTest::test_batch_status_only_accept_and_cancel":0.102,"Tests\\Feature\\BillPaymentTest::test_order_status_progresses_via_business_chain":0.063,"Tests\\Feature\\BillPaymentTest::test_manual_pay_accumulates_store_total_purchase_amount":0.068,"Tests\\Feature\\BillPaymentTest::test_payment_audit_pass_accumulates_store_total_purchase_amount":0.052,"Tests\\Feature\\BillPaymentTest::test_payment_audit_reject_does_not_accumulate":0.049,"Tests\\Feature\\MiniProductTest::test_product_detail_guest_sees_null_price":0.044,"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.045,"Tests\\Feature\\MiniProductTest::test_product_detail_off_shelf_or_missing_rejected":0.049,"Tests\\Feature\\MiniProductTest::test_product_list_store_user_sees_level_price":0.005,"Tests\\Feature\\MiniProductTest::test_product_list_guest_sees_null_price":0.046,"Tests\\Feature\\BillPaymentTest::test_admin_order_list_and_detail_show_bill_info":0.073,"Tests\\Feature\\BillPaymentTest::test_payment_detail_returns_voucher_urls":0.056,"Tests\\Feature\\StoreOrderTest::test_order_list_preview_includes_image_spec_and_unit":0.05,"Tests\\Feature\\ContainerReturnTest::test_press_containers_adds_amount_and_writes_record":0.061,"Tests\\Feature\\ContainerReturnTest::test_return_containers_deducts_amount":0.061,"Tests\\Feature\\ContainerReturnTest::test_mixed_sign_containers":0.06,"Tests\\Feature\\ContainerReturnTest::test_zero_containers_writes_no_record":0.06,"Tests\\Feature\\ContainerReturnTest::test_non_integer_containers_rejected":0.048,"Tests\\Feature\\ContainerReturnTest::test_manual_create_and_delete_routes_removed":0.007,"Tests\\Feature\\ContainerReturnTest::test_export_matrix_merges_same_day_and_zero_fills":0.092,"Tests\\Feature\\ContainerReturnTest::test_export_filters_by_selected_stores":0.129,"Tests\\Feature\\ContainerReturnTest::test_export_invalid_params_rejected":0.009,"Tests\\Feature\\ContainerReturnTest::test_export_empty_range_rejected":0.006,"Tests\\Feature\\ContainerReturnTest::test_export_requires_export_permission":0.009,"Tests\\Feature\\DashboardTest::test_guest_cannot_access_dashboard":0.002,"Tests\\Feature\\DashboardTest::test_requires_dashboard_analysis_ability":0.006,"Tests\\Feature\\DashboardTest::test_empty_database_returns_zero_structure":0.009,"Tests\\Feature\\DashboardTest::test_overview_excludes_cancelled_orders":0.052,"Tests\\Feature\\DashboardTest::test_week_growth_rate":0.055,"Tests\\Feature\\DashboardTest::test_trend_covers_30_days_with_zero_filling":0.051,"Tests\\Feature\\DashboardTest::test_category_sales_grouped_by_category":0.054,"Tests\\Feature\\DashboardTest::test_top_products_aggregated_across_orders":0.053,"Tests\\Feature\\DashboardTest::test_top_stores_ranked_by_amount":0.092,"Tests\\Feature\\DashboardTest::test_purchase_amount_prefers_actual_over_estimate":0.009,"Tests\\Feature\\DashboardTest::test_recon_summary_and_receivable":0.052,"Tests\\Feature\\DashboardTest::test_latest_orders_limited_and_ordered":0.053,"Tests\\Feature\\DashboardTest::test_archives_count_active_records_only":0.131,"Tests\\Feature\\DashboardDebugTest::test_debug_ability_check":0.044,"Tests\\Feature\\DashboardDebugTest::test_debug":0.045,"Tests\\Feature\\ProductPriceTest::test_batch_price_updates_cost_and_notifies_all_stores":0.089,"Tests\\Feature\\ProductPriceTest::test_level_percent_update_notifies_level_stores":0.092,"Tests\\Feature\\ProductPriceTest::test_level_update_without_percent_change_sends_no_notice":0.047,"Tests\\Feature\\ProductPriceTest::test_level_percent_validation":0.008,"Tests\\Feature\\ProductCostPriceTest::test_calc_level_price":0.001,"Tests\\Feature\\ProductCostPriceTest::test_calc_level_price_edge_cases":0.001,"Tests\\Feature\\ProductCostPriceTest::test_admin_list_contains_cost_price_and_level_prices":0.008,"Tests\\Feature\\ProductCostPriceTest::test_create_product_with_cost_price":0.007,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_cost_row_update":0.051,"Tests\\Feature\\ProductCostPriceTest::test_price_matrix_includes_cost_and_level_price_columns":0.007,"Tests\\Feature\\ProductCostPriceTest::test_mini_order_uses_level_price":0.048,"Tests\\Feature\\ProductCostPriceTest::test_mini_cart_uses_level_price":0.049,"Tests\\Feature\\CartTest::test_store_level_missing_rejected":0.006,"Tests\\Feature\\StoreOrderTest::test_store_level_missing_rejected":0.047,"Tests\\Feature\\PurchaseEditTest::test_update_row_skips_catalog_sync_when_product_deleted":0.103,"Tests\\Feature\\PurchaseEditTest::test_store_item_add_creates_item_and_recalculates":0.106,"Tests\\Feature\\PurchaseEditTest::test_store_item_update_merges_multi_order_rows":0.06,"Tests\\Feature\\PurchaseEditTest::test_store_item_remove_deletes_and_recalculates":0.1,"Tests\\Feature\\StoreOrderItemTest::test_update_store_item_recalculates_totals":0.059,"Tests\\Feature\\StoreOrderItemTest::test_store_item_edits_rejected_when_purchase_completed":0.061,"Tests\\Feature\\StoreOrderItemTest::test_update_store_item_validates_negative_values":0.058,"Tests\\Feature\\StoreOrderItemTest::test_add_store_item_uses_level_percent_price":0.06,"Tests\\Feature\\StoreOrderItemTest::test_add_store_item_rejected_when_product_deleted":0.055,"Tests\\Feature\\ExportTest::test_export_lists_all_catalog_products_with_totals_row":0.101,"Tests\\Feature\\ExportTest::test_export_supplier_filter":0.102,"Tests\\Feature\\ExportTest::test_export_stores_multi_sheet":0.105,"Tests\\Feature\\ExportTest::test_export_stores_single_store":0.101,"Tests\\Feature\\ExportTest::test_export_stores_without_items_rejected":0.143,"Tests\\Feature\\ExportTest::test_export_suppliers_multi_sheet":0.103,"Tests\\Feature\\ExportTest::test_export_suppliers_single":0.101,"Tests\\Feature\\ExportTest::test_export_suppliers_without_items_rejected":0.102,"Tests\\Feature\\PurchaseEditTest::test_update_row_cost_change_reprices_items_by_store_level":0.101,"Tests\\Feature\\PurchaseEditTest::test_update_row_without_cost_change_keeps_item_price":0.106,"Tests\\Feature\\CartTest::test_update_other_stores_item_rejected":0.091,"Tests\\Feature\\CartTest::test_delete_other_stores_item_rejected":0.09,"Tests\\Feature\\CartTest::test_cart_isolated_between_stores":0.089,"Tests\\Feature\\MiniAuthTest::test_login_issues_token_for_valid_credentials":0.128,"Tests\\Feature\\MiniAuthTest::test_login_rejects_wrong_password":0.124,"Tests\\Feature\\MiniAuthTest::test_login_rejects_unknown_username":0.003,"Tests\\Feature\\MiniAuthTest::test_disabled_store_cannot_login":0.123,"Tests\\Feature\\MiniAuthTest::test_login_validates_required_params":0.003,"Tests\\Feature\\MiniAuthTest::test_login_token_accesses_protected_routes":0.13,"Tests\\Feature\\MiniAuthTest::test_set_password_succeeds":0.207,"Tests\\Feature\\MiniAuthTest::test_set_password_rejects_wrong_old_password":0.127,"Tests\\Feature\\MiniAuthTest::test_set_password_validates_confirmation":0.046,"Tests\\Feature\\BillExportTest::test_mini_export_scoped_to_own_store":0.093,"Tests\\Feature\\MiniNoticeTest::test_other_stores_notice_not_found":0.086,"Tests\\Feature\\MiniProductTest::test_product_detail_store_sees_level_price":0.046,"Tests\\Feature\\MiniProductTest::test_product_list_store_sees_level_price":0.045,"Tests\\Feature\\MiniStoreTest::test_info_returns_current_store":0.045,"Tests\\Feature\\MiniStoreTest::test_disabled_store_rejected":0.046,"Tests\\Feature\\ProductImportExportTest::test_export_downloads_all_products":0.008,"Tests\\Feature\\ProductImportExportTest::test_export_filters_by_category":0.008,"Tests\\Feature\\ProductImportExportTest::test_export_template_mode":0.005,"Tests\\Feature\\ProductImportExportTest::test_export_requires_permission":0.007,"Tests\\Feature\\ProductImportExportTest::test_import_creates_products_and_auto_supplier":0.043,"Tests\\Feature\\ProductImportExportTest::test_import_matches_existing_supplier":0.018,"Tests\\Feature\\ProductImportExportTest::test_import_errors_abort_all_rows":0.017,"Tests\\Feature\\ProductImportExportTest::test_import_category_path_resolution":0.031,"Tests\\Feature\\ProductImportExportTest::test_import_rejects_invalid_header":0.024,"Tests\\Feature\\ProductImportExportTest::test_import_requires_permission":0.01}} \ 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":8,"Tests\\Feature\\PurchaseEditTest::test_detail_returns_store_matrix":7,"Tests\\Feature\\PurchaseEditTest::test_update_cell_syncs_order_item_and_totals":7,"Tests\\Feature\\PurchaseEditTest::test_update_row_cost_syncs_all_order_items":7,"Tests\\Feature\\PurchaseEditTest::test_update_row_weight_distributed_by_quantity":7,"Tests\\Feature\\PurchaseEditTest::test_update_row_syncs_product_attributes":7,"Tests\\Feature\\PurchaseGenerateTest::test_generate_aggregates_order_items_into_header":7,"Tests\\Feature\\ExportTest::test_export_category_filters_to_vegetables":7,"Tests\\Feature\\ExportTest::test_export_pdf":7,"Tests\\Feature\\PurchaseEditTest::test_update_row_does_not_touch_manual_weight":7,"Tests\\Feature\\PurchaseEditTest::test_update_row_syncs_to_product_catalog":7,"Tests\\Feature\\PurchaseEditTest::test_update_cell_weight_cascades_totals":7,"Tests\\Feature\\PurchaseEditTest::test_cell_sync_refreshes_snapshot_and_cascades":7,"Tests\\Feature\\PurchaseEditTest::test_edits_rejected_when_purchase_completed":7,"Tests\\Feature\\StoreOrderItemTest::test_update_item_recalculates_order_totals":7,"Tests\\Feature\\StoreOrderItemTest::test_update_item_validates_negative_price":7,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_updates_snapshot_from_product":7,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_recalculates_percent_price_with_latest_cost":7,"Tests\\Feature\\PurchaseEditTest::test_cell_detail_returns_items_with_order_info":8,"Tests\\Feature\\ProductCategoryTest::test_create_product_with_leaf_category_ok":7,"Tests\\Feature\\BillExportTest::test_export_filters_by_root_category":8,"Tests\\Feature\\BillExportTest::test_export_requires_export_permission":8,"Tests\\Feature\\DebugPermissionTest::test_debug_permission":8,"Tests\\Feature\\BillPaymentTest::test_order_status_progresses_via_business_chain":7,"Tests\\Feature\\BillPaymentTest::test_payment_audit_reject_does_not_accumulate":7,"Tests\\Feature\\BillPaymentTest::test_payment_detail_returns_voucher_urls":8,"Tests\\Feature\\ContainerReturnTest::test_press_containers_adds_amount_and_writes_record":7,"Tests\\Feature\\ContainerReturnTest::test_return_containers_deducts_amount":7,"Tests\\Feature\\ContainerReturnTest::test_mixed_sign_containers":7,"Tests\\Feature\\ContainerReturnTest::test_zero_containers_writes_no_record":7,"Tests\\Feature\\StoreOrderTest::test_order_list_outputs_status_name_and_item_preview":7,"Tests\\Feature\\DashboardTest::test_requires_dashboard_analysis_ability":7,"Tests\\Feature\\DashboardTest::test_recon_summary_and_receivable":8,"Tests\\Feature\\DashboardDebugTest::test_debug":8,"Tests\\Feature\\StoreOrderItemTest::test_place_order_snapshots_full_product_info":8,"Tests\\Feature\\StoreOrderItemTest::test_mini_detail_hides_cost_price":8,"Tests\\Feature\\StoreOrderItemTest::test_admin_detail_shows_cost_price_and_supplier":8,"Tests\\Feature\\StoreOrderItemTest::test_update_item_rejected_when_completed_or_cancelled":8,"Tests\\Feature\\StoreOrderItemTest::test_add_store_item_uses_level_percent_price":8,"Tests\\Feature\\ExportTest::test_export_suppliers_multi_sheet":7,"Tests\\Feature\\PurchaseEditTest::test_update_row_skips_catalog_sync_when_product_deleted":7,"Tests\\Feature\\ProductImportExportTest::test_export_filters_by_category":8,"Tests\\Feature\\ProductImportExportTest::test_import_creates_products_and_auto_supplier":7,"Tests\\Feature\\ProductImportExportTest::test_import_matches_existing_supplier":7,"Tests\\Feature\\ProductImportExportTest::test_import_errors_abort_all_rows":7,"Tests\\Feature\\ProductImportExportTest::test_import_category_path_resolution":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.096,"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.047,"Tests\\Feature\\StoreOrderTest::test_client_supplied_amount_is_ignored":0.047,"Tests\\Feature\\StoreOrderTest::test_product_without_level_price_rejected":0.007,"Tests\\Feature\\StoreOrderTest::test_cancel_only_pending_orders":0.05,"Tests\\Feature\\StoreOrderTest::test_summarized_order_cannot_be_cancelled":0.052,"Tests\\Feature\\StoreOrderTest::test_order_data_isolated_between_stores":0.105,"Tests\\Feature\\PurchaseGenerateTest::test_generate_aggregates_orders_by_product":0.028,"Tests\\Feature\\PurchaseGenerateTest::test_generate_writes_back_order_status":0.11,"Tests\\Feature\\PurchaseGenerateTest::test_generate_without_pending_orders_fails":0.006,"Tests\\Feature\\PurchaseGenerateTest::test_generate_is_idempotent":0.102,"Tests\\Feature\\AllocationTest::test_allocation_conserves_amount_with_tail_correction":0.076,"Tests\\Feature\\AllocationTest::test_allocation_follows_order_ratio":0.022,"Tests\\Feature\\AllocationTest::test_allocation_is_idempotent":0.028,"Tests\\Feature\\AllocationTest::test_allocation_rejected_without_actual_amount":0.021,"Tests\\Feature\\DebugGenerateTest::test_debug_generate":0.047,"Tests\\Feature\\ReconciliationTest::test_build_creates_reconciliation_items":0.019,"Tests\\Feature\\ReconciliationTest::test_build_filters_by_supplier":0.022,"Tests\\Feature\\ReconciliationTest::test_update_item_recalculates_diff_and_header":0.019,"Tests\\Feature\\ReconciliationTest::test_toggle_reconciled_flag":0.02,"Tests\\Feature\\ReconciliationTest::test_settle_creates_settlements_per_store":0.036,"Tests\\Feature\\StatementTest::test_generate_snapshots_payment_cycle":0.011,"Tests\\Feature\\StatementTest::test_statement_isolated_between_stores":0.013,"Tests\\Feature\\StatementTest::test_generate_excludes_cancelled_and_used_items":0.016,"Tests\\Feature\\MiniAuthTest::test_login_creates_user_and_issues_token":0.056,"Tests\\Feature\\MiniAuthTest::test_disabled_user_cannot_login":0.003,"Tests\\Feature\\MiniAuthTest::test_phone_binding_matches_store":0.005,"Tests\\Feature\\MiniAuthTest::test_phone_no_match_stays_pending":0.001,"Tests\\Feature\\MiniAuthTest::test_sys_token_cannot_access_mini":0.005,"Tests\\Feature\\MiniAuthTest::test_mini_token_cannot_access_admin_api":0.045,"Tests\\Feature\\ExportTest::test_export_xlsx_with_encoded_chinese_filename":0.091,"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.055,"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.047,"Tests\\Feature\\CartTest::test_duplicate_add_merges_quantity":0.048,"Tests\\Feature\\CartTest::test_off_shelf_product_rejected":0.045,"Tests\\Feature\\CartTest::test_soft_deleted_product_rejected":0.008,"Tests\\Feature\\CartTest::test_product_without_level_price_rejected":0.006,"Tests\\Feature\\CartTest::test_unbound_user_rejected":0.006,"Tests\\Feature\\CartTest::test_store_without_level_rejected":0.087,"Tests\\Feature\\CartTest::test_list_with_prices_amounts_and_totals":0.051,"Tests\\Feature\\CartTest::test_list_marks_off_shelf_item_unbuyable":0.053,"Tests\\Feature\\CartTest::test_empty_cart_returns_empty_items":0.046,"Tests\\Feature\\CartTest::test_update_quantity":0.062,"Tests\\Feature\\CartTest::test_update_other_users_item_rejected":0.009,"Tests\\Feature\\CartTest::test_update_missing_item_rejected":0.048,"Tests\\Feature\\CartTest::test_invalid_quantity_rejected":0.047,"Tests\\Feature\\CartTest::test_delete_item":0.047,"Tests\\Feature\\CartTest::test_delete_other_users_item_rejected":0.009,"Tests\\Feature\\CartTest::test_clear_only_own_cart":0.096,"Tests\\Feature\\CartTest::test_unauthenticated_returns_401":0.003,"Tests\\Feature\\CartTest::test_cart_isolated_between_users":0.011,"Tests\\Feature\\DebugCartListTest::test_dump_cart_list_response":0.057,"Tests\\Feature\\ProductCostPriceTest::test_calc_actual_price_fixed_and_percent":0.001,"Tests\\Feature\\ProductCostPriceTest::test_calc_actual_price_edge_cases":0.001,"Tests\\Feature\\ProductCostPriceTest::test_admin_list_contains_cost_price_and_actual_price":0.009,"Tests\\Feature\\ProductCostPriceTest::test_mini_list_returns_actual_price_without_cost_price":0.045,"Tests\\Feature\\ProductCostPriceTest::test_create_product_with_cost_and_percent_pricing":0.008,"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.009,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_mixed_row_updates_cost_and_level":0.008,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_empty_row_rejected":0.007,"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.01,"Tests\\Feature\\PurchaseGenerateTest::test_generate_uses_min_actual_price_across_types":0.014,"Tests\\Feature\\ProductPriceTest::test_store_without_level_sees_products_without_price":0.045,"Tests\\Feature\\ProductPriceTest::test_unbound_user_can_browse_products_without_price_but_cannot_cart":0.009,"Tests\\Feature\\MiniAuthTest::test_login_issues_token_for_registered_user":0.01,"Tests\\Feature\\MiniAuthTest::test_login_rejects_unregistered_openid":0.002,"Tests\\Feature\\MiniAuthTest::test_register_binds_store_and_issues_token":0.009,"Tests\\Feature\\MiniAuthTest::test_register_rejects_unknown_store":0.002,"Tests\\Feature\\MiniAuthTest::test_register_rejects_duplicate_openid":0.003,"Tests\\Feature\\MiniStoreTest::test_info_returns_bound_store":0.005,"Tests\\Feature\\MiniStoreTest::test_update_info":0.046,"Tests\\Feature\\MiniStoreTest::test_update_info_ignores_readonly_fields":0.045,"Tests\\Feature\\MiniStoreTest::test_requires_bound_store":0.007,"Tests\\Feature\\MiniStoreTest::test_update_info_validates_length":0.046,"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.052,"Tests\\Feature\\PurchaseGenerateTest::test_generate_with_selected_order_ids":0.104,"Tests\\Feature\\StoreOrderItemTest::test_place_order_snapshots_full_product_info":0.048,"Tests\\Feature\\StoreOrderItemTest::test_mini_detail_hides_cost_price":0.05,"Tests\\Feature\\StoreOrderItemTest::test_admin_detail_shows_cost_price_and_supplier":0.052,"Tests\\Feature\\StoreOrderItemTest::test_update_item_recalculates_order_totals":0.011,"Tests\\Feature\\StoreOrderItemTest::test_update_item_rejected_when_completed_or_cancelled":0.011,"Tests\\Feature\\StoreOrderItemTest::test_update_item_validates_negative_price":0.012,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_updates_snapshot_from_product":0.013,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_recalculates_percent_price_with_latest_cost":0.01,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_rejected_when_product_deleted":0.01,"Tests\\Feature\\StoreOrderItemTest::test_order_list_search_by_product_name":0.056,"Tests\\Feature\\StoreOrderTest::test_soft_delete_only_cancelled_orders":0.059,"Tests\\Feature\\StoreOrderTest::test_soft_delete_rejected_for_non_cancelled_orders":0.245,"Tests\\Feature\\StoreOrderTest::test_soft_delete_twice_rejected":0.052,"Tests\\Feature\\PurchaseEditTest::test_detail_returns_store_matrix":0.104,"Tests\\Feature\\PurchaseEditTest::test_update_cell_syncs_order_item_and_totals":0.102,"Tests\\Feature\\PurchaseEditTest::test_update_row_cost_syncs_all_order_items":0.104,"Tests\\Feature\\PurchaseEditTest::test_update_row_weight_distributed_by_quantity":0.032,"Tests\\Feature\\PurchaseEditTest::test_update_row_requires_at_least_one_field":0.1,"Tests\\Feature\\PurchaseEditTest::test_update_cell_rejects_negative_quantity":0.099,"Tests\\Feature\\PurchaseGenerateTest::test_generate_aggregates_order_items_into_header":0.098,"Tests\\Feature\\ReconciliationTest::test_build_excludes_unfinished_orders":0.016,"Tests\\Feature\\PurchaseEditTest::test_update_row_syncs_product_attributes":0.104,"Tests\\Feature\\PurchaseEditTest::test_update_row_syncs_to_product_catalog":0.019,"Tests\\Feature\\PurchaseEditTest::test_cell_detail_returns_items_with_order_info":0.101,"Tests\\Feature\\PurchaseEditTest::test_cell_detail_rejects_missing_params":0.1,"Tests\\Feature\\PurchaseEditTest::test_update_cell_weight_cascades_totals":0.103,"Tests\\Feature\\PurchaseEditTest::test_cell_sync_refreshes_snapshot_and_cascades":0.012,"Tests\\Feature\\PurchaseEditTest::test_edits_rejected_when_purchase_completed":0.109,"Tests\\Feature\\PurchaseEditTest::test_update_row_does_not_touch_manual_weight":0.106,"Tests\\Feature\\PurchaseEditTest::test_store_summary_returns_aggregated_items":0.103,"Tests\\Feature\\PurchaseEditTest::test_store_summary_aggregates_multiple_orders_with_weighted_price":0.061,"Tests\\Feature\\PurchaseEditTest::test_store_summary_rejects_missing_or_invalid_params":0.101,"Tests\\Feature\\ExportTest::test_export_invalid_type_rejected":0.016,"Tests\\Feature\\ExportTest::test_export_missing_purchase_rejected":0.006,"Tests\\Feature\\PurchaseEditTest::test_detail_returns_container_summary":0.022,"Tests\\Feature\\PurchaseEditTest::test_update_container_syncs_order_amounts":0.018,"Tests\\Feature\\PurchaseEditTest::test_update_container_rejected_when_purchase_completed":0.018,"Tests\\Feature\\PurchaseEditTest::test_update_container_rejects_incomplete_or_foreign_orders":0.026,"Tests\\Feature\\PurchaseEditTest::test_update_container_validates_negative_and_duplicate_orders":0.018,"Tests\\Feature\\ProductCategoryTest::test_create_child_category_under_top_level":0.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.006,"Tests\\Feature\\ProductCategoryTest::test_update_parent_with_children_cannot_move_under_category":0.006,"Tests\\Feature\\ProductCategoryTest::test_update_with_missing_parent_rejected":0.006,"Tests\\Feature\\ProductCategoryTest::test_create_product_with_parent_category_rejected":0.007,"Tests\\Feature\\ProductCategoryTest::test_create_product_with_leaf_category_ok":0.007,"Tests\\Feature\\ProductCategoryTest::test_mini_categories_returns_all_enabled_categories":0.048,"Tests\\Feature\\MiniBillTest::test_bill_list_derives_pay_state_and_settlement_date":0.048,"Tests\\Feature\\MiniBillTest::test_bill_list_summary_aggregates_unpaid":0.049,"Tests\\Feature\\MiniBillTest::test_bill_list_payable_filter":0.046,"Tests\\Feature\\MiniBillTest::test_bill_list_filters_by_bill_date_range":0.052,"Tests\\Feature\\MiniBillTest::test_bill_data_isolated_between_stores":0.089,"Tests\\Feature\\MiniBillTest::test_bill_detail_outputs_bill_items_and_orders":0.051,"Tests\\Feature\\StoreOrderTest::test_order_list_outputs_status_name_and_item_preview":0.054,"Tests\\Feature\\StoreOrderTest::test_order_list_filters_by_order_date_range":0.058,"Tests\\Feature\\StoreOrderTest::test_order_list_rejects_invalid_filters":0.049,"Tests\\Feature\\BillExportTest::test_export_merges_items_across_bills":0.103,"Tests\\Feature\\BillExportTest::test_export_filters_by_root_category":0.051,"Tests\\Feature\\BillExportTest::test_export_empty_category_rejected":0.069,"Tests\\Feature\\BillExportTest::test_export_requires_export_permission":0.05,"Tests\\Feature\\BillExportTest::test_export_invalid_ids_rejected":0.01,"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.005,"Tests\\Feature\\MiniNoticeTest::test_notice_list_includes_personal_and_broadcast":0.046,"Tests\\Feature\\MiniNoticeTest::test_broadcast_read_creates_personal_copy":0.048,"Tests\\Feature\\MiniNoticeTest::test_personal_notice_read_marks_directly":0.046,"Tests\\Feature\\MiniNoticeTest::test_other_users_notice_not_found":0.007,"Tests\\Feature\\StoreOrderTest::test_manual_status_flow_only_accept_and_cancel":0.1,"Tests\\Feature\\StoreOrderTest::test_manual_status_flow_rejects_delivery_and_completion":0.149,"Tests\\Feature\\StoreOrderTest::test_batch_status_only_accept_and_cancel":0.102,"Tests\\Feature\\BillPaymentTest::test_order_status_progresses_via_business_chain":0.063,"Tests\\Feature\\BillPaymentTest::test_manual_pay_accumulates_store_total_purchase_amount":0.068,"Tests\\Feature\\BillPaymentTest::test_payment_audit_pass_accumulates_store_total_purchase_amount":0.052,"Tests\\Feature\\BillPaymentTest::test_payment_audit_reject_does_not_accumulate":0.048,"Tests\\Feature\\MiniProductTest::test_product_detail_guest_sees_null_price":0.045,"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.045,"Tests\\Feature\\MiniProductTest::test_product_detail_off_shelf_or_missing_rejected":0.048,"Tests\\Feature\\MiniProductTest::test_product_list_store_user_sees_level_price":0.005,"Tests\\Feature\\MiniProductTest::test_product_list_guest_sees_null_price":0.044,"Tests\\Feature\\BillPaymentTest::test_admin_order_list_and_detail_show_bill_info":0.07,"Tests\\Feature\\BillPaymentTest::test_payment_detail_returns_voucher_urls":0.054,"Tests\\Feature\\StoreOrderTest::test_order_list_preview_includes_image_spec_and_unit":0.05,"Tests\\Feature\\ContainerReturnTest::test_press_containers_adds_amount_and_writes_record":0.06,"Tests\\Feature\\ContainerReturnTest::test_return_containers_deducts_amount":0.058,"Tests\\Feature\\ContainerReturnTest::test_mixed_sign_containers":0.061,"Tests\\Feature\\ContainerReturnTest::test_zero_containers_writes_no_record":0.061,"Tests\\Feature\\ContainerReturnTest::test_non_integer_containers_rejected":0.048,"Tests\\Feature\\ContainerReturnTest::test_manual_create_and_delete_routes_removed":0.007,"Tests\\Feature\\ContainerReturnTest::test_export_matrix_merges_same_day_and_zero_fills":0.092,"Tests\\Feature\\ContainerReturnTest::test_export_filters_by_selected_stores":0.129,"Tests\\Feature\\ContainerReturnTest::test_export_invalid_params_rejected":0.01,"Tests\\Feature\\ContainerReturnTest::test_export_empty_range_rejected":0.006,"Tests\\Feature\\ContainerReturnTest::test_export_requires_export_permission":0.009,"Tests\\Feature\\DashboardTest::test_guest_cannot_access_dashboard":0.002,"Tests\\Feature\\DashboardTest::test_requires_dashboard_analysis_ability":0.007,"Tests\\Feature\\DashboardTest::test_empty_database_returns_zero_structure":0.018,"Tests\\Feature\\DashboardTest::test_overview_excludes_cancelled_orders":0.051,"Tests\\Feature\\DashboardTest::test_week_growth_rate":0.054,"Tests\\Feature\\DashboardTest::test_trend_covers_30_days_with_zero_filling":0.052,"Tests\\Feature\\DashboardTest::test_category_sales_grouped_by_category":0.054,"Tests\\Feature\\DashboardTest::test_top_products_aggregated_across_orders":0.052,"Tests\\Feature\\DashboardTest::test_top_stores_ranked_by_amount":0.091,"Tests\\Feature\\DashboardTest::test_purchase_amount_prefers_actual_over_estimate":0.01,"Tests\\Feature\\DashboardTest::test_recon_summary_and_receivable":0.051,"Tests\\Feature\\DashboardTest::test_latest_orders_limited_and_ordered":0.053,"Tests\\Feature\\DashboardTest::test_archives_count_active_records_only":0.132,"Tests\\Feature\\DashboardDebugTest::test_debug_ability_check":0.044,"Tests\\Feature\\DashboardDebugTest::test_debug":0.045,"Tests\\Feature\\ProductPriceTest::test_batch_price_updates_cost_and_notifies_all_stores":0.089,"Tests\\Feature\\ProductPriceTest::test_level_percent_update_notifies_level_stores":0.091,"Tests\\Feature\\ProductPriceTest::test_level_update_without_percent_change_sends_no_notice":0.048,"Tests\\Feature\\ProductPriceTest::test_level_percent_validation":0.012,"Tests\\Feature\\ProductCostPriceTest::test_calc_level_price":0.001,"Tests\\Feature\\ProductCostPriceTest::test_calc_level_price_edge_cases":0.001,"Tests\\Feature\\ProductCostPriceTest::test_admin_list_contains_cost_price_and_level_prices":0.009,"Tests\\Feature\\ProductCostPriceTest::test_create_product_with_cost_price":0.008,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_cost_row_update":0.052,"Tests\\Feature\\ProductCostPriceTest::test_price_matrix_includes_cost_and_level_price_columns":0.007,"Tests\\Feature\\ProductCostPriceTest::test_mini_order_uses_level_price":0.047,"Tests\\Feature\\ProductCostPriceTest::test_mini_cart_uses_level_price":0.048,"Tests\\Feature\\CartTest::test_store_level_missing_rejected":0.006,"Tests\\Feature\\StoreOrderTest::test_store_level_missing_rejected":0.047,"Tests\\Feature\\PurchaseEditTest::test_update_row_skips_catalog_sync_when_product_deleted":0.107,"Tests\\Feature\\PurchaseEditTest::test_store_item_add_creates_item_and_recalculates":0.104,"Tests\\Feature\\PurchaseEditTest::test_store_item_update_merges_multi_order_rows":0.059,"Tests\\Feature\\PurchaseEditTest::test_store_item_remove_deletes_and_recalculates":0.102,"Tests\\Feature\\StoreOrderItemTest::test_update_store_item_recalculates_totals":0.059,"Tests\\Feature\\StoreOrderItemTest::test_store_item_edits_rejected_when_purchase_completed":0.063,"Tests\\Feature\\StoreOrderItemTest::test_update_store_item_validates_negative_values":0.057,"Tests\\Feature\\StoreOrderItemTest::test_add_store_item_uses_level_percent_price":0.059,"Tests\\Feature\\StoreOrderItemTest::test_add_store_item_rejected_when_product_deleted":0.058,"Tests\\Feature\\ExportTest::test_export_lists_all_catalog_products_with_totals_row":0.102,"Tests\\Feature\\ExportTest::test_export_supplier_filter":0.102,"Tests\\Feature\\ExportTest::test_export_stores_multi_sheet":0.108,"Tests\\Feature\\ExportTest::test_export_stores_single_store":0.101,"Tests\\Feature\\ExportTest::test_export_stores_without_items_rejected":0.142,"Tests\\Feature\\ExportTest::test_export_suppliers_multi_sheet":0.103,"Tests\\Feature\\ExportTest::test_export_suppliers_single":0.102,"Tests\\Feature\\ExportTest::test_export_suppliers_without_items_rejected":0.101,"Tests\\Feature\\PurchaseEditTest::test_update_row_cost_change_reprices_items_by_store_level":0.105,"Tests\\Feature\\PurchaseEditTest::test_update_row_without_cost_change_keeps_item_price":0.105,"Tests\\Feature\\CartTest::test_update_other_stores_item_rejected":0.092,"Tests\\Feature\\CartTest::test_delete_other_stores_item_rejected":0.089,"Tests\\Feature\\CartTest::test_cart_isolated_between_stores":0.09,"Tests\\Feature\\MiniAuthTest::test_login_issues_token_for_valid_credentials":0.129,"Tests\\Feature\\MiniAuthTest::test_login_rejects_wrong_password":0.123,"Tests\\Feature\\MiniAuthTest::test_login_rejects_unknown_username":0.003,"Tests\\Feature\\MiniAuthTest::test_disabled_store_cannot_login":0.125,"Tests\\Feature\\MiniAuthTest::test_login_validates_required_params":0.002,"Tests\\Feature\\MiniAuthTest::test_login_token_accesses_protected_routes":0.128,"Tests\\Feature\\MiniAuthTest::test_set_password_succeeds":0.218,"Tests\\Feature\\MiniAuthTest::test_set_password_rejects_wrong_old_password":0.125,"Tests\\Feature\\MiniAuthTest::test_set_password_validates_confirmation":0.045,"Tests\\Feature\\BillExportTest::test_mini_export_scoped_to_own_store":0.094,"Tests\\Feature\\MiniNoticeTest::test_other_stores_notice_not_found":0.086,"Tests\\Feature\\MiniProductTest::test_product_detail_store_sees_level_price":0.045,"Tests\\Feature\\MiniProductTest::test_product_list_store_sees_level_price":0.046,"Tests\\Feature\\MiniStoreTest::test_info_returns_current_store":0.044,"Tests\\Feature\\MiniStoreTest::test_disabled_store_rejected":0.047,"Tests\\Feature\\ProductImportExportTest::test_export_downloads_all_products":0.008,"Tests\\Feature\\ProductImportExportTest::test_export_filters_by_category":0.008,"Tests\\Feature\\ProductImportExportTest::test_export_template_mode":0.005,"Tests\\Feature\\ProductImportExportTest::test_export_requires_permission":0.006,"Tests\\Feature\\ProductImportExportTest::test_import_creates_products_and_auto_supplier":0.044,"Tests\\Feature\\ProductImportExportTest::test_import_matches_existing_supplier":0.018,"Tests\\Feature\\ProductImportExportTest::test_import_errors_abort_all_rows":0.017,"Tests\\Feature\\ProductImportExportTest::test_import_category_path_resolution":0.031,"Tests\\Feature\\ProductImportExportTest::test_import_rejects_invalid_header":0.014,"Tests\\Feature\\ProductImportExportTest::test_import_requires_permission":0.012,"Tests\\Feature\\ProductImportExportTest::test_list_filters_by_market":0.01}} \ No newline at end of file diff --git a/app/Exports/ProductExport.php b/app/Exports/ProductExport.php index 40d5910..b22c191 100644 --- a/app/Exports/ProductExport.php +++ b/app/Exports/ProductExport.php @@ -20,7 +20,7 @@ use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; class ProductExport implements FromCollection, WithStrictNullComparison, WithStyles { /** 列头(与 ProductImport 的解析顺序一一对应,改动需同步) */ - public const array HEADERS = ['品名', '分类', '供应商', '规格/包规', '单位', '成本价', '排序', '库存', '保质期', '状态', '备注']; + public const array HEADERS = ['品名', '分类', '供应商', '市场', '规格/包规', '单位', '成本价', '排序', '库存', '保质期', '状态', '备注']; private ?Collection $rows = null; @@ -46,7 +46,7 @@ class ProductExport implements FromCollection, WithStrictNullComparison, WithSty $rows = [self::HEADERS]; if ($this->template) { - $rows[] = ['西红柿', '蔬菜/茄果类', '示例供应商', '10斤/箱', '斤', 2.5, 0, 100, 3, '上架', '示例行,导入前请删除']; + $rows[] = ['西红柿', '蔬菜/茄果类', '示例供应商', '新发地', '10斤/箱', '斤', 2.5, 0, 100, 3, '上架', '示例行,导入前请删除']; return $this->rows = collect($rows); } @@ -65,6 +65,7 @@ class ProductExport implements FromCollection, WithStrictNullComparison, WithSty $product->name, $categoryPaths[(int) $product->category_id] ?? '', $supplierNames[(int) $product->supplier_id] ?? '', + $product->market, $product->spec, $product->unit, (float) $product->cost_price, @@ -87,7 +88,7 @@ class ProductExport implements FromCollection, WithStrictNullComparison, WithSty $this->collection(); $sheet->freezePane('A2'); - $widths = [20, 16, 14, 14, 8, 10, 8, 8, 8, 8, 24]; + $widths = [20, 16, 14, 12, 14, 8, 10, 8, 8, 8, 8, 24]; foreach ($widths as $index => $width) { $sheet->getColumnDimension(Coordinate::stringFromColumnIndex($index + 1))->setWidth($width); } diff --git a/app/Exports/PurchaseOrderExport.php b/app/Exports/PurchaseOrderExport.php index c1615c0..d8f6d47 100644 --- a/app/Exports/PurchaseOrderExport.php +++ b/app/Exports/PurchaseOrderExport.php @@ -126,6 +126,7 @@ class PurchaseOrderExport implements FromCollection, WithStrictNullComparison, W 'category' => $categoryNames[(int) $productId] ?? '', 'product_name' => (string) ($snapshot->product_name ?? $product->name), 'supplier' => $supplierNames[$rowSupplierId] ?? '', + 'market' => (string) ($product->market ?? ''), 'product_spec' => $spec, 'unit' => (string) ($snapshot->unit ?? $product->unit), 'cost_price' => (float) ($snapshot->cost_price ?? $product->cost_price), @@ -172,7 +173,7 @@ class PurchaseOrderExport implements FromCollection, WithStrictNullComparison, W // 列头 $rows[] = array_merge( - ['序号', '分类', '品名', '供应商', '包规', '单位', '成本', '参考零售价', '数量', '实际称重', '金额'], + ['序号', '分类', '品名', '供应商', '市场', '包规', '单位', '成本', '参考零售价', '数量', '实际称重', '金额'], array_values($this->storeNames), ); $this->specialRows[++$rowIndex] = 'header'; @@ -189,6 +190,7 @@ class PurchaseOrderExport implements FromCollection, WithStrictNullComparison, W $item['category'], $item['product_name'], $item['supplier'], + $item['market'], $item['product_spec'], $item['unit'], $item['cost_price'], @@ -208,7 +210,7 @@ class PurchaseOrderExport implements FromCollection, WithStrictNullComparison, W // 合计行(行合计 + 门店列合计) $rows[] = array_merge( - ['', '', '合计', '', '', '', '', '', (float) $totalQuantity, (float) $totalWeight, (float) $totalAmount], + ['', '', '合计', '', '', '', '', '', '', (float) $totalQuantity, (float) $totalWeight, (float) $totalAmount], array_values($storeTotals), ); $this->specialRows[++$rowIndex] = 'summary'; @@ -225,7 +227,7 @@ class PurchaseOrderExport implements FromCollection, WithStrictNullComparison, W $this->collection(); $sheet->freezePane('A' . ($this->headerRow + 1)); - $widths = [6, 10, 20, 12, 12, 8, 10, 12, 10, 12, 12]; + $widths = [6, 10, 20, 12, 12, 12, 8, 10, 12, 10, 12, 12]; foreach ($widths as $index => $width) { $sheet->getColumnDimension(Coordinate::stringFromColumnIndex($index + 1))->setWidth($width); } @@ -233,7 +235,7 @@ class PurchaseOrderExport implements FromCollection, WithStrictNullComparison, W // 门店列整列(列头 → 合计行)填充突出颜色 $storeCount = count($this->storeNames); for ($i = 0; $i < $storeCount; $i++) { - $column = Coordinate::stringFromColumnIndex(12 + $i); + $column = Coordinate::stringFromColumnIndex(13 + $i); $sheet->getColumnDimension($column)->setWidth(12); $sheet->getStyle($column . $this->headerRow . ':' . $column . $this->lastRow) ->getFill() diff --git a/app/Exports/PurchaseStoreSheet.php b/app/Exports/PurchaseStoreSheet.php index 867282e..82eb494 100644 --- a/app/Exports/PurchaseStoreSheet.php +++ b/app/Exports/PurchaseStoreSheet.php @@ -55,7 +55,7 @@ class PurchaseStoreSheet implements FromCollection, WithStrictNullComparison, Wi $rowIndex++; // 列头 - $rows[] = ['序号', '品名', '包规', '单位', '单价', '数量', '重量(斤)', '预计金额']; + $rows[] = ['序号', '品名', '市场', '包规', '单位', '单价', '数量', '重量(斤)', '预计金额']; $this->specialRows[++$rowIndex] = 'header'; $this->headerRow = $rowIndex; @@ -67,6 +67,7 @@ class PurchaseStoreSheet implements FromCollection, WithStrictNullComparison, Wi $rows[] = [ $sort + 1, $item['product_name'], + $item['market'], $item['product_spec'], $item['unit'], (float) $item['price'], @@ -81,7 +82,7 @@ class PurchaseStoreSheet implements FromCollection, WithStrictNullComparison, Wi } // 合计行 - $rows[] = ['', '合计', '', '', '', $totalQuantity, (float) $totalWeight, (float) $totalAmount]; + $rows[] = ['', '合计', '', '', '', '', $totalQuantity, (float) $totalWeight, (float) $totalAmount]; $this->specialRows[++$rowIndex] = 'summary'; return $this->rows = collect($rows); @@ -100,7 +101,7 @@ class PurchaseStoreSheet implements FromCollection, WithStrictNullComparison, Wi $this->collection(); $sheet->freezePane('A' . ($this->headerRow + 1)); - $widths = [6, 24, 14, 8, 10, 10, 12, 12]; + $widths = [6, 24, 12, 14, 8, 10, 10, 12, 12]; foreach ($widths as $index => $width) { $sheet->getColumnDimension(\PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($index + 1)) ->setWidth($width); diff --git a/app/Exports/PurchaseSupplierSheet.php b/app/Exports/PurchaseSupplierSheet.php index 61bfe04..20f0efa 100644 --- a/app/Exports/PurchaseSupplierSheet.php +++ b/app/Exports/PurchaseSupplierSheet.php @@ -56,7 +56,7 @@ class PurchaseSupplierSheet implements FromCollection, WithStrictNullComparison, $rowIndex++; // 列头 - $rows[] = ['序号', '品名', '包规', '单位', '成本价', '数量', '重量(斤)', '金额']; + $rows[] = ['序号', '品名', '市场', '包规', '单位', '成本价', '数量', '重量(斤)', '金额']; $this->specialRows[++$rowIndex] = 'header'; $this->headerRow = $rowIndex; @@ -68,6 +68,7 @@ class PurchaseSupplierSheet implements FromCollection, WithStrictNullComparison, $rows[] = [ $sort + 1, $item['product_name'], + $item['market'], $item['product_spec'], $item['unit'], (float) $item['cost_price'], @@ -82,7 +83,7 @@ class PurchaseSupplierSheet implements FromCollection, WithStrictNullComparison, } // 合计行 - $rows[] = ['', '合计', '', '', '', $totalQuantity, (float) $totalWeight, (float) $totalAmount]; + $rows[] = ['', '合计', '', '', '', '', $totalQuantity, (float) $totalWeight, (float) $totalAmount]; $this->specialRows[++$rowIndex] = 'summary'; return $this->rows = collect($rows); @@ -101,7 +102,7 @@ class PurchaseSupplierSheet implements FromCollection, WithStrictNullComparison, $this->collection(); $sheet->freezePane('A' . ($this->headerRow + 1)); - $widths = [6, 24, 14, 8, 10, 10, 12, 12]; + $widths = [6, 24, 12, 14, 8, 10, 10, 12, 12]; foreach ($widths as $index => $width) { $sheet->getColumnDimension(Coordinate::stringFromColumnIndex($index + 1))->setWidth($width); } diff --git a/app/Http/Controllers/Product/ProductController.php b/app/Http/Controllers/Product/ProductController.php index e6d97ce..3ad044e 100644 --- a/app/Http/Controllers/Product/ProductController.php +++ b/app/Http/Controllers/Product/ProductController.php @@ -35,6 +35,7 @@ class ProductController extends BaseController 'name' => 'like', 'category_id' => '=', 'supplier_id' => '=', + 'market' => 'like', 'status' => '=', ]; diff --git a/app/Http/Controllers/Purchase/PurchaseOrderController.php b/app/Http/Controllers/Purchase/PurchaseOrderController.php index ca0ef1d..976ff4c 100644 --- a/app/Http/Controllers/Purchase/PurchaseOrderController.php +++ b/app/Http/Controllers/Purchase/PurchaseOrderController.php @@ -134,6 +134,7 @@ class PurchaseOrderController extends BaseController 'unit' => $first->unit, // 单位 'supplier_id' => (int) $first->supplier_id, // 供应商id 'supplier' => $supplier, // 供应商 + 'market' => (string) ($product?->market ?? ''), // 市场(取商品档案) 'cost_price' => $first->cost_price, // 成本价 'quantity' => $quantity, 'weight' => (float) $weight, diff --git a/app/Http/Requests/Product/ProductFormRequest.php b/app/Http/Requests/Product/ProductFormRequest.php index d2e9f83..29db4c4 100644 --- a/app/Http/Requests/Product/ProductFormRequest.php +++ b/app/Http/Requests/Product/ProductFormRequest.php @@ -22,6 +22,7 @@ class ProductFormRequest extends BaseFormRequest return [ 'category_id' => ['required','integer', new Exists(ProductCategoryModel::class,'id')], 'supplier_id' => ['nullable','integer', 'exclude_if:supplier_id,0', new Exists(SupplierModel::class,'id')], + 'market' => 'nullable|string|max:50', 'name' => 'required|string|max:100', 'spec' => 'nullable|string|max:100', 'unit' => 'nullable|string|max:20', diff --git a/app/Imports/ProductImport.php b/app/Imports/ProductImport.php index 059fdbd..9cc6ca7 100644 --- a/app/Imports/ProductImport.php +++ b/app/Imports/ProductImport.php @@ -92,8 +92,8 @@ class ProductImport implements ToCollection */ private function parseRow(array $cells, Collection $categories, array $leafIds): array { - [$name, $categoryName, $supplierName, $spec, $unit, $costPrice, $sort, $stock, $shelfLife, $statusText, $remark] = - array_pad(array_slice($cells, 0, 11), 11, null); + [$name, $categoryName, $supplierName, $market, $spec, $unit, $costPrice, $sort, $stock, $shelfLife, $statusText, $remark] = + array_pad(array_slice($cells, 0, 12), 12, null); $errors = []; @@ -120,6 +120,11 @@ class ProductImport implements ToCollection $errors[] = '供应商名称最长 100 个字符'; } + $market = (string) $market; + if (mb_strlen($market) > 50) { + $errors[] = '市场最长 50 个字符'; + } + $spec = (string) $spec; if (mb_strlen($spec) > 100) { $errors[] = '规格/包规最长 100 个字符'; @@ -169,6 +174,7 @@ class ProductImport implements ToCollection 'category_id' => $categoryId, 'supplier_id' => 0, // 写入阶段按 supplier_name 解析(匹配或自动创建) 'supplier_name' => $supplierName, // 写入阶段解析,不入 product 表 + 'market' => $market, 'name' => $name, 'spec' => $spec, 'unit' => $unit, diff --git a/app/Models/ProductModel.php b/app/Models/ProductModel.php index f23d410..eea6d31 100644 --- a/app/Models/ProductModel.php +++ b/app/Models/ProductModel.php @@ -27,6 +27,7 @@ class ProductModel extends Model protected $fillable = [ 'category_id', 'supplier_id', + 'market', 'name', 'spec', 'unit', diff --git a/app/Services/PurchaseItemService.php b/app/Services/PurchaseItemService.php index e79195b..03d51b0 100644 --- a/app/Services/PurchaseItemService.php +++ b/app/Services/PurchaseItemService.php @@ -80,6 +80,7 @@ class PurchaseItemService 'product_name' => $first->product_name, 'product_spec' => $first->product_spec, 'unit' => $first->unit, + 'market' => (string) data_get($first, 'market', ''), // 加权平均单价(保证 单价×数量=预计金额) 'price' => $quantity > 0 ? bcdiv($amount, (string) $quantity, 2) @@ -124,6 +125,7 @@ class PurchaseItemService 'product_name' => $first->product_name, 'product_spec' => $first->product_spec, 'unit' => $first->unit, + 'market' => (string) data_get($first, 'market', ''), 'cost_price' => (string) $first->cost_price, 'quantity' => $quantity, 'weight' => $weight, @@ -151,17 +153,18 @@ class PurchaseItemService ->get() ->makeVisible('cost_price'); - // 排序键:商品分类 sort → 商品 sort(商品含软删除,保证历史单据可导出) + // 排序键:商品分类 sort → 商品 sort;市场取商品档案(商品含软删除,保证历史单据可导出) $products = ProductModel::withTrashed() ->with('category:id,sort') ->whereIn('id', $items->pluck('product_id')->unique()) - ->get(['id', 'category_id', 'sort']) + ->get(['id', 'category_id', 'sort', 'market']) ->keyBy('id'); foreach ($items as $item) { $product = $products->get((int) $item->product_id); $item->setAttribute('category_sort', (int) ($product?->category?->sort ?? 9999)); $item->setAttribute('product_sort', (int) ($product?->sort ?? 9999)); + $item->setAttribute('market', (string) ($product?->market ?? '')); } return $items; diff --git a/database/factories/ProductModelFactory.php b/database/factories/ProductModelFactory.php index 73075f5..a3de552 100644 --- a/database/factories/ProductModelFactory.php +++ b/database/factories/ProductModelFactory.php @@ -27,6 +27,7 @@ class ProductModelFactory extends Factory return [ 'category_id' => 0, 'supplier_id' => 0, + 'market' => '', 'name' => self::NAMES[$seq % count(self::NAMES)] . $seq, 'spec' => self::SPECS[$seq % count(self::SPECS)], 'unit' => self::UNITS[$seq % count(self::UNITS)], diff --git a/database/migrations/2026_07_23_030609_create_product_table.php b/database/migrations/2026_07_23_030609_create_product_table.php index fe16929..37ac733 100644 --- a/database/migrations/2026_07_23_030609_create_product_table.php +++ b/database/migrations/2026_07_23_030609_create_product_table.php @@ -32,6 +32,7 @@ return new class extends Migration $table->increments('id')->comment('商品ID'); $table->integer('category_id')->default(0)->comment('商品分类ID'); $table->integer('supplier_id')->default(0)->comment('默认供应商ID'); + $table->string('market', 50)->default('')->comment('市场(如:新发地、岳各庄)'); $table->string('name', 100)->comment('品名'); $table->string('spec', 100)->default('')->comment('规格/包规'); $table->string('unit', 20)->default('斤')->comment('计价单位(斤/件/箱等)'); diff --git a/docs/小程序端修改文档.md b/docs/小程序端修改文档.md deleted file mode 100644 index c5a2062..0000000 --- a/docs/小程序端修改文档.md +++ /dev/null @@ -1,172 +0,0 @@ -# 小程序端修改文档(门店端) - -> 版本:2026-08-17 -> 后端变更:用户表与门店表合并(一个用户就是一个门店),门店登录由「微信静默登录」改为「账号 + 密码登录」。 -> 本文档面向小程序(门店端)开发者,说明需要配合修改的内容。**未列出的接口均无变化**。 - ---- - -## 一、变更总览 - -| 事项 | 旧 | 新 | -|------|-----|-----| -| 登录方式 | `wx.login` 拿 code,调 `/mini/auth/login` 静默登录 | 账号 + 密码登录(账号由商家后台分配) | -| 注册 | `POST /mini/auth/register`(微信 code + 手机号 + 门店编码) | **接口下线**,门店账号由商家在 PC 后台创建 | -| 登录主体 | 微信用户(user),再绑定门店(store) | **门店即用户**:登录成功返回的就是门店本身 | -| 修改密码 | 无 | 新增 `PUT /mini/auth/password` | -| Token 鉴权 | Bearer Token | **不变** | -| 其他业务接口 | 购物车 / 订单 / 账单 / 支付 / 通知 / 商品 / 首页 | **全部不变**(路径、入参、出参) | - -### 需要小程序端配合的改动 - -1. **重做登录页**:去掉 `wx.login` 流程,改为账号 + 密码表单(账号密码由商家线下告知门店)。 -2. **删除注册页/绑定门店页**:注册接口已下线,「尚未绑定门店」的场景已不存在。 -3. **用户信息结构调整**:登录与 `auth/info` 返回的对象字段变化(见下文)。 -4. **新增「修改密码」入口**(建议放在「我的」页面)。 -5. **重新登录**:旧 token 全部失效,需引导用户用账号密码重新登录一次。 - ---- - -## 二、登录接口(变更) - -### `POST /mini/auth/login` - -**请求参数(变更):** - -| 参数 | 类型 | 必填 | 说明 | -|------|------|------|------| -| username | string | 是 | 登录账号(商家后台分配,4~20 位) | -| password | string | 是 | 登录密码 | - -> 旧参数 `code`(wx.login 凭证)已废弃,无需再传,也无需调用 `wx.login`。 - -**成功响应:** - -```json -{ - "success": true, - "msg": "登录成功", - "data": { - "token": "1|xxxxxxxxxxxxxxxx", - "user": { - "id": 3, - "name": "好又多超市", - "code": "S000003", - "username": "hyd001", - "avatar": "", - "level_id": 1, - "level": { "id": 1, "name": "一级客户" }, - "contact": "张三", - "phone": "13800000000", - "address": "xx路 1 号", - "payment_cycle_days": 2, - "status": 1, - "last_login_at": "2026-08-17 10:00:00", - "created_at": "2026-08-01 09:00:00" - } - } -} -``` - -**user 字段变化要点:** - -- `user` 现在**就是门店对象**(扁平结构),不再有 `user.store` 嵌套,也不再有 `openid`、`unionid`、`nickname`、`email`、`store_id` 字段。 -- 门店名称取 `user.name`(原 `user.store.name`);客户等级取 `user.level`。 -- `password` 字段永不返回。 - -**失败响应(`success: false`,`msg` 提示):** - -| 场景 | msg | -|------|-----| -| 账号不存在或密码错误 | 账号或密码错误 | -| 门店已停用 | 账号已被停用,请联系客服处理 | -| 参数缺失 | 请输入登录账号 / 请输入登录密码 | - -**Token 使用(不变):** 后续所有请求携带请求头 `Authorization: Bearer {token}`。 - ---- - -## 三、注册接口(下线) - -### ~~`POST /mini/auth/register`~~ —— 已删除 - -- 门店账号全部由商家在 PC 后台「客户管理 → 门店管理」创建并分配(含登录账号、初始密码)。 -- 小程序端请移除注册页、「输入门店编码绑定」页及相关逻辑。 -- 同步移除 `wx.getPhoneNumber` 手机号授权流程(后端已不再使用)。 - ---- - -## 四、当前用户信息(结构变化) - -### `GET /mini/auth/info`(需登录) - -返回当前登录门店完整信息(含 `level` 等级嵌套),字段与登录接口的 `user` 一致(见上文示例)。原来读取 `info.store.xxx` 的地方改为直接读 `info.xxx`。 - ---- - -## 五、修改密码(新增) - -### `PUT /mini/auth/password`(需登录) - -**请求参数:** - -| 参数 | 类型 | 必填 | 说明 | -|------|------|------|------| -| oldPassword | string | 是 | 原密码 | -| newPassword | string | 是 | 新密码(6~20 位) | -| rePassword | string | 是 | 确认新密码(须与 newPassword 一致) | - -**失败场景:** 原密码不正确 / 两次输入的密码不一致 / 新密码至少 6 位。 - -> 修改密码成功后现有 token 仍然有效,无需强制重新登录。 - ---- - -## 六、门店信息(不变) - -### `GET /mini/store/info`(需登录) - -返回:`{ id, name, code, contact, phone, address, payment_cycle_days }`(门店名称/编码/回款周期只读,由后台维护)。 - -### `PUT /mini/store/info`(需登录) - -可改字段不变:`contact`(联系人)、`phone`(联系电话)、`address`(地址)。 - ---- - -## 七、以下接口完全不变 - -路径、入参、出参均无变化,仅内部归属从「用户」变为「门店」(对小程序透明): - -| 模块 | 接口 | -|------|------| -| 首页 | `GET /mini/home/*`(轮播/导航/促销) | -| 商品 | `GET /mini/product/categories`、`GET /mini/product/list`、`GET /mini/product/{id}`(登录后按本店等级显示价格) | -| 购物车 | `POST /mini/cart`、`GET /mini/cart`、`PUT /mini/cart/{id}`、`DELETE /mini/cart/{id}`、`DELETE /mini/cart` | -| 订单 | `POST /mini/order`、`GET /mini/order`、`GET /mini/order/summary`、`GET /mini/order/{id}`、`PUT /mini/order/{id}/cancel` | -| 账单 | `GET /mini/bill`、`GET /mini/bill/{id}`、`GET /mini/bill/export` | -| 支付 | `GET /mini/payment/config`、`GET /mini/payment`、`POST /mini/payment`、`GET /mini/payment/{id}` | -| 通知 | `GET /mini/notice`、`PUT /mini/notice/{id}/read` | -| 上传 | `POST /mini/upload` | - -> 唯一语义差异:数据隔离现在天然按门店划分(一个门店一个账号),原「同一门店多个微信账号各自购物车」的合并场景不再存在。 - ---- - -## 八、错误语义(不变) - -- 未携带/无效 token:HTTP `401`。 -- 业务错误:HTTP `200` + `{ success: false, msg: "..." }`,直接 toast `msg` 即可。 -- 「门店未设置客户等级,无法加购/下单,请联系客服」等提示文案不变。 -- 「尚未绑定门店」提示已移除(该场景不存在)。 - ---- - -## 九、上线 Checklist(小程序端) - -- [ ] 登录页改为账号 + 密码表单,移除 `wx.login` / `wx.getPhoneNumber` 调用 -- [ ] 移除注册页、门店绑定页及路由 -- [ ] 全局用户信息读取点从 `user.store.*` 调整为 `user.*`(门店名、等级、回款周期等) -- [ ] 「我的」页面新增修改密码入口 -- [ ] 旧版本缓存的 token 失效处理:401 时引导重新登录 -- [ ] 等级价格展示逻辑不变(接口返回的 `price` 字段含义不变) diff --git a/tests/Feature/ExportTest.php b/tests/Feature/ExportTest.php index 8696d79..4305d48 100644 --- a/tests/Feature/ExportTest.php +++ b/tests/Feature/ExportTest.php @@ -108,12 +108,14 @@ class ExportTest extends ProcurementTestCase 'cost_price' => '5.00', 'spec' => '10斤/箱', 'supplier_id' => $supplierA->id, + 'market' => '新发地', ]); $meat = ProductModel::factory()->create([ 'status' => ProductModel::STATUS_ON, 'cost_price' => '20.00', 'spec' => '20斤/箱', 'supplier_id' => $supplierB->id, + 'market' => '岳各庄', ]); $this->actingAsMiniStore($storeA); @@ -154,24 +156,25 @@ class ExportTest extends ProcurementTestCase if ($rows->count() !== 8) { return false; } - // 列头含参考零售价与门店列 + // 列头含市场/参考零售价与门店列(市场列在供应商后,门店列从索引 12 起) $header = $rows[3]; - if ($header[7] !== '参考零售价' || $header[11] !== $storeA->name || $header[12] !== $storeB->name) { + if ($header[4] !== '市场' || $header[8] !== '参考零售价' || $header[12] !== $storeA->name || $header[13] !== $storeB->name) { return false; } - // 蔬菜行:数量 2+3=5、金额 25、参考零售价 5÷10=0.50、门店列 2/3 + // 蔬菜行:市场 新发地、数量 2+3=5、金额 25、参考零售价 5÷10=0.50、门店列 2/3 $vegRow = $rows->firstWhere(2, $veg->name); if ($vegRow === null - || (float) $vegRow[8] !== 5.0 || (float) $vegRow[10] !== 25.0 - || (float) $vegRow[7] !== 0.5 - || (float) $vegRow[11] !== 2.0 || (float) $vegRow[12] !== 3.0) { + || $vegRow[4] !== '新发地' + || (float) $vegRow[9] !== 5.0 || (float) $vegRow[11] !== 25.0 + || (float) $vegRow[8] !== 0.5 + || (float) $vegRow[12] !== 2.0 || (float) $vegRow[13] !== 3.0) { return false; } // 无订货商品行:数量 0、参考零售价留空、门店列 0 $extraRow = $rows->firstWhere(2, $extra->name); if ($extraRow === null - || (float) $extraRow[8] !== 0.0 || $extraRow[7] !== '' - || (float) $extraRow[11] !== 0.0) { + || (float) $extraRow[9] !== 0.0 || $extraRow[8] !== '' + || (float) $extraRow[12] !== 0.0) { return false; } // 合计行:总数量 6、总金额 45、门店列合计 3/3 @@ -179,8 +182,8 @@ class ExportTest extends ProcurementTestCase if ($total[2] !== '合计') { return false; } - return (float) $total[8] === 6.0 && (float) $total[10] === 45.0 - && (float) $total[11] === 3.0 && (float) $total[12] === 3.0; + return (float) $total[9] === 6.0 && (float) $total[11] === 45.0 + && (float) $total[12] === 3.0 && (float) $total[13] === 3.0; } ); } @@ -234,12 +237,12 @@ class ExportTest extends ProcurementTestCase if ($rows->count() !== 6) { return false; } - if ($rows[2] !== ['序号', '品名', '包规', '单位', '单价', '数量', '重量(斤)', '预计金额']) { + if ($rows[2] !== ['序号', '品名', '市场', '包规', '单位', '单价', '数量', '重量(斤)', '预计金额']) { return false; } // 合计:数量 2+1=3,金额 10+20=30 $total = $rows->last(); - return $total[1] === '合计' && (int) $total[5] === 3 && (float) $total[7] === 30.0; + return $total[1] === '合计' && (int) $total[6] === 3 && (float) $total[8] === 30.0; } ); } @@ -294,23 +297,23 @@ class ExportTest extends ProcurementTestCase if ($titles !== [$supplierA->name, $supplierB->name]) { return false; } - // 供应商甲:蔬菜 2+3=5 件、金额 5×5=25 + // 供应商甲:蔬菜 2+3=5 件、金额 5×5=25(市场列在品名后) $rowsA = $sheets[0]->collection()->values(); - if ($rowsA[2] !== ['序号', '品名', '包规', '单位', '成本价', '数量', '重量(斤)', '金额']) { + if ($rowsA[2] !== ['序号', '品名', '市场', '包规', '单位', '成本价', '数量', '重量(斤)', '金额']) { return false; } $vegRow = $rowsA->firstWhere(1, $veg->name); - if ($vegRow === null || (int) $vegRow[5] !== 5 || (float) $vegRow[7] !== 25.0) { + if ($vegRow === null || $vegRow[2] !== '新发地' || (int) $vegRow[6] !== 5 || (float) $vegRow[8] !== 25.0) { return false; } $totalA = $rowsA->last(); - if ($totalA[1] !== '合计' || (int) $totalA[5] !== 5 || (float) $totalA[7] !== 25.0) { + if ($totalA[1] !== '合计' || (int) $totalA[6] !== 5 || (float) $totalA[8] !== 25.0) { return false; } // 供应商乙:肉 1 件、金额 20 $rowsB = $sheets[1]->collection()->values(); $meatRow = $rowsB->firstWhere(1, $meat->name); - return $meatRow !== null && (int) $meatRow[5] === 1 && (float) $meatRow[7] === 20.0; + return $meatRow !== null && $meatRow[2] === '岳各庄' && (int) $meatRow[6] === 1 && (float) $meatRow[8] === 20.0; } ); } diff --git a/tests/Feature/ProductImportExportTest.php b/tests/Feature/ProductImportExportTest.php index 4e96a5b..d1fdba8 100644 --- a/tests/Feature/ProductImportExportTest.php +++ b/tests/Feature/ProductImportExportTest.php @@ -62,6 +62,7 @@ class ProductImportExportTest extends ProcurementTestCase ProductModel::factory()->create([ 'category_id' => $leaf->id, 'supplier_id' => $supplier->id, + 'market' => '新发地', 'name' => '上海青', 'spec' => '10斤/箱', 'unit' => '斤', @@ -93,19 +94,19 @@ class ProductImportExportTest extends ProcurementTestCase if ($row === null) { return false; } - // [品名, 分类路径, 供应商, 规格, 单位, 成本价, 排序, 库存, 保质期, 状态, 备注] - if ($row[1] !== '蔬菜/叶菜类' || $row[2] !== '张三蔬菜批发' || $row[9] !== '上架') { + // [品名, 分类路径, 供应商, 市场, 规格, 单位, 成本价, 排序, 库存, 保质期, 状态, 备注] + if ($row[1] !== '蔬菜/叶菜类' || $row[2] !== '张三蔬菜批发' || $row[3] !== '新发地' || $row[10] !== '上架') { return false; } - if ((float) $row[5] !== 2.5 || (int) $row[6] !== 3 || (int) $row[7] !== 100 || (int) $row[8] !== 2) { + if ((float) $row[6] !== 2.5 || (int) $row[7] !== 3 || (int) $row[8] !== 100 || (int) $row[9] !== 2) { return false; } - if ($row[10] !== '新鲜直达') { + if ($row[11] !== '新鲜直达') { return false; } $offRow = $rows->firstWhere(0, '下架商品'); - return $offRow !== null && $offRow[9] === '下架' && $offRow[2] === ''; + return $offRow !== null && $offRow[10] === '下架' && $offRow[2] === '' && $offRow[3] === ''; } ); } @@ -164,6 +165,22 @@ class ProductImportExportTest extends ProcurementTestCase $this->assertFalse($response->json('success'), '缺少导出权限点应被拦截'); } + /** 列表按市场模糊筛选 */ + public function test_list_filters_by_market(): void + { + ProductModel::factory()->create(['name' => '大白菜X', 'market' => '新发地']); + ProductModel::factory()->create(['name' => '土豆X', 'market' => '岳各庄']); + ProductModel::factory()->create(['name' => '无市场商品X', 'market' => '']); + + $this->actingAsSysUser(); + $response = $this->getJson('/product/goods?market=新发'); + $response->assertJsonPath('success', true); + $names = collect($response->json('data.data'))->pluck('name'); + $this->assertContains('大白菜X', $names); + $this->assertNotContains('土豆X', $names); + $this->assertNotContains('无市场商品X', $names); + } + // ==================== 导入 ==================== /** 正常导入:新增商品 + 供应商自动创建 + 默认值(单位斤/状态上架) */ @@ -174,8 +191,8 @@ class ProductImportExportTest extends ProcurementTestCase $this->actingAsSysUser(); $file = $this->makeUploadFile([ ProductExport::HEADERS, - ['上海青', '叶菜类', '新供应商A', '10斤/箱', '', 2.5, 3, 100, 2, '下架', '备注信息'], - ['西红柿', '蔬菜/茄果类', '', '散装', '箱', 1.8, '', '', '', '', ''], + ['上海青', '叶菜类', '新供应商A', '新发地', '10斤/箱', '', 2.5, 3, 100, 2, '下架', '备注信息'], + ['西红柿', '蔬菜/茄果类', '', '', '散装', '箱', 1.8, '', '', '', '', ''], ]); // 第二行用到了不存在的「茄果类」路径 → 先补建该分类 ProductCategoryModel::create(['name' => '茄果类', 'parent_id' => $leaf->parent_id, 'sort' => 1, 'status' => 1]); @@ -187,6 +204,7 @@ class ProductImportExportTest extends ProcurementTestCase $productA = ProductModel::where('name', '上海青')->first(); $this->assertNotNull($productA); $this->assertSame($leaf->id, $productA->category_id); + $this->assertSame('新发地', $productA->market); $this->assertSame('斤', $productA->unit, '单位留空应默认斤'); $this->assertSame(ProductModel::STATUS_OFF, $productA->status); $this->assertSame('2.50', (string) $productA->cost_price); @@ -197,6 +215,7 @@ class ProductImportExportTest extends ProcurementTestCase $productB = ProductModel::where('name', '西红柿')->first(); $this->assertNotNull($productB); + $this->assertSame('', $productB->market, '市场留空应为空字符串'); $this->assertSame(0, $productB->supplier_id, '供应商留空应为 0(不创建)'); $this->assertSame(ProductModel::STATUS_ON, $productB->status, '状态留空应默认上架'); $this->assertSame(0, $productB->sort); @@ -211,7 +230,7 @@ class ProductImportExportTest extends ProcurementTestCase $this->actingAsSysUser(); $file = $this->makeUploadFile([ ProductExport::HEADERS, - ['黄瓜', '叶菜类', '老供应商', '散装', '斤', 1.2, 0, 0, 0, '上架', ''], + ['黄瓜', '叶菜类', '老供应商', '', '散装', '斤', 1.2, 0, 0, 0, '上架', ''], ]); $this->post('/product/goods/import', ['file' => $file])->assertJsonPath('success', true); @@ -227,10 +246,10 @@ class ProductImportExportTest extends ProcurementTestCase $this->actingAsSysUser(); $file = $this->makeUploadFile([ ProductExport::HEADERS, - ['正常商品', '叶菜类', '供应商X', '散装', '斤', 1.0, 0, 0, 0, '上架', ''], - ['', '叶菜类', '', '散装', '斤', 1.0, 0, 0, 0, '上架', ''], - ['坏商品A', '不存在的分类', '', '散装', '斤', 1.0, 0, 0, 0, '上架', ''], - ['坏商品B', '叶菜类', '', '散装', '斤', 'abc', 0, 0, 0, '上架', ''], + ['正常商品', '叶菜类', '供应商X', '', '散装', '斤', 1.0, 0, 0, 0, '上架', ''], + ['', '叶菜类', '', '', '散装', '斤', 1.0, 0, 0, 0, '上架', ''], + ['坏商品A', '不存在的分类', '', '', '散装', '斤', 1.0, 0, 0, 0, '上架', ''], + ['坏商品B', '叶菜类', '', '', '散装', '斤', 'abc', 0, 0, 0, '上架', ''], ]); $response = $this->post('/product/goods/import', ['file' => $file]); @@ -260,16 +279,17 @@ class ProductImportExportTest extends ProcurementTestCase // 路径写法命中蔬菜/叶菜 $file = $this->makeUploadFile([ ProductExport::HEADERS, - ['菠菜', '蔬菜/叶菜', '', '散装', '斤', 1.0, 0, 0, 0, '上架', ''], + ['菠菜', '蔬菜/叶菜', '', '岳各庄', '散装', '斤', 1.0, 0, 0, 0, '上架', ''], ]); $this->post('/product/goods/import', ['file' => $file])->assertJsonPath('success', true); $this->assertSame($vegLeaf->id, ProductModel::where('name', '菠菜')->value('category_id')); + $this->assertSame('岳各庄', ProductModel::where('name', '菠菜')->value('market')); // 单名称歧义 + 非末级分类 → 报错零写入 $file = $this->makeUploadFile([ ProductExport::HEADERS, - ['歧义商品', '叶菜', '', '散装', '斤', 1.0, 0, 0, 0, '上架', ''], - ['非末级商品', '蔬菜', '', '散装', '斤', 1.0, 0, 0, 0, '上架', ''], + ['歧义商品', '叶菜', '', '', '散装', '斤', 1.0, 0, 0, 0, '上架', ''], + ['非末级商品', '蔬菜', '', '', '散装', '斤', 1.0, 0, 0, 0, '上架', ''], ]); $response = $this->post('/product/goods/import', ['file' => $file]); $response->assertJsonPath('success', false); @@ -299,7 +319,7 @@ class ProductImportExportTest extends ProcurementTestCase $file = $this->makeUploadFile([ ProductExport::HEADERS, - ['上海青', '叶菜类', '', '散装', '斤', 1.0, 0, 0, 0, '上架', ''], + ['上海青', '叶菜类', '', '', '散装', '斤', 1.0, 0, 0, 0, '上架', ''], ]); $response = $this->post('/product/goods/import', ['file' => $file]); $this->assertFalse($response->json('success'), '缺少导入权限点应被拦截'); diff --git a/web/domain/iProduct.ts b/web/domain/iProduct.ts index e303aa5..6c00fc4 100644 --- a/web/domain/iProduct.ts +++ b/web/domain/iProduct.ts @@ -21,6 +21,8 @@ export default interface IProduct { category_id?: number; /** 供应商ID */ supplier_id?: number; + /** 市场(如:新发地、岳各庄) */ + market?: string; /** 商品名称 */ name?: string; /** 规格/包规 */ diff --git a/web/domain/iPurchaseOrder.ts b/web/domain/iPurchaseOrder.ts index 715f98e..42b6dc8 100644 --- a/web/domain/iPurchaseOrder.ts +++ b/web/domain/iPurchaseOrder.ts @@ -17,6 +17,8 @@ export interface IPurchaseDetailRow { unit: string; supplier_id: number; supplier?: { id: number; name: string } | null; + /** 市场(取商品档案) */ + market?: string; /** 成本 */ cost_price: number; /** 合计数量 */ @@ -35,6 +37,8 @@ export interface IPurchaseSupplierItem { /** 规格/包规 */ product_spec: string; unit: string; + /** 市场(取商品档案) */ + market?: string; /** 成本价 */ cost_price: string; /** 数量(包数) */ @@ -193,6 +197,8 @@ export interface IPurchaseStoreItem { /** 规格/包规 */ product_spec: string; unit: string; + /** 市场(取商品档案) */ + market?: string; /** 单价(每包;多笔单价时为加权平均,保证 单价×数量=预计金额) */ price: string; /** 数量(包数) */ diff --git a/web/pages/product/goods.tsx b/web/pages/product/goods.tsx index 2feff58..2a8d5bf 100644 --- a/web/pages/product/goods.tsx +++ b/web/pages/product/goods.tsx @@ -401,6 +401,18 @@ const ProductGoodsPage: React.FC = () => { }, render: (_, record) => record.supplier?.name ?? '-', }, + { + title: '市场', + dataIndex: 'market', + valueType: 'text', + align: 'center', + fieldProps: { + placeholder: '市场名称,如:新发地', + maxLength: 50, + allowClear: true, + }, + render: (_, record) => record.market || '-', + }, { title: '状态', dataIndex: 'status', @@ -552,7 +564,7 @@ const ProductGoodsPage: React.FC = () => { >