commonUtil = $commonUtil; $this->entityLocationSyncService = $entityLocationSyncService; } public function index() { if (! auth()->user()->can('holding.view')) { abort(403, 'Unauthorized action.'); } $businessId = request()->session()->get('user.business_id'); $entities = HoldingEntity::where('business_id', $businessId)->with('units')->latest()->get(); $units = OrgUnit::where('business_id', $businessId)->with(['entity', 'parent'])->latest()->get(); $assignments = EmploymentAssignment::where('business_id', $businessId) ->with(['user', 'entity', 'orgUnit', 'role']) ->latest() ->get(); $relations = HoldingEntityRelation::where('business_id', $businessId) ->with(['ownerEntity', 'relatedEntity']) ->latest() ->get(); $users = \App\User::where('business_id', $businessId)->user()->pluck('first_name', 'id'); $roles = Role::where('business_id', $businessId)->pluck('name', 'id'); $entityTypes = $this->entityTypeOptions(); $relationTypes = $this->relationTypeOptions(); $employmentTypes = $this->employmentTypeOptions(); $orgTrees = $this->buildOrgTrees($entities, $units); $userAssignments = $assignments ->where('user_id', auth()->id()) ->where('is_active', true) ->filter(function ($assignment) { if ($assignment->start_date && $assignment->start_date->isFuture()) { return false; } if ($assignment->end_date && $assignment->end_date->isPast()) { return false; } return true; }) ->values(); $stats = [ 'entities' => $entities->count(), 'units' => $units->count(), 'relations' => $relations->count(), 'assignments' => $assignments->where('is_active', true)->count(), 'hrm_departments' => Category::where('business_id', $businessId)->where('category_type', 'hrm_department')->count(), 'hrm_designations' => Category::where('business_id', $businessId)->where('category_type', 'hrm_designation')->count(), ]; return view('holding.index', compact( 'entities', 'units', 'assignments', 'relations', 'users', 'roles', 'entityTypes', 'relationTypes', 'employmentTypes', 'orgTrees', 'stats', 'userAssignments' )); } public function syncHrm(HoldingHrmSyncService $syncService) { if (! auth()->user()->can('holding.update')) { abort(403, 'Unauthorized action.'); } $businessId = (int) request()->session()->get('user.business_id'); $result = $syncService->syncForBusiness($businessId); return $this->holdingResponseSuccess( __('lang_v1.holding_hrm_sync_done', [ 'departments' => $result['departments'], 'assignments' => $result['assignments'], 'designations' => $result['designations'], ]), 'assignments_tab' ); } public function createEntity() { $this->authorizeCreate(); return view('holding.partials.modal_entity', [ 'entity' => null, 'entityTypes' => $this->entityTypeOptions(), ]); } public function editEntity($id) { $this->authorizeUpdate(); $entity = $this->findEntity($id); return view('holding.partials.modal_entity', [ 'entity' => $entity, 'entityTypes' => $this->entityTypeOptions(), ]); } public function storeEntity(Request $request) { $this->authorizeCreate(); $businessId = $request->session()->get('user.business_id'); $this->entityLocationSyncService->createEntityWithLocation($businessId, [ 'business_id' => $businessId, 'name' => $request->input('name'), 'code' => $request->input('code'), 'entity_type' => $request->input('entity_type', 'company'), 'is_internal' => $request->boolean('is_internal'), 'is_active' => $request->boolean('is_active', true), ]); return $this->holdingResponseSuccess(__('messages.success'), 'entities_tab'); } public function updateEntity(Request $request, $id) { $this->authorizeUpdate(); $entity = $this->findEntity($id); $entity->update([ 'name' => $request->input('name'), 'code' => $request->input('code'), 'entity_type' => $request->input('entity_type', 'company'), 'is_internal' => $request->boolean('is_internal'), 'is_active' => $request->boolean('is_active', true), ]); $this->entityLocationSyncService->syncLocationName($entity->fresh()); return $this->holdingResponseSuccess(__('messages.success'), 'entities_tab'); } public function destroyEntity($id) { $this->authorizeDelete(); $this->findEntity($id)->delete(); return $this->holdingResponseSuccess(__('messages.success'), 'entities_tab'); } public function createUnit() { $this->authorizeCreate(); $businessId = request()->session()->get('user.business_id'); return view('holding.partials.modal_unit', [ 'unit' => null, 'entities' => HoldingEntity::where('business_id', $businessId)->pluck('name', 'id'), 'units' => OrgUnit::where('business_id', $businessId)->pluck('name', 'id'), ]); } public function editUnit($id) { $this->authorizeUpdate(); $businessId = request()->session()->get('user.business_id'); $unit = $this->findUnit($id); return view('holding.partials.modal_unit', [ 'unit' => $unit, 'entities' => HoldingEntity::where('business_id', $businessId)->pluck('name', 'id'), 'units' => OrgUnit::where('business_id', $businessId)->where('id', '!=', $id)->pluck('name', 'id'), ]); } public function storeUnit(Request $request) { $this->authorizeCreate(); $businessId = $request->session()->get('user.business_id'); $parent = ! empty($request->input('parent_id')) ? OrgUnit::find($request->input('parent_id')) : null; $depth = $parent ? ((int) $parent->depth + 1) : 0; $path = $parent ? ($parent->path.'/'.$request->input('name')) : $request->input('name'); OrgUnit::create([ 'business_id' => $businessId, 'entity_id' => $request->input('entity_id'), 'parent_id' => $request->input('parent_id'), 'name' => $request->input('name'), 'code' => $request->input('code'), 'depth' => $depth, 'path' => $path, 'is_active' => $request->boolean('is_active', true), ]); return $this->holdingResponseSuccess(__('messages.success'), 'units_tab'); } public function updateUnit(Request $request, $id) { $this->authorizeUpdate(); $unit = $this->findUnit($id); $parent = ! empty($request->input('parent_id')) ? OrgUnit::find($request->input('parent_id')) : null; $depth = $parent ? ((int) $parent->depth + 1) : 0; $path = $parent ? ($parent->path.'/'.$request->input('name')) : $request->input('name'); $unit->update([ 'entity_id' => $request->input('entity_id'), 'parent_id' => $request->input('parent_id'), 'name' => $request->input('name'), 'code' => $request->input('code'), 'depth' => $depth, 'path' => $path, 'is_active' => $request->boolean('is_active', true), ]); return $this->holdingResponseSuccess(__('messages.success'), 'units_tab'); } public function destroyUnit($id) { $this->authorizeDelete(); $this->findUnit($id)->delete(); return $this->holdingResponseSuccess(__('messages.success'), 'units_tab'); } public function createRelation() { $this->authorizeCreate(); $businessId = request()->session()->get('user.business_id'); return view('holding.partials.modal_relation', [ 'relation' => null, 'entities' => HoldingEntity::where('business_id', $businessId)->pluck('name', 'id'), 'relationTypes' => $this->relationTypeOptions(), ]); } public function editRelation($id) { $this->authorizeUpdate(); $businessId = request()->session()->get('user.business_id'); $relation = $this->findRelation($id); return view('holding.partials.modal_relation', [ 'relation' => $relation, 'entities' => HoldingEntity::where('business_id', $businessId)->pluck('name', 'id'), 'relationTypes' => $this->relationTypeOptions(), ]); } public function storeRelation(Request $request) { $this->authorizeCreate(); $businessId = $request->session()->get('user.business_id'); HoldingEntityRelation::create([ 'business_id' => $businessId, 'owner_entity_id' => $request->input('owner_entity_id'), 'related_entity_id' => $request->input('related_entity_id'), 'relation_type' => $request->input('relation_type'), 'contract_start_date' => $this->parseOptionalDate($request->input('contract_start_date'), 'contract_start_date'), 'contract_end_date' => $this->parseOptionalDate($request->input('contract_end_date'), 'contract_end_date'), 'contract_ref' => $request->input('contract_ref'), 'is_active' => $request->boolean('is_active', true), ]); return $this->holdingResponseSuccess(__('messages.success'), 'relations_tab'); } public function updateRelation(Request $request, $id) { $this->authorizeUpdate(); $relation = $this->findRelation($id); $relation->update([ 'owner_entity_id' => $request->input('owner_entity_id'), 'related_entity_id' => $request->input('related_entity_id'), 'relation_type' => $request->input('relation_type'), 'contract_start_date' => $this->parseOptionalDate($request->input('contract_start_date'), 'contract_start_date'), 'contract_end_date' => $this->parseOptionalDate($request->input('contract_end_date'), 'contract_end_date'), 'contract_ref' => $request->input('contract_ref'), 'is_active' => $request->boolean('is_active', true), ]); return $this->holdingResponseSuccess(__('messages.success'), 'relations_tab'); } public function destroyRelation($id) { $this->authorizeDelete(); $this->findRelation($id)->delete(); return $this->holdingResponseSuccess(__('messages.success'), 'relations_tab'); } public function createAssignment() { if (! auth()->user()->can('holding.assignments.manage')) { abort(403, 'Unauthorized action.'); } return view('holding.partials.modal_assignment', $this->assignmentFormData()); } public function editAssignment($id) { if (! auth()->user()->can('holding.assignments.manage')) { abort(403, 'Unauthorized action.'); } return view('holding.partials.modal_assignment', $this->assignmentFormData($this->findAssignment($id))); } public function storeAssignment(Request $request, HoldingHrmSyncService $syncService) { if (! auth()->user()->can('holding.assignments.manage')) { abort(403, 'Unauthorized action.'); } $businessId = $request->session()->get('user.business_id'); $userId = (int) $request->input('user_id'); $entityId = (int) $request->input('entity_id'); $orgUnitId = $request->input('org_unit_id') ?: null; $duplicate = EmploymentAssignment::where('business_id', $businessId) ->where('user_id', $userId) ->where('entity_id', $entityId) ->where('org_unit_id', $orgUnitId) ->where('is_active', true) ->exists(); if ($duplicate) { return $this->holdingResponseError(__('lang_v1.holding_duplicate_assignment')); } if ($request->boolean('is_primary')) { EmploymentAssignment::where('business_id', $businessId) ->where('user_id', $userId) ->update(['is_primary' => false]); } EmploymentAssignment::create(array_merge([ 'business_id' => $businessId, 'user_id' => $userId, 'entity_id' => $entityId, 'org_unit_id' => $orgUnitId, ], $this->buildAssignmentAttributes($request, $syncService))); $assignment = EmploymentAssignment::where('business_id', $businessId) ->where('user_id', $userId) ->where('entity_id', $entityId) ->where('org_unit_id', $orgUnitId) ->latest('id') ->first(); if ($assignment) { $syncService->syncUserHrmFromAssignment($assignment->load('user')); } return $this->holdingResponseSuccess(__('messages.success'), 'assignments_tab'); } public function updateAssignment(Request $request, $id, HoldingHrmSyncService $syncService) { if (! auth()->user()->can('holding.assignments.manage')) { abort(403, 'Unauthorized action.'); } $businessId = $request->session()->get('user.business_id'); $assignment = $this->findAssignment($id); $userId = (int) $request->input('user_id'); $entityId = (int) $request->input('entity_id'); $orgUnitId = $request->input('org_unit_id') ?: null; $duplicate = EmploymentAssignment::where('business_id', $businessId) ->where('user_id', $userId) ->where('entity_id', $entityId) ->where('org_unit_id', $orgUnitId) ->where('is_active', true) ->where('id', '!=', $assignment->id) ->exists(); if ($duplicate) { return $this->holdingResponseError(__('lang_v1.holding_duplicate_assignment')); } if ($request->boolean('is_primary')) { EmploymentAssignment::where('business_id', $businessId) ->where('user_id', $userId) ->where('id', '!=', $assignment->id) ->update(['is_primary' => false]); } $assignment->update(array_merge([ 'user_id' => $userId, 'entity_id' => $entityId, 'org_unit_id' => $orgUnitId, ], $this->buildAssignmentAttributes($request, $syncService, $assignment))); $syncService->syncUserHrmFromAssignment($assignment->fresh('user')); return $this->holdingResponseSuccess(__('messages.success'), 'assignments_tab'); } public function destroyAssignment($id) { if (! auth()->user()->can('holding.assignments.manage')) { abort(403, 'Unauthorized action.'); } $this->findAssignment($id)->delete(); return $this->holdingResponseSuccess(__('messages.success'), 'assignments_tab'); } public function switchContext(Request $request, ContextAssignmentService $contextAssignmentService) { if (! auth()->user()->can('holding.context.switch')) { abort(403, 'Unauthorized action.'); } $assignment = $contextAssignmentService->getActiveAssignment(auth()->user(), (int) $request->input('assignment_id')); if (empty($assignment)) { return redirect()->back()->with('status', ['success' => 0, 'msg' => __('messages.something_went_wrong')]); } session(['holding.active_assignment_id' => $assignment->id]); return redirect()->back()->with('status', ['success' => 1, 'msg' => __('messages.success')]); } private function buildOrgTrees(Collection $entities, Collection $units): array { $unitsByEntity = $units->groupBy('entity_id'); $trees = []; foreach ($entities as $entity) { $entityUnits = $unitsByEntity->get($entity->id, collect()); $trees[$entity->id] = [ 'entity' => $entity, 'nodes' => $this->buildUnitTreeNodes($entityUnits), ]; } return $trees; } private function buildUnitTreeNodes(Collection $units): array { $byParent = $units->groupBy(function ($unit) { return $unit->parent_id ?? '__root__'; }); $build = function ($parentKey) use (&$build, $byParent) { return $byParent ->get($parentKey, collect()) ->values() ->map(function ($unit) use (&$build) { return [ 'unit' => $unit, 'children' => $build($unit->id), ]; }) ->all(); }; return $build('__root__'); } private function findEntity($id): HoldingEntity { $businessId = request()->session()->get('user.business_id'); return HoldingEntity::where('business_id', $businessId)->findOrFail($id); } private function findUnit($id): OrgUnit { $businessId = request()->session()->get('user.business_id'); return OrgUnit::where('business_id', $businessId)->findOrFail($id); } private function findRelation($id): HoldingEntityRelation { $businessId = request()->session()->get('user.business_id'); return HoldingEntityRelation::where('business_id', $businessId)->findOrFail($id); } private function findAssignment($id): EmploymentAssignment { $businessId = request()->session()->get('user.business_id'); return EmploymentAssignment::where('business_id', $businessId)->findOrFail($id); } private function authorizeCreate(): void { if (! auth()->user()->can('holding.create')) { abort(403, 'Unauthorized action.'); } } private function authorizeUpdate(): void { if (! auth()->user()->can('holding.update')) { abort(403, 'Unauthorized action.'); } } private function authorizeDelete(): void { if (! auth()->user()->can('holding.delete')) { abort(403, 'Unauthorized action.'); } } private function holdingResponseSuccess(string $msg, string $tab = '') { if (request()->ajax() || request()->wantsJson()) { return response()->json(['success' => true, 'msg' => $msg, 'tab' => $tab]); } $redirect = redirect()->back()->with('status', ['success' => 1, 'msg' => $msg]); return $tab ? $redirect->withFragment($tab) : $redirect; } private function holdingResponseError(string $msg) { if (request()->ajax() || request()->wantsJson()) { return response()->json(['success' => false, 'msg' => $msg]); } return redirect()->back()->with('status', ['success' => 0, 'msg' => $msg]); } private function entityTypeOptions(): array { return [ 'holding' => __('lang_v1.holding_entity_type_holding'), 'company' => __('lang_v1.holding_entity_type_company'), 'factory' => __('lang_v1.holding_entity_type_factory'), 'agency' => __('lang_v1.holding_entity_type_agency'), 'person' => __('lang_v1.holding_entity_type_person'), 'other' => __('lang_v1.holding_entity_type_other'), ]; } private function relationTypeOptions(): array { return [ 'subsidiary' => __('lang_v1.holding_relation_subsidiary'), 'affiliate' => __('lang_v1.holding_relation_affiliate'), 'partner' => __('lang_v1.holding_relation_partner'), 'contractor' => __('lang_v1.holding_relation_contractor'), 'vendor' => __('lang_v1.holding_relation_vendor'), 'other' => __('lang_v1.holding_relation_other'), ]; } private function employmentTypeOptions(): array { return [ 'employee' => __('lang_v1.holding_employment_employee'), 'contractor' => __('lang_v1.holding_employment_contractor'), 'consultant' => __('lang_v1.holding_employment_consultant'), 'outsource' => __('lang_v1.holding_employment_outsource'), 'other' => __('lang_v1.holding_employment_other'), ]; } private function assignmentFormData(?EmploymentAssignment $assignment = null): array { $businessId = (int) request()->session()->get('user.business_id'); $users = \App\User::where('business_id', $businessId) ->user() ->get(['id', 'first_name', 'last_name', 'essentials_department_id', 'essentials_designation_id']); $departmentToOrgUnit = OrgUnit::where('business_id', $businessId) ->get(['id', 'meta']) ->filter(fn ($unit) => ! empty($unit->meta['hrm_department_id'])) ->mapWithKeys(fn ($unit) => [(int) $unit->meta['hrm_department_id'] => $unit->id]); $userHrmMap = []; foreach ($users as $user) { $userHrmMap[$user->id] = [ 'designation_id' => $user->essentials_designation_id, 'org_unit_id' => $departmentToOrgUnit[$user->essentials_department_id] ?? null, ]; } return [ 'assignment' => $assignment, 'users' => $users->mapWithKeys(fn ($user) => [$user->id => trim($user->first_name.' '.($user->last_name ?? ''))]), 'entities' => HoldingEntity::where('business_id', $businessId)->pluck('name', 'id'), 'units' => OrgUnit::where('business_id', $businessId)->pluck('name', 'id'), 'roles' => Role::where('business_id', $businessId)->pluck('name', 'id'), 'designations' => Category::forDropdown($businessId, 'hrm_designation'), 'employmentTypes' => $this->employmentTypeOptions(), 'userHrmMap' => $userHrmMap, ]; } private function buildAssignmentAttributes(Request $request, HoldingHrmSyncService $syncService, ?EmploymentAssignment $assignment = null): array { $designationId = $request->input('hrm_designation_id') ?: null; $orgUnitId = $request->input('org_unit_id') ?: ($assignment?->org_unit_id); $title = $syncService->resolveTitleFromDesignation( $designationId ? (int) $designationId : null, $request->input('title') ); $meta = $assignment?->meta ?? []; if ($designationId) { $meta['hrm_designation_id'] = (int) $designationId; } else { unset($meta['hrm_designation_id']); } $meta = array_merge($meta, $syncService->resolveOrgUnitMeta($orgUnitId ? (int) $orgUnitId : null)); if (empty($meta['hrm_department_id'])) { unset($meta['hrm_department_id']); } return [ 'role_id' => $request->input('role_id'), 'title' => $title, 'employment_type' => $request->input('employment_type', 'employee'), 'start_date' => $this->parseOptionalDate($request->input('start_date'), 'start_date'), 'end_date' => $this->parseOptionalDate($request->input('end_date'), 'end_date'), 'is_primary' => $request->boolean('is_primary'), 'is_active' => $request->boolean('is_active', true), 'meta' => $meta, ]; } private function parseOptionalDate($value, string $field = 'date'): ?string { if ($value === null || trim((string) $value) === '') { return null; } $parsed = $this->commonUtil->uf_date($value); if (empty($parsed)) { $message = __('validation.date', ['attribute' => __('lang_v1.holding_start_date')]); if (request()->ajax() || request()->wantsJson()) { throw \Illuminate\Validation\ValidationException::withMessages([ $field => $message, ]); } throw \Illuminate\Validation\ValidationException::withMessages([ $field => $message, ]); } return $parsed; } }