Implement subuser verification and invoice/self-serve API fixes
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace config;
|
||||
|
||||
use traits\module_config_variable;
|
||||
|
||||
class economic_invoice_discount_layout_c
|
||||
{
|
||||
use module_config_variable;
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
self::setupConfigVariable(
|
||||
'economic',
|
||||
'invoiceDiscountLayoutNumber',
|
||||
'int',
|
||||
false,
|
||||
null,
|
||||
'The duplicated invoice layout number used when collected invoices include itemized discounts',
|
||||
'1',
|
||||
false,
|
||||
null
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
<?php
|
||||
require_once WD . '/modules/economic/config/economic_invoice_layout_c.php';
|
||||
require_once WD . '/modules/economic/config/economic_invoice_discount_layout_c.php';
|
||||
require_once WD . '/modules/economic/config/economic_payment_terms_c.php';
|
||||
require_once WD . '/modules/economic/config/economic_admin_fee_monthly_c.php';
|
||||
require_once WD . '/modules/economic/config/economic_admin_fee_order_c.php';
|
||||
@@ -8,6 +9,7 @@ require_once WD . '/modules/economic/config/economic_transaction_draft_customer_
|
||||
require_once WD . '/modules/economic/config/economic_default_department_id_c.php';
|
||||
|
||||
use config\economic_invoice_layout_c;
|
||||
use config\economic_invoice_discount_layout_c;
|
||||
use config\economic_payment_terms_c;
|
||||
use config\economic_admin_fee_monthly_c;
|
||||
use config\economic_admin_fee_order_c;
|
||||
@@ -21,6 +23,7 @@ class economic_c
|
||||
use module_config_t;
|
||||
|
||||
public economic_invoice_layout_c $invoice_layout;
|
||||
public economic_invoice_discount_layout_c $invoice_discount_layout;
|
||||
public economic_payment_terms_c $payment_terms;
|
||||
public economic_admin_fee_monthly_c $admin_fee_monthly;
|
||||
public economic_admin_fee_order_c $admin_fee_order;
|
||||
@@ -33,6 +36,7 @@ class economic_c
|
||||
$this->setupConfig('economic');
|
||||
$this->allowUpdate([
|
||||
economic_invoice_layout_c::class,
|
||||
economic_invoice_discount_layout_c::class,
|
||||
economic_payment_terms_c::class,
|
||||
economic_admin_fee_monthly_c::class,
|
||||
economic_admin_fee_order_c::class,
|
||||
@@ -41,6 +45,7 @@ class economic_c
|
||||
economic_transaction_draft_customer_number_c::class,
|
||||
]);
|
||||
$this->invoice_layout = new economic_invoice_layout_c();
|
||||
$this->invoice_discount_layout = new economic_invoice_discount_layout_c();
|
||||
$this->payment_terms = new economic_payment_terms_c();
|
||||
$this->admin_fee_monthly = new economic_admin_fee_monthly_c();
|
||||
$this->admin_fee_order = new economic_admin_fee_order_c();
|
||||
|
||||
+2
-2
@@ -72,7 +72,7 @@ class economic_invoices_draft_endpoint
|
||||
* @return array{order_count:int,orders_with_invoice_lines:int,line_count:int,batch_count:int,batch_sizes:array<int,int>}
|
||||
* @throws Exception If the request fails
|
||||
*/
|
||||
public function add_orders(int $invoiceDraftId, array $orders, string $currency = 'DKK', int $line_batch_size = 500): array
|
||||
public function add_orders(int $invoiceDraftId, array $orders, string $currency = 'DKK', int $line_batch_size = 500, bool $use_itemized_discounts = false): array
|
||||
{
|
||||
$draftInvoice = (new economic())->getInvoiceDraft($invoiceDraftId, strtoupper($currency), true);
|
||||
$orders_with_invoice_lines = 0;
|
||||
@@ -90,7 +90,7 @@ class economic_invoices_draft_endpoint
|
||||
// Add the transaction header (Timestamp, department, etc.)
|
||||
$draftInvoice->addNewTransactionHeader($order);
|
||||
// Add the order lines
|
||||
$draftInvoice->addOrderItemLines($order);
|
||||
$draftInvoice->addOrderItemLines($order, $use_itemized_discounts);
|
||||
// Add an empty line, so the invoice is not empty
|
||||
$draftInvoice->addTextLine('');
|
||||
}
|
||||
|
||||
+4
-2
@@ -104,10 +104,12 @@ class economic_invoices_drafts_endpoint
|
||||
* @return object {id: number, getExternalId: string}
|
||||
* @throws Exception If the request fails
|
||||
*/
|
||||
public function add(int $customer_number, string $external_id = '', string|null $date = null): object
|
||||
public function add(int $customer_number, string $external_id = '', string|null $date = null, ?int $layout_number = null): object
|
||||
{
|
||||
// Get the layout number
|
||||
$layout_number = (int)(new economic())->config->invoice_layout->getVariableValue();
|
||||
$layout_number = $layout_number !== null && $layout_number > 0
|
||||
? $layout_number
|
||||
: (int)(new economic())->config->invoice_layout->getVariableValue();
|
||||
|
||||
// Check if the date is set
|
||||
if (is_null($date)) {
|
||||
|
||||
@@ -249,7 +249,7 @@ class economic_invoice_draft
|
||||
* @throws Exception if the order is not found
|
||||
* @throws Exception if the order is not valid
|
||||
*/
|
||||
public function addOrderItemLines(orders_o $order): void
|
||||
public function addOrderItemLines(orders_o $order, bool $use_itemized_discounts = false): void
|
||||
{
|
||||
// Get the order items
|
||||
$order_items = $order->getOrderItems($order->id);
|
||||
@@ -274,12 +274,12 @@ class economic_invoice_draft
|
||||
continue;
|
||||
}
|
||||
// Add the order item to the draft invoice
|
||||
self::addOrderItemLine($order_item, $department);
|
||||
self::addOrderItemLine($order_item, $department, false, $use_itemized_discounts);
|
||||
// Add the line discount to the total discount
|
||||
$total_discount += ($order_item['product']['price'] - $order_item['price']) * $order_item['quantity'];
|
||||
}
|
||||
// If the total discount is greater than 0, add it to the invoice
|
||||
if ($total_discount > 0) {
|
||||
if (!$use_itemized_discounts && $total_discount > 0) {
|
||||
// Add the discount to the invoice
|
||||
self::addProductDiscountLine($total_discount, $department['economic_department_id'] ?? 0, $department['dimension'] ?? 0);
|
||||
}
|
||||
@@ -295,7 +295,7 @@ class economic_invoice_draft
|
||||
* @throws Exception if the order item is not found
|
||||
* @throws Exception if the order item is not valid
|
||||
*/
|
||||
public function addOrderItemLine(array $order_item, array $department, bool $show_discount = false): void
|
||||
public function addOrderItemLine(array $order_item, array $department, bool $show_discount = false, bool $use_itemized_discount = false): void
|
||||
{
|
||||
// Check if the order item is valid
|
||||
if (!isset($order_item['id'])) {
|
||||
@@ -308,14 +308,21 @@ class economic_invoice_draft
|
||||
$economic_department_id = $department['economic_department_id'] ?? 0;
|
||||
// Get the dimension id
|
||||
$economic_dimension_id = $department['economic_dimension_id'] ?? 0;
|
||||
$pricing = self::resolveOrderItemInvoicePricing($order_item);
|
||||
$discount_percentage = $use_itemized_discount
|
||||
? $this->resolveItemizedDiscountPercentageForInvoiceCurrency($pricing)
|
||||
: 0;
|
||||
// Add the order item to the draft invoice
|
||||
self::addProductLine(
|
||||
(string)$order_item['product']['economic_product_id'],
|
||||
(string)$order_item['product']['name'],
|
||||
(int)$order_item['quantity'] ?? 1,
|
||||
(int)($order_item['product']['price'] == 0 ? $order_item['price'] : $order_item['product']['price']),
|
||||
$use_itemized_discount
|
||||
? $pricing['invoice_unit_price']
|
||||
: (int)($order_item['product']['price'] == 0 ? $order_item['price'] : $order_item['product']['price']),
|
||||
(int)$economic_department_id,
|
||||
$economic_dimension_id
|
||||
$economic_dimension_id,
|
||||
$discount_percentage
|
||||
);
|
||||
|
||||
// Calculate the discount percentage (If the final price is 0, set the discount percentage to 100)
|
||||
@@ -378,11 +385,90 @@ class economic_invoice_draft
|
||||
return abs($price) < 0.00001;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return exact line discount data from the original department price and stored final item price.
|
||||
*
|
||||
* @return array{
|
||||
* original_unit_price:float,
|
||||
* final_unit_price:float,
|
||||
* invoice_unit_price:float,
|
||||
* quantity:float,
|
||||
* discount_unit_amount:float,
|
||||
* discount_total_amount:float,
|
||||
* discount_percentage:float,
|
||||
* has_discount:bool
|
||||
* }
|
||||
*/
|
||||
public static function resolveOrderItemInvoicePricing(array $order_item): array
|
||||
{
|
||||
$quantity = isset($order_item['quantity']) && is_numeric($order_item['quantity'])
|
||||
? (float)$order_item['quantity']
|
||||
: 0.0;
|
||||
$final_unit_price = isset($order_item['price']) && is_numeric($order_item['price'])
|
||||
? (float)$order_item['price']
|
||||
: 0.0;
|
||||
$original_unit_price = isset($order_item['product']['price']) && is_numeric($order_item['product']['price'])
|
||||
? (float)$order_item['product']['price']
|
||||
: 0.0;
|
||||
|
||||
if (abs($original_unit_price) < 0.00001) {
|
||||
$original_unit_price = $final_unit_price;
|
||||
}
|
||||
|
||||
$has_discount = $quantity > 0.0
|
||||
&& $final_unit_price > 0.0
|
||||
&& $original_unit_price > 0.0
|
||||
&& $final_unit_price < ($original_unit_price - 0.00001);
|
||||
|
||||
$discount_unit_amount = $has_discount
|
||||
? round($original_unit_price - $final_unit_price, 2)
|
||||
: 0.0;
|
||||
$discount_total_amount = round($discount_unit_amount * $quantity, 2);
|
||||
|
||||
return [
|
||||
'original_unit_price' => $original_unit_price,
|
||||
'final_unit_price' => $final_unit_price,
|
||||
'invoice_unit_price' => $has_discount ? $original_unit_price : $final_unit_price,
|
||||
'quantity' => $quantity,
|
||||
'discount_unit_amount' => $discount_unit_amount,
|
||||
'discount_total_amount' => $discount_total_amount,
|
||||
'discount_percentage' => $has_discount
|
||||
? round((1 - ($final_unit_price / $original_unit_price)) * 100, 10)
|
||||
: 0.0,
|
||||
'has_discount' => $has_discount,
|
||||
];
|
||||
}
|
||||
|
||||
public static function orderItemHasBillableDiscount(array $order_item): bool
|
||||
{
|
||||
return self::resolveOrderItemInvoicePricing($order_item)['has_discount'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the hidden e-conomic percentage from converted amounts so the rendered monetary discount stays exact.
|
||||
*
|
||||
* @param array{invoice_unit_price:float,final_unit_price:float,has_discount:bool,discount_percentage:float} $pricing
|
||||
*/
|
||||
private function resolveItemizedDiscountPercentageForInvoiceCurrency(array $pricing): float
|
||||
{
|
||||
if (!$pricing['has_discount']) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
$converted_original = self::convertCurrency($pricing['invoice_unit_price']);
|
||||
$converted_final = self::convertCurrency($pricing['final_unit_price']);
|
||||
if ($converted_original <= 0.0 || $converted_final <= 0.0) {
|
||||
return $pricing['discount_percentage'];
|
||||
}
|
||||
|
||||
return round((1 - ($converted_final / $converted_original)) * 100, 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a product line to the draft invoice
|
||||
* @note The lines won't be saved until the addLines() method is called.
|
||||
*/
|
||||
public function addProductLine(string $productNumber, string $description, float $quantity, float $unitNetPrice, int $economic_department_id, int $dimension): void
|
||||
public function addProductLine(string $productNumber, string $description, float $quantity, float $unitNetPrice, int $economic_department_id, int $dimension, float $discountPercentage = 0): void
|
||||
{
|
||||
// We then add a 0 in front of the product number to make sure it's the right one, made by FlexPOS.
|
||||
// Add a line to the invoice
|
||||
@@ -392,7 +478,7 @@ class economic_invoice_draft
|
||||
],
|
||||
'quantity' => $quantity,
|
||||
'unitNetPrice' => self::convertCurrency($unitNetPrice),
|
||||
'discountPercentage' => 0,
|
||||
'discountPercentage' => $discountPercentage,
|
||||
'description' => $description,
|
||||
];
|
||||
// If the department is set, add it to the line
|
||||
|
||||
@@ -297,7 +297,25 @@ trait selfserve_lane_invoice_t
|
||||
$billing_customer_number > 0 ? $billing_customer_number : null,
|
||||
$subuser_id
|
||||
);
|
||||
return $session->exists() ? $session : null;
|
||||
if ($session->exists()) {
|
||||
return $session;
|
||||
}
|
||||
|
||||
if ($subuser_id === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$session = (new selfserve_wash_sessions_o())->selectLatestOpenByLaneAndReg(
|
||||
(int)$this->id,
|
||||
selfserve::standardize_registration($license_plate),
|
||||
$billing_customer_number > 0 ? $billing_customer_number : null
|
||||
);
|
||||
if (!$session->exists()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$sessionSubuserId = $session->subuser_id->value() === null ? null : (int)$session->subuser_id->value();
|
||||
return $sessionSubuserId === null || $sessionSubuserId === $subuser_id ? $session : null;
|
||||
} catch (\Throwable) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user