848 lines
23 KiB
Markdown
848 lines
23 KiB
Markdown
# Banco de Dados SQLite - Estrutura e Funcionalidades
|
|
|
|
## Visão Geral
|
|
|
|
O aplicativo utiliza SQLite como banco de dados local principal, com fallback para AsyncStorage quando SQLite não está disponível (ex: plataforma web). O banco é usado para armazenamento offline, cache de dados e sincronização.
|
|
|
|
## Configuração do Banco
|
|
|
|
### Inicialização
|
|
**Arquivo**: `src/services/database.ts`
|
|
|
|
```typescript
|
|
import SQLite from 'expo-sqlite';
|
|
|
|
// Verificar disponibilidade do SQLite
|
|
let SQLite: any;
|
|
let db: any;
|
|
let usingSQLite = false;
|
|
|
|
try {
|
|
if (Platform.OS !== "web") {
|
|
SQLite = require("expo-sqlite");
|
|
if (SQLite && typeof SQLite.openDatabase === "function") {
|
|
db = SQLite.openDatabase("truckdelivery.db");
|
|
usingSQLite = true;
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.warn("SQLite não disponível, usando AsyncStorage");
|
|
}
|
|
```
|
|
|
|
### Fallback para AsyncStorage
|
|
```typescript
|
|
// Prefixos para chaves do AsyncStorage
|
|
const USERS_KEY = "@TruckDelivery:users:"
|
|
const DELIVERIES_KEY = "@TruckDelivery:deliveries:"
|
|
const ROUTES_KEY = "@TruckDelivery:routes:"
|
|
const SETTINGS_KEY = "@TruckDelivery:settings:"
|
|
```
|
|
|
|
## Estrutura das Tabelas
|
|
|
|
### 1. Tabela `users`
|
|
**Propósito**: Armazenar dados dos usuários
|
|
|
|
```sql
|
|
CREATE TABLE IF NOT EXISTS users (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT,
|
|
email TEXT,
|
|
role TEXT,
|
|
last_login INTEGER
|
|
);
|
|
```
|
|
|
|
**Campos**:
|
|
- `id`: Identificador único do usuário
|
|
- `name`: Nome completo do usuário
|
|
- `email`: Email do usuário
|
|
- `role`: Função/cargo do usuário
|
|
- `last_login`: Timestamp do último login
|
|
|
|
**Operações**:
|
|
```typescript
|
|
// Inserir usuário
|
|
await executeQuery(
|
|
"INSERT INTO users (id, name, email, role, last_login) VALUES (?, ?, ?, ?, ?)",
|
|
[id, name, email, role, lastLogin]
|
|
);
|
|
|
|
// Buscar usuário
|
|
const result = await executeQuery("SELECT * FROM users WHERE id = ?", [id]);
|
|
```
|
|
|
|
### 2. Tabela `deliveries`
|
|
**Propósito**: Armazenar dados das entregas
|
|
|
|
```sql
|
|
CREATE TABLE IF NOT EXISTS deliveries (
|
|
id TEXT PRIMARY KEY,
|
|
outId TEXT,
|
|
customerId TEXT,
|
|
customerName TEXT,
|
|
street TEXT,
|
|
streetNumber TEXT,
|
|
neighborhood TEXT,
|
|
city TEXT,
|
|
state TEXT,
|
|
zipCode TEXT,
|
|
customerPhone TEXT,
|
|
lat REAL,
|
|
lng REAL,
|
|
latFrom REAL,
|
|
lngFrom REAL,
|
|
deliverySeq INTEGER,
|
|
routing INTEGER,
|
|
sellerId TEXT,
|
|
storeId TEXT,
|
|
status TEXT,
|
|
outDate TEXT,
|
|
notes TEXT,
|
|
signature TEXT,
|
|
photos TEXT,
|
|
completedTime INTEGER,
|
|
completedBy TEXT,
|
|
version INTEGER DEFAULT 1,
|
|
lastModified INTEGER DEFAULT (strftime('%s', 'now')),
|
|
syncTimestamp INTEGER,
|
|
syncStatus TEXT DEFAULT 'pending'
|
|
);
|
|
```
|
|
|
|
**Campos**:
|
|
- `id`: Identificador único da entrega
|
|
- `outId`: ID da entrega no sistema externo
|
|
- `customerId`: ID do cliente
|
|
- `customerName`: Nome do cliente
|
|
- `street`: Rua do endereço
|
|
- `streetNumber`: Número do endereço
|
|
- `neighborhood`: Bairro
|
|
- `city`: Cidade
|
|
- `state`: Estado
|
|
- `zipCode`: CEP
|
|
- `customerPhone`: Telefone do cliente
|
|
- `lat/lng`: Coordenadas de destino
|
|
- `latFrom/lngFrom`: Coordenadas de origem
|
|
- `deliverySeq`: Sequência na rota
|
|
- `routing`: ID da rota
|
|
- `sellerId`: ID do vendedor
|
|
- `storeId`: ID da loja
|
|
- `status`: Status da entrega (pending, in_progress, delivered, failed)
|
|
- `outDate`: Data de saída
|
|
- `notes`: Observações da entrega
|
|
- `signature`: Assinatura em base64
|
|
- `photos`: URLs das fotos em JSON
|
|
- `completedTime`: Timestamp de conclusão
|
|
- `completedBy`: ID do usuário que completou
|
|
- `version`: Versão do registro
|
|
- `lastModified`: Última modificação
|
|
- `syncTimestamp`: Timestamp da sincronização
|
|
- `syncStatus`: Status de sincronização (pending, synced)
|
|
|
|
**Operações**:
|
|
```typescript
|
|
// Salvar entrega
|
|
await executeQuery(
|
|
`INSERT INTO deliveries (
|
|
id, client, address, coordinates, status,
|
|
scheduled_time, completed_time, signature, photos, notes, sync_status
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
[id, client, address, JSON.stringify(coordinates), status,
|
|
scheduledTime, completedTime, signature, JSON.stringify(photos),
|
|
notes, syncStatus]
|
|
);
|
|
|
|
// Buscar entregas por status
|
|
const result = await executeQuery(
|
|
"SELECT * FROM deliveries WHERE status = ? ORDER BY scheduled_time ASC",
|
|
[status]
|
|
);
|
|
```
|
|
|
|
### 3. Tabela `routes`
|
|
**Propósito**: Armazenar dados das rotas
|
|
|
|
```sql
|
|
CREATE TABLE IF NOT EXISTS routes (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT,
|
|
date TEXT,
|
|
deliveries TEXT,
|
|
status TEXT,
|
|
sync_status TEXT
|
|
);
|
|
```
|
|
|
|
**Campos**:
|
|
- `id`: Identificador único da rota
|
|
- `name`: Nome da rota
|
|
- `date`: Data da rota
|
|
- `deliveries`: IDs das entregas em JSON
|
|
- `status`: Status da rota
|
|
- `sync_status`: Status de sincronização
|
|
|
|
### 4. Tabela `settings`
|
|
**Propósito**: Armazenar configurações do aplicativo
|
|
|
|
```sql
|
|
CREATE TABLE IF NOT EXISTS settings (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
key TEXT UNIQUE,
|
|
value TEXT
|
|
);
|
|
```
|
|
|
|
**Campos**:
|
|
- `id`: Chave primária auto-incremento
|
|
- `key`: Chave da configuração
|
|
- `value`: Valor da configuração
|
|
|
|
**Configurações Padrão**:
|
|
```sql
|
|
INSERT OR IGNORE INTO settings (key, value) VALUES
|
|
('last_sync', '0'),
|
|
('offline_mode', 'false'),
|
|
('auto_sync', 'true');
|
|
```
|
|
|
|
### 5. Tabela `deliveries_offline`
|
|
**Propósito**: Armazenar entregas offline para sincronização
|
|
|
|
```sql
|
|
CREATE TABLE IF NOT EXISTS deliveries_offline (
|
|
id TEXT PRIMARY KEY,
|
|
outId INTEGER,
|
|
transactionId INTEGER,
|
|
deliveryDate TEXT,
|
|
receiverDoc TEXT,
|
|
receiverName TEXT,
|
|
lat REAL,
|
|
lng REAL,
|
|
broken INTEGER,
|
|
devolution INTEGER,
|
|
reasonDevolution TEXT,
|
|
deliveryImages TEXT,
|
|
userId INTEGER,
|
|
sync_status TEXT
|
|
);
|
|
```
|
|
|
|
**Campos**:
|
|
- `id`: Identificador único
|
|
- `outId`: ID da entrega no sistema
|
|
- `transactionId`: ID da transação
|
|
- `deliveryDate`: Data da entrega
|
|
- `receiverDoc`: Documento do receptor
|
|
- `receiverName`: Nome do receptor
|
|
- `lat`: Latitude
|
|
- `lng`: Longitude
|
|
- `broken`: Produto quebrado (0/1)
|
|
- `devolution`: Devolução (0/1)
|
|
- `reasonDevolution`: Motivo da devolução
|
|
- `deliveryImages`: Imagens em JSON
|
|
- `userId`: ID do usuário
|
|
- `sync_status`: Status de sincronização
|
|
|
|
### 6. Tabela `customer_invoices`
|
|
**Propósito**: Armazenar notas fiscais dos clientes
|
|
|
|
```sql
|
|
CREATE TABLE IF NOT EXISTS customer_invoices (
|
|
id TEXT PRIMARY KEY,
|
|
invoiceId TEXT,
|
|
transactionId INTEGER,
|
|
customerId TEXT,
|
|
customerName TEXT,
|
|
invoiceValue REAL,
|
|
status TEXT,
|
|
items TEXT,
|
|
created_at INTEGER DEFAULT (strftime('%s', 'now')),
|
|
sync_status TEXT DEFAULT 'pending'
|
|
);
|
|
```
|
|
|
|
**Campos**:
|
|
- `id`: Identificador único
|
|
- `invoiceId`: ID da nota fiscal
|
|
- `transactionId`: ID da transação
|
|
- `customerId`: ID do cliente
|
|
- `customerName`: Nome do cliente
|
|
- `invoiceValue`: Valor da nota fiscal
|
|
- `status`: Status da nota fiscal
|
|
- `items`: Itens da nota fiscal em JSON
|
|
- `created_at`: Data de criação
|
|
- `sync_status`: Status de sincronização
|
|
|
|
### 7. Tabela `delivery_images`
|
|
**Propósito**: Gerenciar imagens das entregas
|
|
|
|
```sql
|
|
CREATE TABLE IF NOT EXISTS delivery_images (
|
|
id TEXT PRIMARY KEY,
|
|
deliveryId TEXT,
|
|
transactionId INTEGER,
|
|
imagePath TEXT,
|
|
imageUrl TEXT,
|
|
uploadStatus TEXT DEFAULT 'pending',
|
|
uploadAttempts INTEGER DEFAULT 0,
|
|
lastUploadAttempt INTEGER,
|
|
created_at INTEGER DEFAULT (strftime('%s', 'now')),
|
|
FOREIGN KEY (deliveryId) REFERENCES deliveries(id)
|
|
);
|
|
```
|
|
|
|
**Campos**:
|
|
- `id`: Identificador único
|
|
- `deliveryId`: ID da entrega
|
|
- `transactionId`: ID da transação
|
|
- `imagePath`: Caminho local da imagem
|
|
- `imageUrl`: URL da imagem no servidor
|
|
- `uploadStatus`: Status do upload (pending, uploaded, failed)
|
|
- `uploadAttempts`: Número de tentativas de upload
|
|
- `lastUploadAttempt`: Timestamp da última tentativa
|
|
- `created_at`: Data de criação
|
|
|
|
### 8. Tabela `sync_queue`
|
|
**Propósito**: Fila de sincronização para operações pendentes
|
|
|
|
```sql
|
|
CREATE TABLE IF NOT EXISTS sync_queue (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
table_name TEXT NOT NULL,
|
|
record_id TEXT NOT NULL,
|
|
action TEXT NOT NULL,
|
|
data TEXT,
|
|
priority INTEGER DEFAULT 1,
|
|
attempts INTEGER DEFAULT 0,
|
|
max_attempts INTEGER DEFAULT 3,
|
|
last_attempt INTEGER,
|
|
created_at INTEGER DEFAULT (strftime('%s', 'now')),
|
|
status TEXT DEFAULT 'pending'
|
|
);
|
|
```
|
|
|
|
**Campos**:
|
|
- `id`: Identificador único
|
|
- `table_name`: Nome da tabela
|
|
- `record_id`: ID do registro
|
|
- `action`: Ação a ser executada (INSERT, UPDATE, DELETE)
|
|
- `data`: Dados em JSON
|
|
- `priority`: Prioridade da operação
|
|
- `attempts`: Número de tentativas
|
|
- `max_attempts`: Máximo de tentativas
|
|
- `last_attempt`: Timestamp da última tentativa
|
|
- `created_at`: Data de criação
|
|
- `status`: Status da operação (pending, processing, completed, failed)
|
|
|
|
### 9. Tabela `photo_uploads`
|
|
**Propósito**: Controle de upload de fotos
|
|
|
|
```sql
|
|
CREATE TABLE IF NOT EXISTS photo_uploads (
|
|
id TEXT PRIMARY KEY,
|
|
deliveryId TEXT,
|
|
transactionId INTEGER,
|
|
localPath TEXT,
|
|
serverUrl TEXT,
|
|
uploadStatus TEXT DEFAULT 'pending',
|
|
uploadProgress REAL DEFAULT 0,
|
|
uploadAttempts INTEGER DEFAULT 0,
|
|
lastUploadAttempt INTEGER,
|
|
errorMessage TEXT,
|
|
created_at INTEGER DEFAULT (strftime('%s', 'now')),
|
|
FOREIGN KEY (deliveryId) REFERENCES deliveries(id)
|
|
);
|
|
```
|
|
|
|
**Campos**:
|
|
- `id`: Identificador único
|
|
- `deliveryId`: ID da entrega
|
|
- `transactionId`: ID da transação
|
|
- `localPath`: Caminho local da foto
|
|
- `serverUrl`: URL da foto no servidor
|
|
- `uploadStatus`: Status do upload (pending, uploading, completed, failed)
|
|
- `uploadProgress`: Progresso do upload (0-1)
|
|
- `uploadAttempts`: Número de tentativas
|
|
- `lastUploadAttempt`: Timestamp da última tentativa
|
|
- `errorMessage`: Mensagem de erro
|
|
- `created_at`: Data de criação
|
|
|
|
## Funções de Acesso aos Dados
|
|
|
|
### 1. Função Genérica de Query
|
|
```typescript
|
|
export const executeQuery = async (query: string, params: any[] = []): Promise<any> => {
|
|
if (usingSQLite) {
|
|
return new Promise((resolve, reject) => {
|
|
db.transaction((tx: any) => {
|
|
tx.executeSql(
|
|
query,
|
|
params,
|
|
(_: any, result: any) => resolve(result),
|
|
(_: any, error: any) => {
|
|
reject(error);
|
|
return false;
|
|
}
|
|
);
|
|
});
|
|
});
|
|
} else {
|
|
// Implementação para AsyncStorage
|
|
return executeAsyncStorageQuery(query, params);
|
|
}
|
|
};
|
|
```
|
|
|
|
### 2. Operações CRUD para Entregas
|
|
|
|
#### Buscar Entregas
|
|
```typescript
|
|
export const getDeliveries = async (status?: string): Promise<any[]> => {
|
|
try {
|
|
if (usingSQLite) {
|
|
let query = "SELECT * FROM deliveries";
|
|
const params: any[] = [];
|
|
|
|
if (status) {
|
|
query += " WHERE status = ?";
|
|
params.push(status);
|
|
}
|
|
|
|
query += " ORDER BY scheduled_time ASC";
|
|
const result = await executeQuery(query, params);
|
|
return result.rows._array;
|
|
} else {
|
|
// Fallback para AsyncStorage
|
|
const allDeliveries = await getAllDeliveriesFromAsyncStorage();
|
|
return status ? allDeliveries.filter(d => d.status === status) : allDeliveries;
|
|
}
|
|
} catch (error) {
|
|
console.error("Erro ao obter entregas:", error);
|
|
return [];
|
|
}
|
|
};
|
|
```
|
|
|
|
#### Salvar Entrega
|
|
```typescript
|
|
export const saveDelivery = async (delivery: any): Promise<boolean> => {
|
|
try {
|
|
const {
|
|
id, client, address, coordinates, status,
|
|
scheduled_time, completed_time, signature, photos, notes, sync_status
|
|
} = delivery;
|
|
|
|
if (usingSQLite) {
|
|
const existingDelivery = await getDeliveryById(id);
|
|
|
|
if (existingDelivery) {
|
|
// Atualizar entrega existente
|
|
await executeQuery(
|
|
`UPDATE deliveries SET
|
|
client = ?, address = ?, coordinates = ?, status = ?,
|
|
scheduled_time = ?, completed_time = ?, signature = ?,
|
|
photos = ?, notes = ?, sync_status = ?
|
|
WHERE id = ?`,
|
|
[client, address, JSON.stringify(coordinates), status,
|
|
scheduled_time, completed_time, signature,
|
|
JSON.stringify(photos), notes, sync_status, id]
|
|
);
|
|
} else {
|
|
// Inserir nova entrega
|
|
await executeQuery(
|
|
`INSERT INTO deliveries (
|
|
id, client, address, coordinates, status,
|
|
scheduled_time, completed_time, signature, photos, notes, sync_status
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
[id, client, address, JSON.stringify(coordinates), status,
|
|
scheduled_time, completed_time, signature,
|
|
JSON.stringify(photos), notes, sync_status]
|
|
);
|
|
}
|
|
} else {
|
|
// AsyncStorage fallback
|
|
await AsyncStorage.setItem(`${DELIVERIES_KEY}${id}`, JSON.stringify(delivery));
|
|
}
|
|
|
|
return true;
|
|
} catch (error) {
|
|
console.error("Erro ao salvar entrega:", error);
|
|
return false;
|
|
}
|
|
};
|
|
```
|
|
|
|
#### Atualizar Status
|
|
```typescript
|
|
export const updateDeliveryStatus = async (
|
|
id: string,
|
|
status: string,
|
|
completedTime?: number
|
|
): Promise<boolean> => {
|
|
try {
|
|
if (usingSQLite) {
|
|
let query = "UPDATE deliveries SET status = ?, sync_status = ?";
|
|
const params: any[] = [status, "pending"];
|
|
|
|
if (completedTime) {
|
|
query += ", completed_time = ?";
|
|
params.push(completedTime);
|
|
}
|
|
|
|
query += " WHERE id = ?";
|
|
params.push(id);
|
|
|
|
await executeQuery(query, params);
|
|
} else {
|
|
// AsyncStorage fallback
|
|
const deliveryJson = await AsyncStorage.getItem(`${DELIVERIES_KEY}${id}`);
|
|
if (deliveryJson) {
|
|
const delivery = JSON.parse(deliveryJson);
|
|
delivery.status = status;
|
|
delivery.sync_status = "pending";
|
|
if (completedTime) {
|
|
delivery.completed_time = completedTime;
|
|
}
|
|
await AsyncStorage.setItem(`${DELIVERIES_KEY}${id}`, JSON.stringify(delivery));
|
|
}
|
|
}
|
|
return true;
|
|
} catch (error) {
|
|
console.error("Erro ao atualizar status da entrega:", error);
|
|
return false;
|
|
}
|
|
};
|
|
```
|
|
|
|
### 3. Operações de Sincronização
|
|
|
|
#### Buscar Entregas Não Sincronizadas
|
|
```typescript
|
|
export const getUnsyncedDeliveries = async (): Promise<any[]> => {
|
|
try {
|
|
if (usingSQLite) {
|
|
const result = await executeQuery(
|
|
"SELECT * FROM deliveries WHERE sync_status = ?",
|
|
["pending"]
|
|
);
|
|
return result.rows._array;
|
|
} else {
|
|
const allDeliveries = await getAllDeliveriesFromAsyncStorage();
|
|
return allDeliveries.filter(d => d.sync_status === "pending");
|
|
}
|
|
} catch (error) {
|
|
console.error("Erro ao obter entregas não sincronizadas:", error);
|
|
return [];
|
|
}
|
|
};
|
|
```
|
|
|
|
#### Marcar como Sincronizada
|
|
```typescript
|
|
export const markDeliveryAsSynced = async (id: string): Promise<boolean> => {
|
|
try {
|
|
if (usingSQLite) {
|
|
await executeQuery(
|
|
"UPDATE deliveries SET sync_status = ? WHERE id = ?",
|
|
["synced", id]
|
|
);
|
|
} else {
|
|
const deliveryJson = await AsyncStorage.getItem(`${DELIVERIES_KEY}${id}`);
|
|
if (deliveryJson) {
|
|
const delivery = JSON.parse(deliveryJson);
|
|
delivery.sync_status = "synced";
|
|
await AsyncStorage.setItem(`${DELIVERIES_KEY}${id}`, JSON.stringify(delivery));
|
|
}
|
|
}
|
|
return true;
|
|
} catch (error) {
|
|
console.error("Erro ao marcar entrega como sincronizada:", error);
|
|
return false;
|
|
}
|
|
};
|
|
```
|
|
|
|
### 4. Operações de Configurações
|
|
|
|
#### Obter Configuração
|
|
```typescript
|
|
export const getSetting = async (key: string): Promise<string | null> => {
|
|
try {
|
|
if (usingSQLite) {
|
|
const result = await executeQuery(
|
|
"SELECT value FROM settings WHERE key = ?",
|
|
[key]
|
|
);
|
|
if (result.rows.length > 0) {
|
|
return result.rows._array[0].value;
|
|
}
|
|
return null;
|
|
} else {
|
|
return await AsyncStorage.getItem(`${SETTINGS_KEY}${key}`);
|
|
}
|
|
} catch (error) {
|
|
console.error("Erro ao obter configuração:", error);
|
|
return null;
|
|
}
|
|
};
|
|
```
|
|
|
|
#### Salvar Configuração
|
|
```typescript
|
|
export const saveSetting = async (key: string, value: string): Promise<boolean> => {
|
|
try {
|
|
if (usingSQLite) {
|
|
await executeQuery(
|
|
"INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)",
|
|
[key, value]
|
|
);
|
|
} else {
|
|
await AsyncStorage.setItem(`${SETTINGS_KEY}${key}`, value);
|
|
}
|
|
return true;
|
|
} catch (error) {
|
|
console.error("Erro ao salvar configuração:", error);
|
|
return false;
|
|
}
|
|
};
|
|
```
|
|
|
|
## Implementação AsyncStorage (Fallback)
|
|
|
|
### Funções Auxiliares
|
|
```typescript
|
|
async function getAllUsersFromAsyncStorage() {
|
|
try {
|
|
const keys = await AsyncStorage.getAllKeys();
|
|
const userKeys = keys.filter(key => key.startsWith(USERS_KEY));
|
|
const userItems = await AsyncStorage.multiGet(userKeys);
|
|
return userItems.map(([_, value]) => JSON.parse(value));
|
|
} catch (error) {
|
|
console.error("Erro ao obter usuários do AsyncStorage:", error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
async function getAllDeliveriesFromAsyncStorage() {
|
|
try {
|
|
const keys = await AsyncStorage.getAllKeys();
|
|
const deliveryKeys = keys.filter(key => key.startsWith(DELIVERIES_KEY));
|
|
const deliveryItems = await AsyncStorage.multiGet(deliveryKeys);
|
|
return deliveryItems.map(([_, value]) => JSON.parse(value));
|
|
} catch (error) {
|
|
console.error("Erro ao obter entregas do AsyncStorage:", error);
|
|
return [];
|
|
}
|
|
}
|
|
```
|
|
|
|
### Simulação de Queries SQL
|
|
```typescript
|
|
async function executeAsyncStorageQuery(query: string, params: any[]): Promise<any> {
|
|
try {
|
|
if (query.toUpperCase().startsWith("SELECT")) {
|
|
if (query.includes("FROM users")) {
|
|
const allUsers = await getAllUsersFromAsyncStorage();
|
|
return { rows: { _array: allUsers } };
|
|
} else if (query.includes("FROM deliveries")) {
|
|
const allDeliveries = await getAllDeliveriesFromAsyncStorage();
|
|
return { rows: { _array: allDeliveries } };
|
|
} else if (query.includes("FROM settings")) {
|
|
const key = params[0];
|
|
const value = await AsyncStorage.getItem(`${SETTINGS_KEY}${key}`);
|
|
return { rows: { _array: value ? [{ value }] : [] } };
|
|
}
|
|
} else if (query.toUpperCase().startsWith("INSERT") || query.toUpperCase().startsWith("UPDATE")) {
|
|
// Implementação simplificada para INSERT/UPDATE
|
|
return { rowsAffected: 1 };
|
|
}
|
|
return { rowsAffected: 1 };
|
|
} catch (error) {
|
|
console.error("Erro na operação do AsyncStorage:", error);
|
|
throw error;
|
|
}
|
|
}
|
|
```
|
|
|
|
## Operações de Entregas Offline
|
|
|
|
### Salvar Entrega Offline
|
|
```typescript
|
|
export const saveOfflineDelivery = async (delivery: any): Promise<boolean> => {
|
|
try {
|
|
if (usingSQLite) {
|
|
await executeQuery(
|
|
`INSERT OR REPLACE INTO deliveries_offline (
|
|
id, outId, transactionId, deliveryDate, receiverDoc, receiverName,
|
|
lat, lng, broken, devolution, reasonDevolution, deliveryImages, userId, sync_status
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
[
|
|
delivery.id, delivery.outId, delivery.transactionId, delivery.deliveryDate,
|
|
delivery.receiverDoc, delivery.receiverName, delivery.lat, delivery.lng,
|
|
delivery.broken ? 1 : 0, delivery.devolution ? 1 : 0,
|
|
delivery.reasonDevolution, JSON.stringify(delivery.deliveryImages),
|
|
delivery.userId, delivery.sync_status || 'pending'
|
|
]
|
|
);
|
|
} else {
|
|
await AsyncStorage.setItem(
|
|
`@TruckDelivery:deliveries_offline:${delivery.id}`,
|
|
JSON.stringify(delivery)
|
|
);
|
|
}
|
|
return true;
|
|
} catch (error) {
|
|
console.error('Erro ao salvar entrega offline:', error);
|
|
return false;
|
|
}
|
|
};
|
|
```
|
|
|
|
### Buscar Entregas Offline
|
|
```typescript
|
|
export const getOfflineDeliveries = async (): Promise<any[]> => {
|
|
try {
|
|
if (usingSQLite) {
|
|
const result = await executeQuery(
|
|
'SELECT * FROM deliveries_offline WHERE sync_status = ?',
|
|
['pending']
|
|
);
|
|
return result.rows._array.map((row: any) => ({
|
|
...row,
|
|
deliveryImages: row.deliveryImages ? JSON.parse(row.deliveryImages) : [],
|
|
broken: !!row.broken,
|
|
devolution: !!row.devolution,
|
|
}));
|
|
} else {
|
|
const keys = await AsyncStorage.getAllKeys();
|
|
const offlineKeys = keys.filter(key =>
|
|
key.startsWith('@TruckDelivery:deliveries_offline:')
|
|
);
|
|
const items = await AsyncStorage.multiGet(offlineKeys);
|
|
return items.map(([_, value]) => JSON.parse(value))
|
|
.filter(d => d.sync_status === 'pending');
|
|
}
|
|
} catch (error) {
|
|
console.error('Erro ao buscar entregas offline:', error);
|
|
return [];
|
|
}
|
|
};
|
|
```
|
|
|
|
## Otimizações e Índices
|
|
|
|
### Índices Recomendados
|
|
```sql
|
|
-- Índices para melhorar performance
|
|
CREATE INDEX IF NOT EXISTS idx_deliveries_status ON deliveries(status);
|
|
CREATE INDEX IF NOT EXISTS idx_deliveries_sync_status ON deliveries(sync_status);
|
|
CREATE INDEX IF NOT EXISTS idx_deliveries_scheduled_time ON deliveries(scheduled_time);
|
|
CREATE INDEX IF NOT EXISTS idx_deliveries_offline_sync_status ON deliveries_offline(sync_status);
|
|
CREATE INDEX IF NOT EXISTS idx_settings_key ON settings(key);
|
|
```
|
|
|
|
### Limpeza de Dados Antigos
|
|
```typescript
|
|
export const cleanupOldData = async (daysOld: number = 30): Promise<void> => {
|
|
try {
|
|
const cutoffDate = Date.now() - (daysOld * 24 * 60 * 60 * 1000);
|
|
|
|
if (usingSQLite) {
|
|
// Remover entregas antigas já sincronizadas
|
|
await executeQuery(
|
|
"DELETE FROM deliveries WHERE completed_time < ? AND sync_status = ?",
|
|
[cutoffDate, "synced"]
|
|
);
|
|
|
|
// Remover entregas offline antigas
|
|
await executeQuery(
|
|
"DELETE FROM deliveries_offline WHERE deliveryDate < ? AND sync_status = ?",
|
|
[cutoffDate, "synced"]
|
|
);
|
|
}
|
|
} catch (error) {
|
|
console.error("Erro na limpeza de dados:", error);
|
|
}
|
|
};
|
|
```
|
|
|
|
## Monitoramento e Logs
|
|
|
|
### Informações de Armazenamento
|
|
```typescript
|
|
export const storageInfo = {
|
|
type: usingSQLite ? "SQLite" : "AsyncStorage",
|
|
isUsingSQLite: usingSQLite,
|
|
};
|
|
|
|
// Função para obter estatísticas do banco
|
|
export const getDatabaseStats = async (): Promise<any> => {
|
|
try {
|
|
if (usingSQLite) {
|
|
const deliveriesResult = await executeQuery("SELECT COUNT(*) as count FROM deliveries");
|
|
const offlineResult = await executeQuery("SELECT COUNT(*) as count FROM deliveries_offline");
|
|
const unsyncedResult = await executeQuery(
|
|
"SELECT COUNT(*) as count FROM deliveries WHERE sync_status = ?",
|
|
["pending"]
|
|
);
|
|
|
|
return {
|
|
totalDeliveries: deliveriesResult.rows._array[0].count,
|
|
offlineDeliveries: offlineResult.rows._array[0].count,
|
|
unsyncedDeliveries: unsyncedResult.rows._array[0].count,
|
|
storageType: "SQLite"
|
|
};
|
|
} else {
|
|
const keys = await AsyncStorage.getAllKeys();
|
|
return {
|
|
totalKeys: keys.length,
|
|
storageType: "AsyncStorage"
|
|
};
|
|
}
|
|
} catch (error) {
|
|
console.error("Erro ao obter estatísticas:", error);
|
|
return null;
|
|
}
|
|
};
|
|
```
|
|
|
|
## Considerações para Sincronização Offline
|
|
|
|
### 1. Estrutura para Sincronização Incremental
|
|
```sql
|
|
-- Adicionar campos de controle de versão
|
|
ALTER TABLE deliveries ADD COLUMN version INTEGER DEFAULT 1;
|
|
ALTER TABLE deliveries ADD COLUMN last_modified INTEGER;
|
|
ALTER TABLE deliveries ADD COLUMN sync_timestamp INTEGER;
|
|
```
|
|
|
|
### 2. Tabela de Log de Sincronização
|
|
```sql
|
|
CREATE TABLE IF NOT EXISTS sync_log (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
table_name TEXT,
|
|
record_id TEXT,
|
|
action TEXT,
|
|
timestamp INTEGER,
|
|
success INTEGER,
|
|
error_message TEXT
|
|
);
|
|
```
|
|
|
|
### 3. Controle de Conflitos
|
|
```sql
|
|
CREATE TABLE IF NOT EXISTS sync_conflicts (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
table_name TEXT,
|
|
record_id TEXT,
|
|
local_data TEXT,
|
|
server_data TEXT,
|
|
resolution TEXT,
|
|
timestamp INTEGER
|
|
);
|
|
```
|
|
|
|
Esta documentação fornece uma visão completa da estrutura do banco de dados SQLite utilizado pelo aplicativo, incluindo todas as tabelas, operações e considerações para implementação de sincronização offline eficiente.
|