Operações em Lote (Batch)
O SDK TinyERP suporta operações em lote para criar e atualizar múltiplos registros de uma vez. Como as requisições são feitas enviando os dados via params, existe um tamanho de dados para serem enviados. O limite de requisições vai depender da sua conta no Tiny.
Por Que Usar Batch?
- ✅ Performance: Uma requisição para múltiplos registros
- ✅ Eficiência: Reduz overhead de rede
- ✅ Controle: Processamento individual de cada item
- ✅ Atomicidade Parcial: Alguns podem falhar, outros suceder
Criar Contatos em Lote
typescript
const contacts = await sdk.contact.create([
{
sequencia: 1,
contato: {
nome: "João Silva",
tipo_pessoa: "F",
cpf_cnpj: "11111111111",
email: "joao@email.com",
},
},
{
sequencia: 2,
contato: {
nome: "Maria Santos",
tipo_pessoa: "F",
cpf_cnpj: "22222222222",
email: "maria@email.com",
},
},
{
sequencia: 3,
contato: {
nome: "Empresa XYZ LTDA",
tipo_pessoa: "J",
cpf_cnpj: "12345678000199",
email: "contato@xyz.com",
},
},
]);
// Processar resultados
const sucessos = contacts.filter((r) => r.status === "OK");
const erros = contacts.filter((r) => r.status === "Erro");
console.log(`✅ ${sucessos.length} criados`);
console.log(`❌ ${erros.length} com erro`);Criar Produtos em Lote
typescript
const products = await sdk.product.create([
{
sequencia: 1,
data: {
nome: "Produto A",
codigo: "PROD-A",
unidade: "UN",
preco: 100,
origem: "0",
situacao: "A",
tipo: "P",
},
},
{
sequencia: 2,
data: {
nome: "Produto B",
codigo: "PROD-B",
unidade: "UN",
preco: 200,
origem: "0",
situacao: "A",
tipo: "P",
},
},
]);Processar Resultados
Separar Sucessos e Erros
typescript
const results = await sdk.contact.create([...]);
const sucessos = results.filter(r => r.status === 'OK');
const erros = results.filter(r => r.status === 'Erro');
console.log(`Processados: ${results.length}`);
console.log(`Sucessos: ${sucessos.length}`);
console.log(`Erros: ${erros.length}`);
// IDs dos registros criados
const ids = sucessos.map(r => r.id);
console.log('IDs criados:', ids);Exibir Erros Detalhados
typescript
if (erros.length > 0) {
console.error("\n❌ Erros encontrados:\n");
erros.forEach((erro) => {
console.error(`Sequência ${erro.sequencia}:`);
if (erro.erros) {
erro.erros.forEach((e) => {
console.error(` - ${e.erro}`);
});
}
console.error("");
});
}Mapear Resultados
typescript
const resultMap = results.reduce((map, result) => {
map[result.sequencia] = result;
return map;
}, {} as Record<number, any>);
// Verificar resultado específico
if (resultMap[1].status === "OK") {
console.log("Item 1 criado com ID:", resultMap[1].id);
}Chunking (Dividir em Lotes)
Para grandes volumes, divida em chunks menores:
typescript
function chunk<T>(array: T[], size: number): T[][] {
const chunks: T[][] = [];
for (let i = 0; i < array.length; i += size) {
chunks.push(array.slice(i, i + size));
}
return chunks;
}
async function createInChunks(contacts: any[], chunkSize: number = 10) {
const chunks = chunk(contacts, chunkSize);
const allResults = [];
for (let i = 0; i < chunks.length; i++) {
console.log(`Processando lote ${i + 1}/${chunks.length}...`);
// Re-sequenciar cada chunk
const chunkWithSequence = chunks[i].map((contact, index) => ({
sequencia: index + 1,
contato: contact
}));
const results = await sdk.contact.create(chunkWithSequence);
allResults.push(...results);
// Delay entre chunks
if (i < chunks.length - 1) {
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
return allResults;
}
// Uso
const manyContacts = [...]; // 100 contatos
const results = await createInChunks(manyContacts, 10); // 10 por vezRetry de Itens Falhados
typescript
async function createWithRetry(items: any[], maxRetries: number = 2) {
let toCreate = items.map((item, index) => ({
sequencia: index + 1,
contato: item,
}));
let attempt = 0;
const allSuccesses = [];
while (toCreate.length > 0 && attempt < maxRetries) {
attempt++;
console.log(`\nTentativa ${attempt}: ${toCreate.length} itens`);
const results = await sdk.contact.create(toCreate);
const sucessos = results.filter((r) => r.status === "OK");
const erros = results.filter((r) => r.status === "Erro");
allSuccesses.push(...sucessos);
if (erros.length === 0) {
break;
}
console.log(` ✅ ${sucessos.length} criados`);
console.log(` ❌ ${erros.length} falharam`);
// Tentar novamente apenas os que falharam
toCreate = erros.map((erro) => {
const originalIndex = erro.sequencia - 1;
return {
sequencia: originalIndex + 1,
contato: items[originalIndex],
};
});
if (attempt < maxRetries && toCreate.length > 0) {
await new Promise((resolve) => setTimeout(resolve, 2000));
}
}
console.log(`\n✅ Total de sucessos: ${allSuccesses.length}`);
return allSuccesses;
}Importação de CSV
typescript
import fs from "fs";
import { parse } from "csv-parse/sync";
async function importContactsFromCSV(filename: string) {
// Ler CSV
const fileContent = fs.readFileSync(filename, "utf-8");
const records = parse(fileContent, {
columns: true,
skip_empty_lines: true,
});
console.log(`📄 ${records.length} registros encontrados no CSV`);
// Converter para formato do SDK
const contacts = records.map((record: any) => ({
nome: record.nome,
tipo_pessoa: record.tipo_pessoa,
cpf_cnpj: record.cpf_cnpj,
email: record.email,
fone: record.telefone,
}));
// Criar em chunks
const results = await createInChunks(contacts, 10);
// Relatório
const sucessos = results.filter((r) => r.status === "OK");
const erros = results.filter((r) => r.status === "Erro");
console.log("\n📊 Relatório de Importação:");
console.log(` Total: ${records.length}`);
console.log(` ✅ Importados: ${sucessos.length}`);
console.log(` ❌ Erros: ${erros.length}`);
// Salvar erros em arquivo
if (erros.length > 0) {
const errorReport = erros.map((e) => ({
sequencia: e.sequencia,
erros: e.erros?.map((err) => err.erro).join("; "),
}));
fs.writeFileSync(
"erros-importacao.json",
JSON.stringify(errorReport, null, 2)
);
console.log("\n❌ Erros salvos em: erros-importacao.json");
}
return { sucessos, erros };
}
// Uso
await importContactsFromCSV("contatos.csv");Atualizar em Lote
typescript
async function updateMultipleContacts(
updates: Array<{ id: number; changes: any }>
) {
const batch = updates.map((update, index) => ({
sequencia: index + 1,
contato: {
id: update.id,
...update.changes,
},
}));
const results = await sdk.contact.update(batch);
const sucessos = results.filter((r) => r.status === "OK");
console.log(`${sucessos.length} contatos atualizados`);
return results;
}
// Uso
await updateMultipleContacts([
{ id: 123, changes: { email: "novo@email.com" } },
{ id: 456, changes: { fone: "11999999999" } },
{ id: 789, changes: { situacao: "I" } },
]);Validação Antes do Batch
typescript
function validateBatch(items: any[]): { valid: any[]; invalid: any[] } {
const valid = [];
const invalid = [];
items.forEach((item, index) => {
const errors = [];
if (!item.nome || item.nome.length < 3) {
errors.push("Nome inválido");
}
if (!["F", "J"].includes(item.tipo_pessoa)) {
errors.push("Tipo de pessoa inválido");
}
if (errors.length > 0) {
invalid.push({ index, item, errors });
} else {
valid.push(item);
}
});
return { valid, invalid };
}
// Uso
const { valid, invalid } = validateBatch(contacts);
if (invalid.length > 0) {
console.error(`❌ ${invalid.length} itens inválidos:`);
invalid.forEach(({ index, errors }) => {
console.error(` Item ${index}: ${errors.join(", ")}`);
});
}
if (valid.length > 0) {
console.log(`✅ ${valid.length} itens válidos, criando...`);
const results = await createInChunks(valid, 10);
}Progress Bar para Batch
typescript
async function createWithProgress(items: any[]) {
const total = items.length;
let completed = 0;
const chunks = chunk(items, 10);
for (const [index, chunkItems] of chunks.entries()) {
const batch = chunkItems.map((item, i) => ({
sequencia: i + 1,
contato: item,
}));
const results = await sdk.contact.create(batch);
completed += chunkItems.length;
const percent = ((completed / total) * 100).toFixed(1);
console.log(`⏳ Progresso: ${percent}% (${completed}/${total})`);
}
}Melhores Práticas
- Tamanho do Lote: Use 5-10 itens por batch
- Sequência: Sempre use sequência única (1, 2, 3...)
- Validação: Valide antes de enviar
- Chunking: Divida grandes volumes
- Delay: Aguarde entre chunks.
- Retry: Implemente retry para falhas temporárias
- Logging: Registre sucessos e erros
- Relatórios: Gere relatórios de importação