added new warehouseorder features
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
* @var WarehouseOrderModel $order
|
||||
* @var Array $positions
|
||||
* @var Array $textElements
|
||||
* @var string $distributorCountryText
|
||||
*/
|
||||
$this->setReturnValue(['filename' => $order["id"] . ".pdf"]);
|
||||
|
||||
@@ -16,6 +17,7 @@ $texts = [
|
||||
'total' => 'Total',
|
||||
'taxFree' => 'Please provide tax-free delivery according to § 6a UStG (Austrian Sales tax law).<br>Our VAT ID number: ATU68711968. Delivery to Austria.',
|
||||
'orderConfirmation' => 'Please send the order confirmation to einkauf@xinon.at',
|
||||
'sendShippingNote' => 'Please send the delivery note with our shipping note and neutral packaging.',
|
||||
'table' => [
|
||||
'pos' => 'POS',
|
||||
'article' => 'Article',
|
||||
@@ -32,6 +34,7 @@ $texts = [
|
||||
'total' => 'Gesamt',
|
||||
'taxFree' => 'Bitte um steuerfreie Lieferung gemäß § 6a UStG.<br> Unsere UID-Nr.: ATU68711968. Lieferung nach Österreich.',
|
||||
'orderConfirmation' => 'Wir bitten um Zusendung der Auftragsbestätigung für die Bestellung an einkauf@xinon.at.',
|
||||
'sendShippingNote' => 'Bitte senden Sie den Lieferschein mit unserem Versandschein und neutraler Verpackung.',
|
||||
'table' => [
|
||||
'pos' => 'POS',
|
||||
'article' => 'Artikel',
|
||||
@@ -182,10 +185,17 @@ $text = $texts[in_array($distributorCountryText, ["Österreich", "Deutschland",
|
||||
|
||||
<div>
|
||||
<?php if ($distributorCountryText !== "Österreich"): ?>
|
||||
<?= $text['taxFree'] ?>
|
||||
<br><?= $text['taxFree'] ?> <br><br>
|
||||
<?php endif; ?>
|
||||
|
||||
<?= $text['orderConfirmation'] ?>
|
||||
|
||||
<?php if ($order['sendShippingNote'] > 0): ?>
|
||||
<br><br>
|
||||
<?= $text['sendShippingNote'] ?>
|
||||
<?php endif; ?>
|
||||
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -89,85 +89,210 @@ class WarehouseOrderController extends TTCrud {
|
||||
return $order;
|
||||
}
|
||||
|
||||
protected function createPDFAction() {
|
||||
protected function createPDFAction($returnFilename = false) {
|
||||
$order = (array) WarehouseOrderModel::get($this->request->id);
|
||||
$order['positions'] = json_decode($order['positions'], true);
|
||||
// check if all positions have the same distributor
|
||||
$distributorId = $order['positions'][0]['distributorId'];
|
||||
foreach ($order['positions'] as $key => $position) {
|
||||
if ($position['distributorId'] !== $distributorId) {
|
||||
self::returnJson(['error' => 'Die Bestellung enthält Positionen von verschiedenen Lieferanten.']);
|
||||
}
|
||||
|
||||
// we need to get the article name and distributor name for the pdf
|
||||
$position['distributorName'] = WarehouseDistributorModel::get($position['distributorId'])->name;
|
||||
$position['articleName'] = WarehouseArticleModel::get($position['article'])->title;
|
||||
$position['articleDescription'] = WarehouseArticleModel::get($position['article'])->description;
|
||||
|
||||
$order['positions'][$key] = $position;
|
||||
// Validate distributor consistency
|
||||
$distributorIds = array_column($order['positions'], 'distributorId');
|
||||
if (count(array_unique($distributorIds)) > 1) {
|
||||
self::returnJson(['error' => 'Die Bestellung enthält Positionen von verschiedenen Lieferanten.']);
|
||||
}
|
||||
|
||||
$pdf_vars = ['order' => $order,
|
||||
'distributor' => WarehouseDistributorModel::get($distributorId),
|
||||
'distributorCountryText' => (new Country(WarehouseDistributorModel::get($distributorId)->countryId))->name,
|
||||
"bank_iban" => TT_INVOICE_BANK_IBAN,
|
||||
"bank_bic" => TT_INVOICE_BANK_BIC,
|
||||
"bank_bank" => TT_INVOICE_BANK_BANK,
|
||||
"bank_owner" => TT_INVOICE_BANK_OWNER];
|
||||
// Prepare data
|
||||
$distributor = WarehouseDistributorModel::get($distributorIds[0]);
|
||||
$countryText = (new Country($distributor->countryId))->name;
|
||||
$isGermanSpeaking = in_array($countryText, ['Österreich', 'Deutschland', 'Schweiz']);
|
||||
$isDefaultAddress = $order['delAddrLine'] === "Fladnitz im Raabtal 150";
|
||||
|
||||
$countryText = (new Country(WarehouseDistributorModel::get($distributorId)->countryId))->name;
|
||||
$shouldGenerateEnglisch = !in_array($countryText, ['Österreich', 'Deutschland', 'Schweiz']);
|
||||
// Enhance positions
|
||||
foreach ($order['positions'] as &$position) {
|
||||
$article = WarehouseArticleModel::get($position['article']);
|
||||
$position += [
|
||||
'distributorName' => $distributor->name,
|
||||
'articleName' => $article->title,
|
||||
'articleDescription' => $article->description
|
||||
];
|
||||
}
|
||||
|
||||
$headerHtml = file_get_contents(BASEDIR . "/Layout/default/WarehouseOrder/PDF_HEADER.html");
|
||||
$headerHtml = str_replace("{{ basedir }}", BASEDIR, $headerHtml);
|
||||
$headerHtml = str_replace("{{ externalReference }}", !empty($order['extReference']) && count($order['extReference']) > 0 ? "<strong>Ext. Ref.:</strong> ". $order['extReference'] : "", $headerHtml);
|
||||
$headerHtml = str_replace("{{ addressLine_header }}", $shouldGenerateEnglisch ? "Supplier" : "Lieferant", $headerHtml);
|
||||
$headerHtml = str_replace("{{ addressLine_1 }}", WarehouseDistributorModel::get($distributorId)->name, $headerHtml);
|
||||
$headerHtml = str_replace("{{ addressLine_2 }}", WarehouseDistributorModel::get($distributorId)->address, $headerHtml);
|
||||
$headerHtml = str_replace("{{ addressLine_3 }}", WarehouseDistributorModel::get($distributorId)->plz . " " . WarehouseDistributorModel::get($distributorId)->city, $headerHtml);
|
||||
$headerHtml = str_replace("{{ addressLine_4 }}", $countryText, $headerHtml);
|
||||
// Prepare PDF variables
|
||||
$pdfVars = [
|
||||
'order' => $order,
|
||||
'distributor' => $distributor,
|
||||
'distributorCountryText' => $countryText,
|
||||
'bank' => [
|
||||
'iban' => TT_INVOICE_BANK_IBAN,
|
||||
'bic' => TT_INVOICE_BANK_BIC,
|
||||
'bank' => TT_INVOICE_BANK_BANK,
|
||||
'owner' => TT_INVOICE_BANK_OWNER
|
||||
]
|
||||
];
|
||||
|
||||
$headerHtml = str_replace("{{ billingAddressLine_header }}", $shouldGenerateEnglisch ? "Billing Address" : "Rechnungsadresse", $headerHtml);
|
||||
$headerHtml = str_replace("{{ billingAddressLine_1 }}", "Xinon GmbH", $headerHtml);
|
||||
$headerHtml = str_replace("{{ billingAddressLine_2 }}", "Fladnitz im Raabtal 150", $headerHtml);
|
||||
$headerHtml = str_replace("{{ billingAddressLine_3 }}", "A-8322 Studenzen", $headerHtml);
|
||||
$headerHtml = str_replace("{{ billingAddressLine_4 }}", "UID: ATU68711968", $headerHtml);
|
||||
$headerHtml = str_replace("{{ billingAddressLine_5 }}", "EORI-Nr.: ATEOS1000085074", $headerHtml);
|
||||
$headerHtml = str_replace("{{ billingAddressLine_6 }}", "<strong>Referenz: ". $order["orderNumber"] . "</strong>", $headerHtml);
|
||||
|
||||
$chk = $order['delAddrLine'] == "Fladnitz im Raabtal 150";
|
||||
|
||||
$headerHtml = str_replace("{{ shippingAddressLine_header }}", $chk ? "" : ($shouldGenerateEnglisch ? "Shipping Address" : "Lieferadresse"), $headerHtml);
|
||||
$headerHtml = str_replace("{{ shippingAddressLine_1 }}", $chk ? "" : $order['delAddrName'], $headerHtml);
|
||||
$headerHtml = str_replace("{{ shippingAddressLine_2 }}", $chk ? "" : $order['delAddrLine'], $headerHtml);
|
||||
$headerHtml = str_replace("{{ shippingAddressLine_3 }}", $chk ? "" : $order['delAddrPLZ'] . " " . $order['delAddrCity'], $headerHtml);
|
||||
$headerHtml = str_replace("{{ shippingAddressLine_4 }}", $chk ? "" : $order['delAddrEMail'], $headerHtml);
|
||||
// Generate HTML templates
|
||||
$replacements = [
|
||||
'basedir' => BASEDIR,
|
||||
'externalReference' => !empty($order['extReference']) ?
|
||||
"<strong>Ext. Ref.:</strong> ".$order['extReference'] : "",
|
||||
'isGermanSpeaking' => $isGermanSpeaking, // Required for conditional logic
|
||||
'isDefaultAddress' => $isDefaultAddress, // Needed for address display rules
|
||||
'distributor' => $distributor,
|
||||
'countryText' => $countryText,
|
||||
'order' => $order, // Full order data access
|
||||
'orderNumber' => $order['orderNumber'],
|
||||
'bank_iban' => TT_INVOICE_BANK_IBAN_FORMATTED, // Direct footer access
|
||||
'bank_bic' => TT_INVOICE_BANK_BIC,
|
||||
'bank_bank' => TT_INVOICE_BANK_BANK,
|
||||
'bank_owner' => TT_INVOICE_BANK_OWNER
|
||||
];
|
||||
|
||||
|
||||
// put header and footer into temp files
|
||||
$headerFile = BASEDIR . "/var/temp/order_header-" . date("U") . "-" . rand(1000, 9999) . ".html";
|
||||
file_put_contents($headerFile, $headerHtml);
|
||||
|
||||
$footerHtml = file_get_contents(BASEDIR . "/Layout/default/WarehouseOrder/PDF_FOOTER.html");
|
||||
$footerHtml = str_replace("{{ bank_iban }}", TT_INVOICE_BANK_IBAN_FORMATTED, $footerHtml);
|
||||
$footerHtml = str_replace("{{ bank_bic }}", TT_INVOICE_BANK_BIC, $footerHtml);
|
||||
$footerHtml = str_replace("{{ bank_bank }}", TT_INVOICE_BANK_BANK, $footerHtml);
|
||||
$footerHtml = str_replace("{{ bank_owner }}", TT_INVOICE_BANK_OWNER, $footerHtml);
|
||||
|
||||
file_put_contents($headerFile, $this->generateTemplate('PDF_HEADER', $replacements));
|
||||
$footerFile = BASEDIR . "/var/temp/order_footer-" . date("U") . "-" . rand(1000, 9999) . ".html";
|
||||
file_put_contents($footerFile, $footerHtml);
|
||||
file_put_contents($footerFile, $this->generateTemplate('PDF_FOOTER', $replacements));
|
||||
|
||||
// Generate PDF
|
||||
$pdf = new PdfForm("WarehouseOrder/PDF_MAIN", $pdfVars);
|
||||
$filename = $pdf->render("--header-html $headerFile --footer-html $footerFile");
|
||||
|
||||
$pdf = new PdfForm("WarehouseOrder/PDF_MAIN", $pdf_vars);
|
||||
$wkhtmltopdfArgs = "--header-html $headerFile --footer-html $footerFile";
|
||||
$filename = $pdf->render($wkhtmltopdfArgs);
|
||||
if ($returnFilename === true) return $filename;
|
||||
|
||||
// return the pdf and die so the client sees the pdf not the filename
|
||||
// Output PDF
|
||||
header('Content-Type: application/pdf');
|
||||
header('Content-Disposition: inline; filename="' . $filename . '"');
|
||||
header('Content-Disposition: inline; filename="' . basename($filename) . '"');
|
||||
readfile($filename);
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
protected function sendEmailAction() {
|
||||
$pdfFilename = $this->createPDFAction(true);
|
||||
|
||||
// Get order details for email content
|
||||
$order = (array) WarehouseOrderModel::get($this->request->id);
|
||||
$orderNumber = $order['orderNumber'];
|
||||
$order['positions'] = json_decode($order['positions'], true);
|
||||
$distributorName = WarehouseDistributorModel::get($order['distributorId'])->name;
|
||||
|
||||
// Instantiate PHPMailer
|
||||
$mail = new PHPMailer\PHPMailer\PHPMailer(true);
|
||||
|
||||
try {
|
||||
// Server settings
|
||||
$mail->isSMTP();
|
||||
$mail->Host = TT_WAREHOUSE_ORDER_SMTP_HOST;
|
||||
$mail->SMTPAuth = true;
|
||||
$mail->Username = TT_WAREHOUSE_ORDER_SMTP_USER;
|
||||
$mail->Password = TT_WAREHOUSE_ORDER_SMTP_PASS;
|
||||
$mail->SMTPSecure = PHPMailer\PHPMailer\PHPMailer::ENCRYPTION_STARTTLS;
|
||||
$mail->Port = 587;
|
||||
|
||||
// Recipients
|
||||
$mail->setFrom('einkauf@xinon.at', 'XINON Einkauf');
|
||||
$mail->addAddress($this->request->email, $this->request->email);
|
||||
// $mail->addCC('cc@example.com'); // Uncomment for CC
|
||||
|
||||
// Content
|
||||
$mail->isHTML(true);
|
||||
$mail->Subject = "Neue Bestellung #$orderNumber";
|
||||
$mail->Body = "<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>XINON E-Mail Template</title>
|
||||
<meta charset='utf-8'/>
|
||||
</head>
|
||||
<body>
|
||||
<h2>Neue Bestellung</h2>
|
||||
<p>
|
||||
Bestellnummer: <strong>$orderNumber</strong><br>
|
||||
Datum: " . date('d.m.Y H:i', $order['create']) . "<br>
|
||||
Lieferant: <strong>$distributorName</strong>
|
||||
</p>
|
||||
<p>Die Bestellung liegt als PDF-Anhang bei.</p>
|
||||
</body>
|
||||
</html>";
|
||||
|
||||
// Attach PDF
|
||||
$mail->addAttachment($pdfFilename, "Bestellung_$orderNumber.pdf");
|
||||
|
||||
if ($order['sendShippingNote'] > 0) {
|
||||
$shippingNoteController = new WarehouseShippingNoteController();
|
||||
$shippingNoteFilename = $shippingNoteController->createPDFAction(true, $this->request->shippingNote);
|
||||
$mail->addAttachment($shippingNoteFilename, "Lieferschein_$orderNumber.pdf");
|
||||
}
|
||||
|
||||
$mail->send();
|
||||
|
||||
$log = [
|
||||
"table" => "WarehouseOrder",
|
||||
"rowId" => $this->request->id,
|
||||
"type" => "noChanges",
|
||||
"message" => "Bestellung per Mail versendet",
|
||||
"createBy" => intval($this->user->id),
|
||||
"create" => time()
|
||||
];
|
||||
WarehouseLogModel::create($log);
|
||||
|
||||
// Cleanup temporary files
|
||||
if (file_exists($pdfFilename)) {
|
||||
unlink($pdfFilename);
|
||||
}
|
||||
|
||||
self::returnJson(['success' => true, 'message' => 'E-Mail erfolgreich versendet']);
|
||||
exit;
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
self::returnJson([
|
||||
'success' => false,
|
||||
'message' => "E-Mail konnte nicht versendet werden: {$mail->ErrorInfo}",
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function generateTemplate(string $type, array $data): string {
|
||||
$template = file_get_contents(BASEDIR . "/Layout/default/WarehouseOrder/{$type}.html");
|
||||
|
||||
$replacements = [
|
||||
'{{ basedir }}' => BASEDIR,
|
||||
'{{ externalReference }}' => !empty($data['order']['extReference']) ?
|
||||
"<strong>Ext. Ref.:</strong> ".$data['order']['extReference'] : "",
|
||||
'{{ addressLine_header }}' => $data['isGermanSpeaking'] ? 'Lieferant' : 'Supplier',
|
||||
'{{ addressLine_1 }}' => $data['distributor']->name,
|
||||
'{{ addressLine_2 }}' => $data['distributor']->address,
|
||||
'{{ addressLine_3 }}' => $data['distributor']->plz . ' ' . $data['distributor']->city,
|
||||
'{{ addressLine_4 }}' => $data['countryText'],
|
||||
'{{ billingAddressLine_header }}' => $this->getBillingHeader($data),
|
||||
'{{ billingAddressLine_1 }}' => 'Xinon GmbH',
|
||||
'{{ billingAddressLine_2 }}' => 'Fladnitz im Raabtal 150',
|
||||
'{{ billingAddressLine_3 }}' => 'A-8322 Studenzen',
|
||||
'{{ billingAddressLine_4 }}' => 'UID: ATU68711968',
|
||||
'{{ billingAddressLine_5 }}' => 'EORI-Nr.: ATEOS1000085074',
|
||||
'{{ billingAddressLine_6 }}' => "<strong>Referenz: ".$data['order']['orderNumber']."</strong>",
|
||||
'{{ shippingAddressLine_header }}' => $data['isDefaultAddress'] ? '' :
|
||||
($data['isGermanSpeaking'] ? 'Lieferadresse' : 'Shipping Address'),
|
||||
'{{ shippingAddressLine_1 }}' => $data['isDefaultAddress'] ? '' : $data['order']['delAddrName'],
|
||||
'{{ shippingAddressLine_2 }}' => $data['isDefaultAddress'] ? '' : $data['order']['delAddrLine'],
|
||||
'{{ shippingAddressLine_3 }}' => $data['isDefaultAddress'] ? '' :
|
||||
$data['order']['delAddrPLZ'] . ' ' . $data['order']['delAddrCity'],
|
||||
'{{ shippingAddressLine_4 }}' => $data['isDefaultAddress'] ? '' : $data['order']['delAddrEMail'],
|
||||
'{{ bank_iban }}' => TT_INVOICE_BANK_IBAN_FORMATTED,
|
||||
'{{ bank_bic }}' => TT_INVOICE_BANK_BIC,
|
||||
'{{ bank_bank }}' => TT_INVOICE_BANK_BANK,
|
||||
'{{ bank_owner }}' => TT_INVOICE_BANK_OWNER
|
||||
];
|
||||
|
||||
return strtr($template, $replacements);
|
||||
}
|
||||
|
||||
private function getBillingHeader(array $data): string {
|
||||
$base = $data['isGermanSpeaking'] ? 'Rechnungsadresse' : 'Billing Address';
|
||||
return $data['isDefaultAddress'] ? $base . ' / ' .
|
||||
($data['isGermanSpeaking'] ? 'Lieferadresse' : 'Shipping Address'):$base;
|
||||
}
|
||||
|
||||
|
||||
protected function getLogAction() {
|
||||
$orderId = $this->request->orderId;
|
||||
if (empty($orderId)) {
|
||||
@@ -198,7 +323,7 @@ class WarehouseOrderController extends TTCrud {
|
||||
];
|
||||
|
||||
try {
|
||||
$order = WarehouseOrderModel::get($log['orderId']);
|
||||
$order = WarehouseOrderModel::get($log['rowId']);
|
||||
if ($postData['status'] !== 'noChanges') {
|
||||
$oldStatusText = array_values(array_filter($this->columns, fn($c) => $c['key'] === 'status'))[0]['modal']['items'][array_search($order->status, array_column(array_values(array_filter($this->columns, fn($c) => $c['key'] === 'status'))[0]['modal']['items'], 'value'))]['text'];
|
||||
$newStatusText = array_values(array_filter($this->columns, fn($c) => $c['key'] === 'status'))[0]['modal']['items'][array_search($postData['status'], array_column(array_values(array_filter($this->columns, fn($c) => $c['key'] === 'status'))[0]['modal']['items'], 'value'))]['text'];
|
||||
|
||||
@@ -33,6 +33,7 @@ class WarehouseOrderModel extends TTCrudBaseModel {
|
||||
public int $editor;
|
||||
public ?string $note;
|
||||
public string $positions;
|
||||
public ?int $sendShippingNote;
|
||||
public int $create;
|
||||
public int $createBy;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ class WarehouseOrderRequestController extends TTCrud {
|
||||
//@formatter:off
|
||||
protected array $columns = [
|
||||
['key' => 'id', 'text' => 'Bestellnummer', 'table' => ['filter' => false], 'modal' => false],
|
||||
['key' => 'addressId', 'text' => 'Kunde', 'required' => false, 'type' => 'autocomplete', 'table' => ['class' => 'text-nowrap', 'filter' => 'autocomplete'], 'modal' => ['apiUrl' => 'Address/api?do=findAddress', 'items' => '/Address/Api?do=findAddress', 'type' => 'autocomplete']],
|
||||
['key' => 'purpose', 'text' => 'Verwendungszweck', 'required' => true],
|
||||
['key' => 'positions', 'text' => 'Positionen', 'required' => true, 'modal' => ['type' => 'positions-manager', 'config' => [
|
||||
'header' => 'Positionen',
|
||||
@@ -35,11 +36,16 @@ class WarehouseOrderRequestController extends TTCrud {
|
||||
['value' => 0, 'text' => 'Bestellwunsch nicht storniert', 'icon' => 'fa-regular fa-circle-check text-success'],
|
||||
['value' => 1, 'text' => 'Bestellwunsch storniert', 'icon' => 'fa-regular fa-circle-xmark text-danger']]], 'table' => ['filter' => 'iconSelect']
|
||||
],
|
||||
['key' => 'done', 'text' => 'Erledigt', 'modal' => ['visible' => false, 'type' => 'icon-select', 'items' => [
|
||||
['value' => 0, 'text' => 'Bestellwunsch nicht erledigt', 'icon' => 'fa-regular fa-circle-check text-success'],
|
||||
['value' => 1, 'text' => 'Bestellwunsch erledigt', 'icon' => 'fa-regular fa-circle-xmark text-danger']]], 'table' => ['filter' => 'iconSelect']
|
||||
],
|
||||
['key' => 'actions', 'text' => 'Aktionen', 'required' => false, 'modal' => false, 'table' => ['filter' => false, 'sortable' => false, 'class' => 'text-center']],
|
||||
];
|
||||
//@formatter:on
|
||||
|
||||
protected array $permissionCheck = ['WarehouseUser'];
|
||||
protected array $defaultOrder = ['key' => 'create', 'order' => 'DESC'];
|
||||
|
||||
protected array $additionalActions = [
|
||||
['key' => 'openHistory', 'title' => 'Historie', 'class' => 'fas fa-history text-primary'],
|
||||
@@ -59,37 +65,6 @@ class WarehouseOrderRequestController extends TTCrud {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function afterCreate($postData): void {
|
||||
return;
|
||||
//TODO: this should be working again
|
||||
if ($_SERVER['HTTP_HOST'] === 'localhost') return;
|
||||
die("TODO we need this to work with new positions manager");
|
||||
|
||||
$email = new Emailnotification();
|
||||
$postData['ware'] = is_numeric($postData['ware']) ? WarehouseArticleModel::get((int) $postData['ware'])->title : $postData['ware'];
|
||||
$paddedId = str_pad($postData['id'], 5, '0', STR_PAD_LEFT);
|
||||
|
||||
$email->setSubject("TheTool: Neue Interne Bestellung #$paddedId")
|
||||
->setBody(<<<BODY
|
||||
Hallo,
|
||||
|
||||
es wurde eine neue interne Bestellung erstellt.
|
||||
|
||||
Bestellnummer: #$paddedId
|
||||
Ware: {$postData['ware']}
|
||||
Anzahl: {$postData['anzahl']}
|
||||
Verwendungszweck: {$postData['verwendungszweck']}
|
||||
Beauftragt von: {$this->user->name}
|
||||
Beauftragt am: {date('d.m.Y H:i')}
|
||||
Notiz: {$postData['note']}
|
||||
|
||||
BODY
|
||||
)
|
||||
->setFrom(TT_OUTGOING_EMAIL_2FA, TT_OUTGOING_EMAIL_2FA)
|
||||
->setTo("einkauf@xinon.at", "Einkauf")
|
||||
->send();
|
||||
}
|
||||
|
||||
protected function cancelAction() {
|
||||
$id = filter_var($this->request->id, FILTER_VALIDATE_INT);
|
||||
$cancel = filter_var($this->request->cancel, FILTER_VALIDATE_INT, ['options' => ['min_range' => 0, 'max_range' => 1]]);
|
||||
@@ -101,6 +76,17 @@ BODY
|
||||
self::returnJson(['success' => true]);
|
||||
}
|
||||
|
||||
protected function doneAction() {
|
||||
$id = filter_var($this->request->id, FILTER_VALIDATE_INT);
|
||||
$done = filter_var($this->request->done, FILTER_VALIDATE_INT, ['options' => ['min_range' => 0, 'max_range' => 1]]);
|
||||
|
||||
if (!$id || $done === false) self::returnJson(['error' => 'Ungültige Anfrage']);
|
||||
if (!(WarehouseOrderRequestModel::get($id))) self::returnJson(['error' => 'Bestellwunsch nicht gefunden']);
|
||||
|
||||
WarehouseOrderRequestModel::update(['id' => $id, 'done' => $done]);
|
||||
self::returnJson(['success' => true]);
|
||||
}
|
||||
|
||||
protected function createNewLogAction() {
|
||||
$postData = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
|
||||
class WarehouseOrderRequestModel extends TTCrudBaseModel {
|
||||
public int $id;
|
||||
public ?int $addressId;
|
||||
public string $purpose;
|
||||
public string $positions;
|
||||
public ?string $note;
|
||||
public ?string $linkedOrderIds;
|
||||
public ?int $cancelled;
|
||||
public ?int $done;
|
||||
public int $create;
|
||||
public int $createBy;
|
||||
}
|
||||
|
||||
@@ -223,8 +223,8 @@ class WarehouseShippingNoteController extends TTCrud {
|
||||
}
|
||||
}
|
||||
|
||||
protected function createPDFAction() {
|
||||
$id = $this->request->id;
|
||||
public function createPDFAction($returnFilename = false, $idOverride = null) {
|
||||
$id = $idOverride ?? $this->request->id;
|
||||
if (strlen($id) < 1) {
|
||||
http_response_code(500);
|
||||
self::returnJson(['success' => false, 'message' => 'Lieferschein wurde nicht gefunden']);
|
||||
@@ -376,10 +376,13 @@ class WarehouseShippingNoteController extends TTCrud {
|
||||
$wkhtmltopdfArgs = "--header-html $headerFile --footer-html $footerFile";
|
||||
$filename = $pdf->render($wkhtmltopdfArgs);
|
||||
|
||||
if ($returnFilename === true) return $filename;
|
||||
|
||||
// return the pdf and die so the client sees the pdf not the filename
|
||||
header('Content-Type: application/pdf');
|
||||
header('Content-Disposition: inline; filename="' . $filename . '"');
|
||||
readfile($filename);
|
||||
die();
|
||||
}
|
||||
|
||||
// TODO: either move this to UserController or make it better
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"textalk/websocket": "^1.6",
|
||||
"chillerlan/php-qrcode": "^4",
|
||||
"phpseclib/phpseclib": "^3.0",
|
||||
"stomp-php/stomp-php": "^5"
|
||||
"stomp-php/stomp-php": "^5",
|
||||
"phpmailer/phpmailer": "^6.9"
|
||||
}
|
||||
}
|
||||
|
||||
37
db/migrations/20250307080000_warehouse_modify_14.php
Normal file
37
db/migrations/20250307080000_warehouse_modify_14.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
use Phinx\Migration\AbstractMigration;
|
||||
|
||||
final class WarehouseModify14 extends AbstractMigration {
|
||||
public function up(): void {
|
||||
if ($this->getEnvironment() == "thetool") {
|
||||
// Modify WarehouseOrderRequest
|
||||
$orderRequestTable = $this->table("WarehouseOrderRequest");
|
||||
$orderRequestTable
|
||||
->addColumn("done", "integer", ['default' => 0])
|
||||
->addColumn("addressId", "integer", ['default' => null, 'null' => true])
|
||||
->save();
|
||||
|
||||
// Modify WarehouseOrder
|
||||
$orderTable = $this->table("WarehouseOrder");
|
||||
$orderTable
|
||||
->addColumn("sendShippingNote", "integer", ['default' => 0])
|
||||
->save();
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void {
|
||||
if ($this->getEnvironment() == "thetool") {
|
||||
$orderRequestTable = $this->table("WarehouseOrderRequest");
|
||||
$orderRequestTable
|
||||
->removeColumn("done")
|
||||
->removeColumn("addressId")
|
||||
->save();
|
||||
|
||||
$orderTable = $this->table("WarehouseOrder");
|
||||
$orderTable
|
||||
->removeColumn("sendShippingNote")
|
||||
->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -127,3 +127,56 @@
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
|
||||
.ios-switch-wrapper {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 50px;
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
.ios-switch-wrapper.disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.ios-switch-wrapper input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.ios-switch-slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: #ccc;
|
||||
transition: .4s;
|
||||
border-radius: 34px;
|
||||
}
|
||||
|
||||
.ios-switch-slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
left: 4px;
|
||||
bottom: 4px;
|
||||
background-color: white;
|
||||
transition: .4s;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
input:checked + .ios-switch-slider {
|
||||
background-color: #4cd964;
|
||||
}
|
||||
|
||||
input:checked + .ios-switch-slider:before {
|
||||
transform: translateX(22px);
|
||||
}
|
||||
|
||||
input:disabled + .ios-switch-slider {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
@@ -5,11 +5,15 @@ Vue.component('change-status-modal', {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
order: null,
|
||||
newStatus: 'noChanges',
|
||||
note: '',
|
||||
file: null,
|
||||
uploadedFiles: []
|
||||
window: window,
|
||||
order: null,
|
||||
newStatus: 'noChanges',
|
||||
note: '',
|
||||
file: null,
|
||||
uploadedFiles: [],
|
||||
sendEmail: false,
|
||||
sendEmailViewedPDF: false,
|
||||
sendEmailMail: '',
|
||||
};
|
||||
},
|
||||
async mounted() {
|
||||
@@ -101,12 +105,25 @@ Vue.component('change-status-modal', {
|
||||
},
|
||||
removeFile: index => this.uploadedFiles.splice(index, 1),
|
||||
async submit() {
|
||||
if (this.newStatus === 'accepted' && this.sendEmail && !this.sendEmailMail) {
|
||||
window.notify('error', 'Bitte geben Sie eine E-Mail-Adresse ein');
|
||||
return;
|
||||
} else if (this.newStatus === 'accepted' && this.sendEmail && !this.sendEmailViewedPDF) {
|
||||
window.notify('error', 'Bitte öffnen Sie das PDF bevor Sie die E-Mail senden');
|
||||
return;
|
||||
} else if (this.newStatus === 'accepted' && this.sendEmail && this.sendEmailMail) {
|
||||
if (this.order.sendShippingNote > 0) {
|
||||
const shippingNoteId = await this.generateShippingNote();
|
||||
await this.submitEmail(shippingNoteId);
|
||||
} else await this.submitEmail();
|
||||
}
|
||||
|
||||
const fileIds = this.uploadedFiles.map(file => file.id);
|
||||
const response = await axios.post(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseOrder/createNewLogAction`, {
|
||||
orderId: this.order.id,
|
||||
status: this.newStatus,
|
||||
note: this.note,
|
||||
fileIds: JSON.stringify(fileIds)
|
||||
fileIds: JSON.stringify(fileIds),
|
||||
});
|
||||
|
||||
if (response.data.success) {
|
||||
@@ -116,6 +133,39 @@ Vue.component('change-status-modal', {
|
||||
window.notify('error',
|
||||
response.data.errors ? Object.values(response.data.errors).join('<br>') : response.data.message || 'Ein Fehler ist aufgetreten');
|
||||
}
|
||||
},
|
||||
async generateShippingNote() {
|
||||
const positions = this.order.positions.map(position => ({
|
||||
article: position.article,
|
||||
amount: position.amount,
|
||||
price: position.buyPrice
|
||||
}));
|
||||
|
||||
const response = await axios.post(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseShippingNote/create`,
|
||||
{
|
||||
"billingAddressId": this.order.sendShippingNote,
|
||||
"deliveryAddressName": this.order.delAddrName,
|
||||
"deliveryAddressLine": this.order.delAddrLine,
|
||||
"deliveryAddressPLZ": this.order.delAddrPLZ,
|
||||
"deliveryAddressCity": this.order.delAddrCity,
|
||||
"deliveryAddressEMail": this.order.delAddrEMail,
|
||||
"textElements": [],
|
||||
"hoursEntries": [],
|
||||
"positions": positions,
|
||||
"note": "Bestellung #" + this.order.orderNumber,
|
||||
"status": "new"
|
||||
});
|
||||
|
||||
return response.data.id;
|
||||
},
|
||||
async submitEmail(shippingNote = null) {
|
||||
const response = await axios.get(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseOrder/sendEmail?id=${this.order.id}&email=${this.sendEmailMail}${shippingNote ? '&shippingNote=' + shippingNote : ''}`);
|
||||
|
||||
if (response.data.success) {
|
||||
window.notify('success', response.data.message);
|
||||
} else {
|
||||
window.notify('error', response.data.message || 'Ein Fehler ist aufgetreten');
|
||||
}
|
||||
}
|
||||
},
|
||||
template: `
|
||||
@@ -163,6 +213,32 @@ Vue.component('change-status-modal', {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="newStatus === 'accepted'">
|
||||
<h4>E-Mail verschicken?</h4>
|
||||
|
||||
<tt-button
|
||||
additional-class="btn-outline-primary"
|
||||
icon="fa fa-file-pdf text-danger"
|
||||
text="PDF anzeigen"
|
||||
@click="sendEmailViewedPDF = true;window.open(window.TT_CONFIG.BASE_PATH + '/WarehouseOrder/createPDF?id=' + order.id)"
|
||||
></tt-button>
|
||||
|
||||
<div class="mt-2 d-flex align-items-center">
|
||||
<div class="mr-2">E-Mail senden:</div>
|
||||
<label class="ios-switch-wrapper" :class="{'disabled': !sendEmailViewedPDF}">
|
||||
<input
|
||||
type="checkbox"
|
||||
v-model="sendEmail"
|
||||
:disabled="!sendEmailViewedPDF"
|
||||
>
|
||||
<span class="ios-switch-slider"></span>
|
||||
</label>
|
||||
<div v-if="!sendEmailViewedPDF" class="text-muted ml-2 text-danger">
|
||||
Bitte erst PDF ansehen
|
||||
</div>
|
||||
</div>
|
||||
<tt-input v-if="sendEmail" label="E-Mail-Adresse" v-model="sendEmailMail" sm row/>
|
||||
</div>
|
||||
</template>
|
||||
</tt-modal>
|
||||
|
||||
@@ -210,6 +286,24 @@ Vue.component('warehouse-order-modal', {
|
||||
<tt-input label="E-Mail" v-model="order.delAddrEMail" sm/>
|
||||
</div>
|
||||
|
||||
<template v-if="id === 'create' && order.delAddrLine !== 'Fladnitz im Raabtal 150'">
|
||||
<div class="mt-2 d-flex align-items-center">
|
||||
<div class="mr-2">Lieferschein erstellen und anhängen?</div>
|
||||
<label class="ios-switch-wrapper">
|
||||
<input type="checkbox" v-model="showSendShippingNote">
|
||||
<span class="ios-switch-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
<template v-if="showSendShippingNote">
|
||||
<hr>
|
||||
<tt-autocomplete v-model="order.sendShippingNote"
|
||||
:api-url="window.TT_CONFIG['BASE_PATH'] + '/Address/Api?do=findAddress&fibu_primary_account=1'"
|
||||
label="Rechnungsadresse"
|
||||
sm
|
||||
row/>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<hr>
|
||||
<tt-textarea label="Notiz" v-model="order.note" sm row/>
|
||||
</div>
|
||||
@@ -218,8 +312,9 @@ Vue.component('warehouse-order-modal', {
|
||||
|
||||
data() {
|
||||
return {
|
||||
window: window,
|
||||
positionsConfig: {
|
||||
window: window,
|
||||
showSendShippingNote: false,
|
||||
positionsConfig: {
|
||||
customOrdering: 'distributorId',
|
||||
fields: {
|
||||
article: {
|
||||
@@ -252,16 +347,17 @@ Vue.component('warehouse-order-modal', {
|
||||
return true;
|
||||
},
|
||||
},
|
||||
order: {
|
||||
extReference: '',
|
||||
delAddrName: 'XINON GmbH',
|
||||
delAddrLine: 'Fladnitz im Raabtal 150',
|
||||
delAddrPLZ: '8322',
|
||||
delAddrCity: 'Studenzen',
|
||||
delAddrEMail: 'einkauf@xinon.at',
|
||||
note: '',
|
||||
editor: window.TT_CONFIG['USER_ID'],
|
||||
positions: [],
|
||||
order: {
|
||||
extReference: '',
|
||||
delAddrName: 'XINON GmbH',
|
||||
delAddrLine: 'Fladnitz im Raabtal 150',
|
||||
delAddrPLZ: '8322',
|
||||
delAddrCity: 'Studenzen',
|
||||
delAddrEMail: 'einkauf@xinon.at',
|
||||
note: '',
|
||||
editor: window.TT_CONFIG['USER_ID'],
|
||||
sendShippingNote: false,
|
||||
positions: [],
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -365,20 +461,20 @@ Vue.component('warehouse-order-modal', {
|
||||
});
|
||||
|
||||
Vue.component('tt-file', {
|
||||
props: ['id'],
|
||||
data: () => ({file: null}),
|
||||
props: ['id'],
|
||||
data: () => ({file: null}),
|
||||
async mounted() {
|
||||
const response = await axios.get(`${window.TT_CONFIG.BASE_PATH}/File/getById`, {params: {id: this.id}});
|
||||
this.file = response.data;
|
||||
},
|
||||
template: `
|
||||
<div>
|
||||
<a :href="'/File/download?id=' + id" target="_blank" v-if="file">{{ file.filename }}</a>
|
||||
<a :href="'/File/download?id=' + id" target="_blank" v-if="file">{{ file.filename }}</a>
|
||||
<template v-else>
|
||||
<div class="spinner-border spinner-border-sm text-primary" role="status"><span class="sr-only">Loading...</span></div>
|
||||
</template>
|
||||
</div>
|
||||
`
|
||||
`
|
||||
})
|
||||
|
||||
|
||||
|
||||
@@ -19,7 +19,19 @@ window.TT_CONFIG["CRUD_CONFIG"]["additionalActions"] = [
|
||||
condition: (row) => window.TT_CONFIG['WAREHOUSE_ADMIN'] === '1'
|
||||
&& row.cancelled === 0 && (!row.linkedOrderIds || row.linkedOrderIds.length === 0)
|
||||
&& JSON.parse(row.positions).filter(position => position.articleId_text).length === 0,
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "doneOrder",
|
||||
title: "Bestellwunsch erledigt",
|
||||
class: "fas fa-check text-success",
|
||||
condition: (row) => window.TT_CONFIG['WAREHOUSE_ADMIN'] === '1' && row.done === 0,
|
||||
},
|
||||
{
|
||||
key: "undoneOrder",
|
||||
title: "Bestellwunsch wieder offen",
|
||||
class: "fas fa-times text-danger",
|
||||
condition: (row) => window.TT_CONFIG['WAREHOUSE_ADMIN'] === '1' && row.done === 1,
|
||||
},
|
||||
]
|
||||
|
||||
Vue.component('add-log-modal', {
|
||||
@@ -188,6 +200,8 @@ Vue.component('warehouse-order-request', {
|
||||
<tt-table-crud @openHistory="openHistory"
|
||||
@cancelRequest="cancelRequest"
|
||||
@uncancelRequest="uncancelRequest"
|
||||
@doneOrder="doneOrder"
|
||||
@undoneOrder="undoneOrder"
|
||||
@createLog="createLog"
|
||||
@createOrder="createOrder"
|
||||
ref="crud">
|
||||
@@ -232,6 +246,19 @@ Vue.component('warehouse-order-request', {
|
||||
res.data.message || (res.data.success ? 'Erfolgreich aktualisiert' : 'Fehler beim aktualisieren'));
|
||||
if (res.data.success) this.$refs.crud.$refs.table.refreshTable();
|
||||
},
|
||||
async doneRequest(row, done) {
|
||||
if (!confirm('Bestellwunsch wirklich als erledigt markieren?')) return;
|
||||
const res = await axios.get(`${window.TT_CONFIG.BASE_PATH}/WarehouseOrderRequest/done?id=${row.id}&done=${done}`);
|
||||
window.notify(res.data.success ? 'success' : 'error',
|
||||
res.data.message || (res.data.success ? 'Erfolgreich aktualisiert' : 'Fehler beim aktualisieren'));
|
||||
if (res.data.success) this.$refs.crud.$refs.table.refreshTable();
|
||||
},
|
||||
async doneOrder(row) {
|
||||
this.doneRequest(row, '1');
|
||||
},
|
||||
async undoneOrder(row) {
|
||||
this.doneRequest(row, '0');
|
||||
},
|
||||
async createLog(row) {
|
||||
this.addLogModal = true;
|
||||
this.addLogModalId = row.id;
|
||||
|
||||
Reference in New Issue
Block a user