Files
api/services/nginx/app/modules/economic/customers/economicCustomers.php
T

253 lines
9.1 KiB
PHP

<?php
namespace customers;
use economic_m;
use Exception;
use objects\users_o;
class economicCustomers extends economic_m
{
public users_o $users_o;
public function searchCustomers(string|int $search, string $filter, int $limit = 10, int $page = 1): object
{
// Make sure the search string is ready for the API
$search = urlencode($search);
// Search for customers
$url = '/customers?filter=' . $filter . '$like:' . $search . '&pagesize=' . $limit . '&skippages=' . ($page - 1);
$response = $this->send_request($url, 'GET', '');
return json_decode($response);
}
public function getCustomerName(int $customer_number): string|null
{
// Check if the customer name is cached
$tmp_user = self::getUsersObject()->getUserByCustomerNumber($customer_number);
$cached = $tmp_user->getCached('economic_customer');
if ($cached) {
return $cached->name;
}
// Get the customer name from the economic system
$tmp_user->getCustomerEcocomicData($customer_number);
$cached = $tmp_user->getCached('economic_customer');
if ($cached) {
return $cached->name;
}
return null;
}
public function getUsersObject(): users_o
{
if (!isset($this->users_o)) {
$this->users_o = new users_o();
}
return $this->users_o;
}
public function getCustomerId(int $customerNumber): object|bool
{
if ($customerNumber <= 0) {
return false;
}
// Check if the customer exists
$url = '/customers/' . $customerNumber;
$response = $this->send_request($url, 'GET', '');
$response = json_decode($response);
return isset($response->customerNumber) ? $response : false;
}
public function getCustomerProduct(int $customer_number, int $product_id): object
{
// Get the customer product
$url = '/customers/' . $customer_number . '/templates/invoiceline/' . $product_id;
$response = $this->send_request($url, 'GET', '');
return json_decode($response);
}
public function getCustomerDiscountPercentage(int $customer_number): int
{
// If the customer number is 0, return 0
if ($customer_number === 0) {
return 0;
}
// The discount is global, but e-conomic resolves it through a product-specific
// invoice-line template. For foreign-currency customers some templates can fail
// if that product has no price in the customer currency, so try a few products
// before falling back to zero.
$products = $this->getCustomerProducts($customer_number, 10);
foreach ($this->extractCustomerProductNumbers($products) as $product_number) {
try {
$discount = $this->getCustomerProductDiscount($customer_number, $product_number);
return (int)($discount->discountPercentage ?? 0);
} catch (\RuntimeException $exception) {
if (!$this->isMissingCurrencyPriceLookupError($exception)) {
throw $exception;
}
}
}
return 0;
}
/**
* @return int[]
*/
private function extractCustomerProductNumbers(object $products): array
{
if (!isset($products->collection) || !is_array($products->collection)) {
return [];
}
$product_numbers = [];
foreach ( $products->collection as $product ) {
$product_number = $product->product->productNumber ?? null;
if ($product_number === null || $product_number === '') {
continue;
}
$product_numbers[] = (int)$product_number;
}
return array_values(array_unique($product_numbers));
}
private function isMissingCurrencyPriceLookupError(\RuntimeException $exception): bool
{
$message = $exception->getMessage();
return str_contains($message, 'No price in currency')
&& str_contains($message, 'can be found for the product');
}
public function getCustomerProducts(int $customer_number, int $limit = 10, int $page = 1): object
{
// Get the customer products
$url = '/customers/' . $customer_number . '/templates/invoiceline/?pagesize=' . $limit . '&skippages=' . ($page - 1);
$response = $this->send_request($url, 'GET', '');
return json_decode($response);
}
public function getCustomerProductDiscount(int $customer_number, int $product_id)
{
// Get the customer global discount
$url = '/customers/' . $customer_number . '/templates/invoiceline/' . $product_id;
$response = $this->send_request($url, 'GET', '');
$response = json_decode($response);
return $response;
}
/**
* Get the list of customers
* @param int $page
* @param int $limit
* @param string|null $search
* @param mixed $barred_filter Supports true/false values (bool, 1/0, true/false, barred/active)
* @return object The list of customers
* @throws Exception
*/
public function listCustomers(int $page, int $limit, string|null $search = null, mixed $barred_filter = null): object
{
// Normalize pagination parameters
$page = max(1, $page); // Ensure it's at least 1
$skipPages = $page - 1;
// Construct the base URL with pagination
$url = '/customers?pagesize=' . $limit . '&skippages=' . $skipPages;
// Define filterable property groups
$likeSupported = [
'zip', 'customerNumber', 'customerGroup.customerGroupNumber', 'name', 'address',
'city', 'country', 'email', 'telephoneAndFaxNumber', 'website', 'mobilePhone', 'corporateIdentificationNumber'
];
$filter_parts = [];
// If a search term is present, build the filter expressions
if (!empty($search)) {
// Escape special characters in the search string
$escapedSearch = str_replace(
['$', '(', ')', '*', ',', '[', ']'],
['$$', '$(', '$)', '$*', '$,', '$[', '$]'],
$search
);
// Build $like filters
$filters = [];
foreach ( $likeSupported as $property ) {
$filters[] = $property . '$like:' . $escapedSearch;
}
// Join the filters with `$or:` and keep grouping explicit for later $and composition
$filter_parts[] = '(' . implode('$or:', $filters) . ')';
}
// Optional barred filter support (all | true/barred | false/active)
$normalized_barred_filter = $this->normalizeBarredFilter($barred_filter);
if ($normalized_barred_filter !== null) {
$filter_parts[] = 'barred$eq:' . ($normalized_barred_filter ? 'true' : 'false');
}
if (!empty($filter_parts)) {
$filter_string = count($filter_parts) === 1
? $filter_parts[0]
: '(' . implode('$and:', $filter_parts) . ')';
$url .= '&filter=' . urlencode($filter_string);
}
// Send the GET request to the API endpoint
$response = $this->send_request($url, 'GET', '');
// Validate and decode the response safely
$responseObject = json_decode($response);
if ($responseObject === null) {
throw new Exception('Failed to decode the response from the API');
}
if (!is_object($responseObject)) {
throw new \RuntimeException('Malformed e-conomic customers response: expected JSON object.');
}
if (!isset($responseObject->collection) || !is_array($responseObject->collection)) {
$upstreamMessage = isset($responseObject->message) && is_string($responseObject->message)
? trim($responseObject->message)
: 'Missing collection.';
throw new \RuntimeException('Malformed e-conomic customers response: ' . $upstreamMessage);
}
if (!isset($responseObject->pagination) || !is_object($responseObject->pagination)) {
throw new \RuntimeException('Malformed e-conomic customers response: missing pagination.');
}
if (!isset($responseObject->pagination->results) || !is_numeric($responseObject->pagination->results)) {
throw new \RuntimeException('Malformed e-conomic customers response: missing pagination results.');
}
$responseObject->pagination->results = (int)$responseObject->pagination->results;
return $responseObject;
}
private function normalizeBarredFilter(mixed $value): ?bool
{
if ($value === null || $value === '') {
return null;
}
if (is_bool($value)) {
return $value;
}
if (is_int($value)) {
return $value === 1 ? true : ($value === 0 ? false : null);
}
$parsed = strtolower(trim((string)$value));
return match ($parsed) {
'1', 'true', 'yes', 'barred', 'only_barred' => true,
'0', 'false', 'no', 'active', 'not_barred' => false,
default => null,
};
}
}