Skip to content

Commit 152e323

Browse files
author
Dorazil
committed
Enable automated payment reminders for groups
Adds functionality to send automated payment reminders for groups with the option to enable or disable this feature. Implements repository queries, CLI support, UI updates, and relevant database changes including a new `is_reminders_enabled` column.
1 parent a170f80 commit 152e323

File tree

15 files changed

+219
-20
lines changed

15 files changed

+219
-20
lines changed

app/AccountancyModule/PaymentModule/components/GroupForm.php

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ private function formSucceeded(BaseForm $form): void
179179
$emails,
180180
$oAuthId,
181181
$v->bankAccount,
182+
$v->emails[EmailType::PAYMENT_REMINDER]?->remindersEnabled ?? false,
182183
);
183184

184185
$this->flashMessage('Skupina byla upravena');
@@ -191,6 +192,7 @@ private function formSucceeded(BaseForm $form): void
191192
$emails,
192193
$oAuthId,
193194
$v->bankAccount,
195+
$v->emails[EmailType::PAYMENT_REMINDER]?->remindersEnabled ?? false,
194196
);
195197

196198
$this->flashMessage('Skupina byla založena');
@@ -219,6 +221,11 @@ private function buildDefaultsFromGroup(): array
219221

220222
foreach (EmailType::getAvailableEnums() as $emailType) {
221223
$emails[$emailType->toString()] = $this->getEmailDefaults($this->groupId, $emailType);
224+
if ($emailType->toString() !== EmailType::PAYMENT_REMINDER) {
225+
continue;
226+
}
227+
228+
$emails[$emailType->toString()]['remindersEnabled'] = $group->isRemindersEnabled();
222229
}
223230

224231
return [
@@ -250,16 +257,23 @@ private function addEmailsToForm(BaseForm $form): void
250257
$container = $emailsContainer->addContainer($type);
251258
$container->setCurrentGroup($group);
252259

253-
$subjectId = $type . '_subject';
254-
$bodyId = $type . '_body';
260+
$subjectId = $type . '_subject';
261+
$bodyId = $type . '_body';
262+
$remindersId = $type . '_reminders';
255263

256264
// Only payment info email is always saved
257265
if ($type !== EmailType::PAYMENT_INFO) {
258266
$container->addCheckbox('enabled', 'Aktivní')
259267
->setOption('class', 'form-check')
260268
->addCondition($form::FILLED)
261269
->toggle($subjectId)
262-
->toggle($bodyId);
270+
->toggle($bodyId)
271+
->toggle($remindersId);
272+
}
273+
274+
if ($type === EmailType::PAYMENT_REMINDER) {
275+
$container->addCheckbox('remindersEnabled', 'Automaticky odeslat email po splatnosti')
276+
->setOption('id', $remindersId);
263277
}
264278

265279
$container->addText('subject', 'Předmět e-mailu')

app/config/config.neon

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,9 @@ services:
151151

152152
- Model\Services\PdfRenderer(%tempDir%)
153153

154+
group:
155+
factory: Model\GroupService
156+
154157
- Model\PaymentService
155158
- Model\Payment\BankAccountService(fioCache: @fio.cache)
156159

@@ -242,7 +245,8 @@ services:
242245
scheduler:
243246
jobs:
244247
# stats must be registered as service and have method calculate
245-
- { cron: '* * * * *', callback: [ @fio.client, get ] }
248+
#- { cron: '* * * * *', callback: [ @fio.client, get ] }
249+
- { cron: '* * * * *', callback: [ @group, reminder ] }
246250

247251

248252
decorator:

app/model/DTO/Payment/Group.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
* @property-read int|NULL $constantSymbol
2222
* @property-read VariableSymbol|NULL $nextVariableSymbol
2323
* @property-read string $state
24+
* @property-read bool $isRemindersEnabled
2425
* @property-read OAuthId|NULL $oAuthId
2526
* @property-read string $note
2627
*/
@@ -43,6 +44,7 @@ public function __construct(
4344
private OAuthId|null $oAuthId = null,
4445
private string $note,
4546
private int|null $bankAccountId = null,
47+
private bool $isRemindersEnabled = false,
4648
) {
4749
}
4850

@@ -116,4 +118,9 @@ public function getBankAccountId(): int|null
116118
{
117119
return $this->bankAccountId;
118120
}
121+
122+
public function isRemindersEnabled(): bool
123+
{
124+
return $this->isRemindersEnabled;
125+
}
119126
}

