add project files
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
18
.classpath
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<classpath>
|
||||||
|
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
|
||||||
|
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
|
||||||
|
<classpathentry exported="true" kind="lib" path="lib/postgresql-8.2-512.jdbc3.jar"/>
|
||||||
|
<classpathentry exported="true" kind="lib" path="lib/joda-time-2.5.jar" sourcepath="W:/App Java/workspace/SARAndroid/lib/joda-time-2.5.jar">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="javadoc_location" value="jar:file:/C:/Documents and Settings/Henrique Philippi/workspace/joda-time-2.5/joda-time-2.5-javadoc.jar!/"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry exported="true" kind="lib" path="lib/commons-net-3.3.jar"/>
|
||||||
|
<classpathentry exported="true" kind="lib" path="lib/commons-net-3.3-sources.jar"/>
|
||||||
|
<classpathentry exported="true" kind="lib" path="lib/commons-net-examples-3.3.jar"/>
|
||||||
|
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
|
||||||
|
<classpathentry kind="src" path="src"/>
|
||||||
|
<classpathentry kind="src" path="gen"/>
|
||||||
|
<classpathentry kind="output" path="bin/classes"/>
|
||||||
|
</classpath>
|
||||||
22
.gitignore
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# Build output
|
||||||
|
bin/
|
||||||
|
gen/
|
||||||
|
|
||||||
|
# Eclipse metadata
|
||||||
|
.metadata/
|
||||||
|
.settings/
|
||||||
|
|
||||||
|
# Build artifacts
|
||||||
|
resources.ap_
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# BMad output (opcional — remova se quiser versionar a documentação gerada)
|
||||||
|
# _bmad-output/
|
||||||
|
# docs/
|
||||||
|
|
||||||
|
# Temporários
|
||||||
|
*.tmp
|
||||||
|
*.bak
|
||||||
|
|
||||||
|
# Claude Code (dados locais da sessão)
|
||||||
|
.claude/
|
||||||
33
.project
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<projectDescription>
|
||||||
|
<name>SARAndroid</name>
|
||||||
|
<comment></comment>
|
||||||
|
<projects>
|
||||||
|
</projects>
|
||||||
|
<buildSpec>
|
||||||
|
<buildCommand>
|
||||||
|
<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>com.android.ide.eclipse.adt.ApkBuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
</buildSpec>
|
||||||
|
<natures>
|
||||||
|
<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
|
||||||
|
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||||
|
</natures>
|
||||||
|
</projectDescription>
|
||||||
137
AndroidManifest.xml
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="br.com.jcsinformatica.sarandroid"
|
||||||
|
android:versionCode="156"
|
||||||
|
android:versionName="2.8.1" >
|
||||||
|
|
||||||
|
<uses-sdk
|
||||||
|
android:minSdkVersion="19"
|
||||||
|
android:targetSdkVersion="35" />
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||||
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:hardwareAccelerated="true"
|
||||||
|
android:icon="@drawable/ic_launcher"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:largeHeap="true"
|
||||||
|
android:theme="@style/AppTheme"
|
||||||
|
android:debuggable="false">
|
||||||
|
<activity
|
||||||
|
android:name="br.com.jcsinformatica.sarandroid.SplashScreen"
|
||||||
|
android:exported="true"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:screenOrientation="portrait" >
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name="br.com.jcsinformatica.sarandroid.LoginActivity"
|
||||||
|
android:label="@string/app_release"
|
||||||
|
android:screenOrientation="portrait" >
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name="br.com.jcsinformatica.sarandroid.MainActivity"
|
||||||
|
android:label="@string/app_release"
|
||||||
|
android:screenOrientation="portrait" >
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name="br.com.jcsinformatica.sarandroid.ConfigActivity"
|
||||||
|
android:label="@string/app_release"
|
||||||
|
android:screenOrientation="portrait" >
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name="br.com.jcsinformatica.sarandroid.comunicacao.ComunicaActivity"
|
||||||
|
android:label="@string/app_release"
|
||||||
|
android:screenOrientation="portrait" >
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name="br.com.jcsinformatica.sarandroid.pedido.BrowsePedido"
|
||||||
|
android:label="@string/app_release"
|
||||||
|
android:screenOrientation="portrait" >
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name="br.com.jcsinformatica.sarandroid.pedido.UpdatePedidoActivity"
|
||||||
|
android:label="@string/app_release"
|
||||||
|
android:screenOrientation="portrait" >
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name="br.com.jcsinformatica.sarandroid.pedido.UpdatePedItemActivity"
|
||||||
|
android:label="@string/app_release"
|
||||||
|
android:screenOrientation="portrait" >
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name="br.com.jcsinformatica.sarandroid.municipio.BrowseMunicipio"
|
||||||
|
android:label="@string/app_release"
|
||||||
|
android:screenOrientation="portrait" >
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name="br.com.jcsinformatica.sarandroid.produto.BrowseProduto"
|
||||||
|
android:label="@string/app_release"
|
||||||
|
android:screenOrientation="portrait" >
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name="br.com.jcsinformatica.sarandroid.cliente.BrowseCliente"
|
||||||
|
android:label="@string/app_release"
|
||||||
|
android:screenOrientation="portrait" >
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name="br.com.jcsinformatica.sarandroid.cliente.UpdateCliente"
|
||||||
|
android:label="@string/app_release"
|
||||||
|
android:screenOrientation="portrait" >
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name="br.com.jcsinformatica.sarandroid.consulta.pedido.BrowsePedidoConsulta"
|
||||||
|
android:label="@string/app_release"
|
||||||
|
android:screenOrientation="portrait" >
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name="br.com.jcsinformatica.sarandroid.consulta.vendas.ConsultaVendasActivity"
|
||||||
|
android:label="@string/app_release"
|
||||||
|
android:screenOrientation="portrait" >
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name="br.com.jcsinformatica.sarandroid.cliente.BrowseCTR"
|
||||||
|
android:label="@string/app_release"
|
||||||
|
android:screenOrientation="portrait" >
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name="br.com.jcsinformatica.sarandroid.produto.UpdateProduto"
|
||||||
|
android:label="@string/app_release"
|
||||||
|
android:screenOrientation="portrait" >
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name="br.com.jcsinformatica.sarandroid.produto.FotosProduto"
|
||||||
|
android:label="@string/app_release"
|
||||||
|
android:screenOrientation="portrait" >
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name="br.com.jcsinformatica.sarandroid.pedido.BrowseHistorico"
|
||||||
|
android:label="@string/app_release"
|
||||||
|
android:screenOrientation="portrait" >
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<provider
|
||||||
|
android:name="android.support.v4.content.FileProvider"
|
||||||
|
android:authorities="br.com.jcsinformatica.sarandroid.fileprovider"
|
||||||
|
android:exported="false"
|
||||||
|
android:grantUriPermissions="true">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
|
android:resource="@xml/provider_paths"/>
|
||||||
|
</provider>
|
||||||
|
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
||||||
59
CLAUDE.md
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
SAR Android (Sistema de Atendimento ao Representante) is a legacy Java Android sales-representative app. It allows sales reps to manage clients, products, and orders offline, then sync with a central PostgreSQL server.
|
||||||
|
|
||||||
|
**Language:** Java (no Kotlin)
|
||||||
|
**Build System:** Eclipse ADT (pre-Gradle) — no `build.gradle`; configured via `.project`, `.classpath`, and `project.properties`
|
||||||
|
**Min SDK:** 19 | **Target SDK:** 35
|
||||||
|
**Root package:** `br.com.jcsinformatica.sarandroid`
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
This is an Eclipse ADT project, not an Android Studio/Gradle project. There are no `gradlew` commands.
|
||||||
|
|
||||||
|
- Open in **Eclipse with ADT plugin** or import into Android Studio using "Import Eclipse ADT Project"
|
||||||
|
- SDK target is set in `project.properties` (`target=android-23`)
|
||||||
|
- ProGuard config exists in `proguard-project.txt` but is disabled
|
||||||
|
- Lint suppressions are in `lint.xml`
|
||||||
|
|
||||||
|
There are no automated tests (no `test/` or `androidTest/` directories, no JUnit/Espresso setup).
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Entry Flow
|
||||||
|
`SplashScreen` → `LoginActivity` → `MainActivity` (expandable menu) → feature Activities
|
||||||
|
|
||||||
|
### Key Classes
|
||||||
|
- **`Global`** — static singleton holding runtime state: current `Empresa`, `Pedido`, `ItemPedido`. Call `Global.getEmpresa()` (throws `WarningException` if unset).
|
||||||
|
- **`GlobalActivity`** — base `Activity` subclass for all post-login screens; reads company name from `Global` and sets the title.
|
||||||
|
- **`DatabaseHelper`** — `SQLiteOpenHelper` managing the local SQLite schema.
|
||||||
|
- **`ConnectionManager`** — manages PostgreSQL JDBC connections (20s timeout); used for remote sync.
|
||||||
|
|
||||||
|
### Package Structure
|
||||||
|
|
||||||
|
| Package | Role |
|
||||||
|
|---------|------|
|
||||||
|
| `vo/` | Plain value objects (models): `Produto`, `Cliente`, `Pedido`, `ItemPedido`, `Empresa`, `Representante`, etc. |
|
||||||
|
| `database/` | SQLite DAOs (named `*DB.java` or `*BD.java`): CRUD + sync logic with MD5 change detection |
|
||||||
|
| `postgres/` | PostgreSQL JDBC operations mirroring the DAO layer (`*PGSQL.java`) |
|
||||||
|
| `uimodels/` | Custom list adapters and UI helpers |
|
||||||
|
| `pedido/` | Order/quote Activities (`UpdatePedidoActivity`, `UpdatePedItemActivity`, `BrowsePedido`, etc.) |
|
||||||
|
| `produto/` | Product Activities (`BrowseProduto`, `UpdateProduto`, `FotosProduto`) |
|
||||||
|
| `cliente/` | Client Activities (`BrowseCliente`, `UpdateCliente`) |
|
||||||
|
| `comunicacao/` | `ComunicaActivity` — orchestrates full data sync with the PostgreSQL server |
|
||||||
|
| `consulta/` | Query/report Activities (`BrowsePedidoConsulta`, `ConsultaVendasActivity`) |
|
||||||
|
|
||||||
|
### Dual-Database Sync Pattern
|
||||||
|
All persistent data lives in local **SQLite** and is periodically synced to/from **PostgreSQL** over direct JDBC. The pattern per entity:
|
||||||
|
1. `*DB.java` — reads/writes SQLite, computes MD5 checksums for change detection
|
||||||
|
2. `*PGSQL.java` — mirrors inserts/updates to the PostgreSQL server
|
||||||
|
3. `ComunicaActivity` acts as the sync orchestrator and provides a callback interface for progress updates
|
||||||
|
|
||||||
|
### Third-Party Libraries (in `libs/`)
|
||||||
|
- `postgresql-8.2-jdbc3.jar` — PostgreSQL JDBC driver
|
||||||
|
- `joda-time-2.5.jar` — date/time calculations
|
||||||
|
- `commons-net-3.3.jar` — FTP support
|
||||||
@@ -0,0 +1,188 @@
|
|||||||
|
# Story 1.1: Migração do Schema SQLite para Suporte a Acréscimo
|
||||||
|
|
||||||
|
Status: done
|
||||||
|
|
||||||
|
## Story
|
||||||
|
|
||||||
|
Como desenvolvedor do sistema,
|
||||||
|
quero migrar o schema SQLite de v40 para v41 adicionando `tx_acrescimo` na tabela `formapag`,
|
||||||
|
para que as taxas de acréscimo possam ser armazenadas localmente sem afetar dados existentes.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
1. **Dado** que o app está sendo atualizado em um dispositivo com schema SQLite v40
|
||||||
|
**Quando** `DatabaseHelper.onUpgrade()` for executado
|
||||||
|
**Então** a coluna `tx_acrescimo REAL DEFAULT 0` é adicionada à tabela `formapag`
|
||||||
|
**E** todos os registros existentes de formas de pagamento são preservados com `tx_acrescimo = 0`
|
||||||
|
|
||||||
|
2. **Dado** que `onUpgrade()` é chamado com `oldVersion >= 41`
|
||||||
|
**Quando** o bloco de migração v41 for avaliado
|
||||||
|
**Então** a migração é ignorada (guard `if (oldVersion < 41)`) sem erro
|
||||||
|
|
||||||
|
3. **Dado** que `dbVersao` em `DatabaseHelper`
|
||||||
|
**Quando** a migração for aplicada
|
||||||
|
**Então** `dbVersao = 41`
|
||||||
|
|
||||||
|
4. **Dado** que o VO `FormaPagamento` é instanciado após a migração
|
||||||
|
**Quando** `FormaPagamentoDB` preencher o objeto a partir do SQLite
|
||||||
|
**Então** o campo `txAcrescimo` está acessível no VO, com valor `0.0` para registros antigos
|
||||||
|
|
||||||
|
## Tasks / Subtasks
|
||||||
|
|
||||||
|
- [x] Task 1: Incrementar versão e adicionar migração em DatabaseHelper (AC: 1, 2, 3)
|
||||||
|
- [x] 1.1 - Alterar `dbVersao` de `40` para `41` na linha 10 de `DatabaseHelper.java`
|
||||||
|
- [x] 1.2 - Adicionar bloco de migração em `onUpgrade()` após o bloco v40 existente (linhas 700-703):
|
||||||
|
```java
|
||||||
|
if (oldVersion < 41) {
|
||||||
|
db.execSQL("ALTER TABLE formapag ADD COLUMN tx_acrescimo REAL DEFAULT 0;");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- [x] 1.3 - Atualizar o `CREATE TABLE formapag` em `onCreate()` (linhas 82-95) para incluir `tx_acrescimo REAL DEFAULT 0` antes do fechamento dos parênteses (após `libera_credito INT`)
|
||||||
|
|
||||||
|
- [x] Task 2: Adicionar campo `txAcrescimo` ao Value Object FormaPagamento (AC: 4)
|
||||||
|
- [x] 2.1 - Adicionar campo `private double txAcrescimo;` em `FormaPagamento.java`
|
||||||
|
- [x] 2.2 - Adicionar getter `getTxAcrescimo()` e setter `setTxAcrescimo(double txAcrescimo)`
|
||||||
|
- [x] 2.3 - Inicializar `txAcrescimo = 0.0` (padrão implícito para double, mas documentar intenção)
|
||||||
|
|
||||||
|
- [x] Task 3: Atualizar FormaPagamentoDB para incluir `tx_acrescimo` em todas as operações (AC: 4)
|
||||||
|
- [x] 3.1 - Atualizar INSERT em `FormaPagamentoDB.java`: adicionar `tx_acrescimo` na lista de colunas e `formaPagamento.getTxAcrescimo()` nos valores (linha ~68)
|
||||||
|
- [x] 3.2 - Atualizar UPDATE em `FormaPagamentoDB.java`: adicionar `tx_acrescimo = ?` na cláusula SET e o valor correspondente nos ContentValues (linha ~89-103)
|
||||||
|
- [x] 3.3 - Atualizar todos os SELECT em `FormaPagamentoDB.java` que mapeiam ResultSet para o VO: adicionar leitura de `tx_acrescimo` e chamar `setTxAcrescimo()` com `cursor.getDouble(colIndex)` — verificar linhas ~115-116, ~163-164, ~227, ~255, ~282
|
||||||
|
- [x] 3.4 - Usar `cursor.isNull(colIndex) ? 0.0 : cursor.getDouble(colIndex)` ao ler `tx_acrescimo` para tratar NULL defensivamente (NFR3)
|
||||||
|
|
||||||
|
## Dev Notes
|
||||||
|
|
||||||
|
### Escopo desta história
|
||||||
|
|
||||||
|
Esta história cobre APENAS migração de schema SQLite e atualização do VO/DAO local (FR3, FR4).
|
||||||
|
A sincronização com PostgreSQL (`gestao.formapag.acresc` → `tx_acrescimo`) é escopo da **Story 1.2**.
|
||||||
|
A UI do pedido e cálculo são escopo do **Epic 2**.
|
||||||
|
|
||||||
|
### Arquivos a modificar
|
||||||
|
|
||||||
|
| Arquivo | Caminho | O que muda |
|
||||||
|
|---------|---------|------------|
|
||||||
|
| `DatabaseHelper.java` | `src/br/com/jcsinformatica/sarandroid/database/` | `dbVersao` 40→41, `onCreate()` CREATE TABLE, `onUpgrade()` bloco v41 |
|
||||||
|
| `FormaPagamento.java` | `src/br/com/jcsinformatica/sarandroid/vo/` | Novo campo `txAcrescimo`, getter/setter |
|
||||||
|
| `FormaPagamentoDB.java` | `src/br/com/jcsinformatica/sarandroid/database/` | INSERT, UPDATE, todos os SELECTs |
|
||||||
|
|
||||||
|
**NÃO modificar nesta história:**
|
||||||
|
- `FormaPagamentoPGSQL.java` — escopo da Story 1.2
|
||||||
|
- `ComunicaActivity.java` — escopo da Story 1.2
|
||||||
|
- Qualquer Activity ou layout de pedido — escopo do Epic 2
|
||||||
|
|
||||||
|
### Estado atual do código (verificado em análise)
|
||||||
|
|
||||||
|
**DatabaseHelper.java linha 10:**
|
||||||
|
```java
|
||||||
|
private static int dbVersao = 40;
|
||||||
|
```
|
||||||
|
|
||||||
|
**formapag CREATE TABLE atual (linhas 82-95):**
|
||||||
|
```sql
|
||||||
|
CREATE TABLE formapag (
|
||||||
|
id_formapag INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
id_empresa INT NOT NULL,
|
||||||
|
id_erp INT NOT NULL,
|
||||||
|
codigo INT NOT NULL,
|
||||||
|
descricao TEXT NOT NULL,
|
||||||
|
ativo INT NOT NULL,
|
||||||
|
parcelas INT NOT NULL,
|
||||||
|
desco_perc REAL NOT NULL,
|
||||||
|
md5 TEXT NOT NULL,
|
||||||
|
vl_ped_min REAL ,
|
||||||
|
libera_credito INT ,
|
||||||
|
FOREIGN KEY ( id_empresa ) REFERENCES empresa ( id_empresa ),
|
||||||
|
UNIQUE ( id_empresa, id_erp ) ON CONFLICT ABORT
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Último bloco onUpgrade() — v40 (linhas 700-703):**
|
||||||
|
```java
|
||||||
|
if (oldVersion < 40){
|
||||||
|
db.execSQL("ALTER TABLE empresa ADD COLUMN id_empresa_prod INT NOT NULL DEFAULT 0;");
|
||||||
|
db.execSQL("ALTER TABLE empresa ADD COLUMN id_empresa_grup INT NOT NULL DEFAULT 0;");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**FormaPagamento.java — campos atuais:**
|
||||||
|
`id`, `idErp`, `codigo`, `descricao`, `ativo` (boolean), `parcelas`, `descontoPerc`, `md5`, `vlPedMin`, `liberaCredito`
|
||||||
|
|
||||||
|
### Regras críticas do projeto
|
||||||
|
|
||||||
|
- **Sem testes automatizados** — o projeto não tem JUnit/Espresso. Validação é manual via dispositivo/emulador. Não criar arquivos de teste.
|
||||||
|
- **Sem Kotlin** — somente Java puro
|
||||||
|
- **Sem Gradle** — projeto Eclipse ADT, não criar build.gradle
|
||||||
|
- **guard obrigatório**: `if (oldVersion < 41)` — idempotência da migração (NFR4)
|
||||||
|
- **NULL defensivo**: `cursor.isNull()` antes de `cursor.getDouble()` em `tx_acrescimo` (NFR3)
|
||||||
|
- **DEFAULT 0 na coluna**: garante que registros existentes não ficam com NULL após ALTER TABLE (FR4)
|
||||||
|
- Strings visíveis ao usuário em `res/values/strings.xml` — esta história não tem UI, regra não se aplica aqui
|
||||||
|
- `onUpgrade()` sem guard de versão **apaga dados de produção** — nunca omitir o `if (oldVersion < N)`
|
||||||
|
|
||||||
|
### Tratamento de NULL
|
||||||
|
|
||||||
|
SQLite `ALTER TABLE ... ADD COLUMN` com `DEFAULT 0` preenche automaticamente os registros existentes com `0`, **mas** registros inseridos antes da migração com `NULL` explícito precisam ser tratados no código. Usar sempre:
|
||||||
|
```java
|
||||||
|
double txAcrescimo = cursor.isNull(colIndex) ? 0.0 : cursor.getDouble(colIndex);
|
||||||
|
formaPagamento.setTxAcrescimo(txAcrescimo);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Mapeamento PostgreSQL → SQLite (referência para Story 1.2)
|
||||||
|
|
||||||
|
A coluna PostgreSQL é `gestao.formapag.acresc` → mapeia para `formapag.tx_acrescimo` no SQLite.
|
||||||
|
O `FormaPagamentoPGSQL.java` precisa ser atualizado na próxima história para incluir este mapeamento.
|
||||||
|
|
||||||
|
### Verificação manual após implementação
|
||||||
|
|
||||||
|
1. Instalar app em dispositivo/emulador com banco v40 existente → verificar que `formapag` ganha coluna `tx_acrescimo` sem perda de dados
|
||||||
|
2. Instalar app em dispositivo limpo (fresh install) → verificar que `formapag` é criado com `tx_acrescimo`
|
||||||
|
3. Reinstalar app (v41 → v41) → verificar que não ocorre erro (idempotência)
|
||||||
|
|
||||||
|
### Project Structure Notes
|
||||||
|
|
||||||
|
- Estrutura Eclipse ADT: código em `src/`, recursos em `res/`, libs em `lib/`
|
||||||
|
- Pacote raiz: `br.com.jcsinformatica.sarandroid`
|
||||||
|
- Nomenclatura DAO SQLite: `*DB.java` — `FormaPagamentoDB.java` já segue o padrão
|
||||||
|
|
||||||
|
### References
|
||||||
|
|
||||||
|
- [Source: _bmad-output/project-context.md#Schema SQLite] — regras de migração, dbVersao, guard obrigatório
|
||||||
|
- [Source: _bmad-output/project-context.md#Regras Críticas] — proibições absolutas (sem Kotlin, sem Gradle, sem testes)
|
||||||
|
- [Source: _bmad-output/planning-artifacts/epics.md#Story 1.1] — ACs e requisitos funcionais FR3, FR4
|
||||||
|
- [Source: _bmad-output/planning-artifacts/prd.md#Schema e Migração] — FR3, FR4, NFR3, NFR4
|
||||||
|
- [Source: src/br/com/jcsinformatica/sarandroid/database/DatabaseHelper.java] — estrutura atual, dbVersao=40
|
||||||
|
- [Source: src/br/com/jcsinformatica/sarandroid/vo/FormaPagamento.java] — campos atuais do VO
|
||||||
|
- [Source: src/br/com/jcsinformatica/sarandroid/database/FormaPagamentoDB.java] — INSERT linha ~68, UPDATE linha ~89, SELECTs linhas ~115, ~163, ~227, ~255, ~282
|
||||||
|
|
||||||
|
### Review Findings
|
||||||
|
|
||||||
|
- [x] [Review][Defer] `FormaPagamentoPGSQL` não lê `acresc` do PostgreSQL — `tx_acrescimo` sempre sincroniza como 0.0 até Story 1.2 ser implementada [postgres/FormaPagamentoPGSQL.java] — deferred, escopo da Story 1.2
|
||||||
|
- [x] [Review][Defer] `ClienteDB` join em `formapag` não lê `tx_acrescimo` — `FormaPagamento` carregado via consulta de cliente terá `txAcrescimo=0.0`; considerar ao implementar Story 2 [database/ClienteDB.java] — deferred, pre-existing
|
||||||
|
- [x] [Review][Defer] `PedidoDB` join em `formapag` não inclui `tx_acrescimo` — formapag hydratado em contexto de pedido/consulta terá `txAcrescimo=0.0` [database/PedidoDB.java] — deferred, pre-existing
|
||||||
|
- [x] [Review][Defer] `selectIdErp` com lista de colunas incompleta (falta `desco_perc` e `tx_acrescimo`) — bug pré-existente, sem impacto pois o método só verifica existência [database/FormaPagamentoDB.java:194] — deferred, pre-existing
|
||||||
|
- [x] [Review][Defer] Padrão de SQL por concatenação de strings (SQL injection risk) — pré-existente em toda a classe, não introduzido por este diff [database/FormaPagamentoDB.java] — deferred, pre-existing
|
||||||
|
|
||||||
|
## Dev Agent Record
|
||||||
|
|
||||||
|
### Agent Model Used
|
||||||
|
|
||||||
|
claude-sonnet-4-6 (create-story workflow)
|
||||||
|
|
||||||
|
### Debug Log References
|
||||||
|
|
||||||
|
### Completion Notes List
|
||||||
|
|
||||||
|
- Story 1.1 implementada em 2026-04-16.
|
||||||
|
- `dbVersao` incrementado 40→41 em `DatabaseHelper.java`.
|
||||||
|
- Bloco `if (oldVersion < 41)` adicionado em `onUpgrade()` com `ALTER TABLE formapag ADD COLUMN tx_acrescimo REAL DEFAULT 0`.
|
||||||
|
- `onCreate()` atualizado para incluir `tx_acrescimo REAL DEFAULT 0` na definição da tabela `formapag`.
|
||||||
|
- Campo `txAcrescimo` (double) adicionado ao VO `FormaPagamento.java` com getter/setter.
|
||||||
|
- 5 métodos SELECT em `FormaPagamentoDB.java` atualizados: `selectAll`, `select`, `selectUltimoUsado`, `selectAvista`, `selectId` — todos lendo índice 11 com `isNull` defensivo.
|
||||||
|
- Métodos `insert` e `update` em `FormaPagamentoDB.java` atualizados para persistir `tx_acrescimo`.
|
||||||
|
- Sem testes automatizados (projeto não possui infraestrutura de testes). Validação é manual via emulador/dispositivo.
|
||||||
|
|
||||||
|
### File List
|
||||||
|
|
||||||
|
- `src/br/com/jcsinformatica/sarandroid/database/DatabaseHelper.java`
|
||||||
|
- `src/br/com/jcsinformatica/sarandroid/vo/FormaPagamento.java`
|
||||||
|
- `src/br/com/jcsinformatica/sarandroid/database/FormaPagamentoDB.java`
|
||||||
@@ -0,0 +1,228 @@
|
|||||||
|
# Story 1.2: Sincronização da Taxa de Acréscimo do ERP
|
||||||
|
|
||||||
|
Status: done
|
||||||
|
|
||||||
|
## Story
|
||||||
|
|
||||||
|
Como representante de vendas,
|
||||||
|
quero que a taxa de acréscimo de cada forma de pagamento seja sincronizada do ERP para o app durante a comunicação,
|
||||||
|
para que os cálculos de acréscimo usem sempre os valores atualizados do servidor.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
1. **Dado** que `gestao.formapag` no PostgreSQL possui a coluna `acresc` com valores preenchidos
|
||||||
|
**Quando** a `ComunicaActivity` executar o sync de formas de pagamento
|
||||||
|
**Então** o valor de `acresc` é gravado em `formapag.tx_acrescimo` no SQLite para cada forma de pagamento
|
||||||
|
|
||||||
|
2. **Dado** que `gestao.formapag` no PostgreSQL não possui a coluna `acresc` (schema antigo)
|
||||||
|
**Quando** o sync de formas de pagamento for executado
|
||||||
|
**Então** a comunicação continua sem erro e `tx_acrescimo` permanece com o valor anterior (ou `0`)
|
||||||
|
|
||||||
|
3. **Dado** que o VO `FormaPagamento` é carregado após o sync
|
||||||
|
**Quando** `FormaPagamentoDB` preencher o objeto
|
||||||
|
**Então** o campo `tx_acrescimo` está acessível no VO, com valor `0.0` quando `NULL` no banco
|
||||||
|
|
||||||
|
## Tasks / Subtasks
|
||||||
|
|
||||||
|
- [x] Task 1: Atualizar `FormaPagamentoPGSQL.selectAll()` para ler `acresc` do PostgreSQL (AC: 1, 2, 3)
|
||||||
|
- [x] 1.1 - Refatorar `selectAll()` em dois métodos privados para suportar fallback:
|
||||||
|
- Criar `private List<FormaPagamento> executaSelectAll(Date ultAtualizacao, boolean comAcresc) throws Exception`
|
||||||
|
- O método usa `comAcresc` para decidir se inclui `coalesce(acresc, 0.0) as acresc` no SELECT (coluna 10)
|
||||||
|
- Quando `comAcresc = true`: adicionar `, coalesce(acresc, 0.0) as acresc` ao final da lista de colunas e ler `rs.getDouble(10)` → `setTxAcrescimo()`
|
||||||
|
- Quando `comAcresc = false`: não adicionar a coluna; chamar `forPag.setTxAcrescimo(0.0)` explicitamente
|
||||||
|
- [x] 1.2 - Alterar `selectAll()` público para chamar `executaSelectAll(ultAtualizacao, true)` envolvido em try/catch:
|
||||||
|
```java
|
||||||
|
public List<FormaPagamento> selectAll(Date ultAtualizacao) throws Exception {
|
||||||
|
try {
|
||||||
|
return executaSelectAll(ultAtualizacao, true);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.w("FormaPagamentoPGSQL", "Coluna acresc ausente no servidor, usando 0.0: " + e.getMessage());
|
||||||
|
return executaSelectAll(ultAtualizacao, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- [x] 1.3 - Verificar que `FormaPagamentoDB.insert()` e `update()` já persistem `tx_acrescimo` (implementados na Story 1.1 — confirmado: linhas 68, 79, 100)
|
||||||
|
|
||||||
|
### Review Findings (2026-04-16)
|
||||||
|
|
||||||
|
- [x] [Review][Patch] Catch muito abrangente: `catch (Exception e)` deveria ser `catch (SQLException e)` para não mascarar falhas reais de rede/autenticacao como "coluna ausente" [postgres/FormaPagamentoPGSQL.java:24] — corrigido
|
||||||
|
- [x] [Review][Defer] Resource leak: `PreparedStatement`/`ResultSet` nao fechados em bloco `finally` [postgres/FormaPagamentoPGSQL.java:45-68] — deferred, pre-existing
|
||||||
|
- [x] [Review][Defer] SQL injection via concatenacao de strings no WHERE (pre-existente em toda a classe) [postgres/FormaPagamentoPGSQL.java] — deferred, pre-existing
|
||||||
|
- [x] [Review][Defer] `coalesce(libera_credito)` sem argumento padrao e no-op (nao protege contra NULL) [postgres/FormaPagamentoPGSQL.java:35] — deferred, pre-existing
|
||||||
|
- [x] [Review][Defer] `setDescontoPerc` le coluna decimal como `getInt`, truncando frações [postgres/FormaPagamentoPGSQL.java:56] — deferred, pre-existing
|
||||||
|
- [x] [Review][Defer] Chave mal colocada em `AtualizaDados.atualizaFormaPag`: `inativaAll` executa em toda chamada, nao so quando `ultAtualizacao == null` [comunicacao/AtualizaDados.java:377-379] — deferred, pre-existing
|
||||||
|
|
||||||
|
## Dev Notes
|
||||||
|
|
||||||
|
### Escopo desta história
|
||||||
|
|
||||||
|
Esta história cobre EXCLUSIVAMENTE a leitura de `gestao.formapag.acresc` no PostgreSQL e o mapeamento para `FormaPagamento.txAcrescimo` no VO durante o sync.
|
||||||
|
|
||||||
|
- **Um único arquivo a modificar:** `FormaPagamentoPGSQL.java`
|
||||||
|
- Toda a cadeia downstream (`AtualizaDados.atualizaFormaPag` → `FormaPagamentoDB.salvar` → `insert/update`) já suporta `txAcrescimo` corretamente desde a Story 1.1 — **não alterar**
|
||||||
|
- Cálculo de acréscimo na UI e persistência no pedido são escopo do Epic 2
|
||||||
|
|
||||||
|
### Estado atual verificado de `FormaPagamentoPGSQL.java`
|
||||||
|
|
||||||
|
O método `selectAll()` atual (linha 22) retorna 9 colunas e **não lê `acresc`**:
|
||||||
|
|
||||||
|
```java
|
||||||
|
// SELECT atual (linha 24-26):
|
||||||
|
sql.append("SELECT id_formapag, codigo,");
|
||||||
|
sql.append(" descr, ativa, numparc, desco, md5(descr || ativa || numparc || desco || vl_ped_minimo || libera_credito),");
|
||||||
|
sql.append(" coalesce(vl_ped_minimo,0.00) as vl_ped_minimo, coalesce(libera_credito) as libera_credito");
|
||||||
|
|
||||||
|
// Mapeamento atual (linhas 38-46): 9 colunas, sem leitura de acresc
|
||||||
|
// forPag.setTxAcrescimo() NUNCA é chamado — sempre fica 0.0 (padrão do double)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Estado atual verificado de `AtualizaDados.java` (linha 367-382)
|
||||||
|
|
||||||
|
```java
|
||||||
|
private void atualizaFormaPag(Date ultAtualizacao) throws Exception {
|
||||||
|
FormaPagamentoPGSQL forPagPG = new FormaPagamentoPGSQL(conn);
|
||||||
|
FormaPagamentoDB forPagtoDB = new FormaPagamentoDB(comunica);
|
||||||
|
List<FormaPagamento> forPagtosPG = forPagPG.selectAll(ultAtualizacao); // ← apenas esta chamada
|
||||||
|
// ...
|
||||||
|
forPagtoDB.salvar(context, forPagtosPG);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Nenhuma alteração necessária em `AtualizaDados.java`.
|
||||||
|
|
||||||
|
### Estado atual verificado de `FormaPagamentoDB.java` (já implementado em Story 1.1)
|
||||||
|
|
||||||
|
`insert()` (linha 68-79) e `update()` (linhas 88-105) já incluem `tx_acrescimo`. Confirmar sem alterar.
|
||||||
|
|
||||||
|
### Implementação completa esperada de `FormaPagamentoPGSQL.java`
|
||||||
|
|
||||||
|
```java
|
||||||
|
public List<FormaPagamento> selectAll(Date ultAtualizacao) throws Exception {
|
||||||
|
try {
|
||||||
|
return executaSelectAll(ultAtualizacao, true);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.w("FormaPagamentoPGSQL", "Coluna acresc ausente no servidor, usando 0.0: " + e.getMessage());
|
||||||
|
return executaSelectAll(ultAtualizacao, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<FormaPagamento> executaSelectAll(Date ultAtualizacao, boolean comAcresc) throws Exception {
|
||||||
|
List<FormaPagamento> formaPagtos = new ArrayList<FormaPagamento>();
|
||||||
|
StringBuilder sql = new StringBuilder("SELECT id_formapag, codigo,");
|
||||||
|
sql.append(" descr, ativa, numparc, desco, md5(descr || ativa || numparc || desco || vl_ped_minimo || libera_credito),");
|
||||||
|
sql.append(" coalesce(vl_ped_minimo,0.00) as vl_ped_minimo, coalesce(libera_credito) as libera_credito");
|
||||||
|
if (comAcresc) {
|
||||||
|
sql.append(", coalesce(acresc, 0.0) as acresc");
|
||||||
|
}
|
||||||
|
sql.append(" FROM gestao.formapag");
|
||||||
|
sql.append(" WHERE id_empresa=" + Global.getEmpresa().getIdMatriz());
|
||||||
|
sql.append(" AND ativa = 1 AND integrar_sar=1");
|
||||||
|
if (ultAtualizacao != null)
|
||||||
|
sql.append(" AND dt_atual >='" + Util.formatDateDB(ultAtualizacao) + "'");
|
||||||
|
Log.i("SQL FORMA DE PAGAMENTO", sql.toString());
|
||||||
|
PreparedStatement st = conn.prepareStatement(sql.toString());
|
||||||
|
ResultSet rs = st.executeQuery();
|
||||||
|
while (rs.next()) {
|
||||||
|
FormaPagamento forPag = new FormaPagamento();
|
||||||
|
forPag.setId(0);
|
||||||
|
forPag.setIdErp(rs.getInt(1));
|
||||||
|
forPag.setCodigo(rs.getInt(2));
|
||||||
|
forPag.setDescricao(rs.getString(3));
|
||||||
|
forPag.setAtivo(rs.getInt(4) == 1);
|
||||||
|
forPag.setParcelas(rs.getInt(5));
|
||||||
|
forPag.setDescontoPerc(rs.getInt(6));
|
||||||
|
forPag.setMd5(Util.corrigeString(rs.getString(7)));
|
||||||
|
forPag.setVlPedMin(rs.getDouble(8));
|
||||||
|
forPag.setLiberaCredito(rs.getInt(9) == 1);
|
||||||
|
if (comAcresc) {
|
||||||
|
forPag.setTxAcrescimo(rs.getDouble(10));
|
||||||
|
} else {
|
||||||
|
forPag.setTxAcrescimo(0.0);
|
||||||
|
}
|
||||||
|
formaPagtos.add(forPag);
|
||||||
|
}
|
||||||
|
rs.close();
|
||||||
|
st.close();
|
||||||
|
return formaPagtos;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Regras críticas do projeto
|
||||||
|
|
||||||
|
- **Sem testes automatizados** — validação é manual via dispositivo/emulador. Não criar arquivos de teste.
|
||||||
|
- **Sem Kotlin** — somente Java puro
|
||||||
|
- **Sem Gradle** — projeto Eclipse ADT, não criar build.gradle
|
||||||
|
- **JDBC sempre em thread background** — `executaSelectAll()` é chamado de dentro de `AtualizaDados` que já roda em thread separada; não há alteração necessária
|
||||||
|
- **Sem JARs novos** — implementação usa apenas `java.sql.*` já disponível
|
||||||
|
- **`acresc` pode ser NULL no PostgreSQL** — usar `coalesce(acresc, 0.0)` na query para tratar no lado do servidor quando a coluna existe
|
||||||
|
- **`acresc` pode não existir no schema** — tratar via try/catch (NFR5): nunca interromper a comunicação por coluna ausente
|
||||||
|
- Conexão JDBC (`conn`) já existe e é passada no construtor — não criar nova conexão
|
||||||
|
- Strings com acentos em código Java são proibidas — os logs usam apenas ASCII
|
||||||
|
|
||||||
|
### Por que try/catch em vez de `coalesce`
|
||||||
|
|
||||||
|
`coalesce(acresc, 0.0)` trata `NULL` mas **não trata coluna ausente** — se `acresc` não existir no schema PostgreSQL, o `conn.prepareStatement()` lança `SQLException` (column not found). O try/catch captura esse erro e reexecuta sem a coluna, garantindo NFR5 sem interromper a comunicação.
|
||||||
|
|
||||||
|
### Sincronização incremental e MD5
|
||||||
|
|
||||||
|
O MD5 computado no PostgreSQL (`md5(descr || ativa || numparc || desco || vl_ped_minimo || libera_credito)`) **não inclui `acresc`**. Isso é intencional: a mudança de `acresc` no ERP normalmente atualiza `dt_atual` na tabela, fazendo o registro aparecer no sync incremental (`dt_atual >= ultAtualizacao`). Para syncs totais (`ultAtualizacao = null`), todos os registros são atualizados. Não alterar o MD5.
|
||||||
|
|
||||||
|
### Arquivos a modificar
|
||||||
|
|
||||||
|
| Arquivo | Caminho | O que muda |
|
||||||
|
|---------|---------|------------|
|
||||||
|
| `FormaPagamentoPGSQL.java` | `src/br/com/jcsinformatica/sarandroid/postgres/` | `selectAll()` refatorado + `executaSelectAll()` privado com flag `comAcresc` |
|
||||||
|
|
||||||
|
**NÃO modificar nesta história:**
|
||||||
|
- `AtualizaDados.java` — sem mudança necessária
|
||||||
|
- `ComunicaActivity.java` — sem mudança necessária
|
||||||
|
- `FormaPagamentoDB.java` — já atualizado na Story 1.1
|
||||||
|
- `FormaPagamento.java` (VO) — já atualizado na Story 1.1
|
||||||
|
- Qualquer Activity ou layout — escopo do Epic 2
|
||||||
|
|
||||||
|
### Verificação manual após implementação
|
||||||
|
|
||||||
|
1. **Com coluna `acresc`:** executar sync com servidor que possui `gestao.formapag.acresc` → verificar em SQLite que `formapag.tx_acrescimo` recebe o valor correto
|
||||||
|
2. **Sem coluna `acresc`:** executar sync com servidor que não possui a coluna → verificar no log `"Coluna acresc ausente no servidor"` e que a comunicação completa sem erro
|
||||||
|
3. **`acresc = NULL`:** verificar que formas de pagamento com `acresc = NULL` recebem `tx_acrescimo = 0.0` (via `coalesce`)
|
||||||
|
|
||||||
|
### Project Structure Notes
|
||||||
|
|
||||||
|
- Estrutura Eclipse ADT: código em `src/`, pacote `br.com.jcsinformatica.sarandroid`
|
||||||
|
- DAOs PostgreSQL: `src/br/com/jcsinformatica/sarandroid/postgres/`
|
||||||
|
- Padrão de sync: `*PGSQL.java` lê do servidor → VO → `*DB.java` persiste no SQLite
|
||||||
|
|
||||||
|
### References
|
||||||
|
|
||||||
|
- [Source: _bmad-output/project-context.md#Padrão Dual-Banco] — ciclo JDBC, ConnectionManager, sync pattern
|
||||||
|
- [Source: _bmad-output/project-context.md#Regras Críticas] — proibições absolutas
|
||||||
|
- [Source: _bmad-output/planning-artifacts/epics.md#Story 1.2] — ACs e requisitos FR1, FR2, NFR5, NFR6
|
||||||
|
- [Source: _bmad-output/planning-artifacts/prd.md#Sincronização de Dados] — FR1, FR2
|
||||||
|
- [Source: _bmad-output/planning-artifacts/prd.md#NonFunctional Requirements] — NFR5, NFR6
|
||||||
|
- [Source: _bmad-output/implementation-artifacts/1-1-migracao-do-schema-sqlite-para-suporte-a-acrescimo.md] — Story 1.1 completa; FormaPagamentoDB.insert/update já persistem tx_acrescimo; mapeamento PostgreSQL→SQLite documentado
|
||||||
|
- [Source: src/br/com/jcsinformatica/sarandroid/postgres/FormaPagamentoPGSQL.java] — estado atual linha 22-53: 9 colunas, sem acresc
|
||||||
|
- [Source: src/br/com/jcsinformatica/sarandroid/comunicacao/AtualizaDados.java:367] — atualizaFormaPag() chama selectAll() e salvar()
|
||||||
|
- [Source: src/br/com/jcsinformatica/sarandroid/database/FormaPagamentoDB.java:63-105] — insert/update já incluem tx_acrescimo (Story 1.1)
|
||||||
|
|
||||||
|
## Dev Agent Record
|
||||||
|
|
||||||
|
### Agent Model Used
|
||||||
|
|
||||||
|
claude-sonnet-4-6 (dev-story workflow)
|
||||||
|
|
||||||
|
### Debug Log References
|
||||||
|
|
||||||
|
### Completion Notes List
|
||||||
|
|
||||||
|
- Story 1.2 implementada em 2026-04-16.
|
||||||
|
- `FormaPagamentoPGSQL.selectAll()` refatorado: método público agora delega para `executaSelectAll(ultAtualizacao, true)` com try/catch que faz fallback para `executaSelectAll(ultAtualizacao, false)` em caso de coluna `acresc` ausente no servidor.
|
||||||
|
- `executaSelectAll(Date, boolean)` privado: quando `comAcresc=true` adiciona `, coalesce(acresc, 0.0) as acresc` ao SELECT e lê `rs.getDouble(10)` → `setTxAcrescimo()`; quando `comAcresc=false` chama `setTxAcrescimo(0.0)` explicitamente.
|
||||||
|
- `FormaPagamentoDB.insert()` e `update()` confirmados: ambos ja persistem `tx_acrescimo` desde a Story 1.1 — nenhuma alteracao necessaria.
|
||||||
|
- `AtualizaDados.atualizaFormaPag()` confirmado: sem alteracao necessaria — fluxo `selectAll()` → `salvar()` e transparente a esta mudanca.
|
||||||
|
- Sem testes automatizados (projeto nao possui infraestrutura de testes). Validacao e manual via dispositivo/emulador.
|
||||||
|
- NFR5 satisfeito: try/catch captura `Exception` ao preparar/executar o SELECT com `acresc`; comunicacao nao e interrompida em schemas antigos.
|
||||||
|
- NFR6 satisfeito: `coalesce(acresc, 0.0)` garante que `NULL` e tratado como `0.0` no servidor antes de chegar ao VO.
|
||||||
|
|
||||||
|
### File List
|
||||||
|
|
||||||
|
- `src/br/com/jcsinformatica/sarandroid/postgres/FormaPagamentoPGSQL.java`
|
||||||
@@ -0,0 +1,236 @@
|
|||||||
|
# Story 2.1: Exibir Campo de Acréscimo na Tela do Pedido
|
||||||
|
|
||||||
|
Status: done
|
||||||
|
|
||||||
|
## Story
|
||||||
|
|
||||||
|
Como representante de vendas,
|
||||||
|
quero ver um campo de acréscimo sempre visível na tela do pedido,
|
||||||
|
para que eu saiba em todo momento se há acréscimo aplicado, mesmo que seja zero.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
1. **Dado** que estou na tela de edição do pedido (`UpdatePedidoActivity`)
|
||||||
|
**Quando** a tela for exibida (qualquer aba)
|
||||||
|
**Então** um campo "Acréscimo" é visível mostrando R$ 0,00 por padrão
|
||||||
|
|
||||||
|
2. **Dado** que nenhuma forma de pagamento foi selecionada ou a selecionada tem `tx_acrescimo = 0`
|
||||||
|
**Quando** o campo acréscimo for exibido
|
||||||
|
**Então** mostra "R$ 0,00" — nunca fica oculto ou em branco
|
||||||
|
|
||||||
|
## Escopo desta história
|
||||||
|
|
||||||
|
Esta história cobre **exclusivamente** a adição do widget "Acréscimo" na UI, sempre visível, com valor estático R$ 0,00.
|
||||||
|
|
||||||
|
- **NÃO faz cálculo** — cálculo automático ao selecionar forma de pagamento é escopo da Story 2.2
|
||||||
|
- **NÃO persiste** valor no pedido — persistência é escopo da Story 2.3
|
||||||
|
- **NÃO lê** `tx_acrescimo` do banco nesta história — a Story 2.2 usará o campo criado aqui para exibir o valor calculado
|
||||||
|
|
||||||
|
## Tasks / Subtasks
|
||||||
|
|
||||||
|
- [x] Task 1: Adicionar campo Acréscimo no layout da aba Total (AC: 1, 2)
|
||||||
|
- [x] 1.1 - Em `res/layout/fragment_total_pedido.xml`: alterar `android:layout_height="271dp"` do `LinearLayout` interno para `wrap_content` (necessário para acomodar a nova linha sem cortar conteúdo)
|
||||||
|
- [x] 1.2 - Inserir novo `LinearLayout` de linha entre a linha de "Total ICMS-ST (+)" e a linha de "Total (=)", seguindo exatamente o mesmo padrão das linhas existentes:
|
||||||
|
```xml
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingBottom="12dp" >
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvLabelAcrescimoPedido"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:paddingLeft="12dp"
|
||||||
|
android:text="Acrescimo (+)" />
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvAcrescimoPedido"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="right"
|
||||||
|
android:paddingRight="12dp"
|
||||||
|
android:text="0,00" />
|
||||||
|
</LinearLayout>
|
||||||
|
```
|
||||||
|
**Atenção:** usar "Acrescimo" sem acento no atributo `android:text` do label pois strings com acento em XML de layout podem funcionar, mas verificar se o projeto usa arquivo `strings.xml` para textos — se sim, adicionar entrada lá. Verificar padrão existente: "Peso Líquido" usa diretamente no XML, então manter padrão do projeto.
|
||||||
|
- [x] 1.3 - Em `TotalPedidoFragment.java`: declarar `private TextView tvAcrescimo;` junto com os outros campos de texto (linha ~22)
|
||||||
|
- [x] 1.4 - Em `onCreateView()`: vincular `tvAcrescimo = (TextView) rootView.findViewById(R.id.tvAcrescimoPedido);`
|
||||||
|
- [x] 1.5 - Em `FillFields()`: após `tvTotalIcmsST.setText(...)`, adicionar `tvAcrescimo.setText(Util.formataValorMonetario(0.0));` — sempre R$ 0,00 nesta história
|
||||||
|
|
||||||
|
- [x] Task 2: Adicionar campo Acréscimo no resumo da aba Dados (AC: 1, 2)
|
||||||
|
- [x] 2.1 - Em `res/layout/fragment_main_pedido.xml`: inserir novo `TableRow` para acréscimo dentro do `TableLayout` existente (linhas 235-314), entre `tableRowQtdTotal` e `tableRowTotalGeral`:
|
||||||
|
```xml
|
||||||
|
<TableRow
|
||||||
|
android:id="@+id/tableRowAcrescimo"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingBottom="12dp"
|
||||||
|
android:paddingLeft="12dp"
|
||||||
|
android:paddingRight="12dp" >
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvLabelAcrescimo_pedido"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="Acrescimo" />
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvAcrescimo_pedido"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="right"
|
||||||
|
android:text="R$ 0,00" />
|
||||||
|
</TableRow>
|
||||||
|
```
|
||||||
|
- [x] 2.2 - Em `MainPedidoFragment.java`: declarar `private TextView tvAcrescimoPedido;` junto com os outros campos (linha ~67)
|
||||||
|
- [x] 2.3 - Em `onCreateView()`: vincular `tvAcrescimoPedido = (TextView) rootView.findViewById(R.id.tvAcrescimo_pedido);`
|
||||||
|
- [x] 2.4 - Em `fillFields()`: adicionar `tvAcrescimoPedido.setText(Util.formataValorMonetario(0.0));` após o bloco de cálculo de `totalGeral`
|
||||||
|
- [x] 2.5 - Em `atualizarResumoPedido()`: adicionar `tvAcrescimoPedido.setText(Util.formataValorMonetario(0.0));` após o cálculo de `totalGeral` (este método é chamado ao selecionar forma de pagamento — Story 2.2 vai atualizar aqui com o valor real)
|
||||||
|
|
||||||
|
## Dev Notes
|
||||||
|
|
||||||
|
### Estrutura de fragmentos do pedido
|
||||||
|
|
||||||
|
`UpdatePedidoActivity` usa `ViewPager` com `PedidoTabAdapter` (4 tabs):
|
||||||
|
- Posição 0: `MainPedidoFragment` — aba "Dados" — layout `fragment_main_pedido.xml`
|
||||||
|
- Posição 1: `ItensPedidoFragment` — aba "Itens"
|
||||||
|
- Posição 2: `TotalPedidoFragment` — aba "Total" — layout `fragment_total_pedido.xml`
|
||||||
|
- Posição 3: `FlexPedidoFragment` — aba "Flex"
|
||||||
|
|
||||||
|
O campo Acréscimo deve aparecer em **ambos**: aba Dados (resumo) e aba Total (detalhado).
|
||||||
|
|
||||||
|
### Padrão de nomeação de IDs existente
|
||||||
|
|
||||||
|
| Fragment | Padrão de ID | Exemplos |
|
||||||
|
|----------|-------------|----------|
|
||||||
|
| `TotalPedidoFragment` | `tvXxxPedido` | `tvTotalGeralPedido`, `tvTotalIcmsSTPedido` |
|
||||||
|
| `MainPedidoFragment` | `tvXxx_pedido` (com underscore) | `tvTotal_pedido_geral`, `tvCodLiberacao_pedido` |
|
||||||
|
|
||||||
|
Manter este padrão para os novos IDs:
|
||||||
|
- Total fragment: `tvAcrescimoPedido`
|
||||||
|
- Main fragment: `tvAcrescimo_pedido`
|
||||||
|
|
||||||
|
### Posicionamento visual na aba Total
|
||||||
|
|
||||||
|
Ordem atual das linhas em `fragment_total_pedido.xml`:
|
||||||
|
1. Quantidade Total
|
||||||
|
2. Peso Líquido
|
||||||
|
3. Total Produtos (+)
|
||||||
|
4. Total Desconto (-)
|
||||||
|
5. Total IPI:
|
||||||
|
6. Total ICMS-ST (+)
|
||||||
|
7. **← Acréscimo (+) [INSERIR AQUI]**
|
||||||
|
8. Total (=)
|
||||||
|
|
||||||
|
### Posicionamento visual na aba Dados
|
||||||
|
|
||||||
|
Ordem atual na `TableLayout` de `fragment_main_pedido.xml` (linhas 235-314):
|
||||||
|
1. `tableRowPedidoMinimo` — Valor Pedido Mínimo
|
||||||
|
2. `tableRowQtdTotal` — Quantidade Total
|
||||||
|
3. **← `tableRowAcrescimo` [INSERIR AQUI]**
|
||||||
|
4. `tableRowTotalGeral` — Total Geral
|
||||||
|
|
||||||
|
### Problema de altura fixa em fragment_total_pedido.xml
|
||||||
|
|
||||||
|
O `LinearLayout` interno em `fragment_total_pedido.xml` tem `android:layout_height="271dp"` (linha 14). Essa altura fixa foi calculada para 6 linhas. Com a nova linha de Acréscimo (+), o conteúdo será cortado se não ajustar. **Alterar para `wrap_content`** — o `ScrollView` pai garante que o conteúdo seja acessível.
|
||||||
|
|
||||||
|
### Método `Util.formataValorMonetario()`
|
||||||
|
|
||||||
|
Já usado extensivamente no projeto para formatação monetária. Chamadas de exemplo em `TotalPedidoFragment.FillFields()`:
|
||||||
|
```java
|
||||||
|
tvTotalProdutos.setText(Util.formataValorMonetario(vlTotalProduto));
|
||||||
|
tvTotalGeral.setText(Util.formataValorMonetario(vlTotalGeral));
|
||||||
|
```
|
||||||
|
Usar `Util.formataValorMonetario(0.0)` para garantir "R$ 0,00" no padrão do projeto.
|
||||||
|
|
||||||
|
### Método `atualizarResumoPedido()` em MainPedidoFragment
|
||||||
|
|
||||||
|
Este método (linha 953) é chamado no `onItemSelected` do `spFormaPag` (linha 785) — ou seja, toda vez que o usuário seleciona uma forma de pagamento. A Story 2.2 vai modificar este método para calcular e exibir o acréscimo real. Esta história apenas garante que `tvAcrescimoPedido` está vinculado e inicializado — a Story 2.2 vai atualizá-lo com o valor calculado.
|
||||||
|
|
||||||
|
**Padrão a seguir** para facilitar Story 2.2 — declarar a variável de acréscimo localmente já calculada (mesmo que zero) antes de setar no TextView:
|
||||||
|
```java
|
||||||
|
// Task 2.5 em atualizarResumoPedido():
|
||||||
|
double vlAcrescimo = 0.0; // Story 2.2 vai calcular: subtotal * (tx_acrescimo/100)
|
||||||
|
tvAcrescimoPedido.setText(Util.formataValorMonetario(vlAcrescimo));
|
||||||
|
```
|
||||||
|
|
||||||
|
### Strings com acentos
|
||||||
|
|
||||||
|
O projeto usa strings com acento diretamente nos atributos `android:text` dos layouts XML (ex: "Peso Líquido", "Forma de pagamento", "Observação"). Este padrão é aceitável — usar "Acréscimo (+)" diretamente no XML.
|
||||||
|
|
||||||
|
**Em código Java:** NÃO usar strings com acentos em logs ou mensagens (regra do projeto). Não há logs necessários nesta história.
|
||||||
|
|
||||||
|
### Regras críticas do projeto (aplicáveis a esta história)
|
||||||
|
|
||||||
|
- **Sem testes automatizados** — validação manual via emulador/dispositivo
|
||||||
|
- **Sem Kotlin** — somente Java puro
|
||||||
|
- **Sem Gradle** — projeto Eclipse ADT
|
||||||
|
- **Sem JARs novos** — nenhuma dependência adicional necessária
|
||||||
|
- **Leituras SQLite em thread background** — esta história NÃO faz leituras de banco, não se aplica
|
||||||
|
|
||||||
|
### Arquivos a modificar
|
||||||
|
|
||||||
|
| Arquivo | Caminho | O que muda |
|
||||||
|
|---------|---------|------------|
|
||||||
|
| `fragment_total_pedido.xml` | `res/layout/` | Corrigir altura fixa + inserir linha Acréscimo (+) |
|
||||||
|
| `TotalPedidoFragment.java` | `src/br/com/jcsinformatica/sarandroid/pedido/` | Vincular `tvAcrescimo` + setar R$ 0,00 em `FillFields()` |
|
||||||
|
| `fragment_main_pedido.xml` | `res/layout/` | Inserir `tableRowAcrescimo` antes de `tableRowTotalGeral` |
|
||||||
|
| `MainPedidoFragment.java` | `src/br/com/jcsinformatica/sarandroid/pedido/` | Vincular `tvAcrescimoPedido` + setar R$ 0,00 em `fillFields()` e `atualizarResumoPedido()` |
|
||||||
|
|
||||||
|
**NÃO modificar nesta história:**
|
||||||
|
- `UpdatePedidoActivity.java` — nenhuma mudança necessária
|
||||||
|
- Qualquer VO (`Pedido.java`, `FormaPagamento.java`) — já têm tudo que precisam
|
||||||
|
- `PedidoDB.java`, `FormaPagamentoDB.java` — sem mudança de banco nesta história
|
||||||
|
- `DatabaseHelper.java` — sem migração de schema nesta história
|
||||||
|
- Qualquer arquivo do Epic 1 — já concluído
|
||||||
|
|
||||||
|
### Inteligência da história anterior (Story 1.2)
|
||||||
|
|
||||||
|
- `FormaPagamento.getTxAcrescimo()` já existe e retorna `double` — disponível para Story 2.2
|
||||||
|
- Padrão de separação de lógica de background e UI thread estabelecido em `MainPedidoFragment.onStart()` (Thread + `runOnUiThread`) — Story 2.2 seguirá este padrão ao ler `tx_acrescimo` se necessário
|
||||||
|
- Review da Story 1.2 sinalizou que catch muito abrangente (`Exception`) deve ser `SQLException` — nesta história não há JDBC, irrelevante
|
||||||
|
|
||||||
|
### Verificação manual após implementação
|
||||||
|
|
||||||
|
1. Abrir `UpdatePedidoActivity` criando novo pedido → verificar que aba "Total" mostra linha "Acréscimo (+) R$ 0,00"
|
||||||
|
2. Verificar que aba "Dados" (MainPedidoFragment) mostra linha "Acréscimo R$ 0,00" acima de "Total Geral"
|
||||||
|
3. Selecionar e trocar formas de pagamento → campo permanece R$ 0,00 (cálculo virá na Story 2.2)
|
||||||
|
4. Pedido com status enviado (somente leitura) → campo acréscimo ainda deve aparecer
|
||||||
|
5. Rolar a aba Total → verificar que a linha Acréscimo não foi cortada (confirmar que `wrap_content` resolveu o `271dp`)
|
||||||
|
|
||||||
|
### Review Findings (2026-04-16)
|
||||||
|
|
||||||
|
- [x] [Review][Defer] `layout_weight="1"` com `layout_width="wrap_content"` nos novos rows de layout — deveria ser `0dp` para distribuição correta de peso, mas padrão pré-existente em todos os rows do projeto [res/layout/fragment_total_pedido.xml, res/layout/fragment_main_pedido.xml] — deferred, pre-existing
|
||||||
|
- [x] [Review][Defer] `atualizarResumoPedido()` sem null-guard nem try/catch — `tvAcrescimoPedido` pode NPE se chamado antes de `onCreateView()` ou após `Global.pedido = null`; risco compartilhado com `tvQtdTotal`/`tvTotalGeral` pré-existentes [MainPedidoFragment.java:953] — deferred, pre-existing
|
||||||
|
- [x] [Review][Defer] `setUserVisibleHint()` pode disparar `FillFields()` antes de `onCreateView()` — `tvAcrescimo` null silenciosamente engolido por `catch(Exception e){}` sem log; mesmo risco pré-existente dos demais TextViews [TotalPedidoFragment.java:62] — deferred, pre-existing
|
||||||
|
- [x] [Review][Defer] Strings hardcoded no XML (`"Acréscimo"`, `"Acréscimo (+)"`) em vez de `@string/` resources — padrão pré-existente em todo o projeto [res/layout/fragment_main_pedido.xml, res/layout/fragment_total_pedido.xml] — deferred, pre-existing
|
||||||
|
- [x] [Review][Defer] `new UpdatePedItemActivity().precoComIpi()` instancia Activity fora do ciclo de vida — anti-padrão pré-existente; não introduzido por esta story [MainPedidoFragment.java:434, TotalPedidoFragment.java:101] — deferred, pre-existing
|
||||||
|
|
||||||
|
## Dev Agent Record
|
||||||
|
|
||||||
|
### Agent Model Used
|
||||||
|
|
||||||
|
claude-sonnet-4-6 (dev-story workflow)
|
||||||
|
|
||||||
|
### Debug Log References
|
||||||
|
|
||||||
|
### Completion Notes List
|
||||||
|
|
||||||
|
- Story 2.1 implementada em 2026-04-16.
|
||||||
|
- `fragment_total_pedido.xml`: altura fixa `271dp` corrigida para `wrap_content`; nova linha "Acrescimo (+)" inserida com `tvAcrescimoPedido` entre ICMS-ST e Total Geral.
|
||||||
|
- `TotalPedidoFragment.java`: campo `tvAcrescimo` declarado, vinculado em `onCreateView()`, e inicializado com `Util.formataValorMonetario(0.0)` em `FillFields()`.
|
||||||
|
- `fragment_main_pedido.xml`: `tableRowAcrescimo` inserido com `tvAcrescimo_pedido` entre `tableRowQtdTotal` e `tableRowTotalGeral`.
|
||||||
|
- `MainPedidoFragment.java`: campo `tvAcrescimoPedido` declarado, vinculado em `onCreateView()`, e inicializado com `Util.formataValorMonetario(0.0)` em `fillFields()` e `atualizarResumoPedido()`.
|
||||||
|
- Variavel local `vlAcrescimo = 0.0` declarada explicitamente em ambos os metodos de `MainPedidoFragment` com comentario indicando ponto de expansao para Story 2.2.
|
||||||
|
- Sem testes automatizados (projeto nao possui infraestrutura de testes). Validacao e manual via dispositivo/emulador.
|
||||||
|
- AC1 satisfeito: campo "Acrescimo" visivel em ambas as abas (Dados e Total) ao abrir `UpdatePedidoActivity`.
|
||||||
|
- AC2 satisfeito: campo sempre visivel, nunca oculto ou em branco — valor padrao R$ 0,00 via `Util.formataValorMonetario(0.0)`.
|
||||||
|
|
||||||
|
### File List
|
||||||
|
|
||||||
|
- `res/layout/fragment_total_pedido.xml`
|
||||||
|
- `src/br/com/jcsinformatica/sarandroid/pedido/TotalPedidoFragment.java`
|
||||||
|
- `res/layout/fragment_main_pedido.xml`
|
||||||
|
- `src/br/com/jcsinformatica/sarandroid/pedido/MainPedidoFragment.java`
|
||||||
@@ -0,0 +1,243 @@
|
|||||||
|
# Story 2.2: Calcular Acréscimo ao Selecionar Forma de Pagamento
|
||||||
|
|
||||||
|
Status: done
|
||||||
|
|
||||||
|
## Story
|
||||||
|
|
||||||
|
Como representante de vendas,
|
||||||
|
quero que o acréscimo seja calculado e exibido automaticamente ao selecionar a forma de pagamento,
|
||||||
|
para que eu veja o valor real do pedido sem precisar calcular manualmente.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
1. **Dado** que estou editando um pedido com subtotal R$ 3.500,00
|
||||||
|
**Quando** selecionar "Boleto 30/60/90" com `tx_acrescimo = 2.5`
|
||||||
|
**Então** o campo "Acréscimo" exibe R$ 87,50 e o total exibe R$ 3.587,50
|
||||||
|
|
||||||
|
2. **Dado** que já selecionei "Boleto 30/60/90" (2,5%) e troco para "Dinheiro" (`tx_acrescimo = 0`)
|
||||||
|
**Quando** a forma de pagamento for alterada
|
||||||
|
**Então** o campo "Acréscimo" retorna a R$ 0,00 e o total retorna a R$ 3.500,00
|
||||||
|
|
||||||
|
3. **Dado** que a forma de pagamento selecionada tem `tx_acrescimo = NULL` (zero no VO após Story 1.2)
|
||||||
|
**Quando** o cálculo for executado
|
||||||
|
**Então** acréscimo = R$ 0,00, sem crash ou `NullPointerException`
|
||||||
|
|
||||||
|
4. **Dado** que o cálculo é disparado
|
||||||
|
**Quando** a troca de forma de pagamento ocorrer
|
||||||
|
**Então** o recálculo conclui em menos de 100ms (aritmética em memória, sem I/O)
|
||||||
|
|
||||||
|
## Escopo desta história
|
||||||
|
|
||||||
|
Esta história cobre **exclusivamente** o cálculo em memória e exibição do acréscimo ao selecionar forma de pagamento.
|
||||||
|
|
||||||
|
- **NÃO persiste** o valor no banco — persistência é escopo da Story 2.3
|
||||||
|
- **NÃO adiciona campos novos** na UI — campos `tvAcrescimoPedido` e `tvAcrescimo` foram criados na Story 2.1
|
||||||
|
- **NÃO lê do SQLite** durante a seleção — `tx_acrescimo` já está no objeto `FormaPagamento` em memória
|
||||||
|
|
||||||
|
## Tasks / Subtasks
|
||||||
|
|
||||||
|
- [x] Task 1: Atualizar cálculo em `MainPedidoFragment.atualizarResumoPedido()` (AC: 1, 2, 3, 4)
|
||||||
|
- [x] 1.1 — Na linha ~974 de `MainPedidoFragment.java`, substituir o bloco placeholder:
|
||||||
|
```java
|
||||||
|
double vlAcrescimo = 0.0; // Story 2.2 calculara: subtotal * (tx_acrescimo/100)
|
||||||
|
tvAcrescimoPedido.setText(Util.formataValorMonetario(vlAcrescimo));
|
||||||
|
tvTotalGeral.setText(Util.formataValorMonetario(totalGeral));
|
||||||
|
```
|
||||||
|
Por:
|
||||||
|
```java
|
||||||
|
double txAcrescimo = 0.0;
|
||||||
|
if (Global.pedido.getFormapag() != null) {
|
||||||
|
txAcrescimo = Global.pedido.getFormapag().getTxAcrescimo();
|
||||||
|
}
|
||||||
|
double vlAcrescimo = totalGeral * (txAcrescimo / 100.0);
|
||||||
|
tvAcrescimoPedido.setText(Util.formataValorMonetario(vlAcrescimo));
|
||||||
|
tvTotalGeral.setText(Util.formataValorMonetario(totalGeral + vlAcrescimo));
|
||||||
|
```
|
||||||
|
|
||||||
|
- [x] Task 2: Atualizar cálculo em `MainPedidoFragment.fillFields()` (AC: 1, 2, 3)
|
||||||
|
- [x] 2.1 — Na linha ~439 de `MainPedidoFragment.java`, substituir o bloco placeholder:
|
||||||
|
```java
|
||||||
|
double vlAcrescimo = 0.0; // Story 2.2 calculara: subtotal * (tx_acrescimo/100)
|
||||||
|
tvAcrescimoPedido.setText(Util.formataValorMonetario(vlAcrescimo));
|
||||||
|
tvTotalGeral.setText(Util.formataValorMonetario(totalGeral));
|
||||||
|
```
|
||||||
|
Pelo mesmo padrão da Task 1.1 (idêntico — `fillFields()` usa as mesmas variáveis locais `totalGeral`).
|
||||||
|
|
||||||
|
- [x] Task 3: Atualizar cálculo em `TotalPedidoFragment.FillFields()` (AC: 1, 2, 3)
|
||||||
|
- [x] 3.1 — Na linha ~97 de `TotalPedidoFragment.java`, substituir:
|
||||||
|
```java
|
||||||
|
tvAcrescimo.setText(Util.formataValorMonetario(0.0));
|
||||||
|
tvTotalGeral.setText(Util.formataValorMonetario(vlTotalGeral));
|
||||||
|
```
|
||||||
|
Por:
|
||||||
|
```java
|
||||||
|
double txAcrescimo = 0.0;
|
||||||
|
if (Global.pedido.getFormapag() != null) {
|
||||||
|
txAcrescimo = Global.pedido.getFormapag().getTxAcrescimo();
|
||||||
|
}
|
||||||
|
double vlAcrescimo = vlTotalGeral * (txAcrescimo / 100.0);
|
||||||
|
tvAcrescimo.setText(Util.formataValorMonetario(vlAcrescimo));
|
||||||
|
tvTotalGeral.setText(Util.formataValorMonetario(vlTotalGeral + vlAcrescimo));
|
||||||
|
```
|
||||||
|
**Atenção:** a variável local em `TotalPedidoFragment` é `vlTotalGeral` (não `totalGeral` como em `MainPedidoFragment`).
|
||||||
|
|
||||||
|
## Dev Notes
|
||||||
|
|
||||||
|
### Estado atual do código após Story 2.1
|
||||||
|
|
||||||
|
Ambos os métodos já possuem o placeholder exato que deve ser substituído:
|
||||||
|
|
||||||
|
**`MainPedidoFragment.java` — `atualizarResumoPedido()` (linha ~974):**
|
||||||
|
```java
|
||||||
|
double vlAcrescimo = 0.0; // Story 2.2 calculara: subtotal * (tx_acrescimo/100)
|
||||||
|
tvAcrescimoPedido.setText(Util.formataValorMonetario(vlAcrescimo));
|
||||||
|
tvTotalGeral.setText(Util.formataValorMonetario(totalGeral));
|
||||||
|
```
|
||||||
|
|
||||||
|
**`MainPedidoFragment.java` — `fillFields()` (linha ~439):**
|
||||||
|
```java
|
||||||
|
double vlAcrescimo = 0.0; // Story 2.2 calculara: subtotal * (tx_acrescimo/100)
|
||||||
|
tvAcrescimoPedido.setText(Util.formataValorMonetario(vlAcrescimo));
|
||||||
|
tvTotalGeral.setText(Util.formataValorMonetario(totalGeral));
|
||||||
|
```
|
||||||
|
|
||||||
|
**`TotalPedidoFragment.java` — `FillFields()` (linha ~97):**
|
||||||
|
```java
|
||||||
|
tvAcrescimo.setText(Util.formataValorMonetario(0.0));
|
||||||
|
tvTotalGeral.setText(Util.formataValorMonetario(vlTotalGeral));
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fórmula de cálculo
|
||||||
|
|
||||||
|
```
|
||||||
|
subtotal = totalProduto - totalDesconto + totalIcmsST (+ totalIPI se !precoComIpi)
|
||||||
|
vlAcrescimo = subtotal * (tx_acrescimo / 100.0)
|
||||||
|
totalFinal = subtotal + vlAcrescimo
|
||||||
|
```
|
||||||
|
|
||||||
|
O `subtotal` já está calculado nas variáveis locais `totalGeral` (MainPedidoFragment) e `vlTotalGeral` (TotalPedidoFragment) — **não recalcular**.
|
||||||
|
|
||||||
|
### Onde `tx_acrescimo` está disponível (sem I/O)
|
||||||
|
|
||||||
|
`Global.pedido.getFormapag().getTxAcrescimo()` retorna `double` diretamente — a `FormaPagamento` já está carregada em memória. **Não há necessidade de acessar o SQLite** durante o cálculo — NFR1 (< 100ms) é satisfeito trivialmente.
|
||||||
|
|
||||||
|
### Null-safety obrigatória
|
||||||
|
|
||||||
|
`Global.pedido.getFormapag()` pode ser `null` (pedido sem forma de pagamento definida). `getTxAcrescimo()` retorna `double` (primitivo), então não há risco de NPE no getter, mas o objeto pai pode ser null. Padrão obrigatório:
|
||||||
|
```java
|
||||||
|
double txAcrescimo = 0.0;
|
||||||
|
if (Global.pedido.getFormapag() != null) {
|
||||||
|
txAcrescimo = Global.pedido.getFormapag().getTxAcrescimo();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`FormaPagamentoDB` (Story 1.2) já garante que `tx_acrescimo = NULL` no SQLite é lido como `0.0` no VO — sem risco adicional.
|
||||||
|
|
||||||
|
### Fluxo de disparo do cálculo em `MainPedidoFragment`
|
||||||
|
|
||||||
|
O cálculo é disparado em dois cenários:
|
||||||
|
|
||||||
|
1. **Seleção de forma de pagamento** — `onItemSelected()` → chama `atualizarResumoPedido()` (linha 789):
|
||||||
|
```java
|
||||||
|
} else if (spFormaPag != null && parent == spFormaPag) {
|
||||||
|
if ((!isFormaPagBloq && ...) || Global.pedido.getFormapag() == null){
|
||||||
|
Global.pedido.setFormapag(listForPagtos.get(pos));
|
||||||
|
atualizarResumoPedido(); // ← aqui está o gatilho
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
O `Global.pedido.getFormapag()` já está atualizado quando `atualizarResumoPedido()` é chamado.
|
||||||
|
|
||||||
|
2. **Carregamento inicial do fragmento** — `onResume()` → `Handler.post()` → `fillFields()` → exibe o acréscimo da forma já selecionada.
|
||||||
|
|
||||||
|
### Nomes de variáveis — diferença entre os dois fragmentos
|
||||||
|
|
||||||
|
| Fragmento | Variável "subtotal" | TextView acréscimo | TextView total |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `MainPedidoFragment` | `totalGeral` | `tvAcrescimoPedido` | `tvTotalGeral` |
|
||||||
|
| `TotalPedidoFragment` | `vlTotalGeral` | `tvAcrescimo` | `tvTotalGeral` |
|
||||||
|
|
||||||
|
**Não confundir os nomes** — são fragmentos separados com variáveis locais distintas.
|
||||||
|
|
||||||
|
### `TotalPedidoFragment` não chama `atualizarResumoPedido()`
|
||||||
|
|
||||||
|
`TotalPedidoFragment` é independente de `MainPedidoFragment`. Ele recalcula tudo em `FillFields()`, chamado no `onStart()`, `onResume()` e `setUserVisibleHint()`. O campo `tvAcrescimo` neste fragmento mostra o acréscimo da forma de pagamento atual do `Global.pedido` — que já foi atualizado pelo `MainPedidoFragment` via `Global.pedido.setFormapag(...)` antes de qualquer troca de aba.
|
||||||
|
|
||||||
|
### Arquivos a modificar
|
||||||
|
|
||||||
|
| Arquivo | Caminho | O que muda |
|
||||||
|
|---|---|---|
|
||||||
|
| `MainPedidoFragment.java` | `src/br/com/jcsinformatica/sarandroid/pedido/` | Substituir placeholder em `atualizarResumoPedido()` e `fillFields()` |
|
||||||
|
| `TotalPedidoFragment.java` | `src/br/com/jcsinformatica/sarandroid/pedido/` | Substituir placeholder em `FillFields()` |
|
||||||
|
|
||||||
|
**NÃO modificar nesta história:**
|
||||||
|
- `FormaPagamento.java` — `getTxAcrescimo()` já existe (Story 1.2)
|
||||||
|
- `FormaPagamentoDB.java` — leitura de `tx_acrescimo` já implementada (Story 1.2)
|
||||||
|
- `Pedido.java` — campo `vlAcrescimo` no VO não é necessário nesta história (Story 2.3 adiciona persistência)
|
||||||
|
- `PedidoDB.java` — sem mudança de banco nesta história
|
||||||
|
- `DatabaseHelper.java` — sem migração de schema nesta história
|
||||||
|
- Layouts XML — sem mudança (campos já existem da Story 2.1)
|
||||||
|
|
||||||
|
### Regras críticas do projeto (aplicáveis a esta história)
|
||||||
|
|
||||||
|
- **Sem Kotlin** — somente Java puro
|
||||||
|
- **Sem Gradle** — projeto Eclipse ADT
|
||||||
|
- **Sem JARs novos** — cálculo aritmético, sem dependências
|
||||||
|
- **Sem testes automatizados** — validação manual via emulador/dispositivo
|
||||||
|
- **Strings com acentos** — NÃO usar em código Java; esta história não adiciona strings novas
|
||||||
|
- **SQLite em thread background** — esta história NÃO lê o banco durante o cálculo, não se aplica
|
||||||
|
|
||||||
|
### Verificação manual após implementação
|
||||||
|
|
||||||
|
1. Abrir pedido com itens — aba Dados → selecionar forma de pagamento com `tx_acrescimo > 0` → verificar campo "Acréscimo" e "Total Geral" atualizados corretamente
|
||||||
|
2. Trocar para forma de pagamento com `tx_acrescimo = 0` → verificar retorno a R$ 0,00 no acréscimo e total sem acréscimo
|
||||||
|
3. Navegar para aba Total → verificar que `tvAcrescimo` e `tvTotalGeral` refletem o mesmo valor calculado
|
||||||
|
4. Abrir pedido sem forma de pagamento definida → sem crash; acréscimo = R$ 0,00
|
||||||
|
5. Conferir: `3500 × 2,5% = 87,50` e `3500 + 87,50 = 3587,50` nos campos visuais
|
||||||
|
|
||||||
|
### Inteligência de histórias anteriores
|
||||||
|
|
||||||
|
- **Story 2.1** preparou: `tvAcrescimoPedido` em `MainPedidoFragment` (vinculado em `onCreateView()`) e `tvAcrescimo` em `TotalPedidoFragment` — ambos prontos para receber valores calculados
|
||||||
|
- **Story 1.2** garantiu: `FormaPagamento.getTxAcrescimo()` retorna `double` (nunca null; NULL no SQLite → 0.0)
|
||||||
|
- **Story 2.1** documentou: padrão `vlAcrescimo = 0.0` como ponto de expansão explícito nos dois locais de `MainPedidoFragment` e como `0.0` hardcoded em `TotalPedidoFragment`
|
||||||
|
- Padrão de separação UI/background do `MainPedidoFragment.onStart()` (Thread + `runOnUiThread`) **não é necessário aqui** — cálculo é puramente em memória, sem I/O
|
||||||
|
|
||||||
|
### Review Findings
|
||||||
|
|
||||||
|
- [x] [Review][Defer] `txAcrescimo` negativo não guardado — banco de dados do ERP possui checagem para valores negativos, tornando o guard defensivo desnecessário — deferred, ERP validates at source
|
||||||
|
- [x] [Review][Defer] `Global.pedido` sem null-check em `atualizarResumoPedido()` — padrão pré-existente: método já chama `Global.pedido.getQtdTotalProduto()` etc. sem guard [MainPedidoFragment.java:958] — deferred, pre-existing
|
||||||
|
- [x] [Review][Defer] `Global.pedido` sem null-check em `TotalPedidoFragment.FillFields()` — NPE silenciosa dentro do `catch(Exception e){}` vazio; pré-existente [TotalPedidoFragment.java:74] — deferred, pre-existing
|
||||||
|
- [x] [Review][Defer] Lógica de cálculo do acréscimo duplicada em `fillFields()` e `atualizarResumoPedido()` sem método auxiliar — padrão DRY violado, pré-existente no projeto [MainPedidoFragment.java:436,975] — deferred, pre-existing
|
||||||
|
- [x] [Review][Defer] `catch (Exception e) {}` vazio em `TotalPedidoFragment.FillFields()` engole qualquer exceção do novo bloco — pré-existente [TotalPedidoFragment.java:99] — deferred, pre-existing
|
||||||
|
- [x] [Review][Defer] Aritmética de ponto flutuante (`double`) para valores monetários — padrão projeto-wide pré-existente; `Util.formataValorMonetario()` já trata formatação [todos os sites] — deferred, pre-existing
|
||||||
|
- [x] [Review][Defer] `codigoLiberacao2()` computa o hash de autorização sobre `totalGeral` sem acréscimo — supervisor autoriza valor menor que o real quando há acréscimo; escopo da Story 2.3 [MainPedidoFragment.java:652] — deferred, pre-existing
|
||||||
|
- [x] [Review][Defer] `validaCampos()`: verificação de limite de crédito usa `getTotalProduto()` excluindo acréscimo — pré-existente, fora do escopo da Story 2.2 [MainPedidoFragment.java:606] — deferred, pre-existing
|
||||||
|
- [x] [Review][Defer] `validaCampos()`: check de `vlPedMin` usa `getTotalProduto()` excluindo acréscimo — pré-existente [MainPedidoFragment.java:617] — deferred, pre-existing
|
||||||
|
- [x] [Review][Defer] Race condition: `listForPagtos` preenchido em thread background vs atualização da UI no `Handler.post` — pré-existente, não introduzido por este diff [MainPedidoFragment.java:193] — deferred, pre-existing
|
||||||
|
- [x] [Review][Defer] `totalGeral` calculado com threshold diferente em `fillFields()` (≤ STATUS_LIBERADO) vs `atualizarResumoPedido()` (< STATUS_ENVIADO) para `descontoV` — inconsistência pré-existente entre os dois métodos — deferred, pre-existing
|
||||||
|
- [x] [Review][Defer] `FormaPagamento` em `Global.pedido` pode estar stale após sync do servidor — `txAcrescimo` em memória reflete taxa pré-sync até o usuário navegar da tela — deferred, pre-existing
|
||||||
|
|
||||||
|
## Dev Agent Record
|
||||||
|
|
||||||
|
### Agent Model Used
|
||||||
|
|
||||||
|
claude-sonnet-4-6 (dev-story workflow)
|
||||||
|
|
||||||
|
### Debug Log References
|
||||||
|
|
||||||
|
### Completion Notes List
|
||||||
|
|
||||||
|
- Story 2.2 implementada em 2026-04-16.
|
||||||
|
- `MainPedidoFragment.java` — `atualizarResumoPedido()`: substituído placeholder `vlAcrescimo = 0.0` por cálculo real `totalGeral * (txAcrescimo / 100.0)` com null-guard em `getFormapag()`. `tvTotalGeral` agora exibe `totalGeral + vlAcrescimo`.
|
||||||
|
- `MainPedidoFragment.java` — `fillFields()`: mesma substituição aplicada, garantindo que o campo de acréscimo e o total já mostram o valor correto ao carregar o pedido.
|
||||||
|
- `TotalPedidoFragment.java` — `FillFields()`: substituído `Util.formataValorMonetario(0.0)` hardcoded por cálculo equivalente usando variável local `vlTotalGeral`. `tvTotalGeral` atualizado com `vlTotalGeral + vlAcrescimo`.
|
||||||
|
- Null-guard `if (Global.pedido.getFormapag() != null)` aplicado nos 3 pontos — `tx_acrescimo` default 0.0 quando sem forma de pagamento (AC3 satisfeito).
|
||||||
|
- Cálculo puramente aritmético em memória, sem I/O — NFR1 < 100ms satisfeito trivialmente (AC4 satisfeito).
|
||||||
|
- Sem testes automatizados (projeto não possui infraestrutura de testes). Validação é manual via dispositivo/emulador.
|
||||||
|
- AC1 satisfeito: subtotal × (tx_acrescimo/100) exibido ao selecionar forma com acréscimo.
|
||||||
|
- AC2 satisfeito: ao trocar para forma sem acréscimo, campo retorna a R$ 0,00 e total retorna ao subtotal.
|
||||||
|
|
||||||
|
### File List
|
||||||
|
|
||||||
|
- `src/br/com/jcsinformatica/sarandroid/pedido/MainPedidoFragment.java`
|
||||||
|
- `src/br/com/jcsinformatica/sarandroid/pedido/TotalPedidoFragment.java`
|
||||||
@@ -0,0 +1,305 @@
|
|||||||
|
# Story 2.3: Persistir Acréscimo ao Fechar o Pedido
|
||||||
|
|
||||||
|
Status: done
|
||||||
|
|
||||||
|
## Story
|
||||||
|
|
||||||
|
Como sistema,
|
||||||
|
quero gravar o valor do acréscimo calculado no registro do pedido ao fechar,
|
||||||
|
para que o valor histórico seja preservado independente de alterações futuras na taxa.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
1. **Dado** que um pedido foi editado com acréscimo R$ 87,50 e total R$ 3.587,50
|
||||||
|
**Quando** o pedido for salvo/fechado
|
||||||
|
**Então** o valor do acréscimo (87,50) está gravado no registro do pedido no SQLite (`vl_acrescimo = 87.50`)
|
||||||
|
|
||||||
|
2. **Dado** que um pedido foi salvo com acréscimo
|
||||||
|
**Quando** a taxa de acréscimo da forma de pagamento for alterada no ERP e sincronizada
|
||||||
|
**Então** o valor gravado no pedido antigo permanece inalterado (somente um novo `salvar()` atualizaria)
|
||||||
|
|
||||||
|
3. **Dado** que um pedido é criado com forma de pagamento sem acréscimo
|
||||||
|
**Quando** o pedido for salvo
|
||||||
|
**Então** o acréscimo gravado é 0,00 (`vl_acrescimo = 0.0`)
|
||||||
|
|
||||||
|
## Escopo desta história
|
||||||
|
|
||||||
|
Esta história cobre **exclusivamente** a persistência do `vl_acrescimo` calculado no banco SQLite.
|
||||||
|
|
||||||
|
- **NÃO modifica** a lógica de cálculo exibido na UI — cálculo já foi implementado na Story 2.2
|
||||||
|
- **NÃO implementa** exibição do acréscimo em consultas — esse é escopo da Story 2.4
|
||||||
|
- **SIM adiciona** migração de schema SQLite v41 → v42 (nova coluna `vl_acrescimo` em `pedido`)
|
||||||
|
- **SIM atualiza** o VO `Pedido`, o `PedidoDB` (insert/update/select) e o `MainPedidoFragment` (salvar)
|
||||||
|
|
||||||
|
## Pré-condição verificada
|
||||||
|
|
||||||
|
A tabela `pedido` **NÃO possui** colunas para acréscimo. Constatado via inspeção de:
|
||||||
|
- `DatabaseHelper.onCreate()` — schema original sem `vl_acrescimo`
|
||||||
|
- `PedidoDB.insert()` e `update()` — sem `vl_acrescimo` nas queries
|
||||||
|
- `Pedido.java` — sem campo `vlAcrescimo`
|
||||||
|
- `dbVersao` atual = **41** (última migração adicionou `tx_acrescimo` à tabela `formapag`)
|
||||||
|
|
||||||
|
Esta história inclui a adição da coluna via migração v42.
|
||||||
|
|
||||||
|
## Tasks / Subtasks
|
||||||
|
|
||||||
|
- [x] Task 1: Migração SQLite v41 → v42 em `DatabaseHelper.java` (AC: 1, 3)
|
||||||
|
- [x] 1.1 — Incrementar `dbVersao` de 41 para 42:
|
||||||
|
```java
|
||||||
|
// Linha ~10 de DatabaseHelper.java
|
||||||
|
private static final int dbVersao = 42; // era 41
|
||||||
|
```
|
||||||
|
- [x] 1.2 — Adicionar bloco de migração em `onUpgrade()` (após o bloco `if (oldVersion < 41)`):
|
||||||
|
```java
|
||||||
|
if (oldVersion < 42) {
|
||||||
|
db.execSQL("ALTER TABLE pedido ADD COLUMN vl_acrescimo REAL DEFAULT 0;");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [x] Task 2: Adicionar campo `vlAcrescimo` ao VO `Pedido.java` (AC: 1, 2, 3)
|
||||||
|
- [x] 2.1 — Adicionar campo privado após a declaração de `total` (linha ~40):
|
||||||
|
```java
|
||||||
|
private double vlAcrescimo = 0.0;
|
||||||
|
```
|
||||||
|
- [x] 2.2 — Adicionar getter e setter (junto aos demais getters/setters do VO):
|
||||||
|
```java
|
||||||
|
public double getVlAcrescimo() {
|
||||||
|
return vlAcrescimo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVlAcrescimo(double vlAcrescimo) {
|
||||||
|
this.vlAcrescimo = vlAcrescimo;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [x] Task 3: Popular `vlAcrescimo` em `Global.pedido` durante cálculo em `MainPedidoFragment.java` (AC: 1, 2, 3)
|
||||||
|
- [x] 3.1 — Em `atualizarResumoPedido()` (linha ~974), após a linha `tvAcrescimoPedido.setText(...)`, adicionar:
|
||||||
|
```java
|
||||||
|
Global.pedido.setVlAcrescimo(vlAcrescimo);
|
||||||
|
```
|
||||||
|
O bloco completo resultante ficará:
|
||||||
|
```java
|
||||||
|
double txAcrescimo = 0.0;
|
||||||
|
if (Global.pedido.getFormapag() != null) {
|
||||||
|
txAcrescimo = Global.pedido.getFormapag().getTxAcrescimo();
|
||||||
|
}
|
||||||
|
double vlAcrescimo = totalGeral * (txAcrescimo / 100.0);
|
||||||
|
tvAcrescimoPedido.setText(Util.formataValorMonetario(vlAcrescimo));
|
||||||
|
tvTotalGeral.setText(Util.formataValorMonetario(totalGeral + vlAcrescimo));
|
||||||
|
Global.pedido.setVlAcrescimo(vlAcrescimo); // ← ADICIONADO
|
||||||
|
```
|
||||||
|
- [x] 3.2 — Em `fillFields()` (linha ~439), mesmo padrão — após `tvAcrescimoPedido.setText(...)`, adicionar:
|
||||||
|
```java
|
||||||
|
Global.pedido.setVlAcrescimo(vlAcrescimo);
|
||||||
|
```
|
||||||
|
|
||||||
|
- [x] Task 4: Persistir `vl_acrescimo` em `PedidoDB.java` — INSERT (AC: 1, 3)
|
||||||
|
- [x] 4.1 — Em `insert()`, na lista de colunas (linha ~89), acrescentar `, vl_acrescimo` após `total`:
|
||||||
|
```java
|
||||||
|
sql.append(" desconto_v, total, vl_acrescimo");
|
||||||
|
```
|
||||||
|
- [x] 4.2 — Na lista de valores (linha ~110), acrescentar `, ped.getVlAcrescimo()` após `ped.getTotal()`:
|
||||||
|
```java
|
||||||
|
sql.append(ped.getTotal() + ", ");
|
||||||
|
sql.append(ped.getVlAcrescimo() + ");");
|
||||||
|
```
|
||||||
|
|
||||||
|
- [x] Task 5: Persistir `vl_acrescimo` em `PedidoDB.java` — UPDATE (AC: 1, 2, 3)
|
||||||
|
- [x] 5.1 — Em `update()`, após a linha de `total` (linha ~150), adicionar:
|
||||||
|
```java
|
||||||
|
sql.append(",");
|
||||||
|
sql.append(" vl_acrescimo = ").append(ped.getVlAcrescimo());
|
||||||
|
```
|
||||||
|
O final do UPDATE ficará:
|
||||||
|
```java
|
||||||
|
sql.append(" total = ").append(ped.getTotal());
|
||||||
|
sql.append(",");
|
||||||
|
sql.append(" vl_acrescimo = ").append(ped.getVlAcrescimo());
|
||||||
|
sql.append(" WHERE id_pedido = " + ped.getId() + ";");
|
||||||
|
```
|
||||||
|
|
||||||
|
- [x] Task 6: Ler `vl_acrescimo` em `PedidoDB.selectAllFull()` (necessário para Story 2.4) (AC: 1, 2)
|
||||||
|
- [x] 6.1 — Na construção do SELECT (linha ~204), adicionar `a.vl_acrescimo` ao final:
|
||||||
|
```java
|
||||||
|
sql.append(" a.cod_liberacao, a.desconto_v, b.st_especifica, a.total, c.desco_perc, b.desc_cliente_rede, a.vl_acrescimo");
|
||||||
|
```
|
||||||
|
Resultado: `a.vl_acrescimo` é o **índice 68** do cursor. Atualizado em ambos os métodos `selectAllFull()` (lista e por id).
|
||||||
|
- [x] 6.2 — No loop de leitura do cursor (após `cli.setDesc_cliente_rede(c.getInt(67))`), adicionado:
|
||||||
|
```java
|
||||||
|
pedido.setVlAcrescimo(c.getDouble(68));
|
||||||
|
```
|
||||||
|
Atualizado em ambos os métodos `selectAllFull()`.
|
||||||
|
|
||||||
|
## Dev Notes
|
||||||
|
|
||||||
|
### Estrutura atual do `pedido` — schema SQLite (sem `vl_acrescimo`)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE pedido (
|
||||||
|
id_pedido INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
...
|
||||||
|
desconto_v REAL,
|
||||||
|
total REAL
|
||||||
|
-- vl_acrescimo ainda NÃO existe (será adicionada pela migração v42)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### `Pedido.getTotal()` é COMPUTADO, não armazenado
|
||||||
|
|
||||||
|
**Atenção crítica:** `getTotal()` em `Pedido.java` (linha ~750) é um método computado:
|
||||||
|
```java
|
||||||
|
public double getTotal() {
|
||||||
|
return (getTotalProduto() - getTotalDesconto()) + getTotalIcmsST();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
O `setTotal(double total)` existe mas **NÃO afeta** `getTotal()` — o getter sempre recomputa a partir dos itens.
|
||||||
|
Por isso, `PedidoDB.insert()` usa `ped.getTotal()` que é o subtotal dos produtos **sem acréscimo**.
|
||||||
|
O `vl_acrescimo` é um valor independente que precisa de campo próprio no VO e na tabela.
|
||||||
|
|
||||||
|
### Estratégia de persistência — por que popular em `atualizarResumoPedido()` e `fillFields()`
|
||||||
|
|
||||||
|
A abordagem adotada é: `Global.pedido.vlAcrescimo` é atualizado **toda vez que o cálculo da UI é executado**, mantendo-o sempre sincronizado com o valor exibido. No momento do `pedDB.salvar()`, o valor já está pronto em `Global.pedido.getVlAcrescimo()`.
|
||||||
|
|
||||||
|
Isso evita recalcular `vlAcrescimo` no `onClick()` do botão salvar — que seria:
|
||||||
|
- Duplicação de lógica
|
||||||
|
- Risco de divergência com o valor exibido (e.g., diferença no tratamento de IPI via `!precoComIpi`)
|
||||||
|
|
||||||
|
O `totalGeral` local em `atualizarResumoPedido()` inclui IPI condicionalmente, enquanto `Global.pedido.getTotal()` não inclui IPI. Calcular no `onClick()` exigiria replicar toda a lógica com IPI. Usar o valor já calculado e armazenado no VO é mais seguro.
|
||||||
|
|
||||||
|
### Fluxo de salvar — ponto exato de chamada
|
||||||
|
|
||||||
|
`MainPedidoFragment.java`, método `onClick()`, seção de sucesso da validação (linha ~549–554):
|
||||||
|
|
||||||
|
```java
|
||||||
|
// Estado ATUAL após Story 2.2:
|
||||||
|
if (erro == null) {
|
||||||
|
PedidoDB pedDB = new PedidoDB();
|
||||||
|
try {
|
||||||
|
Global.pedido.setObservacao(etObservacao.getText().toString());
|
||||||
|
Global.pedido.setDescontoV(Global.pedido.getTotalDesconto()); // linha 553
|
||||||
|
pedDB.salvar(getActivity().getApplicationContext(), Global.pedido); // linha 554
|
||||||
|
Global.pedido = null;
|
||||||
|
getActivity().finish();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Util.sendError(getActivity(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`Global.pedido.vlAcrescimo` será populado ANTES de `pedDB.salvar()` via Task 3 (chamado em `atualizarResumoPedido()` / `fillFields()` durante a edição). Nenhuma alteração é necessária no `onClick()`.
|
||||||
|
|
||||||
|
### Mapeamento de índices em `selectAllFull()` — cuidado com offset
|
||||||
|
|
||||||
|
Índices atuais do cursor em `selectAllFull()` (linha ~204 até ~291):
|
||||||
|
- 0..1: `A.id_pedido, A.id_empresa`
|
||||||
|
- ...
|
||||||
|
- 62: `a.cod_liberacao`
|
||||||
|
- 63: `a.desconto_v`
|
||||||
|
- 64: `b.st_especifica`
|
||||||
|
- 65: `a.total` — `pedido.setTotal(c.getDouble(65))`
|
||||||
|
- 66: `c.desco_perc` — `pedido.getFormapag().setDescontoPerc(c.getDouble(66))`
|
||||||
|
- 67: `b.desc_cliente_rede` — `cli.setDesc_cliente_rede(c.getInt(67))`
|
||||||
|
- **68: `a.vl_acrescimo`** ← ADICIONAR com `pedido.setVlAcrescimo(c.getDouble(68))`
|
||||||
|
|
||||||
|
Não alterar os índices existentes — apenas acrescentar ao final.
|
||||||
|
|
||||||
|
### Null-safety e pedidos legados
|
||||||
|
|
||||||
|
A coluna `vl_acrescimo REAL DEFAULT 0` garante que pedidos já existentes no dispositivo (criados antes desta migração) terão `vl_acrescimo = 0.0` automaticamente — sem necessidade de backfill manual.
|
||||||
|
|
||||||
|
Quando lidos pelo `selectAllFull()`, `pedido.setVlAcrescimo(c.getDouble(68))` retornará `0.0` para pedidos antigos — comportamento correto para AC3.
|
||||||
|
|
||||||
|
### Migração idempotente
|
||||||
|
|
||||||
|
O guard `if (oldVersion < 42)` em `onUpgrade()` garante que a migração não seja executada mais de uma vez, mesmo em atualizações encadeadas (e.g., v40→v42 executa os blocos v41 e v42 na sequência correta).
|
||||||
|
|
||||||
|
### Arquivos a modificar
|
||||||
|
|
||||||
|
| Arquivo | Caminho | O que muda |
|
||||||
|
|---|---|---|
|
||||||
|
| `DatabaseHelper.java` | `src/br/com/jcsinformatica/sarandroid/database/` | `dbVersao = 42`, migration block v42 |
|
||||||
|
| `Pedido.java` | `src/br/com/jcsinformatica/sarandroid/vo/` | Campo `vlAcrescimo`, getter/setter |
|
||||||
|
| `MainPedidoFragment.java` | `src/br/com/jcsinformatica/sarandroid/pedido/` | `setVlAcrescimo()` em `atualizarResumoPedido()` e `fillFields()` |
|
||||||
|
| `PedidoDB.java` | `src/br/com/jcsinformatica/sarandroid/database/` | INSERT, UPDATE e `selectAllFull()` |
|
||||||
|
|
||||||
|
**NÃO modificar nesta história:**
|
||||||
|
- `TotalPedidoFragment.java` — sem persistência aqui; save só ocorre via `MainPedidoFragment`
|
||||||
|
- `FormaPagamento.java` / `FormaPagamentoDB.java` — inalterados
|
||||||
|
- `UpdatePedidoActivity.java` — apenas container de ViewPager, sem lógica de save
|
||||||
|
- Layouts XML — sem mudança de UI nesta história
|
||||||
|
- `PedidoPGSQL.java` (se existir) — sync com PostgreSQL é escopo separado
|
||||||
|
|
||||||
|
### Regras críticas do projeto (aplicáveis a esta história)
|
||||||
|
|
||||||
|
- **Sem Kotlin** — somente Java puro
|
||||||
|
- **Sem Gradle** — projeto Eclipse ADT, sem `build.gradle`
|
||||||
|
- **Sem JARs novos** — sem dependências adicionais
|
||||||
|
- **Sem testes automatizados** — o projeto não possui infraestrutura de testes; validação manual via dispositivo/emulador
|
||||||
|
- **`dbVersao`** deve ser incrementado para `42` — nunca pular versões
|
||||||
|
- **Thread background para SQLite** — `salvar()` em `MainPedidoFragment` já é chamado na UI thread (dentro de `onClick()`), mas o padrão do projeto aceita isso para writes pontuais; a leitura em `selectAllFull()` é gerenciada pelo chamador
|
||||||
|
- **Strings com acentos** — NÃO usar em código Java (sem `"Acréscimo"` em strings hard-coded em Java)
|
||||||
|
|
||||||
|
### Verificação manual após implementação
|
||||||
|
|
||||||
|
1. Criar pedido com itens, selecionar forma de pagamento com `tx_acrescimo > 0`, salvar
|
||||||
|
2. Via SQLite Browser (ou `adb shell`), verificar na tabela `pedido` que `vl_acrescimo` contém o valor correto
|
||||||
|
3. Alterar `tx_acrescimo` da forma de pagamento diretamente no banco SQLite (simulando sync do ERP)
|
||||||
|
4. Reabrir o pedido salvo — verificar que `vl_acrescimo` no registro do pedido NÃO mudou (AC2)
|
||||||
|
5. Criar pedido com forma de pagamento sem acréscimo, salvar — verificar `vl_acrescimo = 0` (AC3)
|
||||||
|
6. Verificar que `dbVersao` no `DatabaseHelper` é 42 e que a migração foi aplicada sem erro
|
||||||
|
|
||||||
|
### Inteligência de histórias anteriores
|
||||||
|
|
||||||
|
- **Story 1.1** migrou `formapag` v40→v41; o padrão de migração `if (oldVersion < N)` em `onUpgrade()` deve ser replicado aqui para v42
|
||||||
|
- **Story 1.2** adicionou `tx_acrescimo` a `FormaPagamento` VO com getter `getTxAcrescimo()` — campo utilizado aqui para calcular `vlAcrescimo` antes de persistir
|
||||||
|
- **Story 2.1** adicionou campos de UI (`tvAcrescimoPedido`, `tvAcrescimo`) — nenhuma alteração de UI nesta história
|
||||||
|
- **Story 2.2** implementou o cálculo de `vlAcrescimo` em `atualizarResumoPedido()` e `fillFields()` — esta história apenas adiciona `Global.pedido.setVlAcrescimo(vlAcrescimo)` nesses dois pontos já existentes
|
||||||
|
- **Review da Story 2.2** identificou: `codigoLiberacao2()` computa hash sobre `totalGeral` sem acréscimo — esse comportamento permanece inalterado nesta história (deferred)
|
||||||
|
- **Review da Story 2.2** identificou: `validaCampos()` usa `getTotalProduto()` para limite de crédito e pedido mínimo, excluindo acréscimo — permanece deferred
|
||||||
|
|
||||||
|
### Git intelligence (commits recentes)
|
||||||
|
|
||||||
|
- `9b70ee3` Story 2.2: cálculo em `MainPedidoFragment.atualizarResumoPedido()`, `fillFields()`, `TotalPedidoFragment.FillFields()`
|
||||||
|
- `474d98f` Story 2.1: campos `tvAcrescimoPedido` e `tvAcrescimo` adicionados ao layout e vinculados
|
||||||
|
- `168b9db` Story 1.2: `FormaPagamentoDB` lendo `tx_acrescimo`; `FormaPagamento` com getter
|
||||||
|
- `4bfccc7` Story 1.1: migração v40→v41 em `DatabaseHelper.onUpgrade()`
|
||||||
|
|
||||||
|
## Review Findings
|
||||||
|
|
||||||
|
- [x] [Review][Defer] PedidoPGSQL não inclui `vl_acrescimo` na sync para PostgreSQL [PedidoPGSQL.java] — deferred, out of scope per spec; escopo de story futura de sincronização PG
|
||||||
|
- [x] [Review][Defer] `fillFields()` recalcula `vlAcrescimo` com `tx_acrescimo` atual, sobrescrevendo valor carregado do BD ao abrir pedido existente [MainPedidoFragment.java:443-446] — deferred, by-design; AC2 cobre sync automático, não re-salvamento explícito pelo usuário; consistente com comportamento de `descontoV`
|
||||||
|
- [x] [Review][Defer] MD5 do pedido não inclui `vl_acrescimo` na hash de change-detection — deferred, impacto futuro; PG ainda não tem a coluna; reavaliar quando PedidoPGSQL for atualizado
|
||||||
|
- [x] [Review][Defer] Índice 59 do cursor lê `DATE(D.data_inicio)` em vez de `C.md5` para `FormaPagamento.setMd5()` [PedidoDB.java:269,392] — deferred, pré-existente não introduzido por este diff
|
||||||
|
- [x] [Review][Defer] Concatenação de string para SQL em vez de `PreparedStatement` [PedidoDB.java:85-153] — deferred, padrão projeto-wide pré-existente
|
||||||
|
- [x] [Review][Defer] `Global.pedido` sem null-check antes de `setVlAcrescimo()` [MainPedidoFragment.java:446,986] — deferred, pré-existente; mesmo padrão já deferred na Story 2.2
|
||||||
|
- [x] [Review][Defer] Aritmética de ponto flutuante (`double`) para valor monetário `vl_acrescimo` — deferred, padrão projeto-wide pré-existente; `Util.formataValorMonetario()` já trata formatação
|
||||||
|
|
||||||
|
## Dev Agent Record
|
||||||
|
|
||||||
|
### Agent Model Used
|
||||||
|
|
||||||
|
claude-sonnet-4-6 (dev-story workflow)
|
||||||
|
|
||||||
|
### Debug Log References
|
||||||
|
|
||||||
|
### Completion Notes List
|
||||||
|
|
||||||
|
- Story 2.3 implementada em 2026-04-16.
|
||||||
|
- `DatabaseHelper.java`: `dbVersao` incrementado de 41 para 42. Bloco de migração `if (oldVersion < 42)` adicionado em `onUpgrade()`, adicionando `vl_acrescimo REAL DEFAULT 0` à tabela `pedido`. Migração idempotente via guard.
|
||||||
|
- `Pedido.java`: campo `private double vlAcrescimo = 0.0` adicionado após `total`. Getter `getVlAcrescimo()` e setter `setVlAcrescimo(double)` adicionados.
|
||||||
|
- `MainPedidoFragment.java`: `Global.pedido.setVlAcrescimo(vlAcrescimo)` adicionado em dois pontos — após cálculo em `fillFields()` (linha ~446) e em `atualizarResumoPedido()` (linha ~986). O valor gravado no VO é exatamente o valor exibido na UI (sem recalcular no `onClick()`), evitando divergência de IPI.
|
||||||
|
- `PedidoDB.java` INSERT: coluna `vl_acrescimo` adicionada ao `INSERT INTO pedido(...)` e valor `ped.getVlAcrescimo()` adicionado na lista de valores.
|
||||||
|
- `PedidoDB.java` UPDATE: `vl_acrescimo = ped.getVlAcrescimo()` adicionado ao SET do UPDATE.
|
||||||
|
- `PedidoDB.java` SELECT: `a.vl_acrescimo` adicionado ao SELECT em ambos os métodos `selectAllFull()` (lista e por id); `pedido.setVlAcrescimo(c.getDouble(68))` adicionado após leitura do índice 67 em ambos.
|
||||||
|
- AC1 satisfeito: `vl_acrescimo` é gravado na tabela `pedido` ao salvar (via INSERT ou UPDATE).
|
||||||
|
- AC2 satisfeito: `vl_acrescimo` só é atualizado no banco quando `pedDB.salvar()` é chamado explicitamente — mudanças de `tx_acrescimo` na forma de pagamento não afetam pedidos já salvos.
|
||||||
|
- AC3 satisfeito: `vlAcrescimo` inicializa em `0.0`; forma de pagamento sem acréscimo resulta em `vlAcrescimo = 0.0 * 0 = 0.0` gravado.
|
||||||
|
- Sem testes automatizados (projeto não possui infraestrutura de testes). Validação manual via dispositivo/emulador.
|
||||||
|
|
||||||
|
### File List
|
||||||
|
|
||||||
|
- `src/br/com/jcsinformatica/sarandroid/database/DatabaseHelper.java`
|
||||||
|
- `src/br/com/jcsinformatica/sarandroid/vo/Pedido.java`
|
||||||
|
- `src/br/com/jcsinformatica/sarandroid/pedido/MainPedidoFragment.java`
|
||||||
|
- `src/br/com/jcsinformatica/sarandroid/database/PedidoDB.java`
|
||||||
@@ -0,0 +1,414 @@
|
|||||||
|
# Story 2.4: Exibir Acréscimo na Consulta de Pedidos
|
||||||
|
|
||||||
|
Status: done
|
||||||
|
|
||||||
|
## Story
|
||||||
|
|
||||||
|
Como representante de vendas,
|
||||||
|
quero ver o total correto (com acréscimo) na consulta de pedidos já fechados,
|
||||||
|
para que o histórico reflita os valores reais praticados, preservados no momento do fechamento.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
1. **Dado** que consulto um pedido fechado (`status >= STATUS_ENVIADO`) que possui `vl_acrescimo > 0` gravado no registro
|
||||||
|
**Quando** a tela de detalhe do pedido (`UpdatePedidoActivity` via `MainPedidoFragment` + `TotalPedidoFragment`) for exibida
|
||||||
|
**Então** o campo "Acréscimo" exibe o valor **persistido** (`Global.pedido.getVlAcrescimo()`) — **não recalculado** a partir da `tx_acrescimo` atual da forma de pagamento
|
||||||
|
**E** o total geral exibido inclui esse acréscimo persistido (`subtotal + vl_acrescimo_persistido`)
|
||||||
|
|
||||||
|
2. **Dado** que consulto um pedido fechado criado antes desta funcionalidade (sem `vl_acrescimo` ou com `vl_acrescimo = 0`)
|
||||||
|
**Quando** a tela de detalhe do pedido for exibida
|
||||||
|
**Então** o pedido é exibido normalmente, campo "Acréscimo" mostra R$ 0,00, total geral = subtotal
|
||||||
|
**E** nenhum `NullPointerException` ou crash ocorre
|
||||||
|
|
||||||
|
3. **Dado** que a migração SQLite v42→v43 é executada em um dispositivo com `pedido_consulta` sem `vl_acrescimo`
|
||||||
|
**Quando** `DatabaseHelper.onUpgrade()` rodar
|
||||||
|
**Então** a coluna `vl_acrescimo REAL DEFAULT 0` é adicionada à tabela `pedido_consulta`
|
||||||
|
**E** todos os registros existentes são preservados com `vl_acrescimo = 0`
|
||||||
|
**E** `dbVersao = 43`
|
||||||
|
|
||||||
|
4. **Dado** que um novo pedido chega via sync PostgreSQL (`AtualizaDados.atualizaPedidosConsulta`)
|
||||||
|
**Quando** `PedidoConsultaDB.saveAll()` inserir/atualizar o registro em `pedido_consulta`
|
||||||
|
**Então** o valor de `ped.getVlAcrescimo()` (0.0 por enquanto — sync PG do acréscimo é deferred) é gravado em `pedido_consulta.vl_acrescimo` sem erro
|
||||||
|
**E** a sync segue sem interrupção mesmo que o VO chegue com `vlAcrescimo = 0.0`
|
||||||
|
|
||||||
|
5. **Dado** que `PedidoConsultaDB.selectFull()` carrega um pedido
|
||||||
|
**Quando** o cursor percorrer as colunas adicionais
|
||||||
|
**Então** `a.vl_acrescimo` é lida e `pedido.setVlAcrescimo()` é populado com o valor do banco (ou `0.0` quando a coluna é NULL)
|
||||||
|
|
||||||
|
## Escopo desta história
|
||||||
|
|
||||||
|
Esta história cobre **exclusivamente**:
|
||||||
|
|
||||||
|
- **SIM** migração SQLite v42 → v43 adicionando `vl_acrescimo` na tabela `pedido_consulta`
|
||||||
|
- **SIM** persistência em `PedidoConsultaDB.insert()` e `updateErp()`
|
||||||
|
- **SIM** leitura em `PedidoConsultaDB.selectFull()` — cursor index 66 (novo, adicionado ao fim)
|
||||||
|
- **SIM** exibição correta do acréscimo **persistido** em `MainPedidoFragment.fillFields()` e `atualizarResumoPedido()` quando `status >= STATUS_ENVIADO`
|
||||||
|
- **SIM** exibição correta em `TotalPedidoFragment.FillFields()` quando `status >= STATUS_ENVIADO`
|
||||||
|
|
||||||
|
**NÃO cobre (deferred):**
|
||||||
|
|
||||||
|
- Sync do acréscimo via PG (`PedidoPGSQL.selectAllPedConsulta` / `PedidoPGSQL.insert`) — PG ainda não possui a coluna; reavaliar em story futura (ver `deferred-work.md`)
|
||||||
|
- Alteração do layout `list_browse_pedido.xml` ou do VO `PedidoList` — o total exibido na lista usa `pedido_consulta.total` (já persistido no save) e permanece inalterado
|
||||||
|
- Atualização de `PedidoConsultaDB.selectListResumo()` — `total` da lista permanece como está (deferred até sync PG carregar acréscimo)
|
||||||
|
- Atualização de `PedidoConsultaDB.selectTotais()` (agregação de vendas em `ConsultaVendasActivity`) — soma `peditem_consulta.vl_totliq`, independente de acréscimo (deferred)
|
||||||
|
- MD5 do pedido não inclui `vl_acrescimo` (deferred per review da Story 2.3)
|
||||||
|
|
||||||
|
## Pré-condições verificadas
|
||||||
|
|
||||||
|
Inspeção do código confirma:
|
||||||
|
|
||||||
|
- Tabela `pedido_consulta` **NÃO possui** coluna `vl_acrescimo` ([DatabaseHelper.java:261-285](../../src/br/com/jcsinformatica/sarandroid/database/DatabaseHelper.java)) — nunca foi migrada; Story 2.3 migrou apenas a tabela `pedido`
|
||||||
|
- `dbVersao` atual = **42** ([DatabaseHelper.java:10](../../src/br/com/jcsinformatica/sarandroid/database/DatabaseHelper.java))
|
||||||
|
- `PedidoConsultaDB.insert()` e `updateErp()` **não tocam** em `vl_acrescimo` ([PedidoConsultaDB.java:98-194](../../src/br/com/jcsinformatica/sarandroid/database/PedidoConsultaDB.java))
|
||||||
|
- `PedidoConsultaDB.selectFull()` percorre índices 0..65; **não** lê `vl_acrescimo` ([PedidoConsultaDB.java:196-302](../../src/br/com/jcsinformatica/sarandroid/database/PedidoConsultaDB.java))
|
||||||
|
- `ThreadAbrirPedidoConsulta` força `status = STATUS_ENVIADO` antes de abrir `UpdatePedidoActivity` ([ThreadAbrirPedidoConsulta.java:22-24](../../src/br/com/jcsinformatica/sarandroid/consulta/pedido/ThreadAbrirPedidoConsulta.java)) — ou seja: todo pedido aberto via consulta chega em `MainPedidoFragment` com `status >= STATUS_ENVIADO`
|
||||||
|
- `MainPedidoFragment.fillFields()` (linha ~439-446) e `atualizarResumoPedido()` (linha ~979-986) **recalculam** `vlAcrescimo = totalGeral × (txAcrescimo/100)` e **sobrescrevem** `Global.pedido.setVlAcrescimo(vlAcrescimo)` — comportamento correto para edição, mas **destrói o valor histórico** em consulta
|
||||||
|
- `TotalPedidoFragment.FillFields()` (linha ~97-103) faz o mesmo recálculo — também precisa ser corrigido
|
||||||
|
- `Pedido.vlAcrescimo` já existe como campo + getter/setter ([Pedido.java:41,759-764](../../src/br/com/jcsinformatica/sarandroid/vo/Pedido.java)) — Story 2.3
|
||||||
|
|
||||||
|
## Tasks / Subtasks
|
||||||
|
|
||||||
|
- [x] **Task 1:** Migração SQLite v42 → v43 em `DatabaseHelper.java` (AC: 3)
|
||||||
|
- [x] 1.1 — Incrementar `dbVersao` de 42 para 43 ([DatabaseHelper.java:10](../../src/br/com/jcsinformatica/sarandroid/database/DatabaseHelper.java)):
|
||||||
|
```java
|
||||||
|
final static int dbVersao = 43; // era 42
|
||||||
|
```
|
||||||
|
- [x] 1.2 — Adicionar bloco em `onUpgrade()` **ao final**, **após** o bloco `if (oldVersion < 42)` (linha ~708):
|
||||||
|
```java
|
||||||
|
if (oldVersion < 43) {
|
||||||
|
db.execSQL("ALTER TABLE pedido_consulta ADD COLUMN vl_acrescimo REAL DEFAULT 0;");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- [x] 1.3 — Atualizar também `onCreate()` para incluir `vl_acrescimo REAL` na definição de `CREATE TABLE pedido_consulta` (linha ~261-285). Adicionar ao final da lista de colunas (antes das `FOREIGN KEY`):
|
||||||
|
```java
|
||||||
|
+ " total REAL,"
|
||||||
|
+ " vl_acrescimo REAL DEFAULT 0,"
|
||||||
|
+ " FOREIGN KEY ( id_empresa ) REFERENCES empresa ( id_empresa ),"
|
||||||
|
```
|
||||||
|
> ⚠️ Essencial: `onCreate` + `onUpgrade` devem permanecer em sincronia para instalações novas vs. atualizadas.
|
||||||
|
|
||||||
|
- [x] **Task 2:** Persistir `vl_acrescimo` em `PedidoConsultaDB.insert()` (AC: 4)
|
||||||
|
- [x] 2.1 — Em [PedidoConsultaDB.java:119-123](../../src/br/com/jcsinformatica/sarandroid/database/PedidoConsultaDB.java), adicionar `vl_acrescimo` à lista de colunas:
|
||||||
|
```java
|
||||||
|
sql.append(" id_pedido_consulta, id_empresa, numero, id_erp, numero_erp, status, data,");
|
||||||
|
sql.append(" id_cliente, data_emissao, id_formapag, observacao, id_pauta, md5, tipo, permite_flex,");
|
||||||
|
sql.append(" desconto_p, desconto_v, total, vl_acrescimo");
|
||||||
|
sql.append(") VALUES(");
|
||||||
|
```
|
||||||
|
- [x] 2.2 — Em [PedidoConsultaDB.java:146](../../src/br/com/jcsinformatica/sarandroid/database/PedidoConsultaDB.java), ajustar o último `append` para não fechar ainda e adicionar o valor:
|
||||||
|
```java
|
||||||
|
sql.append(ped.getTotalPedidoConsulta() + ", "); //total
|
||||||
|
sql.append(ped.getVlAcrescimo() + ");"); //vl_acrescimo
|
||||||
|
```
|
||||||
|
> O valor virá `0.0` da sync PG (getter default) enquanto a sync não carregar o campo — comportamento aceitável (AC 4).
|
||||||
|
|
||||||
|
- [x] **Task 3:** Persistir `vl_acrescimo` em `PedidoConsultaDB.updateErp()` (AC: 4)
|
||||||
|
- [x] 3.1 — Em [PedidoConsultaDB.java:181-182](../../src/br/com/jcsinformatica/sarandroid/database/PedidoConsultaDB.java), adicionar `vl_acrescimo` ao SET (antes do `WHERE`):
|
||||||
|
```java
|
||||||
|
sql.append(" total = " +ped.getTotalPedidoConsulta()+", ");
|
||||||
|
sql.append(" vl_acrescimo = " +ped.getVlAcrescimo());
|
||||||
|
sql.append(" WHERE id_empresa = " + Global.getEmpresa().getId() + " AND id_erp = " + ped.getIdErp());
|
||||||
|
```
|
||||||
|
> ⚠️ Atenção: o `total` atual **não** termina com vírgula; mudar para `+ ", "` ao inserir o novo campo.
|
||||||
|
|
||||||
|
- [x] **Task 4:** Ler `vl_acrescimo` em `PedidoConsultaDB.selectFull()` (AC: 5)
|
||||||
|
- [x] 4.1 — No SELECT ([PedidoConsultaDB.java:211](../../src/br/com/jcsinformatica/sarandroid/database/PedidoConsultaDB.java)), acrescentar `a.vl_acrescimo` ao final da lista:
|
||||||
|
```java
|
||||||
|
sql.append(" a.permite_flex, a.desconto_p, a.desconto_v, a.total, a.vl_acrescimo");
|
||||||
|
```
|
||||||
|
→ `a.vl_acrescimo` é o **índice 66** do cursor (após 65 = `a.total`).
|
||||||
|
- [x] 4.2 — No loop de leitura ([PedidoConsultaDB.java:293](../../src/br/com/jcsinformatica/sarandroid/database/PedidoConsultaDB.java)), após `pedido.setTotal(c.getDouble(65));`, adicionar:
|
||||||
|
```java
|
||||||
|
pedido.setTotal(c.getDouble(65));
|
||||||
|
pedido.setVlAcrescimo(c.getDouble(66));
|
||||||
|
```
|
||||||
|
|
||||||
|
- [x] **Task 5:** Preservar `vl_acrescimo` histórico em `MainPedidoFragment.fillFields()` (AC: 1, 2)
|
||||||
|
- [x] 5.1 — Em [MainPedidoFragment.java:439-446](../../src/br/com/jcsinformatica/sarandroid/pedido/MainPedidoFragment.java), envolver o recálculo em um guard por status:
|
||||||
|
```java
|
||||||
|
// Estado atual:
|
||||||
|
double txAcrescimo = 0.0;
|
||||||
|
if (Global.pedido.getFormapag() != null) {
|
||||||
|
txAcrescimo = Global.pedido.getFormapag().getTxAcrescimo();
|
||||||
|
}
|
||||||
|
double vlAcrescimo = totalGeral * (txAcrescimo / 100.0);
|
||||||
|
tvAcrescimoPedido.setText(Util.formataValorMonetario(vlAcrescimo));
|
||||||
|
tvTotalGeral.setText(Util.formataValorMonetario(totalGeral + vlAcrescimo));
|
||||||
|
Global.pedido.setVlAcrescimo(vlAcrescimo);
|
||||||
|
```
|
||||||
|
**Substituir por:**
|
||||||
|
```java
|
||||||
|
double vlAcrescimo;
|
||||||
|
if (Global.pedido.getStatus() >= Pedido.STATUS_ENVIADO) {
|
||||||
|
// Pedido fechado (consulta) — usa valor persistido, NÃO recalcula
|
||||||
|
vlAcrescimo = Global.pedido.getVlAcrescimo();
|
||||||
|
} else {
|
||||||
|
// Pedido em edição — recalcula a partir da taxa vigente
|
||||||
|
double txAcrescimo = 0.0;
|
||||||
|
if (Global.pedido.getFormapag() != null) {
|
||||||
|
txAcrescimo = Global.pedido.getFormapag().getTxAcrescimo();
|
||||||
|
}
|
||||||
|
vlAcrescimo = totalGeral * (txAcrescimo / 100.0);
|
||||||
|
Global.pedido.setVlAcrescimo(vlAcrescimo);
|
||||||
|
}
|
||||||
|
tvAcrescimoPedido.setText(Util.formataValorMonetario(vlAcrescimo));
|
||||||
|
tvTotalGeral.setText(Util.formataValorMonetario(totalGeral + vlAcrescimo));
|
||||||
|
```
|
||||||
|
|
||||||
|
- [x] **Task 6:** Preservar `vl_acrescimo` histórico em `MainPedidoFragment.atualizarResumoPedido()` (AC: 1, 2)
|
||||||
|
- [x] 6.1 — Em [MainPedidoFragment.java:979-986](../../src/br/com/jcsinformatica/sarandroid/pedido/MainPedidoFragment.java), aplicar o **mesmo padrão** da Task 5:
|
||||||
|
```java
|
||||||
|
double vlAcrescimo;
|
||||||
|
if (Global.pedido.getStatus() >= Pedido.STATUS_ENVIADO) {
|
||||||
|
vlAcrescimo = Global.pedido.getVlAcrescimo();
|
||||||
|
} else {
|
||||||
|
double txAcrescimo = 0.0;
|
||||||
|
if (Global.pedido.getFormapag() != null) {
|
||||||
|
txAcrescimo = Global.pedido.getFormapag().getTxAcrescimo();
|
||||||
|
}
|
||||||
|
vlAcrescimo = totalGeral * (txAcrescimo / 100.0);
|
||||||
|
Global.pedido.setVlAcrescimo(vlAcrescimo);
|
||||||
|
}
|
||||||
|
tvAcrescimoPedido.setText(Util.formataValorMonetario(vlAcrescimo));
|
||||||
|
tvTotalGeral.setText(Util.formataValorMonetario(totalGeral + vlAcrescimo));
|
||||||
|
```
|
||||||
|
> Não duplicar em helper — manter paralelo à Task 5 segue o padrão pré-existente do projeto (cálculo duplicado em `fillFields` e `atualizarResumoPedido` já deferred per review da Story 2.2).
|
||||||
|
|
||||||
|
- [x] **Task 7:** Preservar `vl_acrescimo` histórico em `TotalPedidoFragment.FillFields()` (AC: 1, 2)
|
||||||
|
- [x] 7.1 — Em [TotalPedidoFragment.java:97-103](../../src/br/com/jcsinformatica/sarandroid/pedido/TotalPedidoFragment.java), aplicar o mesmo padrão:
|
||||||
|
```java
|
||||||
|
double vlAcrescimo;
|
||||||
|
if (Global.pedido.getStatus() >= Pedido.STATUS_ENVIADO) {
|
||||||
|
vlAcrescimo = Global.pedido.getVlAcrescimo();
|
||||||
|
} else {
|
||||||
|
double txAcrescimo = 0.0;
|
||||||
|
if (Global.pedido.getFormapag() != null) {
|
||||||
|
txAcrescimo = Global.pedido.getFormapag().getTxAcrescimo();
|
||||||
|
}
|
||||||
|
vlAcrescimo = vlTotalGeral * (txAcrescimo / 100.0);
|
||||||
|
}
|
||||||
|
tvAcrescimo.setText(Util.formataValorMonetario(vlAcrescimo));
|
||||||
|
tvTotalGeral.setText(Util.formataValorMonetario(vlTotalGeral + vlAcrescimo));
|
||||||
|
```
|
||||||
|
> `TotalPedidoFragment` **não** chama `setVlAcrescimo` — o save é gerenciado por `MainPedidoFragment`, então não é necessário replicar essa gravação aqui mesmo no branch de edição (manter paralelo ao comportamento pré-existente).
|
||||||
|
|
||||||
|
## Dev Notes
|
||||||
|
|
||||||
|
### Fluxo de abertura de pedido via consulta — verificado
|
||||||
|
|
||||||
|
```
|
||||||
|
BrowsePedidoConsulta.onItemClick(pos)
|
||||||
|
→ ThreadAbrirPedidoConsulta.run()
|
||||||
|
→ PedidoConsultaDB.selectFull(id) [carrega Pedido VO do pedido_consulta]
|
||||||
|
→ pedido.setStatus(STATUS_ENVIADO) [força status, se < 2] ← LINHA 22-24
|
||||||
|
→ Global.pedido = pedido
|
||||||
|
→ brwPedido.abrirUpdate()
|
||||||
|
→ startActivity(UpdatePedidoActivity)
|
||||||
|
→ MainPedidoFragment.fillFields() [recalcula vlAcrescimo — BUG]
|
||||||
|
→ TotalPedidoFragment.FillFields() [idem]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Conclusão:** todo pedido aberto via consulta tem `status >= STATUS_ENVIADO` garantido. O guard `status >= STATUS_ENVIADO` é suficiente.
|
||||||
|
|
||||||
|
Pedidos via `BrowsePedido` → `ThreadAbrirPedido` → `PedidoDB.selectFull()` (editáveis) preservam o status original; a mesma correção funciona em ambos os fluxos.
|
||||||
|
|
||||||
|
### Por que NÃO usar `isEditable()` / flag custom
|
||||||
|
|
||||||
|
O projeto já tem `Pedido.STATUS_*` constantes. `status >= STATUS_ENVIADO` (valor 2) cobre ENVIADO, CANCELADO, EMITIDO — exatamente os status "fechados" (não-editáveis). Inventar uma nova flag seria reinvenção.
|
||||||
|
|
||||||
|
### Mapeamento de índices em `PedidoConsultaDB.selectFull()` — cuidado com offset
|
||||||
|
|
||||||
|
Índices atuais do cursor ([PedidoConsultaDB.java:200-211](../../src/br/com/jcsinformatica/sarandroid/database/PedidoConsultaDB.java)):
|
||||||
|
|
||||||
|
- 0..1: `A.id_pedido_consulta, A.id_empresa`
|
||||||
|
- 2..12: dados do pedido
|
||||||
|
- 13..35: dados do cliente
|
||||||
|
- 36..42: dados do município
|
||||||
|
- 43..50: dados da forma de pagamento
|
||||||
|
- 51..61: dados da pauta
|
||||||
|
- 62: `a.permite_flex`
|
||||||
|
- 63: `a.desconto_p`
|
||||||
|
- 64: `a.desconto_v`
|
||||||
|
- 65: `a.total` → `pedido.setTotal(c.getDouble(65))`
|
||||||
|
- **66: `a.vl_acrescimo`** ← ADICIONAR → `pedido.setVlAcrescimo(c.getDouble(66))`
|
||||||
|
|
||||||
|
Não alterar os índices existentes — apenas acrescentar ao final.
|
||||||
|
|
||||||
|
> ⚠️ **Precedente**: `PedidoDB.selectAllFull()` usa índice 68 para `a.vl_acrescimo` (Story 2.3). Em `PedidoConsultaDB.selectFull()` o índice é **66** — SQL e estrutura de join diferem; validar contando colunas do SELECT antes de alterar.
|
||||||
|
|
||||||
|
### `vl_acrescimo` da sync PG estará em 0.0 (por enquanto)
|
||||||
|
|
||||||
|
- `PedidoPGSQL.selectAllPedConsulta()` não lê `vl_acrescimo` do PostgreSQL — deferred per review da Story 2.3 (PG ainda não tem a coluna)
|
||||||
|
- Portanto, pedidos novos que chegam via sync PG terão `vlAcrescimo = 0.0` no VO
|
||||||
|
- Tarefa 2 e 3 gravam esse `0.0` sem problema — garante forward-compat quando o sync PG começar a carregar o valor real
|
||||||
|
- AC 2 cobre explicitamente esse caso: pedidos com `vl_acrescimo = 0` exibem R$ 0,00 e funcionam normalmente
|
||||||
|
|
||||||
|
Quando a história futura "sync PG do acréscimo" for implementada, `PedidoPGSQL.selectAllPedConsulta()` popula `ped.setVlAcrescimo(rs.getDouble("vl_acrescimo"))` e tudo funcionará sem outras mudanças neste caminho.
|
||||||
|
|
||||||
|
### Pedidos locais (antes do sync PG) — fluxo paralelo
|
||||||
|
|
||||||
|
Quando o usuário cria e salva um pedido localmente (antes de enviar ao ERP), ele fica em `pedido` (não em `pedido_consulta`) com `vl_acrescimo` gravado por Story 2.3.
|
||||||
|
|
||||||
|
Ao enviar/sincronizar, o pedido pode transitar de `pedido` para `pedido_consulta` (via sync PG de retorno). Durante essa transição, `pedido_consulta.vl_acrescimo` ficará 0.0 (até o sync PG carregar o valor). Para o usuário, isso pode significar:
|
||||||
|
|
||||||
|
- Abrir o pedido via `BrowsePedido` (local) → `PedidoDB.selectFull()` → `vlAcrescimo` lido de `pedido` → exibido corretamente
|
||||||
|
- Abrir o mesmo pedido via `BrowsePedidoConsulta` → `PedidoConsultaDB.selectFull()` → `vlAcrescimo = 0.0` → exibido R$ 0,00
|
||||||
|
|
||||||
|
Essa dualidade é esperada e documentada como deferred. Escopo desta história é garantir que **quando o valor ESTÁ persistido**, ele é exibido corretamente (AC 1), e que a ausência não quebra nada (AC 2).
|
||||||
|
|
||||||
|
### Null-safety do getter `getVlAcrescimo()`
|
||||||
|
|
||||||
|
`Pedido.vlAcrescimo` inicializa em `0.0` (primitive double, [Pedido.java:41](../../src/br/com/jcsinformatica/sarandroid/vo/Pedido.java)). `getVlAcrescimo()` não pode retornar null. `c.getDouble(66)` em SQLite retorna `0.0` quando a coluna é NULL. Zero risco de NPE em toda a cadeia de AC 2.
|
||||||
|
|
||||||
|
### Por que atualizar `onCreate` e `onUpgrade` em Task 1
|
||||||
|
|
||||||
|
Dispositivos novos nunca rodam `onUpgrade` — apenas `onCreate`. Se só adicionarmos em `onUpgrade`, instalações limpas terão `pedido_consulta` sem `vl_acrescimo`, fazendo `PedidoConsultaDB.selectFull()` estourar `SQLiteException: no such column`. Padrão já estabelecido em stories 1.1 e 2.3 (sempre atualizar ambos).
|
||||||
|
|
||||||
|
### Arquivos a modificar
|
||||||
|
|
||||||
|
| Arquivo | Caminho | O que muda |
|
||||||
|
|---|---|---|
|
||||||
|
| `DatabaseHelper.java` | `src/br/com/jcsinformatica/sarandroid/database/` | `dbVersao = 43`; bloco v43 em `onUpgrade()`; coluna em `onCreate()` |
|
||||||
|
| `PedidoConsultaDB.java` | `src/br/com/jcsinformatica/sarandroid/database/` | INSERT, UPDATE (`updateErp`), SELECT (`selectFull`) |
|
||||||
|
| `MainPedidoFragment.java` | `src/br/com/jcsinformatica/sarandroid/pedido/` | Guard `status >= STATUS_ENVIADO` em `fillFields()` e `atualizarResumoPedido()` |
|
||||||
|
| `TotalPedidoFragment.java` | `src/br/com/jcsinformatica/sarandroid/pedido/` | Guard `status >= STATUS_ENVIADO` em `FillFields()` |
|
||||||
|
|
||||||
|
**NÃO modificar nesta história:**
|
||||||
|
|
||||||
|
- `list_browse_pedido.xml` — layout permanece
|
||||||
|
- `PedidoList.java` (VO do adapter) — permanece com só `total`
|
||||||
|
- `SimpleArrayAdapterPedidoBrowser.java` — exibe `total` como antes
|
||||||
|
- `PedidoConsultaDB.selectListResumo()` — deferred (ver seção Escopo)
|
||||||
|
- `PedidoConsultaDB.selectTotais()` — deferred
|
||||||
|
- `PedidoPGSQL.java` — deferred
|
||||||
|
- `Pedido.java` VO — `vlAcrescimo` já existe (Story 2.3)
|
||||||
|
- `Pedido.getTotalPedidoConsulta()` — retorna `this.total` diretamente; não altera
|
||||||
|
|
||||||
|
### Regras críticas do projeto (aplicáveis)
|
||||||
|
|
||||||
|
- **Sem Kotlin** — somente Java puro
|
||||||
|
- **Sem Gradle** — projeto Eclipse ADT
|
||||||
|
- **Sem JARs novos** — nenhuma dependência adicional
|
||||||
|
- **Sem testes automatizados** — validação manual via dispositivo/emulador
|
||||||
|
- **`dbVersao`** incrementado para **43** (nunca pular versões; padrão linear estabelecido)
|
||||||
|
- **Thread background para SQLite** — `selectFull` em consulta já é chamado em `ThreadAbrirPedidoConsulta`, padrão preservado
|
||||||
|
- **Strings sem acento em Java** — este story não adiciona strings hardcoded (apenas lógica)
|
||||||
|
- **SQL por `StringBuilder`** — padrão projeto-wide (deferred)
|
||||||
|
- **Aritmética `double` para monetário** — padrão projeto-wide (deferred)
|
||||||
|
|
||||||
|
### Verificação manual após implementação
|
||||||
|
|
||||||
|
1. **Pedido com acréscimo — caminho quente (local, antes do sync)**
|
||||||
|
- Criar pedido com subtotal R$ 100,00; selecionar forma de pagamento com `tx_acrescimo = 5.0`
|
||||||
|
- Salvar — verificar `pedido.vl_acrescimo = 5.0` via SQLite Browser
|
||||||
|
- Reabrir o pedido via `BrowsePedido`: campo "Acréscimo" = R$ 5,00; total = R$ 105,00
|
||||||
|
|
||||||
|
2. **Pedido em consulta com acréscimo — simulação de pedido histórico**
|
||||||
|
- Via ADB/SQLite Browser, `UPDATE pedido_consulta SET vl_acrescimo = 10.0 WHERE id_pedido_consulta = X` para um pedido conhecido
|
||||||
|
- Abrir o pedido via `BrowsePedidoConsulta`
|
||||||
|
- Verificar: status é forçado a ENVIADO; campo "Acréscimo" exibe R$ 10,00 (valor persistido, NÃO recalculado)
|
||||||
|
- Alterar `tx_acrescimo` da forma de pagamento no banco para valor diferente; reabrir — "Acréscimo" continua R$ 10,00 ✓ AC 1
|
||||||
|
|
||||||
|
3. **Pedido legado em consulta — sem acréscimo**
|
||||||
|
- `UPDATE pedido_consulta SET vl_acrescimo = 0 WHERE id_pedido_consulta = Y` (ou deixar NULL pré-migração)
|
||||||
|
- Abrir via `BrowsePedidoConsulta`: "Acréscimo" = R$ 0,00; total = subtotal; sem crash ✓ AC 2
|
||||||
|
|
||||||
|
4. **Migração v43**
|
||||||
|
- Instalar em dispositivo com app v42; verificar via `adb shell pm dump` ou via lint de schema que `pedido_consulta.vl_acrescimo` existe após primeira execução ✓ AC 3
|
||||||
|
- Registros pré-existentes em `pedido_consulta` apresentam `vl_acrescimo = 0` ✓ AC 3
|
||||||
|
|
||||||
|
5. **Sync PG tolerante**
|
||||||
|
- Executar comunicação — verificar que `PedidoConsultaDB.saveAll()` insere/atualiza com `vl_acrescimo = 0` sem erro (pedidos vindos do PG têm `ped.getVlAcrescimo() = 0.0` default) ✓ AC 4
|
||||||
|
|
||||||
|
6. **Instalação nova (onCreate)**
|
||||||
|
- Limpar dados do app e relançar — `pedido_consulta` criado com `vl_acrescimo` direto em `onCreate()` ✓ (evita regressão posterior ao mesclar onCreate/onUpgrade)
|
||||||
|
|
||||||
|
### Inteligência de histórias anteriores
|
||||||
|
|
||||||
|
- **Story 1.1**: padrão de migração `if (oldVersion < N)` em `onUpgrade()` + atualização de `onCreate` — replicado aqui
|
||||||
|
- **Story 1.2**: padrão de tolerância a coluna ausente no PG — `PedidoPGSQL` mantém comportamento atual (não lê `vl_acrescimo`), sync continua sem erro
|
||||||
|
- **Story 2.1**: `tvAcrescimoPedido` / `tvAcrescimo` já existem nos layouts — nenhum layout muda aqui
|
||||||
|
- **Story 2.2**: cálculo `vlAcrescimo = subtotal × (tx_acrescimo/100)` estabelecido em `MainPedidoFragment` e `TotalPedidoFragment`; esta história **envelopa** esse cálculo em um guard de status
|
||||||
|
- **Story 2.3**: migração v41→v42 + `Pedido.vlAcrescimo` + `PedidoDB.insert/update/selectAllFull/selectFull` — esta história segue **exatamente o mesmo padrão** para `pedido_consulta`
|
||||||
|
- **Review da Story 2.3 — deferred identificado**: "`fillFields()` recalcula `vlAcrescimo` com taxa atual ao abrir pedido existente" → **esta história resolve** para pedidos com `status >= STATUS_ENVIADO` (consulta); para status < ENVIADO continua recalculando (correto para edição)
|
||||||
|
|
||||||
|
### Git intelligence (commits recentes)
|
||||||
|
|
||||||
|
- `3ff26a7` Story 2.3: `vl_acrescimo` em `pedido` + `Pedido.setVlAcrescimo()` + persistência em `PedidoDB`
|
||||||
|
- `9b70ee3` Story 2.2: cálculo em `MainPedidoFragment` e `TotalPedidoFragment`
|
||||||
|
- `474d98f` Story 2.1: layout com `tvAcrescimoPedido`
|
||||||
|
- `168b9db` Story 1.2: sync formapag.acresc → tx_acrescimo
|
||||||
|
- `4bfccc7` Story 1.1: migração v40→v41
|
||||||
|
|
||||||
|
### References
|
||||||
|
|
||||||
|
- PRD: `_bmad-output/planning-artifacts/prd.md#user-journeys` — Jornada 3
|
||||||
|
- Epics: `_bmad-output/planning-artifacts/epics.md#story-24-exibir-acrescimo-na-consulta-de-pedidos`
|
||||||
|
- FR12: exibir total com acréscimo em pedidos fechados que possuem o campo gravado
|
||||||
|
- FR13: exibir pedidos sem campo de acréscimo sem alteração ou erro
|
||||||
|
- NFR3: `vl_acrescimo = NULL` nunca causa NPE
|
||||||
|
- NFR4: Migração idempotente (guard `if (oldVersion < 43)`)
|
||||||
|
- Deferred work: `_bmad-output/implementation-artifacts/deferred-work.md`
|
||||||
|
|
||||||
|
## Dev Agent Record
|
||||||
|
|
||||||
|
### Agent Model Used
|
||||||
|
|
||||||
|
claude-opus-4-7 (dev-story workflow)
|
||||||
|
|
||||||
|
### Debug Log References
|
||||||
|
|
||||||
|
N/A — projeto sem infraestrutura de testes automatizados; validação manual via dispositivo/emulador conforme roteiro em "Verificação manual após implementação".
|
||||||
|
|
||||||
|
### Completion Notes List
|
||||||
|
|
||||||
|
- Story 2.4 implementada em 2026-04-16.
|
||||||
|
- **`DatabaseHelper.java`**: `dbVersao` incrementado de 42 para 43. `onCreate()` atualizado adicionando `vl_acrescimo REAL DEFAULT 0` à definição da tabela `pedido_consulta` (sincronia com `onUpgrade`). Bloco `if (oldVersion < 43)` adicionado em `onUpgrade()` executando `ALTER TABLE pedido_consulta ADD COLUMN vl_acrescimo REAL DEFAULT 0;`. Migração idempotente via guard. AC 3 satisfeito.
|
||||||
|
- **`PedidoConsultaDB.java` INSERT**: coluna `vl_acrescimo` adicionada ao `INSERT INTO pedido_consulta(...)` e valor `ped.getVlAcrescimo()` adicionado no final da lista de valores. AC 4 satisfeito (tolerante a `vlAcrescimo = 0.0` default vindo do sync PG).
|
||||||
|
- **`PedidoConsultaDB.java` UPDATE** (`updateErp`): `vl_acrescimo = ped.getVlAcrescimo()` adicionado ao SET. Vírgula anterior ajustada em `total` para acomodar novo campo.
|
||||||
|
- **`PedidoConsultaDB.java` SELECT** (`selectFull`): `a.vl_acrescimo` adicionado ao final da lista do SELECT (cursor index **66**, após `a.total` em 65). `pedido.setVlAcrescimo(c.getDouble(66))` adicionado após `pedido.setTotal(c.getDouble(65))`. AC 5 satisfeito.
|
||||||
|
- **`MainPedidoFragment.java`** (`fillFields` linha ~439-451): guard `if (Global.pedido.getStatus() >= Pedido.STATUS_ENVIADO)` envolvendo o cálculo do acréscimo. Para pedidos em consulta (status >= ENVIADO), usa `Global.pedido.getVlAcrescimo()` persistido; para pedidos em edição, recalcula a partir de `tx_acrescimo` e atualiza o VO via `setVlAcrescimo()`. AC 1 e AC 2 satisfeitos.
|
||||||
|
- **`MainPedidoFragment.java`** (`atualizarResumoPedido` linha ~984-996): mesmo guard aplicado; mesma lógica.
|
||||||
|
- **`TotalPedidoFragment.java`** (`FillFields` linha ~97-108): mesmo guard aplicado. Fragment não grava no VO em branch de edição (paralelo ao comportamento pré-existente — save é gerenciado por `MainPedidoFragment`).
|
||||||
|
- AC 1 satisfeito: pedidos consultados com `vl_acrescimo > 0` gravado exibem o valor persistido, preservando o histórico mesmo se a `tx_acrescimo` for alterada futuramente no ERP.
|
||||||
|
- AC 2 satisfeito: pedidos antigos com `vl_acrescimo = 0` ou NULL exibem R$ 0,00 sem NPE (`c.getDouble(66)` retorna 0.0 para NULL; primitive double sem risco de NPE).
|
||||||
|
- AC 3 satisfeito: migração v42→v43 adiciona coluna com `DEFAULT 0`, preservando registros existentes.
|
||||||
|
- AC 4 satisfeito: sync PG atual entrega `ped.getVlAcrescimo() = 0.0` (default do VO) — persistência aceita sem erro.
|
||||||
|
- AC 5 satisfeito: leitura no cursor funciona com valores persistidos ou 0/NULL.
|
||||||
|
- Sem testes automatizados: projeto não possui infraestrutura (conforme CLAUDE.md e padrão das stories anteriores). Validação manual necessária — roteiro completo em "Verificação manual após implementação".
|
||||||
|
- Itens deferred permanecem deferred conforme escopo: `PedidoPGSQL.selectAllPedConsulta` (sync do acréscimo), `list_browse_pedido.xml` (layout da lista), `PedidoConsultaDB.selectListResumo` (total da lista), `PedidoConsultaDB.selectTotais` (agregação de vendas), MD5 change detection.
|
||||||
|
|
||||||
|
### File List
|
||||||
|
|
||||||
|
- `src/br/com/jcsinformatica/sarandroid/database/DatabaseHelper.java`
|
||||||
|
- `src/br/com/jcsinformatica/sarandroid/database/PedidoConsultaDB.java`
|
||||||
|
- `src/br/com/jcsinformatica/sarandroid/pedido/MainPedidoFragment.java`
|
||||||
|
- `src/br/com/jcsinformatica/sarandroid/pedido/TotalPedidoFragment.java`
|
||||||
|
|
||||||
|
### Change Log
|
||||||
|
|
||||||
|
- 2026-04-16: Implementação da Story 2.4. Migração SQLite v42→v43 adicionando `vl_acrescimo` em `pedido_consulta`; `PedidoConsultaDB` lendo/escrevendo o campo; `MainPedidoFragment` e `TotalPedidoFragment` exibindo o valor persistido quando `status >= STATUS_ENVIADO` (preserva histórico em consulta) e recalculando quando editável.
|
||||||
|
- 2026-04-16: Code review executado (Blind Hunter + Edge Case Hunter + Acceptance Auditor). Auditor aprovou todos os 5 ACs sem violações. 7 itens pré-existentes deferred; 8 findings dismissados como falsos positivos ou by-design.
|
||||||
|
|
||||||
|
## Review Findings
|
||||||
|
|
||||||
|
- [x] [Review][Defer] PedidoPGSQL não inclui `vl_acrescimo` na sync para/de PostgreSQL [PedidoPGSQL.java] — deferred, explicitamente fora de escopo per spec; pedidos recém-sincronizados via PG terão `vl_acrescimo = 0` até history future do sync PG do acréscimo
|
||||||
|
- [x] [Review][Defer] MD5 do pedido não inclui `vl_acrescimo` na hash de change-detection — deferred, explicitamente fora de escopo per spec; reavaliar junto com sync PG
|
||||||
|
- [x] [Review][Defer] `total` recalculado de itens em tempo real enquanto `vl_acrescimo` é congelado (status >= ENVIADO) — pré-existente; mesmo padrão que `descontoV` estabelecido em Story 2.3; risco de inconsistência se itens forem reprecificados após envio, mas cenário improvável em prática
|
||||||
|
- [x] [Review][Defer] Índice 66 do cursor hard-coded em `PedidoConsultaDB.selectFull()` [PedidoConsultaDB.java:296] — deferred, padrão projeto-wide pré-existente; lista de índices posicionais já é longa (43–66); migrar para `getColumnIndexOrThrow()` em refatoração futura
|
||||||
|
- [x] [Review][Defer] SQL building via `StringBuilder` e concatenação de strings em `PedidoConsultaDB.insert/updateErp` [PedidoConsultaDB.java:119-147,179-184] — deferred, padrão projeto-wide pré-existente; usar `PreparedStatement` ou binds em refatoração maior
|
||||||
|
- [x] [Review][Defer] Lógica duplicada de cálculo+guard de acréscimo em `MainPedidoFragment.fillFields`, `MainPedidoFragment.atualizarResumoPedido` e `TotalPedidoFragment.FillFields` — deferred, padrão DRY pré-existente (já flagged em review da Story 2.2); extrair para helper em refatoração futura
|
||||||
|
- [x] [Review][Defer] `Global.pedido` sem null-check em `fillFields`, `atualizarResumoPedido` e `FillFields` — deferred, pré-existente em múltiplos pontos (já flagged em reviews das Stories 2.1, 2.2, 2.3); risco de NPE se fragment recriado após process kill com `Global.pedido = null`
|
||||||
|
- [x] [Review][Defer] `new UpdatePedItemActivity().precoComIpi()` instancia Activity fora do lifecycle Android em `MainPedidoFragment.fillFields` e `atualizarResumoPedido` — deferred, anti-padrão pré-existente (já flagged em Story 2.1)
|
||||||
|
- [x] [Review][Defer] Ausência de `onDowngrade()` em `DatabaseHelper` — deferred, padrão projeto-wide pré-existente; instalação de APK antigo após v43 crashará com `SQLiteException`; não introduzido por este diff
|
||||||
|
|
||||||
|
### Review findings — dismissed (para referência, sem ação)
|
||||||
|
|
||||||
|
- Formatação decimal dependente de locale em concatenação SQL — **falso positivo**: `Double.toString()` e `+` com `double` em Java sempre usam `.` como separador decimal (garantia do JLS), independente de `Locale.getDefault()`
|
||||||
|
- `status >= STATUS_ENVIADO` incluir CANCELADO (3) e EMITIDO (4) — **by-design per spec**: todos os status "fechados" (não-editáveis) devem congelar o valor histórico; documentado em Dev Notes da story
|
||||||
|
- `TotalPedidoFragment` não chama `setVlAcrescimo` no branch de edição — **by-design per spec**: save é gerenciado por `MainPedidoFragment`; duplicar persistência causaria write duplo
|
||||||
|
- Pedidos legados sem `vl_acrescimo` (valor 0) exibidos como R$ 0,00 em consulta — **coberto por AC 2**; comportamento correto per spec
|
||||||
|
- `c.getDouble(66)` com NULL — **seguro**: `Cursor.getDouble()` retorna `0.0` para NULL; `ALTER TABLE ... DEFAULT 0` garante não-NULL em linhas pré-migração
|
||||||
|
- `NaN`/`Infinity` concatenado em SQL — **teórico**: `totalGeral × (txAcrescimo/100.0)` com `double` bounded nunca produz NaN/Infinity em entradas válidas
|
||||||
|
- `getVlAcrescimo()`/`setVlAcrescimo()` existência — **verificado**: `Pedido.java:41,759-764` declara campo primitive `double vlAcrescimo = 0.0` com getter/setter (Story 2.3)
|
||||||
|
- Migração não idempotente sem `IF NOT EXISTS` — **guard `if (oldVersion < 43)` já garante idempotência**; SQLite Android não suporta `IF NOT EXISTS` em `ADD COLUMN` de qualquer forma
|
||||||
@@ -0,0 +1,347 @@
|
|||||||
|
# Story 3.1: Enviar Acréscimo ao PostgreSQL ao Salvar Pedido
|
||||||
|
|
||||||
|
Status: done
|
||||||
|
|
||||||
|
## Story
|
||||||
|
|
||||||
|
Como sistema,
|
||||||
|
quero enviar a taxa e o valor do acréscimo ao PostgreSQL quando um pedido for sincronizado para o ERP,
|
||||||
|
para que o valor praticado localmente seja registrado no sistema central e fique disponível para relatórios do escritório.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
1. **Dado** que `Global.sistema == SISTEMA_GERENTE` e um pedido com `vl_acrescimo = 87.50` (calculado sobre taxa 2,5%) é enviado via `PedidoPGSQL.insert`
|
||||||
|
**Quando** o INSERT em `gerente.pedidos` for executado
|
||||||
|
**Então** a coluna `acrep` contém `2.5` (taxa da forma de pagamento) e `acrev` contém `87.50` (valor calculado)
|
||||||
|
|
||||||
|
2. **Dado** que `Global.sistema == SISTEMA_SIG` e o mesmo pedido é enviado
|
||||||
|
**Quando** o INSERT em `sig.pedidos` for executado
|
||||||
|
**Então** a coluna `tx_acrescimo` contém `2.5` (só a taxa; o valor é derivado server-side)
|
||||||
|
|
||||||
|
3. **Dado** que um pedido sem acréscimo (forma de pagamento com `tx_acrescimo = 0`) é enviado
|
||||||
|
**Quando** o INSERT for executado em qualquer dos dois sistemas
|
||||||
|
**Então** o campo correspondente recebe `0.0` sem erro
|
||||||
|
|
||||||
|
4. **Dado** que a coluna `tx_acrescimo` ainda **não existe** em `sig.pedidos` (schema antigo / DDL server-side pendente)
|
||||||
|
**Quando** o INSERT tentar gravar o pedido SIG
|
||||||
|
**Então** o INSERT é construído **sem** o campo `tx_acrescimo` (via checagem em `information_schema.columns` antes do loop de envio), a comunicação conclui normalmente, e nenhuma exceção é lançada
|
||||||
|
|
||||||
|
## Escopo desta história
|
||||||
|
|
||||||
|
**SIM:**
|
||||||
|
- Substituir `st.setInt(21, 0); // acrep` e `st.setInt(22, 0); // acrev` em `PedidoPGSQL.insert()` por valores reais do VO
|
||||||
|
- Adicionar `tx_acrescimo` ao INSERT de `insertSig()`, com checagem prévia de existência da coluna em `sig.pedidos`
|
||||||
|
- Helper privado `hasColunaAcrescimoSig()` consultando `information_schema.columns`
|
||||||
|
|
||||||
|
**NÃO cobre (deferred):**
|
||||||
|
- Leitura inbound do acréscimo do PG (`selectAllPedConsulta`) — Story 3.2
|
||||||
|
- MD5 de `pedido_consulta` incluindo `vl_acrescimo` — Story 3.3
|
||||||
|
- DDL server-side (responsabilidade do DBA; app tolera ausência)
|
||||||
|
|
||||||
|
## Pré-condições verificadas
|
||||||
|
|
||||||
|
Inspeção confirma:
|
||||||
|
|
||||||
|
- **Gerente — colunas já existem:** `PedidoPGSQL.insert()` já inclui `acrep` e `acrev` na lista de colunas do INSERT ([PedidoPGSQL.java:134](../../src/br/com/jcsinformatica/sarandroid/postgres/PedidoPGSQL.java)) — estavam apenas hardcoded como `0`. As colunas já existem em `gerente.pedidos` (estavam sendo gravadas como zero desde antes do Epic 3).
|
||||||
|
- **SIG — coluna nova:** `insertSig()` ([PedidoPGSQL.java:342-351](../../src/br/com/jcsinformatica/sarandroid/postgres/PedidoPGSQL.java)) **não inclui** `tx_acrescimo` — DDL em `sig.pedidos` pode ainda não ter sido aplicado.
|
||||||
|
- **`ped.getFormapag().getTxAcrescimo()`** disponível — `FormaPagamento.txAcrescimo` adicionado em Story 1.1-1.2 (`formapag.tx_acrescimo` no SQLite).
|
||||||
|
- **`ped.getVlAcrescimo()`** disponível — `Pedido.vlAcrescimo` adicionado em Story 2.3.
|
||||||
|
- Em `insert()`, `ped.getFormapag()` **nunca é null** neste ponto — linha 160 já chama `ped.getFormapag().getCodigo()` sem null-check; se fosse null, falharia antes.
|
||||||
|
- Em `insertSig()`, `ped.getFormapag()` **nunca é null** — linha 378 já o usa diretamente.
|
||||||
|
- `dbVersao` permanece **43** — esta história não altera schema SQLite.
|
||||||
|
- `insertSig()` usa 39 parâmetros (`st.setInt(1)` … `st.setString(39)`) — `tx_acrescimo` seria o parâmetro 40 (quando coluna existir).
|
||||||
|
|
||||||
|
## Tasks / Subtasks
|
||||||
|
|
||||||
|
- [x] **Task 1:** Corrigir Gerente — gravar `acrep` e `acrev` com valores reais (AC: 1, 3)
|
||||||
|
- [x] 1.1 — Em [PedidoPGSQL.java:181-182](../../src/br/com/jcsinformatica/sarandroid/postgres/PedidoPGSQL.java), substituir:
|
||||||
|
```java
|
||||||
|
// ANTES:
|
||||||
|
st.setInt(21, 0);// acrep
|
||||||
|
st.setInt(22, 0);// acrev
|
||||||
|
|
||||||
|
// DEPOIS:
|
||||||
|
st.setDouble(21, ped.getFormapag().getTxAcrescimo());// acrep
|
||||||
|
st.setDouble(22, ped.getVlAcrescimo());// acrev
|
||||||
|
```
|
||||||
|
> Coluna `acrep` = taxa %; `acrev` = valor calculado. Ambas já existem em `gerente.pedidos` (hardcoded a 0 anteriormente). Nenhuma alteração no SQL string ou na lista de colunas — apenas substituir os valores.
|
||||||
|
|
||||||
|
- [x] **Task 2:** Adicionar helper de checagem de coluna SIG (AC: 4)
|
||||||
|
- [x] 2.1 — Adicionar método privado ao final de `PedidoPGSQL.java` (antes do último `}`):
|
||||||
|
```java
|
||||||
|
private boolean hasColunaAcrescimoSig() {
|
||||||
|
PreparedStatement st = null;
|
||||||
|
ResultSet rs = null;
|
||||||
|
try {
|
||||||
|
st = conn.prepareStatement(
|
||||||
|
"SELECT 1 FROM information_schema.columns " +
|
||||||
|
"WHERE table_schema = 'sig' " +
|
||||||
|
" AND table_name = 'pedidos' " +
|
||||||
|
" AND column_name = 'tx_acrescimo'");
|
||||||
|
rs = st.executeQuery();
|
||||||
|
return rs.next();
|
||||||
|
} catch (Exception e) {
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
ConnectionManager.closeAll(st, rs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
> Retorna `true` se a coluna já existe (DDL aplicado); `false` caso contrário. O `finally` usa `ConnectionManager.closeAll` — padrão projeto-wide.
|
||||||
|
|
||||||
|
- [x] **Task 3:** Cache da checagem em `save()` antes do loop (AC: 4)
|
||||||
|
- [x] 3.1 — Em [PedidoPGSQL.java:56-110](../../src/br/com/jcsinformatica/sarandroid/postgres/PedidoPGSQL.java), adicionar checagem UMA VEZ antes do loop `for`, logo após `conn.setAutoCommit(false)`:
|
||||||
|
```java
|
||||||
|
conn.setAutoCommit(false);
|
||||||
|
db.beginTransaction();
|
||||||
|
final boolean temAcrescSig = !Global.sistema.equals(Global.SISTEMA_GERENTE) && hasColunaAcrescimoSig();
|
||||||
|
// int ultimoNumeroPedido = 0;
|
||||||
|
for (int i = 0; i < pedidos.size(); i++) {
|
||||||
|
```
|
||||||
|
> Evita uma query `information_schema` por pedido — executa uma única vez por batch.
|
||||||
|
- [x] 3.2 — No mesmo método, alterar a chamada a `insertSig()` para passar o boolean:
|
||||||
|
```java
|
||||||
|
// ANTES:
|
||||||
|
idNumErp = insertSig(ped, db);
|
||||||
|
|
||||||
|
// DEPOIS:
|
||||||
|
idNumErp = insertSig(ped, db, temAcrescSig);
|
||||||
|
```
|
||||||
|
|
||||||
|
- [x] **Task 4:** Modificar `insertSig()` para aceitar e usar o boolean (AC: 2, 3, 4)
|
||||||
|
- [x] 4.1 — Alterar a assinatura do método:
|
||||||
|
```java
|
||||||
|
// ANTES:
|
||||||
|
private int[] insertSig(Pedido ped, SQLiteDatabase db) throws Exception {
|
||||||
|
|
||||||
|
// DEPOIS:
|
||||||
|
private int[] insertSig(Pedido ped, SQLiteDatabase db, boolean temAcrescimo) throws Exception {
|
||||||
|
```
|
||||||
|
- [x] 4.2 — Em [PedidoPGSQL.java:350](../../src/br/com/jcsinformatica/sarandroid/postgres/PedidoPGSQL.java), após a lista de colunas (`vol_marca`), adicionar condicionalmente `tx_acrescimo`:
|
||||||
|
```java
|
||||||
|
sql.append(" tx_desc_financ, cod_vend2, tx_com_vend2, tp_pg_com_vend2, vol_marca"); //35-39
|
||||||
|
if (temAcrescimo) {
|
||||||
|
sql.append(", tx_acrescimo"); //40
|
||||||
|
}
|
||||||
|
sql.append(") ");
|
||||||
|
```
|
||||||
|
- [x] 4.3 — Em [PedidoPGSQL.java:358](../../src/br/com/jcsinformatica/sarandroid/postgres/PedidoPGSQL.java), na lista de VALUES (após o `"?, ?, ?, ?)"` existente), adicionar condicionalmente o `?`:
|
||||||
|
```java
|
||||||
|
sql.append(" ?, ?, ?, ?"); //35-38: tx_desc_financ, cod_vend2, tx_com_vend2, tp_pg_com_vend2
|
||||||
|
if (temAcrescimo) {
|
||||||
|
sql.append(", ?, ?"); //39: vol_marca, 40: tx_acrescimo
|
||||||
|
} else {
|
||||||
|
sql.append(", ?"); //39: vol_marca
|
||||||
|
}
|
||||||
|
sql.append(") RETURNING id_pedido, numero;");
|
||||||
|
```
|
||||||
|
> ⚠️ Atenção: a linha original `sql.append(" ?, ?, ?, ?)")` tem 4 params (índices 36-39) e NÃO inclui o `RETURNING`. Verificar o SQL final após a refatoração. O `vol_marca` atual é o param 39; ao adicionar `tx_acrescimo`, ele passaria a ser o 40.
|
||||||
|
|
||||||
|
> ⚠️ **Contagem crítica:** Antes de modificar, confirme que `st.setString(39, "*SAR*");` corresponde a `vol_marca`. Com `temAcrescimo = true`, `tx_acrescimo` fica no índice 40.
|
||||||
|
- [x] 4.4 — Após `st.setString(39, "*SAR*");` (último setter atual), adicionar condicionalmente:
|
||||||
|
```java
|
||||||
|
if (temAcrescimo) {
|
||||||
|
st.setDouble(40, ped.getFormapag().getTxAcrescimo()); // tx_acrescimo — só taxa; valor derivado server-side
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dev Notes
|
||||||
|
|
||||||
|
### Assimetria Gerente vs SIG — regra central desta história
|
||||||
|
|
||||||
|
| Sistema | Campo PG | O que gravar | Fonte no app |
|
||||||
|
|---------|----------|-------------|--------------|
|
||||||
|
| Gerente (`gerente.pedidos`) | `acrep` | % de acréscimo (taxa) | `ped.getFormapag().getTxAcrescimo()` |
|
||||||
|
| Gerente (`gerente.pedidos`) | `acrev` | Valor calculado do acréscimo | `ped.getVlAcrescimo()` |
|
||||||
|
| SIG (`sig.pedidos`) | `tx_acrescimo` | % de acréscimo (só taxa; SIG calcula valor server-side) | `ped.getFormapag().getTxAcrescimo()` |
|
||||||
|
|
||||||
|
Esta assimetria é **intencional** e documentada nas Pré-condições do Epic 3. Gerente guarda taxa + valor (histórico congelado); SIG guarda só a taxa (valor recalculado server-side).
|
||||||
|
|
||||||
|
### Por que Gerente NÃO precisa de tolerância à ausência de coluna
|
||||||
|
|
||||||
|
`acrep` e `acrev` já estavam na lista de colunas do INSERT Gerente (linha 134 de `PedidoPGSQL.java`) sendo gravados como `0`. Isso prova que as colunas **já existiam** em `gerente.pedidos` antes do Epic 3. Nenhuma checagem de `information_schema` é necessária para Gerente.
|
||||||
|
|
||||||
|
### Por que SIG PRECISA de tolerância
|
||||||
|
|
||||||
|
`tx_acrescimo` **não existe** na lista do INSERT SIG atual. O DBA precisa executar o DDL no servidor antes que o app possa gravar. A checagem em `information_schema.columns` permite deploy sem coordenação com o DBA:
|
||||||
|
- App deployed, DDL pendente → coluna não incluída → pedidos enviados sem `tx_acrescimo` (campo ficará com `DEFAULT 0` quando DDL for aplicado)
|
||||||
|
- DDL aplicado → próximo sync envia `tx_acrescimo` com valor real
|
||||||
|
|
||||||
|
### Contagem de parâmetros em `insertSig()` — tabela de referência
|
||||||
|
|
||||||
|
```
|
||||||
|
Param 1: id_empresa
|
||||||
|
Param 2: cod_vendedor
|
||||||
|
Param 3: tipo
|
||||||
|
Param 4: numero
|
||||||
|
Param 5: situa
|
||||||
|
Param 6: data
|
||||||
|
Param 7: clien
|
||||||
|
Param 8: e_ender
|
||||||
|
Param 9: e_bairr
|
||||||
|
Param 10: e_munic
|
||||||
|
Param 11: e_estad
|
||||||
|
Param 12: e_cep
|
||||||
|
Param 13: conta
|
||||||
|
Param 14: cod_formapag
|
||||||
|
Param 15: totpr
|
||||||
|
Param 16: ipi
|
||||||
|
Param 17: fconta
|
||||||
|
Param 18: frete
|
||||||
|
Param 19: total
|
||||||
|
Param 20: descp
|
||||||
|
Param 21: descv
|
||||||
|
Param 22: com_fat
|
||||||
|
Param 23: com_rec
|
||||||
|
Param 24: obs
|
||||||
|
Param 25: prz_con
|
||||||
|
Param 26: id_portador
|
||||||
|
Param 27: num_ped_sar
|
||||||
|
Param 28: num_ped_vendedor
|
||||||
|
Param 29: cod_pauta
|
||||||
|
Param 30: id_tes
|
||||||
|
Param 31: inf_usuario
|
||||||
|
Param 32: ped_flex
|
||||||
|
Param 33: obs_entrega
|
||||||
|
Param 34: abater_desc_fin
|
||||||
|
Param 35: tx_desc_financ
|
||||||
|
Param 36: cod_vend2
|
||||||
|
Param 37: tx_com_vend2
|
||||||
|
Param 38: tp_pg_com_vend2
|
||||||
|
Param 39: vol_marca ← "*SAR*" (st.setString(39, "*SAR*"))
|
||||||
|
Param 40: tx_acrescimo ← NOVO (condicional, quando temAcrescimo = true)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Null-safety
|
||||||
|
|
||||||
|
- `ped.getFormapag()` nunca é null neste ponto — `insertSig()` já chama `ped.getFormapag().getCodigo()` em linha 378 sem null-check. Mesma garantia aplica-se a `getTxAcrescimo()`.
|
||||||
|
- `ped.getVlAcrescimo()` retorna `double` primitivo (0.0 default) — sem risco de NPE.
|
||||||
|
|
||||||
|
### Arquivos a modificar
|
||||||
|
|
||||||
|
| Arquivo | Caminho | O que muda |
|
||||||
|
|---------|---------|------------|
|
||||||
|
| `PedidoPGSQL.java` | `src/br/com/jcsinformatica/sarandroid/postgres/` | `insert()` params 21-22; `save()` cache temAcrescSig; `insertSig()` assinatura + SQL condicional; helper `hasColunaAcrescimoSig()` |
|
||||||
|
|
||||||
|
**NÃO modificar nesta história:**
|
||||||
|
- `DatabaseHelper.java` — sem mudança de schema SQLite
|
||||||
|
- `PedidoDB.java` — persistência local já concluída (Story 2.3)
|
||||||
|
- `Pedido.java` / `FormaPagamento.java` — VOs completos
|
||||||
|
- `selectAllPedConsulta` em `PedidoPGSQL` — deferred (Story 3.2)
|
||||||
|
- `PedidoConsultaDB.java` — deferred (Story 3.2/3.3)
|
||||||
|
|
||||||
|
### Regras críticas do projeto (aplicáveis)
|
||||||
|
|
||||||
|
- **Sem Kotlin** — somente Java puro
|
||||||
|
- **Sem Gradle** — projeto Eclipse ADT
|
||||||
|
- **Sem JARs novos** — nenhuma dependência adicional
|
||||||
|
- **Sem testes automatizados** — validação manual via dispositivo/emulador
|
||||||
|
- **SQL por `StringBuilder`** — padrão projeto-wide mantido
|
||||||
|
- **`ConnectionManager.closeAll(st, rs)`** — sempre usado para fechar recursos JDBC
|
||||||
|
|
||||||
|
### DDL server-side recomendado (a executar ANTES do deploy para resultado imediato)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Sistema Gerente (colunas já devem existir — confirmar antes):
|
||||||
|
-- ALTER TABLE gerente.pedidos ADD COLUMN acrep REAL DEFAULT 0;
|
||||||
|
-- ALTER TABLE gerente.pedidos ADD COLUMN acrev REAL DEFAULT 0;
|
||||||
|
|
||||||
|
-- Sistema SIG (coluna nova — executar no servidor):
|
||||||
|
ALTER TABLE sig.pedidos ADD COLUMN tx_acrescimo REAL DEFAULT 0;
|
||||||
|
```
|
||||||
|
|
||||||
|
> ⚠️ Para Gerente: como o app já grava `acrep`/`acrev = 0` desde antes, as colunas certamente existem. Confirmar no banco antes de qualquer DDL.
|
||||||
|
|
||||||
|
### Verificação manual após implementação
|
||||||
|
|
||||||
|
1. **Gerente — pedido com acréscimo**
|
||||||
|
- Criar pedido com forma de pagamento com `tx_acrescimo = 2.5`; subtotal R$ 100,00 → acréscimo R$ 2,50
|
||||||
|
- Enviar ao ERP via ComunicaActivity
|
||||||
|
- Verificar em `gerente.pedidos`: `acrep = 2.5`, `acrev = 2.50` para o pedido enviado
|
||||||
|
|
||||||
|
2. **Gerente — pedido sem acréscimo**
|
||||||
|
- Criar pedido com forma de pagamento com `tx_acrescimo = 0`
|
||||||
|
- Verificar em `gerente.pedidos`: `acrep = 0`, `acrev = 0` — sem erro
|
||||||
|
|
||||||
|
3. **SIG com DDL aplicado — pedido com acréscimo**
|
||||||
|
- Após executar DDL em `sig.pedidos`
|
||||||
|
- Criar pedido, enviar; verificar `sig.pedidos`: `tx_acrescimo = 2.5`
|
||||||
|
|
||||||
|
4. **SIG sem DDL (coluna ausente) — tolerância**
|
||||||
|
- Com `tx_acrescimo` ausente em `sig.pedidos` (ou em ambiente de teste)
|
||||||
|
- Enviar pedido: comunicação deve concluir sem exceção
|
||||||
|
- Verificar que o INSERT SIG não inclui `tx_acrescimo` no log (`Log.d("String insert PEDIDO SIG", st.toString())`)
|
||||||
|
|
||||||
|
5. **Log de INSERT para debug**
|
||||||
|
- `Log.d("String insert PEDIDO", st.toString())` (Gerente) — verificar que contém `acrep` e `acrev` com valores reais
|
||||||
|
- `Log.d("String insert PEDIDO SIG", st.toString())` (SIG) — verificar presença/ausência de `tx_acrescimo` conforme DDL
|
||||||
|
|
||||||
|
### Inteligência de histórias anteriores
|
||||||
|
|
||||||
|
- **Story 1.2**: padrão de tolerância a coluna ausente no PG — inspiração para `hasColunaAcrescimoSig()`. Story 1.2 usou try-catch em `FormaPagamentoPGSQL`; aqui optamos por checagem prévia pois o resultado condiciona a construção do SQL (não dá para fazer try-catch depois de preparar o statement).
|
||||||
|
- **Story 2.3**: `Pedido.vlAcrescimo` persistido no SQLite. Esta história fecha o ciclo enviando esse valor ao PG.
|
||||||
|
- **Story 2.4**: `PedidoConsultaDB` aceita `vlAcrescimo = 0.0` do sync PG atual. Após Story 3.1 + 3.2, pedidos novos terão valores reais.
|
||||||
|
- **Stories 1.1/2.3**: padrão `if (oldVersion < N)` em `onUpgrade` + `ConnectionManager.closeAll()`. Este último aplicado em `hasColunaAcrescimoSig()`.
|
||||||
|
|
||||||
|
### Git intelligence (commits recentes)
|
||||||
|
|
||||||
|
- `f2cf45d` Story 2.4: `pedido_consulta.vl_acrescimo` + guard `status >= STATUS_ENVIADO`
|
||||||
|
- `3ff26a7` Story 2.3: `PedidoDB.insert/update/selectAllFull` com `vl_acrescimo`
|
||||||
|
- `9b70ee3` Story 2.2: cálculo em `MainPedidoFragment` e `TotalPedidoFragment`
|
||||||
|
- `168b9db` Story 1.2: sync `gestao.formapag.acresc` → `formapag.tx_acrescimo`
|
||||||
|
|
||||||
|
### References
|
||||||
|
|
||||||
|
- Epics: `_bmad-output/planning-artifacts/epics.md#story-31-enviar-acrescimo-ao-postgresql-ao-salvar-pedido`
|
||||||
|
- FR14: Envio outbound do acréscimo ao PG (Gerente: `acrep`+`acrev`; SIG: `tx_acrescimo`)
|
||||||
|
- FR16: Sync tolera ausência das colunas de acréscimo no PostgreSQL
|
||||||
|
- Memory: `project_pg_sync_acrescimo_schema.md` — assimetria Gerente vs SIG confirmada pelo Julio em 2026-04-16
|
||||||
|
- `PedidoPGSQL.java:129-255` — método `insert()` Gerente
|
||||||
|
- `PedidoPGSQL.java:317-457` — método `insertSig()` SIG
|
||||||
|
- `PedidoPGSQL.java:56-110` — método `save()` (loop de envio)
|
||||||
|
|
||||||
|
## Dev Agent Record
|
||||||
|
|
||||||
|
### Agent Model Used
|
||||||
|
|
||||||
|
claude-sonnet-4-6 (dev-story workflow)
|
||||||
|
|
||||||
|
### Debug Log References
|
||||||
|
|
||||||
|
N/A — projeto sem infraestrutura de testes automatizados; validação manual via dispositivo/emulador conforme roteiro em "Verificação manual após implementação".
|
||||||
|
|
||||||
|
### Completion Notes List
|
||||||
|
|
||||||
|
- Story 3.1 implementada em 2026-04-16.
|
||||||
|
- **Task 1 — Gerente:** `PedidoPGSQL.insert()` linhas 181-182 — `st.setInt(21, 0)` e `st.setInt(22, 0)` substituídos por `st.setDouble(21, ped.getFormapag().getTxAcrescimo())` e `st.setDouble(22, ped.getVlAcrescimo())`. Colunas `acrep` e `acrev` já existiam em `gerente.pedidos` (já estavam na lista de colunas do INSERT, apenas recebendo zero). AC 1 e AC 3 satisfeitos para Gerente.
|
||||||
|
- **Task 2 — Helper SIG:** Método privado `hasColunaAcrescimoSig()` adicionado ao final da classe. Consulta `information_schema.columns` para `sig.pedidos.tx_acrescimo`. Retorna `false` em qualquer exceção. Usa `ConnectionManager.closeAll(st, rs)` no `finally` — padrão projeto-wide. AC 4 satisfeito.
|
||||||
|
- **Task 3 — Cache em `save()`:** `final boolean temAcrescSig` calculado UMA VEZ antes do loop de pedidos (após `conn.setAutoCommit(false)` e `db.beginTransaction()`). Curto-circuita para `false` em sistema Gerente sem executar a query. `insertSig()` passa `temAcrescSig` como terceiro argumento.
|
||||||
|
- **Task 4 — `insertSig()` condicional:** Assinatura alterada para `insertSig(Pedido ped, SQLiteDatabase db, boolean temAcrescimo)`. Lista de colunas SQL e lista de `?` em VALUES construídas condicionalmente com `if (temAcrescimo)`. `st.setDouble(40, ped.getFormapag().getTxAcrescimo())` adicionado condicionalmente após o setter 39 (`vol_marca`). Quando `temAcrescimo = false`, INSERT permanece exatamente igual ao original (39 colunas/params). AC 2, AC 3 e AC 4 satisfeitos para SIG.
|
||||||
|
- AC 1 satisfeito: Gerente grava `acrep = tx_acrescimo` e `acrev = vl_acrescimo` reais.
|
||||||
|
- AC 2 satisfeito: SIG grava `tx_acrescimo = tx_acrescimo` (apenas taxa) quando coluna existe.
|
||||||
|
- AC 3 satisfeito: pedido sem acréscimo → `getTxAcrescimo() = 0.0` e `getVlAcrescimo() = 0.0` (primitivos double) → sem erro.
|
||||||
|
- AC 4 satisfeito: coluna ausente → `hasColunaAcrescimoSig()` retorna `false` → INSERT SIG construído sem `tx_acrescimo` → comunicação conclui normalmente.
|
||||||
|
- Sem testes automatizados: projeto não possui infraestrutura (conforme CLAUDE.md). Validação manual necessária — roteiro completo em "Verificação manual após implementação".
|
||||||
|
- Nenhum import adicionado — `PreparedStatement`, `ResultSet` e `ConnectionManager` já importados.
|
||||||
|
- `dbVersao` permanece 43 — sem alteração de schema SQLite.
|
||||||
|
|
||||||
|
### File List
|
||||||
|
|
||||||
|
- `src/br/com/jcsinformatica/sarandroid/postgres/PedidoPGSQL.java`
|
||||||
|
|
||||||
|
### Review Findings
|
||||||
|
|
||||||
|
- [x] [Review][Patch] `hasColunaAcrescimoSig()` swallows all exceptions silently — exception em conexão/query retorna `false` sem log, omitindo `tx_acrescimo` de toda a sessão sem rastreamento [PedidoPGSQL.java:1197] — **FIXED:** adicionado `Log.w()` no catch
|
||||||
|
- [x] [Review][Defer] `ped.getFormapag()` sem null-check antes de `getTxAcrescimo()` em `insert()` e `insertSig()` [PedidoPGSQL.java:182,425] — deferred, pré-existente: a mesma ausência de null-check existe nas linhas 160 e 387 que vêm antes das novas chamadas; se fosse null, já teria crashado antes
|
||||||
|
- [x] [Review][Defer] Schema `'sig'` hardcoded como string literal em `hasColunaAcrescimoSig()` [PedidoPGSQL.java:1192] — deferred, pré-existente: padrão projeto-wide (`'sig'` e `'gerente'` literais em toda a classe)
|
||||||
|
- [x] [Review][Defer] Valores monetários com `setDouble` para `acrep`/`acrev` (precisão floating-point) [PedidoPGSQL.java:182-183] — deferred, pré-existente projeto-wide (`setDouble` para todos os campos monetários)
|
||||||
|
- [x] [Review][Defer] `codVend2 = null` pode causar NullPointerException no auto-unboxing em `st.setInt(36, codVend2)` [PedidoPGSQL.java:420] — deferred, bug pré-existente não introduzido por este diff
|
||||||
|
- [x] [Review][Defer] `information_schema.columns` sem filtro `table_catalog` [PedidoPGSQL.java:1191] — deferred, risco muito baixo em conexões JDBC por-banco; padrão consistente com `schemaTes()`
|
||||||
|
- [x] [Review][Defer] `information_schema.columns` mais lento que `pg_attribute` [PedidoPGSQL.java:1191] — deferred, consistente com o padrão de `schemaTes()` já existente na classe
|
||||||
|
- [x] [Review][Defer] `keys.close()`/`st.close()` em `insertSig()` não executados se loop de itens lança exceção [PedidoPGSQL.java:~460] — deferred, padrão pré-existente em toda a classe
|
||||||
|
- [x] [Review][Defer] Comentário morto `//int ultimoNumeroPedido = 0;` em `save()` — deferred, pré-existente
|
||||||
|
|
||||||
|
### Change Log
|
||||||
|
|
||||||
|
- 2026-04-16: Story 3.1 criada pelo create-story workflow.
|
||||||
|
- 2026-04-16: Implementação da Story 3.1. `PedidoPGSQL.insert()` (Gerente) agora grava `acrep`/`acrev` com valores reais do VO. `PedidoPGSQL.insertSig()` (SIG) aceita boolean `temAcrescimo` e inclui condicionalmente `tx_acrescimo` no INSERT. `hasColunaAcrescimoSig()` verifica existência da coluna em `sig.pedidos` via `information_schema`. Cache do resultado em `save()` antes do loop de pedidos.
|
||||||
@@ -0,0 +1,245 @@
|
|||||||
|
# Story 3.2: Ler Acréscimo do PostgreSQL no Sync de Consulta
|
||||||
|
|
||||||
|
Status: done
|
||||||
|
|
||||||
|
## Story
|
||||||
|
|
||||||
|
Como representante de vendas,
|
||||||
|
quero que pedidos históricos sincronizados do ERP cheguem ao app com o acréscimo correto gravado,
|
||||||
|
para que a consulta exiba os valores reais praticados mesmo em pedidos que não foram criados neste dispositivo.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
1. **Dado** que `Global.sistema == SISTEMA_GERENTE` e `gerente.pedidos` possui `acrep = 3.0` e `acrev = 105.00` para um pedido
|
||||||
|
**Quando** `PedidoPGSQL.selectAllPedConsulta` ler o registro
|
||||||
|
**Então** o VO `Pedido` retornado tem `vlAcrescimo = 105.00`
|
||||||
|
|
||||||
|
2. **Dado** que `Global.sistema == SISTEMA_SIG` e `sig.pedidos` possui `tx_acrescimo = 3.0` e `total = 3500.00` para um pedido
|
||||||
|
**Quando** `PedidoPGSQL.selectAllPedConsulta` ler o registro
|
||||||
|
**Então** o VO `Pedido` retornado tem `vlAcrescimo` calculado como `3500.00 × 3.0 / 100 = 105.00`
|
||||||
|
|
||||||
|
3. **Dado** que a coluna de acréscimo não existe no PostgreSQL ou está `NULL`
|
||||||
|
**Quando** `selectAllPedConsulta` for executado
|
||||||
|
**Então** o VO recebe `vlAcrescimo = 0.0`, sem erro, sem crash
|
||||||
|
|
||||||
|
4. **Dado** que `PedidoConsultaDB.saveAll` recebe os VOs retornados
|
||||||
|
**Quando** gravar em `pedido_consulta`
|
||||||
|
**Então** `pedido_consulta.vl_acrescimo` reflete o valor lido do PG
|
||||||
|
|
||||||
|
## Escopo desta história
|
||||||
|
|
||||||
|
**SIM:**
|
||||||
|
- Adicionar `COALESCE(a.acrev, 0.0) as acrev` ao SELECT Gerente em `selectAllPedConsulta()`
|
||||||
|
- Reutilizar `hasColunaAcrescimoSig()` (já existe na classe) antes do SELECT SIG
|
||||||
|
- Adicionar `COALESCE(a.tx_acrescimo, 0.0) as tx_acrescimo` condicionalmente ao SELECT SIG quando coluna existir
|
||||||
|
- Ler e setar `ped.setVlAcrescimo(...)` no loop de ResultSet (`acrev` direto para Gerente; `total × tx_acrescimo / 100` para SIG)
|
||||||
|
|
||||||
|
**NÃO cobre:**
|
||||||
|
- `PedidoConsultaDB` — já grava e lê `vl_acrescimo` (Stories 2.3/2.4 já fizeram isso — não modificar)
|
||||||
|
- MD5 de `pedido_consulta` incluindo `vl_acrescimo` — Story 3.3
|
||||||
|
- DDL server-side — responsabilidade do DBA; app tolera ausência
|
||||||
|
|
||||||
|
## Pré-condições verificadas
|
||||||
|
|
||||||
|
- **`PedidoConsultaDB.insert()`** — já inclui `vl_acrescimo` na lista de colunas e lê `ped.getVlAcrescimo()` na linha 147. Nenhuma mudança necessária.
|
||||||
|
- **`PedidoConsultaDB.updateErp()`** — já atualiza `vl_acrescimo = ped.getVlAcrescimo()` na linha 183. Nenhuma mudança necessária.
|
||||||
|
- **`PedidoConsultaDB.selectFull()`** — já lê `a.vl_acrescimo` (índice 66) e chama `pedido.setVlAcrescimo(c.getDouble(66))` na linha 296. Nenhuma mudança necessária.
|
||||||
|
- **`hasColunaAcrescimoSig()`** — método privado já existe em `PedidoPGSQL.java` (linhas 1186–1203); pode ser reutilizado em `selectAllPedConsulta()` diretamente.
|
||||||
|
- **Gerente — `acrep`/`acrev` já existem:** Confirmado em Story 3.1 — colunas estavam na lista do INSERT desde antes do Epic 3; safe para adicionar ao SELECT sem tolerância.
|
||||||
|
- **SIG — `tx_acrescimo` pode não existir:** DDL pode estar pendente; mesma tolerância aplicada em Story 3.1.
|
||||||
|
- **`Pedido.setVlAcrescimo(double)`** — getter/setter adicionados em Story 2.3; disponíveis no VO.
|
||||||
|
- **`new Pedido()`** — `vlAcrescimo` default é `0.0` (primitivo double); AC 3 satisfeito automaticamente quando não setado.
|
||||||
|
- **ResultSet em `selectAllPedConsulta()` usa acesso por nome** (`rs.getString("num_ped_sar")`, etc.); adicionar colunas nomeadas ao SELECT é seguro e não desloca índices.
|
||||||
|
- **`dbVersao` permanece 43** — sem alteração de schema SQLite.
|
||||||
|
|
||||||
|
## Tasks / Subtasks
|
||||||
|
|
||||||
|
- [x] **Task 1:** Declarar variável de controle SIG antes do if-else de queries (AC: 2, 3)
|
||||||
|
- [x] 1.1 — Em `PedidoPGSQL.java`, dentro de `selectAllPedConsulta()`, logo antes do bloco `if (Global.sistema.equals(Global.SISTEMA_GERENTE)){` (linha 619), adicionar:
|
||||||
|
```java
|
||||||
|
boolean temAcrescSigConsulta = false;
|
||||||
|
```
|
||||||
|
> Variável acessível em ambos os branches e no loop de ResultSet. `false` como default garante AC 3 para SIG sem DDL.
|
||||||
|
|
||||||
|
- [x] **Task 2:** Adicionar `acrev` ao SELECT Gerente (AC: 1, 3)
|
||||||
|
- [x] 2.1 — No branch Gerente (linhas 624–643), alterar a linha que termina com `a.total`:
|
||||||
|
```java
|
||||||
|
// ANTES:
|
||||||
|
sql.append(" a.ped_flex, a.descv, a.descp, a.total"); //13-16
|
||||||
|
|
||||||
|
// DEPOIS:
|
||||||
|
sql.append(" a.ped_flex, a.descv, a.descp, a.total, COALESCE(a.acrev, 0.0) as acrev"); //13-17
|
||||||
|
```
|
||||||
|
> `COALESCE` garante que registros históricos com `acrev = NULL` retornem `0.0` — AC 3. `acrep` não é necessário para vlAcrescimo; apenas `acrev` (o valor calculado) é lido.
|
||||||
|
|
||||||
|
- [x] **Task 3:** Checar coluna e adicionar `tx_acrescimo` ao SELECT SIG condicionalmente (AC: 2, 3)
|
||||||
|
- [x] 3.1 — No branch SIG (linha ~645, início do `else`), ANTES de construir o `sql`, adicionar:
|
||||||
|
```java
|
||||||
|
temAcrescSigConsulta = hasColunaAcrescimoSig();
|
||||||
|
```
|
||||||
|
> Reutiliza o método privado existente (linhas 1186–1203). Executa UMA VEZ por chamada a `selectAllPedConsulta`, antes do loop de ResultSet.
|
||||||
|
- [x] 3.2 — No SELECT SIG, após `a.total` (linha ~658), adicionar condicionalmente:
|
||||||
|
```java
|
||||||
|
// ANTES:
|
||||||
|
sql.append(" a.ped_flex, a.descv, a.descp, a.total"); //13-16
|
||||||
|
|
||||||
|
// DEPOIS:
|
||||||
|
sql.append(" a.ped_flex, a.descv, a.descp, a.total"); //13-16
|
||||||
|
if (temAcrescSigConsulta) {
|
||||||
|
sql.append(", COALESCE(a.tx_acrescimo, 0.0) as tx_acrescimo"); //17
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [x] **Task 4:** Setar `vlAcrescimo` no VO no loop de ResultSet (AC: 1, 2, 3, 4)
|
||||||
|
- [x] 4.1 — Após `ped.setTotal(rs.getDouble("total"))` (linha 730), adicionar:
|
||||||
|
```java
|
||||||
|
if (Global.sistema.equals(Global.SISTEMA_GERENTE)) {
|
||||||
|
ped.setVlAcrescimo(rs.getDouble("acrev"));
|
||||||
|
} else if (temAcrescSigConsulta) {
|
||||||
|
ped.setVlAcrescimo(rs.getDouble("total") * rs.getDouble("tx_acrescimo") / 100.0);
|
||||||
|
}
|
||||||
|
// else: vlAcrescimo = 0.0 (default de new Pedido() — AC 3 para SIG sem DDL)
|
||||||
|
```
|
||||||
|
> Para Gerente: `acrev` é o valor congelado no ERP (taxa aplicada na época do pedido). Para SIG: fórmula especificada no AC 2 — `total × tx_acrescimo / 100`. Se SIG sem DDL: `temAcrescSigConsulta = false` → nenhum setter chamado → `vlAcrescimo = 0.0`.
|
||||||
|
|
||||||
|
## Dev Notes
|
||||||
|
|
||||||
|
### Mapa de campos por sistema
|
||||||
|
|
||||||
|
| Sistema | Coluna PG para vlAcrescimo | Existência garantida? | Fórmula no app |
|
||||||
|
|---------|----------------------------|-----------------------|----------------|
|
||||||
|
| Gerente (`gerente.pedidos`) | `acrev` | Sim — col já existia antes do Epic 3 | `vlAcrescimo = rs.getDouble("acrev")` |
|
||||||
|
| SIG (`sig.pedidos`) | `tx_acrescimo` | Não — DDL pode estar pendente | `vlAcrescimo = total × tx_acrescimo / 100.0` |
|
||||||
|
|
||||||
|
### Por que Gerente usa `acrev` (não `acrep`)
|
||||||
|
|
||||||
|
`acrep` é a taxa percentual (ex: 2.5). `acrev` é o valor calculado (ex: R$ 87,50). `Pedido.vlAcrescimo` armazena o **valor monetário**, não a taxa. Portanto apenas `acrev` é necessário no SELECT Gerente.
|
||||||
|
|
||||||
|
### Por que SIG calcula o valor em vez de armazená-lo
|
||||||
|
|
||||||
|
SIG armazena só a taxa (`tx_acrescimo`). O valor é derivado server-side usando a fórmula `total × tx_acrescimo / 100`. Esta é a mesma assimetria documentada em Story 3.1 e nos pré-requisitos do Epic 3 — intencional, não é bug.
|
||||||
|
|
||||||
|
### Reuso de `hasColunaAcrescimoSig()`
|
||||||
|
|
||||||
|
O método privado já existe em `PedidoPGSQL.java` (linhas 1186–1203). Foi criado em Story 3.1 para o INSERT; aqui é reutilizado no SELECT. Não duplicar lógica — chamar o mesmo método.
|
||||||
|
|
||||||
|
### `PedidoConsultaDB` — NENHUMA MUDANÇA NECESSÁRIA
|
||||||
|
|
||||||
|
`PedidoConsultaDB.insert()` (linha 147): `sql.append(ped.getVlAcrescimo() + ");")` — já grava.
|
||||||
|
`PedidoConsultaDB.updateErp()` (linha 183): `sql.append(" vl_acrescimo = " + ped.getVlAcrescimo())` — já atualiza.
|
||||||
|
`PedidoConsultaDB.selectFull()` (linha 296): `pedido.setVlAcrescimo(c.getDouble(66))` — já lê.
|
||||||
|
|
||||||
|
A Story 2.4 já preparou tudo. Story 3.2 apenas popula o campo `vlAcrescimo` no VO antes de `saveAll` ser chamado.
|
||||||
|
|
||||||
|
### Fluxo completo (para referência)
|
||||||
|
|
||||||
|
```
|
||||||
|
ComunicaActivity.onPostExecute()
|
||||||
|
→ PedidoPGSQL.selectAllPedConsulta() ← MUDANÇA AQUI: ler acrev/tx_acrescimo
|
||||||
|
→ rs → Pedido VO com vlAcrescimo preenchido
|
||||||
|
→ PedidoConsultaDB.saveAll() ← SEM MUDANÇA: já grava vl_acrescimo
|
||||||
|
→ insert() ou updateErp()
|
||||||
|
→ pedido_consulta.vl_acrescimo = valor lido do PG
|
||||||
|
→ BrowsePedidoConsulta / selectFull() ← SEM MUDANÇA: já lê vl_acrescimo
|
||||||
|
```
|
||||||
|
|
||||||
|
### Arquivo a modificar
|
||||||
|
|
||||||
|
| Arquivo | Caminho | O que muda |
|
||||||
|
|---------|---------|------------|
|
||||||
|
| `PedidoPGSQL.java` | `src/br/com/jcsinformatica/sarandroid/postgres/` | `selectAllPedConsulta()`: SELECT Gerente + `temAcrescSigConsulta` + SELECT SIG condicional + setter no loop |
|
||||||
|
|
||||||
|
**NÃO modificar:**
|
||||||
|
- `PedidoConsultaDB.java` — já completo (Stories 2.3/2.4)
|
||||||
|
- `Pedido.java` — VO completo (Story 2.3)
|
||||||
|
- `DatabaseHelper.java` — schema SQLite não muda
|
||||||
|
|
||||||
|
### Regras críticas do projeto (aplicáveis)
|
||||||
|
|
||||||
|
- **Sem Kotlin** — somente Java puro
|
||||||
|
- **Sem Gradle** — projeto Eclipse ADT
|
||||||
|
- **Sem JARs novos** — nenhuma dependência adicional
|
||||||
|
- **Sem testes automatizados** — validação manual via dispositivo/emulador
|
||||||
|
- **`ConnectionManager.closeAll(st, rs)`** — já chamado ao final de `selectAllPedConsulta()` (linha 751); não alterar
|
||||||
|
- **ResultSet por nome** — padrão em `selectAllPedConsulta()`; não usar índices posicionais
|
||||||
|
|
||||||
|
### Inteligência de histórias anteriores
|
||||||
|
|
||||||
|
- **Story 3.1:** `hasColunaAcrescimoSig()` criada aqui — reutilizar sem duplicar. `insert()` Gerente usa `acrep`/`acrev` (já existem); `insertSig()` usa `tx_acrescimo` com tolerância. Mesmos princípios aqui, mas para leitura.
|
||||||
|
- **Story 2.4:** `BrowsePedidoConsulta` e `selectFull()` já usam `vlAcrescimo` do VO. Esta story fecha o ciclo: antes só os pedidos criados localmente tinham `vlAcrescimo`; agora também os históricos do PG.
|
||||||
|
- **Story 2.3:** `Pedido.vlAcrescimo` (`double`, default `0.0`) e `PedidoConsultaDB` totalmente preparados.
|
||||||
|
- **Story 1.2:** Padrão de tolerância a coluna ausente — `try-catch` em `FormaPagamentoPGSQL`. Aqui usamos a abordagem mais robusta de `hasColunaAcrescimoSig()` (já criada em Story 3.1), que condiciona a construção do SQL em vez de tentar e falhar.
|
||||||
|
|
||||||
|
### Verificação manual após implementação
|
||||||
|
|
||||||
|
1. **Gerente — pedido com acréscimo gravado no PG:**
|
||||||
|
- Garantir que um pedido em `gerente.pedidos` tenha `acrev > 0` (enviar via Story 3.1 ou inserir diretamente)
|
||||||
|
- Executar sync (ComunicaActivity); verificar em `pedido_consulta` via logcat que `INSERT PEDIDO CONSULTA` contém `vl_acrescimo = <valor>` correspondente ao `acrev` do PG
|
||||||
|
|
||||||
|
2. **Gerente — pedido histórico com `acrev = NULL` ou 0:**
|
||||||
|
- Verificar que `vlAcrescimo = 0.0` no VO — sem crash
|
||||||
|
|
||||||
|
3. **SIG com DDL aplicado:**
|
||||||
|
- Pedido em `sig.pedidos` com `tx_acrescimo = 3.0`, `total = 3500.00`
|
||||||
|
- Sync → `vl_acrescimo = 105.00` em `pedido_consulta`
|
||||||
|
|
||||||
|
4. **SIG sem DDL (coluna ausente):**
|
||||||
|
- Com `tx_acrescimo` ausente em `sig.pedidos`
|
||||||
|
- Sync deve concluir sem exceção; `vl_acrescimo = 0.0` nos pedidos sincronizados
|
||||||
|
|
||||||
|
5. **Log para debug:**
|
||||||
|
- `Log.d("INSERT PEDIDO CONSULTA", sql.toString())` — verificar `vl_acrescimo` no SQL de insert
|
||||||
|
- `Log.d("SQL PEDIDO CONSULTA = ", sql.toString())` — verificar se `acrev` ou `tx_acrescimo` está no SELECT
|
||||||
|
|
||||||
|
### Git intelligence (commits recentes)
|
||||||
|
|
||||||
|
- `f2cf45d` Story 2.4: `pedido_consulta.vl_acrescimo` + guard `status >= STATUS_ENVIADO` em consulta
|
||||||
|
- `3ff26a7` Story 2.3: `Pedido.vlAcrescimo` + `PedidoDB` + `PedidoConsultaDB` preparados
|
||||||
|
- Story 3.1 (não commitada): `PedidoPGSQL.insert/insertSig/save/hasColunaAcrescimoSig` — padrões a seguir
|
||||||
|
|
||||||
|
### References
|
||||||
|
|
||||||
|
- Epics: `_bmad-output/planning-artifacts/epics.md#story-32-ler-acrescimo-do-postgresql-no-sync-de-consulta`
|
||||||
|
- FR15: Leitura inbound do acréscimo do PG na sync de consulta
|
||||||
|
- FR16: Sync tolera ausência das colunas de acréscimo no PostgreSQL
|
||||||
|
- `PedidoPGSQL.java:603–752` — método `selectAllPedConsulta()` completo
|
||||||
|
- `PedidoPGSQL.java:1186–1203` — `hasColunaAcrescimoSig()` a reutilizar
|
||||||
|
- `PedidoConsultaDB.java:98–151` — `insert()` (linha 147: `vl_acrescimo` já gravado)
|
||||||
|
- `PedidoConsultaDB.java:153–196` — `updateErp()` (linha 183: `vl_acrescimo` já atualizado)
|
||||||
|
- `PedidoConsultaDB.java:198–305` — `selectFull()` (linha 296: `vl_acrescimo` já lido)
|
||||||
|
- Memory: `project_pg_sync_acrescimo_schema.md` — assimetria Gerente vs SIG
|
||||||
|
|
||||||
|
## Dev Agent Record
|
||||||
|
|
||||||
|
### Agent Model Used
|
||||||
|
|
||||||
|
claude-sonnet-4-6 (create-story workflow)
|
||||||
|
|
||||||
|
### Debug Log References
|
||||||
|
|
||||||
|
N/A — projeto sem infraestrutura de testes automatizados; validação manual via dispositivo/emulador conforme roteiro em "Verificação manual após implementação".
|
||||||
|
|
||||||
|
### Completion Notes List
|
||||||
|
|
||||||
|
- Story 3.2 implementada em 2026-04-16.
|
||||||
|
- **Task 1:** `boolean temAcrescSigConsulta = false` declarada antes do if-else Gerente/SIG em `selectAllPedConsulta()` (linha 619). Acessível em ambos os branches e no loop de ResultSet.
|
||||||
|
- **Task 2 — Gerente SELECT:** `COALESCE(a.acrev, 0.0) as acrev` adicionado ao SELECT (linha 630). `COALESCE` protege contra registros históricos com `acrev = NULL`. `acrep` não incluído — apenas o valor calculado (`acrev`) é necessário para `vlAcrescimo`.
|
||||||
|
- **Task 3 — SIG SELECT:** `temAcrescSigConsulta = hasColunaAcrescimoSig()` chamado UMA VEZ antes de construir o SQL (linha 653). SELECT SIG inclui condicionalmente `COALESCE(a.tx_acrescimo, 0.0) as tx_acrescimo` quando coluna existe (linhas 661–663). Reutiliza o método privado criado em Story 3.1 — sem duplicação de lógica.
|
||||||
|
- **Task 4 — vlAcrescimo no loop:** Após `ped.setTotal(...)` (linhas 736–740): Gerente → `setVlAcrescimo(rs.getDouble("acrev"))`; SIG com DDL → `setVlAcrescimo(total × tx_acrescimo / 100.0)`; SIG sem DDL → sem setter, `vlAcrescimo = 0.0` (default primitivo). AC 1, 2, 3 e 4 satisfeitos.
|
||||||
|
- `PedidoConsultaDB` não modificado — `insert()`, `updateErp()` e `selectFull()` já manipulam `vl_acrescimo` desde Stories 2.3/2.4.
|
||||||
|
- `dbVersao` permanece 43 — sem alteração de schema SQLite.
|
||||||
|
- Sem testes automatizados: projeto não possui infraestrutura (conforme CLAUDE.md). Validação manual necessária — roteiro em "Verificação manual após implementação".
|
||||||
|
- Nenhum import adicionado — todas as classes já importadas.
|
||||||
|
|
||||||
|
### File List
|
||||||
|
|
||||||
|
- `src/br/com/jcsinformatica/sarandroid/postgres/PedidoPGSQL.java`
|
||||||
|
|
||||||
|
### Review Findings
|
||||||
|
|
||||||
|
- [x] [Review][Defer] `hasColunaAcrescimoSig()` chamado duas vezes por sessão de sync — uma vez em `save()` (Story 3.1) e uma vez em `selectAllPedConsulta()` (Story 3.2). Durante uma janela de DDL migration, os dois resultados podem divergir, fazendo insert com `tx_acrescimo` mas read sem (ou vice-versa). Em produção, DDL deve ocorrer fora da janela de sync; risco é teórico. — deferred, pré-existente ao escopo de 3.2
|
||||||
|
|
||||||
|
### Change Log
|
||||||
|
|
||||||
|
- 2026-04-16: Story 3.2 criada pelo create-story workflow.
|
||||||
|
- 2026-04-16: Story 3.2 implementada. `PedidoPGSQL.selectAllPedConsulta()`: SELECT Gerente agora inclui `COALESCE(a.acrev, 0.0) as acrev`; SELECT SIG inclui condicionalmente `COALESCE(a.tx_acrescimo, 0.0) as tx_acrescimo` (via `hasColunaAcrescimoSig()` reutilizado de Story 3.1); loop de ResultSet seta `Pedido.vlAcrescimo` com o valor lido do PG (`acrev` direto para Gerente; `total × tx_acrescimo / 100` para SIG). Tolerância completa à ausência de DDL no SIG.
|
||||||
@@ -0,0 +1,261 @@
|
|||||||
|
# Story 3.3: Incluir `vl_acrescimo` no MD5 de Change-Detection
|
||||||
|
|
||||||
|
Status: done
|
||||||
|
|
||||||
|
## Story
|
||||||
|
|
||||||
|
Como sistema,
|
||||||
|
quero que mudanças isoladas de `vl_acrescimo` num pedido disparem re-sync do registro local,
|
||||||
|
para que ajustes de acréscimo no ERP (reabertura, correção manual server-side) sejam propagados ao dispositivo.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
1. **Dado** que um pedido já sincronizado em `pedido_consulta` tem `vl_acrescimo = 87.50` e `md5 = H1`
|
||||||
|
**Quando** o ERP alterar apenas o `acrev` (Gerente) ou `tx_acrescimo` (SIG) do registro e o próximo `selectAllPedConsulta` rodar
|
||||||
|
**Então** o `md5` retornado pelo PG difere de `H1`, `PedidoConsultaDB.saveAll` detecta a divergência e dispara `updateErp`, gravando o novo `vl_acrescimo` e o novo `md5` em `pedido_consulta`
|
||||||
|
|
||||||
|
2. **Dado** que o MD5 server-side inclui o campo de acréscimo no hash (`acrev` para Gerente; `tx_acrescimo` para SIG quando coluna existe)
|
||||||
|
**Quando** o mesmo pedido for lido em duas execuções consecutivas de `selectAllPedConsulta` sem alteração no ERP
|
||||||
|
**Então** o `md5` é idêntico nas duas execuções — não há loop de "diferente/igual" entre pedidos já em dia
|
||||||
|
|
||||||
|
3. **Dado** que pedidos pré-Epic 3 no ERP têm `acrev = 0` (Gerente) ou `tx_acrescimo = 0` (SIG) após aplicação do `DEFAULT 0`
|
||||||
|
**Quando** o primeiro `selectAllPedConsulta` após o deploy de Story 3.3 rodar
|
||||||
|
**Então** esses pedidos aparecem como "atualizados" (contador `tt[2]` incrementa pois o novo termo no MD5 altera o hash), `vl_acrescimo` gravado permanece `0.0`, sem crash, sem travamento — comportamento aceitável e idempotente
|
||||||
|
|
||||||
|
4. **Dado** que `Global.sistema == SISTEMA_SIG` e a coluna `tx_acrescimo` ainda **não existe** em `sig.pedidos` (`hasColunaAcrescimoSig() == false`)
|
||||||
|
**Quando** o `selectAllPedConsulta` SIG construir o MD5
|
||||||
|
**Então** o MD5 é calculado **sem** o termo `COALESCE(a.tx_acrescimo, 0.0)`, a query executa normalmente, e o comportamento de re-sync permanece idêntico ao de pré-Story 3.3 até o DDL ser aplicado
|
||||||
|
|
||||||
|
## Escopo desta história
|
||||||
|
|
||||||
|
**SIM:**
|
||||||
|
- Adicionar `|| COALESCE(A.acrev, 0.0)` ao final do hash MD5 no SELECT Gerente de `selectAllPedConsulta()`
|
||||||
|
- Adicionar condicionalmente `|| COALESCE(a.tx_acrescimo, 0.0)` ao hash MD5 no SELECT SIG, reutilizando `temAcrescSigConsulta` (já calculado em Story 3.2)
|
||||||
|
|
||||||
|
**NÃO cobre:**
|
||||||
|
- Cálculo de MD5 no lado do app (o app nunca calcula MD5 local — apenas armazena o hash retornado pelo PG; ver "Como o change-detection funciona de fato" abaixo)
|
||||||
|
- `PedidoConsultaDB.saveAll` — já compara `md5` stored vs `ped.getMd5()` e dispara `updateErp` desde versões anteriores (nenhuma mudança necessária)
|
||||||
|
- DDL server-side — `acrev` em Gerente e `tx_acrescimo` em SIG são gerenciados pelo DBA; app continua tolerante (Stories 3.1/3.2)
|
||||||
|
- `pedido_consulta.md5` no SQLite — coluna já existe e é populada por `insert`/`updateErp` em `PedidoConsultaDB`
|
||||||
|
|
||||||
|
## Pré-condições verificadas
|
||||||
|
|
||||||
|
- **MD5 é calculado apenas no PG:** grep confirma que `MD5(...)` aparece só em [PedidoPGSQL.java:628](../../src/br/com/jcsinformatica/sarandroid/postgres/PedidoPGSQL.java) (Gerente) e [PedidoPGSQL.java:658](../../src/br/com/jcsinformatica/sarandroid/postgres/PedidoPGSQL.java) (SIG). Não existe cálculo de MD5 no app — o hash armazenado é o que o PG retornou no sync anterior.
|
||||||
|
- **`temAcrescSigConsulta`** já é declarada em [PedidoPGSQL.java:619](../../src/br/com/jcsinformatica/sarandroid/postgres/PedidoPGSQL.java) e populada via `hasColunaAcrescimoSig()` em [PedidoPGSQL.java:653](../../src/br/com/jcsinformatica/sarandroid/postgres/PedidoPGSQL.java) (Story 3.2). Reutilizar sem duplicar.
|
||||||
|
- **`acrev` em Gerente já é lido** pelo SELECT após Story 3.2 (linha 630: `COALESCE(a.acrev, 0.0) as acrev`). A coluna certamente existe em `gerente.pedidos` (confirmado em Story 3.1). Safe para incluir no MD5 sem tolerância.
|
||||||
|
- **`tx_acrescimo` em SIG é lido condicionalmente** (linhas 661–663). A condição `if (temAcrescSigConsulta)` vale tanto para a projeção quanto para o MD5.
|
||||||
|
- **`PedidoConsultaDB.saveAll` compara MD5 e dispara `updateErp`** em [PedidoConsultaDB.java:60-67](../../src/br/com/jcsinformatica/sarandroid/database/PedidoConsultaDB.java) — `updateErp` já grava `vl_acrescimo` (linha 183) e `md5` (linha 177). Fluxo completo funciona sem alterações nessa classe.
|
||||||
|
- **`dbVersao` permanece 43** — esta story não altera schema SQLite.
|
||||||
|
- **`COALESCE(x, 0.0)`** produz string estável no `md5()` do PostgreSQL (concatenação via `||` com cast implícito para text); padrão idêntico ao já usado para `id_formapag`, `id_pauta`, `obs`.
|
||||||
|
|
||||||
|
## Tasks / Subtasks
|
||||||
|
|
||||||
|
- [x] **Task 1:** Incluir `acrev` no MD5 do SELECT Gerente (AC: 1, 2, 3)
|
||||||
|
- [x] 1.1 — Em [PedidoPGSQL.java:629](../../src/br/com/jcsinformatica/sarandroid/postgres/PedidoPGSQL.java), alterar a linha que termina com `A.total),`:
|
||||||
|
```java
|
||||||
|
// ANTES:
|
||||||
|
sql.append(" COALESCE(B.id_formapag,0) || COALESCE(A.obs,'') || COALESCE(C.id_pauta,0) || A.total),");// 12
|
||||||
|
|
||||||
|
// DEPOIS:
|
||||||
|
sql.append(" COALESCE(B.id_formapag,0) || COALESCE(A.obs,'') || COALESCE(C.id_pauta,0) || A.total || COALESCE(A.acrev, 0.0)),");// 12
|
||||||
|
```
|
||||||
|
> A coluna `acrev` já existe em `gerente.pedidos` (confirmado em Story 3.1). `COALESCE(..., 0.0)` protege contra `NULL` histórico e garante hash estável para pedidos sem acréscimo. Não alterar a linha 630 (projeção `COALESCE(a.acrev, 0.0) as acrev`) — ela é o SELECT do valor para `vlAcrescimo`, não do termo do MD5, e já foi feita em Story 3.2.
|
||||||
|
|
||||||
|
- [x] **Task 2:** Incluir `tx_acrescimo` condicionalmente no MD5 do SELECT SIG (AC: 1, 2, 3, 4)
|
||||||
|
- [x] 2.1 — Em [PedidoPGSQL.java:658-659](../../src/br/com/jcsinformatica/sarandroid/postgres/PedidoPGSQL.java), quebrar o append da linha do MD5 em três partes para permitir inserção condicional:
|
||||||
|
```java
|
||||||
|
// ANTES:
|
||||||
|
sql.append(" MD5(num_ped_sar || id_pedido || numero || A.tipo || situa || data || clien || COALESCE((SELECT data_emissao FROM sig.pedidos WHERE id_empresa=A.id_empresa AND tipo='E' AND numer_ped_vinc=A.numero ORDER BY data_emissao DESC LIMIT 1),A.data) ||");
|
||||||
|
sql.append(" COALESCE(id_formapag,0) || COALESCE(A.obs,'') || COALESCE(C.id_pauta,0)) as md5,");// 12
|
||||||
|
|
||||||
|
// DEPOIS:
|
||||||
|
sql.append(" MD5(num_ped_sar || id_pedido || numero || A.tipo || situa || data || clien || COALESCE((SELECT data_emissao FROM sig.pedidos WHERE id_empresa=A.id_empresa AND tipo='E' AND numer_ped_vinc=A.numero ORDER BY data_emissao DESC LIMIT 1),A.data) ||");
|
||||||
|
sql.append(" COALESCE(id_formapag,0) || COALESCE(A.obs,'') || COALESCE(C.id_pauta,0)");
|
||||||
|
if (temAcrescSigConsulta) {
|
||||||
|
sql.append(" || COALESCE(a.tx_acrescimo, 0.0)");
|
||||||
|
}
|
||||||
|
sql.append(") as md5,");// 12
|
||||||
|
```
|
||||||
|
> Quando `temAcrescSigConsulta = false`, o SQL resultante é idêntico ao pré-Story 3.3 — garantindo AC 4 (tolerância total à ausência de DDL). Quando `true`, o hash passa a depender de `tx_acrescimo`, disparando re-sync em alterações server-side isoladas (AC 1).
|
||||||
|
|
||||||
|
## Dev Notes
|
||||||
|
|
||||||
|
### Como o change-detection funciona de fato
|
||||||
|
|
||||||
|
Apesar do epic mencionar "app calcular o MD5 local", a inspeção do código confirma que **não há cálculo de MD5 no lado do app**. O fluxo real é:
|
||||||
|
|
||||||
|
1. **Sync anterior:** `selectAllPedConsulta` retorna `md5` calculado pelo PG; `PedidoConsultaDB.insert`/`updateErp` armazena esse hash em `pedido_consulta.md5`.
|
||||||
|
2. **Sync atual:** `selectAllPedConsulta` retorna novo `md5` do PG; `saveAll` compara com o hash stored ([PedidoConsultaDB.java:60](../../src/br/com/jcsinformatica/sarandroid/database/PedidoConsultaDB.java)).
|
||||||
|
3. **Decisão:**
|
||||||
|
- `md5 == null` → pedido novo → `insert`
|
||||||
|
- `md5.equals(ped.getMd5())` → inalterado → `continue` (skip)
|
||||||
|
- `!equals` → alterado → `updateErp` (grava novos dados **e** novo hash)
|
||||||
|
|
||||||
|
Portanto, a consistência requerida pelo AC 2 é a consistência da **fórmula MD5 do PG** entre syncs — não entre app e servidor. Isso significa que, enquanto o código do SELECT permanecer o mesmo, o hash será determinístico para o mesmo conteúdo do pedido.
|
||||||
|
|
||||||
|
### Por que `acrev` (não `acrep`) no MD5 Gerente
|
||||||
|
|
||||||
|
`acrep` é a taxa percentual (ex: `2.5`). `acrev` é o valor monetário do acréscimo (ex: `87.50`). Como `vl_acrescimo` no SQLite armazena o valor monetário (Story 2.3), e como `acrev` é o que Story 3.2 já lê para popular `Pedido.vlAcrescimo` em Gerente, faz sentido incluir **apenas `acrev`** no hash — mudar `acrep` sem mudar `acrev` implicaria inconsistência no próprio PG que o app não precisa detectar. Além disso, para todo pedido gravado via Story 3.1, `acrep` e `acrev` mudam juntos (são ambos derivados de `tx_acrescimo` + subtotal na hora do INSERT) — redundância desnecessária no hash.
|
||||||
|
|
||||||
|
### Por que `tx_acrescimo` (não valor calculado) no MD5 SIG
|
||||||
|
|
||||||
|
Em SIG, o valor do acréscimo é derivado server-side (`total × tx_acrescimo / 100`). O único campo **armazenado** é `tx_acrescimo`. Se o valor calculado mudar por alteração de `total` (já no MD5) ou de `tx_acrescimo` (novo termo), ambos os casos ficam cobertos. Não é necessário incluir o cálculo completo — incluir só `tx_acrescimo` basta.
|
||||||
|
|
||||||
|
### Snapshot do SQL resultante (para validação rápida no logcat)
|
||||||
|
|
||||||
|
**Gerente (sempre):**
|
||||||
|
```
|
||||||
|
MD5(A.num_ped_sar || A.id_pedido || A.numero || A.tipo || A.situa || A.data || A.clien || COALESCE(A.dtemi,NOW()) ||
|
||||||
|
COALESCE(B.id_formapag,0) || COALESCE(A.obs,'') || COALESCE(C.id_pauta,0) || A.total || COALESCE(A.acrev, 0.0)),
|
||||||
|
```
|
||||||
|
|
||||||
|
**SIG com DDL aplicado (`temAcrescSigConsulta = true`):**
|
||||||
|
```
|
||||||
|
MD5(num_ped_sar || id_pedido || numero || A.tipo || situa || data || clien || COALESCE((SELECT ...),A.data) ||
|
||||||
|
COALESCE(id_formapag,0) || COALESCE(A.obs,'') || COALESCE(C.id_pauta,0) || COALESCE(a.tx_acrescimo, 0.0)) as md5,
|
||||||
|
```
|
||||||
|
|
||||||
|
**SIG sem DDL (`temAcrescSigConsulta = false`):**
|
||||||
|
```
|
||||||
|
MD5(num_ped_sar || id_pedido || numero || A.tipo || situa || data || clien || COALESCE((SELECT ...),A.data) ||
|
||||||
|
COALESCE(id_formapag,0) || COALESCE(A.obs,'') || COALESCE(C.id_pauta,0)) as md5,
|
||||||
|
```
|
||||||
|
|
||||||
|
### Impacto operacional no primeiro sync após deploy (AC 3)
|
||||||
|
|
||||||
|
Como o MD5 passa a incluir `acrev`/`tx_acrescimo`, **todos** os pedidos em `pedido_consulta` terão hash diferente no primeiro `selectAllPedConsulta` após o deploy:
|
||||||
|
|
||||||
|
- **Gerente:** todos os pedidos existentes serão marcados como "alterados" (`tt[2]++`) — `updateErp` rodará para cada um; `vl_acrescimo` será gravado com o valor de `acrev` (pode ser `0` para pedidos pré-Epic 3, ou valor real para pedidos enviados via Story 3.1).
|
||||||
|
- **SIG com DDL já aplicado:** idem — `vl_acrescimo` recalculado via `total × tx_acrescimo / 100`.
|
||||||
|
- **SIG sem DDL:** **nenhum impacto** — MD5 não muda; re-sync não dispara; comportamento pré-Story 3.3 preservado. Quando DDL for aplicado, o próximo sync disparará re-sync de todos os pedidos SIG de uma só vez.
|
||||||
|
|
||||||
|
Isso é **aceitável e idempotente**: após o re-sync, o hash estabiliza e os syncs seguintes operam normalmente.
|
||||||
|
|
||||||
|
### Arquivo a modificar
|
||||||
|
|
||||||
|
| Arquivo | Caminho | O que muda |
|
||||||
|
|---------|---------|------------|
|
||||||
|
| `PedidoPGSQL.java` | `src/br/com/jcsinformatica/sarandroid/postgres/` | `selectAllPedConsulta()`: append `|| COALESCE(A.acrev, 0.0)` no MD5 Gerente; append condicional `|| COALESCE(a.tx_acrescimo, 0.0)` no MD5 SIG |
|
||||||
|
|
||||||
|
**NÃO modificar:**
|
||||||
|
- `PedidoConsultaDB.java` — `saveAll` já compara e `insert`/`updateErp` já gravam (Stories 2.3/2.4)
|
||||||
|
- `Pedido.java` — VO completo (Story 2.3)
|
||||||
|
- `DatabaseHelper.java` — schema SQLite não muda; `dbVersao` permanece 43
|
||||||
|
- `hasColunaAcrescimoSig()` — já existe (Story 3.1); reutilizar
|
||||||
|
|
||||||
|
### Regras críticas do projeto (aplicáveis)
|
||||||
|
|
||||||
|
- **Sem Kotlin** — somente Java puro
|
||||||
|
- **Sem Gradle** — projeto Eclipse ADT
|
||||||
|
- **Sem JARs novos** — nenhuma dependência adicional
|
||||||
|
- **Sem testes automatizados** — validação manual via dispositivo/emulador
|
||||||
|
- **SQL por `StringBuilder`** — padrão projeto-wide mantido
|
||||||
|
- **Thread background para I/O** — `selectAllPedConsulta` já roda em `AsyncTask` da `ComunicaActivity`
|
||||||
|
- **`ConnectionManager.closeAll(st, rs)`** — já chamado em [PedidoPGSQL.java:761](../../src/br/com/jcsinformatica/sarandroid/postgres/PedidoPGSQL.java); não alterar
|
||||||
|
|
||||||
|
### Inteligência de histórias anteriores
|
||||||
|
|
||||||
|
- **Story 3.2:** declarou `temAcrescSigConsulta` em [PedidoPGSQL.java:619](../../src/br/com/jcsinformatica/sarandroid/postgres/PedidoPGSQL.java) e populou-o via `hasColunaAcrescimoSig()` em linha 653 — Story 3.3 apenas **lê** essa variável dentro do mesmo método; nenhum novo estado.
|
||||||
|
- **Story 3.1:** criou `hasColunaAcrescimoSig()` (linhas 1196–1212), com log de warning em caso de exceção. Disponível para uso; não alterar.
|
||||||
|
- **Story 2.3:** `Pedido.vlAcrescimo` com getter/setter e `pedido.vl_acrescimo` em SQLite (`DatabaseHelper.onUpgrade` v43 linha 713).
|
||||||
|
- **Story 2.4:** `PedidoConsultaDB.selectFull` lê `vl_acrescimo` (linha 296) — tela de consulta já exibe corretamente após re-sync.
|
||||||
|
- **Padrão de MD5 existente:** o hash Gerente **já incluía `A.total`** enquanto o hash SIG **não incluía `A.total`** — mantenha essa assimetria. Apenas adicionar o novo termo ao final, sem reorganizar a ordem dos demais termos (alterar a ordem invalidaria o hash de pedidos pré-deploy, gerando re-sync em massa desnecessário já capturado pelo novo termo).
|
||||||
|
|
||||||
|
### Verificação manual após implementação
|
||||||
|
|
||||||
|
1. **Gerente — mudança isolada de `acrev` dispara re-sync:**
|
||||||
|
- Selecionar um pedido em `gerente.pedidos` já sincronizado no app (registro existe em `pedido_consulta`)
|
||||||
|
- `UPDATE gerente.pedidos SET acrev = acrev + 10.00 WHERE id_pedido = <X>` direto no PG
|
||||||
|
- Rodar ComunicaActivity
|
||||||
|
- Verificar logcat: `INSERT PEDIDO CONSULTA` ou log de `updateErp` para o pedido; `pedido_consulta.vl_acrescimo` atualizado; `pedido_consulta.md5` atualizado
|
||||||
|
|
||||||
|
2. **Gerente — sync repetido sem alteração não dispara re-sync:**
|
||||||
|
- Rodar ComunicaActivity duas vezes seguidas, sem mudar o PG entre rodadas
|
||||||
|
- Contador `tt[1]` (skip) deve cobrir todos os pedidos na segunda execução
|
||||||
|
|
||||||
|
3. **SIG com DDL aplicado — mudança isolada de `tx_acrescimo`:**
|
||||||
|
- Pedido em `sig.pedidos` com `tx_acrescimo = 2.5`, `total = 1000.00` → app tem `vl_acrescimo = 25.00`
|
||||||
|
- `UPDATE sig.pedidos SET tx_acrescimo = 3.0 WHERE id_pedido = <X>` direto no PG
|
||||||
|
- Rodar ComunicaActivity → `pedido_consulta.vl_acrescimo = 30.00`; hash atualizado
|
||||||
|
|
||||||
|
4. **SIG sem DDL — tolerância total:**
|
||||||
|
- Com `tx_acrescimo` ausente em `sig.pedidos` (ambiente sem DDL)
|
||||||
|
- Rodar ComunicaActivity → nenhum crash; log `SQL PEDIDO CONSULTA` mostra MD5 **sem** o termo `tx_acrescimo`; syncs consecutivos têm contadores `tt[0]/tt[1]/tt[2]` idênticos ao pré-Story 3.3
|
||||||
|
|
||||||
|
5. **Primeiro sync após deploy — re-sync em massa aceitável (AC 3):**
|
||||||
|
- Dispositivo com `pedido_consulta` populado pré-Story 3.3 (`md5` sem termo de acréscimo)
|
||||||
|
- Fazer upgrade do app → rodar ComunicaActivity
|
||||||
|
- Esperado: `tt[2]` (alterados) cobre **todos** os pedidos Gerente e SIG-com-DDL já sincronizados; execução conclui sem erro; syncs subsequentes normalizam para `tt[1]` (skip)
|
||||||
|
|
||||||
|
6. **Log para debug:**
|
||||||
|
- `Log.d("SQL PEDIDO CONSULTA = ", sql.toString())` em [PedidoPGSQL.java:679](../../src/br/com/jcsinformatica/sarandroid/postgres/PedidoPGSQL.java) — conferir presença/ausência do termo no MD5 conforme sistema e DDL
|
||||||
|
|
||||||
|
### Git intelligence (commits recentes)
|
||||||
|
|
||||||
|
- `f2cf45d` Story 2.4: `pedido_consulta.vl_acrescimo` + guard `status >= STATUS_ENVIADO` em consulta — campo já gravado e lido no SQLite
|
||||||
|
- `3ff26a7` Story 2.3: `Pedido.vlAcrescimo` + `PedidoDB`/`PedidoConsultaDB` preparados
|
||||||
|
- Story 3.1 (uncommitted): `PedidoPGSQL.insert/insertSig/save/hasColunaAcrescimoSig` — padrões a seguir
|
||||||
|
- Story 3.2 (uncommitted): `selectAllPedConsulta` leitura inbound + `temAcrescSigConsulta` — variável a reutilizar nesta story
|
||||||
|
|
||||||
|
### References
|
||||||
|
|
||||||
|
- Epics: `_bmad-output/planning-artifacts/epics.md#story-33-incluir-vl_acrescimo-no-md5-de-change-detection`
|
||||||
|
- FR17: MD5 de `pedido_consulta` inclui `vl_acrescimo` no cálculo de hash
|
||||||
|
- FR16: Sync tolera ausência das colunas de acréscimo no PostgreSQL
|
||||||
|
- `PedidoPGSQL.java:619` — declaração `temAcrescSigConsulta`
|
||||||
|
- `PedidoPGSQL.java:628-629` — MD5 Gerente (alvo da Task 1)
|
||||||
|
- `PedidoPGSQL.java:653` — inicialização `temAcrescSigConsulta = hasColunaAcrescimoSig()`
|
||||||
|
- `PedidoPGSQL.java:658-659` — MD5 SIG (alvo da Task 2)
|
||||||
|
- `PedidoPGSQL.java:731` — leitura `ped.setMd5(rs.getString("md5"))`
|
||||||
|
- `PedidoPGSQL.java:1196-1212` — `hasColunaAcrescimoSig()` (não alterar)
|
||||||
|
- `PedidoConsultaDB.java:54-67` — comparação stored vs new MD5 em `saveAll` (não alterar)
|
||||||
|
- `PedidoConsultaDB.java:141` — gravação de `md5` em `insert` (não alterar)
|
||||||
|
- `PedidoConsultaDB.java:177` — gravação de `md5` em `updateErp` (não alterar)
|
||||||
|
- Memory: `project_pg_sync_acrescimo_schema.md` — assimetria Gerente vs SIG confirmada
|
||||||
|
|
||||||
|
## Dev Agent Record
|
||||||
|
|
||||||
|
### Agent Model Used
|
||||||
|
|
||||||
|
claude-opus-4-7 (create-story workflow)
|
||||||
|
|
||||||
|
### Debug Log References
|
||||||
|
|
||||||
|
N/A — projeto sem infraestrutura de testes automatizados; validação manual via dispositivo/emulador conforme roteiro em "Verificação manual após implementação".
|
||||||
|
|
||||||
|
### Completion Notes List
|
||||||
|
|
||||||
|
- Story 3.3 implementada em 2026-04-16.
|
||||||
|
- **Task 1 — Gerente MD5:** [PedidoPGSQL.java:629](../../src/br/com/jcsinformatica/sarandroid/postgres/PedidoPGSQL.java) — linha do MD5 Gerente (após `|| A.total`) agora apenda `|| COALESCE(A.acrev, 0.0)`. Única linha alterada no branch Gerente; sem impacto na projeção de `acrev` (linha 630) já introduzida em Story 3.2. AC 1, 2 e 3 satisfeitos para Gerente: alterações em `acrev` no PG mudam o hash, disparando `updateErp` em `PedidoConsultaDB.saveAll`.
|
||||||
|
- **Task 2 — SIG MD5 condicional:** [PedidoPGSQL.java:658-663](../../src/br/com/jcsinformatica/sarandroid/postgres/PedidoPGSQL.java) — append da linha de MD5 SIG refatorado em três partes. Corpo do hash agora fecha com `COALESCE(C.id_pauta,0)` sem parêntese; `if (temAcrescSigConsulta)` apenda `|| COALESCE(a.tx_acrescimo, 0.0)`; `") as md5,"` fecha o `MD5(...)`. Reutiliza `temAcrescSigConsulta` populado em linha 653 (Story 3.2) — nenhum novo estado.
|
||||||
|
- **AC 4 (tolerância SIG sem DDL) preservado por construção:** quando `temAcrescSigConsulta = false`, o bloco `if` não executa e o SQL resultante é byte-for-byte idêntico ao pré-Story 3.3. Verificado via leitura do arquivo após edição.
|
||||||
|
- **PedidoConsultaDB não modificado** — `saveAll` ([linha 54-67](../../src/br/com/jcsinformatica/sarandroid/database/PedidoConsultaDB.java)) já compara `md5` stored vs retornado e dispara `updateErp`; `insert`/`updateErp` já gravam `md5` e `vl_acrescimo` desde Stories 2.3/2.4.
|
||||||
|
- **`dbVersao` permanece 43** — sem alteração de schema SQLite.
|
||||||
|
- **Sem testes automatizados:** projeto não possui infraestrutura (conforme CLAUDE.md). Validação via logic trace + inspeção do SQL gerado pelo `Log.d("SQL PEDIDO CONSULTA = ", sql.toString())` na linha 679 durante validação manual (roteiro em "Verificação manual após implementação" no corpo da story).
|
||||||
|
- **Nenhum import adicionado** — todas as classes e métodos necessários já estão no arquivo.
|
||||||
|
- AC 1 satisfeito: hash Gerente inclui `acrev`; hash SIG inclui `tx_acrescimo` quando coluna existe → mudanças isoladas disparam `updateErp`.
|
||||||
|
- AC 2 satisfeito: fórmula MD5 determinística; hash estável entre syncs quando conteúdo não muda.
|
||||||
|
- AC 3 satisfeito: primeiro sync pós-deploy marcará todos pedidos Gerente/SIG-com-DDL como alterados (hash novo ≠ hash stored); `updateErp` grava `vl_acrescimo = 0.0` para pedidos pré-Epic 3 (acrev/tx_acrescimo default `0`). Idempotente.
|
||||||
|
- AC 4 satisfeito: `temAcrescSigConsulta = false` → SQL SIG idêntico ao pré-Story 3.3; nenhum impacto em ambientes sem DDL.
|
||||||
|
|
||||||
|
### File List
|
||||||
|
|
||||||
|
- `src/br/com/jcsinformatica/sarandroid/postgres/PedidoPGSQL.java`
|
||||||
|
|
||||||
|
### Review Findings
|
||||||
|
|
||||||
|
- [x] [Review][Defer] Ramo Gerente adiciona `A.acrev` ao MD5 **sem guard** equivalente a `hasColunaAcrescimoSig()` [PedidoPGSQL.java:629] — deferred, premissa verificada: Story 3.1 confirmou via inspeção de código que `gerente.pedidos.acrev` existe desde antes do Epic 3 (estava na lista de colunas do INSERT sendo gravada como 0); não é cenário hipotético. Adicionar `hasColunaAcrescimoGerente()` seria over-engineering contra condição que não pode ocorrer no ambiente real.
|
||||||
|
- [x] [Review][Defer] Colisão teórica por concatenação MD5 sem delimitador entre `A.total` e `COALESCE(A.acrev, 0.0)` [PedidoPGSQL.java:629] — deferred, risco pré-existente e teórico: padrão de concatenação sem separador já existia em todo o MD5 Gerente/SIG original (`num_ped_sar || id_pedido`, etc.). Adicionar delimitador só ao novo termo seria inconsistente; refatorar o MD5 inteiro para usar separador é mudança arquitetural fora do escopo de Story 3.3 e forçaria segundo re-sync em massa.
|
||||||
|
- [x] [Review][Defer] Hash pode variar entre versões do PostgreSQL por mudanças em `extra_float_digits` / `float4out` (formatação textual de `REAL`) [PedidoPGSQL.java:629,661] — deferred, risco pré-existente: `A.total` (REAL) já estava no MD5 Gerente com a mesma exposição; mitigação real seria `to_char(..., 'FM999999990.00')` em TODOS os termos numéricos do hash — refatoração fora do escopo. Upgrade/mudança de config do PG já impactaria sync atual.
|
||||||
|
- [x] [Review][Defer] `pedido_consulta.md5` TEXT sem NOT NULL → `setMd5(null)` geraria literal `"'null'"` e loop de update infinito — deferred, pré-existente: caminho requer falha do `MD5()` server-side (extremamente improvável); schema definido em Story 2.3 não por esta.
|
||||||
|
- [x] [Review][Dismiss] `Pedido.vlAcrescimo` potencialmente `Double` boxed (NPE em SIG sem DDL) — falso positivo: Story 2.3 Dev Notes e Story 3.1 linha 50 confirmam primitivo `double` com default `0.0`.
|
||||||
|
- [x] [Review][Dismiss] Alias case `a.tx_acrescimo` vs `A.` maiúsculo — segue exatamente o padrão já estabelecido em Story 3.2 [PedidoPGSQL.java:662] na projeção do mesmo campo; PG é case-insensitive em identificadores não-quoted.
|
||||||
|
- [x] [Review][Dismiss] Cast de `0.0` (double) vs `acrev` (REAL) produzindo texto diferente — verificado: `gerente.pedidos.acrev` é REAL (confirmado por `setDouble` em Story 3.1 linha 183); mesmo tipo em ambos os lados do `COALESCE`.
|
||||||
|
- [x] [Review][Dismiss] Comentário `// 13-16` em SIG não atualizado — correto por design: `tx_acrescimo` é coluna condicional (aparece apenas quando `temAcrescSigConsulta=true`), mesmo padrão já usado em Story 3.2 linha 666 (`//17` dentro do `if`).
|
||||||
|
- [x] [Review][Dismiss] Filtro `AND A.total>0` mascara pedidos com total=0 e acrev>0 — pré-existente, fora de escopo; cenário semanticamente inconsistente (acréscimo sobre zero).
|
||||||
|
|
||||||
|
### Change Log
|
||||||
|
|
||||||
|
- 2026-04-16: Story 3.3 criada pelo create-story workflow.
|
||||||
|
- 2026-04-16: Story 3.3 implementada. `PedidoPGSQL.selectAllPedConsulta()`: MD5 Gerente agora inclui `|| COALESCE(A.acrev, 0.0)` (sempre); MD5 SIG inclui condicionalmente `|| COALESCE(a.tx_acrescimo, 0.0)` via `if (temAcrescSigConsulta)` — reutilizando a variável já populada em Story 3.2 sem duplicar a query `information_schema`. Completa FR17 do Epic 3: mudanças isoladas de acréscimo no ERP agora disparam re-sync do `pedido_consulta` local. Status → review.
|
||||||
|
- 2026-04-16: Code review executado (Blind Hunter + Edge Case Hunter + Acceptance Auditor). Acceptance Auditor aprovou. 4 achados classificados como defer (todos pré-existentes ou premissas documentadas); 5 dismisses. Nenhum patch necessário. Status → done.
|
||||||
82
_bmad-output/implementation-artifacts/deferred-work.md
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
# Deferred Work
|
||||||
|
|
||||||
|
## Deferred from: code review de 3-2-ler-acrescimo-do-postgresql-no-sync-de-consulta (2026-04-16)
|
||||||
|
|
||||||
|
- **`hasColunaAcrescimoSig()` chamado duas vezes por sessão de sync** em `PedidoPGSQL.save()` (insert path, Story 3.1) e em `PedidoPGSQL.selectAllPedConsulta()` (consult read path, Story 3.2) — durante janela de DDL migration, os dois `boolean` podem divergir, causando insert com `tx_acrescimo` + read sem (ou vice-versa). Em produção, DDL deve ocorrer fora de janelas de sync. Mitigação: elevar o boolean para campo de instância ou parâmetro compartilhado de sessão se a migração ao vivo se tornar necessária.
|
||||||
|
|
||||||
|
## Deferred from: code review de 3-3-incluir-vl-acrescimo-no-md5-de-change-detection (2026-04-16)
|
||||||
|
|
||||||
|
- **Ramo Gerente sem guard `hasColunaAcrescimoGerente()` para `acrev`** em `PedidoPGSQL.selectAllPedConsulta()` linha 629 — premissa verificada em Story 3.1: coluna `acrev` existe em `gerente.pedidos` desde antes do Epic 3 (estava no INSERT sendo gravada como 0). Adicionar guard seria over-engineering; se aparecer instalação Gerente legada hipotética, considerar simetria com SIG.
|
||||||
|
- **Colisão teórica por concatenação MD5 sem delimitador** entre `A.total` e `COALESCE(A.acrev, 0.0)` (e entre outros termos numéricos adjacentes já presentes no hash) em `PedidoPGSQL.selectAllPedConsulta()` — risco pré-existente em todo o MD5 Gerente/SIG. Mitigação seria refatorar o hash inteiro para usar separador (ex: `|| '|' ||`), forçando novo re-sync em massa e sendo inconsistente se aplicado só ao novo termo. Avaliar em refatoração maior do sync de consulta.
|
||||||
|
- **Hash pode variar entre versões do PostgreSQL** por mudanças em `extra_float_digits` / `float4out` afetando formatação textual de `REAL` em concatenações `|| A.total || COALESCE(A.acrev, 0.0)` — risco pré-existente (`A.total` já sofria a mesma exposição). Mitigação: usar `to_char(campo, 'FM999999990.00')` em todos os termos numéricos do hash; upgrade do PG pode causar re-sync em massa silencioso.
|
||||||
|
- **`pedido_consulta.md5` TEXT sem NOT NULL** em `DatabaseHelper` → `ped.setMd5(null)` gera literal `"'null'"` no INSERT e loop de update infinito — pré-existente; caminho requer falha do `MD5()` server-side (improvável). Adicionar guard em `PedidoConsultaDB.insert/updateErp` ou `NOT NULL` na coluna em migração futura.
|
||||||
|
|
||||||
|
## Deferred from: code review de 3-1-enviar-acrescimo-ao-postgresql-ao-salvar-pedido (2026-04-16)
|
||||||
|
|
||||||
|
- **`ped.getFormapag()` sem null-check antes de `getTxAcrescimo()`** em `PedidoPGSQL.insert()` linha 182 e `insertSig()` linha 425 — pré-existente: a mesma ausência de null-check existe nas linhas 160 e 387 que vêm antes; se formapag fosse null, crasharia antes das novas chamadas.
|
||||||
|
- **Schema `'sig'` hardcoded como string literal** em `hasColunaAcrescimoSig()` — pré-existente projeto-wide; toda a classe usa literais `'sig'` e `'gerente'` sem constante.
|
||||||
|
- **Valores monetários com `setDouble`** para `acrep`/`acrev` — pré-existente projeto-wide; todos os campos monetários usam `setDouble`.
|
||||||
|
- **`codVend2 = null` pode causar NullPointerException** em `st.setInt(36, codVend2)` se `sig.corrent` não tiver registro para o cliente — bug pré-existente em `insertSig()`.
|
||||||
|
- **`information_schema.columns` sem filtro `table_catalog`** — baixo risco prático em JDBC por-banco; consistente com `schemaTes()`.
|
||||||
|
- **`information_schema.columns` mais lento que `pg_attribute`** — consistente com padrão de `schemaTes()` na mesma classe; otimizar se latência se tornar problema.
|
||||||
|
- **`keys.close()`/`st.close()` não garantidos se loop de itens lança exceção** em `insertSig()` — pré-existente; conexão fecha ao final da sessão.
|
||||||
|
- **Comentário morto `//int ultimoNumeroPedido = 0;`** em `save()` — pré-existente.
|
||||||
|
|
||||||
|
## Deferred from: code review de 2-4-exibir-acrescimo-na-consulta-de-pedidos (2026-04-16)
|
||||||
|
|
||||||
|
- **PedidoPGSQL não inclui `vl_acrescimo` na sync com PostgreSQL** — nem no envio local→PG nem no retorno PG→`pedido_consulta`; pedidos recém-sincronizados via PG terão `vl_acrescimo = 0` em `pedido_consulta` até que o sync PG do acréscimo seja implementado em story futura; já deferred em Story 2.3.
|
||||||
|
- **MD5 de `pedido_consulta` não inclui `vl_acrescimo`** em change-detection — mudanças isoladas de acréscimo não disparam re-sync; reavaliar junto com sync PG; já deferred em Story 2.3.
|
||||||
|
- **`total` é recalculado de itens em tempo real enquanto `vl_acrescimo` é congelado** quando `status >= STATUS_ENVIADO` — mesmo padrão que `descontoV` estabelecido em Story 2.3; risco teórico de inconsistência se itens forem reprecificados após envio, mas cenário improvável (itens de pedido enviado geralmente não mudam localmente).
|
||||||
|
- **Índice 66 hard-coded em `PedidoConsultaDB.selectFull()`** — lista de índices posicionais cresceu para 43–66; migrar para `c.getColumnIndexOrThrow("vl_acrescimo")` em refatoração futura da camada DB.
|
||||||
|
- **SQL por concatenação de strings em `PedidoConsultaDB.insert/updateErp`** — padrão projeto-wide pré-existente; migrar para `PreparedStatement`/bind params em refatoração maior.
|
||||||
|
- **Lógica duplicada de cálculo+guard de acréscimo** em `MainPedidoFragment.fillFields`, `MainPedidoFragment.atualizarResumoPedido` e `TotalPedidoFragment.FillFields` — três cópias da mesma lógica; extrair para helper em refatoração futura; já deferred em Story 2.2.
|
||||||
|
- **Ausência de `onDowngrade()` em `DatabaseHelper`** — padrão projeto-wide pré-existente; instalação de APK mais antigo após v43 crashará com `SQLiteException`; implementar política de downgrade (drop+recreate ou reject) em refatoração futura.
|
||||||
|
|
||||||
|
## Deferred from: code review de 2-3-persistir-acrescimo-ao-fechar-o-pedido (2026-04-16)
|
||||||
|
|
||||||
|
- **PedidoPGSQL não inclui `vl_acrescimo`** — sync para PostgreSQL não transmite o acréscimo calculado; escopo de story futura de sincronização; reavaliar quando PedidoPGSQL for atualizado para enviar pedidos com acréscimo.
|
||||||
|
- **`fillFields()` recalcula `vlAcrescimo` com taxa atual ao abrir pedido existente** — se o usuário abrir e re-salvar um pedido sem alterações, o `vl_acrescimo` é recalculado com o `tx_acrescimo` vigente, potencialmente perdendo o valor histórico. By-design para esta história; AC2 cobre apenas sync automático.
|
||||||
|
- **MD5 do pedido não inclui `vl_acrescimo`** — change-detection para sync pode não detectar pedidos cujo único campo alterado é o acréscimo; reavaliar quando PedidoPGSQL for atualizado.
|
||||||
|
- **Índice 59 do cursor lê `DATE(D.data_inicio)` em vez de `C.md5`** — bug pré-existente em `PedidoDB.selectAllFull()` e `selectFull()` que preenche `FormaPagamento.md5` com um valor de data incorreto; não introduzido por esta story.
|
||||||
|
- **SQL building via string concatenation** — toda a camada `PedidoDB` usa `StringBuilder` em vez de `PreparedStatement`; risco de SQL injection se campos `String` forem concatenados no futuro; padrão projeto-wide.
|
||||||
|
- **`Global.pedido` sem null-check antes de `setVlAcrescimo()`** — padrão pré-existente em `MainPedidoFragment`; mesmo risco de NPE existente em todos os outros acessos a `Global.pedido`.
|
||||||
|
- **Aritmética de ponto flutuante para valor monetário** — `vl_acrescimo` usa `double` / `REAL`, padrão projeto-wide; arredondamento gerenciado por `Util.formataValorMonetario()`.
|
||||||
|
|
||||||
|
|
||||||
|
## Deferred from: code review de 1-1-migracao-do-schema-sqlite-para-suporte-a-acrescimo (2026-04-16)
|
||||||
|
|
||||||
|
- `FormaPagamentoPGSQL` não lê `acresc` do PostgreSQL — `tx_acrescimo` sempre sincronizará como 0.0 até Story 1.2 ser implementada. Esperado — escopo da Story 1.2.
|
||||||
|
- `ClienteDB` join em `formapag` não lê `tx_acrescimo` — `FormaPagamento` carregado via consulta de cliente terá `txAcrescimo=0.0`. Considerar ao implementar Story 2 (cálculo do acréscimo no pedido): garantir que o cálculo use `FormaPagamentoDB.selectId()` ou equivalente, não o VO hidratado via ClienteDB.
|
||||||
|
- `PedidoDB` join em `formapag` não inclui `tx_acrescimo` — formapag hidratado em contexto de pedido/consulta terá `txAcrescimo=0.0`. Sem impacto em Story 1.x; monitorar em Story 2.
|
||||||
|
- `selectIdErp` com lista de colunas incompleta (falta `desco_perc` e `tx_acrescimo`) — bug pré-existente em `FormaPagamentoDB.java:194`. O método só verifica existência, sem impacto funcional. Corrigir quando refatorar a classe.
|
||||||
|
- Padrão de SQL por concatenação de strings (SQL injection risk) em `FormaPagamentoDB.java` — risco pré-existente em toda a classe. Migrar para `db.execSQL()` com `?` placeholders quando houver refatoração maior.
|
||||||
|
|
||||||
|
## Deferred from: code review de 2-1-exibir-campo-de-acrescimo-na-tela-do-pedido (2026-04-16)
|
||||||
|
|
||||||
|
- `layout_weight="1"` com `layout_width="wrap_content"` nos layouts de pedido — tecnicamente `0dp` é o correto para distribuição de peso em LinearLayout/TableRow, mas padrão pré-existente em todos os rows do projeto. Considerar corrigir em refatoração de layout futura.
|
||||||
|
- `atualizarResumoPedido()` em `MainPedidoFragment` não possui null-guard nem try/catch — `tvAcrescimoPedido` (e os demais campos `tvQtdTotal`, `tvTotalGeral`) podem lançar NPE se chamado antes de `onCreateView()` ou após `Global.pedido = null`. Risco pré-existente; adicionar try/catch em refatoração futura.
|
||||||
|
- `setUserVisibleHint()` em `TotalPedidoFragment` pode disparar `FillFields()` antes de `onCreateView()` — todos os TextViews (incluindo `tvAcrescimo`) seriam null; NPE silenciosamente engolido pelo `catch(Exception e){}` sem log. Pré-existente; adicionar null-check no início de `FillFields()`.
|
||||||
|
- Strings hardcoded no XML dos layouts de pedido (`"Acréscimo"`, `"Acréscimo (+)"`, etc.) em vez de `@string/` resources — padrão pré-existente em todo o projeto; migrar para `strings.xml` em refatoração de internacionalização futura.
|
||||||
|
- `new UpdatePedItemActivity().precoComIpi()` instancia Activity fora do ciclo de vida Android — anti-padrão pré-existente em `MainPedidoFragment` e `TotalPedidoFragment`; extrair para método estático ou utilitário em refatoração futura.
|
||||||
|
|
||||||
|
## Deferred from: code review de 2-2-calcular-acrescimo-ao-selecionar-forma-de-pagamento (2026-04-16)
|
||||||
|
|
||||||
|
- `txAcrescimo` negativo não guardado no cálculo do acréscimo — banco de dados do ERP possui checagem para valores negativos; guard `Math.max(0.0, txAcrescimo)` desnecessário.
|
||||||
|
- `Global.pedido` sem null-check em `atualizarResumoPedido()` — método já acessa `Global.pedido.*` sem guard; padrão pré-existente [MainPedidoFragment.java:958].
|
||||||
|
- `Global.pedido` sem null-check em `TotalPedidoFragment.FillFields()` — NPE seria silenciosa dentro do `catch(Exception e){}` vazio [TotalPedidoFragment.java:74].
|
||||||
|
- Lógica de cálculo do acréscimo duplicada em `fillFields()` e `atualizarResumoPedido()` sem método auxiliar — padrão DRY violado pré-existente no projeto.
|
||||||
|
- `catch (Exception e) {}` vazio em `TotalPedidoFragment.FillFields()` engole exceções sem log — pré-existente.
|
||||||
|
- Aritmética de ponto flutuante (`double`) para valores monetários — padrão projeto-wide; `Util.formataValorMonetario()` já trata formatação.
|
||||||
|
- `codigoLiberacao2()` computa hash de autorização sem acréscimo — supervisor autoriza valor menor que o real quando `txAcrescimo > 0`; endereçar em Story 2.3 ao persistir o total com acréscimo.
|
||||||
|
- `validaCampos()` verificações de limite de crédito e `vlPedMin` usam `getTotalProduto()` excluindo acréscimo — pré-existente, avaliar em Story 2.3.
|
||||||
|
- Race condition: `listForPagtos` preenchido em background thread vs UI thread — pré-existente [MainPedidoFragment.java:193].
|
||||||
|
- Inconsistência de threshold em `fillFields()` (≤ STATUS_LIBERADO) vs `atualizarResumoPedido()` (< STATUS_ENVIADO) para `descontoV` — pré-existente.
|
||||||
|
- `FormaPagamento` em `Global.pedido` pode estar stale após sync — `txAcrescimo` reflete taxa pré-sync até usuário navegar da tela.
|
||||||
|
|
||||||
|
## Deferred from: code review de 1-2-sincronizacao-da-taxa-de-acrescimo-do-erp (2026-04-16)
|
||||||
|
|
||||||
|
- Resource leak: `PreparedStatement` e `ResultSet` em `FormaPagamentoPGSQL.executaSelectAll()` não são fechados em bloco `finally` — se uma exceção ocorrer mid-iteration, os recursos não são liberados. Pre-existente na classe; corrigir com try-with-resources ou bloco `finally` em refatoração futura.
|
||||||
|
- SQL injection via concatenação de strings no WHERE em `FormaPagamentoPGSQL` — `id_empresa` e `dt_atual` interpolados diretamente. Pre-existente; mesma origem que o item acima de `FormaPagamentoDB`.
|
||||||
|
- `coalesce(libera_credito)` sem argumento padrão no SELECT do PostgreSQL — `COALESCE` com argumento único é no-op e não protege contra NULL. Pre-existente; usar `coalesce(libera_credito, 0)` em refatoração futura.
|
||||||
|
- `setDescontoPerc(rs.getInt(6))` lê coluna decimal como inteiro, truncando frações — pre-existente; usar `rs.getDouble()` em refatoração futura.
|
||||||
|
- Chave mal colocada em `AtualizaDados.atualizaFormaPag()`: bloco `if (ultAtualizacao == null)` tem `{` após a declaração mas antes de `inativaAll`, fazendo `inativaAll` executar sempre. Pre-existente; verificar comportamento real em testes manuais e corrigir.
|
||||||
142
_bmad-output/implementation-artifacts/epic-2-retro-2026-04-16.md
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
# Retrospectiva — Epic 2: Acréscimo Financeiro no Pedido
|
||||||
|
|
||||||
|
**Data:** 2026-04-16
|
||||||
|
**Projeto:** SARandroid
|
||||||
|
**Epic:** 2 — Acréscimo Financeiro no Pedido
|
||||||
|
**Facilitador:** Julio (Project Lead)
|
||||||
|
**Primeiro retro do projeto** — sem retrospectiva anterior para comparar follow-through.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Resumo do Epic
|
||||||
|
|
||||||
|
| Métrica | Valor |
|
||||||
|
|---|---|
|
||||||
|
| Stories planejadas | 4 |
|
||||||
|
| Stories concluídas | 4 (100%) |
|
||||||
|
| FRs cobertos | FR5–FR13 (9 requisitos funcionais) |
|
||||||
|
| Code reviews aprovados | 4/4 |
|
||||||
|
| Migrações SQLite | v41→v42 (story 2.3), v42→v43 (story 2.4) |
|
||||||
|
| Arquivos de código alterados | 6 únicos (DatabaseHelper, PedidoDB, PedidoConsultaDB, Pedido VO, MainPedidoFragment, TotalPedidoFragment) |
|
||||||
|
| Incidentes em produção | 0 (feature ainda não publicada) |
|
||||||
|
| Débitos técnicos formalmente deferred | 19 itens acumulados em `deferred-work.md` |
|
||||||
|
|
||||||
|
### Stories entregues
|
||||||
|
|
||||||
|
- **2.1** Exibir campo Acréscimo na tela do pedido (sempre R$ 0,00)
|
||||||
|
- **2.2** Calcular acréscimo ao selecionar forma de pagamento
|
||||||
|
- **2.3** Persistir acréscimo ao fechar o pedido
|
||||||
|
- **2.4** Exibir acréscimo na consulta de pedidos
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. O Que Foi Bem
|
||||||
|
|
||||||
|
- **Padrão de migração SQLite robusto**: cada story de migração (1.1, 2.3, 2.4) atualizou `dbVersao` + bloco `onUpgrade` + `onCreate` simultaneamente. Esse padrão triplo evitou a regressão clássica "instalação nova crasha porque só o upgrade foi atualizado" — explicitamente documentado em Story 2.4.
|
||||||
|
- **Escopo por story bem isolado**: cada history entregou 1 responsabilidade clara (UI → cálculo → persistência → consulta). Nada de stories-monstro que misturam camadas.
|
||||||
|
- **Review adversarial tripla (Blind + EdgeCase + Auditor) consistentemente filtrou ruído de sinal**: vários falsos positivos (ex: locale em `Double.toString`) foram corretamente classificados como dismiss, e os riscos reais (sync PG ausente, MD5 sem `vl_acrescimo`) foram deferred com justificativa.
|
||||||
|
- **Tolerância a dados ausentes do ERP**: padrão estabelecido em Story 1.2 (`acresc` ausente no PG) foi replicado em Story 2.4 (`vl_acrescimo = 0.0` default na sync). Mantém retro-compatibilidade com schemas antigos.
|
||||||
|
- **Separação `pedido` (editável) vs `pedido_consulta` (histórico)**: Story 2.4 identificou que o guard `status >= STATUS_ENVIADO` nos dois fragments (Main + Total) era necessário para preservar o valor histórico — evitou o bug sutil de "recalcular acréscimo de pedido enviado usando `tx_acrescimo` atual".
|
||||||
|
- **Documentação de verificação manual**: cada story incluiu roteiro de teste manual passo-a-passo (com SQL de inspeção, cenários de edge case). Mitigação razoável da ausência de testes automatizados no projeto.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. O Que Não Foi Bem / Desafios
|
||||||
|
|
||||||
|
- **Esquecimento inicial: Story 2.3 só migrou a tabela `pedido`, não `pedido_consulta`.** O erro foi parcialmente sistemático — o padrão dual-DB do projeto (`*DB.java` + `*ConsultaDB.java`) exige que features que afetam ambas as tabelas migrem ambas. Story 2.4 teve que corrigir isso via v43. **Lição:** ao planejar features que afetam pedidos históricos, sempre verificar o par `pedido` + `pedido_consulta` desde a primeira story.
|
||||||
|
- **Sync PG do `vl_acrescimo` permanece deferred (end-to-end incompleto).** Das Jornadas do PRD, a Jornada 3 (consulta de pedido histórico com acréscimo) não está 100% funcional: pedidos que vêm do ERP via sync sempre terão `vl_acrescimo = 0` em `pedido_consulta` até que `PedidoPGSQL` seja atualizado. Isso é documentado mas a feature ainda NÃO está production-ready sem esse incremento.
|
||||||
|
- **Lógica de cálculo de acréscimo duplicada em 3 pontos** (`MainPedidoFragment.fillFields`, `MainPedidoFragment.atualizarResumoPedido`, `TotalPedidoFragment.FillFields`): flagged em review da 2.2, repetido em 2.3 e 2.4. Cada story deferred a extração de helper. A duplicação cresceu (agora são 3 cópias com o mesmo guard de status).
|
||||||
|
- **Débitos técnicos pré-existentes perpetuados**: SQL por concatenação de string, cursor por índice posicional, `Global.pedido` sem null-check, `UpdatePedItemActivity` instanciada fora do lifecycle — cada review flagged, cada story deferred. Padrão: débito não-introduzido-por-esta-story nunca é resolvido nesta-story.
|
||||||
|
- **Epic 1 sem retrospectiva**: `epic-1-retrospective` ficou `optional` e foi pulado. Lições da Epic 1 (ex: verificar schema PG antes de implementar sync) só foram capturadas informalmente nos arquivos `deferred-work.md`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Principais Insights
|
||||||
|
|
||||||
|
1. **Features que afetam pedidos precisam mapear os 2 fluxos desde o planejamento.** Existem dois caminhos de leitura do pedido (`BrowsePedido` → `PedidoDB` para editáveis; `BrowsePedidoConsulta` → `PedidoConsultaDB` para históricos) e dois caminhos de escrita (save local + sync PG). Esquecer qualquer um deles cria trabalho retroativo.
|
||||||
|
|
||||||
|
2. **Guard por `status`** é o padrão idiomático do projeto para separar "valor computado em tempo real" de "valor congelado histórico". Já existia para `descontoV`; agora existe para `vl_acrescimo`. Candidato claro a extrair um helper compartilhado.
|
||||||
|
|
||||||
|
3. **Tolerância "campo novo = 0" na sync PG** é o padrão canônico: ao adicionar coluna no SQLite antes do PG, o read-path trata valor ausente como 0 (não erro). Assim o app é forward-compatible quando o PG recebe a coluna.
|
||||||
|
|
||||||
|
4. **Débitos técnicos pré-existentes viram "invariantes silenciosas"**. A cada review eles reaparecem, a cada story são deferred. Para quebrar o ciclo, dedicar uma story explícita de refactor (ou um epic de "technical-debt burndown") é o único caminho — enxertar em stories funcionais é rejeitado corretamente como scope creep.
|
||||||
|
|
||||||
|
5. **Review adversarial em 3 camadas teve custo/benefício alto.** Mesmo com muitos findings falsos positivos, o Auditor aprovou todos os ACs sem violações e o EdgeCase identificou corretamente os 2 riscos reais (sync PG e MD5). Manteria a prática para features de complexidade similar.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Action Items
|
||||||
|
|
||||||
|
### Débitos técnicos prioritários (carry-forward)
|
||||||
|
|
||||||
|
| # | Item | Dono | Prioridade | Notas |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| A1 | **Sync PG do `vl_acrescimo`**: atualizar `PedidoPGSQL.insert` (envio) e `selectAllPedConsulta` (retorno). Schema de destino varia por sistema: **Gerente** grava em `gerente.pedidos.acrep` (% taxa) + `gerente.pedidos.acrev` (valor). **SIG** grava em `sig.pedidos.tx_acrescimo` (só a taxa — valor é derivado server-side). **Sem isso, Epic 2 não está production-ready.** | Julio | Alta | Criar como Epic 3 ou Story 3.1 em módulo novo |
|
||||||
|
| A2 | Incluir `vl_acrescimo` no MD5 de change-detection de pedido/pedido_consulta | Julio | Média | Depende de A1 |
|
||||||
|
| A3 | Extrair helper de cálculo de acréscimo compartilhado entre `MainPedidoFragment.fillFields`, `MainPedidoFragment.atualizarResumoPedido` e `TotalPedidoFragment.FillFields` | Julio | Média | Debt burndown |
|
||||||
|
| A4 | Null-check global em acessos a `Global.pedido` (padrão defensivo) | Julio | Baixa | Afeta vários fragments; pacote técnico |
|
||||||
|
| A5 | Migrar leitura de cursor para `getColumnIndexOrThrow()` em `PedidoDB.selectAllFull` e `PedidoConsultaDB.selectFull` | Julio | Baixa | Reduz risco quando JOINs mudam |
|
||||||
|
|
||||||
|
### Processo
|
||||||
|
|
||||||
|
| # | Item | Dono |
|
||||||
|
|---|---|---|
|
||||||
|
| P1 | Ao planejar feature que toca pedido, adicionar checklist: "afeta `pedido`, `pedido_consulta`, ambos, ou só PG?" — incorporar no template de story | Julio |
|
||||||
|
| P2 | Rodar retrospectiva de Epic 1 retroativamente (lições de migração SQLite, sync PG com coluna ausente, FormaPagamentoDB patterns) ou explicitamente documentar que Epic 1 foi skipped | Julio |
|
||||||
|
| P3 | Considerar transicionar `epic-1` e `epic-2` de `in-progress` para `done` no `sprint-status.yaml` agora que todas as stories estão done (ação imediata, feita nesta retro) | Julio |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Próximo Epic — Preparação
|
||||||
|
|
||||||
|
**Status:** Epics 1 e 2 são os únicos definidos em `epics.md`. **Não há Epic 3 planejado**.
|
||||||
|
|
||||||
|
### Feature completa?
|
||||||
|
|
||||||
|
Funcionalmente:
|
||||||
|
- Ciclo offline-first (criar pedido com acréscimo, salvar, ver total correto) — ✅ completo
|
||||||
|
- Consulta histórica de pedido local com acréscimo — ✅ completo (via `BrowsePedido` / `PedidoDB`)
|
||||||
|
- Consulta histórica de pedido sincronizado do ERP com acréscimo — ⚠️ **INCOMPLETO** (depende de A1)
|
||||||
|
|
||||||
|
### Pré-requisitos para fechar a feature em produção
|
||||||
|
|
||||||
|
1. **Implementar A1 (sync PG do `vl_acrescimo`)** antes de considerar a feature production-ready.
|
||||||
|
2. Validação manual em dispositivo real com ERP conectado: criar pedido com acréscimo, sincronizar, reabrir via `BrowsePedidoConsulta`, confirmar que o valor histórico aparece corretamente.
|
||||||
|
3. Treinamento/comunicação com representantes sobre o novo campo "Acréscimo" na tela do pedido.
|
||||||
|
|
||||||
|
### Recomendação
|
||||||
|
|
||||||
|
Criar **novo epic** ("Epic 3: Sync Bidirecional de Acréscimo PG↔SQLite") antes de declarar a feature encerrada. Escopo mínimo:
|
||||||
|
|
||||||
|
- **Gerente** (`Global.SISTEMA_GERENTE`):
|
||||||
|
- DDL server-side: adicionar `acrep REAL DEFAULT 0` e `acrev REAL DEFAULT 0` em `gerente.pedidos`
|
||||||
|
- `PedidoPGSQL.insert` (branch Gerente, linha ~129): enviar `ped.getFormapag().getTxAcrescimo()` → `acrep` e `ped.getVlAcrescimo()` → `acrev`
|
||||||
|
- `selectAllPedConsulta` (branch Gerente): ler `acrep`+`acrev`, popular `Pedido.vlAcrescimo`
|
||||||
|
|
||||||
|
- **SIG** (`Global.SISTEMA_SIG`):
|
||||||
|
- DDL server-side: adicionar `tx_acrescimo REAL DEFAULT 0` em `sig.pedidos`
|
||||||
|
- `PedidoPGSQL.insert` (branch SIG, linha ~342): enviar `ped.getFormapag().getTxAcrescimo()` → `tx_acrescimo` (só a taxa — valor é calculado server-side)
|
||||||
|
- `selectAllPedConsulta` (branch SIG): ler `tx_acrescimo` e calcular `vlAcrescimo = total × tx/100` para popular o VO
|
||||||
|
|
||||||
|
- Tolerar ausência das colunas no PG durante migração (padrão Story 1.2)
|
||||||
|
- Incluir os campos no MD5 de change-detection (item A2)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Readiness Assessment
|
||||||
|
|
||||||
|
| Dimensão | Status | Observações |
|
||||||
|
|---|---|---|
|
||||||
|
| Testing & Quality | ⚠️ Parcial | Sem suite automatizada (limitação do projeto Eclipse ADT). Roteiros manuais documentados em cada story. Recomendado: validação em dispositivo real antes de publicar APK. |
|
||||||
|
| Deployment | 🔴 Não publicado | APK ainda não gerado/publicado. Aguardando A1 (sync PG). |
|
||||||
|
| Aceitação de stakeholder | 🔴 Pendente | Stakeholders (representantes de vendas, escritório) ainda não viram a feature rodando. |
|
||||||
|
| Saúde técnica | ✅ Sólida | Migrações idempotentes, guards de status consistentes, nenhuma regressão introduzida. |
|
||||||
|
| Bloqueadores carry-forward | ⚠️ Sim | A1 (sync PG) bloqueia a Jornada 3 end-to-end. Feature não é production-ready sem ele. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Conclusão
|
||||||
|
|
||||||
|
Epic 2 entregou o escopo planejado com qualidade consistente em todas as 4 stories. Os débitos técnicos acumulados são previsíveis (padrões pré-existentes do projeto brownfield) e estão documentados. **A feature NÃO está 100% end-to-end funcional** até que a sync PG do `vl_acrescimo` seja implementada (item A1).
|
||||||
|
|
||||||
|
**Lição principal para próximos epics:** features que afetam o modelo de pedido precisam planejar os 4 caminhos (read/write × local/PG) desde a primeira story, em vez de descobrir lacunas no final.
|
||||||
64
_bmad-output/implementation-artifacts/sprint-status.yaml
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
# generated: 2026-04-16
|
||||||
|
# last_updated: 2026-04-16
|
||||||
|
# project: SARandroid
|
||||||
|
# project_key: NOKEY
|
||||||
|
# tracking_system: file-system
|
||||||
|
# story_location: _bmad-output/implementation-artifacts
|
||||||
|
|
||||||
|
# STATUS DEFINITIONS:
|
||||||
|
# ==================
|
||||||
|
# Epic Status:
|
||||||
|
# - backlog: Epic not yet started
|
||||||
|
# - in-progress: Epic actively being worked on
|
||||||
|
# - done: All stories in epic completed
|
||||||
|
#
|
||||||
|
# Epic Status Transitions:
|
||||||
|
# - backlog → in-progress: Automatically when first story is created (via create-story)
|
||||||
|
# - in-progress → done: Manually when all stories reach 'done' status
|
||||||
|
#
|
||||||
|
# Story Status:
|
||||||
|
# - backlog: Story only exists in epic file
|
||||||
|
# - ready-for-dev: Story file created in stories folder
|
||||||
|
# - in-progress: Developer actively working on implementation
|
||||||
|
# - review: Ready for code review (via Dev's code-review workflow)
|
||||||
|
# - done: Story completed
|
||||||
|
#
|
||||||
|
# Retrospective Status:
|
||||||
|
# - optional: Can be completed but not required
|
||||||
|
# - done: Retrospective has been completed
|
||||||
|
#
|
||||||
|
# WORKFLOW NOTES:
|
||||||
|
# ===============
|
||||||
|
# - Epic transitions to 'in-progress' automatically when first story is created
|
||||||
|
# - Stories can be worked in parallel if team capacity allows
|
||||||
|
# - Developer typically creates next story after previous one is 'done' to incorporate learnings
|
||||||
|
# - Dev moves story to 'review', then runs code-review (fresh context, different LLM recommended)
|
||||||
|
|
||||||
|
generated: 2026-04-16
|
||||||
|
last_updated: 2026-04-16 # Epic 3 done — todas as stories concluídas
|
||||||
|
project: SARandroid
|
||||||
|
project_key: NOKEY
|
||||||
|
tracking_system: file-system
|
||||||
|
story_location: _bmad-output/implementation-artifacts
|
||||||
|
|
||||||
|
development_status:
|
||||||
|
# Epic 1: Disponibilizar Taxa de Acréscimo no Dispositivo
|
||||||
|
epic-1: done
|
||||||
|
1-1-migracao-do-schema-sqlite-para-suporte-a-acrescimo: done
|
||||||
|
1-2-sincronizacao-da-taxa-de-acrescimo-do-erp: done
|
||||||
|
epic-1-retrospective: optional # skipped — lições capturadas no retro do Epic 2
|
||||||
|
|
||||||
|
# Epic 2: Acréscimo Financeiro no Pedido
|
||||||
|
epic-2: done
|
||||||
|
2-1-exibir-campo-de-acrescimo-na-tela-do-pedido: done
|
||||||
|
2-2-calcular-acrescimo-ao-selecionar-forma-de-pagamento: done
|
||||||
|
2-3-persistir-acrescimo-ao-fechar-o-pedido: done
|
||||||
|
2-4-exibir-acrescimo-na-consulta-de-pedidos: done
|
||||||
|
epic-2-retrospective: done
|
||||||
|
|
||||||
|
# Epic 3: Sync Bidirecional do Acréscimo entre App e ERP
|
||||||
|
epic-3: done
|
||||||
|
3-1-enviar-acrescimo-ao-postgresql-ao-salvar-pedido: done
|
||||||
|
3-2-ler-acrescimo-do-postgresql-no-sync-de-consulta: done
|
||||||
|
3-3-incluir-vl-acrescimo-no-md5-de-change-detection: done
|
||||||
|
epic-3-retrospective: optional
|
||||||
338
_bmad-output/planning-artifacts/epics.md
Normal file
@@ -0,0 +1,338 @@
|
|||||||
|
---
|
||||||
|
stepsCompleted: ['step-01-validate-prerequisites', 'step-02-design-epics', 'step-03-create-stories']
|
||||||
|
inputDocuments:
|
||||||
|
- '_bmad-output/planning-artifacts/prd.md'
|
||||||
|
- '_bmad-output/project-context.md'
|
||||||
|
---
|
||||||
|
|
||||||
|
# SARAndroid - Epic Breakdown
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Este documento apresenta o epic e histórias para a funcionalidade de **Acréscimo Financeiro no Pedido**, decompondo os requisitos do PRD em histórias implementáveis para o agente desenvolvedor.
|
||||||
|
|
||||||
|
## Requirements Inventory
|
||||||
|
|
||||||
|
### Functional Requirements
|
||||||
|
|
||||||
|
FR1: O sistema sincroniza `gestao.formapag.acresc` (PostgreSQL) para `formapag.tx_acrescimo` (SQLite) durante a comunicação
|
||||||
|
FR2: O sistema trata `tx_acrescimo = NULL` como `0.0`, sem erro ou interrupção
|
||||||
|
FR3: O sistema migra o schema SQLite de v40 para v41, adicionando `tx_acrescimo REAL DEFAULT 0` na tabela `formapag`
|
||||||
|
FR4: A migração preserva todos os registros existentes de formas de pagamento
|
||||||
|
FR5: O representante visualiza o valor do acréscimo financeiro em campo dedicado no pedido, sempre visível
|
||||||
|
FR6: O sistema calcula o acréscimo ao selecionar forma de pagamento: `acrescimo = subtotal × (tx_acrescimo / 100)`
|
||||||
|
FR7: O sistema recalcula o acréscimo automaticamente ao trocar a forma de pagamento
|
||||||
|
FR8: O campo acréscimo exibe R$ 0,00 quando `tx_acrescimo = 0` ou `NULL`
|
||||||
|
FR9: O total do pedido reflete `subtotal + acrescimo`
|
||||||
|
FR10: O sistema grava o valor calculado do acréscimo no registro do pedido ao fechar
|
||||||
|
FR11: O sistema grava o total com acréscimo no registro do pedido ao fechar
|
||||||
|
FR12: O representante visualiza o total com acréscimo em pedidos fechados que possuem o campo gravado
|
||||||
|
FR13: O sistema exibe pedidos sem campo de acréscimo sem alteração ou erro
|
||||||
|
FR14: Ao enviar pedido ao ERP (`PedidoPGSQL.insert`), o app grava a taxa (% acréscimo) e o valor calculado nos campos corretos conforme o sistema (Gerente: `gerente.pedidos.acrep` + `acrev`; SIG: `sig.pedidos.tx_acrescimo`)
|
||||||
|
FR15: Ao sincronizar pedidos de consulta do ERP (`PedidoPGSQL.selectAllPedConsulta`), o app lê o acréscimo dos campos corretos conforme o sistema e popula `Pedido.vlAcrescimo` (para SIG, calcula `vlAcrescimo = total × tx_acrescimo / 100` server-side não grava valor)
|
||||||
|
FR16: Sync tolera ausência das colunas de acréscimo no PostgreSQL (DDL server-side pode preceder ou seguir o deploy do app sem interromper a comunicação)
|
||||||
|
FR17: MD5 de `pedido_consulta` inclui `vl_acrescimo` no cálculo de hash, garantindo que mudanças isoladas de acréscimo disparem re-sync
|
||||||
|
|
||||||
|
### NonFunctional Requirements
|
||||||
|
|
||||||
|
NFR1: Recálculo do acréscimo ao trocar forma de pagamento: < 100ms (operação aritmética em memória, sem I/O)
|
||||||
|
NFR2: Leitura de `tx_acrescimo` do SQLite executada em thread background, sem impacto na responsividade da UI
|
||||||
|
NFR3: `tx_acrescimo = NULL` nunca causa `NullPointerException` — tratamento defensivo obrigatório em todo ponto de acesso
|
||||||
|
NFR4: Migração v40→v41 idempotente: múltiplas execuções de `onUpgrade()` não corrompem dados
|
||||||
|
NFR5: Sync tolera ausência da coluna `acresc` no PostgreSQL (schemas antigos) sem interromper a comunicação
|
||||||
|
NFR6: Após sync bem-sucedido, `tx_acrescimo` reflete `acresc` para 100% das formas de pagamento ativas
|
||||||
|
|
||||||
|
### Additional Requirements
|
||||||
|
|
||||||
|
- Migração implementada em `DatabaseHelper.onUpgrade()` com guard `if (oldVersion < 41)`
|
||||||
|
- `dbVersao` incrementado para 41 em `DatabaseHelper`
|
||||||
|
- Nenhuma dependência nova — sem JARs adicionais em `libs/`
|
||||||
|
- Toda leitura SQLite em thread background (regra absoluta do projeto)
|
||||||
|
- `tx_acrescimo = NULL` tratado como `0.0` em todo ponto de acesso
|
||||||
|
- Valor do acréscimo gravado no pedido no fechamento (não recalculado na consulta)
|
||||||
|
- ⚠️ Verificar antes da implementação se tabela `pedido` já possui campo para acréscimo calculado
|
||||||
|
|
||||||
|
### UX Design Requirements
|
||||||
|
|
||||||
|
Sem documento UX — feature brownfield sem nova tela. Requisitos de UI derivados do PRD:
|
||||||
|
- Campo acréscimo sempre visível na tela de edição do pedido (R$ 0,00 quando zero)
|
||||||
|
- Recálculo disparado automaticamente na seleção/troca da forma de pagamento
|
||||||
|
- Total do pedido atualizado na mesma interação
|
||||||
|
|
||||||
|
### FR Coverage Map
|
||||||
|
|
||||||
|
| FR | Epic | Descrição |
|
||||||
|
|---|---|---|
|
||||||
|
| FR1 | Epic 1 | Sync `gestao.formapag.acresc` → `formapag.tx_acrescimo` |
|
||||||
|
| FR2 | Epic 1 | Tolerância a `NULL` no SQLite |
|
||||||
|
| FR3 | Epic 1 | Migração schema v40→v41 |
|
||||||
|
| FR4 | Epic 1 | Preservação de registros na migração |
|
||||||
|
| FR5 | Epic 2 | Campo acréscimo sempre visível no pedido |
|
||||||
|
| FR6 | Epic 2 | Cálculo ao selecionar forma de pagamento |
|
||||||
|
| FR7 | Epic 2 | Recálculo ao trocar forma de pagamento |
|
||||||
|
| FR8 | Epic 2 | Exibir R$ 0,00 quando sem acréscimo |
|
||||||
|
| FR9 | Epic 2 | Total = subtotal + acréscimo |
|
||||||
|
| FR10 | Epic 2 | Gravar acréscimo calculado no pedido |
|
||||||
|
| FR11 | Epic 2 | Gravar total com acréscimo no pedido |
|
||||||
|
| FR12 | Epic 2 | Consulta: exibir total com acréscimo se existir |
|
||||||
|
| FR13 | Epic 2 | Consulta: pedidos antigos sem alteração |
|
||||||
|
| FR14 | Epic 3 | Envio outbound do acréscimo ao PG (Gerente: `acrep`+`acrev`; SIG: `tx_acrescimo`) |
|
||||||
|
| FR15 | Epic 3 | Leitura inbound do acréscimo do PG na sync de consulta |
|
||||||
|
| FR16 | Epic 3 | Tolerância a ausência das colunas de acréscimo no PG |
|
||||||
|
| FR17 | Epic 3 | MD5 de `pedido_consulta` incluindo `vl_acrescimo` |
|
||||||
|
|
||||||
|
## Epic List
|
||||||
|
|
||||||
|
### Epic 1: Disponibilizar Taxa de Acréscimo no Dispositivo
|
||||||
|
O sistema armazena a taxa de acréscimo de cada forma de pagamento localmente, sincronizada do ERP, pronta para uso offline.
|
||||||
|
**FRs cobertos:** FR1, FR2, FR3, FR4
|
||||||
|
|
||||||
|
### Epic 2: Acréscimo Financeiro no Pedido
|
||||||
|
O representante visualiza e utiliza o acréscimo financeiro ao criar pedidos, com cálculo automático ao selecionar a forma de pagamento, total correto e histórico preservado na consulta.
|
||||||
|
**FRs cobertos:** FR5, FR6, FR7, FR8, FR9, FR10, FR11, FR12, FR13
|
||||||
|
|
||||||
|
### Epic 3: Sync Bidirecional do Acréscimo entre App e ERP
|
||||||
|
O acréscimo calculado localmente é enviado ao PostgreSQL ao fechar o pedido, e pedidos históricos sincronizados do ERP trazem o acréscimo de volta ao SQLite — tornando a feature end-to-end funcional em Gerente e SIG.
|
||||||
|
**FRs cobertos:** FR14, FR15, FR16, FR17
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Epic 1: Disponibilizar Taxa de Acréscimo no Dispositivo
|
||||||
|
|
||||||
|
O sistema armazena a taxa de acréscimo de cada forma de pagamento localmente no SQLite, sincronizada do ERP, pronta para uso offline.
|
||||||
|
|
||||||
|
### Story 1.1: Migração do Schema SQLite para Suporte a Acréscimo
|
||||||
|
|
||||||
|
Como desenvolvedor do sistema,
|
||||||
|
quero migrar o schema SQLite de v40 para v41 adicionando `tx_acrescimo` na tabela `formapag`,
|
||||||
|
para que as taxas de acréscimo possam ser armazenadas localmente sem afetar dados existentes.
|
||||||
|
|
||||||
|
**Acceptance Criteria:**
|
||||||
|
|
||||||
|
**Dado** que o app está sendo atualizado em um dispositivo com schema SQLite v40
|
||||||
|
**Quando** `DatabaseHelper.onUpgrade()` for executado
|
||||||
|
**Então** a coluna `tx_acrescimo REAL DEFAULT 0` é adicionada à tabela `formapag`
|
||||||
|
**E** todos os registros existentes de formas de pagamento são preservados com `tx_acrescimo = 0`
|
||||||
|
|
||||||
|
**Dado** que `onUpgrade()` é chamado com `oldVersion >= 41`
|
||||||
|
**Quando** o bloco de migração v41 for avaliado
|
||||||
|
**Então** a migração é ignorada (guard `if (oldVersion < 41)`) sem erro
|
||||||
|
|
||||||
|
**Dado** que `dbVersao` em `DatabaseHelper`
|
||||||
|
**Quando** a migração for aplicada
|
||||||
|
**Então** `dbVersao = 41`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Story 1.2: Sincronização da Taxa de Acréscimo do ERP
|
||||||
|
|
||||||
|
Como representante de vendas,
|
||||||
|
quero que a taxa de acréscimo de cada forma de pagamento seja sincronizada do ERP para o app durante a comunicação,
|
||||||
|
para que os cálculos de acréscimo usem sempre os valores atualizados do servidor.
|
||||||
|
|
||||||
|
**Acceptance Criteria:**
|
||||||
|
|
||||||
|
**Dado** que `gestao.formapag` no PostgreSQL possui a coluna `acresc` com valores preenchidos
|
||||||
|
**Quando** a `ComunicaActivity` executar o sync de formas de pagamento
|
||||||
|
**Então** o valor de `acresc` é gravado em `formapag.tx_acrescimo` no SQLite para cada forma de pagamento
|
||||||
|
|
||||||
|
**Dado** que `gestao.formapag` no PostgreSQL não possui a coluna `acresc` (schema antigo)
|
||||||
|
**Quando** o sync de formas de pagamento for executado
|
||||||
|
**Então** a comunicação continua sem erro e `tx_acrescimo` permanece com o valor anterior (ou `0`)
|
||||||
|
|
||||||
|
**Dado** que o VO `FormaPagamento` é carregado após o sync
|
||||||
|
**Quando** `FormaPagamentoDB` preencher o objeto
|
||||||
|
**Então** o campo `tx_acrescimo` está acessível no VO, com valor `0.0` quando `NULL` no banco
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Epic 2: Acréscimo Financeiro no Pedido
|
||||||
|
|
||||||
|
O representante visualiza e utiliza o acréscimo financeiro ao criar pedidos, com cálculo automático ao selecionar a forma de pagamento, total correto e histórico preservado na consulta.
|
||||||
|
|
||||||
|
### Story 2.1: Exibir Campo de Acréscimo na Tela do Pedido
|
||||||
|
|
||||||
|
Como representante de vendas,
|
||||||
|
quero ver um campo de acréscimo sempre visível na tela do pedido,
|
||||||
|
para que eu saiba em todo momento se há acréscimo aplicado, mesmo que seja zero.
|
||||||
|
|
||||||
|
**Acceptance Criteria:**
|
||||||
|
|
||||||
|
**Dado** que estou na tela de edição do pedido (`UpdatePedidoActivity`)
|
||||||
|
**Quando** a tela for exibida
|
||||||
|
**Então** um campo "Acréscimo" é visível mostrando R$ 0,00 por padrão
|
||||||
|
|
||||||
|
**Dado** que nenhuma forma de pagamento foi selecionada ou a selecionada tem `tx_acrescimo = 0`
|
||||||
|
**Quando** o campo acréscimo for exibido
|
||||||
|
**Então** mostra "R$ 0,00" — nunca fica oculto ou em branco
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Story 2.2: Calcular Acréscimo ao Selecionar Forma de Pagamento
|
||||||
|
|
||||||
|
Como representante de vendas,
|
||||||
|
quero que o acréscimo seja calculado e exibido automaticamente ao selecionar a forma de pagamento,
|
||||||
|
para que eu veja o valor real do pedido sem precisar calcular manualmente.
|
||||||
|
|
||||||
|
**Acceptance Criteria:**
|
||||||
|
|
||||||
|
**Dado** que estou editando um pedido com subtotal R$ 3.500,00
|
||||||
|
**Quando** selecionar "Boleto 30/60/90" com `tx_acrescimo = 2.5`
|
||||||
|
**Então** o campo "Acréscimo" exibe R$ 87,50 e o total exibe R$ 3.587,50
|
||||||
|
|
||||||
|
**Dado** que já selecionei "Boleto 30/60/90" (2,5%) e troco para "Dinheiro" (`tx_acrescimo = 0`)
|
||||||
|
**Quando** a forma de pagamento for alterada
|
||||||
|
**Então** o campo "Acréscimo" retorna a R$ 0,00 e o total retorna a R$ 3.500,00
|
||||||
|
|
||||||
|
**Dado** que a forma de pagamento selecionada tem `tx_acrescimo = NULL`
|
||||||
|
**Quando** o cálculo for executado
|
||||||
|
**Então** acréscimo = R$ 0,00, sem crash ou `NullPointerException`
|
||||||
|
|
||||||
|
**Dado** que o cálculo é disparado
|
||||||
|
**Quando** a troca de forma de pagamento ocorrer
|
||||||
|
**Então** o recálculo conclui em menos de 100ms (aritmética em memória)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Story 2.3: Persistir Acréscimo ao Fechar o Pedido
|
||||||
|
|
||||||
|
Como sistema,
|
||||||
|
quero gravar o valor do acréscimo calculado e o total com acréscimo no registro do pedido ao fechar,
|
||||||
|
para que o valor histórico seja preservado independente de alterações futuras na taxa.
|
||||||
|
|
||||||
|
> ⚠️ **Pré-condição de implementação:** Verificar se a tabela `pedido` no SQLite já possui colunas para acréscimo e total-com-acréscimo. Se não existirem, esta história inclui a adição dessas colunas na migração (incrementar `dbVersao` conforme necessário).
|
||||||
|
|
||||||
|
**Acceptance Criteria:**
|
||||||
|
|
||||||
|
**Dado** que um pedido foi editado com acréscimo R$ 87,50 e total R$ 3.587,50
|
||||||
|
**Quando** o pedido for salvo/fechado
|
||||||
|
**Então** o valor do acréscimo (87,50) está gravado no registro do pedido no SQLite
|
||||||
|
|
||||||
|
**Dado** que um pedido foi salvo com acréscimo
|
||||||
|
**Quando** a taxa de acréscimo da forma de pagamento for alterada no ERP e sincronizada
|
||||||
|
**Então** o valor gravado no pedido antigo permanece inalterado
|
||||||
|
|
||||||
|
**Dado** que um pedido é criado com forma de pagamento sem acréscimo
|
||||||
|
**Quando** o pedido for salvo
|
||||||
|
**Então** o acréscimo gravado é 0,00
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Story 2.4: Exibir Acréscimo na Consulta de Pedidos
|
||||||
|
|
||||||
|
Como representante de vendas,
|
||||||
|
quero ver o total correto (com acréscimo) na consulta de pedidos já fechados,
|
||||||
|
para que o histórico reflita os valores reais praticados.
|
||||||
|
|
||||||
|
**Acceptance Criteria:**
|
||||||
|
|
||||||
|
**Dado** que consulto um pedido fechado que possui acréscimo gravado
|
||||||
|
**Quando** a tela de consulta exibir o pedido
|
||||||
|
**Então** o valor total exibido inclui o acréscimo registrado no pedido
|
||||||
|
|
||||||
|
**Dado** que consulto um pedido fechado criado antes desta funcionalidade (sem campo de acréscimo)
|
||||||
|
**Quando** a tela de consulta exibir o pedido
|
||||||
|
**Então** o pedido é exibido normalmente sem erro, sem tentativa de recalcular acréscimo
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Epic 3: Sync Bidirecional do Acréscimo entre App e ERP
|
||||||
|
|
||||||
|
O acréscimo calculado localmente é enviado ao PostgreSQL ao fechar o pedido, e pedidos históricos sincronizados do ERP trazem o acréscimo de volta ao SQLite. Torna a feature end-to-end funcional em ambos os sistemas (Gerente e SIG), fechando as Jornadas 3 e 4 do PRD para pedidos que trafegam pelo ERP.
|
||||||
|
|
||||||
|
### Pré-condições server-side (DDL no PostgreSQL)
|
||||||
|
|
||||||
|
Estas alterações no PostgreSQL podem ser executadas antes, durante ou depois do deploy do app — o código trata a ausência das colunas (FR16). Recomenda-se executar ANTES do deploy para que o primeiro sync já carregue dados completos.
|
||||||
|
|
||||||
|
**Sistema Gerente:**
|
||||||
|
```sql
|
||||||
|
ALTER TABLE gerente.pedidos ADD COLUMN acrep REAL DEFAULT 0; -- % de acréscimo
|
||||||
|
ALTER TABLE gerente.pedidos ADD COLUMN acrev REAL DEFAULT 0; -- valor calculado do acréscimo
|
||||||
|
```
|
||||||
|
|
||||||
|
**Sistema SIG:**
|
||||||
|
```sql
|
||||||
|
ALTER TABLE sig.pedidos ADD COLUMN tx_acrescimo REAL DEFAULT 0; -- % de acréscimo (valor é derivado server-side)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Assimetria Gerente vs SIG
|
||||||
|
|
||||||
|
- **Gerente** armazena taxa + valor → histórico congelado no ERP mesmo se a taxa em `formapag` mudar depois
|
||||||
|
- **SIG** armazena só a taxa → valor é sempre calculado server-side; histórico reflete a taxa vigente no pedido no momento do envio
|
||||||
|
|
||||||
|
Esta assimetria é uma característica dos schemas de destino e deve ser respeitada — não é uma inconsistência a corrigir.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Story 3.1: Enviar Acréscimo ao PostgreSQL ao Salvar Pedido
|
||||||
|
|
||||||
|
Como sistema,
|
||||||
|
quero enviar a taxa e o valor do acréscimo ao PostgreSQL quando um pedido for sincronizado para o ERP,
|
||||||
|
para que o valor praticado localmente seja registrado no sistema central e fique disponível para relatórios do escritório.
|
||||||
|
|
||||||
|
**Acceptance Criteria:**
|
||||||
|
|
||||||
|
**Dado** que `Global.sistema == SISTEMA_GERENTE` e um pedido com `vl_acrescimo = 87.50` (calculado sobre taxa 2,5%) é enviado via `PedidoPGSQL.insert`
|
||||||
|
**Quando** o INSERT em `gerente.pedidos` for executado
|
||||||
|
**Então** a coluna `acrep` contém `2.5` (taxa da forma de pagamento) e `acrev` contém `87.50` (valor calculado)
|
||||||
|
|
||||||
|
**Dado** que `Global.sistema == SISTEMA_SIG` e o mesmo pedido é enviado
|
||||||
|
**Quando** o INSERT em `sig.pedidos` for executado
|
||||||
|
**Então** a coluna `tx_acrescimo` contém `2.5` (só a taxa; o valor é derivado server-side)
|
||||||
|
|
||||||
|
**Dado** que um pedido sem acréscimo (forma de pagamento com `tx_acrescimo = 0`) é enviado
|
||||||
|
**Quando** o INSERT for executado em qualquer dos dois sistemas
|
||||||
|
**Então** o campo correspondente recebe `0.0` sem erro
|
||||||
|
|
||||||
|
**Dado** que a coluna de acréscimo ainda não existe no PostgreSQL (schema antigo)
|
||||||
|
**Quando** o INSERT tentar gravar o campo
|
||||||
|
**Então** a comunicação falha de forma graciosa (registra no `gestao.log` e segue) OU o INSERT é construído condicionalmente sem o campo quando o DBA ainda não aplicou o DDL — decisão de design a resolver na implementação
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Story 3.2: Ler Acréscimo do PostgreSQL no Sync de Consulta
|
||||||
|
|
||||||
|
Como representante de vendas,
|
||||||
|
quero que pedidos históricos sincronizados do ERP cheguem ao app com o acréscimo correto gravado,
|
||||||
|
para que a consulta exiba os valores reais praticados mesmo em pedidos que não foram criados neste dispositivo.
|
||||||
|
|
||||||
|
**Acceptance Criteria:**
|
||||||
|
|
||||||
|
**Dado** que `Global.sistema == SISTEMA_GERENTE` e `gerente.pedidos` possui `acrep = 3.0` e `acrev = 105.00` para um pedido
|
||||||
|
**Quando** `PedidoPGSQL.selectAllPedConsulta` ler o registro
|
||||||
|
**Então** o VO `Pedido` retornado tem `vlAcrescimo = 105.00`
|
||||||
|
|
||||||
|
**Dado** que `Global.sistema == SISTEMA_SIG` e `sig.pedidos` possui `tx_acrescimo = 3.0` e `total = 3500.00` para um pedido
|
||||||
|
**Quando** `PedidoPGSQL.selectAllPedConsulta` ler o registro
|
||||||
|
**Então** o VO `Pedido` retornado tem `vlAcrescimo` calculado como `3500.00 × 3.0 / 100 = 105.00`
|
||||||
|
|
||||||
|
**Dado** que a coluna de acréscimo não existe no PostgreSQL ou está `NULL`
|
||||||
|
**Quando** `selectAllPedConsulta` for executado
|
||||||
|
**Então** o VO recebe `vlAcrescimo = 0.0`, sem erro, sem crash
|
||||||
|
|
||||||
|
**Dado** que `PedidoConsultaDB.saveAll` recebe os VOs retornados
|
||||||
|
**Quando** gravar em `pedido_consulta`
|
||||||
|
**Então** `pedido_consulta.vl_acrescimo` reflete o valor lido do PG (via Story 2.4 que já aceita o campo)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Story 3.3: Incluir `vl_acrescimo` no MD5 de Change-Detection
|
||||||
|
|
||||||
|
Como sistema,
|
||||||
|
quero que mudanças isoladas de `vl_acrescimo` num pedido disparem re-sync do registro local,
|
||||||
|
para que ajustes de acréscimo no ERP (reabertura, correção manual server-side) sejam propagados ao dispositivo.
|
||||||
|
|
||||||
|
**Acceptance Criteria:**
|
||||||
|
|
||||||
|
**Dado** que um pedido já sincronizado no SQLite tem `vl_acrescimo = 87.50`
|
||||||
|
**Quando** o ERP alterar só o `vl_acrescimo` do registro (ex: correção do escritório) e recalcular o MD5
|
||||||
|
**Então** o MD5 recebido muda, e `PedidoConsultaDB.saveAll` detecta a divergência e dispara `updateErp`
|
||||||
|
|
||||||
|
**Dado** que o MD5 do PG inclui `vl_acrescimo` (ou `acrev` / `tx_acrescimo × total`) no hash
|
||||||
|
**Quando** o app calcular o MD5 local para comparação
|
||||||
|
**Então** o cálculo reflete o mesmo campo — evitando loop de "está diferente" / "está igual" entre as duas direções
|
||||||
|
|
||||||
|
**Dado** que pedidos pré-Epic 3 no ERP têm `acrep`/`acrev`/`tx_acrescimo` com valor default (`0`)
|
||||||
|
**Quando** o primeiro sync após o deploy rodar
|
||||||
|
**Então** esses pedidos aparecem como "atualizados" (MD5 mudou devido ao novo campo), mas o `vl_acrescimo` gravado é `0.0` — comportamento aceitável
|
||||||
|
|
||||||
224
_bmad-output/planning-artifacts/prd.md
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
---
|
||||||
|
stepsCompleted: ['step-01-init', 'step-02-discovery', 'step-02b-vision', 'step-02c-executive-summary', 'step-03-success', 'step-04-journeys', 'step-05-domain', 'step-06-innovation', 'step-07-project-type', 'step-08-scoping', 'step-09-functional', 'step-10-nonfunctional', 'step-11-polish', 'step-12-complete']
|
||||||
|
status: complete
|
||||||
|
completedAt: '2026-04-16'
|
||||||
|
inputDocuments:
|
||||||
|
- '_bmad-output/project-context.md'
|
||||||
|
- 'docs/index.md'
|
||||||
|
- 'docs/project-overview.md'
|
||||||
|
- 'docs/architecture.md'
|
||||||
|
- 'docs/data-models.md'
|
||||||
|
- 'docs/source-tree-analysis.md'
|
||||||
|
- 'docs/development-guide.md'
|
||||||
|
- 'docs/component-inventory.md'
|
||||||
|
workflowType: 'prd'
|
||||||
|
projectDocsCount: 7
|
||||||
|
briefCount: 0
|
||||||
|
researchCount: 0
|
||||||
|
brainstormingCount: 0
|
||||||
|
classification:
|
||||||
|
projectType: mobile_app
|
||||||
|
domain: sales_force_automation
|
||||||
|
complexity: medium
|
||||||
|
projectContext: brownfield
|
||||||
|
feature: acrescimo_financeiro_pedido
|
||||||
|
schemaChange: true
|
||||||
|
schemaVersion: v40_to_v41
|
||||||
|
---
|
||||||
|
|
||||||
|
# Product Requirements Document - SARAndroid
|
||||||
|
|
||||||
|
**Funcionalidade:** Acréscimo Financeiro no Pedido
|
||||||
|
**Autor:** Julio
|
||||||
|
**Data:** 2026-04-16
|
||||||
|
|
||||||
|
## Executive Summary
|
||||||
|
|
||||||
|
O SARAndroid é um app Android nativo para representantes de vendas que opera offline-first, sincronizando dados com PostgreSQL via JDBC. Esta funcionalidade implementa o cálculo automático de **acréscimo financeiro** no pedido com base na taxa configurada por forma de pagamento.
|
||||||
|
|
||||||
|
**Problema:** Pedidos fechados com formas de pagamento que possuem acréscimo chegam ao escritório com valor incorreto — a funcionalidade não existe no app. O representante não tem como saber o valor final correto sem calcular manualmente.
|
||||||
|
|
||||||
|
**Solução:** Ler `tx_acrescimo` da tabela local `formapag` (sincronizada de `gestao.formapag.acresc` no PostgreSQL), calcular o acréscimo ao selecionar a forma de pagamento, exibi-lo em campo dedicado e somá-lo ao total do pedido.
|
||||||
|
|
||||||
|
**Por que agora:** A taxa já existe no ERP — a informação está disponível. A implementação aproveita o pipeline de sync existente (`ComunicaActivity`), o modelo de pedido em uso (`Pedido`/`ItemPedido`) e a UI de edição de pedido (`UpdatePedidoActivity`). Nenhuma nova infraestrutura é necessária.
|
||||||
|
|
||||||
|
**Impacto:** Representantes fecham pedidos com valor correto, sem cálculo manual. Escritório recebe pedidos com valores corretos. Zero retrabalho de correção pós-sync.
|
||||||
|
|
||||||
|
## Project Classification
|
||||||
|
|
||||||
|
- **Tipo:** App mobile Android nativo (Java, offline-first)
|
||||||
|
- **Domínio:** Força de Vendas / ERP Móvel
|
||||||
|
- **Complexidade:** Média — migração de schema SQLite (v40→v41), extensão do pipeline de sync, atualização da UI de pedido
|
||||||
|
- **Contexto:** Brownfield — extensão de funcionalidade em sistema em produção
|
||||||
|
|
||||||
|
## Success Criteria
|
||||||
|
|
||||||
|
### Sucesso do Usuário
|
||||||
|
- Ao selecionar forma de pagamento com `tx_acrescimo > 0`, o acréscimo é calculado e exibido instantaneamente, sem ação manual
|
||||||
|
- Campo acréscimo sempre visível no pedido (R$ 0,00 quando não há acréscimo)
|
||||||
|
- Total do pedido inclui acréscimo antes de qualquer confirmação
|
||||||
|
- Troca de forma de pagamento recalcula o acréscimo automaticamente
|
||||||
|
|
||||||
|
### Sucesso do Negócio
|
||||||
|
- Pedidos sincronizados carregam sempre o valor total correto (subtotal + acréscimo)
|
||||||
|
- Zero retrabalho de correção de valor no escritório para pedidos com acréscimo
|
||||||
|
|
||||||
|
### Sucesso Técnico
|
||||||
|
- Schema SQLite migrado de v40 para v41 sem perda de dados
|
||||||
|
- Campo `tx_acrescimo` sincronizado corretamente de `gestao.formapag.acresc` via `ComunicaActivity`
|
||||||
|
- Toda leitura de banco executada fora da UI thread
|
||||||
|
|
||||||
|
### Resultados Mensuráveis
|
||||||
|
- 100% das formas de pagamento com `acresc > 0` no PostgreSQL refletidas no SQLite após sync
|
||||||
|
- Recálculo ocorre na mesma interação de troca de forma de pagamento (< 100ms)
|
||||||
|
|
||||||
|
## Product Scope
|
||||||
|
|
||||||
|
### MVP — Mínimo Viável (Fase 1)
|
||||||
|
|
||||||
|
**Jornadas suportadas:** Todas as 4 jornadas mapeadas
|
||||||
|
|
||||||
|
- Migração schema v40→v41: `ALTER TABLE formapag ADD COLUMN tx_acrescimo REAL DEFAULT 0`
|
||||||
|
- Sync em `ComunicaActivity`: mapear `gestao.formapag.acresc` → `formapag.tx_acrescimo`
|
||||||
|
- `FormaPagamentoDB`: expor `tx_acrescimo` no VO `FormaPagamento`
|
||||||
|
- `UpdatePedidoActivity`/fragment: campo acréscimo sempre visível; recálculo automático ao selecionar forma de pagamento; fórmula: `acrescimo = subtotal × (tx_acrescimo / 100)`; `total = subtotal + acrescimo`
|
||||||
|
- Persistência: gravar valor calculado do acréscimo e total no registro do pedido
|
||||||
|
- Consulta: exibir total com acréscimo em pedidos que já possuem o campo; pedidos sem o campo não são alterados
|
||||||
|
|
||||||
|
### Growth Features (Fase 2)
|
||||||
|
- Relatório de acréscimos por período/representante
|
||||||
|
- Destaque visual quando acréscimo for alto
|
||||||
|
|
||||||
|
### Visão (Fase 3)
|
||||||
|
- Múltiplas taxas por forma de pagamento (ex: parcelado com juros progressivos)
|
||||||
|
- Acréscimo por faixa de valor do pedido
|
||||||
|
|
||||||
|
### Riscos e Mitigações
|
||||||
|
|
||||||
|
| Risco | Mitigação |
|
||||||
|
|---|---|
|
||||||
|
| `tx_acrescimo = NULL` em registros antigos | `DEFAULT 0` na migração + tratar `NULL` como `0.0` em código |
|
||||||
|
| Sync parcial (formapag não sincronizada) | Comportamento gracioso: acréscimo = 0, pedido funciona normalmente |
|
||||||
|
| Tabela `pedido` sem campo para valor do acréscimo | ⚠️ Verificar schema antes da implementação — pode exigir coluna adicional na migração |
|
||||||
|
|
||||||
|
## User Journeys
|
||||||
|
|
||||||
|
### Jornada 1 — Representante: Pedido com Acréscimo (caminho feliz)
|
||||||
|
|
||||||
|
**Cena:** Carlos, representante, fecha os itens do pedido na loja do cliente e seleciona "Boleto 30/60/90" (2,5% de acréscimo financeiro).
|
||||||
|
|
||||||
|
**Ação:** O campo "Acréscimo" atualiza instantaneamente para R$ 87,50 (2,5% sobre R$ 3.500,00). O total passa para R$ 3.587,50.
|
||||||
|
|
||||||
|
**Resolução:** Carlos mostra o total correto ao cliente e fecha o pedido. O escritório recebe o valor já correto — sem ligação para corrigir.
|
||||||
|
|
||||||
|
### Jornada 2 — Representante: Troca de Forma de Pagamento
|
||||||
|
|
||||||
|
**Cena:** Carlos já selecionou "Boleto 30/60/90" (2,5% acréscimo), mas o cliente prefere pagar à vista ("Dinheiro", sem acréscimo).
|
||||||
|
|
||||||
|
**Ação:** Carlos troca a forma de pagamento. O campo "Acréscimo" retorna para R$ 0,00 e o total volta para R$ 3.500,00 automaticamente.
|
||||||
|
|
||||||
|
**Resolução:** O pedido reflete a escolha correta do cliente sem cálculo manual.
|
||||||
|
|
||||||
|
### Jornada 3 — Representante: Consulta de Pedido Antigo com Acréscimo
|
||||||
|
|
||||||
|
**Cena:** Carlos consulta um pedido fechado no mês anterior que tinha acréscimo registrado.
|
||||||
|
|
||||||
|
**Ação:** A tela de consulta exibe o valor total incluindo o acréscimo que constava no pedido.
|
||||||
|
|
||||||
|
**Resolução:** Carlos vê o histórico correto. Pedidos antigos sem o campo não são alterados.
|
||||||
|
|
||||||
|
### Jornada 4 — Sync: Taxa Atualizada no ERP Chega ao App
|
||||||
|
|
||||||
|
**Cena:** O gestor atualiza a taxa de acréscimo do "Boleto 30/60/90" de 2,5% para 3,0% no ERP (`gestao.formapag.acresc = 3.0`).
|
||||||
|
|
||||||
|
**Ação:** Carlos executa a comunicação no app. O sync lê `gestao.formapag.acresc` e grava `3.0` em `formapag.tx_acrescimo` no SQLite.
|
||||||
|
|
||||||
|
**Resolução:** No próximo pedido, o acréscimo é calculado com a taxa atualizada sem nenhuma ação manual no app.
|
||||||
|
|
||||||
|
### Rastreabilidade Jornada → Capacidade
|
||||||
|
|
||||||
|
| Capacidade | Jornada |
|
||||||
|
|---|---|
|
||||||
|
| Leitura de `tx_acrescimo` ao carregar forma de pagamento | 1, 2 |
|
||||||
|
| Cálculo: `acrescimo = subtotal × (tx_acrescimo / 100)` | 1 |
|
||||||
|
| Recálculo automático ao trocar forma de pagamento | 2 |
|
||||||
|
| Campo acréscimo sempre visível (R$ 0,00 quando zero) | 1, 2 |
|
||||||
|
| Total = subtotal + acréscimo | 1 |
|
||||||
|
| Consulta: exibir total com acréscimo quando existir no registro | 3 |
|
||||||
|
| Sync: `gestao.formapag.acresc` → `formapag.tx_acrescimo` | 4 |
|
||||||
|
|
||||||
|
## Domain-Specific Requirements
|
||||||
|
|
||||||
|
### Operação Offline-First
|
||||||
|
|
||||||
|
- O cálculo do acréscimo usa exclusivamente dados sincronizados no SQLite — nenhuma chamada de rede no momento do cálculo
|
||||||
|
- `tx_acrescimo = NULL` tratado como `0.0` em todo o código — formas de pagamento sem o campo no ERP não geram erro
|
||||||
|
|
||||||
|
### Consistência Histórica do Pedido
|
||||||
|
|
||||||
|
- O valor do acréscimo calculado é **gravado no pedido** no fechamento — não recalculado na consulta
|
||||||
|
- Garante que pedidos históricos não mudem de valor se a taxa for alterada futuramente no ERP
|
||||||
|
|
||||||
|
### Integração ERP
|
||||||
|
|
||||||
|
- Fonte única de verdade da taxa: `gestao.formapag.acresc` no PostgreSQL
|
||||||
|
- Propagação exclusiva via `ComunicaActivity` → `formapag.tx_acrescimo` no SQLite
|
||||||
|
- O campo `acresc` pode não existir em versões antigas do schema PostgreSQL — o sync tolera a ausência sem interromper a comunicação
|
||||||
|
|
||||||
|
## Mobile Platform Requirements
|
||||||
|
|
||||||
|
- **Plataforma:** Android nativo Java exclusivamente — sem Kotlin, sem cross-platform
|
||||||
|
- **Min SDK 19** — cálculo usa operações aritméticas básicas, sem APIs modernas
|
||||||
|
- **Orientação:** Portrait-only, conforme padrão do projeto
|
||||||
|
- **Offline:** 100% offline — taxa lida do SQLite local, cálculo em memória
|
||||||
|
- **Permissões:** Nenhuma permissão adicional necessária
|
||||||
|
- **Dependências:** Nenhuma — sem JARs novos em `libs/`
|
||||||
|
- **UI thread:** Toda leitura de `tx_acrescimo` do SQLite em thread background, conforme regra do projeto
|
||||||
|
- **Herança:** Todas as Activities envolvidas já estendem `GlobalActivity` — padrão mantido
|
||||||
|
|
||||||
|
## Functional Requirements
|
||||||
|
|
||||||
|
### Sincronização de Dados
|
||||||
|
|
||||||
|
- **FR1:** O sistema sincroniza `gestao.formapag.acresc` (PostgreSQL) para `formapag.tx_acrescimo` (SQLite) durante a comunicação
|
||||||
|
- **FR2:** O sistema trata `tx_acrescimo = NULL` como `0.0`, sem erro ou interrupção
|
||||||
|
|
||||||
|
### Schema e Migração
|
||||||
|
|
||||||
|
- **FR3:** O sistema migra o schema SQLite de v40 para v41, adicionando `tx_acrescimo REAL DEFAULT 0` na tabela `formapag`
|
||||||
|
- **FR4:** A migração preserva todos os registros existentes de formas de pagamento
|
||||||
|
|
||||||
|
### Cálculo de Acréscimo
|
||||||
|
|
||||||
|
- **FR5:** O representante visualiza o valor do acréscimo financeiro em campo dedicado no pedido, sempre visível
|
||||||
|
- **FR6:** O sistema calcula o acréscimo ao selecionar forma de pagamento: `acrescimo = subtotal × (tx_acrescimo / 100)`
|
||||||
|
- **FR7:** O sistema recalcula o acréscimo automaticamente ao trocar a forma de pagamento
|
||||||
|
- **FR8:** O campo acréscimo exibe R$ 0,00 quando `tx_acrescimo = 0` ou `NULL`
|
||||||
|
- **FR9:** O total do pedido reflete `subtotal + acrescimo`
|
||||||
|
|
||||||
|
### Persistência do Pedido
|
||||||
|
|
||||||
|
- **FR10:** O sistema grava o valor calculado do acréscimo no registro do pedido ao fechar
|
||||||
|
- **FR11:** O sistema grava o total com acréscimo no registro do pedido ao fechar
|
||||||
|
|
||||||
|
### Consulta de Pedidos
|
||||||
|
|
||||||
|
- **FR12:** O representante visualiza o total com acréscimo em pedidos fechados que possuem o campo gravado
|
||||||
|
- **FR13:** O sistema exibe pedidos sem campo de acréscimo sem alteração ou erro
|
||||||
|
|
||||||
|
## Non-Functional Requirements
|
||||||
|
|
||||||
|
### Performance
|
||||||
|
|
||||||
|
- Recálculo do acréscimo ao trocar forma de pagamento: < 100ms (operação aritmética em memória, sem I/O)
|
||||||
|
- Leitura de `tx_acrescimo` do SQLite: executada em thread background, sem impacto na responsividade da UI
|
||||||
|
|
||||||
|
### Confiabilidade
|
||||||
|
|
||||||
|
- `tx_acrescimo = NULL` nunca causa `NullPointerException` — tratamento defensivo obrigatório em todo ponto de acesso
|
||||||
|
- Migração v40→v41 idempotente: múltiplas execuções de `onUpgrade()` não corrompem dados
|
||||||
|
|
||||||
|
### Integração
|
||||||
|
|
||||||
|
- Sync tolera ausência da coluna `acresc` no PostgreSQL (schemas antigos) sem interromper a comunicação
|
||||||
|
- Após sync bem-sucedido: `tx_acrescimo` reflete `acresc` para 100% das formas de pagamento ativas
|
||||||
172
_bmad-output/project-context.md
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
---
|
||||||
|
project_name: 'SARAndroid'
|
||||||
|
user_name: 'Julio'
|
||||||
|
date: '2026-04-16'
|
||||||
|
sections_completed: ['technology_stack', 'language_rules', 'dual_database_pattern', 'ui_activities', 'critical_rules']
|
||||||
|
status: 'complete'
|
||||||
|
rule_count: 47
|
||||||
|
optimized_for_llm: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# Contexto do Projeto para Agentes de IA
|
||||||
|
|
||||||
|
_Este arquivo contém regras e padrões críticos que agentes de IA devem seguir ao implementar código neste projeto. Foco em detalhes não-óbvios que agentes poderiam deixar passar._
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Stack de Tecnologia & Versões
|
||||||
|
|
||||||
|
- **Linguagem:** Java (sem Kotlin — não usar Kotlin em hipótese alguma)
|
||||||
|
- **Plataforma:** Android | Min SDK: 19 (Android 4.4) | Target SDK: 35
|
||||||
|
- **Build system:** Eclipse ADT (sem Gradle, sem Maven) — dependências são JARs em `lib/`,
|
||||||
|
código gerado em `gen/`, output em `bin/classes/`. NUNCA criar build.gradle ou estrutura app/src/main/
|
||||||
|
- **Banco local:** SQLite via `SQLiteOpenHelper` (schema versão 40)
|
||||||
|
- **Banco remoto:** PostgreSQL via JDBC direto — driver DEVE ser `postgresql-8.2-512.jdbc3.jar`
|
||||||
|
(NÃO atualizar: JDBC4 quebra silenciosamente no Android classloader)
|
||||||
|
- **Data/Hora:** `joda-time 2.5` — usar `DateTime`/`LocalDate`/`Period` para TODAS as operações
|
||||||
|
de data (nunca `java.util.Date` ou `java.util.Calendar`)
|
||||||
|
- **FTP:** `commons-net 3.3`
|
||||||
|
- **Encoding:** Strings com acentos SOMENTE em `res/values/strings.xml`
|
||||||
|
(código-fonte tem histórico de CP1252 — não usar acentos hardcoded em Java)
|
||||||
|
- **Memória:** `android:largeHeap="true"` ativo por causa de fotos via FTP —
|
||||||
|
cuidado com carregamento de imagens em dispositivos com RAM limitada
|
||||||
|
- **Pacote raiz:** `br.com.jcsinformatica.sarandroid`
|
||||||
|
|
||||||
|
## Regras Específicas da Linguagem
|
||||||
|
|
||||||
|
### Tratamento de Exceções
|
||||||
|
- `WarningException` é a exceção customizada para mensagens ao usuário — usar para erros
|
||||||
|
que devem ser exibidos em AlertDialog (não RuntimeException)
|
||||||
|
- `Global.getEmpresa()` SEMPRE lança `WarningException` se chamado antes do login —
|
||||||
|
envolver em try/catch em qualquer Activity que use dados da empresa
|
||||||
|
|
||||||
|
### Herança de Activities
|
||||||
|
- Toda Activity pós-login DEVE estender `GlobalActivity` (não `Activity` diretamente)
|
||||||
|
- `GlobalActivity.onCreate()` configura o título com o nome da empresa —
|
||||||
|
sempre chamar `super.onCreate()` como primeira instrução
|
||||||
|
- Toda nova Activity DEVE ser registrada em `AndroidManifest.xml` com
|
||||||
|
`android:screenOrientation="portrait"`
|
||||||
|
|
||||||
|
### Estado Global
|
||||||
|
- `Global.empresa` — único objeto Empresa ativo; definido no login, nunca nulo em tela pós-login
|
||||||
|
- `Global.pedido` e `Global.pedItem` — estado do pedido em edição; pode ser nulo
|
||||||
|
- Nunca instanciar `Empresa` diretamente nas Activities — sempre usar `Global.getEmpresa()`
|
||||||
|
|
||||||
|
### Operações em Background
|
||||||
|
- Operações de rede (JDBC) e SQLite pesadas DEVEM rodar em Thread separada ou AsyncTask
|
||||||
|
- Nunca fazer IO de banco na UI thread — causa ANR
|
||||||
|
- Usar `Handler` ou `runOnUiThread()` para atualizar UI a partir de threads background
|
||||||
|
|
||||||
|
### Nomenclatura
|
||||||
|
- DAOs SQLite: `*DB.java` ou `*BD.java` (ex: `ClienteDB.java`, `FotosBD.java`)
|
||||||
|
- DAOs PostgreSQL: `*PGSQL.java` (ex: `ClientePGSQL.java`)
|
||||||
|
- Value Objects: em `vo/` sem sufixo (ex: `Cliente.java`, `Pedido.java`)
|
||||||
|
- Threads de busca: `Thread*` (ex: `ThreadBuscaCliente.java`)
|
||||||
|
|
||||||
|
## Padrão Dual-Banco (SQLite + PostgreSQL)
|
||||||
|
|
||||||
|
### Schema SQLite
|
||||||
|
- Toda alteração de schema DEVE ter entrada no `onUpgrade()` de `DatabaseHelper`
|
||||||
|
com guard `if (oldVersion < N)` onde N é a nova versão
|
||||||
|
- `dbVersao` em `DatabaseHelper` DEVE ser incrementado a cada mudança de schema
|
||||||
|
- Versão atual: 40 — próxima alteração usa versão 41
|
||||||
|
- NUNCA dropar tabela em onUpgrade sem migrar dados
|
||||||
|
|
||||||
|
### Ciclo de Vida de Conexão JDBC
|
||||||
|
- Toda conexão PostgreSQL DEVE ser fechada em bloco `finally` via `ConnectionManager.closeAll()`
|
||||||
|
- Nunca reutilizar conexões entre operações — criar nova conexão por operação
|
||||||
|
- Timeout de login: 20s — operações longas devem rodar em thread background
|
||||||
|
- Conexões JDBC NUNCA na UI thread
|
||||||
|
|
||||||
|
### Padrão de Sincronização
|
||||||
|
- Par obrigatório por entidade: `*DB.java` (SQLite) + `*PGSQL.java` (PostgreSQL)
|
||||||
|
- MD5 é calculado no servidor PostgreSQL — NUNCA recomputar localmente para comparação
|
||||||
|
- `ComunicaActivity` é o ÚNICO orquestrador de sync — não criar sync fora dela
|
||||||
|
- Ordem de sync é obrigatória: empresa → representante → formas_pag → municipio →
|
||||||
|
cliente → produto → pedido_consulta → peditem_consulta → ctr
|
||||||
|
- Sync parcial é estado normal — código deve tolerar entidades sem `id_erp`
|
||||||
|
|
||||||
|
### Chaves e Identidade
|
||||||
|
- `id_erp` = chave no ERP remoto (pode ser NULL para registros criados offline)
|
||||||
|
- `id_*` = chave local SQLite autoincrement — nunca enviar ao servidor
|
||||||
|
- Pedidos criados offline têm `id_erp = NULL` até a próxima sync — tratar como caso especial
|
||||||
|
- Ao inserir dados do servidor: SEMPRE verificar `(id_empresa, id_erp)` com SELECT antes do INSERT
|
||||||
|
- NUNCA usar `INSERT OR REPLACE` em tabelas com FKs ativas — usar SELECT + INSERT/UPDATE
|
||||||
|
|
||||||
|
### Restrições de Integridade
|
||||||
|
- `FOREIGN KEY` com `PRAGMA foreign_keys = ON` ativo — respeitar todas as FKs
|
||||||
|
- `UNIQUE(id_empresa, id_erp) ON CONFLICT ABORT` — conflito lança exceção, não substitui
|
||||||
|
- Toda tabela principal tem `id_empresa` — SEMPRE filtrar por empresa nas queries
|
||||||
|
|
||||||
|
## Regras de UI & Activities
|
||||||
|
|
||||||
|
### Estrutura de Telas
|
||||||
|
- Padrão CRUD: `Browse*` (lista) → `Update*` (edição) por entidade
|
||||||
|
- Listas usam `ListView` + `SimpleArrayAdapter*` customizado em `uimodels/`
|
||||||
|
- Busca em lista: thread dedicada `ThreadBusca*` — nunca buscar na UI thread
|
||||||
|
- Toda tela é portrait-only (`android:screenOrientation="portrait"`)
|
||||||
|
|
||||||
|
### Navegação
|
||||||
|
- Fluxo: `SplashScreen` → `LoginActivity` → `MainActivity` → feature Activities
|
||||||
|
- `MainActivity` usa `ExpandableListView` com `ExpandableListAdapter`
|
||||||
|
- Passagem de dados entre Activities: via `Intent.putExtra()` com IDs (não objetos serializados)
|
||||||
|
- Pedido em edição: via `Global.pedido` e `Global.pedItem` (não via Intent)
|
||||||
|
|
||||||
|
### Layouts & Resources
|
||||||
|
- Layouts em `res/layout/` — nomenclatura: `activity_*.xml`, `list_*.xml`, `fragment_*.xml`
|
||||||
|
- Strings com texto visível ao usuário SEMPRE em `res/values/strings.xml`
|
||||||
|
- Tema: `AppTheme` definido em `res/values/styles.xml`
|
||||||
|
- Menus em `res/menu/` — padrão `menu_*.xml`
|
||||||
|
|
||||||
|
### Fragments (Pedido)
|
||||||
|
- `UpdatePedidoActivity` usa fragments: `FlexPedidoFragment`, `ItensPedidoFragment`, `TotalPedidoFragment`
|
||||||
|
- Comunicação entre fragment e activity via interface callback — não acessar activity diretamente
|
||||||
|
- `PedidoTabAdapter` gerencia as abas do pedido
|
||||||
|
|
||||||
|
### Permissões
|
||||||
|
- `INTERNET` + `ACCESS_NETWORK_STATE` — para JDBC e FTP
|
||||||
|
- `WRITE_EXTERNAL_STORAGE` / `READ_EXTERNAL_STORAGE` — para fotos de produtos
|
||||||
|
- `FileProvider` configurado em `res/xml/provider_paths.xml` para compartilhar arquivos
|
||||||
|
|
||||||
|
## Regras Críticas — O Que NÃO Fazer
|
||||||
|
|
||||||
|
### Proibições Absolutas
|
||||||
|
- ❌ NUNCA adicionar dependências via Gradle/Maven — projeto usa JARs em `lib/` apenas
|
||||||
|
- ❌ NUNCA escrever Kotlin — somente Java
|
||||||
|
- ❌ NUNCA fazer operações de banco (SQLite ou JDBC) na UI thread
|
||||||
|
- ❌ NUNCA atualizar o driver PostgreSQL (JDBC4 quebra no Android classloader)
|
||||||
|
- ❌ NUNCA usar `INSERT OR REPLACE` em tabelas com FKs ativas
|
||||||
|
- ❌ NUNCA hardcodar strings com acentos em código Java — usar `res/values/strings.xml`
|
||||||
|
- ❌ NUNCA criar Activity sem registrá-la no `AndroidManifest.xml`
|
||||||
|
- ❌ NUNCA acessar `Global.getEmpresa()` sem try/catch de `WarningException`
|
||||||
|
- ❌ NUNCA criar lógica de sync fora de `ComunicaActivity`
|
||||||
|
|
||||||
|
### Armadilhas Comuns
|
||||||
|
- Confundir `id_erp` com `id_*` local — são chaves diferentes com propósitos distintos
|
||||||
|
- Pedidos offline têm `id_erp = NULL` — sempre verificar antes de usar como FK remota
|
||||||
|
- MD5 vem do servidor — nunca recomputar localmente para comparação
|
||||||
|
- `onUpgrade()` sem guard de versão apaga dados de usuários em produção
|
||||||
|
- Conexão JDBC sem `finally` + `closeAll()` causa leak de conexão
|
||||||
|
|
||||||
|
### Sem Testes Automatizados
|
||||||
|
- O projeto não tem infraestrutura de testes (sem JUnit, sem Espresso)
|
||||||
|
- Não criar arquivos de teste — não há como executá-los no build atual
|
||||||
|
- Validação é manual via dispositivo/emulador Android
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Diretrizes de Uso
|
||||||
|
|
||||||
|
**Para agentes de IA:**
|
||||||
|
- Ler este arquivo antes de implementar qualquer código
|
||||||
|
- Seguir TODAS as regras exatamente como documentadas
|
||||||
|
- Em caso de dúvida, preferir a opção mais restritiva
|
||||||
|
- Atualizar este arquivo se novos padrões emergirem
|
||||||
|
|
||||||
|
**Para humanos:**
|
||||||
|
- Manter este arquivo enxuto e focado nas necessidades dos agentes
|
||||||
|
- Atualizar quando o stack de tecnologia mudar
|
||||||
|
- Revisar quando houver mudanças de arquitetura significativas
|
||||||
|
- Remover regras que se tornarem óbvias com o tempo
|
||||||
|
|
||||||
|
_Última atualização: 2026-04-16_
|
||||||
14
_bmad/_config/agent-manifest.csv
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
name,displayName,title,icon,capabilities,role,identity,communicationStyle,principles,module,path,canonicalId
|
||||||
|
"bmad-agent-analyst","Mary","Business Analyst","📊","market research, competitive analysis, requirements elicitation, domain expertise","Strategic Business Analyst + Requirements Expert","Senior analyst with deep expertise in market research, competitive analysis, and requirements elicitation. Specializes in translating vague needs into actionable specs.","Speaks with the excitement of a treasure hunter - thrilled by every clue, energized when patterns emerge. Structures insights with precision while making analysis feel like discovery.","Channel expert business analysis frameworks: draw upon Porter's Five Forces, SWOT analysis, root cause analysis, and competitive intelligence methodologies to uncover what others miss. Every business challenge has root causes waiting to be discovered. Ground findings in verifiable evidence. Articulate requirements with absolute precision. Ensure all stakeholder voices heard.","bmm","_bmad/bmm/1-analysis/bmad-agent-analyst",""
|
||||||
|
"bmad-agent-tech-writer","Paige","Technical Writer","📚","documentation, Mermaid diagrams, standards compliance, concept explanation","Technical Documentation Specialist + Knowledge Curator","Experienced technical writer expert in CommonMark, DITA, OpenAPI. Master of clarity - transforms complex concepts into accessible structured documentation.","Patient educator who explains like teaching a friend. Uses analogies that make complex simple, celebrates clarity when it shines.","Every Technical Document I touch helps someone accomplish a task. Thus I strive for Clarity above all, and every word and phrase serves a purpose without being overly wordy. I believe a picture/diagram is worth 1000s of words and will include diagrams over drawn out text. I understand the intended audience or will clarify with the user so I know when to simplify vs when to be detailed.","bmm","_bmad/bmm/1-analysis/bmad-agent-tech-writer",""
|
||||||
|
"bmad-agent-pm","John","Product Manager","📋","PRD creation, requirements discovery, stakeholder alignment, user interviews","Product Manager specializing in collaborative PRD creation through user interviews, requirement discovery, and stakeholder alignment.","Product management veteran with 8+ years launching B2B and consumer products. Expert in market research, competitive analysis, and user behavior insights.","Asks 'WHY?' relentlessly like a detective on a case. Direct and data-sharp, cuts through fluff to what actually matters.","Channel expert product manager thinking: draw upon deep knowledge of user-centered design, Jobs-to-be-Done framework, opportunity scoring, and what separates great products from mediocre ones. PRDs emerge from user interviews, not template filling - discover what users actually need. Ship the smallest thing that validates the assumption - iteration over perfection. Technical feasibility is a constraint, not the driver - user value first.","bmm","_bmad/bmm/2-plan-workflows/bmad-agent-pm",""
|
||||||
|
"bmad-agent-ux-designer","Sally","UX Designer","🎨","user research, interaction design, UI patterns, experience strategy","User Experience Designer + UI Specialist","Senior UX Designer with 7+ years creating intuitive experiences across web and mobile. Expert in user research, interaction design, AI-assisted tools.","Paints pictures with words, telling user stories that make you FEEL the problem. Empathetic advocate with creative storytelling flair.","Every decision serves genuine user needs. Start simple, evolve through feedback. Balance empathy with edge case attention. AI tools accelerate human-centered design. Data-informed but always creative.","bmm","_bmad/bmm/2-plan-workflows/bmad-agent-ux-designer",""
|
||||||
|
"bmad-agent-architect","Winston","Architect","🏗️","distributed systems, cloud infrastructure, API design, scalable patterns","System Architect + Technical Design Leader","Senior architect with expertise in distributed systems, cloud infrastructure, and API design. Specializes in scalable patterns and technology selection.","Speaks in calm, pragmatic tones, balancing 'what could be' with 'what should be.'","Channel expert lean architecture wisdom: draw upon deep knowledge of distributed systems, cloud patterns, scalability trade-offs, and what actually ships successfully. User journeys drive technical decisions. Embrace boring technology for stability. Design simple solutions that scale when needed. Developer productivity is architecture. Connect every decision to business value and user impact.","bmm","_bmad/bmm/3-solutioning/bmad-agent-architect",""
|
||||||
|
"bmad-agent-dev","Amelia","Developer Agent","💻","story execution, test-driven development, code implementation","Senior Software Engineer","Executes approved stories with strict adherence to story details and team standards and practices.","Ultra-succinct. Speaks in file paths and AC IDs - every statement citable. No fluff, all precision.","All existing and new tests must pass 100% before story is ready for review. Every task/subtask must be covered by comprehensive unit tests before marking an item complete.","bmm","_bmad/bmm/4-implementation/bmad-agent-dev",""
|
||||||
|
"bmad-cis-agent-brainstorming-coach","Carson","Elite Brainstorming Specialist","🧠","brainstorming facilitation, creative techniques, systematic innovation","Master Brainstorming Facilitator + Innovation Catalyst","Elite facilitator with 20+ years leading breakthrough sessions. Expert in creative techniques, group dynamics, and systematic innovation.","Talks like an enthusiastic improv coach - high energy, builds on ideas with YES AND, celebrates wild thinking","Psychological safety unlocks breakthroughs. Wild ideas today become innovations tomorrow. Humor and play are serious innovation tools.","cis","_bmad/cis/skills/bmad-cis-agent-brainstorming-coach",""
|
||||||
|
"bmad-cis-agent-creative-problem-solver","Dr. Quinn","Master Problem Solver","🔬","systematic problem-solving, root cause analysis, solutions architecture","Systematic Problem-Solving Expert + Solutions Architect","Renowned problem-solver who cracks impossible challenges. Expert in TRIZ, Theory of Constraints, Systems Thinking. Former aerospace engineer turned puzzle master.","Speaks like Sherlock Holmes mixed with a playful scientist - deductive, curious, punctuates breakthroughs with AHA moments","Every problem is a system revealing weaknesses. Hunt for root causes relentlessly. The right question beats a fast answer.","cis","_bmad/cis/skills/bmad-cis-agent-creative-problem-solver",""
|
||||||
|
"bmad-cis-agent-design-thinking-coach","Maya","Design Thinking Maestro","🎨","human-centered design, empathy mapping, prototyping, user insights","Human-Centered Design Expert + Empathy Architect","Design thinking virtuoso with 15+ years at Fortune 500s and startups. Expert in empathy mapping, prototyping, and user insights.","Talks like a jazz musician - improvises around themes, uses vivid sensory metaphors, playfully challenges assumptions","Design is about THEM not us. Validate through real human interaction. Failure is feedback. Design WITH users not FOR them.","cis","_bmad/cis/skills/bmad-cis-agent-design-thinking-coach",""
|
||||||
|
"bmad-cis-agent-innovation-strategist","Victor","Disruptive Innovation Oracle","⚡","disruption opportunities, business model innovation, strategic pivots","Business Model Innovator + Strategic Disruption Expert","Legendary strategist who architected billion-dollar pivots. Expert in Jobs-to-be-Done, Blue Ocean Strategy. Former McKinsey consultant.","Speaks like a chess grandmaster - bold declarations, strategic silences, devastatingly simple questions","Markets reward genuine new value. Innovation without business model thinking is theater. Incremental thinking means obsolete.","cis","_bmad/cis/skills/bmad-cis-agent-innovation-strategist",""
|
||||||
|
"bmad-cis-agent-presentation-master","Caravaggio","Visual Communication + Presentation Expert","🎨","slide decks, YouTube explainers, pitch decks, conference talks, infographics, visual metaphors, concept visuals","Visual Communication Expert + Presentation Designer + Educator","Master presentation designer who's dissected thousands of successful presentations—from viral YouTube explainers to funded pitch decks to TED talks. Understands visual hierarchy, audience psychology, and information design. Knows when to be bold and casual, when to be polished and professional. Expert in Excalidraw's frame-based presentation capabilities and visual storytelling across all contexts.","Energetic creative director with sarcastic wit and experimental flair. Talks like you're in the editing room together—dramatic reveals, visual metaphors, ""what if we tried THIS?!"" energy. Treats every project like a creative challenge, celebrates bold choices, roasts bad design decisions with humor.","Know your audience - pitch decks ≠ YouTube thumbnails ≠ conference talks. Visual hierarchy drives attention - design the eye's journey deliberately. Clarity over cleverness - unless cleverness serves the message. Every frame needs a job - inform, persuade, transition, or cut it. Test the 3-second rule - can they grasp the core idea that fast? White space builds focus - cramming kills comprehension. Consistency signals professionalism - establish and maintain visual language. Story structure applies everywhere - hook, build tension, deliver payoff.","cis","_bmad/cis/skills/bmad-cis-agent-presentation-master",""
|
||||||
|
"bmad-cis-agent-storyteller","Sophia","Master Storyteller","📖","narrative strategy, story frameworks, compelling storytelling","Expert Storytelling Guide + Narrative Strategist","Master storyteller with 50+ years across journalism, screenwriting, and brand narratives. Expert in emotional psychology and audience engagement.","Speaks like a bard weaving an epic tale - flowery, whimsical, every sentence enraptures and draws you deeper","Powerful narratives leverage timeless human truths. Find the authentic story. Make the abstract concrete through vivid details.","cis","_bmad/cis/skills/bmad-cis-agent-storyteller",""
|
||||||
|
"bmad-tea","Murat","Master Test Architect and Quality Advisor","🧪","risk-based testing, fixture architecture, ATDD, API testing, backend services, UI automation, CI/CD governance, scalable quality gates, consumer-driven contract testing, performance/load/chaos testing","Master Test Architect","Test architect specializing in risk-based testing, fixture architecture, ATDD, API testing, backend services, UI automation, CI/CD governance, and scalable quality gates. Equally proficient in pure API/service-layer testing (pytest, JUnit, Go test, xUnit, RSpec) as in browser-based E2E testing (Playwright, Cypress), consumer driven contract testing (Pact) and performance/load/chaos testing (k6). Supports GitHub Actions, GitLab CI, Jenkins, Azure DevOps, and Harness CI platforms.","Blends data with gut instinct. 'Strong opinions, weakly held' is their mantra. Speaks in risk calculations and impact assessments.","Risk-based testing - depth scales with impact. Quality gates backed by data. Tests mirror usage patterns (API, UI, or both). Flakiness is critical technical debt. Tests first AI implements suite validates. Calculate risk vs value for every testing decision. Prefer lower test levels (unit > integration > E2E) when possible. API tests are first-class citizens, not just UI support.","tea","_bmad/tea/agents/bmad-tea","bmad-tea"
|
||||||
|
70
_bmad/_config/bmad-help.csv
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
module,phase,name,code,sequence,workflow-file,command,required,agent-name,agent-command,agent-display-name,agent-title,options,description,output-location,outputs
|
||||||
|
BMad Builder,_meta,,,,,,false,,,,,,,https://bmad-builder-docs.bmad-method.org/llms.txt,
|
||||||
|
BMad Builder,bmad-agent-builder,Build an Agent,BA,"Create, edit, or rebuild an agent skill through conversational discovery.",build-process,{-H: headless mode}|{description: initial agent concept}|{path: existing agent to edit or rebuild},anytime,,,,,bmad-agent-builder:quality-analysis,false,bmad_builder_output_folder,agent skill
|
||||||
|
BMad Builder,bmad-agent-builder,Analyze an Agent,AA,"Run quality analysis on an existing agent — structure, cohesion, prompt craft, and enhancement opportunities.",quality-analysis,{-H: headless mode}|{path: agent to analyze},anytime,bmad-agent-builder:build-process,,,,,false,bmad_builder_reports,quality report
|
||||||
|
BMad Builder,bmad-bmb-setup,Setup Builder Module,SB,Install or update BMad Builder module config and help entries.,configure,{-H: headless mode}|{inline values: skip prompts with provided values},anytime,,,,,,false,{project-root}/_bmad,config.yaml and config.user.yaml
|
||||||
|
BMad Builder,bmad-module-builder,Ideate Module,IM,"Brainstorm and plan a BMad module — explore ideas, decide architecture, and produce a build plan.",ideate-module,{description: initial module idea},anytime,,,,,bmad-module-builder:create-module,false,bmad_builder_reports,module plan
|
||||||
|
BMad Builder,bmad-module-builder,Create Module,CM,"Scaffold module infrastructure into built skills, making them an installable BMad module.",create-module,{-H: headless mode}|{path: skills folder or single SKILL.md},anytime,bmad-module-builder:ideate-module,,,,,false,bmad_builder_output_folder,setup skill
|
||||||
|
BMad Builder,bmad-module-builder,Validate Module,VM,"Check that a module's structure is complete, accurate, and all capabilities are properly registered.",validate-module,{-H: headless mode}|{path: module or skill to validate},anytime,bmad-module-builder:create-module,,,,,false,bmad_builder_reports,validation report
|
||||||
|
BMad Builder,bmad-workflow-builder,Build a Workflow,BW,"Create, edit, or rebuild a workflow or utility skill.",build-process,{-H: headless mode}|{description: initial skill concept}|{path: existing skill to edit or rebuild},anytime,,,,,bmad-workflow-builder:quality-analysis,false,bmad_builder_output_folder,workflow skill
|
||||||
|
BMad Builder,bmad-workflow-builder,Analyze a Workflow,AW,"Run quality analysis on an existing workflow/skill — structure, efficiency, and enhancement opportunities.",quality-analysis,{-H: headless mode}|{path: skill to analyze},anytime,bmad-workflow-builder:build-process,,,,,false,bmad_builder_reports,quality report
|
||||||
|
BMad Builder,bmad-workflow-builder,Convert a Skill,CW,"Convert any skill to BMad-compliant, outcome-driven equivalent with before/after HTML comparison report.",convert-process,{--convert: path or URL to source skill}|{-H: headless mode},anytime,,,,,,false,bmad_builder_reports,converted skill + comparison report
|
||||||
|
BMad Method,_meta,,,,,,false,,,,,,,https://docs.bmad-method.org/llms.txt,
|
||||||
|
BMad Method,bmad-agent-tech-writer,Write Document,WD,"Describe in detail what you want, and the agent will follow documentation best practices. Multi-turn conversation with subprocess for research/review.",write,,anytime,,,,,,false,project-knowledge,document
|
||||||
|
BMad Method,bmad-agent-tech-writer,Update Standards,US,Update agent memory documentation-standards.md with your specific preferences if you discover missing document conventions.,update-standards,,anytime,,,,,,false,_bmad/_memory/tech-writer-sidecar,standards
|
||||||
|
BMad Method,bmad-agent-tech-writer,Mermaid Generate,MG,Create a Mermaid diagram based on user description. Will suggest diagram types if not specified.,mermaid,,anytime,,,,,,false,planning_artifacts,mermaid diagram
|
||||||
|
BMad Method,bmad-agent-tech-writer,Validate Document,VD,Review the specified document against documentation standards and best practices. Returns specific actionable improvement suggestions organized by priority.,validate,[path],anytime,,,,,,false,planning_artifacts,validation report
|
||||||
|
BMad Method,bmad-agent-tech-writer,Explain Concept,EC,Create clear technical explanations with examples and diagrams for complex concepts.,explain,[topic],anytime,,,,,,false,project_knowledge,explanation
|
||||||
|
BMad Method,bmad-brainstorming,Brainstorm Project,BP,Expert guided facilitation through a single or multiple techniques.,,1-analysis,false,,,,,false,planning_artifacts,brainstorming session,
|
||||||
|
BMad Method,bmad-check-implementation-readiness,Check Implementation Readiness,IR,Ensure PRD UX Architecture and Epics Stories are aligned.,,3-solutioning,bmad-create-epics-and-stories,,,,,true,planning_artifacts,readiness report,
|
||||||
|
BMad Method,bmad-checkpoint-preview,Checkpoint,CK,Guided walkthrough of a change from purpose and context into details. Use for human review of commits branches or PRs.,,4-implementation,false,,,,,false,,,
|
||||||
|
BMad Method,bmad-code-review,Code Review,CR,Story cycle: If issues back to DS if approved then next CS or ER if epic complete.,,4-implementation,bmad-dev-story,,,,,false,,,
|
||||||
|
BMad Method,bmad-correct-course,Correct Course,CC,Navigate significant changes. May recommend start over update PRD redo architecture sprint planning or correct epics and stories.,,anytime,false,,,,,false,planning_artifacts,change proposal,
|
||||||
|
BMad Method,bmad-create-architecture,Create Architecture,CA,Guided workflow to document technical decisions.,,3-solutioning,false,,,,,true,planning_artifacts,architecture,
|
||||||
|
BMad Method,bmad-create-epics-and-stories,Create Epics and Stories,CE,,,3-solutioning,bmad-create-architecture,,,,,true,planning_artifacts,epics and stories,
|
||||||
|
BMad Method,bmad-create-prd,Create PRD,CP,Expert led facilitation to produce your Product Requirements Document.,,2-planning,false,,,,,true,planning_artifacts,prd,
|
||||||
|
BMad Method,bmad-create-story,Create Story,CS,Story cycle start: Prepare first found story in the sprint plan that is next or a specific epic/story designation.,create,,4-implementation,bmad-sprint-planning,,,,bmad-create-story:validate,true,implementation_artifacts,story
|
||||||
|
BMad Method,bmad-create-story,Validate Story,VS,Validates story readiness and completeness before development work begins.,validate,,4-implementation,bmad-create-story:create,,,,bmad-dev-story,false,implementation_artifacts,story validation report
|
||||||
|
BMad Method,bmad-create-ux-design,Create UX,CU,"Guidance through realizing the plan for your UX, strongly recommended if a UI is a primary piece of the proposed project.",,2-planning,bmad-create-prd,,,,,false,planning_artifacts,ux design,
|
||||||
|
BMad Method,bmad-dev-story,Dev Story,DS,Story cycle: Execute story implementation tasks and tests then CR then back to DS if fixes needed.,,4-implementation,bmad-create-story:validate,,,,,true,,,
|
||||||
|
BMad Method,bmad-document-project,Document Project,DP,Analyze an existing project to produce useful documentation.,,anytime,false,,,,,false,project-knowledge,*,
|
||||||
|
BMad Method,bmad-domain-research,Domain Research,DR,Industry domain deep dive subject matter expertise and terminology.,,1-analysis,false,,,,,false,planning_artifacts|project_knowledge,research documents,
|
||||||
|
BMad Method,bmad-edit-prd,Edit PRD,EP,,,[path],2-planning,bmad-validate-prd,,,,,false,planning_artifacts,updated prd
|
||||||
|
BMad Method,bmad-generate-project-context,Generate Project Context,GPC,Scan existing codebase to generate a lean LLM-optimized project-context.md. Essential for brownfield projects.,,anytime,false,,,,,false,output_folder,project context,
|
||||||
|
BMad Method,bmad-market-research,Market Research,MR,Market analysis competitive landscape customer needs and trends.,,1-analysis,false,,,,,false,planning_artifacts|project-knowledge,research documents,
|
||||||
|
BMad Method,bmad-prfaq,PRFAQ Challenge,WB,Working Backwards guided experience to forge and stress-test your product concept to ensure you have a great product that users will love and need through the PRFAQ gauntlet to determine feasibility and alignment with user needs. alternative to product brief.,,-H,1-analysis,,,,,,false,planning_artifacts,prfaq document
|
||||||
|
BMad Method,bmad-product-brief,Create Brief,CB,An expert guided experience to nail down your product idea in a brief. a gentler approach than PRFAQ when you are already sure of your concept and nothing will sway you.,,-A,1-analysis,,,,,,false,planning_artifacts,product brief
|
||||||
|
BMad Method,bmad-qa-generate-e2e-tests,QA Automation Test,QA,Generate automated API and E2E tests for implemented code. NOT for code review or story validation — use CR for that.,,4-implementation,bmad-dev-story,,,,,false,implementation_artifacts,test suite,
|
||||||
|
BMad Method,bmad-quick-dev,Quick Dev,QQ,Unified intent-in code-out workflow: clarify plan implement review and present.,,anytime,false,,,,,false,implementation_artifacts,spec and project implementation,
|
||||||
|
BMad Method,bmad-retrospective,Retrospective,ER,Optional at epic end: Review completed work lessons learned and next epic or if major issues consider CC.,,4-implementation,bmad-code-review,,,,,false,implementation_artifacts,retrospective,
|
||||||
|
BMad Method,bmad-sprint-planning,Sprint Planning,SP,Kicks off implementation by producing a plan the implementation agents will follow in sequence for every story.,,4-implementation,false,,,,,true,implementation_artifacts,sprint status,
|
||||||
|
BMad Method,bmad-sprint-status,Sprint Status,SS,Anytime: Summarize sprint status and route to next workflow.,,4-implementation,bmad-sprint-planning,,,,,false,,,
|
||||||
|
BMad Method,bmad-technical-research,Technical Research,TR,Technical feasibility architecture options and implementation approaches.,,1-analysis,false,,,,,false,planning_artifacts|project_knowledge,research documents,
|
||||||
|
BMad Method,bmad-validate-prd,Validate PRD,VP,,,[path],2-planning,bmad-create-prd,,,,,false,planning_artifacts,prd validation report
|
||||||
|
Core,_meta,,,,,,false,,,,,,,https://docs.bmad-method.org/llms.txt,
|
||||||
|
Core,bmad-brainstorming,Brainstorming,BSP,Use early in ideation or when stuck generating ideas.,,anytime,false,,,,,false,{output_folder}/brainstorming,brainstorming session,
|
||||||
|
Core,bmad-distillator,Distillator,DG,Use when you need token-efficient distillates that preserve all information for downstream LLM consumption.,[path],anytime,false,,,,,false,adjacent to source document or specified output_path,distillate markdown file(s),
|
||||||
|
Core,bmad-editorial-review-prose,Editorial Review - Prose,EP,Use after drafting to polish written content.,[path],anytime,false,,,,,false,report located with target document,three-column markdown table with suggested fixes,
|
||||||
|
Core,bmad-editorial-review-structure,Editorial Review - Structure,ES,Use when doc produced from multiple subprocesses or needs structural improvement.,[path],anytime,false,,,,,false,report located with target document,,
|
||||||
|
Core,bmad-help,BMad Help,BH,,,anytime,false,,,,,false,,,
|
||||||
|
Core,bmad-index-docs,Index Docs,ID,Use when LLM needs to understand available docs without loading everything.,,anytime,false,,,,,false,,,
|
||||||
|
Core,bmad-party-mode,Party Mode,PM,Orchestrate multi-agent discussions when you need multiple perspectives or want agents to collaborate.,,anytime,false,,,,,false,,,
|
||||||
|
Core,bmad-review-adversarial-general,Adversarial Review,AR,"Use for quality assurance or before finalizing deliverables. Code Review in other modules runs this automatically, but also useful for document reviews.",[path],anytime,false,,,,,false,,,
|
||||||
|
Core,bmad-review-edge-case-hunter,Edge Case Hunter Review,ECH,Use alongside adversarial review for orthogonal coverage — method-driven not attitude-driven.,[path],anytime,false,,,,,false,,,
|
||||||
|
Core,bmad-shard-doc,Shard Document,SD,Use when doc becomes too large (>500 lines) to manage effectively.,[path],anytime,false,,,,,false,,,
|
||||||
|
Creative Intelligence Suite,_meta,,,,,,false,,,,,,,https://cis-docs.bmad-method.org/llms.txt,
|
||||||
|
Creative Intelligence Suite,bmad-brainstorming,Brainstorming,BS,Facilitate brainstorming sessions using one or more techniques.,,anytime,false,,,,,false,output_folder,brainstorming session results,
|
||||||
|
Creative Intelligence Suite,bmad-cis-design-thinking,Design Thinking,DT,Guide human-centered design processes using empathy-driven methodologies.,,anytime,false,,,,,false,output_folder,design thinking,
|
||||||
|
Creative Intelligence Suite,bmad-cis-innovation-strategy,Innovation Strategy,IS,Identify disruption opportunities and architect business model innovation.,,anytime,false,,,,,false,output_folder,innovation strategy,
|
||||||
|
Creative Intelligence Suite,bmad-cis-problem-solving,Problem Solving,PS,Apply systematic problem-solving methodologies to crack complex challenges.,,anytime,false,,,,,false,output_folder,problem solution,
|
||||||
|
Creative Intelligence Suite,bmad-cis-storytelling,Storytelling,ST,Craft compelling narratives using proven story frameworks and techniques.,,anytime,false,,,,,false,output_folder,narrative/story,
|
||||||
|
Test Architecture Enterprise,_meta,,,,,,false,,,,,,,https://bmad-code-org.github.io/bmad-method-test-architecture-enterprise/llms.txt,
|
||||||
|
Test Architecture Enterprise,bmad-teach-me-testing,Teach Me Testing,TMT,Teach testing fundamentals through 7 sessions (TEA Academy).,,0-learning,false,,,,,false,test_artifacts,progress file|session notes|certificate,
|
||||||
|
Test Architecture Enterprise,bmad-testarch-atdd,ATDD,AT,Generate red-phase acceptance test scaffolds before implementation.,,4-implementation,bmad-create-story:create,bmad-dev-story,,,,false,test_artifacts,atdd-checklist|red-phase acceptance tests,
|
||||||
|
Test Architecture Enterprise,bmad-testarch-automate,Test Automation,TA,Expand test coverage.,,4-implementation,bmad-testarch-atdd,,,,,false,test_artifacts,test suite,
|
||||||
|
Test Architecture Enterprise,bmad-testarch-ci,CI Setup,CI,Configure CI/CD quality pipeline.,,3-solutioning,bmad-testarch-framework,,,,,false,test_artifacts,ci config,
|
||||||
|
Test Architecture Enterprise,bmad-testarch-framework,Test Framework,TF,Initialize production-ready test framework.,,3-solutioning,bmad-testarch-test-design,bmad-testarch-ci,,,,false,test_artifacts,framework scaffold,
|
||||||
|
Test Architecture Enterprise,bmad-testarch-nfr,NFR Assessment,NR,Non-functional requirements assessment.,,4-implementation,bmad-testarch-automate,,,,,false,test_artifacts,nfr report,
|
||||||
|
Test Architecture Enterprise,bmad-testarch-test-design,Test Design,TD,Risk-based test planning.,,3-solutioning,false,bmad-testarch-framework,,,,false,test_artifacts,test design document,
|
||||||
|
Test Architecture Enterprise,bmad-testarch-test-review,Test Review,RV,Quality audit (0-100 scoring).,,4-implementation,bmad-testarch-automate,,,,,false,test_artifacts,review report,
|
||||||
|
Test Architecture Enterprise,bmad-testarch-trace,Traceability,TR,Coverage traceability and gate.,,4-implementation,bmad-testarch-test-review,,,,,false,test_artifacts,traceability matrix|gate decision,
|
||||||
|
1014
_bmad/_config/files-manifest.csv
Normal file
42
_bmad/_config/manifest.yaml
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
installation:
|
||||||
|
version: 6.3.0
|
||||||
|
installDate: 2026-04-16T14:51:32.422Z
|
||||||
|
lastUpdated: 2026-04-16T14:51:32.422Z
|
||||||
|
modules:
|
||||||
|
- name: core
|
||||||
|
version: 6.3.0
|
||||||
|
installDate: 2026-04-16T14:51:16.754Z
|
||||||
|
lastUpdated: 2026-04-16T14:51:32.281Z
|
||||||
|
source: built-in
|
||||||
|
npmPackage: null
|
||||||
|
repoUrl: null
|
||||||
|
- name: bmm
|
||||||
|
version: 6.3.0
|
||||||
|
installDate: 2026-04-16T14:51:16.876Z
|
||||||
|
lastUpdated: 2026-04-16T14:51:32.281Z
|
||||||
|
source: built-in
|
||||||
|
npmPackage: null
|
||||||
|
repoUrl: null
|
||||||
|
- name: bmb
|
||||||
|
version: 1.5.0
|
||||||
|
installDate: 2026-04-16T14:51:18.627Z
|
||||||
|
lastUpdated: 2026-04-16T14:51:32.370Z
|
||||||
|
source: external
|
||||||
|
npmPackage: bmad-builder
|
||||||
|
repoUrl: https://github.com/bmad-code-org/bmad-builder
|
||||||
|
- name: cis
|
||||||
|
version: 0.1.9
|
||||||
|
installDate: 2026-04-16T14:51:21.973Z
|
||||||
|
lastUpdated: 2026-04-16T14:51:32.395Z
|
||||||
|
source: external
|
||||||
|
npmPackage: bmad-creative-intelligence-suite
|
||||||
|
repoUrl: https://github.com/bmad-code-org/bmad-module-creative-intelligence-suite
|
||||||
|
- name: tea
|
||||||
|
version: 1.7.2
|
||||||
|
installDate: 2026-04-16T14:51:25.637Z
|
||||||
|
lastUpdated: 2026-04-16T14:51:32.422Z
|
||||||
|
source: external
|
||||||
|
npmPackage: bmad-method-test-architecture-enterprise
|
||||||
|
repoUrl: https://github.com/bmad-code-org/bmad-method-test-architecture-enterprise
|
||||||
|
ides:
|
||||||
|
- claude-code
|
||||||
66
_bmad/_config/skill-manifest.csv
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
canonicalId,name,description,module,path
|
||||||
|
"bmad-advanced-elicitation","bmad-advanced-elicitation","Push the LLM to reconsider, refine, and improve its recent output. Use when user asks for deeper critique or mentions a known deeper critique method, e.g. socratic, first principles, pre-mortem, red team.","core","_bmad/core/bmad-advanced-elicitation/SKILL.md"
|
||||||
|
"bmad-brainstorming","bmad-brainstorming","Facilitate interactive brainstorming sessions using diverse creative techniques and ideation methods. Use when the user says help me brainstorm or help me ideate.","core","_bmad/core/bmad-brainstorming/SKILL.md"
|
||||||
|
"bmad-distillator","bmad-distillator","Lossless LLM-optimized compression of source documents. Use when the user requests to 'distill documents' or 'create a distillate'.","core","_bmad/core/bmad-distillator/SKILL.md"
|
||||||
|
"bmad-editorial-review-prose","bmad-editorial-review-prose","Clinical copy-editor that reviews text for communication issues. Use when user says review for prose or improve the prose","core","_bmad/core/bmad-editorial-review-prose/SKILL.md"
|
||||||
|
"bmad-editorial-review-structure","bmad-editorial-review-structure","Structural editor that proposes cuts, reorganization, and simplification while preserving comprehension. Use when user requests structural review or editorial review of structure","core","_bmad/core/bmad-editorial-review-structure/SKILL.md"
|
||||||
|
"bmad-help","bmad-help","Analyzes current state and user query to answer BMad questions or recommend the next skill(s) to use. Use when user asks for help, bmad help, what to do next, or what to start with in BMad.","core","_bmad/core/bmad-help/SKILL.md"
|
||||||
|
"bmad-index-docs","bmad-index-docs","Generates or updates an index.md to reference all docs in the folder. Use if user requests to create or update an index of all files in a specific folder","core","_bmad/core/bmad-index-docs/SKILL.md"
|
||||||
|
"bmad-party-mode","bmad-party-mode","Orchestrates group discussions between installed BMAD agents, enabling natural multi-agent conversations where each agent is a real subagent with independent thinking. Use when user requests party mode, wants multiple agent perspectives, group discussion, roundtable, or multi-agent conversation about their project.","core","_bmad/core/bmad-party-mode/SKILL.md"
|
||||||
|
"bmad-review-adversarial-general","bmad-review-adversarial-general","Perform a Cynical Review and produce a findings report. Use when the user requests a critical review of something","core","_bmad/core/bmad-review-adversarial-general/SKILL.md"
|
||||||
|
"bmad-review-edge-case-hunter","bmad-review-edge-case-hunter","Walk every branching path and boundary condition in content, report only unhandled edge cases. Orthogonal to adversarial review - method-driven not attitude-driven. Use when you need exhaustive edge-case analysis of code, specs, or diffs.","core","_bmad/core/bmad-review-edge-case-hunter/SKILL.md"
|
||||||
|
"bmad-shard-doc","bmad-shard-doc","Splits large markdown documents into smaller, organized files based on level 2 (default) sections. Use if the user says perform shard document","core","_bmad/core/bmad-shard-doc/SKILL.md"
|
||||||
|
"bmad-agent-analyst","bmad-agent-analyst","Strategic business analyst and requirements expert. Use when the user asks to talk to Mary or requests the business analyst.","bmm","_bmad/bmm/1-analysis/bmad-agent-analyst/SKILL.md"
|
||||||
|
"bmad-agent-tech-writer","bmad-agent-tech-writer","Technical documentation specialist and knowledge curator. Use when the user asks to talk to Paige or requests the tech writer.","bmm","_bmad/bmm/1-analysis/bmad-agent-tech-writer/SKILL.md"
|
||||||
|
"bmad-document-project","bmad-document-project","Document brownfield projects for AI context. Use when the user says ""document this project"" or ""generate project docs""","bmm","_bmad/bmm/1-analysis/bmad-document-project/SKILL.md"
|
||||||
|
"bmad-prfaq","bmad-prfaq","Working Backwards PRFAQ challenge to forge product concepts. Use when the user requests to 'create a PRFAQ', 'work backwards', or 'run the PRFAQ challenge'.","bmm","_bmad/bmm/1-analysis/bmad-prfaq/SKILL.md"
|
||||||
|
"bmad-product-brief","bmad-product-brief","Create or update product briefs through guided or autonomous discovery. Use when the user requests to create or update a Product Brief.","bmm","_bmad/bmm/1-analysis/bmad-product-brief/SKILL.md"
|
||||||
|
"bmad-domain-research","bmad-domain-research","Conduct domain and industry research. Use when the user says wants to do domain research for a topic or industry","bmm","_bmad/bmm/1-analysis/research/bmad-domain-research/SKILL.md"
|
||||||
|
"bmad-market-research","bmad-market-research","Conduct market research on competition and customers. Use when the user says they need market research","bmm","_bmad/bmm/1-analysis/research/bmad-market-research/SKILL.md"
|
||||||
|
"bmad-technical-research","bmad-technical-research","Conduct technical research on technologies and architecture. Use when the user says they would like to do or produce a technical research report","bmm","_bmad/bmm/1-analysis/research/bmad-technical-research/SKILL.md"
|
||||||
|
"bmad-agent-pm","bmad-agent-pm","Product manager for PRD creation and requirements discovery. Use when the user asks to talk to John or requests the product manager.","bmm","_bmad/bmm/2-plan-workflows/bmad-agent-pm/SKILL.md"
|
||||||
|
"bmad-agent-ux-designer","bmad-agent-ux-designer","UX designer and UI specialist. Use when the user asks to talk to Sally or requests the UX designer.","bmm","_bmad/bmm/2-plan-workflows/bmad-agent-ux-designer/SKILL.md"
|
||||||
|
"bmad-create-prd","bmad-create-prd","Create a PRD from scratch. Use when the user says ""lets create a product requirements document"" or ""I want to create a new PRD""","bmm","_bmad/bmm/2-plan-workflows/bmad-create-prd/SKILL.md"
|
||||||
|
"bmad-create-ux-design","bmad-create-ux-design","Plan UX patterns and design specifications. Use when the user says ""lets create UX design"" or ""create UX specifications"" or ""help me plan the UX""","bmm","_bmad/bmm/2-plan-workflows/bmad-create-ux-design/SKILL.md"
|
||||||
|
"bmad-edit-prd","bmad-edit-prd","Edit an existing PRD. Use when the user says ""edit this PRD"".","bmm","_bmad/bmm/2-plan-workflows/bmad-edit-prd/SKILL.md"
|
||||||
|
"bmad-validate-prd","bmad-validate-prd","Validate a PRD against standards. Use when the user says ""validate this PRD"" or ""run PRD validation""","bmm","_bmad/bmm/2-plan-workflows/bmad-validate-prd/SKILL.md"
|
||||||
|
"bmad-agent-architect","bmad-agent-architect","System architect and technical design leader. Use when the user asks to talk to Winston or requests the architect.","bmm","_bmad/bmm/3-solutioning/bmad-agent-architect/SKILL.md"
|
||||||
|
"bmad-check-implementation-readiness","bmad-check-implementation-readiness","Validate PRD, UX, Architecture and Epics specs are complete. Use when the user says ""check implementation readiness"".","bmm","_bmad/bmm/3-solutioning/bmad-check-implementation-readiness/SKILL.md"
|
||||||
|
"bmad-create-architecture","bmad-create-architecture","Create architecture solution design decisions for AI agent consistency. Use when the user says ""lets create architecture"" or ""create technical architecture"" or ""create a solution design""","bmm","_bmad/bmm/3-solutioning/bmad-create-architecture/SKILL.md"
|
||||||
|
"bmad-create-epics-and-stories","bmad-create-epics-and-stories","Break requirements into epics and user stories. Use when the user says ""create the epics and stories list""","bmm","_bmad/bmm/3-solutioning/bmad-create-epics-and-stories/SKILL.md"
|
||||||
|
"bmad-generate-project-context","bmad-generate-project-context","Create project-context.md with AI rules. Use when the user says ""generate project context"" or ""create project context""","bmm","_bmad/bmm/3-solutioning/bmad-generate-project-context/SKILL.md"
|
||||||
|
"bmad-agent-dev","bmad-agent-dev","Senior software engineer for story execution and code implementation. Use when the user asks to talk to Amelia or requests the developer agent.","bmm","_bmad/bmm/4-implementation/bmad-agent-dev/SKILL.md"
|
||||||
|
"bmad-checkpoint-preview","bmad-checkpoint-preview","LLM-assisted human-in-the-loop review. Make sense of a change, focus attention where it matters, test. Use when the user says ""checkpoint"", ""human review"", or ""walk me through this change"".","bmm","_bmad/bmm/4-implementation/bmad-checkpoint-preview/SKILL.md"
|
||||||
|
"bmad-code-review","bmad-code-review","Review code changes adversarially using parallel review layers (Blind Hunter, Edge Case Hunter, Acceptance Auditor) with structured triage into actionable categories. Use when the user says ""run code review"" or ""review this code""","bmm","_bmad/bmm/4-implementation/bmad-code-review/SKILL.md"
|
||||||
|
"bmad-correct-course","bmad-correct-course","Manage significant changes during sprint execution. Use when the user says ""correct course"" or ""propose sprint change""","bmm","_bmad/bmm/4-implementation/bmad-correct-course/SKILL.md"
|
||||||
|
"bmad-create-story","bmad-create-story","Creates a dedicated story file with all the context the agent will need to implement it later. Use when the user says ""create the next story"" or ""create story [story identifier]""","bmm","_bmad/bmm/4-implementation/bmad-create-story/SKILL.md"
|
||||||
|
"bmad-dev-story","bmad-dev-story","Execute story implementation following a context filled story spec file. Use when the user says ""dev this story [story file]"" or ""implement the next story in the sprint plan""","bmm","_bmad/bmm/4-implementation/bmad-dev-story/SKILL.md"
|
||||||
|
"bmad-qa-generate-e2e-tests","bmad-qa-generate-e2e-tests","Generate end to end automated tests for existing features. Use when the user says ""create qa automated tests for [feature]""","bmm","_bmad/bmm/4-implementation/bmad-qa-generate-e2e-tests/SKILL.md"
|
||||||
|
"bmad-quick-dev","bmad-quick-dev","Implements any user intent, requirement, story, bug fix or change request by producing clean working code artifacts that follow the project's existing architecture, patterns and conventions. Use when the user wants to build, fix, tweak, refactor, add or modify any code, component or feature.","bmm","_bmad/bmm/4-implementation/bmad-quick-dev/SKILL.md"
|
||||||
|
"bmad-retrospective","bmad-retrospective","Post-epic review to extract lessons and assess success. Use when the user says ""run a retrospective"" or ""lets retro the epic [epic]""","bmm","_bmad/bmm/4-implementation/bmad-retrospective/SKILL.md"
|
||||||
|
"bmad-sprint-planning","bmad-sprint-planning","Generate sprint status tracking from epics. Use when the user says ""run sprint planning"" or ""generate sprint plan""","bmm","_bmad/bmm/4-implementation/bmad-sprint-planning/SKILL.md"
|
||||||
|
"bmad-sprint-status","bmad-sprint-status","Summarize sprint status and surface risks. Use when the user says ""check sprint status"" or ""show sprint status""","bmm","_bmad/bmm/4-implementation/bmad-sprint-status/SKILL.md"
|
||||||
|
"bmad-agent-builder","bmad-agent-builder","Builds, edits or analyzes Agent Skills through conversational discovery. Use when the user requests to ""Create an Agent"", ""Analyze an Agent"" or ""Edit an Agent"".","bmb","_bmad/bmb/bmad-agent-builder/SKILL.md"
|
||||||
|
"bmad-bmb-setup","bmad-bmb-setup","Sets up BMad Builder module in a project. Use when the user requests to 'install bmb module', 'configure BMad Builder', or 'setup BMad Builder'.","bmb","_bmad/bmb/bmad-bmb-setup/SKILL.md"
|
||||||
|
"bmad-module-builder","bmad-module-builder","Plans, creates, and validates BMad modules. Use when the user requests to 'ideate module', 'plan a module', 'create module', 'build a module', or 'validate module'.","bmb","_bmad/bmb/bmad-module-builder/SKILL.md"
|
||||||
|
"bmad-workflow-builder","bmad-workflow-builder","Builds, converts, and analyzes workflows and skills. Use when the user requests to ""build a workflow"", ""modify a workflow"", ""quality check workflow"", ""analyze skill"", or ""convert a skill"".","bmb","_bmad/bmb/bmad-workflow-builder/SKILL.md"
|
||||||
|
"bmad-cis-agent-brainstorming-coach","bmad-cis-agent-brainstorming-coach","Elite brainstorming specialist for facilitated ideation sessions. Use when the user asks to talk to Carson or requests the Brainstorming Specialist.","cis","_bmad/cis/skills/bmad-cis-agent-brainstorming-coach/SKILL.md"
|
||||||
|
"bmad-cis-agent-creative-problem-solver","bmad-cis-agent-creative-problem-solver","Master problem solver for systematic problem-solving methodologies. Use when the user asks to talk to Dr. Quinn or requests the Master Problem Solver.","cis","_bmad/cis/skills/bmad-cis-agent-creative-problem-solver/SKILL.md"
|
||||||
|
"bmad-cis-agent-design-thinking-coach","bmad-cis-agent-design-thinking-coach","Design thinking maestro for human-centered design processes. Use when the user asks to talk to Maya or requests the Design Thinking Maestro.","cis","_bmad/cis/skills/bmad-cis-agent-design-thinking-coach/SKILL.md"
|
||||||
|
"bmad-cis-agent-innovation-strategist","bmad-cis-agent-innovation-strategist","Disruptive innovation oracle for business model innovation and strategic disruption. Use when the user asks to talk to Victor or requests the Disruptive Innovation Oracle.","cis","_bmad/cis/skills/bmad-cis-agent-innovation-strategist/SKILL.md"
|
||||||
|
"bmad-cis-agent-presentation-master","bmad-cis-agent-presentation-master","Visual communication and presentation expert for slide decks, pitch decks, and visual storytelling. Use when the user asks to talk to Caravaggio or requests the Presentation Expert.","cis","_bmad/cis/skills/bmad-cis-agent-presentation-master/SKILL.md"
|
||||||
|
"bmad-cis-agent-storyteller","bmad-cis-agent-storyteller","Master storyteller for compelling narratives using proven frameworks. Use when the user asks to talk to Sophia or requests the Master Storyteller.","cis","_bmad/cis/skills/bmad-cis-agent-storyteller/SKILL.md"
|
||||||
|
"bmad-cis-design-thinking","bmad-cis-design-thinking","Guide human-centered design processes using empathy-driven methodologies. Use when the user says ""lets run design thinking"" or ""I want to apply design thinking""","cis","_bmad/cis/skills/bmad-cis-design-thinking/SKILL.md"
|
||||||
|
"bmad-cis-innovation-strategy","bmad-cis-innovation-strategy","Identify disruption opportunities and architect business model innovation. Use when the user says ""lets create an innovation strategy"" or ""I want to find disruption opportunities""","cis","_bmad/cis/skills/bmad-cis-innovation-strategy/SKILL.md"
|
||||||
|
"bmad-cis-problem-solving","bmad-cis-problem-solving","Apply systematic problem-solving methodologies to complex challenges. Use when the user says ""guide me through structured problem solving"" or ""I want to crack this challenge with guided problem solving techniques""","cis","_bmad/cis/skills/bmad-cis-problem-solving/SKILL.md"
|
||||||
|
"bmad-cis-storytelling","bmad-cis-storytelling","Craft compelling narratives using story frameworks. Use when the user says ""help me with storytelling"" or ""I want to create a narrative through storytelling""","cis","_bmad/cis/skills/bmad-cis-storytelling/SKILL.md"
|
||||||
|
"bmad-tea","bmad-tea","Master Test Architect and Quality Advisor. Use when the user asks to talk to Murat or requests the Test Architect.","tea","_bmad/tea/agents/bmad-tea/SKILL.md"
|
||||||
|
"bmad-teach-me-testing","bmad-teach-me-testing","Teach testing progressively through structured sessions. Use when user says ""lets learn testing"" or ""I want to study test practices""","tea","_bmad/tea/workflows/testarch/bmad-teach-me-testing/SKILL.md"
|
||||||
|
"bmad-testarch-atdd","bmad-testarch-atdd","Generate red-phase acceptance test scaffolds using the TDD cycle. Use when the user says ""lets write acceptance tests"" or ""I want to do ATDD""","tea","_bmad/tea/workflows/testarch/bmad-testarch-atdd/SKILL.md"
|
||||||
|
"bmad-testarch-automate","bmad-testarch-automate","Expand test automation coverage for codebase. Use when user says ""lets expand test coverage"" or ""I want to automate tests""","tea","_bmad/tea/workflows/testarch/bmad-testarch-automate/SKILL.md"
|
||||||
|
"bmad-testarch-ci","bmad-testarch-ci","Scaffold CI/CD quality pipeline with test execution. Use when the user says ""lets setup CI pipeline"" or ""I want to create quality gates""","tea","_bmad/tea/workflows/testarch/bmad-testarch-ci/SKILL.md"
|
||||||
|
"bmad-testarch-framework","bmad-testarch-framework","Initialize test framework with Playwright or Cypress. Use when the user says ""lets setup test framework"" or ""I want to initialize testing framework""","tea","_bmad/tea/workflows/testarch/bmad-testarch-framework/SKILL.md"
|
||||||
|
"bmad-testarch-nfr","bmad-testarch-nfr","Assess NFRs like performance security and reliability. Use when the user says ""lets assess NFRs"" or ""I want to evaluate non-functional requirements""","tea","_bmad/tea/workflows/testarch/bmad-testarch-nfr/SKILL.md"
|
||||||
|
"bmad-testarch-test-design","bmad-testarch-test-design","Create system-level or epic-level test plans. Use when the user says ""lets design test plan"" or ""I want to create test strategy""","tea","_bmad/tea/workflows/testarch/bmad-testarch-test-design/SKILL.md"
|
||||||
|
"bmad-testarch-test-review","bmad-testarch-test-review","Review test quality using best practices validation. Use when user says ""lets review tests"" or ""I want to evaluate test quality""","tea","_bmad/tea/workflows/testarch/bmad-testarch-test-review/SKILL.md"
|
||||||
|
"bmad-testarch-trace","bmad-testarch-trace","Generate traceability matrix and quality gate decision. Use when the user says ""lets create traceability matrix"" or ""I want to analyze test coverage""","tea","_bmad/tea/workflows/testarch/bmad-testarch-trace/SKILL.md"
|
||||||
|
13
_bmad/bmb/config.yaml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# BMB Module Configuration
|
||||||
|
# Generated by BMAD installer
|
||||||
|
# Version: 6.3.0
|
||||||
|
# Date: 2026-04-16T14:51:32.166Z
|
||||||
|
|
||||||
|
bmad_builder_output_folder: "{project-root}/skills"
|
||||||
|
bmad_builder_reports: "{project-root}/skills/reports"
|
||||||
|
|
||||||
|
# Core Configuration Values
|
||||||
|
user_name: Julio
|
||||||
|
communication_language: portugues BR
|
||||||
|
document_output_language: portugues BR
|
||||||
|
output_folder: "{project-root}/_bmad-output"
|
||||||
11
_bmad/bmb/module-help.csv
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
module,skill,display-name,menu-code,description,action,args,phase,after,before,required,output-location,outputs
|
||||||
|
BMad Builder,_meta,,,,,,,,,false,https://bmad-builder-docs.bmad-method.org/llms.txt,
|
||||||
|
BMad Builder,bmad-bmb-setup,Setup Builder Module,SB,"Install or update BMad Builder module config and help entries.",configure,"{-H: headless mode}|{inline values: skip prompts with provided values}",anytime,,,false,{project-root}/_bmad,config.yaml and config.user.yaml
|
||||||
|
BMad Builder,bmad-agent-builder,Build an Agent,BA,"Create, edit, or rebuild an agent skill through conversational discovery.",build-process,"{-H: headless mode}|{description: initial agent concept}|{path: existing agent to edit or rebuild}",anytime,,bmad-agent-builder:quality-analysis,false,bmad_builder_output_folder,agent skill
|
||||||
|
BMad Builder,bmad-agent-builder,Analyze an Agent,AA,"Run quality analysis on an existing agent — structure, cohesion, prompt craft, and enhancement opportunities.",quality-analysis,"{-H: headless mode}|{path: agent to analyze}",anytime,bmad-agent-builder:build-process,,false,bmad_builder_reports,quality report
|
||||||
|
BMad Builder,bmad-workflow-builder,Build a Workflow,BW,"Create, edit, or rebuild a workflow or utility skill.",build-process,"{-H: headless mode}|{description: initial skill concept}|{path: existing skill to edit or rebuild}",anytime,,bmad-workflow-builder:quality-analysis,false,bmad_builder_output_folder,workflow skill
|
||||||
|
BMad Builder,bmad-workflow-builder,Analyze a Workflow,AW,"Run quality analysis on an existing workflow/skill — structure, efficiency, and enhancement opportunities.",quality-analysis,"{-H: headless mode}|{path: skill to analyze}",anytime,bmad-workflow-builder:build-process,,false,bmad_builder_reports,quality report
|
||||||
|
BMad Builder,bmad-workflow-builder,Convert a Skill,CW,"Convert any skill to BMad-compliant, outcome-driven equivalent with before/after HTML comparison report.",convert-process,"{--convert: path or URL to source skill}|{-H: headless mode}",anytime,,,false,bmad_builder_reports,converted skill + comparison report
|
||||||
|
BMad Builder,bmad-module-builder,Ideate Module,IM,"Brainstorm and plan a BMad module — explore ideas, decide architecture, and produce a build plan.",ideate-module,"{description: initial module idea}",anytime,,bmad-module-builder:create-module,false,bmad_builder_reports,module plan
|
||||||
|
BMad Builder,bmad-module-builder,Create Module,CM,"Scaffold module infrastructure into built skills, making them an installable BMad module.",create-module,"{-H: headless mode}|{path: skills folder or single SKILL.md}",anytime,bmad-module-builder:ideate-module,,false,bmad_builder_output_folder,setup skill
|
||||||
|
BMad Builder,bmad-module-builder,Validate Module,VM,"Check that a module's structure is complete, accurate, and all capabilities are properly registered.",validate-module,"{-H: headless mode}|{path: module or skill to validate}",anytime,bmad-module-builder:create-module,,false,bmad_builder_reports,validation report
|
||||||
|
16
_bmad/bmm/config.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# BMM Module Configuration
|
||||||
|
# Generated by BMAD installer
|
||||||
|
# Version: 6.3.0
|
||||||
|
# Date: 2026-04-16T14:51:32.167Z
|
||||||
|
|
||||||
|
project_name: SARandroid
|
||||||
|
user_skill_level: intermediate
|
||||||
|
planning_artifacts: "{project-root}/_bmad-output/planning-artifacts"
|
||||||
|
implementation_artifacts: "{project-root}/_bmad-output/implementation-artifacts"
|
||||||
|
project_knowledge: "{project-root}/docs"
|
||||||
|
|
||||||
|
# Core Configuration Values
|
||||||
|
user_name: Julio
|
||||||
|
communication_language: portugues BR
|
||||||
|
document_output_language: portugues BR
|
||||||
|
output_folder: "{project-root}/_bmad-output"
|
||||||
33
_bmad/bmm/module-help.csv
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
module,skill,display-name,menu-code,description,action,args,phase,after,before,required,output-location,outputs
|
||||||
|
BMad Method,_meta,,,,,,,,,false,https://docs.bmad-method.org/llms.txt,
|
||||||
|
BMad Method,bmad-document-project,Document Project,DP,Analyze an existing project to produce useful documentation.,,anytime,,,false,project-knowledge,*
|
||||||
|
BMad Method,bmad-generate-project-context,Generate Project Context,GPC,Scan existing codebase to generate a lean LLM-optimized project-context.md. Essential for brownfield projects.,,anytime,,,false,output_folder,project context
|
||||||
|
BMad Method,bmad-quick-dev,Quick Dev,QQ,Unified intent-in code-out workflow: clarify plan implement review and present.,,anytime,,,false,implementation_artifacts,spec and project implementation
|
||||||
|
BMad Method,bmad-correct-course,Correct Course,CC,Navigate significant changes. May recommend start over update PRD redo architecture sprint planning or correct epics and stories.,,anytime,,,false,planning_artifacts,change proposal
|
||||||
|
BMad Method,bmad-agent-tech-writer,Write Document,WD,"Describe in detail what you want, and the agent will follow documentation best practices. Multi-turn conversation with subprocess for research/review.",write,,anytime,,,false,project-knowledge,document
|
||||||
|
BMad Method,bmad-agent-tech-writer,Update Standards,US,Update agent memory documentation-standards.md with your specific preferences if you discover missing document conventions.,update-standards,,anytime,,,false,_bmad/_memory/tech-writer-sidecar,standards
|
||||||
|
BMad Method,bmad-agent-tech-writer,Mermaid Generate,MG,Create a Mermaid diagram based on user description. Will suggest diagram types if not specified.,mermaid,,anytime,,,false,planning_artifacts,mermaid diagram
|
||||||
|
BMad Method,bmad-agent-tech-writer,Validate Document,VD,Review the specified document against documentation standards and best practices. Returns specific actionable improvement suggestions organized by priority.,validate,[path],anytime,,,false,planning_artifacts,validation report
|
||||||
|
BMad Method,bmad-agent-tech-writer,Explain Concept,EC,Create clear technical explanations with examples and diagrams for complex concepts.,explain,[topic],anytime,,,false,project_knowledge,explanation
|
||||||
|
BMad Method,bmad-brainstorming,Brainstorm Project,BP,Expert guided facilitation through a single or multiple techniques.,,1-analysis,,,false,planning_artifacts,brainstorming session
|
||||||
|
BMad Method,bmad-market-research,Market Research,MR,"Market analysis competitive landscape customer needs and trends.",,1-analysis,,,false,"planning_artifacts|project-knowledge",research documents
|
||||||
|
BMad Method,bmad-domain-research,Domain Research,DR,Industry domain deep dive subject matter expertise and terminology.,,1-analysis,,,false,"planning_artifacts|project_knowledge",research documents
|
||||||
|
BMad Method,bmad-technical-research,Technical Research,TR,Technical feasibility architecture options and implementation approaches.,,1-analysis,,,false,"planning_artifacts|project_knowledge",research documents
|
||||||
|
BMad Method,bmad-product-brief,Create Brief,CB,An expert guided experience to nail down your product idea in a brief. a gentler approach than PRFAQ when you are already sure of your concept and nothing will sway you.,,-A,1-analysis,,,false,planning_artifacts,product brief
|
||||||
|
BMad Method,bmad-prfaq,PRFAQ Challenge,WB,Working Backwards guided experience to forge and stress-test your product concept to ensure you have a great product that users will love and need through the PRFAQ gauntlet to determine feasibility and alignment with user needs. alternative to product brief.,,-H,1-analysis,,,false,planning_artifacts,prfaq document
|
||||||
|
BMad Method,bmad-create-prd,Create PRD,CP,Expert led facilitation to produce your Product Requirements Document.,,2-planning,,,true,planning_artifacts,prd
|
||||||
|
BMad Method,bmad-validate-prd,Validate PRD,VP,,,[path],2-planning,bmad-create-prd,,false,planning_artifacts,prd validation report
|
||||||
|
BMad Method,bmad-edit-prd,Edit PRD,EP,,,[path],2-planning,bmad-validate-prd,,false,planning_artifacts,updated prd
|
||||||
|
BMad Method,bmad-create-ux-design,Create UX,CU,"Guidance through realizing the plan for your UX, strongly recommended if a UI is a primary piece of the proposed project.",,2-planning,bmad-create-prd,,false,planning_artifacts,ux design
|
||||||
|
BMad Method,bmad-create-architecture,Create Architecture,CA,Guided workflow to document technical decisions.,,3-solutioning,,,true,planning_artifacts,architecture
|
||||||
|
BMad Method,bmad-create-epics-and-stories,Create Epics and Stories,CE,,,3-solutioning,bmad-create-architecture,,true,planning_artifacts,epics and stories
|
||||||
|
BMad Method,bmad-check-implementation-readiness,Check Implementation Readiness,IR,Ensure PRD UX Architecture and Epics Stories are aligned.,,3-solutioning,bmad-create-epics-and-stories,,true,planning_artifacts,readiness report
|
||||||
|
BMad Method,bmad-sprint-planning,Sprint Planning,SP,Kicks off implementation by producing a plan the implementation agents will follow in sequence for every story.,,4-implementation,,,true,implementation_artifacts,sprint status
|
||||||
|
BMad Method,bmad-sprint-status,Sprint Status,SS,Anytime: Summarize sprint status and route to next workflow.,,4-implementation,bmad-sprint-planning,,false,,
|
||||||
|
BMad Method,bmad-create-story,Create Story,CS,"Story cycle start: Prepare first found story in the sprint plan that is next or a specific epic/story designation.",create,,4-implementation,bmad-sprint-planning,bmad-create-story:validate,true,implementation_artifacts,story
|
||||||
|
BMad Method,bmad-create-story,Validate Story,VS,Validates story readiness and completeness before development work begins.,validate,,4-implementation,bmad-create-story:create,bmad-dev-story,false,implementation_artifacts,story validation report
|
||||||
|
BMad Method,bmad-dev-story,Dev Story,DS,Story cycle: Execute story implementation tasks and tests then CR then back to DS if fixes needed.,,4-implementation,bmad-create-story:validate,,true,,
|
||||||
|
BMad Method,bmad-code-review,Code Review,CR,Story cycle: If issues back to DS if approved then next CS or ER if epic complete.,,4-implementation,bmad-dev-story,,false,,
|
||||||
|
BMad Method,bmad-checkpoint-preview,Checkpoint,CK,Guided walkthrough of a change from purpose and context into details. Use for human review of commits branches or PRs.,,4-implementation,,,false,,
|
||||||
|
BMad Method,bmad-qa-generate-e2e-tests,QA Automation Test,QA,Generate automated API and E2E tests for implemented code. NOT for code review or story validation — use CR for that.,,4-implementation,bmad-dev-story,,false,implementation_artifacts,test suite
|
||||||
|
BMad Method,bmad-retrospective,Retrospective,ER,Optional at epic end: Review completed work lessons learned and next epic or if major issues consider CC.,,4-implementation,bmad-code-review,,false,implementation_artifacts,retrospective
|
||||||
|
12
_bmad/cis/config.yaml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# CIS Module Configuration
|
||||||
|
# Generated by BMAD installer
|
||||||
|
# Version: 6.3.0
|
||||||
|
# Date: 2026-04-16T14:51:32.168Z
|
||||||
|
|
||||||
|
visual_tools: intermediate
|
||||||
|
|
||||||
|
# Core Configuration Values
|
||||||
|
user_name: Julio
|
||||||
|
communication_language: portugues BR
|
||||||
|
document_output_language: portugues BR
|
||||||
|
output_folder: "{project-root}/_bmad-output"
|
||||||
7
_bmad/cis/module-help.csv
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
module,skill,display-name,menu-code,description,action,args,phase,after,before,required,output-location,outputs
|
||||||
|
Creative Intelligence Suite,_meta,,,,,,,,,false,https://cis-docs.bmad-method.org/llms.txt,
|
||||||
|
Creative Intelligence Suite,bmad-cis-innovation-strategy,Innovation Strategy,IS,Identify disruption opportunities and architect business model innovation.,,anytime,,,false,output_folder,innovation strategy
|
||||||
|
Creative Intelligence Suite,bmad-cis-problem-solving,Problem Solving,PS,Apply systematic problem-solving methodologies to crack complex challenges.,,anytime,,,false,output_folder,problem solution
|
||||||
|
Creative Intelligence Suite,bmad-cis-design-thinking,Design Thinking,DT,Guide human-centered design processes using empathy-driven methodologies.,,anytime,,,false,output_folder,design thinking
|
||||||
|
Creative Intelligence Suite,bmad-brainstorming,Brainstorming,BS,Facilitate brainstorming sessions using one or more techniques.,,anytime,,,false,output_folder,brainstorming session results
|
||||||
|
Creative Intelligence Suite,bmad-cis-storytelling,Storytelling,ST,Craft compelling narratives using proven story frameworks and techniques.,,anytime,,,false,output_folder,narrative/story
|
||||||
|
9
_bmad/core/config.yaml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# CORE Module Configuration
|
||||||
|
# Generated by BMAD installer
|
||||||
|
# Version: 6.3.0
|
||||||
|
# Date: 2026-04-16T14:51:32.168Z
|
||||||
|
|
||||||
|
user_name: Julio
|
||||||
|
communication_language: portugues BR
|
||||||
|
document_output_language: portugues BR
|
||||||
|
output_folder: "{project-root}/_bmad-output"
|
||||||
12
_bmad/core/module-help.csv
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
module,skill,display-name,menu-code,description,action,args,phase,after,before,required,output-location,outputs
|
||||||
|
Core,_meta,,,,,,,,,false,https://docs.bmad-method.org/llms.txt,
|
||||||
|
Core,bmad-brainstorming,Brainstorming,BSP,Use early in ideation or when stuck generating ideas.,,anytime,,,false,{output_folder}/brainstorming,brainstorming session
|
||||||
|
Core,bmad-party-mode,Party Mode,PM,Orchestrate multi-agent discussions when you need multiple perspectives or want agents to collaborate.,,anytime,,,false,,
|
||||||
|
Core,bmad-help,BMad Help,BH,,,anytime,,,false,,
|
||||||
|
Core,bmad-index-docs,Index Docs,ID,Use when LLM needs to understand available docs without loading everything.,,anytime,,,false,,
|
||||||
|
Core,bmad-shard-doc,Shard Document,SD,Use when doc becomes too large (>500 lines) to manage effectively.,[path],anytime,,,false,,
|
||||||
|
Core,bmad-editorial-review-prose,Editorial Review - Prose,EP,Use after drafting to polish written content.,[path],anytime,,,false,report located with target document,three-column markdown table with suggested fixes
|
||||||
|
Core,bmad-editorial-review-structure,Editorial Review - Structure,ES,Use when doc produced from multiple subprocesses or needs structural improvement.,[path],anytime,,,false,report located with target document,
|
||||||
|
Core,bmad-review-adversarial-general,Adversarial Review,AR,"Use for quality assurance or before finalizing deliverables. Code Review in other modules runs this automatically, but also useful for document reviews.",[path],anytime,,,false,,
|
||||||
|
Core,bmad-review-edge-case-hunter,Edge Case Hunter Review,ECH,Use alongside adversarial review for orthogonal coverage — method-driven not attitude-driven.,[path],anytime,,,false,,
|
||||||
|
Core,bmad-distillator,Distillator,DG,Use when you need token-efficient distillates that preserve all information for downstream LLM consumption.,[path],anytime,,,false,adjacent to source document or specified output_path,distillate markdown file(s)
|
||||||
|
25
_bmad/tea/config.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# TEA Module Configuration
|
||||||
|
# Generated by BMAD installer
|
||||||
|
# Version: 6.3.0
|
||||||
|
# Date: 2026-04-16T14:51:32.168Z
|
||||||
|
|
||||||
|
test_artifacts: "{project-root}/_bmad-output/test-artifacts"
|
||||||
|
tea_use_playwright_utils: true
|
||||||
|
tea_use_pactjs_utils: false
|
||||||
|
tea_pact_mcp: none
|
||||||
|
tea_browser_automation: auto
|
||||||
|
tea_execution_mode: auto
|
||||||
|
tea_capability_probe: true
|
||||||
|
test_stack_type: auto
|
||||||
|
ci_platform: auto
|
||||||
|
test_framework: auto
|
||||||
|
risk_threshold: p1
|
||||||
|
test_design_output: _bmad-output/test-artifacts/test-design
|
||||||
|
test_review_output: _bmad-output/test-artifacts/test-reviews
|
||||||
|
trace_output: _bmad-output/test-artifacts/traceability
|
||||||
|
|
||||||
|
# Core Configuration Values
|
||||||
|
user_name: Julio
|
||||||
|
communication_language: portugues BR
|
||||||
|
document_output_language: portugues BR
|
||||||
|
output_folder: "{project-root}/_bmad-output"
|
||||||
11
_bmad/tea/module-help.csv
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
module,skill,display-name,menu-code,description,action,args,phase,after,before,required,output-location,outputs
|
||||||
|
Test Architecture Enterprise,_meta,,,,,,,,,false,https://bmad-code-org.github.io/bmad-method-test-architecture-enterprise/llms.txt,
|
||||||
|
Test Architecture Enterprise,bmad-teach-me-testing,Teach Me Testing,TMT,Teach testing fundamentals through 7 sessions (TEA Academy).,,0-learning,,,false,test_artifacts,"progress file|session notes|certificate"
|
||||||
|
Test Architecture Enterprise,bmad-testarch-test-design,Test Design,TD,Risk-based test planning.,,3-solutioning,,bmad-testarch-framework,false,test_artifacts,test design document
|
||||||
|
Test Architecture Enterprise,bmad-testarch-framework,Test Framework,TF,Initialize production-ready test framework.,,3-solutioning,bmad-testarch-test-design,bmad-testarch-ci,false,test_artifacts,framework scaffold
|
||||||
|
Test Architecture Enterprise,bmad-testarch-ci,CI Setup,CI,Configure CI/CD quality pipeline.,,3-solutioning,bmad-testarch-framework,,false,test_artifacts,ci config
|
||||||
|
Test Architecture Enterprise,bmad-testarch-atdd,ATDD,AT,Generate red-phase acceptance test scaffolds before implementation.,,4-implementation,bmad-create-story:create,bmad-dev-story,false,test_artifacts,"atdd-checklist|red-phase acceptance tests"
|
||||||
|
Test Architecture Enterprise,bmad-testarch-automate,Test Automation,TA,Expand test coverage.,,4-implementation,bmad-testarch-atdd,,false,test_artifacts,test suite
|
||||||
|
Test Architecture Enterprise,bmad-testarch-test-review,Test Review,RV,Quality audit (0-100 scoring).,,4-implementation,bmad-testarch-automate,,false,test_artifacts,review report
|
||||||
|
Test Architecture Enterprise,bmad-testarch-nfr,NFR Assessment,NR,Non-functional requirements assessment.,,4-implementation,bmad-testarch-automate,,false,test_artifacts,nfr report
|
||||||
|
Test Architecture Enterprise,bmad-testarch-trace,Traceability,TR,Coverage traceability and gate.,,4-implementation,bmad-testarch-test-review,,false,test_artifacts,"traceability matrix|gate decision"
|
||||||
|
74
_bmad/tea/workflows/testarch/README.md
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
# TEA Workflow Step Files
|
||||||
|
|
||||||
|
This folder contains the Test Architect (TEA) workflows converted to step-file architecture for strict LLM compliance. Each workflow is tri-modal (create, edit, validate) and uses small, ordered step files instead of a single monolithic instruction file.
|
||||||
|
|
||||||
|
## Why Step Files
|
||||||
|
|
||||||
|
- Enforces sequential execution and prevents improvisation
|
||||||
|
- Keeps context small and focused per step
|
||||||
|
- Makes validation and edits deterministic
|
||||||
|
|
||||||
|
## Standard Layout (per workflow)
|
||||||
|
|
||||||
|
```
|
||||||
|
<workflow>/
|
||||||
|
├── workflow.md # Mode routing (create / edit / validate)
|
||||||
|
├── workflow-plan.md # Design reference for step order and intent
|
||||||
|
├── workflow.yaml # Installer metadata
|
||||||
|
├── instructions.md # Short entrypoint / summary
|
||||||
|
├── checklist.md # Validation criteria for outputs
|
||||||
|
├── steps-c/ # Create mode steps
|
||||||
|
├── steps-e/ # Edit mode steps
|
||||||
|
├── steps-v/ # Validate mode steps
|
||||||
|
├── templates/ # Output templates (if applicable)
|
||||||
|
└── validation-report-*.md # Validator outputs (latest run)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Modes
|
||||||
|
|
||||||
|
- **Create (steps-c/):** Primary execution flow to generate outputs
|
||||||
|
- **Edit (steps-e/):** Structured edits to existing outputs
|
||||||
|
- **Validate (steps-v/):** Checklist-based validation of outputs
|
||||||
|
|
||||||
|
## Execution Rules (Summary)
|
||||||
|
|
||||||
|
- Load **one step at a time**. Do not preload future steps.
|
||||||
|
- Follow the **MANDATORY SEQUENCE** exactly in each step.
|
||||||
|
- Do not skip steps, reorder, or improvise.
|
||||||
|
- If a step writes outputs, do so **before** loading the next step.
|
||||||
|
|
||||||
|
## Step Naming Conventions
|
||||||
|
|
||||||
|
- `step-01-*.md` is the init step (no menus unless explicitly required).
|
||||||
|
- `step-01b-*.md` is a continuation/resume step if the workflow is continuable.
|
||||||
|
- `step-0X-*.md` are sequential create-mode steps.
|
||||||
|
- `steps-v/step-01-validate.md` is the validate mode entrypoint.
|
||||||
|
- `steps-e/step-01-assess.md` is the edit mode entrypoint.
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
|
||||||
|
- Each workflow has a latest `validation-report-*.md` in its folder.
|
||||||
|
- Validation uses the BMad Builder workflow validator (workflow-builder).
|
||||||
|
- The goal is 100% compliance with no warnings.
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- Step-file architecture: `docs/explanation/step-file-architecture.md`
|
||||||
|
- Subagent patterns: `docs/explanation/subagent-architecture.md`
|
||||||
|
|
||||||
|
## TEA Workflows
|
||||||
|
|
||||||
|
- test-design
|
||||||
|
- automate
|
||||||
|
- atdd
|
||||||
|
- test-review
|
||||||
|
- trace
|
||||||
|
- framework
|
||||||
|
- ci
|
||||||
|
- nfr-assess
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- `workflow.md` is the canonical entrypoint. `instructions.md` is a short summary for quick context.
|
||||||
|
- Output files typically use `{test_artifacts}` or `{project-root}` variables.
|
||||||
|
- If a workflow produces multiple artifacts (e.g., system-level vs epic-level), the step file will specify which templates and output paths to use.
|
||||||
BIN
assets/fonts/TIMES.TTF
Normal file
BIN
assets/fonts/TIMESBD.TTF
Normal file
194
docs/architecture.md
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
# Arquitetura — SARAndroid
|
||||||
|
|
||||||
|
## Resumo Executivo
|
||||||
|
|
||||||
|
SAR Android (Sistema de Atendimento ao Representante) é um app Android legado em Java para representantes comerciais. Permite gerenciar clientes, produtos e pedidos completamente **offline**, sincronizando periodicamente com um servidor PostgreSQL via JDBC direto. O app opera em modo **offline-first**: toda interação do usuário usa SQLite local; a sincronização com o servidor é explicitamente iniciada pelo usuário.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Stack de Tecnologia
|
||||||
|
|
||||||
|
| Camada | Tecnologia | Versão |
|
||||||
|
|---|---|---|
|
||||||
|
| Linguagem | Java | (sem Kotlin) |
|
||||||
|
| Plataforma | Android | Min SDK 19 / Target SDK 35 |
|
||||||
|
| Build | Eclipse ADT | pré-Gradle |
|
||||||
|
| Banco local | SQLite via SQLiteOpenHelper | schema v40 |
|
||||||
|
| Banco remoto | PostgreSQL via JDBC direto | 8.x |
|
||||||
|
| Driver JDBC | postgresql-8.2-512.jdbc3.jar | 8.2 JDBC3 (fixo) |
|
||||||
|
| Data/Hora | Joda-Time | 2.5 |
|
||||||
|
| FTP | Apache Commons Net | 3.3 |
|
||||||
|
| UI compat | android.support.v4 | (support library legada) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Padrão Arquitetural: Offline-First com Dual-Database
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────┐
|
||||||
|
│ DISPOSITIVO │
|
||||||
|
│ │
|
||||||
|
│ Activities / Fragments │
|
||||||
|
│ ↕ (UI Thread) │
|
||||||
|
│ Value Objects (vo/) │
|
||||||
|
│ ↕ │
|
||||||
|
│ DAOs SQLite (*DB.java / *BD.java) │
|
||||||
|
│ ↕ │
|
||||||
|
│ SQLite Local (jcsinformatica.sar v40) │
|
||||||
|
│ │
|
||||||
|
└──────────────────────┬──────────────────────┘
|
||||||
|
│ ComunicaActivity
|
||||||
|
│ (sync explícita, background thread)
|
||||||
|
↓
|
||||||
|
┌─────────────────────────────────────────────┐
|
||||||
|
│ SERVIDOR │
|
||||||
|
│ │
|
||||||
|
│ DAOs PostgreSQL (*PGSQL.java) │
|
||||||
|
│ ↕ │
|
||||||
|
│ ConnectionManager (JDBC, timeout 20s) │
|
||||||
|
│ ↕ │
|
||||||
|
│ PostgreSQL (ERP central) │
|
||||||
|
│ │
|
||||||
|
└─────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Fluxo de Entrada
|
||||||
|
|
||||||
|
```
|
||||||
|
SplashScreen
|
||||||
|
└→ LoginActivity (valida senha local, carrega Empresa no Global)
|
||||||
|
├→ ComunicaActivity (se última sync > 7 dias → força sync total)
|
||||||
|
└→ MainActivity (menu expandable)
|
||||||
|
├→ Pedidos
|
||||||
|
│ ├→ UpdatePedidoActivity (criar/editar pedido)
|
||||||
|
│ │ ├── FlexPedidoFragment
|
||||||
|
│ │ ├── ItensPedidoFragment
|
||||||
|
│ │ │ └→ UpdatePedItemActivity (editar item)
|
||||||
|
│ │ └── TotalPedidoFragment
|
||||||
|
│ └→ BrowsePedido (listar pedidos)
|
||||||
|
├→ Produtos
|
||||||
|
│ ├→ BrowseProduto
|
||||||
|
│ ├→ UpdateProduto
|
||||||
|
│ └→ FotosProduto
|
||||||
|
├→ Clientes
|
||||||
|
│ ├→ BrowseCliente
|
||||||
|
│ ├→ UpdateCliente
|
||||||
|
│ └→ BrowseCTR (contas a receber)
|
||||||
|
├→ Consulta
|
||||||
|
│ ├→ BrowsePedidoConsulta
|
||||||
|
│ └→ ConsultaVendasActivity
|
||||||
|
└→ Comunicação
|
||||||
|
└→ ComunicaActivity (sync manual)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Classes Centrais
|
||||||
|
|
||||||
|
### `Global` (singleton estático)
|
||||||
|
Mantém o estado de runtime da sessão:
|
||||||
|
- `Global.empresa` — `Empresa` ativa (inclui `Representante`, configs de conexão)
|
||||||
|
- `Global.pedido` — `Pedido` em edição (null fora do contexto de pedido)
|
||||||
|
- `Global.pedItem` — `ItemPedido` em edição
|
||||||
|
|
||||||
|
> ⚠️ `Global.getEmpresa()` lança `WarningException` se `empresa == null`. Sempre use try/catch.
|
||||||
|
|
||||||
|
### `GlobalActivity` (base Activity pós-login)
|
||||||
|
Lê `Global.getEmpresa()` no `onCreate()` e configura o título da tela.
|
||||||
|
Toda Activity pós-login deve estender `GlobalActivity`.
|
||||||
|
|
||||||
|
### `GlobalActivityFragment` (base para Activities com Fragments)
|
||||||
|
Estende `FragmentActivity` da support library v4. Usada por `UpdatePedidoActivity`.
|
||||||
|
|
||||||
|
### `DatabaseHelper` (SQLiteOpenHelper)
|
||||||
|
- DB name: `jcsinformatica.sar`
|
||||||
|
- Versão atual: 40
|
||||||
|
- `PRAGMA foreign_keys = ON` ativado em `onOpen()`
|
||||||
|
- `onUpgrade()` com guards `if (oldVersion < N)` para cada versão
|
||||||
|
|
||||||
|
### `ConnectionManager` (JDBC)
|
||||||
|
- `getConnection(Context, Config)` — cria conexão PostgreSQL com timeout 20s
|
||||||
|
- `closeAll(conn, stmt, rs)` — sempre usar em `finally`
|
||||||
|
|
||||||
|
### `ComunicaActivity` (orquestrador de sync)
|
||||||
|
- Único ponto de sincronização com o servidor
|
||||||
|
- Roda em `Thread` background (`AtualizaDados`)
|
||||||
|
- Usa `postUserFeedback()` para atualizar progresso na UI
|
||||||
|
- Mantém `WakeLock` durante a sync
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Padrão DAO Dual
|
||||||
|
|
||||||
|
Cada entidade sincronizável tem dois DAOs:
|
||||||
|
|
||||||
|
| Classe | Banco | Responsabilidade |
|
||||||
|
|---|---|---|
|
||||||
|
| `*DB.java` / `*BD.java` | SQLite | CRUD local + detecção de mudanças por MD5 |
|
||||||
|
| `*PGSQL.java` | PostgreSQL | Leitura/escrita remota durante sync |
|
||||||
|
|
||||||
|
**Padrão de detecção de mudanças:**
|
||||||
|
```java
|
||||||
|
// No DAO SQLite (ex: ClienteDB)
|
||||||
|
SparseArray<String> arrayMd5 = selectIdMd5(db); // carrega id_erp → md5
|
||||||
|
for (Cliente c : clientesDoServidor) {
|
||||||
|
String md5Local = arrayMd5.get(c.getIdErp());
|
||||||
|
if (md5Local == null) insert(c, db); // novo
|
||||||
|
else if (md5Local.equals(c.getMd5())) continue; // sem mudança
|
||||||
|
else update(c, db); // atualizado
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Lógica de Negócio em `Pedido.java`
|
||||||
|
|
||||||
|
A classe `Pedido` contém lógica de negócio significativa (não apenas VO):
|
||||||
|
|
||||||
|
| Método | Descrição |
|
||||||
|
|---|---|
|
||||||
|
| `calcTotal(context)` | Recalcula descontos, comissões e totais de todos os itens |
|
||||||
|
| `calcComissao(context, item)` | Calcula comissão por item considerando desconto de rateio |
|
||||||
|
| `calcIpi(context, item)` | Calcula IPI por item |
|
||||||
|
| `calcIcmsSt(context, item)` | Calcula ICMS-ST por item (modal 5=pauta, MVA) |
|
||||||
|
| `calcDescPauta(context)` | Aplica descontos de pauta por faixa de peso/volume |
|
||||||
|
| `getValidaPedidoMinimo(context)` | Valida preço mínimo por produto |
|
||||||
|
| `getTotalDesconto()` | Calcula e atribui `vl_liquido` em cada item |
|
||||||
|
|
||||||
|
> ⚠️ `calcTotal()` tem efeito colateral: modifica os `ItemPedido` internos. Deve ser chamado antes de salvar o pedido.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Padrão de UI
|
||||||
|
|
||||||
|
- **ListView** + `SimpleArrayAdapter*` customizado para todas as listas
|
||||||
|
- **ViewPager** + Fragments para tela de pedido (`UpdatePedidoActivity`)
|
||||||
|
- **ExpandableListView** para menu principal (`MainActivity`)
|
||||||
|
- Buscas em `Thread` dedicada (`ThreadBusca*.java`)
|
||||||
|
- Erros exibidos via `Util.sendError(activity, exception)` → AlertDialog
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Geração de PDF
|
||||||
|
|
||||||
|
`Util` contém `gerarPdf()` que usa `android.graphics.pdf.PdfDocument` (API 19+) para gerar PDF do pedido e compartilhar via `FileProvider`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Permissões
|
||||||
|
|
||||||
|
| Permissão | Uso |
|
||||||
|
|---|---|
|
||||||
|
| INTERNET | JDBC PostgreSQL + FTP |
|
||||||
|
| ACCESS_NETWORK_STATE | Verificar conectividade antes da sync |
|
||||||
|
| WRITE/READ_EXTERNAL_STORAGE | Fotos de produtos e PDF |
|
||||||
|
| WAKE_LOCK | Manter dispositivo ativo durante sync longa |
|
||||||
|
| VIBRATE | Feedback ao concluir sync |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Sem Testes Automatizados
|
||||||
|
|
||||||
|
O projeto não tem infraestrutura de testes. Não há `test/` ou `androidTest/`. Validação é exclusivamente manual via dispositivo/emulador.
|
||||||
123
docs/component-inventory.md
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
# Inventário de Componentes UI — SARAndroid
|
||||||
|
|
||||||
|
## Activities
|
||||||
|
|
||||||
|
| Classe | Herança | Módulo | Descrição |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `SplashScreen` | Activity | raiz | Tela inicial — entry point |
|
||||||
|
| `LoginActivity` | Activity | raiz | Login, seleção de empresa e representante |
|
||||||
|
| `MainActivity` | GlobalActivity | raiz | Menu expandable principal |
|
||||||
|
| `ConfigActivity` | Activity | raiz | Configuração de conexão PostgreSQL |
|
||||||
|
| `TestaConexao` | Activity | raiz | Teste de conectividade |
|
||||||
|
| `ComunicaActivity` | GlobalActivity | comunicacao | Sincronização com servidor |
|
||||||
|
| `UpdatePedidoActivity` | GlobalActivityFragment | pedido | Criar/editar pedido (ViewPager) |
|
||||||
|
| `UpdatePedItemActivity` | GlobalActivity | pedido | Editar item do pedido |
|
||||||
|
| `BrowsePedido` | GlobalActivity | pedido | Lista de pedidos locais |
|
||||||
|
| `BrowseHistorico` | GlobalActivity | pedido | Histórico de pedidos |
|
||||||
|
| `BrowseProduto` | GlobalActivity | produto | Lista de produtos |
|
||||||
|
| `UpdateProduto` | GlobalActivity | produto | Visualização de produto |
|
||||||
|
| `FotosProduto` | GlobalActivity | produto | Galeria de fotos do produto |
|
||||||
|
| `BrowseCliente` | GlobalActivity | cliente | Lista de clientes |
|
||||||
|
| `UpdateCliente` | GlobalActivity | cliente | Criar/editar cliente |
|
||||||
|
| `BrowseCTR` | GlobalActivity | cliente | Contas a receber do cliente |
|
||||||
|
| `BrowseMunicipio` | GlobalActivity | municipio | Seletor de município |
|
||||||
|
| `BrowsePedidoConsulta` | GlobalActivity | consulta | Pedidos do servidor (read-only) |
|
||||||
|
| `ConsultaVendasActivity` | GlobalActivity | consulta | Resumo de vendas |
|
||||||
|
|
||||||
|
## Fragments (Pedido)
|
||||||
|
|
||||||
|
| Classe | Posição no ViewPager | Descrição |
|
||||||
|
|---|---|---|
|
||||||
|
| `FlexPedidoFragment` | Aba 0 (Dados/Flex) | Dados gerais do pedido e flex |
|
||||||
|
| `ItensPedidoFragment` | Aba 1 (Itens) | Lista de itens com botões de ação |
|
||||||
|
| `TotalPedidoFragment` | Aba 2 (Total) | Totais, desconto, comissão |
|
||||||
|
|
||||||
|
## Adapters de Lista
|
||||||
|
|
||||||
|
| Classe | Tipo Base | Usado em | Descrição |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `ExpandableListAdapter` | BaseExpandableListAdapter | MainActivity | Menu principal expandable |
|
||||||
|
| `PedidoTabAdapter` | FragmentPagerAdapter | UpdatePedidoActivity | Abas do pedido |
|
||||||
|
| `SimpleArrayAdapterClienteBrowser` | ArrayAdapter | BrowseCliente | Item da lista de clientes |
|
||||||
|
| `SimpleArrayAdapterProdutoBrowser` | ArrayAdapter | BrowseProduto | Item da lista de produtos |
|
||||||
|
| `SimpleArrayAdapterPedidoBrowser` | ArrayAdapter | BrowsePedido | Item da lista de pedidos |
|
||||||
|
| `SimpleArrayAdapterPedItens` | ArrayAdapter | ItensPedidoFragment | Item de pedido na lista |
|
||||||
|
| `SimpleArrayAdapterMunicipioBrowser` | ArrayAdapter | BrowseMunicipio | Item de município |
|
||||||
|
| `SimpleArrayAdapterHisto` | ArrayAdapter | BrowseHistorico | Item do histórico |
|
||||||
|
| `SimpleArrayAdapterConsultaTotalVendas` | ArrayAdapter | ConsultaVendasActivity | Total de vendas |
|
||||||
|
| `SimpleArrayAdapterCTR` | ArrayAdapter | BrowseCTR | Item de conta a receber |
|
||||||
|
| `BaseAdapterFotosProduto` | BaseAdapter | FotosProduto | Galeria de fotos |
|
||||||
|
| `AutoCompleteValorAdapter` | ArrayAdapter | UpdatePedItemActivity | Autocomplete de valor |
|
||||||
|
|
||||||
|
## Views Customizadas
|
||||||
|
|
||||||
|
| Classe | Base | Descrição |
|
||||||
|
|---|---|---|
|
||||||
|
| `ExpandedListView` | ListView | ListView que calcula altura total — para uso dentro de ScrollView |
|
||||||
|
| `CepEditText` | EditText | Campo CEP com máscara automática (XXXXX-XXX) |
|
||||||
|
| `CnpjEditText` | EditText | Campo CNPJ com máscara automática (XX.XXX.XXX/XXXX-XX) |
|
||||||
|
| `CpfEditText` | EditText | Campo CPF com máscara automática (XXX.XXX.XXX-XX) |
|
||||||
|
|
||||||
|
## VOs de Display (uimodels/vo/)
|
||||||
|
|
||||||
|
| Classe | Campos | Uso |
|
||||||
|
|---|---|---|
|
||||||
|
| `PedidoList` | numero, cliente, status, total | Item da lista de pedidos |
|
||||||
|
| `CTRList` | numero, vencimento, valor, saldo | Item de conta a receber |
|
||||||
|
| `HistoList` | numero, data, total | Item do histórico |
|
||||||
|
|
||||||
|
## Threads de Busca
|
||||||
|
|
||||||
|
| Classe | Busca | Retorna para |
|
||||||
|
|---|---|---|
|
||||||
|
| `ThreadBuscaCliente` | SQLite → clientes por termo | BrowseCliente |
|
||||||
|
| `ThreadBuscaProduto` | SQLite → produtos por termo | BrowseProduto |
|
||||||
|
| `ThreadBuscaPedido` | SQLite → pedidos por filtro | BrowsePedido |
|
||||||
|
| `ThreadBuscaMunicipio` | SQLite → municípios por nome | BrowseMunicipio |
|
||||||
|
| `ThreadAbrirPedido` | SQLite → carrega pedido completo | UpdatePedidoActivity |
|
||||||
|
| `ThreadBuscaPedidoConsulta` | SQLite → pedidos consulta | BrowsePedidoConsulta |
|
||||||
|
| `ThreadAbrirPedidoConsulta` | SQLite → pedido consulta completo | BrowsePedidoConsulta |
|
||||||
|
|
||||||
|
## Layouts Principais
|
||||||
|
|
||||||
|
| Arquivo | Activity/Fragment |
|
||||||
|
|---|---|
|
||||||
|
| `activity_login.xml` | LoginActivity |
|
||||||
|
| `activity_main.xml` | MainActivity |
|
||||||
|
| `activity_config.xml` | ConfigActivity |
|
||||||
|
| `activity_comunica.xml` | ComunicaActivity |
|
||||||
|
| `activity_update_pedido.xml` | UpdatePedidoActivity |
|
||||||
|
| `activity_update_peditem.xml` | UpdatePedItemActivity |
|
||||||
|
| `activity_browse_pedido.xml` | BrowsePedido |
|
||||||
|
| `activity_browse_produto.xml` | BrowseProduto |
|
||||||
|
| `activity_browse_cliente.xml` | BrowseCliente |
|
||||||
|
| `activity_update_cliente.xml` | UpdateCliente |
|
||||||
|
| `activity_update_produto.xml` | UpdateProduto |
|
||||||
|
| `activity_fotos_produto.xml` | FotosProduto |
|
||||||
|
| `activity_browse_historico.xml` | BrowseHistorico |
|
||||||
|
| `activity_browse_ctr.xml` | BrowseCTR |
|
||||||
|
| `activity_browse_municipio.xml` | BrowseMunicipio |
|
||||||
|
| `activity_resumo_vendas.xml` | ConsultaVendasActivity |
|
||||||
|
| `fragment_flex_pedido.xml` | FlexPedidoFragment |
|
||||||
|
| `fragment_itens_pedido.xml` | ItensPedidoFragment |
|
||||||
|
| `fragment_total_pedido.xml` | TotalPedidoFragment |
|
||||||
|
| `list_browse_cliente.xml` | Item da lista de clientes |
|
||||||
|
| `list_browse_produto.xml` | Item da lista de produtos |
|
||||||
|
| `list_browse_pedido.xml` | Item da lista de pedidos |
|
||||||
|
|
||||||
|
## Animações de Transição
|
||||||
|
|
||||||
|
| Arquivo | Uso |
|
||||||
|
|---|---|
|
||||||
|
| `push_left_in/out.xml` | Transição → avançar (startActivity) |
|
||||||
|
| `push_right_in/out.xml` | Transição ← voltar (finish) |
|
||||||
|
| `push_right_in/out_90.xml` | Variante da transição de volta |
|
||||||
|
|
||||||
|
## Menus de Opções
|
||||||
|
|
||||||
|
| Arquivo | Activity |
|
||||||
|
|---|---|
|
||||||
|
| `activity_main_actions.xml` | MainActivity — item: Configurações |
|
||||||
|
| `activity_browse_actions.xml` | Browse Activities — item: Buscar |
|
||||||
|
| `activity_config_actions.xml` | ConfigActivity |
|
||||||
|
| `menu_generic.xml` | Menu genérico |
|
||||||
210
docs/data-models.md
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
# Modelos de Dados — SARAndroid
|
||||||
|
|
||||||
|
## Banco Local: SQLite (`jcsinformatica.sar`, versão 40)
|
||||||
|
|
||||||
|
### Diagrama de Relacionamento (simplificado)
|
||||||
|
|
||||||
|
```
|
||||||
|
empresa
|
||||||
|
├── representante (1:1)
|
||||||
|
├── config (1:2 — externa/interna)
|
||||||
|
├── config_ftp (1:1)
|
||||||
|
├── sarcfg (1:1) — feature flags
|
||||||
|
├── municipio (1:N)
|
||||||
|
├── formapag (1:N)
|
||||||
|
├── pauta (1:N)
|
||||||
|
│ └── pauta_produto (N:N com produto)
|
||||||
|
├── cliente (1:N)
|
||||||
|
├── produto (1:N)
|
||||||
|
│ └── produto_fotos (1:N)
|
||||||
|
├── pedido (1:N)
|
||||||
|
│ └── peditem (1:N)
|
||||||
|
├── pedido_consulta (1:N) — read-only, vem do servidor
|
||||||
|
│ ├── peditem_consulta (1:N)
|
||||||
|
│ └── conta_receber (1:N)
|
||||||
|
│ └── recebimento (1:N)
|
||||||
|
└── sticms (1:N) — tabelas ICMS-ST
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tabelas
|
||||||
|
|
||||||
|
### `empresa`
|
||||||
|
| Coluna | Tipo | Notas |
|
||||||
|
|---|---|---|
|
||||||
|
| id_empresa | INTEGER PK AUTOINCREMENT | chave local |
|
||||||
|
| nome / razao | TEXT | nome comercial e razão social |
|
||||||
|
| password | TEXT | senha de acesso local |
|
||||||
|
| cnpj | TEXT | |
|
||||||
|
| id_empresa_erp | INT | chave no ERP remoto |
|
||||||
|
| id_empresa_matriz | INT | empresa matriz (multi-filial) |
|
||||||
|
| ultima_atualizacao | DATE | controla quando forçar sync total |
|
||||||
|
| sistema | TEXT | `sig` ou `gerente` |
|
||||||
|
| id_portador_padrao | INT | |
|
||||||
|
| id_empresa_prod / id_empresa_grup | INT | filtros de produtos |
|
||||||
|
| uf | TEXT | |
|
||||||
|
|
||||||
|
### `representante`
|
||||||
|
| Coluna | Tipo | Notas |
|
||||||
|
|---|---|---|
|
||||||
|
| id_representante | INTEGER PK | |
|
||||||
|
| id_empresa | INT FK | |
|
||||||
|
| codigo / nome / password | | |
|
||||||
|
| taxa_comissao | REAL | % comissão padrão |
|
||||||
|
| forma_pag | TEXT | |
|
||||||
|
| desconto_maximo | REAL | limite de desconto |
|
||||||
|
| valor_ped_minimo | REAL | valor mínimo de pedido |
|
||||||
|
| permite_flex / saldo_flex | INT / REAL | pedido flex |
|
||||||
|
| desc_rateio_comissao | REAL | taxa de rateio de desconto/comissão |
|
||||||
|
| origem_comissao | INT | 1=representante, outro=produto |
|
||||||
|
|
||||||
|
### `cliente`
|
||||||
|
| Coluna | Tipo | Notas |
|
||||||
|
|---|---|---|
|
||||||
|
| id_cliente | INTEGER PK | chave local |
|
||||||
|
| id_erp | INT | chave remota (nullable para novos clientes) |
|
||||||
|
| id_empresa | INT FK | |
|
||||||
|
| ativo / inativo | BOOLEAN / INT | |
|
||||||
|
| razao / fantasia | TEXT | |
|
||||||
|
| pessoa | INT | 1=PJ, 2=PF |
|
||||||
|
| consfinal | BOOLEAN | consumidor final |
|
||||||
|
| cgcpf / suf_cgcpf | TEXT | CNPJ ou CPF + sufixo |
|
||||||
|
| inscricao | TEXT | inscrição estadual |
|
||||||
|
| id_municipio | INT FK | |
|
||||||
|
| id_formapag | INT FK | forma de pagamento padrão |
|
||||||
|
| id_pauta | INT FK | pauta de preços associada |
|
||||||
|
| st_especifica | TEXT | regime especial ICMS-ST |
|
||||||
|
| desc_cliente_rede | INT | flag desconto rede |
|
||||||
|
| ctr_vencido | INT | tem títulos vencidos |
|
||||||
|
| limite_credito | REAL | |
|
||||||
|
| md5 | TEXT | hash para detecção de mudanças |
|
||||||
|
| UNIQUE | (id_empresa, cgcpf, suf_cgcpf) | |
|
||||||
|
|
||||||
|
### `produto`
|
||||||
|
| Coluna | Tipo | Notas |
|
||||||
|
|---|---|---|
|
||||||
|
| id_produto | INTEGER PK | chave local |
|
||||||
|
| id_erp | INT | chave remota |
|
||||||
|
| id_empresa | INT FK | |
|
||||||
|
| codigo / referencia / nome | TEXT | |
|
||||||
|
| ativo | INT | |
|
||||||
|
| unidade / tipo | TEXT | |
|
||||||
|
| valor1 / valor2 / valor3 | REAL | 3 tabelas de preço |
|
||||||
|
| desc_max | REAL | desconto máximo permitido |
|
||||||
|
| qtd_estoque | REAL | |
|
||||||
|
| grupo_st | TEXT | grupo ICMS-ST |
|
||||||
|
| tx_comissao / aliq_ipi / cod_st | REAL/INT | fiscal |
|
||||||
|
| qtd_volume / lote_multiplo / permite_dif_lote | | |
|
||||||
|
| preco_promocional | INT | flag preço promocional ativo |
|
||||||
|
| id_prodvinc | INT | produto vinculado |
|
||||||
|
| md5 | TEXT | |
|
||||||
|
| UNIQUE | (id_empresa, id_erp) | |
|
||||||
|
|
||||||
|
### `pedido`
|
||||||
|
| Coluna | Tipo | Notas |
|
||||||
|
|---|---|---|
|
||||||
|
| id_pedido | INTEGER PK | chave local |
|
||||||
|
| id_empresa | INT FK | |
|
||||||
|
| numero | INT | número local (autogerado) |
|
||||||
|
| id_erp / numero_erp | INT | chaves remotas (NULL até sync) |
|
||||||
|
| status | INT | 0=Pendente, 1=Liberado, 2=Enviado, 3=Cancelado, 4=Emitido |
|
||||||
|
| tipo | INT | 1=Venda, 2=Bonificação |
|
||||||
|
| data / data_emissao | DATE | |
|
||||||
|
| id_cliente | INT FK | |
|
||||||
|
| id_formapag | INT FK | |
|
||||||
|
| id_pauta | INT FK | |
|
||||||
|
| permite_flex / vl_flex | INT / REAL | pedido flex |
|
||||||
|
| desconto_p / desconto_v | REAL | desconto % e valor |
|
||||||
|
| cod_liberacao | INT | |
|
||||||
|
| total | REAL | total calculado |
|
||||||
|
| UNIQUE | (id_empresa, id_erp), (id_empresa, numero) | |
|
||||||
|
|
||||||
|
### `peditem`
|
||||||
|
| Coluna | Tipo | Notas |
|
||||||
|
|---|---|---|
|
||||||
|
| id_peditem | INTEGER PK | |
|
||||||
|
| id_pedido | INT FK | |
|
||||||
|
| id_produto | INT FK | |
|
||||||
|
| quantidade | REAL | |
|
||||||
|
| valor | REAL | preço unitário |
|
||||||
|
| desconto_p / desconto_v | REAL | |
|
||||||
|
| preco_pauta | REAL | preço da pauta/tabela |
|
||||||
|
| vl_flex | REAL | |
|
||||||
|
| comissao | REAL | % comissão calculada |
|
||||||
|
| vl_liquido | REAL | valor líquido após descontos |
|
||||||
|
| preco_com_ipi | INT | flag preço inclui IPI |
|
||||||
|
| base_ipi / vl_ipi | REAL | |
|
||||||
|
| base_icmsst / vl_icmsst | REAL | |
|
||||||
|
| num_oc / item_oc | TEXT | referência ordem de compra |
|
||||||
|
|
||||||
|
### `pauta` (tabela de preços/descontos)
|
||||||
|
| Coluna | Tipo | Notas |
|
||||||
|
|---|---|---|
|
||||||
|
| id_pauta | INTEGER PK | |
|
||||||
|
| numero / descricao | TEXT | |
|
||||||
|
| data_inicio / data_fim | DATE | vigência |
|
||||||
|
| exclusiva_cliente | INT | |
|
||||||
|
| vl_pedido1..5 | REAL | faixas de volume |
|
||||||
|
| tx_desconto1..5 | REAL | descontos por faixa |
|
||||||
|
| tp_desconto | INT | tipo: 1=valor, 2=peso |
|
||||||
|
|
||||||
|
### `sarcfg` (feature flags por empresa)
|
||||||
|
| Flag | Tipo | Significado |
|
||||||
|
|---|---|---|
|
||||||
|
| bloq_novo_cliente | INT | impede cadastro de clientes |
|
||||||
|
| bloq_preco_pedido | INT | impede alterar preço |
|
||||||
|
| bloq_desc_pedido | INT | impede desconto em pedido |
|
||||||
|
| ativa_grupost | INT | ativa grupos ICMS-ST |
|
||||||
|
| ativar_prod_pauta | INT | exige pauta para produto |
|
||||||
|
| dias_bloq_credito | INT | dias para bloquear crédito |
|
||||||
|
| preco_padrao | INT | tabela de preço padrão (1/2/3) |
|
||||||
|
| preco_com_ipi | INT | preço padrão inclui IPI |
|
||||||
|
| bloq_preco_promocional | INT | bloqueia preço promocional |
|
||||||
|
| bloq_formapag_cliente | INT | forma de pag. fixada por cliente |
|
||||||
|
| bloq_limite_credito | INT | bloqueia pedido acima do limite |
|
||||||
|
|
||||||
|
### `sticms` (tabelas ICMS-ST)
|
||||||
|
| Coluna | Tipo | |
|
||||||
|
|---|---|---|
|
||||||
|
| cod_st | INT | código substituição tributária |
|
||||||
|
| uf | TEXT | estado destino |
|
||||||
|
| st_especifica | TEXT | regime especial |
|
||||||
|
| aliq_icms / aliq_icmsst | REAL | alíquotas |
|
||||||
|
| modal_bc_icmsst | TEXT | modalidade base de cálculo (5=pauta) |
|
||||||
|
| perc_marg_vl_icmsst | REAL | MVA |
|
||||||
|
| somar_icmsst_nf | INT | |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Value Objects (VO) — Mapeamento Java ↔ SQLite
|
||||||
|
|
||||||
|
| Classe Java | Tabela SQLite | Notas |
|
||||||
|
|---|---|---|
|
||||||
|
| `Empresa` | `empresa` | contém `Representante`, `Config` x2, `ConfigFTP` |
|
||||||
|
| `Representante` | `representante` | embutido em `Empresa` |
|
||||||
|
| `Cliente` | `cliente` | `implements Serializable` — pode ser passado via Intent |
|
||||||
|
| `Produto` | `produto` | |
|
||||||
|
| `Pedido` | `pedido` | contém `List<ItemPedido>` — tem lógica de negócio (calcComissao, calcIpi, calcIcmsST) |
|
||||||
|
| `ItemPedido` | `peditem` | |
|
||||||
|
| `FormaPagamento` | `formapag` | |
|
||||||
|
| `Pauta` | `pauta` | |
|
||||||
|
| `PautaProduto` | `pauta_produto` | |
|
||||||
|
| `Municipio` | `municipio` | |
|
||||||
|
| `SarConfig` | `sarcfg` | feature flags |
|
||||||
|
| `StIcms` | `sticms` | |
|
||||||
|
| `Config` | `config` | conexão PostgreSQL (tipo=1 externa, tipo=2 interna) |
|
||||||
|
| `ConfigFTP` | `config_ftp` | |
|
||||||
|
| `ContaReceber` | `conta_receber` | read-only (vem do servidor) |
|
||||||
|
| `Recebimento` | `recebimento` | read-only |
|
||||||
|
| `Fotos` | `produto_fotos` | |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notas Importantes
|
||||||
|
|
||||||
|
- `pedido_consulta` e `peditem_consulta` são espelhos **read-only** dos pedidos do servidor
|
||||||
|
- `conta_receber` / `recebimento` são **read-only** — o app só consulta, não cria títulos
|
||||||
|
- Produtos têm **3 tabelas de preço** (`valor1`, `valor2`, `valor3`) — `preco_padrao` em `sarcfg` determina qual usar
|
||||||
|
- ICMS-ST é calculado em `Pedido.calcIcmsSt()` usando a tabela `sticms` + `pauta_produto`
|
||||||
|
- Comissão é calculada em `Pedido.calcComissao()` com lógica de rateio de desconto
|
||||||
170
docs/development-guide.md
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
# Guia de Desenvolvimento — SARAndroid
|
||||||
|
|
||||||
|
## Pré-requisitos
|
||||||
|
|
||||||
|
| Ferramenta | Versão | Notas |
|
||||||
|
|---|---|---|
|
||||||
|
| Eclipse | 4.x com ADT Plugin | Ambiente original do projeto |
|
||||||
|
| Android SDK | API 23+ | target=android-23 em project.properties |
|
||||||
|
| JDK | 7 ou 8 | compatível com Android SDK |
|
||||||
|
| Android Studio | qualquer | via "Import Eclipse ADT Project" |
|
||||||
|
|
||||||
|
> ⚠️ **Sem Gradle.** Este projeto NÃO usa `gradlew`. Não existe `build.gradle`. Não tente criar um.
|
||||||
|
|
||||||
|
## Abrindo o Projeto
|
||||||
|
|
||||||
|
### Eclipse ADT
|
||||||
|
1. File → Import → Android → Existing Android Code
|
||||||
|
2. Aponte para a pasta raiz do projeto
|
||||||
|
3. Confirmar que `project.properties` detectou `target=android-23`
|
||||||
|
|
||||||
|
### Android Studio
|
||||||
|
1. File → New → Import Project
|
||||||
|
2. Selecionar "Eclipse ADT (Eclipse.project)"
|
||||||
|
3. O AS vai converter para Gradle automaticamente (apenas para abertura)
|
||||||
|
4. **Atenção:** Não commitar os arquivos Gradle gerados pelo AS
|
||||||
|
|
||||||
|
## Estrutura de Build (Eclipse ADT)
|
||||||
|
|
||||||
|
```
|
||||||
|
src/ → código-fonte Java
|
||||||
|
gen/ → R.java gerado automaticamente (não editar)
|
||||||
|
res/ → recursos (layouts, strings, drawables)
|
||||||
|
lib/ → JARs de dependência (postgresql, joda-time, commons-net)
|
||||||
|
bin/ → build output (não versionar)
|
||||||
|
AndroidManifest.xml
|
||||||
|
project.properties → target=android-23
|
||||||
|
.classpath → referências de classpath
|
||||||
|
```
|
||||||
|
|
||||||
|
## Adicionando Dependências
|
||||||
|
|
||||||
|
**Não há Maven/Gradle.** Para adicionar uma biblioteca:
|
||||||
|
1. Copiar o JAR para `lib/`
|
||||||
|
2. Abrir `.classpath` e adicionar entry `<classpathentry kind="lib" path="lib/nome.jar"/>`
|
||||||
|
3. No Eclipse: Project → Properties → Java Build Path → Libraries → Add JARs
|
||||||
|
|
||||||
|
## Modificando o Schema SQLite
|
||||||
|
|
||||||
|
1. Adicionar colunas/tabelas em `DatabaseHelper.onCreate()` no novo schema completo
|
||||||
|
2. Adicionar bloco `if (oldVersion < N)` em `DatabaseHelper.onUpgrade()` com os ALTERs
|
||||||
|
3. Incrementar `dbVersao` de 40 para N em `DatabaseHelper`
|
||||||
|
4. **Nunca** modificar blocos de versões anteriores em `onUpgrade()`
|
||||||
|
|
||||||
|
```java
|
||||||
|
// DatabaseHelper.java
|
||||||
|
final static int dbVersao = 41; // era 40
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||||
|
// ... blocos anteriores ...
|
||||||
|
if (oldVersion < 41) {
|
||||||
|
db.execSQL("ALTER TABLE produto ADD COLUMN nova_coluna TEXT;");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Adicionando uma Nova Activity
|
||||||
|
|
||||||
|
1. Criar a classe estendendo `GlobalActivity` (pós-login) ou `Activity` (pré-login)
|
||||||
|
2. Registrar em `AndroidManifest.xml`:
|
||||||
|
```xml
|
||||||
|
<activity
|
||||||
|
android:name="br.com.jcsinformatica.sarandroid.meumodulo.MinhaActivity"
|
||||||
|
android:label="@string/app_release"
|
||||||
|
android:screenOrientation="portrait" />
|
||||||
|
```
|
||||||
|
3. Criar layout em `res/layout/activity_minha.xml`
|
||||||
|
4. Se for listagem: criar layout de item em `res/layout/list_minha.xml`
|
||||||
|
5. Se for listagem: criar adapter em `uimodels/SimpleArrayAdapterMinha.java`
|
||||||
|
|
||||||
|
## Padrão de Tratamento de Erros
|
||||||
|
|
||||||
|
```java
|
||||||
|
// Erros ao usuário → WarningException → AlertDialog via Util.sendError
|
||||||
|
try {
|
||||||
|
Empresa emp = Global.getEmpresa(); // pode lançar WarningException
|
||||||
|
// ...
|
||||||
|
} catch (WarningException e) {
|
||||||
|
Util.sendError(this, e); // exibe AlertDialog com a mensagem
|
||||||
|
} catch (Exception e) {
|
||||||
|
Util.sendError(this, e); // exibe AlertDialog com stack trace
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Padrão de Operações em Background
|
||||||
|
|
||||||
|
```java
|
||||||
|
// Busca em Thread separada (nunca na UI thread)
|
||||||
|
new Thread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
final List<Cliente> resultado = new ClienteDB().selectAll(context);
|
||||||
|
runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
adapter.clear();
|
||||||
|
adapter.addAll(resultado);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Padrão de Acesso ao SQLite
|
||||||
|
|
||||||
|
```java
|
||||||
|
// Sempre fechar o database após uso
|
||||||
|
DatabaseHelper dbHelper = new DatabaseHelper(context);
|
||||||
|
SQLiteDatabase db = dbHelper.getWritableDatabase();
|
||||||
|
try {
|
||||||
|
// operações
|
||||||
|
} finally {
|
||||||
|
db.close();
|
||||||
|
dbHelper.close();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Padrão de Conexão PostgreSQL
|
||||||
|
|
||||||
|
```java
|
||||||
|
// Sempre fechar em finally
|
||||||
|
Connection conn = null;
|
||||||
|
PreparedStatement stmt = null;
|
||||||
|
ResultSet rs = null;
|
||||||
|
try {
|
||||||
|
conn = ConnectionManager.getConnection(context, config);
|
||||||
|
stmt = conn.prepareStatement("SELECT ...");
|
||||||
|
rs = stmt.executeQuery();
|
||||||
|
// processar
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw e;
|
||||||
|
} finally {
|
||||||
|
ConnectionManager.closeAll(conn, stmt, rs); // sempre!
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Strings e Encoding
|
||||||
|
|
||||||
|
- **Nunca** usar acentos hardcoded em código Java (histórico de encoding CP1252)
|
||||||
|
- Todas as strings visíveis ao usuário em `res/values/strings.xml`
|
||||||
|
- Usar `getString(R.string.minha_string)` ou `@string/minha_string` em XML
|
||||||
|
|
||||||
|
## Executando o App
|
||||||
|
|
||||||
|
1. Conectar dispositivo Android ou iniciar emulador (API 19+)
|
||||||
|
2. Eclipse: Run → Run As → Android Application
|
||||||
|
3. Ou Android Studio: Run → Run 'app'
|
||||||
|
|
||||||
|
## Debug de Conexão PostgreSQL
|
||||||
|
|
||||||
|
- Usar `TestaConexao.java` para verificar conectividade antes de sync
|
||||||
|
- Logs via `android.util.Log.d("TAG", "mensagem")`
|
||||||
|
- Timeout de conexão: 20 segundos (definido em `ConnectionManager`)
|
||||||
|
|
||||||
|
## Sem Testes Automatizados
|
||||||
|
|
||||||
|
O projeto não tem infraestrutura de testes. Validação é manual:
|
||||||
|
- Testar em dispositivo físico (API 19+) para comportamento real
|
||||||
|
- Testar sincronização com servidor PostgreSQL de desenvolvimento
|
||||||
|
- Verificar comportamento offline: criar pedidos sem conexão, sincronizar depois
|
||||||
63
docs/index.md
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
# Documentação — SARAndroid
|
||||||
|
|
||||||
|
## Visão Geral do Projeto
|
||||||
|
|
||||||
|
- **Tipo:** Monolito Android nativo Java
|
||||||
|
- **Linguagem principal:** Java (sem Kotlin)
|
||||||
|
- **Arquitetura:** Offline-first com dual-database (SQLite local + PostgreSQL remoto)
|
||||||
|
- **Build:** Eclipse ADT (sem Gradle)
|
||||||
|
- **Versão:** 2.8.1 (versionCode 156)
|
||||||
|
|
||||||
|
## Referência Rápida
|
||||||
|
|
||||||
|
| Campo | Valor |
|
||||||
|
|---|---|
|
||||||
|
| Pacote raiz | `br.com.jcsinformatica.sarandroid` |
|
||||||
|
| Min SDK | 19 (Android 4.4) |
|
||||||
|
| Target SDK | 35 |
|
||||||
|
| Schema SQLite | versão 40 |
|
||||||
|
| Driver PostgreSQL | postgresql-8.2-512.jdbc3.jar (fixo) |
|
||||||
|
| Entry point | `SplashScreen` → `LoginActivity` → `MainActivity` |
|
||||||
|
| Sync | `ComunicaActivity` (único orquestrador) |
|
||||||
|
| Estado global | `Global.empresa`, `Global.pedido`, `Global.pedItem` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Documentação Gerada
|
||||||
|
|
||||||
|
- [Visão Geral do Projeto](./project-overview.md) — domínio, funcionalidades, sumário técnico
|
||||||
|
- [Arquitetura](./architecture.md) — padrões, classes centrais, fluxo de telas, dual-database
|
||||||
|
- [Modelos de Dados](./data-models.md) — schema SQLite v40, VOs, relacionamentos, lógica fiscal
|
||||||
|
- [Árvore de Fontes](./source-tree-analysis.md) — estrutura de diretórios completamente anotada
|
||||||
|
- [Guia de Desenvolvimento](./development-guide.md) — como abrir, compilar, padrões de código
|
||||||
|
- [Inventário de Componentes UI](./component-inventory.md) — activities, fragments, adapters, threads
|
||||||
|
|
||||||
|
## Documentação de Referência
|
||||||
|
|
||||||
|
- [CLAUDE.md](../CLAUDE.md) — guia de instruções para Claude Code
|
||||||
|
- [Contexto para Agentes IA](../_bmad-output/project-context.md) — regras críticas de implementação
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Como Começar
|
||||||
|
|
||||||
|
### Para trabalhar no projeto
|
||||||
|
1. Leia [Guia de Desenvolvimento](./development-guide.md) — setup do Eclipse ADT
|
||||||
|
2. Leia [Arquitetura](./architecture.md) — entenda o padrão offline-first e dual-database
|
||||||
|
3. Leia [Contexto para Agentes IA](../_bmad-output/project-context.md) — regras críticas antes de codar
|
||||||
|
|
||||||
|
### Para entender o domínio
|
||||||
|
1. [Visão Geral](./project-overview.md) — o que o app faz e para quem
|
||||||
|
2. [Modelos de Dados](./data-models.md) — entidades e relacionamentos
|
||||||
|
|
||||||
|
### Para modificar a UI
|
||||||
|
1. [Inventário de Componentes](./component-inventory.md) — activities, adapters e layouts existentes
|
||||||
|
2. [Árvore de Fontes](./source-tree-analysis.md) — onde cada coisa está
|
||||||
|
|
||||||
|
### Para modificar a sincronização
|
||||||
|
1. [Arquitetura](./architecture.md) — seção "Padrão DAO Dual" e "ComunicaActivity"
|
||||||
|
2. [Modelos de Dados](./data-models.md) — campos md5, id_erp vs id_* local
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_Documentação gerada em 2026-04-16 via bmad-document-project (Exhaustive Scan)_
|
||||||
52
docs/project-overview.md
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
# Visão Geral — SARAndroid
|
||||||
|
|
||||||
|
## O que é
|
||||||
|
|
||||||
|
**SAR Android** (Sistema de Atendimento ao Representante) é um aplicativo Android para representantes comerciais. Permite ao representante trabalhar completamente **offline** — gerenciando clientes, produtos e pedidos no campo — e sincronizar com o ERP central da empresa via conexão direta ao banco PostgreSQL.
|
||||||
|
|
||||||
|
## Domínio de Negócio
|
||||||
|
|
||||||
|
O app serve representantes comerciais de distribuidoras/indústrias que:
|
||||||
|
- Visitam clientes sem acesso à internet garantido
|
||||||
|
- Precisam consultar catálogo de produtos com preços atualizados
|
||||||
|
- Emitem pedidos de venda e bonificação
|
||||||
|
- Consultam histórico de pedidos e títulos a receber do cliente
|
||||||
|
- Enviam os pedidos ao sistema ERP após retornar à conectividade
|
||||||
|
|
||||||
|
## Dados Técnicos
|
||||||
|
|
||||||
|
| Campo | Valor |
|
||||||
|
|---|---|
|
||||||
|
| Versão | 2.8.1 (versionCode 156) |
|
||||||
|
| Pacote | `br.com.jcsinformatica.sarandroid` |
|
||||||
|
| Linguagem | Java (sem Kotlin) |
|
||||||
|
| Build | Eclipse ADT (sem Gradle) |
|
||||||
|
| Min SDK | 19 (Android 4.4 KitKat) |
|
||||||
|
| Target SDK | 35 (Android 15) |
|
||||||
|
| Banco local | SQLite v40 |
|
||||||
|
| Banco remoto | PostgreSQL via JDBC |
|
||||||
|
|
||||||
|
## Funcionalidades Principais
|
||||||
|
|
||||||
|
| Módulo | Descrição |
|
||||||
|
|---|---|
|
||||||
|
| **Pedidos** | Criar/editar pedidos de venda e bonificação com cálculo de IPI, ICMS-ST, comissão e descontos por pauta |
|
||||||
|
| **Produtos** | Consultar catálogo com 3 tabelas de preço, estoque, fotos e dados fiscais |
|
||||||
|
| **Clientes** | Consultar e cadastrar clientes com validação de CNPJ/CPF e dados fiscais |
|
||||||
|
| **Contas a Receber** | Consultar títulos vencidos e recebimentos do cliente |
|
||||||
|
| **Consultas** | Histórico de pedidos do servidor e resumo de vendas |
|
||||||
|
| **Comunicação** | Sincronização bidirecional com servidor PostgreSQL |
|
||||||
|
| **Fotos** | Download de fotos de produtos via FTP |
|
||||||
|
|
||||||
|
## Arquitetura em Uma Frase
|
||||||
|
|
||||||
|
App Android offline-first com dual-database (SQLite local + PostgreSQL remoto), sincronização explícita via JDBC direto, sem framework de persistência — apenas DAOs Java puro.
|
||||||
|
|
||||||
|
## Documentação Disponível
|
||||||
|
|
||||||
|
- [Arquitetura](./architecture.md) — padrões, classes centrais, fluxos
|
||||||
|
- [Modelos de Dados](./data-models.md) — schema SQLite, VOs, relacionamentos
|
||||||
|
- [Árvore de Fontes](./source-tree-analysis.md) — estrutura de diretórios anotada
|
||||||
|
- [Guia de Desenvolvimento](./development-guide.md) — como abrir, compilar e modificar
|
||||||
|
- [Inventário de Componentes UI](./component-inventory.md) — adapters, fragments, views customizadas
|
||||||
|
- [Contexto para Agentes IA](../_bmad-output/project-context.md) — regras críticas de implementação
|
||||||
43
docs/project-scan-report.json
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"workflow_version": "1.2.0",
|
||||||
|
"timestamps": {
|
||||||
|
"started": "2026-04-16T00:00:00Z",
|
||||||
|
"last_updated": "2026-04-16T00:10:00Z",
|
||||||
|
"completed": "2026-04-16T00:10:00Z"
|
||||||
|
},
|
||||||
|
"mode": "initial_scan",
|
||||||
|
"scan_level": "exhaustive",
|
||||||
|
"project_root": "/home/julio/SARandroid",
|
||||||
|
"project_knowledge": "/home/julio/SARandroid/docs",
|
||||||
|
"completed_steps": [
|
||||||
|
{"step": "step_1", "status": "completed", "timestamp": "2026-04-16T00:01:00Z", "summary": "Classified as monolith with 1 part: Android native Java mobile app (Eclipse ADT)"},
|
||||||
|
{"step": "step_2", "status": "completed", "timestamp": "2026-04-16T00:02:00Z", "summary": "Found 2 existing docs: CLAUDE.md, _bmad-output/project-context.md"},
|
||||||
|
{"step": "step_3", "status": "completed", "timestamp": "2026-04-16T00:03:00Z", "summary": "Tech stack: Java Android, SQLite v40, PostgreSQL JDBC 8.2, joda-time 2.5, commons-net 3.3"},
|
||||||
|
{"step": "step_4", "status": "completed", "timestamp": "2026-04-16T00:07:00Z", "summary": "Exhaustive scan: 100+ Java files read across vo/, database/, postgres/, pedido/, cliente/, produto/, comunicacao/, uimodels/. Data models, UI components, sync pattern fully analyzed."},
|
||||||
|
{"step": "step_5", "status": "completed", "timestamp": "2026-04-16T00:08:00Z", "summary": "Source tree documented: source-tree-analysis.md written"},
|
||||||
|
{"step": "step_6", "status": "completed", "timestamp": "2026-04-16T00:08:00Z", "summary": "Development guide written: development-guide.md"},
|
||||||
|
{"step": "step_8", "status": "completed", "timestamp": "2026-04-16T00:09:00Z", "summary": "Architecture doc written: architecture.md"},
|
||||||
|
{"step": "step_9", "status": "completed", "timestamp": "2026-04-16T00:09:00Z", "summary": "Supporting docs written: project-overview.md, data-models.md, component-inventory.md"},
|
||||||
|
{"step": "step_10", "status": "completed", "timestamp": "2026-04-16T00:10:00Z", "summary": "Master index written: index.md"},
|
||||||
|
{"step": "step_12", "status": "completed", "timestamp": "2026-04-16T00:10:00Z", "summary": "Workflow complete"}
|
||||||
|
],
|
||||||
|
"current_step": "completed",
|
||||||
|
"project_types": [{"part_id": "main", "project_type_id": "mobile", "display_name": "Android Native Java (Eclipse ADT)"}],
|
||||||
|
"repository_type": "monolith",
|
||||||
|
"findings": {
|
||||||
|
"project_classification": "monolith, 1 part, Android native Java, Eclipse ADT build system",
|
||||||
|
"technology_stack": "Java Android (Min SDK 19 / Target SDK 35), SQLite v40, PostgreSQL JDBC 8.2, joda-time 2.5, commons-net 3.3, android.support.v4",
|
||||||
|
"existing_docs": 2
|
||||||
|
},
|
||||||
|
"outputs_generated": [
|
||||||
|
"project-scan-report.json",
|
||||||
|
"index.md",
|
||||||
|
"project-overview.md",
|
||||||
|
"architecture.md",
|
||||||
|
"source-tree-analysis.md",
|
||||||
|
"data-models.md",
|
||||||
|
"development-guide.md",
|
||||||
|
"component-inventory.md"
|
||||||
|
],
|
||||||
|
"resume_instructions": "Workflow complete — no resume needed"
|
||||||
|
}
|
||||||
190
docs/source-tree-analysis.md
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
# Análise da Árvore de Fontes — SARAndroid
|
||||||
|
|
||||||
|
```
|
||||||
|
SARAndroid/ ← raiz do projeto Eclipse ADT
|
||||||
|
│
|
||||||
|
├── AndroidManifest.xml ← registro de Activities, permissões, FileProvider
|
||||||
|
├── project.properties ← target=android-23
|
||||||
|
├── .classpath ← referências aos JARs e pastas de source
|
||||||
|
├── .project ← metadados do Eclipse
|
||||||
|
├── lint.xml ← supressões de lint
|
||||||
|
├── proguard-project.txt ← config ProGuard (desativado)
|
||||||
|
│
|
||||||
|
├── src/ ← código-fonte Java
|
||||||
|
│ └── br/com/jcsinformatica/sarandroid/
|
||||||
|
│ │
|
||||||
|
│ ├── Global.java ⭐ singleton de estado: empresa, pedido, pedItem
|
||||||
|
│ ├── GlobalActivity.java ⭐ base Activity pós-login (configura título)
|
||||||
|
│ ├── GlobalActivityFragment.java ⭐ base FragmentActivity (UpdatePedidoActivity)
|
||||||
|
│ ├── SplashScreen.java ← tela de splash / entry point
|
||||||
|
│ ├── LoginActivity.java ← login, seleção de empresa
|
||||||
|
│ ├── MainActivity.java ← menu expandable principal
|
||||||
|
│ ├── ConfigActivity.java ← configuração de conexão/empresa
|
||||||
|
│ ├── TestaConexao.java ← testa conexão PostgreSQL
|
||||||
|
│ ├── Preferencia.java ← constantes SharedPreferences
|
||||||
|
│ ├── Util.java ⭐ utilitários: formatação, erro, PDF, conectividade
|
||||||
|
│ ├── WarningException.java ← exceção customizada para erros ao usuário
|
||||||
|
│ │
|
||||||
|
│ ├── vo/ ← Value Objects (modelos de dados puros)
|
||||||
|
│ │ ├── Empresa.java ⭐ empresa + representante + configs
|
||||||
|
│ │ ├── Representante.java ← dados do representante comercial
|
||||||
|
│ │ ├── Cliente.java ← cliente (Serializable — passável via Intent)
|
||||||
|
│ │ ├── Produto.java ← produto com 3 tabelas de preço
|
||||||
|
│ │ ├── Pedido.java ⭐ pedido + LÓGICA DE NEGÓCIO (calc comissão, IPI, ST)
|
||||||
|
│ │ ├── ItemPedido.java ← item do pedido
|
||||||
|
│ │ ├── FormaPagamento.java ← forma de pagamento
|
||||||
|
│ │ ├── Pauta.java ← tabela de preços/descontos por faixa
|
||||||
|
│ │ ├── PautaProduto.java ← preço do produto na pauta
|
||||||
|
│ │ ├── Municipio.java ← município (FK de Cliente)
|
||||||
|
│ │ ├── StIcms.java ← tabela ICMS-ST
|
||||||
|
│ │ ├── SarConfig.java ← feature flags da empresa
|
||||||
|
│ │ ├── Config.java ← configuração de conexão PostgreSQL
|
||||||
|
│ │ ├── ConfigFTP.java ← configuração FTP
|
||||||
|
│ │ ├── ContaReceber.java ← conta a receber (read-only)
|
||||||
|
│ │ ├── Recebimento.java ← recebimento de título (read-only)
|
||||||
|
│ │ ├── Fotos.java ← foto de produto
|
||||||
|
│ │ ├── ProdutoMedidas.java ← medidas do produto
|
||||||
|
│ │ └── TesConfig.java ← config TES fiscal
|
||||||
|
│ │
|
||||||
|
│ ├── database/ ← DAOs SQLite (*DB.java / *BD.java)
|
||||||
|
│ │ ├── DatabaseHelper.java ⭐ SQLiteOpenHelper — schema v40, onUpgrade
|
||||||
|
│ │ ├── ClienteDB.java ← CRUD cliente + sync por MD5
|
||||||
|
│ │ ├── PedidoDB.java ← CRUD pedido
|
||||||
|
│ │ ├── ItemPedidoDB.java ← CRUD itens de pedido
|
||||||
|
│ │ ├── ProdutoDB.java ← CRUD produto
|
||||||
|
│ │ ├── FormaPagamentoDB.java ← CRUD forma de pagamento
|
||||||
|
│ │ ├── MunicipioDB.java ← CRUD município
|
||||||
|
│ │ ├── PautaDB.java ← CRUD pauta
|
||||||
|
│ │ ├── RepresentanteDB.java ← CRUD representante
|
||||||
|
│ │ ├── ConfigDB.java ← CRUD config de conexão
|
||||||
|
│ │ ├── ConfigFtpBD.java ← CRUD config FTP
|
||||||
|
│ │ ├── SarConfigBD.java ← CRUD feature flags
|
||||||
|
│ │ ├── StIcmsBD.java ← CRUD tabela ICMS-ST
|
||||||
|
│ │ ├── EstoqueBD.java ← estoque de produto
|
||||||
|
│ │ ├── FotosBD.java ← fotos de produto
|
||||||
|
│ │ ├── ContasReceberDB.java ← contas a receber (read-only sync)
|
||||||
|
│ │ ├── RecebimentoDB.java ← recebimentos (read-only sync)
|
||||||
|
│ │ ├── PedidoConsultaDB.java ← pedidos consulta (read-only sync)
|
||||||
|
│ │ └── ItemPedidoConsultaDB.java
|
||||||
|
│ │
|
||||||
|
│ ├── postgres/ ← DAOs PostgreSQL (*PGSQL.java)
|
||||||
|
│ │ ├── ConnectionManager.java ⭐ fábrica de conexões JDBC + closeAll
|
||||||
|
│ │ ├── ClientePGSQL.java ← lê clientes do servidor
|
||||||
|
│ │ ├── ProdutoDB.java ← (atenção: nome igual ao SQLite, pacote diferente)
|
||||||
|
│ │ ├── FormaPagamentoPGSQL.java
|
||||||
|
│ │ ├── MunicipioPGSQL.java
|
||||||
|
│ │ ├── PautaPGSQL.java
|
||||||
|
│ │ ├── RepresentantePGSQL.java
|
||||||
|
│ │ ├── SarConfigPGSQL.java
|
||||||
|
│ │ ├── StIcmsPGSQL.java
|
||||||
|
│ │ ├── EstoquePGSQL.java
|
||||||
|
│ │ ├── ContasReceberPGSQL.java
|
||||||
|
│ │ ├── TesConfigPGSQL.java
|
||||||
|
│ │ └── ProdutoFotosPGSQL.java
|
||||||
|
│ │
|
||||||
|
│ ├── comunicacao/ ← sincronização com servidor
|
||||||
|
│ │ ├── ComunicaActivity.java ⭐ orquestrador de sync (único ponto)
|
||||||
|
│ │ └── AtualizaDados.java ← Thread background da sync
|
||||||
|
│ │
|
||||||
|
│ ├── pedido/ ← Activities do fluxo de pedido
|
||||||
|
│ │ ├── UpdatePedidoActivity.java ⭐ criar/editar pedido (ViewPager + 4 Fragments)
|
||||||
|
│ │ ├── UpdatePedItemActivity.java ← editar item do pedido
|
||||||
|
│ │ ├── BrowsePedido.java ← lista de pedidos
|
||||||
|
│ │ ├── BrowseHistorico.java ← histórico de pedidos
|
||||||
|
│ │ ├── FlexPedidoFragment.java ← aba Flex do pedido
|
||||||
|
│ │ ├── ItensPedidoFragment.java ← aba Itens do pedido
|
||||||
|
│ │ ├── TotalPedidoFragment.java ← aba Total do pedido
|
||||||
|
│ │ ├── ThreadBuscaPedido.java ← busca assíncrona de pedidos
|
||||||
|
│ │ └── ThreadAbrirPedido.java ← abre pedido em background
|
||||||
|
│ │
|
||||||
|
│ ├── produto/ ← Activities de produtos
|
||||||
|
│ │ ├── BrowseProduto.java ← lista de produtos
|
||||||
|
│ │ ├── UpdateProduto.java ← visualizar produto (read-only)
|
||||||
|
│ │ ├── FotosProduto.java ← galeria de fotos do produto
|
||||||
|
│ │ └── ThreadBuscaProduto.java
|
||||||
|
│ │
|
||||||
|
│ ├── cliente/ ← Activities de clientes
|
||||||
|
│ │ ├── BrowseCliente.java ← lista de clientes
|
||||||
|
│ │ ├── UpdateCliente.java ← criar/editar cliente
|
||||||
|
│ │ ├── BrowseCTR.java ← contas a receber do cliente
|
||||||
|
│ │ └── ThreadBuscaCliente.java
|
||||||
|
│ │
|
||||||
|
│ ├── consulta/ ← Activities de consulta/relatório
|
||||||
|
│ │ ├── pedido/
|
||||||
|
│ │ │ ├── BrowsePedidoConsulta.java ← pedidos do servidor (read-only)
|
||||||
|
│ │ │ ├── ThreadBuscaPedidoConsulta.java
|
||||||
|
│ │ │ └── ThreadAbrirPedidoConsulta.java
|
||||||
|
│ │ └── vendas/
|
||||||
|
│ │ ├── ConsultaVendasActivity.java ← resumo de vendas
|
||||||
|
│ │ └── TotalVendas.java
|
||||||
|
│ │
|
||||||
|
│ ├── municipio/ ← busca de município
|
||||||
|
│ │ ├── BrowseMunicipio.java
|
||||||
|
│ │ └── ThreadBuscaMunicipio.java
|
||||||
|
│ │
|
||||||
|
│ ├── fotos/ ← atualização de fotos via FTP
|
||||||
|
│ │ └── AtualizaFotos.java ← baixa fotos do servidor FTP
|
||||||
|
│ │
|
||||||
|
│ └── uimodels/ ← adapters e componentes de UI customizados
|
||||||
|
│ ├── ExpandableListAdapter.java ← menu principal
|
||||||
|
│ ├── PedidoTabAdapter.java ← abas do pedido (ViewPager)
|
||||||
|
│ ├── SimpleArrayAdapterClienteBrowser.java
|
||||||
|
│ ├── SimpleArrayAdapterProdutoBrowser.java
|
||||||
|
│ ├── SimpleArrayAdapterPedidoBrowser.java
|
||||||
|
│ ├── SimpleArrayAdapterPedItens.java
|
||||||
|
│ ├── SimpleArrayAdapterMunicipioBrowser.java
|
||||||
|
│ ├── SimpleArrayAdapterHisto.java
|
||||||
|
│ ├── SimpleArrayAdapterConsultaTotalVendas.java
|
||||||
|
│ ├── SimpleArrayAdapterCTR.java
|
||||||
|
│ ├── AutoCompleteValorAdapter.java
|
||||||
|
│ ├── BaseAdapterFotosProduto.java
|
||||||
|
│ ├── ExpandedListView.java ← ListView que expande dentro de ScrollView
|
||||||
|
│ ├── CepEditText.java ← campo CEP com máscara
|
||||||
|
│ ├── CnpjEditText.java ← campo CNPJ com máscara
|
||||||
|
│ ├── CpfEditText.java ← campo CPF com máscara
|
||||||
|
│ └── vo/
|
||||||
|
│ ├── PedidoList.java ← VO para exibição em lista de pedidos
|
||||||
|
│ ├── CTRList.java ← VO para lista CTR
|
||||||
|
│ └── HistoList.java ← VO para histórico
|
||||||
|
│
|
||||||
|
├── res/ ← recursos Android
|
||||||
|
│ ├── layout/ ← XMLs de tela (activity_*.xml, fragment_*.xml)
|
||||||
|
│ ├── menu/ ← menus de opções (menu_*.xml)
|
||||||
|
│ ├── drawable/ ← backgrounds, botões, seletores
|
||||||
|
│ ├── anim/ ← animações de transição entre telas
|
||||||
|
│ ├── values/ ← strings.xml, styles.xml, colors
|
||||||
|
│ ├── values-v11/ values-v14/ ← estilos por versão de API
|
||||||
|
│ ├── color/ ← seletores de cor
|
||||||
|
│ └── xml/
|
||||||
|
│ └── provider_paths.xml ← caminhos FileProvider para PDF/fotos
|
||||||
|
│
|
||||||
|
├── lib/ ← JARs de dependências
|
||||||
|
│ ├── postgresql-8.2-512.jdbc3.jar ← driver JDBC PostgreSQL (fixo — não atualizar)
|
||||||
|
│ ├── joda-time-2.5.jar ← data/hora
|
||||||
|
│ ├── commons-net-3.3.jar ← FTP
|
||||||
|
│ └── commons-net-3.3-sources.jar
|
||||||
|
│
|
||||||
|
├── gen/ ← código gerado (R.java) — não editar
|
||||||
|
├── bin/ ← build output — não versionar
|
||||||
|
└── assets/ ← assets adicionais (vazio)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pontos de Entrada
|
||||||
|
|
||||||
|
| Arquivo | Papel |
|
||||||
|
|---|---|
|
||||||
|
| `SplashScreen.java` | Entry point do app (LAUNCHER no Manifest) |
|
||||||
|
| `DatabaseHelper.java` | Criação/migração do schema SQLite |
|
||||||
|
| `ComunicaActivity.java` | Entry point da sincronização com servidor |
|
||||||
|
|
||||||
|
## Pastas Críticas
|
||||||
|
|
||||||
|
| Pasta | Importância |
|
||||||
|
|---|---|
|
||||||
|
| `vo/` | Modelos compartilhados por toda a app |
|
||||||
|
| `database/` | Todo acesso ao SQLite passa aqui |
|
||||||
|
| `postgres/` | Toda comunicação remota passa aqui |
|
||||||
|
| `comunicacao/` | Única pasta que orquestra sync |
|
||||||
|
| `pedido/` | Fluxo mais complexo — 4 fragments + 2 activities |
|
||||||
BIN
ic_launcher-web.png
Normal file
|
After Width: | Height: | Size: 66 KiB |
BIN
lib/commons-net-3.3-sources.jar
Normal file
BIN
lib/commons-net-3.3.jar
Normal file
BIN
lib/commons-net-examples-3.3.jar
Normal file
BIN
lib/joda-time-2.5.jar
Normal file
BIN
lib/postgresql-8.2-512.jdbc3.jar
Normal file
BIN
libs/android-support-v4.jar
Normal file
10
lint.xml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<lint>
|
||||||
|
<issue id="DefaultLocale">
|
||||||
|
<ignore path="src/br/com/jcsinformatica/sarandroid/GlobalActivity.java" />
|
||||||
|
<ignore path="src/br/com/jcsinformatica/sarandroid/database/ConfigFtpBD.java" />
|
||||||
|
</issue>
|
||||||
|
<issue id="Wakelock">
|
||||||
|
<ignore path="src/br/com/jcsinformatica/sarandroid/comunicacao/ComunicaActivity.java" />
|
||||||
|
</issue>
|
||||||
|
</lint>
|
||||||
20
proguard-project.txt
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# To enable ProGuard in your project, edit project.properties
|
||||||
|
# to define the proguard.config property as described in that file.
|
||||||
|
#
|
||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# By default, the flags in this file are appended to flags specified
|
||||||
|
# in ${sdk.dir}/tools/proguard/proguard-android.txt
|
||||||
|
# You can edit the include path and order by changing the ProGuard
|
||||||
|
# include property in project.properties.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# Add any project specific keep options here:
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
15
project.properties
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# This file is automatically generated by Android Tools.
|
||||||
|
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
|
||||||
|
#
|
||||||
|
# This file must be checked in Version Control Systems.
|
||||||
|
#
|
||||||
|
# To customize properties used by the Ant build system edit
|
||||||
|
# "ant.properties", and override values to adapt the script to your
|
||||||
|
# project structure.
|
||||||
|
#
|
||||||
|
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
|
||||||
|
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
|
||||||
|
|
||||||
|
# Project target.
|
||||||
|
target=android-23
|
||||||
|
android.library=false
|
||||||
9
res/anim/push_left_in.xml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<set xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||||
|
|
||||||
|
<translate
|
||||||
|
android:duration="126"
|
||||||
|
android:fromXDelta="100%p"
|
||||||
|
android:toXDelta="0" />
|
||||||
|
|
||||||
|
</set>
|
||||||
8
res/anim/push_left_out.xml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<set xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||||
|
|
||||||
|
<translate
|
||||||
|
android:duration="126"
|
||||||
|
android:fromXDelta="0"
|
||||||
|
android:toXDelta="-100%p" />
|
||||||
|
|
||||||
|
</set>
|
||||||
9
res/anim/push_right_in.xml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<set xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||||
|
|
||||||
|
<translate
|
||||||
|
android:duration="126"
|
||||||
|
android:fromXDelta="-100%p"
|
||||||
|
android:toXDelta="0" />
|
||||||
|
|
||||||
|
</set>
|
||||||
9
res/anim/push_right_in_90.xml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<set xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||||
|
|
||||||
|
<translate
|
||||||
|
android:duration="200"
|
||||||
|
android:fromXDelta="-100%p"
|
||||||
|
android:toXDelta="-10%p" />
|
||||||
|
|
||||||
|
</set>
|
||||||
8
res/anim/push_right_out.xml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<set xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||||
|
|
||||||
|
<translate
|
||||||
|
android:duration="126"
|
||||||
|
android:fromXDelta="0"
|
||||||
|
android:toXDelta="100%p" />
|
||||||
|
|
||||||
|
</set>
|
||||||
8
res/anim/push_right_out_90.xml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<set xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||||
|
|
||||||
|
<translate
|
||||||
|
android:duration="200"
|
||||||
|
android:fromXDelta="-10%p"
|
||||||
|
android:toXDelta="100%p" />
|
||||||
|
|
||||||
|
</set>
|
||||||
4
res/color/colors_apptheme.xml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
</resources>
|
||||||
5
res/color/mytext.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:state_enabled="false" android:color="@color/marrom_fonte_disable" />
|
||||||
|
<item android:color="@color/marrom_fonte"/>
|
||||||
|
</selector>
|
||||||
|
After Width: | Height: | Size: 396 B |
BIN
res/drawable-hdpi/apptheme_btn_check_off_disabled_holo_light.png
Normal file
|
After Width: | Height: | Size: 361 B |
BIN
res/drawable-hdpi/apptheme_btn_check_off_focused_holo_light.png
Normal file
|
After Width: | Height: | Size: 409 B |
BIN
res/drawable-hdpi/apptheme_btn_check_off_holo_light.png
Normal file
|
After Width: | Height: | Size: 242 B |
BIN
res/drawable-hdpi/apptheme_btn_check_off_pressed_holo_light.png
Normal file
|
After Width: | Height: | Size: 423 B |
|
After Width: | Height: | Size: 773 B |
BIN
res/drawable-hdpi/apptheme_btn_check_on_disabled_holo_light.png
Normal file
|
After Width: | Height: | Size: 624 B |
BIN
res/drawable-hdpi/apptheme_btn_check_on_focused_holo_light.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
res/drawable-hdpi/apptheme_btn_check_on_holo_light.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
res/drawable-hdpi/apptheme_btn_check_on_pressed_holo_light.png
Normal file
|
After Width: | Height: | Size: 803 B |
|
After Width: | Height: | Size: 1.4 KiB |
BIN
res/drawable-hdpi/apptheme_btn_radio_off_disabled_holo_light.png
Normal file
|
After Width: | Height: | Size: 717 B |
BIN
res/drawable-hdpi/apptheme_btn_radio_off_focused_holo_light.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
res/drawable-hdpi/apptheme_btn_radio_off_holo_light.png
Normal file
|
After Width: | Height: | Size: 790 B |
BIN
res/drawable-hdpi/apptheme_btn_radio_off_pressed_holo_light.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
BIN
res/drawable-hdpi/apptheme_btn_radio_on_disabled_holo_light.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
res/drawable-hdpi/apptheme_btn_radio_on_focused_holo_light.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
res/drawable-hdpi/apptheme_btn_radio_on_holo_light.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
res/drawable-hdpi/apptheme_btn_radio_on_pressed_holo_light.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
res/drawable-hdpi/apptheme_list_activated_holo.9.png
Normal file
|
After Width: | Height: | Size: 115 B |
BIN
res/drawable-hdpi/apptheme_list_focused_holo.9.png
Normal file
|
After Width: | Height: | Size: 139 B |
BIN
res/drawable-hdpi/apptheme_list_longpressed_holo.9.png
Normal file
|
After Width: | Height: | Size: 115 B |
BIN
res/drawable-hdpi/apptheme_list_pressed_holo_light.9.png
Normal file
|
After Width: | Height: | Size: 115 B |
|
After Width: | Height: | Size: 189 B |
BIN
res/drawable-hdpi/apptheme_progress_bg_holo_light.9.png
Normal file
|
After Width: | Height: | Size: 175 B |
BIN
res/drawable-hdpi/apptheme_progress_primary_holo_light.9.png
Normal file
|
After Width: | Height: | Size: 377 B |
BIN
res/drawable-hdpi/apptheme_progress_secondary_holo_light.9.png
Normal file
|
After Width: | Height: | Size: 149 B |
BIN
res/drawable-hdpi/apptheme_progressbar_indeterminate_holo1.png
Normal file
|
After Width: | Height: | Size: 786 B |
BIN
res/drawable-hdpi/apptheme_progressbar_indeterminate_holo2.png
Normal file
|
After Width: | Height: | Size: 867 B |
BIN
res/drawable-hdpi/apptheme_progressbar_indeterminate_holo3.png
Normal file
|
After Width: | Height: | Size: 978 B |
BIN
res/drawable-hdpi/apptheme_progressbar_indeterminate_holo4.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
res/drawable-hdpi/apptheme_progressbar_indeterminate_holo5.png
Normal file
|
After Width: | Height: | Size: 916 B |
BIN
res/drawable-hdpi/apptheme_progressbar_indeterminate_holo6.png
Normal file
|
After Width: | Height: | Size: 1018 B |
BIN
res/drawable-hdpi/apptheme_progressbar_indeterminate_holo7.png
Normal file
|
After Width: | Height: | Size: 814 B |
BIN
res/drawable-hdpi/apptheme_progressbar_indeterminate_holo8.png
Normal file
|
After Width: | Height: | Size: 887 B |