- TenantSetting CRUD API 추가 - Calendar, Entertainment, VAT 서비스 개선 - 5130 BOM 계산 로직 수정 - quote_items에 item_type 컬럼 추가 - tenant_settings 테이블 마이그레이션 - Swagger 문서 업데이트
263 lines
9.3 KiB
PHP
263 lines
9.3 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\Construction\Contract;
|
|
use App\Models\Production\WorkOrder;
|
|
use App\Models\Tenants\Leave;
|
|
use App\Models\Tenants\Schedule;
|
|
use Illuminate\Support\Collection;
|
|
|
|
/**
|
|
* CEO 대시보드 캘린더 서비스
|
|
*
|
|
* 각 카테고리별 일정을 집계하여 캘린더 데이터 제공
|
|
* - 작업지시(WorkOrder): 생산 일정
|
|
* - 계약(Contract): 시공 일정
|
|
* - 휴가(Leave): 직원 휴가 일정
|
|
* - 일정(Schedule): 본사 공통 일정 + 테넌트 일정 (세금 신고, 공휴일 등)
|
|
*/
|
|
class CalendarService extends Service
|
|
{
|
|
/**
|
|
* 캘린더 일정 조회
|
|
*
|
|
* @param string $startDate 조회 시작일 (Y-m-d)
|
|
* @param string $endDate 조회 종료일 (Y-m-d)
|
|
* @param string|null $type 일정 타입 필터 (schedule|order|construction|other|null=전체)
|
|
* @param string|null $departmentFilter 부서 필터 (all|department|personal)
|
|
*/
|
|
public function getSchedules(
|
|
string $startDate,
|
|
string $endDate,
|
|
?string $type = null,
|
|
?string $departmentFilter = 'all'
|
|
): array {
|
|
$tenantId = $this->tenantId();
|
|
$userId = $this->apiUserId();
|
|
|
|
$schedules = collect();
|
|
|
|
// 타입 필터에 따라 데이터 수집
|
|
if ($type === null || $type === 'order') {
|
|
$schedules = $schedules->merge(
|
|
$this->getWorkOrderSchedules($tenantId, $startDate, $endDate, $departmentFilter, $userId)
|
|
);
|
|
}
|
|
|
|
if ($type === null || $type === 'construction') {
|
|
$schedules = $schedules->merge(
|
|
$this->getContractSchedules($tenantId, $startDate, $endDate, $departmentFilter, $userId)
|
|
);
|
|
}
|
|
|
|
if ($type === null || $type === 'schedule') {
|
|
$schedules = $schedules->merge(
|
|
$this->getLeaveSchedules($tenantId, $startDate, $endDate, $departmentFilter, $userId)
|
|
);
|
|
}
|
|
|
|
// 범용 일정 (본사 공통 + 테넌트 일정): 항상 포함 또는 'other' 필터 시
|
|
if ($type === null || $type === 'other') {
|
|
$schedules = $schedules->merge(
|
|
$this->getGeneralSchedules($tenantId, $startDate, $endDate)
|
|
);
|
|
}
|
|
|
|
// startDate 기준 정렬
|
|
$sortedSchedules = $schedules
|
|
->sortBy('startDate')
|
|
->values()
|
|
->toArray();
|
|
|
|
return [
|
|
'items' => $sortedSchedules,
|
|
'total_count' => count($sortedSchedules),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* 작업지시(발주) 일정 조회
|
|
*/
|
|
private function getWorkOrderSchedules(
|
|
int $tenantId,
|
|
string $startDate,
|
|
string $endDate,
|
|
string $departmentFilter,
|
|
int $userId
|
|
): Collection {
|
|
$query = WorkOrder::query()
|
|
->where('tenant_id', $tenantId)
|
|
->where('is_active', true)
|
|
->whereNotNull('scheduled_date')
|
|
->where('scheduled_date', '>=', $startDate)
|
|
->where('scheduled_date', '<=', $endDate)
|
|
->with(['assignee:id,name', 'assignee.tenantProfile:id,user_id,department_id', 'assignee.tenantProfile.department:id,name']);
|
|
|
|
// 부서 필터 적용
|
|
if ($departmentFilter === 'personal') {
|
|
$query->where('assignee_id', $userId);
|
|
}
|
|
// department 필터는 부서별 필터링 로직 추가 필요 (현재는 전체)
|
|
|
|
$workOrders = $query->orderBy('scheduled_date')->limit(100)->get();
|
|
|
|
return $workOrders->map(function ($wo) {
|
|
$assigneeName = $wo->assignee?->name;
|
|
$departmentName = $wo->assignee?->tenantProfile?->department?->name;
|
|
|
|
return [
|
|
'id' => 'wo_'.$wo->id,
|
|
'title' => $wo->project_name ?? $wo->work_order_no,
|
|
'startDate' => $wo->scheduled_date?->format('Y-m-d'),
|
|
'endDate' => $wo->scheduled_date?->format('Y-m-d'),
|
|
'startTime' => null,
|
|
'endTime' => null,
|
|
'isAllDay' => true,
|
|
'type' => 'order',
|
|
'department' => $departmentName,
|
|
'personName' => $assigneeName,
|
|
'color' => null,
|
|
];
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 계약(시공) 일정 조회
|
|
*/
|
|
private function getContractSchedules(
|
|
int $tenantId,
|
|
string $startDate,
|
|
string $endDate,
|
|
string $departmentFilter,
|
|
int $userId
|
|
): Collection {
|
|
$query = Contract::query()
|
|
->where('tenant_id', $tenantId)
|
|
->where('is_active', true)
|
|
->whereNotNull('contract_start_date')
|
|
->where(function ($q) use ($startDate, $endDate) {
|
|
// 기간이 겹치는 계약 조회
|
|
$q->where(function ($sub) use ($startDate, $endDate) {
|
|
$sub->where('contract_start_date', '<=', $endDate)
|
|
->where(function ($inner) use ($startDate) {
|
|
$inner->where('contract_end_date', '>=', $startDate)
|
|
->orWhereNull('contract_end_date');
|
|
});
|
|
});
|
|
})
|
|
->with(['constructionPm:id,name', 'constructionPm.tenantProfile:id,user_id,department_id', 'constructionPm.tenantProfile.department:id,name']);
|
|
|
|
// 부서 필터 적용
|
|
if ($departmentFilter === 'personal') {
|
|
$query->where('construction_pm_id', $userId);
|
|
}
|
|
|
|
$contracts = $query->orderBy('contract_start_date')->limit(100)->get();
|
|
|
|
return $contracts->map(function ($contract) {
|
|
$pmName = $contract->constructionPm?->name;
|
|
$departmentName = $contract->constructionPm?->tenantProfile?->department?->name;
|
|
|
|
return [
|
|
'id' => 'contract_'.$contract->id,
|
|
'title' => $contract->project_name ?? $contract->contract_code,
|
|
'startDate' => $contract->contract_start_date?->format('Y-m-d'),
|
|
'endDate' => $contract->contract_end_date?->format('Y-m-d') ?? $contract->contract_start_date?->format('Y-m-d'),
|
|
'startTime' => null,
|
|
'endTime' => null,
|
|
'isAllDay' => true,
|
|
'type' => 'construction',
|
|
'department' => $departmentName,
|
|
'personName' => $pmName,
|
|
'color' => null,
|
|
];
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 휴가 일정 조회
|
|
*/
|
|
private function getLeaveSchedules(
|
|
int $tenantId,
|
|
string $startDate,
|
|
string $endDate,
|
|
string $departmentFilter,
|
|
int $userId
|
|
): Collection {
|
|
$query = Leave::query()
|
|
->where('tenant_id', $tenantId)
|
|
->where('status', 'approved')
|
|
->where(function ($q) use ($startDate, $endDate) {
|
|
// 기간이 겹치는 휴가 조회
|
|
$q->where('start_date', '<=', $endDate)
|
|
->where('end_date', '>=', $startDate);
|
|
})
|
|
->with(['user:id,name', 'user.tenantProfile:id,user_id,department_id', 'user.tenantProfile.department:id,name']);
|
|
|
|
// 부서 필터 적용
|
|
if ($departmentFilter === 'personal') {
|
|
$query->where('user_id', $userId);
|
|
}
|
|
|
|
$leaves = $query->orderBy('start_date')->limit(100)->get();
|
|
|
|
return $leaves->map(function ($leave) {
|
|
$userName = $leave->user?->name;
|
|
$departmentName = $leave->user?->tenantProfile?->department?->name;
|
|
$leaveType = $leave->type ?? 'leave';
|
|
$title = $userName
|
|
? __('message.calendar.leave_title', ['name' => $userName])
|
|
: __('message.calendar.leave_default');
|
|
|
|
return [
|
|
'id' => 'leave_'.$leave->id,
|
|
'title' => $title,
|
|
'startDate' => $leave->start_date?->format('Y-m-d'),
|
|
'endDate' => $leave->end_date?->format('Y-m-d'),
|
|
'startTime' => null,
|
|
'endTime' => null,
|
|
'isAllDay' => true,
|
|
'type' => 'schedule',
|
|
'department' => $departmentName,
|
|
'personName' => $userName,
|
|
'color' => null,
|
|
];
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 범용 일정 조회 (본사 공통 + 테넌트 일정)
|
|
*/
|
|
private function getGeneralSchedules(
|
|
int $tenantId,
|
|
string $startDate,
|
|
string $endDate
|
|
): Collection {
|
|
$schedules = Schedule::query()
|
|
->forTenant($tenantId)
|
|
->active()
|
|
->betweenDates($startDate, $endDate)
|
|
->with(['creator:id,name'])
|
|
->orderBy('start_date')
|
|
->limit(100)
|
|
->get();
|
|
|
|
return $schedules->map(function ($schedule) {
|
|
return [
|
|
'id' => 'schedule_'.$schedule->id,
|
|
'title' => $schedule->title,
|
|
'startDate' => $schedule->start_date?->format('Y-m-d'),
|
|
'endDate' => $schedule->end_date?->format('Y-m-d') ?? $schedule->start_date?->format('Y-m-d'),
|
|
'startTime' => $schedule->start_time,
|
|
'endTime' => $schedule->end_time,
|
|
'isAllDay' => $schedule->is_all_day,
|
|
'type' => 'other',
|
|
'department' => null,
|
|
'personName' => $schedule->creator?->name,
|
|
'color' => $schedule->color,
|
|
];
|
|
});
|
|
}
|
|
}
|