Fix Send invoices

This commit is contained in:
Philipp Lang 2023-12-18 01:15:16 +01:00
parent 68a654494d
commit be29a284d5
17 changed files with 114 additions and 155 deletions

View File

@ -17,6 +17,8 @@ trait HasValidation
'status' => ['required', 'string', 'max:255', Rule::in(InvoiceStatus::values())],
'via' => ['required', 'string', 'max:255', Rule::in(BillKind::values())],
'usage' => 'required|max:255|string',
'mail_name' => 'nullable|string|max:255',
'mail_email' => 'nullable|string|max:255|email',
'to' => 'array',
'to.address' => 'required|string|max:255',
'to.location' => 'required|string|max:255',

View File

@ -2,11 +2,9 @@
namespace App\Invoice\Actions;
use App\Invoice\BillKind;
use App\Invoice\DocumentFactory;
use App\Invoice\Queries\BillKindQuery;
use App\Payment\Payment;
use App\Payment\PaymentMail;
use App\Invoice\BillDocument;
use App\Invoice\Mails\BillMail;
use App\Invoice\Models\Invoice;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Storage;
use Lorisleiva\Actions\Concerns\AsAction;
@ -33,15 +31,11 @@ class InvoiceSendAction
*/
public function handle(): int
{
foreach (app(DocumentFactory::class)->getTypes() as $type) {
$memberCollection = (new BillKindQuery(BillKind::EMAIL))->type($type)->getMembers();
foreach ($memberCollection as $members) {
$invoice = $type::fromMembers($members);
$invoicePath = Storage::disk('temp')->path(Tex::compile($invoice)->storeIn('', 'temp'));
Mail::to($invoice->getRecipient())->send(new PaymentMail($invoice, $invoicePath));
app(DocumentFactory::class)->afterSingle($invoice, $members);
}
foreach (Invoice::whereNeedsBill()->get() as $invoice) {
$document = BillDocument::fromInvoice($invoice);
$path = Storage::disk('temp')->path(Tex::compile($document)->storeIn('', 'temp'));
Mail::to($invoice->getMailRecipient())->send(new BillMail($invoice, $path));
$invoice->sent($document);
}
return 0;

View File

@ -35,11 +35,6 @@ class BillDocument extends InvoiceDocument
]);
}
public function getMailSubject(): string
{
return 'Jahresrechnung';
}
/**
* @param HasMany<Payment> $query
*

View File

@ -1,37 +0,0 @@
<?php
namespace App\Invoice;
use App\Member\Member;
use Illuminate\Support\Collection;
class DocumentFactory
{
/**
* @var array<int, class-string<Invoice>>
*/
private array $types = [
BillDocument::class,
RememberDocument::class,
];
/**
* @return Collection<int, class-string<Invoice>>
*/
public function getTypes(): Collection
{
return collect($this->types);
}
/**
* @param Collection<(int|string), Member> $members
*/
public function afterSingle(Invoice $invoice, Collection $members): void
{
foreach ($members as $member) {
foreach ($member->payments as $payment) {
$invoice->afterSingle($payment);
}
}
}
}

View File

@ -4,7 +4,6 @@ namespace App\Invoice;
use App\Invoice\Models\Invoice;
use App\Payment\Payment;
use Exception;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Str;
use Zoomyboy\Tex\Document;
@ -90,25 +89,6 @@ abstract class InvoiceDocument extends Document
return $this;
}
public function getRecipient(): MailRecipient
{
throw_unless($this->email, Exception::class, 'Cannot get Recipient. Mail not set.');
return new MailRecipient($this->email, $this->familyName);
}
/**
* @return view-string
*/
public function mailView(): string
{
$view = 'mail.payment.' . Str::snake(class_basename($this));
throw_unless(view()->exists($view), Exception::class, 'Mail view ' . $view . ' existiert nicht.');
return $view;
}
/**
* @return array<string, string>
*/

View File

@ -0,0 +1,36 @@
<?php
namespace App\Invoice\Mails;
use App\Invoice\Models\Invoice;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class BillMail extends Mailable
{
use Queueable;
use SerializesModels;
/**
* Create a new message instance.
*
* @return void
*/
public function __construct(public Invoice $invoice, public string $filename)
{
}
/**
* Build the message.
*
* @return $this
*/
public function build()
{
return $this->markdown('mail.invoice.bill')
->attach($this->filename)
->replyTo('kasse@stamm-silva.de')
->subject('Rechnung | DPSG Stamm Silva');
}
}

View File

