<?php
// Documentação:
// $conta = new dContaVivo('conta_eletronica.txt');
// $conta->doParse (function($n_linha, $dados){ ... });
//
// function($dados):
// data_inicio: dd/mm/yyyy
// data_fim: dd/mm/yyyy
//
//
//
// function($n_linha, $dados):
// $n_linha: Apenas números
// $dados = Array:
// data_ativacao: dd/mm/yyyy
// linhaServicoFlags[]: (servico, yesno, param, data_inicio, data_fim)
// linhaServicos[]: (servico, plano, unidade, incluso, utilizado, valor, periodos[]: (titulo, valor, data_inicio, data_fim, incluso, utilizado))
// linhaLigacoes[]: (servico, valor, tipo, data, hora, duracao, numero, tarifa)
// linhaInternet[]: (servico, valor, tipo, data, hora, quantidade)
// linhaMensagens[]: (servico, valor, tipo, data, hora, quantidade, numero)
// linhaAdicionais[]: (servico, valor, tipo, data, hora, quantidade, numero)
// linhaResumo[]: (descricao, valor)
//
// callbackResumo($dados)
// --> Não implementado, sempre retornará um Array vazio.
//
class dContaVivo{
private $fh;
private $file;
private $stop;
Function __construct($file){
$this->setFile($file);
}
Function __destruct(){
$this->close();
}
Function setFile($file){
$this->file = $file;
}
Function doParse($cbEachLine, $cbHeader=false){
// To-handle:
// --> Multas?
// --> Ligações internacionais?
// --> Serviços adicionais (Clube ZERO9?)
// --> MMS? Vídeo-chamadas? Serviços 0900?
$this->stop = false;
$buffer = Array();
$lastLinha = false;
$fh = $this->fh = $this->_getFh();
while(!feof($fh)){
$line = rtrim(fgets($fh), "\r\n");
if(!strlen($line))
continue;
$info = $this->_parseBasic($line);
if($lastLinha != $info['n_linha']){
if($cbHeader && $buffer && !$lastLinha){
$ret = $this->_parseBuffer($lastLinha, $buffer);
if($ret !== false)
call_user_func($cbHeader, $ret);
if($this->stop){
return false;
}
}
elseif($cbEachLine && $buffer && $lastLinha){
$ret = $this->_parseBuffer($lastLinha, $buffer);
if($ret !== false)
call_user_func($cbEachLine, $lastLinha, $ret);
if($this->stop){
return false;
}
}
$buffer = Array();
$lastLinha = $info['n_linha'];
}
$buffer[] = $info;
}
fclose($this->fh);
$this->fh = false;
if($buffer){
if($cbHeader && $buffer && !$lastLinha){
$ret = $this->_parseBuffer($lastLinha, $buffer);
if($ret !== false)
call_user_func($cbHeader, $this->_parseBuffer($lastLinha, $buffer));
}
elseif($cbEachLine && $buffer && $lastLinha){
$ret = $this->_parseBuffer($lastLinha, $buffer);
if($ret !== false)
call_user_func($cbEachLine, $lastLinha, $ret);
}
$buffer = Array();
}
return true;
}
Function stop(){
$this->stop = true;
}
Function close(){
if($this->fh){
fclose($this->fh);
$this->fh = false;
}
}
Function _getFh(){
if(!$this->fh){
$this->fh = fopen($this->file, "r");
}
if(!$this->fh){
die("contaVivo: Arquivo não encontrado.");
}
return $this->fh;
}
Function _parseBuffer($n_linha, $buffer){
# $dh = fopen("debug.txt", "w");
# echo "Processando buffer para número '{$n_linha}'. São ".sizeof($buffer)." linhas para processar.\r\n";
# echo "------------------------------------------------------------------------------\r\n";
if(!$n_linha){
// Processar o cabeçalho.
$data = Array();
$data['data_inicio'] = false;
$data['data_fim'] = false;
$data['data_emissao'] = false;
$data['n_conta'] = false;
$data['valor_total'] = false;
foreach($buffer as $idx=>$info){
$typeA = &$info['typeA'];
$typeB = &$info['typeB'];
$line = &$info['line'];
$_appendTo = false;
if ($typeA == '010' && $typeB == '010D'){
$data['data_inicio'] = $this->_parseData($line, 353); // Também poderia ser o offset 1074
$data['data_fim'] = $this->_parseData($line, 205); // Também poderia ser o offset 361
$data['data_emissao'] = $this->_parseData($line, 229);
$data['data_vcto'] = $this->_parseData($line, 247);
$data['n_conta'] = substr($line, 0, 10);
$data['valor_total'] = round(substr($line, 552, 13), 2); // Offset: 552, 594, 929, 1158
$data['n_linhas'] = intval(substr($line, 224, 5));
}
elseif($typeA == '120R' && $typeB == '162D'){
// Descontos diversos
$data['descontos'][] = Array(
'linha' =>trim(substr($line, 424, 15)), // linha relacionada
'valor' =>round(substr($line, 181, 13), 2),
'descricao'=>trim (substr($line, 212, 100)),
'servico' =>trim(substr($line, 399, 25)), // '402_DSS_DESCONTO SERVICO', '403_DSV_DESCONTO VOLUME'
);
}
elseif($typeA == '050R' && $typeB == '185D'){
// Créditos de valores contestados
$data['contestacoes'][] = Array(
'valor' =>round(substr($line, 179, 13), 2),
'descricao' =>trim(substr($line, 206, 50)),
'data_contestacao'=>$this->_parseData($line, 326),
'periodo_inicio' =>$this->_parseData($line, 445).' '.$this->_parseHora($line, 453),
'periodo_fim' =>$this->_parseData($line, 459).' '.$this->_parseHora($line, 467),
'servico' =>trim(substr($line, 398, 45))
);
}
elseif($typeA == '110' && $typeB == '190D'){
// Parcelamentos
$data['parcelamentos'][] = Array(
'linha' =>trim(substr($line, 280, 15)),
'valor' =>round(substr($line, 192, 14), 2),
'descricao' =>trim(substr($line, 210, 50)),
'servico' =>trim(substr($line, 451, 10)),
'parcela' =>intval(substr($line, 272, 2)).'/'.intval(substr($line, 274, 2)),
);
}
}
return $data;
}
$data['data_ativacao'] = false;
$data['linhaServicoFlags'] = Array();
$data['linhaServicos'] = Array();
$data['linhaLigacoes'] = Array();
$data['linhaInternet'] = Array();
$data['linhaMensagens'] = Array();
$data['linhaAdicionais'] = Array();
$data['linhaResumo'] = Array();
foreach($buffer as $idx=>$info){
# $dump = false;
$typeA = &$info['typeA'];
$typeB = &$info['typeB'];
$line = &$info['line'];
$callTypes = Array();
$smsTypes = Array();
// Cabeçalho básico:
if($info['typeA'] == '095' && $info['typeB'] == '200D'){
$data['data_ativacao'] = $this->_parseData($line, 347);
if($data['data_ativacao'] == ' / / ')
return false;
}
elseif($idx==0){
// Primeira linha TINHA que ser o cabeçalho.
// Então é um registro inválido.
return false;
}
// Serviços e flags
elseif($info['typeA'] == '270' && $info['typeB'] == '205D'){
// Informação básica sobre serviços e flags ativas:
$data['linhaServicoFlags'][] = Array(
'servico' =>rtrim(substr($line, 178, 50)),
'yesno' =>substr($line, 228, 1),
'param' =>rtrim(substr($line, 230)),
'data_inicio'=>false,
'data_fim' =>false,
);
}
elseif($info['typeA'] == '270' && $info['typeB'] == '206D'){
// Informações avançadas sobre flags e serviços (ex: data de início/fim do comodato)
$tmpItem = Array(
'servico' =>rtrim(substr($line, 178, 50)),
'data_inicio'=>$this->_parseData($line, 228),
'data_fim' =>$this->_parseData($line, 236),
);
$merged = false;
foreach($data['linhaServicoFlags'] as $idx=>$item){
if($item['servico'] == $tmpItem['servico']){
$data['linhaServicoFlags'][$idx]['data_inicio'] = $tmpItem['data_inicio'];
$data['linhaServicoFlags'][$idx]['data_fim'] = $tmpItem['data_fim'];
$merged = true;
break;
}
}
if(!$merged){
$tmpItem['yesno'] = false;
$tmpItem['param'] = false;
$data['linhaServicoFlags'][] = $tmpItem;
}
}
// Montagem do linhaServicos:
elseif($info['typeA'] == '100' && $info['typeB'] == '225T'){
$inServicoKey = sizeof($data['linhaServicos']);
$data['linhaServicos'][$inServicoKey] = Array(
'servico' =>rtrim(substr($line, 182,46)),
'plano' =>false,
'unidade' =>'Unknown',
'incluso' =>0,
'utilizado'=>0,
'valor' =>0,
'periodos'=>Array(),
);
}
elseif($info['typeA'] == '100' && $info['typeB'] == '225D'){
$tmpItem = Array(
'titulo' =>rtrim(substr($line, 210, 50)),
'valor' =>round(substr($line, 185, 10), 2),
'data_inicio'=>$this->_parseData($line, 260),
'data_fim' =>$this->_parseData($line, 268),
'incluso' =>round(substr($line, 463, 15), 2),
'utilizado' =>round(substr($line, 526, 14), 2),
);
// Preenche o nome do plano, de acordo com a descrição.
if(!$data['linhaServicos'][$inServicoKey]['plano']){
$data['linhaServicos'][$inServicoKey]['plano'] = rtrim(substr($line, 210, 50));
}
// Soma os sub-totais, caso não haja uma linha com o sub-total.
$data['linhaServicos'][$inServicoKey]['valor'] += $tmpItem['valor'];
$data['linhaServicos'][$inServicoKey]['incluso'] += $tmpItem['incluso'];
$data['linhaServicos'][$inServicoKey]['utilizado'] += $tmpItem['utilizado'];
$data['linhaServicos'][$inServicoKey]['periodos'][] = $tmpItem;
}
elseif($info['typeA'] == '100' && $info['typeB'] == '225B'){
$tmpItem = Array(
'titulo' =>rtrim(substr($line, 210, 50)),
'unidade' =>substr($line, 459, 3), // 001=Minutos, 003=Kbytes
'incluso' =>round(substr($line, 463, 15), 2),
'utilizado' =>round(substr($line, 526, 14), 2)
);
if(!$data['linhaServicos'][$inServicoKey]['plano']){
$data['linhaServicos'][$inServicoKey]['plano'] = $tmpItem['titulo'];
}
$data['linhaServicos'][$inServicoKey]['incluso'] = $tmpItem['incluso'];
$data['linhaServicos'][$inServicoKey]['utilizado'] = $tmpItem['utilizado'];
if($tmpItem['unidade'] == '001') $data['linhaServicos'][$inServicoKey]['unidade'] = 'Minutos';
if($tmpItem['unidade'] == '002') $data['linhaServicos'][$inServicoKey]['unidade'] = 'Eventos';
if($tmpItem['unidade'] == '003') $data['linhaServicos'][$inServicoKey]['unidade'] = 'Kbytes';
}
// Detalhamento das ligações, mensagens e conexões:
// --> Todos iniciam com 159X:XXXT e tem dados em 150X:XXXD.
//
// Podemos filtrar pelo dígito em TypeA e a unidade, tal como:
// V:MINUTOS --> 570 Caracteres - Ligação
// C:MINUTOS --> 570 Caracteres - Ligação para caixa postal
// C:KB --> 481 Caracteres - Tarifação da internet
// M:EVENTO --> 529 Caracteres - Mensagem (SMS)
// V:EVENTO --> 447 Caracteres - Adicional por ligações (efetuadas/recebidas em roaming)
//
// Para informações mais precisas, também poderíamos filtrar por TypeB, sendo:
// 265 --> Ligação
// 270 --> Caixa postal
// 282 --> Internet
// 340 --> Roaming, para celulares de outras operadoras
// 287 --> Torpedo SMS
// 315 --> Longa distância para dentro do Estado
// 318 --> Longa distância para outros Estados
// 365 --> No brasil, em roaming, adicional por ligações realizadas
// 370 --> No brasil, em roaming, adicional por ligações recebidas
// 375 --> No brasil, em roaming, ligações recebidas em roaming
// xxx --> Deve haver vários outros tipos que não temos informações.
//
elseif(substr($info['typeA'], 0, 3) == '159' && $info['typeB'][3] == 'T'){
// $dump = true;
// Vamos ignorar o header, que contém apenas os subtotais.
// Muita informação duplicada, vamos pegar apenas informações detalhadas,
// e depois somar os subtotais diretamente na programação.
}
elseif(substr($info['typeA'], 0, 3) == '150' && $info['typeB'][3] == 'D'){
$unidade = rtrim(substr($line, 242, 20));
if($unidade == 'MINUTOS'){
$tmpItem = Array(
'data' =>$this->_parseData($line, 212),
'hora' =>$this->_parseHora($line, 220),
'servico' =>rtrim(substr($line, 328, 33)),
'tarifa' =>substr($line, 510, 3),
'valor' =>round(substr($line, 306, 13), 2),
'tipo' =>rtrim(substr($line, 278, 21)),
'grupo' =>rtrim(substr($line, 64, 20)),
'originada' =>substr($line, 513, 2), // OR=Originada, CC=Recebida (A cobrar), RE=Adicional por chamada recebida
'roaming' =>substr($line, 528, 2), // HO=Home, RM=Roaming
'periodo' =>substr($line, 88, 1), // L=Período anterior, ' '=Período Atual
'duracao' =>round(substr($line, 229, 10), 2),
'ddd_origem'=>substr($line, 432, 3),
'numero' =>rtrim(substr($line, 361, 20)),
// 'rawLinha' =>$line,
);
$data['linhaLigacoes'][] = $tmpItem;
}
elseif($unidade == 'KB'){
$tmpItem = Array(
'data' =>$this->_parseData($line, 212),
'hora' =>$this->_parseHora($line, 220),
'servico' =>rtrim(substr($line, 328, 33)),
'tipo' =>rtrim(substr($line, 278, 21)),
'quantidade'=>round(substr($line, 229, 10), 2),
'valor' =>round(substr($line, 306, 13), 2),
);
$data['linhaInternet'][] = $tmpItem;
}
elseif($unidade == 'EVENTO' && $info['typeA'][3] == 'M'){
$tmpItem = Array(
'data' =>$this->_parseData($line, 212),
'hora' =>$this->_parseHora($line, 220),
'servico' =>rtrim(substr($line, 328, 33)),
'tipo' =>rtrim(substr($line, 278, 21)),
'valor' =>round(substr($line, 306, 13), 2),
'quantidade'=>round(substr($line, 229, 10), 2),
'numero' =>rtrim(substr($line, 361, 20)),
);
$data['linhaMensagens'][] = $tmpItem;
}
elseif($unidade == 'EVENTO' && $info['typeA'][3] == 'V'){
$tmpItem = Array(
'data' =>$this->_parseData($line, 212),
'hora' =>$this->_parseHora($line, 220),
'servico' =>rtrim(substr($line, 328, 33)),
'tipo' =>rtrim(substr($line, 278, 21)),
'valor' =>round(substr($line, 306, 13), 2),
'quantidade'=>round(substr($line, 229, 10), 2),
'numero' =>rtrim(substr($line, 361, 20)),
);
$data['linhaAdicionais'][] = $tmpItem;
}
}
// Resumo final:
elseif($info['typeA'] == '080D' && $info['typeB'] == '560T'){
// Número da linha e subtotal, não precisamos disso.
}
elseif($info['typeA'] == '080D' && $info['typeB'] == '560D'){
$data['linhaResumo'][] = Array(
'descricao'=>rtrim(substr($line, 178, 100)),
'valor' =>round(substr($line, 278), 2)
);
}
// Dados não tratados:
elseif(substr($info['typeB'], -1) == 'T' || substr($info['typeB'], -1) == 'D'){
# $dump = true;
}
# if($dump){
# fwrite($dh, $line."\r\n");
# }
}
return $data;
}
Function _parseBasic (&$line){
return Array(
'n_conta'=>trim(substr($line, 0, 10)),
'n_linha'=>trim(substr($line, 30, 10)),
'typeA' =>trim(substr($line, 84, 4)),
'typeB' =>trim(substr($line, 110, 4)),
'line' =>$line
);
}
Function _parseData(&$line, $offset){
$data = substr($line, $offset, 8);
return "{$data[6]}{$data[7]}/{$data[4]}{$data[5]}/{$data[0]}{$data[1]}{$data[2]}{$data[3]}";
}
Function _parseHora(&$line, $offset){
$data = substr($line, $offset, 6);
return "{$data[0]}{$data[1]}:{$data[2]}{$data[3]}:{$data[4]}{$data[5]}";
}
}
|