'id', 'text' => 'ID', 'table' => false, 'modal' => false], ['key' => 'projectNumber', 'text' => 'Projekt-Nr.', 'required' => false, 'modal' => ['disabled' => true, 'placeholder' => 'Wird automatisch generiert']], ['key' => 'title', 'text' => 'Bezeichnung', 'required' => true], ['key' => 'status', 'text' => 'Status', 'required' => true, 'modal' => ['type' => 'select', 'items' => [ ['value' => 'new', 'text' => 'Neu'], ['value' => 'wip', 'text' => 'In Bearbeitung'], ['value' => 'finished', 'text' => 'Abgeschlossen'], ['value' => 'cancelled', 'text' => 'Storniert'], ]], 'table' => ['filter' => 'select']], ['key' => 'startDate', 'text' => 'Startdatum', 'required' => true, 'modal' => ['type' => 'date'], 'table' => ['filter' => 'date']], ['key' => 'endDate', 'text' => 'Enddatum', 'required' => true, 'modal' => ['type' => 'date'], 'table' => ['filter' => 'date']], ['key' => 'financials', 'text' => 'Gesamtsumme', 'required' => false, 'modal' => ['disabled' => true], 'table' => ['formatter' => 'formatPrice']], ['key' => 'storageLocation', 'text' => 'Lagerort', 'required' => false], ['key' => 'externalTeam', 'text' => 'Externes Team', 'required' => false, 'modal' => ['type' => 'textarea'], 'table' => false], ['key' => 'description', 'text' => 'Beschreibung', 'required' => false, 'modal' => ['type' => 'textarea'], 'table' => false], ['key' => 'create', 'text' => 'Erstellt', 'required' => false, 'modal' => false, 'table' => ['formatter' => 'formatDate']], ['key' => 'actions', 'text' => 'Aktionen', 'required' => false, 'modal' => false, 'table' => ['filter' => false, 'sortable' => false, 'class' => 'text-center']], ]; //@formatter:on protected array $permissionCheck = ['WarehouseUser']; protected array $additionalJSVariables = ['WAREHOUSE_ADMIN' => false]; protected function prepareCrudConfig(): void { if ($this->user->can('WarehouseAdmin')) { $this->additionalJSVariables['WAREHOUSE_ADMIN'] = true; } } private array $tempInternalTeam = []; protected function beforeCreate($postData): bool { $json = json_decode(file_get_contents('php://input'), true); if ($json) { $this->postData = array_merge($this->postData ?? [], $json); } if (isset($this->postData['internalTeam'])) { $this->tempInternalTeam = $this->postData['internalTeam']; unset($this->postData['internalTeam']); } $this->postData['projectNumber'] = WarehouseProjectModel::getNextProjectNumber(); // Ensure defaults if not provided if (!isset($this->postData['status'])) $this->postData['status'] = 'new'; if (!isset($this->postData['financials'])) $this->postData['financials'] = 0.00; return true; } protected function afterCreate($id, $postData): void { WarehouseProjectJournalModel::create([ 'projectId' => $id, 'text' => 'Projekt erstellt.', 'createBy' => $this->user->id, 'create' => time() ]); // Handle initial Internal Team if (!empty($this->tempInternalTeam) && is_array($this->tempInternalTeam)) { foreach ($this->tempInternalTeam as $userId) { WarehouseProjectMemberModel::create([ 'projectId' => $id, 'userId' => $userId, 'create' => time() ]); $u = UserModel::getOne($userId); $this->logJournal($id, "Teammitglied initial hinzugefügt: " . ($u ? $u->name : $userId)); } } } protected function afterUpdate($id, $postData): void { // Simple journaling of main record update WarehouseProjectJournalModel::create([ 'projectId' => $id, 'text' => 'Projektstammdaten aktualisiert.', 'createBy' => $this->user->id, 'create' => time() ]); } // --- API for Vue --- public function getProjectDetailsAction() { $id = $this->request->id; if (!$id) self::sendError("Projekt ID fehlt"); $project = WarehouseProjectModel::get($id); if (!$project) self::sendError("Projekt nicht gefunden"); self::returnJson(['project' => $project]); } public function getTasksAction() { $projectId = $this->request->id; if (!$projectId) self::sendError("Projekt ID fehlt"); $tasks = WarehouseProjectTaskModel::getAll(['projectId' => $projectId], null, 0, ['key' => 'order', 'order' => 'ASC']); foreach ($tasks as $task) { if ($task->assignedUserId) { $user = UserModel::getOne($task->assignedUserId); $task->assignedUserName = $user ? $user->name : 'Unbekannt'; } else { $task->assignedUserName = null; } } self::returnJson($tasks); } public function saveTaskAction() { $data = json_decode(file_get_contents('php://input'), true); $projectId = $data['projectId'] ?? null; if (!$projectId) self::sendError("Projekt ID fehlt"); $taskData = [ 'projectId' => $projectId, 'title' => $data['title'], 'description' => $data['description'] ?? '', 'status' => $data['status'] ?? 'todo', 'assignedUserId' => !empty($data['assignedUserId']) ? $data['assignedUserId'] : null, 'createBy' => $this->user->id, 'create' => time() ]; if (!empty($data['id'])) { $existingTask = WarehouseProjectTaskModel::get($data['id']); if (!$existingTask) self::sendError("Aufgabe nicht gefunden"); // Merge existing data with new data to ensure all required fields are present $updatedData = array_merge((array)$existingTask, $taskData); $updatedData['id'] = $data['id']; // Ensure ID is in the data for update // update method expects an array with 'id' key for update. WarehouseProjectTaskModel::update($updatedData); $this->logJournal($projectId, "Aufgabe aktualisiert: {$data['title']}"); } else { // Get max order to append $count = WarehouseProjectTaskModel::count(['projectId' => $projectId]); $taskData['order'] = $count + 1; WarehouseProjectTaskModel::create($taskData); $this->logJournal($projectId, "Aufgabe erstellt: {$data['title']}"); } self::returnJson(['success' => true]); } public function updateTaskStatusAction() { $data = json_decode(file_get_contents('php://input'), true); if (empty($data['id']) || empty($data['status'])) self::sendError("Daten fehlen"); $task = WarehouseProjectTaskModel::get($data['id']); if ($task) { // Retrieve existing task data to preserve projectId and other required fields $updatedData = (array)$task; $updatedData['status'] = $data['status']; // WarehouseProjectTaskModel::update expects an array with 'id' key for update. WarehouseProjectTaskModel::update($updatedData); $this->logJournal($task->projectId, "Aufgabenstatus '{$task->title}' geändert auf {$data['status']}"); } self::returnJson(['success' => true]); } public function deleteTaskAction() { $id = $this->request->id; if (!$id) self::sendError("ID fehlt"); $task = WarehouseProjectTaskModel::get($id); if ($task) { WarehouseProjectTaskModel::delete($id); $this->logJournal($task->projectId, "Aufgabe gelöscht: {$task->title}"); } self::returnJson(['success' => true]); } public function getTeamAction() { $projectId = $this->request->id; if (!$projectId) self::sendError("ID fehlt"); $members = WarehouseProjectMemberModel::getAll(['projectId' => $projectId]); $users = []; foreach($members as $m) { $u = UserModel::getOne($m->userId); if ($u) { $users[] = [ 'memberId' => $m->id, 'userId' => $u->id, 'name' => $u->name, 'role' => $m->role ]; } } self::returnJson($users); } public function addTeamMemberAction() { $data = json_decode(file_get_contents('php://input'), true); if (empty($data['projectId']) || empty($data['userId'])) self::sendError("Daten fehlen"); $exists = WarehouseProjectMemberModel::count(['projectId' => $data['projectId'], 'userId' => $data['userId']]); if ($exists > 0) self::sendError("Benutzer bereits im Team"); WarehouseProjectMemberModel::create([ 'projectId' => $data['projectId'], 'userId' => $data['userId'], 'role' => $data['role'] ?? null, 'create' => time() ]); $u = UserModel::getOne($data['userId']); $this->logJournal($data['projectId'], "Teammitglied hinzugefügt: " . ($u ? $u->name : $data['userId'])); self::returnJson(['success' => true]); } public function removeTeamMemberAction() { $id = $this->request->id; $member = WarehouseProjectMemberModel::get($id); if ($member) { $u = UserModel::getOne($member->userId); WarehouseProjectMemberModel::delete($id); $this->logJournal($member->projectId, "Teammitglied entfernt: " . ($u ? $u->name : $member->userId)); } self::returnJson(['success' => true]); } public function getAvailableOrderRequestsAction() { // Return open requests (not done, not cancelled) // You might want to filter out ones already linked to THIS project, but maybe not strictly necessary. $requests = WarehouseOrderRequest::getAll([], 100, 0, ['key' => 'create', 'order' => 'DESC']); $available = []; foreach($requests as $r) { if (!$r->done && !$r->cancelled) { $available[] = [ 'id' => $r->id, 'purpose' => $r->purpose, 'create' => $r->create ]; } } self::returnJson($available); } public function getLinkedOrdersAction() { $projectId = $this->request->id; if (!$projectId) self::sendError("ID fehlt"); $links = WarehouseProjectOrderRequestModel::getAll(['projectId' => $projectId]); $result = []; foreach($links as $l) { $req = WarehouseOrderRequest::get($l->orderRequestId); if ($req) { $positions = json_decode($req->positions, true); // Resolve actual Orders $orders = []; $ids = []; if (!empty($req->linkedOrderIds)) { // Check if it is JSON array $decoded = json_decode($req->linkedOrderIds, true); if (is_array($decoded)) { $ids = $decoded; } else { // Fallback to comma separated $ids = explode(',', $req->linkedOrderIds); } } foreach($ids as $oid) { $oid = trim($oid); if (empty($oid)) continue; $o = WarehouseOrderModel::get($oid); if ($o) { $orders[] = [ 'id' => $o->id, 'orderNumber' => $o->orderNumber, 'status' => $o->status, 'distributorId' => $o->distributorId ]; } } $result[] = [ 'linkId' => $l->id, 'requestId' => $req->id, 'purpose' => $req->purpose, 'create' => $req->create, 'status' => $req->done ? 'done' : ($req->cancelled ? 'cancelled' : 'open'), 'positionsCount' => is_array($positions) ? count($positions) : 0, 'orders' => $orders ]; } } self::returnJson($result); } public function linkOrderAction() { $data = json_decode(file_get_contents('php://input'), true); if (empty($data['projectId']) || empty($data['orderId'])) self::sendError("Daten fehlen"); $order = WarehouseOrderRequest::get($data['orderId']); if (!$order) self::sendError("Bestellwunsch nicht gefunden"); WarehouseProjectOrderRequestModel::create([ 'projectId' => $data['projectId'], 'orderRequestId' => $data['orderId'], 'create' => time() ]); $this->logJournal($data['projectId'], "Bestellwunsch #{$data['orderId']} verknüpft."); self::returnJson(['success' => true]); } public function unlinkOrderAction() { $id = $this->request->id; $link = WarehouseProjectOrderRequestModel::get($id); if ($link) { WarehouseProjectOrderRequestModel::delete($id); $this->logJournal($link->projectId, "Bestellwunsch #{$link->orderRequestId} Verknüpfung aufgehoben."); } self::returnJson(['success' => true]); } public function createJournalEntryAction() { $data = json_decode(file_get_contents('php://input'), true); if (empty($data['projectId'])) self::sendError("Projekt ID fehlt"); $this->logJournal($data['projectId'], $data['message'] ?? ''); self::returnJson(['success' => true]); } public function getJournalAction() { $projectId = $this->request->id; $logs = WarehouseProjectJournalModel::getAll(['projectId' => $projectId], null, 0, ['order' => 'DESC', 'key' => 'create']); foreach($logs as $log) { $u = UserModel::getOne($log->createBy); $log->userName = $u ? $u->name : 'System'; } self::returnJson($logs); } private function logJournal($projectId, $text) { WarehouseProjectJournalModel::create([ 'projectId' => $projectId, 'text' => $text, 'createBy' => $this->user->id, 'create' => time() ]); } // Users for Team Selection public function getUsersAction() { $users = array_map(function($u) { return ['id' => $u->id, 'name' => $u->name]; }, UserModel::search(['employee' => true])); self::returnJson($users); } }