@ -2,13 +2,16 @@
namespace App\Invoice\Models;
use App\Invoice\BillDocument;
use App\Invoice\BillKind;
use App\Invoice\Enums\InvoiceStatus;
use App\Invoice\InvoiceDocument;
use App\Member\Member;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use stdClass;
class Invoice extends Model
{
@ -48,6 +51,8 @@ class Invoice extends Model
'status' => InvoiceStatus::NEW,
'via' => $member->bill_kind,
'usage' => 'Mitgliedsbeitrag für ' . $member->lastname,
'mail_email' => $member->email,
'mail_name' => 'Familie ' . $member->lastname,
]);
}
@ -67,4 +72,32 @@ class Invoice extends Model
{
return $query->whereIn('status', [InvoiceStatus::NEW->value, InvoiceStatus::SENT->value]);
}
/**
* @param Builder<self> $query
*
* @return Builder<self>
*/
public function scopeWhereNeedsBill(Builder $query): Builder
{
return $query->where('status', InvoiceStatus::NEW);
}
public function getMailRecipient(): stdClass
{
return (object) [
'email' => $this->mail_email,
'name' => $this->mail_name,
];
}
public function sent(InvoiceDocument $document): void
{
if (is_a($document, BillDocument::class)) {
$this->update([
'sent_at' => now(),
'status' => InvoiceStatus::SENT,
]);
}
}
}

View File

@ -2,7 +2,7 @@
namespace App\Invoice\Queries;
use App\Invoice\Invoice;
use App\Invoice\InvoiceDocument;
use App\Member\Member;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
@ -12,7 +12,7 @@ use Illuminate\Support\Str;
abstract class InvoiceMemberQuery
{
/**
* @param class-string<Invoice> $type
* @param class-string<InvoiceDocument> $type
*/
public string $type;
@ -34,7 +34,7 @@ abstract class InvoiceMemberQuery
}
/**
* @param class-string<Invoice> $type
* @param class-string<InvoiceDocument> $type
*/
public function type(string $type): self
{

View File

@ -32,11 +32,6 @@ class RememberDocument extends InvoiceDocument
$payment->update(['last_remembered_at' => now()]);
}
public function getMailSubject(): string
{
return 'Zahlungserinnerung';
}
/**
* @param HasMany<Payment> $query
*

View File

@ -35,6 +35,8 @@ class InvoiceResource extends JsonResource
'positions' => InvoicePositionResource::collection($this->whenLoaded('positions')),
'greeting' => $this->greeting,
'usage' => $this->usage,
'mail_name' => $this->mail_name,
'mail_email' => $this->mail_email,
'links' => [
'pdf' => route('invoice.pdf', ['invoice' => $this->getModel()]),
'rememberpdf' => route('invoice.rememberpdf', ['invoice' => $this->getModel()]),
@ -69,6 +71,8 @@ class InvoiceResource extends JsonResource
'status' => InvoiceStatus::NEW->value,
'via' => null,
'usage' => '',
'mail_name' => '',
'mail_email' => '',
],
'default_position' => [
'id' => null,

View File

@ -1,43 +0,0 @@
<?php
namespace App\Payment;
use App\Invoice\Invoice;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class PaymentMail extends Mailable
{
use Queueable;
use SerializesModels;
public Invoice $invoice;
public string $filename;
public string $salutation;
/**
* Create a new message instance.
*
* @return void
*/
public function __construct(Invoice $invoice, string $filename)
{
$this->invoice = $invoice;
$this->filename = $filename;
$this->salutation = 'Liebe Familie ' . $invoice->familyName;
}
/**
* Build the message.
*
* @return $this
*/
public function build()
{
return $this->markdown($this->invoice->mailView())
->attach($this->filename)
->replyTo('kasse@stamm-silva.de')
->subject($this->invoice->getSubject() . ' | DPSG Stamm Silva');
}
}

View File

@ -21,6 +21,8 @@ return new class extends Migration
$table->date('sent_at')->nullable();
$table->string('via');
$table->string('usage');
$table->string('mail_name')->nullable();
$table->string('mail_email')->nullable();
$table->timestamps();
});

View File

@ -492,11 +492,6 @@ parameters:
count: 1
path: app/Contribution/ContributionFactory.php
-
message: "#^Return type of call to method Illuminate\\\\Support\\\\Collection\\<int,class\\-string\\<App\\\\Invoice\\\\Invoice\\>\\>\\:\\:map\\(\\) contains unresolvable type\\.$#"
count: 1
path: app/Payment/ActionFactory.php
-
message: "#^Return type of call to method Illuminate\\\\Support\\\\Collection\\<int,class\\-string\\<App\\\\Setting\\\\LocalSettings\\>\\>\\:\\:map\\(\\) contains unresolvable type\\.$#"
count: 1

View File

