PluginProbe ʕ •ᴥ•ʔ
VikAppointments Services Booking Calendar / 1.2.21
VikAppointments Services Booking Calendar v1.2.21
1.2.21 1.2.20 trunk 1.2.17 1.2.18 1.2.19
vikappointments / admin / models / invoice.php
vikappointments / admin / models Last commit date
apiban.php 3 days ago apilog.php 3 days ago apiplugin.php 3 days ago apiuser.php 3 days ago apiuseroptions.php 3 days ago backup.php 3 days ago caldays.php 3 days ago calendar.php 3 days ago city.php 3 days ago closure.php 3 days ago configapp.php 3 days ago configcldays.php 3 days ago configcron.php 3 days ago configemp.php 3 days ago configsmsapi.php 3 days ago configuration.php 3 days ago conversion.php 3 days ago country.php 3 days ago coupon.php 3 days ago couponemployee.php 3 days ago coupongroup.php 3 days ago couponservice.php 3 days ago cronjob.php 3 days ago cronjoblog.php 3 days ago customer.php 3 days ago customf.php 3 days ago customfservice.php 3 days ago customizer.php 3 days ago empgroup.php 3 days ago employee.php 3 days ago empsettings.php 3 days ago file.php 3 days ago findreservation.php 3 days ago group.php 3 days ago import.php 3 days ago index.html 3 days ago invoice.php 3 days ago langcustomf.php 3 days ago langempgroup.php 3 days ago langemployee.php 3 days ago langgroup.php 3 days ago langmedia.php 3 days ago langoption.php 3 days ago langoptiongroup.php 3 days ago langoptionvar.php 3 days ago langpackage.php 3 days ago langpackgroup.php 3 days ago langpayment.php 3 days ago langservice.php 3 days ago langstatuscode.php 3 days ago langsubscr.php 3 days ago langtax.php 3 days ago langtaxrule.php 3 days ago location.php 3 days ago mailtext.php 3 days ago makerecurrence.php 3 days ago media.php 3 days ago multiorder.php 3 days ago option.php 3 days ago optiongroup.php 3 days ago optionvar.php 3 days ago orderstatus.php 3 days ago package.php 3 days ago packageservice.php 3 days ago packgroup.php 3 days ago packorder.php 3 days ago packorderitem.php 3 days ago payment.php 3 days ago rate.php 3 days ago reportsemp.php 3 days ago reportsser.php 3 days ago reservation.php 3 days ago resoptassoc.php 3 days ago restriction.php 3 days ago review.php 3 days ago serempassoc.php 3 days ago seroptassoc.php 3 days ago serrateassoc.php 3 days ago serrestrassoc.php 3 days ago service.php 3 days ago state.php 3 days ago statswidget.php 3 days ago statuscode.php 3 days ago subscription.php 3 days ago subscrorder.php 3 days ago tag.php 3 days ago tax.php 3 days ago taxrule.php 3 days ago updateprogram.php 3 days ago usernote.php 3 days ago waitinglist.php 3 days ago webhook.php 3 days ago worktime.php 3 days ago
invoice.php
605 lines
1 <?php
2 /**
3 * @package VikAppointments
4 * @subpackage core
5 * @author E4J s.r.l.
6 * @copyright Copyright (C) 2021 E4J s.r.l. All Rights Reserved.
7 * @license http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
8 * @link https://vikwp.com
9 */
10
11 // No direct access
12 defined('ABSPATH') or die('No script kiddies please!');
13
14 VAPLoader::import('libraries.mvc.model');
15 VAPLoader::import('libraries.order.factory');
16 VAPLoader::import('libraries.invoice.factory');
17
18 /**
19 * VikAppointments invoice model.
20 *
21 * @since 1.7
22 */
23 class VikAppointmentsModelInvoice extends JModelVAP
24 {
25 /**
26 * Generates the invoices in mass.
27 *
28 * @param mixed $data Either an array or an object of data to save.
29 *
30 * @return array An array of imported rows on success.
31 */
32 public function saveMass($data)
33 {
34 $dbo = JFactory::getDbo();
35
36 $generated = 0;
37 $notified = 0;
38
39 // retrieve all reservation/orders
40 $q = $dbo->getQuery(true);
41
42 // select only the ID
43 $q->select($dbo->qn('id'));
44
45 // load the correct table
46 if ($data['group'] == 'packages')
47 {
48 $q->from($dbo->qn('#__vikappointments_package_order'));
49
50 // get any approved codes
51 $approved = JHtml::fetch('vaphtml.status.find', 'code', array('packages' => 1, 'approved' => 1));
52 }
53 else if ($data['group'] == 'employees')
54 {
55 $q->from($dbo->qn('#__vikappointments_subscr_order'));
56 $q->where($dbo->qn('id_employee') . ' > 0');
57
58 // get any approved codes
59 $approved = JHtml::fetch('vaphtml.status.find', 'code', array('subscriptions' => 1, 'approved' => 1));
60 }
61 else if ($data['group'] == 'subscriptions')
62 {
63 $q->from($dbo->qn('#__vikappointments_subscr_order'));
64 $q->where($dbo->qn('id_user') . ' > 0');
65
66 // get any approved codes
67 $approved = JHtml::fetch('vaphtml.status.find', 'code', array('subscriptions' => 1, 'approved' => 1));
68 }
69 else
70 {
71 $q->from($dbo->qn('#__vikappointments_reservation'));
72
73 // exclude reservations that belong to a parent order
74 $q->where(1)->andWhere([
75 $dbo->qn('id_parent') . ' = ' . $dbo->qn('id'),
76 $dbo->qn('id_parent') . ' = -1',
77 ], 'OR');
78
79 // get any approved codes
80 $approved = JHtml::fetch('vaphtml.status.find', 'code', array('appointments' => 1, 'approved' => 1));
81 }
82
83 if (!empty($data['cid']))
84 {
85 // get specified orders
86 $q->where($dbo->qn('id') . ' IN (' . implode(',', array_map('intval', $data['cid'])) . ')');
87 }
88 else
89 {
90 // create range of dates
91 $start = JFactory::getDate("{$data['year']}-{$data['month']}-1 00:00:00");
92
93 $end = clone $start;
94 $end->modify('+1 month');
95
96 // get orders with creation date in the specified month
97 $q->where($dbo->qn('createdon') . ' >= ' . $dbo->q($start->toSql()));
98 $q->where($dbo->qn('createdon') . ' < ' . $dbo->q($end->toSql()));
99 }
100
101 if ($approved)
102 {
103 // filter by approved status
104 $q->where($dbo->qn('status') . ' IN (' . implode(',', array_map(array($dbo, 'q'), $approved)) . ')');
105 }
106
107 // order by ascending date
108 $q->order($dbo->qn('createdon') . ' ASC');
109
110 $dbo->setQuery($q);
111
112 // generate invoices one by one
113 foreach ($dbo->loadColumn() as $order_id)
114 {
115 // reset ID
116 $data['id'] = 0;
117 // specify order ID
118 $data['id_order'] = $order_id;
119
120 if ($this->save($data))
121 {
122 // update generated count on success
123 $generated++;
124
125 if ($this->isNotified())
126 {
127 // increase notified count in case the invoice was sent to the customer
128 $notified++;
129 }
130 }
131 }
132
133 return array(
134 'generated' => $generated,
135 'notified' => $notified,
136 );
137 }
138
139 /**
140 * Basic save implementation.
141 *
142 * @param mixed $data Either an array or an object of data to save.
143 *
144 * @return mixed The ID of the record on success, false otherwise.
145 */
146 public function save($data)
147 {
148 $dbo = JFactory::getDbo();
149
150 $data = (array) $data;
151
152 if (empty($data['id']))
153 {
154 if (empty($data['id_order']) || !isset($data['group']))
155 {
156 // ID order is mandatory when creating an invoice
157 $this->setError('Missing Order ID');
158
159 return false;
160 }
161
162 // check if there is already an invoice for the given order
163 $q = $dbo->getQuery(true)
164 ->select($dbo->qn('id'))
165 ->from($dbo->qn('#__vikappointments_invoice'))
166 ->where($dbo->qn('id_order') . ' = ' . (int) $data['id_order'])
167 ->where($dbo->qn('group') . ' = ' . $dbo->q($data['group']));
168
169 $dbo->setQuery($q, 0, 1);
170
171 // if order exists, switch to UPDATE
172 $data['id'] = (int) $dbo->loadResult();
173 }
174
175 if (!empty($data['id']) && empty($data['id_order']))
176 {
177 // retrieve order ID of the stored invoice
178 $q = $dbo->getQuery(true)
179 ->select($dbo->qn('id_order'))
180 ->from($dbo->qn('#__vikappointments_invoice'))
181 ->where($dbo->qn('id') . ' = ' . (int) $data['id']);
182
183 $dbo->setQuery($q, 0, 1);
184 $data['id_order'] = (int) $dbo->loadResult();
185
186 if (!$data['id_order'])
187 {
188 // invoice not found, abort
189 $this->setError(sprintf('Invoice [%d] not found', $data['id']));
190
191 return false;
192 }
193 }
194
195 if ($data['id'] && empty($data['overwrite']))
196 {
197 // do not overwrite existing record (error not needed)
198 return false;
199 }
200
201 // generate invoice and obtain resulting data
202 $invoice = $this->generateInvoice($data);
203
204 if (!$invoice)
205 {
206 return false;
207 }
208
209 // inject found data into the array to bind
210 $data = array_merge($data, $invoice);
211
212 // attempt to save the invoice
213 $id = parent::save($data);
214
215 if (!$id)
216 {
217 // an error occurred, do not go ahead
218 return false;
219 }
220
221 // get generator instance
222 $generator = $this->createGenerator($data);
223
224 $this->_notified = false;
225
226 if (!isset($data['notify']))
227 {
228 // rely on the global configuration
229 $data['notify'] = (bool) $generator->getParams()->sendinvoice;
230 }
231
232 if ($data['notify'])
233 {
234 // send e-mail notification
235 $this->_notified = $generator->send($data['path']);
236 }
237
238 return $id;
239 }
240
241 /**
242 * Returns whether the customer has been notified or not.
243 *
244 * @return boolean
245 */
246 public function isNotified()
247 {
248 return !empty($this->_notified);
249 }
250
251 /**
252 * Extend delete implementation to delete any related records
253 * stored within a separated table.
254 *
255 * @param mixed $ids Either the record ID or a list of records.
256 *
257 * @return boolean True on success, false otherwise.
258 */
259 public function delete($ids)
260 {
261 // only int values are accepted
262 $ids = array_map('intval', (array) $ids);
263
264 $dbo = JFactory::getDbo();
265
266 // get all invoice files
267 $q = $dbo->getQuery(true)
268 ->select($dbo->qn(array('file', 'group')))
269 ->from($dbo->qn('#__vikappointments_invoice'))
270 ->where($dbo->qn('id') . ' IN (' . implode(',', $ids) . ')');
271
272 $dbo->setQuery($q);
273 $files = $dbo->loadObjectList();
274
275 if (!$files)
276 {
277 // nothing to delete
278 return false;
279 }
280
281 // invoke parent first
282 if (!parent::delete($ids))
283 {
284 // nothing to delete
285 return false;
286 }
287
288 // delete invoices from file system
289 foreach ($files as $inv)
290 {
291 // create invoice object to obtain the right destination folder
292 $invoice = VAPInvoiceFactory::getInvoice(null, $inv->group);
293 $path = $invoice->getInvoiceFolderPath() . DIRECTORY_SEPARATOR . $inv->file;
294
295 // delete file only if exists
296 if (is_file($path))
297 {
298 unlink($path);
299 }
300 }
301
302 return true;
303 }
304
305 /**
306 * Helper method used to generate an invoice.
307 *
308 * @param mixed $data Either an array or an object of data helpful
309 * for the creation of the invoice.
310 *
311 * @return mixed The invoice path on success, false otherwise.
312 */
313 public function generateInvoice($data)
314 {
315 $data = (array) $data;
316
317 try
318 {
319 // load order details according to the specified group
320 if ($data['group'] == 'packages')
321 {
322 $order = VAPOrderFactory::getPackages($data['id_order']);
323 }
324 else if ($data['group'] == 'employees')
325 {
326 $order = VAPOrderFactory::getEmployeeSubscription($data['id_order']);
327 }
328 else if ($data['group'] == 'subscriptions')
329 {
330 $order = VAPOrderFactory::getCustomerSubscription($data['id_order']);
331 }
332 else
333 {
334 // fallback to default appointments
335 $order = VAPOrderFactory::getAppointments($data['id_order']);
336 }
337 }
338 catch (Exception $e)
339 {
340 // probably order not found
341 $this->setError($e);
342
343 return false;
344 }
345
346 // get invoices generator
347 $generator = $this->createGenerator($data);
348
349 try
350 {
351 // load invoice data
352 $invoice = VAPInvoiceFactory::getInvoice($order, $data['group']);
353 }
354 catch (Exception $e)
355 {
356 // an error occurred, register it and abort
357 $this->setError($e);
358
359 return false;
360 }
361
362 // attach invoice to generator
363 $generator->setInvoice($invoice);
364
365 if (isset($data['increase']))
366 {
367 // increase only if specified
368 $increaseNumber = (bool) $data['increase'];
369 }
370 else
371 {
372 // increase invoice number only on insert
373 $increaseNumber = empty($data['id']);
374 }
375
376 try
377 {
378 // try to generate the invoice and return the resulting path
379 $path = $generator->generate($increaseNumber);
380 }
381 catch (Exception $e)
382 {
383 // an error occurred, register it and abort
384 $this->setError($e);
385
386 return false;
387 }
388
389 return $path;
390 }
391
392 /**
393 * Creates the invoices generator by passing the specified
394 * parameters and constraints. Notice that the given data
395 * will be injected within the generator only once.
396 *
397 * In order to force the parameters, it will be needed to
398 * manually chain setParams() after getting the generator
399 * instance.
400 *
401 * @param mixed $data Either an object or an array.
402 *
403 * @return VAPInvoiceGenerator
404 */
405 public function createGenerator($data)
406 {
407 if (!isset($this->_invoiceGenerator))
408 {
409 $data = (array) $data;
410
411 // create invoice generator only once
412 $this->_invoiceGenerator = VAPInvoiceFactory::getGenerator();
413
414 if (!empty($data['params']))
415 {
416 // inject passed parameters
417 $this->_invoiceGenerator->setParams($data['params']);
418 }
419
420 if (!empty($data['constraints']))
421 {
422 // inject passed constraints
423 $this->_invoiceGenerator->setConstraints($data['constraints']);
424 }
425 }
426
427 return $this->_invoiceGenerator;
428 }
429
430 /**
431 * Method to download one or more invoices.
432 *
433 * @param mixed $ids Either the record ID or a list of records.
434 *
435 * @return mixed The path of the file to download (either a PDF or a ZIP).
436 * Returns false in case of errors.
437 */
438 public function download($ids)
439 {
440 $ids = (array) $ids;
441
442 $dbo = JFactory::getDbo();
443
444 if (!$ids)
445 {
446 // nothing to search
447 return false;
448 }
449
450 // get all invoice files
451 $q = $dbo->getQuery(true)
452 ->select($dbo->qn(array('file', 'group')))
453 ->from($dbo->qn('#__vikappointments_invoice'))
454 ->where($dbo->qn('id') . ' IN (' . implode(',', array_map('intval', $ids)) . ')');
455
456 $dbo->setQuery($q);
457 $files = $dbo->loadObjectList();
458
459 if (!$files)
460 {
461 // abort, nothing else to do here
462 return false;
463 }
464
465 if (count($files) == 1)
466 {
467 // create invoice object to obtain the right destination folder
468 $invoice = VAPInvoiceFactory::getInvoice(null, $files[0]->group);
469 $path = $invoice->getInvoiceFolderPath() . DIRECTORY_SEPARATOR . $files[0]->file;
470
471 if (!is_file($path))
472 {
473 // file not found, raise error
474 $this->setError(JText::translate('JGLOBAL_NO_MATCHING_RESULTS'));
475 return false;
476 }
477
478 // only one record, return the base path of the file to download
479 return $path;
480 }
481
482 // create a package to download multiple files at once
483 if (!class_exists('ZipArchive'))
484 {
485 // ZipArchive class is mandatory to create a package
486 $this->setError('The ZipArchive class is not installed on your server.');
487 return false;
488 }
489
490 $name = JHtml::fetch('date', 'now', 'Y-m-d H_i_s');
491 $zipname = VAPINVOICE . DIRECTORY_SEPARATOR . 'invoices-' . $name . '.zip';
492
493 // init package
494 $zip = new ZipArchive;
495 $zip->open($zipname, ZipArchive::CREATE);
496
497 // add files to the package
498 foreach ($files as $inv)
499 {
500 // create invoice object to obtain the right destination folder
501 $invoice = VAPInvoiceFactory::getInvoice(null, $inv->group);
502 $path = $invoice->getInvoiceFolderPath() . DIRECTORY_SEPARATOR . $inv->file;
503
504 // make sure the file exists before adding it
505 if (is_file($path))
506 {
507 $zip->addFile($path, basename($path));
508 }
509 }
510
511 // compress the package
512 $zip->close();
513
514 // return the path of the archive
515 return $zipname;
516 }
517
518 /**
519 * Returns the invoice details of the given order.
520 *
521 * @param integer $id The order ID.
522 * @param string $group The group to which the order belongs.
523 *
524 * @return mixed The invoice details on success, false otherwise.
525 */
526 public function getInvoice($id, $group = null)
527 {
528 if (!$group)
529 {
530 // group not specified, use the most common one
531 $group = 'appointments';
532 }
533
534 // prepare conditions
535 $where = array(
536 'id_order' => (int) $id,
537 'group' => $group,
538 );
539
540 // load invoice
541 $invoice = $this->getItem($where);
542
543 if (!$invoice)
544 {
545 // invoice not found
546 return false;
547 }
548
549 // create invoice instance
550 $instance = VAPInvoiceFactory::getInvoice(null, $group);
551
552 // set invoice path and URI
553 $invoice->path = $instance->getInvoiceFolderPath() . DIRECTORY_SEPARATOR . $invoice->file;
554 $invoice->uri = $instance->getInvoiceFolderURI() . $invoice->file;
555
556 if (!is_file($invoice->path))
557 {
558 // the invoice was created but the file is missing...
559 return false;
560 }
561
562 return $invoice;
563 }
564
565 /**
566 * Returns the invoice details of the given order.
567 * Helper method to retrieve those invoices that have
568 * been generated before the 1.7 version.
569 *
570 * @param integer $id The order ID.
571 * @param string $sid The order serial ID.
572 * @param string $group The group to which the order belongs.
573 *
574 * @return mixed The invoice details on success, false otherwise.
575 */
576 public function getInvoiceBC($id, $sid, $group = null)
577 {
578 if (!$group)
579 {
580 // group not specified, use the most common one
581 $group = 'appointments';
582 }
583
584 // create invoice instance
585 $instance = VAPInvoiceFactory::getInvoice(null, $group);
586
587 // build file name
588 $file = $id . '-' . $sid . '.pdf';
589
590 $invoice = new stdClass;
591
592 // set invoice path and URI
593 $invoice->path = $instance->getInvoiceFolderPath() . DIRECTORY_SEPARATOR . $file;
594 $invoice->uri = $instance->getInvoiceFolderURI() . $file;
595
596 if (!is_file($invoice->path))
597 {
598 // missing file
599 return false;
600 }
601
602 return $invoice;
603 }
604 }
605