Skip to content

Commit

Permalink
Improved parsed banking transaction mails/parsed mail logs.
Browse files Browse the repository at this point in the history
- Added ability to change parsed mail's state.
- Added ability to change parsed mail's note.
- Added ability to track source bank account (updated `CsobMailParser` & `TatraBankaMailParser` + logging through `MailProcessor`)
- Refactored `ParsedMailLog`'s State constants/enum
- Added `EnumHelper` to simplify work with enum especially with Nette forms (enum values listing)

remp/respekt#189
  • Loading branch information
burithetech committed May 20, 2024
1 parent cb0507e commit 8613cc0
Show file tree
Hide file tree
Showing 18 changed files with 398 additions and 16 deletions.
41 changes: 41 additions & 0 deletions src/Forms/ParsedMailLogFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

namespace Crm\PaymentsModule\Forms;

use Crm\PaymentsModule\Models\ParsedMailLog\State;
use Nette\Application\UI\Form;
use Nette\Localization\Translator;
use Tomaj\Form\Renderer\BootstrapRenderer;

class ParsedMailLogFactory
{
public function __construct(
private readonly Translator $translator,
) {
}

public function create(int $parsedMailLogId, array $defaults): Form
{
$form = new Form;
$form->setTranslator($this->translator);
$form->setRenderer(new BootstrapRenderer());

// State
$form->addSelect('state', 'payments.admin.parsed_mails.state.label', items: State::getFriendlyList());

// Note
$form->addTextArea('note', 'payments.admin.parsed_mails.note.label')
->setNullable()
->setHtmlAttribute('rows', 4);

// Buttons
$form->addSubmit('save', 'payments.admin.parsed_mails_edit_form.save');
$form->addButton('cancel', 'payments.admin.parsed_mails_edit_form.cancel')
->setHtmlAttribute('data-toggle', 'modal')
->setHtmlAttribute('data-target', sprintf('#parsedMailLogEditModal%d', $parsedMailLogId));

$form->setDefaults($defaults);

return $form;
}
}
5 changes: 5 additions & 0 deletions src/Models/Builder/ParsedMailLogsBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,9 @@ public function setMessage($message)
{
return $this->set('message', $message);
}

public function setSourceAccountNumber(?string $sourceAccountNumber): self
{
return $this->set('source_account_number', $sourceAccountNumber);
}
}
3 changes: 2 additions & 1 deletion src/Models/MailConfirmation/MailProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ private function processBankAccountMovements()

$this->logBuilder
->setMessage($this->mailContent->getReceiverMessage())
->setAmount($this->mailContent->getAmount());
->setAmount($this->mailContent->getAmount())
->setSourceAccountNumber($this->mailContent->getSourceAccountNumber());

