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\Document;
|
12
|
|
|
|
13
|
|
|
use DateTime;
|
14
|
|
|
use F72X\F72X;
|
15
|
|
|
use F72X\Company;
|
16
|
|
|
use F72X\Repository;
|
17
|
|
|
use F72X\Object\DocumentIssuer;
|
18
|
|
|
use F72X\Tools\TplRenderer;
|
19
|
|
|
use F72X\Tools\XmlDSig;
|
20
|
|
|
|
21
|
|
|
/**
|
22
|
|
|
* AbstractDocument
|
23
|
|
|
*
|
24
|
|
|
* This is base for all kind of documents
|
25
|
|
|
*/
|
26
|
|
|
abstract class AbstractDocument implements DocumentInterface {
|
27
|
|
|
|
28
|
|
|
/**
|
29
|
|
|
* The document's signature id.
|
30
|
|
|
*
|
31
|
|
|
* By now this must be 'SignIMM' as in:
|
32
|
|
|
* {@see \Greenter\XMLSecLibs\XMLSecurityDSig} line 70
|
33
|
|
|
*
|
34
|
|
|
*/
|
35
|
|
|
const SIGNATURE_ID = 'SignIMM';
|
36
|
|
|
|
37
|
|
|
/**
|
38
|
|
|
* The Peruvian currency code
|
39
|
|
|
*/
|
40
|
|
|
const LOCAL_CURRENCY_CODE = 'PEN';
|
41
|
|
|
|
42
|
|
|
/**
|
43
|
|
|
* The document's id.
|
44
|
|
|
*
|
45
|
|
|
* @var string
|
46
|
|
|
*/
|
47
|
|
|
protected $id;
|
48
|
|
|
|
49
|
|
|
/**
|
50
|
|
|
* By default all the documents use series, however it's false in case of
|
51
|
|
|
* Resumenes y Comunicaciones de baja.
|
52
|
|
|
* @var type
|
|
|
|
|
53
|
|
|
*/
|
54
|
|
|
protected $usesSeries = true;
|
55
|
|
|
|
56
|
|
|
/**
|
57
|
|
|
* The document's series.
|
58
|
|
|
*
|
59
|
|
|
* @var string
|
60
|
|
|
*/
|
61
|
|
|
protected $series;
|
62
|
|
|
|
63
|
|
|
/**
|
64
|
|
|
* The document's series prefix
|
65
|
|
|
*
|
66
|
|
|
* Use:
|
67
|
|
|
* F: Factura
|
68
|
|
|
* B: Boleta
|
69
|
|
|
* P: Percepción
|
70
|
|
|
* R: Retención
|
71
|
|
|
*
|
72
|
|
|
* @var string
|
73
|
|
|
*/
|
74
|
|
|
protected $seriesPrefix;
|
75
|
|
|
|
76
|
|
|
/**
|
77
|
|
|
* The document's number.
|
78
|
|
|
*
|
79
|
|
|
* @var string
|
80
|
|
|
*/
|
81
|
|
|
protected $number;
|
82
|
|
|
|
83
|
|
|
/**
|
84
|
|
|
* The document's number max length
|
85
|
|
|
*
|
86
|
|
|
* Default: 8 for Boletas, Facturas, Nota de crédito, Nota de débito,
|
87
|
|
|
* Percepción y Retención.
|
88
|
|
|
*
|
89
|
|
|
* Use: 5 for Resumen diario y comunicacón de baja.
|
90
|
|
|
*
|
91
|
|
|
* @var int
|
92
|
|
|
*/
|
93
|
|
|
protected $numberMaxLength = 8;
|
94
|
|
|
|
95
|
|
|
/**
|
96
|
|
|
* The date when the document is generated.
|
97
|
|
|
*
|
98
|
|
|
* @var DateTime
|
99
|
|
|
*/
|
100
|
|
|
protected $issueDate;
|
101
|
|
|
|
102
|
|
|
/**
|
103
|
|
|
* Saves the legal validity of the document
|
104
|
|
|
*
|
105
|
|
|
* @var DateTime
|
106
|
|
|
*/
|
107
|
|
|
protected $legalValidity;
|
108
|
|
|
|
109
|
|
|
/**
|
110
|
|
|
* The document's filename.
|
111
|
|
|
*
|
112
|
|
|
* @var string
|
113
|
|
|
*/
|
114
|
|
|
protected $fileName;
|
115
|
|
|
|
116
|
|
|
/**
|
117
|
|
|
* The entity that produces the document usually the company.
|
118
|
|
|
*
|
119
|
|
|
* @var DocumentIssuer
|
120
|
|
|
*/
|
121
|
|
|
protected $issuer;
|
122
|
|
|
|
123
|
|
|
/**
|
124
|
|
|
* The document's lines.
|
125
|
|
|
*
|
126
|
|
|
* @var array
|
127
|
|
|
*/
|
128
|
|
|
protected $lines;
|
129
|
|
|
|
130
|
|
|
/**
|
131
|
|
|
* Use this to set the default fields for the document lines.
|
132
|
|
|
* @var array
|
133
|
|
|
*/
|
134
|
|
|
protected $lineDefaults = [];
|
135
|
|
|
|
136
|
|
|
/**
|
137
|
|
|
* Use this to indicate the template that the document is going to use in
|
138
|
|
|
* the XML file creation.
|
139
|
|
|
*
|
140
|
|
|
* @var string
|
141
|
|
|
*/
|
142
|
|
|
protected $xmlTplName;
|
143
|
|
|
|
144
|
|
|
/**
|
145
|
|
|
* Saves the received data by the constructor.
|
146
|
|
|
*
|
147
|
|
|
* @var array
|
148
|
|
|
*/
|
149
|
|
|
private $rawData;
|
150
|
|
|
|
151
|
|
|
/**
|
152
|
|
|
* Saves the parsed data that is returned by the *parseInput* method.
|
153
|
|
|
* @var array
|
154
|
|
|
*/
|
155
|
|
|
private $parsedData;
|
156
|
|
|
|
157
|
|
|
/**
|
158
|
|
|
* Creates the document
|
159
|
|
|
*
|
160
|
|
|
* @param array $inputData
|
161
|
|
|
*/
|
162
|
|
|
public function __construct(array $inputData) {
|
163
|
|
|
// Input validation
|
164
|
|
|
$this->validateInput($inputData);
|
165
|
|
|
// Input process
|
166
|
|
|
$this->processInput($inputData);
|
167
|
|
|
// Set Number
|
168
|
|
|
$this->setNumber();
|
169
|
|
|
// Set Series
|
170
|
|
|
if ($this->usesSeries) {
|
171
|
|
|
$this->setSeries();
|
172
|
|
|
}
|
173
|
|
|
|
174
|
|
|
// Set Common fields
|
175
|
|
|
$this->setCommonFields();
|
176
|
|
|
// Create an issuer instance
|
177
|
|
|
$this->issuer = new DocumentIssuer();
|
178
|
|
|
// Sets the company fields such as (RUC, Razón Social, etc.)
|
179
|
|
|
$this->setIssuerFields();
|
180
|
|
|
// Sets the fields related to the tipe of document.
|
181
|
|
|
$this->setBodyFields();
|
182
|
|
|
/**
|
183
|
|
|
* Set Document ID This is last because some
|
184
|
|
|
* fields depend on issuer and body fields.
|
185
|
|
|
*/
|
186
|
|
|
$this->setId();
|
187
|
|
|
// Set Filename
|
188
|
|
|
$this->setFileName();
|
189
|
|
|
}
|
190
|
|
|
|
191
|
|
|
protected function setNumber() {
|
192
|
|
|
$this->number = str_pad($this->parsedData['documentNumber'], $this->numberMaxLength, '0', STR_PAD_LEFT);
|
193
|
|
|
return $this;
|
194
|
|
|
}
|
195
|
|
|
|
196
|
|
|
protected function setSeries() {
|
197
|
|
|
$this->series = $this->seriesPrefix . $this->parsedData['documentSeries'];
|
198
|
|
|
return $this;
|
199
|
|
|
}
|
200
|
|
|
|
201
|
|
|
/**
|
202
|
|
|
* Sets the document's id
|
203
|
|
|
*
|
204
|
|
|
* This can be overridden to create a new id format
|
205
|
|
|
* Default SERIES-NUMBER
|
206
|
|
|
*/
|
207
|
|
|
protected function setId() {
|
208
|
|
|
$this->id = $this->series . '-' . $this->number;
|
209
|
|
|
}
|
210
|
|
|
|
211
|
|
|
private function setCommonFields() {
|
212
|
|
|
$this->issueDate = $this->parsedData['issueDate'];
|
213
|
|
|
$this->legalValidity = $this->parsedData['legalValidity'];
|
214
|
|
|
}
|
215
|
|
|
|
216
|
|
|
protected function setFileName() {
|
217
|
|
|
// The file name
|
218
|
|
|
$this->fileName = $this->issuer->getIdDocNumber() . '-' . $this->id;
|
219
|
|
|
}
|
220
|
|
|
|
221
|
|
|
private function processInput(array $in) {
|
222
|
|
|
$this->rawData = $in;
|
223
|
|
|
$out1 = $this->parseCommonFields($in);
|
224
|
|
|
$out2 = $this->parseInput($out1);
|
225
|
|
|
$this->parsedData = $out2;
|
226
|
|
|
}
|
227
|
|
|
|
228
|
|
|
private function parseCommonFields(array $in) {
|
229
|
|
|
$out = $in;
|
230
|
|
|
$out['issueDate'] = new DateTime($in['issueDate']);
|
231
|
|
|
return $out;
|
232
|
|
|
}
|
233
|
|
|
|
234
|
|
|
/**
|
235
|
|
|
* Parses the raw input data
|
236
|
|
|
* @param array $in The input data
|
237
|
|
|
* @return array The parsed data
|
238
|
|
|
*/
|
239
|
|
|
abstract protected function parseInput(array $in);
|
240
|
|
|
|
241
|
|
|
public function parseInputLines(array $rawLines) {
|
242
|
|
|
$parsedLines = [];
|
243
|
|
|
foreach ($rawLines as $line) {
|
244
|
|
|
$line = array_merge($this->lineDefaults, $line);
|
245
|
|
|
$parsedLines[] = $this->parseInputLine($line);
|
246
|
|
|
}
|
247
|
|
|
return $parsedLines;
|
248
|
|
|
}
|
249
|
|
|
|
250
|
|
|
/**
|
251
|
|
|
*
|
252
|
|
|
* @param array $in The raw input line
|
253
|
|
|
* @return array
|
254
|
|
|
*/
|
255
|
|
|
public function parseInputLine(array $in) {
|
256
|
|
|
return $in;
|
257
|
|
|
}
|
258
|
|
|
|
259
|
|
|
public function setIssuerFields() {
|
260
|
|
|
$this->issuer->setIdDocNumber(Company::getRUC());
|
261
|
|
|
$this->issuer->setRegName(Company::getCompanyName());
|
262
|
|
|
}
|
263
|
|
|
|
264
|
|
|
/**
|
265
|
|
|
* Returns the base fields for an XML file rendering.
|
266
|
|
|
* @return array
|
267
|
|
|
*/
|
268
|
|
|
protected function getBaseFieldsForXml() {
|
269
|
|
|
$issuer = $this->issuer;
|
270
|
|
|
return [
|
271
|
|
|
'id' => $this->id,
|
272
|
|
|
'issueDate' => $this->issueDate->format('Y-m-d'),
|
273
|
|
|
'issuer' => [
|
274
|
|
|
'idDocType' => $issuer->getIdDocType(),
|
275
|
|
|
'idDocNumber' => $issuer->getIdDocNumber(),
|
276
|
|
|
'regName' => $issuer->getRegName(),
|
277
|
|
|
],
|
278
|
|
|
'lines' => $this->getLinesForXml($this->lines)
|
279
|
|
|
];
|
280
|
|
|
}
|
281
|
|
|
|
282
|
|
|
/**
|
283
|
|
|
* Returns the parsed lines for the XML file rendering.
|
284
|
|
|
*
|
285
|
|
|
* @param array $in The input data
|
286
|
|
|
* @return array
|
287
|
|
|
*/
|
288
|
|
|
protected function getLinesForXml(array $in) {
|
289
|
|
|
$out = [];
|
290
|
|
|
foreach ($in as $line) {
|
291
|
|
|
$out[] = $this->getLineForXml($line);
|
292
|
|
|
}
|
293
|
|
|
return $out;
|
294
|
|
|
}
|
295
|
|
|
|
296
|
|
|
/**
|
297
|
|
|
* Returns the parsed line for the XML file rendering.
|
298
|
|
|
*
|
299
|
|
|
* @param array $in The input data
|
300
|
|
|
* @return array
|
301
|
|
|
*/
|
302
|
|
|
protected function getLineForXml(array $in) {
|
303
|
|
|
return $in;
|
304
|
|
|
}
|
305
|
|
|
|
306
|
|
|
/**
|
307
|
|
|
* Generates the document base files
|
308
|
|
|
* Witch are the next:
|
309
|
|
|
* Document input
|
310
|
|
|
* The XML file
|
311
|
|
|
* The signed XML file
|
312
|
|
|
* The ZIP file
|
313
|
|
|
*/
|
314
|
|
|
public function generateFiles() {
|
315
|
|
|
$this->saveDocumentInput();
|
316
|
|
|
$this->generateXmlFile();
|
317
|
|
|
$this->signXmlFile();
|
318
|
|
|
$this->generateZipFile();
|
319
|
|
|
}
|
320
|
|
|
|
321
|
|
|
public function saveDocumentInput() {
|
322
|
|
|
Repository::saveDocumentInput($this->fileName, json_encode($this->getRawData(), JSON_PRETTY_PRINT));
|
323
|
|
|
}
|
324
|
|
|
|
325
|
|
|
/**
|
326
|
|
|
* Generates the document's XML file
|
327
|
|
|
*/
|
328
|
|
|
public function generateXmlFile() {
|
329
|
|
|
$tplRenderer = TplRenderer::getRenderer(F72X::getSrcDir() . '/templates');
|
330
|
|
|
$xmlData = $this->getDataForXml();
|
331
|
|
|
$xmlData['signatureId'] = self::SIGNATURE_ID;
|
332
|
|
|
$xmlContent = $tplRenderer->render($this->xmlTplName, $xmlData);
|
333
|
|
|
Repository::saveDocument($this->fileName, $xmlContent);
|
334
|
|
|
}
|
335
|
|
|
|
336
|
|
|
/**
|
337
|
|
|
* Signs the XML document witch should be generated previously.
|
338
|
|
|
*/
|
339
|
|
|
public function signXmlFile() {
|
340
|
|
|
XmlDSig::sign($this->fileName);
|
341
|
|
|
}
|
342
|
|
|
|
343
|
|
|
/**
|
344
|
|
|
* Generates the document's ZIP file
|
345
|
|
|
*/
|
346
|
|
|
public function generateZipFile() {
|
347
|
|
|
Repository::zipDocument($this->fileName);
|
348
|
|
|
}
|
349
|
|
|
|
350
|
|
|
public function getId() {
|
351
|
|
|
return $this->id;
|
352
|
|
|
}
|
353
|
|
|
|
354
|
|
|
public function getSeries() {
|
355
|
|
|
return $this->series;
|
356
|
|
|
}
|
357
|
|
|
|
358
|
|
|
public function getNumber() {
|
359
|
|
|
return $this->number;
|
360
|
|
|
}
|
361
|
|
|
|
362
|
|
|
public function getIssueDate() {
|
363
|
|
|
return $this->issueDate;
|
364
|
|
|
}
|
365
|
|
|
|
366
|
|
|
public function getFileName() {
|
367
|
|
|
return $this->fileName;
|
368
|
|
|
}
|
369
|
|
|
|
370
|
|
|
public function hasLegalValidity() {
|
371
|
|
|
return $this->legalValidity;
|
372
|
|
|
}
|
373
|
|
|
|
374
|
|
|
public function getIssuer() {
|
375
|
|
|
return $this->issuer;
|
376
|
|
|
}
|
377
|
|
|
|
378
|
|
|
public function getLines() {
|
379
|
|
|
return $this->lines;
|
380
|
|
|
}
|
381
|
|
|
|
382
|
|
|
public function getLineDefaults() {
|
383
|
|
|
return $this->lineDefaults;
|
384
|
|
|
}
|
385
|
|
|
|
386
|
|
|
public function getXmlTplName() {
|
387
|
|
|
return $this->xmlTplName;
|
388
|
|
|
}
|
389
|
|
|
|
390
|
|
|
public function getRawData() {
|
391
|
|
|
return $this->rawData;
|
392
|
|
|
}
|
393
|
|
|
|
394
|
|
|
public function getParsedData() {
|
395
|
|
|
return $this->parsedData;
|
396
|
|
|
}
|
397
|
|
|
|
398
|
|
|
public function setIssueDate(DateTime $issueDate) {
|
399
|
|
|
$this->issueDate = $issueDate;
|
400
|
|
|
return $this;
|
401
|
|
|
}
|
402
|
|
|
|
403
|
|
|
public function setIssuer(DocumentIssuer $issuer) {
|
404
|
|
|
$this->issuer = $issuer;
|
405
|
|
|
return $this;
|
406
|
|
|
}
|
407
|
|
|
|
408
|
|
|
public function setXmlTplName($xmlTplName) {
|
409
|
|
|
$this->xmlTplName = $xmlTplName;
|
410
|
|
|
return $this;
|
411
|
|
|
}
|
412
|
|
|
|
413
|
|
|
}
|
414
|
|
|
|
The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g.
excluded_paths: ["lib/*"]
, you can move it to the dependency path list as follows:For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths