Files
api/services/nginx/app/modules/economic/helpers/economic_invoice_draft.php
T
Jepp9350 60bcbb6e75 Enhance invoice handling and filtering mechanisms
Refactored invoice draft handling to improve error checks, added support for optional fetch skipping, and enhanced currency management. Expanded filtering capabilities with date range and attribute-based filters. Adjusted Nginx config to increase FastCGI read timeout for long-running processes.
2025-04-07 07:43:47 +02:00

412 lines
15 KiB
PHP

<?php
namespace helpers;
use classes\economic;
use Exception;
use objects\currency_conversion_rates_o;
use objects\departments_o;
use objects\orders_o;
class economic_invoice_draft
{
/**
* The Economic draftInvoiceNumber
* @var int $draft_invoice_number
*/
protected int $draft_invoice_number;
/**
* The draft lines
* @var array $draft_lines
*/
protected array $draft_lines = [];
/**
* The raw draft invoice data
* @var object $draft_invoice_data
*/
protected object $draft_invoice_data;
/**
* The currency for the draft invoice
* @var string $currency
* @note The currency is set to DKK by default
*/
protected string $currency = 'DKK';
/**
* The currency conversion rate
* @var float $conversion_rate
* @note The conversion rate is set to 1.0 by default
*/
protected float $conversion_rate = 1.0;
/**
* Construct a new Economic draft invoice object
* @throws Exception if the invoice data is invalid or empty
*/
public function __construct(int $draft_invoice_number, string $currency = 'DKK', bool $skip_fetch = false)
{
$this->setDraftInvoiceNumber($draft_invoice_number);
$this->setCurrency(strtoupper($currency));
// If the skip fetch is set to false, fetch the draft invoice data
if (!$skip_fetch) {
$this->fetchDraftInvoiceData();
} else {
$this->draft_invoice_data = new \stdClass();
$this->draft_invoice_data->draftInvoiceNumber = $this->draft_invoice_number;
}
$this->requireSelected();
}
/**
* Set the draft invoice number
* @param int $draft_invoice_number
* @return void
*/
private function setDraftInvoiceNumber(int $draft_invoice_number): void
{
$this->draft_invoice_number = $draft_invoice_number;
}
/**
* Set the currency for the draft invoice
* @param string $currency The currency to set
* @return void
* @throws Exception If the currency is not valid
* @throws Exception If the conversion rate is not valid
*/
private function setCurrency(string $currency): void
{
// Set the currency for the draft invoice
$this->currency = $currency;
// Get the conversion rate for the currency
$this->conversion_rate = (float)(new currency_conversion_rates_o())->convertTo($this->currency, 1);
}
/**
* Fetch the draft invoice data from the Economic system
* @throws Exception If the request fails
* @throws Exception If the invoice is not found
*/
private function fetchDraftInvoiceData(): void
{
$economic = new economic();
$this->draft_invoice_data = $economic->invoices->draft->get($this->draft_invoice_number);
if (!isset($this->draft_invoice_data->draftInvoiceNumber)) {
throw new Exception('Draft invoice not found');
}
}
/**
* Require that the draft invoice data is selected
* @throws Exception if the draft invoice data is not set
*/
protected function requireSelected(): void
{
if (!isset($this->draft_invoice_data->draftInvoiceNumber)) {
throw new Exception('The draft invoice data is not set');
}
}
/**
* Add the lines to the draft invoice
* @return void
*/
public function addLines(): void
{
$economic = new economic();
$economic->invoices->draft->add_lines($this->draft_invoice_number, $this->draft_lines);
}
/**
* Get the draft invoice number
* @return int The draft invoice number
*/
public function getDraftInvoiceNumber(): int
{
return $this->draft_invoice_number;
}
/**
* Add a new transaction header to the draft invoice
* @note The lines won't be saved until the addLines() method is called.
* @note This is the "[28/02/2025 18:29 Roskilde #685]" line in the invoice
* @param orders_o $order The order to add
* @return void
*/
public function addNewTransactionHeader(orders_o $order): void
{
// Get the department name
$department_name = (new departments_o())->getDepartmentName((int)$order->department_id->value());
// Parse the date of the transaction.
$parsed_date = date('d/m/Y H:i', strtotime($order->created_at->value()));
// Add the text line to the draft invoice
self::addTextLine("[ " . $parsed_date . ' ' . $department_name . ' #' . $order->id . " ]");
// If there's a reference, add it to the invoice
if ($order->reference->value() !== '') {
self::addTextLine('Reference:');
// Add "# " in the beginning of each line (If there's more than one line, otherwise we just add the prefix once)
if (str_contains($order->reference->value(), "\n")) {
foreach ( explode("\n", $order->reference->value()) as $line ) {
self::addTextLine('# ' . $line);
}
} else {
self::addTextLine('# ' . $order->reference->value());
}
}
// Add the registration numbers (if any)
$line_reg = '';
if ($order->reg_1->value() !== '')
$line_reg .= 'Reg 1: ' . strtoupper($order->reg_1->value());
if ($order->reg_2->value() !== '')
$line_reg .= ', Reg 2: ' . strtoupper($order->reg_2->value());
if ($order->reg_3->value() !== '')
$line_reg .= ', Reg 3: ' . strtoupper($order->reg_3->value());
// Add the line to the invoice (If there's any registration numbers)
if ($line_reg !== '')
self::addTextLine($line_reg);
// If there's a note, add it to the invoice
if ($order->notes->value() !== '') {
self::addTextLine('Notat:');
// Add "# " in the beginning of each line (If there's more than one line, otherwise we just add the prefix once)
if (str_contains($order->notes->value(), "\n")) {
foreach ( explode("\n", $order->notes->value()) as $line ) {
self::addTextLine('# ' . $line);
}
} else {
self::addTextLine('# ' . $order->notes->value());
}
}
}
/**
* Add a text line to the draft invoice
* @note The lines won't be saved until the addLines() method is called.
* @param string $text The text to add
* @return void
*/
public function addTextLine(string $text): void
{
$this->draft_lines[] = [
'description' => $text
];
}
/**
* Add an order to the draft invoice
* @note The lines won't be saved until the addLines() method is called.
* @param orders_o $order The order to add
* @return void
* @throws Exception if the order is not found
* @throws Exception if the order is not valid
*/
public function addOrderItemLines(orders_o $order): void
{
// Get the order items
$order_items = $order->getOrderItems($order->id);
// Apply the department pricing
$order_items = $order->applyDepartmentPrices($order_items, $order->department_id->value());
// Get the department
$department = $order->getDepartmentByOrderId($order->id);
// Define the total discount applied to the order
$total_discount = 0;
// Loop through the order items
foreach ( $order_items as $order_item ) {
// Add the order item to the draft invoice
self::addOrderItemLine($order_item, $department);
// Add the line discount to the total discount
$total_discount += $order_item['product']['price'] - $order_item['price'];
}
// If the total discount is greater than 0, add it to the invoice
if ($total_discount > 0) {
// Add the discount to the invoice
self::addProductDiscountLine($total_discount, $department['economic_department_id'], $department['economic_dimension_id'] ?? 0);
}
}
/**
* Add an order item line to the draft invoice
* @note The lines won't be saved until the addLines() method is called.
* @param array $order_item The order item to add
* @param array $department The department to add
* @param boolean $show_discount Whether to show the discount or not
* @return void
* @throws Exception if the order item is not found
* @throws Exception if the order item is not valid
*/
public function addOrderItemLine(array $order_item, array $department, bool $show_discount = false): void
{
// Check if the order item is valid
if (!isset($order_item['id'])) {
throw new Exception('The order item is not valid');
}
// Get the department id
$economic_department_id = $department['economic_department_id'];
// Get the dimension id
$economic_dimension_id = $department['economic_dimension_id'] ?? 0;
// Add the order item to the draft invoice
self::addProductLine(
(string)$order_item['product']['economic_product_id'],
(string)$order_item['product']['name'],
(int)$order_item['quantity'] ?? 1,
(int)$order_item['product']['price'],
$economic_department_id,
$economic_dimension_id
);
// Calculate the discount percentage
$discountPercentage = round((($order_item['product']['price'] - $order_item['price']) / $order_item['product']['price']) * 100, 0);
// If the price is different from the product price, add it to the line
if ($order_item['price'] !== $order_item['product']['price'] && $show_discount)
self::addTextLine('Rabat: ' . ($order_item['price'] - $order_item['product']['price']) . ' ' . $this->currency . ' (' . $discountPercentage . '%)');
// If there's a reference, add it to the line
if ($order_item['reference'] !== '') {
self::addTextLine('Reference:');
// Add "# " in the beginning of each line (If there's more than one line, otherwise we just add the prefix once)
if (str_contains($order_item['reference'], "\n")) {
foreach ( explode("\n", $order_item['reference']) as $line ) {
self::addTextLine('# ' . $line);
}
} else {
self::addTextLine('# ' . $order_item['reference']);
}
}
// If there's a note, add it to the line
if (!empty($order_item['notes'])) {
self::addTextLine('Notat:');
// Add "# " in the beginning of each line (If there's more than one line, otherwise we just add the prefix once)
if (str_contains($order_item['notes'], "\n")) {
foreach ( explode("\n", $order_item['notes']) as $line ) {
self::addTextLine('# ' . $line);
}
} else {
self::addTextLine('# ' . $order_item['notes']);
}
}
}
/**
* Add a product line to the draft invoice
* @note The lines won't be saved until the addLines() method is called.
*/
public function addProductLine(string $productNumber, string $description, float $quantity, float $unitNetPrice, int $economic_department_id, int $dimension): void
{
// We then add a 0 in front of the product number to make sure it's the right one, made by FlexPOS.
// Add a line to the invoice
$line = [
'product' => [
'productNumber' => $productNumber,
],
'quantity' => $quantity,
'unitNetPrice' => self::convertCurrency($unitNetPrice),
'discountPercentage' => 0,
'description' => $description,
];
// If the department is set, add it to the line
if ($economic_department_id) {
$line['departmentalDistribution'] = [
'departmentalDistributionNumber' => $economic_department_id,
'DistributionType' => 'Department',
'dimension' => $dimension
];
}
$this->draft_lines[] = $line;
}
private function convertCurrency(float $amount): float
{
// Convert the amount to the correct currency
$converted_value = $amount * self::getConversionRate();
// Round the value to 2 decimal places
return round($converted_value, 2);
}
private function getConversionRate(): float
{
// Get the conversion rate for the currency
return $this->conversion_rate;
}
/**
* Add a discount line to the draft invoice
* @note The lines won't be saved until the addLines() method is called.
* @param float $discount The discount to add
* @param int $economic_department_id The department id to add
* @param int $economic_dimension_id The dimension id to add
* @return void
*/
public function addProductDiscountLine(float $discount, int $economic_department_id, int $economic_dimension_id): void
{
// Add a line to the invoice
$line = [
'product' => [
'productNumber' => 'TotDiscount',
],
'quantity' => 1,
'unitNetPrice' => self::convertCurrency(-$discount),
'discountPercentage' => 0,
'description' => 'Rabat',
];
// If the department is set, add it to the line
if ($economic_department_id) {
$line['departmentalDistribution'] = [
'departmentalDistributionNumber' => $economic_department_id,
'DistributionType' => 'Department',
'dimension' => $economic_dimension_id
];
}
$this->draft_lines[] = $line;
}
/**
* Check if a draft invoice exists in the Economic system
* @throws Exception if the draft invoice does not exist
*/
protected function requireExists(): void
{
if (!self::exists()) {
throw new Exception('The draft invoice does not exist');
}
}
/**
* Check if a customer exists in the Economic system
* @return bool True if the customer exists, false if not
* @throws Exception
*/
public function exists(): bool
{
// Make sure the draft invoice number is set before calling
self::requireDraftInvoiceNumber();
// Check if the draft invoice exists
$economic = new economic();
return $economic->invoices->drafts->exists($this->draft_invoice_number);
}
/**
* Require that the draft invoice number is set
* @throws Exception if the draft invoice number is not set
*/
protected function requireDraftInvoiceNumber(): void
{
if (!isset($this->draft_invoice_number)) {
throw new Exception('The draft invoice number is not set');
}
}
/**
* Require that the draft invoice data is set
* @throws Exception if the draft invoice data is not set
*/
protected function requireDraftInvoiceData(): void
{
if (!isset($this->draft_invoice_data)) {
throw new Exception('The draft invoice data is not set');
}
}
}