diff --git a/.phpunit.result.cache b/.phpunit.result.cache index 29daa9d..4bd90e5 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.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 +{"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,"Tests\\Feature\\MiniReportTest::test_report_accumulates_same_product_across_orders":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,"Tests\\Feature\\MiniReportTest::test_report_aggregates_total_and_item_percent":0.089,"Tests\\Feature\\MiniReportTest::test_report_accumulates_same_product_across_orders":0.047,"Tests\\Feature\\MiniReportTest::test_report_filters_by_custom_date_range":0.047,"Tests\\Feature\\MiniReportTest::test_report_excludes_cancelled_and_deleted_orders":0.048,"Tests\\Feature\\MiniReportTest::test_report_preset_month_and_last_month":0.05,"Tests\\Feature\\MiniReportTest::test_report_preset_week_and_last_week":0.051,"Tests\\Feature\\MiniReportTest::test_report_defaults_to_current_month":0.047,"Tests\\Feature\\MiniReportTest::test_report_data_isolated_between_stores":0.087,"Tests\\Feature\\MiniReportTest::test_report_empty_period_returns_zero":0.048,"Tests\\Feature\\MiniReportTest::test_report_validates_params":0.052}} \ No newline at end of file diff --git a/app/Http/Controllers/Mini/ProductController.php b/app/Http/Controllers/Mini/ProductController.php index de95c43..14215ba 100644 --- a/app/Http/Controllers/Mini/ProductController.php +++ b/app/Http/Controllers/Mini/ProductController.php @@ -18,7 +18,7 @@ use Modules\AnnoRoute\Attribute\RequestAttribute; class ProductController extends BaseMiniController { /** 分类树(全部启用分类,含暂无商品的分类) */ - #[GetRoute('/product/categories', authorize: true)] + #[GetRoute('/product/categories', authorize: false)] public function categories(): JsonResponse { return $this->success(ProductCategoryModel::getTreeData(['*'], true)); diff --git a/app/Http/Controllers/Mini/ReportController.php b/app/Http/Controllers/Mini/ReportController.php new file mode 100644 index 0000000..f99df43 --- /dev/null +++ b/app/Http/Controllers/Mini/ReportController.php @@ -0,0 +1,142 @@ +validate([ + 'preset' => 'nullable|string|in:week,last_week,month,last_month', + 'start_date' => 'required_with:end_date|nullable|date_format:Y-m-d', + 'end_date' => 'required_with:start_date|nullable|date_format:Y-m-d|after_or_equal:start_date', + ], [ + 'preset.in' => 'preset 参数只能是 week/last_week/month/last_month', + 'start_date.required_with' => '自定义区间需同时提供开始与结束日期', + 'end_date.required_with' => '自定义区间需同时提供开始与结束日期', + 'start_date.date_format' => '开始日期格式为 Y-m-d', + 'end_date.date_format' => '结束日期格式为 Y-m-d', + 'end_date.after_or_equal' => '结束日期不能早于开始日期', + ]); + + $store = $this->currentStore($request); + + [$startDate, $endDate, $preset] = $this->resolveDateRange($params); + + // 明细联订货单:按订单日期/状态过滤(已取消、已删除订单不计入) + $baseQuery = StoreOrderItemModel::query() + ->join('store_order', static function ($join): void { + $join->on('store_order.id', '=', 'store_order_item.order_id') + ->whereNull('store_order.deleted_at'); + }) + ->where('store_order_item.store_id', $store->id) + ->where('store_order.status', '<>', StoreOrderModel::STATUS_CANCELLED) + ->whereDate('store_order.order_date', '>=', $startDate) + ->whereDate('store_order.order_date', '<=', $endDate); + + $totals = (clone $baseQuery) + ->selectRaw('COALESCE(SUM(store_order_item.amount), 0) as total_amount') + ->selectRaw('COALESCE(SUM(store_order_item.quantity), 0) as total_quantity') + ->selectRaw('COUNT(DISTINCT store_order_item.order_id) as order_count') + ->first(); + + $totalAmount = bcadd((string) $totals->total_amount, '0', 2); + + $rows = (clone $baseQuery) + ->select('store_order_item.product_id') + ->selectRaw('MAX(store_order_item.product_name) as product_name') + ->selectRaw('MAX(store_order_item.product_spec) as product_spec') + ->selectRaw('MAX(store_order_item.unit) as unit') + ->selectRaw('COALESCE(SUM(store_order_item.quantity), 0) as quantity') + ->selectRaw('COALESCE(SUM(store_order_item.weight), 0) as weight') + ->selectRaw('COALESCE(SUM(store_order_item.amount), 0) as amount') + ->groupBy('store_order_item.product_id') + ->orderByDesc('amount') + ->orderBy('store_order_item.product_id') + ->get(); + + $items = $rows->map(static function (StoreOrderItemModel $row) use ($totalAmount): array { + // 占比 %(1 位小数,四舍五入):tenths = round(amount × 1000 ÷ total),再 ÷10 + $percent = 0.0; + if (bccomp($totalAmount, '0', 2) > 0) { + $tenths = bcdiv( + bcadd(bcmul((string) $row->amount, '1000', 2), bcdiv($totalAmount, '2', 0), 2), + $totalAmount, + 0 + ); + $percent = (float) bcdiv($tenths, '10', 1); + } + + return [ + 'product_id' => (int) $row->product_id, + 'product_name' => (string) $row->product_name, + 'product_spec' => (string) $row->product_spec, + 'unit' => (string) $row->unit, + 'quantity' => (int) $row->quantity, + 'weight' => bcadd((string) $row->weight, '0', 3), + 'amount' => bcadd((string) $row->amount, '0', 2), + 'percent' => $percent, + ]; + })->values()->all(); + + return $this->success([ + 'preset' => $preset, + 'start_date' => $startDate, + 'end_date' => $endDate, + 'total_amount' => $totalAmount, + 'total_quantity' => (int) $totals->total_quantity, + 'order_count' => (int) $totals->order_count, + 'item_count' => count($items), + 'items' => $items, + ]); + } + + /** + * 解析统计区间:自定义日期优先;否则按 preset 推导(进行中的周期封顶到今天) + * + * @param array{preset?: ?string, start_date?: ?string, end_date?: ?string} $params + * @return array{0: string, 1: string, 2: string} [开始日期, 结束日期, 实际 preset] + */ + private function resolveDateRange(array $params): array + { + if (! empty($params['start_date']) && ! empty($params['end_date'])) { + return [$params['start_date'], $params['end_date'], 'custom']; + } + + $preset = (string) ($params['preset'] ?? 'month'); + $today = Carbon::today(); + [$start, $end] = match ($preset) { + 'week' => [$today->copy()->startOfWeek(), $today->copy()->endOfWeek()], + 'last_week' => [$today->copy()->subWeek()->startOfWeek(), $today->copy()->subWeek()->endOfWeek()], + 'last_month' => [$today->copy()->subMonthNoOverflow()->startOfMonth(), $today->copy()->subMonthNoOverflow()->endOfMonth()], + default => [$today->copy()->startOfMonth(), $today->copy()->endOfMonth()], + }; + // 未来日期本无数据,封顶到今天避免误解 + if ($end->greaterThan($today)) { + $end = $today->copy(); + } + + return [$start->toDateString(), $end->toDateString(), $preset]; + } +} diff --git a/docs/api/mini-report-purchase.md b/docs/api/mini-report-purchase.md new file mode 100644 index 0000000..b604a5b --- /dev/null +++ b/docs/api/mini-report-purchase.md @@ -0,0 +1,125 @@ +# 小程序接口文档:采购运营报表 + +> 门店端运营报表:拉取「一周 / 一月 / 任意选定时间段」内的采购总金额,以及每个单品的累计采购金额与金额占比。 +> +> 示例场景:上个月我店一共采购了 10 万元,其中土豆 8764 元(占比 8.8%)、洋葱 3664 元(占比 3.7%)。 + +## 接口信息 + +| 项 | 值 | +|---|---| +| 请求方式 | `GET` | +| 路径 | `/mini/report/purchase` | +| 鉴权 | 门店 token(`Authorization: Bearer `),与其他小程序接口一致 | +| 数据范围 | 仅当前登录门店自身的订货数据 | + +## 请求参数 + +| 参数 | 类型 | 必填 | 说明 | +|---|---|---|---| +| `preset` | string | 否 | 周期预设:`week` 本周 / `last_week` 上周 / `month` 本月 / `last_month` 上月。**缺省为 `month`** | +| `start_date` | string | 否 | 自定义开始日期,格式 `Y-m-d`(如 `2026-07-01`)。需与 `end_date` 成对出现 | +| `end_date` | string | 否 | 自定义结束日期,格式 `Y-m-d`,不能早于 `start_date` | + +规则: + +- 同时传 `start_date` + `end_date` 时为**自定义区间,优先于 `preset`**(响应中 `preset` 返回 `custom`); +- 只传其中一个日期、日期格式错误、结束日期早于开始日期、preset 非枚举值,均返回参数错误; +- 进行中的周期(本周/本月)结束日期自动**封顶为今天**;周以**周一**为起点。 + +## 响应示例 + +`GET /mini/report/purchase?preset=last_month` + +```json +{ + "success": true, + "msg": "ok", + "data": { + "preset": "last_month", + "start_date": "2026-07-01", + "end_date": "2026-07-31", + "total_amount": "100000.00", + "total_quantity": 1700, + "order_count": 20, + "item_count": 2, + "items": [ + { + "product_id": 11, + "product_name": "土豆", + "product_spec": "50斤/袋", + "unit": "斤", + "quantity": 500, + "weight": "0.000", + "amount": "8764.00", + "percent": 8.8 + }, + { + "product_id": 12, + "product_name": "洋葱", + "product_spec": "20斤/袋", + "unit": "斤", + "quantity": 200, + "weight": "0.000", + "amount": "3664.00", + "percent": 3.7 + } + ] + } +} +``` + +## 响应字段 + +### 顶层(`data`) + +| 字段 | 类型 | 说明 | +|---|---|---| +| `preset` | string | 实际生效的周期预设(`week`/`last_week`/`month`/`last_month`/`custom`) | +| `start_date` | string | 实际统计开始日期 `Y-m-d` | +| `end_date` | string | 实际统计结束日期 `Y-m-d` | +| `total_amount` | string | 周期内采购总金额(元,2 位小数;金额一律以字符串返回防精度丢失) | +| `total_quantity` | number | 周期内订货总量(各单品数量之和) | +| `order_count` | number | 周期内有效订货单数 | +| `item_count` | number | 单品个数(= `items` 长度) | +| `items` | array | 单品累计列表,**按金额降序** | + +### 单品行(`data.items[]`) + +| 字段 | 类型 | 说明 | +|---|---|---| +| `product_id` | number | 商品 ID | +| `product_name` | string | 品名(下单时快照,商品档案改名/删除不影响历史统计) | +| `product_spec` | string | 规格/包规(快照) | +| `unit` | string | 计价单位(快照) | +| `quantity` | number | 周期内累计订货量 | +| `weight` | string | 周期内累计重量(3 位小数,未称重为 `0.000`) | +| `amount` | string | 周期内累计采购金额(元,2 位小数) | +| `percent` | number | 金额占比(%,1 位小数,四舍五入;如 `8.8` 表示 8.8%。周期内无数据时为 `0`) | + +## 统计口径 + +- 数据源为**门店订货单明细**(下单时快照的等级单价 × 数量),即门店视角的应付采购金额; +- **已取消**订单与后台**已删除**订单不计入统计,其余状态(待接单/已接单/采购中/配送中/已完成)全部计入; +- 单品按 `product_id` 聚合,同一商品在周期内多次下单会累计为一行; +- 占比 = 单品金额 ÷ 总金额 × 100,保留 1 位小数(四舍五入),各行占比之和可能因舍入存在 ±0.1 的误差,属正常现象。 + +## 错误响应 + +与小程序其他接口一致:`success: false` + `msg` 描述原因。 + +| 场景 | msg 示例 | +|---|---| +| 未登录 / token 失效 | `Unauthenticated`(按全局鉴权约定) | +| 账号被停用 | `账号不存在或已被停用` | +| preset 非法 | `preset 参数只能是 week/last_week/month/last_month` | +| 日期格式错误 | `开始日期格式为 Y-m-d` | +| 结束早于开始 | `结束日期不能早于开始日期` | +| 自定义区间只传一侧 | `自定义区间需同时提供开始与结束日期` | + +## 前端对接建议 + +- 顶部放周期切换 Tab(本周 / 上周 / 本月 / 上月 / 自定义),自定义时弹出日期选择器,选中后传 `start_date` + `end_date`; +- 头部卡片展示 `total_amount`(总金额)与 `order_count`(单数); +- 列表直接渲染 `items`(已按金额降序),每行展示品名、规格、累计金额与 `percent`%;如需饼图可直接用 `items[].percent`; +- 区间无数据时 `items` 为空数组、`total_amount` 为 `"0.00"`,前端展示空状态即可。 diff --git a/tests/Feature/MiniReportTest.php b/tests/Feature/MiniReportTest.php new file mode 100644 index 0000000..56479d3 --- /dev/null +++ b/tests/Feature/MiniReportTest.php @@ -0,0 +1,257 @@ +create([ + 'store_id' => $store->id, + 'order_date' => $date, + 'status' => $status, + ]); + } + + /** 造一条明细(金额默认按 price × quantity 计算) */ + private function makeItem(StoreOrderModel $order, array $attributes = []): StoreOrderItemModel + { + return StoreOrderItemModel::factory()->create(array_merge([ + 'order_id' => $order->id, + 'store_id' => $order->store_id, + ], $attributes)); + } + + /** 汇总与占比:总金额=全部明细金额之和,单品按金额降序,占比 1 位小数四舍五入 */ + public function test_report_aggregates_total_and_item_percent(): void + { + $store = StoreModel::factory()->create(); + $this->actingAsMiniStore($store); + + // 总额恰好 100000.00:土豆 8764.00 → 8.8%,洋葱 3664.00 → 3.7%,白菜 87572.00 → 87.6% + $orderA = $this->makeOrder($store, '2026-07-03'); + $this->makeItem($orderA, ['product_id' => 11, 'product_name' => '土豆', 'quantity' => 500, 'price' => '17.53', 'amount' => '8764.00']); + $this->makeItem($orderA, ['product_id' => 12, 'product_name' => '洋葱', 'quantity' => 200, 'price' => '18.32', 'amount' => '3664.00']); + $orderB = $this->makeOrder($store, '2026-07-20'); + $this->makeItem($orderB, ['product_id' => 13, 'product_name' => '白菜', 'quantity' => 1000, 'price' => '87.57', 'amount' => '87572.00']); + + $data = $this->getJson('/mini/report/purchase?start_date=2026-07-01&end_date=2026-07-31') + ->assertOk() + ->json('data'); + + $this->assertSame('custom', $data['preset']); + $this->assertSame('2026-07-01', $data['start_date']); + $this->assertSame('2026-07-31', $data['end_date']); + $this->assertSame('100000.00', $data['total_amount']); + $this->assertSame(1700, $data['total_quantity']); + $this->assertSame(2, $data['order_count']); + $this->assertSame(3, $data['item_count']); + + // 按金额降序 + $this->assertSame(['白菜', '土豆', '洋葱'], array_column($data['items'], 'product_name')); + + $potato = $data['items'][1]; + $this->assertSame(11, $potato['product_id']); + $this->assertSame('8764.00', $potato['amount']); + $this->assertSame(500, $potato['quantity']); + $this->assertSame(8.8, $potato['percent']); + + $onion = $data['items'][2]; + $this->assertSame('3664.00', $onion['amount']); + $this->assertSame(3.7, $onion['percent']); + + $cabbage = $data['items'][0]; + $this->assertSame(87.6, $cabbage['percent']); + } + + /** 同一单品跨多订单累计:数量/金额求和,品名取快照 */ + public function test_report_accumulates_same_product_across_orders(): void + { + $store = StoreModel::factory()->create(); + $this->actingAsMiniStore($store); + + $orderA = $this->makeOrder($store, '2026-08-01'); + $this->makeItem($orderA, ['product_id' => 11, 'product_name' => '土豆', 'quantity' => 100, 'price' => '10.00', 'amount' => '1000.00']); + $orderB = $this->makeOrder($store, '2026-08-05'); + $this->makeItem($orderB, ['product_id' => 11, 'product_name' => '土豆', 'quantity' => 300, 'price' => '10.00', 'amount' => '3000.00']); + + $data = $this->getJson('/mini/report/purchase?start_date=2026-08-01&end_date=2026-08-31') + ->assertOk() + ->json('data'); + + $this->assertSame('4000.00', $data['total_amount']); + $this->assertSame(1, $data['item_count']); + $this->assertSame(400, $data['items'][0]['quantity']); + $this->assertSame('4000.00', $data['items'][0]['amount']); + $this->assertEquals(100.0, $data['items'][0]['percent'], '单一单品占比 100%(整数百分比 JSON 编码为 100)'); + } + + /** 自定义区间:区间外订单不计入 */ + public function test_report_filters_by_custom_date_range(): void + { + $store = StoreModel::factory()->create(); + $this->actingAsMiniStore($store); + + $in = $this->makeOrder($store, '2026-08-10'); + $this->makeItem($in, ['product_id' => 11, 'amount' => '100.00']); + $out = $this->makeOrder($store, '2026-08-20'); + $this->makeItem($out, ['product_id' => 11, 'amount' => '900.00']); + + $data = $this->getJson('/mini/report/purchase?start_date=2026-08-01&end_date=2026-08-15') + ->assertOk() + ->json('data'); + + $this->assertSame('100.00', $data['total_amount']); + $this->assertSame(1, $data['order_count']); + } + + /** 已取消与后台已删除订单不计入 */ + public function test_report_excludes_cancelled_and_deleted_orders(): void + { + $store = StoreModel::factory()->create(); + $this->actingAsMiniStore($store); + + $normal = $this->makeOrder($store, '2026-08-10'); + $this->makeItem($normal, ['product_id' => 11, 'amount' => '100.00']); + + $cancelled = $this->makeOrder($store, '2026-08-10', StoreOrderModel::STATUS_CANCELLED); + $this->makeItem($cancelled, ['product_id' => 12, 'amount' => '500.00']); + + $deleted = $this->makeOrder($store, '2026-08-10', StoreOrderModel::STATUS_CANCELLED); + $this->makeItem($deleted, ['product_id' => 13, 'amount' => '700.00']); + $deleted->delete(); + + $data = $this->getJson('/mini/report/purchase?start_date=2026-08-01&end_date=2026-08-31') + ->assertOk() + ->json('data'); + + $this->assertSame('100.00', $data['total_amount']); + $this->assertSame(1, $data['order_count']); + $this->assertSame(1, $data['item_count']); + $this->assertSame(11, $data['items'][0]['product_id']); + } + + /** preset 推导:本月(结束日期封顶到今天)与上月 */ + public function test_report_preset_month_and_last_month(): void + { + Carbon::setTestNow('2026-08-21 12:00:00'); + + $store = StoreModel::factory()->create(); + $this->actingAsMiniStore($store); + + $july = $this->makeOrder($store, '2026-07-15'); + $this->makeItem($july, ['product_id' => 11, 'amount' => '200.00']); + $august = $this->makeOrder($store, '2026-08-15'); + $this->makeItem($august, ['product_id' => 11, 'amount' => '300.00']); + + $month = $this->getJson('/mini/report/purchase?preset=month')->assertOk()->json('data'); + $this->assertSame('2026-08-01', $month['start_date']); + $this->assertSame('2026-08-21', $month['end_date'], '进行中的本月封顶到今天'); + $this->assertSame('300.00', $month['total_amount']); + + $lastMonth = $this->getJson('/mini/report/purchase?preset=last_month')->assertOk()->json('data'); + $this->assertSame('2026-07-01', $lastMonth['start_date']); + $this->assertSame('2026-07-31', $lastMonth['end_date']); + $this->assertSame('200.00', $lastMonth['total_amount']); + } + + /** preset 推导:本周(周一起算)与上周 */ + public function test_report_preset_week_and_last_week(): void + { + Carbon::setTestNow('2026-08-21 12:00:00'); // 周五,本周一 = 2026-08-17 + + $store = StoreModel::factory()->create(); + $this->actingAsMiniStore($store); + + $thisWeek = $this->makeOrder($store, '2026-08-18'); + $this->makeItem($thisWeek, ['product_id' => 11, 'amount' => '100.00']); + $lastWeek = $this->makeOrder($store, '2026-08-12'); + $this->makeItem($lastWeek, ['product_id' => 11, 'amount' => '400.00']); + + $week = $this->getJson('/mini/report/purchase?preset=week')->assertOk()->json('data'); + $this->assertSame('2026-08-17', $week['start_date']); + $this->assertSame('2026-08-21', $week['end_date']); + $this->assertSame('100.00', $week['total_amount']); + + $last = $this->getJson('/mini/report/purchase?preset=last_week')->assertOk()->json('data'); + $this->assertSame('2026-08-10', $last['start_date']); + $this->assertSame('2026-08-16', $last['end_date']); + $this->assertSame('400.00', $last['total_amount']); + } + + /** 不传参数默认本月 */ + public function test_report_defaults_to_current_month(): void + { + Carbon::setTestNow('2026-08-21 12:00:00'); + + $store = StoreModel::factory()->create(); + $this->actingAsMiniStore($store); + + $order = $this->makeOrder($store, '2026-08-02'); + $this->makeItem($order, ['product_id' => 11, 'amount' => '66.00']); + + $data = $this->getJson('/mini/report/purchase')->assertOk()->json('data'); + $this->assertSame('month', $data['preset']); + $this->assertSame('2026-08-01', $data['start_date']); + $this->assertSame('66.00', $data['total_amount']); + } + + /** 门店数据隔离:仅统计本店订单 */ + public function test_report_data_isolated_between_stores(): void + { + $storeA = StoreModel::factory()->create(); + $orderA = $this->makeOrder($storeA, '2026-08-10'); + $this->makeItem($orderA, ['product_id' => 11, 'amount' => '999.00']); + + $storeB = StoreModel::factory()->create(); + $this->actingAsMiniStore($storeB); + + $data = $this->getJson('/mini/report/purchase?start_date=2026-08-01&end_date=2026-08-31') + ->assertOk() + ->json('data'); + + $this->assertSame('0.00', $data['total_amount']); + $this->assertSame(0, $data['order_count']); + $this->assertSame([], $data['items']); + } + + /** 空周期:总额为 0、明细为空,不出现除零 */ + public function test_report_empty_period_returns_zero(): void + { + $store = StoreModel::factory()->create(); + $this->actingAsMiniStore($store); + + $data = $this->getJson('/mini/report/purchase?start_date=2026-01-01&end_date=2026-01-31') + ->assertOk() + ->json('data'); + + $this->assertSame('0.00', $data['total_amount']); + $this->assertSame(0, $data['total_quantity']); + $this->assertSame(0, $data['order_count']); + $this->assertSame(0, $data['item_count']); + $this->assertSame([], $data['items']); + } + + /** 参数校验:非法 preset / 日期格式 / 结束早于开始 / 区间缺一侧 */ + public function test_report_validates_params(): void + { + $store = StoreModel::factory()->create(); + $this->actingAsMiniStore($store); + + $this->getJson('/mini/report/purchase?preset=year')->assertJsonPath('success', false); + $this->getJson('/mini/report/purchase?start_date=2026/08/01&end_date=2026-08-31')->assertJsonPath('success', false); + $this->getJson('/mini/report/purchase?start_date=2026-08-31&end_date=2026-08-01')->assertJsonPath('success', false); + $this->getJson('/mini/report/purchase?start_date=2026-08-01')->assertJsonPath('success', false); + $this->getJson('/mini/report/purchase?end_date=2026-08-31')->assertJsonPath('success', false); + } +}