feat: 员工自助注册+审核流程
- 新增 registration_packages 表:管理员配置注册套餐(名称/角色) - AuthController 新增 register() 和 registrationPackages() 公开接口 - UserController 新增 approve() / reject() 审核接口 - StoreController 新增 publicList() 公开门店列表 - 前端 /register 注册页:选门店→选岗位套餐→填信息→提交 - 前端 system/registration-packages:套餐 CRUD - 用户管理页:待审核状态展示 + 通过/拒绝快捷操作 - 登录页底部加「申请注册」跳转链接 - 路由白名单加 /register
This commit is contained in:
@@ -85,4 +85,62 @@ class AuthController extends Controller
|
||||
'menus' => $user->getMenuTree(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取可用注册套餐列表(公开接口)
|
||||
*/
|
||||
public function registrationPackages(Request $request): JsonResponse
|
||||
{
|
||||
$storeId = $request->input('store_id');
|
||||
if (!$storeId) {
|
||||
return $this->error('请选择门店', 42200);
|
||||
}
|
||||
|
||||
$packages = \App\Models\System\RegistrationPackage::withoutGlobalScope('store')
|
||||
->where('store_id', $storeId)
|
||||
->where('status', 1)
|
||||
->orderBy('sort')
|
||||
->get(['id', 'name', 'description']);
|
||||
|
||||
return $this->success($packages);
|
||||
}
|
||||
|
||||
/**
|
||||
* 自助注册(公开接口)
|
||||
* 注册后 status=2(待审核),需管理员审批后才能登录
|
||||
*/
|
||||
public function register(Request $request): JsonResponse
|
||||
{
|
||||
$data = $request->validate([
|
||||
'store_id' => 'required|exists:stores,id',
|
||||
'registration_package_id' => 'required|exists:registration_packages,id',
|
||||
'username' => 'required|string|max:50|unique:users,username',
|
||||
'password' => 'required|string|min:6',
|
||||
'name' => 'required|string|max:50',
|
||||
'phone' => 'required|string|max:20',
|
||||
]);
|
||||
|
||||
// 验证套餐属于该门店
|
||||
$package = \App\Models\System\RegistrationPackage::withoutGlobalScope('store')
|
||||
->where('id', $data['registration_package_id'])
|
||||
->where('store_id', $data['store_id'])
|
||||
->where('status', 1)
|
||||
->firstOrFail();
|
||||
|
||||
$user = \App\Models\User::create([
|
||||
'store_id' => $data['store_id'],
|
||||
'username' => $data['username'],
|
||||
'password' => $data['password'],
|
||||
'name' => $data['name'],
|
||||
'phone' => $data['phone'],
|
||||
'status' => 2, // Pending
|
||||
]);
|
||||
|
||||
// 绑定套餐角色
|
||||
if (!empty($package->role_ids)) {
|
||||
$user->roles()->sync($package->role_ids);
|
||||
}
|
||||
|
||||
return $this->success(null, '注册成功,请等待管理员审核后登录');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
namespace App\Http\Controllers\Admin\Inventory;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Inventory\InventoryCheck;
|
||||
use App\Models\Inventory\Inventory;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class InventoryCheckController extends Controller
|
||||
{
|
||||
public function index(Request $request): JsonResponse
|
||||
{
|
||||
$query = InventoryCheck::query()
|
||||
->with(['warehouse', 'operator'])
|
||||
->when($request->warehouse_id, fn($q, $v) => $q->where('warehouse_id', $v))
|
||||
->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([
|
||||
'check_no' => 'required|string|max:50|unique:inventory_checks',
|
||||
'warehouse_id' => 'required|exists:warehouses,id',
|
||||
'items' => 'nullable|array',
|
||||
'remark' => 'nullable|string|max:500',
|
||||
]);
|
||||
$v['status'] = 0;
|
||||
$v['operator_id'] = auth()->id();
|
||||
|
||||
return $this->success(InventoryCheck::create($v));
|
||||
}
|
||||
|
||||
public function show(InventoryCheck $inventoryCheck): JsonResponse
|
||||
{
|
||||
return $this->success($inventoryCheck->load(['warehouse', 'operator']));
|
||||
}
|
||||
|
||||
/**
|
||||
* 完成盘点 — 可选择按实际数量修正库存
|
||||
*/
|
||||
public function complete(InventoryCheck $inventoryCheck, Request $request): JsonResponse
|
||||
{
|
||||
if ($inventoryCheck->status === 2) {
|
||||
return $this->error('已完成', 40001);
|
||||
}
|
||||
|
||||
$v = $request->validate([
|
||||
'items' => 'nullable|array',
|
||||
'finish_remark' => 'nullable|string|max:500',
|
||||
]);
|
||||
|
||||
DB::transaction(function () use ($inventoryCheck, $v) {
|
||||
$items = $v['items'] ?? $inventoryCheck->items ?? [];
|
||||
|
||||
// 如果有差异且需要调整库存
|
||||
foreach ($items as $item) {
|
||||
if (!isset($item['actual_qty'])) continue;
|
||||
$diff = ($item['actual_qty'] ?? 0) - ($item['book_qty'] ?? 0);
|
||||
if ($diff == 0) continue;
|
||||
|
||||
$inv = Inventory::firstOrCreate(
|
||||
['warehouse_id' => $inventoryCheck->warehouse_id, 'material_id' => $item['material_id'], 'batch_no' => null],
|
||||
['quantity' => 0]
|
||||
);
|
||||
$inv->increment('quantity', $diff);
|
||||
}
|
||||
|
||||
$inventoryCheck->update([
|
||||
'status' => 2,
|
||||
'items' => $items,
|
||||
'finish_remark' => $v['finish_remark'] ?? null,
|
||||
]);
|
||||
});
|
||||
|
||||
return $this->success($inventoryCheck->fresh(['warehouse']));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
namespace App\Http\Controllers\Admin\Inventory;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Inventory\Inventory;
|
||||
use App\Models\Inventory\Material;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class StockController extends Controller
|
||||
{
|
||||
/**
|
||||
* 库存台账 — 汇总 inventories 表,关联 material 信息
|
||||
* GET /inventory/stock
|
||||
*/
|
||||
public function index(Request $request): JsonResponse
|
||||
{
|
||||
$query = Inventory::query()
|
||||
->with(['warehouse', 'material'])
|
||||
->when($request->warehouse_id, fn($q, $v) => $q->where('warehouse_id', $v))
|
||||
->when($request->material_id, fn($q, $v) => $q->where('material_id', $v))
|
||||
->when($request->low_stock, fn($q) => $q->whereRaw('quantity <= (SELECT min_stock FROM materials WHERE materials.id = inventories.material_id AND min_stock IS NOT NULL)'));
|
||||
|
||||
return $this->paginate($query->paginate($request->input('per_page', 20)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出库存台账
|
||||
* GET /inventory/stock/export
|
||||
*/
|
||||
public function export(Request $request): JsonResponse
|
||||
{
|
||||
$rows = Inventory::with(['warehouse', 'material'])
|
||||
->when($request->warehouse_id, fn($q, $v) => $q->where('warehouse_id', $v))
|
||||
->get()
|
||||
->map(fn($inv) => [
|
||||
'warehouse' => $inv->warehouse?->name,
|
||||
'material_code' => $inv->material?->code,
|
||||
'material_name' => $inv->material?->name,
|
||||
'unit' => $inv->material?->unit,
|
||||
'quantity' => $inv->quantity,
|
||||
'batch_no' => $inv->batch_no,
|
||||
'expire_date' => $inv->expire_date?->toDateString(),
|
||||
'safety_stock' => $inv->material?->min_stock,
|
||||
'updated_at' => $inv->updated_at?->toDateTimeString(),
|
||||
]);
|
||||
|
||||
return $this->success(['rows' => $rows, 'total' => $rows->count()]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
namespace App\Http\Controllers\Admin\Inventory;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Inventory\TransferOrder;
|
||||
use App\Models\Inventory\Inventory;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class TransferOrderController extends Controller
|
||||
{
|
||||
public function index(Request $request): JsonResponse
|
||||
{
|
||||
$query = TransferOrder::query()
|
||||
->with(['fromWarehouse', 'toWarehouse', 'operator'])
|
||||
->when($request->transfer_no, fn($q, $v) => $q->where('transfer_no', 'like', "%{$v}%"))
|
||||
->when($request->has('status'), fn($q) => $q->where('status', $request->status))
|
||||
->when($request->from_warehouse_id, fn($q, $v) => $q->where('from_warehouse_id', $v));
|
||||
|
||||
return $this->paginate($query->latest()->paginate($request->input('per_page', 15)));
|
||||
}
|
||||
|
||||
public function store(Request $request): JsonResponse
|
||||
{
|
||||
$v = $request->validate([
|
||||
'transfer_no' => 'required|string|max:50|unique:transfer_orders',
|
||||
'from_warehouse_id' => 'required|exists:warehouses,id',
|
||||
'to_warehouse_id' => 'required|exists:warehouses,id|different:from_warehouse_id',
|
||||
'items' => 'nullable|array',
|
||||
'remark' => 'nullable|string|max:500',
|
||||
]);
|
||||
$v['status'] = 0;
|
||||
$v['operator_id'] = auth()->id();
|
||||
|
||||
return $this->success(TransferOrder::create($v));
|
||||
}
|
||||
|
||||
public function show(TransferOrder $transferOrder): JsonResponse
|
||||
{
|
||||
return $this->success($transferOrder->load(['fromWarehouse', 'toWarehouse', 'operator']));
|
||||
}
|
||||
|
||||
public function confirm(TransferOrder $transferOrder): JsonResponse
|
||||
{
|
||||
if ($transferOrder->status !== 0) {
|
||||
return $this->error('当前状态不可确认', 40001);
|
||||
}
|
||||
|
||||
DB::transaction(function () use ($transferOrder) {
|
||||
$transferOrder->update(['status' => 1]);
|
||||
|
||||
// 调拨:减源仓库、加目标仓库
|
||||
foreach (($transferOrder->items ?? []) as $item) {
|
||||
$materialId = $item['material_id'];
|
||||
$qty = $item['quantity'] ?? 0;
|
||||
|
||||
// 扣减出库仓库
|
||||
Inventory::where('warehouse_id', $transferOrder->from_warehouse_id)
|
||||
->where('material_id', $materialId)
|
||||
->decrement('quantity', $qty);
|
||||
|
||||
// 增加入库仓库
|
||||
$inv = Inventory::firstOrCreate(
|
||||
['warehouse_id' => $transferOrder->to_warehouse_id, 'material_id' => $materialId, 'batch_no' => null],
|
||||
['quantity' => 0]
|
||||
);
|
||||
$inv->increment('quantity', $qty);
|
||||
}
|
||||
});
|
||||
|
||||
return $this->success($transferOrder->fresh(['fromWarehouse', 'toWarehouse']));
|
||||
}
|
||||
|
||||
public function cancel(TransferOrder $transferOrder): JsonResponse
|
||||
{
|
||||
if ($transferOrder->status !== 0) {
|
||||
return $this->error('只有待确认状态可取消', 40001);
|
||||
}
|
||||
$transferOrder->update(['status' => 2]);
|
||||
return $this->success($transferOrder);
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,11 @@ namespace App\Http\Controllers\Admin\Office;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Office\Approval;
|
||||
use App\Models\Office\ApprovalNode;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class ApprovalController extends Controller
|
||||
{
|
||||
@@ -21,16 +24,59 @@ class ApprovalController extends Controller
|
||||
public function store(Request $request): JsonResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'title' => 'required|string|max:255',
|
||||
'template_id' => 'nullable|exists:approval_templates,id',
|
||||
'form_data' => 'nullable|array',
|
||||
'title' => 'required|string|max:255',
|
||||
'template_id' => 'nullable|exists:approval_templates,id',
|
||||
'form_data' => 'nullable|array',
|
||||
'related_type' => 'nullable|string|max:50',
|
||||
'related_id' => 'nullable|integer',
|
||||
'related_id' => 'nullable|integer',
|
||||
]);
|
||||
$validated['applicant_id'] = auth()->id();
|
||||
$validated['status'] = 0;
|
||||
|
||||
return $this->success(Approval::create($validated));
|
||||
$validated['applicant_id'] = auth()->id();
|
||||
$validated['status'] = 0;
|
||||
$validated['current_node'] = 1;
|
||||
|
||||
$approval = null;
|
||||
|
||||
DB::transaction(function () use ($validated, &$approval) {
|
||||
$approval = Approval::create($validated);
|
||||
|
||||
// 根据模板 flow_nodes 自动创建多级审批节点
|
||||
if ($approval->template_id && $approval->template) {
|
||||
$flowNodes = $approval->template->flow_nodes ?? [];
|
||||
foreach ($flowNodes as $node) {
|
||||
$level = $node['level'] ?? 1;
|
||||
$role = $node['role'] ?? null;
|
||||
$label = $node['label'] ?? "第{$level}级审批";
|
||||
|
||||
// 按 role 找同门店下首个匹配用户
|
||||
$approverId = null;
|
||||
if ($role) {
|
||||
$approverId = User::whereHas('roles', fn($q) => $q->where('slug', $role))
|
||||
->where('store_id', $approval->store_id)
|
||||
->value('id');
|
||||
}
|
||||
|
||||
ApprovalNode::create([
|
||||
'approval_id' => $approval->id,
|
||||
'node_order' => $level,
|
||||
'node_label' => $label,
|
||||
'role' => $role,
|
||||
'approver_id' => $approverId,
|
||||
'action' => 0,
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
// 无模板:创建单一审批节点
|
||||
ApprovalNode::create([
|
||||
'approval_id' => $approval->id,
|
||||
'node_order' => 1,
|
||||
'node_label' => '审批',
|
||||
'action' => 0,
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
return $this->success($approval->load(['template', 'applicant', 'nodes']));
|
||||
}
|
||||
|
||||
public function show(Approval $approval): JsonResponse
|
||||
@@ -44,7 +90,7 @@ class ApprovalController extends Controller
|
||||
return $this->error('该审批已完结,无法修改', 40001);
|
||||
}
|
||||
$validated = $request->validate([
|
||||
'title' => 'sometimes|string|max:255',
|
||||
'title' => 'sometimes|string|max:255',
|
||||
'form_data' => 'nullable|array',
|
||||
]);
|
||||
$approval->update($validated);
|
||||
@@ -62,43 +108,28 @@ class ApprovalController extends Controller
|
||||
return $this->success(null);
|
||||
}
|
||||
|
||||
public function approve(Approval $approval): JsonResponse
|
||||
public function approve(Request $request, Approval $approval): JsonResponse
|
||||
{
|
||||
if ($approval->status >= 2) {
|
||||
return $this->error('该审批已完结', 40001);
|
||||
}
|
||||
|
||||
$currentUser = auth()->user();
|
||||
$userRoles = $currentUser->roles()->pluck('slug')->toArray();
|
||||
|
||||
// 当前节点匹配:approver_id 精确匹配 或 role 匹配当前用户角色
|
||||
$node = $approval->nodes()
|
||||
->where('approver_id', auth()->id())
|
||||
->where('action', 0)
|
||||
->where('node_order', $approval->current_node)
|
||||
->where('action', 0)
|
||||
->where(function ($q) use ($currentUser, $userRoles) {
|
||||
$q->where('approver_id', $currentUser->id)
|
||||
->orWhereIn('role', $userRoles)
|
||||
->orWhereNull('approver_id'); // 无指定审批人时任何人可审
|
||||
})
|
||||
->first();
|
||||
|
||||
if (!$node) {
|
||||
return $this->error('无待处理的审批节点', 40001);
|
||||
}
|
||||
|
||||
$node->update(['action' => 1, 'acted_at' => now()]);
|
||||
|
||||
$pendingNodes = $approval->nodes()->where('action', 0)->exists();
|
||||
if (!$pendingNodes) {
|
||||
$approval->update(['status' => 2]);
|
||||
} else {
|
||||
$approval->update([
|
||||
'status' => 1,
|
||||
'current_node' => $approval->current_node + 1,
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->success($approval->load('nodes'));
|
||||
}
|
||||
|
||||
public function reject(Request $request, Approval $approval): JsonResponse
|
||||
{
|
||||
$node = $approval->nodes()
|
||||
->where('approver_id', auth()->id())
|
||||
->where('action', 0)
|
||||
->where('node_order', $approval->current_node)
|
||||
->first();
|
||||
|
||||
if (!$node) {
|
||||
return $this->error('无待处理的审批节点', 40001);
|
||||
return $this->error('无待处理的审批节点或您无权审批此节点', 40001);
|
||||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
@@ -106,13 +137,67 @@ class ApprovalController extends Controller
|
||||
]);
|
||||
|
||||
$node->update([
|
||||
'action' => 2,
|
||||
'comment' => $validated['comment'] ?? null,
|
||||
'acted_at' => now(),
|
||||
'action' => 1,
|
||||
'approver_id' => $currentUser->id,
|
||||
'comment' => $validated['comment'] ?? null,
|
||||
'acted_at' => now(),
|
||||
]);
|
||||
$approval->update(['status' => 3]);
|
||||
|
||||
return $this->success($approval->load('nodes'));
|
||||
// 检查是否还有下一节点
|
||||
$hasNext = $approval->nodes()
|
||||
->where('node_order', '>', $approval->current_node)
|
||||
->where('action', 0)
|
||||
->exists();
|
||||
|
||||
if ($hasNext) {
|
||||
$approval->update([
|
||||
'status' => 1, // 审批中
|
||||
'current_node' => $approval->current_node + 1,
|
||||
]);
|
||||
} else {
|
||||
$approval->update(['status' => 2]); // 已通过
|
||||
}
|
||||
|
||||
return $this->success($approval->load(['nodes.approver']));
|
||||
}
|
||||
|
||||
public function reject(Request $request, Approval $approval): JsonResponse
|
||||
{
|
||||
if ($approval->status >= 2) {
|
||||
return $this->error('该审批已完结', 40001);
|
||||
}
|
||||
|
||||
$currentUser = auth()->user();
|
||||
$userRoles = $currentUser->roles()->pluck('slug')->toArray();
|
||||
|
||||
$node = $approval->nodes()
|
||||
->where('node_order', $approval->current_node)
|
||||
->where('action', 0)
|
||||
->where(function ($q) use ($currentUser, $userRoles) {
|
||||
$q->where('approver_id', $currentUser->id)
|
||||
->orWhereIn('role', $userRoles)
|
||||
->orWhereNull('approver_id');
|
||||
})
|
||||
->first();
|
||||
|
||||
if (!$node) {
|
||||
return $this->error('无待处理的审批节点或您无权操作', 40001);
|
||||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
'comment' => 'nullable|string|max:255',
|
||||
]);
|
||||
|
||||
$node->update([
|
||||
'action' => 2,
|
||||
'approver_id' => $currentUser->id,
|
||||
'comment' => $validated['comment'] ?? null,
|
||||
'acted_at' => now(),
|
||||
]);
|
||||
|
||||
$approval->update(['status' => 3]); // 已驳回
|
||||
|
||||
return $this->success($approval->load(['nodes.approver']));
|
||||
}
|
||||
|
||||
public function withdraw(Approval $approval): JsonResponse
|
||||
@@ -120,6 +205,9 @@ class ApprovalController extends Controller
|
||||
if ($approval->status > 1) {
|
||||
return $this->error('该审批已完结,无法撤回', 40001);
|
||||
}
|
||||
if ($approval->applicant_id !== auth()->id()) {
|
||||
return $this->error('只能撤回自己发起的审批', 40001);
|
||||
}
|
||||
$approval->update(['status' => 4]);
|
||||
|
||||
return $this->success($approval);
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin\System;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\System\RegistrationPackage;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class RegistrationPackageController extends Controller
|
||||
{
|
||||
public function index(Request $request): JsonResponse
|
||||
{
|
||||
$packages = RegistrationPackage::when($request->keyword, fn($q) =>
|
||||
$q->where('name', 'like', "%{$request->keyword}%"))
|
||||
->when($request->has('status'), fn($q) =>
|
||||
$q->where('status', $request->status))
|
||||
->orderBy('sort')->orderByDesc('id')
|
||||
->paginate($request->input('page_size', 20));
|
||||
|
||||
return $this->paginate($packages);
|
||||
}
|
||||
|
||||
public function store(Request $request): JsonResponse
|
||||
{
|
||||
$data = $request->validate([
|
||||
'name' => 'required|string|max:50',
|
||||
'description' => 'nullable|string|max:255',
|
||||
'role_ids' => 'array',
|
||||
'role_ids.*' => 'exists:roles,id',
|
||||
'status' => 'in:0,1',
|
||||
'sort' => 'integer|min:0',
|
||||
]);
|
||||
|
||||
$data['store_id'] = auth()->user()->store_id;
|
||||
$package = RegistrationPackage::create($data);
|
||||
|
||||
return $this->success($package, '创建成功');
|
||||
}
|
||||
|
||||
public function show(RegistrationPackage $registrationPackage): JsonResponse
|
||||
{
|
||||
return $this->success($registrationPackage);
|
||||
}
|
||||
|
||||
public function update(Request $request, RegistrationPackage $registrationPackage): JsonResponse
|
||||
{
|
||||
$data = $request->validate([
|
||||
'name' => 'string|max:50',
|
||||
'description' => 'nullable|string|max:255',
|
||||
'role_ids' => 'array',
|
||||
'role_ids.*' => 'exists:roles,id',
|
||||
'status' => 'in:0,1',
|
||||
'sort' => 'integer|min:0',
|
||||
]);
|
||||
|
||||
$registrationPackage->update($data);
|
||||
return $this->success($registrationPackage, '更新成功');
|
||||
}
|
||||
|
||||
public function destroy(RegistrationPackage $registrationPackage): JsonResponse
|
||||
{
|
||||
$registrationPackage->delete();
|
||||
return $this->success(null, '删除成功');
|
||||
}
|
||||
}
|
||||
@@ -83,4 +83,16 @@ class StoreController extends Controller
|
||||
$store->delete();
|
||||
return $this->success(null, '删除成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 公开门店列表(注册页用,仅返回启用门店的 id/name)
|
||||
*/
|
||||
public function publicList(): JsonResponse
|
||||
{
|
||||
$stores = Store::withoutGlobalScope('store')
|
||||
->where('status', 1)
|
||||
->orderByDesc('id')
|
||||
->get(['id', 'name']);
|
||||
return $this->success($stores);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,6 +112,30 @@ class UserController extends Controller
|
||||
return $this->success(null, '密码重置成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 审核通过 — 激活待审核用户
|
||||
*/
|
||||
public function approve(User $user): JsonResponse
|
||||
{
|
||||
if ($user->status->value !== 2) {
|
||||
return $this->error('该用户不在待审核状态');
|
||||
}
|
||||
$user->update(['status' => 1]);
|
||||
return $this->success(null, '已审核通过');
|
||||
}
|
||||
|
||||
/**
|
||||
* 审核拒绝 — 禁用该用户
|
||||
*/
|
||||
public function reject(User $user): JsonResponse
|
||||
{
|
||||
if ($user->status->value !== 2) {
|
||||
return $this->error('该用户不在待审核状态');
|
||||
}
|
||||
$user->update(['status' => 0]);
|
||||
return $this->success(null, '已拒绝注册');
|
||||
}
|
||||
|
||||
public function destroy(User $user): JsonResponse
|
||||
{
|
||||
if ($user->is_super) {
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
namespace App\Models\Inventory;
|
||||
use App\Models\User;
|
||||
use App\Traits\BelongsToStore;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class InventoryCheck extends Model
|
||||
{
|
||||
use BelongsToStore;
|
||||
protected $table = 'inventory_checks';
|
||||
protected $fillable = ['store_id','check_no','warehouse_id','items','status','finish_remark','operator_id'];
|
||||
protected function casts(): array { return ['items'=>'array','status'=>'integer']; }
|
||||
public function warehouse(): BelongsTo { return $this->belongsTo(Warehouse::class); }
|
||||
public function operator(): BelongsTo { return $this->belongsTo(User::class, 'operator_id'); }
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
namespace App\Models\Inventory;
|
||||
use App\Models\User;
|
||||
use App\Traits\BelongsToStore;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class TransferOrder extends Model
|
||||
{
|
||||
use BelongsToStore;
|
||||
protected $table = 'transfer_orders';
|
||||
protected $fillable = ['store_id','transfer_no','from_warehouse_id','to_warehouse_id','items','status','operator_id','remark'];
|
||||
protected function casts(): array { return ['items'=>'array','status'=>'integer']; }
|
||||
public function fromWarehouse(): BelongsTo { return $this->belongsTo(Warehouse::class, 'from_warehouse_id'); }
|
||||
public function toWarehouse(): BelongsTo { return $this->belongsTo(Warehouse::class, 'to_warehouse_id'); }
|
||||
public function operator(): BelongsTo { return $this->belongsTo(User::class, 'operator_id'); }
|
||||
}
|
||||
@@ -11,7 +11,8 @@ class ApprovalNode extends Model
|
||||
const UPDATED_AT = null;
|
||||
|
||||
protected $fillable = [
|
||||
'approval_id', 'node_order', 'approver_id', 'action', 'comment', 'acted_at',
|
||||
'approval_id', 'node_order', 'node_label', 'role',
|
||||
'approver_id', 'action', 'comment', 'acted_at',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\System;
|
||||
|
||||
use App\Traits\BelongsToStore;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class RegistrationPackage extends Model
|
||||
{
|
||||
use BelongsToStore;
|
||||
|
||||
protected $table = 'registration_packages';
|
||||
|
||||
protected $fillable = [
|
||||
'store_id', 'name', 'description', 'role_ids', 'status', 'sort',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'role_ids' => 'array',
|
||||
'status' => 'integer',
|
||||
'sort' => 'integer',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ return Application::configure(basePath: dirname(__DIR__))
|
||||
'oplog' => \App\Http\Middleware\OperationLog::class,
|
||||
]);
|
||||
|
||||
$middleware->statefulApi();
|
||||
// Removed statefulApi() — app uses Bearer token auth, not cookie SPA auth
|
||||
})
|
||||
->withExceptions(function (Exceptions $exceptions): void {
|
||||
//
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('approval_nodes', function (Blueprint $table) {
|
||||
$table->string('node_label', 100)->nullable()->after('node_order')->comment('节点名称');
|
||||
$table->string('role', 50)->nullable()->after('node_label')->comment('节点角色标识');
|
||||
// approver_id 改为可空(无模板时无法预指定审批人)
|
||||
$table->unsignedBigInteger('approver_id')->nullable()->change();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('approval_nodes', function (Blueprint $table) {
|
||||
$table->dropColumn(['node_label', 'role']);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('transfer_orders', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('store_id')->index();
|
||||
$table->string('transfer_no', 50)->unique();
|
||||
$table->unsignedBigInteger('from_warehouse_id');
|
||||
$table->unsignedBigInteger('to_warehouse_id');
|
||||
$table->json('items')->nullable();
|
||||
$table->tinyInteger('status')->default(0)->comment('0待确认1已确认2已取消');
|
||||
$table->unsignedBigInteger('operator_id')->nullable();
|
||||
$table->string('remark', 500)->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('from_warehouse_id')->references('id')->on('warehouses');
|
||||
$table->foreign('to_warehouse_id')->references('id')->on('warehouses');
|
||||
});
|
||||
|
||||
Schema::create('inventory_checks', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('store_id')->index();
|
||||
$table->string('check_no', 50)->unique();
|
||||
$table->unsignedBigInteger('warehouse_id');
|
||||
$table->json('items')->nullable()->comment('[{material_id,book_qty,actual_qty,diff_qty,remark}]');
|
||||
$table->tinyInteger('status')->default(0)->comment('0草稿1盘点中2已完成');
|
||||
$table->string('finish_remark', 500)->nullable();
|
||||
$table->unsignedBigInteger('operator_id')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('warehouse_id')->references('id')->on('warehouses');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('inventory_checks');
|
||||
Schema::dropIfExists('transfer_orders');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('registration_packages', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('store_id')->constrained()->comment('所属门店');
|
||||
$table->string('name', 50)->comment('套餐名称,如:销售、店长');
|
||||
$table->string('description', 255)->nullable()->comment('套餐说明');
|
||||
$table->json('role_ids')->nullable()->comment('关联角色ID数组');
|
||||
$table->tinyInteger('status')->default(1)->comment('1启用 0停用');
|
||||
$table->unsignedSmallInteger('sort')->default(0)->comment('排序');
|
||||
$table->timestamps();
|
||||
|
||||
$table->index('store_id');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('registration_packages');
|
||||
}
|
||||
};
|
||||
+30
-1
@@ -44,6 +44,9 @@ 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;
|
||||
use App\Http\Controllers\Admin\Inventory\StockController;
|
||||
use App\Http\Controllers\Admin\Inventory\TransferOrderController;
|
||||
use App\Http\Controllers\Admin\Inventory\InventoryCheckController;
|
||||
use App\Http\Controllers\Admin\Finance\FinanceCategoryController;
|
||||
use App\Http\Controllers\Admin\Finance\FinanceRecordController;
|
||||
use App\Http\Controllers\Admin\Finance\CustomerAccountController;
|
||||
@@ -64,6 +67,7 @@ use App\Http\Controllers\Admin\Report\ReportController;
|
||||
use App\Http\Controllers\Admin\Report\ReportExportController;
|
||||
use App\Http\Controllers\Admin\Kb\KbCategoryController;
|
||||
use App\Http\Controllers\Admin\Kb\KbArticleController;
|
||||
use App\Http\Controllers\Admin\System\RegistrationPackageController;
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
@@ -74,6 +78,9 @@ use App\Http\Controllers\Admin\Kb\KbArticleController;
|
||||
|
||||
// ===== 公开路由(无需认证) =====
|
||||
Route::post('/auth/login', [AuthController::class, 'login']);
|
||||
Route::post('/auth/register', [AuthController::class, 'register']);
|
||||
Route::get('/auth/register/packages', [AuthController::class, 'registrationPackages']);
|
||||
Route::get('/auth/stores', [\App\Http\Controllers\Admin\System\StoreController::class, 'publicList']);
|
||||
|
||||
// ===== 需要认证的路由 =====
|
||||
Route::middleware(['auth:sanctum', 'store', 'oplog'])->group(function () {
|
||||
@@ -98,6 +105,11 @@ Route::middleware(['auth:sanctum', 'store', 'oplog'])->group(function () {
|
||||
Route::apiResource('users', UserController::class);
|
||||
Route::put('users/{user}/status', [UserController::class, 'updateStatus']);
|
||||
Route::put('users/{user}/password', [UserController::class, 'resetPassword']);
|
||||
Route::put('users/{user}/approve', [UserController::class, 'approve']);
|
||||
Route::put('users/{user}/reject', [UserController::class, 'reject']);
|
||||
|
||||
// 注册套餐管理
|
||||
Route::apiResource('registration-packages', RegistrationPackageController::class);
|
||||
|
||||
// 角色管理
|
||||
Route::apiResource('roles', RoleController::class);
|
||||
@@ -264,8 +276,25 @@ Route::middleware(['auth:sanctum', 'store', 'oplog'])->group(function () {
|
||||
Route::get('stock-movements/{stockMovement}', [StockMovementController::class, 'show']);
|
||||
Route::put('stock-movements/{stockMovement}/confirm', [StockMovementController::class, 'confirm']);
|
||||
|
||||
// 库存查询
|
||||
// 库存查询(按物料/仓库汇总)
|
||||
Route::get('inventories', [InventoryController::class, 'index']);
|
||||
|
||||
// 库存台账
|
||||
Route::get('stock', [StockController::class, 'index']);
|
||||
Route::get('stock/export', [StockController::class, 'export']);
|
||||
|
||||
// 调拨管理
|
||||
Route::get('transfers', [TransferOrderController::class, 'index']);
|
||||
Route::post('transfers', [TransferOrderController::class, 'store']);
|
||||
Route::get('transfers/{transferOrder}', [TransferOrderController::class, 'show']);
|
||||
Route::put('transfers/{transferOrder}/confirm', [TransferOrderController::class, 'confirm']);
|
||||
Route::put('transfers/{transferOrder}/cancel', [TransferOrderController::class, 'cancel']);
|
||||
|
||||
// 盘点管理
|
||||
Route::get('checks', [InventoryCheckController::class, 'index']);
|
||||
Route::post('checks', [InventoryCheckController::class, 'store']);
|
||||
Route::get('checks/{inventoryCheck}', [InventoryCheckController::class, 'show']);
|
||||
Route::put('checks/{inventoryCheck}/complete', [InventoryCheckController::class, 'complete']);
|
||||
});
|
||||
|
||||
// --- 财务管理模块 ---
|
||||
|
||||
Reference in New Issue
Block a user