ultimatepos/app/Http/Controllers/HoldingController.php

673 lines
25 KiB
PHP

<?php
namespace App\Http\Controllers;
use App\Category;
use App\EmploymentAssignment;
use App\HoldingEntity;
use App\HoldingEntityRelation;
use App\OrgUnit;
use App\Services\Holding\ContextAssignmentService;
use App\Services\Holding\HoldingEntityLocationSyncService;
use App\Services\Holding\HoldingHrmSyncService;
use App\Utils\Util;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Spatie\Permission\Models\Role;
class HoldingController extends Controller
{
protected $commonUtil;
protected $entityLocationSyncService;
public function __construct(Util $commonUtil, HoldingEntityLocationSyncService $entityLocationSyncService)
{
$this->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;
}
}