From 2a6fd1152b216429fda3e22a95c093715b583558 Mon Sep 17 00:00:00 2001 From: Philipp Lang Date: Tue, 19 Dec 2023 02:00:42 +0100 Subject: [PATCH] Remove old payments --- app/Invoice/Actions/MassPostPdfAction.php | 40 ++++++ app/Invoice/BillDocument.php | 40 ------ app/Invoice/InvoiceDocument.php | 15 -- app/Invoice/Models/Invoice.php | 10 +- app/Invoice/Queries/BillKindQuery.php | 23 ---- app/Invoice/Queries/InvoiceMemberQuery.php | 59 -------- app/Invoice/RememberDocument.php | 37 ----- app/Invoice/Resources/InvoiceResource.php | 1 + app/Member/Actions/MemberShowAction.php | 17 +-- app/Member/Member.php | 29 +--- app/Member/MemberResource.php | 6 +- app/Payment/ActionFactory.php | 25 ---- app/Payment/Payment.php | 87 ------------ app/Payment/PaymentResource.php | 56 -------- app/Payment/SendpaymentController.php | 49 ------- app/Payment/Status.php | 52 ------- database/factories/Member/MemberFactory.php | 16 --- database/factories/Payment/PaymentFactory.php | 60 -------- database/factories/Payment/StatusFactory.php | 28 ---- ...021_07_04_101300_create_payments_table.php | 4 - docker-compose.yml | 2 + packages/tex | 2 +- phpstan.neon | 15 -- resources/js/views/invoice/Index.vue | 3 + resources/js/views/member/VIndex.vue | 2 - routes/web.php | 5 +- tests/EndToEnd/MemberIndexTest.php | 6 +- .../Invoice/BillRememberDocumentTest.php | 44 ------ .../Invoice/InvoiceIndexActionTest.php | 1 + .../Feature/Invoice/InvoiceSendActionTest.php | 2 +- .../Feature/Invoice/MassPostPdfActionTest.php | 61 +++++++++ tests/Feature/Member/IndexTest.php | 5 +- tests/Feature/Member/ShowTest.php | 22 ++- tests/Feature/Sendpayment/SendpaymentTest.php | 129 ------------------ 34 files changed, 150 insertions(+), 803 deletions(-) create mode 100644 app/Invoice/Actions/MassPostPdfAction.php delete mode 100644 app/Invoice/Queries/BillKindQuery.php delete mode 100644 app/Invoice/Queries/InvoiceMemberQuery.php delete mode 100644 app/Payment/ActionFactory.php delete mode 100644 app/Payment/Payment.php delete mode 100644 app/Payment/PaymentResource.php delete mode 100644 app/Payment/SendpaymentController.php delete mode 100644 app/Payment/Status.php delete mode 100644 database/factories/Payment/PaymentFactory.php delete mode 100644 database/factories/Payment/StatusFactory.php delete mode 100644 tests/Feature/Invoice/BillRememberDocumentTest.php create mode 100644 tests/Feature/Invoice/MassPostPdfActionTest.php delete mode 100644 tests/Feature/Sendpayment/SendpaymentTest.php diff --git a/app/Invoice/Actions/MassPostPdfAction.php b/app/Invoice/Actions/MassPostPdfAction.php new file mode 100644 index 00000000..c57f1f7f --- /dev/null +++ b/app/Invoice/Actions/MassPostPdfAction.php @@ -0,0 +1,40 @@ +where('via', BillKind::POST)->get() as $invoice) { + $document = BillDocument::fromInvoice($invoice); + $documents[] = $document; + $invoice->sent($document); + } + + foreach (Invoice::whereNeedsRemember()->where('via', BillKind::POST)->get() as $invoice) { + $document = RememberDocument::fromInvoice($invoice); + $documents[] = $document; + $invoice->sent($document); + } + + if (!count($documents)) { + return response()->noContent(); + } + + return Tex::merge($documents); + } +} diff --git a/app/Invoice/BillDocument.php b/app/Invoice/BillDocument.php index ccdeb711..f43b7631 100644 --- a/app/Invoice/BillDocument.php +++ b/app/Invoice/BillDocument.php @@ -7,10 +7,6 @@ use Illuminate\Database\Eloquent\Relations\HasMany; class BillDocument extends InvoiceDocument { - public function linkLabel(): string - { - return 'Rechnung erstellen'; - } public function getSubject(): string { @@ -21,40 +17,4 @@ class BillDocument extends InvoiceDocument { return 'tex.bill'; } - - public static function sendAllLabel(): string - { - return 'Rechnungen versenden'; - } - - public function afterSingle(Payment $payment): void - { - $payment->update([ - 'invoice_data' => $this->toArray(), - 'status_id' => 2, - ]); - } - - /** - * @param HasMany $query - * - * @return HasMany - */ - public static function paymentsQuery(HasMany $query): HasMany - { - return $query->whereNeedsBill(); - } - - /** - * Get Descriptions for sendpayment page. - * - * @return array - */ - public static function getDescription(): array - { - return [ - 'Diese Funktion erstellt ein PDF mit allen noch nicht versendenden Rechnungen bei den Mitgliedern die Post als Versandweg haben.', - 'Die Rechnungen werden automatisch auf "Rechnung gestellt" aktualisiert.', - ]; - } } diff --git a/app/Invoice/InvoiceDocument.php b/app/Invoice/InvoiceDocument.php index 5317523f..b27aef9b 100644 --- a/app/Invoice/InvoiceDocument.php +++ b/app/Invoice/InvoiceDocument.php @@ -14,21 +14,6 @@ abstract class InvoiceDocument extends Document { abstract public function getSubject(): string; abstract public function view(): string; - abstract public function afterSingle(Payment $payment): void; - abstract public function linkLabel(): string; - abstract public static function sendAllLabel(): string; - - /** - * @param HasMany $query - * - * @return HasMany - */ - abstract public static function paymentsQuery(HasMany $query): HasMany; - - /** - * @return array - */ - abstract public static function getDescription(): array; public string $until; public string $filename; diff --git a/app/Invoice/Models/Invoice.php b/app/Invoice/Models/Invoice.php index 260eea17..bec47aa3 100644 --- a/app/Invoice/Models/Invoice.php +++ b/app/Invoice/Models/Invoice.php @@ -91,10 +91,11 @@ class Invoice extends Model */ public function scopeWhereNeedsRemember(Builder $query): Builder { - return $query->where('status', InvoiceStatus::SENT)->whereNotNull('sent_at')->where(function ($query) { - return $query->orWhere('last_remembered_at', '<=', now()->subMonths(3)) - ->orWhereNull('last_remembered_at'); - }); + return $query + ->where('status', InvoiceStatus::SENT) + ->whereNotNull('sent_at') + ->whereNotNull('last_remembered_at') + ->where('last_remembered_at', '<=', now()->subMonths(3)); } public function getMailRecipient(): stdClass @@ -111,6 +112,7 @@ class Invoice extends Model $this->update([ 'sent_at' => now(), 'status' => InvoiceStatus::SENT, + 'last_remembered_at' => now(), ]); } diff --git a/app/Invoice/Queries/BillKindQuery.php b/app/Invoice/Queries/BillKindQuery.php deleted file mode 100644 index e3f48f74..00000000 --- a/app/Invoice/Queries/BillKindQuery.php +++ /dev/null @@ -1,23 +0,0 @@ - - */ - protected function getQuery(): Builder - { - return Member::where('bill_kind', $this->billKind); - } -} diff --git a/app/Invoice/Queries/InvoiceMemberQuery.php b/app/Invoice/Queries/InvoiceMemberQuery.php deleted file mode 100644 index 7ad3903e..00000000 --- a/app/Invoice/Queries/InvoiceMemberQuery.php +++ /dev/null @@ -1,59 +0,0 @@ - $type - */ - public string $type; - - /** - * @return Builder - */ - abstract protected function getQuery(): Builder; - - /** - * @return Collection<(int|string), EloquentCollection<(int|string), Member>> - */ - public function getMembers(): Collection - { - return $this->get()->groupBy( - fn ($member) => Str::slug( - "{$member->lastname}{$member->address}{$member->zip}{$member->location}", - ), - )->toBase(); - } - - /** - * @param class-string $type - */ - public function type(string $type): self - { - $this->type = $type; - - return $this; - } - - /** - * @return EloquentCollection - */ - private function get(): EloquentCollection - { - return $this->getQuery() - ->with([ - 'payments' => fn ($query) => $this->type::paymentsQuery($query) - ->orderByRaw('nr, member_id'), - ]) - ->get() - ->filter(fn (Member $member) => $member->payments->count() > 0); - } -} diff --git a/app/Invoice/RememberDocument.php b/app/Invoice/RememberDocument.php index 450ebff6..f5a67ef7 100644 --- a/app/Invoice/RememberDocument.php +++ b/app/Invoice/RememberDocument.php @@ -7,10 +7,6 @@ use Illuminate\Database\Eloquent\Relations\HasMany; class RememberDocument extends InvoiceDocument { - public function linkLabel(): string - { - return 'Erinnerung erstellen'; - } public function getSubject(): string { @@ -21,37 +17,4 @@ class RememberDocument extends InvoiceDocument { return 'tex.remember'; } - - public static function sendAllLabel(): string - { - return 'Erinnerungen versenden'; - } - - public function afterSingle(Payment $payment): void - { - $payment->update(['last_remembered_at' => now()]); - } - - /** - * @param HasMany $query - * - * @return HasMany - */ - public static function paymentsQuery(HasMany $query): HasMany - { - return $query->whereNeedsRemember(); - } - - /** - * Get Descriptions for sendpayment page. - * - * @return array - */ - public static function getDescription(): array - { - return [ - 'Diese Funktion erstellt Erinnerungs-PDFs mit allen versendeten aber noch nich bezahlten Rechnungen bei den Mitgliedern die Post als Versandweg haben.', - 'Das zuletzt erinnerte Datum wird auf heute gesetzt.', - ]; - } } diff --git a/app/Invoice/Resources/InvoiceResource.php b/app/Invoice/Resources/InvoiceResource.php index d4c7ffc5..2be9ca39 100644 --- a/app/Invoice/Resources/InvoiceResource.php +++ b/app/Invoice/Resources/InvoiceResource.php @@ -54,6 +54,7 @@ class InvoiceResource extends JsonResource 'links' => [ 'mass-store' => route('invoice.mass-store'), 'store' => route('invoice.store'), + 'masspdf' => route('invoice.masspdf'), ], 'vias' => BillKind::forSelect(), 'statuses' => InvoiceStatus::forSelect(), diff --git a/app/Member/Actions/MemberShowAction.php b/app/Member/Actions/MemberShowAction.php index 0ff44be3..55515e60 100644 --- a/app/Member/Actions/MemberShowAction.php +++ b/app/Member/Actions/MemberShowAction.php @@ -18,13 +18,14 @@ class MemberShowAction public function handle(Member $member): array { return [ - 'data' => new MemberResource($member - ->load('memberships') - ->load('payments.subscription.children') - ->load('nationality') - ->load('region') - ->load('subscription') - ->load('courses.course') + 'data' => new MemberResource( + $member + ->load('memberships') + ->load('invoicePositions.invoice') + ->load('nationality') + ->load('region') + ->load('subscription') + ->load('courses.course') ), 'meta' => MemberResource::meta(), ]; @@ -33,7 +34,7 @@ class MemberShowAction public function asController(Member $member): Response { session()->put('menu', 'member'); - session()->put('title', 'Mitglied '.$member->fullname); + session()->put('title', 'Mitglied ' . $member->fullname); return Inertia::render('member/ShowView', $this->handle($member)); } diff --git a/app/Member/Member.php b/app/Member/Member.php index 5d1921f7..de710cf0 100644 --- a/app/Member/Member.php +++ b/app/Member/Member.php @@ -11,7 +11,6 @@ use App\Invoice\BillKind; use App\Invoice\Models\InvoicePosition; use App\Nami\HasNamiField; use App\Nationality; -use App\Payment\Payment; use App\Payment\Subscription; use App\Pdf\Sender; use App\Region; @@ -111,13 +110,6 @@ class Member extends Model implements Geolocatable $this->update(['version' => $version]); } - public function createPayment(array $attributes): void - { - $this->payments()->create(array_merge($attributes, [ - 'last_remembered_at' => now(), - ])); - } - // ----------------------------------- Getters ----------------------------------- public function getFullnameAttribute(): string { @@ -270,14 +262,6 @@ class Member extends Model implements Geolocatable return $this->hasMany(Membership::class); } - /** - * @return HasMany - */ - public function payments(): HasMany - { - return $this->hasMany(Payment::class)->orderBy('nr'); - } - /** * @return HasMany */ @@ -297,7 +281,6 @@ class Member extends Model implements Geolocatable public static function booted() { static::deleting(function (self $model): void { - $model->payments->each->delete(); $model->memberships->each->delete(); $model->courses->each->delete(); $model->invoicePositions->each(function ($position) { @@ -327,11 +310,9 @@ class Member extends Model implements Geolocatable public function scopeWithPendingPayment(Builder $query): Builder { return $query->addSelect([ - 'pending_payment' => Payment::selectRaw('SUM(subscription_children.amount)') - ->whereColumn('payments.member_id', 'members.id') - ->whereNeedsPayment() - ->join('subscriptions', 'subscriptions.id', 'payments.subscription_id') - ->join('subscription_children', 'subscriptions.id', 'subscription_children.parent_id'), + 'pending_payment' => InvoicePosition::selectRaw('SUM(price)') + ->whereColumn('invoice_positions.member_id', 'members.id') + ->whereHas('invoice', fn ($query) => $query->whereNeedsPayment()) ]); } @@ -352,8 +333,8 @@ class Member extends Model implements Geolocatable */ public function scopeWhereAusstand(Builder $query): Builder { - return $query->whereHas('payments', function ($q) { - return $q->whereHas('status', fn ($q) => $q->where('is_remember', true)); + return $query->whereHas('invoicePositions', function ($q) { + return $q->whereHas('invoice', fn ($query) => $query->whereNeedsPayment()); }); } diff --git a/app/Member/MemberResource.php b/app/Member/MemberResource.php index 40d92729..3ebd946b 100644 --- a/app/Member/MemberResource.php +++ b/app/Member/MemberResource.php @@ -8,14 +8,13 @@ use App\Course\Models\Course; use App\Course\Resources\CourseMemberResource; use App\Gender; use App\Invoice\BillKind; +use App\Invoice\Resources\InvoicePositionResource; use App\Lib\HasMeta; use App\Member\Data\NestedGroup; use App\Member\Resources\NationalityResource; use App\Member\Resources\RegionResource; use App\Membership\MembershipResource; use App\Nationality; -use App\Payment\PaymentResource; -use App\Payment\Status; use App\Payment\Subscription; use App\Payment\SubscriptionResource; use App\Region; @@ -72,11 +71,11 @@ class MemberResource extends JsonResource 'bill_kind_name' => optional($this->bill_kind)->value, 'has_nami' => null !== $this->nami_id, 'children_phone' => $this->children_phone, - 'payments' => PaymentResource::collection($this->whenLoaded('payments')), 'pending_payment' => $this->pending_payment ? number_format($this->pending_payment / 100, 2, ',', '.') . ' €' : null, 'age_group_icon' => $this->ageGroupMemberships->first()?->subactivity->slug, 'courses' => CourseMemberResource::collection($this->whenLoaded('courses')), 'memberships' => MembershipResource::collection($this->whenLoaded('memberships')), + 'invoicePositions' => InvoicePositionResource::collection($this->whenLoaded('invoicePositions')), 'nationality' => new NationalityResource($this->whenLoaded('nationality')), 'region' => new RegionResource($this->whenLoaded('region')), 'full_address' => $this->fullAddress, @@ -157,7 +156,6 @@ class MemberResource extends JsonResource 'links' => [ 'index' => route('member.index'), 'create' => route('member.create'), - 'sendpayment' => route('sendpayment.create'), ], ]; } diff --git a/app/Payment/ActionFactory.php b/app/Payment/ActionFactory.php deleted file mode 100644 index cd7716d5..00000000 --- a/app/Payment/ActionFactory.php +++ /dev/null @@ -1,25 +0,0 @@ - - */ - public function allLinks(): Collection - { - return app(DocumentFactory::class)->getTypes()->map(function ($repo) { - return [ - 'link' => [ - 'href' => route('sendpayment.pdf', ['type' => $repo]), - 'label' => $repo::sendAllLabel(), - ], - 'text' => $repo::getDescription(), - ]; - }); - } -} diff --git a/app/Payment/Payment.php b/app/Payment/Payment.php deleted file mode 100644 index 6b624291..00000000 --- a/app/Payment/Payment.php +++ /dev/null @@ -1,87 +0,0 @@ - - */ - public $fillable = ['member_id', 'invoice_data', 'subscription_id', 'nr', 'status_id', 'last_remembered_at']; - - /** - * @var array - */ - public $casts = [ - 'invoice_data' => 'json', - 'last_remembered_at' => 'date', - ]; - - /** - * @return BelongsTo - */ - public function member(): BelongsTo - { - return $this->belongsTo(Member::class); - } - - /** - * @return BelongsTo - */ - public function subscription(): BelongsTo - { - return $this->belongsTo(Subscription::class); - } - - /** - * @return BelongsTo - */ - public function status(): BelongsTo - { - return $this->belongsTo(Status::class); - } - - /** - * @param Builder $query - * - * @return Builder - */ - public function scopeWhereNeedsPayment(Builder $query): Builder - { - return $query->whereHas('status', function ($q) { - return $q->needsPayment(); - }); - } - - /** - * @param Builder $query - * - * @return Builder - */ - public function scopeWhereNeedsBill(Builder $query): Builder - { - return $query->whereHas('status', function ($q) { - return $q->where('is_bill', true); - }); - } - - /** - * @param Builder $query - * - * @return Builder - */ - public function scopeWhereNeedsRemember(Builder $query): Builder - { - return $query->whereHas('status', function ($q) { - return $q->where('is_remember', true); - })->where(fn ($query) => $query->whereNull('last_remembered_at')->orWhere('last_remembered_at', '<=', now()->subMonths(3))); - } -} diff --git a/app/Payment/PaymentResource.php b/app/Payment/PaymentResource.php deleted file mode 100644 index 26fe939a..00000000 --- a/app/Payment/PaymentResource.php +++ /dev/null @@ -1,56 +0,0 @@ - $this->subscription_id, - 'subscription' => new SubscriptionResource($this->whenLoaded('subscription')), - 'status_name' => $this->status->name, - 'status_id' => $this->status->id, - 'nr' => $this->nr, - 'id' => $this->id, - 'is_accepted' => $this->status->isAccepted(), - 'links' => [ - 'show' => $this->invoice_data - ? route('payment.pdf', ['payment' => $this->getModel()]) - : null, - ] - ]; - } - - /** - * @return array - */ - public static function memberMeta(Member $member): array - { - return [ - 'statuses' => Status::forSelect(), - 'subscriptions' => Subscription::forSelect(), - 'default' => [ - 'nr' => '', - 'subscription_id' => null, - 'status_id' => null - ], - 'links' => [ - 'store' => route('member.payment.store', ['member' => $member]), - ] - ]; - } -} diff --git a/app/Payment/SendpaymentController.php b/app/Payment/SendpaymentController.php deleted file mode 100644 index 5893d217..00000000 --- a/app/Payment/SendpaymentController.php +++ /dev/null @@ -1,49 +0,0 @@ -put('menu', 'member'); - session()->put('title', 'Rechnungen versenden'); - - return Inertia::render('sendpayment/VForm', [ - 'types' => app(ActionFactory::class)->allLinks(), - ]); - } - - /** - * @return Response|Responsable - */ - public function send(Request $request) - { - $memberCollection = (new BillKindQuery(BillKind::POST))->type($request->type)->getMembers(); - - if ($memberCollection->isEmpty()) { - return response()->noContent(); - } - - $documents = $memberCollection->map(function ($members) use ($request) { - $document = $request->type::fromMembers($members); - app(DocumentFactory::class)->afterSingle($document, $members); - return $document; - }); - - - return Tex::merge($documents->all()); - } -} diff --git a/app/Payment/Status.php b/app/Payment/Status.php deleted file mode 100644 index 8be3c960..00000000 --- a/app/Payment/Status.php +++ /dev/null @@ -1,52 +0,0 @@ - 'boolean', - 'is_remember' => 'boolean', - ]; - - public static function default(): int - { - return static::where('is_bill', true)->where('is_remember', true)->first()->id; - } - - public function isAccepted(): bool - { - return false === $this->is_bill && false === $this->is_remember; - } - - // ---------------------------------- Scopes ----------------------------------- - /** - * @param Builder $query - * @return Builder - */ - public function scopeNeedsPayment(Builder $query): Builder - { - return $query->where(function (Builder $query): Builder { - return $query->where('is_bill', true)->orWhere('is_remember', true); - }); - } - - /** - * @return array - */ - public static function forSelect(): array - { - return static::select('name', 'id')->get()->toArray(); - } -} diff --git a/database/factories/Member/MemberFactory.php b/database/factories/Member/MemberFactory.php index 185b9ba5..490f0019 100644 --- a/database/factories/Member/MemberFactory.php +++ b/database/factories/Member/MemberFactory.php @@ -8,10 +8,8 @@ use App\Group; use App\Invoice\BillKind; use App\Member\Member; use App\Nationality; -use App\Payment\Payment; use App\Payment\Subscription; use Illuminate\Database\Eloquent\Factories\Factory; -use Illuminate\Database\Eloquent\Model; /** * @extends Factory @@ -82,20 +80,6 @@ class MemberFactory extends Factory return $this->state(['nami_id' => $namiId]); } - /** - * @param array $payments - */ - public function withPayments(array $payments): self - { - return $this->afterCreating(function (Model $model) use ($payments): void { - foreach ($payments as $paymentClosure) { - $factory = Payment::factory()->for($model); - $factory = call_user_func($paymentClosure, $factory); - $factory->create(); - } - }); - } - public function sameFamilyAs(Member $member): self { return $this->state([ diff --git a/database/factories/Payment/PaymentFactory.php b/database/factories/Payment/PaymentFactory.php deleted file mode 100644 index 29f9d282..00000000 --- a/database/factories/Payment/PaymentFactory.php +++ /dev/null @@ -1,60 +0,0 @@ - - */ -class PaymentFactory extends Factory -{ - protected $model = Payment::class; - - public function definition(): array - { - return [ - 'nr' => $this->faker->year, - 'subscription_id' => Subscription::factory()->create()->id, - 'status_id' => Status::factory()->create()->id, - 'last_remembered_at' => now(), - ]; - } - - public function notPaid(): self - { - return $this->for(Status::whereName('Nicht bezahlt')->first()); - } - - public function pending(): self - { - return $this->for(Status::whereName('Rechnung gestellt')->first())->state(['last_remembered_at' => now()->subYears(2)]);; - } - - public function paid(): self - { - return $this->for(Status::whereName('Rechnung beglichen')->first()); - } - - public function nr(string $nr): self - { - return $this->state(['nr' => $nr]); - } - - /** - * @param array $children - * @param array $state - */ - public function subscription(string $name, array $children, array $state = []): self - { - return $this->for( - Subscription::factory()->state(['name' => $name])->for(Fee::factory())->children($children)->state($state) - ); - } -} diff --git a/database/factories/Payment/StatusFactory.php b/database/factories/Payment/StatusFactory.php deleted file mode 100644 index 19ee114f..00000000 --- a/database/factories/Payment/StatusFactory.php +++ /dev/null @@ -1,28 +0,0 @@ - - */ -class StatusFactory extends Factory -{ - public $model = Status::class; - - /** - * Define the model's default state. - * - * @return array - */ - public function definition() - { - return [ - 'name' => $this->faker->sentence, - 'is_bill' => $this->faker->boolean, - 'is_remember' => $this->faker->boolean, - ]; - } -} diff --git a/database/migrations/2021_07_04_101300_create_payments_table.php b/database/migrations/2021_07_04_101300_create_payments_table.php index 6d03f558..eb60ebff 100644 --- a/database/migrations/2021_07_04_101300_create_payments_table.php +++ b/database/migrations/2021_07_04_101300_create_payments_table.php @@ -21,10 +21,6 @@ class CreatePaymentsTable extends Migration $table->boolean('is_remember'); }); - Status::create(['name' => 'Nicht bezahlt', 'is_bill' => true, 'is_remember' => true]); - Status::create(['name' => 'Rechnung gestellt', 'is_bill' => false, 'is_remember' => true]); - Status::create(['name' => 'Rechnung beglichen', 'is_bill' => false, 'is_remember' => false]); - Schema::create('payments', function (Blueprint $table) { $table->id(); $table->string('nr'); diff --git a/docker-compose.yml b/docker-compose.yml index b9bf4f1f..1d027775 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -92,6 +92,8 @@ services: socketi: image: quay.io/soketi/soketi:89604f268623cf799573178a7ba56b7491416bde-16-debian + ports: + - "6001:6001" environment: SOKETI_DEFAULT_APP_ID: adremaid SOKETI_DEFAULT_APP_KEY: adremakey diff --git a/packages/tex b/packages/tex index bbab104f..b4dbd7d3 160000 --- a/packages/tex +++ b/packages/tex @@ -1 +1 @@ -Subproject commit bbab104f7e00c059ffffa115f6b769e2333e137a +Subproject commit b4dbd7d3125aca2c16ca9f99ec81c12a46a18e3b diff --git a/phpstan.neon b/phpstan.neon index 182df5da..abe94e9d 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -116,11 +116,6 @@ parameters: count: 1 path: app/Member/Member.php - - - message: "#^Method App\\\\Member\\\\Member\\:\\:createPayment\\(\\) has parameter \\$attributes with no value type specified in iterable type array\\.$#" - count: 1 - path: app/Member/Member.php - - message: "#^Unsafe usage of new static\\(\\)\\.$#" count: 1 @@ -136,11 +131,6 @@ parameters: count: 1 path: app/Membership/MembershipResource.php - - - message: "#^Method App\\\\Payment\\\\PaymentResource\\:\\:toArray\\(\\) return type has no value type specified in iterable type array\\.$#" - count: 1 - path: app/Payment/PaymentResource.php - - message: "#^Method App\\\\Payment\\\\SubscriptionResource\\:\\:toArray\\(\\) return type has no value type specified in iterable type array\\.$#" count: 1 @@ -181,11 +171,6 @@ parameters: count: 1 path: database/factories/NationalityFactory.php - - - message: "#^Method Database\\\\Factories\\\\Payment\\\\StatusFactory\\:\\:definition\\(\\) return type has no value type specified in iterable type array\\.$#" - count: 1 - path: database/factories/Payment/StatusFactory.php - - message: "#^Call to an undefined method Phake\\\\Proxies\\\\StubberProxy.*#" diff --git a/resources/js/views/invoice/Index.vue b/resources/js/views/invoice/Index.vue index 91369350..ca28129c 100644 --- a/resources/js/views/invoice/Index.vue +++ b/resources/js/views/invoice/Index.vue @@ -4,7 +4,10 @@ Rechnung anlegen Massenrechnung anlegen + Post-Briefe + abrufen +
diff --git a/resources/js/views/member/VIndex.vue b/resources/js/views/member/VIndex.vue index 3835f67d..acf86dd5 100644 --- a/resources/js/views/member/VIndex.vue +++ b/resources/js/views/member/VIndex.vue @@ -5,8 +5,6 @@ anlegen Rechnungen erstellen - Rechnungen versenden
diff --git a/routes/web.php b/routes/web.php index 36e65c88..5ca36864 100644 --- a/routes/web.php +++ b/routes/web.php @@ -30,6 +30,7 @@ use App\Invoice\Actions\DisplayRememberpdfAction; use App\Invoice\Actions\InvoiceDestroyAction; use App\Invoice\Actions\InvoiceIndexAction; use App\Invoice\Actions\InvoiceUpdateAction; +use App\Invoice\Actions\MassPostPdfAction; use App\Invoice\Actions\MassStoreAction; use App\Invoice\Actions\PaymentPositionIndexAction; use App\Maildispatcher\Actions\CreateAction; @@ -52,7 +53,6 @@ use App\Membership\Actions\MembershipDestroyAction; use App\Membership\Actions\MembershipStoreAction; use App\Membership\Actions\MembershipUpdateAction; use App\Membership\Actions\StoreForGroupAction; -use App\Payment\SendpaymentController; use App\Payment\SubscriptionController; Route::group(['namespace' => 'App\\Http\\Controllers'], function (): void { @@ -70,8 +70,6 @@ Route::group(['middleware' => 'auth:web'], function (): void { Route::delete('/member/{member}', MemberDeleteAction::class); Route::get('/member/{member}', MemberShowAction::class)->name('member.show'); Route::resource('subscription', SubscriptionController::class); - Route::get('/sendpayment', [SendpaymentController::class, 'create'])->name('sendpayment.create'); - Route::get('/sendpayment/pdf', [SendpaymentController::class, 'send'])->name('sendpayment.pdf'); Route::get('/member/{member}/efz', ShowEfzDocumentAction::class)->name('efz'); Route::get('/member/{member}/resync', MemberResyncAction::class)->name('member.resync'); Route::get('member-export', ExportAction::class)->name('member-export'); @@ -114,6 +112,7 @@ Route::group(['middleware' => 'auth:web'], function (): void { Route::delete('/invoice/{invoice}', InvoiceDestroyAction::class)->name('invoice.destroy'); Route::get('/invoice/{invoice}/pdf', DisplayPdfAction::class)->name('invoice.pdf'); Route::get('/invoice/{invoice}/rememberpdf', DisplayRememberpdfAction::class)->name('invoice.rememberpdf'); + Route::get('/invoice/masspdf', MassPostPdfAction::class)->name('invoice.masspdf'); // ----------------------------- invoice-position ------------------------------ diff --git a/tests/EndToEnd/MemberIndexTest.php b/tests/EndToEnd/MemberIndexTest.php index 24953fc7..ec680e49 100644 --- a/tests/EndToEnd/MemberIndexTest.php +++ b/tests/EndToEnd/MemberIndexTest.php @@ -3,6 +3,8 @@ namespace Tests\EndToEnd; use App\Group; +use App\Invoice\Models\Invoice; +use App\Invoice\Models\InvoicePosition; use App\Member\Member; use App\Payment\Payment; use Illuminate\Foundation\Testing\DatabaseMigrations; @@ -57,9 +59,7 @@ class MemberIndexTest extends TestCase $this->withoutExceptionHandling()->login()->loginNami(); $group = Group::factory()->create(); Member::factory()->defaults()->for($group) - ->has(Payment::factory()->notPaid()->subscription('tollerbeitrag', [ - new Child('a', 5400), - ])) + ->has(InvoicePosition::factory()->for(Invoice::factory())) ->create(['firstname' => '::firstname::']); Member::factory()->defaults()->for($group)->create(['firstname' => '::firstname::']); diff --git a/tests/Feature/Invoice/BillRememberDocumentTest.php b/tests/Feature/Invoice/BillRememberDocumentTest.php deleted file mode 100644 index 8a605c09..00000000 --- a/tests/Feature/Invoice/BillRememberDocumentTest.php +++ /dev/null @@ -1,44 +0,0 @@ -defaults() - ->postBillKind() - ->state(['firstname' => 'Max1', 'lastname' => '::lastname::', 'address' => '::address::', 'zip' => '12345', 'location' => '::location::']) - ->has(Payment::factory()->notPaid()->nr('nr1')) - ->create(); - Member::factory() - ->defaults() - ->postBillKind() - ->state(['firstname' => 'Max2', 'lastname' => '::lastname::', 'address' => '::address::', 'zip' => '12345', 'location' => '::location::']) - ->has(Payment::factory()->notPaid()->nr('nr2')) - ->create(); - - $this->assertCount(2, $this->query(BillDocument::class)->getMembers()->first()); - } - - /** - * @param class-string $type - */ - private function query(string $type): InvoiceMemberQuery - { - return (new BillKindQuery(BillKind::POST))->type($type); - } -} diff --git a/tests/Feature/Invoice/InvoiceIndexActionTest.php b/tests/Feature/Invoice/InvoiceIndexActionTest.php index 801aea90..9248b124 100644 --- a/tests/Feature/Invoice/InvoiceIndexActionTest.php +++ b/tests/Feature/Invoice/InvoiceIndexActionTest.php @@ -48,6 +48,7 @@ class InvoiceIndexActionTest extends TestCase ->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.links.masspdf', route('invoice.masspdf')) ->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']) diff --git a/tests/Feature/Invoice/InvoiceSendActionTest.php b/tests/Feature/Invoice/InvoiceSendActionTest.php index cd4e8e9f..807d021a 100644 --- a/tests/Feature/Invoice/InvoiceSendActionTest.php +++ b/tests/Feature/Invoice/InvoiceSendActionTest.php @@ -52,7 +52,7 @@ class InvoiceSendActionTest extends TestCase ->has(InvoicePosition::factory()->withMember(), 'positions') ->via(BillKind::EMAIL) ->status(InvoiceStatus::SENT) - ->create(['sent_at' => now()->subMonths(6), 'mail_email' => 'max@muster.de']); + ->create(['sent_at' => now()->subMonths(6), 'mail_email' => 'max@muster.de', 'last_remembered_at' => now()->subMonths(6)]); InvoiceSendAction::run(); diff --git a/tests/Feature/Invoice/MassPostPdfActionTest.php b/tests/Feature/Invoice/MassPostPdfActionTest.php new file mode 100644 index 00000000..dde791cb --- /dev/null +++ b/tests/Feature/Invoice/MassPostPdfActionTest.php @@ -0,0 +1,61 @@ +withoutExceptionHandling()->login()->loginNami(); + + $this->get(route('invoice.masspdf'))->assertStatus(204); + } + + public function testItDoesntDisplayPdfWhenAllInvoicesPaid(): void + { + $this->withoutExceptionHandling()->login()->loginNami(); + + Invoice::factory()->has(InvoicePosition::factory()->withMember(), 'positions')->via(BillKind::POST)->status(InvoiceStatus::PAID)->create(); + + $this->get(route('invoice.masspdf'))->assertStatus(204); + } + + public function testItDoesntDisplayEmailBills(): void + { + $this->withoutExceptionHandling()->login()->loginNami(); + + Invoice::factory()->has(InvoicePosition::factory()->withMember(), 'positions')->via(BillKind::EMAIL)->create(); + + $this->get(route('invoice.masspdf'))->assertStatus(204); + } + + public function testItMergesRememberAndBill(): void + { + $this->withoutExceptionHandling()->login()->loginNami(); + $invoice1 = Invoice::factory()->has(InvoicePosition::factory()->withMember(), 'positions')->status(InvoiceStatus::NEW) + ->via(BillKind::POST) + ->create(); + $invoice2 = Invoice::factory()->has(InvoicePosition::factory()->withMember(), 'positions')->status(InvoiceStatus::SENT) + ->via(BillKind::POST) + ->create(['sent_at' => now()->subMonths(10), 'last_remembered_at' => now()->subMonths(4)]); + + $this->get(route('invoice.masspdf'))->assertPdfPageCount(2); + + $this->assertEquals(InvoiceStatus::SENT, $invoice1->fresh()->status); + $this->assertEquals(now()->format('Y-m-d'), $invoice1->fresh()->last_remembered_at->format('Y-m-d')); + $this->assertEquals(now()->format('Y-m-d'), $invoice1->fresh()->sent_at->format('Y-m-d')); + + $this->assertEquals(InvoiceStatus::SENT, $invoice2->fresh()->status); + $this->assertEquals(now()->format('Y-m-d'), $invoice2->fresh()->last_remembered_at->format('Y-m-d')); + $this->assertEquals(now()->subMonths(10)->format('Y-m-d'), $invoice2->fresh()->sent_at->format('Y-m-d')); + } +} diff --git a/tests/Feature/Member/IndexTest.php b/tests/Feature/Member/IndexTest.php index fe0fecba..c3b9354f 100644 --- a/tests/Feature/Member/IndexTest.php +++ b/tests/Feature/Member/IndexTest.php @@ -4,6 +4,9 @@ namespace Tests\Feature\Member; use App\Activity; use App\Group; +use App\Invoice\Enums\InvoiceStatus; +use App\Invoice\Models\Invoice; +use App\Invoice\Models\InvoicePosition; use App\Member\Member; use App\Member\Membership; use App\Payment\Payment; @@ -175,7 +178,7 @@ class IndexTest extends TestCase { $this->withoutExceptionHandling()->login()->loginNami(); Member::factory() - ->has(Payment::factory()->notPaid()->subscription('Free', [new Child('b', 50)])) + ->has(InvoicePosition::factory()->for(Invoice::factory()->status(InvoiceStatus::NEW))) ->defaults()->create(); Member::factory()->defaults()->create(); Member::factory()->defaults()->create(); diff --git a/tests/Feature/Member/ShowTest.php b/tests/Feature/Member/ShowTest.php index 72f09672..065fe716 100644 --- a/tests/Feature/Member/ShowTest.php +++ b/tests/Feature/Member/ShowTest.php @@ -7,6 +7,8 @@ use App\Course\Models\CourseMember; use App\Fee; use App\Gender; use App\Group; +use App\Invoice\Models\Invoice; +use App\Invoice\Models\InvoicePosition; use App\Member\Member; use App\Member\Membership; use App\Nationality; @@ -32,10 +34,7 @@ class ShowTest extends TestCase ->defaults() ->for(Group::factory()->name('Stamm Beispiel')) ->has(Membership::factory()->promise(now())->in('€ LeiterIn', 5, 'Jungpfadfinder', 88)->from('2022-11-19')) - ->has(Payment::factory()->notPaid()->nr('2019')->subscription('Free', [ - new Child('uu', 1000), - new Child('a', 50), - ])) + ->has(InvoicePosition::factory()->for(Invoice::factory())->price(1050)->description('uu')) ->for(Gender::factory()->name('Männlich')) ->for(Region::factory()->name('NRW')) ->postBillKind() @@ -130,15 +129,12 @@ class ShowTest extends TestCase ], ], $response, 'data.courses.0'); $this->assertInertiaHas([ - 'subscription' => [ - 'name' => 'Free', - 'id' => $member->payments->first()->subscription->id, - 'amount' => 1050, - 'amount_human' => '10,50 €', - ], - 'status_name' => 'Nicht bezahlt', - 'nr' => '2019', - ], $response, 'data.payments.0'); + 'description' => 'uu', + 'price_human' => '10,50 €', + 'invoice' => [ + 'status' => 'Neu', + ] + ], $response, 'data.invoicePositions.0'); } public function testItShowsMinimalSingleMember(): void diff --git a/tests/Feature/Sendpayment/SendpaymentTest.php b/tests/Feature/Sendpayment/SendpaymentTest.php deleted file mode 100644 index 2628c926..00000000 --- a/tests/Feature/Sendpayment/SendpaymentTest.php +++ /dev/null @@ -1,129 +0,0 @@ -withoutExceptionHandling(); - $this->login()->loginNami(); - - $response = $this->get(route('sendpayment.create')); - - $response->assertOk(); - $this->assertInertiaHas('Rechnungen versenden', $response, 'types.0.link.label'); - $href = $this->inertia($response, 'types.0.link.href'); - $this->assertStringContainsString('BillDocument', $href); - } - - public function testItDownloadsPdfOfAllMembersForBill(): void - { - InvoiceSettings::fake(InvoiceSettingsFake::new()->create()); - $this->withoutExceptionHandling()->login()->loginNami(); - Member::factory()->defaults()->postBillKind()->count(3) - ->has(Payment::factory()->notPaid()->subscription('tollerbeitrag', [new Child('a', 5400)])) - ->create(); - - $response = $this->call('GET', route('sendpayment.pdf'), ['type' => 'App\\Invoice\\BillDocument']); - $response->assertOk(); - $this->assertPdfPageCount(3, $response->getFile()); - } - - public function testItDownloadsPdfOfAllMembersForRemember(): void - { - InvoiceSettings::fake(InvoiceSettingsFake::new()->create()); - $this->withoutExceptionHandling()->login()->loginNami(); - Member::factory()->defaults()->postBillKind()->count(3) - ->has(Payment::factory()->pending()->subscription('tollerbeitrag', [new Child('a', 5400)])) - ->create(); - - $response = $this->call('GET', route('sendpayment.pdf'), ['type' => 'App\\Invoice\\RememberDocument']); - $response->assertOk(); - $this->assertPdfPageCount(3, $response->getFile()); - } - - public function testItCanCreatePdfPayments(): void - { - InvoiceSettings::fake(InvoiceSettingsFake::new()->create()); - Tex::spy(); - $this->withoutExceptionHandling()->login()->loginNami(); - $members = Member::factory() - ->defaults() - ->has(Payment::factory()->notPaid()->nr('1997')->subscription('tollerbeitrag', [new Child('a', 5400)])) - ->has(Payment::factory()->paid()->nr('1998')->subscription('bezahltdesc', [new Child('b', 5800)])) - ->postBillKind() - ->count(3) - ->create(); - $member = $members->first(); - - $this->call('GET', route('sendpayment.pdf'), ['type' => 'App\\Invoice\\BillDocument']); - $this->assertEquals(Status::firstWhere('name', 'Rechnung gestellt')->id, $member->payments->firstWhere('nr', '1997')->status_id); - $this->assertEquals(Status::firstWhere('name', 'Rechnung beglichen')->id, $member->payments->firstWhere('nr', '1998')->status_id); - Tex::assertCompiled( - BillDocument::class, - fn ($document) => $document->hasAllContent(['1997', 'tollerbeitrag', '54.00']) - && $document->missesAllContent(['1998', 'bezahltdesc', '58.00']) - ); - - $member->payments->firstWhere('nr', '1997')->update(['status_id' => Status::firstWhere('name', 'Nicht bezahlt')->id]); - $invoice = BillDocument::fromMembers((new BillKindQuery(BillKind::POST))->type(BillDocument::class)->getMembers()->first()); - $this->assertEquals( - BillDocument::from($member->payments->firstWhere('nr', '1997')->invoice_data)->renderBody(), - $invoice->renderBody() - ); - } - - public function testItCanCreatePdfPaymentsForRemember(): void - { - InvoiceSettings::fake(InvoiceSettingsFake::new()->create()); - Tex::spy(); - $this->withoutExceptionHandling()->login()->loginNami(); - $member = Member::factory() - ->defaults() - ->has(Payment::factory()->pending()->nr('1997')->subscription('tollerbeitrag', [new Child('a', 5400)])) - ->postBillKind() - ->create(); - - $this->call('GET', route('sendpayment.pdf'), ['type' => 'App\\Invoice\\RememberDocument']); - Tex::assertCompiled( - RememberDocument::class, - fn ($document) => $document->hasAllContent(['1997', 'tollerbeitrag', '54.00']) - ); - $this->assertNull($member->payments()->first()->invoice_data); - $this->assertEquals(now()->format('Y-m-d'), $member->payments->first()->last_remembered_at->format('Y-m-d')); - } - - public function testItDoesntCreatePdfsWhenUserHasEmail(): void - { - Tex::spy(); - $this->withoutExceptionHandling(); - $this->login()->loginNami(); - Member::factory() - ->defaults() - ->has(Payment::factory()->notPaid()->nr('1997')->subscription('tollerbeitrag', [new Child('u', 5400)])) - ->emailBillKind() - ->create(); - - $response = $this->call('GET', route('sendpayment.pdf'), ['type' => 'App\\Invoice\\BillDocument']); - - $response->assertStatus(204); - Tex::assertNotCompiled(BillDocument::class); - } -}