Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HP-1751 Create configurations for billing types #93

Open
wants to merge 68 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 54 commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
01dc6ff
HP-1751 Create configurations for billing types
VadymHrechukha Nov 28, 2024
f819552
HP-1751 Created Unit test for InvoiceDescriptionsBuilder class
VadymHrechukha Nov 28, 2024
f5292a4
HP-1751 Create configurations for billing types
VadymHrechukha Dec 6, 2024
ccb0b9c
HP-1751 Create configurations for billing types
VadymHrechukha Dec 6, 2024
9f7c0da
HP-1751 Create configurations for billing types
VadymHrechukha Dec 9, 2024
83d242f
HP-1751 created product Type
VadymHrechukha Dec 9, 2024
0e26ed5
HP-1751 added stubs for all types in ServerTariff config
VadymHrechukha Dec 10, 2024
ff69913
HP-1751 tiny
VadymHrechukha Dec 10, 2024
1ac3f7d
HP-1751 tiny
VadymHrechukha Dec 10, 2024
99371a4
HP-1751 implemented BillingRegistry::getRepresentationsByType() method
VadymHrechukha Dec 10, 2024
01e778a
HP-1751 fixed the case when we pass two sql for one type in AbstractL…
VadymHrechukha Dec 10, 2024
e95efbc
HP-1751 tiny
VadymHrechukha Dec 12, 2024
1e731a9
HP-1751 added ability to extend PriceTypeDefinition class
VadymHrechukha Dec 12, 2024
ef63f8f
HP-1751 Created QuantityFormatterDefinition for storing both the $for…
VadymHrechukha Dec 17, 2024
e72ae0f
HP-1751 created formatters
VadymHrechukha Dec 17, 2024
94f7fb3
HP-1751 configured QuantityFormatter
VadymHrechukha Dec 17, 2024
93c0ea8
HP-1751 working on QuantityFormatter classes
VadymHrechukha Dec 18, 2024
7972157
HP-1751 created interface for easy get QuantityFormatter from Billing…
VadymHrechukha Dec 18, 2024
e967f1b
HP-1751 changed signature of BillingRegistry::createQuantityFormatter…
VadymHrechukha Dec 19, 2024
944f07d
HP-1751 added consumption config for TariffType
VadymHrechukha Dec 20, 2024
821f83e
HP-1751 added ability to fetch consumption columns and groups into Bi…
VadymHrechukha Dec 22, 2024
9bc5a8e
HP-1751 added consumption classes
VadymHrechukha Dec 22, 2024
b5065b0
HP-1751 created BehaviorInterface
VadymHrechukha Dec 23, 2024
0247a8b
HP-1751 added aggregation to PriceTypeDefinition
VadymHrechukha Dec 23, 2024
e2fd487
HP-1751 created BehaviorNotFoundException
VadymHrechukha Dec 23, 2024
48a032e
HP-1751 removed Consumption from billing-registry
VadymHrechukha Dec 23, 2024
bc62b19
HP-1751 created TariffType class and use it in Behavior
VadymHrechukha Dec 26, 2024
ea5c85f
HP-1751 Throw BehaviorNotFoundException instead of returning null in …
VadymHrechukha Dec 27, 2024
02b3aee
HP-1751 created ParentNodeDefinitionInterface
VadymHrechukha Dec 30, 2024
a7d1c34
HP-1751 changed directory of TariffType, Unit and FractionUnit
VadymHrechukha Dec 30, 2024
baab974
HP-1751 tiny
VadymHrechukha Dec 30, 2024
18c7690
HP-1751 tiny
VadymHrechukha Dec 30, 2024
e65d27d
HP-1751 added annotation for BillingRegistry::getBehavior() method
VadymHrechukha Dec 31, 2024
fea22bd
HP-1751 Implemented BillingRegistry::getAggregate() method
VadymHrechukha Jan 2, 2025
f6c5c1c
HP-1751 added ability to check if PriceTypeDefinition has specific be…
VadymHrechukha Jan 3, 2025
9b8bb1d
HP-1751 tiny
VadymHrechukha Jan 6, 2025
d9a5ef7
HP-1751 added TariffTypeDefinition::hasBehavior() method
VadymHrechukha Jan 6, 2025
2bb5310
HP-1751 Removed dependency php-billing package from TariffType class
VadymHrechukha Mar 11, 2025
881679f
HP-1751 Removed dependency php-billing package from Unit and Fraction…
VadymHrechukha Mar 11, 2025
1acbb7b
HP-1751 Removed dependency php-billing package from PriceTypeDefiniti…
VadymHrechukha Mar 11, 2025
0c060ab
HP-1751 Removed dependency php-billing package from Aggregate class
VadymHrechukha Mar 11, 2025
93686bb
HP-1751 Attempt to removed dependency php-billing package from Aggreg…
VadymHrechukha Mar 11, 2025
77b9efe
HP-1751 Removed dependency php-billing package from QuantityFormatter…
VadymHrechukha Mar 11, 2025
9dad8c6
HP-1751 Attempt to removed dependency php-billing package from Quanti…
VadymHrechukha Mar 12, 2025
285ae11
HP-1751 Attempt to removed dependency php-billing package from Paymen…
VadymHrechukha Mar 12, 2025
afe5a62
HP-1751 Removed dependency php-billing package from InvoiceRepresenta…
VadymHrechukha Mar 12, 2025
57394fe
HP-1751 Removed dependency php-billing package from Product class
VadymHrechukha Mar 12, 2025
b649b44
HP-1751 Applied Single Responsibility Principle to PriceTypeDefinitio…
VadymHrechukha Mar 13, 2025
2cc1971
HP-1751 tiny
VadymHrechukha Mar 13, 2025
6411325
HP-1751 tiny
VadymHrechukha Mar 13, 2025
0328f42
HP-1751 Removed dependency php-billing package from GType, PriceType …
VadymHrechukha Mar 13, 2025
1f7fb11
HP-1751 Created TariffTypeDefinitionInterface
VadymHrechukha Mar 13, 2025
1cfa210
HP-1751 tiny
VadymHrechukha Mar 14, 2025
c75b5d4
HP-1751 Added psalm template annotation
VadymHrechukha Mar 14, 2025
27c16d1
HP-1751 tiny
VadymHrechukha Mar 14, 2025
6808daf
HP-1751 fixed error during update composer
VadymHrechukha Mar 17, 2025
413f729
HP-1751 tiny
VadymHrechukha Mar 20, 2025
d7d838a
HP-1751 fixed the bug with reusing PriceTypeDefinitionCollection class
VadymHrechukha Mar 20, 2025
2c6a195
HP-1751 refactored BillingRegistry::getBehavior() method
VadymHrechukha Mar 20, 2025
6f0783b
HP-1751 added TariffTypeDefinitionInterface::getProduct() interface
VadymHrechukha Mar 20, 2025
0e0bc7c
HP-1751 tiny
VadymHrechukha Mar 20, 2025
75ee687
HP-1751 created InvalidQuantityFormatterException
VadymHrechukha Mar 20, 2025
9b25ddf
HP-1751 created InvalidRepresentationException
VadymHrechukha Mar 20, 2025
7d09102
HP-1751 fixed BillingRegistry::createQuantityFormatter() method
VadymHrechukha Mar 20, 2025
c00ea1e
HP-1751 tiny
VadymHrechukha Mar 20, 2025
4ce2b38
HP-1751 created AggregateNotDefinedException
VadymHrechukha Mar 20, 2025
7269bf9
HP-1751 fixing InvoiceDescriptionsBuilder class
VadymHrechukha Mar 20, 2025
03ee8dc
HP-1751 tiny
VadymHrechukha Mar 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/product/AggregateInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php declare(strict_types=1);

