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 69 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 40 commits
Commits
Show all changes
69 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
97f0d4f
HP-1751 tiny
VadymHrechukha Mar 21, 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
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
{
}
135 changes: 135 additions & 0 deletions src/product/BillingRegistry.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
<?php declare(strict_types=1);

namespace hiqdev\php\billing\product;

use hiqdev\billing\registry\invoice\RepresentationInterface;
use hiqdev\billing\registry\product\Aggregate;
use hiqdev\billing\registry\quantity\formatter\QuantityFormatterNotFoundException;
use hiqdev\billing\registry\quantity\FractionQuantityData;
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

Replace external references from hiqdev\billing\registry\... with internal interfaces if possible.
Based on your learnings, the hiqdev/php-billing repository should avoid depending on advancedhosters/billing-registry. Consider moving or replicating any needed definitions internally.

-use hiqdev\billing\registry\invoice\RepresentationInterface;
-use hiqdev\billing\registry\product\Aggregate;
-use hiqdev\billing\registry\quantity\formatter\QuantityFormatterNotFoundException;
-use hiqdev\billing\registry\quantity\FractionQuantityData;
+use hiqdev\php\billing\product\RepresentationInterface; // or your own interface
+use hiqdev\php\billing\product\Aggregate;               // or your own implementation
+use hiqdev\php\billing\product\QuantityFormatterNotFoundException; 
+use hiqdev\php\billing\product\FractionQuantityData;
📝 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
use hiqdev\billing\registry\invoice\RepresentationInterface;
use hiqdev\billing\registry\product\Aggregate;
use hiqdev\billing\registry\quantity\formatter\QuantityFormatterNotFoundException;
use hiqdev\billing\registry\quantity\FractionQuantityData;
use hiqdev\php\billing\product\RepresentationInterface; // or your own interface
use hiqdev\php\billing\product\Aggregate; // or your own implementation
use hiqdev\php\billing\product\QuantityFormatterNotFoundException;
use hiqdev\php\billing\product\FractionQuantityData;

use hiqdev\php\billing\product\behavior\BehaviorInterface;
use hiqdev\php\billing\product\behavior\BehaviorNotFoundException;
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

Remove dependencies on external packages.

These imports reference classes from the hiqdev\billing\registry namespace, which goes against the project requirement that hiqdev/php-billing should not depend on advancedhosters/billing-registry. This was previously noted in the learnings.

Replace these external dependencies with internal interfaces and implementations:

-use hiqdev\billing\registry\product\GType;
-use hiqdev\billing\registry\product\PriceType;
-use hiqdev\billing\registry\quantity\formatter\QuantityFormatterNotFoundException;
-use hiqdev\billing\registry\quantity\FractionQuantityData;
+use hiqdev\php\billing\product\GType;
+use hiqdev\php\billing\product\PriceType;
+use hiqdev\php\billing\product\formatter\QuantityFormatterNotFoundException;
+use hiqdev\php\billing\product\quantity\FractionQuantityData;

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): Aggregate
{
$type = $this->convertStringTypeToType($type);

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

throw new AggregateNotFoundException('Aggregate was not found');
}
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

⚠️ Potential issue

The Aggregate class appears to be missing from the codebase

The class Aggregate is imported from hiqdev\billing\registry\product\Aggregate namespace in both BillingRegistry.php and PriceTypeDefinition.php, but:

  • The class definition is not found in the codebase
  • It's not available in the vendor directory (which is not present in the sandbox)
  • The namespace suggests it should be in a package named hiqdev/billing-registry, but this package is not listed in composer dependencies

This could lead to runtime errors. You should either:

  • Add the missing package that contains this class to composer dependencies
  • Create the Aggregate class in the correct namespace if it's meant to be part of this package
  • Update the code to use a different class if the dependency was renamed or moved
🔗 Analysis chain

Validate existence of Aggregate class prior to usage.

