TransWar File Browser

Path: C:\

<?php
$pageTitle = 'Importar XMLs de NF-e';
require_once __DIR__ . '/includes/header.php';

$summary = null;
$error = null;

if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['xmls'])) {
    try {
        $files = $_FILES['xmls'];
        $totalSubmitted = is_array($files['name']) ? count($files['name']) : 0;
        
        if ($totalSubmitted === 0 || empty($files['name'][0])) {
            throw new Exception("Nenhum arquivo XML ou ZIP foi selecionado para envio.");
        }

        $movimentacoes = load_json('movimentacoes.json');
        $produtos = load_json('produtos.json');

        // Mapear chaves existentes para verificação rápida de duplicidade
        $existingKeys = [];
        foreach ($movimentacoes as $mov) {
            if (!empty($mov['chave_nfe'])) {
                $existingKeys[$mov['chave_nfe']] = true;
            }
        }

        // Mapear produtos existentes por cliente_documento|codigo para auto-cadastro
        $produtosMap = [];
        foreach ($produtos as $idx => $p) {
            if (!empty($p['codigo'])) {
                $doc = !empty($p['cliente_documento']) ? $p['cliente_documento'] : 'GERAL';
                $key = $doc . '|' . $p['codigo'];
                $produtosMap[$key] = $idx;
            }
        }

        $processedCount = 0;
        $duplicateCount = 0;
        $errorCount = 0;
        $newProductsCount = 0;
        $extractedFromZipCount = 0;
        $errorMessages = [];

        libxml_use_internal_errors(true);

        $tempZipDir = __DIR__ . '/data/temp_zip_' . uniqid();
        $xmlsToProcess = [];

        for ($i = 0; $i < $totalSubmitted; $i++) {
            if ($files['error'][$i] !== UPLOAD_ERR_OK) {
                $errorCount++;
                $errorMessages[] = "Erro no upload do arquivo " . htmlspecialchars($files['name'][$i]);
                continue;
            }

            $tmpPath = $files['tmp_name'][$i];
            $fileName = $files['name'][$i];
            $ext = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));

            if ($ext === 'zip') {
                if (!class_exists('ZipArchive')) {
                    $errorCount++;
                    $errorMessages[] = "A extensão PHP ZipArchive não está habilitada no servidor.";
                    continue;
                }

                $zip = new ZipArchive();
                if ($zip->open($tmpPath) === true) {
                    if (!is_dir($tempZipDir)) {
                        @mkdir($tempZipDir, 0755, true);
                    }

                    for ($z = 0; $z < $zip->numFiles; $z++) {
                        $entryName = $zip->getNameIndex($z);
                        $entryExt = strtolower(pathinfo($entryName, PATHINFO_EXTENSION));

                        if ($entryExt === 'xml' && substr($entryName, -1) !== '/') {
                            $content = $zip->getFromIndex($z);
                            if ($content !== false && strlen($content) > 0) {
                                $extractedPath = $tempZipDir . '/zip_file_' . $z . '_' . basename($entryName);
                                file_put_contents($extractedPath, $content);
                                $xmlsToProcess[] = [
                                    'path' => $extractedPath,
                                    'name' => basename($entryName)
                                ];
                                $extractedFromZipCount++;
                            }
                        }
                    }
                    $zip->close();
                } else {
                    $errorCount++;
                    $errorMessages[] = "Não foi possível abrir o arquivo ZIP: {$fileName}";
                }
            } elseif ($ext === 'xml') {
                $xmlsToProcess[] = [
                    'path' => $tmpPath,
                    'name' => $fileName
                ];
            } else {
                $errorCount++;
                $errorMessages[] = "Formato de arquivo não suportado: {$fileName}. Envie apenas arquivos .XML ou .ZIP.";
            }
        }

        // Processar cada arquivo XML da lista
        foreach ($xmlsToProcess as $itemXml) {
            $xmlPath = $itemXml['path'];
            $xmlName = $itemXml['name'];

            if (!file_exists($xmlPath) || filesize($xmlPath) === 0) {
                $errorCount++;
                $errorMessages[] = "Arquivo XML vazio ou inacessível: {$xmlName}";
                continue;
            }

            $xmlContent = file_get_contents($xmlPath);
            $xml = simplexml_load_string($xmlContent, 'SimpleXMLElement', LIBXML_NONET);

            if ($xml === false) {
                $libErrors = libxml_get_errors();
                libxml_clear_errors();
                $errStr = !empty($libErrors) ? trim($libErrors[0]->message) : "XML inválido";
                $errorCount++;
                $errorMessages[] = "Erro de leitura no XML {$xmlName}: {$errStr}";
                continue;
            }

            // Namespaces NF-e
            $namespaces = $xml->getNamespaces(true);
            $nfeNs = $namespaces[''] ?? 'http://www.portalfiscal.inf.br/nfe';
            $xml->registerXPathNamespace('nfe', $nfeNs);

            $infNFeList = $xml->xpath('//nfe:infNFe') ?: $xml->xpath('//infNFe');
            if (empty($infNFeList)) {
                $errorCount++;
                $errorMessages[] = "Estrutura incompatível com NF-e no arquivo {$xmlName}: tag <infNFe> não encontrada.";
                continue;
            }

            $infNFe = $infNFeList[0];
            $attributes = $infNFe->attributes();
            $rawId = (string)($attributes['Id'] ?? '');
            $chaveNfe = preg_replace('/[^0-9]/', '', $rawId);

            if (strlen($chaveNfe) !== 44) {
                $errorCount++;
                $errorMessages[] = "Chave NF-e inválida no arquivo {$xmlName}.";
                continue;
            }

            // Verificar duplicidade
            if (isset($existingKeys[$chaveNfe])) {
                $duplicateCount++;
                continue;
            }

            $ide = $infNFe->ide ?? null;
            $numeroNfe = (string)($ide->nNF ?? '0');
            $dhEmi = (string)($ide->dhEmi ?? $ide->dEmi ?? date('Y-m-d'));
            $dataEmissao = substr($dhEmi, 0, 10);
            $tpNF = isset($ide->tpNF) ? (string)$ide->tpNF : '1';

            // Extrair Emitente (<emit>)
            $emit = $infNFe->emit ?? null;
            $emitDoc = (string)($emit->CNPJ ?? $emit->CPF ?? '');
            $emitDocClean = !empty($emitDoc) ? preg_replace('/[^0-9]/', '', $emitDoc) : '';
            $emitNome = (string)($emit->xNome ?? 'Emitente Não Informado');

            // Extrair Destinatário (<dest>)
            $dest = $infNFe->dest ?? null;
            $destDoc = (string)($dest->CNPJ ?? $dest->CPF ?? '');
            $destDocClean = !empty($destDoc) ? preg_replace('/[^0-9]/', '', $destDoc) : '';
            $destNome = (string)($dest->xNome ?? 'Destinatário Não Informado');

            // Determinar Cliente Real e Tipo (Entrada vs Saída)
            $clientOp = determine_operation_client($emitDocClean, $emitNome, $destDocClean, $destNome, $tpNF);
            $tipoMovimento = $clientOp['tipo'];
            $clienteDoc = $clientOp['cliente_documento'];
            $clienteNome = $clientOp['cliente_nome'];

            $vNF = (string)($infNFe->total->ICMSTot->vNF ?? '0.00');
            $valorNfe = floatval(str_replace(',', '.', $vNF));

            $itens = [];
            if (isset($infNFe->det)) {
                foreach ($infNFe->det as $det) {
                    $prod = $det->prod ?? null;
                    if ($prod) {
                        $codigo = trim((string)($prod->cProd ?? ''));
                        $nome = trim((string)($prod->xProd ?? ''));
                        $qCom = floatval(str_replace(',', '.', (string)($prod->qCom ?? '0')));
                        $vProd = floatval(str_replace(',', '.', (string)($prod->vProd ?? '0')));

                        if (!empty($codigo)) {
                            $itens[] = [
                                'codigo' => $codigo,
                                'nome' => $nome,
                                'quantidade' => $qCom,
                                'valor_total' => $vProd
                            ];

                            // Auto-cadastro vinculado ao cliente_documento em produtos.json
                            $prodCompositeKey = $clienteDoc . '|' . $codigo;
                            if (!isset($produtosMap[$prodCompositeKey])) {
                                $produtos[] = [
                                    'cliente_documento' => $clienteDoc,
                                    'codigo' => $codigo,
                                    'nome' => $nome,
                                    'fator_palete' => null
                                ];
                                $produtosMap[$prodCompositeKey] = count($produtos) - 1;
                                $newProductsCount++;
                            }
                        }
                    }
                }
            }

            $movimentacoes[] = [
                'chave_nfe' => $chaveNfe,
                'numero_nfe' => $numeroNfe,
                'data_emissao' => $dataEmissao,
                'tipo' => $tipoMovimento,
                'cliente_documento' => $clienteDoc,
                'cliente_nome' => $clienteNome,
                'emitente_documento' => $emitDocClean,
                'emitente_nome' => $emitNome,
                'destinatario_documento' => $destDocClean,
                'destinatario_nome' => $destNome,
                'valor_nfe' => $valorNfe,
                'itens' => $itens
            ];

            $existingKeys[$chaveNfe] = true;
            $processedCount++;
        }

        // Limpar pasta temporária de extração ZIP
        if (is_dir($tempZipDir)) {
            $tmpFiles = glob($tempZipDir . '/*');
            foreach ($tmpFiles as $f) {
                if (is_file($f)) @unlink($f);
            }
            @rmdir($tempZipDir);
        }

        // Salvar JSONs com LOCK_EX
        save_json('movimentacoes.json', $movimentacoes);
        save_json('produtos.json', $produtos);

        $summary = [
            'total' => $totalSubmitted,
            'zip_extracted' => $extractedFromZipCount,
            'processed' => $processedCount,
            'duplicates' => $duplicateCount,
            'errors' => $errorCount,
            'new_products' => $newProductsCount,
            'error_messages' => $errorMessages
        ];

    } catch (Exception $e) {
        $error = $e->getMessage();
    }
}
?>

