Introduce goals module with methods for result, percentage, and remaining calculations
- Add `goals` class for managing goal metrics and calculations. - Extend `goals_criteria` with timeframe initialization and validation. - Implement `getListByCriteria` in `order_items_o.php` for product-based goal evaluation. - Add unit tests for `goals` to validate
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace goals\classes;
|
||||
|
||||
use goals\helpers\goals_criteria_type;
|
||||
|
||||
/**
|
||||
* Class goals
|
||||
* Main class for the goals module.
|
||||
*/
|
||||
class goals
|
||||
{
|
||||
/**
|
||||
* The goals criteria
|
||||
* @var goals_criteria $criteria
|
||||
*/
|
||||
public goals_criteria $criteria;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param goals_criteria|null $criteria
|
||||
*/
|
||||
public function __construct(?goals_criteria $criteria = null)
|
||||
{
|
||||
$this->criteria = $criteria ?? new goals_criteria();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current result for the goal based on the criteria.
|
||||
* @return int|float
|
||||
*/
|
||||
public function getResult(): int|float
|
||||
{
|
||||
return match ($this->criteria->type) {
|
||||
goals_criteria_type::PRODUCT => $this->criteria->products->count($this->criteria),
|
||||
default => 0,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the percentage completion of the goal.
|
||||
* @return float
|
||||
*/
|
||||
public function getPercentage(): float
|
||||
{
|
||||
if ($this->criteria->target <= 0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
return ($this->getResult() / $this->criteria->target) * 100;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the remaining value to reach the goal target.
|
||||
* @return int|float
|
||||
*/
|
||||
public function getRemaining(): int|float
|
||||
{
|
||||
$remaining = $this->criteria->target - $this->getResult();
|
||||
return max(0, $remaining);
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,8 @@ class goals_criteria implements goals_criteria_i
|
||||
$this->users = new goals_criteria_users();
|
||||
$this->departments = new goals_criteria_departments();
|
||||
$this->products = new goals_criteria_products();
|
||||
$this->start = new \DateTime();
|
||||
$this->end = new \DateTime();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,17 +18,19 @@ class goals_criteria_products implements goals_criteria_products_i
|
||||
public function count(goals_criteria $goal): int
|
||||
{
|
||||
$count = 0;
|
||||
$datetime_start = $goal->start ?? new \DateTime();
|
||||
$datetime_end = $goal->end ?? new \DateTime();
|
||||
|
||||
$items = order_items_o::getListByCriteria(
|
||||
department_ids: $goal->departments->listIDs(),
|
||||
product_ids: $this->listIDs(),
|
||||
customer_numbers: $goal->users->listCustomerNumbers(),
|
||||
datetime_start: $goal->start,
|
||||
datetime_end: $goal->end,
|
||||
datetime_start: $datetime_start,
|
||||
datetime_end: $datetime_end,
|
||||
);
|
||||
foreach ($items as $item) {
|
||||
$count += $item->quantity;
|
||||
$count += (int)$item->quantity->value();
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
|
||||
@@ -278,4 +278,65 @@ class order_items_o extends db
|
||||
$response->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of order items based on the criteria.
|
||||
* @param int[] $department_ids
|
||||
* @param int[] $product_ids
|
||||
* @param int[] $customer_numbers
|
||||
* @param \DateTime|null $datetime_start
|
||||
* @param \DateTime|null $datetime_end
|
||||
* @return order_items_o[]
|
||||
*/
|
||||
public static function getListByCriteria(
|
||||
array $department_ids = [],
|
||||
array $product_ids = [],
|
||||
array $customer_numbers = [],
|
||||
\DateTime $datetime_start = null,
|
||||
\DateTime $datetime_end = null
|
||||
): array
|
||||
{
|
||||
global $db;
|
||||
$sql = "SELECT oi.* FROM order_items oi
|
||||
JOIN orders o ON oi.order_id = o.id
|
||||
WHERE oi.deleted_at IS NULL AND o.deleted_at IS NULL";
|
||||
|
||||
if (!empty($department_ids)) {
|
||||
$sql .= " AND o.department_id IN (" . implode(',', array_map('intval', $department_ids)) . ")";
|
||||
}
|
||||
|
||||
if (!empty($product_ids)) {
|
||||
$sql .= " AND oi.product_id IN (" . implode(',', array_map('intval', $product_ids)) . ")";
|
||||
}
|
||||
|
||||
if (!empty($customer_numbers)) {
|
||||
// customer_id in orders table matches customer_number in users table (at least it is used this way in many places)
|
||||
$sql .= " AND o.customer_id IN (" . implode(',', array_map('intval', $customer_numbers)) . ")";
|
||||
}
|
||||
|
||||
if ($datetime_start) {
|
||||
$sql .= " AND o.created_at >= '" . $datetime_start->format('Y-m-d H:i:s') . "'";
|
||||
}
|
||||
|
||||
if ($datetime_end) {
|
||||
$sql .= " AND o.created_at <= '" . $datetime_end->format('Y-m-d H:i:s') . "'";
|
||||
}
|
||||
|
||||
$result = $db->query($sql);
|
||||
$items = [];
|
||||
if ($result && $result->num_rows > 0) {
|
||||
while ($row = $result->fetch_assoc()) {
|
||||
$item = new order_items_o();
|
||||
$item->id = (int)$row['id'];
|
||||
// We don't call getObjectProperties() here to avoid many SQL queries,
|
||||
// but the count() method in goals_criteria_products only uses $item->quantity
|
||||
// Let's check if we can populate quantity directly.
|
||||
// Actually, goals_criteria_products uses $item->quantity.
|
||||
// In this codebase, properties are often object_property objects.
|
||||
$item->getObjectProperties();
|
||||
$items[] = $item;
|
||||
}
|
||||
}
|
||||
return $items;
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,8 @@ namespace routes;
|
||||
|
||||
use classes\entra;
|
||||
use classes\redis;
|
||||
use goals\classes\goals;
|
||||
use goals\classes\goals_criteria;
|
||||
use objects\departments_o;
|
||||
use traits\route_t;
|
||||
|
||||
@@ -49,8 +51,7 @@ class exampleRoute
|
||||
|
||||
$this->get('/debug', function () {
|
||||
global $response;
|
||||
$entra = new entra();
|
||||
$GraphClient = $entra->getGraphClient();
|
||||
$goal = (new goals());
|
||||
$response->success(['message' => 'Debugging route!']);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace tests\goalsModule;
|
||||
|
||||
use goals\classes\goals;
|
||||
use goals\classes\goals_criteria;
|
||||
use goals\helpers\goals_criteria_type;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class goalsTest extends TestCase
|
||||
{
|
||||
public function testGoalsInitialization()
|
||||
{
|
||||
$goal = new goals();
|
||||
$this->assertInstanceOf(goals::class, $goal);
|
||||
$this->assertInstanceOf(goals_criteria::class, $goal->criteria);
|
||||
}
|
||||
|
||||
public function testGetResultDefault()
|
||||
{
|
||||
$goal = new goals();
|
||||
$this->assertEquals(0, $goal->getResult());
|
||||
}
|
||||
|
||||
public function testGetPercentage()
|
||||
{
|
||||
$goal = new goals();
|
||||
$goal->criteria->setTarget(100);
|
||||
|
||||
// Mocking getResult is hard without dependency injection or better structure,
|
||||
// but since we know it returns 0 by default:
|
||||
$this->assertEquals(0.0, $goal->getPercentage());
|
||||
|
||||
// Manually setting a result isn't possible as getResult() calculates it.
|
||||
// But we can check if it handles target 0 correctly.
|
||||
$goal->criteria->setTarget(0);
|
||||
$this->assertEquals(0.0, $goal->getPercentage());
|
||||
}
|
||||
|
||||
public function testGetRemaining()
|
||||
{
|
||||
$goal = new goals();
|
||||
$goal->criteria->setTarget(50);
|
||||
// Result is 0, so remaining should be 50
|
||||
$this->assertEquals(50, $goal->getRemaining());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user