namespace hiqdev\php\billing\product;

interface AggregateInterface
{
public function isMax(): bool;
}
9 changes: 9 additions & 0 deletions src/product/AggregateNotFoundException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php declare(strict_types=1);

namespace hiqdev\php\billing\product;

use hiqdev\php\billing\Exception\RuntimeException;

class AggregateNotFoundException extends RuntimeException
{
}
134 changes: 134 additions & 0 deletions src/product/BillingRegistry.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
<?php declare(strict_types=1);

namespace hiqdev\php\billing\product;

use hiqdev\php\billing\product\invoice\RepresentationInterface;
use hiqdev\php\billing\product\quantity\QuantityFormatterNotFoundException;
use hiqdev\php\billing\product\quantity\FractionQuantityData;
use hiqdev\php\billing\product\behavior\BehaviorInterface;
use hiqdev\php\billing\product\behavior\BehaviorNotFoundException;
use hiqdev\php\billing\type\Type;
use hiqdev\php\billing\type\TypeInterface;

class BillingRegistry implements BillingRegistryInterface
{
/** @var TariffTypeDefinition[] */
private array $tariffTypes = [];
private bool $locked = false;

public function addTariffType(TariffTypeDefinition $tariffType): void
{
if ($this->locked) {
throw new \RuntimeException("BillingRegistry is locked and cannot be modified.");
}

$this->tariffTypes[] = $tariffType;
}

public function lock(): void
{
$this->locked = true;
}

public function priceTypes(): \Generator
{
foreach ($this->tariffTypes as $tariffType) {
foreach ($tariffType->withPrices() as $priceTypeDefinition) {
yield $priceTypeDefinition;
}
}
}

/**
* @param string $representationClass
* @return RepresentationInterface[]
*/
public function getRepresentationsByType(string $representationClass): array
{
$representations = [];
foreach ($this->priceTypes() as $priceTypeDefinition) {
foreach ($priceTypeDefinition->documentRepresentation() as $representation) {
if ($representation instanceof $representationClass) {
$representations[] = $representation;
}
}
}

return $representations;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add input validation and fix return type hint

The method needs validation for the representation class and updated return type documentation.

Apply these improvements:

     /**
      * @param string $representationClass
-     * @return RepresentationInterface[]
+     * @return DocumentRepresentationInterface[]
+     * @throws \InvalidArgumentException When representation class is invalid
      */
     public function getRepresentationsByType(string $representationClass): array
     {
+        if (!class_exists($representationClass)) {
+            throw new \InvalidArgumentException(
+                sprintf('Class "%s" does not exist', $representationClass)
+            );
+        }
+        if (!is_subclass_of($representationClass, DocumentRepresentationInterface::class)) {
+            throw new \InvalidArgumentException(
+                sprintf('Class "%s" must implement DocumentRepresentationInterface', $representationClass)
+            );
+        }
+
         $representations = [];
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* @param string $representationClass
* @return RepresentationInterface[]
*/
public function getRepresentationsByType(string $representationClass): array
{
$representations = [];
foreach ($this->priceTypes() as $priceTypeDefinition) {
foreach ($priceTypeDefinition->documentRepresentation() as $representation) {
if ($representation instanceof $representationClass) {
$representations[] = $representation;
}
}
}
return $representations;
}
/**
* @param string $representationClass
* @return DocumentRepresentationInterface[]
* @throws \InvalidArgumentException When representation class is invalid
*/
public function getRepresentationsByType(string $representationClass): array
{
if (!class_exists($representationClass)) {
throw new \InvalidArgumentException(
sprintf('Class "%s" does not exist', $representationClass)
);
}
if (!is_subclass_of($representationClass, DocumentRepresentationInterface::class)) {
throw new \InvalidArgumentException(
sprintf('Class "%s" must implement DocumentRepresentationInterface', $representationClass)
);
}
$representations = [];
foreach ($this->priceTypes() as $priceTypeDefinition) {
foreach ($priceTypeDefinition->documentRepresentation() as $representation) {
if ($representation instanceof $representationClass) {
$representations[] = $representation;
}
}
}
return $representations;
}
🧰 Tools
🪛 GitHub Check: PHP 8.3

[failure] 42-42: UndefinedDocblockClass
src/product/BillingRegistry.php:42:16: UndefinedDocblockClass: Docblock-defined class, interface or enum named hiqdev\billing\registry\invoice\RepresentationInterface does not exist (see https://psalm.dev/200)


public function createQuantityFormatter(
string $type,
FractionQuantityData $data,
): array {
$type = $this->convertStringTypeToType($type);

foreach ($this->priceTypes() as $priceTypeDefinition) {
if ($priceTypeDefinition->hasType($type)) {
return $priceTypeDefinition->createQuantityFormatter($data);
}
}

throw new QuantityFormatterNotFoundException('Quantity formatter not found');
}

private function convertStringTypeToType(string $type): TypeInterface
{
return Type::anyId($type);
}

/**
* @param string $type - full type like 'overuse,lb_capacity_unit'
* @param string $behaviorClassWrapper
* @return BehaviorInterface
* @throws BehaviorNotFoundException
*/
public function getBehavior(string $type, string $behaviorClassWrapper): BehaviorInterface
{
$type = $this->convertStringTypeToType($type);

foreach ($this->priceTypes() as $priceTypeDefinition) {
if ($priceTypeDefinition->hasType($type)) {
foreach ($priceTypeDefinition->withBehaviors() as $behavior) {
if ($behavior instanceof $behaviorClassWrapper) {
return $behavior;
}
}
}
}

throw new BehaviorNotFoundException('Behaviour was not found');
}

public function getBehaviors(string $behaviorClassWrapper): \Generator
{
foreach ($this->tariffTypes as $tariffType) {
foreach ($tariffType->withBehaviors() as $behavior) {
if ($behavior instanceof $behaviorClassWrapper) {
yield $behavior;
}
}
}

foreach ($this->priceTypes() as $priceTypeDefinition) {
foreach ($priceTypeDefinition->withBehaviors() as $behavior) {
if ($behavior instanceof $behaviorClassWrapper) {
yield $behavior;
}
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Refactor behavior management for better maintainability

The behavior management implementation needs several improvements:

  1. Input validation
  2. Better error messages
  3. Code deduplication
  4. Better documentation

Apply these improvements:

+    /**
+     * @param string $behaviorClassWrapper The behavior class to match
+     * @return \Generator<int, BehaviorInterface>
+     * @throws \InvalidArgumentException When behavior class is invalid
+     */
+    private function findBehaviorsInCollection(iterable $collection, string $behaviorClassWrapper): \Generator
+    {
+        foreach ($collection as $behavior) {
+            if ($behavior instanceof $behaviorClassWrapper) {
+                yield $behavior;
+            }
+        }
+    }

     /**
      * @param string $type - full type like 'overuse,lb_capacity_unit'
      * @param string $behaviorClassWrapper
      * @return BehaviorInterface
      * @throws BehaviorNotFoundException
+     * @throws \InvalidArgumentException When parameters are invalid
      */
     public function getBehavior(string $type, string $behaviorClassWrapper): BehaviorInterface
     {
+        if (empty($type)) {
+            throw new \InvalidArgumentException('Type cannot be empty');
+        }
+        if (!class_exists($behaviorClassWrapper)) {
+            throw new \InvalidArgumentException(
+                sprintf('Behavior class "%s" does not exist', $behaviorClassWrapper)
+            );
+        }
+
         $type = $this->convertStringTypeToType($type);

         foreach ($this->priceTypes() as $priceTypeDefinition) {
             if ($priceTypeDefinition->hasType($type)) {
-                foreach ($priceTypeDefinition->withBehaviors() as $behavior) {
-                    if ($behavior instanceof $behaviorClassWrapper) {
-                        return $behavior;
-                    }
+                foreach ($this->findBehaviorsInCollection($priceTypeDefinition->withBehaviors(), $behaviorClassWrapper) as $behavior) {
+                    return $behavior;
                 }
             }
         }

-        throw new BehaviorNotFoundException('Behaviour was not found');
+        throw new BehaviorNotFoundException(
+            sprintf('Behavior of class "%s" not found for type "%s"', $behaviorClassWrapper, $type)
+        );
     }

+    /**
+     * @param string $behaviorClassWrapper The behavior class to match
+     * @return \Generator<int, BehaviorInterface>
+     * @throws \InvalidArgumentException When behavior class is invalid
+     */
     public function getBehaviors(string $behaviorClassWrapper): \Generator
     {
+        if (!class_exists($behaviorClassWrapper)) {
+            throw new \InvalidArgumentException(
+                sprintf('Behavior class "%s" does not exist', $behaviorClassWrapper)
+            );
+        }
+
         foreach ($this->tariffTypes as $tariffType) {
-            foreach ($tariffType->withBehaviors() as $behavior) {
-                if ($behavior instanceof $behaviorClassWrapper) {
-                    yield $behavior;
-                }
-            }
+            yield from $this->findBehaviorsInCollection($tariffType->withBehaviors(), $behaviorClassWrapper);
         }

         foreach ($this->priceTypes() as $priceTypeDefinition) {
-            foreach ($priceTypeDefinition->withBehaviors() as $behavior) {
-                if ($behavior instanceof $behaviorClassWrapper) {
-                    yield $behavior;
-                }
-            }
+            yield from $this->findBehaviorsInCollection($priceTypeDefinition->withBehaviors(), $behaviorClassWrapper);
         }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* @param string $type - full type like 'overuse,lb_capacity_unit'
* @param string $behaviorClassWrapper
* @return BehaviorInterface
* @throws BehaviorNotFoundException
*/
public function getBehavior(string $type, string $behaviorClassWrapper): BehaviorInterface
{
$type = $this->convertStringTypeToType($type);
foreach ($this->priceTypes() as $priceTypeDefinition) {
if ($priceTypeDefinition->hasType($type)) {
foreach ($priceTypeDefinition->withBehaviors() as $behavior) {
if ($behavior instanceof $behaviorClassWrapper) {
return $behavior;
}
}
}
}
throw new BehaviorNotFoundException('Behaviour was not found');
}
public function getBehaviors(string $behaviorClassWrapper): \Generator
{
foreach ($this->tariffTypes as $tariffType) {
foreach ($tariffType->withBehaviors() as $behavior) {
if ($behavior instanceof $behaviorClassWrapper) {
yield $behavior;
}
}
}
foreach ($this->priceTypes() as $priceTypeDefinition) {
foreach ($priceTypeDefinition->withBehaviors() as $behavior) {
if ($behavior instanceof $behaviorClassWrapper) {
yield $behavior;
}
}
}
}
/**
* @param string $behaviorClassWrapper The behavior class to match
* @return \Generator<int, BehaviorInterface>
* @throws \InvalidArgumentException When behavior class is invalid
*/
private function findBehaviorsInCollection(iterable $collection, string $behaviorClassWrapper): \Generator
{
foreach ($collection as $behavior) {
if ($behavior instanceof $behaviorClassWrapper) {
yield $behavior;
}
}
}
/**
* @param string $type - full type like 'overuse,lb_capacity_unit'
* @param string $behaviorClassWrapper
* @return BehaviorInterface
* @throws BehaviorNotFoundException
* @throws \InvalidArgumentException When parameters are invalid
*/
public function getBehavior(string $type, string $behaviorClassWrapper): BehaviorInterface
{
if (empty($type)) {
throw new \InvalidArgumentException('Type cannot be empty');
}
if (!class_exists($behaviorClassWrapper)) {
throw new \InvalidArgumentException(
sprintf('Behavior class "%s" does not exist', $behaviorClassWrapper)
);
}
$type = $this->convertStringTypeToType($type);
foreach ($this->priceTypes() as $priceTypeDefinition) {
if ($priceTypeDefinition->hasType($type)) {
foreach ($this->findBehaviorsInCollection($priceTypeDefinition->withBehaviors(), $behaviorClassWrapper) as $behavior) {
return $behavior;
}
}
}
throw new BehaviorNotFoundException(
sprintf('Behavior of class "%s" not found for type "%s"', $behaviorClassWrapper, $type)
);
}
/**
* @param string $behaviorClassWrapper The behavior class to match
* @return \Generator<int, BehaviorInterface>
* @throws \InvalidArgumentException When behavior class is invalid
*/
public function getBehaviors(string $behaviorClassWrapper): \Generator
{
if (!class_exists($behaviorClassWrapper)) {
throw new \InvalidArgumentException(
sprintf('Behavior class "%s" does not exist', $behaviorClassWrapper)
);
}
foreach ($this->tariffTypes as $tariffType) {
yield from $this->findBehaviorsInCollection($tariffType->withBehaviors(), $behaviorClassWrapper);
}
foreach ($this->priceTypes() as $priceTypeDefinition) {
yield from $this->findBehaviorsInCollection($priceTypeDefinition->withBehaviors(), $behaviorClassWrapper);
}
}
🧰 Tools
🪛 GitHub Check: PHP 8.3

[failure] 104-104: UndefinedClass
src/product/BillingRegistry.php:104:22: UndefinedClass: Class, interface or enum named hiqdev\billing\registry\behavior\TariffTypeBehaviorCollection does not exist (see https://psalm.dev/019)


public function getAggregate(string $type): AggregateInterface
{
$type = $this->convertStringTypeToType($type);

foreach ($this->priceTypes() as $priceTypeDefinition) {
if ($priceTypeDefinition->hasType($type)) {
return $priceTypeDefinition->getAggregate();
}
}

throw new AggregateNotFoundException('Aggregate was not found');
}
Comment on lines +164 to +175
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add import for AggregateInterface and improve error message.

The AggregateInterface is used but not imported. Also, enhance the error message for better debugging.

Apply these changes:

  1. Add the missing import at the top of the file:
use hiqdev\php\billing\product\AggregateInterface;
  1. Improve the error message:
-throw new AggregateNotFoundException('Aggregate was not found');
+throw new AggregateNotFoundException(
+    sprintf("Aggregate not found for type '%s'", $type)
+);

}
13 changes: 13 additions & 0 deletions src/product/BillingRegistryInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php declare(strict_types=1);

namespace hiqdev\php\billing\product;

use hiqdev\php\billing\product\price\PriceTypeDefinition;

interface BillingRegistryInterface
{
/**
* @return PriceTypeDefinition[]
*/
public function priceTypes(): \Generator;
}
8 changes: 8 additions & 0 deletions src/product/DocumentRepresentationInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php declare(strict_types=1);

namespace hiqdev\php\billing\product;

interface DocumentRepresentationInterface
{

}
8 changes: 8 additions & 0 deletions src/product/Domain/Model/TariffTypeInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php declare(strict_types=1);

namespace hiqdev\php\billing\product\Domain\Model;

interface TariffTypeInterface
{

}
8 changes: 8 additions & 0 deletions src/product/Domain/Model/Unit/FractionUnitInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php declare(strict_types=1);

namespace hiqdev\php\billing\product\Domain\Model\Unit;

interface FractionUnitInterface
{

}
12 changes: 12 additions & 0 deletions src/product/Domain/Model/Unit/UnitInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php declare(strict_types=1);

namespace hiqdev\php\billing\product\Domain\Model\Unit;

use \hiqdev\php\units\UnitInterface as BaseUnitInterface;

interface UnitInterface
{
public function createExternalUnit(): BaseUnitInterface;

public function fractionUnit(): FractionUnitInterface;
}
10 changes: 10 additions & 0 deletions src/product/GTypeInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php declare(strict_types=1);

namespace hiqdev\php\billing\product;

interface GTypeInterface
{
public function name(): string;

public function equals(GTypeInterface $otherGType): bool;
}
23 changes: 23 additions & 0 deletions src/product/InvoiceDescriptionsBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php declare(strict_types=1);

namespace hiqdev\php\billing\product;

class InvoiceDescriptionsBuilder
{
private BillingRegistry $registry;

public function __construct(BillingRegistry $registry)
{
$this->registry = $registry;
}

public function build(): array
{
$descriptions = [];
foreach ($this->registry->priceTypes() as $priceType) {
$descriptions[] = $priceType->representInvoice();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Integration verification reveals potential issues

The codebase analysis shows a potential mismatch in the implementation. While InvoiceDescriptionsBuilder calls representInvoice() on price types, the actual implementation shows:

  • BillingRegistry::priceTypes() exists but returns a Generator
  • Price types are managed through PriceTypesCollection and PriceTypeDefinition
  • PriceTypeDefinition has invoiceRepresentation() method, but no representInvoice() method
  • No class or interface was found implementing the required representInvoice() method

This suggests that either:

  1. The representInvoice() method is missing from the implementation
  2. There's a naming mismatch between the expected and actual method names
  3. The implementation might be using a different pattern than what's being called
🔗 Analysis chain

Verify integration with dependent components.

Let's ensure the required methods exist in the dependent classes.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if BillingRegistry and price types have required methods

# Check BillingRegistry for priceTypes method
ast-grep --pattern 'class BillingRegistry {
  $$$
  public function priceTypes() {
    $$$
  }
  $$$
}'

# Check for classes implementing representInvoice method
ast-grep --pattern 'class $_ {
  $$$
  public function representInvoice() {
    $$$
  }
  $$$
}'

Length of output: 204


Script:

#!/bin/bash
# Let's try a different approach using ripgrep to find these methods

# Search for BillingRegistry class and priceTypes method
echo "=== Searching for BillingRegistry and priceTypes ==="
rg "class BillingRegistry" -A 10
rg "function priceTypes"

# Search for representInvoice method
echo -e "\n=== Searching for representInvoice method ==="
rg "function representInvoice"

# Search for any interface that might define representInvoice
echo -e "\n=== Searching for interfaces that might define representInvoice ==="
rg "interface.*Price" -A 10

Length of output: 4025


Script:

#!/bin/bash
# Let's check for TariffType class and its relationship with representInvoice

# Search for TariffType class/interface
echo "=== Searching for TariffType definition ==="
rg "class TariffType|interface TariffType" -A 10

# Search for files that might contain representInvoice in price or tariff related files
echo -e "\n=== Searching for representInvoice in price/tariff related files ==="
fd -e php | grep -i "price\|tariff" | xargs rg "representInvoice"

# Let's also check if there's an interface that might define this method
echo -e "\n=== Searching for interfaces that might define invoice representation ==="
rg "interface.*Invoice" -A 10

Length of output: 1992


Script:

#!/bin/bash
# Let's check PriceTypesCollection and its relationship with invoice representation

# Search for PriceTypesCollection
echo "=== Searching for PriceTypesCollection ==="
rg "class PriceTypesCollection" -A 10

# Search for any invoice-related methods in price types
echo -e "\n=== Searching for invoice-related methods in price types ==="
rg -i "invoice.*function|function.*invoice" -A 3

# Let's also check if there are any invoice-related traits
echo -e "\n=== Searching for invoice-related traits ==="
rg "trait.*Invoice" -A 10

Length of output: 1407


return $descriptions;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling and consider using array_map.

The build method could benefit from several improvements:

  1. Error handling for potential failures in representInvoice()
  2. Using array_map for a more functional approach
  3. PHPDoc explaining the return format
+/**
+ * Builds an array of invoice descriptions from registered price types.
+ *
+ * @return array<int, mixed> Array of invoice descriptions
+ * @throws \RuntimeException When failed to represent a price type
+ */
 public function build(): array
 {
-    $descriptions = [];
-    foreach ($this->registry->priceTypes() as $priceType) {
-        $descriptions[] = $priceType->representInvoice();
+    try {
+        return array_map(
+            static fn($priceType) => $priceType->representInvoice(),
+            $this->registry->priceTypes()
+        );
+    } catch (\Throwable $e) {
+        throw new \RuntimeException(
+            'Failed to build invoice descriptions: ' . $e->getMessage(),
+            0,
+            $e
+        );
     }
-
-    return $descriptions;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public function build(): array
{
$descriptions = [];
foreach ($this->registry->priceTypes() as $priceType) {
$descriptions[] = $priceType->representInvoice();
}
return $descriptions;
}
/**
* Builds an array of invoice descriptions from registered price types.
*
* @return array<int, mixed> Array of invoice descriptions
* @throws \RuntimeException When failed to represent a price type
*/
public function build(): array
{
try {
return array_map(
static fn($priceType) => $priceType->representInvoice(),
$this->registry->priceTypes()
);
} catch (\Throwable $e) {
throw new \RuntimeException(
'Failed to build invoice descriptions: ' . $e->getMessage(),
0,
$e
);
}
}

}
12 changes: 12 additions & 0 deletions src/product/ParentNodeDefinitionInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php declare(strict_types=1);

namespace hiqdev\php\billing\product;

use hiqdev\php\billing\product\behavior\BehaviorCollectionInterface;

interface ParentNodeDefinitionInterface
{
public function withBehaviors(): BehaviorCollectionInterface;

public function hasBehavior(string $behaviorClassName): bool;
}
10 changes: 10 additions & 0 deletions src/product/ProductInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php declare(strict_types=1);

namespace hiqdev\php\billing\product;

interface ProductInterface
{
public function toProductName(): string;

public function label(): string;
}
68 changes: 68 additions & 0 deletions src/product/TariffTypeDefinition.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php declare(strict_types=1);

namespace hiqdev\php\billing\product;

use hiqdev\php\billing\product\behavior\BehaviorTariffTypeCollection;
use hiqdev\php\billing\product\Domain\Model\TariffTypeInterface;
use hiqdev\php\billing\product\price\PriceTypeDefinitionCollection;
use hiqdev\php\billing\product\price\PriceTypeDefinitionFactory;

class TariffTypeDefinition implements TariffTypeDefinitionInterface
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Resolve Psalm’s missing template param error.

Static analysis indicates that TariffTypeDefinition is missing a template parameter expected by TariffTypeDefinitionInterface. Consult the interface definition to determine the correct template syntax, for example:

-class TariffTypeDefinition implements TariffTypeDefinitionInterface
+/**
+ * @template TWhatever
+ * @implements TariffTypeDefinitionInterface<TWhatever>
+ */
+class TariffTypeDefinition implements TariffTypeDefinitionInterface
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
class TariffTypeDefinition implements TariffTypeDefinitionInterface
/**
* @template TWhatever
* @implements TariffTypeDefinitionInterface<TWhatever>
*/
class TariffTypeDefinition implements TariffTypeDefinitionInterface
🧰 Tools
🪛 GitHub Check: PHP 8.3

[failure] 10-10:
src/product/TariffTypeDefinition.php:10:39: MissingTemplateParam: hiqdev\php\billing\product\TariffTypeDefinition has missing template params when extending hiqdev\php\billing\product\TariffTypeDefinitionInterface, expecting 1 (see https://psalm.dev/182)

🪛 GitHub Actions: Psalm Static Analysis

[error] 10-10: MissingTemplateParam: hiqdev\php\billing\product\TariffTypeDefinition has missing template params when extending hiqdev\php\billing\product\TariffTypeDefinitionInterface, expecting 1 (see https://psalm.dev/182)

{
private ProductInterface $product;

private PriceTypeDefinitionCollection $prices;

private BehaviorTariffTypeCollection $behaviorCollection;

public function __construct(private readonly TariffTypeInterface $tariffType)
{
$this->prices = new PriceTypeDefinitionCollection($this, new PriceTypeDefinitionFactory());
$this->behaviorCollection = new BehaviorTariffTypeCollection($this, $tariffType);
}

public function tariffType(): TariffTypeInterface
{
return $this->tariffType;
}

public function ofProduct(ProductInterface $product): TariffTypeDefinitionInterface
{
$this->product = $product;

return $this;
}

public function setPricesSuggester(string $suggesterClass): TariffTypeDefinitionInterface
{
// Validate or store the suggester class
return $this;
}

public function withPrices(): PriceTypeDefinitionCollection
{
return $this->prices;
}

public function withBehaviors(): BehaviorTariffTypeCollection
{
return $this->behaviorCollection;
}

public function hasBehavior(string $behaviorClassName): bool
{
foreach ($this->behaviorCollection as $behavior) {
if ($behavior instanceof $behaviorClassName) {
return true;
}
}

return false;
}

public function end(): TariffTypeDefinitionInterface
{
// Validate the TariffType and lock its state
return $this;
}
Comment on lines +72 to +76
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Implement validation in the end() method

The end() method contains a comment about validating the tariff type and locking its state, but doesn't implement any actual validation logic. This should be implemented to ensure tariff types are properly configured before use.

 public function end(): TariffTypeDefinitionInterface
 {
-    // Validate the TariffType and lock its state
+    // Validate required properties are set
+    if (!isset($this->product)) {
+        throw new \LogicException('Product must be set using ofProduct() method');
+    }
+    
+    // Validate prices configuration is complete
+    if ($this->prices->isEmpty()) {
+        throw new \LogicException('At least one price type must be defined');
+    }
+    
+    // Lock the state to prevent further modifications
+    $this->locked = true;
     return $this;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public function end(): TariffTypeDefinitionInterface
{
// Validate the TariffType and lock its state
return $this;
}
public function end(): TariffTypeDefinitionInterface
{
// Validate required properties are set
if (!isset($this->product)) {
throw new \LogicException('Product must be set using ofProduct() method');
}
// Validate prices configuration is complete
if ($this->prices->isEmpty()) {
throw new \LogicException('At least one price type must be defined');
}
// Lock the state to prevent further modifications
$this->locked = true;
return $this;
}

}
11 changes: 11 additions & 0 deletions src/product/TariffTypeDefinitionFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php declare(strict_types=1);

namespace hiqdev\php\billing\product;

class TariffTypeDefinitionFactory
{
public static function create(TariffTypeInterface $tariffType): TariffTypeDefinition
{
return new TariffTypeDefinition($tariffType);
}
}
26 changes: 26 additions & 0 deletions src/product/TariffTypeDefinitionInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php declare(strict_types=1);

namespace hiqdev\php\billing\product;

use hiqdev\php\billing\product\Domain\Model\TariffTypeInterface;
use hiqdev\php\billing\product\price\PriceTypeDefinitionCollectionInterface;

/**
* @template T of PriceTypeDefinitionCollectionInterface
*/
interface TariffTypeDefinitionInterface extends ParentNodeDefinitionInterface
{
public function tariffType(): TariffTypeInterface;

public function ofProduct(ProductInterface $product): self;

public function setPricesSuggester(string $suggesterClass): self;

/**
* @return PriceTypeDefinitionCollectionInterface
* @psalm-return T
*/
public function withPrices(): PriceTypeDefinitionCollectionInterface;

public function end(): static;
}
Loading
Loading