diff --git a/backend/app/Http/Controllers/Admin/Finance/AccountTransactionController.php b/backend/app/Http/Controllers/Admin/Finance/AccountTransactionController.php new file mode 100644 index 0000000..71e248a --- /dev/null +++ b/backend/app/Http/Controllers/Admin/Finance/AccountTransactionController.php @@ -0,0 +1,21 @@ +with(['account', 'customer']); + $query->when($request->customer_account_id, fn($q, $v) => $q->where('customer_account_id', $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)); + + return $this->paginate($query->latest()->paginate($request->input('per_page', 15))); + } +} diff --git a/backend/app/Http/Controllers/Admin/Finance/CustomerAccountController.php b/backend/app/Http/Controllers/Admin/Finance/CustomerAccountController.php new file mode 100644 index 0000000..818d6d1 --- /dev/null +++ b/backend/app/Http/Controllers/Admin/Finance/CustomerAccountController.php @@ -0,0 +1,59 @@ +with(['customer']); + $query->when($request->customer_id, fn($q, $v) => $q->where('customer_id', $v)); + + return $this->paginate($query->latest()->paginate($request->input('per_page', 15))); + } + + public function show(CustomerAccount $customerAccount): JsonResponse + { + return $this->success($customerAccount->load(['customer', 'transactions'])); + } + + public function recharge(Request $request): JsonResponse + { + $validated = $request->validate([ + 'customer_id' => 'required|exists:customers,id', + 'amount' => 'required|numeric|min:0.01', + 'pay_method' => 'nullable|integer|in:1,2,3,4', + 'description' => 'nullable|string|max:255', + ]); + + return DB::transaction(function () use ($validated) { + $account = CustomerAccount::firstOrCreate( + ['customer_id' => $validated['customer_id'], 'store_id' => auth()->user()->store_id ?? 0], + ['cash_balance' => 0, 'card_balance' => 0, 'points' => 0] + ); + + $account->increment('cash_balance', $validated['amount']); + $account->refresh(); + + AccountTransaction::create([ + 'store_id' => $account->store_id, + 'customer_account_id' => $account->id, + 'customer_id' => $validated['customer_id'], + 'type' => 1, + 'amount' => $validated['amount'], + 'balance_after' => $account->cash_balance, + 'description' => $validated['description'] ?? '账户充值', + 'operator_id' => auth()->id(), + ]); + + return $this->success($account); + }); + } +} diff --git a/backend/app/Http/Controllers/Admin/Finance/FinanceCategoryController.php b/backend/app/Http/Controllers/Admin/Finance/FinanceCategoryController.php new file mode 100644 index 0000000..818e3bf --- /dev/null +++ b/backend/app/Http/Controllers/Admin/Finance/FinanceCategoryController.php @@ -0,0 +1,77 @@ +when($request->type, fn($q, $v) => $q->where('type', $v)); + $query->when($request->has('status'), fn($q) => $q->where('status', $request->status)); + + $categories = $query->orderBy('sort')->get(); + + // 构建树形结构 + $tree = $categories->where('parent_id', 0)->values()->map(function ($item) use ($categories) { + $item->children = $categories->where('parent_id', $item->id)->values(); + return $item; + }); + + return $this->success($tree); + } + + public function store(Request $request): JsonResponse + { + $validated = $request->validate([ + 'name' => 'required|string|max:50', + 'type' => 'required|integer|in:1,2', + 'parent_id' => 'sometimes|integer', + 'sort' => 'sometimes|integer', + 'status' => 'sometimes|integer|in:0,1', + ]); + + if (!empty($validated['parent_id'])) { + $parent = FinanceCategory::find($validated['parent_id']); + $validated['level'] = $parent ? $parent->level + 1 : 1; + } + + return $this->success(FinanceCategory::create($validated)); + } + + public function show(FinanceCategory $financeCategory): JsonResponse + { + return $this->success($financeCategory->load('children')); + } + + public function update(Request $request, FinanceCategory $financeCategory): JsonResponse + { + $validated = $request->validate([ + 'name' => 'sometimes|string|max:50', + 'type' => 'sometimes|integer|in:1,2', + 'parent_id' => 'sometimes|integer', + 'sort' => 'sometimes|integer', + 'status' => 'sometimes|integer|in:0,1', + ]); + + $financeCategory->update($validated); + + return $this->success($financeCategory); + } + + public function destroy(FinanceCategory $financeCategory): JsonResponse + { + if ($financeCategory->children()->count() > 0) { + return $this->error('存在子分类,无法删除', 40001); + } + + $financeCategory->delete(); + + return $this->success(null); + } +} diff --git a/backend/app/Http/Controllers/Admin/Finance/FinanceRecordController.php b/backend/app/Http/Controllers/Admin/Finance/FinanceRecordController.php new file mode 100644 index 0000000..1bd0cae --- /dev/null +++ b/backend/app/Http/Controllers/Admin/Finance/FinanceRecordController.php @@ -0,0 +1,96 @@ +with(['category']); + $query->when($request->type, fn($q, $v) => $q->where('type', $v)); + $query->when($request->category_id, fn($q, $v) => $q->where('category_id', $v)); + $query->when($request->has('audit_status'), fn($q) => $q->where('audit_status', $request->audit_status)); + $query->when($request->start_date, fn($q, $v) => $q->where('record_date', '>=', $v)); + $query->when($request->end_date, fn($q, $v) => $q->where('record_date', '<=', $v)); + + return $this->paginate($query->latest()->paginate($request->input('per_page', 15))); + } + + public function store(Request $request): JsonResponse + { + $validated = $request->validate([ + 'record_no' => 'required|string|max:50|unique:finance_records', + 'category_id' => 'nullable|exists:finance_categories,id', + 'type' => 'required|integer|in:1,2', + 'amount' => 'required|numeric|min:0.01', + 'pay_method' => 'nullable|integer|in:1,2,3,4', + 'description' => 'nullable|string|max:255', + 'record_date' => 'required|date', + ]); + + $validated['operator_id'] = auth()->id(); + $validated['audit_status'] = 0; + + return $this->success(FinanceRecord::create($validated)); + } + + public function show(FinanceRecord $financeRecord): JsonResponse + { + return $this->success($financeRecord->load(['category', 'auditor', 'operator'])); + } + + public function update(Request $request, FinanceRecord $financeRecord): JsonResponse + { + if ($financeRecord->audit_status !== 0) { + return $this->error('只能修改待审核的记录', 40001); + } + + $validated = $request->validate([ + 'category_id' => 'nullable|exists:finance_categories,id', + 'type' => 'sometimes|integer|in:1,2', + 'amount' => 'sometimes|numeric|min:0.01', + 'pay_method' => 'nullable|integer|in:1,2,3,4', + 'description' => 'nullable|string|max:255', + 'record_date' => 'sometimes|date', + ]); + + $financeRecord->update($validated); + + return $this->success($financeRecord); + } + + public function destroy(FinanceRecord $financeRecord): JsonResponse + { + if ($financeRecord->audit_status !== 0) { + return $this->error('只能删除待审核的记录', 40001); + } + + $financeRecord->delete(); + + return $this->success(null); + } + + public function audit(Request $request, FinanceRecord $financeRecord): JsonResponse + { + if ($financeRecord->audit_status !== 0) { + return $this->error('该记录已审核', 40001); + } + + $validated = $request->validate([ + 'status' => 'required|integer|in:1,2', + ]); + + $financeRecord->update([ + 'audit_status' => $validated['status'], + 'audit_user_id' => auth()->id(), + 'audit_at' => now(), + ]); + + return $this->success($financeRecord); + } +} diff --git a/backend/app/Http/Controllers/Admin/Finance/InvoiceController.php b/backend/app/Http/Controllers/Admin/Finance/InvoiceController.php new file mode 100644 index 0000000..b2f46f1 --- /dev/null +++ b/backend/app/Http/Controllers/Admin/Finance/InvoiceController.php @@ -0,0 +1,94 @@ +with(['customer']); + $query->when($request->invoice_no, fn($q, $v) => $q->where('invoice_no', 'like', "%{$v}%")); + $query->when($request->customer_id, fn($q, $v) => $q->where('customer_id', $v)); + $query->when($request->has('status'), fn($q) => $q->where('status', $request->status)); + $query->when($request->type, fn($q, $v) => $q->where('type', $v)); + + return $this->paginate($query->latest()->paginate($request->input('per_page', 15))); + } + + public function store(Request $request): JsonResponse + { + $validated = $request->validate([ + 'invoice_no' => 'required|string|max:50|unique:invoices', + 'customer_id' => 'nullable|exists:customers,id', + 'type' => 'sometimes|integer|in:1,2', + 'amount' => 'required|numeric|min:0', + 'tax_amount' => 'sometimes|numeric|min:0', + 'title' => 'nullable|string|max:255', + 'tax_no' => 'nullable|string|max:50', + 'related_type' => 'nullable|string|max:50', + 'related_id' => 'nullable|integer', + ]); + + $validated['operator_id'] = auth()->id(); + $validated['status'] = 0; + + return $this->success(Invoice::create($validated)); + } + + public function show(Invoice $invoice): JsonResponse + { + return $this->success($invoice->load(['customer', 'operator'])); + } + + public function update(Request $request, Invoice $invoice): JsonResponse + { + if ($invoice->status !== 0) { + return $this->error('只能修改待开票的发票', 40001); + } + + $validated = $request->validate([ + 'customer_id' => 'nullable|exists:customers,id', + 'type' => 'sometimes|integer|in:1,2', + 'amount' => 'sometimes|numeric|min:0', + 'tax_amount' => 'sometimes|numeric|min:0', + 'title' => 'nullable|string|max:255', + 'tax_no' => 'nullable|string|max:50', + 'related_type' => 'nullable|string|max:50', + 'related_id' => 'nullable|integer', + ]); + + $invoice->update($validated); + + return $this->success($invoice); + } + + public function destroy(Invoice $invoice): JsonResponse + { + if ($invoice->status !== 0) { + return $this->error('只能删除待开票的发票', 40001); + } + + $invoice->delete(); + + return $this->success(null); + } + + public function issue(Invoice $invoice): JsonResponse + { + if ($invoice->status !== 0) { + return $this->error('该发票已开具或已红冲', 40001); + } + + $invoice->update([ + 'status' => 1, + 'issued_at' => now(), + ]); + + return $this->success($invoice); + } +} diff --git a/backend/app/Http/Controllers/Admin/Finance/PrepaidCardController.php b/backend/app/Http/Controllers/Admin/Finance/PrepaidCardController.php new file mode 100644 index 0000000..5ad384c --- /dev/null +++ b/backend/app/Http/Controllers/Admin/Finance/PrepaidCardController.php @@ -0,0 +1,64 @@ +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->orderBy('sort')->paginate($request->input('per_page', 15))); + } + + public function store(Request $request): JsonResponse + { + $validated = $request->validate([ + 'name' => 'required|string|max:100', + 'face_value' => 'required|numeric|min:0', + 'sell_price' => 'required|numeric|min:0', + 'discount_rate' => 'sometimes|numeric|min:0|max:1', + 'validity_days' => 'sometimes|integer|min:1', + 'status' => 'sometimes|integer|in:0,1', + 'sort' => 'sometimes|integer', + ]); + + return $this->success(PrepaidCard::create($validated)); + } + + public function show(PrepaidCard $prepaidCard): JsonResponse + { + return $this->success($prepaidCard); + } + + public function update(Request $request, PrepaidCard $prepaidCard): JsonResponse + { + $validated = $request->validate([ + 'name' => 'sometimes|string|max:100', + 'face_value' => 'sometimes|numeric|min:0', + 'sell_price' => 'sometimes|numeric|min:0', + 'discount_rate' => 'sometimes|numeric|min:0|max:1', + 'validity_days' => 'sometimes|integer|min:1', + 'status' => 'sometimes|integer|in:0,1', + 'sort' => 'sometimes|integer', + ]); + + $prepaidCard->update($validated); + + return $this->success($prepaidCard); + } + + public function destroy(PrepaidCard $prepaidCard): JsonResponse + { + $prepaidCard->delete(); + + return $this->success(null); + } +} diff --git a/backend/app/Http/Controllers/Admin/Hr/AttendanceRecordController.php b/backend/app/Http/Controllers/Admin/Hr/AttendanceRecordController.php new file mode 100644 index 0000000..8bc9dec --- /dev/null +++ b/backend/app/Http/Controllers/Admin/Hr/AttendanceRecordController.php @@ -0,0 +1,62 @@ +with('user'); + $query->when($request->user_id, fn($q, $v) => $q->where('user_id', $v)); + $query->when($request->start_date, fn($q, $v) => $q->where('attendance_date', '>=', $v)); + $query->when($request->end_date, fn($q, $v) => $q->where('attendance_date', '<=', $v)); + $query->when($request->has('status'), fn($q) => $q->where('status', $request->status)); + + return $this->paginate($query->latest('attendance_date')->paginate($request->input('per_page', 15))); + } + + public function store(Request $request): JsonResponse + { + $v = $request->validate([ + 'user_id' => 'required|exists:users,id', + 'attendance_date' => 'required|date', + 'clock_in' => 'nullable|date_format:H:i', + 'clock_out' => 'nullable|date_format:H:i', + 'status' => 'nullable|integer|in:1,2,3,4,5', + 'overtime_hours' => 'nullable|numeric|min:0', + 'remark' => 'nullable|string|max:255', + ]); + + return $this->success(AttendanceRecord::create($v)); + } + + public function clockIn(): JsonResponse + { + $record = AttendanceRecord::firstOrCreate( + ['user_id' => auth()->id(), 'attendance_date' => now()->toDateString()], + ['status' => 1] + ); + $record->update(['clock_in' => now()->format('H:i:s')]); + + return $this->success($record); + } + + public function clockOut(): JsonResponse + { + $record = AttendanceRecord::where('user_id', auth()->id()) + ->where('attendance_date', now()->toDateString()) + ->first(); + + if (!$record) { + return $this->error('今日未打卡上班', 40001); + } + $record->update(['clock_out' => now()->format('H:i:s')]); + + return $this->success($record); + } +} diff --git a/backend/app/Http/Controllers/Admin/Hr/EmployeeProfileController.php b/backend/app/Http/Controllers/Admin/Hr/EmployeeProfileController.php new file mode 100644 index 0000000..af6e228 --- /dev/null +++ b/backend/app/Http/Controllers/Admin/Hr/EmployeeProfileController.php @@ -0,0 +1,72 @@ +with('user'); + $query->when($request->user_id, fn($q, $v) => $q->where('user_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([ + 'user_id' => 'required|exists:users,id', + 'employee_no' => 'nullable|string|max:30', + 'hire_date' => 'nullable|date', + 'birth_date' => 'nullable|date', + 'gender' => 'nullable|integer|in:0,1,2', + 'id_card' => 'nullable|string|max:20', + 'education' => 'nullable|string|max:30', + 'emergency_contact' => 'nullable|string|max:50', + 'emergency_phone' => 'nullable|string|max:20', + 'base_salary' => 'nullable|numeric|min:0', + 'position_salary' => 'nullable|numeric|min:0', + 'status' => 'nullable|integer|in:1,2,3', + ]); + + return $this->success(EmployeeProfile::create($v)); + } + + public function show(EmployeeProfile $employeeProfile): JsonResponse + { + return $this->success($employeeProfile->load('user')); + } + + public function update(Request $request, EmployeeProfile $employeeProfile): JsonResponse + { + $v = $request->validate([ + 'employee_no' => 'nullable|string|max:30', + 'hire_date' => 'nullable|date', + 'birth_date' => 'nullable|date', + 'gender' => 'nullable|integer|in:0,1,2', + 'id_card' => 'nullable|string|max:20', + 'education' => 'nullable|string|max:30', + 'emergency_contact' => 'nullable|string|max:50', + 'emergency_phone' => 'nullable|string|max:20', + 'base_salary' => 'nullable|numeric|min:0', + 'position_salary' => 'nullable|numeric|min:0', + 'status' => 'nullable|integer|in:1,2,3', + 'left_date' => 'nullable|date', + ]); + $employeeProfile->update($v); + + return $this->success($employeeProfile); + } + + public function destroy(EmployeeProfile $employeeProfile): JsonResponse + { + $employeeProfile->delete(); + return $this->success(null); + } +} diff --git a/backend/app/Http/Controllers/Admin/Hr/LeaveRequestController.php b/backend/app/Http/Controllers/Admin/Hr/LeaveRequestController.php new file mode 100644 index 0000000..4bb996d --- /dev/null +++ b/backend/app/Http/Controllers/Admin/Hr/LeaveRequestController.php @@ -0,0 +1,85 @@ +with('user'); + $query->when($request->user_id, fn($q, $v) => $q->where('user_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([ + 'user_id' => 'required|exists:users,id', + 'type' => 'required|integer|in:1,2,3,4,5,6', + 'start_date' => 'required|date', + 'end_date' => 'required|date|after_or_equal:start_date', + 'days' => 'required|numeric|min:0.5', + 'reason' => 'nullable|string', + ]); + $v['status'] = 0; + + return $this->success(LeaveRequest::create($v)); + } + + public function show(LeaveRequest $leaveRequest): JsonResponse + { + return $this->success($leaveRequest->load(['user', 'auditor'])); + } + + public function update(Request $request, LeaveRequest $leaveRequest): JsonResponse + { + if ($leaveRequest->status !== 0) { + return $this->error('已审批的申请不可修改', 40001); + } + $v = $request->validate([ + 'type' => 'sometimes|integer|in:1,2,3,4,5,6', + 'start_date' => 'sometimes|date', + 'end_date' => 'sometimes|date|after_or_equal:start_date', + 'days' => 'sometimes|numeric|min:0.5', + 'reason' => 'nullable|string', + ]); + $leaveRequest->update($v); + + return $this->success($leaveRequest); + } + + public function destroy(LeaveRequest $leaveRequest): JsonResponse + { + if ($leaveRequest->status !== 0) { + return $this->error('已审批的申请不可删除', 40001); + } + $leaveRequest->delete(); + + return $this->success(null); + } + + public function audit(Request $request, LeaveRequest $leaveRequest): JsonResponse + { + if ($leaveRequest->status !== 0) { + return $this->error('该申请已审批', 40001); + } + $v = $request->validate([ + 'status' => 'required|integer|in:1,2', + ]); + $leaveRequest->update([ + 'status' => $v['status'], + 'audit_user_id' => auth()->id(), + 'audit_at' => now(), + ]); + + return $this->success($leaveRequest); + } +} diff --git a/backend/app/Http/Controllers/Admin/Hr/SalaryRecordController.php b/backend/app/Http/Controllers/Admin/Hr/SalaryRecordController.php new file mode 100644 index 0000000..a715242 --- /dev/null +++ b/backend/app/Http/Controllers/Admin/Hr/SalaryRecordController.php @@ -0,0 +1,106 @@ +with('user'); + $query->when($request->user_id, fn($q, $v) => $q->where('user_id', $v)); + $query->when($request->year_month, fn($q, $v) => $q->where('year_month', $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([ + 'user_id' => 'required|exists:users,id', + 'year_month' => 'required|string|max:7', + 'base_salary' => 'nullable|numeric|min:0', + 'position_salary' => 'nullable|numeric|min:0', + 'performance' => 'nullable|numeric|min:0', + 'overtime_pay' => 'nullable|numeric|min:0', + 'subsidy' => 'nullable|numeric|min:0', + 'social_insurance' => 'nullable|numeric|min:0', + 'housing_fund' => 'nullable|numeric|min:0', + 'tax' => 'nullable|numeric|min:0', + 'absence_deduction' => 'nullable|numeric|min:0', + 'other_deduction' => 'nullable|numeric|min:0', + 'actual_amount' => 'nullable|numeric|min:0', + ]); + + return $this->success(SalaryRecord::create($v)); + } + + public function show(SalaryRecord $salaryRecord): JsonResponse + { + return $this->success($salaryRecord->load('user')); + } + + public function update(Request $request, SalaryRecord $salaryRecord): JsonResponse + { + if ($salaryRecord->status >= 2) { + return $this->error('已发放的工资记录不可修改', 40001); + } + $v = $request->validate([ + 'base_salary' => 'nullable|numeric|min:0', + 'position_salary' => 'nullable|numeric|min:0', + 'performance' => 'nullable|numeric|min:0', + 'overtime_pay' => 'nullable|numeric|min:0', + 'subsidy' => 'nullable|numeric|min:0', + 'social_insurance' => 'nullable|numeric|min:0', + 'housing_fund' => 'nullable|numeric|min:0', + 'tax' => 'nullable|numeric|min:0', + 'absence_deduction' => 'nullable|numeric|min:0', + 'other_deduction' => 'nullable|numeric|min:0', + 'actual_amount' => 'nullable|numeric|min:0', + ]); + $salaryRecord->update($v); + + return $this->success($salaryRecord); + } + + public function destroy(SalaryRecord $salaryRecord): JsonResponse + { + if ($salaryRecord->status >= 1) { + return $this->error('已确认的工资记录不可删除', 40001); + } + $salaryRecord->delete(); + + return $this->success(null); + } + + public function confirm(SalaryRecord $salaryRecord): JsonResponse + { + if ($salaryRecord->status !== 0) { + return $this->error('仅待确认状态可确认', 40001); + } + $salaryRecord->update([ + 'status' => 1, + 'confirmed_at' => now(), + ]); + + return $this->success($salaryRecord); + } + + public function pay(SalaryRecord $salaryRecord): JsonResponse + { + if ($salaryRecord->status !== 1) { + return $this->error('仅已确认状态可发放', 40001); + } + $salaryRecord->update([ + 'status' => 2, + 'paid_at' => now(), + ]); + + return $this->success($salaryRecord); + } +} diff --git a/backend/app/Http/Controllers/Admin/Hr/ScheduleController.php b/backend/app/Http/Controllers/Admin/Hr/ScheduleController.php new file mode 100644 index 0000000..c0dad32 --- /dev/null +++ b/backend/app/Http/Controllers/Admin/Hr/ScheduleController.php @@ -0,0 +1,39 @@ +with('user'); + $query->when($request->user_id, fn($q, $v) => $q->where('user_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->latest('schedule_date')->paginate($request->input('per_page', 15))); + } + + public function store(Request $request): JsonResponse + { + $v = $request->validate([ + 'user_id' => 'required|exists:users,id', + 'schedule_date' => 'required|date', + 'shift_type' => 'required|integer|in:1,2,3,4,5', + 'start_time' => 'nullable|date_format:H:i', + 'end_time' => 'nullable|date_format:H:i', + ]); + + $schedule = Schedule::updateOrCreate( + ['user_id' => $v['user_id'], 'schedule_date' => $v['schedule_date']], + $v + ); + + return $this->success($schedule); + } +} diff --git a/backend/app/Models/Finance/AccountTransaction.php b/backend/app/Models/Finance/AccountTransaction.php new file mode 100644 index 0000000..4766a7b --- /dev/null +++ b/backend/app/Models/Finance/AccountTransaction.php @@ -0,0 +1,37 @@ + 'integer', + 'amount' => 'decimal:2', + 'balance_after' => 'decimal:2', + ]; + } + + public function account(): BelongsTo + { + return $this->belongsTo(CustomerAccount::class, 'customer_account_id'); + } + + public function customer(): BelongsTo + { + return $this->belongsTo(Customer::class); + } +} diff --git a/backend/app/Models/Finance/CustomerAccount.php b/backend/app/Models/Finance/CustomerAccount.php new file mode 100644 index 0000000..9d0aa03 --- /dev/null +++ b/backend/app/Models/Finance/CustomerAccount.php @@ -0,0 +1,37 @@ + 'decimal:2', + 'card_balance' => 'decimal:2', + 'points' => 'integer', + ]; + } + + public function customer(): BelongsTo + { + return $this->belongsTo(Customer::class); + } + + public function transactions(): HasMany + { + return $this->hasMany(AccountTransaction::class, 'customer_account_id'); + } +} diff --git a/backend/app/Models/Finance/FinanceCategory.php b/backend/app/Models/Finance/FinanceCategory.php new file mode 100644 index 0000000..66afc96 --- /dev/null +++ b/backend/app/Models/Finance/FinanceCategory.php @@ -0,0 +1,37 @@ + 'integer', + 'level' => 'integer', + 'sort' => 'integer', + 'status' => 'integer', + ]; + } + + public function children(): HasMany + { + return $this->hasMany(self::class, 'parent_id'); + } + + public function parent(): BelongsTo + { + return $this->belongsTo(self::class, 'parent_id'); + } +} diff --git a/backend/app/Models/Finance/FinanceRecord.php b/backend/app/Models/Finance/FinanceRecord.php new file mode 100644 index 0000000..e78ff8a --- /dev/null +++ b/backend/app/Models/Finance/FinanceRecord.php @@ -0,0 +1,47 @@ + 'integer', + 'pay_method' => 'integer', + 'audit_status' => 'integer', + 'amount' => 'decimal:2', + 'audit_at' => 'datetime', + 'record_date' => 'date', + ]; + } + + public function category(): BelongsTo + { + return $this->belongsTo(FinanceCategory::class, 'category_id'); + } + + public function auditor(): BelongsTo + { + return $this->belongsTo(User::class, 'audit_user_id'); + } + + public function operator(): BelongsTo + { + return $this->belongsTo(User::class, 'operator_id'); + } +} diff --git a/backend/app/Models/Finance/Invoice.php b/backend/app/Models/Finance/Invoice.php new file mode 100644 index 0000000..ea8579d --- /dev/null +++ b/backend/app/Models/Finance/Invoice.php @@ -0,0 +1,41 @@ + 'integer', + 'status' => 'integer', + 'amount' => 'decimal:2', + 'tax_amount' => 'decimal:2', + 'issued_at' => 'datetime', + ]; + } + + public function customer(): BelongsTo + { + return $this->belongsTo(Customer::class); + } + + public function operator(): BelongsTo + { + return $this->belongsTo(User::class, 'operator_id'); + } +} diff --git a/backend/app/Models/Finance/PrepaidCard.php b/backend/app/Models/Finance/PrepaidCard.php new file mode 100644 index 0000000..c2eec26 --- /dev/null +++ b/backend/app/Models/Finance/PrepaidCard.php @@ -0,0 +1,28 @@ + 'decimal:2', + 'sell_price' => 'decimal:2', + 'discount_rate' => 'decimal:2', + 'validity_days' => 'integer', + 'status' => 'integer', + 'sort' => 'integer', + ]; + } +} diff --git a/backend/app/Models/Hr/AttendanceRecord.php b/backend/app/Models/Hr/AttendanceRecord.php new file mode 100644 index 0000000..c8a34cb --- /dev/null +++ b/backend/app/Models/Hr/AttendanceRecord.php @@ -0,0 +1,32 @@ + 'integer', + 'overtime_hours' => 'decimal:1', + 'attendance_date' => 'date', + ]; + } + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } +} diff --git a/backend/app/Models/Hr/EmployeeProfile.php b/backend/app/Models/Hr/EmployeeProfile.php new file mode 100644 index 0000000..70e8f1b --- /dev/null +++ b/backend/app/Models/Hr/EmployeeProfile.php @@ -0,0 +1,37 @@ + 'date', + 'birth_date' => 'date', + 'left_date' => 'date', + 'gender' => 'integer', + 'status' => 'integer', + 'base_salary' => 'decimal:2', + 'position_salary' => 'decimal:2', + ]; + } + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } +} diff --git a/backend/app/Models/Hr/LeaveRequest.php b/backend/app/Models/Hr/LeaveRequest.php new file mode 100644 index 0000000..4eb75eb --- /dev/null +++ b/backend/app/Models/Hr/LeaveRequest.php @@ -0,0 +1,40 @@ + 'integer', + 'status' => 'integer', + 'days' => 'decimal:1', + 'start_date' => 'date', + 'end_date' => 'date', + 'audit_at' => 'datetime', + ]; + } + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } + + public function auditor(): BelongsTo + { + return $this->belongsTo(User::class, 'audit_user_id'); + } +} diff --git a/backend/app/Models/Hr/SalaryRecord.php b/backend/app/Models/Hr/SalaryRecord.php new file mode 100644 index 0000000..e762b4b --- /dev/null +++ b/backend/app/Models/Hr/SalaryRecord.php @@ -0,0 +1,46 @@ + 'decimal:2', + 'position_salary' => 'decimal:2', + 'performance' => 'decimal:2', + 'overtime_pay' => 'decimal:2', + 'subsidy' => 'decimal:2', + 'social_insurance' => 'decimal:2', + 'housing_fund' => 'decimal:2', + 'tax' => 'decimal:2', + 'absence_deduction' => 'decimal:2', + 'other_deduction' => 'decimal:2', + 'actual_amount' => 'decimal:2', + 'status' => 'integer', + 'confirmed_at' => 'datetime', + 'paid_at' => 'datetime', + ]; + } + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } +} diff --git a/backend/app/Models/Hr/Schedule.php b/backend/app/Models/Hr/Schedule.php new file mode 100644 index 0000000..a79faf7 --- /dev/null +++ b/backend/app/Models/Hr/Schedule.php @@ -0,0 +1,33 @@ + 'integer', + 'schedule_date' => 'date', + ]; + } + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } +} diff --git a/backend/database/migrations/2024_01_04_000001_create_finance_tables.php b/backend/database/migrations/2024_01_04_000001_create_finance_tables.php new file mode 100644 index 0000000..1bf7084 --- /dev/null +++ b/backend/database/migrations/2024_01_04_000001_create_finance_tables.php @@ -0,0 +1,118 @@ +id(); + $table->unsignedBigInteger('store_id')->index(); + $table->unsignedBigInteger('parent_id')->default(0)->index(); + $table->string('name', 50); + $table->tinyInteger('type')->default(1)->comment('1=收入 2=支出'); + $table->tinyInteger('level')->default(1); + $table->integer('sort')->default(0); + $table->tinyInteger('status')->default(1); + $table->timestamps(); + }); + + // 收支记录 + Schema::create('finance_records', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('store_id')->index(); + $table->string('record_no', 50)->unique(); + $table->unsignedBigInteger('category_id')->nullable(); + $table->tinyInteger('type')->comment('1=收入 2=支出'); + $table->decimal('amount', 12, 2); + $table->tinyInteger('pay_method')->nullable()->comment('1=现金 2=微信 3=支付宝 4=银行'); + $table->string('related_type', 50)->nullable()->comment('关联类型'); + $table->unsignedBigInteger('related_id')->nullable(); + $table->string('description')->nullable(); + $table->tinyInteger('audit_status')->default(0)->comment('0=待审核 1=已通过 2=已驳回'); + $table->unsignedBigInteger('audit_user_id')->nullable(); + $table->timestamp('audit_at')->nullable(); + $table->unsignedBigInteger('operator_id')->nullable(); + $table->date('record_date'); + $table->timestamps(); + $table->index(['store_id', 'type']); + $table->index('record_date'); + }); + + // 客户账户 + Schema::create('customer_accounts', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('store_id')->index(); + $table->unsignedBigInteger('customer_id')->index(); + $table->decimal('cash_balance', 12, 2)->default(0); + $table->decimal('card_balance', 12, 2)->default(0); + $table->integer('points')->default(0); + $table->timestamps(); + $table->unique(['store_id', 'customer_id']); + }); + + // 账户流水 + Schema::create('account_transactions', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('store_id')->index(); + $table->unsignedBigInteger('customer_account_id')->index(); + $table->unsignedBigInteger('customer_id')->index(); + $table->tinyInteger('type')->comment('1=充值 2=消费 3=退款 4=积分变动'); + $table->decimal('amount', 12, 2); + $table->decimal('balance_after', 12, 2); + $table->string('related_type', 50)->nullable(); + $table->unsignedBigInteger('related_id')->nullable(); + $table->string('description')->nullable(); + $table->unsignedBigInteger('operator_id')->nullable(); + $table->timestamp('created_at')->nullable(); + }); + + // 储值卡种 + Schema::create('prepaid_cards', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('store_id')->index(); + $table->string('name', 100); + $table->decimal('face_value', 10, 2); + $table->decimal('sell_price', 10, 2); + $table->decimal('discount_rate', 5, 2)->default(1.00); + $table->integer('validity_days')->default(365); + $table->tinyInteger('status')->default(1); + $table->integer('sort')->default(0); + $table->timestamps(); + }); + + // 发票 + Schema::create('invoices', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('store_id')->index(); + $table->string('invoice_no', 50)->unique(); + $table->unsignedBigInteger('customer_id')->nullable(); + $table->tinyInteger('type')->default(1)->comment('1=电子 2=纸质'); + $table->decimal('amount', 12, 2); + $table->decimal('tax_amount', 10, 2)->default(0); + $table->string('title')->nullable(); + $table->string('tax_no', 50)->nullable(); + $table->string('related_type', 50)->nullable(); + $table->unsignedBigInteger('related_id')->nullable(); + $table->tinyInteger('status')->default(0)->comment('0=待开 1=已开 2=已红冲'); + $table->unsignedBigInteger('operator_id')->nullable(); + $table->timestamp('issued_at')->nullable(); + $table->timestamps(); + $table->index(['store_id', 'status']); + }); + } + + public function down(): void + { + Schema::dropIfExists('invoices'); + Schema::dropIfExists('prepaid_cards'); + Schema::dropIfExists('account_transactions'); + Schema::dropIfExists('customer_accounts'); + Schema::dropIfExists('finance_records'); + Schema::dropIfExists('finance_categories'); + } +}; diff --git a/backend/database/migrations/2024_01_04_000002_create_hr_tables.php b/backend/database/migrations/2024_01_04_000002_create_hr_tables.php new file mode 100644 index 0000000..4b3ff2d --- /dev/null +++ b/backend/database/migrations/2024_01_04_000002_create_hr_tables.php @@ -0,0 +1,108 @@ +id(); + $table->unsignedBigInteger('store_id')->index(); + $table->unsignedBigInteger('user_id')->index(); + $table->string('employee_no', 30)->nullable(); + $table->date('hire_date')->nullable(); + $table->date('birth_date')->nullable(); + $table->tinyInteger('gender')->default(0)->comment('0=未知 1=男 2=女'); + $table->string('id_card', 20)->nullable(); + $table->string('education', 30)->nullable(); + $table->string('emergency_contact', 50)->nullable(); + $table->string('emergency_phone', 20)->nullable(); + $table->decimal('base_salary', 10, 2)->default(0); + $table->decimal('position_salary', 10, 2)->default(0); + $table->tinyInteger('status')->default(1)->comment('1=在职 2=试用 3=离职'); + $table->date('left_date')->nullable(); + $table->timestamps(); + }); + + // 排班 + Schema::create('schedules', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('store_id')->index(); + $table->unsignedBigInteger('user_id')->index(); + $table->date('schedule_date'); + $table->tinyInteger('shift_type')->default(1)->comment('1=早班 2=中班 3=晚班 4=夜班 5=休息'); + $table->time('start_time')->nullable(); + $table->time('end_time')->nullable(); + $table->timestamp('created_at')->nullable(); + $table->index(['store_id', 'user_id', 'schedule_date']); + }); + + // 考勤记录 + Schema::create('attendance_records', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('store_id')->index(); + $table->unsignedBigInteger('user_id')->index(); + $table->date('attendance_date'); + $table->time('clock_in')->nullable(); + $table->time('clock_out')->nullable(); + $table->tinyInteger('status')->default(1)->comment('1=正常 2=迟到 3=早退 4=旷工 5=请假'); + $table->decimal('overtime_hours', 5, 1)->default(0); + $table->string('remark')->nullable(); + $table->timestamps(); + $table->index(['store_id', 'user_id', 'attendance_date']); + }); + + // 请假申请 + Schema::create('leave_requests', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('store_id')->index(); + $table->unsignedBigInteger('user_id')->index(); + $table->tinyInteger('type')->comment('1=年假 2=事假 3=病假 4=调休 5=产假 6=婚假'); + $table->date('start_date'); + $table->date('end_date'); + $table->decimal('days', 4, 1); + $table->text('reason')->nullable(); + $table->tinyInteger('status')->default(0)->comment('0=待审批 1=已批准 2=已拒绝'); + $table->unsignedBigInteger('audit_user_id')->nullable(); + $table->timestamp('audit_at')->nullable(); + $table->timestamps(); + }); + + // 工资记录 + Schema::create('salary_records', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('store_id')->index(); + $table->unsignedBigInteger('user_id')->index(); + $table->string('year_month', 7)->comment('YYYY-MM'); + $table->decimal('base_salary', 10, 2)->default(0); + $table->decimal('position_salary', 10, 2)->default(0); + $table->decimal('performance', 10, 2)->default(0); + $table->decimal('overtime_pay', 10, 2)->default(0); + $table->decimal('subsidy', 10, 2)->default(0); + $table->decimal('social_insurance', 10, 2)->default(0); + $table->decimal('housing_fund', 10, 2)->default(0); + $table->decimal('tax', 10, 2)->default(0); + $table->decimal('absence_deduction', 10, 2)->default(0); + $table->decimal('other_deduction', 10, 2)->default(0); + $table->decimal('actual_amount', 10, 2)->default(0); + $table->tinyInteger('status')->default(0)->comment('0=待确认 1=已确认 2=已发放'); + $table->timestamp('confirmed_at')->nullable(); + $table->timestamp('paid_at')->nullable(); + $table->timestamps(); + $table->unique(['store_id', 'user_id', 'year_month']); + }); + } + + public function down(): void + { + Schema::dropIfExists('salary_records'); + Schema::dropIfExists('leave_requests'); + Schema::dropIfExists('attendance_records'); + Schema::dropIfExists('schedules'); + Schema::dropIfExists('employee_profiles'); + } +}; diff --git a/backend/routes/api.php b/backend/routes/api.php index 05e66b1..3fa0096 100644 --- a/backend/routes/api.php +++ b/backend/routes/api.php @@ -44,6 +44,17 @@ 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\Finance\FinanceCategoryController; +use App\Http\Controllers\Admin\Finance\FinanceRecordController; +use App\Http\Controllers\Admin\Finance\CustomerAccountController; +use App\Http\Controllers\Admin\Finance\AccountTransactionController; +use App\Http\Controllers\Admin\Finance\PrepaidCardController; +use App\Http\Controllers\Admin\Finance\InvoiceController; +use App\Http\Controllers\Admin\Hr\EmployeeProfileController; +use App\Http\Controllers\Admin\Hr\ScheduleController; +use App\Http\Controllers\Admin\Hr\AttendanceRecordController; +use App\Http\Controllers\Admin\Hr\LeaveRequestController; +use App\Http\Controllers\Admin\Hr\SalaryRecordController; /* |-------------------------------------------------------------------------- @@ -247,4 +258,54 @@ Route::middleware(['auth:sanctum', 'store', 'oplog'])->group(function () { // 库存查询 Route::get('inventories', [InventoryController::class, 'index']); }); + + // --- 财务管理模块 --- + Route::prefix('finance')->group(function () { + // 收支分类 + Route::apiResource('categories', FinanceCategoryController::class); + + // 收支记录 + Route::apiResource('records', FinanceRecordController::class); + Route::put('records/{financeRecord}/audit', [FinanceRecordController::class, 'audit']); + + // 客户账户 + Route::get('accounts', [CustomerAccountController::class, 'index']); + Route::get('accounts/{customerAccount}', [CustomerAccountController::class, 'show']); + Route::post('accounts/recharge', [CustomerAccountController::class, 'recharge']); + + // 账户流水 + Route::get('transactions', [AccountTransactionController::class, 'index']); + + // 储值卡 + Route::apiResource('prepaid-cards', PrepaidCardController::class); + + // 发票管理 + Route::apiResource('invoices', InvoiceController::class); + Route::put('invoices/{invoice}/issue', [InvoiceController::class, 'issue']); + }); + + // --- 人事薪资模块 --- + Route::prefix('hr')->group(function () { + // 员工档案 + Route::apiResource('profiles', EmployeeProfileController::class); + + // 排班管理 + Route::get('schedules', [ScheduleController::class, 'index']); + Route::post('schedules', [ScheduleController::class, 'store']); + + // 考勤管理 + Route::get('attendances', [AttendanceRecordController::class, 'index']); + Route::post('attendances', [AttendanceRecordController::class, 'store']); + Route::post('attendances/clock-in', [AttendanceRecordController::class, 'clockIn']); + Route::post('attendances/clock-out', [AttendanceRecordController::class, 'clockOut']); + + // 请假管理 + Route::apiResource('leaves', LeaveRequestController::class); + Route::put('leaves/{leaveRequest}/audit', [LeaveRequestController::class, 'audit']); + + // 工资管理 + Route::apiResource('salaries', SalaryRecordController::class); + Route::put('salaries/{salaryRecord}/confirm', [SalaryRecordController::class, 'confirm']); + Route::put('salaries/{salaryRecord}/pay', [SalaryRecordController::class, 'pay']); + }); }); diff --git a/frontend/src/api/finance.js b/frontend/src/api/finance.js new file mode 100644 index 0000000..2092551 --- /dev/null +++ b/frontend/src/api/finance.js @@ -0,0 +1,50 @@ +import request from '@/utils/request.js' + +// ─── Finance Categories ────────────────────────────────────────────────────── +export const financeCategoryApi = { + getList: (params) => request.get('/finance/categories', { params }), + getDetail: (id) => request.get(`/finance/categories/${id}`), + create: (data) => request.post('/finance/categories', data), + update: (id, data) => request.put(`/finance/categories/${id}`, data), + delete: (id) => request.delete(`/finance/categories/${id}`) +} + +// ─── Finance Records ───────────────────────────────────────────────────────── +export const financeRecordApi = { + getList: (params) => request.get('/finance/records', { params }), + getDetail: (id) => request.get(`/finance/records/${id}`), + create: (data) => request.post('/finance/records', data), + update: (id, data) => request.put(`/finance/records/${id}`, data), + delete: (id) => request.delete(`/finance/records/${id}`), + audit: (id, data) => request.put(`/finance/records/${id}/audit`, data) +} + +// ─── Customer Accounts ─────────────────────────────────────────────────────── +export const customerAccountApi = { + getList: (params) => request.get('/finance/accounts', { params }), + recharge: (data) => request.post('/finance/accounts/recharge', data) +} + +// ─── Account Transactions ──────────────────────────────────────────────────── +export const accountTransactionApi = { + getList: (params) => request.get('/finance/transactions', { params }) +} + +// ─── Prepaid Cards ─────────────────────────────────────────────────────────── +export const prepaidCardApi = { + getList: (params) => request.get('/finance/prepaid-cards', { params }), + getDetail: (id) => request.get(`/finance/prepaid-cards/${id}`), + create: (data) => request.post('/finance/prepaid-cards', data), + update: (id, data) => request.put(`/finance/prepaid-cards/${id}`, data), + delete: (id) => request.delete(`/finance/prepaid-cards/${id}`) +} + +// ─── Invoices ──────────────────────────────────────────────────────────────── +export const invoiceApi = { + getList: (params) => request.get('/finance/invoices', { params }), + getDetail: (id) => request.get(`/finance/invoices/${id}`), + create: (data) => request.post('/finance/invoices', data), + update: (id, data) => request.put(`/finance/invoices/${id}`, data), + delete: (id) => request.delete(`/finance/invoices/${id}`), + issue: (id) => request.put(`/finance/invoices/${id}/issue`) +} diff --git a/frontend/src/api/hr.js b/frontend/src/api/hr.js new file mode 100644 index 0000000..2bdca57 --- /dev/null +++ b/frontend/src/api/hr.js @@ -0,0 +1,45 @@ +import request from '@/utils/request.js' + +// ─── Employee Profiles ─────────────────────────────────────────────────────── +export const employeeProfileApi = { + getList: (params) => request.get('/hr/profiles', { params }), + getDetail: (id) => request.get(`/hr/profiles/${id}`), + create: (data) => request.post('/hr/profiles', data), + update: (id, data) => request.put(`/hr/profiles/${id}`, data), + delete: (id) => request.delete(`/hr/profiles/${id}`) +} + +// ─── Schedules ─────────────────────────────────────────────────────────────── +export const scheduleApi = { + getList: (params) => request.get('/hr/schedules', { params }), + create: (data) => request.post('/hr/schedules', data) +} + +// ─── Attendance Records ────────────────────────────────────────────────────── +export const attendanceApi = { + getList: (params) => request.get('/hr/attendances', { params }), + create: (data) => request.post('/hr/attendances', data), + clockIn: () => request.post('/hr/attendances/clock-in'), + clockOut: () => request.post('/hr/attendances/clock-out') +} + +// ─── Leave Requests ────────────────────────────────────────────────────────── +export const leaveRequestApi = { + getList: (params) => request.get('/hr/leaves', { params }), + getDetail: (id) => request.get(`/hr/leaves/${id}`), + create: (data) => request.post('/hr/leaves', data), + update: (id, data) => request.put(`/hr/leaves/${id}`, data), + delete: (id) => request.delete(`/hr/leaves/${id}`), + audit: (id, data) => request.put(`/hr/leaves/${id}/audit`, data) +} + +// ─── Salary Records ────────────────────────────────────────────────────────── +export const salaryRecordApi = { + getList: (params) => request.get('/hr/salaries', { params }), + getDetail: (id) => request.get(`/hr/salaries/${id}`), + create: (data) => request.post('/hr/salaries', data), + update: (id, data) => request.put(`/hr/salaries/${id}`, data), + delete: (id) => request.delete(`/hr/salaries/${id}`), + confirm: (id) => request.put(`/hr/salaries/${id}/confirm`), + pay: (id) => request.put(`/hr/salaries/${id}/pay`) +} diff --git a/frontend/src/views/finance/accounts/index.vue b/frontend/src/views/finance/accounts/index.vue new file mode 100644 index 0000000..0f64211 --- /dev/null +++ b/frontend/src/views/finance/accounts/index.vue @@ -0,0 +1,162 @@ + + + diff --git a/frontend/src/views/finance/categories/index.vue b/frontend/src/views/finance/categories/index.vue new file mode 100644 index 0000000..6438185 --- /dev/null +++ b/frontend/src/views/finance/categories/index.vue @@ -0,0 +1,143 @@ + + + diff --git a/frontend/src/views/finance/invoices/index.vue b/frontend/src/views/finance/invoices/index.vue new file mode 100644 index 0000000..ab09341 --- /dev/null +++ b/frontend/src/views/finance/invoices/index.vue @@ -0,0 +1,118 @@ + + + diff --git a/frontend/src/views/finance/prepaid-cards/index.vue b/frontend/src/views/finance/prepaid-cards/index.vue new file mode 100644 index 0000000..9651c62 --- /dev/null +++ b/frontend/src/views/finance/prepaid-cards/index.vue @@ -0,0 +1,104 @@ + + + diff --git a/frontend/src/views/finance/records/index.vue b/frontend/src/views/finance/records/index.vue new file mode 100644 index 0000000..e1f93c6 --- /dev/null +++ b/frontend/src/views/finance/records/index.vue @@ -0,0 +1,220 @@ + + + diff --git a/frontend/src/views/hr/attendances/index.vue b/frontend/src/views/hr/attendances/index.vue new file mode 100644 index 0000000..c031904 --- /dev/null +++ b/frontend/src/views/hr/attendances/index.vue @@ -0,0 +1,141 @@ + + + diff --git a/frontend/src/views/hr/leaves/index.vue b/frontend/src/views/hr/leaves/index.vue new file mode 100644 index 0000000..379b258 --- /dev/null +++ b/frontend/src/views/hr/leaves/index.vue @@ -0,0 +1,160 @@ + + + diff --git a/frontend/src/views/hr/profiles/index.vue b/frontend/src/views/hr/profiles/index.vue new file mode 100644 index 0000000..95d2e67 --- /dev/null +++ b/frontend/src/views/hr/profiles/index.vue @@ -0,0 +1,166 @@ + + + diff --git a/frontend/src/views/hr/salaries/index.vue b/frontend/src/views/hr/salaries/index.vue new file mode 100644 index 0000000..1150a26 --- /dev/null +++ b/frontend/src/views/hr/salaries/index.vue @@ -0,0 +1,274 @@ + + + diff --git a/frontend/src/views/hr/schedules/index.vue b/frontend/src/views/hr/schedules/index.vue new file mode 100644 index 0000000..39e7292 --- /dev/null +++ b/frontend/src/views/hr/schedules/index.vue @@ -0,0 +1,97 @@ + + +