Compare commits
No commits in common. "0f80844d204c97ca9b2cb366240f4f06125f68e6" and "5c40b4e64d26412cfed2dae2fd9bff850483514e" have entirely different histories.
0f80844d20
...
5c40b4e64d
|
@ -1,47 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Invoice\Actions;
|
|
||||||
|
|
||||||
use App\Invoice\BillKind;
|
|
||||||
use App\Invoice\Enums\InvoiceStatus;
|
|
||||||
use Illuminate\Validation\Rule;
|
|
||||||
|
|
||||||
trait HasValidation
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @return array<string, string|array<int, string|Rule>>
|
|
||||||
*/
|
|
||||||
public function rules(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'status' => ['required', 'string', 'max:255', Rule::in(InvoiceStatus::values())],
|
|
||||||
'via' => ['required', 'string', 'max:255', Rule::in(BillKind::values())],
|
|
||||||
'to' => 'array',
|
|
||||||
'to.address' => 'required|string|max:255',
|
|
||||||
'to.location' => 'required|string|max:255',
|
|
||||||
'to.zip' => 'required|string|max:255',
|
|
||||||
'to.name' => 'required|string|max:255',
|
|
||||||
'greeting' => 'required|string|max:255',
|
|
||||||
'positions' => 'array',
|
|
||||||
'positions.*.description' => 'required|string|max:300',
|
|
||||||
'positions.*.price' => 'required|integer|min:0',
|
|
||||||
'positions.*.member_id' => 'required|exists:members,id',
|
|
||||||
'positions.*.id' => 'present|nullable|exists:invoice_positions,id',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array<string, string>
|
|
||||||
*/
|
|
||||||
public function getValidationAttributes(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'to.address' => 'Adresse',
|
|
||||||
'to.name' => 'Name',
|
|
||||||
'to.zip' => 'PLZ',
|
|
||||||
'to.location' => 'Ort',
|
|
||||||
'status' => 'Status',
|
|
||||||
'via' => 'Rechnungsweg',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Invoice\Actions;
|
|
||||||
|
|
||||||
use Lorisleiva\Actions\ActionRequest;
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
|
||||||
use App\Invoice\Models\Invoice;
|
|
||||||
use App\Lib\Events\Succeeded;
|
|
||||||
use Illuminate\Http\JsonResponse;
|
|
||||||
|
|
||||||
class InvoiceDestroyAction
|
|
||||||
{
|
|
||||||
use AsAction;
|
|
||||||
|
|
||||||
public function handle(Invoice $invoice): JsonResponse
|
|
||||||
{
|
|
||||||
$invoice->delete();
|
|
||||||
|
|
||||||
Succeeded::message('Rechnung gelöscht.')->dispatch();
|
|
||||||
|
|
||||||
return response()->json([]);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Invoice\Actions;
|
|
||||||
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
|
||||||
use App\Invoice\Models\Invoice;
|
|
||||||
use App\Invoice\Resources\InvoiceResource;
|
|
||||||
use Illuminate\Pagination\LengthAwarePaginator;
|
|
||||||
use Inertia\Inertia;
|
|
||||||
use Inertia\Response;
|
|
||||||
|
|
||||||
class InvoiceIndexAction
|
|
||||||
{
|
|
||||||
use AsAction;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return LengthAwarePaginator<Invoice>
|
|
||||||
*/
|
|
||||||
public function handle(): LengthAwarePaginator
|
|
||||||
{
|
|
||||||
return Invoice::select('*')->with('positions')->paginate(15);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function asController(): Response
|
|
||||||
{
|
|
||||||
session()->put('menu', 'invoice');
|
|
||||||
session()->put('title', 'Rechnungen');
|
|
||||||
|
|
||||||
return Inertia::render('invoice/Index', [
|
|
||||||
'data' => InvoiceResource::collection($this->handle()),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,15 +2,51 @@
|
||||||
|
|
||||||
namespace App\Invoice\Actions;
|
namespace App\Invoice\Actions;
|
||||||
|
|
||||||
|
use App\Invoice\Enums\InvoiceStatus;
|
||||||
use Lorisleiva\Actions\ActionRequest;
|
use Lorisleiva\Actions\ActionRequest;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
use App\Invoice\Models\Invoice;
|
use App\Invoice\Models\Invoice;
|
||||||
use App\Lib\Events\Succeeded;
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
class InvoiceStoreAction
|
class InvoiceStoreAction
|
||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
use HasValidation;
|
|
||||||
|
/**
|
||||||
|
* @return array<string, string|array<int, string|Rule>>
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'status' => ['required', 'string', 'max:255', Rule::in(InvoiceStatus::values())],
|
||||||
|
'to' => 'array',
|
||||||
|
'to.address' => 'required|string|max:255',
|
||||||
|
'to.location' => 'required|string|max:255',
|
||||||
|
'to.zip' => 'required|string|max:255',
|
||||||
|
'to.name' => 'required|string|max:255',
|
||||||
|
'greeting' => 'required|string|max:255',
|
||||||
|
'intro' => 'required|string',
|
||||||
|
'outro' => 'required|string',
|
||||||
|
'positions' => 'array',
|
||||||
|
'positions.*.description' => 'required|string|max:300',
|
||||||
|
'positions.*.price' => 'required|integer|min:0',
|
||||||
|
'positions.*.member_id' => 'required|exists:members,id',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, string>
|
||||||
|
*/
|
||||||
|
public function getValidationAttributes(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'to.address' => 'Adresse',
|
||||||
|
'to.name' => 'Name',
|
||||||
|
'to.zip' => 'PLZ',
|
||||||
|
'to.location' => 'Ort',
|
||||||
|
'status' => 'Status',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
public function handle(ActionRequest $request): void
|
public function handle(ActionRequest $request): void
|
||||||
{
|
{
|
||||||
|
@ -19,7 +55,5 @@ class InvoiceStoreAction
|
||||||
foreach ($request->validated('positions') as $position) {
|
foreach ($request->validated('positions') as $position) {
|
||||||
$invoice->positions()->create($position);
|
$invoice->positions()->create($position);
|
||||||
}
|
}
|
||||||
|
|
||||||
Succeeded::message('Rechnung erstellt.')->dispatch();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Invoice\Actions;
|
|
||||||
|
|
||||||
use Lorisleiva\Actions\ActionRequest;
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
|
||||||
use App\Invoice\Models\Invoice;
|
|
||||||
use App\Lib\Events\Succeeded;
|
|
||||||
use Illuminate\Support\Arr;
|
|
||||||
|
|
||||||
class InvoiceUpdateAction
|
|
||||||
{
|
|
||||||
use AsAction;
|
|
||||||
use HasValidation;
|
|
||||||
|
|
||||||
public function handle(Invoice $invoice, ActionRequest $request): void
|
|
||||||
{
|
|
||||||
$invoice->update($request->safe()->except('positions'));
|
|
||||||
|
|
||||||
foreach ($request->validated('positions') as $position) {
|
|
||||||
if ($position['id']) {
|
|
||||||
$invoice->positions()->firstWhere('id', $position['id'])->update(Arr::except($position, 'id'));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$invoice->positions()->create($position);
|
|
||||||
}
|
|
||||||
|
|
||||||
$invoice->positions()->whereNotIn('id', array_column($request->validated('positions'), 'id'))->delete();
|
|
||||||
|
|
||||||
Succeeded::message('Rechnung bearbeitet.')->dispatch();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,65 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Invoice\Actions;
|
|
||||||
|
|
||||||
use App\Invoice\Models\Invoice;
|
|
||||||
use App\Lib\JobMiddleware\JobChannels;
|
|
||||||
use App\Lib\JobMiddleware\WithJobState;
|
|
||||||
use App\Lib\Queue\TracksJob;
|
|
||||||
use App\Member\Member;
|
|
||||||
use Illuminate\Http\JsonResponse;
|
|
||||||
use Lorisleiva\Actions\ActionRequest;
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
|
||||||
|
|
||||||
class MassStoreAction
|
|
||||||
{
|
|
||||||
use AsAction;
|
|
||||||
use TracksJob;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array<string, string>
|
|
||||||
*/
|
|
||||||
public function rules(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'year' => 'required|numeric',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle(int $year): void
|
|
||||||
{
|
|
||||||
$memberGroup = Member::payable()->get()
|
|
||||||
->groupBy(fn ($member) => "{$member->bill_kind->value}{$member->lastname}{$member->address}{$member->zip}{$member->location}");
|
|
||||||
foreach ($memberGroup as $members) {
|
|
||||||
$invoice = Invoice::createForMember($members->first());
|
|
||||||
|
|
||||||
foreach ($members as $member) {
|
|
||||||
foreach ($member->subscription->children as $child) {
|
|
||||||
$invoice->positions()->create([
|
|
||||||
'description' => str($child->name)->replace('{name}', $member->firstname . ' ' . $member->lastname)->replace('{year}', $year),
|
|
||||||
'price' => $child->amount,
|
|
||||||
'member_id' => $member->id,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function asController(ActionRequest $request): JsonResponse
|
|
||||||
{
|
|
||||||
$this->startJob($request->year);
|
|
||||||
|
|
||||||
return response()->json([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param mixed $parameters
|
|
||||||
*/
|
|
||||||
public function jobState(WithJobState $jobState, ...$parameters): WithJobState
|
|
||||||
{
|
|
||||||
return $jobState
|
|
||||||
->after('Zahlungen erstellt')
|
|
||||||
->failed('Fehler beim Erstellen von Zahlungen')
|
|
||||||
->shouldReload(JobChannels::make()->add('invoice'));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -17,12 +17,4 @@ enum InvoiceStatus: string
|
||||||
{
|
{
|
||||||
return collect(static::cases())->map(fn ($case) => $case->value);
|
return collect(static::cases())->map(fn ($case) => $case->value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array<int, array{id: string, name: string}>
|
|
||||||
*/
|
|
||||||
public static function forSelect(): array
|
|
||||||
{
|
|
||||||
return array_map(fn ($case) => ['id' => $case->value, 'name' => $case->value], static::cases());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,6 @@
|
||||||
|
|
||||||
namespace App\Invoice\Models;
|
namespace App\Invoice\Models;
|
||||||
|
|
||||||
use App\Invoice\BillKind;
|
|
||||||
use App\Invoice\Enums\InvoiceStatus;
|
|
||||||
use App\Member\Member;
|
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
@ -17,13 +14,6 @@ class Invoice extends Model
|
||||||
|
|
||||||
public $casts = [
|
public $casts = [
|
||||||
'to' => 'json',
|
'to' => 'json',
|
||||||
'status' => InvoiceStatus::class,
|
|
||||||
'via' => BillKind::class,
|
|
||||||
];
|
|
||||||
|
|
||||||
/** @var array<int, string> */
|
|
||||||
public $dates = [
|
|
||||||
'sent_at',
|
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -33,26 +23,4 @@ class Invoice extends Model
|
||||||
{
|
{
|
||||||
return $this->hasMany(InvoicePosition::class);
|
return $this->hasMany(InvoicePosition::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function createForMember(Member $member): self
|
|
||||||
{
|
|
||||||
return static::create([
|
|
||||||
'to' => [
|
|
||||||
'name' => 'Familie ' . $member->lastname,
|
|
||||||
'address' => $member->address,
|
|
||||||
'zip' => $member->zip,
|
|
||||||
'location' => $member->location,
|
|
||||||
],
|
|
||||||
'greeting' => 'Liebe Familie ' . $member->lastname,
|
|
||||||
'status' => InvoiceStatus::NEW,
|
|
||||||
'via' => $member->bill_kind,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function booted(): void
|
|
||||||
{
|
|
||||||
static::deleting(function ($model) {
|
|
||||||
$model->positions()->delete();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,39 +2,12 @@
|
||||||
|
|
||||||
namespace App\Invoice\Models;
|
namespace App\Invoice\Models;
|
||||||
|
|
||||||
use App\Member\Member;
|
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
||||||
|
|
||||||
class InvoicePosition extends Model
|
class InvoicePosition extends Model
|
||||||
{
|
{
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
|
||||||
public $guarded = [];
|
public $guarded = [];
|
||||||
|
|
||||||
/**
|
|
||||||
* @return BelongsTo<Member, self>
|
|
||||||
*/
|
|
||||||
public function member(): BelongsTo
|
|
||||||
{
|
|
||||||
return $this->belongsTo(Member::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return BelongsTo<Invoice, self>
|
|
||||||
*/
|
|
||||||
public function invoice(): BelongsTo
|
|
||||||
{
|
|
||||||
return $this->belongsTo(Invoice::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function booted(): void
|
|
||||||
{
|
|
||||||
static::deleted(function ($model) {
|
|
||||||
if ($model->invoice->positions()->get()->count() === 0) {
|
|
||||||
$model->invoice->delete();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Invoice\Resources;
|
|
||||||
|
|
||||||
use App\Invoice\Models\InvoicePosition;
|
|
||||||
use Illuminate\Http\Resources\Json\JsonResource;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @mixin InvoicePosition
|
|
||||||
*/
|
|
||||||
class InvoicePositionResource extends JsonResource
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transform the resource into an array.
|
|
||||||
*
|
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
* @return array<string, mixed>
|
|
||||||
*/
|
|
||||||
public function toArray($request)
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'id' => $this->id,
|
|
||||||
'price' => $this->price,
|
|
||||||
'member_id' => $this->member_id,
|
|
||||||
'description' => $this->description,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,77 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Invoice\Resources;
|
|
||||||
|
|
||||||
use App\Invoice\BillKind;
|
|
||||||
use App\Invoice\Enums\InvoiceStatus;
|
|
||||||
use App\Invoice\Models\Invoice;
|
|
||||||
use App\Lib\HasMeta;
|
|
||||||
use App\Member\Member;
|
|
||||||
use Illuminate\Http\Resources\Json\JsonResource;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @mixin Invoice
|
|
||||||
*/
|
|
||||||
class InvoiceResource extends JsonResource
|
|
||||||
{
|
|
||||||
|
|
||||||
use HasMeta;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transform the resource into an array.
|
|
||||||
*
|
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
* @return array<string, mixed>
|
|
||||||
*/
|
|
||||||
public function toArray($request)
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'id' => $this->id,
|
|
||||||
'to' => $this->to,
|
|
||||||
'sum_human' => number_format($this->positions->sum('price') / 100, 2, ',', '') . ' €',
|
|
||||||
'sent_at_human' => $this->sent_at?->format('d.m.Y') ?: '',
|
|
||||||
'status' => $this->status->value,
|
|
||||||
'via' => $this->via->value,
|
|
||||||
'positions' => InvoicePositionResource::collection($this->whenLoaded('positions')),
|
|
||||||
'greeting' => $this->greeting,
|
|
||||||
'links' => [
|
|
||||||
'update' => route('invoice.update', ['invoice' => $this->getModel()]),
|
|
||||||
'destroy' => route('invoice.destroy', ['invoice' => $this->getModel()]),
|
|
||||||
]
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array<string, mixed>
|
|
||||||
*/
|
|
||||||
public static function meta(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'links' => [
|
|
||||||
'mass-store' => route('invoice.mass-store'),
|
|
||||||
'store' => route('invoice.store'),
|
|
||||||
],
|
|
||||||
'vias' => BillKind::forSelect(),
|
|
||||||
'statuses' => InvoiceStatus::forSelect(),
|
|
||||||
'members' => Member::forSelect(),
|
|
||||||
'default' => [
|
|
||||||
'to' => [
|
|
||||||
'name' => '',
|
|
||||||
'address' => '',
|
|
||||||
'zip' => '',
|
|
||||||
'location' => '',
|
|
||||||
],
|
|
||||||
'positions' => [],
|
|
||||||
'greeting' => '',
|
|
||||||
'status' => InvoiceStatus::NEW->value,
|
|
||||||
'via' => null,
|
|
||||||
],
|
|
||||||
'default_position' => [
|
|
||||||
'id' => null,
|
|
||||||
'price' => 0,
|
|
||||||
'description' => '',
|
|
||||||
'member_id' => null,
|
|
||||||
]
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Lib\Events;
|
|
||||||
|
|
||||||
use Illuminate\Broadcasting\Channel;
|
|
||||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
|
||||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
|
|
||||||
use Illuminate\Foundation\Events\Dispatchable;
|
|
||||||
use Illuminate\Queue\SerializesModels;
|
|
||||||
|
|
||||||
class Succeeded implements ShouldBroadcastNow
|
|
||||||
{
|
|
||||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
|
||||||
|
|
||||||
final private function __construct(public string $message)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function message(string $message): self
|
|
||||||
{
|
|
||||||
return new self($message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function dispatch(): void
|
|
||||||
{
|
|
||||||
event($this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the channels the event should broadcast on.
|
|
||||||
*
|
|
||||||
* @return array<int, Channel>
|
|
||||||
*/
|
|
||||||
public function broadcastOn()
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
new Channel('jobs'),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -17,7 +17,7 @@ trait TracksJob
|
||||||
{
|
{
|
||||||
$jobId = Str::uuid();
|
$jobId = Str::uuid();
|
||||||
$jobState = WithJobState::make($jobId);
|
$jobState = WithJobState::make($jobId);
|
||||||
tap($this->jobState(...[$jobState, ...$parameters])->beforeMessage, fn ($beforeMessage) => $beforeMessage && $beforeMessage->dispatch());;
|
$this->jobState(...[$jobState, ...$parameters])->beforeMessage->dispatch();
|
||||||
$parameters[] = $jobId;
|
$parameters[] = $jobId;
|
||||||
static::dispatch(...$parameters);
|
static::dispatch(...$parameters);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,6 @@ use App\Course\Models\CourseMember;
|
||||||
use App\Gender;
|
use App\Gender;
|
||||||
use App\Group;
|
use App\Group;
|
||||||
use App\Invoice\BillKind;
|
use App\Invoice\BillKind;
|
||||||
use App\Invoice\Models\InvoicePosition;
|
|
||||||
use App\Nami\HasNamiField;
|
use App\Nami\HasNamiField;
|
||||||
use App\Nationality;
|
use App\Nationality;
|
||||||
use App\Payment\Payment;
|
use App\Payment\Payment;
|
||||||
|
@ -214,14 +213,6 @@ class Member extends Model implements Geolocatable
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return HasMany<InvoicePosition>
|
|
||||||
*/
|
|
||||||
public function invoicePositions(): HasMany
|
|
||||||
{
|
|
||||||
return $this->hasMany(InvoicePosition::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return BelongsTo<Confession, self>
|
* @return BelongsTo<Confession, self>
|
||||||
*/
|
*/
|
||||||
|
@ -300,9 +291,6 @@ class Member extends Model implements Geolocatable
|
||||||
$model->payments->each->delete();
|
$model->payments->each->delete();
|
||||||
$model->memberships->each->delete();
|
$model->memberships->each->delete();
|
||||||
$model->courses->each->delete();
|
$model->courses->each->delete();
|
||||||
$model->invoicePositions->each(function ($position) {
|
|
||||||
$position->delete();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
static::saving(fn ($model) => $model->updateSearch());
|
static::saving(fn ($model) => $model->updateSearch());
|
||||||
|
@ -369,6 +357,18 @@ class Member extends Model implements Geolocatable
|
||||||
return $query->where('bill_kind', '!=', null)->where('subscription_id', '!=', null);
|
return $query->where('bill_kind', '!=', null)->where('subscription_id', '!=', null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Builder<self> $query
|
||||||
|
*
|
||||||
|
* @return Builder<self>
|
||||||
|
*/
|
||||||
|
public function scopeWhereNoPayment(Builder $query, int $year): Builder
|
||||||
|
{
|
||||||
|
return $query->whereDoesntHave('payments', function (Builder $q) use ($year) {
|
||||||
|
$q->where('nr', '=', $year);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Builder<self> $query
|
* @param Builder<self> $query
|
||||||
*
|
*
|
||||||
|
@ -506,14 +506,6 @@ class Member extends Model implements Geolocatable
|
||||||
])->implode(' ');
|
])->implode(' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array<int, array{id: int, name: string}>
|
|
||||||
*/
|
|
||||||
public static function forSelect(): array
|
|
||||||
{
|
|
||||||
return static::select(['id', 'firstname', 'lastname'])->get()->map(fn ($member) => ['id' => $member->id, 'name' => $member->fullname])->toArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------------------- Geolocation --------------------------------
|
// -------------------------------- Geolocation --------------------------------
|
||||||
// *****************************************************************************
|
// *****************************************************************************
|
||||||
public function fillCoordinate(Coordinate $coordinate): void
|
public function fillCoordinate(Coordinate $coordinate): void
|
||||||
|
|
|
@ -157,6 +157,7 @@ class MemberResource extends JsonResource
|
||||||
'links' => [
|
'links' => [
|
||||||
'index' => route('member.index'),
|
'index' => route('member.index'),
|
||||||
'create' => route('member.create'),
|
'create' => route('member.create'),
|
||||||
|
'allpayment' => route('allpayment.page'),
|
||||||
'sendpayment' => route('sendpayment.create'),
|
'sendpayment' => route('sendpayment.create'),
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Payment\Actions;
|
||||||
|
|
||||||
|
use Inertia;
|
||||||
|
use Inertia\Response;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class AllpaymentPageAction
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, string>
|
||||||
|
*/
|
||||||
|
public function handle(): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function asController(): Response
|
||||||
|
{
|
||||||
|
session()->put('menu', 'member');
|
||||||
|
session()->put('title', 'Rechnungen erstellen');
|
||||||
|
|
||||||
|
return Inertia::render('allpayment/VForm', $this->handle());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Payment\Actions;
|
||||||
|
|
||||||
|
use App\Member\Member;
|
||||||
|
use App\Member\Membership;
|
||||||
|
use App\Payment\Status;
|
||||||
|
use App\Payment\Subscription;
|
||||||
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Lorisleiva\Actions\ActionRequest;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class AllpaymentStoreAction
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, string>
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'year' => 'required|numeric',
|
||||||
|
'for_promise' => 'present|boolean',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(int $year, bool $forPromise): void
|
||||||
|
{
|
||||||
|
foreach (Member::payable()->whereNoPayment($year)->get() as $member) {
|
||||||
|
$member->createPayment([
|
||||||
|
'nr' => $year,
|
||||||
|
'subscription_id' => $member->subscription_id,
|
||||||
|
'status_id' => Status::default(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!$forPromise) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->createPaymentsForPromise($member, $year);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createPaymentsForPromise(Member $member, int $year): void
|
||||||
|
{
|
||||||
|
$subscription = Subscription::firstWhere('for_promise', true);
|
||||||
|
|
||||||
|
if (is_null($subscription)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->promisedMemberships($member, $year) as $membership) {
|
||||||
|
$attributes = [
|
||||||
|
'nr' => $membership->subactivity->name.' '.$membership->promised_at->year,
|
||||||
|
'subscription_id' => $subscription->id,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!$member->payments()->where($attributes)->exists()) {
|
||||||
|
$member->createPayment([
|
||||||
|
...$attributes,
|
||||||
|
'status_id' => Status::default(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection<int, Membership>
|
||||||
|
*/
|
||||||
|
public function promisedMemberships(Member $member, int $year): Collection
|
||||||
|
{
|
||||||
|
return $member->memberships()->whereNotNull('promised_at')->whereYear('promised_at', now()->year($year)->subYear())->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function asController(ActionRequest $request): RedirectResponse
|
||||||
|
{
|
||||||
|
$this->handle($request->year, $request->for_promise);
|
||||||
|
|
||||||
|
return redirect()->back()->success('Zahlungen erstellt');
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,9 +6,6 @@ use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated
|
|
||||||
*/
|
|
||||||
class Status extends Model
|
class Status extends Model
|
||||||
{
|
{
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
|
|
@ -1,53 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Database\Factories\Invoice\Models;
|
|
||||||
|
|
||||||
use App\Invoice\BillKind;
|
|
||||||
use App\Invoice\Enums\InvoiceStatus;
|
|
||||||
use App\Invoice\Models\Invoice;
|
|
||||||
use Carbon\Carbon;
|
|
||||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
|
||||||
use Tests\Feature\Invoice\ReceiverRequestFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @extends Factory<Invoice>
|
|
||||||
*/
|
|
||||||
class InvoiceFactory extends Factory
|
|
||||||
{
|
|
||||||
protected $model = Invoice::class;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Define the model's default state.
|
|
||||||
*
|
|
||||||
* @return array<string, mixed>
|
|
||||||
*/
|
|
||||||
public function definition()
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'greeting' => $this->faker->words(4, true),
|
|
||||||
'to' => ReceiverRequestFactory::new()->create(),
|
|
||||||
'status' => InvoiceStatus::NEW->value,
|
|
||||||
'via' => BillKind::POST->value
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function to(ReceiverRequestFactory $to): self
|
|
||||||
{
|
|
||||||
return $this->state(['to' => $to->create()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function sentAt(Carbon $sentAt): self
|
|
||||||
{
|
|
||||||
return $this->state(['sent_at' => $sentAt]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function status(InvoiceStatus $status): self
|
|
||||||
{
|
|
||||||
return $this->state(['status' => $status->value]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function via(BillKind $via): self
|
|
||||||
{
|
|
||||||
return $this->state(['via' => $via->value]);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Database\Factories\Invoice\Models;
|
|
||||||
|
|
||||||
use App\Invoice\Models\InvoicePosition;
|
|
||||||
use App\Member\Member;
|
|
||||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @extends Factory<InvoicePosition>
|
|
||||||
*/
|
|
||||||
class InvoicePositionFactory extends Factory
|
|
||||||
{
|
|
||||||
protected $model = InvoicePosition::class;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Define the model's default state.
|
|
||||||
*
|
|
||||||
* @return array<string, mixed>
|
|
||||||
*/
|
|
||||||
public function definition()
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'description' => $this->faker->words(4, true),
|
|
||||||
'member_id' => Member::factory()->defaults()->create()->id,
|
|
||||||
'price' => $this->faker->numberBetween(1000, 2000),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function price(int $price): self
|
|
||||||
{
|
|
||||||
return $this->state(['price' => $price]);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -95,15 +95,4 @@ class MemberFactory extends Factory
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function sameFamilyAs(Member $member): self
|
|
||||||
{
|
|
||||||
return $this->state([
|
|
||||||
'firstname' => $member->firstname . 'a',
|
|
||||||
'lastname' => $member->lastname,
|
|
||||||
'address' => $member->address,
|
|
||||||
'zip' => $member->zip,
|
|
||||||
'location' => $member->location,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,9 +17,9 @@ return new class extends Migration
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->json('to');
|
$table->json('to');
|
||||||
$table->string('greeting');
|
$table->string('greeting');
|
||||||
|
$table->text('intro');
|
||||||
|
$table->text('outro');
|
||||||
$table->string('status');
|
$table->string('status');
|
||||||
$table->date('sent_at')->nullable();
|
|
||||||
$table->string('via');
|
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -4,9 +4,6 @@ includes:
|
||||||
|
|
||||||
parameters:
|
parameters:
|
||||||
|
|
||||||
stubFiles:
|
|
||||||
- tests/stub/phpstan/TestResponse.stub
|
|
||||||
|
|
||||||
paths:
|
paths:
|
||||||
- app
|
- app
|
||||||
- tests
|
- tests
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 512.018 512.018"><path d="M6.98 256.673c-5.504 2.027-8.341 8.128-6.336 13.653l6.699 18.432L58.35 237.75zM55.257 420.513l30.72 84.48a10.57 10.57 0 0 0 5.525 6.016 10.73 10.73 0 0 0 4.501 1.003c1.259 0 2.496-.213 3.691-.661l33.899-12.501zM511.364 348.385l-35.157-96.661-84.373 84.373 41.813-15.403c5.483-2.091 11.669.768 13.696 6.315 2.048 5.525-.789 11.669-6.315 13.696l-53.12 19.584a10.617 10.617 0 0 1-3.691.661c-4.331 0-8.427-2.667-10.005-6.976-.021-.064 0-.128-.021-.192l-89.408 89.408 220.245-81.152c5.525-2.026 8.362-8.128 6.336-13.653M508.889 173.793 338.222 3.126c-4.16-4.16-10.923-4.16-15.083 0l-320 320c-4.16 4.16-4.16 10.923 0 15.083l170.667 170.667a10.56 10.56 0 0 0 7.531 3.136c2.731 0 5.461-1.045 7.552-3.115l320-320a10.7 10.7 0 0 0 0-15.104m-384 121.771L82.222 338.23a10.716 10.716 0 0 1-15.104 0c-4.16-4.16-4.16-10.923 0-15.083l42.667-42.667c4.16-4.16 10.923-4.16 15.083 0 4.16 4.161 4.181 10.902.021 15.084m184.213 13.546c-7.552 7.552-17.813 11.179-29.227 11.179-18.859 0-40.896-9.877-59.328-28.331-13.483-13.483-22.955-29.611-26.645-45.397-4.096-17.6-.725-32.917 9.493-43.157 10.219-10.24 25.536-13.611 43.157-9.493 15.787 3.691 31.915 13.141 45.397 26.645 29.633 29.61 37.185 68.522 17.153 88.554m135.787-120.213-42.667 42.667a10.716 10.716 0 0 1-15.104 0c-4.16-4.16-4.16-10.923 0-15.083l42.667-42.667c4.16-4.16 10.923-4.16 15.083 0s4.181 10.902.021 15.083"/></svg>
|
|
Before Width: | Height: | Size: 1.4 KiB |
|
@ -1,10 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="fixed z-40 top-0 left-0 w-full h-full flex items-center justify-center p-6">
|
<div class="fixed z-40 top-0 left-0 w-full h-full flex items-center justify-center p-6">
|
||||||
<div class="relative rounded-lg p-8 bg-zinc-800 shadow-2xl shadow-black border border-zinc-700 border-solid w-full" :class="innerWidth">
|
<div class="relative rounded-lg p-8 bg-zinc-800 shadow-2xl shadow-black border border-zinc-700 border-solid w-full max-w-xl">
|
||||||
<a href="#" class="absolute top-0 right-0 mt-6 mr-6" @click.prevent="$emit('close')">
|
<a href="#" class="absolute top-0 right-0 mt-6 mr-6" @click.prevent="$emit('close')">
|
||||||
<ui-sprite src="close" class="text-zinc-400 w-6 h-6"></ui-sprite>
|
<ui-sprite src="close" class="text-zinc-400 w-6 h-6"></ui-sprite>
|
||||||
</a>
|
</a>
|
||||||
<h3 v-if="heading" class="font-semibold text-primary-200 text-xl" v-html="heading"></h3>
|
<h3 class="font-semibold text-primary-200 text-xl" v-html="heading"></h3>
|
||||||
<div class="text-primary-100">
|
<div class="text-primary-100">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
|
@ -15,14 +15,7 @@
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
heading: {
|
heading: {},
|
||||||
type: String,
|
|
||||||
default: () => '',
|
|
||||||
},
|
|
||||||
innerWidth: {
|
|
||||||
default: () => 'max-w-xl',
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import {ref, inject, computed, onBeforeUnmount} from 'vue';
|
import {ref, computed, onBeforeUnmount} from 'vue';
|
||||||
import {router} from '@inertiajs/vue3';
|
import {router} from '@inertiajs/vue3';
|
||||||
import useQueueEvents from './useQueueEvents.js';
|
import useQueueEvents from './useQueueEvents.js';
|
||||||
|
|
||||||
export function useIndex(props, siteName) {
|
export function useIndex(props, siteName) {
|
||||||
const axios = inject('axios');
|
|
||||||
const {startListener, stopListener} = useQueueEvents(siteName, () => reload(false));
|
const {startListener, stopListener} = useQueueEvents(siteName, () => reload(false));
|
||||||
const rawProps = JSON.parse(JSON.stringify(props));
|
const rawProps = JSON.parse(JSON.stringify(props));
|
||||||
const inner = {
|
const inner = {
|
||||||
|
@ -57,6 +56,20 @@ export function useIndex(props, siteName) {
|
||||||
reload(true);
|
reload(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function requestCallback(successMessage, failureMessage) {
|
||||||
|
return {
|
||||||
|
onSuccess: () => {
|
||||||
|
this.$success(successMessage);
|
||||||
|
reload(false);
|
||||||
|
},
|
||||||
|
onFailure: () => {
|
||||||
|
this.$error(failureMessage);
|
||||||
|
reload(false);
|
||||||
|
},
|
||||||
|
preserveState: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
startListener();
|
startListener();
|
||||||
onBeforeUnmount(() => stopListener());
|
onBeforeUnmount(() => stopListener());
|
||||||
|
|
||||||
|
@ -66,12 +79,12 @@ export function useIndex(props, siteName) {
|
||||||
can,
|
can,
|
||||||
getFilter,
|
getFilter,
|
||||||
setFilter,
|
setFilter,
|
||||||
|
requestCallback,
|
||||||
meta: inner.meta,
|
meta: inner.meta,
|
||||||
filterString,
|
filterString,
|
||||||
router,
|
router,
|
||||||
toFilterString,
|
toFilterString,
|
||||||
reloadPage,
|
reloadPage,
|
||||||
axios,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,108 +0,0 @@
|
||||||
import {computed, ref, inject, onBeforeUnmount} from 'vue';
|
|
||||||
import {router} from '@inertiajs/vue3';
|
|
||||||
import useQueueEvents from './useQueueEvents.js';
|
|
||||||
|
|
||||||
export function useIndex(props, siteName) {
|
|
||||||
const axios = inject('axios');
|
|
||||||
const {startListener, stopListener} = useQueueEvents(siteName, () => reload(false));
|
|
||||||
const single = ref(null);
|
|
||||||
const rawProps = JSON.parse(JSON.stringify(props));
|
|
||||||
const inner = {
|
|
||||||
data: ref(rawProps.data),
|
|
||||||
meta: ref(rawProps.meta),
|
|
||||||
};
|
|
||||||
|
|
||||||
function toFilterString(data) {
|
|
||||||
return btoa(encodeURIComponent(JSON.stringify(data)));
|
|
||||||
}
|
|
||||||
|
|
||||||
const filterString = computed(() => toFilterString(inner.meta.value.filter));
|
|
||||||
|
|
||||||
function reload(resetPage = true, withMeta = true, data) {
|
|
||||||
data = {
|
|
||||||
filter: filterString.value,
|
|
||||||
page: resetPage ? 1 : inner.meta.value.current_page,
|
|
||||||
...data,
|
|
||||||
};
|
|
||||||
|
|
||||||
router.visit(window.location.pathname, {
|
|
||||||
data,
|
|
||||||
preserveState: true,
|
|
||||||
only: ['data'],
|
|
||||||
onSuccess: (page) => {
|
|
||||||
inner.data.value = page.props.data.data;
|
|
||||||
if (withMeta) {
|
|
||||||
inner.meta.value = {
|
|
||||||
...inner.meta.value,
|
|
||||||
...page.props.data.meta,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function reloadPage(page) {
|
|
||||||
reload(false, true, {page: page});
|
|
||||||
}
|
|
||||||
|
|
||||||
function can(permission) {
|
|
||||||
return inner.meta.value.can[permission];
|
|
||||||
}
|
|
||||||
|
|
||||||
function create() {
|
|
||||||
single.value = JSON.parse(JSON.stringify(inner.meta.value.default));
|
|
||||||
}
|
|
||||||
|
|
||||||
function edit(model) {
|
|
||||||
single.value = JSON.parse(JSON.stringify(model));
|
|
||||||
}
|
|
||||||
|
|
||||||
async function submit() {
|
|
||||||
single.value.id ? await axios.patch(single.value.links.update, single.value) : await axios.post(inner.meta.value.links.store, single.value);
|
|
||||||
reload();
|
|
||||||
single.value = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function remove(model) {
|
|
||||||
await axios.delete(model.links.destroy);
|
|
||||||
reload();
|
|
||||||
}
|
|
||||||
|
|
||||||
function can(permission) {
|
|
||||||
return inner.meta.value.can[permission];
|
|
||||||
}
|
|
||||||
|
|
||||||
function cancel() {
|
|
||||||
single.value = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
startListener();
|
|
||||||
onBeforeUnmount(() => stopListener());
|
|
||||||
|
|
||||||
return {
|
|
||||||
data: inner.data,
|
|
||||||
meta: inner.meta,
|
|
||||||
single,
|
|
||||||
create,
|
|
||||||
edit,
|
|
||||||
reload,
|
|
||||||
reloadPage,
|
|
||||||
can,
|
|
||||||
router,
|
|
||||||
submit,
|
|
||||||
remove,
|
|
||||||
cancel,
|
|
||||||
axios,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const indexProps = {
|
|
||||||
data: {
|
|
||||||
default: () => {
|
|
||||||
return {data: [], meta: {}};
|
|
||||||
},
|
|
||||||
type: Object,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export {indexProps};
|
|
|
@ -11,7 +11,6 @@
|
||||||
<v-link href="/" menu="dashboard" icon="loss">Dashboard</v-link>
|
<v-link href="/" menu="dashboard" icon="loss">Dashboard</v-link>
|
||||||
<v-link href="/member" menu="member" icon="user">Mitglieder</v-link>
|
<v-link href="/member" menu="member" icon="user">Mitglieder</v-link>
|
||||||
<v-link v-show="hasModule('bill')" href="/subscription" menu="subscription" icon="money">Beiträge</v-link>
|
<v-link v-show="hasModule('bill')" href="/subscription" menu="subscription" icon="money">Beiträge</v-link>
|
||||||
<v-link v-show="hasModule('bill')" href="/invoice" menu="invoice" icon="moneypaper">Rechnungen</v-link>
|
|
||||||
<v-link href="/contribution" menu="contribution" icon="contribution">Zuschüsse</v-link>
|
<v-link href="/contribution" menu="contribution" icon="contribution">Zuschüsse</v-link>
|
||||||
<v-link href="/activity" menu="activity" icon="activity">Tätigkeiten</v-link>
|
<v-link href="/activity" menu="activity" icon="activity">Tätigkeiten</v-link>
|
||||||
<v-link href="/group" menu="group" icon="group">Gruppen</v-link>
|
<v-link href="/group" menu="group" icon="group">Gruppen</v-link>
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
<template>
|
||||||
|
<page-layout>
|
||||||
|
<form class="p-6 grid gap-4 justify-start" @submit.prevent="submit">
|
||||||
|
<f-text id="year" v-model="inner.year" label="Jahr" required></f-text>
|
||||||
|
|
||||||
|
<f-switch id="for_promise" label="Versprechen einbeziehen" v-model="inner.for_promise" size="sm"></f-switch>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary">Absenden</button>
|
||||||
|
</form>
|
||||||
|
</page-layout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
inner: {
|
||||||
|
for_promise: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
props: {},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
submit() {
|
||||||
|
this.$inertia.post(`/allpayment`, this.inner);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -1,123 +0,0 @@
|
||||||
<template>
|
|
||||||
<page-layout>
|
|
||||||
<template #toolbar>
|
|
||||||
<page-toolbar-button color="primary" icon="plus" @click="create">Rechnung anlegen</page-toolbar-button>
|
|
||||||
<page-toolbar-button color="primary" icon="plus" @click="massstore = { year: '' }">Massenrechnung
|
|
||||||
anlegen</page-toolbar-button>
|
|
||||||
</template>
|
|
||||||
<ui-popup v-if="massstore !== null" heading="Massenrechnung anlegen" @close="massstore = null">
|
|
||||||
<form @submit.prevent="sendMassstore">
|
|
||||||
<section class="grid grid-cols-2 gap-3 mt-6">
|
|
||||||
<f-text id="year" v-model="massstore.year" name="year" label="Jahr" required></f-text>
|
|
||||||
</section>
|
|
||||||
<section class="flex mt-4 space-x-2">
|
|
||||||
<ui-button type="submit" class="btn-danger">Speichern</ui-button>
|
|
||||||
<ui-button class="btn-primary" @click.prevent="massstore = null">Abbrechen</ui-button>
|
|
||||||
</section>
|
|
||||||
</form>
|
|
||||||
</ui-popup>
|
|
||||||
<ui-popup v-if="deleting !== null" heading="Rechnung löschen?" @close="deleting = null">
|
|
||||||
<div>
|
|
||||||
<p class="mt-4">Diese Rechnung löschen?</p>
|
|
||||||
<div class="grid grid-cols-2 gap-3 mt-6">
|
|
||||||
<a href="#" class="text-center btn btn-danger" @click.prevent="
|
|
||||||
remove(deleting);
|
|
||||||
deleting = null;
|
|
||||||
">Rechnung löschen</a>
|
|
||||||
<a href="#" class="text-center btn btn-primary" @click.prevent="deleting = null">Abbrechen</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ui-popup>
|
|
||||||
<ui-popup v-if="single !== null" :heading="`Rechnung ${single.id ? 'bearbeiten' : 'erstellen'}`"
|
|
||||||
inner-width="max-w-4xl" @close="cancel">
|
|
||||||
<form class="grid grid-cols-2 gap-3 mt-4" @submit.prevent="submit">
|
|
||||||
<ui-box heading="Empfänger" container-class="grid grid-cols-2 gap-3">
|
|
||||||
<f-text id="to_name" v-model="single.to.name" name="to_name" label="Name" class="col-span-full"
|
|
||||||
required></f-text>
|
|
||||||
<f-text id="to_address" v-model="single.to.address" name="to_address" class="col-span-full"
|
|
||||||
label="Adresse" required></f-text>
|
|
||||||
<f-text id="to_zip" v-model="single.to.zip" name="to_zip" label="PLZ" required></f-text>
|
|
||||||
<f-text id="to_location" v-model="single.to.location" name="to_location" label="Ort" required></f-text>
|
|
||||||
</ui-box>
|
|
||||||
<ui-box heading="Status" container-class="grid gap-3">
|
|
||||||
<f-select id="status" v-model="single.status" :options="meta.statuses" name="status" label="Status"
|
|
||||||
required></f-select>
|
|
||||||
<f-select id="via" v-model="single.via" :options="meta.vias" name="via" label="Rechnungsweg"
|
|
||||||
required></f-select>
|
|
||||||
<f-text id="greeting" v-model="single.greeting" name="greeting" label="Anrede" required></f-text>
|
|
||||||
</ui-box>
|
|
||||||
<ui-box heading="Positionen" class="col-span-full" container-class="grid gap-3">
|
|
||||||
<template #in-title>
|
|
||||||
<ui-icon-button class="ml-3 btn-primary" icon="plus"
|
|
||||||
@click="single.positions.push({ ...meta.default_position })">Neu</ui-icon-button>
|
|
||||||
</template>
|
|
||||||
<div v-for="(position, index) in single.positions" :key="index" class="flex items-end space-x-3">
|
|
||||||
<f-text :id="`position-description-${index}`" v-model="position.description" class="grow"
|
|
||||||
:name="`position-description-${index}`" label="Beschreibung" required></f-text>
|
|
||||||
<f-text :id="`position-price-${index}`" v-model="position.price" mode="area"
|
|
||||||
:name="`position-price-${index}`" label="Preis" required></f-text>
|
|
||||||
<f-select :id="`position-member-${index}`" v-model="position.member_id" :options="meta.members"
|
|
||||||
:name="`position-member-${index}`" label="Mitglied" required></f-select>
|
|
||||||
<button type="button" class="btn btn-danger btn-sm h-[35px]" icon="trash"
|
|
||||||
@click="single.positions.splice(index, 1)"><ui-sprite src="trash"></ui-sprite></button>
|
|
||||||
</div>
|
|
||||||
</ui-box>
|
|
||||||
<section class="flex mt-4 space-x-2">
|
|
||||||
<ui-button type="submit" class="btn-danger">Speichern</ui-button>
|
|
||||||
<ui-button class="btn-primary" @click.prevent="cancel">Abbrechen</ui-button>
|
|
||||||
</section>
|
|
||||||
</form>
|
|
||||||
</ui-popup>
|
|
||||||
<table cellspacing="0" cellpadding="0" border="0" class="custom-table custom-table-sm">
|
|
||||||
<thead>
|
|
||||||
<th>Empfänger</th>
|
|
||||||
<th>Gesamtbetrag</th>
|
|
||||||
<th>Status</th>
|
|
||||||
<th>Gesendet am</th>
|
|
||||||
<th>Rechnungsweg</th>
|
|
||||||
<th></th>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<tr v-for="(invoice, index) in data" :key="index">
|
|
||||||
<td>
|
|
||||||
<div v-text="invoice.to.name"></div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div v-text="invoice.sum_human"></div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div v-text="invoice.status"></div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div v-text="invoice.sent_at_human"></div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div v-text="invoice.via"></div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a v-tooltip="`Bearbeiten`" href="#" class="inline-flex btn btn-warning btn-sm"
|
|
||||||
@click.prevent="edit(invoice)"><ui-sprite src="pencil"></ui-sprite></a>
|
|
||||||
<a v-tooltip="`Löschen`" href="#" class="ml-2 inline-flex btn btn-danger btn-sm"
|
|
||||||
@click.prevent="deleting = invoice"><ui-sprite src="trash"></ui-sprite></a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
<div class="px-6">
|
|
||||||
<ui-pagination class="mt-4" :value="meta" @reload="reloadPage"></ui-pagination>
|
|
||||||
</div>
|
|
||||||
</page-layout>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { ref } from 'vue';
|
|
||||||
import { indexProps, useIndex } from '../../composables/useInertiaApiIndex.js';
|
|
||||||
const props = defineProps(indexProps);
|
|
||||||
var { axios, meta, data, reloadPage, create, single, edit, cancel, submit, remove } = useIndex(props.data, 'invoice');
|
|
||||||
const massstore = ref(null);
|
|
||||||
const deleting = ref(null);
|
|
||||||
|
|
||||||
async function sendMassstore() {
|
|
||||||
await axios.post(meta.value.links['mass-store'], massstore.value);
|
|
||||||
massstore.value = null;
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -25,10 +25,6 @@ use App\Initialize\Actions\InitializeFormAction;
|
||||||
use App\Initialize\Actions\NamiGetSearchLayerAction;
|
use App\Initialize\Actions\NamiGetSearchLayerAction;
|
||||||
use App\Initialize\Actions\NamiLoginCheckAction;
|
use App\Initialize\Actions\NamiLoginCheckAction;
|
||||||
use App\Initialize\Actions\NamiSearchAction;
|
use App\Initialize\Actions\NamiSearchAction;
|
||||||
use App\Invoice\Actions\InvoiceDestroyAction;
|
|
||||||
use App\Invoice\Actions\InvoiceIndexAction;
|
|
||||||
use App\Invoice\Actions\InvoiceUpdateAction;
|
|
||||||
use App\Invoice\Actions\MassStoreAction;
|
|
||||||
use App\Maildispatcher\Actions\CreateAction;
|
use App\Maildispatcher\Actions\CreateAction;
|
||||||
use App\Maildispatcher\Actions\DestroyAction;
|
use App\Maildispatcher\Actions\DestroyAction;
|
||||||
use App\Maildispatcher\Actions\EditAction;
|
use App\Maildispatcher\Actions\EditAction;
|
||||||
|
@ -49,6 +45,8 @@ use App\Membership\Actions\MembershipDestroyAction;
|
||||||
use App\Membership\Actions\MembershipStoreAction;
|
use App\Membership\Actions\MembershipStoreAction;
|
||||||
use App\Membership\Actions\MembershipUpdateAction;
|
use App\Membership\Actions\MembershipUpdateAction;
|
||||||
use App\Membership\Actions\StoreForGroupAction;
|
use App\Membership\Actions\StoreForGroupAction;
|
||||||
|
use App\Payment\Actions\AllpaymentPageAction;
|
||||||
|
use App\Payment\Actions\AllpaymentStoreAction;
|
||||||
use App\Payment\Actions\DisplayPdfAction;
|
use App\Payment\Actions\DisplayPdfAction;
|
||||||
use App\Payment\Actions\IndexAction as PaymentIndexAction;
|
use App\Payment\Actions\IndexAction as PaymentIndexAction;
|
||||||
use App\Payment\Actions\PaymentDestroyAction;
|
use App\Payment\Actions\PaymentDestroyAction;
|
||||||
|
@ -71,6 +69,8 @@ Route::group(['middleware' => 'auth:web'], function (): void {
|
||||||
Route::resource('member', MemberController::class)->except('show', 'destroy');
|
Route::resource('member', MemberController::class)->except('show', 'destroy');
|
||||||
Route::delete('/member/{member}', MemberDeleteAction::class);
|
Route::delete('/member/{member}', MemberDeleteAction::class);
|
||||||
Route::get('/member/{member}', MemberShowAction::class)->name('member.show');
|
Route::get('/member/{member}', MemberShowAction::class)->name('member.show');
|
||||||
|
Route::get('allpayment', AllpaymentPageAction::class)->name('allpayment.page');
|
||||||
|
Route::post('allpayment', AllpaymentStoreAction::class)->name('allpayment.store');
|
||||||
Route::resource('subscription', SubscriptionController::class);
|
Route::resource('subscription', SubscriptionController::class);
|
||||||
Route::get('/sendpayment', [SendpaymentController::class, 'create'])->name('sendpayment.create');
|
Route::get('/sendpayment', [SendpaymentController::class, 'create'])->name('sendpayment.create');
|
||||||
Route::get('/sendpayment/pdf', [SendpaymentController::class, 'send'])->name('sendpayment.pdf');
|
Route::get('/sendpayment/pdf', [SendpaymentController::class, 'send'])->name('sendpayment.pdf');
|
||||||
|
@ -113,14 +113,8 @@ Route::group(['middleware' => 'auth:web'], function (): void {
|
||||||
Route::patch('/payment/{payment}', PaymentUpdateAction::class)->name('payment.update');
|
Route::patch('/payment/{payment}', PaymentUpdateAction::class)->name('payment.update');
|
||||||
Route::delete('/payment/{payment}', PaymentDestroyAction::class)->name('payment.destroy');
|
Route::delete('/payment/{payment}', PaymentDestroyAction::class)->name('payment.destroy');
|
||||||
|
|
||||||
// -------------------------------- allpayment ---------------------------------
|
|
||||||
Route::post('/invoice/mass-store', MassStoreAction::class)->name('invoice.mass-store');
|
|
||||||
|
|
||||||
// ---------------------------------- invoice ----------------------------------
|
// ---------------------------------- invoice ----------------------------------
|
||||||
Route::get('/invoice', InvoiceIndexAction::class)->name('invoice.index');
|
|
||||||
Route::post('/invoice', InvoiceStoreAction::class)->name('invoice.store');
|
Route::post('/invoice', InvoiceStoreAction::class)->name('invoice.store');
|
||||||
Route::patch('/invoice/{invoice}', InvoiceUpdateAction::class)->name('invoice.update');
|
|
||||||
Route::delete('/invoice/{invoice}', InvoiceDestroyAction::class)->name('invoice.destroy');
|
|
||||||
|
|
||||||
// --------------------------------- membership --------------------------------
|
// --------------------------------- membership --------------------------------
|
||||||
Route::get('/member/{member}/membership', MembershipIndexAction::class)->name('member.membership.index');
|
Route::get('/member/{member}/membership', MembershipIndexAction::class)->name('member.membership.index');
|
||||||
|
|
|
@ -4,16 +4,19 @@ namespace Tests\Feature\Invoice;
|
||||||
|
|
||||||
use App\Invoice\BillDocument;
|
use App\Invoice\BillDocument;
|
||||||
use App\Invoice\BillKind;
|
use App\Invoice\BillKind;
|
||||||
|
use App\Invoice\DocumentFactory;
|
||||||
use App\Invoice\Invoice;
|
use App\Invoice\Invoice;
|
||||||
use App\Invoice\InvoiceSettings;
|
use App\Invoice\InvoiceSettings;
|
||||||
use App\Invoice\Queries\BillKindQuery;
|
use App\Invoice\Queries\BillKindQuery;
|
||||||
use App\Invoice\Queries\InvoiceMemberQuery;
|
use App\Invoice\Queries\InvoiceMemberQuery;
|
||||||
|
use App\Invoice\Queries\SingleMemberQuery;
|
||||||
use App\Invoice\RememberDocument;
|
use App\Invoice\RememberDocument;
|
||||||
use App\Member\Member;
|
use App\Member\Member;
|
||||||
use App\Payment\Payment;
|
use App\Payment\Payment;
|
||||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||||
use Tests\RequestFactories\Child;
|
use Tests\RequestFactories\Child;
|
||||||
use Tests\TestCase;
|
use Tests\TestCase;
|
||||||
|
use Zoomyboy\Tex\Tex;
|
||||||
|
|
||||||
class BillRememberDocumentTest extends TestCase
|
class BillRememberDocumentTest extends TestCase
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Tests\Feature\Invoice;
|
|
||||||
|
|
||||||
use App\Invoice\Models\Invoice;
|
|
||||||
use App\Invoice\Models\InvoicePosition;
|
|
||||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
|
||||||
use Tests\TestCase;
|
|
||||||
|
|
||||||
class InvoiceDestroyActionTest extends TestCase
|
|
||||||
{
|
|
||||||
|
|
||||||
use DatabaseTransactions;
|
|
||||||
|
|
||||||
public function testItDestroysInvoice(): void
|
|
||||||
{
|
|
||||||
$this->login()->loginNami()->withoutExceptionHandling();
|
|
||||||
$invoice = Invoice::factory()->has(InvoicePosition::factory(), 'positions')->create();
|
|
||||||
|
|
||||||
$this->delete(route('invoice.destroy', ['invoice' => $invoice]))->assertOk();
|
|
||||||
$this->assertDatabaseCount('invoices', 0);
|
|
||||||
$this->assertDatabaseCount('invoice_positions', 0);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,78 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Tests\Feature\Invoice;
|
|
||||||
|
|
||||||
use App\Invoice\BillKind;
|
|
||||||
use App\Invoice\Enums\InvoiceStatus;
|
|
||||||
use App\Invoice\Models\Invoice;
|
|
||||||
use App\Invoice\Models\InvoicePosition;
|
|
||||||
use App\Member\Member;
|
|
||||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
|
||||||
use Tests\TestCase;
|
|
||||||
|
|
||||||
class InvoiceIndexActionTest extends TestCase
|
|
||||||
{
|
|
||||||
|
|
||||||
use DatabaseTransactions;
|
|
||||||
|
|
||||||
public function testItDisplaysInvoices(): void
|
|
||||||
{
|
|
||||||
$this->login()->loginNami()->withoutExceptionHandling();
|
|
||||||
$member = Member::factory()->defaults()->create(['firstname' => 'Aaaa', 'lastname' => 'Aaab']);
|
|
||||||
$invoice = Invoice::factory()
|
|
||||||
->has(InvoicePosition::factory()->price(1100)->for($member)->state(['description' => 'lala']), 'positions')
|
|
||||||
->has(InvoicePosition::factory()->price(2200), 'positions')
|
|
||||||
->to(ReceiverRequestFactory::new()->name('Familie Blabla'))
|
|
||||||
->sentAt(now()->subDay())
|
|
||||||
->via(BillKind::POST)
|
|
||||||
->status(InvoiceStatus::SENT)
|
|
||||||
->create();
|
|
||||||
|
|
||||||
$this->get(route('invoice.index'))
|
|
||||||
->assertInertiaPath('data.data.0.to.name', 'Familie Blabla')
|
|
||||||
->assertInertiaPath('data.data.0.id', $invoice->id)
|
|
||||||
->assertInertiaPath('data.data.0.sum_human', '33,00 €')
|
|
||||||
->assertInertiaPath('data.data.0.sent_at_human', now()->subDay()->format('d.m.Y'))
|
|
||||||
->assertInertiaPath('data.data.0.status', 'Rechnung gestellt')
|
|
||||||
->assertInertiaPath('data.data.0.via', 'Post')
|
|
||||||
->assertInertiaPath('data.data.0.greeting', $invoice->greeting)
|
|
||||||
->assertInertiaPath('data.data.0.positions.0.price', 1100)
|
|
||||||
->assertInertiaPath('data.data.0.positions.0.member_id', $member->id)
|
|
||||||
->assertInertiaPath('data.data.0.positions.0.description', 'lala')
|
|
||||||
->assertInertiaPath('data.data.0.positions.0.id', $invoice->positions->first()->id)
|
|
||||||
->assertInertiaPath('data.data.0.links.update', route('invoice.update', ['invoice' => $invoice]))
|
|
||||||
->assertInertiaPath('data.data.0.links.destroy', route('invoice.destroy', ['invoice' => $invoice]))
|
|
||||||
->assertInertiaPath('data.meta.links.mass-store', route('invoice.mass-store'))
|
|
||||||
->assertInertiaPath('data.meta.links.store', route('invoice.store'))
|
|
||||||
->assertInertiaPath('data.meta.vias.0', ['id' => 'E-Mail', 'name' => 'E-Mail'])
|
|
||||||
->assertInertiaPath('data.meta.statuses.0', ['id' => 'Neu', 'name' => 'Neu'])
|
|
||||||
->assertInertiaPath('data.meta.members.0', ['id' => $member->id, 'name' => 'Aaaa Aaab'])
|
|
||||||
->assertInertiaPath('data.meta.default', [
|
|
||||||
'to' => [
|
|
||||||
'name' => '',
|
|
||||||
'address' => '',
|
|
||||||
'zip' => '',
|
|
||||||
'location' => '',
|
|
||||||
],
|
|
||||||
'positions' => [],
|
|
||||||
'greeting' => '',
|
|
||||||
'status' => InvoiceStatus::NEW->value,
|
|
||||||
'via' => null,
|
|
||||||
])
|
|
||||||
->assertInertiaPath('data.meta.default_position', [
|
|
||||||
'id' => null,
|
|
||||||
'price' => 0,
|
|
||||||
'description' => '',
|
|
||||||
'member_id' => null,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testValuesCanBeNull(): void
|
|
||||||
{
|
|
||||||
$this->login()->loginNami()->withoutExceptionHandling();
|
|
||||||
Invoice::factory()->create();
|
|
||||||
|
|
||||||
$this->get(route('invoice.index'))
|
|
||||||
->assertInertiaPath('data.data.0.sent_at_human', '');
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -12,8 +12,6 @@ class InvoicePositionRequestFactory extends RequestFactory
|
||||||
return [
|
return [
|
||||||
'description' => 'Beitrag Abc',
|
'description' => 'Beitrag Abc',
|
||||||
'price' => 3250,
|
'price' => 3250,
|
||||||
'member_id' => Member::factory()->defaults()->create()->id,
|
|
||||||
'id' => null,
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,9 +29,4 @@ class InvoicePositionRequestFactory extends RequestFactory
|
||||||
{
|
{
|
||||||
return $this->state(['member_id' => $member->id]);
|
return $this->state(['member_id' => $member->id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function id(int $id): self
|
|
||||||
{
|
|
||||||
return $this->state(['id' => $id]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,22 +2,19 @@
|
||||||
|
|
||||||
namespace Tests\Feature\Invoice;
|
namespace Tests\Feature\Invoice;
|
||||||
|
|
||||||
use App\Invoice\BillKind;
|
|
||||||
use App\Invoice\Enums\InvoiceStatus;
|
use App\Invoice\Enums\InvoiceStatus;
|
||||||
|
use App\Member\Member;
|
||||||
use Worksome\RequestFactories\RequestFactory;
|
use Worksome\RequestFactories\RequestFactory;
|
||||||
|
|
||||||
class InvoiceRequestFactory extends RequestFactory
|
class InvoiceRequestFactory extends RequestFactory
|
||||||
{
|
{
|
||||||
/** @var array<int, InvoicePositionRequestFactory> */
|
|
||||||
public $positions = [];
|
|
||||||
|
|
||||||
public function definition(): array
|
public function definition(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'to' => ReceiverRequestFactory::new(),
|
'to' => ReceiverRequestFactory::new(),
|
||||||
'greeting' => 'Hallo Familie',
|
'greeting' => 'Hallo Familie',
|
||||||
'status' => InvoiceStatus::NEW->value,
|
'intro' => 'Hiermit stellen wir ihnen den Beitrag in Rechnung.',
|
||||||
'via' => BillKind::EMAIL->value,
|
'outro' => 'Das ist die Rechnung',
|
||||||
'positions' => []
|
'positions' => []
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -34,21 +31,8 @@ class InvoiceRequestFactory extends RequestFactory
|
||||||
|
|
||||||
public function position(InvoicePositionRequestFactory $factory): self
|
public function position(InvoicePositionRequestFactory $factory): self
|
||||||
{
|
{
|
||||||
$this->positions[] = $factory;
|
return $this->state(['positions' => [
|
||||||
|
$factory->create(),
|
||||||
return $this;
|
]]);
|
||||||
}
|
|
||||||
|
|
||||||
public function create(array $attributes = []): array
|
|
||||||
{
|
|
||||||
return parent::create([
|
|
||||||
'positions' => array_map(fn ($position) => $position->create(), $this->positions),
|
|
||||||
...$attributes,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function via(BillKind $via): self
|
|
||||||
{
|
|
||||||
return $this->state(['via' => $via->value]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
namespace Tests\Feature\Invoice;
|
namespace Tests\Feature\Invoice;
|
||||||
|
|
||||||
use App\Invoice\BillKind;
|
|
||||||
use App\Invoice\Enums\InvoiceStatus;
|
use App\Invoice\Enums\InvoiceStatus;
|
||||||
use App\Invoice\Models\Invoice;
|
use App\Invoice\Models\Invoice;
|
||||||
use App\Member\Member;
|
use App\Member\Member;
|
||||||
|
@ -24,19 +23,21 @@ class InvoiceStoreActionTest extends TestCase
|
||||||
route('invoice.store'),
|
route('invoice.store'),
|
||||||
InvoiceRequestFactory::new()
|
InvoiceRequestFactory::new()
|
||||||
->to(ReceiverRequestFactory::new()->name('Familie Blabla')->address('Musterstr 44')->zip('22222')->location('Solingen'))
|
->to(ReceiverRequestFactory::new()->name('Familie Blabla')->address('Musterstr 44')->zip('22222')->location('Solingen'))
|
||||||
|
->position(InvoicePositionRequestFactory::new()->description('Beitrag Abc')->price(3250)->member($member))
|
||||||
->status(InvoiceStatus::PAID)
|
->status(InvoiceStatus::PAID)
|
||||||
->via(BillKind::POST)
|
|
||||||
->state([
|
->state([
|
||||||
'greeting' => 'Hallo Familie',
|
'greeting' => 'Hallo Familie',
|
||||||
|
'intro' => 'Hiermit stellen wir ihnen den Beitrag in Rechnung.',
|
||||||
|
'outro' => 'Das ist die Rechnung',
|
||||||
])
|
])
|
||||||
->position(InvoicePositionRequestFactory::new()->description('Beitrag Abc')->price(3250)->member($member))
|
|
||||||
->create()
|
->create()
|
||||||
);
|
);
|
||||||
|
|
||||||
$response->assertOk();
|
$response->assertOk();
|
||||||
$this->assertDatabaseHas('invoices', [
|
$this->assertDatabaseHas('invoices', [
|
||||||
'greeting' => 'Hallo Familie',
|
'greeting' => 'Hallo Familie',
|
||||||
'via' => BillKind::POST->value,
|
'intro' => 'Hiermit stellen wir ihnen den Beitrag in Rechnung.',
|
||||||
|
'outro' => 'Das ist die Rechnung',
|
||||||
'status' => InvoiceStatus::PAID->value,
|
'status' => InvoiceStatus::PAID->value,
|
||||||
]);
|
]);
|
||||||
$invoice = Invoice::firstWhere('greeting', 'Hallo Familie');
|
$invoice = Invoice::firstWhere('greeting', 'Hallo Familie');
|
||||||
|
@ -85,16 +86,6 @@ class InvoiceStoreActionTest extends TestCase
|
||||||
['to.zip' => ''],
|
['to.zip' => ''],
|
||||||
['to.zip' => 'PLZ ist erforderlich.']
|
['to.zip' => 'PLZ ist erforderlich.']
|
||||||
];
|
];
|
||||||
|
|
||||||
yield [
|
|
||||||
['via' => ''],
|
|
||||||
['via' => 'Rechnungsweg ist erforderlich.']
|
|
||||||
];
|
|
||||||
|
|
||||||
yield [
|
|
||||||
['via' => 'lala'],
|
|
||||||
['via' => 'Der gewählte Wert für Rechnungsweg ist ungültig.']
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -111,7 +102,6 @@ class InvoiceStoreActionTest extends TestCase
|
||||||
InvoiceRequestFactory::new()
|
InvoiceRequestFactory::new()
|
||||||
->to(ReceiverRequestFactory::new())
|
->to(ReceiverRequestFactory::new())
|
||||||
->position(InvoicePositionRequestFactory::new()->member(Member::factory()->defaults()->create()))
|
->position(InvoicePositionRequestFactory::new()->member(Member::factory()->defaults()->create()))
|
||||||
->via(BillKind::POST)
|
|
||||||
->state($input)
|
->state($input)
|
||||||
->create()
|
->create()
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,165 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Tests\Feature\Invoice;
|
|
||||||
|
|
||||||
use App\Invoice\BillKind;
|
|
||||||
use App\Invoice\Enums\InvoiceStatus;
|
|
||||||
use App\Invoice\Models\Invoice;
|
|
||||||
use App\Invoice\Models\InvoicePosition;
|
|
||||||
use App\Member\Member;
|
|
||||||
use Generator;
|
|
||||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
|
||||||
use Tests\TestCase;
|
|
||||||
|
|
||||||
class InvoiceUpdateActionTest extends TestCase
|
|
||||||
{
|
|
||||||
|
|
||||||
use DatabaseTransactions;
|
|
||||||
|
|
||||||
public function testItCanUpdateAnInvoice(): void
|
|
||||||
{
|
|
||||||
$this->login()->loginNami()->withoutExceptionHandling();
|
|
||||||
$invoice = Invoice::factory()->create();
|
|
||||||
|
|
||||||
$this->patchJson(
|
|
||||||
route('invoice.update', ['invoice' => $invoice]),
|
|
||||||
InvoiceRequestFactory::new()
|
|
||||||
->to(ReceiverRequestFactory::new()->name('Familie Blabla')->address('Musterstr 44')->zip('22222')->location('Solingen'))
|
|
||||||
->status(InvoiceStatus::PAID)
|
|
||||||
->via(BillKind::POST)
|
|
||||||
->state([
|
|
||||||
'greeting' => 'Hallo Familie',
|
|
||||||
])
|
|
||||||
->create()
|
|
||||||
)->assertOk();
|
|
||||||
|
|
||||||
$this->assertDatabaseCount('invoices', 1);
|
|
||||||
$this->assertDatabaseHas('invoices', [
|
|
||||||
'greeting' => 'Hallo Familie',
|
|
||||||
'via' => BillKind::POST->value,
|
|
||||||
'status' => InvoiceStatus::PAID->value,
|
|
||||||
'id' => $invoice->id,
|
|
||||||
]);
|
|
||||||
$invoice = Invoice::firstWhere('greeting', 'Hallo Familie');
|
|
||||||
$this->assertEquals([
|
|
||||||
'name' => 'Familie Blabla',
|
|
||||||
'address' => 'Musterstr 44',
|
|
||||||
'zip' => '22222',
|
|
||||||
'location' => 'Solingen',
|
|
||||||
], $invoice->to);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testItAddsAPosition(): void
|
|
||||||
{
|
|
||||||
$this->login()->loginNami()->withoutExceptionHandling();
|
|
||||||
$invoice = Invoice::factory()->create();
|
|
||||||
|
|
||||||
$this->patchJson(
|
|
||||||
route('invoice.update', ['invoice' => $invoice]),
|
|
||||||
InvoiceRequestFactory::new()
|
|
||||||
->position(InvoicePositionRequestFactory::new())
|
|
||||||
->position(InvoicePositionRequestFactory::new())
|
|
||||||
->create()
|
|
||||||
)->assertOk();
|
|
||||||
|
|
||||||
$this->assertDatabaseCount('invoice_positions', 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testItUpdatesAPosition(): void
|
|
||||||
{
|
|
||||||
$this->login()->loginNami()->withoutExceptionHandling();
|
|
||||||
$invoice = Invoice::factory()->has(InvoicePosition::factory(), 'positions')->create();
|
|
||||||
|
|
||||||
$this->patchJson(
|
|
||||||
route('invoice.update', ['invoice' => $invoice]),
|
|
||||||
InvoiceRequestFactory::new()
|
|
||||||
->position(InvoicePositionRequestFactory::new()->description('la')->id($invoice->positions->first()->id))
|
|
||||||
->create()
|
|
||||||
)->assertOk();
|
|
||||||
|
|
||||||
$this->assertDatabaseCount('invoice_positions', 1);
|
|
||||||
$this->assertDatabaseHas('invoice_positions', [
|
|
||||||
'description' => 'la',
|
|
||||||
'id' => $invoice->positions->first()->id,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testItDeletesAPosition(): void
|
|
||||||
{
|
|
||||||
$this->login()->loginNami()->withoutExceptionHandling();
|
|
||||||
$invoice = Invoice::factory()->has(InvoicePosition::factory(), 'positions')->create();
|
|
||||||
|
|
||||||
$this->patchJson(
|
|
||||||
route('invoice.update', ['invoice' => $invoice]),
|
|
||||||
InvoiceRequestFactory::new()
|
|
||||||
->create()
|
|
||||||
)->assertOk();
|
|
||||||
|
|
||||||
$this->assertDatabaseCount('invoice_positions', 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function validationDataProvider(): Generator
|
|
||||||
{
|
|
||||||
yield [
|
|
||||||
['to.address' => ''],
|
|
||||||
['to.address' => 'Adresse ist erforderlich.']
|
|
||||||
];
|
|
||||||
|
|
||||||
yield [
|
|
||||||
['to.name' => ''],
|
|
||||||
['to.name' => 'Name ist erforderlich.']
|
|
||||||
];
|
|
||||||
|
|
||||||
yield [
|
|
||||||
['to.location' => ''],
|
|
||||||
['to.location' => 'Ort ist erforderlich.']
|
|
||||||
];
|
|
||||||
|
|
||||||
yield [
|
|
||||||
['status' => ''],
|
|
||||||
['status' => 'Status ist erforderlich.']
|
|
||||||
];
|
|
||||||
|
|
||||||
yield [
|
|
||||||
['status' => 'lala'],
|
|
||||||
['status' => 'Der gewählte Wert für Status ist ungültig.']
|
|
||||||
];
|
|
||||||
|
|
||||||
yield [
|
|
||||||
['to.zip' => ''],
|
|
||||||
['to.zip' => 'PLZ ist erforderlich.']
|
|
||||||
];
|
|
||||||
|
|
||||||
yield [
|
|
||||||
['via' => ''],
|
|
||||||
['via' => 'Rechnungsweg ist erforderlich.']
|
|
||||||
];
|
|
||||||
|
|
||||||
yield [
|
|
||||||
['via' => 'lala'],
|
|
||||||
['via' => 'Der gewählte Wert für Rechnungsweg ist ungültig.']
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array<string, mixed> $input
|
|
||||||
* @param array<string, string> $errors
|
|
||||||
* @dataProvider validationDataProvider
|
|
||||||
*/
|
|
||||||
public function testItValidatesInput(array $input, array $errors): void
|
|
||||||
{
|
|
||||||
$this->login()->loginNami();
|
|
||||||
|
|
||||||
$response = $this->postJson(
|
|
||||||
route('invoice.store'),
|
|
||||||
InvoiceRequestFactory::new()
|
|
||||||
->to(ReceiverRequestFactory::new())
|
|
||||||
->position(InvoicePositionRequestFactory::new()->member(Member::factory()->defaults()->create()))
|
|
||||||
->via(BillKind::POST)
|
|
||||||
->state($input)
|
|
||||||
->create()
|
|
||||||
);
|
|
||||||
|
|
||||||
$response->assertJsonValidationErrors($errors);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,106 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Tests\Feature\Invoice;
|
|
||||||
|
|
||||||
use App\Invoice\BillKind;
|
|
||||||
use App\Invoice\Models\Invoice;
|
|
||||||
use App\Member\Member;
|
|
||||||
use App\Payment\Subscription;
|
|
||||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
|
||||||
use Tests\RequestFactories\Child;
|
|
||||||
use Tests\TestCase;
|
|
||||||
|
|
||||||
class MassStoreActionTest extends TestCase
|
|
||||||
{
|
|
||||||
use DatabaseTransactions;
|
|
||||||
|
|
||||||
public function setUp(): void
|
|
||||||
{
|
|
||||||
parent::setUp();
|
|
||||||
|
|
||||||
$this->login()->loginNami()->withoutExceptionHandling();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testItDoesntCreatePaymentsWithoutSubscription(): void
|
|
||||||
{
|
|
||||||
Member::factory()->defaults()->emailBillKind()->create(['subscription_id' => null]);
|
|
||||||
|
|
||||||
$this->postJson(route('invoice.mass-store'), [
|
|
||||||
'year' => now()->addYear()->year,
|
|
||||||
])->assertOk();
|
|
||||||
|
|
||||||
$this->assertDatabaseEmpty('invoices');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testItDoesntCreatePaymentWithoutBillKind(): void
|
|
||||||
{
|
|
||||||
Member::factory()->defaults()->create();
|
|
||||||
|
|
||||||
$this->postJson(route('invoice.mass-store'), [
|
|
||||||
'year' => now()->addYear()->year,
|
|
||||||
])->assertOk();
|
|
||||||
|
|
||||||
$this->assertDatabaseEmpty('invoices');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testItCreatesPayments(): void
|
|
||||||
{
|
|
||||||
$member = Member::factory()->defaults()
|
|
||||||
->for(Subscription::factory()->children([
|
|
||||||
new Child('beitrag {name}', 4466),
|
|
||||||
new Child('beitrag2 für {name} für {year}', 2290),
|
|
||||||
]))->emailBillKind()->create(['firstname' => 'Max', 'lastname' => 'Muster', 'address' => 'Maxstr 4', 'zip' => '33445', 'location' => 'Solingen']);
|
|
||||||
|
|
||||||
$this->postJson(route('invoice.mass-store'), [
|
|
||||||
'year' => now()->addYear()->year,
|
|
||||||
])->assertOk();
|
|
||||||
|
|
||||||
$invoice = Invoice::first();
|
|
||||||
$this->assertNotNull($invoice);
|
|
||||||
$this->assertEquals([
|
|
||||||
'name' => 'Familie Muster',
|
|
||||||
'address' => 'Maxstr 4',
|
|
||||||
'zip' => '33445',
|
|
||||||
'location' => 'Solingen',
|
|
||||||
], $invoice->to);
|
|
||||||
$this->assertEquals(BillKind::EMAIL, $invoice->via);
|
|
||||||
$this->assertDatabaseHas('invoice_positions', [
|
|
||||||
'invoice_id' => $invoice->id,
|
|
||||||
'member_id' => $member->id,
|
|
||||||
'price' => 4466,
|
|
||||||
'description' => 'beitrag Max Muster'
|
|
||||||
]);
|
|
||||||
$this->assertDatabaseHas('invoice_positions', [
|
|
||||||
'invoice_id' => $invoice->id,
|
|
||||||
'member_id' => $member->id,
|
|
||||||
'price' => 2290,
|
|
||||||
'description' => 'beitrag2 für Max Muster für ' . now()->addYear()->year
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testItCreatesOneInvoiceForFamilyMember(): void
|
|
||||||
{
|
|
||||||
$subscription = Subscription::factory()->children([new Child('beitrag {name}', 4466)])->create();
|
|
||||||
$member = Member::factory()->defaults()->for($subscription)->emailBillKind()->create(['firstname' => 'Max', 'lastname' => 'Muster']);
|
|
||||||
Member::factory()->defaults()->for($subscription)->sameFamilyAs($member)->emailBillKind()->create(['firstname' => 'Jane']);
|
|
||||||
|
|
||||||
$this->postJson(route('invoice.mass-store'), ['year' => now()->addYear()->year])->assertOk();
|
|
||||||
|
|
||||||
$this->assertDatabaseCount('invoices', 1);
|
|
||||||
$this->assertDatabaseCount('invoice_positions', 2);
|
|
||||||
$this->assertDatabaseHas('invoice_positions', ['description' => 'beitrag Max Muster']);
|
|
||||||
$this->assertDatabaseHas('invoice_positions', ['description' => 'beitrag Jane Muster']);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testItSeparatesBillKinds(): void
|
|
||||||
{
|
|
||||||
$subscription = Subscription::factory()->children([new Child('beitrag {name]', 4466)])->create();
|
|
||||||
$member = Member::factory()->defaults()->for($subscription)->emailBillKind()->create();
|
|
||||||
Member::factory()->defaults()->for($subscription)->sameFamilyAs($member)->postBillKind()->create();
|
|
||||||
|
|
||||||
$this->postJson(route('invoice.mass-store'), ['year' => now()->addYear()->year])->assertOk();
|
|
||||||
|
|
||||||
$this->assertDatabaseCount('invoices', 2);
|
|
||||||
$this->assertDatabaseCount('invoice_positions', 2);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -5,8 +5,6 @@ namespace Tests\Feature\Member;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use App\Course\Models\Course;
|
use App\Course\Models\Course;
|
||||||
use App\Course\Models\CourseMember;
|
use App\Course\Models\CourseMember;
|
||||||
use App\Invoice\Models\Invoice;
|
|
||||||
use App\Invoice\Models\InvoicePosition;
|
|
||||||
use App\Lib\Events\JobFailed;
|
use App\Lib\Events\JobFailed;
|
||||||
use App\Lib\Events\JobFinished;
|
use App\Lib\Events\JobFinished;
|
||||||
use App\Lib\Events\JobStarted;
|
use App\Lib\Events\JobStarted;
|
||||||
|
@ -77,37 +75,6 @@ class DeleteTest extends TestCase
|
||||||
$this->assertDatabaseMissing('members', ['id' => $member->id]);
|
$this->assertDatabaseMissing('members', ['id' => $member->id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testItDeletesInvoicePositions(): void
|
|
||||||
{
|
|
||||||
$this->withoutExceptionHandling()->login()->loginNami();
|
|
||||||
$member = Member::factory()->defaults()->create();
|
|
||||||
$member2 = Member::factory()->defaults()->create();
|
|
||||||
Invoice::factory()
|
|
||||||
->has(InvoicePosition::factory()->for($member), 'positions')
|
|
||||||
->has(InvoicePosition::factory()->for($member2), 'positions')
|
|
||||||
->create();
|
|
||||||
|
|
||||||
MemberDeleteAction::run($member->id);
|
|
||||||
|
|
||||||
$this->assertDatabaseCount('invoices', 1);
|
|
||||||
$this->assertDatabaseCount('invoice_positions', 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testItDeletesInvoicePositionsAndInvoices(): void
|
|
||||||
{
|
|
||||||
$this->withoutExceptionHandling()->login()->loginNami();
|
|
||||||
$member = Member::factory()->defaults()->create();
|
|
||||||
Invoice::factory()
|
|
||||||
->has(InvoicePosition::factory()->for($member), 'positions')
|
|
||||||
->has(InvoicePosition::factory()->for($member), 'positions')
|
|
||||||
->create();
|
|
||||||
|
|
||||||
MemberDeleteAction::run($member->id);
|
|
||||||
|
|
||||||
$this->assertDatabaseCount('invoices', 0);
|
|
||||||
$this->assertDatabaseCount('invoice_positions', 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testItFiresEventWhenFinished(): void
|
public function testItFiresEventWhenFinished(): void
|
||||||
{
|
{
|
||||||
Event::fake([JobStarted::class, JobFinished::class]);
|
Event::fake([JobStarted::class, JobFinished::class]);
|
||||||
|
|
|
@ -0,0 +1,145 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature\Payment;
|
||||||
|
|
||||||
|
use App\Member\Member;
|
||||||
|
use App\Member\Membership;
|
||||||
|
use App\Payment\Payment;
|
||||||
|
use App\Payment\Status;
|
||||||
|
use App\Payment\Subscription;
|
||||||
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class AllpaymentTest extends TestCase
|
||||||
|
{
|
||||||
|
use DatabaseTransactions;
|
||||||
|
|
||||||
|
public function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
$this->login()->loginNami();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItDoesntCreatePaymentsWithoutSubscription(): void
|
||||||
|
{
|
||||||
|
$member = Member::factory()->defaults()->emailBillKind()->create();
|
||||||
|
$member->update(['subscription_id' => null]);
|
||||||
|
|
||||||
|
$response = $this->from('/allpayment/create')->post('allpayment', [
|
||||||
|
'year' => now()->addYear()->year,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response->assertRedirect('/allpayment/create');
|
||||||
|
$this->assertEmpty($member->payments()->get());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItDoesntCreatePaymentWithoutBillKind(): void
|
||||||
|
{
|
||||||
|
$member = Member::factory()->defaults()->create();
|
||||||
|
|
||||||
|
$response = $this->from('/allpayment/create')->post('allpayment', [
|
||||||
|
'year' => now()->addYear()->year,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response->assertRedirect('/allpayment/create');
|
||||||
|
$this->assertEmpty($member->payments()->get());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItCreatesPayments(): void
|
||||||
|
{
|
||||||
|
$member = Member::factory()->defaults()->emailBillKind()->create();
|
||||||
|
|
||||||
|
$response = $this->from('/allpayment/create')->post('allpayment', [
|
||||||
|
'year' => now()->addYear()->year,
|
||||||
|
'for_promise' => false,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response->assertRedirect('/allpayment/create');
|
||||||
|
$this->assertDatabaseHas('payments', [
|
||||||
|
'member_id' => $member->id,
|
||||||
|
'nr' => now()->addYear()->year,
|
||||||
|
'subscription_id' => $member->subscription->id,
|
||||||
|
'status_id' => Status::first()->id,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItCreatesPromisePayments(): void
|
||||||
|
{
|
||||||
|
$member = Member::factory()
|
||||||
|
->defaults()
|
||||||
|
->emailBillKind()
|
||||||
|
->has(Membership::factory()->in('€ Mitglied', 123, 'Rover', 124)->promise(now()->subYear()->startOfYear()))
|
||||||
|
->create();
|
||||||
|
|
||||||
|
$subscription = Subscription::factory()->forPromise()->create();
|
||||||
|
|
||||||
|
$this->from('/allpayment/create')->post('allpayment', [
|
||||||
|
'year' => now()->year,
|
||||||
|
'for_promise' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('payments', [
|
||||||
|
'member_id' => $member->id,
|
||||||
|
'nr' => 'Rover '.now()->subYear()->year,
|
||||||
|
'subscription_id' => $subscription->id,
|
||||||
|
'status_id' => Status::first()->id,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItDoesntCreatePromisePaymentsWhenPromiseIsOver(): void
|
||||||
|
{
|
||||||
|
$member = Member::factory()
|
||||||
|
->defaults()
|
||||||
|
->emailBillKind()
|
||||||
|
->has(Membership::factory()->in('€ Mitglied', 123, 'Rover', 124)->promise(now()->subYears(2)->startOfYear()))
|
||||||
|
->create();
|
||||||
|
|
||||||
|
$subscription = Subscription::factory()->forPromise()->create();
|
||||||
|
|
||||||
|
$this->from('/allpayment/create')->post('allpayment', [
|
||||||
|
'year' => now()->year,
|
||||||
|
'for_promise' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertDatabaseMissing('payments', [
|
||||||
|
'subscription_id' => $subscription->id,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItDoesntCreatePromisePaymentsWhenUserAlreadyHasPayment(): void
|
||||||
|
{
|
||||||
|
$subscription = Subscription::factory()->forPromise()->create();
|
||||||
|
|
||||||
|
$member = Member::factory()
|
||||||
|
->defaults()
|
||||||
|
->emailBillKind()
|
||||||
|
->has(Membership::factory()->in('€ Mitglied', 123, 'Rover', 124)->promise(now()->subYear()->startOfYear()))
|
||||||
|
->has(Payment::factory()->notPaid()->nr('Rover '.now()->subYear()->year)->for($subscription))
|
||||||
|
->create();
|
||||||
|
|
||||||
|
$this->from('/allpayment/create')->post('allpayment', [
|
||||||
|
'year' => now()->year,
|
||||||
|
'for_promise' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertCount(2, $member->payments);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItDoesntCreatePromisePaymentsWhenNoSubscriptionFound(): void
|
||||||
|
{
|
||||||
|
$member = Member::factory()
|
||||||
|
->defaults()
|
||||||
|
->emailBillKind()
|
||||||
|
->has(Membership::factory()->in('€ Mitglied', 123, 'Rover', 124)->promise(now()->subYear()->startOfYear()))
|
||||||
|
->has(Payment::factory()->notPaid()->nr('Rover '.now()->subYear()->year))
|
||||||
|
->create();
|
||||||
|
|
||||||
|
$this->from('/allpayment/create')->post('allpayment', [
|
||||||
|
'year' => now()->year,
|
||||||
|
'for_promise' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertCount(2, $member->payments);
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,10 +9,8 @@ use App\User;
|
||||||
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
|
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
use Illuminate\Testing\AssertableJsonString;
|
|
||||||
use Illuminate\Testing\TestResponse;
|
use Illuminate\Testing\TestResponse;
|
||||||
use Phake;
|
use Phake;
|
||||||
use PHPUnit\Framework\Assert;
|
|
||||||
use Symfony\Component\HttpFoundation\File\File;
|
use Symfony\Component\HttpFoundation\File\File;
|
||||||
use Tests\Lib\MakesHttpCalls;
|
use Tests\Lib\MakesHttpCalls;
|
||||||
use Tests\Lib\TestsInertia;
|
use Tests\Lib\TestsInertia;
|
||||||
|
@ -31,7 +29,6 @@ abstract class TestCase extends BaseTestCase
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
Auth::fake();
|
Auth::fake();
|
||||||
Member::disableGeolocation();
|
Member::disableGeolocation();
|
||||||
$this->initInertiaTestcase();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function loginNami(int $mglnr = 12345, string $password = 'password', int|Group $groupId = 55): self
|
public function loginNami(int $mglnr = 12345, string $password = 'password', int|Group $groupId = 55): self
|
||||||
|
@ -120,17 +117,4 @@ abstract class TestCase extends BaseTestCase
|
||||||
$this->assertCount(1, $output, 'Failed to parse output format of pdfinfo');
|
$this->assertCount(1, $output, 'Failed to parse output format of pdfinfo');
|
||||||
$this->assertEquals($pageCount, $output[0]);
|
$this->assertEquals($pageCount, $output[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function initInertiaTestcase(): void
|
|
||||||
{
|
|
||||||
TestResponse::macro('assertInertiaPath', function ($path, $value) {
|
|
||||||
/** @var TestResponse */
|
|
||||||
$response = $this;
|
|
||||||
$props = data_get($response->viewData('page'), 'props');
|
|
||||||
Assert::assertNotNull($props);
|
|
||||||
$json = new AssertableJsonString($props);
|
|
||||||
$json->assertPath($path, $value);
|
|
||||||
return $this;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Illuminate\Testing;
|
|
||||||
|
|
||||||
use Symfony\Component\HttpFoundation\File\File;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @method self assertInertiaPath(string $path, string|array<string, mixed>|int $value)
|
|
||||||
* @method File getFile()
|
|
||||||
*/
|
|
||||||
class TestResponse
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
Loading…
Reference in New Issue