added stuff to manualinvoice

This commit is contained in:
Luca Haid
2026-01-13 07:07:03 +01:00
parent 4ed7a7e4af
commit 7cc3c79174
5 changed files with 512 additions and 35 deletions

View File

@@ -208,8 +208,6 @@ class ManualInvoiceController extends TTCrud
$post = json_decode(file_get_contents('php://input'), true);
$id = $post['id'] ?? null;
$recipientEmail = $post['email'] ?? null;
$subject = $post['subject'] ?? 'Ihre Rechnung von XINON GmbH';
$bodyText = $post['body'] ?? 'Sehr geehrte Damen und Herren,\n\nanbei erhalten Sie Ihre Rechnung.\n\nMit freundlichen Grüßen\nIhr Xinon Team';
if (!$id || !$recipientEmail) {
self::returnJson(['success' => false, 'message' => 'ID oder E-Mail-Adresse fehlt']);
@@ -222,6 +220,19 @@ class ManualInvoiceController extends TTCrud
return;
}
// Format invoice date for display
$invoiceDateFormatted = date('d.m.Y', $invoice->invoice_date);
// Set default subject and body with invoice number and date
$defaultSubject = "Ihre Rechnung {$invoice->invoice_number} vom {$invoiceDateFormatted}";
$defaultBody = "Sehr geehrte Damen und Herren,\n\nanbei erhalten Sie Ihre Rechnung Nr. {$invoice->invoice_number} vom {$invoiceDateFormatted}.\n\nMit freundlichen Grüßen\nIhr XINON Team";
$subject = $post['subject'] ?? $defaultSubject;
$bodyText = $post['body'] ?? $defaultBody;
// Convert literal \n strings to actual newlines (in case frontend sends escaped strings)
$bodyText = str_replace('\n', "\n", $bodyText);
// Generate PDF
$pdf_filename = $this->createPDFAction(true);
if (!$pdf_filename || !file_exists($pdf_filename)) {
@@ -232,19 +243,33 @@ class ManualInvoiceController extends TTCrud
$pdfContent = file_get_contents($pdf_filename);
// --- 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>Rechnung</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);">';
// Construct HTML Body with Outlook compatibility
$html = '<!DOCTYPE html>';
$html .= '<html lang="de" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">';
$html .= '<head>';
$html .= '<meta charset="UTF-8">';
$html .= '<meta http-equiv="X-UA-Compatible" content="IE=edge">';
$html .= '<meta name="viewport" content="width=device-width, initial-scale=1.0">';
$html .= '<title>Rechnung</title>';
$html .= '<!--[if mso]><xml><o:OfficeDocumentSettings><o:PixelsPerInch>96</o:PixelsPerInch></o:OfficeDocumentSettings></xml><![endif]-->';
$html .= '<style>body { font-family: Arial, sans-serif; color: #333; margin: 0; padding: 0; }</style>';
$html .= '</head>';
$html .= '<body style="margin:0;padding:20px;background-color:#f3f4f6;">';
// 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;">';
// Outlook-safe container table
$html .= '<!--[if mso]><table role="presentation" width="600" cellspacing="0" cellpadding="0" border="0" align="center"><tr><td><![endif]-->';
$html .= '<div style="background-color:#fff;padding:20px;border-radius:8px;max-width:600px;margin:0 auto;">';
// Logo with Outlook-safe sizing
$html .= '<div style="text-align:center;margin-bottom:20px;border-bottom:1px solid #e5e7eb;padding-bottom:15px;">';
if ($logoXinonExists) {
$html .= '<!--[if mso]><table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0"><tr><td align="center"><![endif]-->';
$html .= '<img src="cid:logo_xinon" alt="XINON GmbH" width="150" height="50" style="display:block;width:150px;height:50px;max-width:150px;margin:0 auto;">';
$html .= '<!--[if mso]></td></tr></table><![endif]-->';
}
$html .= '</div>';
$html .= '<h2 style="color:#00558c;text-align:center;font-size:20px;margin-bottom:20px;">' . htmlspecialchars($subject) . '</h2>';
@@ -254,7 +279,9 @@ class ManualInvoiceController extends TTCrud
$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>';
$html .= '</div></div>';
$html .= '<!--[if mso]></td></tr></table><![endif]-->';
$html .= '</body></html>';
$mail = new PHPMailer(true);
try {
@@ -269,12 +296,11 @@ class ManualInvoiceController extends TTCrud
$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
$mail->Port = 587;
// Logos
if ($logoToolExists) $mail->addEmbeddedImage($logoToolPath, 'logo_thetool');
// Logo embedding
if ($logoXinonExists) $mail->addEmbeddedImage($logoXinonPath, 'logo_xinon');
$mail->addReplyTo('backoffice@xinon.at', 'XINON Backoffice');
$mail->setFrom('thetool@xinon.at', 'XINON TheTool');
$mail->setFrom('thetool@xinon.at', 'XINON GmbH - Rechnungswesen');
$customerName = trim(($invoice->company ?: '') . ' ' . $invoice->firstname . ' ' . $invoice->lastname);
$mail->addAddress($recipientEmail, $customerName);
@@ -283,7 +309,10 @@ class ManualInvoiceController extends TTCrud
$mail->Body = $html;
$mail->AltBody = strip_tags($bodyText);
$mail->addStringAttachment($pdfContent, $invoice->invoice_number . '_Rechnung.pdf', 'base64', 'application/pdf');
// Attachment filename: YYYY-MM-DD_InvoiceNumber_Rechnung.pdf
$invoiceDateFile = date('Y-m-d', $invoice->invoice_date);
$attachmentFilename = "{$invoiceDateFile}_{$invoice->invoice_number}_Rechnung.pdf";
$mail->addStringAttachment($pdfContent, $attachmentFilename, 'base64', 'application/pdf');
$mail->send();
@@ -349,20 +378,21 @@ class ManualInvoiceController extends TTCrud
$data['invoice_date'] = strtotime($data['invoice_date']);
}
$data = array_merge([
'invoice_number' => ManualInvoiceModel::getNextInvoiceNumber(),
'invoice_date' => $data['invoice_date'] ?? time(),
'status' => 'erstellt',
'fibu_payment_skonto' => 0,
'fibu_payment_skonto_rate' => 0,
'gesamtrabatt' => 0,
'total' => 0,
'total_gross' => 0,
'create_by' => $me->id,
'edit_by' => $me->id,
'create' => time(),
'edit' => time()
], $data);
// Always generate invoice number (override any null from frontend)
$data['invoice_number'] = ManualInvoiceModel::getNextInvoiceNumber();
$data['invoice_date'] = $data['invoice_date'] ?? time();
$data['status'] = 'erstellt';
$data['fibu_payment_skonto'] = $data['fibu_payment_skonto'] ?? 0;
$data['fibu_payment_skonto_rate'] = $data['fibu_payment_skonto_rate'] ?? 0;
$data['gesamtrabatt'] = $data['gesamtrabatt'] ?? 0;
$data['total'] = $data['total'] ?? 0;
$data['total_gross'] = $data['total_gross'] ?? 0;
$data['lock'] = 0;
$data['exported'] = 0;
$data['create_by'] = $me->id;
$data['edit_by'] = $me->id;
$data['create'] = time();
$data['edit'] = time();
return true;
}
@@ -389,9 +419,15 @@ class ManualInvoiceController extends TTCrud
unset($data['positions']);
}
if (isset($data['id']) && ($invoice = ManualInvoiceModel::get($data['id'])) && $invoice->status === 'exportiert') {
$this->infoMessages['update'] = 'Rechnung wurde bereits exportiert und kann nicht mehr bearbeitet werden';
return false;
if (isset($data['id']) && ($invoice = ManualInvoiceModel::get($data['id']))) {
if ($invoice->lock == 1) {
$this->infoMessages['update'] = 'Rechnung ist gesperrt und kann nicht bearbeitet werden';
return false;
}
if ($invoice->status === 'exportiert') {
$this->infoMessages['update'] = 'Rechnung wurde bereits exportiert und kann nicht mehr bearbeitet werden';
return false;
}
}
// Convert invoice_date from string to timestamp if needed
@@ -626,6 +662,12 @@ class ManualInvoiceController extends TTCrud
if (!$originalInvoiceId || empty($positions) || !($originalInvoice = ManualInvoiceModel::get($originalInvoiceId))) {
self::returnJson(['success' => false, 'message' => 'Ungültige Anfrage']);
return;
}
if ($originalInvoice->lock == 1) {
self::returnJson(['success' => false, 'message' => 'Originalrechnung ist gesperrt und kann nicht gutgeschrieben werden']);
return;
}
$me = new User();
@@ -673,6 +715,8 @@ class ManualInvoiceController extends TTCrud
'vatgroup_id' => $originalInvoice->vatgroup_id,
'credit_for_invoice_id' => $originalInvoiceId,
'status' => 'erstellt',
'lock' => 0,
'exported' => 0,
'create' => time(),
'edit' => time(),
'create_by' => $me->id,
@@ -681,6 +725,7 @@ class ManualInvoiceController extends TTCrud
if (!($creditInvoiceId = ManualInvoiceModel::create($invoiceData))) {
self::returnJson(['success' => false, 'message' => 'Fehler beim Erstellen der Gutschrift']);
return;
}
foreach ($positions as $pos) {
@@ -718,7 +763,11 @@ class ManualInvoiceController extends TTCrud
protected function beforeDelete(): bool {
if ($id = $this->request->id) {
$invoice = ManualInvoiceModel::get($id);
if ($invoice && $invoice->status === 'exported') {
if ($invoice && $invoice->lock == 1) {
$this->infoMessages['delete'] = 'Rechnung ist gesperrt und kann nicht gelöscht werden';
return false;
}
if ($invoice && ($invoice->status === 'exported' || $invoice->status === 'exportiert')) {
$this->infoMessages['delete'] = 'Rechnung wurde bereits exportiert und kann nicht gelöscht werden';
return false;
}
@@ -732,4 +781,49 @@ class ManualInvoiceController extends TTCrud
}
return true;
}
protected function getArticleVatInfoAction() {
$articleId = $_GET['article_id'] ?? null;
$vatarea = $_GET['vatarea'] ?? 'domestic';
if (!$articleId) {
self::returnJson(['success' => false, 'message' => 'Article ID required']);
return;
}
$article = WarehouseArticleModel::get($articleId);
if (!$article) {
self::returnJson(['success' => false, 'message' => 'Article not found']);
return;
}
// Map revenueAccount to vatgroup_id
// revenueAccount 0 = Dienstleistungen = vatgroup_id 2
// revenueAccount 1 = Handelswaren = vatgroup_id 3
$vatgroupId = $article->revenueAccount == 0 ? 2 : 3;
// Get vatrate for this vatgroup and area
$vatrate = VatrateModel::getFirst(['vatgroup_id' => $vatgroupId, 'area' => $vatarea]);
if (!$vatrate) {
self::returnJson(['success' => false, 'message' => 'Vatrate not found for vatgroup ' . $vatgroupId . ' and area ' . $vatarea]);
return;
}
self::returnJson([
'success' => true,
'article' => [
'id' => $article->id,
'title' => $article->title,
'articleNumber' => $article->articleNumber,
'description' => $article->description,
'revenueAccount' => $article->revenueAccount
],
'vatgroup_id' => $vatgroupId,
'fibu_cost_account' => $vatrate->account,
'fibu_cost_account_legacy' => $vatrate->legacy_account,
'fibu_taxcode' => $vatrate->taxcode,
'vatrate' => $vatrate->rate
]);
}
}