diff --git a/.phpunit.result.cache b/.phpunit.result.cache index 2212027..aa5436d 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},"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.012,"Tests\\Feature\\ProductPriceTest::test_store_without_level_is_rejected":0.007,"Tests\\Feature\\ProductPriceTest::test_batch_price_updates_and_notifies_affected_stores":0.013,"Tests\\Feature\\ProductPriceTest::test_price_matrix_structure":0.014,"Tests\\Feature\\StoreOrderTest::test_place_order_snapshots_level_price_and_recalculates":0.015,"Tests\\Feature\\StoreOrderTest::test_client_supplied_amount_is_ignored":0.014,"Tests\\Feature\\StoreOrderTest::test_product_without_level_price_rejected":0.014,"Tests\\Feature\\StoreOrderTest::test_cancel_only_pending_orders":0.021,"Tests\\Feature\\StoreOrderTest::test_summarized_order_cannot_be_cancelled":0.02,"Tests\\Feature\\StoreOrderTest::test_order_data_isolated_between_stores":0.019,"Tests\\Feature\\PurchaseGenerateTest::test_generate_aggregates_orders_by_product":0.036,"Tests\\Feature\\PurchaseGenerateTest::test_generate_writes_back_order_status":0.038,"Tests\\Feature\\PurchaseGenerateTest::test_generate_without_pending_orders_fails":0.016,"Tests\\Feature\\PurchaseGenerateTest::test_generate_is_idempotent":0.042,"Tests\\Feature\\AllocationTest::test_allocation_conserves_amount_with_tail_correction":0.08,"Tests\\Feature\\AllocationTest::test_allocation_follows_order_ratio":0.023,"Tests\\Feature\\AllocationTest::test_allocation_is_idempotent":0.024,"Tests\\Feature\\AllocationTest::test_allocation_rejected_without_actual_amount":0.017,"Tests\\Feature\\DebugGenerateTest::test_debug_generate":0.047,"Tests\\Feature\\ReconciliationTest::test_build_creates_reconciliation_items":0.057,"Tests\\Feature\\ReconciliationTest::test_build_filters_by_supplier":0.061,"Tests\\Feature\\ReconciliationTest::test_update_item_recalculates_diff_and_header":0.048,"Tests\\Feature\\ReconciliationTest::test_toggle_reconciled_flag":0.053,"Tests\\Feature\\ReconciliationTest::test_settle_creates_settlements_per_store":0.061,"Tests\\Feature\\StatementTest::test_generate_snapshots_payment_cycle":0.021,"Tests\\Feature\\StatementTest::test_statement_isolated_between_stores":0.029,"Tests\\Feature\\StatementTest::test_generate_excludes_cancelled_and_used_items":0.032,"Tests\\Feature\\MiniAuthTest::test_login_creates_user_and_issues_token":0.056,"Tests\\Feature\\MiniAuthTest::test_disabled_user_cannot_login":0.005,"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.008,"Tests\\Feature\\MiniAuthTest::test_mini_token_cannot_access_admin_api":0.005,"Tests\\Feature\\ExportTest::test_export_xlsx_with_encoded_chinese_filename":0.067,"Tests\\Feature\\ExportTest::test_export_category_filters_to_vegetables":0.024,"Tests\\Feature\\ExportTest::test_export_pdf":1.449,"Tests\\Feature\\ExportTest::test_export_invalid_format_rejected":0.043,"Tests\\Feature\\ExportTest::test_export_requires_export_permission":0.032,"Tests\\Unit\\ExampleTest::test_that_true_is_true":0.001,"Tests\\Feature\\MiniAuthTest::test_login_fails_when_wechat_rejects_code":0.006,"Tests\\Feature\\CartTest::test_add_to_cart_succeeds":0.01,"Tests\\Feature\\CartTest::test_duplicate_add_merges_quantity":0.009,"Tests\\Feature\\CartTest::test_off_shelf_product_rejected":0.008,"Tests\\Feature\\CartTest::test_soft_deleted_product_rejected":0.006,"Tests\\Feature\\CartTest::test_product_without_level_price_rejected":0.008,"Tests\\Feature\\CartTest::test_unbound_user_rejected":0.014,"Tests\\Feature\\CartTest::test_store_without_level_rejected":0.009,"Tests\\Feature\\CartTest::test_list_with_prices_amounts_and_totals":0.011,"Tests\\Feature\\CartTest::test_list_marks_off_shelf_item_unbuyable":0.011,"Tests\\Feature\\CartTest::test_empty_cart_returns_empty_items":0.007,"Tests\\Feature\\CartTest::test_update_quantity":0.008,"Tests\\Feature\\CartTest::test_update_other_users_item_rejected":0.015,"Tests\\Feature\\CartTest::test_update_missing_item_rejected":0.01,"Tests\\Feature\\CartTest::test_invalid_quantity_rejected":0.008,"Tests\\Feature\\CartTest::test_delete_item":0.013,"Tests\\Feature\\CartTest::test_delete_other_users_item_rejected":0.014,"Tests\\Feature\\CartTest::test_clear_only_own_cart":0.021,"Tests\\Feature\\CartTest::test_unauthenticated_returns_401":0.003,"Tests\\Feature\\CartTest::test_cart_isolated_between_users":0.016,"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.002,"Tests\\Feature\\ProductCostPriceTest::test_admin_list_contains_cost_price_and_actual_price":0.012,"Tests\\Feature\\ProductCostPriceTest::test_mini_list_returns_actual_price_without_cost_price":0.013,"Tests\\Feature\\ProductCostPriceTest::test_create_product_with_cost_and_percent_pricing":0.019,"Tests\\Feature\\ProductCostPriceTest::test_create_percent_row_without_percent_fails":0.014,"Tests\\Feature\\ProductCostPriceTest::test_update_switches_fixed_to_percent":0.024,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_cost_only_notifies_percent_level_stores":0.02,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_percent_row_update":0.012,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_mixed_row_updates_cost_and_level":0.012,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_empty_row_rejected":0.011,"Tests\\Feature\\ProductCostPriceTest::test_price_matrix_includes_cost_and_percent_columns":0.013,"Tests\\Feature\\ProductCostPriceTest::test_mini_order_uses_actual_price_for_percent_row":0.009,"Tests\\Feature\\ProductCostPriceTest::test_mini_cart_uses_actual_price_for_percent_row":0.012,"Tests\\Feature\\PurchaseGenerateTest::test_generate_uses_min_actual_price_across_types":0.024,"Tests\\Feature\\ProductPriceTest::test_store_without_level_sees_products_without_price":0.013,"Tests\\Feature\\ProductPriceTest::test_unbound_user_can_browse_products_without_price_but_cannot_cart":0.017,"Tests\\Feature\\MiniAuthTest::test_login_issues_token_for_registered_user":0.016,"Tests\\Feature\\MiniAuthTest::test_login_rejects_unregistered_openid":0.004,"Tests\\Feature\\MiniAuthTest::test_register_binds_store_and_issues_token":0.019,"Tests\\Feature\\MiniAuthTest::test_register_rejects_unknown_store":0.005,"Tests\\Feature\\MiniAuthTest::test_register_rejects_duplicate_openid":0.005}} \ 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},"times":{"Tests\\Feature\\ExampleTest::test_the_application_returns_a_successful_response":0.003,"Tests\\Feature\\ProductPriceTest::test_product_list_returns_price_for_store_level":0.007,"Tests\\Feature\\ProductPriceTest::test_store_without_level_is_rejected":0.007,"Tests\\Feature\\ProductPriceTest::test_batch_price_updates_and_notifies_affected_stores":0.01,"Tests\\Feature\\ProductPriceTest::test_price_matrix_structure":0.007,"Tests\\Feature\\StoreOrderTest::test_place_order_snapshots_level_price_and_recalculates":0.007,"Tests\\Feature\\StoreOrderTest::test_client_supplied_amount_is_ignored":0.008,"Tests\\Feature\\StoreOrderTest::test_product_without_level_price_rejected":0.007,"Tests\\Feature\\StoreOrderTest::test_cancel_only_pending_orders":0.01,"Tests\\Feature\\StoreOrderTest::test_summarized_order_cannot_be_cancelled":0.009,"Tests\\Feature\\StoreOrderTest::test_order_data_isolated_between_stores":0.011,"Tests\\Feature\\PurchaseGenerateTest::test_generate_aggregates_orders_by_product":0.019,"Tests\\Feature\\PurchaseGenerateTest::test_generate_writes_back_order_status":0.02,"Tests\\Feature\\PurchaseGenerateTest::test_generate_without_pending_orders_fails":0.006,"Tests\\Feature\\PurchaseGenerateTest::test_generate_is_idempotent":0.021,"Tests\\Feature\\AllocationTest::test_allocation_conserves_amount_with_tail_correction":0.074,"Tests\\Feature\\AllocationTest::test_allocation_follows_order_ratio":0.022,"Tests\\Feature\\AllocationTest::test_allocation_is_idempotent":0.026,"Tests\\Feature\\AllocationTest::test_allocation_rejected_without_actual_amount":0.017,"Tests\\Feature\\DebugGenerateTest::test_debug_generate":0.047,"Tests\\Feature\\ReconciliationTest::test_build_creates_reconciliation_items":0.028,"Tests\\Feature\\ReconciliationTest::test_build_filters_by_supplier":0.03,"Tests\\Feature\\ReconciliationTest::test_update_item_recalculates_diff_and_header":0.029,"Tests\\Feature\\ReconciliationTest::test_toggle_reconciled_flag":0.029,"Tests\\Feature\\ReconciliationTest::test_settle_creates_settlements_per_store":0.03,"Tests\\Feature\\StatementTest::test_generate_snapshots_payment_cycle":0.011,"Tests\\Feature\\StatementTest::test_statement_isolated_between_stores":0.016,"Tests\\Feature\\StatementTest::test_generate_excludes_cancelled_and_used_items":0.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.003,"Tests\\Feature\\ExportTest::test_export_xlsx_with_encoded_chinese_filename":0.078,"Tests\\Feature\\ExportTest::test_export_category_filters_to_vegetables":0.018,"Tests\\Feature\\ExportTest::test_export_pdf":1.307,"Tests\\Feature\\ExportTest::test_export_invalid_format_rejected":0.037,"Tests\\Feature\\ExportTest::test_export_requires_export_permission":0.017,"Tests\\Unit\\ExampleTest::test_that_true_is_true":0.001,"Tests\\Feature\\MiniAuthTest::test_login_fails_when_wechat_rejects_code":0.003,"Tests\\Feature\\CartTest::test_add_to_cart_succeeds":0.009,"Tests\\Feature\\CartTest::test_duplicate_add_merges_quantity":0.009,"Tests\\Feature\\CartTest::test_off_shelf_product_rejected":0.007,"Tests\\Feature\\CartTest::test_soft_deleted_product_rejected":0.006,"Tests\\Feature\\CartTest::test_product_without_level_price_rejected":0.006,"Tests\\Feature\\CartTest::test_unbound_user_rejected":0.007,"Tests\\Feature\\CartTest::test_store_without_level_rejected":0.006,"Tests\\Feature\\CartTest::test_list_with_prices_amounts_and_totals":0.011,"Tests\\Feature\\CartTest::test_list_marks_off_shelf_item_unbuyable":0.011,"Tests\\Feature\\CartTest::test_empty_cart_returns_empty_items":0.005,"Tests\\Feature\\CartTest::test_update_quantity":0.008,"Tests\\Feature\\CartTest::test_update_other_users_item_rejected":0.01,"Tests\\Feature\\CartTest::test_update_missing_item_rejected":0.008,"Tests\\Feature\\CartTest::test_invalid_quantity_rejected":0.007,"Tests\\Feature\\CartTest::test_delete_item":0.008,"Tests\\Feature\\CartTest::test_delete_other_users_item_rejected":0.009,"Tests\\Feature\\CartTest::test_clear_only_own_cart":0.013,"Tests\\Feature\\CartTest::test_unauthenticated_returns_401":0.004,"Tests\\Feature\\CartTest::test_cart_isolated_between_users":0.009,"Tests\\Feature\\DebugCartListTest::test_dump_cart_list_response":0.057,"Tests\\Feature\\ProductCostPriceTest::test_calc_actual_price_fixed_and_percent":0.001,"Tests\\Feature\\ProductCostPriceTest::test_calc_actual_price_edge_cases":0.001,"Tests\\Feature\\ProductCostPriceTest::test_admin_list_contains_cost_price_and_actual_price":0.009,"Tests\\Feature\\ProductCostPriceTest::test_mini_list_returns_actual_price_without_cost_price":0.007,"Tests\\Feature\\ProductCostPriceTest::test_create_product_with_cost_and_percent_pricing":0.009,"Tests\\Feature\\ProductCostPriceTest::test_create_percent_row_without_percent_fails":0.007,"Tests\\Feature\\ProductCostPriceTest::test_update_switches_fixed_to_percent":0.009,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_cost_only_notifies_percent_level_stores":0.01,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_percent_row_update":0.008,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_mixed_row_updates_cost_and_level":0.009,"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.008,"Tests\\Feature\\ProductCostPriceTest::test_mini_cart_uses_actual_price_for_percent_row":0.008,"Tests\\Feature\\PurchaseGenerateTest::test_generate_uses_min_actual_price_across_types":0.013,"Tests\\Feature\\ProductPriceTest::test_store_without_level_sees_products_without_price":0.007,"Tests\\Feature\\ProductPriceTest::test_unbound_user_can_browse_products_without_price_but_cannot_cart":0.009,"Tests\\Feature\\MiniAuthTest::test_login_issues_token_for_registered_user":0.012,"Tests\\Feature\\MiniAuthTest::test_login_rejects_unregistered_openid":0.002,"Tests\\Feature\\MiniAuthTest::test_register_binds_store_and_issues_token":0.028,"Tests\\Feature\\MiniAuthTest::test_register_rejects_unknown_store":0.002,"Tests\\Feature\\MiniAuthTest::test_register_rejects_duplicate_openid":0.003,"Tests\\Feature\\MiniStoreTest::test_info_returns_bound_store":0.004,"Tests\\Feature\\MiniStoreTest::test_update_info":0.005,"Tests\\Feature\\MiniStoreTest::test_update_info_ignores_readonly_fields":0.005,"Tests\\Feature\\MiniStoreTest::test_requires_bound_store":0.007,"Tests\\Feature\\MiniStoreTest::test_update_info_validates_length":0.005}} \ No newline at end of file diff --git a/app/Http/Controllers/Mini/StoreController.php b/app/Http/Controllers/Mini/StoreController.php index 247a9a9..40b5596 100644 --- a/app/Http/Controllers/Mini/StoreController.php +++ b/app/Http/Controllers/Mini/StoreController.php @@ -4,33 +4,57 @@ namespace App\Http\Controllers\Mini; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; +use Modules\AnnoRoute\Attribute\GetRoute; use Modules\AnnoRoute\Attribute\PutRoute; use Modules\AnnoRoute\Attribute\RequestAttribute; /** - * 小程序门店设置(回款周期自配置,影响对账单应结算日期) + * 小程序门店设置(门店信息查看与维护:联系人 / 联系电话 / 地址) */ #[RequestAttribute('/mini', 'mini', authGuard: 'users')] class StoreController extends BaseMiniController { - /** 修改回款周期(≥0,无上限) */ - #[PutRoute('/store/paymentCycle', authorize: true)] - public function paymentCycle(Request $request): JsonResponse + /** 门店详情(编辑回显;门店名称 / 编码 / 回款周期为只读,由后台维护) */ + #[GetRoute('/store/info', authorize: true)] + public function info(Request $request): JsonResponse + { + $user = $this->currentUser($request); + $store = $this->ensureStoreBound($user); + + return $this->success([ + 'id' => $store->id, + 'name' => $store->name, + 'code' => $store->code, + 'contact' => $store->contact, + 'phone' => $store->phone, + 'address' => $store->address, + 'payment_cycle_days' => $store->payment_cycle_days, + ]); + } + + /** 修改门店信息(仅联系人 / 联系电话 / 地址,白名单更新) */ + #[PutRoute('/store/info', authorize: true)] + public function updateInfo(Request $request): JsonResponse { $data = $request->validate([ - 'payment_cycle_days' => 'required|integer|min:0', + 'contact' => 'nullable|string|max:50', + 'phone' => 'nullable|string|max:20', + 'address' => 'nullable|string|max:255', ], [ - 'payment_cycle_days.required' => '回款周期不能为空', - 'payment_cycle_days.integer' => '回款周期必须为整数', - 'payment_cycle_days.min' => '回款周期不能小于 0', + 'contact.max' => '联系人最长 50 个字符', + 'phone.max' => '联系电话最长 20 个字符', + 'address.max' => '地址最长 255 个字符', ]); $user = $this->currentUser($request); $store = $this->ensureStoreBound($user); - $store->payment_cycle_days = (int) $data['payment_cycle_days']; - $store->save(); + $store->update($data); - return $this->success(['payment_cycle_days' => $store->payment_cycle_days], '回款周期已更新'); + return $this->success([ + 'contact' => $store->contact, + 'phone' => $store->phone, + 'address' => $store->address, + ], '门店信息已更新'); } } diff --git a/tests/Feature/MiniStoreTest.php b/tests/Feature/MiniStoreTest.php new file mode 100644 index 0000000..d140205 --- /dev/null +++ b/tests/Feature/MiniStoreTest.php @@ -0,0 +1,110 @@ +paymentCycle(7)->create([ + 'contact' => '张三', + 'phone' => '13800138000', + 'address' => '幸福路 1 号', + ]); + $this->actingAsMiniUser(UserModel::factory()->forStore($store->id)->create()); + + $this->getJson('/mini/store/info') + ->assertOk() + ->assertJsonPath('success', true) + ->assertJsonPath('data.id', $store->id) + ->assertJsonPath('data.contact', '张三') + ->assertJsonPath('data.phone', '13800138000') + ->assertJsonPath('data.address', '幸福路 1 号') + ->assertJsonPath('data.payment_cycle_days', 7); + } + + /** 修改门店信息:联系人 / 电话 / 地址 */ + public function test_update_info(): void + { + $store = StoreModel::factory()->create(); + $this->actingAsMiniUser(UserModel::factory()->forStore($store->id)->create()); + + $this->putJson('/mini/store/info', [ + 'contact' => '李四', + 'phone' => '13900139000', + 'address' => '建设路 88 号', + ]) + ->assertOk() + ->assertJsonPath('success', true) + ->assertJsonPath('data.contact', '李四') + ->assertJsonPath('data.phone', '13900139000') + ->assertJsonPath('data.address', '建设路 88 号'); + + $fresh = $store->fresh(); + $this->assertSame('李四', $fresh->contact); + $this->assertSame('13900139000', $fresh->phone); + $this->assertSame('建设路 88 号', $fresh->address); + } + + /** 白名单更新:名称 / 编码 / 回款周期等字段提交后被忽略 */ + public function test_update_info_ignores_readonly_fields(): void + { + $store = StoreModel::factory()->paymentCycle(5)->create([ + 'name' => '原门店', + 'code' => 'ST100', + ]); + $this->actingAsMiniUser(UserModel::factory()->forStore($store->id)->create()); + + $this->putJson('/mini/store/info', [ + 'contact' => '王五', + 'name' => '改名门店', + 'code' => 'HACK', + 'payment_cycle_days' => 99, + ]) + ->assertOk() + ->assertJsonPath('success', true); + + $fresh = $store->fresh(); + $this->assertSame('王五', $fresh->contact); + $this->assertSame('原门店', $fresh->name); + $this->assertSame('ST100', $fresh->code); + $this->assertSame(5, $fresh->payment_cycle_days); + } + + /** 未绑定门店 → 拒绝访问 */ + public function test_requires_bound_store(): void + { + $this->actingAsMiniUser(UserModel::factory()->create()); + + $this->getJson('/mini/store/info') + ->assertOk() + ->assertJsonPath('success', false) + ->assertJsonPath('msg', '尚未绑定门店,请联系客服处理'); + + $this->putJson('/mini/store/info', ['contact' => '路人']) + ->assertOk() + ->assertJsonPath('success', false); + } + + /** 字段长度校验:联系人超过 50 字符 → 校验失败 */ + public function test_update_info_validates_length(): void + { + $store = StoreModel::factory()->create(); + $this->actingAsMiniUser(UserModel::factory()->forStore($store->id)->create()); + + $this->putJson('/mini/store/info', ['contact' => str_repeat('长', 51)]) + ->assertOk() + ->assertJsonPath('success', false) + ->assertJsonPath('msg', '联系人最长 50 个字符'); + + $this->assertSame($store->contact, $store->fresh()->contact, '校验失败不应落库'); + } +} diff --git a/小程序端API文档.md b/小程序端API文档.md index 9ad57d0..85d824d 100644 --- a/小程序端API文档.md +++ b/小程序端API文档.md @@ -486,19 +486,37 @@ items 每项: ## 7. 门店设置 -### 7.1 修改回款周期 +### 7.1 门店详情 -`PUT /mini/store/paymentCycle`(需登录 + 门店) +`GET /mini/store/info`(需登录 + 门店) + +用于门店信息编辑页回显。响应(`data`): + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | int | 门店 ID | +| name | string | 门店名称(只读,后台维护) | +| code | string | 门店编码(只读) | +| contact | string | 联系人 | +| phone | string | 联系电话 | +| address | string | 地址 | +| payment_cycle_days | int | 回款周期天数(只读,后台维护) | + +### 7.2 修改门店信息 + +`PUT /mini/store/info`(需登录 + 门店) 请求参数: | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| -| payment_cycle_days | int | 是 | 回款周期天数(≥0,无上限;0 = 当天结算) | +| contact | string | 否 | 联系人(≤50 字符) | +| phone | string | 否 | 联系电话(≤20 字符) | +| address | string | 否 | 地址(≤255 字符) | -响应(`data`):`{ "payment_cycle_days": 1 }`,提示「回款周期已更新」。 +响应(`data`):`{ "contact": "张三", "phone": "13800138000", "address": "幸福路 1 号" }`,提示「门店信息已更新」。 -> 该值影响后续生成对账单的 `settlement_date`(周期结束 + 回款周期天)。 +> 仅支持修改联系人 / 电话 / 地址(白名单更新,提交其他字段将被忽略);门店名称、编码、回款周期由后台维护。回款周期影响后续生成对账单的 `settlement_date`(周期结束 + 回款周期天)。 --- diff --git a/开发计划.md b/开发计划.md index 99621c1..76b4875 100644 --- a/开发计划.md +++ b/开发计划.md @@ -364,7 +364,8 @@ return app(ExportService::class)->download('settlement', $settlement, $format); | `/mini/statement/generate` | POST | `{period_start, period_end}`:`StatementGenerateService`——拉周期内订单明细,**快照当前 payment_cycle_days,settlement_date = period_end + cycle 天**;statement_no = ST… | | `/mini/statement/{id}` | GET | 详情(含单品对账状态标识) | | `/mini/statement/{id}/export` | GET | `?format=xlsx\|pdf`,`ExportService::download('statement', ...)`(blob) | -| `/mini/store/paymentCycle` | PUT | `{payment_cycle_days}`(≥0,无上限) | +| `/mini/store/info` | GET | 门店详情(编辑回显;name/code/payment_cycle_days 只读) | +| `/mini/store/info` | PUT | 修改门店信息:仅 `{contact, phone, address}` 白名单更新;回款周期由后台维护 | | `/mini/notice` | GET | 本人通知 + 全员广播(`user_id in [0, 当前id]`),分页 + `unread_count` | | `/mini/notice/{id}/read` | PUT | 标记已读 + read_at |