1489 lines
87 KiB
PHP
1489 lines
87 KiB
PHP
<?php
|
|
|
|
namespace routes;
|
|
|
|
use classes\authentication;
|
|
use classes\economic;
|
|
use classes\economic_v2_compare_engine;
|
|
use classes\economic_v2_line_normalizer;
|
|
use classes\economic_v2_revenue_statistics_service;
|
|
use classes\response;
|
|
use classes\router;
|
|
use Exception;
|
|
use objects\collected_order_invoices_o;
|
|
use objects\customer_fixed_pricing_o;
|
|
use objects\logs_o;
|
|
use objects\users_o;
|
|
use traits\route_t;
|
|
|
|
class orderInvoicesRoute
|
|
{
|
|
use route_t;
|
|
|
|
public function run(): void
|
|
{
|
|
global /** @var response $response */
|
|
/** @var router $router */
|
|
$router, $response;
|
|
|
|
/** Collected order invoices > GET */
|
|
$this->get('/collected-invoices', function () {
|
|
global $response;
|
|
self::requirePermission('list_collected_invoices');
|
|
$user = (new authentication())->get_user();
|
|
if ($user) {
|
|
(new logs_o())->add('orderInvoices', 'global', 1, $user->id, 'LIST_COLLECTED_INVOICES', 'User accessed the list of collected order invoices');
|
|
$collected_order_invoices = new collected_order_invoices_o();
|
|
// Check if the request contains an ID
|
|
if (self::isParametersSet(['id'])) {
|
|
self::requireType((int)self::getParameter('id'), self::type_int());
|
|
$collected_order_invoices->select((int)self::getParameter('id'));
|
|
$collected_order_invoices->requireSelected();
|
|
$response->success($collected_order_invoices->asArray());
|
|
}
|
|
// Define the users
|
|
$users = new users_o();
|
|
// Define the collected order invoices
|
|
$tmp_collected_order_invoices = new collected_order_invoices_o();
|
|
// Return the list of collected order invoices
|
|
$startTime = microtime(true);
|
|
// Build a short-lived cache key to coalesce concurrent identical requests
|
|
$cacheTtl = 15; // seconds
|
|
$pageParam = (string)($response->getRequestParameter('page') ?? '1');
|
|
$limitParam = (string)($response->getRequestParameter('limit') ?? '1000');
|
|
$searchParam = (string)($response->getRequestParameter('search') ?? '');
|
|
$orderParam = (string)($response->getRequestParameter('order') ?? 'id:ASC');
|
|
$filtersParam = (string)($response->getRequestParameter('filters') ?? '');
|
|
$cacheKey = 'collected_invoices:list:' . md5(json_encode([
|
|
'p' => $pageParam,
|
|
'l' => $limitParam,
|
|
's' => $searchParam,
|
|
'o' => $orderParam,
|
|
'f' => $filtersParam,
|
|
], JSON_UNESCAPED_UNICODE));
|
|
|
|
$redis = new \classes\redis();
|
|
$cachedPayload = $redis->get($cacheKey);
|
|
if ($cachedPayload) {
|
|
// Cached payload contains both meta and data
|
|
$payload = json_decode($cachedPayload, true);
|
|
if (isset($payload['meta']) && is_array($payload['meta'])) {
|
|
foreach ($payload['meta'] as $k => $v) {
|
|
$response->add_meta($k, $v);
|
|
}
|
|
}
|
|
$durationMs = (int)round((microtime(true) - $startTime) * 1000);
|
|
(new logs_o())->add(
|
|
'orderInvoices',
|
|
'global',
|
|
1,
|
|
$user->id,
|
|
'LIST_COLLECTED_INVOICES_TIMING_CACHE_HIT',
|
|
'Duration(ms): ' . $durationMs . ', page=' . ((int)$response->getRequestParameter('page')) . ', limit=' . ((int)$response->getRequestParameter('limit')) . ', search=' . (string)($response->getRequestParameter('search') ?? '') . ', order=' . (string)($response->getRequestParameter('order') ?? '')
|
|
);
|
|
$response->success($payload['data'] ?? []);
|
|
}
|
|
|
|
// Cache miss: compute and cache
|
|
$result = $collected_order_invoices->listObjectsWithPaginationIfSet(
|
|
function ($collected_order_invoice) use ($tmp_collected_order_invoices, $users) {
|
|
// Select the orders for each collected order invoice
|
|
$tmp_collected_order_invoices->select((int)$collected_order_invoice['id']);
|
|
return $this->getOrderInvoiceDetails($collected_order_invoice, $users, $tmp_collected_order_invoices);
|
|
},
|
|
null,
|
|
[],
|
|
// Set the where, to where a non-deleted order is connected to the collected order invoice
|
|
(new pagination_helper())->where->addCondition(pagination_condition_where::CUSTOM('EXISTS (SELECT 1 FROM orders o WHERE o.invoice_collection_id = collected_order_invoices.id AND o.deleted_at IS NULL)'))
|
|
);
|
|
// Capture current pagination meta for caching
|
|
$meta = [
|
|
'pagination' => [
|
|
'page' => (int)$pageParam,
|
|
'per_page' => (int)$limitParam,
|
|
// We don't have the total directly here; the response object already has it,
|
|
// but we cache the meta as provided by the client for consistency.
|
|
]
|
|
];
|
|
// Store combined payload
|
|
$redis->setEx($cacheKey, json_encode([
|
|
'data' => $result,
|
|
'meta' => $meta,
|
|
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), $cacheTtl);
|
|
$durationMs = (int)round((microtime(true) - $startTime) * 1000);
|
|
// Add timing log for performance monitoring
|
|
(new logs_o())->add(
|
|
'orderInvoices',
|
|
'global',
|
|
1,
|
|
$user->id,
|
|
'LIST_COLLECTED_INVOICES_TIMING',
|
|
'Duration(ms): ' . $durationMs . ', page=' . ((int)$response->getRequestParameter('page')) . ', limit=' . ((int)$response->getRequestParameter('limit')) . ', search=' . (string)($response->getRequestParameter('search') ?? '') . ', order=' . (string)($response->getRequestParameter('order') ?? '')
|
|
);
|
|
$response->success($result);
|
|
} else {
|
|
(new logs_o())->add('orderInvoices', 'global', 0, 0, 'LIST_COLLECTED_INVOICES', 'User tried to access the list of collected order invoices without a valid session');
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
},
|
|
[
|
|
'list_collected_invoices' => 'List ALL collected order invoices. This is a superuser-only route.'
|
|
]
|
|
);
|
|
|
|
/** Collected order invoices > Compare with E-conomic > GET */
|
|
$this->get('/collected-invoices/economic/compare', function () {
|
|
global $response;
|
|
self::requirePermission('compare_collected_invoice_economic');
|
|
//$user = (new authentication())->get_user();
|
|
self::requireParameters(['collected_invoice_id']);
|
|
self::requireType((int)self::getParameter('collected_invoice_id'), self::type_int());
|
|
$collected_invoice_id = (int)self::getParameter('collected_invoice_id');
|
|
self::requireMinValue($collected_invoice_id, 1);
|
|
self::requireMaxValue($collected_invoice_id, 999999999);
|
|
// Define variables
|
|
$draft_id = null; // E-Conomic draft ID
|
|
$booked_id = null; // E-Conomic booked invoice ID
|
|
$warnings = [];
|
|
// Select the collected order invoice
|
|
/** @var collected_order_invoices_o $invoice */
|
|
$invoice = (new collected_order_invoices_o())->select($collected_invoice_id);
|
|
$invoice->requireSelected(); // Require that the collected order invoice exists
|
|
// Get the E-Conomic draft ID and booked invoice ID
|
|
try {
|
|
$draft_id = $invoice->getInvoiceDraftId();
|
|
|
|
} catch (Exception $e) {
|
|
$warnings[] = 'Error fetching draft ID: ' . $e->getMessage();
|
|
}
|
|
try {
|
|
$booked_id = $invoice->getInvoiceBookedId();
|
|
|
|
} catch (Exception $e) {
|
|
$warnings[] = 'Error fetching booked invoice ID: ' . $e->getMessage();
|
|
}
|
|
// Define the variables for comparison
|
|
$draft_total = null;
|
|
$booked_total = null;
|
|
// Get the total price of the draft (if it exists)
|
|
if ($draft_id !== null) {
|
|
try {
|
|
$economic = new economic();
|
|
$draft_invoice = $economic->getInvoiceDraft((int)$draft_id);
|
|
$draft_total = $draft_invoice['total_amount'] ?? null;
|
|
$internal_total = $invoice->getTotalAmount();
|
|
if ($draft_total === null) {
|
|
$warnings[] = 'Could not fetch total amount for draft invoice ID ' . $draft_id;
|
|
} elseif (abs((float)$draft_total - (float)$internal_total) > 0.01) {
|
|
$warnings[] = 'Total amount mismatch for draft invoice ID ' . $draft_id . ': E-Conomic total is ' . $draft_total . ', internal total is ' . $internal_total;
|
|
}
|
|
} catch (Exception $e) {
|
|
$warnings[] = 'Error comparing draft total: ' . $e->getMessage();
|
|
}
|
|
}
|
|
// Get the total price of the booked invoice (if it exists)
|
|
if ($booked_id !== null) {
|
|
try {
|
|
$economic = new economic();
|
|
$booked_invoice = $economic->getInvoiceBookedFromExternalId((string)$invoice->external_id->value());
|
|
/**
|
|
* stdClass Object ( [bookedInvoiceNumber] => 28368 [orderNumber] => 30342 [date] => 2025-12-31 [currency] => DKK [exchangeRate] => 100 [netAmount] => 694 [netAmountInBaseCurrency] => 694 [grossAmount] => 867.5 [grossAmountInBaseCurrency] => 867.5 [vatAmount] => 173.5 [roundingAmount] => 0 [remainder] => 0 [remainderInBaseCurrency] => 0 [dueDate] => 2026-01-08 [paymentTerms] => stdClass Object ( [paymentTermsNumber] => 1 [daysOfCredit] => 8 [name] => Netto 8 dage [paymentTermsType] => net [self] => https://restapi.e-conomic.com/payment-terms/1 ) [customer] => stdClass Object ( [customerNumber] => 42493959 [self] => https://restapi.e-conomic.com/customers/42493959 ) [recipient] => stdClass Object ( [name] => Dejen Transport ApS [address] => Æblehaven 144, st [zip] => 4000 [city] => Roskilde [vatZone] => stdClass Object ( [name] => Domestic [vatZoneNumber] => 1 [enabledForCustomer] => 1 [enabledForSupplier] => 1 [self] => https://restapi.e-conomic.com/vat-zones/1 ) ) [references] => stdClass Object ( [other] => 2ced7a3f-fab2-edbd-075f-ce981867c340 ) [layout] => stdClass Object ( [layoutNumber] => 12 [self] => https://restapi.e-conomic.com/layouts/12 ) [pdf] => stdClass Object ( [download] => https://restapi.e-conomic.com/invoices/booked/28368/pdf ) [lines] => Array ( [0] => stdClass Object ( [lineNumber] => 1 [sortKey] => 1 [description] => [ 01/12/2025 00:00 PLENO #38679 ] [discountPercentage] => 0 [unitCostPrice] => 0 [vatRate] => 0 ) [1] => stdClass Object ( [lineNumber] => 2 [sortKey] => 2 [description] => Reference: [discountPercentage] => 0 [unitCostPrice] => 0 [vatRate] => 0 ) [2] => stdClass Object ( [lineNumber] => 3 [sortKey] => 3 [description] => # Vaskeabonnementer [discountPercentage] => 0 [unitCostPrice] => 0 [vatRate] => 0 ) [3] => stdClass Object ( [lineNumber] => 4 [sortKey] => 4 [description] => Trækker [quantity] => 2 [unitNetPrice] => 579 [discountPercentage] => 0 [unitCostPrice] => 0 [vatRate] => 25 [vatAmount] => 289.5 [totalNetAmount] => 1158 [product] => stdClass Object ( [productNumber] => 1 [self] => https://restapi.e-conomic.com/products/1 ) [departmentalDistribution] => stdClass Object ( [departmentalDistributionNumber] => 75 [name] => Vaskeaftaler og fastprisaftaler [barred] => [distributionType] => department [distributions] => Array ( [0] => stdClass Object ( [percentage] => 100 [department] => stdClass Object ( [departmentNumber] => 75 [self] => https://restapi.e-conomic.com/departments/75 ) ) ) [self] => https://restapi.e-conomic.com/departmental-distributions/departments/75 ) ) [4] => stdClass Object ( [lineNumber] => 5 [sortKey] => 5 [description] => Reference: [discountPercentage] => 0 [unitCostPrice] => 0 [vatRate] => 0 ) [5] => stdClass Object ( [lineNumber] => 6 [sortKey] => 6 [description] => # EH89254 [discountPercentage] => 0 [unitCostPrice] => 0 [vatRate] => 0 ) [6] => stdClass Object ( [lineNumber] => 7 [sortKey] => 7 [description] => Spot Free- Lastbil [quantity] => 2 [unitNetPrice] => 39 [discountPercentage] => 0 [unitCostPrice] => 0 [vatRate] => 25 [vatAmount] => 19.5 [totalNetAmount] => 78 [product] => stdClass Object ( [productNumber] => 33 [self] => https://restapi.e-conomic.com/products/33 ) [departmentalDistribution] => stdClass Object ( [departmentalDistributionNumber] => 75 [name] => Vaskeaftaler og fastprisaftaler [barred] => [distributionType] => department [distributions] => Array ( [0] => stdClass Object ( [percentage] => 100 [department] => stdClass Object ( [departmentNumber] => 75 [self] => https://restapi.e-conomic.com/departments/75 ) ) ) [self] => https://restapi.e-conomic.com/departmental-distributions/departments/75 ) ) [7] => stdClass Object ( [lineNumber] => 8 [sortKey] => 8 [description] => Reference: [discountPercentage] => 0 [unitCostPrice] => 0 [vatRate] => 0 ) [8] => stdClass Object ( [lineNumber] => 9 [sortKey] => 9 [description] => # EH89254 [discountPercentage] => 0 [unitCostPrice] => 0 [vatRate] => 0 ) [9] => stdClass Object ( [lineNumber] => 10 [sortKey] => 10 [description] => Rabat [quantity] => 1 [unitNetPrice] => -542 [discountPercentage] => 0 [unitCostPrice] => 0 [vatRate] => 25 [vatAmount] => -135.5 [totalNetAmount] => -542 [product] => stdClass Object ( [productNumber] => TotDiscount [self] => https://restapi.e-conomic.com/products/TotDiscount ) [departmentalDistribution] => stdClass Object ( [departmentalDistributionNumber] => 75 [name] => Vaskeaftaler og fastprisaftaler [barred] => [distributionType] => department [distributions] => Array ( [0] => stdClass Object ( [percentage] => 100 [department] => stdClass Object ( [departmentNumber] => 75 [self] => https://restapi.e-conomic.com/departments/75 ) ) ) [self] => https://restapi.e-conomic.com/departmental-distributions/departments/75 ) ) [10] => stdClass Object ( [lineNumber] => 11 [sortKey] => 11 [discountPercentage] => 0 [unitCostPrice] => 0 [vatRate] => 0 ) ) [sent] => https://restapi.e-conomic.com/invoices/booked/28368/sent [self] => https://restapi.e-conomic.com/invoices/booked/28368 )
|
|
*/
|
|
$booked_total = $booked_invoice->total_amount ?? null;
|
|
// Remove tax from booked total if prices are stored as tax inclusive
|
|
$booked_total = $booked_total - ($booked_invoice->vat_amount ?? 0);
|
|
$internal_total = $invoice->getTotalAmount();
|
|
if ($booked_total === null) {
|
|
$warnings[] = 'Could not fetch total amount for booked invoice ID ' . $booked_id;
|
|
} elseif (abs((float)$booked_total - (float)$internal_total) > 0.01) {
|
|
$warnings[] = 'Total amount mismatch for booked invoice ID ' . $booked_id . ': E-Conomic total is ' . $booked_total . ', internal total is ' . $internal_total;
|
|
}
|
|
} catch (Exception $e) {
|
|
$warnings[] = 'Error comparing booked total: ' . $e->getMessage();
|
|
}
|
|
}
|
|
// Determine HTTP status
|
|
// 404 = Not found (in either draft or booked)
|
|
// 200 = OK (Found one, and matches internal total)
|
|
// 409 = Conflict (Both found but do not match)
|
|
$httpStatus = match (true) {
|
|
$draft_id === null && $booked_id === null => 404,
|
|
($draft_total !== null && $internal_total === (float)$draft_total) || ($booked_total !== null && $internal_total === (float)$booked_total) => 200,
|
|
default => 409,
|
|
};
|
|
// Return the comparison result
|
|
$response->success([
|
|
'collected_invoice_id' => $collected_invoice_id,
|
|
'draft_id' => $draft_id,
|
|
'booked_id' => $booked_id,
|
|
'warnings' => $warnings,
|
|
'draft_total' => $draft_total,
|
|
'booked_total' => $booked_total,
|
|
'difference' => ((float)($draft_total ?? $booked_total) - (float)$invoice->getTotalAmount()),
|
|
'internal_total' => $internal_total,
|
|
//'order_ids' => $invoice->getOrderIds(),
|
|
], $httpStatus);
|
|
|
|
},
|
|
[
|
|
'compare_collected_invoice_economic' => 'Compare collected order invoices with E-Conomic. This is a superuser-only route.'
|
|
]
|
|
);
|
|
|
|
/** Collected order invoices > E-conomic V2 details > GET */
|
|
$this->get('/collected-invoices/economic/v2/details', function () {
|
|
global $response;
|
|
self::requirePermission('view_collected_invoice_economic_v2_details');
|
|
$collected_invoice_id = $this->requireCollectedInvoiceId();
|
|
|
|
$payload = $this->buildEconomicV2DetailsPayload($collected_invoice_id);
|
|
$response->success($payload);
|
|
},
|
|
[
|
|
'view_collected_invoice_economic_v2_details' => 'View normalized internal/draft/booked e-conomic invoice details (V2).',
|
|
]
|
|
);
|
|
|
|
/** Collected order invoices > E-conomic V2 compare > GET */
|
|
$this->get('/collected-invoices/economic/v2/compare', function () {
|
|
global $response;
|
|
self::requirePermission('compare_collected_invoice_economic_v2');
|
|
$collected_invoice_id = $this->requireCollectedInvoiceId();
|
|
|
|
$details = $this->buildEconomicV2DetailsPayload($collected_invoice_id);
|
|
$comparison = economic_v2_compare_engine::compare(
|
|
$details['internal']['normalized'],
|
|
$details['draft']['exists'] ? $details['draft']['normalized'] : null,
|
|
$details['booked']['exists'] ? $details['booked']['normalized'] : null
|
|
);
|
|
|
|
$response->success([
|
|
'collected_invoice_id' => $collected_invoice_id,
|
|
'details' => $details,
|
|
'comparison' => $comparison,
|
|
'warnings' => array_values(array_unique(array_merge(
|
|
(array)($details['warnings'] ?? []),
|
|
(array)($comparison['warnings'] ?? [])
|
|
))),
|
|
]);
|
|
},
|
|
[
|
|
'compare_collected_invoice_economic_v2' => 'Compare normalized internal invoice with draft/booked e-conomic targets (V2).',
|
|
]
|
|
);
|
|
|
|
/** Collected order invoices > E-conomic V2 compare bulk > POST */
|
|
$this->post('/collected-invoices/economic/v2/compare/bulk', function () {
|
|
global $response;
|
|
self::requirePermission('compare_collected_invoice_economic_v2_bulk');
|
|
self::requireParameters(['collected_invoice_ids']);
|
|
|
|
$collected_invoice_ids = self::getParameter('collected_invoice_ids');
|
|
if (!is_array($collected_invoice_ids)) {
|
|
$response->error('collected_invoice_ids must be an array', 400);
|
|
}
|
|
|
|
$normalized_ids = array_values(array_unique(array_filter(array_map(static function ($id) {
|
|
return (int)$id;
|
|
}, $collected_invoice_ids), static function ($id) {
|
|
return $id > 0;
|
|
})));
|
|
|
|
if (empty($normalized_ids)) {
|
|
$response->error('collected_invoice_ids must contain at least one positive integer', 400);
|
|
}
|
|
|
|
if (count($normalized_ids) > 200) {
|
|
$response->error('Maximum 200 collected_invoice_ids per bulk compare request', 400);
|
|
}
|
|
|
|
$results = [];
|
|
$errors = [];
|
|
|
|
foreach ($normalized_ids as $collected_invoice_id) {
|
|
try {
|
|
$details = $this->buildEconomicV2DetailsPayload((int)$collected_invoice_id);
|
|
$comparison = economic_v2_compare_engine::compare(
|
|
$details['internal']['normalized'],
|
|
$details['draft']['exists'] ? $details['draft']['normalized'] : null,
|
|
$details['booked']['exists'] ? $details['booked']['normalized'] : null
|
|
);
|
|
|
|
$results[] = [
|
|
'collected_invoice_id' => (int)$collected_invoice_id,
|
|
'details' => $details,
|
|
'comparison' => $comparison,
|
|
'warnings' => array_values(array_unique(array_merge(
|
|
(array)($details['warnings'] ?? []),
|
|
(array)($comparison['warnings'] ?? [])
|
|
))),
|
|
];
|
|
} catch (Exception $e) {
|
|
$errors[] = [
|
|
'collected_invoice_id' => (int)$collected_invoice_id,
|
|
'error' => $e->getMessage(),
|
|
];
|
|
}
|
|
}
|
|
|
|
$response->success([
|
|
'requested' => count($normalized_ids),
|
|
'compared' => count($results),
|
|
'failed' => count($errors),
|
|
'results' => $results,
|
|
'errors' => $errors,
|
|
]);
|
|
},
|
|
[
|
|
'compare_collected_invoice_economic_v2_bulk' => 'Compare multiple collected invoices against draft/booked e-conomic targets (V2).',
|
|
]
|
|
);
|
|
|
|
/** Collected order invoices > E-conomic V2 revenue statistics > GET */
|
|
$this->get('/collected-invoices/economic/v2/revenue-statistics', function () {
|
|
global $response;
|
|
self::requirePermission('view_collected_invoice_economic_v2_revenue_statistics');
|
|
|
|
$dateFrom = (string)(self::fromRequest('dateFrom') ?? date('Y-m-01'));
|
|
$dateTo = (string)(self::fromRequest('dateTo') ?? date('Y-m-d'));
|
|
self::requireDateFormat($dateFrom, self::FORMAT_DATE());
|
|
self::requireDateFormat($dateTo, self::FORMAT_DATE());
|
|
if (strtotime($dateFrom) > strtotime($dateTo)) {
|
|
$response->error('dateFrom must be before or equal to dateTo', 400);
|
|
}
|
|
|
|
$barred = strtolower(trim((string)(self::fromRequest('barred') ?? 'all')));
|
|
self::requireInArray($barred, ['all', 'barred', 'active']);
|
|
|
|
$currency = self::fromRequest('currency');
|
|
$currency = ($currency !== null && trim($currency) !== '') ? strtoupper(trim($currency)) : null;
|
|
if ($currency !== null && !preg_match('/^[A-Z]{3}$/', $currency)) {
|
|
$response->error('currency must be a 3-letter ISO code (e.g. DKK)', 400);
|
|
}
|
|
|
|
$max_pages = (int)(self::fromRequest('max_pages') ?? 10);
|
|
self::requireMinValue($max_pages, 1);
|
|
self::requireMaxValue($max_pages, 200);
|
|
|
|
$payload = (new economic_v2_revenue_statistics_service())->getBookedRevenueStatistics([
|
|
'dateFrom' => $dateFrom,
|
|
'dateTo' => $dateTo,
|
|
'customer_numbers' => $this->parseIntegerListParameter('customer_numbers'),
|
|
'department_numbers' => $this->parseIntegerListParameter('department_numbers'),
|
|
'currency' => $currency,
|
|
'barred' => $barred,
|
|
'max_pages' => $max_pages,
|
|
]);
|
|
|
|
$response->success($payload);
|
|
},
|
|
[
|
|
'view_collected_invoice_economic_v2_revenue_statistics' => 'View aggregated booked revenue statistics from e-conomic (V2), including barred-customer filtering.',
|
|
]
|
|
);
|
|
|
|
/** Collected order invoices > Ready to invoice > GET */
|
|
$this->get('/collected-invoices/ready-to-invoice', function () {
|
|
global $response;
|
|
self::requirePermission('list_collected_invoices');
|
|
$user = (new authentication())->get_user();
|
|
if ($user) {
|
|
(new logs_o())->add('orderInvoices', 'global', 1, $user->id, 'LIST_COLLECTED_INVOICES_READY_TO_INVOICE', 'User accessed the list of collected order invoices ready to invoice');
|
|
$collected_order_invoices = new collected_order_invoices_o();
|
|
// Define the users
|
|
$users = new users_o();
|
|
// Define the collected order invoices
|
|
$tmp_collected_order_invoices = new collected_order_invoices_o();
|
|
// Return the list of collected order invoices
|
|
$response->success($collected_order_invoices->listObjectsWithPaginationIfSet(
|
|
function ($collected_order_invoice) use ($tmp_collected_order_invoices, $users) {
|
|
// Select the orders for each collected order invoice
|
|
$tmp_collected_order_invoices->select((int)$collected_order_invoice['id']);
|
|
return $this->getOrderInvoiceDetails($collected_order_invoice, $users, $tmp_collected_order_invoices);
|
|
},
|
|
$collected_order_invoices->forceRestrictFilters(
|
|
[
|
|
// This makes sure that the user can only see orders from the departments they explicitly have access to
|
|
'customer_number' => $collected_order_invoices->listCustomersWithIndividualOrderInvoicing(),
|
|
]
|
|
)
|
|
));
|
|
} else {
|
|
(new logs_o())->add('orderInvoices', 'global', 0, 0, 'LIST_COLLECTED_INVOICES_READY_TO_INVOICE', 'User tried to access the list of collected order invoices ready to invoice without a valid session');
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
},
|
|
[
|
|
'list_collected_invoices' => 'List ALL collected order invoices. This is a superuser-only route.'
|
|
]
|
|
);
|
|
|
|
/** Collected order invoices > POST */
|
|
$this->post('/collected-invoices', function () {
|
|
global $response;
|
|
self::requirePermission('add_collected_invoice');
|
|
$user = (new authentication())->get_user();
|
|
if ($user) {
|
|
(new logs_o())->add('orderInvoices', 'global', 1, $user->id, 'ADD_COLLECTED_INVOICE', 'User added a collected order invoice');
|
|
// Require the customer number, and validate its type and length
|
|
self::requireParameters(['customer_number']);
|
|
self::requireType((int)self::getParameter('customer_number'), self::type_int());
|
|
self::requireMinLength('customer_number', 1);
|
|
self::requireMaxLength('customer_number', 10);
|
|
// Require the customer number to be above 0
|
|
self::requireMinValue((int)self::getParameter('customer_number'), 1);
|
|
// Validate the customer number against the database
|
|
$customer = (new users_o())->select((int)self::getParameter('customer_number'));
|
|
$customer->requireSelected();
|
|
// Define the variables
|
|
$name = null;
|
|
$notes = null;
|
|
$processor = null;
|
|
$closed_at = null;
|
|
// Check if the name is set
|
|
if (self::isParametersSet(['name'])) {
|
|
self::requireType((string)self::getParameter('name'), self::type_string());
|
|
self::requireMinLength('name', 1);
|
|
self::requireMaxLength('name', 255);
|
|
$name = (string)self::getParameter('name');
|
|
}
|
|
// Check if the notes are set
|
|
if (self::isParametersSet(['notes'])) {
|
|
self::requireType((string)self::getParameter('notes'), self::type_string());
|
|
$notes = (string)self::getParameter('notes');
|
|
}
|
|
// Check if the processor is set
|
|
if (self::isParametersSet(['processor'])) {
|
|
self::requireType((int)self::getParameter('processor'), self::type_int());
|
|
$processor = (int)self::getParameter('processor');
|
|
}
|
|
// Check if the closed_at is set
|
|
if (self::isParametersSet(['closed_at'])) {
|
|
// Check if the closed date is set, and validate its type
|
|
if (!!self::getParameter('closed_at')) {
|
|
self::requireType((string)self::getParameter('closed_at'), self::type_string());
|
|
$closed_at = (string)self::getParameter('closed_at');
|
|
// Check if the closed date is valid
|
|
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $closed_at)) {
|
|
$response->error('Invalid closed date format', 400);
|
|
}
|
|
// Set the time to 00:00:01z
|
|
$closed_at = date('Y-m-d H:i:s', strtotime($closed_at . ' 00:00:01'));
|
|
}
|
|
}
|
|
|
|
// Add the collected order invoice
|
|
$collected_order_invoices = new collected_order_invoices_o();
|
|
$collected_order_invoices->add(
|
|
$customer->id,
|
|
$name,
|
|
$notes,
|
|
$processor,
|
|
$closed_at ?: null,
|
|
);
|
|
$response->success($collected_order_invoices->asArray());
|
|
} else {
|
|
(new logs_o())->add('orderInvoices', 'global', 0, 0, 'ADD_COLLECTED_INVOICE', 'User tried to add a collected order invoice without a valid session');
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
},
|
|
[
|
|
'add_collected_invoice' => 'Add a collected order invoice. This is a superuser-only route.'
|
|
]
|
|
);
|
|
|
|
/** Collected order invoices > Split > POST */
|
|
$this->post('/collected-invoices/split', function () {
|
|
global $response;
|
|
self::requirePermission('split_collected_invoice');
|
|
$user = (new authentication())->get_user();
|
|
if ($user) {
|
|
(new logs_o())->add('orderInvoices', 'global', 1, $user->id, 'SPLIT_COLLECTED_INVOICE', 'User split a collected order invoice');
|
|
// Require the ID, and validate its type and length
|
|
self::requireParameters(['id']);
|
|
self::requireType((int)self::getParameter('id'), self::type_int());
|
|
self::requireMinValue((int)self::getParameter('id'), 1);
|
|
// Require the ID to be above 0
|
|
self::requireMinValue((int)self::getParameter('id'), 1);
|
|
// Validate the ID against the database
|
|
$collected_order_invoices = (new collected_order_invoices_o())->select((int)self::getParameter('id'));
|
|
$collected_order_invoices->requireSelected();
|
|
// Split the collected order invoice
|
|
$collected_order_invoices->split();
|
|
// Return the collected order invoice
|
|
$response->success($collected_order_invoices->asArray());
|
|
} else {
|
|
(new logs_o())->add('orderInvoices', 'global', 0, 0, 'SPLIT_COLLECTED_INVOICE', 'User tried to split a collected order invoice without a valid session');
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
},
|
|
[
|
|
'split_collected_invoice' => 'Split a collected order invoice. This is a superuser-only route.'
|
|
]
|
|
);
|
|
|
|
/** Collected order invoices > E-Conomic > POST */
|
|
$this->post('/collected-invoices/economic', function () {
|
|
global $response;
|
|
self::requirePermission('add_collected_invoice_economic');
|
|
$user = (new authentication())->get_user();
|
|
if ($user) {
|
|
(new logs_o())->add('orderInvoices', 'global', 1, $user->id, 'ADD_COLLECTED_INVOICE_ECONOMIC', 'User added a collected order invoice to E-Conomic');
|
|
// Require the ID, and validate its type and length
|
|
self::requireParameters(['id']);
|
|
self::requireType((int)self::getParameter('id'), self::type_int());
|
|
self::requireMinLength('id', 1);
|
|
self::requireMaxLength('id', 10);
|
|
// Require the ID to be above 0
|
|
self::requireMinValue((int)self::getParameter('id'), 1);
|
|
// If the send_as_is parameter is set, validate its type
|
|
$send_as_is = false; // This is false by default, when true it will send the invoice as is, without adding vehicle subscriptions or fixed pricing. This is used when a customer with fixed pricing has already been invoiced with the fixed price, and we just need to send the invoice to E-Conomic.
|
|
if (self::isParametersSet(['send_as_is'])) {
|
|
self::requireType((bool)self::getParameter('send_as_is'), self::type_bool());
|
|
$send_as_is = (bool)self::getParameter('send_as_is');
|
|
}
|
|
// Validate the ID against the database
|
|
$collected_order_invoices = (new collected_order_invoices_o())->select((int)self::getParameter('id'));
|
|
$collected_order_invoices->requireSelected();
|
|
// Check if the collected order invoice has an external ID
|
|
if ($collected_order_invoices->external_id->value() === null) {
|
|
// Check if the send_as_is parameter is set to true
|
|
if (!$send_as_is) {
|
|
// Add fixed price to the collected order invoice
|
|
$customer_fixed_pricing_o = new customer_fixed_pricing_o();
|
|
if ($customer_fixed_pricing_o->doesUserHaveFixedPricing((int)$collected_order_invoices->customer_number->value())) {
|
|
$customer_fixed_pricing_price = $customer_fixed_pricing_o->selectByCustomerNumber((int)$collected_order_invoices->customer_number->value());
|
|
$customer_fixed_pricing_price = (int)$customer_fixed_pricing_price->price->value();
|
|
$collected_order_invoices->overridePricesFixed((int)$customer_fixed_pricing_price);
|
|
} else {
|
|
// Apply vehicle subscriptions if the customer does not have fixed pricing
|
|
$collected_order_invoices->addVehicleSubscriptionsTransaction();
|
|
}
|
|
} else {
|
|
// Since we are sending the invoice as is, we need to check if the invoice has any left-over subscription / fixed price transactions
|
|
$collected_order_invoices->removeSpecialArrangements(); // Remove any left-over special arrangement transactions (subscriptions / fixed price)
|
|
// Reset the price of all items set not to be included in the invoice
|
|
//$collected_order_invoices->resetPricesOfItemsNotIncludedInInvoice();
|
|
// Set all items to be included in the invoice
|
|
$collected_order_invoices->setAllItemsToBeIncludedInInvoice(); // Set all items to be included in the invoice, since we are sending the invoice as is.
|
|
}
|
|
// Add the collected order invoice to E-Conomic
|
|
$collected_order_invoices->addToEconomic();
|
|
$response->success($collected_order_invoices->asArray());
|
|
}
|
|
// Check if the invoice has been booked
|
|
if ($collected_order_invoices->booked_invoice_id->value() !== null) {
|
|
$response->error('Invoice has already been booked', 400);
|
|
}
|
|
// Check if the invoice draft exists in E-Conomic
|
|
if ($collected_order_invoices->isDraftExisting()) {
|
|
$response->error('Invoice draft already exists in E-Conomic', 400);
|
|
}
|
|
// Add fixed price to the collected order invoice
|
|
$customer_fixed_pricing_o = new customer_fixed_pricing_o();
|
|
if ($customer_fixed_pricing_o->doesUserHaveFixedPricing((int)$collected_order_invoices->customer_number->value())) {
|
|
$customer_fixed_pricing_price = $customer_fixed_pricing_o->selectByCustomerNumber((int)$collected_order_invoices->customer_number->value());
|
|
$customer_fixed_pricing_price = (int)$customer_fixed_pricing_price->price->value();
|
|
$collected_order_invoices->overridePricesFixed((int)$customer_fixed_pricing_price);
|
|
} else {
|
|
// Apply vehicle subscriptions if the customer does not have fixed pricing
|
|
$collected_order_invoices->addVehicleSubscriptionsTransaction();
|
|
}
|
|
// Create the invoice in E-Conomic
|
|
$collected_order_invoices->addToEconomic(true);
|
|
// Return the collected order invoice
|
|
$response->success($collected_order_invoices->asArray());
|
|
} else {
|
|
(new logs_o())->add('orderInvoices', 'global', 0, 0, 'ADD_COLLECTED_INVOICE_ECONOMIC', 'User tried to add a collected order invoice to E-Conomic without a valid session');
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
},
|
|
[
|
|
'add_collected_invoice_economic' => 'Add a collected order invoice to E-Conomic. This is a superuser-only route.'
|
|
]
|
|
);
|
|
|
|
/** Collected order invoices > E-Conomic > Unlink and clear cached data > POST */
|
|
$this->post('/collected-invoices/economic/unlink', function () {
|
|
global $response;
|
|
self::requirePermission('unlink_collected_invoice_economic');
|
|
$user = (new authentication())->get_user();
|
|
if ($user) {
|
|
(new logs_o())->add('orderInvoices', 'global', 1, $user->id, 'UNLINK_COLLECTED_INVOICE_ECONOMIC', 'User unlinked a collected order invoice from E-Conomic');
|
|
// Require the ID, and validate its type and length
|
|
self::requireParameters(['id']);
|
|
self::requireType((int)self::getParameter('id'), self::type_int());
|
|
self::requireMinLength('id', 1);
|
|
self::requireMaxLength('id', 10);
|
|
// Require the ID to be above 0
|
|
self::requireMinValue((int)self::getParameter('id'), 1);
|
|
// Validate the ID against the database
|
|
$collected_order_invoices = (new collected_order_invoices_o())->select((int)self::getParameter('id'));
|
|
$collected_order_invoices->requireSelected();
|
|
// Unlink the collected order invoice from E-Conomic
|
|
$collected_order_invoices->unlinkFromEconomic();
|
|
// Return the collected order invoice
|
|
$response->success($collected_order_invoices->asArray());
|
|
} else {
|
|
(new logs_o())->add('orderInvoices', 'global', 0, 0, 'UNLINK_COLLECTED_INVOICE_ECONOMIC', 'User tried to unlink a collected order invoice from E-Conomic without a valid session');
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
},
|
|
[
|
|
'unlink_collected_invoice_economic' => 'Unlink a collected order invoice from E-Conomic. This is a superuser-only route. This will NOT delete the invoice in E-Conomic, but will clear the cached data in the system.'
|
|
]
|
|
);
|
|
|
|
/** Collected order invoices > E-Conomic > Remove special arrangements, and set all items to be included in the invoice > POST */
|
|
$this->post('/collected-invoices/remove-special-arrangements', function () {
|
|
global $response;
|
|
self::requirePermission('reset_collected_invoice_economic');
|
|
$user = (new authentication())->get_user();
|
|
if ($user) {
|
|
(new logs_o())->add('orderInvoices', 'global', 1, $user->id, 'RESET_COLLECTED_INVOICE_ECONOMIC', 'User reset a collected order invoice in E-Conomic');
|
|
// Require the ID, and validate its type and length
|
|
self::requireParameters(['id']);
|
|
self::requireType((int)self::getParameter('id'), self::type_int());
|
|
self::requireMinLength('id', 1);
|
|
self::requireMaxLength('id', 10);
|
|
// Require the ID to be above 0
|
|
self::requireMinValue((int)self::getParameter('id'), 1);
|
|
// Validate the ID against the database
|
|
$collected_order_invoices = (new collected_order_invoices_o())->select((int)self::getParameter('id'));
|
|
$collected_order_invoices->requireSelected();
|
|
// Remove special arrangements, and set all items to be included in the invoice
|
|
$collected_order_invoices->removeSpecialArrangements(); // Remove any left-over special arrangement transactions (subscriptions / fixed price)
|
|
//$collected_order_invoices->resetPricesOfItemsNotIncludedInInvoice(); // Reset the price of all items set not to be included in the invoice
|
|
$collected_order_invoices->setAllItemsToBeIncludedInInvoice(); // Set all items to be included in the invoice
|
|
$collected_order_invoices->objectChanged();
|
|
// Return the collected order invoice
|
|
$response->success($collected_order_invoices->asArray());
|
|
} else {
|
|
(new logs_o())->add('orderInvoices', 'global', 0, 0, 'RESET_COLLECTED_INVOICE_ECONOMIC', 'User tried to reset a collected order invoice in E-Conomic without a valid session');
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
},
|
|
[
|
|
'reset_collected_invoice_economic' => 'Reset a collected order invoice in E-Conomic. This is a superuser-only route. This will NOT delete the invoice in E-Conomic, but will remove any special arrangement transactions (subscriptions / fixed price), and set all items to be included in the invoice.'
|
|
]
|
|
);
|
|
/** Collected order invoices > E-Conomic > Reset prices of items not included in the invoice > POST */
|
|
$this->post('/collected-invoices/reset-prices-of-items-not-included-in-invoice', function () {
|
|
global $response;
|
|
self::requirePermission('reset_collected_invoice_economic');
|
|
$user = (new authentication())->get_user();
|
|
if ($user) {
|
|
(new logs_o())->add('orderInvoices', 'global', 1, $user->id, 'RESET_COLLECTED_INVOICE_ECONOMIC', 'User reset a collected order invoice in E-Conomic');
|
|
// Require the ID, and validate its type and length
|
|
self::requireParameters(['id']);
|
|
self::requireType((int)self::getParameter('id'), self::type_int());
|
|
self::requireMinLength('id', 1);
|
|
self::requireMaxLength('id', 10);
|
|
// Require the ID to be above 0
|
|
self::requireMinValue((int)self::getParameter('id'), 1);
|
|
// Validate the ID against the database
|
|
$collected_order_invoices = (new collected_order_invoices_o())->select((int)self::getParameter('id'));
|
|
$collected_order_invoices->requireSelected();
|
|
// Reset prices of items not included in the invoice
|
|
$collected_order_invoices->resetPricesOfItemsNotIncludedInInvoice(); // Reset the price of all items set not to be included in the invoice
|
|
$collected_order_invoices->objectChanged();
|
|
// Return the collected order invoice
|
|
$response->success($collected_order_invoices->asArray());
|
|
} else {
|
|
(new logs_o())->add('orderInvoices', 'global', 0, 0, 'RESET_COLLECTED_INVOICE_ECONOMIC', 'User tried to reset a collected order invoice in E-Conomic without a valid session');
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
},
|
|
[
|
|
'reset_collected_invoice_economic' => 'Reset a collected order invoice in E-Conomic. This is a superuser-only route. This will NOT delete the invoice in E-Conomic, but will reset the prices of all items set not to be included in the invoice.'
|
|
]
|
|
);
|
|
|
|
/** Collected order invoices > Stripe > BOOK > POST */
|
|
$this->post('/collected-invoices/stripe/book', function () {
|
|
global $response;
|
|
self::requirePermission('add_collected_invoice_stripe');
|
|
$user = (new authentication())->get_user();
|
|
if ($user) {
|
|
(new logs_o())->add('orderInvoices', 'global', 1, $user->id, 'ADD_COLLECTED_INVOICE_STRIPE', 'User added a collected order invoice to Stripe');
|
|
// Require the ID, and validate its type and length
|
|
self::requireParameters(['id']);
|
|
self::requireType((int)self::getParameter('id'), self::type_int());
|
|
self::requireMinLength('id', 1);
|
|
self::requireMaxLength('id', 10);
|
|
// Require the ID to be above 0
|
|
self::requireMinValue((int)self::getParameter('id'), 1);
|
|
// Validate the ID against the database
|
|
$collected_order_invoices = (new collected_order_invoices_o())->select((int)self::getParameter('id'));
|
|
$collected_order_invoices->requireSelected();
|
|
// Require the external ID to be set
|
|
if ($collected_order_invoices->external_id->value() === null) {
|
|
$response->error('Transaction has not been created in Stripe', 400);
|
|
}
|
|
// Require the booked invoice ID to be not set
|
|
if ($collected_order_invoices->booked_invoice_id->value() !== null) {
|
|
$response->error('Invoice has already been booked', 400);
|
|
}
|
|
// Add the collected order invoice to Stripe
|
|
$collected_order_invoices->addToEconomic(true);
|
|
// Return the collected order invoice
|
|
$response->success($collected_order_invoices->asArray());
|
|
} else {
|
|
(new logs_o())->add('orderInvoices', 'global', 0, 0, 'ADD_COLLECTED_INVOICE_STRIPE', 'User tried to add a collected order invoice to Stripe without a valid session');
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
},
|
|
[
|
|
'add_collected_invoice_stripe' => 'Add a collected order invoice to Stripe. This is a superuser-only route.'
|
|
]
|
|
);
|
|
|
|
/** Collected order invoices > Vehicle subscriptions > POST */
|
|
$this->post('/collected-invoices/vehicle-subscriptions', function () {
|
|
global $response;
|
|
self::requirePermission('add_collected_invoice_vehicle_subscriptions');
|
|
$user = (new authentication())->get_user();
|
|
if ($user) {
|
|
(new logs_o())->add('orderInvoices', 'global', 1, $user->id, 'ADD_COLLECTED_INVOICE_VEHICLE_SUBSCRIPTIONS', 'User added a collected order invoice for vehicle subscriptions');
|
|
// Require the ID, and validate its type and length
|
|
self::requireParameters(['id']);
|
|
self::requireType((int)self::getParameter('id'), self::type_int());
|
|
self::requireMinLength('id', 1);
|
|
self::requireMaxLength('id', 10);
|
|
// Require the ID to be above 0
|
|
self::requireMinValue((int)self::getParameter('id'), 1);
|
|
// Validate the ID against the database
|
|
$collected_order_invoices = (new collected_order_invoices_o())->select((int)self::getParameter('id'));
|
|
$collected_order_invoices->requireSelected();
|
|
// Add the collected order invoice to E-Conomic
|
|
$collected_order_invoices->addVehicleSubscriptionsTransaction();
|
|
// Return the collected order invoice
|
|
$response->success($collected_order_invoices->asArray());
|
|
} else {
|
|
(new logs_o())->add('orderInvoices', 'global', 0, 0, 'ADD_COLLECTED_INVOICE_VEHICLE_SUBSCRIPTIONS', 'User tried to add a collected order invoice for vehicle subscriptions without a valid session');
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
},
|
|
[
|
|
'add_collected_invoice_vehicle_subscriptions' => 'Add a collected order invoice for vehicle subscriptions. This is a superuser-only route.'
|
|
]
|
|
);
|
|
|
|
/** Collected order invoices > Vehicle subscriptions > POST */
|
|
$this->post('/collected-invoices/fixed-price', function () {
|
|
global $response;
|
|
self::requirePermission('add_collected_invoice_fixed_price');
|
|
$user = (new authentication())->get_user();
|
|
if ($user) {
|
|
(new logs_o())->add('orderInvoices', 'global', 1, $user->id, 'ADD_COLLECTED_INVOICE_FIXED_PRICE', 'User added a collected order invoice fixed price modifications');
|
|
// Require the ID, and validate its type and length
|
|
self::requireParameters(['id']);
|
|
self::requireType((int)self::getParameter('id'), self::type_int());
|
|
self::requireMinLength('id', 1);
|
|
self::requireMaxLength('id', 10);
|
|
// Require the ID to be above 0
|
|
self::requireMinValue((int)self::getParameter('id'), 1);
|
|
// Validate the ID against the database
|
|
$collected_order_invoices = (new collected_order_invoices_o())->select((int)self::getParameter('id'));
|
|
$collected_order_invoices->requireSelected();
|
|
// Check if the customer has a fixed price
|
|
$customer = new users_o();
|
|
$customer->getUserByCustomerNumber((int)$collected_order_invoices->customer_number->value());
|
|
$customer_fixed_pricing_o = new customer_fixed_pricing_o();
|
|
if (!$customer_fixed_pricing_o->doesUserHaveFixedPricing((int)$customer->customer_number->value())) {
|
|
$response->error('Customer does not have fixed pricing', 400);
|
|
}
|
|
$customer_fixed_pricing_price = $customer_fixed_pricing_o->selectByCustomerNumber((int)$customer->customer_number->value());
|
|
$customer_fixed_pricing_price = (int)$customer_fixed_pricing_price->price->value();
|
|
// Add the collected order invoice to E-Conomic
|
|
$collected_order_invoices->overridePricesFixed((int)$customer_fixed_pricing_price);
|
|
// Return the collected order invoice
|
|
$response->success($collected_order_invoices->asArray());
|
|
} else {
|
|
(new logs_o())->add('orderInvoices', 'global', 0, 0, 'ADD_COLLECTED_INVOICE_FIXED_PRICE', 'User tried to add a collected order invoice fixed price modifications without a valid session');
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
},
|
|
[
|
|
'add_collected_invoice_fixed_price' => 'Add a collected order invoice fixed price modifications. This is a superuser-only route.'
|
|
]
|
|
);
|
|
|
|
/** Collected order invoices > Vehicle subscriptions > POST */
|
|
$this->post('/collected-invoices/vehicle-subscriptions/custom', function () {
|
|
global $response;
|
|
self::requirePermission('add_collected_invoice_vehicle_subscriptions');
|
|
$user = (new authentication())->get_user();
|
|
if ($user) {
|
|
(new logs_o())->add('orderInvoices', 'global', 1, $user->id, 'ADD_COLLECTED_INVOICE_VEHICLE_SUBSCRIPTIONS', 'User added a collected order invoice for vehicle subscriptions');
|
|
// Require the ID, and validate its type and length
|
|
self::requireParameters(['customer_number', 'month', 'year']);
|
|
self::requireType((int)self::getParameter('customer_number'), self::type_int());
|
|
self::requireMinLength('customer_number', 1);
|
|
self::requireMaxLength('customer_number', 10);
|
|
self::requireMinValue((int)self::getParameter('customer_number'), 1);
|
|
$customer = (new users_o())->getUserByCustomerNumber((int)self::getParameter('customer_number'));
|
|
$customer->requireSelected();
|
|
// Require the month to be between 1 and 12
|
|
self::requireType((int)self::getParameter('month'), self::type_int());
|
|
self::requireMinLength('month', 1);
|
|
self::requireMaxLength('month', 2);
|
|
self::requireMinValue((int)self::getParameter('month'), 1);
|
|
self::requireMaxValue((int)self::getParameter('month'), 12);
|
|
// Require the year to be in the past 2 years
|
|
self::requireType((int)self::getParameter('year'), self::type_int());
|
|
self::requireMinLength('year', 1);
|
|
self::requireMaxLength('year', 4);
|
|
self::requireMinValue((int)self::getParameter('year'), date('Y') - 2);
|
|
self::requireMaxValue((int)self::getParameter('year'), date('Y'));
|
|
// Create the collected order invoice
|
|
$collected_order_invoices = new collected_order_invoices_o();
|
|
$collected_order_invoices->add(
|
|
(int)self::getParameter('customer_number'),
|
|
);
|
|
// Require the collected order invoice to be selected
|
|
$collected_order_invoices->requireSelected();
|
|
// Get the month and year from the request
|
|
$month = (int)self::getParameter('month');
|
|
$year = (int)self::getParameter('year');
|
|
// Add a leading zero to the month if it's less than 10
|
|
$month_with_prefix_if_applicable = str_pad($month, 2, '0', STR_PAD_LEFT);
|
|
// Add a leading zero to the year if it's less than 4 digits
|
|
$year_with_prefix_if_applicable = str_pad($year, 4, '0', STR_PAD_LEFT);
|
|
// Generate the timestamp for the selected month and year, with the first day, and first second
|
|
$timestamp_selected = mktime(0, 0, 1, $month_with_prefix_if_applicable, 1, $year_with_prefix_if_applicable);
|
|
// MySQL requires the date to be in the format YYYY-MM-DD HH:MM:SS
|
|
$timestamp_selected = date('Y-m-d H:i:s', $timestamp_selected);
|
|
// Set the date to the first second of the date specified
|
|
$collected_order_invoices->created_at->set($timestamp_selected);
|
|
// Add the collected order invoice to E-Conomic
|
|
$collected_order_invoices->addVehicleSubscriptionsTransaction();
|
|
// This
|
|
// Close the collected order invoice
|
|
$collected_order_invoices->closed_at->set($timestamp_selected);
|
|
// Update the collected order invoice
|
|
$collected_order_invoices->objectChanged();
|
|
// Return the collected order invoice
|
|
$response->success($collected_order_invoices->asArray());
|
|
} else {
|
|
(new logs_o())->add('orderInvoices', 'global', 0, 0, 'ADD_COLLECTED_INVOICE_VEHICLE_SUBSCRIPTIONS', 'User tried to add a collected order invoice for vehicle subscriptions without a valid session');
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
},
|
|
[
|
|
'add_collected_invoice_vehicle_subscriptions' => 'Add a collected order invoice for vehicle subscriptions. This is a superuser-only route.'
|
|
]
|
|
);
|
|
|
|
/** Collected order invoices > Open > GET Customers */
|
|
$this->get('/collected-invoices/customers', function () {
|
|
global $response;
|
|
self::requirePermission('list_collected_invoices');
|
|
$user = (new authentication())->get_user();
|
|
if ($user) {
|
|
(new logs_o())->add('orderInvoices', 'global', 1, $user->id, 'LIST_COLLECTED_INVOICES_CUSTOMERS', 'User accessed the list of collected order invoices customers');
|
|
// Define the users
|
|
$users_o = new users_o();
|
|
$tmp_customer_numbers = $users_o->getCustomerNumbersWithAttributes([
|
|
'invoiceAllOrdersIndividually'
|
|
]);
|
|
// Filter out the customers that do not have any open invoices
|
|
$customer_numbers = [];
|
|
//print_r($tmp_customer_numbers);
|
|
foreach ( $tmp_customer_numbers as $customer_number ) {
|
|
|
|
// This is the customers open invoices, this might contain invoices that's empty, or only contains deleted orders.
|
|
$tmp_customer_open_invoice_ids = [];
|
|
// This is the net amount of the total transactions for the customer
|
|
$tmp_total_net_amount = 0;
|
|
// This is the number of invoices, that's not empty.
|
|
$tmp_customer_active_invoice_ids = [];
|
|
$collected_order_invoices = new collected_order_invoices_o();
|
|
$customer_open_invoices = $collected_order_invoices->listObjectsWithPaginationIfSet(
|
|
function ($collected_order_invoice) use ($tmp_total_net_amount, $users_o, $collected_order_invoices) {
|
|
// Select the orders for each collected order invoice
|
|
$collected_order_invoices->select((int)$collected_order_invoice['id']);
|
|
// Return the details
|
|
return $this->getOrderInvoiceDetails($collected_order_invoice, $users_o, $collected_order_invoices);
|
|
},
|
|
$collected_order_invoices->forceRestrictFilters([
|
|
'customer_number' => $customer_number,
|
|
'closed_at' => 'null',
|
|
])
|
|
);
|
|
//print_r($customer_open_invoices);
|
|
// Check if the customer has any open invoices
|
|
if (count($customer_open_invoices) > 0) {
|
|
$tmp_customer_orders = 0;
|
|
// Get the total net amount of the open invoices
|
|
foreach ( $customer_open_invoices as $customer_open_invoice ) {
|
|
// Check if the invoice is empty
|
|
if (count($customer_open_invoice['orders']) === 0) {
|
|
continue;
|
|
}
|
|
// Add the invoice collected order invoice ID to the list (if it's not already in the list)
|
|
if (!in_array($customer_open_invoice['id'], $tmp_customer_open_invoice_ids)) {
|
|
$tmp_customer_active_invoice_ids[] = $customer_open_invoice['id'];
|
|
}
|
|
// Update the total net amount, and the number of orders
|
|
$tmp_customer_open_invoice_ids[] = $customer_open_invoice['id'];
|
|
$tmp_total_net_amount += $customer_open_invoice['total_net_amount'];
|
|
$tmp_customer_orders += count($customer_open_invoice['orders']);
|
|
}
|
|
// If the open invoices are not empty, add the customer to the list
|
|
if (empty($tmp_customer_orders)) {
|
|
continue;
|
|
}
|
|
$tmp_user = (new users_o())->getUserByCustomerNumber($customer_number);
|
|
$customer_numbers[] = array(
|
|
'customer_number' => $customer_number,
|
|
'customer_name' => $users_o->getCustomerName($customer_number),
|
|
'user_id' => $tmp_user->id,
|
|
'open_invoices' => $customer_open_invoices,
|
|
'active_invoices' => $tmp_customer_active_invoice_ids,
|
|
'orders' => $tmp_customer_orders,
|
|
'total_net_amount' => $tmp_total_net_amount,
|
|
);
|
|
}
|
|
}
|
|
// Return the list of customers
|
|
$response->success($customer_numbers);
|
|
} else {
|
|
(new logs_o())->add('orderInvoices', 'global', 0, 0, 'LIST_COLLECTED_INVOICES_CUSTOMERS', 'User tried to access the list of collected order invoices customers without a valid session');
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
},
|
|
[
|
|
'list_collected_invoices_customers' => 'List ALL collected order invoices customers. This is a superuser-only route.'
|
|
]
|
|
);
|
|
|
|
$this->get('/collected-invoices/customers/invoicePerOrder', function () {
|
|
global $response;
|
|
self::requirePermission('list_collected_invoices');
|
|
$user = (new authentication())->get_user();
|
|
if ($user) {
|
|
(new logs_o())->add('orderInvoices', 'global', 1, $user->id, 'LIST_COLLECTED_INVOICES_CUSTOMERS_INVOICE_PER_ORDER', 'User accessed the list of collected order invoices customers with invoice per order');
|
|
// Define the users
|
|
$users_o = new users_o();
|
|
$users_o->setView('customers_invoice_count_per_order');
|
|
$result = $users_o
|
|
->setSearchableFields([
|
|
'customer_number',
|
|
'customer_name',
|
|
'active_invoices',
|
|
'closed_invoices',
|
|
])
|
|
->listObjectsWithPaginationIfSet(
|
|
function ($collected_order_invoice) use ($users_o) {
|
|
return [
|
|
'customer_number' => (int)$collected_order_invoice['customer_number'],
|
|
'display_name' => (string)$users_o->getCustomerName((int)$collected_order_invoice['customer_number']),
|
|
'active_invoices' => (int)$collected_order_invoice['active_invoices'],
|
|
'closed_invoices' => (int)$collected_order_invoice['closed_invoices'],
|
|
];
|
|
},
|
|
$users_o->forceRestrictFilters(
|
|
[
|
|
//'active_invoices' => 'NOT ZERO',
|
|
]
|
|
)
|
|
);
|
|
// Return the list of customers
|
|
$response->success($result);
|
|
} else {
|
|
(new logs_o())->add('orderInvoices', 'global', 0, 0, 'LIST_COLLECTED_INVOICES_CUSTOMERS_INVOICE_PER_ORDER', 'User tried to access the list of collected order invoices customers with invoice per order without a valid session');
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
},
|
|
[
|
|
'list_collected_invoices_customers_invoice_per_order' => 'List ALL collected order invoices customers with invoice per order. This is a superuser-only route.'
|
|
]
|
|
);
|
|
|
|
$this->get('/collected-invoices/customers/invoicePerMonth', function () {
|
|
global $response;
|
|
self::requirePermission('list_collected_invoices');
|
|
$user = (new authentication())->get_user();
|
|
if ($user) {
|
|
(new logs_o())->add('orderInvoices', 'global', 1, $user->id, 'LIST_COLLECTED_INVOICES_CUSTOMERS_INVOICE_PER_MONTH', 'User accessed the list of collected order invoices customers with invoice per month');
|
|
// Define the users
|
|
$users_o = new users_o();
|
|
$users_o->setView('customers_invoice_count_per_month');
|
|
$result = $users_o
|
|
->setSearchableFields([
|
|
'customer_number',
|
|
'customer_name',
|
|
'active_invoices',
|
|
'closed_invoices',
|
|
])
|
|
->listObjectsWithPaginationIfSet(
|
|
function ($collected_order_invoice) use ($users_o) {
|
|
return [
|
|
'customer_number' => (int)$collected_order_invoice['customer_number'],
|
|
'display_name' => (string)$users_o->getCustomerName((int)$collected_order_invoice['customer_number']),
|
|
'active_invoices' => (int)$collected_order_invoice['active_invoices'],
|
|
'closed_invoices' => (int)$collected_order_invoice['closed_invoices'],
|
|
];
|
|
},
|
|
$users_o->forceRestrictFilters(
|
|
[
|
|
//'active_invoices' => 'NOT ZERO',
|
|
]
|
|
)
|
|
);
|
|
// Return the list of customers
|
|
$response->success($result);
|
|
} else {
|
|
(new logs_o())->add('orderInvoices', 'global', 0, 0, 'LIST_COLLECTED_INVOICES_CUSTOMERS_INVOICE_PER_MONTH', 'User tried to access the list of collected order invoices customers with invoice per month without a valid session');
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
},
|
|
[
|
|
'list_collected_invoices_customers_invoice_per_month' => 'List ALL collected order invoices customers with invoice per month. This is a superuser-only route.'
|
|
]
|
|
);
|
|
|
|
|
|
$this->post('/collected-invoices/customers/invoiceTotals', function () {
|
|
// This is a superuser-only route
|
|
global $response;
|
|
self::requirePermission('list_collected_invoices');
|
|
$user = (new authentication())->get_user();
|
|
if ($user) {
|
|
(new logs_o())->add('orderInvoices', 'global', 1, $user->id, 'LIST_COLLECTED_INVOICES_CUSTOMERS_INVOICE_TOTALS', 'User accessed the list of collected order invoices customers with invoice totals');
|
|
// Require the customer_numbers parameter
|
|
self::requireParameters(['customer_numbers']);
|
|
// Print the customer_numbers parameter
|
|
$customer_numbers = self::getParameter('customer_numbers');
|
|
// validate the customer_numbers parameter
|
|
if (!is_array($customer_numbers)) {
|
|
$response->error('customer_numbers must be an array', 400);
|
|
}
|
|
// Validate the customer_numbers parameter
|
|
foreach ( $customer_numbers as $customer_number ) {
|
|
self::requireType((int)$customer_number, self::type_int());
|
|
if (!is_numeric($customer_number)) {
|
|
$response->error('customer_numbers must be an array of integers', 400);
|
|
}
|
|
if (!$customer_number || $customer_number < 1) {
|
|
$response->error('customer_numbers must be an array of integers greater than 0', 400);
|
|
}
|
|
}
|
|
// Now we can safely use the customer_numbers parameter
|
|
// Define the result array
|
|
$result = [];
|
|
// Loop through the customer_numbers array, and get the invoice totals for each customer
|
|
foreach ( $customer_numbers as $customer_number ) {
|
|
// Define the invoices array
|
|
$tmp_invoices = [
|
|
'closed_invoices' => [],
|
|
'open_invoices' => [],
|
|
];
|
|
// Define the total net amount
|
|
$tmp_total_net_amount = [
|
|
'closed_invoices' => 0,
|
|
'open_invoices' => 0,
|
|
];
|
|
// Define the collected order invoices
|
|
$collected_order_invoices = new collected_order_invoices_o();
|
|
// Get the collected order invoices for the customer
|
|
$collected_order_invoices->setView('invoices_with_completed_orders');
|
|
$tmp_invoice_collections = $collected_order_invoices->listObjectsWithPaginationIfSet(
|
|
function ($collected_order_invoice) use ($tmp_invoices, $customer_number, $collected_order_invoices) {
|
|
// Select the orders for each collected order invoice
|
|
$collected_order_invoices->select((int)$collected_order_invoice['id']);
|
|
// Get the details for the collected order invoice
|
|
return $this->getOrderInvoiceDetails($collected_order_invoice, new users_o(), $collected_order_invoices);
|
|
},
|
|
$collected_order_invoices->forceRestrictFilters([
|
|
'customer_number' => $customer_number,
|
|
])
|
|
);
|
|
// Loop through the collected order invoices, and get the total net amount
|
|
foreach ( $tmp_invoice_collections as $tmp_invoice_collection ) {
|
|
// Update the total net amount
|
|
if ($tmp_invoice_collection['closed_at'] !== null) {
|
|
$tmp_total_net_amount['closed_invoices'] += $tmp_invoice_collection['total_net_amount'];
|
|
// Add the closed invoice to the closed invoices array
|
|
$tmp_invoices['closed_invoices'][] = $tmp_invoice_collection;
|
|
} else {
|
|
$tmp_total_net_amount['open_invoices'] += $tmp_invoice_collection['total_net_amount'];
|
|
// Add the open invoice to the open invoices array
|
|
$tmp_invoices['open_invoices'][] = $tmp_invoice_collection;
|
|
}
|
|
}
|
|
$tmp_user = (new users_o())->getUserByCustomerNumber($customer_number);
|
|
// Add the customer to the result array
|
|
$result[$customer_number] = [
|
|
'customer_number' => (int)$customer_number,
|
|
'customer_name' => (string)$tmp_user->display_name->value(),
|
|
'user_id' => (int)$tmp_user->id,
|
|
'invoices' => $tmp_invoices,
|
|
'total_net_amount' => $tmp_total_net_amount,
|
|
];
|
|
}
|
|
|
|
// Return the list of customers
|
|
$response->success($result);
|
|
} else {
|
|
(new logs_o())->add('orderInvoices', 'global', 0, 0, 'LIST_COLLECTED_INVOICES_CUSTOMERS_INVOICE_TOTALS', 'User tried to access the list of collected order invoices customers with invoice totals without a valid session');
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
});
|
|
|
|
$this->get('/collected-invoices/economic/overview', function () {
|
|
global $response;
|
|
self::requirePermission('list_collected_invoices_economic_overview');
|
|
$user = (new authentication())->get_user();
|
|
if ($user) {
|
|
// Get the economic module
|
|
$economic = new economic();
|
|
// Get the collected order invoices
|
|
$collected_order_invoices = new collected_order_invoices_o();
|
|
/**
|
|
* Get the total invoices using E-conomic as the processor, with the given restrictions
|
|
* @param $collected_order_invoices collected_order_invoices_o
|
|
* @param $restrictions array
|
|
* @param $view string The MySQL view to use for the query
|
|
* @param $page int The page number to return
|
|
* @param $limit int The number of results to return per page
|
|
* @return int
|
|
*/
|
|
function getTotalInvoices(collected_order_invoices_o $collected_order_invoices, array $restrictions = [], string $view = 'invoices_with_completed_orders', int $page = 1, int $limit = 100000): int
|
|
{
|
|
// Add the economic processor to the restrictions
|
|
$restrictions['processor'] = 1; // E-conomic
|
|
return count(
|
|
$collected_order_invoices->getTotalInvoices(
|
|
$collected_order_invoices,
|
|
$restrictions,
|
|
$view,
|
|
$page,
|
|
$limit,
|
|
1
|
|
)
|
|
);
|
|
}
|
|
|
|
(new logs_o())->add('orderInvoices', 'global', 1, $user->id, 'LIST_COLLECTED_INVOICES_ECONOMIC_OVERVIEW', 'User accessed the list of collected order invoices economic overview');
|
|
// Define the result array
|
|
$result = [
|
|
'total' => getTotalInvoices($collected_order_invoices),
|
|
'paid' => 0,
|
|
'unpaid' => 0,
|
|
'overdue' => 0,
|
|
'booked' => getTotalInvoices(
|
|
$collected_order_invoices,
|
|
[
|
|
'booked_invoice_id' => 'NOT NULL',
|
|
'external_id' => 'NOT NULL',
|
|
],
|
|
'invoices_with_completed_orders'
|
|
),
|
|
'draft' => getTotalInvoices(
|
|
$collected_order_invoices,
|
|
[
|
|
'booked_invoice_id' => null,
|
|
'error_message' => null,
|
|
'external_id' => 'NOT NULL',
|
|
],
|
|
'invoices_with_completed_orders'
|
|
),
|
|
'sent' => 0,
|
|
'error' => getTotalInvoices(
|
|
$collected_order_invoices,
|
|
[
|
|
'booked_invoice_id' => null,
|
|
'external_id' => 'NOT NULL',
|
|
'error_message' => 'NOT NULL',
|
|
],
|
|
'invoices_with_completed_orders'
|
|
),
|
|
];
|
|
// Return the list of customers
|
|
$response->success($result);
|
|
} else {
|
|
(new logs_o())->add('orderInvoices', 'global', 0, 0, 'LIST_COLLECTED_INVOICES_ECONOMIC_OVERVIEW', 'User tried to access the list of collected order invoices economic overview without a valid session');
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
},
|
|
[
|
|
'list_collected_invoices_economic_overview' => 'List ALL collected order invoices overview. This is a superuser-only route.'
|
|
]
|
|
);
|
|
|
|
$this->post('/collected-invoices/economic/run/check-drafts', function () {
|
|
global $response;
|
|
self::requirePermission('module_economic_run_check_drafts');
|
|
$user = (new authentication())->get_user();
|
|
if ($user) {
|
|
(new logs_o())->add('orderInvoices', 'global', 1, $user->id, 'RUN_CHECK_DRAFTS', 'User ran the check drafts');
|
|
// Get the economic module
|
|
$economic = new economic();
|
|
try {
|
|
// Run the check drafts
|
|
$economic->getTasks()->runCheckDrafts();
|
|
} catch (Exception $e) {
|
|
// If the task fails, log the error and return an error response
|
|
(new logs_o())->add('orderInvoices', 'global', 0, 0, 'RUN_CHECK_DRAFTS', 'User tried to run the check drafts, but it failed: ' . $e->getMessage());
|
|
$response->error('Failed to run the check drafts: ' . $e->getMessage(), 500);
|
|
}
|
|
// Return the result
|
|
$response->success('Check drafts completed successfully');
|
|
} else {
|
|
(new logs_o())->add('orderInvoices', 'global', 0, 0, 'RUN_CHECK_DRAFTS', 'User tried to run the check drafts without a valid session');
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
},
|
|
[
|
|
'module_economic_run_check_drafts' => 'Run the check drafts. This is a superuser-only route.'
|
|
]
|
|
);
|
|
|
|
$this->post('/collected-invoices/economic/run/check-errors', function () {
|
|
global $response;
|
|
self::requirePermission('module_economic_run_check_errors');
|
|
$user = (new authentication())->get_user();
|
|
if ($user) {
|
|
(new logs_o())->add('orderInvoices', 'global', 1, $user->id, 'RUN_CHECK_ERRORS', 'User ran the check errors');
|
|
// Get the economic module
|
|
$economic = new economic();
|
|
try {
|
|
// Run the check errors
|
|
$economic->getTasks()->runCheckErrors();
|
|
} catch (Exception $e) {
|
|
// If the task fails, log the error and return an error response
|
|
(new logs_o())->add('orderInvoices', 'global', 0, 0, 'RUN_CHECK_ERRORS', 'User tried to run the check errors, but it failed: ' . $e->getMessage());
|
|
$response->error('Failed to run the check errors: ' . $e->getMessage(), 500);
|
|
}
|
|
// Return the result
|
|
$response->success('Check errors completed successfully');
|
|
} else {
|
|
(new logs_o())->add('orderInvoices', 'global', 0, 0, 'RUN_CHECK_ERRORS', 'User tried to run the check errors without a valid session');
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
},
|
|
[
|
|
'module_economic_run_check_errors' => 'Run the check errors. This is a superuser-only route.'
|
|
]
|
|
);
|
|
}
|
|
|
|
private function requireCollectedInvoiceId(): int
|
|
{
|
|
self::requireParameters(['collected_invoice_id']);
|
|
self::requireType((int)self::getParameter('collected_invoice_id'), self::type_int());
|
|
$collected_invoice_id = (int)self::getParameter('collected_invoice_id');
|
|
self::requireMinValue($collected_invoice_id, 1);
|
|
self::requireMaxValue($collected_invoice_id, 999999999);
|
|
return $collected_invoice_id;
|
|
}
|
|
|
|
/**
|
|
* Build normalized V2 details for a collected invoice and available e-conomic targets.
|
|
* @throws Exception
|
|
*/
|
|
private function buildEconomicV2DetailsPayload(int $collected_invoice_id): array
|
|
{
|
|
$warnings = [];
|
|
$invoice = (new collected_order_invoices_o())->select($collected_invoice_id);
|
|
$invoice->requireSelected();
|
|
|
|
$draft_id = null;
|
|
$booked_id = null;
|
|
$draft_raw = null;
|
|
$booked_raw = null;
|
|
$customer = [
|
|
'internal_customer_number' => (int)$invoice->customer_number->value() > 0 ? (int)$invoice->customer_number->value() : null,
|
|
'draft_customer_number' => null,
|
|
'booked_customer_number' => null,
|
|
'exists' => false,
|
|
'name' => null,
|
|
'barred' => null,
|
|
];
|
|
|
|
$economic = new economic();
|
|
|
|
try {
|
|
$draft_id = $invoice->getInvoiceDraftId();
|
|
} catch (Exception $e) {
|
|
$warnings[] = 'Draft id unavailable: ' . $e->getMessage();
|
|
}
|
|
|
|
try {
|
|
$booked_id = $invoice->getInvoiceBookedId();
|
|
} catch (Exception $e) {
|
|
$warnings[] = 'Booked id unavailable: ' . $e->getMessage();
|
|
}
|
|
|
|
if ($draft_id !== null) {
|
|
try {
|
|
$draft_raw = $economic->invoices->draft->get((int)$draft_id);
|
|
} catch (Exception $e) {
|
|
$warnings[] = 'Unable to fetch draft invoice ' . (int)$draft_id . ': ' . $e->getMessage();
|
|
}
|
|
}
|
|
|
|
if ($booked_id !== null) {
|
|
try {
|
|
$booked_raw = $economic->invoices->booked->getFromId((int)$booked_id);
|
|
} catch (Exception $e) {
|
|
$warnings[] = 'Unable to fetch booked invoice ' . (int)$booked_id . ': ' . $e->getMessage();
|
|
}
|
|
}
|
|
|
|
$customer['draft_customer_number'] = $this->extractEconomicCustomerNumber($draft_raw);
|
|
$customer['booked_customer_number'] = $this->extractEconomicCustomerNumber($booked_raw);
|
|
if (
|
|
$customer['internal_customer_number'] !== null &&
|
|
$customer['draft_customer_number'] !== null &&
|
|
(int)$customer['internal_customer_number'] !== (int)$customer['draft_customer_number']
|
|
) {
|
|
$warnings[] = 'Draft invoice customer number mismatch: internal=' . (int)$customer['internal_customer_number'] . ', draft=' . (int)$customer['draft_customer_number'];
|
|
}
|
|
if (
|
|
$customer['internal_customer_number'] !== null &&
|
|
$customer['booked_customer_number'] !== null &&
|
|
(int)$customer['internal_customer_number'] !== (int)$customer['booked_customer_number']
|
|
) {
|
|
$warnings[] = 'Booked invoice customer number mismatch: internal=' . (int)$customer['internal_customer_number'] . ', booked=' . (int)$customer['booked_customer_number'];
|
|
}
|
|
if ($customer['internal_customer_number'] !== null) {
|
|
try {
|
|
$economic_customer_raw = $economic->customers->customers->get((int)$customer['internal_customer_number']);
|
|
if (isset($economic_customer_raw->customerNumber)) {
|
|
$customer['exists'] = true;
|
|
$customer['name'] = isset($economic_customer_raw->name) ? (string)$economic_customer_raw->name : null;
|
|
$customer['barred'] = isset($economic_customer_raw->barred) ? (bool)$economic_customer_raw->barred : null;
|
|
if ($customer['barred'] === true) {
|
|
$warnings[] = 'The e-conomic customer is barred.';
|
|
}
|
|
} else {
|
|
$warnings[] = 'Unable to resolve e-conomic customer ' . (int)$customer['internal_customer_number'] . '.';
|
|
}
|
|
} catch (\Throwable $e) {
|
|
$warnings[] = 'Failed to fetch e-conomic customer ' . (int)$customer['internal_customer_number'] . ': ' . $e->getMessage();
|
|
}
|
|
}
|
|
|
|
$internal_normalized = economic_v2_line_normalizer::normalizeInternalCollectedInvoice($invoice);
|
|
$draft_normalized = $draft_raw !== null
|
|
? economic_v2_line_normalizer::normalizeDraftInvoice($draft_raw)
|
|
: null;
|
|
$booked_normalized = $booked_raw !== null
|
|
? economic_v2_line_normalizer::normalizeBookedInvoice($booked_raw)
|
|
: null;
|
|
|
|
return [
|
|
'collected_invoice_id' => $collected_invoice_id,
|
|
'external_id' => (string)$invoice->external_id->value(),
|
|
'order_ids' => array_values(array_map(static function ($row) {
|
|
return (int)($row['id'] ?? 0);
|
|
}, $invoice->getOrderIds())),
|
|
'economic' => [
|
|
'draft_id' => $draft_id !== null ? (int)$draft_id : null,
|
|
'booked_id' => $booked_id !== null ? (int)$booked_id : null,
|
|
],
|
|
'customer' => $customer,
|
|
'internal' => [
|
|
'normalized' => $internal_normalized,
|
|
],
|
|
'draft' => [
|
|
'exists' => $draft_raw !== null,
|
|
'raw' => $this->toPlainArray($draft_raw),
|
|
'normalized' => $draft_normalized,
|
|
],
|
|
'booked' => [
|
|
'exists' => $booked_raw !== null,
|
|
'raw' => $this->toPlainArray($booked_raw),
|
|
'normalized' => $booked_normalized,
|
|
],
|
|
'warnings' => array_values(array_unique(array_merge(
|
|
$warnings,
|
|
(array)($internal_normalized['warnings'] ?? []),
|
|
(array)($draft_normalized['warnings'] ?? []),
|
|
(array)($booked_normalized['warnings'] ?? [])
|
|
))),
|
|
];
|
|
}
|
|
|
|
private function extractEconomicCustomerNumber(mixed $invoice_raw): ?int
|
|
{
|
|
if ($invoice_raw === null) {
|
|
return null;
|
|
}
|
|
$data = is_array($invoice_raw) ? $invoice_raw : $this->toPlainArray($invoice_raw);
|
|
$value = $data['customer']['customerNumber']
|
|
?? $data['customer']['customer_number']
|
|
?? $data['customerNumber']
|
|
?? $data['customer_number']
|
|
?? null;
|
|
if ($value === null) {
|
|
return null;
|
|
}
|
|
$customer_number = (int)$value;
|
|
return $customer_number > 0 ? $customer_number : null;
|
|
}
|
|
|
|
private function parseIntegerListParameter(string $parameter): array
|
|
{
|
|
if (!self::isParametersSet([$parameter])) {
|
|
return [];
|
|
}
|
|
|
|
$raw = self::getParameter($parameter);
|
|
$values = [];
|
|
if (is_array($raw)) {
|
|
$values = $raw;
|
|
} elseif (is_string($raw)) {
|
|
$values = explode(',', $raw);
|
|
} elseif (is_numeric($raw)) {
|
|
$values = [$raw];
|
|
}
|
|
|
|
$normalized = [];
|
|
foreach ($values as $value) {
|
|
$int_value = (int)$value;
|
|
if ($int_value > 0) {
|
|
$normalized[$int_value] = true;
|
|
}
|
|
}
|
|
|
|
return array_values(array_map('intval', array_keys($normalized)));
|
|
}
|
|
|
|
private function toPlainArray(mixed $value): mixed
|
|
{
|
|
if ($value === null || is_scalar($value)) {
|
|
return $value;
|
|
}
|
|
return json_decode(json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), true);
|
|
}
|
|
|
|
/**
|
|
* @param $collected_order_invoice
|
|
* @param users_o $users
|
|
* @param collected_order_invoices_o $tmp_collected_order_invoices
|
|
* @return array
|
|
* @throws Exception
|
|
*/
|
|
function getOrderInvoiceDetails($collected_order_invoice, users_o $users, collected_order_invoices_o $tmp_collected_order_invoices): array
|
|
{
|
|
return [
|
|
'id' => (int)$collected_order_invoice['id'],
|
|
'name' => (string)$collected_order_invoice['name'],
|
|
'notes' => (string)$collected_order_invoice['notes'],
|
|
'customer_number' => (int)$collected_order_invoice['customer_number'],
|
|
'customer_name' => (string)$users->getCustomerName((int)$collected_order_invoice['customer_number']),
|
|
'processor' => $collected_order_invoice['processor'] ? (int)$collected_order_invoice['processor'] : null,
|
|
'external_id' => (string)$collected_order_invoice['external_id'],
|
|
'closed_at' => $collected_order_invoice['closed_at'] ? (string)$collected_order_invoice['closed_at'] : null,
|
|
'updated_at' => (string)$collected_order_invoice['updated_at'],
|
|
'created_at' => (string)$collected_order_invoice['created_at'],
|
|
'orders' => $tmp_collected_order_invoices->getOrders(),
|
|
'total_net_amount' => (float)$tmp_collected_order_invoices->getTotalAmount(),
|
|
];
|
|
}
|
|
}
|