diff --git a/services/nginx/app/modules/goals/classes/goals.php b/services/nginx/app/modules/goals/classes/goals.php new file mode 100644 index 00000000..0d6c9bbd --- /dev/null +++ b/services/nginx/app/modules/goals/classes/goals.php @@ -0,0 +1,62 @@ +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); + } +} diff --git a/services/nginx/app/modules/goals/classes/goals_criteria.php b/services/nginx/app/modules/goals/classes/goals_criteria.php index b85be7c2..8918af60 100644 --- a/services/nginx/app/modules/goals/classes/goals_criteria.php +++ b/services/nginx/app/modules/goals/classes/goals_criteria.php @@ -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(); } } \ No newline at end of file diff --git a/services/nginx/app/modules/goals/classes/goals_criteria_products.php b/services/nginx/app/modules/goals/classes/goals_criteria_products.php index 090b98af..d4ef6346 100644 --- a/services/nginx/app/modules/goals/classes/goals_criteria_products.php +++ b/services/nginx/app/modules/goals/classes/goals_criteria_products.php @@ -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; } diff --git a/services/nginx/app/objects/order_items_o.php b/services/nginx/app/objects/order_items_o.php index 81994472..6fa44578 100644 --- a/services/nginx/app/objects/order_items_o.php +++ b/services/nginx/app/objects/order_items_o.php @@ -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; + } } \ No newline at end of file diff --git a/services/nginx/app/routes/exampleRoute.php b/services/nginx/app/routes/exampleRoute.php index 1f4b4d81..e09d68cf 100644 --- a/services/nginx/app/routes/exampleRoute.php +++ b/services/nginx/app/routes/exampleRoute.php @@ -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!']); }); } diff --git a/services/nginx/app/tests/goalsModule/goalsTest.php b/services/nginx/app/tests/goalsModule/goalsTest.php new file mode 100644 index 00000000..8590b9c6 --- /dev/null +++ b/services/nginx/app/tests/goalsModule/goalsTest.php @@ -0,0 +1,47 @@ +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()); + } +}