Add PO number support and enhance permissions for order management
- Introduced `po` property in `orders_o` for handling Purchase Order (PO) numbers, including API integration for retrieval and validation. - Enhanced customer permissions to allow limited order editing (`po` updates) and attachment downloads for their own orders. - Added new helper methods to `users_o` for attributes like `showPricesOnBookingPage` and `usePONumbers`. - Improved order item listing logic with distinct permissions for customers' own orders and price visibility. - Implemented numeric value casting in filters within `db_object_t`.
This commit is contained in:
@@ -143,6 +143,10 @@ class economic_invoice_draft
|
||||
$parsed_date = date('d/m/Y H:i', strtotime($order->created_at->value()));
|
||||
// Add the text line to the draft invoice
|
||||
self::addTextLine("[ " . $parsed_date . ' ' . $department_name . ' #' . $order->id . " ]");
|
||||
// If there's a PO number, add it to the invoice
|
||||
if ($order->po->value() !== '') {
|
||||
self::addTextLine('PO: ' . $order->po->value());
|
||||
}
|
||||
// If there's a reference, add it to the invoice
|
||||
if ($order->reference->value() !== '') {
|
||||
self::addTextLine('Reference:');
|
||||
|
||||
@@ -34,6 +34,7 @@ class orders_o extends db
|
||||
public object_property $booking_id;
|
||||
public object_property $wash_id; // The XL Vask Wash ID, if any
|
||||
public object_property $lane; // The lane used for the order, if any
|
||||
public object_property $po; // The (optional) PO number, filled by the customer.
|
||||
public object_property $using_hand_held; // Whether the order is being processed using a handheld device
|
||||
|
||||
/**
|
||||
@@ -88,6 +89,7 @@ class orders_o extends db
|
||||
$this->booking_id = new object_property($this->table, $this->id, 'booking_id', 'int', false);
|
||||
$this->wash_id = new object_property($this->table, $this->id, 'wash_id', 'string', false);
|
||||
$this->lane = new object_property($this->table, $this->id, 'lane', 'string', false);
|
||||
$this->po = new object_property($this->table, $this->id, 'po', 'string', false);
|
||||
$this->using_hand_held = new object_property($this->table, $this->id, 'using_hand_held', 'bool', false);
|
||||
}
|
||||
|
||||
@@ -563,6 +565,7 @@ class orders_o extends db
|
||||
'booking_id' => (int)$this->booking_id->value(),
|
||||
'wash_id' => $this->wash_id->value(),
|
||||
'lane' => $this->lane->value(),
|
||||
'po' => $this->po->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,
|
||||
'pending_handheld' => $this->isPendingHandheld(),
|
||||
];
|
||||
@@ -1334,4 +1337,13 @@ class orders_o extends db
|
||||
return $post_discount;
|
||||
}, $order_items));
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function isOwnOrder(int $customer_id): bool
|
||||
{
|
||||
self::requireSelected();
|
||||
return (int)$this->customer_id->value() === $customer_id;
|
||||
}
|
||||
}
|
||||
@@ -460,6 +460,24 @@ class users_o extends db
|
||||
return $this->doesUserHaveAttribute('restrictTankCleaning');
|
||||
}
|
||||
|
||||
/**
|
||||
* If the customer is shown prices on the booking page
|
||||
* @return bool
|
||||
*/
|
||||
public function showPricesOnBookingPage(): bool
|
||||
{
|
||||
return $this->doesUserHaveAttribute('showPricesOnBookingPage');
|
||||
}
|
||||
|
||||
/**
|
||||
* If the customer uses PO numbers
|
||||
* @return bool
|
||||
*/
|
||||
public function usePONumbers(): bool
|
||||
{
|
||||
return $this->doesUserHaveAttribute('usePONumbers');
|
||||
}
|
||||
|
||||
public function includeIncludes(array $includes = []): users_o
|
||||
{
|
||||
global /** @var response $response */
|
||||
@@ -519,6 +537,15 @@ class users_o extends db
|
||||
while ($row = $result->fetch_assoc()) {
|
||||
$perms[] = $row['permission'];
|
||||
}
|
||||
// Add the attributes as `has_attribute_key_{attribute_key}`
|
||||
$attributes = array_map(function ($attribute_arr) {
|
||||
return $attribute_arr['attribute'];
|
||||
}, (new users_o())->getUserAttributes($this->id));
|
||||
// Remove all duplicates
|
||||
$attributes = array_unique($attributes, SORT_REGULAR);
|
||||
foreach ($attributes as $attribute) {
|
||||
$perms[] = 'has_attribute_' . $attribute;
|
||||
}
|
||||
$this->permissions = $perms;
|
||||
}
|
||||
|
||||
|
||||
@@ -93,9 +93,18 @@ class orderItemsRoute
|
||||
$this->get('/order/items', function () {
|
||||
// Require the user to be logged in
|
||||
global $response;
|
||||
$this->requirePermission('list_order_items');
|
||||
// Get the user object
|
||||
// Check if the user is requesting their own order items
|
||||
$isCustomerAccess = ($this->hasPermission('user') && !($this->hasPermission('list_order_items')));
|
||||
$user = (new authentication())->get_user();
|
||||
if ($isCustomerAccess) {
|
||||
$hasPermission = $this->hasPermission('list_own_order_items');
|
||||
$hasAttribute = $user->showPricesOnBookingPage(); // Check if the user has the attribute to show prices on the booking page
|
||||
if (!$hasPermission && !$hasAttribute) {
|
||||
$response->error('You do not have permission to list order items, neither your own nor all orders', 403);
|
||||
}
|
||||
} else {
|
||||
$this->requirePermission('list_order_items');
|
||||
}
|
||||
// Check if the request was successful
|
||||
if ($user) {
|
||||
// Get the post data
|
||||
@@ -113,6 +122,10 @@ class orderItemsRoute
|
||||
$response->error('Order not found', 404);
|
||||
}
|
||||
$order = (new orders_o())->getOrderById((int)$data['order_id']);
|
||||
// If the user is requesting their own order items, check if the order belongs to them
|
||||
if ($isCustomerAccess && !$order->isOwnOrder((int)$user->customer_number->value())) {
|
||||
$response->error('Order does not belong to the user', 400);
|
||||
};
|
||||
// Apply the departments unique pricing
|
||||
$orderItems = $order->getOrderItems($order->id);
|
||||
$orderItems = $order->applyDepartmentPrices($orderItems, $order->department_id->value());
|
||||
|
||||
@@ -93,6 +93,8 @@ class ordersRoute
|
||||
$order['user_id'] = (int)$tmp_customer->id;
|
||||
$order['pending_handheld'] = $order_obj->isPendingHandheld();
|
||||
$order['attachments'] = $order_obj->listAttachments();
|
||||
$order['po'] = $order['po'] ?? null;
|
||||
$order['lane'] = $order['lane'] ?? null;
|
||||
/** @var array $order */
|
||||
return $order;
|
||||
},
|
||||
@@ -208,7 +210,13 @@ class ordersRoute
|
||||
$this->put('/orders', function () {
|
||||
// Require the user to be logged in
|
||||
global $response;
|
||||
$this->requirePermission('edit_order');
|
||||
// Check if the user has permission to partially edit the order
|
||||
$isCustomerAccess = ((new authentication())->get_user()->hasPermission('user') && !((new authentication())->get_user()->hasPermission('edit_order')));
|
||||
if (!$isCustomerAccess) {
|
||||
$this->requirePermission('edit_order');
|
||||
} else {
|
||||
$this->requirePermission('user'); // This is used to allow the user to edit their own order
|
||||
}
|
||||
// Get the user object
|
||||
$user = (new authentication())->get_user();
|
||||
// Check if the request was successful
|
||||
@@ -225,6 +233,33 @@ class ordersRoute
|
||||
if (!$order->exists()) {
|
||||
$response->error('Order not found', 400);
|
||||
}
|
||||
// Check if the user has customer edit access
|
||||
if ($isCustomerAccess) {
|
||||
if ($order->customer_id->value() !== $user->customer_number->value()) {
|
||||
$response->error('You do not have permission to edit this order', 400);
|
||||
}
|
||||
// Allowed to edit list
|
||||
$allowed_to_edit = [
|
||||
// Include the order ID (Even though it is not editable)
|
||||
'id',
|
||||
'po',
|
||||
];
|
||||
// Check if the $data contains any non-allowed keys
|
||||
foreach ($data as $key => $value) {
|
||||
if (!in_array($key, $allowed_to_edit)) {
|
||||
$response->error('You do not have permission to edit this order field (key: ' . $key . ')', 400);
|
||||
break;
|
||||
}
|
||||
};
|
||||
// PO
|
||||
if (isset($data['po'])) {
|
||||
$order->po->set((string)$data['po']);
|
||||
}
|
||||
$order->objectChanged();
|
||||
// Return a success message
|
||||
$response->success($order->asArray());
|
||||
}
|
||||
/** Departmental access */
|
||||
// If the customer ID is set, validate it
|
||||
if (isset($data['customer_id'])) {
|
||||
if (!(new users_o())->getCustomerByIdOrCustomerNumber((int)$data['customer_id'])->exists() || empty($data['customer_id'])) {
|
||||
@@ -252,6 +287,10 @@ class ordersRoute
|
||||
if (isset($data['reg_3'])) {
|
||||
$order->reg_3->set($data['reg_3']);
|
||||
}
|
||||
// If the PO is set, validate it
|
||||
if (isset($data['po'])) {
|
||||
$order->po->set((string)$data['po']);
|
||||
}
|
||||
// If the lane is set, validate it
|
||||
if (isset($data['lane'])) {
|
||||
$order->lane->set((int)$data['lane']);
|
||||
@@ -338,7 +377,12 @@ class ordersRoute
|
||||
$this->get('/orders/attachments/download', function () {
|
||||
// Require the user to be logged in
|
||||
global $response;
|
||||
$this->requirePermission('download_order_attachments');
|
||||
$isCustomerAccess = ($this->hasPermission('user') && !($this->hasPermission('download_order_attachments')));
|
||||
if (!$isCustomerAccess) {
|
||||
$this->requirePermission('download_order_attachments');
|
||||
} else {
|
||||
$this->requirePermission('download_order_attachments_own');
|
||||
}
|
||||
// Get the user object
|
||||
$user = (new authentication())->get_user();
|
||||
// Check if the request was successful
|
||||
@@ -362,6 +406,12 @@ class ordersRoute
|
||||
if (!$order->exists()) {
|
||||
$response->error('Order not found', 400);
|
||||
}
|
||||
// Check if the user has customer edit access
|
||||
if ($isCustomerAccess) {
|
||||
if ($order->isOwnOrder($user->customer_number->value()) === false) {
|
||||
$response->error('You do not have permission to download this order', 400);
|
||||
}
|
||||
}
|
||||
// Get the attachment
|
||||
$attachment = $order->getAttachment((int)$attachment_id);
|
||||
if (!$attachment->exists()) {
|
||||
@@ -387,7 +437,8 @@ class ordersRoute
|
||||
}
|
||||
},
|
||||
[
|
||||
'download_order_attachments' => 'Download attachments for an order'
|
||||
'download_order_attachments' => 'Download attachments for an order',
|
||||
'download_order_attachments_own' => 'Download attachments for an order (Only for own orders)'
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
@@ -728,6 +728,11 @@ trait db_object_t
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// If the values can be changed to integers, convert them to integers
|
||||
if (is_numeric($value) && is_numeric($filters[$field])) {
|
||||
$value = (int)$value;
|
||||
$filters[$field] = (int)$filters[$field];
|
||||
}
|
||||
// Check if the user tries to search for a different value than the one provided
|
||||
if ($filters[$field] !== $value) {
|
||||
throw new Exception('Invalid filter: ' . $field . ' - ' . $filters[$field] . '. Permitted value: ' . $value);
|
||||
|
||||
Reference in New Issue
Block a user