Aggregate is imported from hiqdev\billing\registry\product\Aggregate, but static analysis flags it as undefined. Ensure the class is correctly included in composer or located in the repository. Otherwise, reference the correct namespace or rename as needed.


Let me search for more information about the Aggregate class.


Let me check if this is a dependency from composer by looking at composer.json.


Let me try one more search with proper escaping to find any references to this class.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Verify the Aggregate class existence 
rg "class Aggregate" -t php

Length of output: 130


Script:

#!/bin/bash
# Check for any file named Aggregate.php
fd Aggregate.php

# Check for any usage or import of Aggregate class
rg "use.*Aggregate" -t php

Length of output: 209


Script:

#!/bin/bash
# Check composer.json for the package
cat composer.json

# Also check if there are any other references to the Aggregate class
rg "hiqdev\\billing\\registry\\product\\Aggregate" -t php

Length of output: 3132


Script:

#!/bin/bash
# Search for the Aggregate class definition in vendor directory
fd -t f "Aggregate.php" vendor

# Search for references with proper escaping
rg -l "hiqdev\\\\billing\\\\registry\\\\product\\\\Aggregate"

Length of output: 248

🧰 Tools
🪛 GitHub Check: PHP 8.3

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

}
11 changes: 11 additions & 0 deletions src/product/BillingRegistryInterface.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;

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;
}
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;
}
154 changes: 154 additions & 0 deletions src/product/PriceTypeDefinition.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
<?php declare(strict_types=1);

namespace hiqdev\php\billing\product;

use hiqdev\php\billing\product\behavior\BehaviourPriceTypeDefinitionCollection;
use hiqdev\billing\registry\invoice\InvoiceRepresentationCollection;
use hiqdev\billing\registry\product\Aggregate;
use hiqdev\billing\registry\quantity\formatter\QuantityFormatterDefinition;
use hiqdev\billing\registry\quantity\formatter\QuantityFormatterFactory;
use hiqdev\billing\registry\quantity\FractionQuantityData;
use hiqdev\php\billing\product\Domain\Model\TariffTypeInterface;
use hiqdev\php\billing\product\Domain\Model\Unit\FractionUnitInterface;
use hiqdev\php\billing\product\Domain\Model\Unit\UnitInterface;
use hiqdev\php\billing\quantity\QuantityFormatterInterface;
use hiqdev\php\billing\type\TypeInterface;

