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