Componente alterdata para importação de planilhas carregadas pelo usuário.
-
Csv, Xls, Xlsx, Ods - Importação de planilhas excel.
-
Importação genérica - Personalização para leitura de objetos de qualquer entidade.
-
Validação interna - Checagem automática dos tipos Number, Decimal, String, Boolean e Date.
-
Validação externa - Configuração para validação personalizada com mensagens de erro e alertas.
Através de uma modal (alt-modal-service) o usuário pode carregar um arquivo do computador, mapear as colunas da planilha aos campos da entidade e validar os itens para importação, através de um passo a passo simplificado:
- Seleção do arquivo
- Mapeamento - Coluna ↔ Campo
- Regras de valor - Campos de objetos são vinculados à valores das respectivas colunas mapeadas formando "regras" do tipo: onde há valor 'x' use o objeto {nome: 'X', id: 1} do sistema.
- Revisão - Dados submetidos à validação são exibidos agrupados pelas linhas do arquivo com status, mensagens de erro, warnings e opção de "desconsiderar item". Estando tudo ok basta clicar em "Gravar" para salvar o lote para processamento.
- Inicialmente o componente foi elaborado para atender importações assíncronas, ou seja, ao concluir o "gravar lote" a modal é fechada. A visualização do resultado da importação é disponivel em um outro evento de abertura da modal com novos parâmetros.
- Visualização: São listados os itens de lote mantendo o contrato de mensagens de erro, warning e status da etapa de "Revisão". Podendo ser acrescidas mensagens de erro e alteração de status por erros ocorridos no processamento. É disponibilizado também, um botão para editar o item importado na modal de seu próprio CRUD.
Campo de texto - Aceitos valores com até 255 caracteres.
Campo de data - Aceitos valores nos formatos: DD/MM/YYYY, DD-MM-YYYY, DDMMYYYY, DD/MM/YY, DD-MM-YY, DDMMYY e YYYY-MM-DD
Campo numérico - Aceitos números negativos ou positivos, inteiros ou decimais.
Campo boleano - Aceitos como verdadeiro: 1, true, sim, S, V. Aceitos como falso: 0, false, não, N, F. Independente de case ou acentos.
Dependências:
- alt-modal-service
- alt-select-service
- alt-alerta-flutuante-service
- alt-carregando-info
- js-xlsx
- select2
- latinize
- lodash
- moment
Instalação dependencias com bower:
$ bower install alt-modal-service alt-select-service alt-alerta-flutuante alt-carregando-info js-xlsx select2 latinize lodash moment
Instalação com bower:
$ bower install alt-importacao-csv
Inclusão dos arquivos:
CSS:
<link rel="stylesheet" href="bower_components/alt-importacao-csv/dist/alt-importacao-csv.min.css" />
JS:
<script src="bower_components/alt-importacao-csv/dist/alt-importacao-csv.min.js"></script>
A directive da modal de importação é carregada no DOM através do atributo:
<div alt-importacao-csv-especifica></div>
Na controller da aplicação inclua os módulos:
angular.module('myModule', [
'AltImportacaoCsvEspecificaService',
'AltImportacaoCsvOpcoesModel'
'AltImportacaoCsvCampoModel',
]);
E execute a chamada do serviço importacaoEspecificaService - AltImportacaoCsvEspecificaService:
importacaoEspecificaService.exibe(opcoesImportacao); // opcoesImportacao - objeto do tipo AltImportacaoCsvOpcoesModel
Parametro | Tipo | Obrigatório | Descrição |
---|---|---|---|
labelTipo | String | Sim | Label da entidade no plural. |
campos | Array | Sim | Lista de campos da entidade. |
validarLote | Function | Quando importação | Submete lote as regras externas ao componente. Deve retornar promise da funcionalidade. |
gravarLote | Function | Quando importação | Grava o lote de objetos. Deve retornar promise da funcionalidade. |
exibeInfo | Function | Não | Função do clique do "Veja as regras" adicionado ao passo 2 quando setada a variável. |
eventoCriacao | String | Quando importação | Evento que será transmitido na conclusão com resposta da gravação. |
visualizacao | Boolean | Não | Define se componente irá detalhar um lote já processado ou criar nova importação. |
labelTipoSingular | String | Quando visualização | Label da entidade no singular. |
loteProcessado | Array | Quando visualização | Lote de itens com resumo para detalhamento da importação. |
Usando como exemplo uma importação de Vendas:
let opcoesImportacao = new OpcoesImportacao({
labelTipo: 'vendas',
campos: campos,
validarLote: validarLote,
gravarLote: gravarLote,
eventoCriacao: EVENTO_IMPORTACAO_CRIADA
});
importacaoEspecificaService.exibe(self.opcoesImportacao); // AltImportacaoCsvEspecificaService
Para detalhamento de documento já importado:
/*
Dentro de cada item importado com sucesso devem ser inclusos os parametros para abrir modal do objeto.
*/
loteProcessado.itens.forEach((item) => {
item.editar = (idVenda, objeto) => {}; // Deve abrir modal. Recebe o "idObjeto" e o "objeto" do item de lote.
item.eventoEdicaoConcluida = IMPORTACAO_VENDA_EDICAO_CONCLUIDA; // Evento que será transmitido ao fechar a modal do objeto.
});
let opcoesImportacao = new OpcoesImportacao({
labelTipo: 'lançamentos',
labelTipoSingular: 'lançamento',
campos: campos,
visualizacao: true,
loteProcessado: loteProcessado
});
importacaoEspecificaService.exibe(self.opcoesImportacao); // AltImportacaoCsvEspecificaService
Parametro | Tipo | Obrigatório | Descrição |
---|---|---|---|
nome | String | Sim | Nome do campo. |
chave | String | Sim | Identificação única do campo. |
obrigatorio | Boolean | Não | Informa se campo é obrigatório para importação ou não. |
tipo | Function | Não | Enum da função que representa o tipo do campo.¹ |
monetario | Boolean | Não | Indica que se trata de um monetário quando o tipo é Number. |
tamanhoCompleto | Boolean | Não | Não limita a String em 255 caracteres. |
objetoChave | String | Quando tipo é Object | Propriedade única do objeto.² |
objetoReferencia | String | Quando tipo é Object | Propriedade label do objeto (nome, descrição ou equivalente). |
objetoListagem | Function | Quando tipo é Object | Retorna a lista de objetos do tipo em questão para seleção no componente. |
objetoAutoVinculo | Function | Não | Personaliza o vinculo automático da estapa de regras de valor.³ |
objetoOpcoesListagem | Object | Não | Opções select2. |
↳ groupBy | String | Não | Nome da propriedade de agrupamento no select2. |
↳ orderBy | Array | Não | Lista de propriedades com prioridade de ordenação no select2. |
objetoCriarNovo | Object | Não | Opções funcionalidade "criar novo" pelo combo do campo. |
↳ funcao | Function | Não | Responsável por criar novo objeto para inclusão na listagem. |
↳ eventoAtualizacao | String | Não | Evento que é emitido quando um Novo produto é criado. |
template | Object | Não | Opções template da revisão. |
↳ label | String | Não | Label do campo na revisão (quando ausente parametro 'nome' é utilizado). |
↳ width | Number | Não | Largura do campo no template da revisão (1 a 12). Default 12. |
¹ tipo - Tipos atualmente tratados são Number, String, Date, Boolean e Object. Na ausência do parametro o campo é considerado String.
² objetoChave - Usada também para identificar a propriedade a ser exibida quando o retorno é um Array de objetos.
Exemplo com o campo anotacoes, setando objetoChave como texto:
{
"anotacoes":[
{
"texto":"Comentário 1",
"data":"2020-12-18T12:01:51.126Z",
"nomeUsuarioPassaporte":"Nimbus",
"idUsuarioPassaporte":1234
},
{
"texto":"Comentário 2",
"data":"2020-12-18T12:01:51.126Z",
"nomeUsuarioPassaporte":"Nimbus",
"idUsuarioPassaporte":1234
},
{
"texto":"Comentário 3",
"data":"2020-12-18T12:01:51.126Z",
"nomeUsuarioPassaporte":"Nimbus",
"idUsuarioPassaporte":1234
}
]
}
³ objetoAutoVinculo - Esta função por default aplica a regra de valor de campos de objeto, sem que o usuário precise selecionar o objeto manualmente. Ou seja, regras do tipo "onde há valor 'x' use {nome: 'x', id: 1}" são aplicadas automaticamente, o componente traz a função padrão que aplica a regra quando o valor do campo e a prpriedade objetoReferencia do objeto forem "iguais" desconsiderando letras maiúsculas, minúsculas e acentos. Esse método pode ser personalizado por qualquer função que receba um valor (String) e retorne um objeto da lista (mesma lista fornecida em objetoListagem). Por exemplo, no caso de produto pode-se decidir por vincular automaticamente quando valor for igual também ao código de barras, etc.
Mantendo entidade Venda como exemplo:
const campos = [
new CampoImportacao({
nome: 'Produto',
chave: 'produto',
obrigatorio: true,
tipo: Object,
objetoChave: 'id',
objetoReferencia: 'descricao',
objetoListagem: () => {return this.produtos;},
objetoOpcoesListagem: {
groupBy: 'nomeCategoria',
orderBy: ['descricao', 'preco']
},
objetoCriarNovo: {
funcao: abrirModalNovoProduto,
eventoAtualizacao: 'importacao_produtos_atualizados'
}
}),
new CampoImportacao({
nome: 'Data',
chave: 'dataVenda',
obrigatorio: true,
tipo: Date,
template: {
width: 4
}
}),
new CampoImportacao({
nome: 'Valor Total',
chave: 'total',
obrigatorio: true,
tipo: Number,
monetario: true
}),
new CampoImportacao({
nome: 'Observações',
chave: 'observacoes',
template: {
label: 'OBS'
}
})
];
/**
Importante: É necessário atualizar a listagem de objetos para os campos com opção CRIAR NOVO, e notificar alteração da listagem:
*/
$scope.$on('modulo_produto:produto_criado', (evento, produto) => {
this.produtos.push(produto);
$rootScope.$broadcast('importacao_vendas:produtos_atualizados', produto);
});
Ilustrando funções de validação e gravação com o exemplo de vendas:
const validarLote = (lote) => {
return $q((resolve) => {
lote.itens.forEach((item, i) => {
let venda = item.objeto;
/*
O objeto "venda" aqui terá como propriedades os valores do parametro "chave" na configuração dos campos, ou seja, esperamos:
- venda.produto {Object}
- venda.dataVenda {Date}
- venda.total {Number}
- venda.observacoes {String} (opcional)
*/
// Exemplo de validação:
if (dataVendaInferiorADoisAnos(venda.dataVenda)) {
var limite = moment().subtract(2, 'years').format('DD/MM/YYYY');
lote.aplicarStatusErro(i); // Aplica status inválido no item e ajusta o resumo (totais) do lote.
item.resumo.mensagens.push({
textoHtml: $sce.trustAsHtml('O campo <u>Data da Venda</u> deve conter data superior a <u>' + limite + '</u>')
});
}
/*
Obs 1: lote.aplicarStatusAlerta também é disponível. Para itens com status "alerta" mensagens são exibidas mas
usuário não é impedido de submeter lote à gravação. Já com status erro a opção de gravarLote é desabilitada.
Obs 2: Objeto "mensagem" pode conter html como ilustrado acima (propriedade 'textoHtml'), ou, string normal (propriedade 'texto');
*/
});
resolve(lote);
};
};
const gravarLote = (lote) => {
return $q((resolve) => {
let loteGenerico = {};
loteGenerico.nomeArquivo = lote.nomeArquivo;
loteGenerico.itens = [];
lote.itens.forEach((item) => {
const venda = item.objeto;
const itemDeImportacao = {objeto: venda, linha: item.linha};
/*
Aqui vale a regra de contrato estabelecida com o back-end para gravação do lote.
*/
loteGenerico.itens.push(itemDeImportacao);
});
carregandoService.exibe();
vendasService.gravarLoteImportacao(loteGenerico)
.then((resp) => {
resolve(resp);
})
.catch((erro) => {
alertaFlutuanteService.exibe({msg: 'Houve um problema ao gravar lote de vendas.'});
})
.finally(() => {
carregandoService.esconde();
});
});
};
/*
Quando "gravarLote" for finalizado, a janela (modal) de importação é fechada
altomaticamente, e, é transmitido o evento de importação criada (evento definido nas opções
de importação "eventoCriacao"). A resposta do "gravarLote" é repassada na transmissao do evento.
*/
$scope.$on(EVENTO_IMPORTACAO_CRIADA, (ev, resp) => {
_atualizarListagemLotesImportacao(resp);
});