class PriceTypeDefinition implements ParentNodeDefinitionInterface
{
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

Update property type to use internal interface.

This property uses a class from hiqdev\billing\registry which goes against the project requirement not to depend on external registry packages.

-    private InvoiceRepresentationCollection $invoiceCollection;
+    private DocumentRepresentationCollection $invoiceCollection;

Ensure that DocumentRepresentationCollection is defined within this project's namespace.

📝 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
{
<?php
namespace App\Product;
class PriceTypeDefinition
{
// Other properties...
- private InvoiceRepresentationCollection $invoiceCollection;
+ private DocumentRepresentationCollection $invoiceCollection;
// Rest of the class code...
}

private UnitInterface $unit;

private string $description;

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

Update instantiation to use internal implementation.

This creates an instance of an external package class.

-        $this->invoiceCollection = new InvoiceRepresentationCollection($this);
+        $this->invoiceCollection = new DocumentRepresentationCollection($this);

Ensure a compatible implementation exists within this project's namespace.

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

Validate or initialize $description.

Currently, $description may remain unset if a caller never invokes the description() method. Consider assigning a default value in the constructor or validating it before access to avoid uninitialized property errors.

private QuantityFormatterDefinition $quantityFormatterDefinition;

private InvoiceRepresentationCollection $invoiceCollection;

private BehaviourPriceTypeDefinitionCollection $behaviorCollection;

private Aggregate $aggregate;

public function __construct(
private readonly PriceTypeDefinitionCollection $parent,
private readonly TypeInterface $type,
TariffTypeInterface $tariffType,
) {
$this->invoiceCollection = new InvoiceRepresentationCollection($this);
$this->behaviorCollection = new BehaviourPriceTypeDefinitionCollection($this, $tariffType);

$this->init();
}

protected function init(): void
{
// Hook
}

public function unit(UnitInterface $unit): self
{
$this->unit = $unit;

return $this;
}

public function description(string $description): self
{
$this->description = $description;

return $this;
}

public function getDescription(): string
{
return $this->description;
}

/**
* @param string $formatterClass
* @param null|FractionUnitInterface|string $fractionUnit
* @return $this
*/
public function quantityFormatter(string $formatterClass, $fractionUnit = null): self
{
// TODO: check if formatterClass exists
$this->quantityFormatterDefinition = new QuantityFormatterDefinition($formatterClass, $fractionUnit);

return $this;
}

public function createQuantityFormatter(
FractionQuantityData $data,
): QuantityFormatterInterface {
return QuantityFormatterFactory::create(
$this->getUnit()->createExternalUnit(),
$this->quantityFormatterDefinition,
$data,
);
}
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

Guard against uninitialized $unit in createQuantityFormatter.

If unit() is never called before invoking createQuantityFormatter(), $this->unit will be uninitialized. Add an early check or default assignment to prevent runtime errors.


public function end(): PriceTypeDefinitionCollection
{
// Validate the PriceType and lock its state
return $this->parent;
}

public function documentRepresentation(): InvoiceRepresentationCollection
{
return $this->invoiceCollection;
}

public function measuredWith(\hiqdev\billing\registry\measure\RcpTrafCollector $param): self
{
return $this;
}
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

🛠️ Refactor suggestion

Remove unused measuredWith method

The measuredWith method in PriceTypeDefinition.php is not used anywhere in the codebase, and its implementation is a no-op that just returns $this. Since both the method and its parameter type RcpTrafCollector are only referenced in this single location, it's safe to remove this unused method.

🔗 Analysis chain

Implement measuredWith method or remove it.

The measuredWith method is currently a no-op. Either implement the measurement logic or remove the method if it's not needed.

Let me check if this method is used elsewhere:


Let me try to find any usage of this method or the collector class it depends on.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for uses of measuredWith method
rg "->measuredWith\(" --type php

Length of output: 439


Script:

#!/bin/bash
# Search for uses of measuredWith method with proper escaping
rg "measuredWith" --type php

# Also check for any references to RcpTrafCollector class
rg "RcpTrafCollector" --type php

Length of output: 327


public function type(): TypeInterface
{
return $this->type;
}

public function hasType(TypeInterface $type): bool
{
return $this->type->equals($type);
}

public function getUnit(): UnitInterface
{
return $this->unit;
}

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

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

return false;
}

/**
* це параметер визначає агрегатну функцію яка застосовується для щоденно записаних ресурсів щоб визнизначти
* місячне споживання за яке потрібно пробілити клієнта
*
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

Translate documentation to English

The PHPDoc comment is written in a non-English language. For better maintainability and collaboration, documentation should be in English.

Apply this diff:

-     * це параметер визначає агрегатну функцію яка застосовується для щоденно записаних ресурсів щоб визнизначти
-     * місячне споживання за яке потрібно пробілити клієнта
+     * This parameter defines the aggregate function that is applied to daily recorded resources
+     * to determine the monthly consumption for which the client needs to be billed
📝 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
* це параметер визначає агрегатну функцію яка застосовується для щоденно записаних ресурсів щоб визнизначти
* місячне споживання за яке потрібно пробілити клієнта
*
* This parameter defines the aggregate function that is applied to daily recorded resources
* to determine the monthly consumption for which the client needs to be billed
*

* @param Aggregate $aggregate
* @return self
*/
public function aggregation(Aggregate $aggregate): self
{
$this->aggregate = $aggregate;

return $this;
}

public function getAggregate(): Aggregate
{
return $this->aggregate;
}
}
Loading
Loading