Files
api/services/nginx/app/objects/department_daily_reports_o.php
T

840 lines
35 KiB
PHP

<?php
namespace objects;
use classes\db;
use classes\object_property;
use Exception;
use traits\db_object_t;
class department_daily_reports_o extends db
{
use db_object_t;
/**
* The id of the department
* @var object_property $department_id
*/
public object_property $department_id;
/**
* The cm3 of water used in the department today
* @var object_property $water_usage
*/
public object_property $water_usage;
/**
* The cm3 of water used in the department today
* @var object_property $water_usage_morning
*/
public object_property $water_usage_morning;
/**
* Any notes for the report
* @var object_property $notes
*/
public object_property $notes;
/**
* The user (id) that filled the report
* @var object_property $filled_by
*/
public object_property $filled_by;
/**
* The timestamp of when the object was created
* @var object_property $created_at
*/
public object_property $created_at;
public function structure(): void
{
$this->setTable('department_daily_reports');
}
public function asArray(): array
{
return [
'id' => (int)$this->id,
'department_id' => (int)$this->department_id->value(),
'water_usage' => (int)$this->water_usage->value(),
'water_usage_morning' => (int)$this->water_usage_morning->value(),
'notes' => (string)$this->notes->value(),
'filled_by' => (int)$this->filled_by->value(),
'created_at' => (string)$this->created_at->value(),
];
}
/**
* Get the amount of products sold on a given date
* @note This does not count products from removed orders, only products from orders that are still active
* @param string $date The date to get the report for (YYYY-MM-DD)
* @param int $department_id The id of the department
* @param int $product_id The id of the product
* @param string|null $date_to (Optional) The end date to get the report for (YYYY-MM-DD). If provided, the report will be for the date range between $date and $date_to
* @return int The amount of products sold on the given date (Not counting products from removed orders)
* @throws Exception
*/
public function getProductsSoldOnDate(string $date, int $department_id, int $product_id, string $date_to = null): int
{
return self::methodCacheWithParameters(__METHOD__, func_get_args(), 120, function () use ($date, $department_id, $product_id, $date_to) {
global /** @var db $db */
$db;
// If the date_to is null, set it to the date
if ($date_to === null) {
$date_to = $date; // Making the report for an entire day
}
// Method cache for 2 minutes
$department = (new departments_o())->select($department_id);
$department->requireSelected();
return $department->getTotalAddonsSoldInDepartment([$product_id], $date, $date_to, $department_id);
});
}
/**
* Get the amount of products sold, where the product would be applicable as an addon
* E.g. if the product is "Cheese" this would return the amount of "Pizza" sold
* @note This does not count products from removed orders, only products from orders that are still active
* @param string $date The date to get the report for (YYYY-MM-DD)
* @param int $department_id The id of the department
* @param int $product_id The id of the product (the addon)
* @param string|null $date_to (Optional) The end date to get the report for (YYYY-MM-DD). If provided, the report will be for the date range between $date and $date_to
* @return int The amount of products sold on the given date (Not counting products from removed orders)
* @throws Exception
*/
public function getProductsSoldWithAddonApplicable(string $date, int $department_id, int $product_id, string $date_to = null): int
{
global /** @var db $db */
$db;
// Get all the addons for the product
$addons = (new product_options_o())->getOptionProducts($product_id);
$addons = array_map(function ($addon) {
return $addon['product_id'];
}, $addons);
$addons = implode(',', $addons);
// If the date_to is null, set it to the date
if ($date_to === null) {
$date_to = $date; // Making the report for an entire day
}
$department = (new departments_o())->select($department_id);
return $department->getTotalMaxAddonsInDepartment([$product_id], $date, $date_to, $department_id);
}
/**
* Get the amount of products sold on a given date
* @note This does not count products from removed orders, only products from orders that are still active
* @param string $date The date to get the report for (YYYY-MM-DD)
* @param int $department_id The id of the department
* @param string|null $date_to (Optional) The end date to get the report for (YYYY-MM-DD). If provided, the report will be for the date range between $date and $date_to
* @return int The amount of products sold on the given date (Not counting products from removed orders)
*/
public function getTransactionProductsOnDateCount(string $date, int $department_id, string $date_to = null): int
{
return self::methodCacheWithParameters(__METHOD__, func_get_args(), 120, function() use ($date, $department_id, $date_to) {
global /** @var db $db */
$db;
// If the date_to is null, set it to the date
if ($date_to === null) {
$date_to = $date; // Making the report for an entire day
}
// Set the date time to cover the entire day
$date = date('Y-m-d 00:00:00', strtotime($date));
$date_to = date('Y-m-d 23:59:59', strtotime($date_to));
$conn = $db->conn();
$stmt = $conn->prepare(
'SELECT SUM(oi.quantity) as amount FROM orders o
JOIN order_items oi ON o.id = oi.order_id
WHERE o.department_id = ? AND DATE(o.created_at) BETWEEN ? AND ? AND o.deleted_at IS NULL AND oi.deleted_at IS NULL'
);
if ($stmt) {
$stmt->bind_param('iss', $department_id, $date, $date_to); // Bind parameters (i = integer, s = string)
$stmt->execute();
$result = $stmt->get_result(); // Get the result set from the statement
$data = $result->fetch_assoc(); // Fetch the result as an associative array
// Access the "amount" field
$amount = $data['amount'];
$stmt->close(); // Close the statement
} else {
// Handle query preparation error
die('Query preparation failed: ' . $conn->error);
}
return (int)$amount;
});
}
/**
* Get the amount of transactions on a given date
* @note This does not count products from removed orders, only products from orders that are still active
* @param string $date The date to get the report for (YYYY-MM-DD)
* @param int $department_id The id of the department
* @param string|null $date_to (Optional) The end date to get the report for (YYYY-MM-DD). If provided, the report will be for the date range between $date and $date_to
* @return int The amount of transactions on the given date (Not counting products from removed orders)
*/
public function getTransactionsOnDateCount(string $date, int $department_id, string $date_to = null): int
{
return self::methodCacheWithParameters(__METHOD__, func_get_args(), 120, function() use ($date, $department_id, $date_to) {
global /** @var db $db */
$db;
// If the date_to is null, set it to the date
if ($date_to === null) {
$date_to = $date; // Making the report for an entire day
}
// Set the date time to cover the entire day
$date = date('Y-m-d 00:00:00', strtotime($date));
$date_to = date('Y-m-d 23:59:59', strtotime($date_to));
$conn = $db->conn();
// Make sure only to count orders with at least one non-deleted order item
$stmt = $conn->prepare(
'SELECT COUNT(DISTINCT o.id) as amount FROM orders o
JOIN order_items oi ON o.id = oi.order_id
WHERE o.department_id = ? AND DATE(o.created_at) BETWEEN ? AND ? AND o.deleted_at IS NULL AND oi.deleted_at IS NULL'
);
if ($stmt) {
$stmt->bind_param('iss', $department_id, $date, $date_to); // Bind parameters (i = integer, s = string)
$stmt->execute();
$result = $stmt->get_result(); // Get the result set from the statement
$data = $result->fetch_assoc(); // Fetch the result as an associative array
// Access the "amount" field
$amount = $data['amount'];
$stmt->close(); // Close the statement
} else {
// Handle query preparation error
die('Query preparation failed: ' . $conn->error);
}
return (int)$amount;
});
}
/**
* Select the department daily report for today
* @param int $department_id The id of the department
* @return department_daily_reports_o The department daily report
* @throws Exception If a department daily report does not exist for today
* @throws Exception If the object was not selected
*/
public function selectDepartmentDailyReport(int $department_id): department_daily_reports_o
{
global $db;
if (!$this->doesDepartmentDailyReportExist($department_id)) {
throw new Exception('Department daily report does not exist');
}
$conn = $db->conn();
$stmt = $conn->prepare(
'SELECT id FROM department_daily_reports o
WHERE o.department_id = ? AND DATE(o.created_at) = CURDATE()'
);
// Check if the statement was prepared successfully
if ($stmt) {
$stmt->bind_param('i', $department_id); // Bind parameters (i = integer, s = string)
$stmt->execute();
$result = $stmt->get_result(); // Get the result set from the statement
$data = $result->fetch_assoc(); // Fetch the result as an associative array
// Access the "amount" field
$id = $data['id'];
$stmt->close(); // Close the statement
} else {
// Handle query preparation error
die('Query preparation failed: ' . $conn->error);
}
$this->select($id);
return $this;
}
/**
* Check if a department daily report exists for today
* @param int $department_id The id of the department
* @param string $date The date to check for (YYYY-MM-DD)
* @return bool True if the report exists, false otherwise
*/
public function doesDepartmentDailyReportExist(int $department_id, string $date = 'today'): bool
{
global $db;
if ($date === 'today') {
$date = date('Y-m-d');
}
$conn = $db->conn();
$stmt = $conn->prepare(
'SELECT COUNT(*) as amount FROM department_daily_reports o
WHERE o.department_id = ? AND DATE(o.created_at) = ?'
);
if ($stmt) {
$stmt->bind_param('is', $department_id, $date); // Bind parameters (i = integer, s = string)
$stmt->execute();
$result = $stmt->get_result(); // Get the result set from the statement
$data = $result->fetch_assoc(); // Fetch the result as an associative array
// Access the "amount" field
$amount = $data['amount'];
$stmt->close(); // Close the statement
} else {
// Handle query preparation error
die('Query preparation failed: ' . $conn->error);
}
return (int)$amount > 0;
}
/**
* Select the last department daily report for a department
* @param int $department_id The id of the department
* @return department_daily_reports_o The department daily report
* @throws Exception If the object was not selected
*/
public function selectLastDepartmentDailyReport(int $department_id): department_daily_reports_o
{
global $db;
$conn = $db->conn();
$stmt = $conn->prepare(
'SELECT id FROM department_daily_reports o
WHERE o.department_id = ? ORDER BY o.created_at DESC LIMIT 1'
);
// Check if the statement was prepared successfully
if ($stmt) {
$stmt->bind_param('i', $department_id); // Bind parameters (i = integer, s = string)
$stmt->execute();
$result = $stmt->get_result(); // Get the result set from the statement
$data = $result->fetch_assoc(); // Fetch the result as an associative array
// Access the "id" field
if (!$data) {
throw new Exception('Department daily report does not exist');
}
$id = $data['id'];
$stmt->close(); // Close the statement
} else {
// Handle query preparation error
die('Query preparation failed: ' . $conn->error);
}
$this->select($id);
return $this;
}
public function getTransactionsOnDateEarnings(string $date, int $department_id, string $date_to = null): int
{
return self::methodCacheWithParameters(__METHOD__, func_get_args(), 120, function() use ($date, $department_id, $date_to) {
global /** @var db $db */
$db;
// If the date_to is null, set it to the date
if ($date_to === null) {
$date_to = $date; // Making the report for an entire day
}
// Set the date time to cover the entire day
$date = date('Y-m-d 00:00:00', strtotime($date));
$date_to = date('Y-m-d 23:59:59', strtotime($date_to));
$conn = $db->conn();
// Get all the product prices, in all the orders (Not counting removed orders), for the given date and department, and sum them
$stmt = $conn->prepare(
'SELECT SUM(oi.price * oi.quantity) as amount FROM orders o
JOIN order_items oi ON o.id = oi.order_id
WHERE o.department_id = ? AND DATE(o.created_at) BETWEEN ? AND ? AND o.deleted_at IS NULL AND oi.deleted_at IS NULL'
);
if ($stmt) {
$stmt->bind_param('iss', $department_id, $date, $date_to); // Bind parameters (i = integer, s = string)
$stmt->execute();
$result = $stmt->get_result(); // Get the result set from the statement
$data = $result->fetch_assoc(); // Fetch the result as an associative array
// Access the "amount" field
if (!$data) {
// If there are no orders, set the amount to 0
$amount = 0;
} else {
$amount = $data['amount'];
}
$stmt->close(); // Close the statement
} else {
// Handle query preparation error
die('Query preparation failed: ' . $conn->error);
}
return (int)$amount;
});
}
/**
* Check if a department daily report exists for today
* @param int $department_id The id of the department
* @return bool True if the report exists, false otherwise
*/
public function hasReportForToday(int $department_id): bool
{
return $this->doesDepartmentDailyReportExist($department_id);
}
/**
* Select the department daily report for a given date
* @param int $department_id The id of the department
* @param string $date The date to get the report for (YYYY-MM-DD)
* @return department_daily_reports_o The department daily report
* @throws Exception If a department daily report does not exist for today
* @throws Exception If the object was not selected
*/
public function selectDepartmentReportByDate(int $department_id, string $date): department_daily_reports_o
{
global $db;
if (!$this->doesDepartmentDailyReportExist($department_id, $date)) {
// Create a new report for the given date, if it does not exist
return $this->add($department_id, 0, 0, '', 0, $date);
}
$conn = $db->conn();
$stmt = $conn->prepare(
'SELECT id FROM department_daily_reports o
WHERE o.department_id = ? AND DATE(o.created_at) = ?'
);
// Check if the statement was prepared successfully
if ($stmt) {
$stmt->bind_param('is', $department_id, $date); // Bind parameters (i = integer, s = string)
$stmt->execute();
$result = $stmt->get_result(); // Get the result set from the statement
$data = $result->fetch_assoc(); // Fetch the result as an associative array
// Access the "amount" field
if (!$data) {
throw new Exception('Department daily report does not exist');
}
$id = $data['id'];
$stmt->close(); // Close the statement
} else {
// Handle query preparation error
die('Query preparation failed: ' . $conn->error);
}
$this->select($id);
return $this;
}
/**
* Add a department daily report
* @param int $department_id The id of the department
* @param int $water_usage The cm3 of water used in the department today
* @param int $water_usage_morning The cm3 of water used in the department (Checked in the morning)
* @param string $notes Any notes for the report
* @param int $filled_by The user (id) that filled the report
* @return department_daily_reports_o
* @throws Exception If the object was not created successfully
*/
public function add(
int $department_id,
int $water_usage,
int $water_usage_morning,
string $notes,
int $filled_by,
string $created_at = null
): department_daily_reports_o
{
// Validate the created_at date, if provided
if ($created_at !== null) {
$dateTime = \DateTime::createFromFormat('Y-m-d', $created_at);
if (!$dateTime || $dateTime->format('Y-m-d') !== $created_at) {
throw new Exception('Invalid date format for created_at. Expected format: YYYY-MM-DD');
}
}
$tmp_id = $this->add_object([
'department_id' => (int)$department_id,
'water_usage' => (int)$water_usage,
'water_usage_morning' => (int)$water_usage_morning,
'notes' => (string)$notes,
'filled_by' => (int)$filled_by,
'created_at' => $created_at ? $created_at : date('Y-m-d H:i:s'),
]);
$this->id = $tmp_id;
$this->getObjectProperties();
$this->objectChanged();
return $this;
}
public function getObjectProperties(): void
{
$this->department_id = new object_property($this->table, $this->id, 'department_id', 'int', false);
$this->water_usage = new object_property($this->table, $this->id, 'water_usage', 'int', false);
$this->water_usage_morning = new object_property($this->table, $this->id, 'water_usage_morning', 'int', false);
$this->notes = new object_property($this->table, $this->id, 'notes', 'string', false);
$this->filled_by = new object_property($this->table, $this->id, 'filled_by', 'int', false);
$this->created_at = new object_property($this->table, $this->id, 'created_at', 'string', false);
}
public function objectChanged(): void
{
//TODO: Add cache invalidation
}
public function hasReport(int $department_id, string $date): bool
{
return $this->doesDepartmentDailyReportExist($department_id, $date);
}
public function getBookingsOnDateCount(string $date, int $department_id, string $status): int
{
global /** @var db $db */
$db;
$conn = $db->conn();
$stmt = $conn->prepare(
'SELECT COUNT(*) as amount FROM bookings_new o
WHERE o.department = ? AND DATE(o.created_at) = ? AND o.status = ?'
);
if ($stmt) {
$stmt->bind_param('iss', $department_id, $date, $status); // Bind parameters (i = integer, s = string)
$stmt->execute();
$result = $stmt->get_result(); // Get the result set from the statement
$data = $result->fetch_assoc(); // Fetch the result as an associative array
// Access the "amount" field
$amount = $data['amount'];
$stmt->close(); // Close the statement
} else {
// Handle query preparation error
die('Query preparation failed: ' . $conn->error);
}
return (int)$amount;
}
/**
* This function calculates how many of the transactions on a given date included a wash
* That is defined as any product that has the "is_wash" field set to true in the products table
* @note This does not count products from removed orders, only products from orders that are still active
* @param string $date The date to get the report for (YYYY-MM-DD)
* @param int $department_id The id of the department
* @param string|null $date_to (Optional) The end date to get the report for (YYYY-MM-DD). If provided, the report will be for the date range between $date and $date_to
* @return void
*/
public function getTransactionsOnDateWashesCount(string $date, int $department_id, string $date_to = null): int
{
return self::methodCacheWithParameters(__METHOD__, func_get_args(), 120, function() use ($date, $department_id, $date_to) {
global /** @var db $db */
$db;
// If the date_to is null, set it to the date
if ($date_to === null) {
$date_to = $date; // Making the report for an entire day
}
// Set the date time to cover the entire day
$date = date('Y-m-d 00:00:00', strtotime($date));
$date_to = date('Y-m-d 23:59:59', strtotime($date_to));
$conn = $db->conn();
// Get all the product prices, in all the orders (Not counting removed orders), for the given date and department, and sum them
$stmt = $conn->prepare(
'SELECT COUNT(DISTINCT o.id) as amount FROM orders o
JOIN order_items oi ON o.id = oi.order_id
JOIN products p ON oi.product_id = p.id
WHERE o.department_id = ? AND DATE(o.created_at) BETWEEN ? AND ? AND o.deleted_at IS NULL AND oi.deleted_at IS NULL AND p.is_wash = 1'
);
if ($stmt) {
$stmt->bind_param('iss', $department_id, $date, $date_to); // Bind parameters (i = integer, s = string)
$stmt->execute();
$result = $stmt->get_result(); // Get the result set from the statement
$data = $result->fetch_assoc(); // Fetch the result as an associative array
// Access the "amount" field
if (!$data) {
// If there are no orders, set the amount to 0
$amount = 0;
} else {
$amount = $data['amount'];
}
$stmt->close(); // Close the statement
} else {
// Handle query preparation error
die('Query preparation failed: ' . $conn->error);
}
return (int)$amount;
});
}
/**
* This function calculates how much water has been used on the transactions on a given date
* That is calculated by getting the water usage report on the earliest report before the given date
* and the water usage report on the latest report on or before the given date
* The difference between the two is the water usage for the given date
* @note This does not count products from removed orders, only products from orders that are still active
* @param string $date The date to get the report for (YYYY-MM-DD)
* @param int $department_id The id of the department
* @param string|null $date_to (Optional) The end date to get the report for (YYYY-MM-DD). If provided, the report will be for the date range between $date and $date_to
* @return int The amount of water used on the given date
*/
public function getTransactionsOnDateWaterUsage(string $date, int $department_id, string $date_to = null): int
{
return self::methodCacheWithParameters(__METHOD__, func_get_args(), 120, function() use ($date, $department_id, $date_to) {
global /** @var db $db */
$db;
// If the date_to is null, set it to the date
if ($date_to === null) {
$date_to = $date; // Making the report for an entire day
}
// Set the date time to cover the entire day
$date = date('Y-m-d 00:00:00', strtotime($date));
$date_to = date('Y-m-d 23:59:59', strtotime($date_to));
$conn = $db->conn();
// Get the earliest report before the given date
$stmt = $conn->prepare(
'SELECT water_usage FROM department_daily_reports o
WHERE o.created_at < ? AND o.department_id = ? AND o.water_usage > 0 ORDER BY o.created_at DESC LIMIT 1'
);
if ($stmt) {
$stmt->bind_param('si', $date, $department_id); // Bind parameters
$stmt->execute();
$result = $stmt->get_result(); // Get the result set from the statement
$data = $result->fetch_assoc(); // Fetch the result as an associative array
$earliest_usage = $data ? (int)$data['water_usage'] : 0;
$stmt->close(); // Close the statement
} else {
// Handle query preparation error
die('Query preparation failed: ' . $conn->error);
}
// Get the report closest to the date_to, but not after
$stmt = $conn->prepare(
'SELECT water_usage FROM department_daily_reports o
WHERE o.created_at <= ? AND o.department_id = ? AND o.water_usage > 0 ORDER BY o.created_at DESC LIMIT 1'
);
if ($stmt) {
$stmt->bind_param('si', $date_to, $department_id); // Bind parameters
$stmt->execute();
$result = $stmt->get_result(); // Get the result set from the statement
$data = $result->fetch_assoc(); // Fetch the result as an associative array
$latest_usage = $data ? (int)$data['water_usage'] : 0;
$stmt->close(); // Close the statement
} else {
// Handle query preparation error
die('Query preparation failed: ' . $conn->error);
}
return $latest_usage - $earliest_usage;
});
}
/**
* @param array<int|string> $department_ids
* @return array{quantity:int,products:int,earnings:int,washes:int,water_usage:int}
* @throws Exception
*/
public function getTransactionSummaryForDepartments(string $date, array $department_ids, string $date_to = null): array
{
global /** @var db $db */
$db;
$normalized_department_ids = $this->normalizeDepartmentIds($department_ids);
if ($normalized_department_ids === []) {
return [
'quantity' => 0,
'products' => 0,
'earnings' => 0,
'washes' => 0,
'water_usage' => 0,
];
}
[$date_start, $date_end] = $this->resolveDateRange($date, $date_to);
$department_ids_sql = implode(',', $normalized_department_ids);
$escaped_start = $db->escape_string($date_start);
$escaped_end = $db->escape_string($date_end);
$sql = "SELECT COUNT(DISTINCT o.id) AS quantity,
COALESCE(SUM(oi.quantity), 0) AS products,
COALESCE(SUM(oi.price * oi.quantity), 0) AS earnings,
COUNT(DISTINCT CASE WHEN p.is_wash = 1 THEN o.id END) AS washes
FROM orders o
JOIN order_items oi ON oi.order_id = o.id
LEFT JOIN products p ON p.id = oi.product_id
WHERE o.department_id IN ($department_ids_sql)
AND o.created_at BETWEEN '$escaped_start' AND '$escaped_end'
AND o.deleted_at IS NULL
AND oi.deleted_at IS NULL";
$result = $db->query($sql);
$row = is_object($result) ? $result->fetch_assoc() : null;
return [
'quantity' => (int)($row['quantity'] ?? 0),
'products' => (int)($row['products'] ?? 0),
'earnings' => (int)round((float)($row['earnings'] ?? 0)),
'washes' => (int)($row['washes'] ?? 0),
'water_usage' => $this->getWaterUsageForDepartments($date, $normalized_department_ids, $date_to),
];
}
/**
* @param array<int|string> $department_ids
* @return array{completed:int,total:int}
* @throws Exception
*/
public function getBookingSummaryForDepartments(string $date, array $department_ids, string $date_to = null): array
{
global /** @var db $db */
$db;
$normalized_department_ids = $this->normalizeDepartmentIds($department_ids);
if ($normalized_department_ids === []) {
return [
'completed' => 0,
'total' => 0,
];
}
[$date_start, $date_end] = $this->resolveDateRange($date, $date_to);
$department_ids_sql = implode(',', $normalized_department_ids);
$escaped_start = $db->escape_string($date_start);
$escaped_end = $db->escape_string($date_end);
$sql = "SELECT COUNT(*) AS total,
COALESCE(SUM(CASE WHEN order_id IS NOT NULL THEN 1 ELSE 0 END), 0) AS completed
FROM order_bookings
WHERE department IN ($department_ids_sql)
AND datetime BETWEEN '$escaped_start' AND '$escaped_end'
AND deleted_at IS NULL";
$result = $db->query($sql);
$row = is_object($result) ? $result->fetch_assoc() : null;
return [
'completed' => (int)($row['completed'] ?? 0),
'total' => (int)($row['total'] ?? 0),
];
}
/**
* @param array<int|string> $department_ids
* @param array<int|string> $product_ids
* @return array<int,array{product_id:int,quantity:int,out_of:int}>
* @throws Exception
*/
public function getProductOverviewForDepartments(string $date, array $department_ids, array $product_ids, string $date_to = null): array
{
$normalized_department_ids = $this->normalizeDepartmentIds($department_ids);
$normalized_product_ids = $this->normalizeDepartmentIds($product_ids);
$overview = [];
foreach ($normalized_product_ids as $product_id) {
$quantity = 0;
$out_of = 0;
foreach ($normalized_department_ids as $department_id) {
$quantity += $this->getProductsSoldOnDate($date, $department_id, $product_id, $date_to);
$out_of += (int)(new departments_o())->getTotalMaxAddonsInDepartment(
[$product_id],
$date,
$date_to ?? $date,
$department_id
);
}
$overview[$product_id] = [
'product_id' => (int)$product_id,
'quantity' => (int)$quantity,
'out_of' => (int)$out_of,
];
}
return $overview;
}
/**
* @param array<int|string> $department_ids
* @return int
* @throws Exception
*/
public function getWaterUsageForDepartments(string $date, array $department_ids, string $date_to = null): int
{
$normalized_department_ids = $this->normalizeDepartmentIds($department_ids);
$water_usage = 0;
foreach ($normalized_department_ids as $department_id) {
$water_usage += $this->getTransactionsOnDateWaterUsage($date, $department_id, $date_to);
}
return $water_usage;
}
/**
* @param array<int|string> $department_ids
* @return array<int,array{id:int,department_id:int,created_at:string}>
* @throws Exception
*/
public function getWashTransactionsForDepartments(string $date, array $department_ids, string $date_to = null): array
{
global /** @var db $db */
$db;
$normalized_department_ids = $this->normalizeDepartmentIds($department_ids);
if ($normalized_department_ids === []) {
return [];
}
[$date_start, $date_end] = $this->resolveDateRange($date, $date_to);
$department_ids_sql = implode(',', $normalized_department_ids);
$escaped_start = $db->escape_string($date_start);
$escaped_end = $db->escape_string($date_end);
$sql = "SELECT DISTINCT o.id, o.department_id, o.created_at
FROM orders o
JOIN order_items oi ON oi.order_id = o.id
JOIN products p ON p.id = oi.product_id
WHERE o.department_id IN ($department_ids_sql)
AND o.created_at BETWEEN '$escaped_start' AND '$escaped_end'
AND o.deleted_at IS NULL
AND oi.deleted_at IS NULL
AND p.is_wash = 1
ORDER BY o.created_at ASC";
$result = $db->query($sql);
if (!is_object($result) || $result->num_rows === 0) {
return [];
}
$rows = [];
while ($row = $result->fetch_assoc()) {
$rows[] = [
'id' => (int)($row['id'] ?? 0),
'department_id' => (int)($row['department_id'] ?? 0),
'created_at' => (string)($row['created_at'] ?? ''),
];
}
return $rows;
}
/**
* @param array<int|string> $values
* @return array<int>
*/
private function normalizeDepartmentIds(array $values): array
{
$normalized = [];
foreach ($values as $value) {
$id = (int)$value;
if ($id > 0) {
$normalized[$id] = $id;
}
}
return array_values($normalized);
}
/**
* @return array{0:string,1:string}
* @throws Exception
*/
private function resolveDateRange(string $date, string $date_to = null): array
{
if ($date_to === null) {
$date_to = $date;
}
$date_start = date('Y-m-d 00:00:00', strtotime($date));
$date_end = date('Y-m-d 23:59:59', strtotime($date_to));
if ($date_start === false || $date_end === false) {
throw new Exception('Invalid date range provided');
}
return [$date_start, $date_end];
}
}