Compare commits
13 Commits
main
...
ab711109a7
| Author | SHA1 | Date | |
|---|---|---|---|
| ab711109a7 | |||
| 72623df38f | |||
| 9fd6839878 | |||
| fd403f8520 | |||
| 882752472e | |||
| 87531237c7 | |||
| ee7fc637f1 | |||
| bccfc11687 | |||
| 6fc65e195c | |||
| e9ae850002 | |||
| 11108bdfcc | |||
| e280fcfba8 | |||
| 3570f442f5 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -22,3 +22,4 @@
|
||||
Homestead.json
|
||||
Homestead.yaml
|
||||
Thumbs.db
|
||||
/docker-compose.yaml
|
||||
|
||||
5
Makefile
5
Makefile
@@ -2,6 +2,11 @@
|
||||
|
||||
FRONTEND_DIR ?= .
|
||||
|
||||
setup:
|
||||
rm -f docker-compose.yaml
|
||||
cp docker-compose.dev docker-compose.yaml
|
||||
docker-compose up -d
|
||||
|
||||
frontend:
|
||||
@cd $(FRONTEND_DIR) && \
|
||||
export QT_QPA_PLATFORM=offscreen && \
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\CostUnit\Actions\ChangeCostUnitDetails;
|
||||
|
||||
class ChangeCostUnitDetailsCommand {
|
||||
private ChangeCostUnitDetailsRequest $request;
|
||||
|
||||
public function __construct(ChangeCostUnitDetailsRequest $request) {
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function execute(): ChangeCostUnitDetailsResponse {
|
||||
$response = new ChangeCostUnitDetailsResponse();
|
||||
|
||||
$this->request->costUnit->distance_allowance = $this->request->distanceAllowance->getAmount();
|
||||
$this->request->costUnit->mail_on_new = $this->request->mailOnNew;
|
||||
$this->request->costUnit->billing_deadline = $this->request->billingDeadline;
|
||||
|
||||
$response->success = $this->request->costUnit->save();
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\CostUnit\Actions\ChangeCostUnitDetails;
|
||||
|
||||
use App\Models\CostUnit;
|
||||
use App\ValueObjects\Amount;
|
||||
use DateTime;
|
||||
|
||||
class ChangeCostUnitDetailsRequest {
|
||||
public CostUnit $costUnit;
|
||||
|
||||
public Amount $distanceAllowance;
|
||||
public bool $mailOnNew;
|
||||
public ?DateTime $billingDeadline;
|
||||
|
||||
public function __construct(CostUnit $costUnit, Amount $distanceAllowance, bool $mailOnNew, ?DateTime $billingDeadline = null) {
|
||||
$this->costUnit = $costUnit;
|
||||
$this->distanceAllowance = $distanceAllowance;
|
||||
$this->mailOnNew = $mailOnNew;
|
||||
$this->billingDeadline = $billingDeadline;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\CostUnit\Actions\ChangeCostUnitDetails;
|
||||
|
||||
class ChangeCostUnitDetailsResponse {
|
||||
public bool $success;
|
||||
|
||||
public function __construct() {
|
||||
$this->success = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\CostUnit\Actions\ChangeCostUnitState;
|
||||
|
||||
class ChangeCostUnitStateCommand {
|
||||
private ChangeCostUnitStateRequest $request;
|
||||
|
||||
public function __construct(ChangeCostUnitStateRequest $request) {
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function execute() : ChangeCostUnitStateResponse {
|
||||
$response = new ChangeCostUnitStateResponse();
|
||||
|
||||
$this->request->costUnit->allow_new = $this->request->allowNew;
|
||||
$this->request->costUnit->archived = $this->request->isArchived;
|
||||
$response->success = $this->request->costUnit->save();
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\CostUnit\Actions\ChangeCostUnitState;
|
||||
|
||||
use App\Models\CostUnit;
|
||||
|
||||
class ChangeCostUnitStateRequest {
|
||||
public bool $allowNew;
|
||||
public bool $isArchived;
|
||||
|
||||
public CostUnit $costUnit;
|
||||
|
||||
public function __construct(CostUnit $costUnit, bool $allowNew, bool $isArchived) {
|
||||
$this->costUnit = $costUnit;
|
||||
$this->allowNew = $allowNew;
|
||||
$this->isArchived = $isArchived;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\CostUnit\Actions\ChangeCostUnitState;
|
||||
|
||||
class ChangeCostUnitStateResponse {
|
||||
public bool $success;
|
||||
|
||||
public function __construct() {
|
||||
$this->success = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\CostUnit\Actions\ChangeCostUnitTreasurers;
|
||||
|
||||
use App\Models\User;
|
||||
|
||||
class ChangeCostUnitTreasurersCommand {
|
||||
private ChangeCostUnitTreasurersRequest $request;
|
||||
|
||||
public function __construct(ChangeCostUnitTreasurersRequest $request) {
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function execute() : ChangeCostUnitTreasurersResponse {
|
||||
$response = new ChangeCostUnitTreasurersResponse();
|
||||
|
||||
try {
|
||||
$this->request->costUnit->resetTreasurers();
|
||||
|
||||
foreach ($this->request->treasurers as $treasurer) {
|
||||
$this->request->costUnit->treasurers()->attach($treasurer);
|
||||
}
|
||||
|
||||
$this->request->costUnit->save();
|
||||
} catch (\Throwable $th) {
|
||||
$response->success = false;
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\CostUnit\Actions\ChangeCostUnitTreasurers;
|
||||
|
||||
use App\Models\CostUnit;
|
||||
|
||||
class ChangeCostUnitTreasurersRequest {
|
||||
public array $treasurers;
|
||||
public CostUnit $costUnit;
|
||||
|
||||
public function __construct(CostUnit $costUnit, array $treasurers) {
|
||||
$this->treasurers = $treasurers;
|
||||
$this->costUnit = $costUnit;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\CostUnit\Actions\ChangeCostUnitTreasurers;
|
||||
|
||||
class ChangeCostUnitTreasurersResponse {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\CostUnit\Actions\CreateCostUnit;
|
||||
|
||||
use App\Models\CostUnit;
|
||||
|
||||
class CreateCostUnitCommand {
|
||||
private CreateCostUnitRequest $request;
|
||||
|
||||
public function __construct(CreateCostUnitRequest $request) {
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function execute() : CreateCostUnitResponse {
|
||||
$response = new CreateCostUnitResponse();
|
||||
$costUnit = CostUnit::create([
|
||||
'name' => $this->request->name,
|
||||
'tenant' => app('tenant')->slug,
|
||||
'type' => $this->request->type,
|
||||
'billing_deadline' => $this->request->billingDeadline,
|
||||
'distance_allowance' => $this->request->distanceAllowance->getAmount(),
|
||||
'mail_on_new' => $this->request->mailOnNew,
|
||||
'allow_new' => true,
|
||||
'archived' => false,
|
||||
|
||||
]);
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\CostUnit\Actions\CreateCostUnit;
|
||||
|
||||
use App\Enumerations\CostUnitType;
|
||||
use App\ValueObjects\Amount;
|
||||
use DateTime;
|
||||
|
||||
class CreateCostUnitRequest {
|
||||
public string $name;
|
||||
public string $type;
|
||||
public Amount $distanceAllowance;
|
||||
public bool $mailOnNew;
|
||||
public ?DateTime $billingDeadline;
|
||||
|
||||
public function __construct(string $name, string $type, Amount $distanceAllowance, bool $mailOnNew, ?DateTime $billingDeadline = null) {
|
||||
$this->name = $name;
|
||||
$this->type = $type;
|
||||
$this->distanceAllowance = $distanceAllowance;
|
||||
$this->mailOnNew = $mailOnNew;
|
||||
$this->billingDeadline = $billingDeadline;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\CostUnit\Actions\CreateCostUnit;
|
||||
|
||||
class CreateCostUnitResponse {
|
||||
|
||||
}
|
||||
47
app/Domains/CostUnit/Controllers/ChangeStateController.php
Normal file
47
app/Domains/CostUnit/Controllers/ChangeStateController.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\CostUnit\Controllers;
|
||||
|
||||
use App\Domains\CostUnit\Actions\ChangeCostUnitState\ChangeCostUnitStateCommand;
|
||||
use App\Domains\CostUnit\Actions\ChangeCostUnitState\ChangeCostUnitStateRequest;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class ChangeStateController extends CommonController {
|
||||
public function close(int $costUnitId) : JsonResponse {
|
||||
$costUnit = $this->costUnits->getById($costUnitId);
|
||||
|
||||
$changeStatRequest = new ChangeCostUnitStateRequest($costUnit, false, false);
|
||||
return $this->changeCostUnitState($changeStatRequest, 'Der CostUnit wurde geschlossen.');
|
||||
}
|
||||
|
||||
public function open(int $costUnitId) : JsonResponse {
|
||||
$costUnit = $this->costUnits->getById($costUnitId);
|
||||
|
||||
$changeStatRequest = new ChangeCostUnitStateRequest($costUnit, true, false);
|
||||
return $this->changeCostUnitState($changeStatRequest, 'Der CostUnit wurde geöffnet.');
|
||||
}
|
||||
|
||||
public function archive(int $costUnitId) : JsonResponse {
|
||||
$costUnit = $this->costUnits->getById($costUnitId);
|
||||
|
||||
$changeStatRequest = new ChangeCostUnitStateRequest($costUnit, false, true);
|
||||
return $this->changeCostUnitState($changeStatRequest, 'Der CostUnit wurde archiviert.');
|
||||
}
|
||||
|
||||
private function changeCostUnitState(ChangeCostUnitStateRequest $request, string $responseMessage) : JsonResponse {
|
||||
$changeStatCommand = new ChangeCostUnitStateCommand($request);
|
||||
|
||||
if ($changeStatCommand->execute()) {
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'message' => $responseMessage
|
||||
]);
|
||||
};
|
||||
|
||||
return response()->json([
|
||||
'status' => 'error',
|
||||
'message' => 'Ein Fehler ist aufgetreten.'
|
||||
]);
|
||||
}
|
||||
}
|
||||
36
app/Domains/CostUnit/Controllers/CreateController.php
Normal file
36
app/Domains/CostUnit/Controllers/CreateController.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\CostUnit\Controllers;
|
||||
|
||||
use App\Domains\CostUnit\Actions\CreateCostUnit\CreateCostUnitCommand;
|
||||
use App\Domains\CostUnit\Actions\CreateCostUnit\CreateCostUnitRequest;
|
||||
use App\Enumerations\CostUnitType;
|
||||
use App\Models\CostUnit;
|
||||
use App\Providers\FlashMessageProvider;
|
||||
use App\Providers\InertiaProvider;
|
||||
use App\Scopes\CommonController;
|
||||
use App\ValueObjects\Amount;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class CreateController extends CommonController{
|
||||
public function showForm() {
|
||||
$inertiaProvider = new InertiaProvider('CostUnit/Create', []);
|
||||
return $inertiaProvider->render();
|
||||
}
|
||||
|
||||
public function createCostUnitRunningJob(Request $request) : JsonResponse {
|
||||
$createCostUnitRequest = new CreateCostUnitRequest(
|
||||
$request->get('cost_unit_name'),
|
||||
CostUnitType::COST_UNIT_TYPE_RUNNING_JOB,
|
||||
Amount::fromString($request->get('distance_allowance')),
|
||||
$request->get('mailOnNew')
|
||||
);
|
||||
|
||||
$createCostUnitCommand = new CreateCostUnitCommand($createCostUnitRequest);
|
||||
$result = $createCostUnitCommand->execute();
|
||||
new FlashMessageProvider('Die laufende Tätigkeit wurde erfolgreich angelegt.', 'success');
|
||||
|
||||
return response()->json([]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\CostUnit\Controllers;
|
||||
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class DistanceAllowanceController extends CommonController {
|
||||
public function __invoke(int $costUnitId) : JsonResponse {
|
||||
$distanceAllowance = 0.00;
|
||||
$costUnit = $this->costUnits->getById($costUnitId, true);
|
||||
if (null !== $costUnit) {
|
||||
$distanceAllowance = $costUnit->distance_allowance;
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'distanceAllowance' => $distanceAllowance
|
||||
]);
|
||||
}
|
||||
}
|
||||
58
app/Domains/CostUnit/Controllers/EditController.php
Normal file
58
app/Domains/CostUnit/Controllers/EditController.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\CostUnit\Controllers;
|
||||
|
||||
use App\Domains\CostUnit\Actions\ChangeCostUnitDetails\ChangeCostUnitDetailsCommand;
|
||||
use App\Domains\CostUnit\Actions\ChangeCostUnitDetails\ChangeCostUnitDetailsRequest;
|
||||
use App\Resources\CostUnitResource;
|
||||
use App\Scopes\CommonController;
|
||||
use App\ValueObjects\Amount;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class EditController extends CommonController{
|
||||
function __invoke(int $costUnitId) : JsonResponse {
|
||||
$costUnit = $this->costUnits->getById($costUnitId);
|
||||
if (null === $costUnit) {
|
||||
return response()->json([
|
||||
'status' => 'error',
|
||||
'message' => 'Die Kotenstelle konnte nicht geladen werden.'
|
||||
]);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'costUnit' => new CostUnitResource($costUnit)->toArray(request())
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(Request $request, int $costUnitId) : JsonResponse {
|
||||
$costUnit = $this->costUnits->getById($costUnitId);
|
||||
if (null === $costUnit) {
|
||||
return response()->json([
|
||||
'status' => 'error',
|
||||
'message' => 'Die Kotenstelle konnte nicht geladen werden.'
|
||||
]);
|
||||
}
|
||||
|
||||
$saveParams = $request->get('formData');
|
||||
$distanceAllowance = Amount::fromString($saveParams['distanceAllowance']);
|
||||
$billingDeadline = isset($saveParams['billingDeadline']) ? \DateTime::createFromFormat('Y-m-d', $saveParams['billingDeadline']) : null;
|
||||
|
||||
$request = new ChangeCostUnitDetailsRequest($costUnit, $distanceAllowance, $saveParams['mailOnNew'], $billingDeadline);
|
||||
$command = new ChangeCostUnitDetailsCommand($request);
|
||||
$result = $command->execute();
|
||||
|
||||
if (!$result->success) {
|
||||
return response()->json([
|
||||
'status' => 'error',
|
||||
'message' => 'Bei der Verarbeitung ist ein Fehler aufgetreten.'
|
||||
]);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'message' => 'Die Kostenstellendetails wurden erfolgreich gespeichert.',
|
||||
]);
|
||||
}
|
||||
}
|
||||
48
app/Domains/CostUnit/Controllers/ListController.php
Normal file
48
app/Domains/CostUnit/Controllers/ListController.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\CostUnit\Controllers;
|
||||
|
||||
use App\Providers\InertiaProvider;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ListController extends CommonController {
|
||||
public function __invoke() {
|
||||
$inertiaProvider = new InertiaProvider('CostUnit/List', [
|
||||
'cost_unit_id' => 1
|
||||
]);
|
||||
return $inertiaProvider->render();
|
||||
}
|
||||
|
||||
public function listCurrentEvents(Request $request) : JsonResponse {
|
||||
|
||||
return response()->json([
|
||||
'cost_unit_title' => 'Aktuelle Veranstaltungen',
|
||||
'cost_units' => $this->costUnits->getCurrentEvents(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function listCurrentRunningJobs(Request $request) : JsonResponse {
|
||||
return response()->json([
|
||||
'cost_unit_title' => 'Laufende Tätigkeiten',
|
||||
'cost_units' => $this->costUnits->getRunningJobs(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function listClosedCostUnits(Request $request) : JsonResponse {
|
||||
return response()->json([
|
||||
'cost_unit_title' => 'Geschlossene Kostenstellen',
|
||||
'cost_units' => $this->costUnits->getClosedCostUnits(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function listArchivedCostUnits(Request $request) : JsonResponse {
|
||||
return response()->json([
|
||||
'cost_unit_title' => 'Archivierte Kostenstellen',
|
||||
'cost_units' => $this->costUnits->getArchivedCostUnits(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
28
app/Domains/CostUnit/Controllers/OpenController.php
Normal file
28
app/Domains/CostUnit/Controllers/OpenController.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\CostUnit\Controllers;
|
||||
|
||||
use App\Providers\InertiaProvider;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class OpenController extends CommonController {
|
||||
public function __invoke(int $costUnitId) {
|
||||
$inertiaProvider = new InertiaProvider('CostUnit/Open', [
|
||||
'costUnitId' => $costUnitId
|
||||
]);
|
||||
return $inertiaProvider->render();
|
||||
}
|
||||
|
||||
public function listInvoices(int $costUnitId, string $invoiceStatus) : JsonResponse {
|
||||
$costUnit = $this->costUnits->getById($costUnitId);
|
||||
$invoices = $this->invoices->getByStatus($costUnit, $invoiceStatus);
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'costUnit' => $costUnit,
|
||||
'invoices' => $invoices,
|
||||
'endpoint' => $invoiceStatus,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\CostUnit\Controllers;
|
||||
|
||||
use App\Domains\CostUnit\Actions\ChangeCostUnitTreasurers\ChangeCostUnitTreasurersCommand;
|
||||
use App\Domains\CostUnit\Actions\ChangeCostUnitTreasurers\ChangeCostUnitTreasurersRequest;
|
||||
use App\Providers\InertiaProvider;
|
||||
use App\Resources\CostUnitResource;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class TreasurersEditController extends CommonController {
|
||||
public function __invoke(int $costUnitId) : JsonResponse {
|
||||
$costUnit = $this->costUnits->getById($costUnitId);
|
||||
if (null === $costUnit) {
|
||||
return response()->json([
|
||||
'status' => 'error',
|
||||
'message' => 'Die Kostenstelle konnte nicht geladen werden.'
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'costUnit' => new CostUnitResource($costUnit)->toArray(request())
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(Request $request, int $costUnitId) : JsonResponse {
|
||||
$costUnit = $this->costUnits->getById($costUnitId);
|
||||
if (null === $costUnit) {
|
||||
return response()->json([
|
||||
'status' => 'error',
|
||||
'message' => 'Die Kostenstelle konnte nicht geladen werden.'
|
||||
]);
|
||||
}
|
||||
|
||||
$changeTreasurersRequest = new ChangeCostUnitTreasurersRequest($costUnit, $request->get('selectedTreasurers'));
|
||||
$changeTreasurersCommand = new ChangeCostUnitTreasurersCommand($changeTreasurersRequest);
|
||||
if ($changeTreasurersCommand->execute()) {
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'message' => 'Die Schatzis wurden erfolgreich gespeichert.'
|
||||
]);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'status' => 'error',
|
||||
'message' => 'Beim Bearbeiten der Kostenstelle ist ein Fehler aufgetreten.'
|
||||
]);
|
||||
}
|
||||
}
|
||||
53
app/Domains/CostUnit/Routes/api.php
Normal file
53
app/Domains/CostUnit/Routes/api.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
use App\Domains\CostUnit\Controllers\ChangeStateController;
|
||||
use App\Domains\CostUnit\Controllers\CreateController;
|
||||
use App\Domains\CostUnit\Controllers\DistanceAllowanceController;
|
||||
use App\Domains\CostUnit\Controllers\EditController;
|
||||
use App\Domains\CostUnit\Controllers\ListController;
|
||||
use App\Domains\CostUnit\Controllers\OpenController;
|
||||
use App\Domains\CostUnit\Controllers\TreasurersEditController;
|
||||
use App\Middleware\IdentifyTenant;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::prefix('api/v1')
|
||||
->group(function () {
|
||||
Route::middleware(IdentifyTenant::class)->group(function () {
|
||||
Route::prefix('cost-unit')->group(function () {
|
||||
|
||||
Route::get('/get-distance-allowance/{costUnitId}', DistanceAllowanceController::class);
|
||||
|
||||
|
||||
Route::middleware(['auth'])->group(function () {
|
||||
Route::post('/create-running-job', [CreateController::class, 'createCostUnitRunningJob']);
|
||||
|
||||
|
||||
Route::prefix('/{costUnitId}') ->group(function () {
|
||||
Route::get('/invoice-list/{invoiceStatus}', [OpenController::class, 'listInvoices']);
|
||||
|
||||
|
||||
|
||||
|
||||
Route::post('/close', [ChangeStateController::class, 'close']);
|
||||
Route::post('/open', [ChangeStateController::class, 'open']);
|
||||
Route::post('/archive', [ChangeStateController::class, 'archive']);
|
||||
|
||||
Route::get('/details', EditController::class);
|
||||
Route::post('/details', [EditController::class, 'update']);
|
||||
|
||||
Route::get('/treasurers', TreasurersEditController::class);
|
||||
Route::post('/treasurers', [TreasurersEditController::class, 'update']);
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
Route::prefix('open')->group(function () {
|
||||
Route::get('/current-events', [ListController::class, 'listCurrentEvents']);
|
||||
Route::get('/current-running-jobs', [ListController::class, 'listCurrentRunningJobs']);
|
||||
Route::get('/closed-cost-units', [ListController::class, 'listClosedCostUnits']);
|
||||
Route::get('/archived-cost-units', [ListController::class, 'listArchivedCostUnits']);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
39
app/Domains/CostUnit/Routes/web.php
Normal file
39
app/Domains/CostUnit/Routes/web.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
use App\Domains\CostUnit\Controllers\CreateController;
|
||||
use App\Domains\CostUnit\Controllers\ListController;
|
||||
use App\Domains\CostUnit\Controllers\OpenController;
|
||||
use App\Domains\UserManagement\Controllers\EmailVerificationController;
|
||||
use App\Domains\UserManagement\Controllers\LoginController;
|
||||
use App\Domains\UserManagement\Controllers\LogOutController;
|
||||
use App\Domains\UserManagement\Controllers\RegistrationController;
|
||||
use App\Domains\UserManagement\Controllers\ResetPasswordController;
|
||||
use App\Middleware\IdentifyTenant;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::middleware(IdentifyTenant::class)->group(function () {
|
||||
Route::prefix('cost-unit')->group(function () {
|
||||
Route::middleware(['auth'])->group(function () {
|
||||
Route::get('/create', [CreateController::class, 'showForm']);
|
||||
Route::get('/list', ListController::class);
|
||||
Route::get('/{costUnitId}/', OpenController::class);
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
});
|
||||
Route::get('/register', [RegistrationController::class, 'loginForm']);
|
||||
Route::get('/register/verifyEmail', [EmailVerificationController::class, 'verifyEmailForm']);
|
||||
|
||||
Route::get('/reset-password', [ResetPasswordController::class, 'resetPasswordForm']);
|
||||
|
||||
route::get('/logout', LogOutController::class);
|
||||
route::post('/login', [LoginController::class, 'doLogin']);
|
||||
route::get('/login', [LoginController::class, 'loginForm']);
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
78
app/Domains/CostUnit/Views/Create.vue
Normal file
78
app/Domains/CostUnit/Views/Create.vue
Normal file
@@ -0,0 +1,78 @@
|
||||
<script setup>
|
||||
import { reactive, inject } from 'vue';
|
||||
import AppLayout from '../../../../resources/js/layouts/AppLayout.vue';
|
||||
import AmountInput from "../../../Views/Components/AmountInput.vue";
|
||||
import { useAjax } from "../../../../resources/js/components/ajaxHandler.js";
|
||||
import ShadowedBox from "../../../Views/Components/ShadowedBox.vue";
|
||||
import {toast} from "vue3-toastify";
|
||||
|
||||
const props = defineProps({
|
||||
activeUsers: Object,
|
||||
}
|
||||
)
|
||||
|
||||
const { request } = useAjax();
|
||||
|
||||
const formData = reactive({
|
||||
cost_unit_name: '',
|
||||
distance_allowance: '0,25',
|
||||
|
||||
emailAddress: '',
|
||||
mailOnNew: true
|
||||
});
|
||||
|
||||
async function save() {
|
||||
const data = await request("/api/v1/cost-unit/create-running-job", {
|
||||
method: "POST",
|
||||
body: {
|
||||
cost_unit_name: formData.cost_unit_name,
|
||||
distance_allowance: formData.distance_allowance,
|
||||
mailOnNew: formData.mailOnNew
|
||||
}
|
||||
});
|
||||
|
||||
window.location.href = '/cost-unit/list';
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppLayout title="Laufende Tätigkeit hinzufügen">
|
||||
<form method="POST" action="/api/v1/cost-unit/create-running-job" @submit.prevent="save">
|
||||
<input type="hidden" name="_token" :value="csrfToken" />
|
||||
|
||||
<shadowed-box style="width: 90%; margin: 20px auto; padding: 20px;">
|
||||
<p>
|
||||
Über dieses Formular können laufende Tätigkeiten angelegt werden.<br />
|
||||
Eine Kostenstelle für eine Veranstaltung wird automatisch erstellt, sobald die Veranstaltung angelegt wurde.
|
||||
</p>
|
||||
<table style="margin-top: 40px; width: 100%">
|
||||
<tr>
|
||||
<th class="width-medium pr-20">Name der laufenden Tätigkeit</th>
|
||||
<td><input type="text" v-model="formData.cost_unit_name" class="width-half-full" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="pr-20">Kilometerpauschale</th>
|
||||
<td>
|
||||
<AmountInput v-model="formData.distance_allowance" class="width-small" /> Euro / Kilometer
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<label style="display:flex;align-items:center;cursor:pointer;">
|
||||
<input type="checkbox" v-model="formData.mailOnNew" style="margin-right:8px;cursor:pointer;" />
|
||||
<span>E-Mail-Benachrichtigung bei neuen Abrechnungen</span>
|
||||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<input type="submit" value="Speichern" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</shadowed-box>
|
||||
</form>
|
||||
</AppLayout>
|
||||
</template>
|
||||
83
app/Domains/CostUnit/Views/Edit.vue
Normal file
83
app/Domains/CostUnit/Views/Edit.vue
Normal file
@@ -0,0 +1,83 @@
|
||||
<script setup>
|
||||
import { reactive, inject } from 'vue';
|
||||
import AppLayout from '../../../../resources/js/layouts/AppLayout.vue';
|
||||
import AmountInput from "../../../Views/Components/AmountInput.vue";
|
||||
import { useAjax } from "../../../../resources/js/components/ajaxHandler.js";
|
||||
import ShadowedBox from "../../../Views/Components/ShadowedBox.vue";
|
||||
|
||||
const props = defineProps({
|
||||
activeUsers: Object,
|
||||
}
|
||||
)
|
||||
|
||||
const { request } = useAjax();
|
||||
|
||||
const formData = reactive({
|
||||
cost_unit_name: '',
|
||||
distance_allowance: '0,25',
|
||||
|
||||
emailAddress: '',
|
||||
mailOnNew: true
|
||||
});
|
||||
|
||||
async function save() {
|
||||
const data = await request("/wp-json/mareike/costunits/create-new-cost-unit", {
|
||||
method: "POST",
|
||||
body: {
|
||||
mareike_nonce: _mareike_nonce(),
|
||||
cost_unit_name: formData.cost_unit_name,
|
||||
distance_allowance: formData.distance_allowance,
|
||||
email_address: formData.emailAddress,
|
||||
mailOnNew: formData.mailOnNew
|
||||
}
|
||||
});
|
||||
|
||||
toast('Die laufende Tätigkeit wurde erfolgreich angelegt.', { type: 'success' });
|
||||
window.location.href = '/cost-units';
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppLayout title="Laufende Tätigkeit hinzufügen">
|
||||
<shadowed-box style="width: 90%; margin: 20px auto; padding: 20px;">
|
||||
<p>
|
||||
Über dieses Formular können laufende Tätigkeiten angelegt werden.<br />
|
||||
Eine Kostenstelle für eine Veranstaltung wird automatisch erstellt, sobald die Veranstaltung angelegt wurde.
|
||||
</p>
|
||||
<table style="margin-top: 40px; width: 100%">
|
||||
<tr>
|
||||
<th class="width-medium pr-20">Name der laufenden Tätigkeit</th>
|
||||
<td><input type="text" v-model="formData.cost_unit_name" class="width-half-full" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="pr-20">Kilometerpauschale</th>
|
||||
<td>
|
||||
<AmountInput v-model="formData.distance_allowance" class="width-small" /> Euro / Kilometer
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<label style="display:flex;align-items:center;cursor:pointer;">
|
||||
<input type="checkbox" v-model="formData.mailOnNew" style="margin-right:8px;cursor:pointer;" />
|
||||
<span>E-Mail-Benachrichtigung bei neuen Abrechnungen</span>
|
||||
</label>
|
||||
<hr />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<span v-for="user in props.activeUsers">
|
||||
<input type="checkbox" :id="'user_' + user.id" />
|
||||
<label :for="'user_' + user.id">{{user.fullname}} ({{user.localGroup}})</label>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<input type="button" @click="save" value="Speichern" class="mareike-button button-small button-block" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</shadowed-box>
|
||||
</AppLayout>
|
||||
</template>
|
||||
82
app/Domains/CostUnit/Views/List.vue
Normal file
82
app/Domains/CostUnit/Views/List.vue
Normal file
@@ -0,0 +1,82 @@
|
||||
<script setup>
|
||||
import {reactive, inject, onMounted} from 'vue';
|
||||
import AppLayout from '../../../../resources/js/layouts/AppLayout.vue';
|
||||
import { useAjax } from "../../../../resources/js/components/ajaxHandler.js";
|
||||
import ShadowedBox from "../../../Views/Components/ShadowedBox.vue";
|
||||
import TabbedPage from "../../../Views/Components/TabbedPage.vue";
|
||||
import {toast} from "vue3-toastify";
|
||||
|
||||
import ListCostUnits from "./Partials/ListCostUnits.vue";
|
||||
|
||||
const props = defineProps({
|
||||
message: String,
|
||||
|
||||
data: {
|
||||
type: [Array, Object],
|
||||
default: () => []
|
||||
},
|
||||
cost_unit_id: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
|
||||
invoice_id: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
|
||||
|
||||
})
|
||||
|
||||
// Prüfen, ob ein ?id= Parameter in der URL übergeben wurde
|
||||
const urlParams = new URLSearchParams(window.location.search)
|
||||
const initialCostUnitId = props.cost_unit_id
|
||||
const initialInvoiceId = props.invoice_id
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
title: 'Aktuelle Veranstaltungen',
|
||||
component: ListCostUnits,
|
||||
endpoint: "/api/v1/cost-unit/open/current-events",
|
||||
deep_jump_id: initialCostUnitId,
|
||||
deep_jump_id_sub: initialInvoiceId,
|
||||
|
||||
},
|
||||
{
|
||||
title: 'Laufende Tätigkeiten',
|
||||
component: ListCostUnits,
|
||||
endpoint: "/api/v1/cost-unit/open/current-running-jobs",
|
||||
deep_jump_id: initialCostUnitId,
|
||||
deep_jump_id_sub: initialInvoiceId,
|
||||
},
|
||||
{
|
||||
title: 'Geschlossene Kostenstellen',
|
||||
component: ListCostUnits,
|
||||
endpoint: "/api/v1/cost-unit/open/closed-cost-units",
|
||||
deep_jump_id: initialCostUnitId,
|
||||
deep_jump_id_sub: initialInvoiceId,
|
||||
},
|
||||
{
|
||||
title: 'Archivierte Kostenstellen',
|
||||
component: ListCostUnits,
|
||||
endpoint: "/api/v1/cost-unit/open/archived-cost-units",
|
||||
deep_jump_id: initialCostUnitId,
|
||||
deep_jump_id_sub: initialInvoiceId,
|
||||
},
|
||||
]
|
||||
|
||||
onMounted(() => {
|
||||
if (undefined !== props.message) {
|
||||
toast.success(props.message)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppLayout title="Kostenstellen">
|
||||
<shadowed-box style="width: 95%; margin: 20px auto; padding: 20px; overflow-x: hidden;">
|
||||
<tabbed-page :tabs="tabs" :initial-tab-id="initialCostUnitId" :initial-sub-tab-id="initialInvoiceId" />
|
||||
|
||||
</shadowed-box>
|
||||
</AppLayout>
|
||||
</template>
|
||||
67
app/Domains/CostUnit/Views/Open.vue
Normal file
67
app/Domains/CostUnit/Views/Open.vue
Normal file
@@ -0,0 +1,67 @@
|
||||
<script setup>
|
||||
import {reactive, inject, onMounted} from 'vue';
|
||||
import AppLayout from '../../../../resources/js/layouts/AppLayout.vue';
|
||||
import { useAjax } from "../../../../resources/js/components/ajaxHandler.js";
|
||||
import ShadowedBox from "../../../Views/Components/ShadowedBox.vue";
|
||||
import TabbedPage from "../../../Views/Components/TabbedPage.vue";
|
||||
import {toast} from "vue3-toastify";
|
||||
import ListInvoices from "./Partials/ListInvoices.vue";
|
||||
|
||||
const props = defineProps({
|
||||
costUnitId: Number
|
||||
})
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search)
|
||||
const initialCostUnitId = props.cost_unit_id
|
||||
const initialInvoiceId = props.invoice_id
|
||||
|
||||
|
||||
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
title: 'Neue Abrechnungen',
|
||||
component: ListInvoices,
|
||||
endpoint: "/api/v1/cost-unit/" + props.costUnitId + "/invoice-list/new",
|
||||
deep_jump_id: initialCostUnitId,
|
||||
deep_jump_id_sub: initialInvoiceId,
|
||||
|
||||
},
|
||||
{
|
||||
title: 'Nichtexportierte Abrechnungen',
|
||||
component: ListInvoices,
|
||||
endpoint: "/api/v1/cost-unit/" + props.costUnitId + "/invoice-list/approved",
|
||||
deep_jump_id: initialCostUnitId,
|
||||
deep_jump_id_sub: initialInvoiceId,
|
||||
},
|
||||
{
|
||||
title: 'Exportierte Abrechnungen',
|
||||
component: ListInvoices,
|
||||
endpoint: "/api/v1/cost-unit/" + props.costUnitId + "/invoice-list/exported",
|
||||
deep_jump_id: initialCostUnitId,
|
||||
deep_jump_id_sub: initialInvoiceId,
|
||||
},
|
||||
{
|
||||
title: 'Abgelehnte Abrechnungen',
|
||||
component: ListInvoices,
|
||||
endpoint: "/api/v1/cost-unit/" + props.costUnitId + "/invoice-list/denied",
|
||||
deep_jump_id: initialCostUnitId,
|
||||
deep_jump_id_sub: initialInvoiceId,
|
||||
},
|
||||
]
|
||||
|
||||
onMounted(() => {
|
||||
if (undefined !== props.message) {
|
||||
toast.success(props.message)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppLayout title="Abrechnungen">
|
||||
<shadowed-box style="width: 95%; margin: 20px auto; padding: 20px; overflow-x: hidden;">
|
||||
<tabbed-page :tabs="tabs" :initial-tab-id="initialCostUnitId" :initial-sub-tab-id="initialInvoiceId" />
|
||||
|
||||
</shadowed-box>
|
||||
</AppLayout>
|
||||
</template>
|
||||
90
app/Domains/CostUnit/Views/Partials/CostUnitDetails.vue
Normal file
90
app/Domains/CostUnit/Views/Partials/CostUnitDetails.vue
Normal file
@@ -0,0 +1,90 @@
|
||||
<script setup>
|
||||
|
||||
import {reactive, ref} from "vue";
|
||||
import Modal from "../../../../Views/Components/Modal.vue";
|
||||
import { useAjax } from "../../../../../resources/js/components/ajaxHandler.js";
|
||||
import AmountInput from "../../../../Views/Components/AmountInput.vue";
|
||||
import {toast} from "vue3-toastify";
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}, showCostUnit: Boolean
|
||||
})
|
||||
|
||||
const mail_on_new = ref(Boolean(Number(props.data.mail_on_new)))
|
||||
|
||||
const emit = defineEmits(['close'])
|
||||
|
||||
const { request } = useAjax()
|
||||
function close() {
|
||||
emit('close')
|
||||
}
|
||||
|
||||
|
||||
const formData = reactive({
|
||||
billingDeadline: props.data.billingDeadline,
|
||||
mailOnNew: mail_on_new.value,
|
||||
distanceAllowance: props.data.distanceAllowanceSmall,
|
||||
});
|
||||
|
||||
async function updateCostUnit() {
|
||||
const data = await request('/api/v1/cost-unit/' + props.data.id + '/details', {
|
||||
method: "POST",
|
||||
body: {
|
||||
formData
|
||||
}
|
||||
});
|
||||
|
||||
close();
|
||||
if (data.status === 'success') {
|
||||
toast.success(data.message);
|
||||
} else {
|
||||
toast.error(data.message);
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
<Modal
|
||||
:show="showCostUnit"
|
||||
title="Details anpassen"
|
||||
@close="emit('close')"
|
||||
>
|
||||
Kilometerpauschale:
|
||||
<amount-input v-model="formData.distanceAllowance" class="width-small" /> Euro / km
|
||||
<br /><br />
|
||||
|
||||
<span v-if="props.data.type !== 'running_job'">
|
||||
Abrechnungsschluss am:
|
||||
<input type="date" style="margin-top: 10px;" id="autoclose_date" v-model="formData.billingDeadline" />
|
||||
<br /><br />
|
||||
</span>
|
||||
|
||||
<div style="margin-top: 10px;">
|
||||
<label style="display: flex; align-items: center; cursor: pointer;">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="mail_on_new"
|
||||
v-model="formData.mailOnNew"
|
||||
style="margin-right: 8px; cursor: pointer;"
|
||||
/>
|
||||
<span>E-Mail-Benachrichtigung bei neuen Abrechnungen</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<br />
|
||||
<input type="button" value="Speichern" @click="updateCostUnit" />
|
||||
</Modal>
|
||||
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.mareike-save-button {
|
||||
background-color: #2271b1 !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
</style>
|
||||
246
app/Domains/CostUnit/Views/Partials/ListCostUnits.vue
Normal file
246
app/Domains/CostUnit/Views/Partials/ListCostUnits.vue
Normal file
@@ -0,0 +1,246 @@
|
||||
<script setup>
|
||||
import {createApp, ref} from 'vue'
|
||||
/*import {
|
||||
_mareike_download_as_zip,
|
||||
|
||||
_mareike_use_webdav
|
||||
} from "../../../assets/javascripts/library";*/
|
||||
//import LoadingModal from "../../../assets/components/LoadingModal.vue";
|
||||
//import Invoices from '../invoices/index.vue'
|
||||
import { useAjax } from "../../../../../resources/js/components/ajaxHandler.js";
|
||||
import CostUnitDetails from "./CostUnitDetails.vue";
|
||||
import {toast} from "vue3-toastify";
|
||||
import Treasurers from "./Treasurers.vue";
|
||||
|
||||
//import CostUnitDetails from "./CostUnitDetails.vue";
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: [Array, Object],
|
||||
default: () => []
|
||||
},
|
||||
|
||||
deep_jump_id: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
|
||||
deep_jump_id_sub: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
})
|
||||
|
||||
const showInvoiceList = ref(false)
|
||||
const invoices = ref(null)
|
||||
const current_cost_unit = ref(null)
|
||||
const showLoading = ref(false)
|
||||
const show_invoice = ref(false)
|
||||
const invoice = ref(null)
|
||||
|
||||
const show_cost_unit = ref(false)
|
||||
const showTreasurers = ref(false)
|
||||
const costUnit = ref(null)
|
||||
|
||||
|
||||
const { data, loading, error, request, download } = useAjax()
|
||||
|
||||
if (props.deep_jump_id > 0) {
|
||||
// open_invoice_list(props.deep_jump_id, 'new', props.deep_jump_id_sub)
|
||||
}
|
||||
|
||||
async function costUnitDetails(costUnitId) {
|
||||
const data = await request('/api/v1/cost-unit/' + costUnitId + '/details', {
|
||||
method: "GET",
|
||||
});
|
||||
|
||||
showLoading.value = false;
|
||||
|
||||
if (data.status === 'success') {
|
||||
costUnit.value = data.costUnit
|
||||
show_cost_unit.value = true
|
||||
} else {
|
||||
toast.error(data.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function editTreasurers(costUnitId) {
|
||||
const data = await request('/api/v1/cost-unit/' + costUnitId + '/treasurers', {
|
||||
method: "GET",
|
||||
});
|
||||
|
||||
showLoading.value = false;
|
||||
|
||||
if (data.status === 'success') {
|
||||
costUnit.value = data.costUnit
|
||||
showTreasurers.value = true
|
||||
} else {
|
||||
toast.error(data.message);
|
||||
}
|
||||
}
|
||||
|
||||
function loadInvoices(cost_unit_id) {
|
||||
window.location.href = '/cost-unit/' + cost_unit_id;
|
||||
}
|
||||
|
||||
async function denyNewRequests(costUnitId) {
|
||||
changeCostUnitState(costUnitId, 'close');
|
||||
}
|
||||
|
||||
|
||||
async function archiveCostUnit(costUnitId) {
|
||||
changeCostUnitState(costUnitId, 'archive');
|
||||
}
|
||||
|
||||
|
||||
async function allowNewRequests(costUnitId) {
|
||||
changeCostUnitState(costUnitId, 'open');
|
||||
}
|
||||
|
||||
|
||||
async function changeCostUnitState(costUnitId, endPoint) {
|
||||
showLoading.value = true;
|
||||
const data = await request('/api/v1/cost-unit/' + costUnitId + '/' + endPoint, {
|
||||
method: "POST",
|
||||
});
|
||||
|
||||
showLoading.value = false;
|
||||
if (data.status === 'success') {
|
||||
toast.success(data.message);
|
||||
document.getElementById('costUnitBox_' + costUnitId).style.display = 'none';
|
||||
} else {
|
||||
toast.error(data.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function export_payouts(cost_unit_id) {
|
||||
showLoading.value = true;
|
||||
|
||||
try {
|
||||
if (_mareike_download_as_zip()) {
|
||||
const response = await fetch("/wp-json/mareike/costunits/export-payouts", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
mareike_nonce: _mareike_nonce(),
|
||||
costunit: cost_unit_id,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error('Fehler beim Export (ZIP)');
|
||||
const blob = await response.blob();
|
||||
const downloadUrl = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = downloadUrl;
|
||||
a.download = `payouts-${cost_unit_id}.zip`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
window.URL.revokeObjectURL(downloadUrl);
|
||||
} else {
|
||||
await request("/wp-json/mareike/costunits/export-payouts", {
|
||||
method: "POST",
|
||||
body: {
|
||||
mareike_nonce: _mareike_nonce(),
|
||||
costunit: cost_unit_id,
|
||||
}
|
||||
});
|
||||
}
|
||||
showLoading.value = false;
|
||||
toast.success('Die Abrechnungen wurden exportiert.');
|
||||
} catch (err) {
|
||||
showLoading.value = false;
|
||||
toast.error('Beim Export der Abrechnungen ist ein Fehler aufgetreten.');
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="props.data.cost_units && props.data.cost_units.length > 0 && !showInvoiceList">
|
||||
<h2>{{ props.data.cost_unit_title }}</h2>
|
||||
<span v-for="costUnit in props.data.cost_units" class="costunit-list" :id="'costUnitBox_' + costUnit.id">
|
||||
<table style="width: 100%">
|
||||
<thead>
|
||||
<tr><td colspan="5">
|
||||
{{ costUnit.name }}
|
||||
</td></tr>
|
||||
</thead>
|
||||
<tr>
|
||||
<th>Gesamtbeitrag</th>
|
||||
<td>{{ costUnit.totalAmount }}</td>
|
||||
<th>Unbearbeitet</th>
|
||||
<td>{{ costUnit.countNewInvoices }}</td>
|
||||
<td rowspan="4" style="vertical-align: top;">
|
||||
<input v-if="!costUnit.archived" type="button" value="Abrechnungen bearbeiten" @click="loadInvoices(costUnit.id)" />
|
||||
<input v-else type="button" value="Abrechnungen einsehen" />
|
||||
<br />
|
||||
<input v-if="!costUnit.archived" type="button" value="Genehmigte Abrechnungen exportieren" style="margin-top: 10px;" />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Spenden</th>
|
||||
<td>{{ costUnit.donatedAmount }}</td>
|
||||
|
||||
<th>Nicht exportiert</th>
|
||||
<td>{{ costUnit.countApprovedInvoices }}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="2"></td>
|
||||
|
||||
<th>Ohne Auszahlung</th>
|
||||
<td colspan="2">{{ costUnit.countDonatedInvoices }}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="2"></td>
|
||||
|
||||
<th>Abgelehnt</th>
|
||||
<td colspan="2">{{ costUnit.countDeniedInvoices }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="5" style="width: 100%; padding-top: 20px;">
|
||||
<strong @click="costUnitDetails(costUnit.id)" v-if="costUnit.allow_new" class="link">Details anpassen</strong>
|
||||
<strong @click="editTreasurers(costUnit.id)" v-if="!costUnit.archived" class="link">Schatzis zuweisen</strong>
|
||||
<strong @click="denyNewRequests(costUnit.id)" v-if="costUnit.allow_new" class="link" style="color: #ff0000">Neue Abrechnungen verbieten</strong>
|
||||
<strong @click="allowNewRequests(costUnit.id)" v-if="!costUnit.allow_new && !costUnit.archived" class="link" style="color: #529a30">Neue Abrechnungen erlauben</strong>
|
||||
<strong @click="archiveCostUnit(costUnit.id)" v-if="!costUnit.allow_new && !costUnit.archived" class="link" style="color: #ff0000">Veranstaltung archivieren</strong>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</span>
|
||||
|
||||
<CostUnitDetails :data="costUnit" :showCostUnit="show_cost_unit" v-if="show_cost_unit" @close="show_cost_unit = false" />
|
||||
<Treasurers :data="costUnit" :showTreasurers="showTreasurers" v-if="showTreasurers" @closeTreasurers="showTreasurers = false" />
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div v-else-if="showInvoiceList">
|
||||
<invoices :data="invoices" :load_invoice_id="props.deep_jump_id_sub" :cost_unit_id="current_cost_unit" />
|
||||
|
||||
|
||||
<LoadingModal :show="showLoading" v-if="_mareike_use_webdav()" message="Die PDF-Dateien werden asynchron erzeugt, diese sollten in 10 Minuten auf dem Webdav-Server liegen', 'mareike')" />
|
||||
<LoadingModal :show="showLoading" v-else message='Die Abrechnungen werden exportiert, bitte warten.' />
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<strong style="width: 100%; text-align: center; display: block; margin-top: 20px;">
|
||||
Es gibt keine Kostenstellen in dieser Kategorie, für die du verantwortlich bist.
|
||||
</strong>
|
||||
</div>
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.costunit-list {
|
||||
width: 96% !important;
|
||||
}
|
||||
</style>
|
||||
80
app/Domains/CostUnit/Views/Partials/ListInvoices.vue
Normal file
80
app/Domains/CostUnit/Views/Partials/ListInvoices.vue
Normal file
@@ -0,0 +1,80 @@
|
||||
<script setup>
|
||||
import Icon from "../../../../Views/Components/Icon.vue";
|
||||
import InvoiceDetails from "../../../Invoice/Views/Partials/invoiceDetails/InvoiceDetails.vue";
|
||||
import { useAjax } from "../../../../../resources/js/components/ajaxHandler.js";
|
||||
import {ref} from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
data: Object
|
||||
})
|
||||
|
||||
const { request } = useAjax()
|
||||
const invoice = ref(null)
|
||||
const show_invoice = ref(false)
|
||||
const localData = ref(props.data)
|
||||
|
||||
async function openInvoiceDetails(invoiceId) {
|
||||
const url = '/api/v1/invoice/details/' + invoiceId
|
||||
|
||||
try {
|
||||
const response = await fetch(url, { method: 'GET' })
|
||||
|
||||
const result = await response.json()
|
||||
invoice.value = result.invoice
|
||||
show_invoice.value = true
|
||||
} catch (err) {
|
||||
console.error('Error fetching invoices:', err)
|
||||
}
|
||||
}
|
||||
|
||||
async function reload() {
|
||||
const url = "/api/v1/cost-unit/" + props.data.costUnit.id + "/invoice-list/" + props.data.endpoint
|
||||
try {
|
||||
const response = await fetch(url, { method: 'GET' })
|
||||
if (!response.ok) throw new Error('Fehler beim Laden')
|
||||
|
||||
const result = await response.json()
|
||||
localData.value = result
|
||||
} catch (err) {
|
||||
console.error('Error fetching invoices:', err)
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<table v-if="localData.invoices.length > 0" class="invoice-list-table">
|
||||
<tr>
|
||||
<td colspan="6">{{props.data.costUnit.name}}</td>
|
||||
</tr>
|
||||
|
||||
<tr v-for="invoice in localData.invoices" :id="'invoice_' + invoice.id">
|
||||
<td>{{invoice.invoiceNumber}}</td>
|
||||
<td>{{invoice.invoiceType}}</td>
|
||||
<td>
|
||||
{{invoice.amount}}
|
||||
</td>
|
||||
<td style="width: 150px;">
|
||||
<Icon v-if="invoice.donation" name="hand-holding-dollar" style="color: #ffffff; background-color: green" />
|
||||
<Icon v-if="invoice.alreadyPaid" name="comments-dollar" style="color: #ffffff; background-color: green" />
|
||||
</td>
|
||||
<td>
|
||||
{{invoice.contactName}}<br />
|
||||
<label v-if="invoice.contactEmail !== '--'">{{invoice.contactEmail}}<br /></label>
|
||||
<label v-if="invoice.contactPhone !== '--'">{{invoice.contactPhone}}<br /></label>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<input type="button" value="Abrechnung Anzeigen" @click="openInvoiceDetails(invoice.id)" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p v-else>Es sind keine Abrechnungen in dieser Kategorie vorhanden.</p>
|
||||
|
||||
<InvoiceDetails :data="invoice" :show-invoice="show_invoice" v-if="show_invoice" @close="show_invoice = false; reload()" />
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
99
app/Domains/CostUnit/Views/Partials/Treasurers.vue
Normal file
99
app/Domains/CostUnit/Views/Partials/Treasurers.vue
Normal file
@@ -0,0 +1,99 @@
|
||||
<script setup>
|
||||
|
||||
import {onMounted, reactive, ref} from "vue";
|
||||
import Modal from "../../../../Views/Components/Modal.vue";
|
||||
import { useAjax } from "../../../../../resources/js/components/ajaxHandler.js";
|
||||
import AmountInput from "../../../../Views/Components/AmountInput.vue";
|
||||
import {toast} from "vue3-toastify";
|
||||
|
||||
const selectedTreasurers = ref([])
|
||||
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}, showTreasurers: Boolean
|
||||
})
|
||||
|
||||
const commonProps = reactive({
|
||||
activeUsers: [],
|
||||
});
|
||||
|
||||
|
||||
|
||||
onMounted(async () => {
|
||||
const response = await fetch('/api/v1/retreive-global-data');
|
||||
const data = await response.json();
|
||||
Object.assign(commonProps, data);
|
||||
|
||||
selectedTreasurers.value = props.data.treasurers?.map(t => t.id) ?? []
|
||||
});
|
||||
|
||||
|
||||
|
||||
const mail_on_new = ref(Boolean(Number(props.data.mail_on_new)))
|
||||
|
||||
const emit = defineEmits(['closeTreasurers'])
|
||||
|
||||
const { request } = useAjax()
|
||||
function closeTreasurers() {
|
||||
emit('closeTreasurers')
|
||||
}
|
||||
|
||||
|
||||
const formData = reactive({
|
||||
billingDeadline: props.data.billingDeadline,
|
||||
mailOnNew: mail_on_new.value,
|
||||
distanceAllowance: props.data.distanceAllowanceSmall,
|
||||
});
|
||||
|
||||
async function updateCostUnit() {
|
||||
const data = await request('/api/v1/cost-unit/' + props.data.id + '/treasurers', {
|
||||
method: "POST",
|
||||
body: {
|
||||
selectedTreasurers: selectedTreasurers.value,
|
||||
}
|
||||
});
|
||||
|
||||
closeTreasurers();
|
||||
if (data.status === 'success') {
|
||||
toast.success(data.message);
|
||||
} else {
|
||||
toast.error(data.message);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(props.data.treasurers)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
<Modal
|
||||
:show="showTreasurers"
|
||||
title="Schatzis zuweisen"
|
||||
@close="emit('closeTreasurers')"
|
||||
>
|
||||
Zuständige Schatzis:
|
||||
|
||||
<p v-for="user in commonProps.activeUsers">
|
||||
<input
|
||||
type="checkbox"
|
||||
:id="'user_' + user.id"
|
||||
:value="user.id"
|
||||
v-model="selectedTreasurers"
|
||||
/>
|
||||
<label :for="'user_' + user.id">{{user.fullname}}</label>
|
||||
</p>
|
||||
|
||||
<input type="button" value="Speichern" @click="updateCostUnit" />
|
||||
</Modal>
|
||||
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.mareike-save-button {
|
||||
background-color: #2271b1 !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
</style>
|
||||
40
app/Domains/Dashboard/Controllers/DashboardController.php
Normal file
40
app/Domains/Dashboard/Controllers/DashboardController.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Dashboard\Controllers;
|
||||
|
||||
use App\Providers\AuthCheckProvider;
|
||||
use App\Providers\InertiaProvider;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Js;
|
||||
|
||||
class DashboardController extends CommonController {
|
||||
public function __invoke(Request $request) {
|
||||
if ($this->checkAuth()) {
|
||||
return $this->renderForLoggedInUser($request);
|
||||
}
|
||||
|
||||
return redirect()->intended('/login');
|
||||
}
|
||||
|
||||
private function renderForLoggedInUser(Request $request) {
|
||||
$authCheckProvider = new AuthCheckProvider;
|
||||
$inertiaProvider = new InertiaProvider('Dashboard/Dashboard', ['appName' => app('tenant')->name]);
|
||||
return $inertiaProvider->render();
|
||||
}
|
||||
|
||||
private function renderForGuest(Request $request) {
|
||||
}
|
||||
|
||||
public function getMyInvoices() : JsonResponse {
|
||||
$invoices = $this->invoices->getMyInvoicesWidget();
|
||||
return response()->json(['myInvoices' => $invoices]);
|
||||
}
|
||||
|
||||
public function getOpenCostUnits() : JsonResponse {
|
||||
$costUnits = $this->costUnits->listForSummary(5);
|
||||
return response()->json(['openCostUnits' => $costUnits]);
|
||||
}
|
||||
}
|
||||
15
app/Domains/Dashboard/Routes/api.php
Normal file
15
app/Domains/Dashboard/Routes/api.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
use App\Domains\Dashboard\Controllers\DashboardController;
|
||||
use App\Middleware\IdentifyTenant;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::middleware(IdentifyTenant::class)->group(function () {
|
||||
Route::middleware(['auth'])->group(function () {
|
||||
Route::prefix('api/v1/dashboard')->group(function () {
|
||||
Route::get('/my-invoices', [DashboardController::class, 'getMyInvoices']);
|
||||
Route::get('/open-cost-units', [DashboardController::class, 'getOpenCostUnits']);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
1
app/Domains/Dashboard/Routes/web.php
Normal file
1
app/Domains/Dashboard/Routes/web.php
Normal file
@@ -0,0 +1 @@
|
||||
<?php
|
||||
37
app/Domains/Dashboard/Views/Dashboard.vue
Normal file
37
app/Domains/Dashboard/Views/Dashboard.vue
Normal file
@@ -0,0 +1,37 @@
|
||||
<script setup>
|
||||
import AppLayout from '../../../../resources/js/layouts/AppLayout.vue'
|
||||
import ShadowedBox from "../../../Views/Components/ShadowedBox.vue";
|
||||
import {onMounted} from "vue";
|
||||
import {toast} from "vue3-toastify";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppLayout title='Dashboard'>
|
||||
<diV class="dashboard-widget-container">
|
||||
<shadowed-box class="dashboard-widget-box" style="width: 60%;">
|
||||
Meine Anmeldungen
|
||||
</shadowed-box>
|
||||
|
||||
<shadowed-box class="dashboard-widget-box">
|
||||
Meine Abrechnungen
|
||||
</shadowed-box>
|
||||
</diV>
|
||||
</AppLayout>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.dashboard-widget-container {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
flex-wrap: wrap;
|
||||
position: relative;
|
||||
|
||||
|
||||
}
|
||||
|
||||
.dashboard-widget-box {
|
||||
flex-grow: 1; display: inline-block;
|
||||
height: 150px;
|
||||
margin: 0 10px;
|
||||
}
|
||||
</style>
|
||||
31
app/Domains/Dashboard/Views/Partials/Widgets/MyInvoices.vue
Normal file
31
app/Domains/Dashboard/Views/Partials/Widgets/MyInvoices.vue
Normal file
@@ -0,0 +1,31 @@
|
||||
<script setup>
|
||||
import {onMounted, reactive} from "vue";
|
||||
|
||||
const myInvoices = reactive({
|
||||
'myInvoices': '',
|
||||
'approvedInvoices': '',
|
||||
'deniedInvoices': '',
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
const response = await fetch('/api/v1/dashboard/my-invoices');
|
||||
const data = await response.json();
|
||||
Object.assign(myInvoices, data);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<p v-for="invoice in myInvoices.myInvoices" class="widget-content-item">
|
||||
<a :href="'/invoices/my-invoices/' + invoice.slug" class="link">{{invoice.title}} ({{invoice.count}})</a>
|
||||
<label>
|
||||
{{invoice.amount}}
|
||||
</label>
|
||||
</p>
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,40 @@
|
||||
<script setup>
|
||||
import {onMounted, reactive} from "vue";
|
||||
|
||||
const costUnits = reactive({
|
||||
'openCostUnits': '',
|
||||
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
const response = await fetch('/api/v1/dashboard/open-cost-units');
|
||||
const data = await response.json();
|
||||
Object.assign(costUnits, data);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<table class="widget-content-item" v-if="costUnits.openCostUnits.length > 0">
|
||||
<tr>
|
||||
<td style="font-weight: bold">Kostenstelle</td>
|
||||
<td style="font-weight: bold">Neu</td>
|
||||
<td style="font-weight: bold">Ang</td>
|
||||
<td style="font-weight: bold">Betrag</td>
|
||||
</tr>
|
||||
|
||||
<tr v-for="costUnit in costUnits.openCostUnits">
|
||||
<td><a :href="'/cost-unit/' + costUnit.id" class="link">{{costUnit.name}}</a></td>
|
||||
<td>{{costUnit.new_invoices_count}}</td>
|
||||
<td>{{costUnit.approved_invoices_count}}</td>
|
||||
<td>{{costUnit.totalAmount}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p v-else style="padding: 10px; font-weight: bold">Es existieren im Moment keine Abrechnugnen, um die du dich kümmern musst.</p>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Invoice\Actions\ChangeStatus;
|
||||
|
||||
use App\Enumerations\InvoiceStatus;
|
||||
|
||||
class ChangeStatusCommand {
|
||||
private ChangeStatusRequest $request;
|
||||
|
||||
public function __construct(ChangeStatusRequest $request) {
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function execute() : ChangeStatusResponse {
|
||||
$response = new ChangeStatusResponse();
|
||||
|
||||
switch ($this->request->status) {
|
||||
case InvoiceStatus::INVOICE_STATUS_APPROVED:
|
||||
$this->request->invoice->status = InvoiceStatus::INVOICE_STATUS_APPROVED;
|
||||
$this->request->invoice->approved_by = auth()->user()->id;
|
||||
$this->request->invoice->approved_at = now();
|
||||
break;
|
||||
|
||||
case InvoiceStatus::INVOICE_STATUS_DENIED:
|
||||
$this->request->invoice->status = InvoiceStatus::INVOICE_STATUS_DENIED;
|
||||
$this->request->invoice->denied_by = auth()->user()->id;
|
||||
$this->request->invoice->denied_at = now();
|
||||
$this->request->invoice->denied_reason = $this->request->comment;
|
||||
break;
|
||||
case InvoiceStatus::INVOICE_STATUS_NEW:
|
||||
$this->request->invoice->status = InvoiceStatus::INVOICE_STATUS_NEW;
|
||||
$this->request->invoice->approved_by = null;
|
||||
$this->request->invoice->approved_at = null;
|
||||
$this->request->invoice->denied_by = null;
|
||||
$this->request->invoice->denied_at = null;
|
||||
$this->request->invoice->comment = $this->request->invoice->denied_reason;
|
||||
$this->request->invoice->denied_reason = null;
|
||||
break;
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
if ($this->request->invoice->save()) {
|
||||
$response->success = true;
|
||||
}
|
||||
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Invoice\Actions\ChangeStatus;
|
||||
|
||||
use App\Models\Invoice;
|
||||
|
||||
class ChangeStatusRequest {
|
||||
public Invoice $invoice;
|
||||
public string $status;
|
||||
public ?string $comment;
|
||||
|
||||
public function __construct(Invoice $invoice, string $status, ?string $comment = null) {
|
||||
$this->invoice = $invoice;
|
||||
$this->status = $status;
|
||||
$this->comment = $comment;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Invoice\Actions\ChangeStatus;
|
||||
|
||||
class ChangeStatusResponse {
|
||||
public bool $success;
|
||||
|
||||
function __construct() {
|
||||
$this->success = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Invoice\Actions\CreateInvoice;
|
||||
|
||||
use App\Enumerations\InvoiceStatus;
|
||||
use App\Models\Invoice;
|
||||
|
||||
class CreateInvoiceCommand {
|
||||
private CreateInvoiceRequest $request;
|
||||
|
||||
public function __construct(CreateInvoiceRequest $request) {
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function execute() : CreateInvoiceResponse {
|
||||
$response = new CreateInvoiceResponse();
|
||||
|
||||
if ($this->request->accountIban === 'undefined') {
|
||||
$this->request->accountIban = null;
|
||||
}
|
||||
|
||||
$invoice = Invoice::create([
|
||||
'tenant' => app('tenant')->slug,
|
||||
'cost_unit_id' => $this->request->costUnit->id,
|
||||
'invoice_number' => $this->generateInvoiceNumber(),
|
||||
'status' => InvoiceStatus::INVOICE_STATUS_NEW,
|
||||
'type' => $this->request->invoiceType,
|
||||
'type_other' => $this->request->invoiceTypeExtended,
|
||||
'donation' => $this->request->isDonation,
|
||||
'user_id' => $this->request->userId,
|
||||
'contact_name' => $this->request->contactName,
|
||||
'contact_email' => $this->request->contactEmail,
|
||||
'contact_phone' => $this->request->contactPhone,
|
||||
'contact_bank_owner' => $this->request->accountOwner,
|
||||
'contact_bank_iban' => $this->request->accountIban,
|
||||
'amount' => $this->request->totalAmount,
|
||||
'distance' => $this->request->distance,
|
||||
'travel_direction' => $this->request->travelRoute,
|
||||
'passengers' => $this->request->passengers,
|
||||
'transportation' => $this->request->transportations,
|
||||
'document_filename' => $this->request->receiptFile !== null ? $this->request->receiptFile->fullPath : null,
|
||||
]);
|
||||
|
||||
if ($invoice !== null) {
|
||||
$response->success = true;
|
||||
$response->invoice = $invoice;
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
|
||||
private function generateInvoiceNumber() : string {
|
||||
$lastInvoiceNumber = Invoice::query()
|
||||
->where('tenant', app('tenant')->slug)
|
||||
->whereYear('created_at', date('Y'))
|
||||
->count();
|
||||
|
||||
$invoiceNumber = $lastInvoiceNumber + 1;
|
||||
$invoiceNumber = str_pad($invoiceNumber, 4, '0', STR_PAD_LEFT);
|
||||
return sprintf('%1$s-%2$s', date('Y'), $invoiceNumber);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Invoice\Actions\CreateInvoice;
|
||||
|
||||
use App\Models\CostUnit;
|
||||
use App\ValueObjects\InvoiceFile;
|
||||
|
||||
class CreateInvoiceRequest {
|
||||
public CostUnit $costUnit;
|
||||
public string $contactName;
|
||||
public ?string $contactEmail;
|
||||
public ?string $contactPhone;
|
||||
public ?string $accountOwner;
|
||||
public ?string $accountIban;
|
||||
public string $invoiceType;
|
||||
public ?string $invoiceTypeExtended;
|
||||
public ?string $travelRoute;
|
||||
public ?int $distance;
|
||||
public ?int $passengers;
|
||||
public ?int $transportations;
|
||||
public ?InvoiceFile $receiptFile;
|
||||
public float $totalAmount;
|
||||
public bool $isDonation;
|
||||
public ?int $userId;
|
||||
|
||||
|
||||
public function __construct(
|
||||
CostUnit $costUnit,
|
||||
string $contactName,
|
||||
string $invoiceType,
|
||||
float $totalAmount,
|
||||
?InvoiceFile $receiptFile,
|
||||
bool $isDonation,
|
||||
?int $userId = null,
|
||||
?string $contactEmail = null,
|
||||
?string $contactPhone = null,
|
||||
?string $accountOwner = null,
|
||||
?string $accountIban = null,
|
||||
?string $invoiceTypeExtended = null,
|
||||
?string $travelRoute = null,
|
||||
?int $distance = null,
|
||||
?int $passengers = null,
|
||||
?int $transportations,
|
||||
|
||||
) {
|
||||
$this->costUnit = $costUnit;
|
||||
$this->contactName = $contactName;
|
||||
$this->invoiceType = $invoiceType;
|
||||
$this->invoiceTypeExtended = $invoiceTypeExtended;
|
||||
$this->travelRoute = $travelRoute;
|
||||
$this->distance = $distance;
|
||||
$this->passengers = $passengers;
|
||||
$this->transportations = $transportations;
|
||||
$this->receiptFile = $receiptFile;
|
||||
$this->contactEmail = $contactEmail;
|
||||
$this->contactPhone = $contactPhone;
|
||||
$this->accountOwner = $accountOwner;
|
||||
$this->accountIban = $accountIban;
|
||||
$this->totalAmount = $totalAmount;
|
||||
$this->isDonation = $isDonation;
|
||||
$this->userId = $userId;
|
||||
|
||||
if ($accountIban === 'undefined') {
|
||||
$this->accountIban = null;
|
||||
$this->accountOwner = null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Invoice\Actions\CreateInvoice;
|
||||
|
||||
use App\Models\Invoice;
|
||||
|
||||
class CreateInvoiceResponse {
|
||||
public bool $success;
|
||||
public ?Invoice $invoice;
|
||||
|
||||
public function __construct() {
|
||||
$this->success = false;
|
||||
$this->invoice = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Invoice\Actions\UpdateInvoice;
|
||||
|
||||
use App\Domains\Invoice\Actions\ChangeStatus\ChangeStatusCommand;
|
||||
use App\Domains\Invoice\Actions\ChangeStatus\ChangeStatusRequest;
|
||||
use App\Enumerations\InvoiceStatus;
|
||||
|
||||
class UpdateInvoiceCommand {
|
||||
private UpdateInvoiceRequest $request;
|
||||
|
||||
public function __construct(UpdateInvoiceRequest $request) {
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function execute() : UpdateInvoiceResponse {
|
||||
$response = new UpdateInvoiceResponse();
|
||||
|
||||
$this->request->invoice->amount = $this->request->amount->getAmount();
|
||||
$this->request->invoice->cost_unit_id = $this->request->costUnit->id;
|
||||
$this->request->invoice->type = $this->request->invoiceType;
|
||||
$this->request->invoice->comment = $this->request->comment;
|
||||
|
||||
$this->request->invoice->save();
|
||||
|
||||
$request = new ChangeStatusRequest($this->request->invoice, InvoiceStatus::INVOICE_STATUS_APPROVED);
|
||||
$changeStatusCommand = new ChangeStatusCommand($request);
|
||||
$changeStatusCommand->execute();
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Invoice\Actions\UpdateInvoice;
|
||||
|
||||
use App\Enumerations\InvoiceType;
|
||||
use App\Models\CostUnit;
|
||||
use App\Models\Invoice;
|
||||
use App\ValueObjects\Amount;
|
||||
|
||||
class UpdateInvoiceRequest {
|
||||
public string $comment;
|
||||
public string $invoiceType;
|
||||
public CostUnit $costUnit;
|
||||
public Invoice $invoice;
|
||||
public Amount $amount;
|
||||
|
||||
public function __construct(Invoice $invoice, string $comment, string $invoiceType, CostUnit $costUnit, Amount $amount) {
|
||||
$this->comment = $comment;
|
||||
$this->invoiceType = $invoiceType;
|
||||
$this->costUnit = $costUnit;
|
||||
$this->invoice = $invoice;
|
||||
$this->amount = $amount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Invoice\Actions\UpdateInvoice;
|
||||
|
||||
use App\Models\Invoice;
|
||||
|
||||
class UpdateInvoiceResponse {
|
||||
public bool $success;
|
||||
public ?Invoice $invoice;
|
||||
|
||||
public function __construct() {
|
||||
$this->success = false;
|
||||
$this->invoice = null;
|
||||
}
|
||||
}
|
||||
27
app/Domains/Invoice/Controllers/ChangeStateController.php
Normal file
27
app/Domains/Invoice/Controllers/ChangeStateController.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Invoice\Controllers;
|
||||
|
||||
use App\Domains\Invoice\Actions\ChangeStatus\ChangeStatusCommand;
|
||||
use App\Domains\Invoice\Actions\ChangeStatus\ChangeStatusRequest;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class ChangeStateController extends CommonController
|
||||
{
|
||||
public function __invoke(int $invoiceId, string $newState) : JsonResponse {
|
||||
$invoice = $this->invoices->getAsTreasurer($invoiceId);
|
||||
if ($invoice === null) {
|
||||
return response()->json([]);
|
||||
}
|
||||
|
||||
$comment = request()->get('reason') ?? null;
|
||||
$changeStatusRequest = new ChangeStatusRequest($invoice, $newState, $comment);
|
||||
$changeStatusCommand = new ChangeStatusCommand($changeStatusRequest);
|
||||
if ($changeStatusCommand->execute()->success) {
|
||||
return response()->json(['status' => 'success']);
|
||||
}
|
||||
|
||||
return response()->json([]);
|
||||
}
|
||||
}
|
||||
132
app/Domains/Invoice/Controllers/EditController.php
Normal file
132
app/Domains/Invoice/Controllers/EditController.php
Normal file
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Invoice\Controllers;
|
||||
|
||||
use App\Domains\Invoice\Actions\ChangeStatus\ChangeStatusCommand;
|
||||
use App\Domains\Invoice\Actions\ChangeStatus\ChangeStatusRequest;
|
||||
use App\Domains\Invoice\Actions\CreateInvoice\CreateInvoiceCommand;
|
||||
use App\Domains\Invoice\Actions\CreateInvoice\CreateInvoiceRequest;
|
||||
use App\Domains\Invoice\Actions\UpdateInvoice\UpdateInvoiceCommand;
|
||||
use App\Domains\Invoice\Actions\UpdateInvoice\UpdateInvoiceRequest;
|
||||
use App\Enumerations\CostUnitType;
|
||||
use App\Enumerations\InvoiceStatus;
|
||||
use App\Resources\InvoiceResource;
|
||||
use App\Scopes\CommonController;
|
||||
use App\ValueObjects\Amount;
|
||||
use App\ValueObjects\InvoiceFile;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class EditController extends CommonController{
|
||||
public function copyInvoice(int $invoiceId) : JsonResponse{
|
||||
$invoice = $this->invoices->getAsTreasurer($invoiceId);
|
||||
if ($invoice === null) {
|
||||
return response()->json([]);
|
||||
}
|
||||
|
||||
$receiptfile = null;
|
||||
if ($invoice->document_filename !== null) {
|
||||
$receiptfile = new InvoiceFile();
|
||||
$receiptfile->filename = $invoice->document_filename;
|
||||
$receiptfile->fullPath = $invoice->document_filename;
|
||||
}
|
||||
$createInvoiceRequest = new CreateInvoiceRequest(
|
||||
$invoice->costUnit()->first(),
|
||||
$invoice->contact_name,
|
||||
$invoice->type,
|
||||
$invoice->amount,
|
||||
$receiptfile,
|
||||
$invoice->donation,
|
||||
$invoice->user_id,
|
||||
$invoice->contact_email,
|
||||
$invoice->contact_phone,
|
||||
$invoice->contact_bank_owner,
|
||||
$invoice->contact_bank_iban,
|
||||
$invoice->type_other,
|
||||
$invoice->travel_direction,
|
||||
$invoice->distance,
|
||||
$invoice->passengers,
|
||||
$invoice->transportation
|
||||
);
|
||||
|
||||
$invoiceCreationCommand = new CreateInvoiceCommand($createInvoiceRequest);
|
||||
$newInvoice = $invoiceCreationCommand->execute()->invoice;
|
||||
|
||||
$invoiceDenyRequest = new ChangeStatusRequest($invoice,InvoiceStatus::INVOICE_STATUS_DENIED, 'Abrechnungskorrektur in Rechnungsnummer #' . $newInvoice->invoice_number . ' erstellt.');
|
||||
$invoiceDenyCommand = new ChangeStatusCommand($invoiceDenyRequest);
|
||||
$invoiceDenyCommand->execute();
|
||||
|
||||
|
||||
$runningJobs = $this->costUnits->getCostUnitsForNewInvoice(CostUnitType::COST_UNIT_TYPE_RUNNING_JOB);
|
||||
$currentEvents = $this->costUnits->getCostUnitsForNewInvoice(CostUnitType::COST_UNIT_TYPE_EVENT);
|
||||
|
||||
return response()->json([
|
||||
'invoice' => new InvoiceResource($invoice)->toArray(),
|
||||
'status' => 'success',
|
||||
'costUnits' => array_merge($runningJobs, $currentEvents),
|
||||
]);
|
||||
}
|
||||
|
||||
public function updateInvoice(int $invoiceId, Request $request) : JsonResponse {
|
||||
$invoice = $this->invoices->getAsTreasurer($invoiceId);
|
||||
if ($invoice === null) {
|
||||
return response()->json([]);
|
||||
}
|
||||
|
||||
$modifyData = $request->get('invoiceData');
|
||||
|
||||
$newAmount = Amount::fromString($modifyData['amount']);
|
||||
$amountLeft = Amount::fromString($invoice->amount);
|
||||
$amountLeft->subtractAmount($newAmount);
|
||||
|
||||
$newCostUnit = $this->costUnits->getById($modifyData['cost_unit'],true);
|
||||
$updateInvoiceRequest = new UpdateInvoiceRequest(
|
||||
$invoice,
|
||||
$modifyData['reason_of_correction'] ?? 'Abrechnungskorrektur',
|
||||
$modifyData['type_internal'],
|
||||
$newCostUnit,
|
||||
$newAmount
|
||||
);
|
||||
$updateInvoiceCommand = new UpdateInvoiceCommand($updateInvoiceRequest);
|
||||
$updateInvoiceCommand->execute();
|
||||
|
||||
|
||||
$newInvoice = null;
|
||||
if (isset($modifyData['duplicate']) && $modifyData['duplicate'] === true) {
|
||||
$receiptfile = null;
|
||||
if ($invoice->document_filename !== null) {
|
||||
$receiptfile = new InvoiceFile();
|
||||
$receiptfile->filename = $invoice->document_filename;
|
||||
$receiptfile->fullPath = $invoice->document_filename;
|
||||
}
|
||||
$createInvoiceRequest = new CreateInvoiceRequest(
|
||||
$invoice->costUnit()->first(),
|
||||
$invoice->contact_name,
|
||||
$invoice->type,
|
||||
$amountLeft->getAmount(),
|
||||
$receiptfile,
|
||||
$invoice->donation,
|
||||
$invoice->user_id,
|
||||
$invoice->contact_email,
|
||||
$invoice->contact_phone,
|
||||
$invoice->contact_bank_owner,
|
||||
$invoice->contact_bank_iban,
|
||||
$invoice->type_other,
|
||||
$invoice->travel_direction,
|
||||
$invoice->distance,
|
||||
$invoice->passengers,
|
||||
$invoice->transportation
|
||||
);
|
||||
|
||||
$invoiceCreationCommand = new CreateInvoiceCommand($createInvoiceRequest);
|
||||
$newInvoice = $invoiceCreationCommand->execute()->invoice;
|
||||
}
|
||||
|
||||
$useInvoice = $newInvoice ?? $invoice;
|
||||
$do_copy = $newInvoice !== null ? true : false;
|
||||
return response()->json([
|
||||
'invoice' => new InvoiceResource($useInvoice)->toArray(),
|
||||
'do_copy' => $do_copy,
|
||||
]);
|
||||
}
|
||||
}
|
||||
138
app/Domains/Invoice/Controllers/NewInvoiceController.php
Normal file
138
app/Domains/Invoice/Controllers/NewInvoiceController.php
Normal file
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Invoice\Controllers;
|
||||
|
||||
use App\Domains\Invoice\Actions\CreateInvoice\CreateInvoiceCommand;
|
||||
use App\Domains\Invoice\Actions\CreateInvoice\CreateInvoiceRequest;
|
||||
use App\Enumerations\CostUnitType;
|
||||
use App\Enumerations\InvoiceType;
|
||||
use App\Models\CostUnit;
|
||||
use App\Providers\FlashMessageProvider;
|
||||
use App\Providers\InertiaProvider;
|
||||
use App\Providers\UploadFileProvider;
|
||||
use App\Scopes\CommonController;
|
||||
use App\ValueObjects\Amount;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class NewInvoiceController extends CommonController {
|
||||
public function __invoke() {
|
||||
$userData = $this->users->getCurrentUserDetails();
|
||||
|
||||
$runningJobs = $this->costUnits->getCostUnitsForNewInvoice(CostUnitType::COST_UNIT_TYPE_RUNNING_JOB);
|
||||
$currentEvents = $this->costUnits->getCostUnitsForNewInvoice(CostUnitType::COST_UNIT_TYPE_EVENT);
|
||||
|
||||
|
||||
$inertiaProvider = new InertiaProvider('Invoice/NewInvoice', [
|
||||
'userName' => $userData['userName'],
|
||||
'userEmail' => $userData['userEmail'],
|
||||
'userTelephone' => $userData['userTelephone'],
|
||||
'userAccountOwner' => $userData['userAccountOwner'],
|
||||
'userAccountIban' => $userData['userAccountIban'],
|
||||
'runningJobs' => $runningJobs,
|
||||
'currentEvents' => $currentEvents,
|
||||
]);
|
||||
return $inertiaProvider->render();
|
||||
}
|
||||
|
||||
public function saveInvoice(Request $request, int $costUnitId, string $invoiceType) : JsonResponse {
|
||||
$costUnit = $this->costUnits->getById($costUnitId, true);
|
||||
if (null === $costUnit) {
|
||||
return response()->json([
|
||||
'status' => 'error',
|
||||
'message' => 'Beim Speichern ist ein Fehler aufgetreten. Bitte starte den Vorgang erneut.'
|
||||
]);
|
||||
}
|
||||
|
||||
$uploadedFile = null;
|
||||
if (null !== $request->file('receipt')) {
|
||||
$validation = sprintf('%1$s|%2$s|max:%3$s',
|
||||
'required',
|
||||
'mimes:pdf',
|
||||
env('MAX_INVOICE_FILE_SIZE', 16)*10
|
||||
);
|
||||
|
||||
$request->validate([
|
||||
'receipt' => $validation
|
||||
]);
|
||||
|
||||
$uploadFileProvider = new UploadFileProvider($request->file('receipt'), $costUnit);
|
||||
$uploadedFile = $uploadFileProvider->saveUploadedFile();
|
||||
}
|
||||
|
||||
switch ($invoiceType) {
|
||||
case InvoiceType::INVOICE_TYPE_TRAVELLING:
|
||||
|
||||
if ($uploadedFile !== null) {
|
||||
$amount = Amount::fromString($request->get('amount'))->getAmount();
|
||||
$distance = null;
|
||||
} else {
|
||||
$distance = Amount::fromString($request->get('amount'))->getRoundedAmount();
|
||||
$amount = $distance * $costUnit->distance_allowance;
|
||||
|
||||
}
|
||||
|
||||
$createInvoiceRequest = new CreateInvoiceRequest(
|
||||
$costUnit,
|
||||
$request->get('name'),
|
||||
InvoiceType::INVOICE_TYPE_TRAVELLING,
|
||||
$amount,
|
||||
$uploadedFile,
|
||||
'donation' === $request->get('decision') ? true : false,
|
||||
$this->users->getCurrentUserDetails()['userId'],
|
||||
$request->get('contactEmail'),
|
||||
$request->get('telephone'),
|
||||
$request->get('accountOwner'),
|
||||
$request->get('accountIban'),
|
||||
null,
|
||||
$request->get('otherText'),
|
||||
$distance,
|
||||
$request->get('havePassengers'),
|
||||
$request->get('materialTransportation'),
|
||||
);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
$createInvoiceRequest = new CreateInvoiceRequest(
|
||||
$costUnit,
|
||||
$request->get('name'),
|
||||
$invoiceType,
|
||||
Amount::fromString($request->get('amount'))->getAmount(),
|
||||
$uploadedFile,
|
||||
'donation' === $request->get('decision') ? true : false,
|
||||
$this->users->getCurrentUserDetails()['userId'],
|
||||
$request->get('contactEmail'),
|
||||
$request->get('telephone'),
|
||||
$request->get('accountOwner'),
|
||||
$request->get('accountIban'),
|
||||
$request->get('otherText'),
|
||||
null,
|
||||
null,
|
||||
$request->get('havePassengers'),
|
||||
$request->get('materialTransportation'),
|
||||
);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
$command = new CreateInvoiceCommand($createInvoiceRequest);
|
||||
$response = $command->execute();
|
||||
if ($response->success) {
|
||||
new FlashMessageProvider(
|
||||
'Die Abrechnung wurde erfolgreich angelegt.' . PHP_EOL . PHP_EOL . 'Sollten wir Rückfragen haben, melden wir uns bei dir',
|
||||
'success'
|
||||
);
|
||||
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'message' => 'Alright'
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
dd($request->all());
|
||||
}
|
||||
}
|
||||
55
app/Domains/Invoice/Controllers/ShowInvoiceController.php
Normal file
55
app/Domains/Invoice/Controllers/ShowInvoiceController.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Invoice\Controllers;
|
||||
|
||||
use App\Enumerations\CostUnitType;
|
||||
use App\Resources\InvoiceResource;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
|
||||
class ShowInvoiceController extends CommonController {
|
||||
public function __invoke(int $invoiceId) : JsonResponse{
|
||||
$invoice = $this->invoices->getAsTreasurer($invoiceId);
|
||||
|
||||
$runningJobs = $this->costUnits->getCostUnitsForNewInvoice(CostUnitType::COST_UNIT_TYPE_RUNNING_JOB);
|
||||
$currentEvents = $this->costUnits->getCostUnitsForNewInvoice(CostUnitType::COST_UNIT_TYPE_EVENT);
|
||||
|
||||
return response()->json([
|
||||
'invoice' => new InvoiceResource($invoice)->toArray(),
|
||||
'costUnits' => array_merge($runningJobs, $currentEvents),
|
||||
]);
|
||||
}
|
||||
|
||||
public function showReceipt(int $invoiceId): BinaryFileResponse
|
||||
{
|
||||
$invoice = $this->invoices->getAsTreasurer($invoiceId);
|
||||
if (null === $invoice) {
|
||||
abort(404, 'Datei nicht gefunden');
|
||||
}
|
||||
|
||||
if (null === $invoice->document_filename) {
|
||||
abort(404, 'Datei nicht gefunden');
|
||||
}
|
||||
|
||||
$path = $invoice->document_filename;
|
||||
// Pfad zur Datei
|
||||
$fullPath = 'private/' . $path;
|
||||
|
||||
|
||||
|
||||
|
||||
if (!Storage::exists($path)) {
|
||||
|
||||
|
||||
|
||||
abort(404, 'Datei nicht gefunden');
|
||||
}
|
||||
|
||||
return response()->file(storage_path('app/' . $fullPath), [
|
||||
'Content-Type' => 'application/pdf'
|
||||
]);
|
||||
}
|
||||
}
|
||||
32
app/Domains/Invoice/Routes/api.php
Normal file
32
app/Domains/Invoice/Routes/api.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
use App\Domains\CostUnit\Controllers\DistanceAllowanceController;
|
||||
use App\Domains\CostUnit\Controllers\ListController;
|
||||
use App\Domains\Invoice\Controllers\ChangeStateController;
|
||||
use App\Domains\Invoice\Controllers\EditController;
|
||||
use App\Domains\Invoice\Controllers\NewInvoiceController;
|
||||
use App\Domains\Invoice\Controllers\ShowInvoiceController;
|
||||
use App\Middleware\IdentifyTenant;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::middleware(IdentifyTenant::class)->group(function () {
|
||||
Route::prefix('api/v1/invoice')->group(function () {
|
||||
Route::post('/new/{costUnitId}/{invoiceType}', [NewInvoiceController::class, 'saveInvoice']);
|
||||
Route::middleware(['auth'])->group(function () {
|
||||
Route::get('/details/{invoiceId}', ShowInvoiceController::class);
|
||||
Route::get('/showReceipt/{invoiceId}', [ShowInvoiceController::class, 'showReceipt']);
|
||||
|
||||
Route::post('/details/{invoiceId}/change-state/{newState}', ChangeStateController::class);
|
||||
Route::post('/details/{invoiceId}/copy', [EditController::class, 'copyInvoice']);
|
||||
Route::post('/details/{invoiceId}/update', [EditController::class, 'updateInvoice']);
|
||||
|
||||
|
||||
|
||||
|
||||
Route::get('/create', [CreateController::class, 'showForm']);
|
||||
});
|
||||
|
||||
|
||||
|
||||
});
|
||||
});
|
||||
22
app/Domains/Invoice/Routes/web.php
Normal file
22
app/Domains/Invoice/Routes/web.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
use App\Domains\CostUnit\Controllers\CreateController;
|
||||
use App\Domains\CostUnit\Controllers\ListController;
|
||||
use App\Domains\Invoice\Controllers\NewInvoiceController;
|
||||
use App\Middleware\IdentifyTenant;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::middleware(IdentifyTenant::class)->group(function () {
|
||||
Route::prefix('invoice')->group(function () {
|
||||
Route::get('/new', NewInvoiceController::class);
|
||||
|
||||
|
||||
|
||||
Route::middleware(['auth'])->group(function () {
|
||||
Route::get('/create', [CreateController::class, 'showForm']);
|
||||
});
|
||||
|
||||
|
||||
|
||||
});
|
||||
});
|
||||
79
app/Domains/Invoice/Views/NewInvoice.vue
Normal file
79
app/Domains/Invoice/Views/NewInvoice.vue
Normal file
@@ -0,0 +1,79 @@
|
||||
<script setup>
|
||||
import AppLayout from '../../../../resources/js/layouts/AppLayout.vue'
|
||||
import TextResource from "../../../Views/Components/TextResource.vue";
|
||||
import Modal from "../../../Views/Components/Modal.vue";
|
||||
import {onMounted, reactive, ref} from "vue";
|
||||
import ExpenseAccounting from "./Partials/newInvoice/expense-accounting.vue";
|
||||
import TravelExpenseAccounting from "./Partials/newInvoice/travel-expense-accounting.vue";
|
||||
|
||||
const props = defineProps({
|
||||
currentEvents: Object,
|
||||
runningJobs: Object,
|
||||
userId: Number,
|
||||
userName: String,
|
||||
userEmail: String,
|
||||
userTelephone: String,
|
||||
userIban: String,
|
||||
userAccountOwner: String,
|
||||
})
|
||||
|
||||
const isOpen = ref(false)
|
||||
const eventId = ref(0);
|
||||
const invoiceType = ref('');
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppLayout title='Neue Abrechnung'>
|
||||
<div class="invoice-main-flexbox" v-if="eventId === 0">
|
||||
<div
|
||||
class="invoice-type-layer"
|
||||
@click="isOpen = true;invoiceType='expense-accounting'"
|
||||
>
|
||||
<TextResource textName="NEW_COMMON_COST_EXPENSE_DESCRIPTION" />
|
||||
</div>
|
||||
|
||||
|
||||
<div
|
||||
class="invoice-type-layer"
|
||||
@click="isOpen = true;invoiceType='travel-expense-accounting'"
|
||||
>
|
||||
<TextResource textName="NEW_TRAVEL_COST_EXPENSE_DESCRIPTION" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ExpenseAccounting v-if="invoiceType === 'expense-accounting' && eventId !== 0"
|
||||
:eventId="eventId"
|
||||
:userName="props.userName"
|
||||
:userEmail="props.userEmail"
|
||||
:userTelephone="props.userTelephone"
|
||||
:userIban="props.userIban"
|
||||
:userAccountOwner="props.userAccountOwner"
|
||||
:userId="props.userId"
|
||||
/>
|
||||
|
||||
<TravelExpenseAccounting v-else-if="invoiceType === 'travel-expense-accounting' && eventId !== 0"
|
||||
:eventId="eventId"
|
||||
:userName="props.userName"
|
||||
:userEmail="props.userEmail"
|
||||
:userTelephone="props.userTelephone"
|
||||
:userIban="props.userIban"
|
||||
:userAccountOwner="props.userAccountOwner"
|
||||
:userId="props.userId"
|
||||
/>
|
||||
|
||||
<Modal :show="isOpen" title="Veranstaltung auswählen" @close="isOpen = false">
|
||||
<select v-model="eventId" @change="isOpen=false" style="width: 100%">
|
||||
<option value="0" disabled>Bitte auswählen</option>
|
||||
<optgroup label="Laufende Tätigkeiten">
|
||||
<option :value="event.id" v-for="event in props.runningJobs">{{ event.name }}</option>
|
||||
</optgroup>
|
||||
|
||||
<optgroup label="Veranstaltungen">
|
||||
<option :value="event.id" v-for="event in props.currentEvents">{{ event.name }}</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
</Modal>
|
||||
</AppLayout>
|
||||
|
||||
</template>
|
||||
@@ -0,0 +1,78 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
invoice: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<table class="travel_allowance">
|
||||
<tr><td colspan="2">
|
||||
Abrechnung einer Reisekostenpauschale
|
||||
</td></tr>
|
||||
<tr>
|
||||
<th>Reiseroute</th>
|
||||
<td>{{props.invoice.travelRoute}}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Gesamte Wegstrecke</th>
|
||||
<td>{{props.invoice.distance}} km</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Kilometerpauschale</th>
|
||||
<td>{{props.invoice.distanceAllowance}} Euro / km</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Gesamtbetrag</th>
|
||||
<td style="font-weight: bold">{{props.invoice.amount}}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Marterialtransport</th>
|
||||
<td>{{props.invoice.transportation}}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Hat Personen mitgenommen</th>
|
||||
<td>{{props.invoice.passengers}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.travel_allowance {
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
.travel_allowance tr th {
|
||||
width: 300px !important;
|
||||
border-left: 1px solid #ccc;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.travel_allowance tr td,
|
||||
.travel_allowance tr th {
|
||||
font-family: sans-serif;
|
||||
line-height: 1.8em;
|
||||
border-bottom: 1px solid #ccc;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.travel_allowance tr td:last-child {
|
||||
border-right: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.travel_allowance tr:first-child td:first-child {
|
||||
margin-bottom: 10px;
|
||||
font-weight: bold;
|
||||
background: linear-gradient(to bottom, #fff, #f6f7f7);
|
||||
border-top: 1px solid #ccc;
|
||||
border-left: 1px solid #ccc !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,116 @@
|
||||
<script setup>
|
||||
import PdfViewer from "../../../../../Views/Components/PdfViewer.vue";
|
||||
import DistanceAllowance from "./DistanceAllowance.vue";
|
||||
import {onMounted, reactive} from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
newInvoice: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
costUnits: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['submit', 'cancel'])
|
||||
|
||||
const formData = reactive({
|
||||
type_internal: props.newInvoice.internalType || '',
|
||||
cost_unit: props.newInvoice.costUnitId || '',
|
||||
amount: props.newInvoice.amountPlain || '',
|
||||
reason_of_correction: '',
|
||||
})
|
||||
|
||||
const submitForm = () => {
|
||||
emit('submit', formData)
|
||||
}
|
||||
|
||||
|
||||
const invoiceTypeCollection = reactive({
|
||||
invoiceTypes: {}
|
||||
});
|
||||
|
||||
|
||||
onMounted(async () => {
|
||||
const response = await fetch('/api/v1/core/retrieve-invoice-types-all');
|
||||
const data = await response.json();
|
||||
Object.assign(invoiceTypeCollection, data);
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form @submit.prevent="submitForm">
|
||||
<table style="width: 100%; font-family: sans-serif;">
|
||||
<tr>
|
||||
<td style="width: 150px;">Rechnungstyp:</td>
|
||||
<td style="width: 450px;">
|
||||
<select v-model="formData.type_internal" class="width-half-full">
|
||||
<option v-for="invoiceType in invoiceTypeCollection.invoiceTypes" :value="invoiceType.slug">{{invoiceType.name}}</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Kostenstelle:</td>
|
||||
<td>
|
||||
<select v-model="formData.cost_unit" class="width-half-full">
|
||||
<option v-for="costUnit in props.costUnits" :value="costUnit.id">{{costUnit.name}}</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Gesamtbetrag:</td>
|
||||
<td>
|
||||
<input type="text" v-model="formData.amount" class="width-small" /> Euro
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Grund der Korrektur:</td>
|
||||
<td>
|
||||
<input type="text" v-model="formData.reason_of_correction" class="width-half-full" />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<input type="checkbox" v-model="formData.duplicate" id="mareike_correct_invoice_duplicate" />
|
||||
<label for="mareike_correct_invoice_duplicate">Kopie zur Weiterbearbeitung erstellen</label>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<input type="submit" value="Speichern und freigeben" class="button mareike-accept-button" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
<br /><br />
|
||||
|
||||
<PdfViewer :url="'/api/v1/invoice/showReceipt/' + props.newInvoice.id" v-if="props.newInvoice.documentFilename !== null" />
|
||||
<DistanceAllowance v-else :invoice="props.newInvoice" />
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.width-full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.width-almost-full {
|
||||
width: calc(100% - 75px);
|
||||
}
|
||||
|
||||
.width-half-full {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.width-small {
|
||||
width: 100px;
|
||||
}
|
||||
</style>
|
||||
117
app/Domains/Invoice/Views/Partials/invoiceDetails/Header.vue
Normal file
117
app/Domains/Invoice/Views/Partials/invoiceDetails/Header.vue
Normal file
@@ -0,0 +1,117 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
modeShow: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(["accept", "deny", "fix", "reopen"])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span id="invoice_details_header">
|
||||
<table>
|
||||
<tr>
|
||||
<td>Name:</td>
|
||||
<td v-if="modeShow">{{props.data.contactName}}</td>
|
||||
<td v-else style="width: 300px;">{{props.data.contactName}}</td>
|
||||
|
||||
<td v-if="modeShow" style="width: 250px;">Kostenstelle</td>
|
||||
<td v-else style="width: 300px;">Kostensatelle (ursprünglich)</td>
|
||||
|
||||
<td>{{props.data.costUnitName}}</td>
|
||||
<td rowspan="4">
|
||||
<button
|
||||
v-if="props.data.status === 'new' && modeShow"
|
||||
@click="emit('accept')"
|
||||
class="button mareike-button mareike-accept-button"
|
||||
>
|
||||
Abrechnung annehmen
|
||||
</button>
|
||||
<button v-if="props.data.status === 'denied' && modeShow"
|
||||
@click="emit('reopen')"
|
||||
class="button mareike-button mareike-accept-button"
|
||||
>
|
||||
Abrechnung zur Wiedervorlage öffnen
|
||||
</button>
|
||||
<br />
|
||||
|
||||
<button
|
||||
v-if="props.data.status === 'new' && modeShow"
|
||||
@click="emit('fix')"
|
||||
class="button mareike-button mareike-fix-button"
|
||||
>
|
||||
Abrechnung ablehnen und korrigieren
|
||||
</button><br />
|
||||
|
||||
<button
|
||||
v-if="props.data.status === 'new' && modeShow"
|
||||
@click="emit('deny')"
|
||||
class="button mareike-button mareike-deny-button"
|
||||
>
|
||||
Abrechnung ablehnen
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Rest der Tabelle bleibt unverändert -->
|
||||
<tr>
|
||||
<td>E-Mail:</td>
|
||||
<td>{{props.data.contactEmail}}</td>
|
||||
<td>
|
||||
Abrechnungsnummer
|
||||
<label v-if="!modeShow"> (ursprünglich)</label>:
|
||||
</td>
|
||||
|
||||
<td>{{props.data.invoiceNumber}}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Telefon:</td>
|
||||
<td>{{props.data.contactPhone}}</td>
|
||||
<td>
|
||||
Abrechnungstyp
|
||||
<label v-if="!modeShow"> (Ursprünglich)</label>:
|
||||
</td>
|
||||
<td>{{props.data.invoiceType}}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Kontoinhaber*in:</td>
|
||||
<td>{{props.data.accountOwner}}</td>
|
||||
<td>Gesamtbetrag
|
||||
<label v-if="!modeShow"> (Ursprünglich)</label>:
|
||||
</td>
|
||||
<td><strong>{{props.data.amount}}</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>IBAN:</td>
|
||||
<td>{{props.data.accountIban}}</td>
|
||||
<td>Buchungsinformationen:</td>
|
||||
<td v-if="props.data.donation">Als Spende gebucht</td>
|
||||
<td v-else-if="props.data.alreadyPaid">Beleg ohne Auszahlung</td>
|
||||
<td v-else>Klassische Auszahlung</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Status:</td>
|
||||
<td>{{props.data.readableStatus}}</td>
|
||||
<td>Anmerkungen:</td>
|
||||
<td>
|
||||
<span v-if="props.data.status === 'denied'">
|
||||
{{props.data.deniedReason}}
|
||||
</span>
|
||||
<span v-else>{{props.data.comment}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,285 @@
|
||||
<script setup>
|
||||
|
||||
import FullScreenModal from "../../../../../Views/Components/FullScreenModal.vue";
|
||||
import {ref} from "vue";
|
||||
import Modal from "../../../../../Views/Components/Modal.vue";
|
||||
import { useAjax } from "../../../../../../resources/js/components/ajaxHandler.js";
|
||||
import ShowInvoicePartial from "./ShowInvoice.vue";
|
||||
import EditInvoicePartial from "./EditInvoice.vue";
|
||||
import Header from "./Header.vue";
|
||||
import {toast} from "vue3-toastify";
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}, showInvoice: Boolean
|
||||
})
|
||||
const showInvoice = ref(props.showInvoice)
|
||||
const emit = defineEmits(["close"])
|
||||
const denyInvoiceDialog = ref(false)
|
||||
const { data, loading, error, request } = useAjax()
|
||||
const modeShow = ref(true)
|
||||
const costUnits = ref(null)
|
||||
const newInvoice = ref(null)
|
||||
|
||||
async function acceptInvoice() {
|
||||
const data = await request("/api/v1/invoice/details/" + props.data.id + "/change-state/approved", {
|
||||
method: "POST",
|
||||
});
|
||||
|
||||
if (data.status === 'success') {
|
||||
toast.success('Abrechnung wurde freigegeben.');
|
||||
} else {
|
||||
toast.error('Bei der Bearbeitung ist ein Fehler aufgetreten.');
|
||||
|
||||
}
|
||||
|
||||
close();
|
||||
}
|
||||
|
||||
|
||||
function close() {
|
||||
emit('reload')
|
||||
emit('close')
|
||||
}
|
||||
|
||||
async function updateInvoice(formData) {
|
||||
const data = await request("/api/v1/invoice/details/" + props.data.id + "/update", {
|
||||
method: "POST",
|
||||
body: {
|
||||
invoiceData: formData
|
||||
}
|
||||
});
|
||||
|
||||
if (!data.do_copy) {
|
||||
modeShow.value = true;
|
||||
toast.success('Die Koreektur der Abrechnung wurde gespeichert.');
|
||||
close();
|
||||
} else {
|
||||
modeShow.value = true;
|
||||
newInvoice.value = data.invoice;
|
||||
props.data.id = data.invoice.id;
|
||||
reloadInvoiceFixDialog()
|
||||
}
|
||||
}
|
||||
|
||||
async function reloadInvoiceFixDialog() {
|
||||
const data = await request("api/v1/invoice/details/" + props.data.id, {
|
||||
method: "GET",
|
||||
});
|
||||
|
||||
newInvoice.value = data.invoice;
|
||||
props.data.id = data.invoice.id;
|
||||
costUnits.value = data.costUnits;
|
||||
props.data.id = data.invoice.id;
|
||||
|
||||
modeShow.value = false;
|
||||
toast.success('Die Abrechnung wurde gespeichert und eine neue Abrechnung wurde erstellt.');
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function openInvoiceFixDialog() {
|
||||
const data = await request("/api/v1/invoice/details/" + props.data.id + "/copy", {
|
||||
method: "POST",
|
||||
body: {
|
||||
}
|
||||
});
|
||||
|
||||
if (data.status === 'success') {
|
||||
costUnits.value = data.costUnits;
|
||||
newInvoice.value = data.invoice;
|
||||
props.data.id = data.invoice.id;
|
||||
modeShow.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function openDenyInvoiceDialog() {
|
||||
denyInvoiceDialog.value = true;
|
||||
}
|
||||
|
||||
async function denyInvoice() {
|
||||
const data = await request("/api/v1/invoice/details/" + props.data.id + "/change-state/denied", {
|
||||
method: "POST",
|
||||
body: {
|
||||
reason: document.getElementById('deny_invoice_reason').value
|
||||
}
|
||||
});
|
||||
|
||||
if (data.status === 'success') {
|
||||
toast.success('Abrechnung wurde abgelehnt.');
|
||||
} else {
|
||||
toast.error('Bei der Bearbeitung ist ein Fehler aufgetreten.');
|
||||
|
||||
}
|
||||
|
||||
denyInvoiceDialog.value = false;
|
||||
close();
|
||||
}
|
||||
|
||||
async function reopenInvoice() {
|
||||
const data = await request("/api/v1/invoice/details/" + props.data.id + "/change-state/new", {
|
||||
method: "POST",
|
||||
|
||||
});
|
||||
|
||||
if (data.status === 'success') {
|
||||
toast.success('Die Abrechnung wurde zur erneuten Bearbeitung vorgelegt');
|
||||
} else {
|
||||
toast.error('Beim Bearbeiten ist ein Fehler aufgetreten.');
|
||||
}
|
||||
close();
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
<FullScreenModal
|
||||
:show="showInvoice"
|
||||
title="Abrechnungsdetails"
|
||||
@close="emit('close')"
|
||||
>
|
||||
|
||||
<Header :data="props.data"
|
||||
@accept="acceptInvoice"
|
||||
@deny="openDenyInvoiceDialog"
|
||||
@fix="openInvoiceFixDialog"
|
||||
@reopen="reopenInvoice"
|
||||
:modeShow="modeShow"
|
||||
/>
|
||||
|
||||
<ShowInvoicePartial
|
||||
v-if="modeShow"
|
||||
:data="props.data"
|
||||
@accept="acceptInvoice"
|
||||
@deny="openDenyInvoiceDialog"
|
||||
@fix="openInvoiceFixDialog"
|
||||
/>
|
||||
|
||||
<EditInvoicePartial
|
||||
v-else
|
||||
:newInvoice="newInvoice"
|
||||
:costUnits="costUnits"
|
||||
@accept="acceptInvoice"
|
||||
@deny="openDenyInvoiceDialog"
|
||||
@fix="openInvoiceFixDialog"
|
||||
@update="updateInvoice"
|
||||
@submit="updateInvoice"
|
||||
/>
|
||||
|
||||
|
||||
</FullScreenModal>
|
||||
|
||||
<Modal title="Abrechnung ablehnen" :show="denyInvoiceDialog" @close="denyInvoiceDialog = false" >
|
||||
Begründung:
|
||||
<textarea class="mareike-textarea" style="width: 100%; height: 100px; margin-top: 10px;" id="deny_invoice_reason" />
|
||||
<input type="button" class="mareike-button mareike-deny-invoice-button" value="Abrechnung ablehnen" @click="denyInvoice" />
|
||||
</Modal>
|
||||
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.mareike-deny-invoice-button {
|
||||
width: 150px !important;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#invoice_details_header{
|
||||
font-weight: bold;
|
||||
font-size: 12pt;
|
||||
line-height: 1.8em;
|
||||
width: 100%;
|
||||
|
||||
}
|
||||
#invoice_details_header table {
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-radius: 10px !important;
|
||||
width: 98%;
|
||||
border-color: #c0c0c0;
|
||||
box-shadow: 5px 5px 10px #c0c0c0;
|
||||
margin-bottom: 75px;
|
||||
font-weight: normal;
|
||||
}
|
||||
#invoice_details_header table tr td:first-child {
|
||||
padding-right: 50px;
|
||||
width: 175px;
|
||||
}
|
||||
|
||||
#invoice_details_header table tr td:nth-child(2) {
|
||||
padding-right: 25px;
|
||||
}
|
||||
|
||||
#invoice_details_header table tr td:nth-child(3) {
|
||||
padding-right: 50px;
|
||||
width: 100px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
#invoice_details_body {
|
||||
height: 400px;
|
||||
overflow: auto;
|
||||
|
||||
}
|
||||
|
||||
|
||||
#invoice_details_body table tr:nth-child(1) td,
|
||||
#invoice_details_body table tr:nth-child(2) td,
|
||||
#invoice_details_body table tr:nth-child(3) td,
|
||||
#invoice_details_body table tr:nth-child(4) td,
|
||||
#invoice_details_body table tr:nth-child(6) td{
|
||||
vertical-align: top;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
#invoice_details_body table tr:nth-child(5) td {
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
|
||||
#invoice_details_body table {
|
||||
width: 100%;
|
||||
|
||||
}
|
||||
|
||||
#invoice_details_body table tr:nth-child(1) td:first-child {
|
||||
padding-right: 50px;
|
||||
|
||||
}
|
||||
|
||||
#invoice_details_body table tr:nth-child(1) td:nth-child(2),
|
||||
#invoice_details_body table tr:nth-child(1) td:nth-child(3)
|
||||
{
|
||||
width: 250px;
|
||||
|
||||
}
|
||||
|
||||
.mareike-accept-button {
|
||||
background-color: #36c054 !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
.mareike-deny-button {
|
||||
background-color: #ee4b5c !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
.mareike-fix-button {
|
||||
background-color: #d3d669 !important;
|
||||
color: #67683c !important;
|
||||
}
|
||||
|
||||
.mareike-button {
|
||||
padding: 5px 25px !important;
|
||||
font-size: 11pt !important;
|
||||
margin-bottom: 10px;
|
||||
width: 100%;
|
||||
padding-right: 2px;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,21 @@
|
||||
<script setup>
|
||||
import PdfViewer from "../../../../../Views/Components/PdfViewer.vue";
|
||||
import DistanceAllowance from "./DistanceAllowance.vue";
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
console.log(props.data)
|
||||
const emit = defineEmits(["accept", "deny", "fix"])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span id="invoice_details_body">
|
||||
<PdfViewer :url="'/api/v1/invoice/showReceipt/' + props.data.id" v-if="props.data.documentFilename !== null" />
|
||||
<DistanceAllowance v-else :invoice="props.data" />
|
||||
</span>
|
||||
</template>
|
||||
@@ -0,0 +1,124 @@
|
||||
<script setup>
|
||||
|
||||
import { ref, onMounted, reactive } from 'vue'
|
||||
import {checkFilesize} from "../../../../../../resources/js/components/InvoiceUploadChecks.js";
|
||||
import RefundData from "./refund-data.vue";
|
||||
import AmountInput from "../../../../../Views/Components/AmountInput.vue";
|
||||
import {useAjax} from "../../../../../../resources/js/components/ajaxHandler.js";
|
||||
import InfoIcon from '../../../../../Views/Components/InfoIcon.vue'
|
||||
import {toast} from "vue3-toastify";
|
||||
|
||||
|
||||
|
||||
const data = defineProps({
|
||||
eventId: Number,
|
||||
userName: String,
|
||||
userEmail: String,
|
||||
userTelephone: String,
|
||||
userIban: String,
|
||||
userAccountOwner: String,
|
||||
})
|
||||
|
||||
|
||||
const { request } = useAjax();
|
||||
const amount = ref(0.00);
|
||||
const invoiceType = ref(null);
|
||||
const otherText = ref('');
|
||||
const receipt = ref(null)
|
||||
const finalStep = ref(false)
|
||||
|
||||
const invoiceTypeCollection = reactive({
|
||||
invoiceTypes: {}
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
const response = await fetch('/api/v1/core/retrieve-invoice-types');
|
||||
const data = await response.json();
|
||||
Object.assign(invoiceTypeCollection, data);
|
||||
});
|
||||
|
||||
|
||||
function handleFileChange(event) {
|
||||
if (checkFilesize('receipt')) {
|
||||
receipt.value = event.target.files[0]
|
||||
finalStep.value = true
|
||||
} else {
|
||||
event.target.value = null
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<fieldset>
|
||||
<legend><span style="font-weight: bolder;">Wofür hast du den Betrag ausgegeben</span></legend>
|
||||
|
||||
<p v-for="availableInvoiceType in invoiceTypeCollection.invoiceTypes">
|
||||
<input
|
||||
name="invpice_type"
|
||||
type="radio"
|
||||
:value="availableInvoiceType.slug"
|
||||
:id="'invoice_type_' + availableInvoiceType.slug"
|
||||
v-model="invoiceType"
|
||||
>
|
||||
<label :for="'invoice_type_' + availableInvoiceType.slug">{{ availableInvoiceType.name }}</label>
|
||||
<InfoIcon :text="'INFO_INVOICE_TYPE_' + availableInvoiceType.slug" /><br />
|
||||
</p>
|
||||
|
||||
|
||||
<label for="invoice_type_other">
|
||||
<input
|
||||
type="text"
|
||||
class="width-full"
|
||||
name="kostengruppe_sonstiges"
|
||||
placeholder="Sonstige"
|
||||
for="invoice_type_other"
|
||||
v-model="otherText"
|
||||
@focus="invoiceType = 'other'"
|
||||
/>
|
||||
</label>
|
||||
|
||||
</fieldset><br /><br />
|
||||
|
||||
<fieldset>
|
||||
<legend><span style="font-weight: bolder;">Wie hoch ist der Betrag</span></legend>
|
||||
<AmountInput v-model="amount" class="width-small" id="amount" name="amount" /> Euro
|
||||
<info-icon></info-icon><br /><br />
|
||||
|
||||
<input
|
||||
v-if="amount != '' && invoiceType !== null"
|
||||
class="mareike-button"
|
||||
onclick="document.getElementById('receipt').click();"
|
||||
type="button"
|
||||
value="Beleg auswählen und fortfahren" />
|
||||
<input accept="application/pdf" type="file" id="receipt" name="receipt" @change="handleFileChange"
|
||||
style="display: none"/>
|
||||
</fieldset><br />
|
||||
|
||||
|
||||
|
||||
<RefundData
|
||||
v-if="finalStep"
|
||||
:eventId="data.eventId"
|
||||
:invoice-type="invoiceType"
|
||||
:amount="amount"
|
||||
:other-text="otherText"
|
||||
:userName="data.userName"
|
||||
:userEmail="data.userEmail"
|
||||
:userTelephone="data.userTelephone"
|
||||
:userIban="data.userIban"
|
||||
:userAccountOwner="data.userAccountOwner"
|
||||
:receipt="receipt"
|
||||
@close="finalStep = false"
|
||||
/>
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
|
||||
157
app/Domains/Invoice/Views/Partials/newInvoice/refund-data.vue
Normal file
157
app/Domains/Invoice/Views/Partials/newInvoice/refund-data.vue
Normal file
@@ -0,0 +1,157 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import Modal from "../../../../../Views/Components/Modal.vue";
|
||||
import IbanInput from "../../../../../Views/Components/IbanInput.vue";
|
||||
import {useAjax} from "../../../../../../resources/js/components/ajaxHandler.js";
|
||||
import TextResource from "../../../../../Views/Components/TextResource.vue";
|
||||
import {invoiceCheckContactName} from "../../../../../../resources/js/components/InvoiceUploadChecks.js";
|
||||
import {toast} from "vue3-toastify";
|
||||
|
||||
const { request } = useAjax();
|
||||
|
||||
const emit = defineEmits(['close'])
|
||||
|
||||
|
||||
const props = defineProps({
|
||||
eventId: Number,
|
||||
invoiceType: String,
|
||||
amount: [String, Number],
|
||||
otherText: String,
|
||||
receipt: File,
|
||||
userName: String,
|
||||
userEmail: String,
|
||||
userTelephone: String,
|
||||
userAccountOwner: String,
|
||||
userIban: String,
|
||||
havePassengers: Number,
|
||||
materialTransportation: Boolean,
|
||||
})
|
||||
|
||||
console.log(props.receipt)
|
||||
|
||||
const finalStep = ref(true)
|
||||
const userName = ref(props.userName)
|
||||
const userEmail = ref(props.userEmail)
|
||||
const userTelephone = ref(props.userTelephone)
|
||||
const userIban = ref(props.userIban)
|
||||
const userAccountOwner = ref(props.userAccountOwner)
|
||||
const sending = ref(false)
|
||||
const success = ref(false)
|
||||
const decision = ref('')
|
||||
const errorMsg = ref('')
|
||||
const confirmation = ref(null)
|
||||
|
||||
|
||||
async function sendData() {
|
||||
if (!userName.value) return
|
||||
|
||||
sending.value = true
|
||||
errorMsg.value = ''
|
||||
success.value = false
|
||||
|
||||
const formData = new FormData()
|
||||
|
||||
formData.append('name', userName.value)
|
||||
formData.append('email', userEmail.value)
|
||||
formData.append('telephone', userTelephone.value)
|
||||
formData.append('amount', props.amount)
|
||||
formData.append('otherText', props.otherText)
|
||||
formData.append('decision', decision.value)
|
||||
formData.append('accountOwner', userAccountOwner.value)
|
||||
formData.append('accountIban', userIban.value)
|
||||
formData.append('havePassengers', props.havePassengers ? 1 : 0)
|
||||
formData.append('materialTransportation', props.materialTransportation ? 1 : 0)
|
||||
|
||||
if (props.receipt) {
|
||||
formData.append('receipt', props.receipt)
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
const response = await request('/api/v1/invoice/new/' + props.eventId + '/' + props.invoiceType, {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
|
||||
if (response.status === 'success') {
|
||||
window.location.href = '/';
|
||||
}
|
||||
} catch (err) {
|
||||
toast.error(result.message);
|
||||
} finally {
|
||||
sending.value = false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal :show="finalStep" title='Bitte gib deine Daten ein' @close="emit('close')">
|
||||
<label>
|
||||
<strong>Dein Name Name (kein Pfadiname):</strong>
|
||||
</label><br />
|
||||
<input
|
||||
type="text"
|
||||
@keyup="invoiceCheckContactName();"
|
||||
id="contact_name"
|
||||
name="contact_name" v-model="userName"
|
||||
style="font-size: 14pt; width: 550px;" /><br /><br />
|
||||
|
||||
<label>
|
||||
<strong>E-Mail-Adresse (Für Rückfragen):</strong>
|
||||
</label><br />
|
||||
<input
|
||||
type="email"
|
||||
name="contact_email"
|
||||
v-model="userEmail"
|
||||
style="font-size: 14pt; width: 550px;" /><br /><br />
|
||||
|
||||
<label>
|
||||
<strong>Telefonnummer (für Rückfragen):</strong>
|
||||
</label><br />
|
||||
<input
|
||||
type="text"
|
||||
id="contact_telephone"
|
||||
name="contact_telephone" v-model="userTelephone"
|
||||
style="font-size: 14pt; width: 550px;" /><br /><br />
|
||||
|
||||
<span id="decision" v-if="userName !== '' && decision === ''">
|
||||
<label><br />
|
||||
<strong>Möchtest du den Betrag spenden?</strong>
|
||||
</label><br />
|
||||
|
||||
<input type="button" style="border-radius: 0; width: 100px;" @click="decision='donation'" value="Ja" />
|
||||
<input type="button" style="border-radius: 0; width: 100px;" @click="decision='payout'" value="Nein" />
|
||||
</span>
|
||||
|
||||
<span id="confirm_donation" v-if="decision === 'donation'">
|
||||
<input type="radio" name="confirmation_radio" value="donation" id="confirmation_radio_donation" v-model="confirmation">
|
||||
<TextResource belongsTo="confirmation_radio_donation" textName="CONFIRMATION_DONATE" />
|
||||
<br /><br />
|
||||
<input type="button" class="mareike-button" v-if="confirmation !== null && !sending" @click="sendData" value="Beleg einreichen" />
|
||||
</span>
|
||||
|
||||
<span id="confirm_payment" v-if="decision === 'payout'">
|
||||
<label>
|
||||
<strong>Konto-Inhaber*in:</strong></label><br />
|
||||
<input type="text" name="account_owner" id="account_owner" v-model="userAccountOwner" style="font-size: 14pt; width: 550px;" /><br /><br />
|
||||
|
||||
<label>
|
||||
<strong>IBAN:</strong>
|
||||
</label><br />
|
||||
<IbanInput id="account_iban" name="account_iban" v-model="userIban" style="font-size: 14pt; width: 550px;" /><br /><br />
|
||||
|
||||
<span v-if="userAccountOwner != '' && userIban && userIban.length === 27"><br />
|
||||
<input type="radio" name="confirmation_radio" value="payment" id="confirmation_radio_payment" v-model="confirmation">
|
||||
<TextResource belongsTo="confirmation_radio_payment" textName="CONFIRMATION_PAYMENT" /><br /><br />
|
||||
<input type="button" v-if="confirmation !== null && !sending" @click="sendData" value="Beleg einreichen" />
|
||||
</span>
|
||||
</span>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* optional styling */
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, reactive } from 'vue'
|
||||
import {checkFilesize} from "../../../../../../resources/js/components/InvoiceUploadChecks.js";
|
||||
import RefundData from "./refund-data.vue";
|
||||
import NumericInput from "../../../../../Views/Components/NumericInput.vue";
|
||||
import AmountInput from "../../../../../Views/Components/AmountInput.vue";
|
||||
import {useAjax} from "../../../../../../resources/js/components/ajaxHandler.js";
|
||||
|
||||
const data = defineProps({
|
||||
eventId: Number,
|
||||
userName: String,
|
||||
userEmail: String,
|
||||
userTelephone: String,
|
||||
userIban: String,
|
||||
userAccountOwner: String,
|
||||
})
|
||||
|
||||
const { request } = useAjax();
|
||||
const distanceAllowance = ref(null);
|
||||
const travelDirection = ref(null);
|
||||
const have_receipt = ref('')
|
||||
const havePassengers = ref(false);
|
||||
const materialTransportation = ref(false);
|
||||
const amount = ref(0.00);
|
||||
const invoiceType = ref(null);
|
||||
const otherText = ref('');
|
||||
const receipt = ref(null)
|
||||
const finalStep = ref(false)
|
||||
|
||||
async function getDistanceAllowance() {
|
||||
const tempData = await request('/api/v1/cost-unit/get-distance-allowance/' + data.eventId, {
|
||||
method: "GET",
|
||||
});
|
||||
|
||||
distanceAllowance.value = tempData.distanceAllowance;
|
||||
have_receipt.value = 'no';
|
||||
}
|
||||
|
||||
function handleFileChange(event) {
|
||||
if (checkFilesize('receipt')) {
|
||||
receipt.value = event.target.files[0]
|
||||
finalStep.value = true
|
||||
} else {
|
||||
event.target.value = null
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<fieldset>
|
||||
<legend><span style="font-weight: bolder;">Gib deine Reisesetrecke an</span></legend>
|
||||
<input
|
||||
type="text"
|
||||
name="travel-direction"
|
||||
v-model="travelDirection"
|
||||
/>
|
||||
</fieldset><br /><br />
|
||||
|
||||
<fieldset v-if="travelDirection !== null">
|
||||
<legend><span style="font-weight: bolder;">Bist du mit dem ÖPNV gefahren oder besitzt du einen Beleg</span></legend>
|
||||
<input type="button" style="border-radius: 0; width: 100px;" @click="have_receipt='yes'" value="Ja" />
|
||||
<input type="button" style="border-radius: 0; width: 100px;" @click="getDistanceAllowance" value="Nein" />
|
||||
</fieldset>
|
||||
|
||||
<br /><br />
|
||||
|
||||
<fieldset v-if="have_receipt === 'yes'">
|
||||
<legend><span style="font-weight: bolder;">Wie hoch ist der Betrag?</span></legend>
|
||||
<AmountInput v-model="amount" class="width-small" id="amount" name="amount" /> Euro
|
||||
|
||||
<br /><br />
|
||||
<input type="button"
|
||||
v-if="amount !== null && have_receipt === 'yes' && amount != '0'"
|
||||
value="Beleg auswählen und fortfahren"
|
||||
onclick="document.getElementById('receipt').click();"
|
||||
/>
|
||||
<input accept="application/pdf" type="file" id="receipt" name="receipt" @change="handleFileChange"
|
||||
style="display: none"/>
|
||||
|
||||
<RefundData
|
||||
v-if="finalStep && have_receipt === 'yes'"
|
||||
:eventId="data.eventId"
|
||||
invoice-type="travelling"
|
||||
:amount="amount"
|
||||
:other-text="travelDirection"
|
||||
:materialTransportation="materialTransportation"
|
||||
:havePassengers="havePassengers"
|
||||
:userName="data.userName"
|
||||
:userEmail="data.userEmail"
|
||||
:userTelephone="data.userTelephone"
|
||||
:userIban="data.userIban"
|
||||
:userAccountOwner="data.userAccountOwner"
|
||||
:receipt="receipt"
|
||||
@close="finalStep = false"
|
||||
/>
|
||||
</fieldset>
|
||||
|
||||
<fieldset v-else-if="distanceAllowance != null">
|
||||
<legend><span style="font-weight: bolder;">Reiseinformationen</span></legend>
|
||||
Gesamtlänge des Reisewegs:
|
||||
<NumericInput
|
||||
class="width-small"
|
||||
name="total_distance"
|
||||
v-model="amount"
|
||||
/> km<br />
|
||||
<span style="font-weight: normal">({{ amount }} km x {{distanceAllowance.toFixed(2).replace('.', ',')}} Euro / km = <strong>{{ (amount * distanceAllowance).toFixed(2).replace('.', ',') }} Euro</strong>)</span>
|
||||
<br /><br />
|
||||
|
||||
<input
|
||||
type="checkbox"
|
||||
name="havePassengers"
|
||||
v-model="havePassengers"
|
||||
id="havePassengers"
|
||||
/> <label style="margin-bottom: 20px;" for="havePassengers">Ich habe Personen mitgenommen</label>
|
||||
<br />
|
||||
|
||||
<input
|
||||
type="checkbox"
|
||||
name="materialTransportation"
|
||||
v-model="materialTransportation"
|
||||
id="materialTransportation"
|
||||
/> <label style="margin-bottom: 20px;" for="materialTransportation">Ich habe Material transportiert</label>
|
||||
<br /><br />
|
||||
|
||||
<input
|
||||
v-if="amount !== null && have_receipt === 'no' && amount != '0'"
|
||||
@click="finalStep = true;"
|
||||
type="button"
|
||||
value="Pauschalbetrag abrechnen" />
|
||||
|
||||
<RefundData
|
||||
v-if="finalStep && have_receipt === 'no'"
|
||||
:eventId="data.eventId"
|
||||
invoice-type="travelling"
|
||||
:amount="amount"
|
||||
:other-text="travelDirection"
|
||||
:materialTransportation="materialTransportation"
|
||||
:havePassengers="havePassengers"
|
||||
:userName="data.userName"
|
||||
:userEmail="data.userEmail"
|
||||
:userTelephone="data.userTelephone"
|
||||
:userIban="data.userIban"
|
||||
:userAccountOwner="data.userAccountOwner"
|
||||
@close="finalStep = false"
|
||||
/>
|
||||
|
||||
</fieldset>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\MessageSystem\Actions\SendMessage;
|
||||
|
||||
use App\Enumerations\MessageType;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
|
||||
class SendMessageCommand {
|
||||
private SendMessageRequest $request;
|
||||
|
||||
public function __construct(SendMessageRequest $request) {
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function send() : SendMessageResponse {
|
||||
$response = new SendMessageResponse();
|
||||
|
||||
foreach ($this->request->messageTypes as $messageType) {
|
||||
switch (true) {
|
||||
case $messageType->value === MessageType::EMAIL->value:
|
||||
$this->sendAsEmail();
|
||||
break;
|
||||
|
||||
case $messageType->value === MessageType::INTERNAL->value:
|
||||
$this->sendAsInternalMessage();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
private function sendAsEmail() {
|
||||
foreach ($this->request->recipient->getEmailAddresses() as $emailAddress) {
|
||||
Mail::html($this->request->message, function ($message) use ($emailAddress) {
|
||||
$message
|
||||
->to($emailAddress->getValue(), $this->request->recipient->getName())
|
||||
->subject($this->request->subject);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private function sendAsInternalMessage() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\MessageSystem\Actions\SendMessage;
|
||||
|
||||
use App\Enumerations\MessageType;
|
||||
use App\Models\User;
|
||||
use App\ValueObjects\EmailAddress;
|
||||
use App\ValueObjects\MessageRecipient;
|
||||
|
||||
class SendMessageRequest {
|
||||
public string $message;
|
||||
public string $subject;
|
||||
|
||||
public MessageRecipient $recipient;
|
||||
|
||||
/** @var MessageType[] */
|
||||
public array $messageTypes;
|
||||
|
||||
public function __construct(
|
||||
string $message,
|
||||
string $subject,
|
||||
MessageRecipient $recipient,
|
||||
array $messageTypes
|
||||
) {
|
||||
$this->message = $message;
|
||||
$this->recipient = $recipient;
|
||||
$this->subject = $subject;
|
||||
$this->messageTypes = $messageTypes;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\MessageSystem\Actions\SendMessage;
|
||||
|
||||
class SendMessageResponse {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\UserManagement\Actions\GenerateActivationToken;
|
||||
|
||||
use App\Domains\MessageSystem\Actions\SendMessage\SendMessageCommand;
|
||||
use App\Domains\MessageSystem\Actions\SendMessage\SendMessageRequest;
|
||||
use App\Enumerations\MessageType;
|
||||
use App\MessageTemplates\activationCodeTemplate;
|
||||
use App\ValueObjects\EmailAddress;
|
||||
use App\ValueObjects\MessageRecipient;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class GenerateActivationTokenCommand {
|
||||
private GenerateActivationTokenRequest $request;
|
||||
|
||||
public function __construct(GenerateActivationTokenRequest $request) {
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function execute() : GenerateActivationTokenResponse {
|
||||
$response = new GenerateActivationTokenResponse;
|
||||
|
||||
$activationCode = Str::password(24,true,true,false,false);
|
||||
$this->request->user->activation_token = $activationCode;
|
||||
if (null !== $this->request->expirationDate) {
|
||||
$this->request->user->activation_token_expires_at = $this->request->expirationDate;
|
||||
}
|
||||
$this->request->user->save();
|
||||
|
||||
$response->activationCode = $activationCode;
|
||||
|
||||
$activationMessage = new activationCodeTemplate();
|
||||
$activationMessage->composeMessage(EmailAddress::fromString($this->request->user->email), $activationCode);
|
||||
|
||||
$recipient = new MessageRecipient();
|
||||
$recipient->addEmailAddress(EmailAddress::fromString($this->request->user->email));
|
||||
$recipient->setName($this->request->user->getFullname());
|
||||
|
||||
$message = activationCodeTemplate::createForUser(EmailAddress::fromString($this->request->user->email), $activationCode);
|
||||
|
||||
$userMessageRequests = new SendMessageRequest($message->getMessage(), $message->getSubject(), $recipient, [MessageType::EMAIL]);
|
||||
|
||||
$userMessageCommand = new SendMessageCommand($userMessageRequests);
|
||||
$userMessageCommand->send();
|
||||
|
||||
return $response;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\UserManagement\Actions\GenerateActivationToken;
|
||||
|
||||
use App\Models\User;
|
||||
|
||||
class GenerateActivationTokenRequest {
|
||||
public User $user;
|
||||
public ?\DateTime $expirationDate;
|
||||
|
||||
public function __construct(User $user, ?\DateTime $expirationDate = null) {
|
||||
$this->user = $user;
|
||||
$this->expirationDate = $expirationDate;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\UserManagement\Actions\GenerateActivationToken;
|
||||
|
||||
class GenerateActivationTokenResponse {
|
||||
public string $activationCode;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\UserManagement\Actions\UserActivation;
|
||||
|
||||
class UserActivationCommand {
|
||||
private UserActivationRequest $request;
|
||||
public function __construct(UserActivationRequest $request) {
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function execute() : UserActivationResponse {
|
||||
$response = new UserActivationResponse();
|
||||
$this->request->user->active = true;
|
||||
$this->request->user->activation_token = null;
|
||||
$this->request->user->activation_token_expires_at = null;
|
||||
$this->request->user->save();
|
||||
$response->success = true;
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\UserManagement\Actions\UserActivation;
|
||||
|
||||
use App\Models\User;
|
||||
|
||||
class UserActivationRequest {
|
||||
public User $user;
|
||||
|
||||
public function __construct(User $user) {
|
||||
$this->user = $user;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\UserManagement\Actions\UserActivation;
|
||||
|
||||
class UserActivationResponse {
|
||||
public bool $success;
|
||||
|
||||
public function __construct() {
|
||||
$this->success = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\UserManagement\Actions\UserChangePassword;
|
||||
|
||||
|
||||
class UserChangePasswordCommand {
|
||||
private UserChangePasswordRequest $request;
|
||||
|
||||
public function __construct(UserChangePasswordRequest $request)
|
||||
{
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function execute() : UserChangePasswordResponse {
|
||||
$response = new UserChangePasswordResponse();
|
||||
|
||||
$this->request->user->password = $this->request->newPassword;
|
||||
$this->request->user->save();
|
||||
|
||||
$response->success = true;
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\UserManagement\Actions\UserChangePassword;
|
||||
|
||||
use App\Models\User;
|
||||
|
||||
class UserChangePasswordRequest {
|
||||
public User $user;
|
||||
public string $newPassword;
|
||||
|
||||
public function __construct(User $user, string $newPassword) {
|
||||
$this->user = $user;
|
||||
$this->newPassword = $newPassword;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\UserManagement\Actions\UserChangePassword;
|
||||
|
||||
class UserChangePasswordResponse {
|
||||
public bool $success;
|
||||
|
||||
public function __construct() {
|
||||
$this->success = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\UserManagement\Actions\UserActivation;
|
||||
|
||||
class UserDeactivationCommand {
|
||||
private UserDeactivationRequest $request;
|
||||
public function __construct(UserDeactivationRequest $request) {
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function execute() : UserDeactivationResponse {
|
||||
$response = new UserDeactivationResponse();
|
||||
$this->request->user->active = false;
|
||||
$this->request->user->password = NULL;
|
||||
$this->request->user->username = 'deleted-' . $this->request->user->username;
|
||||
$this->request->user->email = 'null@example.com';
|
||||
$this->request->user->save();
|
||||
$response->success = true;
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\UserManagement\Actions\UserActivation;
|
||||
|
||||
use App\Models\User;
|
||||
|
||||
class UserDeactivationRequest {
|
||||
public User $user;
|
||||
|
||||
public function __construct(User $user) {
|
||||
$this->user = $user;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\UserManagement\Actions\UserActivation;
|
||||
|
||||
class UserDeactivationResponse {
|
||||
public bool $success;
|
||||
|
||||
public function __construct() {
|
||||
$this->success = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\UserManagement\Actions\UserRegistration;
|
||||
|
||||
use App\Domains\MessageSystem\Actions\SendMessage\SendMessageCommand;
|
||||
use App\Domains\MessageSystem\Actions\SendMessage\SendMessageRequest;
|
||||
use App\Domains\UserManagement\Actions\GenerateActivationToken\GenerateActivationTokenCommand;
|
||||
use App\Domains\UserManagement\Actions\GenerateActivationToken\GenerateActivationTokenRequest;
|
||||
use App\Enumerations\MessageType;
|
||||
use App\MessageTemplates\Registration\InformAdminAboutNewUserTemplate;
|
||||
use App\Models\User;
|
||||
use App\ValueObjects\EmailAddress;
|
||||
use App\ValueObjects\MessageRecipient;
|
||||
|
||||
class UserRegistrationCommand {
|
||||
private UserRegistrationRequest $request;
|
||||
|
||||
public function __construct(UserRegistrationRequest $request) {
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function execute() : UserRegistrationResponse {
|
||||
$response = new UserRegistrationResponse;
|
||||
|
||||
$user = User::create([
|
||||
'user_role_main' => $this->request->userRoleMain,
|
||||
'user_role_local_group' => $this->request->userRoleLocalGroup,
|
||||
'username' => $this->request->email->getValue(),
|
||||
'local_group' => $this->request->localGroup,
|
||||
'firstname' => $this->request->firstname,
|
||||
'lastname' => $this->request->lastname,
|
||||
'nickname' => $this->request->nickname !== '' ? $this->request->nickname : null,
|
||||
'email' => $this->request->email->getValue(),
|
||||
]);
|
||||
|
||||
if ($user === null) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$generateActivationCoedeRequest = new GenerateActivationTokenRequest($user);
|
||||
$generateActivationCoedeDommand = new GenerateActivationTokenCommand($generateActivationCoedeRequest);
|
||||
$result = $generateActivationCoedeDommand->execute();
|
||||
|
||||
$user->activation_token = $result->activationCode;
|
||||
|
||||
$siteAdmin = new MessageRecipient();
|
||||
$siteAdmin->addEmailAddress(EmailAddress::fromString(env('APP_ADMIN_MAIL')));
|
||||
$siteAdmin->setName(env('APP_ADMIN_NAME'));
|
||||
|
||||
$registrationMessage = InformAdminAboutNewUserTemplate::createNew($user);
|
||||
|
||||
$registrationMessageRequest = new SendMessageRequest($registrationMessage->getMessage(), $registrationMessage->getSubject(), $siteAdmin, [MessageType::EMAIL]);
|
||||
$registrationMessageCommand = new SendMessageCommand($registrationMessageRequest);
|
||||
$registrationMessageCommand->send();
|
||||
|
||||
$response->user = $user;
|
||||
$response->success = true;
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\UserManagement\Actions\UserRegistration;
|
||||
|
||||
use App\ValueObjects\EmailAddress;
|
||||
|
||||
class UserRegistrationRequest {
|
||||
public string $firstname;
|
||||
public string $lastname;
|
||||
public EmailAddress $email;
|
||||
public string $nickname;
|
||||
public string $userRoleMain;
|
||||
public string $userRoleLocalGroup;
|
||||
public string $localGroup;
|
||||
|
||||
public function __construct(string $firstname, string $lastname, string $nickname, EmailAddress $email, string $userRoleMain, string $userRoleLocalGroup, string $localGroup) {
|
||||
$this->firstname = $firstname;
|
||||
$this->lastname = $lastname;
|
||||
$this->nickname = $nickname;
|
||||
$this->email = $email;
|
||||
$this->userRoleMain = $userRoleMain;
|
||||
$this->userRoleLocalGroup = $userRoleLocalGroup;
|
||||
$this->localGroup = $localGroup;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\UserManagement\Actions\UserRegistration;
|
||||
|
||||
use App\Models\User;
|
||||
|
||||
class UserRegistrationResponse {
|
||||
public ?User $user;
|
||||
public bool $success;
|
||||
|
||||
public function __construct() {
|
||||
$this->user = null;
|
||||
$this->success = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\UserManagement\Controllers;
|
||||
|
||||
use App\Domains\UserManagement\Actions\UserActivation\UserActivationCommand;
|
||||
use App\Domains\UserManagement\Actions\UserActivation\UserActivationRequest;
|
||||
use App\Domains\UserManagement\Actions\UserActivation\UserDeactivationCommand;
|
||||
use App\Domains\UserManagement\Actions\UserActivation\UserDeactivationRequest;
|
||||
use App\Domains\UserManagement\Actions\UserChangePassword\UserChangePasswordCommand;
|
||||
use App\Domains\UserManagement\Actions\UserChangePassword\UserChangePasswordRequest;
|
||||
use App\Domains\UserManagement\Actions\UserRegistration\UserRegistrationCommand;
|
||||
use App\Domains\UserManagement\Actions\UserRegistration\UserRegistrationRequest;
|
||||
use App\Enumerations\UserRole;
|
||||
use App\Models\User;
|
||||
use App\Providers\InertiaProvider;
|
||||
use App\Scopes\CommonController;
|
||||
use App\ValueObjects\EmailAddress;
|
||||
use Carbon\Traits\Date;
|
||||
use DateTime;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class EmailVerificationController extends CommonController
|
||||
{
|
||||
public function verifyEmailForm(Request $request) {
|
||||
$inertiaProvider = new InertiaProvider('UserManagement/VerifyEmail', ['appName' => app('tenant')->name]);
|
||||
return $inertiaProvider->render();
|
||||
}
|
||||
|
||||
public function doVerification(Request $request) : JsonResponse
|
||||
{
|
||||
|
||||
$user = $this->users->findByUsername($request->get('email'));
|
||||
if ($user === null) {
|
||||
return response()->json([
|
||||
'error_types' => [
|
||||
'email' => 'Die E-Mail-Adresse konnte nicht zugeordnet werden.',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
if (new DateTime() > DateTime::createFromFormat('Y-m-d H:i:s', $user->activation_token_expires_at)) {
|
||||
return response()->json([
|
||||
'error_types' => [
|
||||
'verificationToken' => 'Der Sicherheitsschlüssel ist abgelaufen.',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
if (!$this->users->checkVerificationToken($user, $request->get('verificationToken'))) {
|
||||
return response()->json([
|
||||
'error_types' => [
|
||||
'verificationToken' => 'Der Sicherheitsschlüssel ist nicht korrekt',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
$userActivationRequest = new UserActivationRequest($user);
|
||||
$userActivationCommand = new UserActivationCommand($userActivationRequest);
|
||||
$activationResult = $userActivationCommand->execute();
|
||||
|
||||
|
||||
if (!$activationResult->success) {
|
||||
return response()->json([
|
||||
'error_types' => [
|
||||
'verificationToken' => 'Ein allgemeiner Fehler ist aufgetreten. Bitte versuche es später noch einmal.',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
$userPasswordResetRequest = new UserChangePasswordRequest($user, $request->get('password'));
|
||||
$userPasswordResetCommand = new UserChangePasswordCommand($userPasswordResetRequest);
|
||||
$userPasswordResetCommand->execute();
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'message' => 'Dein Account wurde aktiviert.Du kannst dich nun mit deinem neuen Passwort einloggen.'
|
||||
]);
|
||||
}
|
||||
}
|
||||
21
app/Domains/UserManagement/Controllers/LogOutController.php
Normal file
21
app/Domains/UserManagement/Controllers/LogOutController.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\UserManagement\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class LogOutController {
|
||||
public function __invoke(Request $request) {
|
||||
Auth::logout();
|
||||
|
||||
// Session invalidieren
|
||||
$request->session()->invalidate();
|
||||
|
||||
// CSRF-Token regenerieren (für Sicherheit)
|
||||
$request->session()->regenerateToken();
|
||||
|
||||
// Redirect z.B. zur Login-Seite
|
||||
return redirect()->intended('/')->with('status', 'Erfolgreich abgemeldet!');
|
||||
}
|
||||
}
|
||||
58
app/Domains/UserManagement/Controllers/LoginController.php
Normal file
58
app/Domains/UserManagement/Controllers/LoginController.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\UserManagement\Controllers;
|
||||
|
||||
use App\Providers\InertiaProvider;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class LoginController extends CommonController {
|
||||
public function loginForm(Request $request) {
|
||||
$errors = [];
|
||||
|
||||
if ($request->session()->has('errors')) {
|
||||
$errors = $request->session()->get('errors')->getBag('default')->getMessages();
|
||||
}
|
||||
|
||||
|
||||
$inertiaProvider = new InertiaProvider('UserManagement/Login', ['errors' => $errors, 'appName' => app('tenant')->name]);
|
||||
return $inertiaProvider->render();
|
||||
}
|
||||
|
||||
public function doLogin(Request $request)
|
||||
{
|
||||
|
||||
$credentials = $request->validate([
|
||||
'username' => ['required', 'string'],
|
||||
'password' => ['required'],
|
||||
],
|
||||
[
|
||||
'username.required' => 'Bitte gib deinen Anmeldenamen ein.',
|
||||
'username.string' => 'Der Anmeldename muss eine E-Mail-Adresse sein.',
|
||||
'password.required' => 'Bitte gib dein Passwort ein.',
|
||||
]);
|
||||
|
||||
$user = $this->users->findByUsername($request->get('username'));
|
||||
if ($user !== null && $user->password === null) {
|
||||
return redirect()->intended('/register/verifyEmail');
|
||||
}
|
||||
|
||||
|
||||
#$credentials = ['username' => 'development', 'password' => 'development'];
|
||||
|
||||
if (!Auth::attempt($credentials)) {
|
||||
return back()->withErrors([
|
||||
'username' => 'Diese Zugangsdaten sind ungültig.',
|
||||
]);
|
||||
}
|
||||
|
||||
$request->session()->regenerate();
|
||||
$user = Auth::user();
|
||||
|
||||
|
||||
# dd($user->firstname . ' ' . $user->lastname);
|
||||
|
||||
return redirect()->intended('/');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\UserManagement\Controllers;
|
||||
|
||||
use App\Domains\MessageSystem\Actions\SendMessage\SendMessageCommand;
|
||||
use App\Domains\MessageSystem\Actions\SendMessage\SendMessageRequest;
|
||||
use App\Domains\UserManagement\Actions\UserRegistration\UserRegistrationCommand;
|
||||
use App\Domains\UserManagement\Actions\UserRegistration\UserRegistrationRequest;
|
||||
use App\Enumerations\MessageType;
|
||||
use App\Enumerations\UserRole;
|
||||
use App\Providers\InertiaProvider;
|
||||
use App\Scopes\CommonController;
|
||||
use App\ValueObjects\EmailAddress;
|
||||
use App\ValueObjects\MessageRecipient;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class RegistrationController extends CommonController {
|
||||
public function loginForm(Request $request) {
|
||||
$errors = [];
|
||||
|
||||
if ($request->session()->has('errors')) {
|
||||
$errors = $request->session()->get('errors')->getBag('default')->getMessages();
|
||||
}
|
||||
|
||||
|
||||
$inertiaProvider = new InertiaProvider('UserManagement/Registration', [
|
||||
'errors' => $errors,
|
||||
'appName' => app('tenant')->name,
|
||||
'tenant' => app('tenant'),
|
||||
]);
|
||||
return $inertiaProvider->render();
|
||||
}
|
||||
|
||||
public function doRegistration(Request $request) : JsonResponse {
|
||||
|
||||
$user = $this->users->findByUsername($request->get('email'));
|
||||
if ($user !== null) {
|
||||
return response()->json([
|
||||
'status' => 'error',
|
||||
'message' => 'Dieser Account existiert bereits.'
|
||||
]);
|
||||
}
|
||||
|
||||
$email = EmailAddress::fromString($request->get('email'));
|
||||
$userRoleMain = UserRole::USER_ROLE_USER;
|
||||
$userRoleLocalGroup = UserRole::USER_ROLE_USER;
|
||||
|
||||
$localGroup = app('tenant')->slug === 'lv' ? $request->get('localGroup') : app('tenant')->slug;
|
||||
|
||||
|
||||
$registrationRequest = new UserRegistrationRequest(
|
||||
$request->get('firstname'),
|
||||
$request->get('lastname'),
|
||||
$request->get('nickname'),
|
||||
$email,
|
||||
$userRoleMain,
|
||||
$userRoleLocalGroup,
|
||||
$localGroup
|
||||
);
|
||||
|
||||
$registrationCommand = new UserRegistrationCommand($registrationRequest);
|
||||
$result = $registrationCommand->execute();
|
||||
|
||||
if (!$result->success) {
|
||||
return response()->json([
|
||||
'status' => 'error',
|
||||
'message' => 'Beim Erstellen des Accounts ist ein Fehler aufgetreten.'
|
||||
]);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'message' => 'Registrierung erfolgreich! Bitte prüfe nun dein E-Mail-Postfach'
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\UserManagement\Controllers;
|
||||
|
||||
use App\Domains\UserManagement\Actions\GenerateActivationToken\GenerateActivationTokenCommand;
|
||||
use App\Domains\UserManagement\Actions\GenerateActivationToken\GenerateActivationTokenRequest;
|
||||
use App\Domains\UserManagement\Actions\GenerateActivationToken\GenerateActivationTokenResponse;
|
||||
use App\Domains\UserManagement\Actions\UserActivation\UserActivationCommand;
|
||||
use App\Domains\UserManagement\Actions\UserActivation\UserActivationRequest;
|
||||
use App\Providers\InertiaProvider;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ResetPasswordController extends CommonController {
|
||||
public function resetPasswordForm() {
|
||||
$inertiaProvider = new InertiaProvider('UserManagement/ResetPassword', []);
|
||||
return $inertiaProvider->render();
|
||||
}
|
||||
|
||||
public function doResetPassword(Request $request) : JsonResponse {
|
||||
$user = $this->users->findByUsername($request->get('email'));
|
||||
|
||||
if (null !== $user) {
|
||||
$expirationDate = new \DateTime()->modify('+15 Minutes');
|
||||
|
||||
$resetAccountRequest = new GenerateActivationTokenRequest($user, $expirationDate);
|
||||
$resetAccountCommand = new GenerateActivationTokenCommand($resetAccountRequest);
|
||||
$resetAccountCommand->execute();
|
||||
}
|
||||
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'message' => 'Falls deine E-Mail-Adresse gefunden wurde, erhältst du nun eine E-Mail mit weiteren Schritten zum Zurücksetzen deines Passwortes.'
|
||||
]);
|
||||
|
||||
}
|
||||
}
|
||||
15
app/Domains/UserManagement/Routes/api.php
Normal file
15
app/Domains/UserManagement/Routes/api.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
use App\Domains\UserManagement\Controllers\EmailVerificationController;
|
||||
use App\Domains\UserManagement\Controllers\RegistrationController;
|
||||
use App\Domains\UserManagement\Controllers\ResetPasswordController;
|
||||
use App\Middleware\IdentifyTenant;
|
||||
use App\Providers\GlobalDataProvider;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Inertia\Inertia;
|
||||
|
||||
Route::prefix('v1')
|
||||
->group(function () {
|
||||
Route::middleware(IdentifyTenant::class)->group(function () {
|
||||
});
|
||||
});
|
||||
28
app/Domains/UserManagement/Routes/web.php
Normal file
28
app/Domains/UserManagement/Routes/web.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use App\Domains\UserManagement\Controllers\EmailVerificationController;
|
||||
use App\Domains\UserManagement\Controllers\LoginController;
|
||||
use App\Domains\UserManagement\Controllers\LogOutController;
|
||||
use App\Domains\UserManagement\Controllers\RegistrationController;
|
||||
use App\Domains\UserManagement\Controllers\ResetPasswordController;
|
||||
use App\Middleware\IdentifyTenant;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::middleware(IdentifyTenant::class)->group(function () {
|
||||
Route::get('/register', [RegistrationController::class, 'loginForm']);
|
||||
Route::get('/register/verifyEmail', [EmailVerificationController::class, 'verifyEmailForm']);
|
||||
|
||||
Route::get('/reset-password', [ResetPasswordController::class, 'resetPasswordForm']);
|
||||
|
||||
route::get('/logout', LogOutController::class);
|
||||
route::post('/login', [LoginController::class, 'doLogin']);
|
||||
route::get('/login', [LoginController::class, 'loginForm']);
|
||||
|
||||
Route::middleware(['auth'])->group(function () {
|
||||
Route::post('/logout', [LogoutController::class, 'logout']);
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
69
app/Domains/UserManagement/Views/Login.vue
Normal file
69
app/Domains/UserManagement/Views/Login.vue
Normal file
@@ -0,0 +1,69 @@
|
||||
<script setup>
|
||||
import AppLayout from '../../../../resources/js/layouts/AppLayout.vue'
|
||||
import {onMounted, ref} from 'vue'
|
||||
import { toast } from 'vue3-toastify'
|
||||
import ShadowedBox from "../../../Views/Components/ShadowedBox.vue";
|
||||
|
||||
|
||||
const props = defineProps({
|
||||
navbar: Object,
|
||||
tenant: String,
|
||||
user: Object,
|
||||
currentPath: String,
|
||||
errors: Object,
|
||||
})
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
if (undefined !== props.errors && undefined !== props.errors.username) {
|
||||
toast.error(props.errors.username[0])
|
||||
}
|
||||
})
|
||||
|
||||
//console.log(props.errors.password[0])
|
||||
const username = ref('')
|
||||
const password = ref('')
|
||||
|
||||
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content')
|
||||
|
||||
function resetPassword() {
|
||||
window.location.href = '/reset-password';
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppLayout title='Anmelden' :user="props.user" :navbar="props.navbar" :tenant="props.tenant" :currentPath="props.currentPath">
|
||||
<form method="POST" action="/login">
|
||||
<input type="hidden" name="_token" :value="csrfToken" />
|
||||
<shadowed-box style="width: 50%; margin: 150px auto; padding: 20px;">
|
||||
<table>
|
||||
<tr>
|
||||
<th>Anmeldename</th>
|
||||
<td>
|
||||
<input type="text" name="username" id="username"></input>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Passwort</th>
|
||||
<td><input type="password" name="password" id="password"></input></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<input type="submit" value="Anmelden" style="margin-top: 20px;" />
|
||||
<input type="button" @click="resetPassword" style="margin-top: 20px; margin-left: 20px;" value="Passwort vergessen" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</shadowed-box>
|
||||
</form>
|
||||
</AppLayout>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
th {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
</style>
|
||||
166
app/Domains/UserManagement/Views/Registration.vue
Normal file
166
app/Domains/UserManagement/Views/Registration.vue
Normal file
@@ -0,0 +1,166 @@
|
||||
<script setup>
|
||||
import AppLayout from '../../../../resources/js/layouts/AppLayout.vue'
|
||||
import {computed, onMounted, reactive, ref} from 'vue'
|
||||
import { toast } from 'vue3-toastify'
|
||||
import ShadowedBox from "../../../Views/Components/ShadowedBox.vue";
|
||||
import {useAjax} from "../../../../resources/js/components/ajaxHandler.js";
|
||||
import ErrorText from "../../../Views/Components/ErrorText.vue";
|
||||
|
||||
|
||||
const props = defineProps({
|
||||
navbar: Object,
|
||||
tenant: String,
|
||||
user: Object,
|
||||
currentPath: String,
|
||||
errors: Object,
|
||||
availableLocalGroups: Array,
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (undefined !== props.errors && undefined !== props.errors.username) {
|
||||
toast.error(props.errors.username[0])
|
||||
}
|
||||
})
|
||||
|
||||
//console.log(props.errors.password[0])
|
||||
const username = ref('')
|
||||
const password = ref('')
|
||||
|
||||
const form = reactive({
|
||||
firstname: '',
|
||||
lastname: '',
|
||||
nickname: '',
|
||||
email: '',
|
||||
localGroup: '',
|
||||
password: '',
|
||||
password_confirmation: ''
|
||||
})
|
||||
|
||||
const errors = reactive({})
|
||||
const { request } = useAjax()
|
||||
|
||||
const isValid = computed(() => {
|
||||
errors.firstname = ''
|
||||
errors.lastname = ''
|
||||
errors.localGroup = ''
|
||||
errors.email = ''
|
||||
|
||||
|
||||
if (!form.firstname) {
|
||||
errors.firstname = 'Bitte gib deinen Vornamen ein'
|
||||
}
|
||||
|
||||
if (!form.lastname) {
|
||||
errors.lastname = 'Bitte gib deinen Nachnamen ein'
|
||||
}
|
||||
|
||||
if (!form.localGroup) {
|
||||
errors.localGroup = 'Bitte gib deinen Stamm an'
|
||||
}
|
||||
|
||||
|
||||
if (!form.email) {
|
||||
errors.email = 'Bitte gib deine E-Mail-Adresse ein'
|
||||
} else if (!/^\S+@\S+\.\S+$/.test(form.email)) {
|
||||
errors.email = 'Ungültige E-Mail'
|
||||
}
|
||||
|
||||
return !errors.password && !errors.firstname && !errors.lastname && !errors.localGroup
|
||||
})
|
||||
|
||||
async function submit() {
|
||||
|
||||
if (!isValid.value) return false
|
||||
|
||||
const data = await request("/api/v1/register", {
|
||||
method: "POST",
|
||||
body: {
|
||||
"firstname": form.firstname,
|
||||
"lastname": form.lastname,
|
||||
'nickname': form.nickname,
|
||||
"email": form.email,
|
||||
"localGroup": form.localGroup,
|
||||
}
|
||||
});
|
||||
|
||||
if (data.status === 'error') {
|
||||
toast.error(data.message);
|
||||
} else {
|
||||
toast.success(data.message)
|
||||
window.location.href = '/register/verifyEmail';
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppLayout title='Registrieren'>
|
||||
<form method="POST" action="/register" @submit.prevent="submit">
|
||||
<input type="hidden" name="_token" :value="csrfToken" />
|
||||
<shadowed-box style="width: 75%; margin: 150px auto; padding: 20px;">
|
||||
<table>
|
||||
<tr>
|
||||
<th>Vorname</th>
|
||||
<td>
|
||||
<input type="text" name="firstname" id="firstname" v-model="form.firstname" />
|
||||
<ErrorText :message="errors.firstname" />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Nachname</th>
|
||||
<td>
|
||||
<input type="text" name="lastname" id="lastname" v-model="form.lastname" />
|
||||
<ErrorText :message="errors.lastname" />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>
|
||||
Pfadi-Name*<br />
|
||||
<small>*Falls vorhanden</small>
|
||||
</th>
|
||||
<td>
|
||||
<input type="text" name="nickname" id="nickname" v-model="form.nickname" />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>E-Mail-Adresse</th>
|
||||
<td>
|
||||
<input type="email" name="email" id="email" v-model="form.email" />
|
||||
<ErrorText :message="errors.email" />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr v-if="props.tenant.slug === 'lv'">
|
||||
<th>Stamm</th>
|
||||
<td>
|
||||
<select name="localgroup" v-model="form.localGroup">
|
||||
<option v-for="localGroup in props.availableLocalGroups" :value="localGroup.slug">{{localGroup.name}}</option>
|
||||
</select>
|
||||
<ErrorText :message="errors.localGroup" />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<input type="submit" value="Registrieren" style="margin-top: 20px;" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</shadowed-box>
|
||||
</form>
|
||||
</AppLayout>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
th {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
</style>
|
||||
102
app/Domains/UserManagement/Views/ResetPassword.vue
Normal file
102
app/Domains/UserManagement/Views/ResetPassword.vue
Normal file
@@ -0,0 +1,102 @@
|
||||
<script setup>
|
||||
import AppLayout from '../../../../resources/js/layouts/AppLayout.vue'
|
||||
import {computed, onMounted, reactive, ref} from 'vue'
|
||||
import { toast } from 'vue3-toastify'
|
||||
import ShadowedBox from "../../../Views/Components/ShadowedBox.vue";
|
||||
import {useAjax} from "../../../../resources/js/components/ajaxHandler.js";
|
||||
import ErrorText from "../../../Views/Components/ErrorText.vue";
|
||||
|
||||
|
||||
const props = defineProps({
|
||||
navbar: Object,
|
||||
tenant: String,
|
||||
user: Object,
|
||||
currentPath: String,
|
||||
errors: Object,
|
||||
availableLocalGroups: Array,
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (undefined !== props.errors && undefined !== props.errors.username) {
|
||||
toast.error(props.errors.username[0])
|
||||
}
|
||||
})
|
||||
const form = reactive({
|
||||
email: '',
|
||||
verificationToken: '',
|
||||
})
|
||||
|
||||
const errors = reactive({})
|
||||
const { request } = useAjax()
|
||||
|
||||
const isValid = computed(() => {
|
||||
errors.email = ''
|
||||
|
||||
if (!form.email) {
|
||||
errors.email = 'Bitte gib deine E-Mail-Adresse ein'
|
||||
}
|
||||
|
||||
|
||||
return !errors.email
|
||||
})
|
||||
|
||||
async function submit() {
|
||||
|
||||
if (!isValid.value) return false
|
||||
|
||||
const data = await request("/api/v1/reset-password", {
|
||||
method: "POST",
|
||||
body: {
|
||||
"email": form.email,
|
||||
}
|
||||
});
|
||||
|
||||
if (data.error_types) {
|
||||
Object.keys(data.error_types).forEach((key) => {
|
||||
if (key in errors) {
|
||||
errors[key] = data.error_types[key]
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
window.location.href = '/register/verifyEmail';
|
||||
toast.success(data.message)
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppLayout title='Passwort zurücksetzen' :user="props.user" :navbar="props.navbar" :tenant="props.tenant" :currentPath="props.currentPath">
|
||||
<form method="POST" action="/reset-password" @submit.prevent="submit">
|
||||
<input type="hidden" name="_token" :value="csrfToken" />
|
||||
<shadowed-box style="width: 75%; margin: 150px auto; padding: 20px;">
|
||||
<table>
|
||||
<tr>
|
||||
<th>E-Mail-Adresse</th>
|
||||
<td>
|
||||
<input type="email" name="email" id="email" v-model="form.email" />
|
||||
<ErrorText :message="errors.email" />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<input type="submit" value="Verifizierung starten" style="margin-top: 20px;" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</shadowed-box>
|
||||
</form>
|
||||
</AppLayout>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
th {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
</style>
|
||||
152
app/Domains/UserManagement/Views/VerifyEmail.vue
Normal file
152
app/Domains/UserManagement/Views/VerifyEmail.vue
Normal file
@@ -0,0 +1,152 @@
|
||||
<script setup>
|
||||
import AppLayout from '../../../../resources/js/layouts/AppLayout.vue'
|
||||
import {computed, onMounted, reactive, ref} from 'vue'
|
||||
import { toast } from 'vue3-toastify'
|
||||
import ShadowedBox from "../../../Views/Components/ShadowedBox.vue";
|
||||
import {useAjax} from "../../../../resources/js/components/ajaxHandler.js";
|
||||
import ErrorText from "../../../Views/Components/ErrorText.vue";
|
||||
|
||||
|
||||
const props = defineProps({
|
||||
navbar: Object,
|
||||
tenant: String,
|
||||
user: Object,
|
||||
currentPath: String,
|
||||
errors: Object,
|
||||
availableLocalGroups: Array,
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (undefined !== props.errors && undefined !== props.errors.username) {
|
||||
toast.error(props.errors.username[0])
|
||||
}
|
||||
})
|
||||
const form = reactive({
|
||||
email: '',
|
||||
verificationToken: '',
|
||||
password: '',
|
||||
})
|
||||
|
||||
const errors = reactive({})
|
||||
const { request } = useAjax()
|
||||
|
||||
const isValid = computed(() => {
|
||||
errors.email = '';
|
||||
errors.verificationToken = '';
|
||||
errors.password = '';
|
||||
|
||||
if (!form.email) {
|
||||
errors.email = 'Bitte gib deine E-Mail-Adresse ein'
|
||||
} else if (!/^\S+@\S+\.\S+$/.test(form.email)) {
|
||||
errors.email = 'Ungültige E-Mail'
|
||||
}
|
||||
|
||||
if (!form.verificationToken) {
|
||||
errors.verificationToken = 'Bitte gib den Sicherheitsschlüssel, den du per E-Mail erhalten hast, ein'
|
||||
}
|
||||
|
||||
if (!form.password) {
|
||||
errors.password = 'Bitte gib ein Passwort ein'
|
||||
} else if (form.password.length < 2) {
|
||||
errors.password = 'Das Passwort muss mindestens 12 Zeichen lang sein'
|
||||
}
|
||||
|
||||
if (form.password !== form.password_confirmation) {
|
||||
errors.password = 'Das Passwort und die Wiederholung stimmen nicht überein'
|
||||
}
|
||||
|
||||
return !errors.password && !errors.email && !errors.verificationToken
|
||||
})
|
||||
|
||||
async function submit() {
|
||||
|
||||
if (!isValid.value) return false
|
||||
|
||||
const data = await request("/api/v1/register/confirmEmail", {
|
||||
method: "POST",
|
||||
body: {
|
||||
"email": form.email,
|
||||
"verificationToken": form.verificationToken,
|
||||
"password": form.password,
|
||||
}
|
||||
});
|
||||
|
||||
if (data.error_types) {
|
||||
Object.keys(data.error_types).forEach((key) => {
|
||||
if (key in errors) {
|
||||
errors[key] = data.error_types[key]
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
toast.success(data.message)
|
||||
window.location.href = '/login';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
function resetPassword() {
|
||||
window.location.href = '/reset-password';
|
||||
}
|
||||
|
||||
|
||||
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppLayout title='E-Mail bestätigen' :user="props.user" :navbar="props.navbar" :tenant="props.tenant" :currentPath="props.currentPath">
|
||||
<form method="POST" action="/register" @submit.prevent="submit">
|
||||
<input type="hidden" name="_token" :value="csrfToken" />
|
||||
<shadowed-box style="width: 75%; margin: 150px auto; padding: 20px;">
|
||||
<p>
|
||||
Bitte prüfe dein E-Mail-Postfach.<br />
|
||||
Solltest du keinen Aktivierungsschlüssel erhalten haben, kontaktiere bitte einen Administrator.
|
||||
</p>
|
||||
<table>
|
||||
<tr>
|
||||
<th>E-Mail-Adresse</th>
|
||||
<td>
|
||||
<input type="email" name="email" id="email" v-model="form.email" />
|
||||
<ErrorText :message="errors.email" />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Sicherheitsschlüssel</th>
|
||||
<td>
|
||||
<input type="text" name="verificationToken" id="verificationToken" v-model="form.verificationToken" />
|
||||
<ErrorText :message="errors.verificationToken" />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Dein neues Passwort</th>
|
||||
<td><input type="password" name="password" id="password" v-model="form.password" /></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Passwort (wiederholen)</th>
|
||||
<td>
|
||||
<input type="password" name="password_retype" id="password_retype" v-model="form.password_confirmation" />
|
||||
<ErrorText :message="errors.password" />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<input type="submit" value="E-Mail validieren" style="margin-top: 20px;" />
|
||||
<input type="button" @click="resetPassword" style="margin-top: 20px; margin-left: 20px;" value="Validierungscode neu zusenden" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</shadowed-box>
|
||||
</form>
|
||||
</AppLayout>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
th {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
</style>
|
||||
21
app/Enumerations/CostUnitType.php
Normal file
21
app/Enumerations/CostUnitType.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enumerations;
|
||||
|
||||
use App\Scopes\CommonModel;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
|
||||
/**
|
||||
* @property string $slug
|
||||
* @property string $name
|
||||
*/
|
||||
class CostUnitType extends CommonModel
|
||||
{
|
||||
public const COST_UNIT_TYPE_EVENT = 'event';
|
||||
public const COST_UNIT_TYPE_RUNNING_JOB = 'running_job';
|
||||
|
||||
protected $fillable = [
|
||||
'slug',
|
||||
'name',
|
||||
];
|
||||
}
|
||||
22
app/Enumerations/EatingHabit.php
Normal file
22
app/Enumerations/EatingHabit.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enumerations;
|
||||
|
||||
use App\Scopes\CommonModel;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
|
||||
/**
|
||||
* @property string $slug
|
||||
* @property string $name
|
||||
*/
|
||||
class EatingHabit extends CommonModel
|
||||
{
|
||||
public const string EATING_HABIT_OMNIVOR = 'EATING_HABIT_OMNIVOR';
|
||||
public const string EATING_HABIT_VEGETARIAN = 'EATING_HABIT_VEGETARIAN';
|
||||
public const string EATING_HABIT_VEGAN = 'EATING_HABIT_VEGAN';
|
||||
|
||||
protected $fillable = [
|
||||
'slug',
|
||||
'name',
|
||||
];
|
||||
}
|
||||
22
app/Enumerations/FirstAidPermission.php
Normal file
22
app/Enumerations/FirstAidPermission.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enumerations;
|
||||
|
||||
use App\Scopes\CommonModel;
|
||||
|
||||
/**
|
||||
* @property string $slug
|
||||
* @property string $name
|
||||
*/
|
||||
class FirstAidPermission extends CommonModel
|
||||
{
|
||||
public const string FIRST_AID_PERMISSION_ALLOWED = 'FIRST_AID_PERMISSION_ALLOWED';
|
||||
public const string FIRST_AID_PERMISSION_DENIED = 'FIRST_AID_PERMISSION_DENIED';
|
||||
|
||||
|
||||
protected $fillable = [
|
||||
'slug',
|
||||
'name',
|
||||
'description'
|
||||
];
|
||||
}
|
||||
23
app/Enumerations/InvoiceStatus.php
Normal file
23
app/Enumerations/InvoiceStatus.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enumerations;
|
||||
|
||||
use App\Scopes\CommonModel;
|
||||
|
||||
class InvoiceStatus extends CommonModel
|
||||
{
|
||||
public const INVOICE_STATUS_NEW = 'new';
|
||||
public const INVOICE_STATUS_APPROVED = 'approved';
|
||||
public const INVOICE_STATUS_DENIED = 'denied';
|
||||
public const INVOICE_STATUS_EXPORTED = 'exported';
|
||||
public const INVOICE_STATUS_DELETED = 'deleted';
|
||||
|
||||
public const INVOICE_META_STATUS_NO_PAYOUT = 'no_payout';
|
||||
public const INVOICE_META_STATUS_DONATED = 'donated';
|
||||
|
||||
protected $table = 'invoice_status';
|
||||
|
||||
protected $fillable = [
|
||||
'slug',
|
||||
];
|
||||
}
|
||||
23
app/Enumerations/InvoiceType.php
Normal file
23
app/Enumerations/InvoiceType.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enumerations;
|
||||
|
||||
use App\Scopes\CommonModel;
|
||||
|
||||
class InvoiceType extends CommonModel {
|
||||
public const INVOICE_TYPE_TRAVELLING = 'travelling';
|
||||
|
||||
public const INVOICE_TYPE_PROGRAM = 'program';
|
||||
|
||||
public const INVOICE_TYPE_OTHER = 'other';
|
||||
|
||||
public const INVOICE_TYPE_ACCOMMODATION = 'accommodation';
|
||||
|
||||
public const INVOICE_TYPE_CATERING = 'catering';
|
||||
|
||||
|
||||
protected $fillable = [
|
||||
'slug',
|
||||
'name',
|
||||
];
|
||||
}
|
||||
8
app/Enumerations/MessageType.php
Normal file
8
app/Enumerations/MessageType.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enumerations;
|
||||
|
||||
enum MessageType : string {
|
||||
case INTERNAL = 'INTERNAL';
|
||||
case EMAIL = 'EMAIL';
|
||||
}
|
||||
26
app/Enumerations/SwimmingPermission.php
Normal file
26
app/Enumerations/SwimmingPermission.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enumerations;
|
||||
|
||||
use App\Scopes\CommonModel;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
|
||||
/**
|
||||
* @property string $slug
|
||||
* @property string $name
|
||||
*/
|
||||
class SwimmingPermission extends CommonModel
|
||||
{
|
||||
public const string SWIMMING_PERMISSION_ALLOWED = 'SWIMMING_PERMISSION_ALLOWED';
|
||||
public const string SWIMMING_PERMISSION_LIMITED = 'SWIMMING_PERMISSION_LIMITED';
|
||||
public const string SWIMMING_PERMISSION_DENIED = 'SWIMMING_PERMISSION_DENIED';
|
||||
|
||||
|
||||
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'slug',
|
||||
'name',
|
||||
];
|
||||
}
|
||||
17
app/Enumerations/UserRole.php
Normal file
17
app/Enumerations/UserRole.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enumerations;
|
||||
|
||||
use App\Scopes\CommonModel;
|
||||
|
||||
class UserRole extends CommonModel {
|
||||
public const USER_ROLE_ADMIN = 'ROLE_ADMINISTRATOR';
|
||||
public const USER_ROLE_GROUP_LEADER = 'ROLE_GROUP_LEADER';
|
||||
public const USER_ROLE_USER = 'ROLE_USER';
|
||||
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'slug'
|
||||
];
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
abstract class Controller
|
||||
{
|
||||
//
|
||||
}
|
||||
13
app/Http/Controllers/TestRenderInertiaProvider.php
Normal file
13
app/Http/Controllers/TestRenderInertiaProvider.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Providers\InertiaProvider;
|
||||
|
||||
class TestRenderInertiaProvider
|
||||
{
|
||||
public function index() {
|
||||
$inertiaProvider = new InertiaProvider('Invoice/CreateInvoice', ['appName' => app('tenant')->name]);
|
||||
return $inertiaProvider->render();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user