Files
api/services/nginx/app/objects/orders_o.php
T
Jepp9350 32f0a1548f Add customer-specific filtering and refactor order invoice logic
Introduced customer attribute-based filtering for individual invoicing and enhanced collected order invoice processing with proper associations to customers and orders. Added new endpoints, fields, and utility methods to streamline data retrieval, ensure consistency, and support new use cases like 'Ready to Invoice'. Includes minor fixes, validations, and optimizations throughout the affected modules.
2025-03-13 14:35:49 +01:00

467 lines
18 KiB
PHP

<?php
namespace objects;
use classes\db;
use classes\motorapi;
use classes\object_property;
use classes\response;
use Exception;
use traits\db_object_t;
class orders_o extends db
{
use db_object_t;
public object_property $customer_id;
public object_property $cashier_id;
public object_property $reference;
public object_property $notes;
public object_property $department_id;
public object_property $reg_1;
public object_property $reg_2;
public object_property $reg_3;
public economic_module_orders $economic_module_orders;
public object_property $created_at;
public object_property $deleted_at;
public stripe_module_orders_o $stripe_module_orders;
public object_property $invoice_collection_id;
public function structure(): void
{
$this->setTable('orders');
}
public function edit(int $id, int $customer_id, int $cashier_id, string $reference, string $notes, int $department_id): void
{
global $db, $response;
$this->id = $id;
try {
// Avoid SQL injection
$reference = $db->escape_string($reference);
$notes = $db->escape_string($notes);
// Update the record in the database
$sql = "UPDATE $this->table SET customer_id = $customer_id, cashier_id = $cashier_id, reference = '$reference', notes = '$notes', department_id = $department_id WHERE id = $id";
$db->query($sql);
// Set the values of the object properties
$this->getObjectProperties();
} catch (Exception $e) {
$response->error($e->getMessage());
}
}
public function getObjectProperties(): void
{
$this->customer_id = new object_property($this->table, $this->id, 'customer_id', 'int', true);
$this->cashier_id = new object_property($this->table, $this->id, 'cashier_id', 'int', true);
$this->reference = new object_property($this->table, $this->id, 'reference', 'string', true);
$this->notes = new object_property($this->table, $this->id, 'notes', 'string', false);
$this->department_id = new object_property($this->table, $this->id, 'department_id', 'int', true);
$this->reg_1 = new object_property($this->table, $this->id, 'reg_1', 'string', false);
$this->reg_2 = new object_property($this->table, $this->id, 'reg_2', 'string', false);
$this->reg_3 = new object_property($this->table, $this->id, 'reg_3', 'string', false);
$this->created_at = new object_property($this->table, $this->id, 'created_at', 'timestamp', false);
$this->economic_module_orders = (new economic_module_orders())->getByOrderId($this->id);
$this->stripe_module_orders = (new stripe_module_orders_o())->select($this->id);
$this->deleted_at = new object_property($this->table, $this->id, 'deleted_at', 'timestamp', false);
$this->invoice_collection_id = new object_property($this->table, $this->id, 'invoice_collection_id', 'int', false);
}
public function includeIncludes(): orders_o
{
global /** @var response $response */
$response;
$includeEverything = $response->getRequestParameter('include_all') === 'true';
/** orderItems */
if ($response->getRequestParameter('includeOrderItems') || $includeEverything) {
$response->add_include('orderItems', $this->applyDepartmentPrices($this->getOrderItems($this->id), $this->department_id->value()));
}
/** customer */
if ($response->getRequestParameter('includeCustomer') || $includeEverything) {
$customer = new users_o();
$response->add_include('customer', $customer->getCustomerByIdOrCustomerNumber($this->customer_id->value())->includeIncludes()->asArray());
}
/** cashier */
if ($response->getRequestParameter('includeCashier') || $includeEverything) {
$cashier = new users_o();
$response->add_include('cashier', $cashier->getUserById($this->cashier_id->value())->asArray());
}
/**
* economicModuleOrders
*/
if ($response->getRequestParameter('includeEconomicModuleOrders') || $includeEverything) {
$response->add_include('economicModuleOrders', $this->economic_module_orders->getByOrderId($this->id)->asArray());
}
/**
* stripeModuleOrders
*/
if ($response->getRequestParameter('includeStripeModuleOrders') || $includeEverything) {
$response->add_include('stripeModuleOrders', $this->stripe_module_orders->exists() ? $this->stripe_module_orders->asArray() : []);
}
return $this;
}
/**
* Apply department pricing to a list of products
* @param array $order_items
* @param int $department_id
* @return array
*/
public function applyDepartmentPrices(array $order_items, int $department_id): array
{
global $response;
$department = new departments_o();
$department->getDepartmentById($department_id);
$department->getDepartmentProductPrices($department_id);
foreach ( $order_items as $key => $order_item ) {
$product = new products_o();
$product->getProductById($order_item['product_id']);
$order_items[$key]['product']['price'] = $product->getDepartmentPrice($department_id);
}
return $order_items;
}
public function getOrderItems(int $order_id): array
{
global $db;
$sql = "SELECT * FROM order_items WHERE order_id = $order_id";
$result = $db->query($sql);
$order_items = [];
if ($result->num_rows > 0 && $result) {
while ($row = $result->fetch_assoc()) {
$order_item = new order_items_o();
$order_items[] = $order_item->getOrderItemById($row['id'])->getItemAsArray();
}
}
return $order_items;
}
public function asArray(): array
{
return [
'id' => $this->id,
'customer_id' => (int)$this->customer_id->value(),
'cashier_id' => (int)$this->cashier_id->value(),
'reference' => $this->reference->value(),
'notes' => $this->notes->value(),
'department_id' => (int)$this->department_id->value(),
'reg_1' => $this->reg_1->value(),
'reg_2' => $this->reg_2->value(),
'reg_3' => $this->reg_3->value(),
'created_at' => $this->created_at->value(),
'deleted_at' => $this->deleted_at->value(),
'total_net_amount' => $this->getNetAmount(),
'invoice_collection_id' => (int)$this->invoice_collection_id->value(),
'closed_at' => (int)$this->invoice_collection_id->value() ? (new collected_order_invoices_o())->select((int)$this->invoice_collection_id->value())->closed_at->value() : null,
];
}
public function getNetAmount(): float
{
self::requireSelected();
$net = 0;
// Get the order items object
$order_items = new order_items_o();
// Get the price, quantity of the order items
$items = $order_items->getAllItemsAsArray($this->id);
// Loop through the items and get the net amount
foreach ( $items as $item ) {
$tmp_price = (int)$item['price'];
$tmp_quantity = (int)$item['quantity'];
// Add the price to the net amount
$net += $tmp_price * $tmp_quantity;
}
return $net;
}
public function exists(): bool
{
// Check if the id is greater than 0, and that the deleted_at property is null
if ($this->id > 0) {
$this->getObjectProperties();
return $this->deleted_at->value() === null;
}
return false;
}
public function getCustomerByOrderId(?string $order_id): users_o
{
global $db;
$sql = "SELECT customer_id FROM orders WHERE id = $order_id";
$result = $db->query($sql);
if ($result->num_rows > 0) {
$row = $result->fetch_assoc();
return (new users_o())->getCustomerByIdOrCustomerNumber($row['customer_id']);
}
return new users_o();
}
public function getCustomerOrdersPaginated(int $customer_number, int $page = 1, int $limit = 10, array $order = ['id' => 'DESC'], string $search = null, array $filters = null): array
{
global /** @var response $response */
$db, $response;
// Add the customer number to the filters
$filters['customer_id'] = $customer_number;
// List the objects with pagination
$array = $this->listObjectsWithPagination($page, $limit, $search, $filters, $order);
// Get the total number of objects
$total = $this->getTotalObjects($search, $filters);
$response->paginate($page, $limit, $total);
return $array;
}
public function getDepartmentByOrderId($order_id): array
{
return (new departments_o())->getDepartmentById($this->department_id->value(), true);
}
public function delete(): void
{
// Set the deleted_at property to the current timestamp
$this->deleted_at->set(date('Y-m-d H:i:s'));
// Save the object
}
public function restore(): void
{
// Set the deleted_at property to null
$this->deleted_at->set(null);
// Save the object
}
/**
* @param string $plate The vehicle plate
* @param int $entries The number of last entries to return (default 10)
* @return array The orders for the vehicle plate
*/
public function getOrderHistoryByVehiclePlate(string $plate, int $entries = 10): array
{
global $db;
$sql = "SELECT * FROM $this->table WHERE reg_1 = '$plate' OR reg_2 = '$plate' OR reg_3 = '$plate' ORDER BY id DESC LIMIT $entries";
$result = $db->query($sql);
return $db->fetch_all($result);
}
public function unlinkOrdersFromInvoiceDraft(int $draftInvoiceNumber): void
{
$this->economic_module_orders->unlinkAllOrdersFromDraft($draftInvoiceNumber);
}
public function getOrderCustomer(int $orderId): users_o
{
$order = new orders_o();
$order->getOrderById($orderId);
return (new users_o())->getCustomerByIdOrCustomerNumber($order->customer_id->value());
}
public function getOrderById(int $id): orders_o
{
global $db;
// Get the record from the database
$sql = "SELECT * FROM $this->table WHERE id = $id";
$result = $db->query($sql);
if ($result->num_rows > 0) {
$this->id = $id;
$this->getObjectProperties();
}
return $this;
}
/**
* This function is used when an order is updated
* It takes the request data and updates the order accordingly.
* @return void
* @example {"id":410,"field":"reg_1","value":"REG12"}
*/
public function updateRequest(): void
{
global $response;
// If the permission check is not skipped, require the user to be logged in
$data = json_decode(file_get_contents('php://input'), true);
$this->id = $data['id'];
$this->getObjectProperties();
// Throw an error if the order does not exist
if (!$this->exists()) {
$response->error('Order not found, or already deleted', 400);
}
// Validate the field value
if (!isset($data['field']) || !isset($data['value'])) {
$response->error('Field and value are required', 400);
}
// Check if the value is null
if ($data['value'] === 'null' || $data['value'] === 'NULL') {
$this->{$data['field']}->nullify();
}
$this->{$data['field']}->set($data['value']);
}
public function getOrderByInvoiceId(int $invoiceId): orders_o
{
global $db;
$sql = "SELECT id FROM economic_module_orders WHERE invoice_id = $invoiceId";
$result = $db->query($sql);
if ($result->num_rows > 0) {
$row = $result->fetch_assoc();
return $this->getOrderById($row['id']);
}
throw new Exception('Order not found');
}
/**
* Set the stripe invoicing for an order
* @throws Exception
*/
public function setStripeInvoicing(string $invoice_id, string $stripe_customer_id, string $url): void
{
self::requireSelected();
$this->stripe_module_orders->add($this->id, $invoice_id, $stripe_customer_id, $url);
self::objectChanged();
}
public function add(int $customer_id, int $cashier_id, string $reference, string $notes, int $department_id, string $reg_1 = '', string $reg_2 = '', string $reg_3 = ''): orders_o
{
global $db, $response;
try {
// Avoid SQL injection
$reference = $db->escape_string($reference);
$notes = $db->escape_string($notes);
$reg_1 = $db->escape_string($reg_1);
$reg_2 = $db->escape_string($reg_2);
$reg_3 = $db->escape_string($reg_3);
// Create a new record in the database
$sql = "INSERT INTO $this->table (customer_id, cashier_id, reference, notes, department_id, reg_1, reg_2, reg_3) VALUES ($customer_id, $cashier_id, '$reference', '$notes', $department_id, '$reg_1', '$reg_2', '$reg_3')";
$db->query($sql);
// Get the id of the new record
$this->id = $db->insert_id();
// Set the values of the object properties
$this->getObjectProperties();
self::requireSelected();
self::assignToInvoiceCollection();
return $this;
} catch (Exception $e) {
$response->error($e->getMessage());
}
}
/**
* Assign the order to an invoice collection
* @param int|null $invoiceCollectionId The invoice collection id, if not set, the default invoice collection id will be used.
* @throws Exception If the order is not selected
*/
public function assignToInvoiceCollection(int $invoiceCollectionId = null): void
{
// If the invoice collection id is not set, get the default invoice collection id
self::requireSelected();
// Get the customer
$customer = new users_o();
$customer->getUserByCustomerNumber($this->customer_id->value());
$customer->requireSelected();
// Get the invoice collection id
$invoiceCollectionId = $invoiceCollectionId ?? $customer->getNewOrderInvoiceCollectionId();
// Assign the order to the invoice collection
$this->invoice_collection_id->set($invoiceCollectionId);
self::objectChanged();
}
public function objectChanged(): void
{
// Since the orders object is not cached, there is no need to invalidate the cache
}
/**
* Get the recommended order items, based on the order history, vehicle plate and department
* @return array The recommended order items
* @throws Exception If the order is not selected
*/
public function getRecommendedOrder(): array
{
self::requireSelected();
$recommended = [
'reg_1' => self::getRecommendedOrderPlate($this->reg_1->value()),
'reg_2' => [],
'reg_3' => [],
];
return $recommended;
}
/**
* @throws Exception If the order is not selected
*/
public function getRecommendedOrderPlate(string $plate): array
{
self::requireSelected();
$result = [
'order_history' => self::get_vehicle_last_orders_items($plate),
];
// If the MotorApi is enabled, get the recommended order items based on the vehicle plate
$MotorApi = new motorapi();
if ($MotorApi->config->enabled->isTrue()) {
$MotorApi_data = $MotorApi->getRecommendedProducts($plate);
if ($MotorApi_data) {
// Get the recommended order items based on the vehicle plate
$result['motorapi'] = $MotorApi_data;
}
}
return $result;
}
/**
* Get the last orders item ids for a vehicle plate
* @param string $plate The vehicle plate
* @return array The last order item ids
*/
public function get_vehicle_last_orders_items(string $plate): array
{
global /** @var db $db */
$db;
$sql = "SELECT id FROM $this->table WHERE reg_1 = '$plate' OR reg_2 = '$plate' OR reg_3 = '$plate' AND deleted_at IS NULL ORDER BY id DESC LIMIT 5";
$result = $db->query($sql);
$orders = $db->fetch_all($result);
$order_items = [
1 => [],
2 => [],
3 => [],
4 => [],
5 => [],
];
// Get the order item (ids) from the last 5 orders
$index = 0;
foreach ( $orders as $order ) {
$index++;
$order_tmp = new orders_o();
// Get the order items for the order
$items_tmp = $order_tmp->getOrderItems($order['id']);
// Add the items to the result
foreach ( $items_tmp as $item ) {
$order_items[$index][] = [
'product_id' => $item['product_id'],
'reference' => $item['reference'],
'quantity' => $item['quantity'],
'product' => $item['product'],
];
}
}
return $order_items;
}
/**
* Get the order history for a vehicle plate
* @param string $plate The vehicle plate
* @return array The order history
*/
public function get_vehicle_order_history(string $plate): array
{
global $db;
$sql = "SELECT * FROM $this->table WHERE reg_1 = '$plate' OR reg_2 = '$plate' OR reg_3 = '$plate' AND deleted_at IS NULL ORDER BY id DESC LIMIT 5";
$result = $db->query($sql);
return $db->fetch_all($result);
}
}