From 62ee9fdd36d060cb0e1a920b5c38c8e4e58190f6 Mon Sep 17 00:00:00 2001 From: Yurii Myronchuk Date: Tue, 26 May 2026 19:42:42 +0200 Subject: [PATCH] =?UTF-8?q?HQD-159:=20rename=20InstallmentWasStarted=20?= =?UTF-8?q?=E2=86=92=20InstallmentWasCharged,=20fire=20every=20month?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously the event fired only on the first billing month (exact match with formula since). If billing missed that month, the Sale was never created. Now the event fires for every month within the installment period. HandleInstallmentWasCharged (in hiapi-legacy) is already idempotent: it checks whether a Sale with the correct till-date already exists and skips creation if so. Renamed to InstallmentWasCharged to reflect the actual semantics — one charge processed, not necessarily the start of the installment. --- docs/formula-and-modifiers.md | 2 +- docs/overview.md | 2 +- src/charge/modifiers/Installment.php | 19 +++---------------- ...sStarted.php => InstallmentWasCharged.php} | 10 +--------- tests/behat/Combination.feature | 9 ++++----- tests/behat/Installment.feature | 18 +++++++++--------- .../unit/charge/modifiers/InstallmentTest.php | 4 ++-- 7 files changed, 21 insertions(+), 43 deletions(-) rename src/charge/modifiers/event/{InstallmentWasStarted.php => InstallmentWasCharged.php} (75%) diff --git a/docs/formula-and-modifiers.md b/docs/formula-and-modifiers.md index aa941d45..8e50e4b3 100644 --- a/docs/formula-and-modifiers.md +++ b/docs/formula-and-modifiers.md @@ -91,7 +91,7 @@ Spreads a charge over a fixed term as monthly payments. installment.since('08.2018').lasts('3 months').reason('TEST') ``` -Returns a single charge with type `leasing` (pre-2024) or `installment` (2024+). The `till()` method is forbidden — use `lasts()` instead. Records domain events `InstallmentWasStarted` / `InstallmentWasFinished`. +Returns a single charge with type `leasing` (pre-2024) or `installment` (2024+). The `till()` method is forbidden — use `lasts()` instead. Records domain events `InstallmentWasCharged` / `InstallmentWasFinished`. ### Cap / MonthlyCap diff --git a/docs/overview.md b/docs/overview.md index ec3db30c..624fe9c1 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -8,7 +8,7 @@ src/ ├── bill/ Bill entity — aggregated invoice line item ├── charge/ Charge entity, ChargeModifier interface, modifier classes and addons ├── customer/ Customer entity — billable party with seller hierarchy -├── event/ Domain events (e.g., InstallmentWasStarted) +├── event/ Domain events (e.g., InstallmentWasCharged) ├── Exception/ Shared exceptions (CannotReassignException, etc.) ├── formula/ FormulaEngine — parses DSL strings into ChargeModifier objects ├── helpers/ Utility classes diff --git a/src/charge/modifiers/Installment.php b/src/charge/modifiers/Installment.php index 6900f416..3bb84f07 100644 --- a/src/charge/modifiers/Installment.php +++ b/src/charge/modifiers/Installment.php @@ -19,8 +19,8 @@ use hiqdev\php\billing\charge\modifiers\addons\Period; use hiqdev\php\billing\charge\modifiers\addons\Reason; use hiqdev\php\billing\charge\modifiers\addons\Since; +use hiqdev\php\billing\charge\modifiers\event\InstallmentWasCharged; use hiqdev\php\billing\charge\modifiers\event\InstallmentWasFinished; -use hiqdev\php\billing\charge\modifiers\event\InstallmentWasStarted; use hiqdev\php\billing\formula\FormulaSemanticsError; use hiqdev\php\billing\price\SinglePrice; use hiqdev\php\billing\target\Target; @@ -102,15 +102,6 @@ protected function ensureIsValid(): void } } - private function isFirstMonthInInstallmentPassed(DateTimeImmutable $time): bool - { - $since = $this->getSince(); - if ($since && $since->getValue() > $time) { - return false; - } - return $since->getValue()->diff($time)->format('%a') === '0'; - } - private function isFirstMonthAfterInstallmentPassed(DateTimeImmutable $time): bool { $since = $this->getSince(); @@ -148,7 +139,7 @@ private function createInstallmentFinishingCharge(ChargeInterface $charge, DateT private function createInstallmentStartingCharge(Charge $charge, DateTimeImmutable $month): ChargeInterface { - $charge->recordThat(InstallmentWasStarted::onCharge($charge, $month)); + $charge->recordThat(InstallmentWasCharged::onCharge($charge, $month)); return $charge; } @@ -169,11 +160,7 @@ private function createInstallmentCharge(ChargeInterface $charge, DateTimeImmuta $result->setComment($charge->getComment()); } - if ($this->isFirstMonthInInstallmentPassed($month)) { - return $this->createInstallmentStartingCharge($result, $month); - } - - return $result; + return $this->createInstallmentStartingCharge($result, $month); } public function getRemainingPeriods(DateTimeImmutable $currentDate): ?Period diff --git a/src/charge/modifiers/event/InstallmentWasStarted.php b/src/charge/modifiers/event/InstallmentWasCharged.php similarity index 75% rename from src/charge/modifiers/event/InstallmentWasStarted.php rename to src/charge/modifiers/event/InstallmentWasCharged.php index 01a5f36b..bc467587 100644 --- a/src/charge/modifiers/event/InstallmentWasStarted.php +++ b/src/charge/modifiers/event/InstallmentWasCharged.php @@ -15,7 +15,7 @@ use League\Event\AbstractEvent; use DateTimeImmutable; -class InstallmentWasStarted extends AbstractEvent implements \JsonSerializable +class InstallmentWasCharged extends AbstractEvent implements \JsonSerializable { private function __construct( private readonly ChargeInterface $charge, @@ -38,14 +38,6 @@ public function getTime(): \DateTimeImmutable return $this->time; } - /** - * Specify data which should be serialized to JSON - * - * @link https://php.net/manual/en/jsonserializable.jsonserialize.php - * @return mixed data which can be serialized by json_encode, - * which is a value of any type other than a resource - * @since 5.4.0 - */ public function jsonSerialize(): array { return [ diff --git a/tests/behat/Combination.feature b/tests/behat/Combination.feature index 5e536ba4..bc64f53a 100644 --- a/tests/behat/Combination.feature +++ b/tests/behat/Combination.feature @@ -35,8 +35,8 @@ Feature: Combination | 2018-08-01 | monthly 100 USD | | discount -10 USD reason TWO | | 2018-09-01 | monthly 100 USD | | discount -20 USD reason TWO | | 2018-10-01 | monthly 100 USD | | discount -30 USD reason TWO | - | 2018-11-01 | leasing 100 USD reason ONE | InstallmentWasStarted | | - | 2018-12-01 | leasing 100 USD reason ONE | | | + | 2018-11-01 | leasing 100 USD reason ONE | InstallmentWasCharged | | + | 2018-12-01 | leasing 100 USD reason ONE | InstallmentWasCharged | | | 2019-01-11 | leasing 0 USD reason ONE | InstallmentWasFinished | | | 2028-11-01 | | | | @@ -52,8 +52,8 @@ Feature: Combination | 2018-08-01 | monthly 100 USD | | discount -10 USD reason ONE | | 2018-09-01 | monthly 100 USD | | discount -20 USD reason ONE | | 2018-10-01 | monthly 100 USD | | discount -30 USD reason ONE | - | 2018-11-01 | leasing 60 USD reason TWO | InstallmentWasStarted | | - | 2018-12-01 | leasing 50 USD reason TWO | | | + | 2018-11-01 | leasing 60 USD reason TWO | InstallmentWasCharged | | + | 2018-12-01 | leasing 50 USD reason TWO | InstallmentWasCharged | | | 2019-01-11 | leasing 0 USD reason TWO | InstallmentWasFinished | | | 2028-11-01 | | | | @@ -99,4 +99,3 @@ Feature: Combination | 2022-01-01 | 1 | monthly 100 USD for 672 hour | monthly 0 USD for 72 hour | | | 2022-04-01 | 1 | monthly 90 USD for 672 hour | monthly 0 USD for 48 hour | | | 2022-05-15 | 0.5 | monthly 49.82 USD for 408 hour | | | - diff --git a/tests/behat/Installment.feature b/tests/behat/Installment.feature index 38c3f886..59d4b931 100644 --- a/tests/behat/Installment.feature +++ b/tests/behat/Installment.feature @@ -15,9 +15,9 @@ Feature: installment Examples: | date | first | events | | 2018-07-01 | | | - | 2018-08-01 | leasing 100 USD reason TEST | InstallmentWasStarted | - | 2018-09-01 | leasing 100 USD reason TEST | | - | 2018-10-01 | leasing 100 USD reason TEST | | + | 2018-08-01 | leasing 100 USD reason TEST | InstallmentWasCharged | + | 2018-09-01 | leasing 100 USD reason TEST | InstallmentWasCharged | + | 2018-10-01 | leasing 100 USD reason TEST | InstallmentWasCharged | | 2018-11-01 | leasing 0 USD reason TEST | InstallmentWasFinished | | 2028-01-01 | | | @@ -28,9 +28,9 @@ Feature: installment Examples: | date | first | events | | 2023-12-01 | | | - | 2024-01-01 | installment 100 USD reason TEST | InstallmentWasStarted | - | 2024-02-01 | installment 100 USD reason TEST | | - | 2024-03-01 | installment 100 USD reason TEST | | + | 2024-01-01 | installment 100 USD reason TEST | InstallmentWasCharged | + | 2024-02-01 | installment 100 USD reason TEST | InstallmentWasCharged | + | 2024-03-01 | installment 100 USD reason TEST | InstallmentWasCharged | | 2024-04-01 | installment 0 USD reason TEST | InstallmentWasFinished | | 2028-01-01 | | | @@ -42,8 +42,8 @@ Feature: installment Examples: | date | first | events | | 2018-07-01 | | | - | 2018-08-01 | leasing 0 USD reason TEST | InstallmentWasStarted | - | 2018-09-01 | leasing 0 USD reason TEST | | - | 2018-10-01 | leasing 0 USD reason TEST | | + | 2018-08-01 | leasing 0 USD reason TEST | InstallmentWasCharged | + | 2018-09-01 | leasing 0 USD reason TEST | InstallmentWasCharged | + | 2018-10-01 | leasing 0 USD reason TEST | InstallmentWasCharged | | 2018-11-01 | leasing 0 USD reason TEST | InstallmentWasFinished | | 2028-01-01 | | | diff --git a/tests/unit/charge/modifiers/InstallmentTest.php b/tests/unit/charge/modifiers/InstallmentTest.php index cb6eb1e2..44b4b999 100644 --- a/tests/unit/charge/modifiers/InstallmentTest.php +++ b/tests/unit/charge/modifiers/InstallmentTest.php @@ -13,7 +13,7 @@ use DateTimeImmutable; use hiqdev\php\billing\charge\modifiers\addons\MonthPeriod; use hiqdev\php\billing\charge\modifiers\addons\YearPeriod; -use hiqdev\php\billing\charge\modifiers\event\InstallmentWasStarted; +use hiqdev\php\billing\charge\modifiers\event\InstallmentWasCharged; use hiqdev\php\billing\charge\modifiers\Installment; use hiqdev\php\billing\price\SinglePrice; use hiqdev\php\billing\tests\unit\action\ActionTest; @@ -76,7 +76,7 @@ public function testModifyCharge() $charge = $this->calculator->calculateCharge($this->price, $action); $charges = $installment->modifyCharge($charge, $action); $event = $charges[0]->releaseEvents()[0]; - $this->assertInstanceOf(InstallmentWasStarted::class, $event); + $this->assertInstanceOf(InstallmentWasCharged::class, $event); $this->assertIsArray($charges); $this->assertSame(1, count($charges)); $this->assertEquals($charge, $charges[0]);