PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.3.3
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.3.3
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 / Services / InvoiceService.php

InvoiceService.php in Easy Invoice – Invoice Generator, PDF Quotes & Payments 2.3.3, at includes/Services/InvoiceService.php

409 lines 13.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Invoice Service Class
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\Services;
13
14 use EasyInvoice\Models\Invoice;
15 use EasyInvoice\Repositories\InvoiceRepository;
16 use EasyInvoice\Interfaces\InvoiceRepositoryInterface;
17
18 /**
19 * Invoice Service Class
20 *
21 * Handles business logic for invoices and provides extension points for plugins.
22 *
23 * @since 1.0.0
24 */
25 class InvoiceService extends BaseService {
26
27 /**
28 * Invoice repository
29 *
30 * @var InvoiceRepositoryInterface
31 */
32 private $repository;
33
34 /**
35 * Constructor
36 *
37 * @since 1.0.0
38 * @param InvoiceRepositoryInterface $repository The invoice repository
39 */
40 public function __construct(InvoiceRepositoryInterface $repository) {
41 parent::__construct('InvoiceService');
42 $this->repository = $repository;
43 }
44
45 /**
46 * Create a new invoice
47 *
48 * @since 1.0.0
49 * @param array $data The invoice data
50 * @return Invoice|false The created invoice or false on failure
51 */
52 public function createInvoice(array $data) {
53 // Allow plugins to modify data before creation
54 $data = apply_filters('easy_invoice_service_invoice_create_data', $data);
55
56 // Validate required fields
57 $required_fields = ['title'];
58 $errors = $this->validateRequiredFields($data, $required_fields);
59
60 if (!empty($errors)) {
61 $this->log('Invoice creation failed: ' . implode(', ', $errors), 'error');
62 return false;
63 }
64
65 // Sanitize data
66 $sanitized_data = $this->sanitizeInvoiceData($data);
67
68 // Allow plugins to perform actions before creation
69 do_action('easy_invoice_service_invoice_before_create', $sanitized_data);
70
71 // Create the invoice
72 $invoice = $this->repository->create($sanitized_data);
73
74 if ($invoice) {
75 $this->log('Invoice created successfully: ' . $invoice->getId());
76
77 // Allow plugins to perform actions after creation
78 do_action('easy_invoice_service_invoice_created', $invoice, $sanitized_data);
79
80 return $invoice;
81 }
82
83 $this->log('Invoice creation failed', 'error');
84 return false;
85 }
86
87 /**
88 * Update an existing invoice
89 *
90 * @since 1.0.0
91 * @param int $id The invoice ID
92 * @param array $data The invoice data
93 * @return Invoice|null The updated invoice or null on failure
94 */
95 public function updateInvoice(int $id, array $data) {
96 // Allow plugins to modify data before update
97 $data = apply_filters('easy_invoice_service_invoice_update_data', $data, $id);
98
99 // Get the existing invoice
100 $invoice = $this->repository->find($id);
101 if (!$invoice) {
102 $this->log('Invoice not found for update: ' . $id, 'error');
103 return null;
104 }
105
106 // Sanitize data
107 $sanitized_data = $this->sanitizeInvoiceData($data);
108
109 // Allow plugins to perform actions before update
110 do_action('easy_invoice_service_invoice_before_update', $invoice, $sanitized_data);
111
112 // Update the invoice
113 $updated_invoice = $this->repository->update($id, $sanitized_data);
114
115 if ($updated_invoice) {
116 $this->log('Invoice updated successfully: ' . $id);
117
118 // Allow plugins to perform actions after update
119 do_action('easy_invoice_service_invoice_updated', $updated_invoice, $sanitized_data);
120
121 return $updated_invoice;
122 }
123
124 $this->log('Invoice update failed: ' . $id, 'error');
125 return null;
126 }
127
128 /**
129 * Delete an invoice
130 *
131 * @since 1.0.0
132 * @param int $id The invoice ID
133 * @return bool True if deleted successfully
134 */
135 public function deleteInvoice(int $id): bool {
136 // Get the invoice before deletion
137 $invoice = $this->repository->find($id);
138 if (!$invoice) {
139 $this->log('Invoice not found for deletion: ' . $id, 'error');
140 return false;
141 }
142
143 // Allow plugins to perform actions before deletion
144 do_action('easy_invoice_service_invoice_before_delete', $invoice);
145
146 // Delete the invoice
147 $deleted = $this->repository->delete($id);
148
149 if ($deleted) {
150 $this->log('Invoice deleted successfully: ' . $id);
151
152 // Allow plugins to perform actions after deletion
153 do_action('easy_invoice_service_invoice_deleted', $id);
154
155 return true;
156 }
157
158 $this->log('Invoice deletion failed: ' . $id, 'error');
159 return false;
160 }
161
162 /**
163 * Get invoice by ID
164 *
165 * @since 1.0.0
166 * @param int $id The invoice ID
167 * @return Invoice|null The invoice or null if not found
168 */
169 public function getInvoice(int $id) {
170 $invoice = $this->repository->find($id);
171
172 // Allow plugins to modify the found invoice
173 return apply_filters('easy_invoice_service_invoice_found', $invoice, $id);
174 }
175
176 /**
177 * Get all invoices
178 *
179 * @since 1.0.0
180 * @param array $args Optional arguments to filter the results
181 * @return array Array of Invoice models
182 */
183 public function getAllInvoices(array $args = []): array {
184 $invoices = $this->repository->all($args);
185
186 // Allow plugins to modify the invoices list
187 return apply_filters('easy_invoice_service_invoices_found', $invoices, $args);
188 }
189
190 /**
191 * Get invoices by customer
192 *
193 * @since 1.0.0
194 * @param int $customer_id The customer ID
195 * @return array Array of Invoice models
196 */
197 public function getInvoicesByCustomer(int $customer_id): array {
198 $invoices = $this->repository->findByCustomer($customer_id);
199
200 // Allow plugins to modify the filtered results
201 return apply_filters('easy_invoice_service_invoices_by_customer', $invoices, $customer_id);
202 }
203
204 /**
205 * Get invoices by status
206 *
207 * @since 1.0.0
208 * @param string $status The invoice status
209 * @return array Array of Invoice models
210 */
211 public function getInvoicesByStatus(string $status): array {
212 $invoices = $this->repository->findByStatus($status);
213
214 // Allow plugins to modify the filtered results
215 return apply_filters('easy_invoice_service_invoices_by_status', $invoices, $status);
216 }
217
218 /**
219 * Get invoices due within a date range
220 *
221 * @since 1.0.0
222 * @param string $start_date The start date in 'Y-m-d' format
223 * @param string $end_date The end date in 'Y-m-d' format
224 * @return array Array of Invoice models
225 */
226 public function getInvoicesByDueDate(string $start_date, ?string $end_date = null): array {
227 $invoices = $this->repository->findByDueDate($start_date, $end_date);
228
229 // Allow plugins to modify the filtered results
230 return apply_filters('easy_invoice_service_invoices_by_due_date', $invoices, $start_date, $end_date);
231 }
232
233 /**
234 * Count invoices
235 *
236 * @since 1.0.0
237 * @param array $args Optional arguments to filter the results
238 * @return int Number of invoices
239 */
240 public function countInvoices(array $args = []): int {
241 $count = $this->repository->count($args);
242
243 // Allow plugins to modify the count
244 return apply_filters('easy_invoice_service_invoice_count', $count, $args);
245 }
246
247 /**
248 * Calculate invoice total
249 *
250 * @since 1.0.0
251 * @param Invoice $invoice The invoice
252 * @return float The calculated total
253 */
254 public function calculateInvoiceTotal(Invoice $invoice): float {
255 $items = $invoice->getItems();
256 $subtotal = 0;
257
258 // Calculate subtotal from items
259 foreach ($items as $item) {
260 $quantity = floatval($item['quantity'] ?? 0);
261 $price = floatval($item['price'] ?? 0);
262 $subtotal += $quantity * $price;
263 }
264
265 // Apply discount
266 $discount_type = $invoice->getDiscountType();
267 $discount_value = floatval($invoice->getDiscountValue() ?? 0);
268
269 if ($discount_type === 'percentage' && $discount_value > 0) {
270 $discount_amount = $subtotal * ($discount_value / 100);
271 $subtotal -= $discount_amount;
272 } elseif ($discount_type === 'fixed' && $discount_value > 0) {
273 $subtotal -= $discount_value;
274 }
275
276 // Apply tax
277 $tax_rate = floatval($invoice->getTaxRate() ?? 0);
278 $prices_include_tax = $invoice->getPricesIncludeTax();
279
280 if ($tax_rate > 0) {
281 if ($prices_include_tax) {
282 // Tax is already included in prices
283 $total = $subtotal;
284 } else {
285 // Add tax to subtotal
286 $tax_amount = $subtotal * ($tax_rate / 100);
287 $total = $subtotal + $tax_amount;
288 }
289 } else {
290 $total = $subtotal;
291 }
292
293 // Allow plugins to modify the calculated total
294 return apply_filters('easy_invoice_service_invoice_total', $total, $invoice, $subtotal);
295 }
296
297 /**
298 * Send invoice to customer
299 *
300 * @since 1.0.0
301 * @param Invoice $invoice The invoice
302 * @param string $email The customer email
303 * @return bool True if email was sent successfully
304 */
305 public function sendInvoiceToCustomer(Invoice $invoice, string $email): bool {
306 // Allow plugins to modify email data
307 $email_data = apply_filters('easy_invoice_service_invoice_email_data', [
308 'to' => $email,
309 'subject' => sprintf(__('Invoice #%s from %s', 'easy-invoice'), $invoice->getNumber(), get_bloginfo('name')),
310 'message' => $this->generateInvoiceEmailMessage($invoice),
311 'headers' => ['Content-Type: text/html; charset=UTF-8']
312 ], $invoice);
313
314 // Allow plugins to handle email sending
315 $sent = apply_filters('easy_invoice_service_invoice_email_send', null, $email_data, $invoice);
316
317 if ($sent === null) {
318 $sent = $this->sendEmail(
319 $email_data['to'],
320 $email_data['subject'],
321 $email_data['message'],
322 $email_data['headers']
323 );
324 }
325
326 if ($sent) {
327 // Allow plugins to perform actions after email sent
328 do_action('easy_invoice_service_invoice_email_sent', $invoice, $email, $sent);
329 }
330
331 return $sent;
332 }
333
334 /**
335 * Generate invoice email message
336 *
337 * @since 1.0.0
338 * @param Invoice $invoice The invoice
339 * @return string The email message
340 */
341 protected function generateInvoiceEmailMessage(Invoice $invoice): string {
342 $message = sprintf(
343 '<p>%s</p>',
344 __('Please find attached your invoice.', 'easy-invoice')
345 );
346
347 $message .= sprintf(
348 '<p><strong>%s:</strong> %s</p>',
349 __('Invoice Number', 'easy-invoice'),
350 $invoice->getNumber()
351 );
352
353 $message .= sprintf(
354 '<p><strong>%s:</strong> %s</p>',
355 __('Amount', 'easy-invoice'),
356 $this->formatCurrency($this->calculateInvoiceTotal($invoice), $invoice->getCurrencyCode(), $invoice->getCurrencyPosition())
357 );
358
359 $message .= sprintf(
360 '<p><strong>%s:</strong> %s</p>',
361 __('Due Date', 'easy-invoice'),
362 $invoice->getDueDate()
363 );
364
365 // Allow plugins to modify the email message
366 return apply_filters('easy_invoice_service_invoice_email_message', $message, $invoice);
367 }
368
369 /**
370 * Sanitize invoice data
371 *
372 * @since 1.0.0
373 * @param array $data The invoice data
374 * @return array The sanitized data
375 */
376 protected function sanitizeInvoiceData(array $data): array {
377 $sanitization_rules = [
378 'title' => 'text_field',
379 'description' => 'textarea',
380 'number' => 'text_field',
381 'issue_date' => 'text_field',
382 'due_date' => 'text_field',
383 'status' => 'text_field',
384 'notes' => 'textarea',
385 'terms' => 'html',
386 'internal_notes' => 'textarea',
387 'payment_instructions' => 'textarea',
388 'payment_gateways' => 'array',
389 'invoice_template' => 'text_field',
390 'customer_name' => 'text_field',
391 'customer_address' => 'textarea',
392 'customer_email' => 'email',
393 'shipping_name' => 'text_field',
394 'shipping_address' => 'textarea',
395 'discount_type' => 'text_field',
396 'discount_value' => 'float',
397 'tax_rate' => 'float',
398 'calculation_method' => 'text_field',
399 'prices_include_tax' => 'int',
400 'currency_code' => 'text_field',
401 'currency_position' => 'text_field',
402 'footer_text' => 'textarea',
403 'items' => 'array',
404 'custom_fields' => 'array'
405 ];
406
407 return $this->sanitizeData($data, $sanitization_rules);
408 }
409 }