Passed
Push — main ( fa7c8c...8e0fd6 )
by Daniel
12:57
created

ClassElectronicInvoiceUserInterface::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 4
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 6
rs 10
1
<?php
2
3
/*
4
 * Copyright (c) 2024, Daniel Popiniuc and its licensors.
5
 *
6
 * All rights reserved. This program and the accompanying materials
7
 * are made available under the terms of the Eclipse Public License v1.0
8
 * which accompanies this distribution, and is available at
9
 * http://www.eclipse.org/legal/epl-v10.html
10
 *
11
 * Contributors:
12
 *    Daniel Popiniuc
13
 */
14
15
namespace danielgp\efactura;
16
17
class ClassElectronicInvoiceUserInterface
18
{
19
    use \danielgp\io_operations\InputOutputFiles;
20
21
    private array $arrayConfiguration;
22
    private \SebastianBergmann\Timer\Timer $classTimer;
23
24
    public function __construct()
25
    {
26
        $this->classTimer         = new \SebastianBergmann\Timer\Timer();
27
        $this->classTimer->start();
28
        $this->arrayConfiguration = $this->getArrayFromJsonFile(__DIR__
29
            . DIRECTORY_SEPARATOR . 'config', 'BasicConfiguration.json');
30
    }
31
32
    public function actionAnalyzeZIPfromANAFfromLocalFolder(string $strFilePath): array
33
    {
34
        $arrayFiles    = new \RecursiveDirectoryIterator($strFilePath, \FilesystemIterator::SKIP_DOTS);
35
        $arrayInvoices = [];
36
        $intFileNo     = 0;
37
        foreach ($arrayFiles as $strFile) {
38
            if ($strFile->isFile() && ($strFile->getExtension() === 'zip')) {
39
                $arrayInvoices[$intFileNo] = $this->setArchiveFromAnaf($strFile->getRealPath());
40
                $intFileNo++;
41
            }
42
        }
43
        return $arrayInvoices;
44
    }
45
46
    private function getButtonToActionSomething(array $arrayButtonFeatures): string
47
    {
48
        $arrayButtonStyle = [
49
            'font: bold 14pt Arial',
50
            'margin: 2px',
51
            'padding: 4px 10px',
52
        ];
53
        $arrayStylePieces = $arrayButtonStyle;
54
        if (array_key_exists('AdditionalStyle', $arrayButtonFeatures)) {
55
            $arrayStylePieces = array_merge($arrayButtonStyle, $arrayButtonFeatures['AdditionalStyle']);
56
        }
57
        return vsprintf('<a href="%s" class="btn btn-outline-primary" style="%s">%s</a>', [
58
            $arrayButtonFeatures['URL'],
59
            implode(';', $arrayStylePieces),
60
            $arrayButtonFeatures['Text'],
61
        ]);
62
    }
63
64
    private function setArchiveFromAnaf(string $strFile)
65
    {
66
        $classZip      = new \ZipArchive();
67
        $arrayToReturn = [];
68
        $res           = $classZip->open($strFile, \ZipArchive::RDONLY);
69
        if ($res === true) {
70
            $intFilesArchived = $classZip->numFiles;
71
            for ($intArchivedFile = 0; $intArchivedFile < $intFilesArchived; $intArchivedFile++) {
72
                $strArchivedFile = $classZip->getNameIndex($intArchivedFile);
73
                $strFileStats    = $classZip->statIndex($intArchivedFile);
74
                $matches         = [];
75
                preg_match('/^[0-9]{5,20}\.xml$/', $strArchivedFile, $matches, PREG_OFFSET_CAPTURE);
76
                $matches2        = [];
77
                preg_match('/^semnatura_[0-9]{5,20}\.xml$/', $strArchivedFile, $matches2, PREG_OFFSET_CAPTURE);
78
                if ($matches !== []) {
79
                    $resInvoice        = $classZip->getStream($strArchivedFile);
80
                    $strInvoiceContent = stream_get_contents($resInvoice);
81
                    fclose($resInvoice);
82
                    $arrayToReturn     = $this->setStandardizedFeedbackArray([
83
                        'Response_Index'      => pathinfo($strFile)['filename'],
84
                        'Size'                => $strFileStats['size'],
85
                        'FileDate'            => date('Y-m-d H:i:s', $strFileStats['mtime']),
86
                        'Matches'             => $matches,
87
                        'strArchivedFileName' => $strArchivedFile,
88
                        'strInvoiceContent'   => $strInvoiceContent,
89
                    ]);
90
                } elseif ($matches2 === []) {
91
                    echo vsprintf('<div>' . $this->arrayConfiguration['Feedback']['DifferentFile'] . '</div>', [
92
                        $strArchivedFile,
93
                        $strFile->getBasename(),
94
                    ]);
95
                }
96
            }
97
        } else {
98
            // @codeCoverageIgnoreStart
99
            throw new \RuntimeException(sprintf('Archive %s could not be opened!', $strFile));
100
            // @codeCoverageIgnoreEnd
101
        }
102
        return $arrayToReturn;
103
    }
104
105
    /**
106
     * Archived document interpretation requires a temporary files to be stored
107
     * and upon processing file is removed immediately
108
     *
109
     * @param array $arrayData
110
     * @return array
111
     */
112
    private function getDocumentDetails(array $arrayData): array
113
    {
114
        file_put_contents($arrayData['strArchivedFileName'], $arrayData['strInvoiceContent']);
115
        $appR               = new \danielgp\efactura\ClassElectronicInvoiceRead();
116
        $arrayElectronicInv = $appR->readElectronicInvoice($arrayData['strArchivedFileName']);
117
        $arrayBasic         = $arrayElectronicInv['Header']['CommonBasicComponents-2'];
118
        $arrayAggregate     = $arrayElectronicInv['Header']['CommonAggregateComponents-2'];
119
        $arrayStandardized  = [
120
            'Customer'    => $arrayAggregate['AccountingCustomerParty']['Party'],
121
            'ID'          => $arrayBasic['ID'],
122
            'IssueDate'   => $arrayBasic['IssueDate'],
123
            'No_of_Lines' => count($arrayElectronicInv['Lines']),
124
            'Supplier'    => $arrayAggregate['AccountingSupplierParty']['Party'],
125
            'TOTAL'       => (float) $arrayAggregate['LegalMonetaryTotal']['TaxInclusiveAmount']['value'],
126
            'wo_VAT'      => (float) $arrayAggregate['LegalMonetaryTotal']['TaxExclusiveAmount']['value'],
127
        ];
128
        unlink($arrayData['strArchivedFileName']);
129
        return $arrayStandardized;
130
    }
131
132
    public function setActionToDo(): void
133
    {
134
        echo '<main>';
135
        $arrayOptions = [
136
            'action' => FILTER_SANITIZE_SPECIAL_CHARS,
137
        ];
138
        $arrayInputs  = filter_input_array(INPUT_GET, $arrayOptions);
139
        if (array_key_exists('action', $arrayInputs)) {
140
            switch ($arrayInputs['action']) {
141
                case 'AnalyzeZIPfromANAFfromLocalFolder':
142
                    $strRelevantFolder = 'P:/eFactura_Responses/Luna_Anterioara_NeDeclarata_Inca/';
143
                    $arrayInvoices     = $this->actionAnalyzeZIPfromANAFfromLocalFolder($strRelevantFolder);
144
                    if (count($arrayInvoices) === 0) {
145
                        echo sprintf('<p style="color:red;">'
146
                            . 'Unfortunatelly there are no zip files in given folder (%s)...'
147
                            . '</p>', $strRelevantFolder);
148
                    } else {
149
                        echo $this->setHtmlTable($arrayInvoices);
150
                    }
151
                    break;
152
            }
153
        }
154
        echo '</main>';
155
    }
156
157
    private function setDataSupplierOrCustomer(array $arrayData)
158
    {
159
        $strCustomerCui = '';
160
        if (isset($arrayData['PartyTaxScheme']['01']['CompanyID'])) {
161
            $strCustomerCui = $arrayData['PartyTaxScheme']['01']['CompanyID'];
162
        } else {
163
            $strCustomerCui = $arrayData['PartyLegalEntity']['CompanyID'];
164
        }
165
        if (is_numeric($strCustomerCui)) {
166
            $strCustomerCui = 'RO' . $strCustomerCui;
167
        }
168
        return $strCustomerCui;
169
    }
170
171
    private function setDaysElapsed(string $strFirstDate, string $strLaterDate): string
172
    {
173
        $origin   = new \DateTimeImmutable($strFirstDate);
174
        $target   = new \DateTimeImmutable($strLaterDate);
175
        $interval = $origin->diff($target);
176
        return $interval->format('%R%a');
177
    }
178
179
    public function setHtmlFooter(): void
180
    {
181
        $strHtmlContent = implode('', $this->getFileEntireContent(implode(DIRECTORY_SEPARATOR, [
182
                __DIR__,
183
                'HTML',
184
                'footer.html',
185
        ])));
186
        echo vsprintf($strHtmlContent, [
187
            (new \SebastianBergmann\Timer\ResourceUsageFormatter())->resourceUsage($this->classTimer->stop()),
188
            date('Y'),
189
            $this->arrayConfiguration['Application']['Developer'],
190
        ]);
191
    }
192
193
    public function setHtmlHeader(): void
194
    {
195
        $strHtmlContent = implode('', $this->getFileEntireContent(implode(DIRECTORY_SEPARATOR, [
196
                __DIR__,
197
                'HTML',
198
                'header.html',
199
        ])));
200
        echo vsprintf($strHtmlContent, [
201
            $this->arrayConfiguration['Application']['Name'],
202
            $this->arrayConfiguration['Application']['Developer'],
203
        ]);
204
    }
205
206
    private function setHtmlTable(array $arrayData): string
207
    {
208
        $strReturn = '<table style="margin-left:auto;margin-right:auto;">';
209
        foreach ($arrayData as $intLineNo => $arrayContent) {
210
            ksort($arrayContent);
211
            if ($intLineNo === 0) {
212
                $strReturn .= $this->setHtmlTableHeader(array_keys($arrayContent))
213
                    . '<tbody>';
214
            }
215
            $strReturn .= $this->setHtmlTableLine(($intLineNo + 1), $arrayContent);
216
        }
217
        return ($strReturn . '</tbody>' . '</table>');
218
    }
219
220
    private function setHtmlTableHeader(array $arrayData): string
221
    {
222
        $arrayMap    = [
223
            'Amount_TOTAL'    => 'TOTAL',
224
            'Amount_VAT'      => 'TVA',
225
            'Amount_wo_VAT'   => 'Valoare',
226
            'Customer_CUI'    => 'CUI client',
227
            'Customer_Name'   => 'Nume client',
228
            'Days_Between'    => 'Zile emitere-depunere',
229
            'Document_No'     => 'Identificator',
230
            'Error'           => 'Eroare',
231
            'Issue_Date'      => 'Data emiterii',
232
            'Issue_YearMonth' => 'Anul și luna emiterii',
233
            'Loading_Index'   => 'Index încărcare',
234
            'No_of_Lines'     => 'Nr. linii',
235
            'Response_Date'   => 'Data răspuns',
236
            'Response_Index'  => 'Index răspuns',
237
            'Size'            => 'Dim. [bytes]',
238
            'Supplier_CUI'    => 'CUI emitent',
239
            'Supplier_Name'   => 'Nume emitent',
240
        ];
241
        $strToReturn = '<th>#</th>';
242
        foreach ($arrayData as $key) {
243
            $strToReturn .= sprintf('<th>%s</th>', (array_key_exists($key, $arrayMap) ? $arrayMap[$key] : $key));
244
        }
245
        return '<thead><tr>' . $strToReturn . '</tr></thead>';
246
    }
247
248
    private function setHtmlTableLine(int $intLineNo, array $arrayLine): string
249
    {
250
        $arrayContent = [];
251
        foreach ($arrayLine as $strColumn => $strValue) {
252
            if (str_starts_with($strColumn, 'Amount_')) {
253
                $arrayContent[] = sprintf('<td style="text-align:right;">%s</td>', $this->setNumbers($strValue, 2, 2));
254
            } elseif (str_starts_with($strColumn, 'Size')) {
255
                $arrayContent[] = sprintf('<td style="text-align:right;">%s</td>', $this->setNumbers($strValue, 0, 0));
256
            } else {
257
                $arrayContent[] = sprintf('<td>%s</td>', $strValue);
258
            }
259
        }
260
        return '<tr' . ($arrayLine['Error'] === '' ? '' : ' style="color:red;"') . '>'
261
            . '<td>' . $intLineNo . '</td>'
262
            . implode('', $arrayContent)
263
            . '</tr>';
264
    }
265
266
    private function setNumbers(float $floatNumber, int $intMinDigits, int $intMaxDigits): string
267
    {
268
        $classFormat = new \NumberFormatter('ro_RO', \NumberFormatter::DECIMAL);
269
        $classFormat->setAttribute(\NumberFormatter::MIN_FRACTION_DIGITS, $intMinDigits);
270
        $classFormat->setAttribute(\NumberFormatter::MAX_FRACTION_DIGITS, $intMaxDigits);
271
        return $classFormat->format($floatNumber);
272
    }
273
274
    private function setStandardizedFeedbackArray(array $arrayData): array
275
    {
276
        $arrayToReturn = [
277
            'Response_Date'   => '',
278
            'Response_Index'  => $arrayData['Response_Index'],
279
            'Loading_Index'   => '',
280
            'Size'            => '',
281
            'Document_No'     => '',
282
            'Issue_Date'      => '',
283
            'Issue_YearMonth' => '',
284
            'Amount_wo_VAT'   => '',
285
            'Amount_TOTAL'    => '',
286
            'Amount_VAT'      => '',
287
            'Supplier_CUI'    => '',
288
            'Supplier_Name'   => '',
289
            'Customer_CUI'    => '',
290
            'Customer_Name'   => '',
291
            'No_Lines'        => '',
292
            'Error'           => '',
293
            'Days_Between'    => '',
294
        ];
295
        if ($arrayData['Size'] > 1000) {
296
            $arrayAttr                        = $this->getDocumentDetails($arrayData);
297
            $arrayToReturn['Loading_Index']   = substr($arrayData['Matches'][0][0], 0, -4);
298
            $arrayToReturn['Size']            = $arrayData['Size'];
299
            $arrayToReturn['Document_No']     = $arrayAttr['ID'];
300
            $arrayToReturn['Issue_Date']      = $arrayAttr['IssueDate'];
301
            $arrayToReturn['Issue_YearMonth'] = (new \IntlDateFormatter(
302
                    'ro_RO',
303
                    \IntlDateFormatter::FULL,
304
                    \IntlDateFormatter::FULL,
305
                    'Europe/Bucharest',
306
                    \IntlDateFormatter::GREGORIAN,
307
                    'r-MM__MMMM'
308
                ))->format(new \DateTime($arrayAttr['IssueDate']));
309
            $arrayToReturn['Response_Date']   = $arrayData['FileDate'];
310
            $arrayToReturn['Amount_wo_VAT']   = $arrayAttr['wo_VAT'];
311
            $arrayToReturn['Amount_TOTAL']    = $arrayAttr['TOTAL'];
312
            $arrayToReturn['Amount_VAT']      = round(($arrayAttr['TOTAL'] - $arrayAttr['wo_VAT']), 2);
313
            $arrayToReturn['Supplier_CUI']    = $this->setDataSupplierOrCustomer($arrayAttr['Supplier']);
314
            $arrayToReturn['Supplier_Name']   = $arrayAttr['Supplier']['PartyLegalEntity']['RegistrationName'];
315
            $arrayToReturn['Customer_CUI']    = $this->setDataSupplierOrCustomer($arrayAttr['Customer']);
316
            $arrayToReturn['Customer_Name']   = $arrayAttr['Customer']['PartyLegalEntity']['RegistrationName'];
317
            $arrayToReturn['No_of_Lines']     = $arrayAttr['No_of_Lines'];
318
            $arrayToReturn['Days_Between']    = $this->setDaysElapsed($arrayAttr['IssueDate'], $arrayData['FileDate']);
319
        } elseif ($arrayData['Size'] > 0) {
320
            $objErrors                      = new \SimpleXMLElement($arrayData['strInvoiceContent']);
321
            $arrayToReturn['Loading_Index'] = $objErrors->attributes()->Index_incarcare->__toString();
322
            $arrayToReturn['Size']          = $arrayData['Size'];
323
            $arrayToReturn['Response_Date'] = $arrayData['FileDate'];
324
            $arrayToReturn['Supplier_CUI']  = 'RO' . $objErrors->attributes()->Cif_emitent->__toString();
325
            $arrayToReturn['Supplier_Name'] = '??????????';
326
            $arrayToReturn['Error']         = '<div style="max-width:200px;font-size:0.8rem;">'
327
                . $objErrors->Error->attributes()->errorMessage->__toString() . '</div>';
328
        }
329
        return $arrayToReturn;
330
    }
331
332
    public function setUserInterface(): void
333
    {
334
        echo '<header class="border-bottom">'
335
        . $this->getButtonToActionSomething([
336
            'Text' => 'Analyze ZIP archives from ANAF from a local folder',
337
            'URL'  => '?action=AnalyzeZIPfromANAFfromLocalFolder',
338
        ])
339
        . '</header>';
340
    }
341
}
342