Preorder/add new filter
This commit is contained in:
@@ -871,9 +871,9 @@ class PreorderModel
|
||||
if (is_array($tool_building_type) && count($tool_building_type)) {
|
||||
$where .= " AND adb_hausnummer.tool_building_type IN ('" . implode("','", $tool_building_type) . "')";
|
||||
} else {
|
||||
$tool_building_type = FronkDB::singleton()->escape($filter['connection_type']);
|
||||
if ($tool_building_type) {
|
||||
$where .= " AND adb_hausnummer.tool_building_type like '%$tool_building_type%'";
|
||||
$tool_building_type = FronkDB::singleton()->escape($filter['tool_building_type']);
|
||||
if ($tool_building_type === '0' || $tool_building_type) {
|
||||
$where .= " AND adb_hausnummer.tool_building_type = $tool_building_type ";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<?php
|
||||
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
use PHPMailer\PHPMailer\Exception;
|
||||
|
||||
class WarehouseOfferController extends TTCrud
|
||||
{
|
||||
protected string $headerTitle = 'Angebote';
|
||||
@@ -53,6 +56,7 @@ class WarehouseOfferController extends TTCrud
|
||||
$this->postData['offerNumber'] = 'AN' . date('Y') . '-' . str_pad($currentCount + 1, 4, '0', STR_PAD_LEFT);
|
||||
$this->postData['status'] = 'new';
|
||||
$this->postData['version'] = 1;
|
||||
$this->postData['validity'] = 14;
|
||||
$this->postData['alternativePositions'] = json_encode([]);
|
||||
return true;
|
||||
}
|
||||
@@ -182,11 +186,16 @@ class WarehouseOfferController extends TTCrud
|
||||
// E-Mail Actions
|
||||
public function sendOfferEmailAction()
|
||||
{
|
||||
//display errors for debugging
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('display_startup_errors', 1);
|
||||
|
||||
$_POST = json_decode(file_get_contents('php://input'), true);
|
||||
$id = $_POST['id'] ?? null;
|
||||
$recipientEmail = $_POST['email'] ?? null;
|
||||
$subject = $_POST['subject'] ?? 'Ihr Angebot von XINON GmbH';
|
||||
$body = $_POST['body'] ?? 'Anbei finden Sie Ihr angefordertes Angebot.';
|
||||
$bodyText = $_POST['body'] ?? 'Anbei finden Sie Ihr angefordertes Angebot.';
|
||||
|
||||
if (!$id || !$recipientEmail) {
|
||||
self::sendError("ID oder E-Mail-Adresse fehlt.");
|
||||
@@ -208,16 +217,66 @@ class WarehouseOfferController extends TTCrud
|
||||
// Generate PDF
|
||||
$pdfContent = $this->createPDFAction(true, $id, true);
|
||||
|
||||
// Send Email
|
||||
$mail = new Mail();
|
||||
$mail->addAddress($recipientEmail, $offer->contactPerson ?? $offer->customerName);
|
||||
$mail->setSubject($subject);
|
||||
$mail->setBody($body);
|
||||
$mail->addStringAttachment($pdfContent, $offer->offerNumber . '_Angebot.pdf', 'base64', 'application/pdf');
|
||||
// --- HTML Email Generation ---
|
||||
$logoToolPath = BASEDIR . '/public/assets/images/the-tool-logo.png';
|
||||
$logoXinonPath = BASEDIR . '/public/assets/images/xinon-full.png';
|
||||
$logoToolExists = file_exists($logoToolPath);
|
||||
$logoXinonExists = file_exists($logoXinonPath);
|
||||
|
||||
// Construct HTML Body
|
||||
$html = '<!DOCTYPE html><html lang="de"><head><meta charset="UTF-8"><title>Angebot</title><style>body { font-family: Arial, sans-serif; color: #333; }</style></head><body style="margin:0;padding:20px;background-color:#f3f4f6;">';
|
||||
$html .= '<div style="background-color:#fff;padding:20px;border-radius:8px;max-width:600px;margin:0 auto;box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);">';
|
||||
|
||||
// Logos
|
||||
$html .= '<div style="text-align:center;margin-bottom:20px;border-bottom: 1px solid #e5e7eb;padding-bottom: 15px;">';
|
||||
if ($logoToolExists) $html .= '<img src="cid:logo_thetool" alt="The Tool" style="height:40px;margin-right:15px;vertical-align:middle;">';
|
||||
if ($logoXinonExists) $html .= '<img src="cid:logo_xinon" alt="Xinon" style="height:40px;vertical-align:middle;">';
|
||||
$html .= '</div>';
|
||||
|
||||
$html .= '<h2 style="color:#00558c;text-align:center;font-size:20px;margin-bottom:20px;">' . htmlspecialchars($subject) . '</h2>';
|
||||
$html .= '<div style="font-size:14px;line-height:1.6;color:#333;">';
|
||||
$html .= nl2br(htmlspecialchars($bodyText));
|
||||
$html .= '</div>';
|
||||
|
||||
$html .= '<br><div style="border-top:1px solid #eee;padding-top:20px;font-size:12px;color:#999;text-align:center;">';
|
||||
$html .= 'XINON GmbH | <a href="https://www.xinon.at" style="color:#00558c;text-decoration:none;">www.xinon.at</a>';
|
||||
$html .= '</div></div></body></html>';
|
||||
|
||||
$mail = new PHPMailer(true);
|
||||
try {
|
||||
// Server settings
|
||||
$mail->isSMTP();
|
||||
$mail->Host = TT_PIPEWORK_SMTP_HOST;
|
||||
$mail->SMTPAuth = true;
|
||||
$mail->Username = TT_PIPEWORK_SMTP_USER;
|
||||
$mail->Password = TT_PIPEWORK_SMTP_PASS;
|
||||
$mail->CharSet = PHPMailer::CHARSET_UTF8;
|
||||
$mail->Encoding = 'base64';
|
||||
$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
|
||||
$mail->Port = 587;
|
||||
|
||||
// Logos
|
||||
if ($logoToolExists) $mail->addEmbeddedImage($logoToolPath, 'logo_thetool');
|
||||
if ($logoXinonExists) $mail->addEmbeddedImage($logoXinonPath, 'logo_xinon');
|
||||
|
||||
$mail->addReplyTo('backoffice@xinon.at', 'XINON Backoffice');
|
||||
$mail->setFrom('thetool@xinon.at', 'XINON TheTool');
|
||||
// set replyto to backoffice@xinon.at
|
||||
$mail->addAddress($recipientEmail, $offer->contactPerson ?? $offer->customerName);
|
||||
$mail->Subject = ($subject);
|
||||
$mail->isHTML(true);
|
||||
$mail->Body = $html;
|
||||
$mail->AltBody = strip_tags($bodyText);
|
||||
|
||||
$mail->addStringAttachment($pdfContent, $offer->offerNumber . '_Angebot.pdf', 'base64', 'application/pdf');
|
||||
|
||||
$mail->send();
|
||||
|
||||
if ($mail->send()) {
|
||||
// Update offer status and last sent date
|
||||
WarehouseOfferModel::update($id, ['status' => 'sent', 'lastSentDate' => time()]);
|
||||
$WarehouseOffer = (array) WarehouseOfferModel::get($id);
|
||||
$WarehouseOffer['status'] = 'sent';
|
||||
$WarehouseOffer['lastSentDate'] = time();
|
||||
WarehouseOfferModel::update($WarehouseOffer);
|
||||
|
||||
// Add Journal Entry
|
||||
WarehouseOfferJournalModel::create([
|
||||
@@ -228,7 +287,7 @@ class WarehouseOfferController extends TTCrud
|
||||
]);
|
||||
|
||||
self::returnJson(['success' => true, 'message' => 'E-Mail erfolgreich versendet.']);
|
||||
} else {
|
||||
} catch (Exception $e) {
|
||||
self::sendError('E-Mail konnte nicht gesendet werden. Fehler: ' . $mail->ErrorInfo);
|
||||
}
|
||||
}
|
||||
@@ -377,6 +436,7 @@ class WarehouseOfferController extends TTCrud
|
||||
"includeTax" => true,
|
||||
"vatRate" => 0.20,
|
||||
"offerText" => $offerData->notes ?? '',
|
||||
"validity" => $offerData->validity ?? 14,
|
||||
"closingText" => $offerData->closingText ?? '',
|
||||
"bank_iban" => TT_INVOICE_BANK_IBAN,
|
||||
"bank_bic" => TT_INVOICE_BANK_BIC,
|
||||
|
||||
@@ -24,6 +24,7 @@ class WarehouseOfferModel extends TTCrudBaseModel {
|
||||
public string $closingText;
|
||||
public string $notes;
|
||||
public string $status;
|
||||
public ?int $validity; // New field
|
||||
public ?int $lastSentDate; // New field
|
||||
public float $totalAmount;
|
||||
public int $create;
|
||||
|
||||
@@ -2,46 +2,378 @@
|
||||
|
||||
class WarehouseProjectController extends TTCrud {
|
||||
protected string $headerTitle = 'Projekte';
|
||||
protected string $createText = 'Neues Projekt erstellen';
|
||||
protected string $singleText = 'Projekt';
|
||||
protected bool $createText = false;
|
||||
|
||||
//@formatter:off
|
||||
protected array $columns = [
|
||||
['key' => 'title', 'text' => 'Titel', 'required' => true],
|
||||
['key' => 'description', 'text' => 'Projektbeschreibung', 'modal' => ['type' => 'textarea']],
|
||||
|
||||
['key' => 'startDate', 'text' => 'Startdatum', 'required' => true, 'modal' => ['type' => 'datepicker']],
|
||||
['key' => 'endDate', 'text' => 'Enddatum', 'required' => true, 'modal' => ['type' => 'datepicker']],
|
||||
['key' => 'positions', 'text' => 'Positionen', 'required' => true, 'modal' => ['type' => 'positions-manager', 'config' => [
|
||||
'header' => 'Positionen',
|
||||
'fields' => [
|
||||
'articleId' => ['apiUrl' => '/WarehouseArticle/autoComplete','type' => 'autocomplete','customFieldReference' => 'WarehouseArticle','label' => 'Artikel'],
|
||||
'amount' => ['type' => 'input', 'label' => 'Menge', 'inputType' => 'number'],
|
||||
'purpose' => ['type' => 'input', 'label' => 'Zweck'],
|
||||
],
|
||||
'validateFormOptions' => [
|
||||
['key' => 'articleId', 'message' => 'Bitte füllen Sie den Artikel aus'],
|
||||
['key' => 'amount', 'message' => 'Bitte füllen Sie die Menge aus'],
|
||||
['key' => 'purpose', 'message' => 'Bitte füllen Sie den Zweck aus'],
|
||||
],
|
||||
]], 'table' => false],
|
||||
['key' => 'linkedOrderIds', 'text' => 'Verlinkte Bestellung', 'modal' => false],
|
||||
//
|
||||
['key' => 'assignedPersons', 'text' => 'Zugewiesene Personen', 'modal' => ['type' => 'positions-manager', 'config' => [
|
||||
'header' => 'Zugewiesene Personen',
|
||||
'fields' => [
|
||||
'userId' => ['apiUrl' => '/WarehouseShippingNote/userAutoComplete','type' => 'autocomplete','label' => 'Person','customFieldReference' => 'User']
|
||||
],
|
||||
'validateFormOptions' => [
|
||||
['key' => 'userId', 'message' => 'Bitte füllen Sie die Person aus'],
|
||||
],
|
||||
]], 'table' => false],
|
||||
|
||||
['key' => 'storageLocation', 'text' => 'Lagerort', 'modal' => ['type' => 'input']],
|
||||
['key' => 'note', 'text' => 'Notiz', 'modal' => ['type' => 'textarea']],
|
||||
['key' => 'createBy', 'text' => 'Erstellt von', 'required' => true, 'modal' => ['visible' => false, 'type' => 'select'], 'table' => ['filter' => 'select']],
|
||||
['key' => 'create', 'text' => 'Erstellt', 'required' => true, 'modal' => false],
|
||||
['key' => '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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,35 @@
|
||||
|
||||
class WarehouseProjectModel extends TTCrudBaseModel {
|
||||
public int $id;
|
||||
public string $projectNumber;
|
||||
public string $title;
|
||||
public string $description;
|
||||
public string $startDate;
|
||||
public string $endDate;
|
||||
public ?string $description;
|
||||
public ?int $startDate;
|
||||
public ?int $endDate;
|
||||
public string $status;
|
||||
public string $priority;
|
||||
|
||||
public int $assignedTo;
|
||||
public float $financials;
|
||||
public ?string $storageLocation;
|
||||
public ?string $externalTeam;
|
||||
public ?int $createdFromOrderId;
|
||||
public int $createBy;
|
||||
public int $create;
|
||||
|
||||
public static function getNextProjectNumber(): string {
|
||||
$year = date('Y');
|
||||
$prefix = "XP-$year-";
|
||||
|
||||
$db = self::getDB();
|
||||
$tableName = self::getFullyQualifiedTable();
|
||||
|
||||
$sql = "SELECT projectNumber FROM $tableName WHERE projectNumber LIKE '$prefix%' ORDER BY projectNumber DESC LIMIT 1";
|
||||
$result = $db->query($sql);
|
||||
|
||||
$nextNum = 1;
|
||||
if ($row = $result->fetch_assoc()) {
|
||||
$lastNumStr = substr($row['projectNumber'], strrpos($row['projectNumber'], '-') + 1);
|
||||
$nextNum = intval($lastNumStr) + 1;
|
||||
}
|
||||
|
||||
return $prefix . str_pad((string)$nextNum, 4, '0', STR_PAD_LEFT);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
class WarehouseProjectJournalModel extends TTCrudBaseModel {
|
||||
public int $id;
|
||||
public int $projectId;
|
||||
public ?string $text;
|
||||
public ?string $data; // json
|
||||
public int $createBy;
|
||||
public int $create;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
class WarehouseProjectMemberModel extends TTCrudBaseModel {
|
||||
public int $id;
|
||||
public int $projectId;
|
||||
public int $userId;
|
||||
public ?string $role;
|
||||
public int $create;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
class WarehouseProjectOrderRequestModel extends TTCrudBaseModel {
|
||||
public int $id;
|
||||
public int $projectId;
|
||||
public int $orderRequestId;
|
||||
public int $create;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
class WarehouseProjectTaskModel extends TTCrudBaseModel {
|
||||
public int $id;
|
||||
public int $projectId;
|
||||
public ?int $parentTaskId;
|
||||
public string $title;
|
||||
public ?string $description;
|
||||
public string $status; // todo, in_progress, done
|
||||
public ?int $assignedUserId;
|
||||
public int $order;
|
||||
public int $createBy;
|
||||
public int $create;
|
||||
}
|
||||
Reference in New Issue
Block a user