app/model/DTO/Payment/GroupFactory.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public static function create(GroupEntity $group): Group
2626
$group->getOauthId(),
2727
$group->getNote(),
2828
$group->getBankAccountId(),
29+
$group->isRemindersEnabled(),
2930
);
3031
}
3132
}

app/model/Infrastructure/Repositories/Payment/GroupRepository.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Model\Common\Services\EventBus;
1111
use Model\Google\OAuthId;
1212
use Model\Payment\DomainEvents\GroupWasRemoved;
13+
use Model\Payment\EmailType;
1314
use Model\Payment\Group;
1415
use Model\Payment\Group\Type;
1516
use Model\Payment\GroupNotFound;
@@ -58,6 +59,23 @@ public function findByIds(array $ids): array
5859
return $groups;
5960
}
6061

62+
/**
63+
* {@inheritDoc}
64+
*/
65+
public function findByReminder(): array
66+
{
67+
return $this->em->createQueryBuilder()
68+
->select('g')
69+
->from(Group::class, 'g', 'g.id')
70+
->leftJoin(Group\Email::class, 'e', Join::WITH, 'e.group = g')
71+
->where('g.isRemindersEnabled = 1')
72+
->andWhere('e.type = :reminder')
73+
->andWhere('e.enabled = 1')
74+
->setParameter('reminder', EmailType::PAYMENT_REMINDER)
75+
->getQuery()
76+
->getResult();
77+
}
78+
6179
/**
6280
* {@inheritDoc}
6381
*/

app/model/Infrastructure/Repositories/Payment/PaymentRepository.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
namespace Model\Infrastructure\Repositories\Payment;
66

77
use Assert\Assert;
8+
use DateTimeImmutable;
89
use Model\Infrastructure\Repositories\AggregateRepository;
10+
use Model\Payment\EmailType;
911
use Model\Payment\Payment;
1012
use Model\Payment\Payment\State;
1113
use Model\Payment\PaymentNotFound;
@@ -104,6 +106,37 @@ public function findByMultipleGroups(array $groupIds): array
104106
->getResult();
105107
}
106108

