PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.2.0
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.2.0
2.4.0 2.4.1 2.3.8 2.3.7 2.3.6 2.3.5 2.3.4 2.3.3 2.3.2 2.3.1 2.2.0 2.1.21 2.1.20 2.1.19 2.1.18 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.13 2.1.14 2.1.15 2.1.16 2.1.2 All 57 releases
easy-invoice / includes / Models / Invoice.php

Invoice.php in Easy Invoice – Invoice Generator, PDF Quotes & Payments 2.2.0, at includes/Models/Invoice.php

918 lines 28.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Invoice Model
4 *
5 * @package EasyInvoice
6 * @author Your Name
7 * @copyright Copyright (c) 2023, Your Company
8 * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
9 * @since 1.0.0
10 */
11
12 namespace EasyInvoice\Models;
13
14 use EasyInvoice\Constants\PostTypes;
15 use EasyInvoice\Models\InvoiceItem;
16
17 /**
18 * Invoice Model
19 *
20 * Represents an invoice in the system with extensible architecture for pro features.
21 *
22 * @since 1.0.0
23 */
24 class Invoice {
25 /**
26 * Invoice ID
27 *
28 * @var int
29 */
30 private $id;
31
32 /**
33 * Dynamic data storage for all fields
34 *
35 * @var array
36 */
37 private $data = [];
38
39 /**
40 * Items
41 *
42 * @var array
43 */
44 private $items = [];
45
46 /**
47 * Modified flag
48 *
49 * @var bool
50 */
51 private $is_modified = false;
52
53 /**
54 * Magic method to get dynamic properties
55 *
56 * @since 1.0.0
57 * @param string $name Property name
58 * @return mixed Property value
59 */
60 public function __get($name) {
61 // Handle special properties
62 if ($name === 'id') {
63 return $this->id;
64 }
65
66 // Special handling for total field
67 if ($name === 'total') {
68 return $this->getTotal();
69 }
70
71 // Return from dynamic data array
72 return $this->data[$name] ?? null;
73 }
74
75 /**
76 * Magic method to set dynamic properties
77 *
78 * @since 1.0.0
79 * @param string $name Property name
80 * @param mixed $value Property value
81 */
82 public function __set($name, $value) {
83 // Handle special properties
84 if ($name === 'id') {
85 $this->id = $value;
86 return;
87 }
88
89 // Store in dynamic data array
90 $this->data[$name] = $value;
91 $this->is_modified = true;
92 }
93
94 /**
95 * Magic method to check if property exists
96 *
97 * @since 1.0.0
98 * @param string $name Property name
99 * @return bool
100 */
101 public function __isset($name) {
102 if ($name === 'id') {
103 return isset($this->id);
104 }
105
106 return isset($this->data[$name]);
107 }
108
109 /**
110 * Constructor
111 *
112 * @since 1.0.0
113 * @param \WP_Post|int $invoice Invoice post object or ID
114 */
115 public function __construct($invoice = null) {
116 // Initialize data array dynamically from field configuration
117 $this->data = $this->getDefaultValuesFromConfiguration();
118
119 if ($invoice instanceof \WP_Post) {
120 $this->loadFromPost($invoice);
121 } elseif (is_numeric($invoice)) {
122 $post = get_post($invoice);
123 if ($post && $post->post_type === PostTypes::EASY_INVOICE_POST_TYPE) {
124 $this->loadFromPost($post);
125 }
126 }
127
128 do_action('easy_invoice_invoice_model_constructed', $this);
129 }
130
131 /**
132 * Get default values from field configuration
133 *
134 * @since 1.0.0
135 * @return array
136 */
137 private function getDefaultValuesFromConfiguration(): array {
138 $default_values = [];
139
140 // Get field definitions to determine default values
141 $field_registration = new \EasyInvoice\Forms\Invoice\InvoiceFieldRegistration();
142
143 // Initialize the field registration to ensure fields are registered
144 $field_registration->registerDefaultTabs();
145 $field_registration->registerDefaultFields();
146
147 $field_definitions = [];
148 $tabs = $field_registration->getTabs();
149 foreach ($tabs as $tab_id => $tab) {
150 $tab_fields = $field_registration->getFields($tab_id);
151 foreach ($tab_fields as $field) {
152 $field_definitions[] = $field;
153 }
154 }
155
156 // Extract default values from field configuration
157 foreach ($field_definitions as $field) {
158 $field_name = $field['name'] ?? '';
159 if (empty($field_name)) {
160 continue;
161 }
162
163 $default_value = $field['default_value'] ?? null;
164
165 // Handle callable default values
166 if (is_callable($default_value)) {
167 $default_value = $default_value();
168 }
169
170 // Set appropriate default based on field type
171 if ($default_value !== null) {
172 $default_values[$field_name] = $default_value;
173 } else {
174 // Set type-appropriate defaults
175 $field_type = $field['type'] ?? 'text';
176 switch ($field_type) {
177 case 'number':
178 $default_values[$field_name] = 0.0;
179 break;
180 case 'checkbox':
181 $default_values[$field_name] = false;
182 break;
183 case 'select':
184 $default_values[$field_name] = '';
185 break;
186 case 'array':
187 $default_values[$field_name] = [];
188 break;
189 default:
190 $default_values[$field_name] = '';
191 break;
192 }
193 }
194 }
195
196 return $default_values;
197 }
198
199 /**
200 * Load invoice data from WP_Post
201 *
202 * @since 1.0.0
203 * @param \WP_Post $post
204 */
205 private function loadFromPost(\WP_Post $post): void {
206 $this->id = $post->ID;
207 $this->data['title'] = $post->post_title ?: '';
208 $this->data['created_date'] = $post->post_date ?: '';
209 $this->data['modified_date'] = $post->post_modified ?: '';
210
211 // Load meta data using configuration-driven approach
212 $this->loadMetaData();
213
214 // Load items
215 $this->loadItems();
216
217 // Auto-calculate totals if they're 0 or if we have items but no totals
218 if ((($this->data['total'] ?? 0) == 0 && !empty($this->items)) ||
219 (($this->data['subtotal'] ?? 0) == 0 && !empty($this->items))) {
220 $this->calculateTotals();
221 }
222
223 // Populate client information if we have a client_id but no customer data
224 if (($this->data['client_id'] ?? 0) > 0 && (empty($this->data['customer_name'] ?? '') || empty($this->data['customer_email'] ?? ''))) {
225 $this->populateClientInfo();
226 }
227 }
228
229 /**
230 * Load meta data from database
231 *
232 * @since 1.0.0
233 */
234 private function loadMetaData(): void {
235 if (!$this->id) {
236 return;
237 }
238
239 // Get field definitions to determine which meta keys to load
240 $field_registration = new \EasyInvoice\Forms\Invoice\InvoiceFieldRegistration();
241
242 // Initialize the field registration to ensure fields are registered
243 $field_registration->registerDefaultTabs();
244 $field_registration->registerDefaultFields();
245
246 $field_definitions = [];
247 $tabs = $field_registration->getTabs();
248 foreach ($tabs as $tab_id => $tab) {
249 $tab_fields = $field_registration->getFields($tab_id);
250 foreach ($tab_fields as $field) {
251 $field_definitions[] = $field;
252 }
253 }
254
255 // Load meta data for each field definition
256 foreach ($field_definitions as $field) {
257 $field_name = $field['name'] ?? '';
258 if (empty($field_name)) {
259 continue;
260 }
261
262 $meta_key = '_easy_invoice_' . $field_name;
263 $value = get_post_meta($this->id, $meta_key, true);
264
265 if ($value !== '') {
266 // Store in dynamic data array
267 $this->data[$field_name] = $value;
268 }
269 }
270
271
272
273
274 }
275
276 /**
277 * Load invoice items
278 *
279 * @since 1.0.0
280 */
281 private function loadItems(): void {
282 if (!$this->id) {
283 return;
284 }
285
286 $items_data = get_post_meta($this->id, '_easy_invoice_items', true) ?: [];
287 $this->items = [];
288
289 foreach ($items_data as $item_data) {
290 $this->items[] = new \EasyInvoice\Models\InvoiceItem($item_data);
291 }
292 }
293
294 /**
295 * Ensure the invoice has a proper slug for pretty permalinks
296 *
297 * @since 1.0.0
298 * @return bool True if successful, false otherwise
299 */
300 public function ensureProperSlug(): bool {
301 if (!$this->id) {
302 return false;
303 }
304
305 $post = get_post($this->id);
306 if (!$post || empty($post->post_name)) {
307 // Generate a slug from the title
308 $post_title = $this->data['title'] ?: $this->data['number'] ?: 'Untitled Invoice';
309 $post_name = sanitize_title($post_title);
310
311 // Ensure uniqueness
312 $original_slug = $post_name;
313 $counter = 1;
314 while (get_page_by_path($post_name, OBJECT, PostTypes::EASY_INVOICE_POST_TYPE)) {
315 $post_name = $original_slug . '-' . $counter;
316 $counter++;
317 }
318
319 // Update the post with the new slug
320 $result = wp_update_post([
321 'ID' => $this->id,
322 'post_name' => $post_name,
323 'post_status' => 'publish' // Ensure it's published for proper permalinks
324 ]);
325
326 return !is_wp_error($result);
327 }
328
329 return true;
330 }
331
332 /**
333 * Save the invoice
334 * Always publish invoices to ensure proper permalinks
335 * Invoice status will be stored in post meta (_easy_invoice_status)
336 *
337 * @since 1.0.0
338 * @return bool True if successful, false otherwise
339 */
340 public function save(): bool {
341 // Allow plugins to modify the invoice before saving
342 do_action('easy_invoice_model_before_save', $this);
343
344 // Always publish invoices to ensure proper permalinks
345 // Invoice status will be stored in post meta (_easy_invoice_status)
346 $post_status = 'publish';
347
348 // Generate a proper slug for pretty permalinks
349 $post_title = $this->data['title'] ?: $this->data['number'] ?: 'Untitled Invoice';
350 $post_name = '';
351
352 if ($this->id) {
353 // For existing posts, get the current post to preserve slug if it exists
354 $existing_post = get_post($this->id);
355 if ($existing_post && !empty($existing_post->post_name)) {
356 $post_name = $existing_post->post_name;
357 }
358 }
359
360 // If no slug exists, generate one from the title
361 if (empty($post_name)) {
362 $post_name = sanitize_title($post_title);
363
364 // Ensure uniqueness by appending number if needed
365 $original_slug = $post_name;
366 $counter = 1;
367 while (get_page_by_path($post_name, OBJECT, PostTypes::EASY_INVOICE_POST_TYPE)) {
368 $post_name = $original_slug . '-' . $counter;
369 $counter++;
370 }
371 }
372
373 // Prepare post data
374 $post_data = [
375 'post_title' => $post_title,
376 'post_content' => $this->data['notes'] ?: '', // Ensure post_content is never null
377 'post_status' => $post_status, // Always publish for proper permalinks
378 'post_type' => PostTypes::EASY_INVOICE_POST_TYPE,
379 'post_name' => $post_name, // Add the generated slug
380 ];
381
382 // Update existing post or create new one
383 if ($this->id) {
384 $post_data['ID'] = $this->id;
385 $post_id = wp_update_post($post_data);
386 } else {
387 $post_id = wp_insert_post($post_data);
388 }
389
390 if (is_wp_error($post_id)) {
391 return false;
392 }
393
394 // Update the ID if this was a new post
395 if (!$this->id) {
396 $this->id = $post_id;
397 }
398
399 // Calculate totals from items before saving
400 $this->calculateTotals();
401
402 // Save meta data (including invoice status)
403 $this->saveMetaData();
404
405 // Allow plugins to perform actions after saving
406 do_action('easy_invoice_model_after_save', $this);
407
408 $this->is_modified = false;
409 return true;
410 }
411
412 /**
413 * Save meta data to database
414 *
415 * @since 1.0.0
416 */
417 private function saveMetaData(): void {
418 if (!$this->id) {
419 return;
420 }
421
422 // Get field definitions to determine which meta keys to save
423 $field_registration = new \EasyInvoice\Forms\Invoice\InvoiceFieldRegistration();
424
425 // Initialize the field registration to ensure fields are registered
426 $field_registration->registerDefaultTabs();
427 $field_registration->registerDefaultFields();
428
429 $field_definitions = [];
430 $tabs = $field_registration->getTabs();
431 foreach ($tabs as $tab_id => $tab) {
432 $tab_fields = $field_registration->getFields($tab_id);
433 foreach ($tab_fields as $field) {
434 $field_definitions[] = $field;
435 }
436 }
437
438 // Save meta data for each field definition
439 foreach ($field_definitions as $field) {
440 $field_name = $field['name'] ?? '';
441 if (empty($field_name)) {
442 continue;
443 }
444
445 $meta_key = '_easy_invoice_' . $field_name;
446
447 // Get the value from dynamic data array
448 // Save all fields that exist in the data array (including empty strings to allow clearing fields)
449 // If a field exists in $this->data, it means it was explicitly set, so we should save it
450 if (array_key_exists($field_name, $this->data)) {
451 $value = $this->data[$field_name];
452 update_post_meta($this->id, $meta_key, $value);
453 }
454 }
455
456
457
458
459
460 // Save items
461 $this->saveItems();
462
463 // Allow plugins to save additional meta data
464 do_action('easy_invoice_model_save_meta_data', $this);
465 }
466
467 /**
468 * Save invoice items
469 *
470 * @since 1.0.0
471 */
472 private function saveItems(): void {
473 if (!$this->id) {
474 return;
475 }
476
477 $items_data = [];
478 foreach ($this->items as $item) {
479 if (is_object($item) && method_exists($item, 'toArray')) {
480 $items_data[] = $item->toArray();
481 } else {
482 $items_data[] = $item;
483 }
484 }
485 update_post_meta($this->id, '_easy_invoice_items', $items_data);
486 }
487
488 /**
489 * Calculate totals from items
490 *
491 * @since 1.0.0
492 */
493 public function calculateTotals(): void {
494 $prices_include_tax = ($this->data['prices_include_tax'] ?? 'no') === 'yes';
495 $tax_rate = floatval($this->data['tax_rate'] ?? 0);
496
497 // Initialize totals
498 $subtotal = 0;
499 $taxable_subtotal = 0;
500 $this->data['tax_amount'] = 0;
501 $this->data['discount_amount'] = 0;
502
503 // First pass: Calculate raw totals
504 foreach ($this->items as $index => $item) {
505 $quantity = 0;
506 $price = 0;
507 $adjust_percentage = 0;
508 $is_taxable = true;
509
510 if (is_object($item) && method_exists($item, 'getAmount')) {
511 $quantity = $item->getQuantity();
512 $price = $item->getPrice();
513 $adjust_percentage = $item->getAdjustPercentage();
514 $is_taxable = $item->isTaxable();
515 } elseif (is_array($item)) {
516 $quantity = isset($item['quantity']) ? (float) $item['quantity'] : 0;
517 $price = isset($item['price']) ? (float) $item['price'] : 0;
518 $adjust_percentage = isset($item['adjust_percentage']) ? (float) $item['adjust_percentage'] : 0;
519 $is_taxable = isset($item['taxable']) ? (bool) $item['taxable'] : true;
520 }
521
522 // If prices include tax and item is taxable, remove tax from price
523 if ($prices_include_tax && $is_taxable && $tax_rate > 0) {
524 $price = $price / (1 + ($tax_rate / 100));
525 }
526
527 // Calculate item total
528 $item_total = $quantity * $price;
529 // Only apply adjust percentage if the adjust field is enabled
530 if ($adjust_percentage != 0 && \EasyInvoice\Controllers\SettingsController::shouldShowInvoiceAdjustField()) {
531 $item_total = $item_total * (1 + $adjust_percentage / 100);
532 }
533
534 $subtotal += $item_total;
535 if ($is_taxable) {
536 $taxable_subtotal += $item_total;
537 }
538 }
539
540 $this->data['subtotal'] = $subtotal;
541
542 // Get discount calculation method (default to before_tax)
543 $discount_calculation_method = $this->data['discount_calculation_method'] ?? 'before_tax';
544
545 // Calculate initial discount amount
546 if (($this->data['discount_type'] ?? '') === 'percentage' && ($this->data['discount_value'] ?? 0) > 0 && $subtotal > 0) {
547 $this->data['discount_amount'] = ($subtotal * $this->data['discount_value']) / 100;
548 } elseif (($this->data['discount_type'] ?? '') === 'fixed' && ($this->data['discount_value'] ?? 0) > 0) {
549 $this->data['discount_amount'] = $this->data['discount_value'];
550 } else {
551 $this->data['discount_amount'] = 0;
552 }
553
554 // Calculate tax and total based on discount calculation method
555 if ($discount_calculation_method === 'before_tax') {
556 // For before_tax: Apply discount first, then calculate tax on remaining taxable amount
557 $discount_ratio = ($subtotal > 0) ? ($this->data['discount_amount'] / $subtotal) : 0;
558 $taxable_amount = $taxable_subtotal * (1 - $discount_ratio);
559
560 if ($tax_rate > 0) {
561 $this->data['tax_amount'] = ($taxable_amount * $tax_rate) / 100;
562 }
563
564 $this->data['total'] = $subtotal - $this->data['discount_amount'] + $this->data['tax_amount'];
565 } else {
566 // For after_tax: Calculate tax first, then apply discount
567 if ($tax_rate > 0) {
568 $this->data['tax_amount'] = ($taxable_subtotal * $tax_rate) / 100;
569 }
570
571 $total_before_discount = $subtotal + $this->data['tax_amount'];
572
573 // Recalculate percentage discount based on total including tax
574 if (($this->data['discount_type'] ?? '') === 'percentage' && ($this->data['discount_value'] ?? 0) > 0 && $total_before_discount > 0) {
575 $this->data['discount_amount'] = ($total_before_discount * $this->data['discount_value']) / 100;
576 }
577
578 $this->data['total'] = $total_before_discount - $this->data['discount_amount'];
579 }
580
581 // Allow plugins to modify calculations
582 do_action('easy_invoice_model_calculate_totals', $this);
583 }
584
585 /**
586 * Populate client information from client repository
587 *
588 * @since 1.0.0
589 */
590 public function populateClientInfo(): void {
591 if (($this->data['client_id'] ?? 0) > 0) {
592 $client_repository = new \EasyInvoice\Repositories\ClientRepository();
593 $client = $client_repository->find($this->data['client_id']);
594
595 if ($client) {
596 $this->data['customer_name'] = $client->getBusinessClientName() ?: '';
597 $this->data['customer_email'] = $client->getEmail() ?: '';
598 $this->data['customer_address'] = $client->getAddress() ?: '';
599 }
600 }
601 }
602
603 /**
604 * Force populate client info and save (for existing invoices that need client data)
605 *
606 * @since 1.0.0
607 * @return bool
608 */
609 public function forcePopulateClientInfo(): bool {
610 $this->populateClientInfo();
611 return $this->save();
612 }
613
614 /**
615 * Recalculate totals and save (for existing invoices)
616 *
617 * @since 1.0.0
618 * @return bool
619 */
620 public function recalculateAndSave(): bool {
621 // Populate client information if we have a client_id
622 $this->populateClientInfo();
623
624 // Calculate totals from items
625 $this->calculateTotals();
626
627 // Save the updated invoice
628 return $this->save();
629 }
630
631 /**
632 * Convert to array
633 *
634 * @since 1.0.0
635 * @return array
636 */
637 public function toArray(): array {
638 $items_array = [];
639 foreach ($this->items as $item) {
640 if (is_object($item) && method_exists($item, 'toArray')) {
641 $items_array[] = $item->toArray();
642 } else {
643 $items_array[] = $item;
644 }
645 }
646
647 // Start with dynamic data
648 $data = $this->data;
649
650 // Add special properties
651 $data['id'] = $this->id;
652 $data['items'] = $items_array;
653
654 // Allow plugins to modify the array data
655 return apply_filters('easy_invoice_model_to_array', $data, $this);
656 }
657
658 /**
659 * Dynamic getter method
660 *
661 * @since 1.0.0
662 * @param string $name Method name
663 * @param array $arguments Method arguments
664 * @return mixed
665 */
666 public function __call($name, $arguments) {
667 // Handle getter methods (getFieldName)
668 if (strpos($name, 'get') === 0) {
669 $field_name = $this->camelCaseToSnakeCase(substr($name, 3)); // Remove 'get' prefix
670 return $this->__get($field_name);
671 }
672
673 // Handle setter methods (setFieldName)
674 if (strpos($name, 'set') === 0) {
675 $field_name = $this->camelCaseToSnakeCase(substr($name, 3)); // Remove 'set' prefix
676 $value = $arguments[0] ?? null;
677 $this->__set($field_name, $value);
678 return null;
679 }
680
681 // Handle isset methods (isFieldName)
682 if (strpos($name, 'is') === 0) {
683 $field_name = $this->camelCaseToSnakeCase(substr($name, 2)); // Remove 'is' prefix
684 return (bool) $this->__get($field_name);
685 }
686
687 // Handle has methods (hasFieldName)
688 if (strpos($name, 'has') === 0) {
689 $field_name = $this->camelCaseToSnakeCase(substr($name, 3)); // Remove 'has' prefix
690 return !empty($this->__get($field_name));
691 }
692
693 throw new \BadMethodCallException("Method $name does not exist");
694 }
695
696 /**
697 * Convert camelCase to snake_case
698 *
699 * @since 1.0.0
700 * @param string $camelCase
701 * @return string
702 */
703 private function camelCaseToSnakeCase($camelCase) {
704 return strtolower(preg_replace('/(?<!^)[A-Z]/', '_$0', $camelCase));
705 }
706
707 // Essential methods only
708 public function getId(): int { return $this->id ?? 0; }
709 public function setId(int $id): void { $this->id = $id; }
710 public function getItems(): array { return $this->items; }
711
712 /**
713 * Check if invoice exists
714 *
715 * @return bool
716 */
717 public function exists(): bool {
718 return $this->id > 0 && get_post($this->id) !== null;
719 }
720 public function setItems(array $items): void {
721 $this->items = [];
722 foreach ($items as $item) {
723 if (is_array($item)) {
724 $this->items[] = new InvoiceItem($item);
725 } elseif (is_object($item) && $item instanceof InvoiceItem) {
726 $this->items[] = $item;
727 }
728 }
729 $this->is_modified = true;
730 }
731 public function isModified(): bool { return $this->is_modified; }
732
733 /**
734 * Set meta data for the invoice
735 *
736 * @since 1.0.0
737 * @param string $key Meta key
738 * @param mixed $value Meta value
739 */
740 public function setMetaData(string $key, $value): void {
741 // Store in dynamic data array without the _easy_invoice_ prefix
742 $field_name = easy_invoice_str_replace('_easy_invoice_', '', $key);
743 $this->data[$field_name] = $value;
744 $this->is_modified = true;
745 }
746
747 /**
748 * Get currency code
749 *
750 * @since 1.0.0
751 * @return string Currency code
752 */
753 public function getCurrencyCode(): string {
754 $currency_code = $this->data['currency_code'] ?? 'USD';
755
756 // If currency is set to 'global', resolve to actual global setting
757 if ($currency_code === 'global') {
758 $currency_code = get_option('easy_invoice_currency_code', 'USD');
759 }
760
761 // Ensure currency code is uppercase for consistency
762 return strtoupper($currency_code);
763 }
764
765 /**
766 * Set currency code
767 *
768 * @since 1.0.0
769 * @param string $currency_code Currency code
770 */
771 public function setCurrencyCode(string $currency_code): void {
772 $this->data['currency_code'] = $currency_code;
773 $this->is_modified = true;
774 }
775
776 /**
777 * Get currency position
778 *
779 * @since 1.0.0
780 * @return string Currency position
781 */
782 public function getCurrencyPosition(): string {
783 $currency_position = $this->data['currency_position'] ?? 'left';
784
785 // If currency position is set to 'global', resolve to actual global setting
786 if ($currency_position === 'global') {
787 $currency_position = get_option('easy_invoice_currency_position', 'left');
788 }
789
790 // Map form format to global settings format
791 if ($currency_position === 'before') {
792 $currency_position = 'left';
793 } elseif ($currency_position === 'after') {
794 $currency_position = 'right';
795 }
796
797 return $currency_position;
798 }
799
800 /**
801 * Set currency position
802 *
803 * @since 1.0.0
804 * @param string $currency_position Currency position
805 */
806 public function setCurrencyPosition(string $currency_position): void {
807 $this->data['currency_position'] = $currency_position;
808 $this->is_modified = true;
809 }
810
811 /**
812 * Get raw currency code (without resolving global)
813 *
814 * @since 1.0.0
815 * @return string Raw currency code
816 */
817 public function getRawCurrencyCode(): string {
818 return $this->data['currency_code'] ?? 'global';
819 }
820
821 /**
822 * Get raw currency position (without resolving global)
823 *
824 * @since 1.0.0
825 * @return string Raw currency position
826 */
827 public function getRawCurrencyPosition(): string {
828 return $this->data['currency_position'] ?? 'global';
829 }
830
831 public function getDescription(): string {
832 return $this->data['description'] ?? '';
833 }
834
835 public function setDescription(string $description): void {
836 $this->data['description'] = $description;
837 $this->is_modified = true;
838 }
839
840 public function getTemplate(): string {
841 return $this->data['invoice_template'] ?? 'standard';
842 }
843
844 public function setTemplate(string $template): void {
845 $this->data['invoice_template'] = $template;
846 $this->is_modified = true;
847 }
848
849 /**
850 * Get discount type (percentage, fixed, none)
851 *
852 * @since 1.0.0
853 * @return string
854 */
855 public function getDiscountType(): string {
856 return $this->data['discount_type'] ?? 'none';
857 }
858
859 /**
860 * Get discount value (percentage or fixed amount)
861 *
862 * @since 1.0.0
863 * @return float
864 */
865 public function getDiscountValue(): float {
866 return floatval($this->data['discount_value'] ?? 0);
867 }
868
869 /**
870 * Get discount amount (calculated value)
871 *
872 * @since 1.0.0
873 * @return float
874 */
875 public function getDiscountAmount(): float {
876 $this->calculateTotals();
877 return floatval($this->data['discount_amount'] ?? 0);
878 }
879
880 /**
881 * Get tax rate percentage
882 *
883 * @since 1.0.0
884 * @return float
885 */
886 public function getTaxRate(): float {
887 return floatval($this->data['tax_rate'] ?? 0);
888 }
889
890 /**
891 * Get tax amount (calculated value)
892 *
893 * @since 1.0.0
894 * @return float
895 */
896 public function getTaxAmount(): float {
897 $this->calculateTotals();
898 return floatval($this->data['tax_amount'] ?? 0);
899 }
900
901 /**
902 * Get the total amount for this invoice
903 * Calculate from items
904 *
905 * @since 1.0.0
906 * @return float
907 */
908 public function getTotal(): float {
909 // Calculate from items
910 $this->calculateTotals();
911 $total = (float) ($this->data['total'] ?? 0);
912
913 // Allow plugins to modify the total
914 return apply_filters('easy_invoice_invoice_total', $total, $this);
915 }
916
917
918 }