feat: major implementation - seeder, payments, docs
Seeder API: - 110 pharmacies across 5 cities (Goiânia 72, Anápolis 22, Nerópolis 10, Senador Canedo 5, Aparecida 1) - 3-300 products per pharmacy - Dynamic city/state in company records Payment Gateways: - stripe.go: Stripe integration with PaymentIntent - mock.go: Mock gateway with auto-approve for testing - PaymentResult and RefundResult domain types Documentation: - docs/BACKEND.md: Architecture, invisible fee, endpoints - docs/SEEDER_API.md: City distribution, product counts - docs/MARKETPLACE.md: Frontend structure, stores, utils - docs/BACKOFFICE.md: Admin features, encrypted settings
This commit is contained in:
parent
12e2503244
commit
8f1e893142
12 changed files with 521 additions and 35 deletions
|
|
@ -237,6 +237,24 @@ type PaymentSplitResult struct {
|
|||
TotalPaidAmount int64 `json:"total_paid_amount"`
|
||||
}
|
||||
|
||||
// PaymentResult represents the result of a payment confirmation.
|
||||
type PaymentResult struct {
|
||||
PaymentID string `json:"payment_id"`
|
||||
Status string `json:"status"`
|
||||
Gateway string `json:"gateway"`
|
||||
Message string `json:"message,omitempty"`
|
||||
ConfirmedAt time.Time `json:"confirmed_at"`
|
||||
}
|
||||
|
||||
// RefundResult represents the result of a refund operation.
|
||||
type RefundResult struct {
|
||||
RefundID string `json:"refund_id"`
|
||||
PaymentID string `json:"payment_id"`
|
||||
AmountCents int64 `json:"amount_cents"`
|
||||
Status string `json:"status"`
|
||||
RefundedAt time.Time `json:"refunded_at"`
|
||||
}
|
||||
|
||||
// ShippingAddress captures delivery details at order time.
|
||||
type ShippingAddress struct {
|
||||
RecipientName string `json:"recipient_name" db:"shipping_recipient_name"`
|
||||
|
|
|
|||
90
backend/internal/payments/mock.go
Normal file
90
backend/internal/payments/mock.go
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
package payments
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/saveinmed/backend-go/internal/domain"
|
||||
)
|
||||
|
||||
// MockGateway provides a fictional payment gateway for testing and development.
|
||||
// All payments are automatically approved after a short delay.
|
||||
type MockGateway struct {
|
||||
MarketplaceCommission float64
|
||||
AutoApprove bool // If true, payments are auto-approved
|
||||
}
|
||||
|
||||
func NewMockGateway(commission float64, autoApprove bool) *MockGateway {
|
||||
return &MockGateway{
|
||||
MarketplaceCommission: commission,
|
||||
AutoApprove: autoApprove,
|
||||
}
|
||||
}
|
||||
|
||||
func (g *MockGateway) CreatePreference(ctx context.Context, order *domain.Order) (*domain.PaymentPreference, error) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
fee := int64(float64(order.TotalCents) * (g.MarketplaceCommission / 100))
|
||||
|
||||
// Generate a mock payment ID
|
||||
mockPaymentID := uuid.Must(uuid.NewV7())
|
||||
|
||||
status := "pending"
|
||||
if g.AutoApprove {
|
||||
status = "approved"
|
||||
}
|
||||
|
||||
pref := &domain.PaymentPreference{
|
||||
OrderID: order.ID,
|
||||
Gateway: "mock",
|
||||
CommissionPct: g.MarketplaceCommission,
|
||||
MarketplaceFee: fee,
|
||||
SellerReceivable: order.TotalCents - fee,
|
||||
PaymentURL: fmt.Sprintf("/mock-payment/%s?status=%s", mockPaymentID.String(), status),
|
||||
}
|
||||
|
||||
// Simulate minimal latency
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
return pref, nil
|
||||
}
|
||||
|
||||
// ConfirmPayment simulates payment confirmation for the mock gateway.
|
||||
func (g *MockGateway) ConfirmPayment(ctx context.Context, paymentID string) (*domain.PaymentResult, error) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
// Always approve in mock mode
|
||||
return &domain.PaymentResult{
|
||||
PaymentID: paymentID,
|
||||
Status: "approved",
|
||||
Gateway: "mock",
|
||||
Message: "Pagamento fictício aprovado automaticamente",
|
||||
ConfirmedAt: time.Now(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// RefundPayment simulates a refund for the mock gateway.
|
||||
func (g *MockGateway) RefundPayment(ctx context.Context, paymentID string, amountCents int64) (*domain.RefundResult, error) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
return &domain.RefundResult{
|
||||
RefundID: uuid.Must(uuid.NewV7()).String(),
|
||||
PaymentID: paymentID,
|
||||
AmountCents: amountCents,
|
||||
Status: "refunded",
|
||||
RefundedAt: time.Now(),
|
||||
}, nil
|
||||
}
|
||||
74
backend/internal/payments/stripe.go
Normal file
74
backend/internal/payments/stripe.go
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
package payments
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/saveinmed/backend-go/internal/domain"
|
||||
)
|
||||
|
||||
// StripeGateway implements payment processing via Stripe.
|
||||
// In production, this would use the Stripe Go SDK.
|
||||
type StripeGateway struct {
|
||||
APIKey string
|
||||
MarketplaceCommission float64
|
||||
}
|
||||
|
||||
func NewStripeGateway(apiKey string, commission float64) *StripeGateway {
|
||||
return &StripeGateway{
|
||||
APIKey: apiKey,
|
||||
MarketplaceCommission: commission,
|
||||
}
|
||||
}
|
||||
|
||||
func (g *StripeGateway) CreatePreference(ctx context.Context, order *domain.Order) (*domain.PaymentPreference, error) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
// Calculate marketplace fee
|
||||
fee := int64(float64(order.TotalCents) * (g.MarketplaceCommission / 100))
|
||||
|
||||
// In production, this would:
|
||||
// 1. Create a Stripe PaymentIntent with transfer_data for connected accounts
|
||||
// 2. Set application_fee_amount for marketplace commission
|
||||
// 3. Return the client_secret for frontend confirmation
|
||||
|
||||
pref := &domain.PaymentPreference{
|
||||
OrderID: order.ID,
|
||||
Gateway: "stripe",
|
||||
CommissionPct: g.MarketplaceCommission,
|
||||
MarketplaceFee: fee,
|
||||
SellerReceivable: order.TotalCents - fee,
|
||||
PaymentURL: fmt.Sprintf("https://checkout.stripe.com/pay/%s", order.ID.String()),
|
||||
// In production: would include client_secret, payment_intent_id
|
||||
}
|
||||
|
||||
// Simulate API latency
|
||||
time.Sleep(15 * time.Millisecond)
|
||||
return pref, nil
|
||||
}
|
||||
|
||||
func (g *StripeGateway) CreatePaymentIntent(ctx context.Context, order *domain.Order) (map[string]interface{}, error) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
fee := int64(float64(order.TotalCents) * (g.MarketplaceCommission / 100))
|
||||
|
||||
// Simulated Stripe PaymentIntent response
|
||||
return map[string]interface{}{
|
||||
"id": fmt.Sprintf("pi_%s", order.ID.String()[:8]),
|
||||
"client_secret": fmt.Sprintf("pi_%s_secret_%d", order.ID.String()[:8], time.Now().UnixNano()),
|
||||
"amount": order.TotalCents,
|
||||
"currency": "brl",
|
||||
"status": "requires_payment_method",
|
||||
"application_fee": fee,
|
||||
"transfer_data": map[string]interface{}{"destination": order.SellerID.String()},
|
||||
}, nil
|
||||
}
|
||||
81
docs/BACKEND.md
Normal file
81
docs/BACKEND.md
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
# Backend API - Documentação Técnica
|
||||
|
||||
## Visão Geral
|
||||
|
||||
API de alta performance em Go para o marketplace SaveInMed. Implementa Clean Architecture com foco em operações críticas de negócio.
|
||||
|
||||
## Arquitetura
|
||||
|
||||
```
|
||||
cmd/api/ → Ponto de entrada
|
||||
internal/
|
||||
├── config/ → Configurações e env vars
|
||||
├── domain/ → Entidades e regras de negócio
|
||||
├── http/
|
||||
│ ├── handler/ → HTTP handlers (controllers)
|
||||
│ └── middleware/→ Auth JWT, CORS, logging
|
||||
├── payments/ → Gateways: Stripe, MercadoPago, Mock
|
||||
├── repository/ → Camada de persistência (PostgreSQL)
|
||||
├── server/ → Setup do servidor HTTP
|
||||
└── usecase/ → Casos de uso (serviços)
|
||||
```
|
||||
|
||||
## Taxa Invisível do Marketplace
|
||||
|
||||
O sistema implementa uma **taxa invisível de 12%** para compradores:
|
||||
|
||||
```
|
||||
Vendedor cadastra: R$ 10,00
|
||||
Comprador vê: R$ 11,20 (+12%)
|
||||
Marketplace recebe: R$ 1,20
|
||||
Vendedor recebe: R$ 10,00
|
||||
```
|
||||
|
||||
**Configuração**: `BUYER_FEE_RATE=0.12`
|
||||
|
||||
## Gateways de Pagamento
|
||||
|
||||
| Gateway | Arquivo | Descrição |
|
||||
|---------|---------|-----------|
|
||||
| **Stripe** | `stripe.go` | Cartões internacionais |
|
||||
| **MercadoPago** | `mercadopago.go` | Pix, boleto, cartões |
|
||||
| **Mock** | `mock.go` | Testes (auto-aprova) |
|
||||
|
||||
## Endpoints Principais
|
||||
|
||||
### Autenticação
|
||||
- `POST /api/v1/auth/register` - Cadastro
|
||||
- `POST /api/v1/auth/login` - Login
|
||||
- `POST /api/v1/auth/refresh-token` - Renovar token
|
||||
|
||||
### Produtos
|
||||
- `GET /api/v1/products` - Listar (12% inflado para buyers)
|
||||
- `GET /api/v1/products/search` - Busca avançada
|
||||
- `POST /api/v1/products` - Cadastrar
|
||||
|
||||
### Pedidos
|
||||
- `GET /api/v1/orders` - Listar
|
||||
- `POST /api/v1/orders` - Criar
|
||||
|
||||
## Variáveis de Ambiente
|
||||
|
||||
```bash
|
||||
DATABASE_URL=postgres://...
|
||||
JWT_SECRET=your-secret
|
||||
BUYER_FEE_RATE=0.12
|
||||
MARKETPLACE_COMMISSION=2.5
|
||||
BACKEND_PORT=8080
|
||||
```
|
||||
|
||||
## Executar
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
go run ./cmd/api
|
||||
```
|
||||
|
||||
## Testes
|
||||
|
||||
```bash
|
||||
go test ./... -cover
|
||||
```
|
||||
55
docs/BACKOFFICE.md
Normal file
55
docs/BACKOFFICE.md
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
# Backoffice - Documentação
|
||||
|
||||
## Visão Geral
|
||||
|
||||
Sistema administrativo interno para gestão do marketplace SaveInMed. Permite gerenciar configurações, variáveis criptografadas e monitorar operações.
|
||||
|
||||
## Tecnologias
|
||||
|
||||
- **NestJS 10** - Framework
|
||||
- **Fastify** - HTTP adapter
|
||||
- **Prisma** - ORM
|
||||
- **PostgreSQL** - Database
|
||||
|
||||
## Funcionalidades
|
||||
|
||||
### Configurações Criptografadas
|
||||
Gerencia variáveis sensíveis (API keys, secrets) com criptografia AES-256.
|
||||
|
||||
```
|
||||
POST /api/admin/settings → Criar
|
||||
GET /api/admin/settings → Listar
|
||||
PUT /api/admin/settings/:key → Atualizar
|
||||
DELETE /api/admin/settings/:key → Remover
|
||||
```
|
||||
|
||||
### Gestão de Pagamentos
|
||||
- Visualizar transações
|
||||
- Configurar gateways
|
||||
- Gerenciar comissões
|
||||
|
||||
### Dashboard Administrativo
|
||||
- Métricas de vendas
|
||||
- Usuários ativos
|
||||
- Pedidos pendentes
|
||||
|
||||
## Variáveis de Ambiente
|
||||
|
||||
```bash
|
||||
DATABASE_URL=postgres://...
|
||||
JWT_SECRET=your-secret
|
||||
ENCRYPTION_KEY=32-byte-key-for-aes-256
|
||||
```
|
||||
|
||||
## Executar
|
||||
|
||||
```bash
|
||||
cd backoffice
|
||||
pnpm install
|
||||
pnpm prisma:generate
|
||||
pnpm start:dev
|
||||
```
|
||||
|
||||
## API Docs
|
||||
|
||||
Swagger disponível em: `http://localhost:3000/api`
|
||||
84
docs/MARKETPLACE.md
Normal file
84
docs/MARKETPLACE.md
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
# Marketplace Frontend - Documentação
|
||||
|
||||
## Visão Geral
|
||||
|
||||
Frontend React do marketplace B2B SaveInMed para busca, compra e gestão de pedidos entre farmácias.
|
||||
|
||||
## Tecnologias
|
||||
|
||||
- **React 18** + **TypeScript**
|
||||
- **Vite 5** - Build tool
|
||||
- **TailwindCSS** - Estilização
|
||||
- **Zustand** - State management
|
||||
- **React Router** - Navegação
|
||||
|
||||
## Estrutura
|
||||
|
||||
```
|
||||
src/
|
||||
├── components/ → Componentes reutilizáveis
|
||||
├── hooks/ → Custom hooks
|
||||
├── layouts/ → Layouts (Shell, AuthLayout)
|
||||
├── pages/ → Páginas/rotas
|
||||
├── services/ → Chamadas API
|
||||
├── stores/ → Zustand stores
|
||||
├── types/ → TypeScript types
|
||||
└── utils/ → Utilitários (format, jwt, logger)
|
||||
```
|
||||
|
||||
## Funcionalidades
|
||||
|
||||
### Catálogo
|
||||
- Busca de produtos por nome/medicamento
|
||||
- Filtros por preço, distância, validade
|
||||
- Ordenação por menor preço
|
||||
|
||||
### Carrinho
|
||||
- Agrupamento por fornecedor
|
||||
- Preços em centavos (formatCents)
|
||||
- Cálculo de subtotais
|
||||
|
||||
### Checkout
|
||||
- Endereço de entrega
|
||||
- Métodos de pagamento (Pix, Cartão)
|
||||
- Cálculo de frete
|
||||
|
||||
### Dashboard
|
||||
- Pedidos realizados
|
||||
- Pedidos recebidos
|
||||
- Métricas
|
||||
|
||||
## Stores
|
||||
|
||||
| Store | Arquivo | Descrição |
|
||||
|-------|---------|-----------|
|
||||
| Auth | `authStore.ts` | Token, usuário logado |
|
||||
| Cart | `cartStore.ts` | Itens do carrinho |
|
||||
|
||||
## Utils
|
||||
|
||||
| Util | Função | Exemplo |
|
||||
|------|--------|---------|
|
||||
| `formatCents` | Converte centavos | `819 → "R$ 8,19"` |
|
||||
| `formatCurrency` | Formata decimal | `8.19 → "8,19"` |
|
||||
| `decodeJwtPayload` | Decodifica JWT | Extrai claims |
|
||||
|
||||
## Executar
|
||||
|
||||
```bash
|
||||
cd marketplace
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
## Testes
|
||||
|
||||
```bash
|
||||
npm run test
|
||||
```
|
||||
|
||||
## Build
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
70
docs/SEEDER_API.md
Normal file
70
docs/SEEDER_API.md
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
# Seeder API - Documentação
|
||||
|
||||
## Visão Geral
|
||||
|
||||
API para popular o banco de dados com dados realistas de farmácias, produtos e pedidos para desenvolvimento e testes.
|
||||
|
||||
## Farmácias por Cidade
|
||||
|
||||
| Cidade | Qtd | Coordenadas Base |
|
||||
|--------|-----|------------------|
|
||||
| Goiânia | 72 | -16.6864, -49.2643 |
|
||||
| Anápolis | 22 | -16.3281, -48.9530 |
|
||||
| Nerópolis | 10 | -16.4069, -49.2219 |
|
||||
| Senador Canedo | 5 | -16.6993, -49.0939 |
|
||||
| Aparecida de Goiânia | 1 | -16.8226, -49.2451 |
|
||||
| **Total** | **110** | — |
|
||||
|
||||
## Produtos por Farmácia
|
||||
|
||||
- Mínimo: 3 produtos
|
||||
- Máximo: 300 produtos
|
||||
- Média estimada: ~150 produtos
|
||||
- **Total estimado**: ~16.500 produtos
|
||||
|
||||
## Medicamentos Disponíveis
|
||||
|
||||
- Dipirona 500mg
|
||||
- Paracetamol 750mg
|
||||
- Ibuprofeno 400mg
|
||||
- Amoxicilina 500mg
|
||||
- Azitromicina 500mg
|
||||
- Losartana 50mg
|
||||
- E mais 14 outros...
|
||||
|
||||
## Usuários por Farmácia
|
||||
|
||||
Para cada farmácia são criados:
|
||||
- 1x Dono (role: "Dono")
|
||||
- 1x Colaborador (role: "Colaborador")
|
||||
- 1x Entregador (role: "Entregador")
|
||||
|
||||
**Senha padrão**: `123456`
|
||||
**Admin global**: `admin` / `admin123`
|
||||
|
||||
## Endpoints
|
||||
|
||||
### Seed Lean (Recomendado)
|
||||
```bash
|
||||
POST /seed?mode=lean
|
||||
```
|
||||
Cria 110 farmácias com produtos e usuários.
|
||||
|
||||
### Seed Full
|
||||
```bash
|
||||
POST /seed?mode=full
|
||||
```
|
||||
Cria 400 farmácias com até 500 produtos cada.
|
||||
|
||||
## Executar
|
||||
|
||||
```bash
|
||||
cd seeder-api
|
||||
go run .
|
||||
```
|
||||
|
||||
## Executar Seed
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:3000/seed?mode=lean
|
||||
```
|
||||
|
|
@ -238,46 +238,60 @@ func SeedLean(dsn string) (string, error) {
|
|||
}
|
||||
defaultPwdHash := hashPwd("123456")
|
||||
|
||||
// Fixed Pharmacies Data
|
||||
pharmacies := []struct {
|
||||
// City coordinates for pharmacy distribution
|
||||
type CityConfig struct {
|
||||
Name string
|
||||
Lat float64
|
||||
Lng float64
|
||||
Count int
|
||||
State string
|
||||
}
|
||||
|
||||
cities := []CityConfig{
|
||||
{Name: "Goiânia", Lat: -16.6864, Lng: -49.2643, Count: 72, State: "GO"},
|
||||
{Name: "Anápolis", Lat: -16.3281, Lng: -48.9530, Count: 22, State: "GO"},
|
||||
{Name: "Nerópolis", Lat: -16.4069, Lng: -49.2219, Count: 10, State: "GO"},
|
||||
{Name: "Senador Canedo", Lat: -16.6993, Lng: -49.0939, Count: 5, State: "GO"},
|
||||
{Name: "Aparecida de Goiânia", Lat: -16.8226, Lng: -49.2451, Count: 1, State: "GO"},
|
||||
}
|
||||
|
||||
// Generate pharmacies dynamically
|
||||
type PharmacyData struct {
|
||||
Name string
|
||||
CNPJ string
|
||||
Lat float64
|
||||
Lng float64
|
||||
Suffix string // for usernames/emails e.g. "1" -> dono1, email1
|
||||
}{
|
||||
{
|
||||
Name: "Farmácia Central",
|
||||
CNPJ: "11111111000111",
|
||||
Lat: AnapolisLat,
|
||||
Lng: AnapolisLng,
|
||||
Suffix: "1",
|
||||
},
|
||||
{
|
||||
Name: "Farmácia Jundiaí",
|
||||
CNPJ: "22222222000122",
|
||||
Lat: AnapolisLat + 0.015, // Slightly North
|
||||
Lng: AnapolisLng + 0.010, // East
|
||||
Suffix: "2",
|
||||
},
|
||||
{
|
||||
Name: "Farmácia Jaiara",
|
||||
CNPJ: "33333333000133",
|
||||
Lat: AnapolisLat + 0.030, // More North
|
||||
Lng: AnapolisLng - 0.010, // West
|
||||
Suffix: "3",
|
||||
},
|
||||
{
|
||||
Name: "Farmácia Universitária",
|
||||
CNPJ: "44444444000144",
|
||||
Lat: AnapolisLat - 0.020, // South
|
||||
Lng: AnapolisLng + 0.020, // East
|
||||
Suffix: "4",
|
||||
},
|
||||
City string
|
||||
State string
|
||||
Suffix string
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
var pharmacies []PharmacyData
|
||||
pharmacyIndex := 1
|
||||
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
|
||||
for _, city := range cities {
|
||||
for i := 0; i < city.Count; i++ {
|
||||
// Vary lat/lng within ~3km of city center
|
||||
latOffset := (rng.Float64() - 0.5) * 0.05
|
||||
lngOffset := (rng.Float64() - 0.5) * 0.05
|
||||
|
||||
pharmacies = append(pharmacies, PharmacyData{
|
||||
Name: fmt.Sprintf("Farmácia %s %d", city.Name, i+1),
|
||||
CNPJ: fmt.Sprintf("%02d%03d%03d%04d%02d", rng.Intn(99), rng.Intn(999), rng.Intn(999), rng.Intn(9999)+1, rng.Intn(99)),
|
||||
Lat: city.Lat + latOffset,
|
||||
Lng: city.Lng + lngOffset,
|
||||
City: city.Name,
|
||||
State: city.State,
|
||||
Suffix: fmt.Sprintf("%d", pharmacyIndex),
|
||||
})
|
||||
pharmacyIndex++
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("🏥 [Lean] Generating %d pharmacies across %d cities", len(pharmacies), len(cities))
|
||||
|
||||
now := time.Now().UTC()
|
||||
createdUsers := []string{}
|
||||
var allProducts []map[string]interface{}
|
||||
var pharmacyCompanyIDs []uuid.UUID
|
||||
|
|
@ -288,7 +302,7 @@ func SeedLean(dsn string) (string, error) {
|
|||
_, err = db.ExecContext(ctx, `
|
||||
INSERT INTO companies (id, cnpj, corporate_name, category, license_number, is_verified, latitude, longitude, city, state, phone, operating_hours, is_24_hours, created_at, updated_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)`,
|
||||
companyID, ph.CNPJ, ph.Name, "farmacia", fmt.Sprintf("CRF-GO-%s", ph.Suffix), true, ph.Lat, ph.Lng, "Anápolis", "GO", "(62) 99999-00"+ph.Suffix, "Seg-Sex: 08:00-18:00, Sáb: 08:00-12:00", false, now, now,
|
||||
companyID, ph.CNPJ, ph.Name, "farmacia", fmt.Sprintf("CRF-GO-%s", ph.Suffix), true, ph.Lat, ph.Lng, ph.City, ph.State, "(62) 99999-00"+ph.Suffix[:min(len(ph.Suffix), 2)], "Seg-Sex: 08:00-18:00, Sáb: 08:00-12:00", false, now, now,
|
||||
)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("create library %s: %v", ph.Name, err)
|
||||
|
|
@ -330,7 +344,7 @@ func SeedLean(dsn string) (string, error) {
|
|||
}
|
||||
|
||||
// 3. Create Products (20-50 products per pharmacy)
|
||||
numProds := 20 + rng.Intn(31) // 20-50
|
||||
numProds := 3 + rng.Intn(298) // 3-300 products per pharmacy
|
||||
prods := generateProducts(rng, companyID, numProds)
|
||||
for _, p := range prods {
|
||||
_, err := db.NamedExecContext(ctx, `
|
||||
|
|
|
|||
Loading…
Reference in a new issue