From e7d4d9b414df26e62a5bb298b55c7c7ed63db0e9 Mon Sep 17 00:00:00 2001 From: li Date: Fri, 13 Mar 2026 20:29:45 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=AC=AC=E4=B8=89=E9=98=B6=E6=AE=B5?= =?UTF-8?q?=E4=B8=9A=E5=8A=A1=E6=A8=A1=E5=9D=97(=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E4=BA=A7=E5=BA=B7/=E6=9C=88=E5=AB=82/=E8=BF=9B=E9=94=80?= =?UTF-8?q?=E5=AD=98)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增3个migration(15张表): service/nanny/inventory - 新增15个Model + 13个Controller - 新增55条API路由(总计212条) - 新增3个前端API模块 + 11个管理页面 - vite build验证通过 --- .../Admin/Inventory/InventoryController.php | 17 ++ .../Admin/Inventory/MaterialController.php | 26 ++ .../Inventory/PurchaseOrderController.php | 44 +++ .../Inventory/StockMovementController.php | 48 ++++ .../Admin/Inventory/SupplierController.php | 24 ++ .../Admin/Inventory/WarehouseController.php | 24 ++ .../Admin/Nanny/NannyController.php | 50 ++++ .../Admin/Nanny/NannyOrderController.php | 55 ++++ .../Admin/Nanny/NannyScheduleController.php | 27 ++ .../Service/ServiceExecutionController.php | 43 +++ .../Admin/Service/ServiceItemController.php | 51 ++++ .../Admin/Service/ServiceOrderController.php | 64 +++++ .../Service/ServicePackageController.php | 34 +++ backend/app/Models/Inventory/Inventory.php | 13 + backend/app/Models/Inventory/Material.php | 10 + .../app/Models/Inventory/PurchaseOrder.php | 15 + .../app/Models/Inventory/StockMovement.php | 14 + backend/app/Models/Inventory/Supplier.php | 10 + backend/app/Models/Inventory/Warehouse.php | 13 + backend/app/Models/Nanny/Nanny.php | 13 + backend/app/Models/Nanny/NannyOrder.php | 18 ++ backend/app/Models/Nanny/NannyReview.php | 14 + backend/app/Models/Nanny/NannySchedule.php | 12 + .../app/Models/Service/ServiceExecution.php | 15 + backend/app/Models/Service/ServiceItem.php | 10 + backend/app/Models/Service/ServiceOrder.php | 17 ++ backend/app/Models/Service/ServicePackage.php | 10 + backend/app/Models/Service/ServiceReview.php | 13 + ...024_01_03_000001_create_service_tables.php | 96 +++++++ .../2024_01_03_000002_create_nanny_tables.php | 84 ++++++ ...4_01_03_000003_create_inventory_tables.php | 106 +++++++ backend/routes/api.php | 71 +++++ frontend/src/api/inventory.js | 51 ++++ frontend/src/api/nanny.js | 25 ++ frontend/src/api/service.js | 38 +++ .../src/views/inventory/materials/index.vue | 103 +++++++ .../views/inventory/purchase-orders/index.vue | 141 +++++++++ .../views/inventory/stock-movements/index.vue | 170 +++++++++++ .../src/views/inventory/suppliers/index.vue | 105 +++++++ .../src/views/inventory/warehouses/index.vue | 270 ++++++++++++++++++ frontend/src/views/nanny/nannies/index.vue | 105 +++++++ frontend/src/views/nanny/orders/index.vue | 107 +++++++ frontend/src/views/nanny/schedules/index.vue | 80 ++++++ frontend/src/views/service/items/index.vue | 102 +++++++ frontend/src/views/service/orders/index.vue | 115 ++++++++ frontend/src/views/service/packages/index.vue | 100 +++++++ 46 files changed, 2573 insertions(+) create mode 100644 backend/app/Http/Controllers/Admin/Inventory/InventoryController.php create mode 100644 backend/app/Http/Controllers/Admin/Inventory/MaterialController.php create mode 100644 backend/app/Http/Controllers/Admin/Inventory/PurchaseOrderController.php create mode 100644 backend/app/Http/Controllers/Admin/Inventory/StockMovementController.php create mode 100644 backend/app/Http/Controllers/Admin/Inventory/SupplierController.php create mode 100644 backend/app/Http/Controllers/Admin/Inventory/WarehouseController.php create mode 100644 backend/app/Http/Controllers/Admin/Nanny/NannyController.php create mode 100644 backend/app/Http/Controllers/Admin/Nanny/NannyOrderController.php create mode 100644 backend/app/Http/Controllers/Admin/Nanny/NannyScheduleController.php create mode 100644 backend/app/Http/Controllers/Admin/Service/ServiceExecutionController.php create mode 100644 backend/app/Http/Controllers/Admin/Service/ServiceItemController.php create mode 100644 backend/app/Http/Controllers/Admin/Service/ServiceOrderController.php create mode 100644 backend/app/Http/Controllers/Admin/Service/ServicePackageController.php create mode 100644 backend/app/Models/Inventory/Inventory.php create mode 100644 backend/app/Models/Inventory/Material.php create mode 100644 backend/app/Models/Inventory/PurchaseOrder.php create mode 100644 backend/app/Models/Inventory/StockMovement.php create mode 100644 backend/app/Models/Inventory/Supplier.php create mode 100644 backend/app/Models/Inventory/Warehouse.php create mode 100644 backend/app/Models/Nanny/Nanny.php create mode 100644 backend/app/Models/Nanny/NannyOrder.php create mode 100644 backend/app/Models/Nanny/NannyReview.php create mode 100644 backend/app/Models/Nanny/NannySchedule.php create mode 100644 backend/app/Models/Service/ServiceExecution.php create mode 100644 backend/app/Models/Service/ServiceItem.php create mode 100644 backend/app/Models/Service/ServiceOrder.php create mode 100644 backend/app/Models/Service/ServicePackage.php create mode 100644 backend/app/Models/Service/ServiceReview.php create mode 100644 backend/database/migrations/2024_01_03_000001_create_service_tables.php create mode 100644 backend/database/migrations/2024_01_03_000002_create_nanny_tables.php create mode 100644 backend/database/migrations/2024_01_03_000003_create_inventory_tables.php create mode 100644 frontend/src/api/inventory.js create mode 100644 frontend/src/api/nanny.js create mode 100644 frontend/src/api/service.js create mode 100644 frontend/src/views/inventory/materials/index.vue create mode 100644 frontend/src/views/inventory/purchase-orders/index.vue create mode 100644 frontend/src/views/inventory/stock-movements/index.vue create mode 100644 frontend/src/views/inventory/suppliers/index.vue create mode 100644 frontend/src/views/inventory/warehouses/index.vue create mode 100644 frontend/src/views/nanny/nannies/index.vue create mode 100644 frontend/src/views/nanny/orders/index.vue create mode 100644 frontend/src/views/nanny/schedules/index.vue create mode 100644 frontend/src/views/service/items/index.vue create mode 100644 frontend/src/views/service/orders/index.vue create mode 100644 frontend/src/views/service/packages/index.vue diff --git a/backend/app/Http/Controllers/Admin/Inventory/InventoryController.php b/backend/app/Http/Controllers/Admin/Inventory/InventoryController.php new file mode 100644 index 0000000..5651e60 --- /dev/null +++ b/backend/app/Http/Controllers/Admin/Inventory/InventoryController.php @@ -0,0 +1,17 @@ +with(['warehouse','material']); + $query->when($request->warehouse_id, fn($q, $v) => $q->where('warehouse_id', $v)); + $query->when($request->material_id, fn($q, $v) => $q->where('material_id', $v)); + return $this->paginate($query->paginate($request->input('per_page', 50))); + } +} diff --git a/backend/app/Http/Controllers/Admin/Inventory/MaterialController.php b/backend/app/Http/Controllers/Admin/Inventory/MaterialController.php new file mode 100644 index 0000000..87fd23d --- /dev/null +++ b/backend/app/Http/Controllers/Admin/Inventory/MaterialController.php @@ -0,0 +1,26 @@ +when($request->name, fn($q, $v) => $q->where('name', 'like', "%{$v}%")); + $query->when($request->category, fn($q, $v) => $q->where('category', $v)); + $query->when($request->code, fn($q, $v) => $q->where('code', 'like', "%{$v}%")); + return $this->paginate($query->latest()->paginate($request->input('per_page', 15))); + } + public function store(Request $request): JsonResponse + { + $v = $request->validate(['name'=>'required|string|max:100','code'=>'nullable|string|max:50','category'=>'nullable|string|max:50','unit'=>'nullable|string|max:20','spec'=>'nullable|string|max:100','min_stock'=>'sometimes|integer|min:0','shelf_life_days'=>'nullable|integer|min:1','status'=>'sometimes|integer|in:0,1']); + return $this->success(Material::create($v)); + } + public function show(Material $material): JsonResponse { return $this->success($material); } + public function update(Request $request, Material $material): JsonResponse { $v = $request->validate(['name'=>'sometimes|string|max:100','code'=>'nullable|string|max:50','category'=>'nullable|string|max:50','unit'=>'nullable|string|max:20','spec'=>'nullable|string|max:100','min_stock'=>'sometimes|integer|min:0','shelf_life_days'=>'nullable|integer|min:1','status'=>'sometimes|integer|in:0,1']); $material->update($v); return $this->success($material); } + public function destroy(Material $material): JsonResponse { $material->delete(); return $this->success(null); } +} diff --git a/backend/app/Http/Controllers/Admin/Inventory/PurchaseOrderController.php b/backend/app/Http/Controllers/Admin/Inventory/PurchaseOrderController.php new file mode 100644 index 0000000..aad6773 --- /dev/null +++ b/backend/app/Http/Controllers/Admin/Inventory/PurchaseOrderController.php @@ -0,0 +1,44 @@ +with(['supplier','auditor']); + $query->when($request->order_no, fn($q, $v) => $q->where('order_no', 'like', "%{$v}%")); + $query->when($request->supplier_id, fn($q, $v) => $q->where('supplier_id', $v)); + $query->when($request->has('status'), fn($q) => $q->where('status', $request->status)); + return $this->paginate($query->latest()->paginate($request->input('per_page', 15))); + } + public function store(Request $request): JsonResponse + { + $v = $request->validate(['order_no'=>'required|string|max:50|unique:purchase_orders','supplier_id'=>'nullable|exists:suppliers,id','total_amount'=>'nullable|numeric|min:0','items'=>'nullable|array','remark'=>'nullable|string']); + $v['status'] = 0; $v['created_by'] = auth()->id(); + return $this->success(PurchaseOrder::create($v)); + } + public function show(PurchaseOrder $purchaseOrder): JsonResponse { return $this->success($purchaseOrder->load(['supplier','auditor','creator'])); } + public function update(Request $request, PurchaseOrder $purchaseOrder): JsonResponse + { + if ($purchaseOrder->status >= 2) return $this->error('已审批的采购单不可修改', 40001); + $v = $request->validate(['supplier_id'=>'nullable|exists:suppliers,id','total_amount'=>'nullable|numeric|min:0','items'=>'nullable|array','remark'=>'nullable|string']); + $purchaseOrder->update($v); + return $this->success($purchaseOrder); + } + public function audit(Request $request, PurchaseOrder $purchaseOrder): JsonResponse + { + if ($purchaseOrder->status >= 2) return $this->error('已审批', 40001); + $v = $request->validate(['status'=>'required|integer|in:2,4']); + $purchaseOrder->update(['status'=>$v['status'],'audit_user_id'=>auth()->id(),'audit_at'=>now()]); + return $this->success($purchaseOrder); + } + public function destroy(PurchaseOrder $purchaseOrder): JsonResponse + { + if ($purchaseOrder->status >= 2) return $this->error('已审批不可删除', 40001); + $purchaseOrder->delete(); return $this->success(null); + } +} diff --git a/backend/app/Http/Controllers/Admin/Inventory/StockMovementController.php b/backend/app/Http/Controllers/Admin/Inventory/StockMovementController.php new file mode 100644 index 0000000..43d84d5 --- /dev/null +++ b/backend/app/Http/Controllers/Admin/Inventory/StockMovementController.php @@ -0,0 +1,48 @@ +with('warehouse'); + $query->when($request->warehouse_id, fn($q, $v) => $q->where('warehouse_id', $v)); + $query->when($request->type, fn($q, $v) => $q->where('type', $v)); + $query->when($request->has('status'), fn($q) => $q->where('status', $request->status)); + return $this->paginate($query->latest()->paginate($request->input('per_page', 15))); + } + public function store(Request $request): JsonResponse + { + $v = $request->validate([ + 'warehouse_id'=>'required|exists:warehouses,id','movement_no'=>'required|string|max:50|unique:stock_movements', + 'type'=>'required|integer|in:1,2,3,4','direction'=>'nullable|integer|in:1,2', + 'items'=>'nullable|array','related_order'=>'nullable|string|max:50','remark'=>'nullable|string', + ]); + $v['status'] = 0; $v['operator_id'] = auth()->id(); + return $this->success(StockMovement::create($v)); + } + public function show(StockMovement $stockMovement): JsonResponse { return $this->success($stockMovement->load(['warehouse','operator'])); } + public function confirm(StockMovement $stockMovement): JsonResponse + { + if ($stockMovement->status === 1) return $this->error('已确认', 40001); + $stockMovement->update(['status' => 1]); + // 更新库存 + if ($stockMovement->items) { + foreach ($stockMovement->items as $item) { + $inv = Inventory::firstOrCreate( + ['warehouse_id'=>$stockMovement->warehouse_id,'material_id'=>$item['material_id'],'batch_no'=>$item['batch_no']??null], + ['quantity'=>0] + ); + $qty = $item['quantity'] ?? 0; + $inv->quantity = $stockMovement->direction === 1 ? $inv->quantity + $qty : $inv->quantity - $qty; + $inv->save(); + } + } + return $this->success($stockMovement); + } +} diff --git a/backend/app/Http/Controllers/Admin/Inventory/SupplierController.php b/backend/app/Http/Controllers/Admin/Inventory/SupplierController.php new file mode 100644 index 0000000..475c3c5 --- /dev/null +++ b/backend/app/Http/Controllers/Admin/Inventory/SupplierController.php @@ -0,0 +1,24 @@ +when($request->name, fn($q, $v) => $q->where('name', 'like', "%{$v}%")); + return $this->paginate($query->latest()->paginate($request->input('per_page', 15))); + } + public function store(Request $request): JsonResponse + { + $v = $request->validate(['name'=>'required|string|max:100','contact'=>'nullable|string|max:50','phone'=>'nullable|string|max:20','address'=>'nullable|string|max:255','bank_info'=>'nullable|string|max:255','status'=>'sometimes|integer|in:0,1']); + return $this->success(Supplier::create($v)); + } + public function show(Supplier $supplier): JsonResponse { return $this->success($supplier); } + public function update(Request $request, Supplier $supplier): JsonResponse { $v = $request->validate(['name'=>'sometimes|string|max:100','contact'=>'nullable|string|max:50','phone'=>'nullable|string|max:20','address'=>'nullable|string|max:255','bank_info'=>'nullable|string|max:255','status'=>'sometimes|integer|in:0,1']); $supplier->update($v); return $this->success($supplier); } + public function destroy(Supplier $supplier): JsonResponse { $supplier->delete(); return $this->success(null); } +} diff --git a/backend/app/Http/Controllers/Admin/Inventory/WarehouseController.php b/backend/app/Http/Controllers/Admin/Inventory/WarehouseController.php new file mode 100644 index 0000000..669476b --- /dev/null +++ b/backend/app/Http/Controllers/Admin/Inventory/WarehouseController.php @@ -0,0 +1,24 @@ +with('manager'); + $query->when($request->name, fn($q, $v) => $q->where('name', 'like', "%{$v}%")); + return $this->paginate($query->latest()->paginate($request->input('per_page', 15))); + } + public function store(Request $request): JsonResponse + { + $v = $request->validate(['name'=>'required|string|max:50','manager_id'=>'nullable|exists:users,id','status'=>'sometimes|integer|in:0,1']); + return $this->success(Warehouse::create($v)); + } + public function show(Warehouse $warehouse): JsonResponse { return $this->success($warehouse->load('manager')); } + public function update(Request $request, Warehouse $warehouse): JsonResponse { $v = $request->validate(['name'=>'sometimes|string|max:50','manager_id'=>'nullable|exists:users,id','status'=>'sometimes|integer|in:0,1']); $warehouse->update($v); return $this->success($warehouse); } + public function destroy(Warehouse $warehouse): JsonResponse { $warehouse->delete(); return $this->success(null); } +} diff --git a/backend/app/Http/Controllers/Admin/Nanny/NannyController.php b/backend/app/Http/Controllers/Admin/Nanny/NannyController.php new file mode 100644 index 0000000..e98e752 --- /dev/null +++ b/backend/app/Http/Controllers/Admin/Nanny/NannyController.php @@ -0,0 +1,50 @@ +when($request->name, fn($q, $v) => $q->where('name', 'like', "%{$v}%")); + $query->when($request->level, fn($q, $v) => $q->where('level', $v)); + $query->when($request->status, fn($q, $v) => $q->where('status', $v)); + $query->when($request->cooperation_type, fn($q, $v) => $q->where('cooperation_type', $v)); + return $this->paginate($query->latest()->paginate($request->input('per_page', 15))); + } + + public function store(Request $request): JsonResponse + { + $validated = $request->validate([ + 'name'=>'required|string|max:50','phone'=>'nullable|string|max:20','id_card'=>'nullable|string|max:20', + 'level'=>'nullable|integer|in:1,2,3,4','skills'=>'nullable|array','experience_years'=>'nullable|integer|min:0', + 'health_cert'=>'nullable|string|max:255','cooperation_type'=>'nullable|integer|in:1,2,3', + 'base_salary'=>'nullable|numeric|min:0','introduction'=>'nullable|string','status'=>'sometimes|integer|in:1,2,3,4', + ]); + return $this->success(Nanny::create($validated)); + } + + public function show(Nanny $nanny): JsonResponse { return $this->success($nanny->load(['orders.customer','schedules'])); } + + public function update(Request $request, Nanny $nanny): JsonResponse + { + $validated = $request->validate([ + 'name'=>'sometimes|string|max:50','phone'=>'nullable|string|max:20','level'=>'nullable|integer|in:1,2,3,4', + 'skills'=>'nullable|array','experience_years'=>'nullable|integer|min:0','cooperation_type'=>'nullable|integer|in:1,2,3', + 'base_salary'=>'nullable|numeric|min:0','introduction'=>'nullable|string','status'=>'sometimes|integer|in:1,2,3,4', + ]); + $nanny->update($validated); + return $this->success($nanny); + } + + public function destroy(Nanny $nanny): JsonResponse + { + if ($nanny->status === 2) return $this->error('服务中的月嫂无法删除', 40001); + $nanny->delete(); + return $this->success(null); + } +} diff --git a/backend/app/Http/Controllers/Admin/Nanny/NannyOrderController.php b/backend/app/Http/Controllers/Admin/Nanny/NannyOrderController.php new file mode 100644 index 0000000..4ba690e --- /dev/null +++ b/backend/app/Http/Controllers/Admin/Nanny/NannyOrderController.php @@ -0,0 +1,55 @@ +with(['customer','nanny']); + $query->when($request->customer_id, fn($q, $v) => $q->where('customer_id', $v)); + $query->when($request->nanny_id, fn($q, $v) => $q->where('nanny_id', $v)); + $query->when($request->has('status'), fn($q) => $q->where('status', $request->status)); + return $this->paginate($query->latest()->paginate($request->input('per_page', 15))); + } + + public function store(Request $request): JsonResponse + { + $validated = $request->validate([ + 'customer_id'=>'required|exists:customers,id','nanny_id'=>'nullable|exists:nannies,id', + 'order_no'=>'required|string|max:50|unique:nanny_orders','service_type'=>'nullable|integer|in:1,2', + 'start_date'=>'nullable|date','end_date'=>'nullable|date|after_or_equal:start_date', + 'days'=>'nullable|integer|min:1','price'=>'nullable|numeric|min:0','remark'=>'nullable|string', + ]); + $validated['status'] = 0; + $validated['created_by'] = auth()->id(); + return $this->success(NannyOrder::create($validated)); + } + + public function show(NannyOrder $nannyOrder): JsonResponse { return $this->success($nannyOrder->load(['customer','nanny','reviews'])); } + + public function update(Request $request, NannyOrder $nannyOrder): JsonResponse + { + if ($nannyOrder->status >= 4) return $this->error('已完成/取消的订单不可修改', 40001); + $validated = $request->validate([ + 'nanny_id'=>'nullable|exists:nannies,id','start_date'=>'nullable|date','end_date'=>'nullable|date', + 'days'=>'nullable|integer|min:1','price'=>'nullable|numeric|min:0','status'=>'sometimes|integer|in:0,1,2,3,4,5', + 'match_candidates'=>'nullable|array','remark'=>'nullable|string', + ]); + $nannyOrder->update($validated); + if (isset($validated['nanny_id']) && $validated['nanny_id']) { + $nannyOrder->nanny->update(['status' => 2]); + } + return $this->success($nannyOrder); + } + + public function destroy(NannyOrder $nannyOrder): JsonResponse + { + if ($nannyOrder->status >= 2) return $this->error('已签约后不可删除', 40001); + $nannyOrder->delete(); + return $this->success(null); + } +} diff --git a/backend/app/Http/Controllers/Admin/Nanny/NannyScheduleController.php b/backend/app/Http/Controllers/Admin/Nanny/NannyScheduleController.php new file mode 100644 index 0000000..bb07eeb --- /dev/null +++ b/backend/app/Http/Controllers/Admin/Nanny/NannyScheduleController.php @@ -0,0 +1,27 @@ +with(['nanny','order']); + $query->when($request->nanny_id, fn($q, $v) => $q->where('nanny_id', $v)); + $query->when($request->start_date, fn($q, $v) => $q->where('schedule_date', '>=', $v)); + $query->when($request->end_date, fn($q, $v) => $q->where('schedule_date', '<=', $v)); + return $this->paginate($query->orderBy('schedule_date')->paginate($request->input('per_page', 31))); + } + + public function store(Request $request): JsonResponse + { + $validated = $request->validate([ + 'nanny_id'=>'required|exists:nannies,id','nanny_order_id'=>'nullable|exists:nanny_orders,id', + 'schedule_date'=>'required|date','type'=>'nullable|integer|in:1,2,3','remark'=>'nullable|string|max:255', + ]); + return $this->success(NannySchedule::create($validated)); + } +} diff --git a/backend/app/Http/Controllers/Admin/Service/ServiceExecutionController.php b/backend/app/Http/Controllers/Admin/Service/ServiceExecutionController.php new file mode 100644 index 0000000..f2c2199 --- /dev/null +++ b/backend/app/Http/Controllers/Admin/Service/ServiceExecutionController.php @@ -0,0 +1,43 @@ +with(['order','serviceItem','customer','technician']); + $query->when($request->service_order_id, fn($q, $v) => $q->where('service_order_id', $v)); + $query->when($request->technician_id, fn($q, $v) => $q->where('technician_id', $v)); + $query->when($request->has('status'), fn($q) => $q->where('status', $request->status)); + return $this->paginate($query->latest()->paginate($request->input('per_page', 15))); + } + + public function store(Request $request): JsonResponse + { + $validated = $request->validate([ + 'service_order_id'=>'required|exists:service_orders,id','service_item_id'=>'required|exists:service_items,id', + 'customer_id'=>'required|exists:customers,id','technician_id'=>'nullable|exists:users,id', + 'scheduled_at'=>'nullable|date','remark'=>'nullable|string', + ]); + $validated['status'] = 0; + return $this->success(ServiceExecution::create($validated)); + } + + public function start(ServiceExecution $serviceExecution): JsonResponse + { + if ($serviceExecution->status !== 0) return $this->error('只有待执行可开始', 40001); + $serviceExecution->update(['status'=>1,'started_at'=>now()]); + return $this->success($serviceExecution); + } + + public function finish(ServiceExecution $serviceExecution): JsonResponse + { + if ($serviceExecution->status !== 1) return $this->error('只有执行中可完成', 40001); + $serviceExecution->update(['status'=>2,'ended_at'=>now()]); + return $this->success($serviceExecution); + } +} diff --git a/backend/app/Http/Controllers/Admin/Service/ServiceItemController.php b/backend/app/Http/Controllers/Admin/Service/ServiceItemController.php new file mode 100644 index 0000000..62157a4 --- /dev/null +++ b/backend/app/Http/Controllers/Admin/Service/ServiceItemController.php @@ -0,0 +1,51 @@ +when($request->name, fn($q, $v) => $q->where('name', 'like', "%{$v}%")); + $query->when($request->category, fn($q, $v) => $q->where('category', $v)); + $query->when($request->has('status'), fn($q) => $q->where('status', $request->status)); + return $this->paginate($query->latest()->paginate($request->input('per_page', 15))); + } + + public function store(Request $request): JsonResponse + { + $validated = $request->validate([ + 'name' => 'required|string|max:100', + 'category' => 'nullable|integer|in:1,2,3,4', + 'price' => 'required|numeric|min:0', + 'duration' => 'nullable|integer|min:1', + 'description' => 'nullable|string', + 'image' => 'nullable|string|max:255', + 'status' => 'sometimes|integer|in:0,1', + ]); + return $this->success(ServiceItem::create($validated)); + } + + public function show(ServiceItem $serviceItem): JsonResponse { return $this->success($serviceItem); } + + public function update(Request $request, ServiceItem $serviceItem): JsonResponse + { + $validated = $request->validate([ + 'name' => 'sometimes|string|max:100', + 'category' => 'nullable|integer|in:1,2,3,4', + 'price' => 'sometimes|numeric|min:0', + 'duration' => 'nullable|integer|min:1', + 'description' => 'nullable|string', + 'image' => 'nullable|string|max:255', + 'status' => 'sometimes|integer|in:0,1', + ]); + $serviceItem->update($validated); + return $this->success($serviceItem); + } + + public function destroy(ServiceItem $serviceItem): JsonResponse { $serviceItem->delete(); return $this->success(null); } +} diff --git a/backend/app/Http/Controllers/Admin/Service/ServiceOrderController.php b/backend/app/Http/Controllers/Admin/Service/ServiceOrderController.php new file mode 100644 index 0000000..9d533ab --- /dev/null +++ b/backend/app/Http/Controllers/Admin/Service/ServiceOrderController.php @@ -0,0 +1,64 @@ +with(['customer']); + $query->when($request->order_no, fn($q, $v) => $q->where('order_no', 'like', "%{$v}%")); + $query->when($request->customer_id, fn($q, $v) => $q->where('customer_id', $v)); + $query->when($request->type, fn($q, $v) => $q->where('type', $v)); + $query->when($request->has('status'), fn($q) => $q->where('status', $request->status)); + return $this->paginate($query->latest()->paginate($request->input('per_page', 15))); + } + + public function store(Request $request): JsonResponse + { + $validated = $request->validate([ + 'customer_id'=>'required|exists:customers,id','order_no'=>'required|string|max:50|unique:service_orders', + 'type'=>'nullable|integer|in:1,2,3,4','items'=>'nullable|array', + 'total_amount'=>'required|numeric|min:0','discount_amount'=>'sometimes|numeric|min:0','actual_amount'=>'required|numeric|min:0', + 'pay_method'=>'nullable|integer|in:1,2,3,4','remark'=>'nullable|string', + ]); + $validated['status'] = 0; + $validated['created_by'] = auth()->id(); + return $this->success(ServiceOrder::create($validated)); + } + + public function show(ServiceOrder $serviceOrder): JsonResponse { return $this->success($serviceOrder->load(['customer','executions.serviceItem','executions.technician'])); } + + public function update(Request $request, ServiceOrder $serviceOrder): JsonResponse + { + if ($serviceOrder->status >= 2) return $this->error('已完成/取消的订单不可修改', 40001); + $validated = $request->validate(['items'=>'nullable|array','total_amount'=>'sometimes|numeric|min:0','discount_amount'=>'sometimes|numeric|min:0','actual_amount'=>'sometimes|numeric|min:0','remark'=>'nullable|string']); + $serviceOrder->update($validated); + return $this->success($serviceOrder); + } + + public function destroy(ServiceOrder $serviceOrder): JsonResponse + { + if ($serviceOrder->status >= 1) return $this->error('已付款的订单不可删除', 40001); + $serviceOrder->delete(); + return $this->success(null); + } + + public function pay(Request $request, ServiceOrder $serviceOrder): JsonResponse + { + if ($serviceOrder->status !== 0) return $this->error('该订单不可付款', 40001); + $validated = $request->validate(['pay_method'=>'required|integer|in:1,2,3,4']); + $serviceOrder->update(['status'=>1,'pay_method'=>$validated['pay_method'],'paid_at'=>now()]); + return $this->success($serviceOrder); + } + + public function complete(ServiceOrder $serviceOrder): JsonResponse + { + if ($serviceOrder->status !== 1) return $this->error('只有已付款的订单可完成', 40001); + $serviceOrder->update(['status'=>2]); + return $this->success($serviceOrder); + } +} diff --git a/backend/app/Http/Controllers/Admin/Service/ServicePackageController.php b/backend/app/Http/Controllers/Admin/Service/ServicePackageController.php new file mode 100644 index 0000000..833f707 --- /dev/null +++ b/backend/app/Http/Controllers/Admin/Service/ServicePackageController.php @@ -0,0 +1,34 @@ +when($request->name, fn($q, $v) => $q->where('name', 'like', "%{$v}%")); + $query->when($request->has('status'), fn($q) => $q->where('status', $request->status)); + return $this->paginate($query->latest()->paginate($request->input('per_page', 15))); + } + + public function store(Request $request): JsonResponse + { + $validated = $request->validate(['name'=>'required|string|max:100','items'=>'nullable|array','price'=>'required|numeric|min:0','validity_days'=>'nullable|integer|min:1','status'=>'sometimes|integer|in:0,1']); + return $this->success(ServicePackage::create($validated)); + } + + public function show(ServicePackage $servicePackage): JsonResponse { return $this->success($servicePackage); } + + public function update(Request $request, ServicePackage $servicePackage): JsonResponse + { + $validated = $request->validate(['name'=>'sometimes|string|max:100','items'=>'nullable|array','price'=>'sometimes|numeric|min:0','validity_days'=>'nullable|integer|min:1','status'=>'sometimes|integer|in:0,1']); + $servicePackage->update($validated); + return $this->success($servicePackage); + } + + public function destroy(ServicePackage $servicePackage): JsonResponse { $servicePackage->delete(); return $this->success(null); } +} diff --git a/backend/app/Models/Inventory/Inventory.php b/backend/app/Models/Inventory/Inventory.php new file mode 100644 index 0000000..798b23a --- /dev/null +++ b/backend/app/Models/Inventory/Inventory.php @@ -0,0 +1,13 @@ +'decimal:2','expire_date'=>'date']; } + public function warehouse(): BelongsTo { return $this->belongsTo(Warehouse::class); } + public function material(): BelongsTo { return $this->belongsTo(Material::class); } +} diff --git a/backend/app/Models/Inventory/Material.php b/backend/app/Models/Inventory/Material.php new file mode 100644 index 0000000..e406a8b --- /dev/null +++ b/backend/app/Models/Inventory/Material.php @@ -0,0 +1,10 @@ +'integer','shelf_life_days'=>'integer','status'=>'integer']; } +} diff --git a/backend/app/Models/Inventory/PurchaseOrder.php b/backend/app/Models/Inventory/PurchaseOrder.php new file mode 100644 index 0000000..21871ff --- /dev/null +++ b/backend/app/Models/Inventory/PurchaseOrder.php @@ -0,0 +1,15 @@ +'decimal:2','status'=>'integer','items'=>'array','audit_at'=>'datetime']; } + public function supplier(): BelongsTo { return $this->belongsTo(Supplier::class); } + public function auditor(): BelongsTo { return $this->belongsTo(User::class, 'audit_user_id'); } + public function creator(): BelongsTo { return $this->belongsTo(User::class, 'created_by'); } +} diff --git a/backend/app/Models/Inventory/StockMovement.php b/backend/app/Models/Inventory/StockMovement.php new file mode 100644 index 0000000..24032a8 --- /dev/null +++ b/backend/app/Models/Inventory/StockMovement.php @@ -0,0 +1,14 @@ +'integer','direction'=>'integer','items'=>'array','status'=>'integer']; } + public function warehouse(): BelongsTo { return $this->belongsTo(Warehouse::class); } + public function operator(): BelongsTo { return $this->belongsTo(User::class, 'operator_id'); } +} diff --git a/backend/app/Models/Inventory/Supplier.php b/backend/app/Models/Inventory/Supplier.php new file mode 100644 index 0000000..89f844a --- /dev/null +++ b/backend/app/Models/Inventory/Supplier.php @@ -0,0 +1,10 @@ +'integer']; } +} diff --git a/backend/app/Models/Inventory/Warehouse.php b/backend/app/Models/Inventory/Warehouse.php new file mode 100644 index 0000000..41cf7c5 --- /dev/null +++ b/backend/app/Models/Inventory/Warehouse.php @@ -0,0 +1,13 @@ +'integer']; } + public function manager(): BelongsTo { return $this->belongsTo(User::class, 'manager_id'); } +} diff --git a/backend/app/Models/Nanny/Nanny.php b/backend/app/Models/Nanny/Nanny.php new file mode 100644 index 0000000..482fb28 --- /dev/null +++ b/backend/app/Models/Nanny/Nanny.php @@ -0,0 +1,13 @@ +'integer','skills'=>'array','experience_years'=>'integer','cooperation_type'=>'integer','base_salary'=>'decimal:2','status'=>'integer']; } + public function orders(): HasMany { return $this->hasMany(NannyOrder::class); } + public function schedules(): HasMany { return $this->hasMany(NannySchedule::class); } +} diff --git a/backend/app/Models/Nanny/NannyOrder.php b/backend/app/Models/Nanny/NannyOrder.php new file mode 100644 index 0000000..2d16cbd --- /dev/null +++ b/backend/app/Models/Nanny/NannyOrder.php @@ -0,0 +1,18 @@ +'integer','start_date'=>'date','end_date'=>'date','days'=>'integer','price'=>'decimal:2','status'=>'integer','match_candidates'=>'array']; } + public function customer(): BelongsTo { return $this->belongsTo(Customer::class); } + public function nanny(): BelongsTo { return $this->belongsTo(Nanny::class); } + public function creator(): BelongsTo { return $this->belongsTo(User::class, 'created_by'); } + public function reviews(): HasMany { return $this->hasMany(NannyReview::class); } +} diff --git a/backend/app/Models/Nanny/NannyReview.php b/backend/app/Models/Nanny/NannyReview.php new file mode 100644 index 0000000..e484115 --- /dev/null +++ b/backend/app/Models/Nanny/NannyReview.php @@ -0,0 +1,14 @@ +'integer']; } + public function order(): BelongsTo { return $this->belongsTo(NannyOrder::class, 'nanny_order_id'); } + public function customer(): BelongsTo { return $this->belongsTo(Customer::class); } + public function nanny(): BelongsTo { return $this->belongsTo(Nanny::class); } +} diff --git a/backend/app/Models/Nanny/NannySchedule.php b/backend/app/Models/Nanny/NannySchedule.php new file mode 100644 index 0000000..866aae5 --- /dev/null +++ b/backend/app/Models/Nanny/NannySchedule.php @@ -0,0 +1,12 @@ +'date','type'=>'integer']; } + public function nanny(): BelongsTo { return $this->belongsTo(Nanny::class); } + public function order(): BelongsTo { return $this->belongsTo(NannyOrder::class, 'nanny_order_id'); } +} diff --git a/backend/app/Models/Service/ServiceExecution.php b/backend/app/Models/Service/ServiceExecution.php new file mode 100644 index 0000000..cdb06d7 --- /dev/null +++ b/backend/app/Models/Service/ServiceExecution.php @@ -0,0 +1,15 @@ +'datetime','started_at'=>'datetime','ended_at'=>'datetime','status'=>'integer']; } + public function order(): BelongsTo { return $this->belongsTo(ServiceOrder::class, 'service_order_id'); } + public function serviceItem(): BelongsTo { return $this->belongsTo(ServiceItem::class); } + public function customer(): BelongsTo { return $this->belongsTo(Customer::class); } + public function technician(): BelongsTo { return $this->belongsTo(User::class, 'technician_id'); } +} diff --git a/backend/app/Models/Service/ServiceItem.php b/backend/app/Models/Service/ServiceItem.php new file mode 100644 index 0000000..1ce9efb --- /dev/null +++ b/backend/app/Models/Service/ServiceItem.php @@ -0,0 +1,10 @@ +'integer','price'=>'decimal:2','duration'=>'integer','status'=>'integer']; } +} diff --git a/backend/app/Models/Service/ServiceOrder.php b/backend/app/Models/Service/ServiceOrder.php new file mode 100644 index 0000000..b424f90 --- /dev/null +++ b/backend/app/Models/Service/ServiceOrder.php @@ -0,0 +1,17 @@ +'integer','items'=>'array','total_amount'=>'decimal:2','discount_amount'=>'decimal:2','actual_amount'=>'decimal:2','status'=>'integer','pay_method'=>'integer','paid_at'=>'datetime']; } + public function customer(): BelongsTo { return $this->belongsTo(Customer::class); } + public function creator(): BelongsTo { return $this->belongsTo(User::class, 'created_by'); } + public function executions(): HasMany { return $this->hasMany(ServiceExecution::class); } +} diff --git a/backend/app/Models/Service/ServicePackage.php b/backend/app/Models/Service/ServicePackage.php new file mode 100644 index 0000000..2415d15 --- /dev/null +++ b/backend/app/Models/Service/ServicePackage.php @@ -0,0 +1,10 @@ +'array','price'=>'decimal:2','validity_days'=>'integer','status'=>'integer']; } +} diff --git a/backend/app/Models/Service/ServiceReview.php b/backend/app/Models/Service/ServiceReview.php new file mode 100644 index 0000000..3c057c9 --- /dev/null +++ b/backend/app/Models/Service/ServiceReview.php @@ -0,0 +1,13 @@ +'integer']; } + public function order(): BelongsTo { return $this->belongsTo(ServiceOrder::class, 'service_order_id'); } + public function customer(): BelongsTo { return $this->belongsTo(Customer::class); } +} diff --git a/backend/database/migrations/2024_01_03_000001_create_service_tables.php b/backend/database/migrations/2024_01_03_000001_create_service_tables.php new file mode 100644 index 0000000..04bdad5 --- /dev/null +++ b/backend/database/migrations/2024_01_03_000001_create_service_tables.php @@ -0,0 +1,96 @@ +id(); + $table->foreignId('store_id')->constrained(); + $table->string('name', 100); + $table->tinyInteger('category')->nullable()->comment('1产康 2零售 3加餐 4其他'); + $table->decimal('price', 10, 2); + $table->integer('duration')->nullable()->comment('服务时长(分钟)'); + $table->text('description')->nullable(); + $table->string('image', 255)->nullable(); + $table->tinyInteger('status')->default(1); + $table->timestamps(); + $table->index(['store_id', 'category']); + }); + + // 服务包 + Schema::create('service_packages', function (Blueprint $table) { + $table->id(); + $table->foreignId('store_id')->constrained(); + $table->string('name', 100); + $table->json('items')->nullable()->comment('包含项目与次数'); + $table->decimal('price', 10, 2); + $table->integer('validity_days')->nullable()->comment('有效天数'); + $table->tinyInteger('status')->default(1); + $table->timestamps(); + }); + + // 服务订单 + Schema::create('service_orders', function (Blueprint $table) { + $table->id(); + $table->foreignId('store_id')->constrained(); + $table->foreignId('customer_id')->constrained(); + $table->string('order_no', 50)->unique(); + $table->tinyInteger('type')->nullable()->comment('1单项 2服务包 3零售 4加餐'); + $table->json('items')->nullable()->comment('订单项明细'); + $table->decimal('total_amount', 12, 2)->nullable(); + $table->decimal('discount_amount', 12, 2)->default(0); + $table->decimal('actual_amount', 12, 2)->nullable(); + $table->tinyInteger('status')->default(0)->comment('0待付 1已付 2已完成 3已取消 4已退'); + $table->tinyInteger('pay_method')->nullable()->comment('1微信 2支付宝 3现金 4挂账'); + $table->timestamp('paid_at')->nullable(); + $table->unsignedBigInteger('created_by')->nullable(); + $table->timestamps(); + $table->index('store_id'); + $table->index('customer_id'); + }); + + // 服务执行记录 + Schema::create('service_executions', function (Blueprint $table) { + $table->id(); + $table->foreignId('service_order_id')->constrained()->cascadeOnDelete(); + $table->foreignId('service_item_id')->constrained(); + $table->foreignId('customer_id')->constrained(); + $table->unsignedBigInteger('technician_id')->nullable()->comment('技师/产康师'); + $table->timestamp('scheduled_at')->nullable()->comment('预约时间'); + $table->timestamp('started_at')->nullable(); + $table->timestamp('ended_at')->nullable(); + $table->tinyInteger('status')->default(0)->comment('0待执行 1执行中 2已完成 3已取消'); + $table->text('remark')->nullable(); + $table->timestamps(); + $table->index('service_order_id'); + $table->index('technician_id'); + }); + + // 服务评价 + Schema::create('service_reviews', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('service_order_id')->nullable(); + $table->unsignedBigInteger('service_execution_id')->nullable(); + $table->foreignId('customer_id')->constrained(); + $table->tinyInteger('score')->nullable()->comment('1-5星'); + $table->text('content')->nullable(); + $table->timestamp('created_at')->nullable(); + $table->index('service_order_id'); + }); + } + + public function down(): void + { + Schema::dropIfExists('service_reviews'); + Schema::dropIfExists('service_executions'); + Schema::dropIfExists('service_orders'); + Schema::dropIfExists('service_packages'); + Schema::dropIfExists('service_items'); + } +}; diff --git a/backend/database/migrations/2024_01_03_000002_create_nanny_tables.php b/backend/database/migrations/2024_01_03_000002_create_nanny_tables.php new file mode 100644 index 0000000..9ebb18b --- /dev/null +++ b/backend/database/migrations/2024_01_03_000002_create_nanny_tables.php @@ -0,0 +1,84 @@ +id(); + $table->foreignId('store_id')->constrained(); + $table->string('name', 50); + $table->string('phone', 20)->nullable(); + $table->string('id_card', 20)->nullable(); + $table->string('avatar', 255)->nullable(); + $table->tinyInteger('level')->nullable()->comment('1初级 2中级 3高级 4金牌'); + $table->json('skills')->nullable()->comment('技能标签'); + $table->integer('experience_years')->nullable(); + $table->string('health_cert', 255)->nullable()->comment('健康证'); + $table->tinyInteger('cooperation_type')->nullable()->comment('1自有 2合作 3兼职'); + $table->decimal('base_salary', 10, 2)->nullable(); + $table->text('introduction')->nullable(); + $table->tinyInteger('status')->default(1)->comment('1空闲 2服务中 3休假 4停用'); + $table->timestamps(); + $table->index(['store_id', 'status']); + }); + + // 月嫂订单 + Schema::create('nanny_orders', function (Blueprint $table) { + $table->id(); + $table->foreignId('store_id')->constrained(); + $table->foreignId('customer_id')->constrained(); + $table->unsignedBigInteger('nanny_id')->nullable(); + $table->string('order_no', 50)->unique(); + $table->tinyInteger('service_type')->nullable()->comment('1月嫂 2育儿嫂'); + $table->date('start_date')->nullable(); + $table->date('end_date')->nullable(); + $table->integer('days')->nullable(); + $table->decimal('price', 12, 2)->nullable(); + $table->tinyInteger('status')->default(0)->comment('0待匹配 1待确认 2已签约 3服务中 4已完成 5已取消'); + $table->json('match_candidates')->nullable()->comment('匹配候选月嫂'); + $table->text('remark')->nullable(); + $table->unsignedBigInteger('created_by')->nullable(); + $table->timestamps(); + $table->index('store_id'); + $table->index('nanny_id'); + }); + + // 月嫂排班 + Schema::create('nanny_schedules', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('nanny_id'); + $table->unsignedBigInteger('nanny_order_id')->nullable(); + $table->date('schedule_date'); + $table->tinyInteger('type')->nullable()->comment('1服务 2休息 3培训'); + $table->string('remark', 255)->nullable(); + $table->timestamp('created_at')->nullable(); + $table->index(['nanny_id', 'schedule_date']); + }); + + // 月嫂评价 + Schema::create('nanny_reviews', function (Blueprint $table) { + $table->id(); + $table->foreignId('nanny_order_id')->constrained()->cascadeOnDelete(); + $table->foreignId('customer_id')->constrained(); + $table->unsignedBigInteger('nanny_id'); + $table->tinyInteger('score')->nullable()->comment('1-5'); + $table->text('content')->nullable(); + $table->timestamp('created_at')->nullable(); + $table->index('nanny_id'); + }); + } + + public function down(): void + { + Schema::dropIfExists('nanny_reviews'); + Schema::dropIfExists('nanny_schedules'); + Schema::dropIfExists('nanny_orders'); + Schema::dropIfExists('nannies'); + } +}; diff --git a/backend/database/migrations/2024_01_03_000003_create_inventory_tables.php b/backend/database/migrations/2024_01_03_000003_create_inventory_tables.php new file mode 100644 index 0000000..b1cb13f --- /dev/null +++ b/backend/database/migrations/2024_01_03_000003_create_inventory_tables.php @@ -0,0 +1,106 @@ +id(); + $table->foreignId('store_id')->constrained(); + $table->string('name', 50); + $table->unsignedBigInteger('manager_id')->nullable(); + $table->tinyInteger('status')->default(1); + $table->timestamps(); + }); + + // 供应商 + Schema::create('suppliers', function (Blueprint $table) { + $table->id(); + $table->foreignId('store_id')->constrained(); + $table->string('name', 100); + $table->string('contact', 50)->nullable(); + $table->string('phone', 20)->nullable(); + $table->string('address', 255)->nullable(); + $table->string('bank_info', 255)->nullable(); + $table->tinyInteger('status')->default(1); + $table->timestamps(); + }); + + // 物资(SKU) + Schema::create('materials', function (Blueprint $table) { + $table->id(); + $table->foreignId('store_id')->constrained(); + $table->string('name', 100); + $table->string('code', 50)->nullable()->comment('SKU编码'); + $table->string('category', 50)->nullable(); + $table->string('unit', 20)->nullable()->comment('单位'); + $table->string('spec', 100)->nullable()->comment('规格'); + $table->integer('min_stock')->default(0)->comment('安全库存'); + $table->integer('shelf_life_days')->nullable()->comment('保质天数'); + $table->tinyInteger('status')->default(1); + $table->timestamps(); + $table->index('store_id'); + $table->index('code'); + }); + + // 库存 + Schema::create('inventories', function (Blueprint $table) { + $table->id(); + $table->foreignId('warehouse_id')->constrained(); + $table->foreignId('material_id')->constrained(); + $table->decimal('quantity', 12, 2)->default(0); + $table->string('batch_no', 50)->nullable(); + $table->date('expire_date')->nullable(); + $table->timestamp('updated_at')->nullable(); + $table->unique(['warehouse_id', 'material_id', 'batch_no'], 'idx_wh_mat_batch'); + }); + + // 采购单 + Schema::create('purchase_orders', function (Blueprint $table) { + $table->id(); + $table->foreignId('store_id')->constrained(); + $table->string('order_no', 50)->unique(); + $table->unsignedBigInteger('supplier_id')->nullable(); + $table->decimal('total_amount', 12, 2)->nullable(); + $table->tinyInteger('status')->default(0)->comment('0草稿 1待审批 2已审批 3已入库 4已取消'); + $table->json('items')->nullable()->comment('采购明细'); + $table->unsignedBigInteger('audit_user_id')->nullable(); + $table->timestamp('audit_at')->nullable(); + $table->text('remark')->nullable(); + $table->unsignedBigInteger('created_by')->nullable(); + $table->timestamps(); + }); + + // 出入库单 + Schema::create('stock_movements', function (Blueprint $table) { + $table->id(); + $table->foreignId('store_id')->constrained(); + $table->foreignId('warehouse_id')->constrained(); + $table->string('movement_no', 50)->unique(); + $table->tinyInteger('type')->comment('1入库 2出库 3调拨 4盘点'); + $table->tinyInteger('direction')->nullable()->comment('1入 2出'); + $table->json('items')->nullable()->comment('物资明细'); + $table->string('related_order', 50)->nullable()->comment('关联单号'); + $table->tinyInteger('status')->default(0)->comment('0草稿 1已确认'); + $table->unsignedBigInteger('operator_id')->nullable(); + $table->text('remark')->nullable(); + $table->timestamps(); + $table->index(['store_id', 'type']); + }); + } + + public function down(): void + { + Schema::dropIfExists('stock_movements'); + Schema::dropIfExists('purchase_orders'); + Schema::dropIfExists('inventories'); + Schema::dropIfExists('materials'); + Schema::dropIfExists('suppliers'); + Schema::dropIfExists('warehouses'); + } +}; diff --git a/backend/routes/api.php b/backend/routes/api.php index e5982e4..05e66b1 100644 --- a/backend/routes/api.php +++ b/backend/routes/api.php @@ -31,6 +31,19 @@ use App\Http\Controllers\Admin\Meal\DishController; use App\Http\Controllers\Admin\Meal\MealPlanTemplateController; use App\Http\Controllers\Admin\Meal\DailyMealPlanController; use App\Http\Controllers\Admin\Meal\MealReviewController; +use App\Http\Controllers\Admin\Service\ServiceItemController; +use App\Http\Controllers\Admin\Service\ServicePackageController; +use App\Http\Controllers\Admin\Service\ServiceOrderController; +use App\Http\Controllers\Admin\Service\ServiceExecutionController; +use App\Http\Controllers\Admin\Nanny\NannyController; +use App\Http\Controllers\Admin\Nanny\NannyOrderController; +use App\Http\Controllers\Admin\Nanny\NannyScheduleController; +use App\Http\Controllers\Admin\Inventory\WarehouseController; +use App\Http\Controllers\Admin\Inventory\SupplierController; +use App\Http\Controllers\Admin\Inventory\MaterialController; +use App\Http\Controllers\Admin\Inventory\PurchaseOrderController; +use App\Http\Controllers\Admin\Inventory\StockMovementController; +use App\Http\Controllers\Admin\Inventory\InventoryController; /* |-------------------------------------------------------------------------- @@ -176,4 +189,62 @@ Route::middleware(['auth:sanctum', 'store', 'oplog'])->group(function () { Route::get('reviews', [MealReviewController::class, 'index']); Route::post('reviews', [MealReviewController::class, 'store']); }); + + // --- 服务产康模块 --- + Route::prefix('service')->group(function () { + // 服务项目 + Route::apiResource('items', ServiceItemController::class); + + // 服务套餐 + Route::apiResource('packages', ServicePackageController::class); + + // 服务订单 + Route::apiResource('orders', ServiceOrderController::class); + Route::put('orders/{serviceOrder}/pay', [ServiceOrderController::class, 'pay']); + Route::put('orders/{serviceOrder}/complete', [ServiceOrderController::class, 'complete']); + + // 服务执行 + Route::get('executions', [ServiceExecutionController::class, 'index']); + Route::post('executions', [ServiceExecutionController::class, 'store']); + Route::put('executions/{serviceExecution}/start', [ServiceExecutionController::class, 'start']); + Route::put('executions/{serviceExecution}/finish', [ServiceExecutionController::class, 'finish']); + }); + + // --- 月嫂管理模块 --- + Route::prefix('nanny')->group(function () { + // 月嫂信息 + Route::apiResource('nannies', NannyController::class); + + // 月嫂订单 + Route::apiResource('orders', NannyOrderController::class); + + // 月嫂排班 + Route::get('schedules', [NannyScheduleController::class, 'index']); + Route::post('schedules', [NannyScheduleController::class, 'store']); + }); + + // --- 进销存模块 --- + Route::prefix('inventory')->group(function () { + // 仓库管理 + Route::apiResource('warehouses', WarehouseController::class); + + // 供应商管理 + Route::apiResource('suppliers', SupplierController::class); + + // 物料管理 + Route::apiResource('materials', MaterialController::class); + + // 采购管理 + Route::apiResource('purchase-orders', PurchaseOrderController::class); + Route::put('purchase-orders/{purchaseOrder}/audit', [PurchaseOrderController::class, 'audit']); + + // 出入库流水 + Route::get('stock-movements', [StockMovementController::class, 'index']); + Route::post('stock-movements', [StockMovementController::class, 'store']); + Route::get('stock-movements/{stockMovement}', [StockMovementController::class, 'show']); + Route::put('stock-movements/{stockMovement}/confirm', [StockMovementController::class, 'confirm']); + + // 库存查询 + Route::get('inventories', [InventoryController::class, 'index']); + }); }); diff --git a/frontend/src/api/inventory.js b/frontend/src/api/inventory.js new file mode 100644 index 0000000..57deb32 --- /dev/null +++ b/frontend/src/api/inventory.js @@ -0,0 +1,51 @@ +import request from '@/utils/request.js' + +// ─── Warehouses ────────────────────────────────────────────────────────────── +export const warehouseApi = { + getList: (params) => request.get('/inventory/warehouses', { params }), + getDetail: (id) => request.get(`/inventory/warehouses/${id}`), + create: (data) => request.post('/inventory/warehouses', data), + update: (id, data) => request.put(`/inventory/warehouses/${id}`, data), + delete: (id) => request.delete(`/inventory/warehouses/${id}`) +} + +// ─── Suppliers ─────────────────────────────────────────────────────────────── +export const supplierApi = { + getList: (params) => request.get('/inventory/suppliers', { params }), + getDetail: (id) => request.get(`/inventory/suppliers/${id}`), + create: (data) => request.post('/inventory/suppliers', data), + update: (id, data) => request.put(`/inventory/suppliers/${id}`, data), + delete: (id) => request.delete(`/inventory/suppliers/${id}`) +} + +// ─── Materials ─────────────────────────────────────────────────────────────── +export const materialApi = { + getList: (params) => request.get('/inventory/materials', { params }), + getDetail: (id) => request.get(`/inventory/materials/${id}`), + create: (data) => request.post('/inventory/materials', data), + update: (id, data) => request.put(`/inventory/materials/${id}`, data), + delete: (id) => request.delete(`/inventory/materials/${id}`) +} + +// ─── Purchase Orders ───────────────────────────────────────────────────────── +export const purchaseOrderApi = { + getList: (params) => request.get('/inventory/purchase-orders', { params }), + getDetail: (id) => request.get(`/inventory/purchase-orders/${id}`), + create: (data) => request.post('/inventory/purchase-orders', data), + update: (id, data) => request.put(`/inventory/purchase-orders/${id}`, data), + delete: (id) => request.delete(`/inventory/purchase-orders/${id}`), + audit: (id, data) => request.put(`/inventory/purchase-orders/${id}/audit`, data) +} + +// ─── Stock Movements ───────────────────────────────────────────────────────── +export const stockMovementApi = { + getList: (params) => request.get('/inventory/stock-movements', { params }), + getDetail: (id) => request.get(`/inventory/stock-movements/${id}`), + create: (data) => request.post('/inventory/stock-movements', data), + confirm: (id) => request.put(`/inventory/stock-movements/${id}/confirm`) +} + +// ─── Inventories ───────────────────────────────────────────────────────────── +export const inventoryApi = { + getList: (params) => request.get('/inventory/inventories', { params }) +} diff --git a/frontend/src/api/nanny.js b/frontend/src/api/nanny.js new file mode 100644 index 0000000..b97a924 --- /dev/null +++ b/frontend/src/api/nanny.js @@ -0,0 +1,25 @@ +import request from '@/utils/request.js' + +// ─── Nannies ───────────────────────────────────────────────────────────────── +export const nannyApi = { + getList: (params) => request.get('/nanny/nannies', { params }), + getDetail: (id) => request.get(`/nanny/nannies/${id}`), + create: (data) => request.post('/nanny/nannies', data), + update: (id, data) => request.put(`/nanny/nannies/${id}`, data), + delete: (id) => request.delete(`/nanny/nannies/${id}`) +} + +// ─── Nanny Orders ──────────────────────────────────────────────────────────── +export const nannyOrderApi = { + getList: (params) => request.get('/nanny/orders', { params }), + getDetail: (id) => request.get(`/nanny/orders/${id}`), + create: (data) => request.post('/nanny/orders', data), + update: (id, data) => request.put(`/nanny/orders/${id}`, data), + delete: (id) => request.delete(`/nanny/orders/${id}`) +} + +// ─── Nanny Schedules ───────────────────────────────────────────────────────── +export const nannyScheduleApi = { + getList: (params) => request.get('/nanny/schedules', { params }), + create: (data) => request.post('/nanny/schedules', data) +} diff --git a/frontend/src/api/service.js b/frontend/src/api/service.js new file mode 100644 index 0000000..df16f85 --- /dev/null +++ b/frontend/src/api/service.js @@ -0,0 +1,38 @@ +import request from '@/utils/request.js' + +// ─── Service Items ─────────────────────────────────────────────────────────── +export const serviceItemApi = { + getList: (params) => request.get('/service/items', { params }), + getDetail: (id) => request.get(`/service/items/${id}`), + create: (data) => request.post('/service/items', data), + update: (id, data) => request.put(`/service/items/${id}`, data), + delete: (id) => request.delete(`/service/items/${id}`) +} + +// ─── Service Packages ──────────────────────────────────────────────────────── +export const servicePackageApi = { + getList: (params) => request.get('/service/packages', { params }), + getDetail: (id) => request.get(`/service/packages/${id}`), + create: (data) => request.post('/service/packages', data), + update: (id, data) => request.put(`/service/packages/${id}`, data), + delete: (id) => request.delete(`/service/packages/${id}`) +} + +// ─── Service Orders ────────────────────────────────────────────────────────── +export const serviceOrderApi = { + getList: (params) => request.get('/service/orders', { params }), + getDetail: (id) => request.get(`/service/orders/${id}`), + create: (data) => request.post('/service/orders', data), + update: (id, data) => request.put(`/service/orders/${id}`, data), + delete: (id) => request.delete(`/service/orders/${id}`), + pay: (id, data) => request.put(`/service/orders/${id}/pay`, data), + complete: (id) => request.put(`/service/orders/${id}/complete`) +} + +// ─── Service Executions ────────────────────────────────────────────────────── +export const serviceExecutionApi = { + getList: (params) => request.get('/service/executions', { params }), + create: (data) => request.post('/service/executions', data), + start: (id) => request.put(`/service/executions/${id}/start`), + finish: (id) => request.put(`/service/executions/${id}/finish`) +} diff --git a/frontend/src/views/inventory/materials/index.vue b/frontend/src/views/inventory/materials/index.vue new file mode 100644 index 0000000..154170a --- /dev/null +++ b/frontend/src/views/inventory/materials/index.vue @@ -0,0 +1,103 @@ + + + diff --git a/frontend/src/views/inventory/purchase-orders/index.vue b/frontend/src/views/inventory/purchase-orders/index.vue new file mode 100644 index 0000000..22373bb --- /dev/null +++ b/frontend/src/views/inventory/purchase-orders/index.vue @@ -0,0 +1,141 @@ + + + diff --git a/frontend/src/views/inventory/stock-movements/index.vue b/frontend/src/views/inventory/stock-movements/index.vue new file mode 100644 index 0000000..ea95fb6 --- /dev/null +++ b/frontend/src/views/inventory/stock-movements/index.vue @@ -0,0 +1,170 @@ + + + diff --git a/frontend/src/views/inventory/suppliers/index.vue b/frontend/src/views/inventory/suppliers/index.vue new file mode 100644 index 0000000..a6276d6 --- /dev/null +++ b/frontend/src/views/inventory/suppliers/index.vue @@ -0,0 +1,105 @@ + + + diff --git a/frontend/src/views/inventory/warehouses/index.vue b/frontend/src/views/inventory/warehouses/index.vue new file mode 100644 index 0000000..2d09204 --- /dev/null +++ b/frontend/src/views/inventory/warehouses/index.vue @@ -0,0 +1,270 @@ + + + diff --git a/frontend/src/views/nanny/nannies/index.vue b/frontend/src/views/nanny/nannies/index.vue new file mode 100644 index 0000000..1f9c555 --- /dev/null +++ b/frontend/src/views/nanny/nannies/index.vue @@ -0,0 +1,105 @@ + + + diff --git a/frontend/src/views/nanny/orders/index.vue b/frontend/src/views/nanny/orders/index.vue new file mode 100644 index 0000000..03ea04d --- /dev/null +++ b/frontend/src/views/nanny/orders/index.vue @@ -0,0 +1,107 @@ + + + diff --git a/frontend/src/views/nanny/schedules/index.vue b/frontend/src/views/nanny/schedules/index.vue new file mode 100644 index 0000000..a1c7690 --- /dev/null +++ b/frontend/src/views/nanny/schedules/index.vue @@ -0,0 +1,80 @@ + + + diff --git a/frontend/src/views/service/items/index.vue b/frontend/src/views/service/items/index.vue new file mode 100644 index 0000000..b83c341 --- /dev/null +++ b/frontend/src/views/service/items/index.vue @@ -0,0 +1,102 @@ + + + diff --git a/frontend/src/views/service/orders/index.vue b/frontend/src/views/service/orders/index.vue new file mode 100644 index 0000000..f9bcc77 --- /dev/null +++ b/frontend/src/views/service/orders/index.vue @@ -0,0 +1,115 @@ + + + diff --git a/frontend/src/views/service/packages/index.vue b/frontend/src/views/service/packages/index.vue new file mode 100644 index 0000000..6b5a15f --- /dev/null +++ b/frontend/src/views/service/packages/index.vue @@ -0,0 +1,100 @@ + + +