Invoice upload by robot
This commit is contained in:
@@ -40,7 +40,7 @@ class ChangeStatusCommand {
|
|||||||
$this->request->invoice->status = InvoiceStatus::INVOICE_STATUS_DELETED;
|
$this->request->invoice->status = InvoiceStatus::INVOICE_STATUS_DELETED;
|
||||||
break;
|
break;
|
||||||
case InvoiceStatus::INVOICE_STATUS_EXPORTED:
|
case InvoiceStatus::INVOICE_STATUS_EXPORTED:
|
||||||
//$this->request->invoice->status = InvoiceStatus::INVOICE_STATUS_EXPORTED;
|
$this->request->invoice->status = InvoiceStatus::INVOICE_STATUS_EXPORTED;
|
||||||
$this->request->invoice->upload_required = true;
|
$this->request->invoice->upload_required = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Invoice\Actions\UploadInvoice;
|
||||||
|
|
||||||
|
use App\Domains\Invoice\Actions\CreateInvoiceReceipt\CreateInvoiceReceiptCommand;
|
||||||
|
use App\Domains\Invoice\Actions\CreateInvoiceReceipt\CreateInvoiceReceiptRequest;
|
||||||
|
use App\Providers\WebDavProvider;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
class UploadInvoiceCommand {
|
||||||
|
private UploadInvoiceRequest $request;
|
||||||
|
public function __construct(UploadInvoiceRequest $request) {
|
||||||
|
$this->request = $request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute() : UploadInvoiceResponse {
|
||||||
|
$uploadResponse = new UploadInvoiceResponse();
|
||||||
|
|
||||||
|
$uploadDir = sprintf(
|
||||||
|
'%1$s%2$s/%3$s',
|
||||||
|
WebDavProvider::INVOICE_PREFIX,
|
||||||
|
app('tenant')->url,
|
||||||
|
$this->request->invoice->costUnit()->first()->name
|
||||||
|
);
|
||||||
|
|
||||||
|
$webDavProvider = new WebDavProvider($uploadDir);
|
||||||
|
|
||||||
|
$createInvoiceReceiptRequest = new CreateInvoiceReceiptRequest($this->request->invoice);
|
||||||
|
$createInvoiceReceiptCommand = new CreateInvoiceReceiptCommand($createInvoiceReceiptRequest);
|
||||||
|
$response = $createInvoiceReceiptCommand->execute();
|
||||||
|
if ('' === $response->fileName) {
|
||||||
|
app('taskLogger')->error('PDF oder ZIP zur Abrechnung konnte nicht erstellt werden.');
|
||||||
|
return $uploadResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($webDavProvider->uploadFile($response->fileName)) {
|
||||||
|
$this->request->invoice->upload_required = false;
|
||||||
|
$this->request->invoice->save();
|
||||||
|
$uploadResponse->success = true;
|
||||||
|
} else {
|
||||||
|
app('taskLogger')->error('PDF oder ZIP zur Abrechnung konnte nicht hochgeladen werden.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Storage::exists($response->fileName)) {
|
||||||
|
Storage::delete($response->fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $uploadResponse;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Invoice\Actions\UploadInvoice;
|
||||||
|
|
||||||
|
use App\Models\Invoice;
|
||||||
|
|
||||||
|
class UploadInvoiceRequest {
|
||||||
|
public Invoice $invoice;
|
||||||
|
|
||||||
|
public function __construct(Invoice $invoice) {
|
||||||
|
$this->invoice = $invoice;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Invoice\Actions\UploadInvoice;
|
||||||
|
|
||||||
|
class UploadInvoiceResponse {
|
||||||
|
public bool $success;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->success = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
11
app/Enumerations/CronTaskType.php
Normal file
11
app/Enumerations/CronTaskType.php
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enumerations;
|
||||||
|
|
||||||
|
use App\Scopes\CommonModel;
|
||||||
|
|
||||||
|
class CronTaskType extends CommonModel{
|
||||||
|
public const CRON_TASK_TYPE_REALTIME = 'realtime';
|
||||||
|
public const CRON_TASK_TYPE_DAILY = 'daily';
|
||||||
|
}
|
||||||
|
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Installer;
|
namespace App\Installer;
|
||||||
|
|
||||||
use App\Enumerations\CostUnitType;
|
use App\Enumerations\CostUnitType;
|
||||||
|
use App\Enumerations\CronTaskType;
|
||||||
use App\Enumerations\EatingHabit;
|
use App\Enumerations\EatingHabit;
|
||||||
use App\Enumerations\FirstAidPermission;
|
use App\Enumerations\FirstAidPermission;
|
||||||
use App\Enumerations\InvoiceStatus;
|
use App\Enumerations\InvoiceStatus;
|
||||||
@@ -13,6 +14,7 @@ use App\Models\Tenant;
|
|||||||
|
|
||||||
class ProductionDataSeeder {
|
class ProductionDataSeeder {
|
||||||
public function execute() {
|
public function execute() {
|
||||||
|
$this->installCronTypes();
|
||||||
$this->installUserRoles();
|
$this->installUserRoles();
|
||||||
$this->installCostUnitTypes();
|
$this->installCostUnitTypes();
|
||||||
$this->installSwimmingPermissions();
|
$this->installSwimmingPermissions();
|
||||||
@@ -21,6 +23,7 @@ class ProductionDataSeeder {
|
|||||||
$this->installTenants();
|
$this->installTenants();
|
||||||
$this->installInvoiceMetaData();
|
$this->installInvoiceMetaData();
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function installUserRoles() {
|
private function installUserRoles() {
|
||||||
@@ -105,6 +108,11 @@ class ProductionDataSeeder {
|
|||||||
InvoiceStatus::create(['slug' => InvoiceStatus::INVOICE_STATUS_APPROVED]);
|
InvoiceStatus::create(['slug' => InvoiceStatus::INVOICE_STATUS_APPROVED]);
|
||||||
InvoiceStatus::create(['slug' => InvoiceStatus::INVOICE_STATUS_EXPORTED]);
|
InvoiceStatus::create(['slug' => InvoiceStatus::INVOICE_STATUS_EXPORTED]);
|
||||||
InvoiceStatus::create(['slug' => InvoiceStatus::INVOICE_STATUS_DENIED]);
|
InvoiceStatus::create(['slug' => InvoiceStatus::INVOICE_STATUS_DENIED]);
|
||||||
InvoiceStatus::create(['slug' => InvoiceStatus::INVOICE_STAUTS_DELETED]);
|
InvoiceStatus::create(['slug' => InvoiceStatus::INVOICE_STATUS_DELETED]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function installCronTypes() {
|
||||||
|
CronTaskType::creata(['slug' => CronTaskType::CRON_TASK_TYPE_REALTIME]);
|
||||||
|
CronTaskType::creata(['slug' => CronTaskType::CRON_TASK_TYPE_DAILY]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
12
app/Models/CronTask.php
Normal file
12
app/Models/CronTask.php
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Scopes\CommonModel;
|
||||||
|
|
||||||
|
class CronTask extends CommonModel
|
||||||
|
{
|
||||||
|
protected $table = 'cron_tasks';
|
||||||
|
protected $fillable = ['name', 'execution_type', 'schedule_time', 'last_run'];
|
||||||
|
protected $dates = ['last_run'];
|
||||||
|
}
|
||||||
88
app/Providers/CronTaskHandleProvider.php
Normal file
88
app/Providers/CronTaskHandleProvider.php
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use App\Enumerations\CronTaskType;
|
||||||
|
use App\Models\Tenant;
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use App\Models\CronTask;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
|
class CronTaskHandleProvider extends CommonController
|
||||||
|
{
|
||||||
|
public function run(Request $request)
|
||||||
|
{
|
||||||
|
$now = Carbon::now();
|
||||||
|
|
||||||
|
$tenants = Tenant::where('has_active_instance', true)->get();
|
||||||
|
|
||||||
|
foreach ($tenants as $tenant) {
|
||||||
|
app()->instance('tenant', $tenant);
|
||||||
|
$this->runTenantTasks($tenant, $now);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'ok',
|
||||||
|
'time' => $now->toDateTimeString(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function runTenantTasks(Tenant $tenant, Carbon $now) {
|
||||||
|
$tasks = CronTask::all();
|
||||||
|
|
||||||
|
foreach ($tasks as $task) {
|
||||||
|
|
||||||
|
// --- Every-Time Tasks ---
|
||||||
|
if ($task->execution_type === CronTaskType::CRON_TASK_TYPE_REALTIME) {
|
||||||
|
$this->runTask($task);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Daily Tasks ---
|
||||||
|
if ($task->execution_type === CronTaskType::CRON_TASK_TYPE_DAILY) {
|
||||||
|
$scheduledTime = $task->schedule_time;
|
||||||
|
$alreadyRunToday = $task->last_run?->isToday() ?? false;
|
||||||
|
|
||||||
|
if (!$alreadyRunToday && $now->format('H:i') === $scheduledTime) {
|
||||||
|
$this->runTask($task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function runTask(CronTask $task)
|
||||||
|
{
|
||||||
|
$logger = $this->taskLogger($task->name, app('tenant'));
|
||||||
|
app()->instance('taskLogger', $logger);
|
||||||
|
|
||||||
|
|
||||||
|
$taskClass = "\\App\\Tasks\\" . $task->name;
|
||||||
|
if (class_exists($taskClass)) {
|
||||||
|
$instance = new $taskClass();
|
||||||
|
$instance->handle();
|
||||||
|
|
||||||
|
// Update last_run
|
||||||
|
$task->last_run = now();
|
||||||
|
$task->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function taskLogger(string $taskName, $tenant = null) : LoggerInterface
|
||||||
|
{
|
||||||
|
$tenantSlug = $tenant->slug;
|
||||||
|
$logDir = storage_path("logs/{$tenantSlug}");
|
||||||
|
|
||||||
|
if (!file_exists($logDir)) {
|
||||||
|
mkdir($logDir, 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$logPath = "{$logDir}/{$taskName}.log";
|
||||||
|
|
||||||
|
return \Illuminate\Support\Facades\Log::build([
|
||||||
|
'driver' => 'single',
|
||||||
|
'path' => $logPath,
|
||||||
|
'level' => 'debug',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,10 +13,10 @@ class WebDavProvider {
|
|||||||
$this->workingDirectory = $workingDirectory;
|
$this->workingDirectory = $workingDirectory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function uploadFile(string $fileName) {
|
public function uploadFile(string $fileName) : bool {
|
||||||
$baseDir = storage_path('app/private/');
|
$baseDir = storage_path('app/private/');
|
||||||
|
|
||||||
$this->webDavClient->upload_file($baseDir . $fileName, $this->workingDirectory . '/'.
|
return $this->webDavClient->upload_file($baseDir . $fileName, $this->workingDirectory . '/'.
|
||||||
basename($fileName)
|
basename($fileName)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,10 @@ class InvoiceRepository {
|
|||||||
return $invoices;
|
return $invoices;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getUnexportedInvoices() : Collection {
|
||||||
|
return Invoice::where(['tenant' => app('tenant')->slug, 'status' => InvoiceStatus::INVOICE_STATUS_EXPORTED, 'upload_required' => true])->get();
|
||||||
|
}
|
||||||
|
|
||||||
public function getByStatus(CostUnit $costUnit, string $status, bool $forDisplay = true) : array {
|
public function getByStatus(CostUnit $costUnit, string $status, bool $forDisplay = true) : array {
|
||||||
$returnData = [];
|
$returnData = [];
|
||||||
foreach ($costUnit->invoices()->where('status', $status)->get() as $invoice) {
|
foreach ($costUnit->invoices()->where('status', $status)->get() as $invoice) {
|
||||||
|
|||||||
8
app/Tasks/CronTask.php
Normal file
8
app/Tasks/CronTask.php
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Tasks;
|
||||||
|
|
||||||
|
interface CronTask
|
||||||
|
{
|
||||||
|
public function handle(): void;
|
||||||
|
}
|
||||||
31
app/Tasks/UploadInvoices.php
Normal file
31
app/Tasks/UploadInvoices.php
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Tasks;
|
||||||
|
|
||||||
|
use App\Domains\Invoice\Actions\UploadInvoice\UploadInvoiceCommand;
|
||||||
|
use App\Domains\Invoice\Actions\UploadInvoice\UploadInvoiceRequest;
|
||||||
|
use App\Repositories\InvoiceRepository;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
|
class UploadInvoices implements CronTask {
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
if (!app('tenant')->upload_exports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$invoiceRepository = new InvoiceRepository();
|
||||||
|
foreach ($invoiceRepository->getUnexportedInvoices() as $invoice) {
|
||||||
|
app('taskLogger')->info("Uploading invoice {$invoice->invoice_number}");
|
||||||
|
$request = new UploadInvoiceRequest($invoice);
|
||||||
|
$command = new UploadInvoiceCommand($request);
|
||||||
|
if ($command->execute()->success) {
|
||||||
|
app('taskLogger')->info('Upload successful');
|
||||||
|
} else {
|
||||||
|
app('taskLogger')->error('Upload failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
app('taskLogger')->info('------------------------------------');
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -126,7 +126,6 @@ return [
|
|||||||
'emergency' => [
|
'emergency' => [
|
||||||
'path' => storage_path('logs/laravel.log'),
|
'path' => storage_path('logs/laravel.log'),
|
||||||
],
|
],
|
||||||
|
|
||||||
],
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
35
database/migrations/2026_02_01_140010_create_cron_tasks.php
Normal file
35
database/migrations/2026_02_01_140010_create_cron_tasks.php
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration {
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('cron_task_types', function (Blueprint $table) {
|
||||||
|
$table->string('slug')->primary();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
Schema::create('cron_tasks', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('name')->unique();
|
||||||
|
$table->string('execution_type');
|
||||||
|
$table->time('schedule_time')->nullable();
|
||||||
|
$table->timestamp('last_run')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
|
||||||
|
$table->foreign('execution_type')->references('slug')->on('cron_task_types')->cascadeOnDelete()->cascadeOnUpdate();
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('cron_tasks');
|
||||||
|
Schema::dropIfExists('cron_task_types');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -3,6 +3,8 @@
|
|||||||
use App\Domains\Dashboard\Controllers\DashboardController;
|
use App\Domains\Dashboard\Controllers\DashboardController;
|
||||||
use App\Http\Controllers\TestRenderInertiaProvider;
|
use App\Http\Controllers\TestRenderInertiaProvider;
|
||||||
use App\Middleware\IdentifyTenant;
|
use App\Middleware\IdentifyTenant;
|
||||||
|
use App\Providers\CronController;
|
||||||
|
use App\Providers\CronTaskHandleProvider;
|
||||||
use App\Providers\GlobalDataProvider;
|
use App\Providers\GlobalDataProvider;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
@@ -17,7 +19,7 @@ require_once __DIR__ . '/../app/Domains/Invoice/Routes/api.php';
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Route::get('/execute-crons', [CronTaskHandleProvider::class, 'run']);
|
||||||
|
|
||||||
Route::middleware(IdentifyTenant::class)->group(function () {
|
Route::middleware(IdentifyTenant::class)->group(function () {
|
||||||
Route::get('/', DashboardController::class);
|
Route::get('/', DashboardController::class);
|
||||||
|
|||||||
Reference in New Issue
Block a user