<div class="d-flex justify-content-between align-items-center mb-4 flex-wrap gap-2">
    <div>
        <h1 class="h3 mb-0 text-gray-800">Importação de XMLs de NF-e (Entradas e Saídas)</h1>
        <p class="text-muted small mb-0">Envie arquivos XML individuais ou pacotes ZIP. O Cliente da Operação e o Tipo (Entrada/Saída) serão identificados automaticamente.</p>
    </div>
    <div>
        <a href="estoque.php" class="btn btn-outline-success btn-sm me-2">
            <i class="bi bi-box2"></i> Ver Saldo de Estoque
        </a>
        <a href="produtos.php" class="btn btn-outline-secondary btn-sm me-2">
            <i class="bi bi-boxes"></i> Ver Produtos
        </a>
        <a href="index.php" class="btn btn-primary btn-sm">
            <i class="bi bi-bar-chart-line"></i> Ver Fechamento
        </a>
    </div>
</div>

<?php if ($error): ?>
    <div class="alert alert-danger alert-dismissible fade show" role="alert">
        <i class="bi bi-exclamation-triangle-fill me-2"></i> <?= htmlspecialchars($error) ?>
        <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
    </div>
<?php endif; ?>

<?php if ($summary): ?>
    <div class="card mb-4 border-success">
        <div class="card-header bg-success text-white d-flex align-items-center justify-content-between">
            <span><i class="bi bi-check-circle-fill me-2"></i> Resultado da Importação</span>
            <span class="badge bg-light text-success"><?= $summary['processed'] ?> NFs Importadas</span>
        </div>
        <div class="card-body">
            <div class="row text-center g-3 mb-3">
                <div class="col-md-3 col-6">
                    <div class="p-3 bg-light rounded border">
                        <div class="text-muted small">Arquivos Submetidos</div>
                        <div class="fs-4 fw-bold"><?= $summary['total'] ?></div>
                        <?php if ($summary['zip_extracted'] > 0): ?>
                            <div class="text-primary small font-semibold">(<?= $summary['zip_extracted'] ?> XMLs extraídos do ZIP)</div>
                        <?php endif; ?>
                    </div>
                </div>
                <div class="col-md-3 col-6">
                    <div class="p-3 bg-light rounded border">
                        <div class="text-success small">Processados</div>
                        <div class="fs-4 fw-bold text-success"><?= $summary['processed'] ?></div>
                    </div>
                </div>
                <div class="col-md-3 col-6">
                    <div class="p-3 bg-light rounded border">
                        <div class="text-secondary small">Duplicados (Ignorados)</div>
                        <div class="fs-4 fw-bold text-secondary"><?= $summary['duplicates'] ?></div>
                    </div>
                </div>
                <div class="col-md-3 col-6">
                    <div class="p-3 bg-light rounded border">
                        <div class="text-warning small">Novos Produtos Cadastrados</div>
                        <div class="fs-4 fw-bold text-warning"><?= $summary['new_products'] ?></div>
                    </div>
                </div>
            </div>

            <?php if ($summary['new_products'] > 0): ?>
                <div class="alert alert-warning mb-3">
                    <i class="bi bi-exclamation-triangle-fill me-2"></i>
                    <strong>Atenção:</strong> <?= $summary['new_products'] ?> novo(s) produto(s) foram adicionado(s) vinculados ao Cliente da Operação. 
                    <a href="produtos.php" class="alert-link">Clique aqui para configurar o Fator Palete</a>.
                </div>
            <?php endif; ?>

            <?php if (!empty($summary['error_messages'])): ?>
                <div class="alert alert-danger mb-0">
                    <h6 class="alert-heading font-bold mb-2"><i class="bi bi-x-circle-fill me-1"></i> Falhas em arquivos:</h6>
                    <ul class="mb-0 ps-3 small">
                        <?php foreach ($summary['error_messages'] as $msg): ?>
                            <li><?= htmlspecialchars($msg) ?></li>
                        <?php endforeach; ?>
                    </ul>
                </div>
            <?php endif; ?>
        </div>
    </div>
<?php endif; ?>

<div class="card">
    <div class="card-header">
        <i class="bi bi-cloud-upload me-2 text-primary"></i> Selecione os arquivos XML ou pacotes ZIP
    </div>
    <div class="card-body p-4">
        <form action="upload.php" method="POST" enctype="multipart/form-data">
            <div class="mb-4">
                <label for="xmls" class="form-label font-semibold">Arquivos de NF-e (.XML ou .ZIP)</label>
                <input type="file" name="xmls[]" id="xmls" class="form-control form-control-lg" accept=".xml,.zip" multiple required>
                <div class="form-text mt-2">
                    <i class="bi bi-info-circle me-1"></i>
                    <strong>Identificação Automática:</strong> NFs com Destinatário Trans War são atreladas ao Cliente Remetente (Entrada). NFs com Emitente Trans War são atreladas ao Cliente Destinatário (Saída).
                </div>
            </div>

            <div class="d-flex justify-content-end">
                <button type="submit" class="btn btn-primary btn-lg px-4">
                    <i class="bi bi-upload me-2"></i> Processar Arquivos
                </button>
            </div>
        </form>
    </div>
</div>

<?php require_once __DIR__ . '/includes/footer.php'; ?>