@ -26,7 +26,7 @@ class InvoiceIndexActionTest extends TestCase
->sentAt(now()->subDay())
->via(BillKind::POST)
->status(InvoiceStatus::SENT)
->create(['usage' => 'Usa']);
->create(['usage' => 'Usa', 'mail_name' => 'lala', 'mail_email' => 'a@b.de']);
$this->get(route('invoice.index'))
->assertInertiaPath('data.data.0.to.name', 'Familie Blabla')
@ -35,6 +35,8 @@ class InvoiceIndexActionTest extends TestCase
->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.mail_name', 'lala')
->assertInertiaPath('data.data.0.mail_email', 'a@b.de')
->assertInertiaPath('data.data.0.usage', 'Usa')
->assertInertiaPath('data.data.0.greeting', $invoice->greeting)
->assertInertiaPath('data.data.0.positions.0.price', 1100)
@ -62,6 +64,8 @@ class InvoiceIndexActionTest extends TestCase
'status' => InvoiceStatus::NEW->value,
'via' => null,
'usage' => '',
'mail_name' => '',
'mail_email' => '',
])
->assertInertiaPath('data.meta.default_position', [
'id' => null,

View File

@ -4,13 +4,14 @@ namespace Tests\Feature\Invoice;
use App\Invoice\Actions\InvoiceSendAction;
use App\Invoice\BillDocument;
use App\Member\Member;
use App\Payment\Payment;
use App\Payment\PaymentMail;
use App\Invoice\BillKind;
use App\Invoice\Enums\InvoiceStatus;
use App\Invoice\Mails\BillMail;
use App\Invoice\Models\Invoice;
use App\Invoice\Models\InvoicePosition;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Storage;
use Tests\RequestFactories\Child;
use Tests\TestCase;
use Zoomyboy\Tex\Tex;
@ -23,24 +24,18 @@ class InvoiceSendActionTest extends TestCase
Mail::fake();
Tex::spy();
Storage::fake('temp');
$this->withoutExceptionHandling();
$this->login()->loginNami();
$member = Member::factory()
->defaults()
->has(Payment::factory()->notPaid()->nr('1997')->subscription('tollerbeitrag', [
new Child('a', 5400),
]))
->emailBillKind()
->create(['firstname' => 'Lah', 'lastname' => 'Mom', 'email' => 'peter@example.com']);
$this->withoutExceptionHandling()->login()->loginNami();
$invoice = Invoice::factory()
->to(ReceiverRequestFactory::new()->name('Familie Muster'))
->has(InvoicePosition::factory()->description('lalab')->withMember(), 'positions')
->via(BillKind::EMAIL)
->create(['mail_name' => 'Muster', 'mail_email' => 'max@muster.de']);
InvoiceSendAction::run();
Mail::assertSent(PaymentMail::class, fn ($mail) => Storage::disk('temp')->path('rechnung-fur-mom.pdf') === $mail->filename && Storage::disk('temp')->exists('rechnung-fur-mom.pdf'));
Tex::assertCompiled(
BillDocument::class,
fn ($document) => 'Mom' === $document->familyName
&& $document->positions === ['tollerbeitrag 1997 für Lah Mom' => '54.00']
);
Tex::assertCompiledContent(BillDocument::class, BillDocument::from($member->payments->first()->invoice_data)->renderBody());
Mail::assertSent(BillMail::class, fn ($mail) => $mail->build() && $mail->hasTo('max@muster.de', 'Muster') && Storage::disk('temp')->path('rechnung-fur-familie-muster.pdf') === $mail->filename && Storage::disk('temp')->exists('rechnung-fur-familie-muster.pdf'));
Tex::assertCompiled(BillDocument::class, fn ($document) => 'Familie Muster' === $document->toName);
$this->assertEquals(InvoiceStatus::SENT, $invoice->fresh()->status);
$this->assertEquals(now()->format('Y-m-d'), $invoice->fresh()->sent_at->format('Y-m-d'));
}
}

View File

@ -30,7 +30,7 @@ class InvoiceStoreActionTest extends TestCase
'greeting' => 'Hallo Familie',
])
->position(InvoicePositionRequestFactory::new()->description('Beitrag Abc')->price(3250)->member($member))
->create()
->create(['mail_email' => 'a@b.de', 'mail_name' => 'lala'])
);
$response->assertOk();
@ -38,6 +38,8 @@ class InvoiceStoreActionTest extends TestCase
'greeting' => 'Hallo Familie',
'via' => BillKind::POST->value,
'status' => InvoiceStatus::PAID->value,
'mail_email' => 'a@b.de',
'mail_name' => 'lala'
]);
$invoice = Invoice::firstWhere('greeting', 'Hallo Familie');
$this->assertDatabaseHas('invoice_positions', [

View File

@ -49,7 +49,7 @@ class MassStoreActionTest extends TestCase
->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']);
]))->emailBillKind()->create(['firstname' => 'Max', 'lastname' => 'Muster', 'address' => 'Maxstr 4', 'zip' => '33445', 'location' => 'Solingen', 'email' => 'lala@b.de']);
$this->postJson(route('invoice.mass-store'), [
'year' => now()->addYear()->year,
@ -64,6 +64,8 @@ class MassStoreActionTest extends TestCase
'location' => 'Solingen',
], $invoice->to);
$this->assertEquals('Mitgliedsbeitrag für Muster', $invoice->usage);
$this->assertEquals('lala@b.de', $invoice->mail_email);
$this->assertEquals('Familie Muster', $invoice->mail_name);
$this->assertEquals(BillKind::EMAIL, $invoice->via);
$this->assertDatabaseHas('invoice_positions', [
'invoice_id' => $invoice->id,