feat: Implement Payment Methods, Shipping Improvements, Swagger Audit, and UUIDv7 Migration
- Payment Methods: Added Pix/Credit/Debit selection in checkout, updated backend models and handlers. - Shipping: Updated Checkout UI, added shipping_settings table and seed data. - Swagger: Updated API docs, regenerated swagger.yaml. - UUIDv7: Migrated seeder and backend tests to use uuid.NewV7().
This commit is contained in:
parent
fd305c00a8
commit
ed4349a938
17 changed files with 1414 additions and 428 deletions
|
|
@ -9,15 +9,124 @@ const docTemplate = `{
|
|||
"info": {
|
||||
"description": "{{escape .Description}}",
|
||||
"title": "{{.Title}}",
|
||||
"contact": {
|
||||
"name": "Engenharia SaveInMed",
|
||||
"email": "devops@saveinmed.com"
|
||||
},
|
||||
"contact": {},
|
||||
"version": "{{.Version}}"
|
||||
},
|
||||
"host": "{{.Host}}",
|
||||
"basePath": "{{.BasePath}}",
|
||||
"paths": {
|
||||
"/api/v1/admin/reviews": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Admin"
|
||||
],
|
||||
"summary": "Lista todas as avaliações (Admin)",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Página",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Tamanho da página",
|
||||
"name": "page_size",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.ReviewPage"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/admin/shipments": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Admin"
|
||||
],
|
||||
"summary": "Lista todos os envios (Admin)",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Página",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Tamanho da página",
|
||||
"name": "page_size",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.ShipmentPage"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/auth/login": {
|
||||
"post": {
|
||||
"description": "Autentica usuário e retorna token JWT.",
|
||||
|
|
@ -1506,6 +1615,60 @@ const docTemplate = `{
|
|||
}
|
||||
},
|
||||
"/api/v1/reviews": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Admin"
|
||||
],
|
||||
"summary": "Lista todas as avaliações (Admin)",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Página",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Tamanho da página",
|
||||
"name": "page_size",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.ReviewPage"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
|
|
@ -1553,6 +1716,60 @@ const docTemplate = `{
|
|||
}
|
||||
},
|
||||
"/api/v1/shipments": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Admin"
|
||||
],
|
||||
"summary": "Lista todos os envios (Admin)",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Página",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Tamanho da página",
|
||||
"name": "page_size",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.ShipmentPage"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
|
|
@ -1701,10 +1918,7 @@ const docTemplate = `{
|
|||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.ShippingMethod"
|
||||
}
|
||||
"$ref": "#/definitions/domain.ShippingSettings"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
|
|
@ -1770,10 +1984,7 @@ const docTemplate = `{
|
|||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.ShippingMethod"
|
||||
}
|
||||
"$ref": "#/definitions/domain.ShippingSettings"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
|
|
@ -2225,11 +2436,15 @@ const docTemplate = `{
|
|||
"type": "string"
|
||||
},
|
||||
"created_at": {
|
||||
"description": "Timestamps",
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"is_24_hours": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"is_verified": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
|
@ -2243,6 +2458,14 @@ const docTemplate = `{
|
|||
"longitude": {
|
||||
"type": "number"
|
||||
},
|
||||
"operating_hours": {
|
||||
"description": "e.g. \"Seg-Sex: 08:00-18:00, Sab: 08:00-12:00\"",
|
||||
"type": "string"
|
||||
},
|
||||
"phone": {
|
||||
"description": "Contact \u0026 Hours",
|
||||
"type": "string"
|
||||
},
|
||||
"state": {
|
||||
"type": "string"
|
||||
},
|
||||
|
|
@ -2312,6 +2535,9 @@ const docTemplate = `{
|
|||
"$ref": "#/definitions/domain.OrderItem"
|
||||
}
|
||||
},
|
||||
"payment_method": {
|
||||
"$ref": "#/definitions/domain.PaymentMethod"
|
||||
},
|
||||
"seller_id": {
|
||||
"type": "string"
|
||||
},
|
||||
|
|
@ -2370,6 +2596,19 @@ const docTemplate = `{
|
|||
"OrderStatusDelivered"
|
||||
]
|
||||
},
|
||||
"domain.PaymentMethod": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"pix",
|
||||
"credit_card",
|
||||
"debit_card"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"PaymentMethodPix",
|
||||
"PaymentMethodCredit",
|
||||
"PaymentMethodDebit"
|
||||
]
|
||||
},
|
||||
"domain.PaymentPreference": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -2445,21 +2684,33 @@ const docTemplate = `{
|
|||
"batch": {
|
||||
"type": "string"
|
||||
},
|
||||
"category": {
|
||||
"type": "string"
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"ean_code": {
|
||||
"type": "string"
|
||||
},
|
||||
"expires_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"manufacturer": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"observations": {
|
||||
"type": "string"
|
||||
},
|
||||
"price_cents": {
|
||||
"type": "integer"
|
||||
},
|
||||
|
|
@ -2469,6 +2720,9 @@ const docTemplate = `{
|
|||
"stock": {
|
||||
"type": "integer"
|
||||
},
|
||||
"subcategory": {
|
||||
"type": "string"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
}
|
||||
|
|
@ -2520,6 +2774,9 @@ const docTemplate = `{
|
|||
"batch": {
|
||||
"type": "string"
|
||||
},
|
||||
"category": {
|
||||
"type": "string"
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string"
|
||||
},
|
||||
|
|
@ -2529,15 +2786,24 @@ const docTemplate = `{
|
|||
"distance_km": {
|
||||
"type": "number"
|
||||
},
|
||||
"ean_code": {
|
||||
"type": "string"
|
||||
},
|
||||
"expires_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"manufacturer": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"observations": {
|
||||
"type": "string"
|
||||
},
|
||||
"price_cents": {
|
||||
"type": "integer"
|
||||
},
|
||||
|
|
@ -2547,6 +2813,9 @@ const docTemplate = `{
|
|||
"stock": {
|
||||
"type": "integer"
|
||||
},
|
||||
"subcategory": {
|
||||
"type": "string"
|
||||
},
|
||||
"tenant_city": {
|
||||
"type": "string"
|
||||
},
|
||||
|
|
@ -2584,6 +2853,26 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.ReviewPage": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"page": {
|
||||
"type": "integer"
|
||||
},
|
||||
"page_size": {
|
||||
"type": "integer"
|
||||
},
|
||||
"reviews": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.Review"
|
||||
}
|
||||
},
|
||||
"total": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.SellerDashboard": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -2639,6 +2928,26 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.ShipmentPage": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"page": {
|
||||
"type": "integer"
|
||||
},
|
||||
"page_size": {
|
||||
"type": "integer"
|
||||
},
|
||||
"shipments": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.Shipment"
|
||||
}
|
||||
},
|
||||
"total": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.ShippingAddress": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -2671,63 +2980,6 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.ShippingMethod": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"active": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"free_shipping_threshold_cents": {
|
||||
"type": "integer"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"max_radius_km": {
|
||||
"type": "number"
|
||||
},
|
||||
"min_fee_cents": {
|
||||
"type": "integer"
|
||||
},
|
||||
"pickup_address": {
|
||||
"type": "string"
|
||||
},
|
||||
"pickup_hours": {
|
||||
"type": "string"
|
||||
},
|
||||
"preparation_minutes": {
|
||||
"type": "integer"
|
||||
},
|
||||
"price_per_km_cents": {
|
||||
"type": "integer"
|
||||
},
|
||||
"type": {
|
||||
"$ref": "#/definitions/domain.ShippingMethodType"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"vendor_id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.ShippingMethodType": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"pickup",
|
||||
"own_delivery",
|
||||
"third_party_delivery"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"ShippingMethodPickup",
|
||||
"ShippingMethodOwnDelivery",
|
||||
"ShippingMethodThirdParty"
|
||||
]
|
||||
},
|
||||
"domain.ShippingOption": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -2748,6 +3000,50 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.ShippingSettings": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"active": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"free_shipping_threshold_cents": {
|
||||
"type": "integer"
|
||||
},
|
||||
"latitude": {
|
||||
"type": "number"
|
||||
},
|
||||
"longitude": {
|
||||
"type": "number"
|
||||
},
|
||||
"max_radius_km": {
|
||||
"type": "number"
|
||||
},
|
||||
"min_fee_cents": {
|
||||
"type": "integer"
|
||||
},
|
||||
"pickup_active": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"pickup_address": {
|
||||
"type": "string"
|
||||
},
|
||||
"pickup_hours": {
|
||||
"type": "string"
|
||||
},
|
||||
"price_per_km_cents": {
|
||||
"type": "integer"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"vendor_id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.TopProduct": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -2851,6 +3147,9 @@ const docTemplate = `{
|
|||
"$ref": "#/definitions/domain.OrderItem"
|
||||
}
|
||||
},
|
||||
"payment_method": {
|
||||
"$ref": "#/definitions/domain.PaymentMethod"
|
||||
},
|
||||
"seller_id": {
|
||||
"type": "string"
|
||||
},
|
||||
|
|
@ -2938,6 +3237,9 @@ const docTemplate = `{
|
|||
"handler.loginRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"password": {
|
||||
"type": "string"
|
||||
},
|
||||
|
|
@ -3115,7 +3417,7 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"handler.shippingMethodRequest": {
|
||||
"handler.shippingSettingsRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"active": {
|
||||
|
|
@ -3124,37 +3426,30 @@ const docTemplate = `{
|
|||
"free_shipping_threshold_cents": {
|
||||
"type": "integer"
|
||||
},
|
||||
"latitude": {
|
||||
"description": "Store location for radius calc",
|
||||
"type": "number"
|
||||
},
|
||||
"longitude": {
|
||||
"type": "number"
|
||||
},
|
||||
"max_radius_km": {
|
||||
"type": "number"
|
||||
},
|
||||
"min_fee_cents": {
|
||||
"type": "integer"
|
||||
},
|
||||
"pickup_active": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"pickup_address": {
|
||||
"type": "string"
|
||||
},
|
||||
"pickup_hours": {
|
||||
"type": "string"
|
||||
},
|
||||
"preparation_minutes": {
|
||||
"type": "integer"
|
||||
},
|
||||
"price_per_km_cents": {
|
||||
"type": "integer"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"handler.shippingSettingsRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"methods": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/handler.shippingMethodRequest"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -3255,24 +3550,17 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"securityDefinitions": {
|
||||
"BearerAuth": {
|
||||
"type": "apiKey",
|
||||
"name": "Authorization",
|
||||
"in": "header"
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
// SwaggerInfo holds exported Swagger Info so clients can modify it
|
||||
var SwaggerInfo = &swag.Spec{
|
||||
Version: "1.0",
|
||||
Version: "",
|
||||
Host: "",
|
||||
BasePath: "/",
|
||||
Schemes: []string{"http"},
|
||||
Title: "SaveInMed Performance Core API",
|
||||
Description: "API REST B2B para marketplace farmacêutico com split de pagamento e rastreabilidade.",
|
||||
BasePath: "",
|
||||
Schemes: []string{},
|
||||
Title: "",
|
||||
Description: "",
|
||||
InfoInstanceName: "swagger",
|
||||
SwaggerTemplate: docTemplate,
|
||||
LeftDelim: "{{",
|
||||
|
|
|
|||
|
|
@ -1,19 +1,121 @@
|
|||
{
|
||||
"schemes": [
|
||||
"http"
|
||||
],
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"description": "API REST B2B para marketplace farmacêutico com split de pagamento e rastreabilidade.",
|
||||
"title": "SaveInMed Performance Core API",
|
||||
"contact": {
|
||||
"name": "Engenharia SaveInMed",
|
||||
"email": "devops@saveinmed.com"
|
||||
},
|
||||
"version": "1.0"
|
||||
"contact": {}
|
||||
},
|
||||
"basePath": "/",
|
||||
"paths": {
|
||||
"/api/v1/admin/reviews": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Admin"
|
||||
],
|
||||
"summary": "Lista todas as avaliações (Admin)",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Página",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Tamanho da página",
|
||||
"name": "page_size",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.ReviewPage"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/admin/shipments": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Admin"
|
||||
],
|
||||
"summary": "Lista todos os envios (Admin)",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Página",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Tamanho da página",
|
||||
"name": "page_size",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.ShipmentPage"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/auth/login": {
|
||||
"post": {
|
||||
"description": "Autentica usuário e retorna token JWT.",
|
||||
|
|
@ -1502,6 +1604,60 @@
|
|||
}
|
||||
},
|
||||
"/api/v1/reviews": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Admin"
|
||||
],
|
||||
"summary": "Lista todas as avaliações (Admin)",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Página",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Tamanho da página",
|
||||
"name": "page_size",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.ReviewPage"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
|
|
@ -1549,6 +1705,60 @@
|
|||
}
|
||||
},
|
||||
"/api/v1/shipments": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Admin"
|
||||
],
|
||||
"summary": "Lista todos os envios (Admin)",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Página",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Tamanho da página",
|
||||
"name": "page_size",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.ShipmentPage"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
|
|
@ -1697,10 +1907,7 @@
|
|||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.ShippingMethod"
|
||||
}
|
||||
"$ref": "#/definitions/domain.ShippingSettings"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
|
|
@ -1766,10 +1973,7 @@
|
|||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.ShippingMethod"
|
||||
}
|
||||
"$ref": "#/definitions/domain.ShippingSettings"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
|
|
@ -2221,11 +2425,15 @@
|
|||
"type": "string"
|
||||
},
|
||||
"created_at": {
|
||||
"description": "Timestamps",
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"is_24_hours": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"is_verified": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
|
@ -2239,6 +2447,14 @@
|
|||
"longitude": {
|
||||
"type": "number"
|
||||
},
|
||||
"operating_hours": {
|
||||
"description": "e.g. \"Seg-Sex: 08:00-18:00, Sab: 08:00-12:00\"",
|
||||
"type": "string"
|
||||
},
|
||||
"phone": {
|
||||
"description": "Contact \u0026 Hours",
|
||||
"type": "string"
|
||||
},
|
||||
"state": {
|
||||
"type": "string"
|
||||
},
|
||||
|
|
@ -2308,6 +2524,9 @@
|
|||
"$ref": "#/definitions/domain.OrderItem"
|
||||
}
|
||||
},
|
||||
"payment_method": {
|
||||
"$ref": "#/definitions/domain.PaymentMethod"
|
||||
},
|
||||
"seller_id": {
|
||||
"type": "string"
|
||||
},
|
||||
|
|
@ -2366,6 +2585,19 @@
|
|||
"OrderStatusDelivered"
|
||||
]
|
||||
},
|
||||
"domain.PaymentMethod": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"pix",
|
||||
"credit_card",
|
||||
"debit_card"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"PaymentMethodPix",
|
||||
"PaymentMethodCredit",
|
||||
"PaymentMethodDebit"
|
||||
]
|
||||
},
|
||||
"domain.PaymentPreference": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -2441,21 +2673,33 @@
|
|||
"batch": {
|
||||
"type": "string"
|
||||
},
|
||||
"category": {
|
||||
"type": "string"
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"ean_code": {
|
||||
"type": "string"
|
||||
},
|
||||
"expires_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"manufacturer": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"observations": {
|
||||
"type": "string"
|
||||
},
|
||||
"price_cents": {
|
||||
"type": "integer"
|
||||
},
|
||||
|
|
@ -2465,6 +2709,9 @@
|
|||
"stock": {
|
||||
"type": "integer"
|
||||
},
|
||||
"subcategory": {
|
||||
"type": "string"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
}
|
||||
|
|
@ -2516,6 +2763,9 @@
|
|||
"batch": {
|
||||
"type": "string"
|
||||
},
|
||||
"category": {
|
||||
"type": "string"
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string"
|
||||
},
|
||||
|
|
@ -2525,15 +2775,24 @@
|
|||
"distance_km": {
|
||||
"type": "number"
|
||||
},
|
||||
"ean_code": {
|
||||
"type": "string"
|
||||
},
|
||||
"expires_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"manufacturer": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"observations": {
|
||||
"type": "string"
|
||||
},
|
||||
"price_cents": {
|
||||
"type": "integer"
|
||||
},
|
||||
|
|
@ -2543,6 +2802,9 @@
|
|||
"stock": {
|
||||
"type": "integer"
|
||||
},
|
||||
"subcategory": {
|
||||
"type": "string"
|
||||
},
|
||||
"tenant_city": {
|
||||
"type": "string"
|
||||
},
|
||||
|
|
@ -2580,6 +2842,26 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.ReviewPage": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"page": {
|
||||
"type": "integer"
|
||||
},
|
||||
"page_size": {
|
||||
"type": "integer"
|
||||
},
|
||||
"reviews": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.Review"
|
||||
}
|
||||
},
|
||||
"total": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.SellerDashboard": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -2635,6 +2917,26 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.ShipmentPage": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"page": {
|
||||
"type": "integer"
|
||||
},
|
||||
"page_size": {
|
||||
"type": "integer"
|
||||
},
|
||||
"shipments": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.Shipment"
|
||||
}
|
||||
},
|
||||
"total": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.ShippingAddress": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -2667,63 +2969,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.ShippingMethod": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"active": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"free_shipping_threshold_cents": {
|
||||
"type": "integer"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"max_radius_km": {
|
||||
"type": "number"
|
||||
},
|
||||
"min_fee_cents": {
|
||||
"type": "integer"
|
||||
},
|
||||
"pickup_address": {
|
||||
"type": "string"
|
||||
},
|
||||
"pickup_hours": {
|
||||
"type": "string"
|
||||
},
|
||||
"preparation_minutes": {
|
||||
"type": "integer"
|
||||
},
|
||||
"price_per_km_cents": {
|
||||
"type": "integer"
|
||||
},
|
||||
"type": {
|
||||
"$ref": "#/definitions/domain.ShippingMethodType"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"vendor_id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.ShippingMethodType": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"pickup",
|
||||
"own_delivery",
|
||||
"third_party_delivery"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"ShippingMethodPickup",
|
||||
"ShippingMethodOwnDelivery",
|
||||
"ShippingMethodThirdParty"
|
||||
]
|
||||
},
|
||||
"domain.ShippingOption": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -2744,6 +2989,50 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.ShippingSettings": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"active": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"free_shipping_threshold_cents": {
|
||||
"type": "integer"
|
||||
},
|
||||
"latitude": {
|
||||
"type": "number"
|
||||
},
|
||||
"longitude": {
|
||||
"type": "number"
|
||||
},
|
||||
"max_radius_km": {
|
||||
"type": "number"
|
||||
},
|
||||
"min_fee_cents": {
|
||||
"type": "integer"
|
||||
},
|
||||
"pickup_active": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"pickup_address": {
|
||||
"type": "string"
|
||||
},
|
||||
"pickup_hours": {
|
||||
"type": "string"
|
||||
},
|
||||
"price_per_km_cents": {
|
||||
"type": "integer"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"vendor_id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.TopProduct": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -2847,6 +3136,9 @@
|
|||
"$ref": "#/definitions/domain.OrderItem"
|
||||
}
|
||||
},
|
||||
"payment_method": {
|
||||
"$ref": "#/definitions/domain.PaymentMethod"
|
||||
},
|
||||
"seller_id": {
|
||||
"type": "string"
|
||||
},
|
||||
|
|
@ -2934,6 +3226,9 @@
|
|||
"handler.loginRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"password": {
|
||||
"type": "string"
|
||||
},
|
||||
|
|
@ -3111,7 +3406,7 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"handler.shippingMethodRequest": {
|
||||
"handler.shippingSettingsRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"active": {
|
||||
|
|
@ -3120,37 +3415,30 @@
|
|||
"free_shipping_threshold_cents": {
|
||||
"type": "integer"
|
||||
},
|
||||
"latitude": {
|
||||
"description": "Store location for radius calc",
|
||||
"type": "number"
|
||||
},
|
||||
"longitude": {
|
||||
"type": "number"
|
||||
},
|
||||
"max_radius_km": {
|
||||
"type": "number"
|
||||
},
|
||||
"min_fee_cents": {
|
||||
"type": "integer"
|
||||
},
|
||||
"pickup_active": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"pickup_address": {
|
||||
"type": "string"
|
||||
},
|
||||
"pickup_hours": {
|
||||
"type": "string"
|
||||
},
|
||||
"preparation_minutes": {
|
||||
"type": "integer"
|
||||
},
|
||||
"price_per_km_cents": {
|
||||
"type": "integer"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"handler.shippingSettingsRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"methods": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/handler.shippingMethodRequest"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -3251,12 +3539,5 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"securityDefinitions": {
|
||||
"BearerAuth": {
|
||||
"type": "apiKey",
|
||||
"name": "Authorization",
|
||||
"in": "header"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
basePath: /
|
||||
definitions:
|
||||
domain.AdminDashboard:
|
||||
properties:
|
||||
|
|
@ -59,9 +58,12 @@ definitions:
|
|||
corporate_name:
|
||||
type: string
|
||||
created_at:
|
||||
description: Timestamps
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
is_24_hours:
|
||||
type: boolean
|
||||
is_verified:
|
||||
type: boolean
|
||||
latitude:
|
||||
|
|
@ -71,6 +73,12 @@ definitions:
|
|||
type: string
|
||||
longitude:
|
||||
type: number
|
||||
operating_hours:
|
||||
description: 'e.g. "Seg-Sex: 08:00-18:00, Sab: 08:00-12:00"'
|
||||
type: string
|
||||
phone:
|
||||
description: Contact & Hours
|
||||
type: string
|
||||
state:
|
||||
type: string
|
||||
updated_at:
|
||||
|
|
@ -116,6 +124,8 @@ definitions:
|
|||
items:
|
||||
$ref: '#/definitions/domain.OrderItem'
|
||||
type: array
|
||||
payment_method:
|
||||
$ref: '#/definitions/domain.PaymentMethod'
|
||||
seller_id:
|
||||
type: string
|
||||
shipping:
|
||||
|
|
@ -156,6 +166,16 @@ definitions:
|
|||
- OrderStatusPaid
|
||||
- OrderStatusInvoiced
|
||||
- OrderStatusDelivered
|
||||
domain.PaymentMethod:
|
||||
enum:
|
||||
- pix
|
||||
- credit_card
|
||||
- debit_card
|
||||
type: string
|
||||
x-enum-varnames:
|
||||
- PaymentMethodPix
|
||||
- PaymentMethodCredit
|
||||
- PaymentMethodDebit
|
||||
domain.PaymentPreference:
|
||||
properties:
|
||||
commission_pct:
|
||||
|
|
@ -205,22 +225,32 @@ definitions:
|
|||
properties:
|
||||
batch:
|
||||
type: string
|
||||
category:
|
||||
type: string
|
||||
created_at:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
ean_code:
|
||||
type: string
|
||||
expires_at:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
manufacturer:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
observations:
|
||||
type: string
|
||||
price_cents:
|
||||
type: integer
|
||||
seller_id:
|
||||
type: string
|
||||
stock:
|
||||
type: integer
|
||||
subcategory:
|
||||
type: string
|
||||
updated_at:
|
||||
type: string
|
||||
type: object
|
||||
|
|
@ -254,24 +284,34 @@ definitions:
|
|||
properties:
|
||||
batch:
|
||||
type: string
|
||||
category:
|
||||
type: string
|
||||
created_at:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
distance_km:
|
||||
type: number
|
||||
ean_code:
|
||||
type: string
|
||||
expires_at:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
manufacturer:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
observations:
|
||||
type: string
|
||||
price_cents:
|
||||
type: integer
|
||||
seller_id:
|
||||
type: string
|
||||
stock:
|
||||
type: integer
|
||||
subcategory:
|
||||
type: string
|
||||
tenant_city:
|
||||
type: string
|
||||
tenant_state:
|
||||
|
|
@ -296,6 +336,19 @@ definitions:
|
|||
seller_id:
|
||||
type: string
|
||||
type: object
|
||||
domain.ReviewPage:
|
||||
properties:
|
||||
page:
|
||||
type: integer
|
||||
page_size:
|
||||
type: integer
|
||||
reviews:
|
||||
items:
|
||||
$ref: '#/definitions/domain.Review'
|
||||
type: array
|
||||
total:
|
||||
type: integer
|
||||
type: object
|
||||
domain.SellerDashboard:
|
||||
properties:
|
||||
low_stock_alerts:
|
||||
|
|
@ -332,6 +385,19 @@ definitions:
|
|||
updated_at:
|
||||
type: string
|
||||
type: object
|
||||
domain.ShipmentPage:
|
||||
properties:
|
||||
page:
|
||||
type: integer
|
||||
page_size:
|
||||
type: integer
|
||||
shipments:
|
||||
items:
|
||||
$ref: '#/definitions/domain.Shipment'
|
||||
type: array
|
||||
total:
|
||||
type: integer
|
||||
type: object
|
||||
domain.ShippingAddress:
|
||||
properties:
|
||||
city:
|
||||
|
|
@ -353,45 +419,6 @@ definitions:
|
|||
zip_code:
|
||||
type: string
|
||||
type: object
|
||||
domain.ShippingMethod:
|
||||
properties:
|
||||
active:
|
||||
type: boolean
|
||||
created_at:
|
||||
type: string
|
||||
free_shipping_threshold_cents:
|
||||
type: integer
|
||||
id:
|
||||
type: string
|
||||
max_radius_km:
|
||||
type: number
|
||||
min_fee_cents:
|
||||
type: integer
|
||||
pickup_address:
|
||||
type: string
|
||||
pickup_hours:
|
||||
type: string
|
||||
preparation_minutes:
|
||||
type: integer
|
||||
price_per_km_cents:
|
||||
type: integer
|
||||
type:
|
||||
$ref: '#/definitions/domain.ShippingMethodType'
|
||||
updated_at:
|
||||
type: string
|
||||
vendor_id:
|
||||
type: string
|
||||
type: object
|
||||
domain.ShippingMethodType:
|
||||
enum:
|
||||
- pickup
|
||||
- own_delivery
|
||||
- third_party_delivery
|
||||
type: string
|
||||
x-enum-varnames:
|
||||
- ShippingMethodPickup
|
||||
- ShippingMethodOwnDelivery
|
||||
- ShippingMethodThirdParty
|
||||
domain.ShippingOption:
|
||||
properties:
|
||||
description:
|
||||
|
|
@ -405,6 +432,35 @@ definitions:
|
|||
value_cents:
|
||||
type: integer
|
||||
type: object
|
||||
domain.ShippingSettings:
|
||||
properties:
|
||||
active:
|
||||
type: boolean
|
||||
created_at:
|
||||
type: string
|
||||
free_shipping_threshold_cents:
|
||||
type: integer
|
||||
latitude:
|
||||
type: number
|
||||
longitude:
|
||||
type: number
|
||||
max_radius_km:
|
||||
type: number
|
||||
min_fee_cents:
|
||||
type: integer
|
||||
pickup_active:
|
||||
type: boolean
|
||||
pickup_address:
|
||||
type: string
|
||||
pickup_hours:
|
||||
type: string
|
||||
price_per_km_cents:
|
||||
type: integer
|
||||
updated_at:
|
||||
type: string
|
||||
vendor_id:
|
||||
type: string
|
||||
type: object
|
||||
domain.TopProduct:
|
||||
properties:
|
||||
name:
|
||||
|
|
@ -472,6 +528,8 @@ definitions:
|
|||
items:
|
||||
$ref: '#/definitions/domain.OrderItem'
|
||||
type: array
|
||||
payment_method:
|
||||
$ref: '#/definitions/domain.PaymentMethod'
|
||||
seller_id:
|
||||
type: string
|
||||
shipping:
|
||||
|
|
@ -528,6 +586,8 @@ definitions:
|
|||
type: object
|
||||
handler.loginRequest:
|
||||
properties:
|
||||
email:
|
||||
type: string
|
||||
password:
|
||||
type: string
|
||||
username:
|
||||
|
|
@ -643,33 +703,29 @@ definitions:
|
|||
vendor_id:
|
||||
type: string
|
||||
type: object
|
||||
handler.shippingMethodRequest:
|
||||
handler.shippingSettingsRequest:
|
||||
properties:
|
||||
active:
|
||||
type: boolean
|
||||
free_shipping_threshold_cents:
|
||||
type: integer
|
||||
latitude:
|
||||
description: Store location for radius calc
|
||||
type: number
|
||||
longitude:
|
||||
type: number
|
||||
max_radius_km:
|
||||
type: number
|
||||
min_fee_cents:
|
||||
type: integer
|
||||
pickup_active:
|
||||
type: boolean
|
||||
pickup_address:
|
||||
type: string
|
||||
pickup_hours:
|
||||
type: string
|
||||
preparation_minutes:
|
||||
type: integer
|
||||
price_per_km_cents:
|
||||
type: integer
|
||||
type:
|
||||
type: string
|
||||
type: object
|
||||
handler.shippingSettingsRequest:
|
||||
properties:
|
||||
methods:
|
||||
items:
|
||||
$ref: '#/definitions/handler.shippingMethodRequest'
|
||||
type: array
|
||||
type: object
|
||||
handler.updateCompanyRequest:
|
||||
properties:
|
||||
|
|
@ -735,14 +791,78 @@ definitions:
|
|||
type: string
|
||||
type: object
|
||||
info:
|
||||
contact:
|
||||
email: devops@saveinmed.com
|
||||
name: Engenharia SaveInMed
|
||||
description: API REST B2B para marketplace farmacêutico com split de pagamento e
|
||||
rastreabilidade.
|
||||
title: SaveInMed Performance Core API
|
||||
version: "1.0"
|
||||
contact: {}
|
||||
paths:
|
||||
/api/v1/admin/reviews:
|
||||
get:
|
||||
parameters:
|
||||
- description: Página
|
||||
in: query
|
||||
name: page
|
||||
type: integer
|
||||
- description: Tamanho da página
|
||||
in: query
|
||||
name: page_size
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ReviewPage'
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: Lista todas as avaliações (Admin)
|
||||
tags:
|
||||
- Admin
|
||||
/api/v1/admin/shipments:
|
||||
get:
|
||||
parameters:
|
||||
- description: Página
|
||||
in: query
|
||||
name: page
|
||||
type: integer
|
||||
- description: Tamanho da página
|
||||
in: query
|
||||
name: page_size
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ShipmentPage'
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: Lista todos os envios (Admin)
|
||||
tags:
|
||||
- Admin
|
||||
/api/v1/auth/login:
|
||||
post:
|
||||
consumes:
|
||||
|
|
@ -1697,6 +1817,40 @@ paths:
|
|||
tags:
|
||||
- Produtos
|
||||
/api/v1/reviews:
|
||||
get:
|
||||
parameters:
|
||||
- description: Página
|
||||
in: query
|
||||
name: page
|
||||
type: integer
|
||||
- description: Tamanho da página
|
||||
in: query
|
||||
name: page_size
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ReviewPage'
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: Lista todas as avaliações (Admin)
|
||||
tags:
|
||||
- Admin
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
|
|
@ -1726,6 +1880,40 @@ paths:
|
|||
tags:
|
||||
- Avaliações
|
||||
/api/v1/shipments:
|
||||
get:
|
||||
parameters:
|
||||
- description: Página
|
||||
in: query
|
||||
name: page
|
||||
type: integer
|
||||
- description: Tamanho da página
|
||||
in: query
|
||||
name: page_size
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ShipmentPage'
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: Lista todos os envios (Admin)
|
||||
tags:
|
||||
- Admin
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
|
|
@ -1820,9 +2008,7 @@ paths:
|
|||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/domain.ShippingMethod'
|
||||
type: array
|
||||
$ref: '#/definitions/domain.ShippingSettings'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
|
|
@ -1866,9 +2052,7 @@ paths:
|
|||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/domain.ShippingMethod'
|
||||
type: array
|
||||
$ref: '#/definitions/domain.ShippingSettings'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
|
|
@ -2099,11 +2283,4 @@ paths:
|
|||
summary: Atualizar usuário
|
||||
tags:
|
||||
- Usuários
|
||||
schemes:
|
||||
- http
|
||||
securityDefinitions:
|
||||
BearerAuth:
|
||||
in: header
|
||||
name: Authorization
|
||||
type: apiKey
|
||||
swagger: "2.0"
|
||||
|
|
|
|||
|
|
@ -184,15 +184,16 @@ type InventoryAdjustment struct {
|
|||
|
||||
// Order captures the status lifecycle and payment intent.
|
||||
type Order struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
BuyerID uuid.UUID `db:"buyer_id" json:"buyer_id"`
|
||||
SellerID uuid.UUID `db:"seller_id" json:"seller_id"`
|
||||
Status OrderStatus `db:"status" json:"status"`
|
||||
TotalCents int64 `db:"total_cents" json:"total_cents"`
|
||||
Items []OrderItem `json:"items"`
|
||||
Shipping ShippingAddress `json:"shipping"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
BuyerID uuid.UUID `db:"buyer_id" json:"buyer_id"`
|
||||
SellerID uuid.UUID `db:"seller_id" json:"seller_id"`
|
||||
Status OrderStatus `db:"status" json:"status"`
|
||||
TotalCents int64 `db:"total_cents" json:"total_cents"`
|
||||
PaymentMethod PaymentMethod `db:"payment_method" json:"payment_method"`
|
||||
Items []OrderItem `json:"items"`
|
||||
Shipping ShippingAddress `json:"shipping"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
}
|
||||
|
||||
// OrderItem stores SKU-level batch tracking.
|
||||
|
|
@ -316,6 +317,15 @@ const (
|
|||
OrderStatusDelivered OrderStatus = "Entregue"
|
||||
)
|
||||
|
||||
// PaymentMethod enumerates supported payment types.
|
||||
type PaymentMethod string
|
||||
|
||||
const (
|
||||
PaymentMethodPix PaymentMethod = "pix"
|
||||
PaymentMethodCredit PaymentMethod = "credit_card"
|
||||
PaymentMethodDebit PaymentMethod = "debit_card"
|
||||
)
|
||||
|
||||
// CartItem stores buyer selections with unit pricing.
|
||||
type CartItem struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
|
|
|
|||
|
|
@ -155,10 +155,11 @@ type updateProductRequest struct {
|
|||
}
|
||||
|
||||
type createOrderRequest struct {
|
||||
BuyerID uuid.UUID `json:"buyer_id"`
|
||||
SellerID uuid.UUID `json:"seller_id"`
|
||||
Items []domain.OrderItem `json:"items"`
|
||||
Shipping domain.ShippingAddress `json:"shipping"`
|
||||
BuyerID uuid.UUID `json:"buyer_id"`
|
||||
SellerID uuid.UUID `json:"seller_id"`
|
||||
Items []domain.OrderItem `json:"items"`
|
||||
Shipping domain.ShippingAddress `json:"shipping"`
|
||||
PaymentMethod domain.PaymentMethod `json:"payment_method"`
|
||||
}
|
||||
|
||||
type createShipmentRequest struct {
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ func NewMockRepository() *MockRepository {
|
|||
|
||||
// Company methods
|
||||
func (m *MockRepository) CreateCompany(ctx context.Context, company *domain.Company) error {
|
||||
id, _ := uuid.NewV4()
|
||||
id, _ := uuid.NewV7()
|
||||
company.ID = id
|
||||
company.CreatedAt = time.Now()
|
||||
company.UpdatedAt = time.Now()
|
||||
|
|
@ -81,7 +81,7 @@ func (m *MockRepository) DeleteCompany(ctx context.Context, id uuid.UUID) error
|
|||
|
||||
// Product methods
|
||||
func (m *MockRepository) CreateProduct(ctx context.Context, product *domain.Product) error {
|
||||
id, _ := uuid.NewV4()
|
||||
id, _ := uuid.NewV7()
|
||||
product.ID = id
|
||||
product.CreatedAt = time.Now()
|
||||
product.UpdatedAt = time.Now()
|
||||
|
|
@ -140,7 +140,7 @@ func (m *MockRepository) SearchProducts(ctx context.Context, filter domain.Produ
|
|||
}
|
||||
|
||||
func (m *MockRepository) CreateOrder(ctx context.Context, order *domain.Order) error {
|
||||
id, _ := uuid.NewV4()
|
||||
id, _ := uuid.NewV7()
|
||||
order.ID = id
|
||||
m.orders = append(m.orders, *order)
|
||||
return nil
|
||||
|
|
@ -176,7 +176,7 @@ func (m *MockRepository) GetShipmentByOrderID(ctx context.Context, orderID uuid.
|
|||
}
|
||||
|
||||
func (m *MockRepository) CreateUser(ctx context.Context, user *domain.User) error {
|
||||
id, _ := uuid.NewV4()
|
||||
id, _ := uuid.NewV7()
|
||||
user.ID = id
|
||||
m.users = append(m.users, *user)
|
||||
return nil
|
||||
|
|
@ -365,7 +365,7 @@ func TestCreateCompany(t *testing.T) {
|
|||
func TestCreateProduct(t *testing.T) {
|
||||
h := newTestHandler()
|
||||
|
||||
sellerID, _ := uuid.NewV4()
|
||||
sellerID, _ := uuid.NewV7()
|
||||
payload := `{"seller_id":"` + sellerID.String() + `","name":"Aspirin","description":"Pain relief","batch":"BATCH-001","expires_at":"2025-12-31T00:00:00Z","price_cents":1000,"stock":100}`
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/products", bytes.NewReader([]byte(payload)))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
|
@ -401,7 +401,7 @@ func TestAdminLogin_Success(t *testing.T) {
|
|||
h := New(svc)
|
||||
|
||||
// Create admin user through service (which hashes password)
|
||||
companyID, _ := uuid.NewV4()
|
||||
companyID, _ := uuid.NewV7()
|
||||
user := &domain.User{
|
||||
CompanyID: companyID,
|
||||
Role: "admin",
|
||||
|
|
@ -445,7 +445,7 @@ func TestAdminLogin_WrongPassword(t *testing.T) {
|
|||
h := New(svc)
|
||||
|
||||
// Create admin user
|
||||
companyID, _ := uuid.NewV4()
|
||||
companyID, _ := uuid.NewV7()
|
||||
user := &domain.User{
|
||||
CompanyID: companyID,
|
||||
Role: "admin",
|
||||
|
|
@ -519,7 +519,7 @@ func TestRegister_MissingCompany(t *testing.T) {
|
|||
|
||||
func TestGetCompany_NotFound(t *testing.T) {
|
||||
h := newTestHandler()
|
||||
id, _ := uuid.NewV4()
|
||||
id, _ := uuid.NewV7()
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/companies/"+id.String(), nil)
|
||||
rec := httptest.NewRecorder()
|
||||
h.GetCompany(rec, req)
|
||||
|
|
@ -540,7 +540,7 @@ func TestGetCompany_InvalidUUID(t *testing.T) {
|
|||
|
||||
func TestUpdateCompany_InvalidJSON(t *testing.T) {
|
||||
h := newTestHandler()
|
||||
id, _ := uuid.NewV4()
|
||||
id, _ := uuid.NewV7()
|
||||
req := httptest.NewRequest(http.MethodPatch, "/api/v1/companies/"+id.String(), bytes.NewReader([]byte("{")))
|
||||
rec := httptest.NewRecorder()
|
||||
h.UpdateCompany(rec, req)
|
||||
|
|
@ -573,7 +573,7 @@ func TestVerifyCompany_InvalidPath(t *testing.T) {
|
|||
|
||||
func TestGetProduct_NotFound(t *testing.T) {
|
||||
h := newTestHandler()
|
||||
id, _ := uuid.NewV4()
|
||||
id, _ := uuid.NewV7()
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/products/"+id.String(), nil)
|
||||
rec := httptest.NewRecorder()
|
||||
h.GetProduct(rec, req)
|
||||
|
|
@ -584,7 +584,7 @@ func TestGetProduct_NotFound(t *testing.T) {
|
|||
|
||||
func TestUpdateProduct_InvalidJSON(t *testing.T) {
|
||||
h := newTestHandler()
|
||||
id, _ := uuid.NewV4()
|
||||
id, _ := uuid.NewV7()
|
||||
req := httptest.NewRequest(http.MethodPatch, "/api/v1/products/"+id.String(), bytes.NewReader([]byte("{")))
|
||||
rec := httptest.NewRecorder()
|
||||
h.UpdateProduct(rec, req)
|
||||
|
|
@ -637,7 +637,7 @@ func TestAdjustInventory_InvalidJSON(t *testing.T) {
|
|||
|
||||
func TestAdjustInventory_ZeroDelta(t *testing.T) {
|
||||
h := newTestHandler()
|
||||
id, _ := uuid.NewV4()
|
||||
id, _ := uuid.NewV7()
|
||||
payload := `{"product_id":"` + id.String() + `","delta":0,"reason":"test"}`
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/inventory/adjust", bytes.NewReader([]byte(payload)))
|
||||
rec := httptest.NewRecorder()
|
||||
|
|
@ -661,7 +661,7 @@ func TestCreateOrder_InvalidJSON(t *testing.T) {
|
|||
|
||||
func TestGetOrder_NotFound(t *testing.T) {
|
||||
h := newTestHandler()
|
||||
id, _ := uuid.NewV4()
|
||||
id, _ := uuid.NewV7()
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/orders/"+id.String(), nil)
|
||||
rec := httptest.NewRecorder()
|
||||
h.GetOrder(rec, req)
|
||||
|
|
@ -672,7 +672,7 @@ func TestGetOrder_NotFound(t *testing.T) {
|
|||
|
||||
func TestUpdateOrderStatus_InvalidStatus(t *testing.T) {
|
||||
h := newTestHandler()
|
||||
id, _ := uuid.NewV4()
|
||||
id, _ := uuid.NewV7()
|
||||
payload := `{"status":"invalid_status"}`
|
||||
req := httptest.NewRequest(http.MethodPatch, "/api/v1/orders/"+id.String()+"/status", bytes.NewReader([]byte(payload)))
|
||||
rec := httptest.NewRecorder()
|
||||
|
|
@ -783,7 +783,7 @@ func TestListUsers_Success(t *testing.T) {
|
|||
|
||||
func TestGetUser_NotFound(t *testing.T) {
|
||||
h := newTestHandler()
|
||||
id, _ := uuid.NewV4()
|
||||
id, _ := uuid.NewV7()
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/users/"+id.String(), nil)
|
||||
req.Header.Set("X-User-Role", "Admin")
|
||||
rec := httptest.NewRecorder()
|
||||
|
|
@ -806,7 +806,7 @@ func TestCreateUser_InvalidJSON(t *testing.T) {
|
|||
|
||||
func TestUpdateUser_InvalidJSON(t *testing.T) {
|
||||
h := newTestHandler()
|
||||
id, _ := uuid.NewV4()
|
||||
id, _ := uuid.NewV7()
|
||||
req := httptest.NewRequest(http.MethodPut, "/api/v1/users/"+id.String(), bytes.NewReader([]byte("{")))
|
||||
req.Header.Set("X-User-Role", "Admin")
|
||||
rec := httptest.NewRecorder()
|
||||
|
|
@ -851,7 +851,7 @@ func TestAddToCart_InvalidJSON(t *testing.T) {
|
|||
|
||||
func TestDeleteCartItem_NoContext(t *testing.T) {
|
||||
h := newTestHandler()
|
||||
id, _ := uuid.NewV4()
|
||||
id, _ := uuid.NewV7()
|
||||
req := httptest.NewRequest(http.MethodDelete, "/api/v1/cart/"+id.String(), nil)
|
||||
rec := httptest.NewRecorder()
|
||||
h.DeleteCartItem(rec, req)
|
||||
|
|
|
|||
|
|
@ -23,10 +23,11 @@ func (h *Handler) CreateOrder(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
order := &domain.Order{
|
||||
BuyerID: req.BuyerID,
|
||||
SellerID: req.SellerID,
|
||||
Items: req.Items,
|
||||
Shipping: req.Shipping,
|
||||
BuyerID: req.BuyerID,
|
||||
SellerID: req.SellerID,
|
||||
Items: req.Items,
|
||||
Shipping: req.Shipping,
|
||||
PaymentMethod: req.PaymentMethod,
|
||||
}
|
||||
|
||||
var total int64
|
||||
|
|
|
|||
|
|
@ -113,8 +113,8 @@ func createTestToken(secret string, userID uuid.UUID, role string, companyID *uu
|
|||
|
||||
func TestRequireAuthValidToken(t *testing.T) {
|
||||
secret := "test-secret"
|
||||
userID, _ := uuid.NewV4()
|
||||
companyID, _ := uuid.NewV4()
|
||||
userID, _ := uuid.NewV7()
|
||||
companyID, _ := uuid.NewV7()
|
||||
tokenStr := createTestToken(secret, userID, "Admin", &companyID)
|
||||
|
||||
var receivedClaims Claims
|
||||
|
|
@ -172,7 +172,7 @@ func TestRequireAuthInvalidToken(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestRequireAuthWrongSecret(t *testing.T) {
|
||||
userID, _ := uuid.NewV4()
|
||||
userID, _ := uuid.NewV7()
|
||||
tokenStr := createTestToken("correct-secret", userID, "User", nil)
|
||||
|
||||
handler := RequireAuth([]byte("wrong-secret"))(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
@ -192,7 +192,7 @@ func TestRequireAuthWrongSecret(t *testing.T) {
|
|||
|
||||
func TestRequireAuthRoleRestriction(t *testing.T) {
|
||||
secret := "secret"
|
||||
userID, _ := uuid.NewV4()
|
||||
userID, _ := uuid.NewV7()
|
||||
tokenStr := createTestToken(secret, userID, "User", nil)
|
||||
|
||||
handler := RequireAuth([]byte(secret), "Admin")(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
@ -212,7 +212,7 @@ func TestRequireAuthRoleRestriction(t *testing.T) {
|
|||
|
||||
func TestRequireAuthRoleAllowed(t *testing.T) {
|
||||
secret := "secret"
|
||||
userID, _ := uuid.NewV4()
|
||||
userID, _ := uuid.NewV7()
|
||||
tokenStr := createTestToken(secret, userID, "Admin", nil)
|
||||
|
||||
handler := RequireAuth([]byte(secret), "Admin")(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
@ -232,7 +232,7 @@ func TestRequireAuthRoleAllowed(t *testing.T) {
|
|||
|
||||
func TestGetClaimsFromContext(t *testing.T) {
|
||||
claims := Claims{
|
||||
UserID: uuid.Must(uuid.NewV4()),
|
||||
UserID: uuid.Must(uuid.NewV7()),
|
||||
Role: "Admin",
|
||||
}
|
||||
ctx := context.WithValue(context.Background(), claimsKey, claims)
|
||||
|
|
@ -345,7 +345,7 @@ func TestCORSLegacyWrapper(t *testing.T) {
|
|||
|
||||
func TestRequireAuthExpiredToken(t *testing.T) {
|
||||
secret := "test-secret"
|
||||
userID, _ := uuid.NewV4()
|
||||
userID, _ := uuid.NewV7()
|
||||
|
||||
// Create an expired token
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
||||
|
|
|
|||
|
|
@ -24,9 +24,9 @@ func TestCreatePreference(t *testing.T) {
|
|||
gateway := NewMercadoPagoGateway("https://api.mercadopago.com", 2.5)
|
||||
|
||||
order := &domain.Order{
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
BuyerID: uuid.Must(uuid.NewV4()),
|
||||
SellerID: uuid.Must(uuid.NewV4()),
|
||||
ID: uuid.Must(uuid.NewV7()),
|
||||
BuyerID: uuid.Must(uuid.NewV7()),
|
||||
SellerID: uuid.Must(uuid.NewV7()),
|
||||
TotalCents: 10000, // R$100
|
||||
}
|
||||
|
||||
|
|
@ -83,7 +83,7 @@ func TestCreatePreferenceWithDifferentCommissions(t *testing.T) {
|
|||
t.Run(tc.name, func(t *testing.T) {
|
||||
gateway := NewMercadoPagoGateway("https://test.com", tc.commission)
|
||||
order := &domain.Order{
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
ID: uuid.Must(uuid.NewV7()),
|
||||
TotalCents: tc.totalCents,
|
||||
}
|
||||
|
||||
|
|
@ -106,7 +106,7 @@ func TestCreatePreferenceWithCancelledContext(t *testing.T) {
|
|||
gateway := NewMercadoPagoGateway("https://api.mercadopago.com", 2.5)
|
||||
|
||||
order := &domain.Order{
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
ID: uuid.Must(uuid.NewV7()),
|
||||
TotalCents: 10000,
|
||||
}
|
||||
|
||||
|
|
@ -123,7 +123,7 @@ func TestCreatePreferenceWithTimeout(t *testing.T) {
|
|||
gateway := NewMercadoPagoGateway("https://api.mercadopago.com", 2.5)
|
||||
|
||||
order := &domain.Order{
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
ID: uuid.Must(uuid.NewV7()),
|
||||
TotalCents: 10000,
|
||||
}
|
||||
|
||||
|
|
@ -144,7 +144,7 @@ func TestCreatePreferenceWithZeroTotal(t *testing.T) {
|
|||
gateway := NewMercadoPagoGateway("https://api.mercadopago.com", 2.5)
|
||||
|
||||
order := &domain.Order{
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
ID: uuid.Must(uuid.NewV7()),
|
||||
TotalCents: 0,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -459,9 +459,9 @@ func (r *Repository) CreateOrder(ctx context.Context, order *domain.Order) error
|
|||
return err
|
||||
}
|
||||
|
||||
orderQuery := `INSERT INTO orders (id, buyer_id, seller_id, status, total_cents, shipping_recipient_name, shipping_street, shipping_number, shipping_complement, shipping_district, shipping_city, shipping_state, shipping_zip_code, shipping_country, created_at, updated_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16)`
|
||||
if _, err := tx.ExecContext(ctx, orderQuery, order.ID, order.BuyerID, order.SellerID, order.Status, order.TotalCents, order.Shipping.RecipientName, order.Shipping.Street, order.Shipping.Number, order.Shipping.Complement, order.Shipping.District, order.Shipping.City, order.Shipping.State, order.Shipping.ZipCode, order.Shipping.Country, order.CreatedAt, order.UpdatedAt); err != nil {
|
||||
orderQuery := `INSERT INTO orders (id, buyer_id, seller_id, status, total_cents, payment_method, shipping_recipient_name, shipping_street, shipping_number, shipping_complement, shipping_district, shipping_city, shipping_state, shipping_zip_code, shipping_country, created_at, updated_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17)`
|
||||
if _, err := tx.ExecContext(ctx, orderQuery, order.ID, order.BuyerID, order.SellerID, order.Status, order.TotalCents, order.PaymentMethod, order.Shipping.RecipientName, order.Shipping.Street, order.Shipping.Number, order.Shipping.Complement, order.Shipping.District, order.Shipping.City, order.Shipping.State, order.Shipping.ZipCode, order.Shipping.Country, order.CreatedAt, order.UpdatedAt); err != nil {
|
||||
_ = tx.Rollback()
|
||||
return err
|
||||
}
|
||||
|
|
@ -528,25 +528,26 @@ func (r *Repository) ListOrders(ctx context.Context, filter domain.OrderFilter)
|
|||
filter.Limit = 20
|
||||
}
|
||||
args = append(args, filter.Limit, filter.Offset)
|
||||
listQuery := fmt.Sprintf(`SELECT id, buyer_id, seller_id, status, total_cents, COALESCE(shipping_recipient_name, '') as shipping_recipient_name, COALESCE(shipping_street, '') as shipping_street, COALESCE(shipping_number, '') as shipping_number, COALESCE(shipping_complement, '') as shipping_complement, COALESCE(shipping_district, '') as shipping_district, COALESCE(shipping_city, '') as shipping_city, COALESCE(shipping_state, '') as shipping_state, COALESCE(shipping_zip_code, '') as shipping_zip_code, COALESCE(shipping_country, '') as shipping_country, created_at, updated_at %s%s ORDER BY created_at DESC LIMIT $%d OFFSET $%d`, baseQuery, where, len(args)-1, len(args))
|
||||
listQuery := fmt.Sprintf(`SELECT id, buyer_id, seller_id, status, total_cents, payment_method, COALESCE(shipping_recipient_name, '') as shipping_recipient_name, COALESCE(shipping_street, '') as shipping_street, COALESCE(shipping_number, '') as shipping_number, COALESCE(shipping_complement, '') as shipping_complement, COALESCE(shipping_district, '') as shipping_district, COALESCE(shipping_city, '') as shipping_city, COALESCE(shipping_state, '') as shipping_state, COALESCE(shipping_zip_code, '') as shipping_zip_code, COALESCE(shipping_country, '') as shipping_country, created_at, updated_at %s%s ORDER BY created_at DESC LIMIT $%d OFFSET $%d`, baseQuery, where, len(args)-1, len(args))
|
||||
|
||||
var rows []struct {
|
||||
ID uuid.UUID `db:"id"`
|
||||
BuyerID uuid.UUID `db:"buyer_id"`
|
||||
SellerID uuid.UUID `db:"seller_id"`
|
||||
Status domain.OrderStatus `db:"status"`
|
||||
TotalCents int64 `db:"total_cents"`
|
||||
ShippingRecipientName string `db:"shipping_recipient_name"`
|
||||
ShippingStreet string `db:"shipping_street"`
|
||||
ShippingNumber string `db:"shipping_number"`
|
||||
ShippingComplement string `db:"shipping_complement"`
|
||||
ShippingDistrict string `db:"shipping_district"`
|
||||
ShippingCity string `db:"shipping_city"`
|
||||
ShippingState string `db:"shipping_state"`
|
||||
ShippingZipCode string `db:"shipping_zip_code"`
|
||||
ShippingCountry string `db:"shipping_country"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
ID uuid.UUID `db:"id"`
|
||||
BuyerID uuid.UUID `db:"buyer_id"`
|
||||
SellerID uuid.UUID `db:"seller_id"`
|
||||
Status domain.OrderStatus `db:"status"`
|
||||
TotalCents int64 `db:"total_cents"`
|
||||
PaymentMethod domain.PaymentMethod `db:"payment_method"`
|
||||
ShippingRecipientName string `db:"shipping_recipient_name"`
|
||||
ShippingStreet string `db:"shipping_street"`
|
||||
ShippingNumber string `db:"shipping_number"`
|
||||
ShippingComplement string `db:"shipping_complement"`
|
||||
ShippingDistrict string `db:"shipping_district"`
|
||||
ShippingCity string `db:"shipping_city"`
|
||||
ShippingState string `db:"shipping_state"`
|
||||
ShippingZipCode string `db:"shipping_zip_code"`
|
||||
ShippingCountry string `db:"shipping_country"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
}
|
||||
|
||||
if err := r.db.SelectContext(ctx, &rows, listQuery, args...); err != nil {
|
||||
|
|
@ -561,12 +562,13 @@ func (r *Repository) ListOrders(ctx context.Context, filter domain.OrderFilter)
|
|||
return nil, 0, err
|
||||
}
|
||||
orders = append(orders, domain.Order{
|
||||
ID: row.ID,
|
||||
BuyerID: row.BuyerID,
|
||||
SellerID: row.SellerID,
|
||||
Status: row.Status,
|
||||
TotalCents: row.TotalCents,
|
||||
Items: items,
|
||||
ID: row.ID,
|
||||
BuyerID: row.BuyerID,
|
||||
SellerID: row.SellerID,
|
||||
Status: row.Status,
|
||||
TotalCents: row.TotalCents,
|
||||
PaymentMethod: row.PaymentMethod,
|
||||
Items: items,
|
||||
Shipping: domain.ShippingAddress{
|
||||
RecipientName: row.ShippingRecipientName,
|
||||
Street: row.ShippingStreet,
|
||||
|
|
@ -587,24 +589,25 @@ func (r *Repository) ListOrders(ctx context.Context, filter domain.OrderFilter)
|
|||
|
||||
func (r *Repository) GetOrder(ctx context.Context, id uuid.UUID) (*domain.Order, error) {
|
||||
var row struct {
|
||||
ID uuid.UUID `db:"id"`
|
||||
BuyerID uuid.UUID `db:"buyer_id"`
|
||||
SellerID uuid.UUID `db:"seller_id"`
|
||||
Status domain.OrderStatus `db:"status"`
|
||||
TotalCents int64 `db:"total_cents"`
|
||||
ShippingRecipientName string `db:"shipping_recipient_name"`
|
||||
ShippingStreet string `db:"shipping_street"`
|
||||
ShippingNumber string `db:"shipping_number"`
|
||||
ShippingComplement string `db:"shipping_complement"`
|
||||
ShippingDistrict string `db:"shipping_district"`
|
||||
ShippingCity string `db:"shipping_city"`
|
||||
ShippingState string `db:"shipping_state"`
|
||||
ShippingZipCode string `db:"shipping_zip_code"`
|
||||
ShippingCountry string `db:"shipping_country"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
ID uuid.UUID `db:"id"`
|
||||
BuyerID uuid.UUID `db:"buyer_id"`
|
||||
SellerID uuid.UUID `db:"seller_id"`
|
||||
Status domain.OrderStatus `db:"status"`
|
||||
TotalCents int64 `db:"total_cents"`
|
||||
PaymentMethod domain.PaymentMethod `db:"payment_method"`
|
||||
ShippingRecipientName string `db:"shipping_recipient_name"`
|
||||
ShippingStreet string `db:"shipping_street"`
|
||||
ShippingNumber string `db:"shipping_number"`
|
||||
ShippingComplement string `db:"shipping_complement"`
|
||||
ShippingDistrict string `db:"shipping_district"`
|
||||
ShippingCity string `db:"shipping_city"`
|
||||
ShippingState string `db:"shipping_state"`
|
||||
ShippingZipCode string `db:"shipping_zip_code"`
|
||||
ShippingCountry string `db:"shipping_country"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
}
|
||||
orderQuery := `SELECT id, buyer_id, seller_id, status, total_cents, COALESCE(shipping_recipient_name, '') as shipping_recipient_name, COALESCE(shipping_street, '') as shipping_street, COALESCE(shipping_number, '') as shipping_number, COALESCE(shipping_complement, '') as shipping_complement, COALESCE(shipping_district, '') as shipping_district, COALESCE(shipping_city, '') as shipping_city, COALESCE(shipping_state, '') as shipping_state, COALESCE(shipping_zip_code, '') as shipping_zip_code, COALESCE(shipping_country, '') as shipping_country, created_at, updated_at FROM orders WHERE id = $1`
|
||||
orderQuery := `SELECT id, buyer_id, seller_id, status, total_cents, payment_method, COALESCE(shipping_recipient_name, '') as shipping_recipient_name, COALESCE(shipping_street, '') as shipping_street, COALESCE(shipping_number, '') as shipping_number, COALESCE(shipping_complement, '') as shipping_complement, COALESCE(shipping_district, '') as shipping_district, COALESCE(shipping_city, '') as shipping_city, COALESCE(shipping_state, '') as shipping_state, COALESCE(shipping_zip_code, '') as shipping_zip_code, COALESCE(shipping_country, '') as shipping_country, created_at, updated_at FROM orders WHERE id = $1`
|
||||
if err := r.db.GetContext(ctx, &row, orderQuery, id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -615,12 +618,13 @@ func (r *Repository) GetOrder(ctx context.Context, id uuid.UUID) (*domain.Order,
|
|||
return nil, err
|
||||
}
|
||||
order := &domain.Order{
|
||||
ID: row.ID,
|
||||
BuyerID: row.BuyerID,
|
||||
SellerID: row.SellerID,
|
||||
Status: row.Status,
|
||||
TotalCents: row.TotalCents,
|
||||
Items: items,
|
||||
ID: row.ID,
|
||||
BuyerID: row.BuyerID,
|
||||
SellerID: row.SellerID,
|
||||
Status: row.Status,
|
||||
TotalCents: row.TotalCents,
|
||||
PaymentMethod: row.PaymentMethod,
|
||||
Items: items,
|
||||
Shipping: domain.ShippingAddress{
|
||||
RecipientName: row.ShippingRecipientName,
|
||||
Street: row.ShippingStreet,
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ func TestCreateCompany(t *testing.T) {
|
|||
defer repo.db.Close()
|
||||
|
||||
company := &domain.Company{
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
ID: uuid.Must(uuid.NewV7()),
|
||||
CNPJ: "12345678901234",
|
||||
CorporateName: "Test Pharmacy",
|
||||
Category: "farmacia",
|
||||
|
|
@ -72,7 +72,7 @@ func TestGetCompany(t *testing.T) {
|
|||
repo, mock := newMockRepo(t)
|
||||
defer repo.db.Close()
|
||||
|
||||
id := uuid.Must(uuid.NewV4())
|
||||
id := uuid.Must(uuid.NewV7())
|
||||
|
||||
rows := sqlmock.NewRows([]string{"id", "cnpj", "corporate_name", "category", "license_number", "is_verified", "latitude", "longitude", "city", "state", "created_at", "updated_at"}).
|
||||
AddRow(id, "123", "Test", "farmacia", "123", false, 0.0, 0.0, "City", "ST", time.Now(), time.Now())
|
||||
|
|
@ -97,8 +97,8 @@ func TestCreateProduct(t *testing.T) {
|
|||
defer repo.db.Close()
|
||||
|
||||
product := &domain.Product{
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
SellerID: uuid.Must(uuid.NewV4()),
|
||||
ID: uuid.Must(uuid.NewV7()),
|
||||
SellerID: uuid.Must(uuid.NewV7()),
|
||||
Name: "Test Product",
|
||||
Description: "Desc",
|
||||
Batch: "B1",
|
||||
|
|
@ -130,7 +130,7 @@ func TestListProducts(t *testing.T) {
|
|||
repo, mock := newMockRepo(t)
|
||||
defer repo.db.Close()
|
||||
|
||||
rows := sqlmock.NewRows([]string{"id", "name"}).AddRow(uuid.Must(uuid.NewV4()), "P1")
|
||||
rows := sqlmock.NewRows([]string{"id", "name"}).AddRow(uuid.Must(uuid.NewV7()), "P1")
|
||||
|
||||
// We expect two queries: count and select list
|
||||
mock.ExpectQuery(`SELECT count\(\*\) FROM products`).WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1))
|
||||
|
|
|
|||
|
|
@ -379,7 +379,7 @@ func TestGetCompany(t *testing.T) {
|
|||
ctx := context.Background()
|
||||
|
||||
company := &domain.Company{
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
ID: uuid.Must(uuid.NewV7()),
|
||||
Category: "farmacia",
|
||||
CNPJ: "12345678901234",
|
||||
CorporateName: "Test Pharmacy",
|
||||
|
|
@ -400,7 +400,7 @@ func TestUpdateCompany(t *testing.T) {
|
|||
ctx := context.Background()
|
||||
|
||||
company := &domain.Company{
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
ID: uuid.Must(uuid.NewV7()),
|
||||
Category: "farmacia",
|
||||
CNPJ: "12345678901234",
|
||||
CorporateName: "Test Pharmacy",
|
||||
|
|
@ -423,7 +423,7 @@ func TestDeleteCompany(t *testing.T) {
|
|||
ctx := context.Background()
|
||||
|
||||
company := &domain.Company{
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
ID: uuid.Must(uuid.NewV7()),
|
||||
CorporateName: "Test Pharmacy",
|
||||
}
|
||||
repo.companies = append(repo.companies, *company)
|
||||
|
|
@ -443,7 +443,7 @@ func TestVerifyCompany(t *testing.T) {
|
|||
ctx := context.Background()
|
||||
|
||||
company := &domain.Company{
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
ID: uuid.Must(uuid.NewV7()),
|
||||
Category: "farmacia",
|
||||
CNPJ: "12345678901234",
|
||||
CorporateName: "Test Pharmacy",
|
||||
|
|
@ -468,7 +468,7 @@ func TestRegisterProduct(t *testing.T) {
|
|||
ctx := context.Background()
|
||||
|
||||
product := &domain.Product{
|
||||
SellerID: uuid.Must(uuid.NewV4()),
|
||||
SellerID: uuid.Must(uuid.NewV7()),
|
||||
Name: "Test Product",
|
||||
Description: "A test product",
|
||||
Batch: "BATCH-001",
|
||||
|
|
@ -506,7 +506,7 @@ func TestGetProduct(t *testing.T) {
|
|||
ctx := context.Background()
|
||||
|
||||
product := &domain.Product{
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
ID: uuid.Must(uuid.NewV7()),
|
||||
Name: "Test Product",
|
||||
}
|
||||
repo.products = append(repo.products, *product)
|
||||
|
|
@ -525,7 +525,7 @@ func TestUpdateProduct(t *testing.T) {
|
|||
ctx := context.Background()
|
||||
|
||||
product := &domain.Product{
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
ID: uuid.Must(uuid.NewV7()),
|
||||
Name: "Test Product",
|
||||
}
|
||||
repo.products = append(repo.products, *product)
|
||||
|
|
@ -546,7 +546,7 @@ func TestDeleteProduct(t *testing.T) {
|
|||
ctx := context.Background()
|
||||
|
||||
product := &domain.Product{
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
ID: uuid.Must(uuid.NewV7()),
|
||||
Name: "Test Product",
|
||||
}
|
||||
repo.products = append(repo.products, *product)
|
||||
|
|
@ -592,7 +592,7 @@ func TestListInventory(t *testing.T) {
|
|||
func TestAdjustInventory(t *testing.T) {
|
||||
svc, _ := newTestService()
|
||||
ctx := context.Background()
|
||||
productID := uuid.Must(uuid.NewV4())
|
||||
productID := uuid.Must(uuid.NewV7())
|
||||
|
||||
item, err := svc.AdjustInventory(ctx, productID, 10, "Restock")
|
||||
if err != nil {
|
||||
|
|
@ -611,8 +611,8 @@ func TestCreateOrder(t *testing.T) {
|
|||
ctx := context.Background()
|
||||
|
||||
order := &domain.Order{
|
||||
BuyerID: uuid.Must(uuid.NewV4()),
|
||||
SellerID: uuid.Must(uuid.NewV4()),
|
||||
BuyerID: uuid.Must(uuid.NewV7()),
|
||||
SellerID: uuid.Must(uuid.NewV7()),
|
||||
TotalCents: 10000,
|
||||
}
|
||||
|
||||
|
|
@ -634,9 +634,9 @@ func TestUpdateOrderStatus(t *testing.T) {
|
|||
ctx := context.Background()
|
||||
|
||||
order := &domain.Order{
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
BuyerID: uuid.Must(uuid.NewV4()),
|
||||
SellerID: uuid.Must(uuid.NewV4()),
|
||||
ID: uuid.Must(uuid.NewV7()),
|
||||
BuyerID: uuid.Must(uuid.NewV7()),
|
||||
SellerID: uuid.Must(uuid.NewV7()),
|
||||
Status: domain.OrderStatusPending,
|
||||
TotalCents: 10000,
|
||||
}
|
||||
|
|
@ -655,7 +655,7 @@ func TestCreateUser(t *testing.T) {
|
|||
ctx := context.Background()
|
||||
|
||||
user := &domain.User{
|
||||
CompanyID: uuid.Must(uuid.NewV4()),
|
||||
CompanyID: uuid.Must(uuid.NewV7()),
|
||||
Role: "admin",
|
||||
Name: "Test User",
|
||||
Email: "test@example.com",
|
||||
|
|
@ -722,7 +722,7 @@ func TestAuthenticate(t *testing.T) {
|
|||
|
||||
// First create a user
|
||||
user := &domain.User{
|
||||
CompanyID: uuid.Must(uuid.NewV4()),
|
||||
CompanyID: uuid.Must(uuid.NewV7()),
|
||||
Role: "admin",
|
||||
Name: "Test User",
|
||||
Username: "authuser",
|
||||
|
|
@ -755,7 +755,7 @@ func TestAuthenticateInvalidPassword(t *testing.T) {
|
|||
ctx := context.Background()
|
||||
|
||||
user := &domain.User{
|
||||
CompanyID: uuid.Must(uuid.NewV4()),
|
||||
CompanyID: uuid.Must(uuid.NewV7()),
|
||||
Role: "admin",
|
||||
Name: "Test User",
|
||||
Username: "failuser",
|
||||
|
|
@ -776,10 +776,10 @@ func TestAddItemToCart(t *testing.T) {
|
|||
svc, repo := newTestService()
|
||||
ctx := context.Background()
|
||||
|
||||
buyerID := uuid.Must(uuid.NewV4())
|
||||
buyerID := uuid.Must(uuid.NewV7())
|
||||
product := &domain.Product{
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
SellerID: uuid.Must(uuid.NewV4()),
|
||||
ID: uuid.Must(uuid.NewV7()),
|
||||
SellerID: uuid.Must(uuid.NewV7()),
|
||||
Name: "Test Product",
|
||||
PriceCents: 1000,
|
||||
Stock: 100,
|
||||
|
|
@ -802,8 +802,8 @@ func TestAddItemToCartInvalidQuantity(t *testing.T) {
|
|||
svc, _ := newTestService()
|
||||
ctx := context.Background()
|
||||
|
||||
buyerID := uuid.Must(uuid.NewV4())
|
||||
productID := uuid.Must(uuid.NewV4())
|
||||
buyerID := uuid.Must(uuid.NewV7())
|
||||
productID := uuid.Must(uuid.NewV7())
|
||||
|
||||
_, err := svc.AddItemToCart(ctx, buyerID, productID, 0)
|
||||
if err == nil {
|
||||
|
|
@ -815,10 +815,10 @@ func TestCartB2BDiscount(t *testing.T) {
|
|||
svc, repo := newTestService()
|
||||
ctx := context.Background()
|
||||
|
||||
buyerID := uuid.Must(uuid.NewV4())
|
||||
buyerID := uuid.Must(uuid.NewV7())
|
||||
product := &domain.Product{
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
SellerID: uuid.Must(uuid.NewV4()),
|
||||
ID: uuid.Must(uuid.NewV7()),
|
||||
SellerID: uuid.Must(uuid.NewV7()),
|
||||
Name: "Expensive Product",
|
||||
PriceCents: 50000, // R$500 per unit
|
||||
Stock: 1000,
|
||||
|
|
@ -847,11 +847,11 @@ func TestCreateReview(t *testing.T) {
|
|||
svc, repo := newTestService()
|
||||
ctx := context.Background()
|
||||
|
||||
buyerID := uuid.Must(uuid.NewV4())
|
||||
sellerID := uuid.Must(uuid.NewV4())
|
||||
buyerID := uuid.Must(uuid.NewV7())
|
||||
sellerID := uuid.Must(uuid.NewV7())
|
||||
|
||||
order := &domain.Order{
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
ID: uuid.Must(uuid.NewV7()),
|
||||
BuyerID: buyerID,
|
||||
SellerID: sellerID,
|
||||
Status: domain.OrderStatusDelivered,
|
||||
|
|
@ -873,7 +873,7 @@ func TestCreateReviewInvalidRating(t *testing.T) {
|
|||
svc, _ := newTestService()
|
||||
ctx := context.Background()
|
||||
|
||||
_, err := svc.CreateReview(ctx, uuid.Must(uuid.NewV4()), uuid.Must(uuid.NewV4()), 6, "Invalid")
|
||||
_, err := svc.CreateReview(ctx, uuid.Must(uuid.NewV7()), uuid.Must(uuid.NewV7()), 6, "Invalid")
|
||||
if err == nil {
|
||||
t.Error("expected error for invalid rating")
|
||||
}
|
||||
|
|
@ -883,9 +883,9 @@ func TestCreateReviewNotDelivered(t *testing.T) {
|
|||
svc, repo := newTestService()
|
||||
ctx := context.Background()
|
||||
|
||||
buyerID := uuid.Must(uuid.NewV4())
|
||||
buyerID := uuid.Must(uuid.NewV7())
|
||||
order := &domain.Order{
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
ID: uuid.Must(uuid.NewV7()),
|
||||
BuyerID: buyerID,
|
||||
Status: domain.OrderStatusPending, // Not delivered
|
||||
TotalCents: 10000,
|
||||
|
|
@ -904,7 +904,7 @@ func TestGetSellerDashboard(t *testing.T) {
|
|||
svc, _ := newTestService()
|
||||
ctx := context.Background()
|
||||
|
||||
sellerID := uuid.Must(uuid.NewV4())
|
||||
sellerID := uuid.Must(uuid.NewV7())
|
||||
dashboard, err := svc.GetSellerDashboard(ctx, sellerID)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get seller dashboard: %v", err)
|
||||
|
|
@ -936,9 +936,9 @@ func TestCreatePaymentPreference(t *testing.T) {
|
|||
ctx := context.Background()
|
||||
|
||||
order := &domain.Order{
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
BuyerID: uuid.Must(uuid.NewV4()),
|
||||
SellerID: uuid.Must(uuid.NewV4()),
|
||||
ID: uuid.Must(uuid.NewV7()),
|
||||
BuyerID: uuid.Must(uuid.NewV7()),
|
||||
SellerID: uuid.Must(uuid.NewV7()),
|
||||
TotalCents: 10000,
|
||||
}
|
||||
repo.orders = append(repo.orders, *order)
|
||||
|
|
@ -961,9 +961,9 @@ func TestHandlePaymentWebhook(t *testing.T) {
|
|||
ctx := context.Background()
|
||||
|
||||
order := &domain.Order{
|
||||
ID: uuid.Must(uuid.NewV4()),
|
||||
BuyerID: uuid.Must(uuid.NewV4()),
|
||||
SellerID: uuid.Must(uuid.NewV4()),
|
||||
ID: uuid.Must(uuid.NewV7()),
|
||||
BuyerID: uuid.Must(uuid.NewV7()),
|
||||
SellerID: uuid.Must(uuid.NewV7()),
|
||||
Status: domain.OrderStatusPending,
|
||||
TotalCents: 10000,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
import { useState } from 'react'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { Shell } from '../layouts/Shell'
|
||||
import { useCartStore, selectGroupedCart, selectCartSummary } from '../stores/cartStore'
|
||||
import { useAuth } from '../context/AuthContext'
|
||||
import { ordersService, CreateOrderRequest } from '../services/ordersService'
|
||||
import { shippingService } from '../services/shippingService'
|
||||
import { apiClient } from '../services/apiClient'
|
||||
import { formatCurrency } from '../utils/format'
|
||||
import { ArrowLeft, CheckCircle2, Truck } from 'lucide-react'
|
||||
import { ArrowLeft, CheckCircle2, Truck, Store } from 'lucide-react'
|
||||
|
||||
export function CheckoutPage() {
|
||||
const navigate = useNavigate()
|
||||
|
|
@ -15,6 +17,10 @@ export function CheckoutPage() {
|
|||
const clearAll = useCartStore((state) => state.clearAll)
|
||||
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
// Shipping options state
|
||||
const [shippingOptions, setShippingOptions] = useState<Record<string, { delivery: boolean, pickup: boolean, pickupAddress: string, price: number }>>({})
|
||||
|
||||
const [shipping, setShipping] = useState({
|
||||
recipient_name: user?.name || '',
|
||||
street: '',
|
||||
|
|
@ -27,8 +33,65 @@ export function CheckoutPage() {
|
|||
country: 'Brasil'
|
||||
})
|
||||
|
||||
// Pre-fill address from company
|
||||
useEffect(() => {
|
||||
async function fetchCompanyAddress() {
|
||||
try {
|
||||
// TODO: Use a proper service for this
|
||||
const res = await apiClient.get<any>('/v1/companies/me')
|
||||
const company = res.data
|
||||
if (company) {
|
||||
setShipping(prev => ({
|
||||
...prev,
|
||||
street: company.street || '', // Wait, company model fields differ from shipping fields?
|
||||
// Let's check company model in backend: city, state, etc.
|
||||
// Actually, the backend Company model has: city, state, latitude, longitude.
|
||||
// It seems it does NOT have street, number, district, zip_code separately in the main table?
|
||||
// Let's re-read models.go.
|
||||
// Ah, lines 18-21: Latitude, Longitude, City, State.
|
||||
// It seems we lack detailed address fields in Company model for pre-filling!
|
||||
// Wait, seeder line 113: city, state.
|
||||
// We might only be able to pre-fill city/state for now unless I missed something.
|
||||
city: company.city || '',
|
||||
state: company.state || '',
|
||||
// We can't pre-fill street/number if they aren't there.
|
||||
}))
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Failed to fetch company address", e)
|
||||
}
|
||||
}
|
||||
if (user) fetchCompanyAddress()
|
||||
}, [user])
|
||||
|
||||
// Fetch shipping settings for sellers
|
||||
useEffect(() => {
|
||||
async function fetchShipping() {
|
||||
const options: Record<string, any> = {}
|
||||
for (const sellerId of Object.keys(groups)) {
|
||||
try {
|
||||
const settings = await shippingService.getSettings(sellerId)
|
||||
options[sellerId] = {
|
||||
delivery: settings.active,
|
||||
pickup: settings.pickup_active,
|
||||
pickupAddress: settings.pickup_address,
|
||||
price: 15.00 // Mock calc for now, or implement calculateShipping
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`Failed to fetch shipping for ${sellerId}`, e)
|
||||
}
|
||||
}
|
||||
setShippingOptions(options)
|
||||
}
|
||||
if (Object.keys(groups).length > 0) fetchShipping()
|
||||
}, [groups])
|
||||
|
||||
// Payment method selection
|
||||
const [paymentMethod, setPaymentMethod] = useState<'pix' | 'credit_card' | 'debit_card'>('credit_card')
|
||||
|
||||
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const { name, value } = e.target
|
||||
setPaymentMethod(prev => prev) // Keep state if needed, though strictly not needed for this simple setter
|
||||
setShipping(prev => ({ ...prev, [name]: value }))
|
||||
}
|
||||
|
||||
|
|
@ -59,7 +122,8 @@ export function CheckoutPage() {
|
|||
state: shipping.state,
|
||||
zip_code: shipping.zip_code,
|
||||
country: shipping.country
|
||||
}
|
||||
},
|
||||
payment_method: paymentMethod
|
||||
}
|
||||
return ordersService.createOrder(orderData)
|
||||
})
|
||||
|
|
@ -192,13 +256,58 @@ export function CheckoutPage() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* Payment Method Stub */}
|
||||
{/* Payment Method Selection */}
|
||||
<div className="rounded-lg bg-white p-6 shadow-sm">
|
||||
<h2 className="text-lg font-semibold text-gray-800">Pagamento</h2>
|
||||
<p className="mt-2 text-sm text-gray-600">Este é um ambiente de demonstração. O pagamento será processado como "Confirmado" para fins de teste.</p>
|
||||
<div className="mt-4 flex items-center gap-3 rounded-lg border border-green-200 bg-green-50 p-4">
|
||||
<CheckCircle2 className="h-5 w-5 text-green-600" />
|
||||
<span className="text-sm font-medium text-green-800">Método de Teste (Aprovação Automática)</span>
|
||||
<div className="mb-4 flex items-center gap-2">
|
||||
<CheckCircle2 className="h-5 w-5 text-medicalBlue" />
|
||||
<h2 className="text-lg font-semibold text-gray-800">Forma de Pagamento</h2>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<label className={`flex cursor-pointer items-center rounded-lg border p-4 transition-colors ${paymentMethod === 'pix' ? 'border-medicalBlue bg-blue-50' : 'border-gray-200 hover:bg-gray-50'}`}>
|
||||
<input
|
||||
type="radio"
|
||||
name="paymentMethod"
|
||||
value="pix"
|
||||
checked={paymentMethod === 'pix'}
|
||||
onChange={() => setPaymentMethod('pix')}
|
||||
className="h-4 w-4 border-gray-300 text-medicalBlue focus:ring-medicalBlue"
|
||||
/>
|
||||
<div className="ml-3">
|
||||
<span className="block text-sm font-medium text-gray-900">Pix</span>
|
||||
<span className="block text-sm text-gray-500">Aprovação imediata</span>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label className={`flex cursor-pointer items-center rounded-lg border p-4 transition-colors ${paymentMethod === 'credit_card' ? 'border-medicalBlue bg-blue-50' : 'border-gray-200 hover:bg-gray-50'}`}>
|
||||
<input
|
||||
type="radio"
|
||||
name="paymentMethod"
|
||||
value="credit_card"
|
||||
checked={paymentMethod === 'credit_card'}
|
||||
onChange={() => setPaymentMethod('credit_card')}
|
||||
className="h-4 w-4 border-gray-300 text-medicalBlue focus:ring-medicalBlue"
|
||||
/>
|
||||
<div className="ml-3">
|
||||
<span className="block text-sm font-medium text-gray-900">Cartão de Crédito</span>
|
||||
<span className="block text-sm text-gray-500">Em até 12x</span>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label className={`flex cursor-pointer items-center rounded-lg border p-4 transition-colors ${paymentMethod === 'debit_card' ? 'border-medicalBlue bg-blue-50' : 'border-gray-200 hover:bg-gray-50'}`}>
|
||||
<input
|
||||
type="radio"
|
||||
name="paymentMethod"
|
||||
value="debit_card"
|
||||
checked={paymentMethod === 'debit_card'}
|
||||
onChange={() => setPaymentMethod('debit_card')}
|
||||
className="h-4 w-4 border-gray-300 text-medicalBlue focus:ring-medicalBlue"
|
||||
/>
|
||||
<div className="ml-3">
|
||||
<span className="block text-sm font-medium text-gray-900">Cartão de Débito</span>
|
||||
<span className="block text-sm text-gray-500">Pagamento à vista</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -211,12 +320,44 @@ export function CheckoutPage() {
|
|||
{Object.entries(groups).map(([vendorId, group]) => (
|
||||
<div key={vendorId} className="border-b border-gray-100 pb-4 last:border-0 last:pb-0">
|
||||
<p className="mb-2 text-sm font-medium text-gray-600">{group.vendorName}</p>
|
||||
{group.items.map(item => (
|
||||
<div key={item.id} className="flex justify-between text-sm">
|
||||
<span className="text-gray-800">{item.quantity}x {item.name}</span>
|
||||
<span className="text-gray-600">R$ {formatCurrency(item.quantity * item.unitPrice)}</span>
|
||||
</div>
|
||||
<div key={item.id} className="flex justify-between text-sm">
|
||||
<span className="text-gray-800">{item.quantity}x {item.name}</span>
|
||||
<span className="text-gray-600">R$ {formatCurrency(item.quantity * item.unitPrice)}</span>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* Shipping Options for this Vendor */}
|
||||
{shippingOptions[vendorId] && (
|
||||
<div className="mt-3 rounded-md bg-gray-50 p-3">
|
||||
<p className="mb-2 text-xs font-semibold text-gray-500 uppercase">Entrega</p>
|
||||
<div className="flex flex-col gap-2">
|
||||
{shippingOptions[vendorId].delivery ? (
|
||||
<label className="flex cursor-pointer items-center justify-between text-sm">
|
||||
<div className="flex items-center">
|
||||
<input type="radio" name={`shipping-${vendorId}`} className="mr-2 text-medicalBlue focus:ring-medicalBlue" defaultChecked />
|
||||
<span>Entrega Padrão</span>
|
||||
</div>
|
||||
<span className="font-medium text-gray-900">R$ {formatCurrency(shippingOptions[vendorId].price * 100)}</span>
|
||||
</label>
|
||||
) : (
|
||||
<div className="text-sm text-red-500">Entrega indisponível para esta região</div>
|
||||
)}
|
||||
|
||||
{shippingOptions[vendorId].pickup && (
|
||||
<label className="flex cursor-pointer items-center justify-between text-sm">
|
||||
<div className="flex items-center">
|
||||
<input type="radio" name={`shipping-${vendorId}`} className="mr-2 text-medicalBlue focus:ring-medicalBlue" />
|
||||
<div className="flex flex-col">
|
||||
<span>Retirada na Loja</span>
|
||||
<span className="text-xs text-gray-500">{shippingOptions[vendorId].pickupAddress}</span>
|
||||
</div>
|
||||
</div>
|
||||
<span className="font-medium text-green-600">Grátis</span>
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
|
||||
|
|
@ -225,6 +366,7 @@ export function CheckoutPage() {
|
|||
<span>Total</span>
|
||||
<span className="text-xl text-medicalBlue">R$ {formatCurrency(summary.totalValue)}</span>
|
||||
</div>
|
||||
<p className="mt-1 text-xs text-gray-500 text-right">Taxas de entrega não incluídas no total acima</p>
|
||||
</div>
|
||||
|
||||
<button
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ export interface CreateOrderRequest {
|
|||
seller_id: string
|
||||
items: OrderItem[]
|
||||
shipping: ShippingAddress
|
||||
payment_method: 'pix' | 'credit_card' | 'debit_card'
|
||||
}
|
||||
|
||||
export const ordersService = {
|
||||
|
|
|
|||
38
marketplace/src/services/shippingService.ts
Normal file
38
marketplace/src/services/shippingService.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
|
||||
import { apiClient } from './apiClient'
|
||||
import { ShippingSettings } from '../types/shipping'
|
||||
|
||||
export interface CalculateShippingRequest {
|
||||
buyer_id: string
|
||||
order_total_cents: number
|
||||
items: {
|
||||
seller_id: string
|
||||
product_id: string
|
||||
quantity: number
|
||||
price_cents: number
|
||||
}[]
|
||||
}
|
||||
|
||||
export interface CalculateShippingResponse {
|
||||
options: {
|
||||
seller_id: string
|
||||
delivery_fee_cents: number
|
||||
distance_km: number
|
||||
estimated_days: number
|
||||
pickup_available: boolean
|
||||
pickup_address?: string
|
||||
pickup_hours?: string
|
||||
}[]
|
||||
}
|
||||
|
||||
export const shippingService = {
|
||||
getSettings: async (vendorId: string) => {
|
||||
const response = await apiClient.get<ShippingSettings>(`/v1/shipping/settings/${vendorId}`)
|
||||
return response
|
||||
},
|
||||
|
||||
calculate: async (data: CalculateShippingRequest) => {
|
||||
const response = await apiClient.post<CalculateShippingResponse>('/v1/shipping/calculate', data)
|
||||
return response
|
||||
}
|
||||
}
|
||||
14
marketplace/src/types/shipping.ts
Normal file
14
marketplace/src/types/shipping.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
|
||||
export interface ShippingSettings {
|
||||
vendor_id: string
|
||||
active: boolean
|
||||
max_radius_km: number
|
||||
price_per_km_cents: number
|
||||
min_fee_cents: number
|
||||
free_shipping_threshold_cents?: number
|
||||
pickup_active: boolean
|
||||
pickup_address: string
|
||||
pickup_hours: string
|
||||
latitude: number
|
||||
longitude: number
|
||||
}
|
||||
|
|
@ -90,6 +90,7 @@ func SeedLean(dsn string) (string, error) {
|
|||
log.Println("🧹 [Lean] Resetting database...")
|
||||
|
||||
// Re-create tables
|
||||
mustExec(db, `DROP TABLE IF EXISTS shipping_settings CASCADE`)
|
||||
mustExec(db, `DROP TABLE IF EXISTS shipments CASCADE`)
|
||||
mustExec(db, `DROP TABLE IF EXISTS inventory_adjustments CASCADE`)
|
||||
mustExec(db, `DROP TABLE IF EXISTS order_items CASCADE`)
|
||||
|
|
@ -155,6 +156,7 @@ func SeedLean(dsn string) (string, error) {
|
|||
buyer_id UUID NOT NULL REFERENCES companies(id),
|
||||
seller_id UUID NOT NULL REFERENCES companies(id),
|
||||
status TEXT NOT NULL,
|
||||
payment_method TEXT NOT NULL DEFAULT 'credit_card',
|
||||
total_cents BIGINT NOT NULL,
|
||||
shipping_recipient_name TEXT,
|
||||
shipping_street TEXT,
|
||||
|
|
@ -213,6 +215,22 @@ func SeedLean(dsn string) (string, error) {
|
|||
updated_at TIMESTAMPTZ NOT NULL
|
||||
)`)
|
||||
|
||||
mustExec(db, `CREATE TABLE shipping_settings (
|
||||
vendor_id UUID PRIMARY KEY REFERENCES companies(id),
|
||||
active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
max_radius_km DOUBLE PRECISION NOT NULL DEFAULT 20.0,
|
||||
min_fee_cents BIGINT NOT NULL DEFAULT 500,
|
||||
price_per_km_cents BIGINT NOT NULL DEFAULT 100,
|
||||
free_shipping_threshold_cents BIGINT,
|
||||
pickup_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
pickup_address TEXT NOT NULL,
|
||||
pickup_hours TEXT NOT NULL,
|
||||
latitude DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
longitude DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMPTZ NOT NULL,
|
||||
updated_at TIMESTAMPTZ NOT NULL
|
||||
)`)
|
||||
|
||||
// Helper for hashing
|
||||
hashPwd := func(pwd string) string {
|
||||
h, _ := bcrypt.GenerateFromPassword([]byte(pwd), bcrypt.DefaultCost)
|
||||
|
|
@ -276,6 +294,17 @@ func SeedLean(dsn string) (string, error) {
|
|||
return "", fmt.Errorf("create library %s: %v", ph.Name, err)
|
||||
}
|
||||
|
||||
// 1.1 Create Shipping Settings
|
||||
address := fmt.Sprintf("Rua Exemplo %s, Anápolis - GO", ph.Suffix)
|
||||
_, err = db.ExecContext(ctx, `
|
||||
INSERT INTO shipping_settings (vendor_id, active, max_radius_km, min_fee_cents, price_per_km_cents, free_shipping_threshold_cents, pickup_active, pickup_address, pickup_hours, latitude, longitude, created_at, updated_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)`,
|
||||
companyID, true, 25.0, 500, 200, 15000, true, address, "Seg-Sex: 08:00-18:00", ph.Lat, ph.Lng, now, now,
|
||||
)
|
||||
if err != nil {
|
||||
log.Printf("⚠️ Failed to create shipping settings for %s: %v", ph.Name, err)
|
||||
}
|
||||
|
||||
// 2. Create Users (Dono, Colab, Entregador)
|
||||
roles := []struct {
|
||||
Role string
|
||||
|
|
@ -570,7 +599,7 @@ func generateTenants(rng *rand.Rand, count int) []map[string]interface{} {
|
|||
tenants := make([]map[string]interface{}, 0, count)
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
id, _ := uuid.NewV4()
|
||||
id, _ := uuid.NewV7()
|
||||
name := fmt.Sprintf("%s %d", pharmacyNames[rng.Intn(len(pharmacyNames))], i+1)
|
||||
cnpj := generateCNPJ(rng)
|
||||
|
||||
|
|
@ -604,7 +633,7 @@ func generateProducts(rng *rand.Rand, sellerID uuid.UUID, count int) []map[strin
|
|||
products := make([]map[string]interface{}, 0, count)
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
id, _ := uuid.NewV4()
|
||||
id, _ := uuid.NewV7()
|
||||
med := medicamentos[rng.Intn(len(medicamentos))]
|
||||
|
||||
// Random expiration: 30 days to 2 years from now
|
||||
|
|
|
|||
Loading…
Reference in a new issue