Test Setup Failed
Push — master ( 782689...30368a )
by JAIME ELMER
02:52
created

DataMap::getPendingAmount()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * MÓDULO DE EMISIÓN ELECTRÓNICA F72X
5
 * UBL 2.1
6
 * Version 1.0
7
 *
8
 * Copyright 2019, Jaime Cruz
9
 */
10
11
namespace F72X\Sunat;
12
13
use DateTime;
14
use InvalidArgumentException;
15
use F72X\Company;
16
use F72X\Sunat\Catalogo;
17
use F72X\Sunat\DocumentGenerator;
18
19
/**
20
 * DataMap
21
 *
22
 * Esta clase es una representación interna de una factura o boleta, la cual se
23
 * encarga de aplicar toda la logica de negocio en cuanto a cálculos de tributos
24
 * y totales
25
 *
26
 */
27
class DataMap
28
{
29
30
    private $_rawData;
31
    private $documentType;
32
    private $currencyCode;
33
    private $documentId;
34
    private $officialDocumentName;
35
    private $documentSeries;
36
    private $documentNumber;
37
38
    /** @var DateTime */
39
    private $issueDate;
40
41
    /** @var DateTime */
42
    private $dueDate;
43
    private $operationType;
44
    private $customerDocType;
45
    private $customerDocNumber;
46
    private $customerRegName;
47
    private $customerAddress;
48
    private $purchaseOrder;
49
50
    private $formOfPayment;
51
    private $installments = [];
52
    private $pendingAmount = 0;
53
54
    /** @var InvoiceItems */
55
    private $_items;
56
    private $_rawItems;
57
    private $allowancesAndCharges = [];
58
59
    /** NOTES FIELDS */
60
    private $note;
61
    private $noteType;
62
    private $discrepancyResponseReason;
63
    private $noteAffectedDocType;
64
    private $noteAffectedDocId;
65
    /**
66
     *
67
     * @param array $data
68
     * @param string $type 01|03|07|08 The document type
69
     */
70
    public function __construct(array $data, $type)
71
    {
72
        // Type validation
73
        if (!in_array($type, ['01', '03', '07', '08'])) {
74
            throw new InvalidArgumentException("DataMap Error, el tipo de documento '$type', no es válido use 01|03|07|08");
75
        }
76
77
        // Items
78
        $items = new InvoiceItems();
79
        $items->populate($data['items'], $data['currencyCode']);
80
81
        $this->_rawData  = $data;
82
        $this->_rawItems = $data['items'];
83
        $this->setDefaults($data);
84
        $this->currencyCode         = $data['currencyCode'];
85
        $this->documentType         = $type;
86
        $this->documentSeries       = DocumentGenerator::buildDocumentSeries($type, $data['affectedDocType'], $data['documentSeries']);
87
        $this->documentNumber       = str_pad($data['documentNumber'], 8, '0', STR_PAD_LEFT);
88
        $this->officialDocumentName = Catalogo::getOfficialDocumentName($type);
89
        $this->documentId           = $this->documentSeries . '-' . $this->documentNumber;
90
        $this->issueDate            = new DateTime($data['issueDate']);
91
        if (isset($data['dueDate'])) {
92
            $this->dueDate            = new DateTime($data['dueDate']);
93
        }
94
        $this->customerDocType      = $data['customerDocType'];
95
        $this->customerDocNumber    = $data['customerDocNumber'];
96
        $this->customerRegName      = mb_strtoupper($data['customerRegName']);
97
        $this->customerAddress      = mb_strtoupper($data['customerAddress']);
98
        $this->_items               = $items;
99
        $this->allowancesAndCharges = $data['allowancesCharges'];
100
        // Facturas y notas de crédito deben establecer explicitamnte la forma de pago.
101
        if ($type == Catalogo::DOCTYPE_FACTURA || $type == Catalogo::DOCTYPE_NOTA_CREDITO) {
102
            $this->setFormOfPaymentField($data);
103
        }
104
        // Note
105
        $this->note = $data['note'];
106
        $this->setSpecificFields($data, $type);
107
    }
108
    private function setFormOfPaymentField(&$data)
109
    {
110
        if (!isset($data['payment'])) {
111
            throw new InvalidArgumentException("El campo 'payment', es obligatorio para facturas y notas de crédito");
112
        }
113
        $payment = $data['payment'];
114
        if (!isset($payment['formOfPayment'])) {
115
            throw new InvalidArgumentException("El campo 'formOfPayment', es obligatorio para facturas y notas de crédito");
116
        }
117
        $formOfPayment = $payment['formOfPayment'];
118
        if (!in_array($formOfPayment, Catalogo::$FAC_FORMS_OF_PAYMENT)) {
119
            throw new InvalidArgumentException("El campo 'formOfPayment', no es válido use " . implode(', ', Catalogo::$FAC_FORMS_OF_PAYMENT));
120
        }
121
        $this->formOfPayment = $formOfPayment;
122
        // Caso crédito
123
        if ($formOfPayment == Catalogo::FAC_FORM_OF_PAYMENT_CREDITO) {
124
            if (!isset($payment['pendingAmount']) || is_nan($payment['pendingAmount']) || $payment['pendingAmount'] <= 0) {
125
                throw new InvalidArgumentException("El campo 'pendingAmount', es obligatorio y debe ser mayor que cero para facturas y notas de crédito con forma de pago '" . Catalogo::FAC_FORM_OF_PAYMENT_CREDITO . "'");
126
            }
127
            if (!isset($payment['installments']) || !is_array($payment['installments']) || count($payment['installments']) == 0) {
128
                throw new InvalidArgumentException("El campo 'installments', es obligatorio para facturas y notas de crédito con forma de pago '" . Catalogo::FAC_FORM_OF_PAYMENT_CREDITO . "'");
129
            }
130
            $this->pendingAmount = $payment['pendingAmount'];
131
            $this->installments = $this->parseInstallments($payment['installments']);
132
        }
133
    }
134
    private function parseInstallments($data)
135
    {
136
        $out = [];
137
        foreach ($data as $key => $item) {
138
            $inst = new CreditInstallment();
139
            $id = "Cuota" . str_pad($key + 1, 3, '0', STR_PAD_LEFT);
140
            $inst
141
                ->setId($id)
142
                ->setAmount($item['amount'])
143
                ->setPaymentDueDate(new DateTime($item['paymentDueDate']));
144
            $out[] = $inst;
145
        }
146
        return $out;
147
    }
148
149
    private function setSpecificFields(array $data, $type)
150
    {
151
        if (in_array($type, [Catalogo::DOCTYPE_FACTURA, Catalogo::DOCTYPE_BOLETA])) {
152
            $this->operationType = $data['operationType'];
153
            $this->purchaseOrder = $data['purchaseOrder'];
154
        } else {
155
            // Catálogo 9 tipo de nota de crédito, 10 tipo de nota de débito
156
            $catNumber = ($type == Catalogo::DOCTYPE_NOTA_CREDITO ? 9 : 10);
157
            $this->discrepancyResponseReason = Catalogo::getCatItemValue($catNumber, $data['noteType']);
158
            $this->noteType            = $data['noteType'];
159
            $this->noteAffectedDocType = $data['affectedDocType'];
160
            $this->noteAffectedDocId   = $data['affectedDocId'];
161
        }
162
    }
163
164
    private function setDefaults(array &$data)
165
    {
166
        $data['allowancesCharges'] = isset($data['allowancesCharges']) ? $data['allowancesCharges'] : [];
167
        $data['purchaseOrder'] = isset($data['purchaseOrder']) ? $data['purchaseOrder'] : null;
168
        $data['affectedDocType'] = isset($data['affectedDocType']) ? $data['affectedDocType'] : null;
169
        $data['note'] = isset($data['note']) ? $data['note'] : '';
170
    }
171
    public function getNoteType()
172
    {
173
        return $this->noteType;
174
    }
175
176
    public function getDiscrepancyResponseReason()
177
    {
178
        return $this->discrepancyResponseReason;
179
    }
180
181
    public function getNoteDescription()
182
    {
183
        return $this->note;
184
    }
185
    public function getNote()
186
    {
187
        return $this->note;
188
    }
189
    public function getNoteAffectedDocType()
190
    {
191
        return $this->noteAffectedDocType;
192
    }
193
194
    public function getNoteAffectedDocId()
195
    {
196
        return $this->noteAffectedDocId;
197
    }
198
199
    public function getDocumentId()
200
    {
201
        return $this->documentId;
202
    }
203
204
    public function getRawData()
205
    {
206
        return $this->_rawData;
207
    }
208
209
    /**
210
     * Boleta o Factura @CAT1
211
     * @return string
212
     */
213
    public function getDocumentType()
214
    {
215
        return $this->documentType;
216
    }
217
218
    public function getCurrencyCode()
219
    {
220
        return $this->currencyCode;
221
    }
222
223
    public function getOfficialDocumentName()
224
    {
225
        return $this->officialDocumentName;
226
    }
227
228
    public function getDocumentSeries()
229
    {
230
        return $this->documentSeries;
231
    }
232
233
    public function setDocumentSeries($documentSeries)
234
    {
235
        $this->documentSeries = $documentSeries;
236
        return $this;
237
    }
238
239
    public function getDocumentNumber()
240
    {
241
        return $this->documentNumber;
242
    }
243
244
    public function setDocumentNumber($documentNumber)
245
    {
246
        $this->documentNumber = $documentNumber;
247
        return $this;
248
    }
249
250
    public function getIssueDate()
251
    {
252
        return $this->issueDate;
253
    }
254
255
    public function setIssueDate(DateTime $IssueDate)
256
    {
257
        $this->issueDate = $IssueDate;
258
        return $this;
259
    }
260
261
    public function getDueDate()
262
    {
263
        return $this->dueDate;
264
    }
265
266
    public function setDueDate(DateTime $DueDate)
267
    {
268
        $this->dueDate = $DueDate;
269
        return $this;
270
    }
271
272
    public function getOperationType()
273
    {
274
        return $this->operationType;
275
    }
276
277
    public function setOperationType($operationType)
278
    {
279
        $this->operationType = $operationType;
280
        return $this;
281
    }
282
283
    public function getCustomerDocType()
284
    {
285
        return $this->customerDocType;
286
    }
287
288
    public function setCustomerDocType($customerDocType)
289
    {
290
        $this->customerDocType = $customerDocType;
291
        return $this;
292
    }
293
294
    public function getCustomerDocNumber()
295
    {
296
        return $this->customerDocNumber;
297
    }
298
299
    public function setCustomerDocNumber($customerDocNumber)
300
    {
301
        $this->customerDocNumber = $customerDocNumber;
302
        return $this;
303
    }
304
305
    public function getCustomerRegName()
306
    {
307
        return $this->customerRegName;
308
    }
309
310
    public function setCustomerRegName($customerRegName)
311
    {
312
        $this->customerRegName = $customerRegName;
313
        return $this;
314
    }
315
    public function getCustomerAddress()
316
    {
317
        return $this->customerAddress;
318
    }
319
320
    public function setCustomerAddress($customerAddress)
321
    {
322
        $this->customerAddress = $customerAddress;
323
        return $this;
324
    }
325
326
    public function getPurchaseOrder()
327
    {
328
        return $this->purchaseOrder;
329
    }
330
331
    public function setPurchaseOrder($purchaseOrder)
332
    {
333
        $this->purchaseOrder = $purchaseOrder;
334
        return $this;
335
    }
336
337
    public function getFormOfPayment()
338
    {
339
        return $this->formOfPayment;
340
    }
341
342
    /**
343
     *
344
     * @return InvoiceItems
345
     */
346
    public function getItems()
347
    {
348
        return $this->_items;
349
    }
350
    /**
351
     *
352
     * @return CreditInstallment[]
353
     */
354
    public function getInstallments()
355
    {
356
        return $this->installments;
357
    }
358
    public function getPendingAmount()
359
    {
360
        return $this->pendingAmount;
361
    }
362
    /**
363
     * Numero de items del documento
364
     * @return int
365
     */
366
    public function getTotalItems()
367
    {
368
        return $this->_items->getCount();
369
    }
370
371
    public function getRawItems()
372
    {
373
        return $this->_rawItems;
374
    }
375
376
    public function getAllowancesAndCharges()
377
    {
378
        return $this->allowancesAndCharges;
379
    }
380
381
    public function setAllowancesAndCharges($allowancesAndCharges)
382
    {
383
        $this->allowancesAndCharges = $allowancesAndCharges;
384
        return $this;
385
    }
386
387
    /**
388
     * Monto facturable (Valor de venta)
389
     *
390
     * Formula = SUM(BASE_FACTURABLE_X_ITEM)
391
     *
392
     * @return float
393
     */
394
    public function getBillableAmount()
395
    {
396
        return $this->_items->getTotalBillableAmount();
397
    }
398
399
    /**
400
     * Base imponible
401
     *
402
     * Formula = SUM(BASE_IMPONIBLE_X_ITEM) - DESCUENTOS_GLOBALES + CARGOS_GLOBALES
403
     *
404
     * @return float
405
     */
406
    public function getTaxableAmount()
407
    {
408
        $totalItems = $this->_items->getTotalTaxableOperations();
409
        return $this->applyAllowancesAndCharges($totalItems);
410
    }
411
412
    /**
413
     * Monto con impuestos
414
     *
415
     * Formula = BASE_IMPONIBLE + IGV
416
     *
417
     * @return float
418
     */
419
    public function getTaxedAmount()
420
    {
421
        $totalTaxableAmount = $this->getTaxableAmount();
422
        $totalIgv = $this->getIGV();
423
        return $totalTaxableAmount + $totalIgv;
424
    }
425
426
    /**
427
     * Total operaciones gravadas
428
     * @return float
429
     */
430
    public function getTotalTaxableOperations()
431
    {
432
        return $this->getTaxableAmount();
433
    }
434
435
    /**
436
     * Total operaciones gratuitas
437
     * @return float
438
     */
439
    public function getTotalFreeOperations()
440
    {
441
        return $this->_items->getTotalFreeOperations();
442
    }
443
444
    /**
445
     * Total operationes exoneradas
446
     *
447
     * Formula = SUM(EXEMPTED_OPERATIONS_X_ITEM) - DESCUENTOS_GLOBALES + CARGOS_GLOBALES
448
     *
449
     * @return float
450
     */
451
    public function getTotalExemptedOperations()
452
    {
453
        $totalItems = $this->_items->getTotalExemptedOperations();
454
        return $this->applyAllowancesAndCharges($totalItems);
455
    }
456
457
    /**
458
     * Total operaciones inafectas
459
     * @return float
460
     */
461
    public function getTotalUnaffectedOperations()
462
    {
463
        $totalItems = $this->_items->getTotalUnaffectedOperations();
464
        return $this->applyAllowancesAndCharges($totalItems);
465
    }
466
467
    /**
468
     * Valor de venta
469
     *
470
     * Valor total de la factura sin considerar descuentos impuestos u otros tributos
471
     * @return float
472
     */
473
    public function getBillableValue()
474
    {
475
        return $this->_items->getTotalBillableValue();
476
    }
477
478
    /**
479
     * Total descuentos
480
     *
481
     * Formula: SUM(DESCUENTOS_X_ITEM) + DESCUENTOS_GLOBALES
482
     * @return float
483
     */
484
    public function getTotalAllowances()
485
    {
486
        $totalItems = $this->_items->getTotalAllowances();
487
        $totalBillableAmount = $this->getBillableAmount();
488
        $totalGlobal = Operations::getTotalAllowances($totalBillableAmount, $this->allowancesAndCharges);
489
        return $totalItems + $totalGlobal;
490
    }
491
492
    /**
493
     * Total cargos
494
     *
495
     * Formula: SUM(CARGOS_X_ITEM) + CARGOS_GLOBALES
496
     * @return float
497
     */
498
    public function getTotalCharges()
499
    {
500
        $totalItems = $this->_items->getTotalCharges();
501
        $totalBillableAmount = $this->getBillableAmount();
502
        $totalGlobal = Operations::getTotalCharges($totalBillableAmount, $this->allowancesAndCharges);
503
        return $totalItems + $totalGlobal;
504
    }
505
506
    /**
507
     * Total a pagar
508
     *
509
     * El importe que el usuario está obligado a pagar
510
     *
511
     * Formula = OPERACIONES_GRAVADAS + IGV + OPERACIONES_EXONERADAS + OPERACIONES_INAFECTAS
512
     *
513
     * @return float
514
     */
515
    public function getPayableAmount()
516
    {
517
        // Totals
518
        $totalTaxableOperations = $this->getTotalTaxableOperations();
519
        $totalIGV = $this->getIGV();
520
        $totalExemptedOperations = $this->getTotalExemptedOperations();
521
        return $totalTaxableOperations + $totalIGV + $totalExemptedOperations;
522
    }
523
524
    /**
525
     *
526
     * Total impuestos
527
     *
528
     * Fórmula: IGV + ISC + IVAP
529
     *
530
     * @return float
531
     */
532
    public function getTotalTaxes()
533
    {
534
        $IGV = $this->getIGV();
535
        $ISC = $this->getISC();
536
        $IVAP = $this->getIVAP();
537
        return $IGV + $ISC + $IVAP;
538
    }
539
540
    /**
541
     * IGV
542
     * @return float
543
     */
544
    public function getIGV()
545
    {
546
        $baseAmount = $this->getTaxableAmount();
547
        return Operations::calcIGV($baseAmount);
548
    }
549
550
    /**
551
     * ISC
552
     * @IMP
553
     * @return float
554
     */
555
    public function getISC()
556
    {
557
        return Operations::calcISC();
558
    }
559
560
    /**
561
     * IVAP
562
     * @IMP
563
     * @return float
564
     */
565
    public function getIVAP()
566
    {
567
        return Operations::calcIVAP();
568
    }
569
570
    /**
571
     *
572
     * @param float $amount
573
     * @return float
574
     */
575
    private function applyAllowancesAndCharges($amount)
576
    {
577
        return Operations::applyAllowancesAndCharges($amount, $this->allowancesAndCharges);
578
    }
579
580
    public function getDocumentName()
581
    {
582
        return Company::getRUC() . '-' . $this->documentType . '-' . $this->documentId;
583
    }
584
}
585