$payment = $this->getPaymentFromMailContentVs();
if (!$payment) {
Expand Down
6 changes: 6 additions & 0 deletions src/Models/MailParser/CsobMailParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ public function parse(string $content): ?MailContent
$mailContent->setTransactionDate(strtotime($result[1]));
$mailContent->setAccountNumber($result[2]);

$pattern3 = '/Účet protistrany(?:\/IBAN)*: (.*)/mu';
$res = preg_match($pattern3, $content, $result);
if ($res) {
$mailContent->setSourceAccountNumber(trim($result[1]));
}

$pattern2 = '/Částka: ([+-])(.*?) ([A-Z]+)/mu';
$res = preg_match($pattern2, $content, $result);
if ($res) {
Expand Down
22 changes: 22 additions & 0 deletions src/Models/ParsedMailLog/State.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);

namespace Crm\PaymentsModule\Models\ParsedMailLog;

use Crm\ApplicationModule\Helpers\EnumHelper;

enum State: string
{
use EnumHelper;

case WITHOUT_VS = 'without_vs';
case ALREADY_PAID = 'already_paid';
case DUPLICATED_PAYMENT = 'duplicated_payment';
case CHANGED_TO_PAID = 'changed_to_paid';
case PAYMENT_NOT_FOUND = 'payment_not_found';
case DIFFERENT_AMOUNT = 'different_amount';
case AUTO_NEW_PAYMENT = 'auto_new_payment';
case NO_SIGN = 'no_sign';
case NOT_VALID_SIGN = 'no_valid_sign';
case ALREADY_REFUNDED = 'already_refunded';
}
106 changes: 92 additions & 14 deletions src/Presenters/ParsedMailsPresenter.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,17 @@

use Crm\AdminModule\Presenters\AdminPresenter;
use Crm\ApplicationModule\Components\PreviousNextPaginator\PreviousNextPaginator;
use Crm\ApplicationModule\Models\Database\Selection;
use Crm\PaymentsModule\Forms\ParsedMailLogFactory;
use Crm\PaymentsModule\Models\ParsedMailLog\State;
use Crm\PaymentsModule\Repositories\ParsedMailLogsRepository;
use Crm\PaymentsModule\Repositories\PaymentsRepository;
use Exception;
use Nette\Application\Attributes\Persistent;
use Nette\Application\UI\Form;
use Nette\Application\UI\Multiplier;
use Nette\DI\Attributes\Inject;
use Tomaj\Form\Renderer\BootstrapInlineRenderer;
use Tomaj\Form\Renderer\BootstrapRenderer;

class ParsedMailsPresenter extends AdminPresenter
{
Expand All @@ -28,12 +33,24 @@ class ParsedMailsPresenter extends AdminPresenter
#[Persistent]
public $paymentStatus;

#[Persistent]
public $amountFrom;

#[Persistent]
public $amountTo;

public function __construct(
private readonly ParsedMailLogFactory $parsedMailLogFactory,
) {
parent::__construct();
}

/**
* @admin-access-level read
*/
public function renderDefault()
{
$logs = $this->parsedMailLogsRepository->all($this->vs, $this->state, $this->paymentStatus);
$logs = $this->getFilteredLogsSelection();

$pnp = new PreviousNextPaginator();
$this->addComponent($pnp, 'paginator');
Expand All @@ -49,23 +66,21 @@ public function renderDefault()
public function createComponentFilterForm()
{
$form = new Form();
$form->setRenderer(new BootstrapInlineRenderer());
$form->setRenderer(new BootstrapRenderer());
$form->setTranslator($this->translator);

$form->addGroup('main')->setOption('label', null); // main group

$collapseGroup = $form->addGroup('collapse', false)
->setOption('container', 'div class="collapse"')
->setOption('label', null)
->setOption('id', 'filterFormCollapsableGroup');
$buttonGroup = $form->addGroup('button', false)->setOption('label', null);

$form->addText('vs', 'payments.admin.parsed_mails.variable_symbol.label')
->setHtmlAttribute('autofocus');

$states = [
ParsedMailLogsRepository::STATE_WITHOUT_VS => ParsedMailLogsRepository::STATE_WITHOUT_VS,
ParsedMailLogsRepository::STATE_ALREADY_PAID => ParsedMailLogsRepository::STATE_ALREADY_PAID,
ParsedMailLogsRepository::STATE_CHANGED_TO_PAID => ParsedMailLogsRepository::STATE_CHANGED_TO_PAID,
ParsedMailLogsRepository::STATE_PAYMENT_NOT_FOUND => ParsedMailLogsRepository::STATE_PAYMENT_NOT_FOUND,
ParsedMailLogsRepository::STATE_DIFFERENT_AMOUNT => ParsedMailLogsRepository::STATE_DIFFERENT_AMOUNT,
ParsedMailLogsRepository::STATE_AUTO_NEW_PAYMENT => ParsedMailLogsRepository::STATE_AUTO_NEW_PAYMENT,
ParsedMailLogsRepository::STATE_DUPLICATED_PAYMENT => ParsedMailLogsRepository::STATE_DUPLICATED_PAYMENT,
ParsedMailLogsRepository::STATE_ALREADY_REFUNDED => ParsedMailLogsRepository::STATE_ALREADY_REFUNDED,
];
$form->addSelect('state', 'payments.admin.parsed_mails.state.label', $states)
$form->addSelect('state', 'payments.admin.parsed_mails.state.label', State::getFriendlyList())
->setPrompt('--');

$form->addSelect(
Expand All @@ -74,6 +89,15 @@ public function createComponentFilterForm()
$this->paymentsRepository->getStatusPairs(),
)->setPrompt('--');

$form->setCurrentGroup($collapseGroup);
$form->addText('amount_from', 'payments.admin.parsed_mails_filter_form.amount_from.label')
->setHtmlAttribute('type', 'number');

$form->addText('amount_to', 'payments.admin.parsed_mails_filter_form.amount_to.label')
->setHtmlAttribute('type', 'number');

$form->setCurrentGroup($buttonGroup);

$form->addSubmit('send', 'payments.admin.parsed_mails.filter')
->getControlPrototype()
->setName('button')
Expand All @@ -87,11 +111,21 @@ public function createComponentFilterForm()
]);
};

$form->addButton('more')
->setHtmlAttribute('data-toggle', 'collapse')
->setHtmlAttribute('data-target', '#filterFormCollapsableGroup')
->setHtmlAttribute('class', 'btn btn-info')
->getControlPrototype()
->setName('button')
->setHtml('<i class="fas fa-caret-down"></i> ' . $this->translator->translate('payments.admin.parsed_mails_filter_form.filter.more'));

$form->onSuccess[] = [$this, 'adminFilterSubmited'];
$form->setDefaults([
'state' => $this->state,
'payment_status' => $this->paymentStatus,
'vs' => $this->vs,
'amount_from' => $this->amountFrom,
'amount_to' => $this->amountTo,
]);
return $form;
}
Expand All @@ -102,6 +136,50 @@ public function adminFilterSubmited($form, $values)
'state' => $values['state'],
'paymentStatus' => $values['payment_status'],
'vs' => $values['vs'],
'amountFrom' => $values['amount_from'],
'amountTo' => $values['amount_to'],
]);
}

