Add booked invoice helper and retrieval methods

- Introduce `economic_invoice_booked` helper for normalizing booked invoice data.
- Add `getFromId` method to fetch booked invoice details by ID.
- Extend `economic` class with `getInvoiceBookedFromExternalId` for external ID-based retrieval.
- Update imports and economic helper class registrations to include booked invoice functionality.
This commit is contained in:
Jeppe Bundgaard
2026-02-03 12:19:42 +01:00
parent ab93866295
commit 5aa575268f
4 changed files with 375 additions and 0 deletions
+12
View File
@@ -23,6 +23,7 @@ use endpoints\economic_payment_terms_endpoint;
use endpoints\economic_products_endpoint;
use helpers\economic_customer;
use helpers\economic_invoice_draft;
use helpers\economic_invoice_booked;
use helpers\economic_tasks;
use interfaces\economic_i;
@@ -141,4 +142,15 @@ class economic implements economic_i
return new $this->helpers->economic_customer($customer_number);
}
/**
* @throws \Exception
*/
public function getInvoiceBookedFromExternalId(string $external_id): economic_invoice_booked
{
$bookedId = $this->invoices->booked->get_from_external_id($external_id);
$raw = $this->invoices->booked->getFromId($bookedId);
// Return the booked invoice helper
return (new economic_invoice_booked($raw));
}
}
@@ -1,11 +1,13 @@
<?php
require_once WD . '/modules/economic/helpers/economic_customer.php';
require_once WD . '/modules/economic/helpers/economic_invoice_draft.php';
require_once WD . '/modules/economic/helpers/economic_invoice_booked.php';
require_once WD . '/modules/economic/helpers/economic_tasks.php';
use helpers\economic_customer;
use helpers\economic_invoice_draft;
use helpers\economic_invoice_booked;
use helpers\economic_tasks;
class economic_helpers
@@ -20,6 +22,11 @@ class economic_helpers
* @var string $economic_invoice_draft
*/
public string $economic_invoice_draft = economic_invoice_draft::class;
/**
* The Economic booked invoice helper
* @var string $economic_invoice_booked
*/
public string $economic_invoice_booked = economic_invoice_booked::class;
/**
* The Economic tasks helper
* @var string $economic_tasks
@@ -66,4 +66,14 @@ class economic_invoices_booked_endpoint
}
}
public function getFromId(int $invoice_id): object
{
$response = $this->send_request(
'/invoices/booked/' . $invoice_id,
'GET'
);
// Return the response as an object
return json_decode($response);
}
}
@@ -0,0 +1,346 @@
<?php
namespace helpers;
/**
* Helper to normalize/booked invoice data from E-conomic (stdClass) to a PHP array structure
* with snake_case keys and convenient totals.
*
* This helper also exposes documented object properties mirroring the most relevant
* top-level fields from the Economic booked invoice payload for easier IDE discovery.
*
* Notes on mapping (Economic → helper):
* - bookedInvoiceNumber → booked_invoice_number
* - orderNumber → order_number
* - date → date (Y-m-d)
* - currency → currency (e.g. DKK)
* - exchangeRate → exchange_rate
* - netAmount → net_amount
* - netAmountInBaseCurrency → net_amount_in_base_currency
* - grossAmount → gross_amount (also exposed as total_amount)
* - grossAmountInBaseCurrency → gross_amount_in_base_currency
* - vatAmount → vat_amount
* - roundingAmount → rounding_amount
* - remainder → remainder
* - remainderInBaseCurrency → remainder_in_base_currency
* - dueDate → due_date (Y-m-d)
* - paymentTerms → payment_terms (array)
* - customer → customer (array)
* - recipient → recipient (array with nested vat_zone)
* - references → references (array, typically contains other)
* - layout → layout (array)
* - pdf → pdf (array with download)
* - lines → lines (array of line arrays)
* - sent → sent (string URL) if present
* - self → self (string URL) if present
*
* Convenience fields:
* - total_amount (float) mirrors gross_amount
* - total_net_amount (float) mirrors net_amount
*/
class economic_invoice_booked
{
/**
* Raw booked invoice data as returned by the E-conomic API (stdClass)
* @var object
*/
protected object $booked_invoice_data;
/**
* Normalized array (snake_case keys)
* @var array
*/
protected array $normalized = [];
/**
* The booked invoice number in Economic.
* Origin: bookedInvoiceNumber
* @var int
*/
public int $booked_invoice_number = 0;
/**
* The order number associated with the invoice, if any.
* Origin: orderNumber
* @var int|null
*/
public ?int $order_number = null;
/**
* Invoice booking date (YYYY-MM-DD).
* Origin: date
* @var string
*/
public string $date = '';
/**
* Invoice currency code (e.g., DKK).
* Origin: currency
* @var string
*/
public string $currency = '';
/**
* Exchange rate for the invoice currency relative to base currency.
* Origin: exchangeRate
* @var float
*/
public float $exchange_rate = 1.0;
/**
* Net amount in invoice currency.
* Origin: netAmount
* @var float
*/
public float $net_amount = 0.0;
/**
* Net amount in base currency.
* Origin: netAmountInBaseCurrency
* @var float
*/
public float $net_amount_in_base_currency = 0.0;
/**
* Gross amount in invoice currency (includes VAT if applicable).
* Origin: grossAmount
* Also exposed as total_amount.
* @var float
*/
public float $gross_amount = 0.0;
/**
* Gross amount in base currency.
* Origin: grossAmountInBaseCurrency
* @var float
*/
public float $gross_amount_in_base_currency = 0.0;
/**
* Total VAT amount for the invoice.
* Origin: vatAmount
* @var float
*/
public float $vat_amount = 0.0;
/**
* Rounding adjustment amount.
* Origin: roundingAmount
* @var float
*/
public float $rounding_amount = 0.0;
/**
* Remainder amount (outstanding) in invoice currency.
* Origin: remainder
* @var float
*/
public float $remainder = 0.0;
/**
* Remainder amount (outstanding) in base currency.
* Origin: remainderInBaseCurrency
* @var float
*/
public float $remainder_in_base_currency = 0.0;
/**
* Invoice due date (YYYY-MM-DD) when applicable.
* Origin: dueDate
* @var string|null
*/
public ?string $due_date = null;
/**
* Payment terms info.
* Origin: paymentTerms
* @var array{payment_terms_number?:int,days_of_credit?:int,name?:string,payment_terms_type?:string,self?:string}
*/
public array $payment_terms = [];
/**
* Customer reference.
* Origin: customer
* @var array{customer_number?:int,self?:string}
*/
public array $customer = [];
/**
* Recipient details and VAT zone.
* Origin: recipient
* @var array{name?:string,address?:string,zip?:string|int,city?:string,vat_zone?:array{name?:string,vat_zone_number?:int,enabled_for_customer?:int|bool,enabled_for_supplier?:int|bool,self?:string}}
*/
public array $recipient = [];
/**
* References (e.g., other/external id).
* Origin: references
* @var array{other?:string}
*/
public array $references = [];
/**
* Layout reference used for the invoice.
* Origin: layout
* @var array{layout_number?:int,self?:string}
*/
public array $layout = [];
/**
* PDF info (download link).
* Origin: pdf
* @var array{download?:string}
*/
public array $pdf = [];
/**
* Invoice lines (normalized to snake_case keys).
* Origin: lines
* @var array<int, array{
* line_number?:int,sort_key?:int,description?:string,
* quantity?:int|float,unit_net_price?:int|float,discount_percentage?:int|float,
* unit_cost_price?:int|float,vat_rate?:int|float,vat_amount?:int|float,total_net_amount?:int|float,
* product?:array{product_number?:int|string,self?:string},
* departmental_distribution?:array{
* departmental_distribution_number?:int,name?:string,barred?:bool|int|null,distribution_type?:string,
* distributions?:array<int, array{percentage?:int|float,department?:array{department_number?:int,self?:string}}>,
* self?:string
* }
* }>
*/
public array $lines = [];
/**
* Link to Economic API endpoint indicating sent status (if present).
* Origin: sent
* @var string|null
*/
public ?string $sent = null;
/**
* Self link to this resource in Economic.
* Origin: self
* @var string|null
*/
public ?string $self = null;
/**
* Convenience: mirrors gross_amount.
* @var float
*/
public float $total_amount = 0.0;
/**
* Convenience: mirrors net_amount.
* @var float
*/
public float $total_net_amount = 0.0;
public function __construct(object $booked_invoice_data)
{
$this->booked_invoice_data = $booked_invoice_data;
// Prepare normalized structure immediately
$this->normalized = $this->normalize($this->booked_invoice_data);
// Provide convenience totals expected by internal callers
if (!isset($this->normalized['total_amount']) && isset($this->normalized['gross_amount'])) {
$this->normalized['total_amount'] = (float)$this->normalized['gross_amount'];
}
if (!isset($this->normalized['total_net_amount']) && isset($this->normalized['net_amount'])) {
$this->normalized['total_net_amount'] = (float)$this->normalized['net_amount'];
}
// Populate documented properties for IDE discoverability
$this->populatePropertiesFromArray($this->normalized);
}
/**
* Convert the booked invoice stdClass into a normalized array (snake_case keys)
* and add convenience fields like total_amount and total_net_amount.
*/
public function toArray(): array
{
return $this->normalized;
}
/**
* Recursively normalize stdClass/array values to arrays with snake_case keys
* while preserving scalars.
* @param mixed $value
* @return mixed
*/
protected function normalize($value)
{
if (is_object($value)) {
// Convert stdClass to assoc array with snake_case keys
$result = [];
foreach (get_object_vars($value) as $k => $v) {
$key = $this->camelToSnake((string)$k);
$result[$key] = $this->normalize($v);
}
return $result;
}
if (is_array($value)) {
$result = [];
foreach ($value as $k => $v) {
// Preserve numeric array indexes; convert associative keys to snake_case
if (is_string($k)) {
$key = $this->camelToSnake($k);
$result[$key] = $this->normalize($v);
} else {
$result[$k] = $this->normalize($v);
}
}
return $result;
}
// Scalars: cast numeric strings to appropriate numeric types when reasonable
if (is_string($value)) {
if (is_numeric($value)) {
// Decide int vs float based on presence of decimal point
return (strpos($value, '.') !== false) ? (float)$value : (int)$value;
}
return $value;
}
return $value;
}
private function camelToSnake(string $input): string
{
// Preserve common abbreviations and already snake keys
if (strpos($input, '_') !== false) {
return strtolower($input);
}
$snake = preg_replace('/(?<!^)[A-Z]/', '_$0', $input);
return strtolower($snake);
}
/**
* Populate documented public properties from the normalized array.
*/
private function populatePropertiesFromArray(array $data): void
{
$this->booked_invoice_number = (int)($data['booked_invoice_number'] ?? 0);
$this->order_number = isset($data['order_number']) ? (int)$data['order_number'] : null;
$this->date = (string)($data['date'] ?? '');
$this->currency = (string)($data['currency'] ?? '');
$this->exchange_rate = (float)($data['exchange_rate'] ?? 1.0);
$this->net_amount = (float)($data['net_amount'] ?? 0.0);
$this->net_amount_in_base_currency = (float)($data['net_amount_in_base_currency'] ?? 0.0);
$this->gross_amount = (float)($data['gross_amount'] ?? 0.0);
$this->gross_amount_in_base_currency = (float)($data['gross_amount_in_base_currency'] ?? 0.0);
$this->vat_amount = (float)($data['vat_amount'] ?? 0.0);
$this->rounding_amount = (float)($data['rounding_amount'] ?? 0.0);
$this->remainder = (float)($data['remainder'] ?? 0.0);
$this->remainder_in_base_currency = (float)($data['remainder_in_base_currency'] ?? 0.0);
$this->due_date = isset($data['due_date']) && $data['due_date'] !== '' ? (string)$data['due_date'] : null;
$this->payment_terms = isset($data['payment_terms']) && is_array($data['payment_terms']) ? $data['payment_terms'] : [];
$this->customer = isset($data['customer']) && is_array($data['customer']) ? $data['customer'] : [];
$this->recipient = isset($data['recipient']) && is_array($data['recipient']) ? $data['recipient'] : [];
$this->references = isset($data['references']) && is_array($data['references']) ? $data['references'] : [];
$this->layout = isset($data['layout']) && is_array($data['layout']) ? $data['layout'] : [];
$this->pdf = isset($data['pdf']) && is_array($data['pdf']) ? $data['pdf'] : [];
$this->lines = isset($data['lines']) && is_array($data['lines']) ? $data['lines'] : [];
$this->sent = isset($data['sent']) ? (string)$data['sent'] : null;
$this->self = isset($data['self']) ? (string)$data['self'] : null;
$this->total_amount = (float)($data['total_amount'] ?? $this->gross_amount);
$this->total_net_amount = (float)($data['total_net_amount'] ?? $this->net_amount);
}
}