453 lines
18 KiB
PHP
453 lines
18 KiB
PHP
<?php
|
|
|
|
namespace App\Repositories;
|
|
|
|
use App\Libraries\InvoicePDF;
|
|
use App\Libraries\MyPDFMerger;
|
|
use App\Models\ShoppingOrder;
|
|
use App\Models\UserInvoice;
|
|
use App\Models\UserSalesVolume;
|
|
use App\Services\BusinessPlan\SalesPointsVolume;
|
|
use App\Services\Incentive\IncentiveTracker;
|
|
use App\Services\Invoice;
|
|
use App\Services\UserService;
|
|
use App\Services\Util;
|
|
use Storage;
|
|
|
|
class InvoiceRepository extends BaseRepository
|
|
{
|
|
/** @var \App\Models\ShoppingOrder */
|
|
protected $model;
|
|
|
|
private $invoice_date;
|
|
|
|
private $invoice_number;
|
|
|
|
private $filename;
|
|
|
|
private $dir;
|
|
|
|
private $user_sales_volume;
|
|
|
|
private $delivery_dir;
|
|
|
|
private $delivery_filename;
|
|
|
|
public function __construct(ShoppingOrder $model)
|
|
{
|
|
$this->model = $model;
|
|
}
|
|
|
|
public function create($request = [])
|
|
{
|
|
// Wrap entire invoice creation in transaction to ensure atomicity
|
|
return \DB::transaction(function () use ($request) {
|
|
// Get and increment invoice number atomically (includes its own lock)
|
|
$number = Invoice::makeNextInvoiceNumber();
|
|
|
|
if ($payt = $this->model->getLastShoppingPaymentTransaction()) {
|
|
$invoice_date = $payt->created_at->format('d.m.Y');
|
|
}
|
|
$this->invoice_date = isset($request['invoice_date']) ? $request['invoice_date'] : $invoice_date;
|
|
$invoice_send_mail = isset($request['invoice_send_mail']) && $request['invoice_send_mail'] ? true : false;
|
|
$this->invoice_number = Invoice::createInvoiceNumber($number, $this->invoice_date);
|
|
$this->dir = Invoice::getInvoiceStorageDir($this->invoice_date);
|
|
$this->filename = Invoice::makeInvoiceFilename($this->invoice_number);
|
|
$this->delivery_dir = Invoice::getDeliveryStorageDir($this->invoice_date);
|
|
$this->delivery_filename = Invoice::makeDeliveryFilename($this->invoice_number);
|
|
|
|
$this->makePDF();
|
|
|
|
$user_invoice = UserInvoice::create([
|
|
'shopping_order_id' => $this->model->id,
|
|
'year' => \Carbon::parse($this->invoice_date)->format('Y'),
|
|
'month' => \Carbon::parse($this->invoice_date)->format('m'),
|
|
'date' => $this->invoice_date,
|
|
'full_number' => $this->invoice_number,
|
|
'number' => $number,
|
|
'filename' => $this->filename,
|
|
'dir' => $this->dir,
|
|
'delivery_filename' => $this->delivery_filename,
|
|
'delivery_dir' => $this->delivery_dir,
|
|
'disk' => 'public',
|
|
'status' => $this->model->getStatusByOrder(),
|
|
]);
|
|
|
|
if ($invoice_send_mail) {
|
|
Invoice::sendInvoiceMail($this->model, $user_invoice);
|
|
}
|
|
|
|
return $user_invoice;
|
|
});
|
|
}
|
|
|
|
public function update($request = [])
|
|
{
|
|
if ($user_invoice = $this->model->user_invoice) {
|
|
$number = $user_invoice->number;
|
|
$this->invoice_date = isset($request['invoice_date']) ? $request['invoice_date'] : $user_invoice->date;
|
|
$invoice_send_mail = isset($request['invoice_send_mail']) ? false : true;
|
|
$this->invoice_number = Invoice::createInvoiceNumber($number, $this->invoice_date);
|
|
$this->dir = Invoice::getInvoiceStorageDir($this->invoice_date);
|
|
$this->filename = Invoice::makeInvoiceFilename($this->invoice_number);
|
|
$this->delivery_dir = Invoice::getDeliveryStorageDir($this->invoice_date);
|
|
$this->delivery_filename = Invoice::makeDeliveryFilename($this->invoice_number);
|
|
|
|
$this->user_sales_volume = UserSalesVolume::where('user_invoice_id', $this->model->user_invoice->id)->first();
|
|
$this->makePDF();
|
|
|
|
$user_invoice->fill([
|
|
'shopping_order_id' => $this->model->id,
|
|
'year' => \Carbon::parse($this->invoice_date)->format('Y'),
|
|
'month' => \Carbon::parse($this->invoice_date)->format('m'),
|
|
'date' => $this->invoice_date,
|
|
'full_number' => $this->invoice_number,
|
|
'number' => $number,
|
|
'filename' => $this->filename,
|
|
'dir' => $this->dir,
|
|
'delivery_filename' => $this->delivery_filename,
|
|
'delivery_dir' => $this->delivery_dir,
|
|
'disk' => 'public',
|
|
])->save();
|
|
|
|
if ($invoice_send_mail) {
|
|
Invoice::sendInvoiceMail($this->model, $user_invoice);
|
|
}
|
|
|
|
return $user_invoice;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Erstellt die PDFs für Rechnung und Lieferschein.
|
|
* Das deutsche Original wird immer erstellt (Finanzamt-Anforderung).
|
|
* Bei anderer Kundensprache wird zusätzlich eine Kopie in der Kundensprache erstellt.
|
|
*/
|
|
private function makePDF()
|
|
{
|
|
$data = [
|
|
'shopping_order' => $this->model,
|
|
'invoice_date' => $this->invoice_date,
|
|
'invoice_number' => $this->invoice_number,
|
|
'user_sales_volume' => $this->user_sales_volume,
|
|
];
|
|
|
|
if ($this->model->auth_user_id) {
|
|
UserService::checkUserTaxShippingCountry($this->model->auth_user, $this->model->country_id);
|
|
$data = array_merge($data, UserService::getYardInfo());
|
|
}
|
|
|
|
if (! Storage::disk('public')->exists($this->dir)) {
|
|
Storage::disk('public')->makeDirectory($this->dir);
|
|
}
|
|
if (! Storage::disk('public')->exists($this->delivery_dir)) {
|
|
Storage::disk('public')->makeDirectory($this->delivery_dir);
|
|
}
|
|
|
|
// Kundensprache ermitteln
|
|
$customerLocale = $this->model->shopping_user ? $this->model->shopping_user->getLocale() : 'de';
|
|
$originalLocale = \App::getLocale();
|
|
|
|
// 1. IMMER deutsches Original erstellen (Finanzamt-Anforderung)
|
|
\App::setLocale('de');
|
|
$this->createPDFFiles($data, 'de');
|
|
|
|
// 2. Wenn Kundensprache != DE, Kopie in Kundensprache erstellen
|
|
if ($customerLocale && $customerLocale !== 'de') {
|
|
\App::setLocale($customerLocale);
|
|
$this->createPDFFiles($data, $customerLocale);
|
|
}
|
|
|
|
// Locale zurücksetzen
|
|
\App::setLocale($originalLocale);
|
|
}
|
|
|
|
/**
|
|
* Erstellt die PDF-Dateien für eine bestimmte Sprache.
|
|
*/
|
|
private function createPDFFiles(array $data, string $locale)
|
|
{
|
|
$path = Storage::disk('public')->path('');
|
|
|
|
// Dateinamen für diese Sprache
|
|
$invoiceFilename = Invoice::makeInvoiceFilenameLocale($this->invoice_number, $locale);
|
|
$deliveryFilename = Invoice::makeDeliveryFilenameLocale($this->invoice_number, $locale);
|
|
|
|
// Kopie-Flag: true wenn nicht Deutsch (das Original)
|
|
$data['is_copy'] = ($locale !== 'de');
|
|
|
|
// Template basierend auf Locale
|
|
$template = $this->getTemplateForLocale($locale);
|
|
|
|
// Rechnung erstellen
|
|
$pdf_file = new InvoicePDF('pdf.invoice');
|
|
$pdf_file->create($data, $invoiceFilename, 'save', $path.$this->dir);
|
|
$pdfMerger = new MyPDFMerger;
|
|
$pdfMerger->addPDF($path.$this->dir.$invoiceFilename);
|
|
$file = $pdfMerger->myMerge('string', $invoiceFilename, $template);
|
|
Storage::disk('public')->put($this->dir.$invoiceFilename, $file);
|
|
|
|
// Lieferschein erstellen (außer bei Sammelbestellung)
|
|
if (! $this->model->shopping_collect_order) {
|
|
$pdf_file = new InvoicePDF('pdf.delivery');
|
|
$pdf_file->create($data, $deliveryFilename, 'save', $path.$this->delivery_dir);
|
|
$pdfMerger = new MyPDFMerger;
|
|
$pdfMerger->addPDF($path.$this->delivery_dir.$deliveryFilename);
|
|
$file = $pdfMerger->myMerge('string', $deliveryFilename, $template);
|
|
Storage::disk('public')->put($this->delivery_dir.$deliveryFilename, $file);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gibt das PDF-Template für die angegebene Locale zurück.
|
|
* Verfügbare Templates werden aus config/localization.php geladen.
|
|
*/
|
|
private function getTemplateForLocale(string $locale): string
|
|
{
|
|
$availableTemplates = config('localization.availableTemplates', ['de']);
|
|
|
|
if (in_array($locale, $availableTemplates)) {
|
|
return 'template_invoice_'.$locale;
|
|
}
|
|
|
|
return 'template_invoice_de';
|
|
}
|
|
|
|
public function userSalesVolume() {}
|
|
|
|
public function createAndSalesVolume($request = [])
|
|
{
|
|
$this->user_sales_volume = SalesPointsVolume::User($this->model);
|
|
if (! Util::isTestSystem(true)) { // rechnung erstellen nur in production
|
|
$user_invoice = $this->create($request);
|
|
$this->user_sales_volume->user_invoice_id = $user_invoice->id;
|
|
$this->user_sales_volume->save();
|
|
}
|
|
|
|
// Incentive: Track sales volume points
|
|
IncentiveTracker::trackSalesVolume($this->user_sales_volume);
|
|
}
|
|
|
|
/**
|
|
* Erstellt eine Stornorechnung mit Punktekorrektur
|
|
*
|
|
* @param array $request
|
|
* @return UserInvoice
|
|
*/
|
|
public function createCancellation($request = [])
|
|
{
|
|
return \DB::transaction(function () use ($request) {
|
|
$original_invoice = $this->model->user_invoice;
|
|
|
|
if (! $original_invoice) {
|
|
throw new \Exception('Keine Originalrechnung gefunden.');
|
|
}
|
|
|
|
// Nächste Rechnungsnummer für Storno holen
|
|
$number = Invoice::makeNextInvoiceNumber();
|
|
|
|
// Stornodatum
|
|
$cancellation_date = isset($request['cancellation_date'])
|
|
? $request['cancellation_date']
|
|
: now()->format('d.m.Y');
|
|
|
|
$cancellation_send_mail = isset($request['cancellation_send_mail']) && $request['cancellation_send_mail'] ? true : false;
|
|
|
|
// Rechnungsnummer erstellen
|
|
$cancellation_number = Invoice::createInvoiceNumber($number, $cancellation_date);
|
|
$cancellation_dir = Invoice::getInvoiceStorageDir($cancellation_date);
|
|
$cancellation_filename = Invoice::makeCancellationFilename($cancellation_number);
|
|
$cancellation_delivery_dir = Invoice::getDeliveryStorageDir($cancellation_date);
|
|
$cancellation_delivery_filename = Invoice::makeCancellationDeliveryFilename($cancellation_number);
|
|
|
|
// Stornorechnung PDF erstellen
|
|
$this->makeCancellationPDF(
|
|
$cancellation_date,
|
|
$cancellation_number,
|
|
$cancellation_dir,
|
|
$cancellation_filename,
|
|
$cancellation_delivery_dir,
|
|
$cancellation_delivery_filename,
|
|
$original_invoice
|
|
);
|
|
|
|
// Stornorechnung in DB speichern
|
|
$cancellation_invoice = UserInvoice::create([
|
|
'shopping_order_id' => $this->model->id,
|
|
'year' => \Carbon::parse($cancellation_date)->format('Y'),
|
|
'month' => \Carbon::parse($cancellation_date)->format('m'),
|
|
'date' => $cancellation_date,
|
|
'full_number' => $cancellation_number,
|
|
'number' => $number,
|
|
'filename' => $cancellation_filename,
|
|
'dir' => $cancellation_dir,
|
|
'delivery_filename' => $cancellation_delivery_filename,
|
|
'delivery_dir' => $cancellation_delivery_dir,
|
|
'disk' => 'public',
|
|
'cancellation' => true,
|
|
'status' => $original_invoice->status === 1 ? 11 : 12, // 11 = storniert B., 12 = storniert Shop
|
|
]);
|
|
|
|
// Original-Rechnung als storniert markieren
|
|
$original_invoice->cancellation = true;
|
|
$original_invoice->cancellation_id = $cancellation_invoice->id;
|
|
$original_invoice->cancellation_date = $cancellation_date;
|
|
$original_invoice->save();
|
|
|
|
// Bestellstatus auf "storniert" setzen
|
|
$this->model->txaction = 'cancelled';
|
|
// Versandstatus auf "storniert" (10) setzen, wenn noch nicht versendet
|
|
if (in_array($this->model->shipped, [0, 1])) {
|
|
$this->model->shipped = 10;
|
|
}
|
|
$this->model->save();
|
|
|
|
\Log::info('Bestellstatus aktualisiert nach Storno', [
|
|
'order_id' => $this->model->id,
|
|
'txaction' => $this->model->txaction,
|
|
'shipped' => $this->model->shipped,
|
|
]);
|
|
|
|
// Punktekorrektur durchführen (nach Erstellung der Stornorechnung)
|
|
$this->correctPointsForCancellation($original_invoice, $cancellation_invoice);
|
|
|
|
// Optional: E-Mail versenden
|
|
if ($cancellation_send_mail) {
|
|
Invoice::sendInvoiceMail($this->model, $cancellation_invoice);
|
|
}
|
|
|
|
return $cancellation_invoice;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Erstellt die Storno-PDFs (Rechnung und Lieferschein)
|
|
*/
|
|
private function makeCancellationPDF(
|
|
$cancellation_date,
|
|
$cancellation_number,
|
|
$cancellation_dir,
|
|
$cancellation_filename,
|
|
$cancellation_delivery_dir,
|
|
$cancellation_delivery_filename,
|
|
$original_invoice
|
|
) {
|
|
$data = [
|
|
'shopping_order' => $this->model,
|
|
'invoice_date' => $cancellation_date,
|
|
'invoice_number' => $cancellation_number,
|
|
'original_invoice' => $original_invoice,
|
|
'is_cancellation' => true,
|
|
];
|
|
|
|
if ($this->model->auth_user_id) {
|
|
UserService::checkUserTaxShippingCountry($this->model->auth_user, $this->model->country_id);
|
|
$data = array_merge($data, UserService::getYardInfo());
|
|
}
|
|
|
|
// Verzeichnisse erstellen
|
|
if (! Storage::disk('public')->exists($cancellation_dir)) {
|
|
Storage::disk('public')->makeDirectory($cancellation_dir);
|
|
}
|
|
if (! Storage::disk('public')->exists($cancellation_delivery_dir)) {
|
|
Storage::disk('public')->makeDirectory($cancellation_delivery_dir);
|
|
}
|
|
|
|
// Kundensprache ermitteln
|
|
$customerLocale = $this->model->shopping_user ? $this->model->shopping_user->getLocale() : 'de';
|
|
$originalLocale = \App::getLocale();
|
|
|
|
// Deutsches Original (Finanzamt-Anforderung)
|
|
\App::setLocale('de');
|
|
$this->createCancellationPDFFiles(
|
|
$data,
|
|
'de',
|
|
$cancellation_number,
|
|
$cancellation_dir,
|
|
$cancellation_filename,
|
|
$cancellation_delivery_dir,
|
|
$cancellation_delivery_filename
|
|
);
|
|
|
|
// Lokalisierte Version wenn gewünscht
|
|
if ($customerLocale && $customerLocale !== 'de') {
|
|
\App::setLocale($customerLocale);
|
|
$data['is_copy'] = true;
|
|
$localizedFilename = str_replace('.pdf', '-'.$customerLocale.'.pdf', $cancellation_filename);
|
|
$localizedDeliveryFilename = str_replace('.pdf', '-'.$customerLocale.'.pdf', $cancellation_delivery_filename);
|
|
|
|
$this->createCancellationPDFFiles(
|
|
$data,
|
|
$customerLocale,
|
|
$cancellation_number,
|
|
$cancellation_dir,
|
|
$localizedFilename,
|
|
$cancellation_delivery_dir,
|
|
$localizedDeliveryFilename
|
|
);
|
|
}
|
|
|
|
\App::setLocale($originalLocale);
|
|
}
|
|
|
|
/**
|
|
* Erstellt die PDF-Dateien für eine Stornorechnung in einer bestimmten Sprache
|
|
*/
|
|
private function createCancellationPDFFiles(
|
|
array $data,
|
|
string $locale,
|
|
string $cancellation_number,
|
|
string $cancellation_dir,
|
|
string $cancellation_filename,
|
|
string $cancellation_delivery_dir,
|
|
string $cancellation_delivery_filename
|
|
) {
|
|
$path = Storage::disk('public')->path('');
|
|
$template = $this->getTemplateForLocale($locale);
|
|
|
|
// Stornorechnung erstellen
|
|
$pdf_file = new InvoicePDF('pdf.cancellation');
|
|
$pdf_file->create($data, $cancellation_filename, 'save', $path.$cancellation_dir);
|
|
$pdfMerger = new MyPDFMerger;
|
|
$pdfMerger->addPDF($path.$cancellation_dir.$cancellation_filename);
|
|
$file = $pdfMerger->myMerge('string', $cancellation_filename, $template);
|
|
Storage::disk('public')->put($cancellation_dir.$cancellation_filename, $file);
|
|
|
|
// Storno-Lieferschein erstellen (außer bei Sammelbestellung)
|
|
if (! $this->model->shopping_collect_order) {
|
|
$pdf_file = new InvoicePDF('pdf.cancellation_delivery');
|
|
$pdf_file->create($data, $cancellation_delivery_filename, 'save', $path.$cancellation_delivery_dir);
|
|
$pdfMerger = new MyPDFMerger;
|
|
$pdfMerger->addPDF($path.$cancellation_delivery_dir.$cancellation_delivery_filename);
|
|
$file = $pdfMerger->myMerge('string', $cancellation_delivery_filename, $template);
|
|
Storage::disk('public')->put($cancellation_delivery_dir.$cancellation_delivery_filename, $file);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Korrigiert die Punkte nach Stornierung einer Rechnung
|
|
* Nutzt den SalesPointsVolume Service für konsistente Berechnung
|
|
*
|
|
* @param UserInvoice $original_invoice Die ursprüngliche Rechnung
|
|
* @param UserInvoice $cancellation_invoice Die Stornorechnung
|
|
*/
|
|
private function correctPointsForCancellation($original_invoice, $cancellation_invoice)
|
|
{
|
|
// Original UserSalesVolume finden
|
|
$original_sales_volume = UserSalesVolume::where('user_invoice_id', $original_invoice->id)->first();
|
|
|
|
if (! $original_sales_volume) {
|
|
\Log::warning('Keine UserSalesVolume gefunden für Rechnung', [
|
|
'invoice_id' => $original_invoice->id,
|
|
'order_id' => $this->model->id,
|
|
]);
|
|
|
|
return;
|
|
}
|
|
|
|
// Service-Methode verwenden für konsistente Punktekorrektur
|
|
SalesPointsVolume::cancelSalesPointsVolume($original_sales_volume, $cancellation_invoice->id);
|
|
}
|
|
}
|