109+
/**
110+
* {@inheritDoc}
111+
*/
112+
public function findByReminder(array $groupIds): array
113+
{
114+
Assert::thatAll($groupIds)->integer();
115+
116+
if (empty($groupIds)) {
117+
return [];
118+
}
119+
120+
return $this->getEntityManager()->createQueryBuilder()
121+
->select('p, e')
122+
->from(Payment::class, 'p')
123+
->leftJoin('p.sentEmails', 'e')
124+
->where('p.groupId IN (:groupIds)')
125+
->andWhere('p.state = :state')
126+
->andWhere('p.dueDate <= :dueDate')
127+
->andWhere(
128+
'NOT EXISTS (
129+
SELECT 1 FROM ' . Payment\SentEmail::class . ' se
130+
WHERE se.payment = p AND se.type = :reminderType)',
131+
)
132+
->setParameter('groupIds', $groupIds)
133+
->setParameter('state', State::PREPARING)
134+
->setParameter('dueDate', (new DateTimeImmutable())->format('Y-m-d'))
135+
->setParameter('reminderType', EmailType::PAYMENT_REMINDER)
136+
137+
->getQuery()->getResult();
138+
}
139+
107140
public function save(Payment $payment): void
108141
{
109142
$this->saveAndDispatchEvents($payment);

app/model/Payment/Commands/Mailing/SendPaymentReminder.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,17 @@
99
/** @see SendPaymentReminderHandler */
1010
final class SendPaymentReminder
1111
{
12-
public function __construct(private int $paymentId)
12+
public function __construct(private int $paymentId, private bool $cli = false)
1313
{
1414
}
1515

1616
public function getPaymentId(): int
1717
{
1818
return $this->paymentId;
1919
}
20+
21+
public function isCli(): bool
22+
{
23+
return $this->cli;
24+
}
2025
}

app/model/Payment/Group.php

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ class Group
6868
/** @ORM\Column(type="datetime_immutable", nullable=true) */
6969
private DateTimeImmutable|null $createdAt = null;
7070

71+
/** @ORM\Column(type="boolean", options={"default"=0}) */
72+
private bool $isRemindersEnabled = false;
73+
7174
/**
7275
* @ORM\Embedded(class=Group\BankAccount::class, columnPrefix=false)
7376
*
@@ -109,6 +112,7 @@ public function __construct(
109112
BankAccount|null $bankAccount,
110113
IBankAccountAccessChecker $bankAccountAccessChecker,
111114
IOAuthAccessChecker $oAuthAccessChecker,
115+
bool $isRemindersEnabled = false,
112116
) {
113117
Assertion::notEmpty($unitIds);
114118
$this->object = $object;
@@ -133,6 +137,7 @@ public function __construct(
133137

134138
$this->changeBankAccount($bankAccount, $bankAccountAccessChecker);
135139
$this->changeOAuth($oAuthId, $oAuthAccessChecker);
140+
$this->isRemindersEnabled = $isRemindersEnabled;
136141
}
137142

138143
public function update(
@@ -142,13 +147,15 @@ public function update(
142147
BankAccount|null $bankAccount,
143148
IBankAccountAccessChecker $bankAccountAccessChecker,
144149
IOAuthAccessChecker $oAuthAccessChecker,
150+
bool $isRemindersEnabled = false,
145151
): void {
146152
$this->changeBankAccount($bankAccount, $bankAccountAccessChecker);
147153
$this->changeOAuth($oAuthId, $oAuthAccessChecker);
148154

149-
$this->name = $name;
150-
$this->paymentDefaults = $paymentDefaults;
151-
$this->oauthId = $oAuthId;
155+
$this->name = $name;
156+
$this->paymentDefaults = $paymentDefaults;
157+
$this->oauthId = $oAuthId;
158+
$this->isRemindersEnabled = $isRemindersEnabled;
152159
}
153160

154161
public function open(string $note): void
@@ -250,6 +257,11 @@ public function getState(): string
250257
return $this->state;
251258
}
252259

260+
public function isRemindersEnabled(): bool
261+
{
262+
return $this->isRemindersEnabled;
263+
}
264+
253265
public function getCreatedAt(): DateTimeImmutable|null
254266
{
255267
return $this->createdAt;

app/model/Payment/GroupService.php

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Model;
6+
7+
use App\AccountancyModule\PaymentModule\Components\EmailButton;
8+
use Model\Common\Services\CommandBus;
9+
use Model\Google\Exception\OAuthNotSet;
10+
use Model\Google\InvalidOAuth;
11+
use Model\Payment\Commands\Mailing\SendPaymentReminder;
12+
use Model\Payment\EmailTemplateNotSet;
13+
use Model\Payment\InvalidBankAccount;
14+
use Model\Payment\PaymentHasNoEmails;
15+
use Model\Payment\Repositories\IGroupRepository;
16+
use Model\Payment\Repositories\IPaymentRepository;
17+
use Psr\Log\LoggerInterface;
18+
19+
use function array_map;
20+
21+
class GroupService
22+
{
23+
public function __construct(
24+
private IGroupRepository $groups,
25+
private IPaymentRepository $payments,
26+
private CommandBus $commandBus,
27+
private LoggerInterface $logger,
28+
) {
29+
}
30+
31+
public function reminder(): void
32+
{
33+
$count = 0;
34+
$reminderGroups = $this->groups->findByReminder();
35+
$reminderPayments = $this->payments->findByReminder(array_map(function ($group) {
36+
return (int) $group->getId();
37+
}, $reminderGroups));
38+
39+
foreach ($reminderPayments as $payment) {
40+
try {
41+
$this->commandBus->handle(new SendPaymentReminder($payment->getId(), true));
42+
$count++;
43+
} catch (OAuthNotSet) {
44+
$this->logger->error('OAuth not set');
45+
} catch (InvalidBankAccount) {
46+
$this->logger->error(EmailButton::NO_BANK_ACCOUNT_MESSAGE);
47+
} catch (InvalidOAuth) {
48+
$this->logger->error('Invalid OAuth');
49+
} catch (EmailTemplateNotSet) {
50+
$this->logger->error(EmailButton::NO_TEMPLATE_ASSIGNED);
51+
} catch (PaymentHasNoEmails) {
52+
$this->logger->error('Payment has no emails');
53+
}
54+
}
55+
56+
$this->logger->info('Sent reminders for ' . $count . ' payments');
57+
}
58+
}

app/model/Payment/Handlers/Mailing/SendPaymentReminderHandler.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,6 @@ public function __invoke(SendPaymentReminder $command): void
2626
throw PaymentClosed::withName($payment->getName());
2727
}
2828

29-
$this->mailingService->sendEmail($payment->getId(), EmailType::get(EmailType::PAYMENT_REMINDER));
29+
$this->mailingService->sendEmail($payment->getId(), EmailType::get(EmailType::PAYMENT_REMINDER), $command->isCli());
3030
}
3131
}

0 commit comments

Comments
 (0)