protected function createComponentEditForm(): Multiplier
{
return new Multiplier(function (string $parsedMailLogId) {
$parsedMailLog = $this->parsedMailLogsRepository->find((int) $parsedMailLogId);
if (!$parsedMailLog) {
throw new Exception('Parsed mail log not found.');
}

$form = $this->parsedMailLogFactory->create($parsedMailLog->id, [
'state' => $parsedMailLog->state,
'note' => $parsedMailLog->note,
]);

$form->onSuccess[] = function (Form $form, array $values) use ($parsedMailLog): void {
$this->parsedMailLogsRepository->update($parsedMailLog, [
'state' => $values['state'],
'note' => $values['note'],
]);

$this->flashMessage($this->translator->translate('payments.admin.parsed_mails_edit_form.success_message'));
$this->redirect('default');
};

return $form;
});
}

private function getFilteredLogsSelection(): Selection
{
$logs = $this->parsedMailLogsRepository->all($this->vs, $this->state, $this->paymentStatus);

if ($this->amountFrom) {
$logs->where('amount >= ?', $this->amountFrom);
}

if ($this->amountTo) {
$logs->where('amount <= ?', $this->amountTo);
}

return $logs;
}
}
27 changes: 26 additions & 1 deletion src/Repositories/ParsedMailLogsRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,55 @@
namespace Crm\PaymentsModule\Repositories;

use Crm\ApplicationModule\Models\Database\Repository;
use Crm\ApplicationModule\Repositories\AuditLogRepository;
use Crm\PaymentsModule\Models\VariableSymbolVariant;
use Nette\Caching\Storage;
use Nette\Database\Explorer;

class ParsedMailLogsRepository extends Repository
{
/** @deprecated Use \Crm\PaymentsModule\Models\ParsedMailLog\State::values() enum instead. */
public const ALL_STATES = [
ParsedMailLogsRepository::STATE_WITHOUT_VS,
ParsedMailLogsRepository::STATE_ALREADY_PAID,
ParsedMailLogsRepository::STATE_CHANGED_TO_PAID,
ParsedMailLogsRepository::STATE_PAYMENT_NOT_FOUND,
ParsedMailLogsRepository::STATE_DIFFERENT_AMOUNT,
ParsedMailLogsRepository::STATE_AUTO_NEW_PAYMENT,
ParsedMailLogsRepository::STATE_DUPLICATED_PAYMENT,
ParsedMailLogsRepository::STATE_ALREADY_REFUNDED,
];

/** @deprecated Use \Crm\PaymentsModule\Models\ParsedMailLog\State::WITHOUT_VS enum instead. */
const STATE_WITHOUT_VS = 'without_vs';
/** @deprecated Use \Crm\PaymentsModule\Models\ParsedMailLog\State::ALREADY_PAID enum instead. */
const STATE_ALREADY_PAID = 'already_paid';
/** @deprecated Use \Crm\PaymentsModule\Models\ParsedMailLog\State::DUPLICATED_PAYMENT enum instead. */
const STATE_DUPLICATED_PAYMENT = 'duplicated_payment';
/** @deprecated Use \Crm\PaymentsModule\Models\ParsedMailLog\State::CHANGED_TO_PAID enum instead. */
const STATE_CHANGED_TO_PAID = 'changed_to_paid';
/** @deprecated Use \Crm\PaymentsModule\Models\ParsedMailLog\State::PAYMENT_NOT_FOUND enum instead. */
const STATE_PAYMENT_NOT_FOUND = 'payment_not_found';
/** @deprecated Use \Crm\PaymentsModule\Models\ParsedMailLog\State::DIFFERENT_AMOUNT enum instead. */
const STATE_DIFFERENT_AMOUNT = 'different_amount';
/** @deprecated Use \Crm\PaymentsModule\Models\ParsedMailLog\State::AUTO_NEW_PAYMENT enum instead. */
const STATE_AUTO_NEW_PAYMENT = 'auto_new_payment';
/** @deprecated Use \Crm\PaymentsModule\Models\ParsedMailLog\State::NO_SIGN enum instead. */
const STATE_NO_SIGN = 'no_sign';
/** @deprecated Use \Crm\PaymentsModule\Models\ParsedMailLog\State::NOT_VALID_SIGN enum instead. */
const STATE_NOT_VALID_SIGN = 'no_valid_sign';
/** @deprecated Use \Crm\PaymentsModule\Models\ParsedMailLog\State::ALREADY_REFUNDED enum instead. */
const STATE_ALREADY_REFUNDED = 'already_refunded';

protected $tableName = 'parsed_mail_logs';

public function __construct(
Explorer $database,
Storage $cacheStorage = null
AuditLogRepository $auditLogRepository,
Storage $cacheStorage = null,
) {
parent::__construct($database, $cacheStorage);
$this->auditLogRepository = $auditLogRepository;
}

public function all(?string $vs = null, ?string $state = null, ?string $paymentStatus = null)
Expand Down
Loading

0 comments on commit 8613cc0

Please sign in to comment.