post('/economic/invoice/draft/export', function () { global $response; $this->requirePermission('economic_invoice_draft_export'); $user = (new authentication())->get_user(); if (!$user) { (new logs_o())->add('economic_invoice_draft', 'global', 1, 0, 'ECONOMIC_INVOICE_DRAFT_EXPORT', 'No user found, or invalid session'); $response->error('Invalid session', 400); } $order_id = $response->getRequestParameter('order_id'); if (!isset($order_id)) { $response->error('Order ID is required', 400); } if (!is_numeric($order_id) || (int)$order_id < 1) { $response->error('Order ID must be a positive number', 400); } $order = (new orders_o())->getOrderById((int)$order_id); if (!$order->exists()) { $response->error('Order not found', 404); } try { $this->assertOrderCanBeExportedToEconomic((int)$order_id); } catch (\Throwable $e) { $response->error($e->getMessage(), 400); } if (!$this->isEconomicTransferQueueAvailable()) { try { $result = $this->exportOrderDraftSynchronously((int)$order_id, (int)$user->id); } catch (\Throwable $e) { $response->error($e->getMessage(), 400); } (new logs_o())->add( 'economic_invoice_draft', 'global', 1, (int)$user->id, 'ECONOMIC_INVOICE_DRAFT_EXPORT_FALLBACK', 'Processed economic invoice draft export synchronously because queue dependencies are unavailable' ); $response->success([ 'message' => 'Economic invoice draft export processed synchronously', 'mode' => 'synchronous_fallback', 'result' => $result, ]); } $queue = new economic_transfer_queue(); $job = $queue->enqueue( economic_transfer_queue::TYPE_ORDER_DRAFT_EXPORT, [ 'order_id' => (int)$order_id, 'requested_by' => (int)$user->id, ], (int)$user->id ); $job_id = (int)($job['id'] ?? 0); if ($job_id < 1) { $response->error('Failed to enqueue draft export job: missing queue job id in enqueue response', 500); } (new logs_o())->add('economic_invoice_draft', 'global', 1, $user->id, 'ECONOMIC_INVOICE_DRAFT_EXPORT_QUEUED', 'Queued economic invoice draft export'); $response->success([ 'message' => 'Economic invoice draft export queued', 'job_id' => $job_id, 'job' => $job, ], 202); }, [ 'economic_invoice_draft_export' => 'Export an economic invoice draft' ]); $this->delete('/economic/invoice/draft/delete', function () { global /** @var response $response */ $response; $this->requirePermission('economic_invoice_draft_delete'); $user = (new authentication())->get_user(); if ($user) { $order_id = $response->getRequestParameter('order_id'); if (!isset($order_id)) { $response->error('Order ID is required', 400); } $order = (new orders_o())->getOrderById($order_id); if (!$order->exists()) { $response->error('Order not found', 404); } $economic_module_orders = (new economic_module_orders())->getByOrderId($order_id); if ($economic_module_orders->economic_invoice_draft_id->value() === 0) { $response->error('No economic invoice draft found', 404); } $economic_invoice_draft = (new economic_invoice_draft_mo()); $invoiceDraftId = $economic_module_orders->economic_invoice_draft_id->value(); $customer = (new orders_o())->getCustomerByOrderId($order_id); if (!$customer->invoicePerOrder()) { if ($customer->hasOpenInvoiceDraft()) { if ((int)$customer->getOpenInvoiceDraft() === (int)$invoiceDraftId) { $customer->deleteOpenInvoiceDraft(); } } } $economic_invoice_draft->deleteInvoiceDraft($invoiceDraftId); $economic_module_orders->economic_invoice_draft_id->set(null); (new logs_o())->add('economic_invoice_draft', 'global', 1, $user->id, 'ECONOMIC_INVOICE_DRAFT_DELETE', 'Successfully deleted an economic invoice draft'); $response->success($economic_module_orders->getArray()); } else { (new logs_o())->add('economic_invoice_draft', 'global', 1, 0, 'ECONOMIC_INVOICE_DRAFT_DELETE', 'No user found, or invalid session'); $response->error('Invalid session', 400); } }, [ 'economic_invoice_draft_delete' => 'Delete an economic invoice draft' ]); $this->post('/economic/invoice/export', function () { global $response; $this->requirePermission('economic_invoice_export'); $user = (new authentication())->get_user(); if (!$user) { (new logs_o())->add('economic_invoice', 'global', 1, 0, 'ECONOMIC_INVOICE_EXPORT', 'No user found, or invalid session'); $response->error('Invalid session', 400); } $order_id = $response->getRequestParameter('order_id'); if (!isset($order_id)) { $response->error('Order ID is required', 400); } if (!is_numeric($order_id) || (int)$order_id < 1) { $response->error('Order ID must be a positive number', 400); } $order = (new orders_o())->getOrderById((int)$order_id); if (!$order->exists()) { $response->error('Order not found', 404); } $customer = (new orders_o())->getCustomerByOrderId((int)$order_id); if (!$customer->exists()) { $response->error('Customer not found', 404); } try { (new economic())->assertCustomerNumberIsNotDraft((int)$customer->customer_number->value()); } catch (\Throwable $e) { $response->error($e->getMessage(), 400); } $economic_module_orders = (new economic_module_orders())->getByOrderId((int)$order_id); if ($economic_module_orders->economic_invoice_draft_id->value() === 0) { $response->error('No economic invoice draft found', 404); } if ((int)$economic_module_orders->economic_invoice_id->value() > 0) { $response->error('An invoice has already been created, invoice ID: ' . (int)$economic_module_orders->economic_invoice_id->value(), 400); } if (!$this->isEconomicTransferQueueAvailable()) { try { $result = $this->exportOrderInvoiceSynchronously((int)$order_id, (int)$user->id); } catch (\Throwable $e) { $response->error($e->getMessage(), 400); } (new logs_o())->add( 'economic_invoice', 'global', 1, (int)$user->id, 'ECONOMIC_INVOICE_EXPORT_FALLBACK', 'Processed economic invoice export synchronously because queue dependencies are unavailable' ); $response->success([ 'message' => 'Economic invoice export processed synchronously', 'mode' => 'synchronous_fallback', 'result' => $result, ]); } $queue = new economic_transfer_queue(); $job = $queue->enqueue( economic_transfer_queue::TYPE_ORDER_INVOICE_EXPORT, [ 'order_id' => (int)$order_id, 'requested_by' => (int)$user->id, ], (int)$user->id ); $job_id = (int)($job['id'] ?? 0); if ($job_id < 1) { $response->error('Failed to enqueue invoice export job: missing queue job id in enqueue response', 500); } (new logs_o())->add('economic_invoice', 'global', 1, $user->id, 'ECONOMIC_INVOICE_EXPORT_QUEUED', 'Queued economic invoice export'); $response->success([ 'message' => 'Economic invoice export queued', 'job_id' => $job_id, 'job' => $job, ], 202); }, [ 'economic_invoice_export' => 'Export an economic invoice' ]); $this->get('/economic/invoice/draft/export/status', function () { global $response; $this->requirePermission('economic_invoice_draft_export'); $user = (new authentication())->get_user(); if (!$user) { $response->error('Invalid session', 400); } $this->ensureEconomicTransferQueueIsAvailable(); $job_id = $response->getRequestParameter('job_id'); if (!isset($job_id) || !is_numeric($job_id) || (int)$job_id < 1) { $response->error('job_id is required and must be a positive number', 400); } $queue = new economic_transfer_queue(); $job = $queue->getJobByIdForUser((int)$job_id, (int)$user->id); if ($job === null || ($job['transfer_type'] ?? null) !== economic_transfer_queue::TYPE_ORDER_DRAFT_EXPORT) { $response->error('Draft export queue job not found', 404); } $response->success($job); }, [ 'economic_invoice_draft_export' => 'Read queue status for economic draft invoice export' ]); $this->post('/economic/invoice/draft/export/retry', function () { global $response; $this->requirePermission('economic_invoice_draft_export'); $user = (new authentication())->get_user(); if (!$user) { $response->error('Invalid session', 400); } $this->ensureEconomicTransferQueueIsAvailable(); $job_id = $response->getRequestParameter('job_id'); if (!isset($job_id) || !is_numeric($job_id) || (int)$job_id < 1) { $response->error('job_id is required and must be a positive number', 400); } $queue = new economic_transfer_queue(); $existing_job = $queue->getJobByIdForUser((int)$job_id, (int)$user->id); if ($existing_job === null || ($existing_job['transfer_type'] ?? null) !== economic_transfer_queue::TYPE_ORDER_DRAFT_EXPORT) { $response->error('Draft export queue job not found', 404); } try { $job = $queue->retryJobForUser((int)$job_id, (int)$user->id); } catch (\Throwable $e) { $response->error($e->getMessage(), 400); } $response->success([ 'message' => 'Draft export queue job retried', 'job' => $job, ]); }, [ 'economic_invoice_draft_export' => 'Retry failed queue job for economic draft invoice export' ]); $this->get('/economic/invoice/export/status', function () { global $response; $this->requirePermission('economic_invoice_export'); $user = (new authentication())->get_user(); if (!$user) { $response->error('Invalid session', 400); } $this->ensureEconomicTransferQueueIsAvailable(); $job_id = $response->getRequestParameter('job_id'); if (!isset($job_id) || !is_numeric($job_id) || (int)$job_id < 1) { $response->error('job_id is required and must be a positive number', 400); } $queue = new economic_transfer_queue(); $job = $queue->getJobByIdForUser((int)$job_id, (int)$user->id); if ($job === null || ($job['transfer_type'] ?? null) !== economic_transfer_queue::TYPE_ORDER_INVOICE_EXPORT) { $response->error('Invoice export queue job not found', 404); } $response->success($job); }, [ 'economic_invoice_export' => 'Read queue status for economic invoice export' ]); $this->post('/economic/invoice/export/retry', function () { global $response; $this->requirePermission('economic_invoice_export'); $user = (new authentication())->get_user(); if (!$user) { $response->error('Invalid session', 400); } $this->ensureEconomicTransferQueueIsAvailable(); $job_id = $response->getRequestParameter('job_id'); if (!isset($job_id) || !is_numeric($job_id) || (int)$job_id < 1) { $response->error('job_id is required and must be a positive number', 400); } $queue = new economic_transfer_queue(); $existing_job = $queue->getJobByIdForUser((int)$job_id, (int)$user->id); if ($existing_job === null || ($existing_job['transfer_type'] ?? null) !== economic_transfer_queue::TYPE_ORDER_INVOICE_EXPORT) { $response->error('Invoice export queue job not found', 404); } try { $job = $queue->retryJobForUser((int)$job_id, (int)$user->id); } catch (\Throwable $e) { $response->error($e->getMessage(), 400); } $response->success([ 'message' => 'Invoice export queue job retried', 'job' => $job, ]); }, [ 'economic_invoice_export' => 'Retry failed queue job for economic invoice export' ]); } private function isEconomicTransferQueueAvailable(): bool { return class_exists('\\classes\\economic_transfer_executor') && class_exists('\\classes\\economic_transfer_queue_schema_bootstrap') && class_exists(economic_transfer_queue::class); } /** * @throws \Exception */ private function exportOrderDraftSynchronously(int $order_id, int $user_id): array { $order = (new orders_o())->getOrderById($order_id); if (!$order->exists()) { throw new \Exception('Order not found'); } $economic_module_orders = (new economic_module_orders())->getByOrderId($order_id); $invoice_id = (int)$economic_module_orders->economic_invoice_id->value(); if ($invoice_id > 0) { throw new \Exception('An invoice has already been created, invoice ID: ' . $invoice_id); } $order_items = (new orders_o())->getOrderItems($order_id); $order_items = (new orders_o())->applyDepartmentPrices($order_items, $order->department_id->value()); if (count($order_items) === 0) { throw new \Exception('No order items found'); } $customer = (new orders_o())->getCustomerByOrderId($order_id); if (!$customer->exists()) { throw new \Exception('Customer not found'); } (new economic())->assertCustomerNumberIsNotDraft((int)$customer->customer_number->value()); $customer_economic = $customer->getCustomerEcocomicData()->economic_customer; $economic_invoice_draft = new economic_invoice_draft_mo(); $economic_invoice_draft->setCustomerNumber((int)$customer_economic->customer_number); $economic_invoice_draft->setRecipient( $customer_economic->name ?? 'Ukendt', $customer_economic->address ?? 'Ukendt', $customer_economic->zip ?? 'Ukendt', $customer_economic->city ?? 'Ukendt' ); $department = (new departments_o())->getDepartmentById($order->department_id->value()); $this->addTheDepartmentDateReference($economic_invoice_draft, $department['name'], $order); foreach ($order_items as $order_item) { $this->addOrderItemToInvoice( $customer, $order, $order_item, $economic_invoice_draft, (int)($order_item['quantity'] ?? 1) ); } $result = null; if ($customer->hasOpenInvoiceDraft() && !$customer->invoicePerOrder()) { $open_invoice_draft = (int)$customer->getOpenInvoiceDraft(); $result = $this->addOrderToInvoiceDraft($open_invoice_draft, $order, $customer, $order_items); } if ($result === null) { $result = $economic_invoice_draft->createInvoiceDraftExample(); } if (!isset($result->draftInvoiceNumber) && !isset($result->lines[0])) { (new logs_o())->add( 'economic_invoice_draft', 'global', 3, $user_id, 'ECONOMIC_INVOICE_DRAFT_EXPORT', 'Failed to create economic invoice draft' ); $message = $result->message ?? 'Failed to create economic invoice draft'; throw new \Exception((string)$message); } if ($economic_module_orders->economic_invoice_draft_id->value() > 0) { $economic_invoice_draft->deleteInvoiceDraft($economic_module_orders->economic_invoice_draft_id->value()); } $new_draft_id = (int)($result->draftInvoiceNumber ?? $customer->getOpenInvoiceDraft()); $economic_module_orders->economic_invoice_draft_id->set($new_draft_id); if (!$customer->invoicePerOrder()) { $customer->setOpenInvoiceDraft($new_draft_id); } else { $customer->unsetOpenInvoiceDraft(); } (new logs_o())->add( 'economic_invoice_draft', 'global', 1, $user_id, 'ECONOMIC_INVOICE_DRAFT_EXPORT', 'Successfully exported an economic invoice draft' ); return $economic_module_orders->getArray(); } /** * @throws \Exception */ private function exportOrderInvoiceSynchronously(int $order_id, int $user_id): array { $order = (new orders_o())->getOrderById($order_id); if (!$order->exists()) { throw new \Exception('Order not found'); } $customer = (new orders_o())->getCustomerByOrderId($order_id); if (!$customer->exists()) { throw new \Exception('Customer not found'); } (new economic())->assertCustomerNumberIsNotDraft((int)$customer->customer_number->value()); $economic_module_orders = (new economic_module_orders())->getByOrderId($order_id); if ($economic_module_orders->economic_invoice_draft_id->value() === 0) { throw new \Exception('No economic invoice draft found'); } $invoice_id = (int)$economic_module_orders->economic_invoice_id->value(); if ($invoice_id > 0) { throw new \Exception('An invoice has already been created, invoice ID: ' . $invoice_id); } $invoice_draft_id = (int)$economic_module_orders->economic_invoice_draft_id->value(); $economic_invoice_draft = new economic_invoice_draft_mo(); $result = $economic_invoice_draft->publishInvoiceDraft($invoice_draft_id); if (!isset($result->bookedInvoiceNumber)) { (new logs_o())->add( 'economic_invoice', 'global', 3, $user_id, 'ECONOMIC_INVOICE_EXPORT', 'Failed to create economic invoice from draft: ' . $invoice_draft_id ); $message = $result->message ?? 'Failed to create economic invoice'; throw new \Exception((string)$message); } $economic_module_orders = (new economic_module_orders())->getByOrderId($order_id); $economic_module_orders->economic_invoice_id->set((int)$result->bookedInvoiceNumber); $customer->unsetOpenInvoiceDraft(); (new logs_o())->add( 'economic_invoice', 'global', 1, $user_id, 'ECONOMIC_INVOICE_EXPORT', 'Successfully exported an economic invoice' ); return $economic_module_orders->getArray(); } private function addTheDepartmentDateReference(economic_invoice_draft_mo $economic_invoice_draft, mixed $department_name, orders_o $order): void { $parsed_date = date('d/m/Y H:i', strtotime($order->created_at->value())); $economic_invoice_draft->addLineTEXT("[ " . $parsed_date . ' ' . $department_name . ' #' . $order->id . " ]"); if ($order->reference->value() !== '') { $economic_invoice_draft->addLineTEXT('Reference:'); if (str_contains($order->reference->value(), "\n")) { foreach (explode("\n", $order->reference->value()) as $line) { $economic_invoice_draft->addLineTEXT('# ' . $line); } } else { $economic_invoice_draft->addLineTEXT('# ' . $order->reference->value()); } } $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()); } $economic_invoice_draft->addLineTEXT($line_reg); if ($order->notes->value() !== '') { $economic_invoice_draft->addLineTEXT('Notat:'); if (str_contains($order->notes->value(), "\n")) { foreach (explode("\n", $order->notes->value()) as $line) { $economic_invoice_draft->addLineTEXT('# ' . $line); } } else { $economic_invoice_draft->addLineTEXT('# ' . $order->notes->value()); } } } /** * @throws \Exception */ private function addOrderItemToInvoice(users_o $customer, orders_o $order, mixed $order_item, economic_invoice_draft_mo $economic_invoice_draft, int $quantity = 1): void { $department = $order->getDepartmentByOrderId($order->id); $economic_department_id = $department['economic_department_id'] ?? 0; $economic_dimension_id = $department['economic_dimension_id'] ?? 0; $economic_invoice_draft->addLine( (string)$order_item['product']['economic_product_id'], (string)$order_item['product']['name'], (int)$quantity, (int)$order_item['price'], 0, (int)$economic_department_id, (int)$economic_dimension_id ); $discount_percentage = round((($order_item['product']['price'] - $order_item['price']) / $order_item['product']['price']) * 100, 0); if ($order_item['price'] !== $order_item['product']['price']) { $economic_invoice_draft->addLineTEXT('Rabat: ' . ($order_item['price'] - $order_item['product']['price']) . ' DKK (' . $discount_percentage . '%)'); } if ($order_item['reference'] !== '') { $economic_invoice_draft->addLineTEXT('Reference:'); if (str_contains($order_item['reference'], "\n")) { foreach (explode("\n", $order_item['reference']) as $line) { $economic_invoice_draft->addLineTEXT('# ' . $line); } } else { $economic_invoice_draft->addLineTEXT('# ' . $order_item['reference']); } } if ($customer->doesUserHaveAttribute('requiresRegistrationNumbersInvoice')) { $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()); } $economic_invoice_draft->addLineTEXT($line_reg); } if ($order_item['notes'] !== '') { $economic_invoice_draft->addLineTEXT('Notat:'); if (str_contains($order_item['notes'], "\n")) { foreach (explode("\n", $order_item['notes']) as $line) { $economic_invoice_draft->addLineTEXT('# ' . $line); } } else { $economic_invoice_draft->addLineTEXT('# ' . $order_item['notes']); } } } private function addOrderToInvoiceDraft(int $economic_invoice_draft_id, orders_o $order, users_o $customer, array $order_items): object { $economic_invoice_draft = new economic_invoice_draft_mo(); $economic_invoice_draft->addLineTEXT(''); $economic_invoice_draft->addLineTEXT(''); $this->addTheDepartmentDateReference($economic_invoice_draft, $order->getDepartmentByOrderId($order->id)['name'], $order); foreach ($order_items as $order_item) { $this->addOrderItemToInvoice( $customer, $order, $order_item, $economic_invoice_draft, (int)($order_item['quantity'] ?? 1) ); } return $economic_invoice_draft->addLinesToInvoiceDraft($economic_invoice_draft_id); } private function ensureEconomicTransferQueueIsAvailable(): void { global $response; if (!$this->isEconomicTransferQueueAvailable()) { $response->error('Economic transfer queue is unavailable in this deployment', 503); } } /** * @throws \Exception */ private function assertOrderCanBeExportedToEconomic(int $order_id): void { $customer = (new orders_o())->getCustomerByOrderId($order_id); if (!$customer->exists()) { throw new \Exception('Customer not found'); } (new economic())->assertCustomerNumberIsNotDraft((int)$customer->customer_number->value()); } }