Add Transaction

This commit is contained in:
aditya.siregar 2024-07-30 23:39:55 +07:00
parent 719218da03
commit b6f75082e9
11 changed files with 395 additions and 26 deletions

View File

@ -1,23 +1,19 @@
package entity package entity
import ( import (
"furtuna-be/internal/constants/transaction"
"time" "time"
) )
type Transaction struct { type Transaction struct {
ID int64 ID string `gorm:"type:uuid;primaryKey;default:uuid_generate_v4()"`
PartnerID int64 PartnerID int64 `gorm:"not null"`
Status transaction.PaymentStatus TransactionType string `gorm:"not null"`
Amount float64 ReferenceID string `gorm:"size:255"`
OrderID int64 Status string `gorm:"size:255"`
PaymentMethod transaction.PaymentMethod CreatedBy int64 `gorm:"not null"`
CustomerName string UpdatedBy int64 `gorm:"not null"`
CustomerPhone string CreatedAt time.Time `gorm:"autoCreateTime"`
CreatedAt time.Time UpdatedAt time.Time `gorm:"autoUpdateTime"`
UpdatedAt time.Time
CreatedBy int64
UpdatedBy int64
} }
type TransactionDB struct { type TransactionDB struct {
@ -33,3 +29,23 @@ func (b *Transaction) ToTransactionDB() *TransactionDB {
func (TransactionDB) TableName() string { func (TransactionDB) TableName() string {
return "transactions" return "transactions"
} }
type TransactionSearch struct {
PartnerID *int64
SiteID *int64
Type string
Status string
Limit int
Offset int
Date string
}
type TransactionList struct {
ID string
TransactionType string
Status string
CreatedAt time.Time
SiteName string
PartnerName string
Amount int64
}

View File

@ -0,0 +1,86 @@
package transaction
import (
"furtuna-be/internal/common/errors"
"furtuna-be/internal/entity"
"furtuna-be/internal/handlers/request"
"furtuna-be/internal/handlers/response"
"furtuna-be/internal/services"
"github.com/gin-gonic/gin"
"net/http"
)
type TransactionHandler struct {
service services.Transaction
}
func (h *TransactionHandler) Route(group *gin.RouterGroup, jwt gin.HandlerFunc) {
route := group.Group("/transaction")
route.GET("/search", jwt, h.Search)
}
func New(service services.Transaction) *TransactionHandler {
return &TransactionHandler{
service: service,
}
}
// Search retrieves a list of studios based on search criteria.
// @Summary Search for studios
// @Description Search for studios based on query parameters.
// @Accept json
// @Produce json
// @Param Authorization header string true "JWT token"
// @Param Name query string false "Studio name for search"
// @Param Status query string false "Studio status for search"
// @Param Limit query int false "Number of items to retrieve (default 10)"
// @Param Offset query int false "Offset for pagination (default 0)"
// @Success 200 {object} response.BaseResponse{data=response.StudioList} "List of studios"
// @Failure 400 {object} response.BaseResponse{data=errors.Error} "Bad request"
// @Failure 401 {object} response.BaseResponse{data=errors.Error} "Unauthorized"
// @Router /api/v1/studio/search [get]
// @Tags Studio APIs
func (h *TransactionHandler) Search(c *gin.Context) {
var req request.TransactionSearch
if err := c.ShouldBindQuery(&req); err != nil {
response.ErrorWrapper(c, errors.ErrorBadRequest)
return
}
ctx := request.GetMyContext(c)
transactions, total, err := h.service.GetTransactionList(ctx, req.ToEntity(ctx))
if err != nil {
response.ErrorWrapper(c, err)
return
}
c.JSON(http.StatusOK, response.BaseResponse{
Success: true,
Status: http.StatusOK,
Data: h.ToTransactionListResponse(transactions, total, req.Limit, req.Offset),
})
}
func (h *TransactionHandler) ToTransactionListResponse(transactions []*entity.TransactionList, totalCount, limit, offset int) response.TransactionListResponse {
responseItems := make([]response.TransactionListItem, len(transactions))
for i, transaction := range transactions {
responseItems[i] = response.TransactionListItem{
ID: transaction.ID,
TransactionType: transaction.TransactionType,
Status: transaction.Status,
CreatedAt: transaction.CreatedAt,
SiteName: transaction.SiteName,
Amount: transaction.Amount,
PartnerName: transaction.PartnerName,
}
}
return response.TransactionListResponse{
Transactions: responseItems,
TotalCount: totalCount,
Limit: limit,
Offset: offset,
}
}

View File

@ -1,9 +1,31 @@
package request package request
import ( import (
"furtuna-be/internal/common/mycontext"
"furtuna-be/internal/constants/transaction" "furtuna-be/internal/constants/transaction"
"furtuna-be/internal/entity"
) )
type Transaction struct { type Transaction struct {
PaymentMethod transaction.PaymentMethod PaymentMethod transaction.PaymentMethod
} }
type TransactionSearch struct {
Id string `form:"id" json:"id" example:"1"`
Date string `form:"date" json:"date" example:"1"`
Limit int `form:"limit,default=10"`
Offset int `form:"offset,default=0"`
Status string `form:"status,default="`
Type string `form:"type,default="`
}
func (t *TransactionSearch) ToEntity(ctx mycontext.Context) entity.TransactionSearch {
return entity.TransactionSearch{
PartnerID: ctx.GetPartnerID(),
Type: t.Type,
Status: t.Status,
Limit: t.Limit,
Offset: t.Offset,
Date: t.Date,
}
}

View File

@ -0,0 +1,20 @@
package response
import "time"
type TransactionListItem struct {
ID string `json:"id"`
TransactionType string `json:"transaction_type"`
Status string `json:"status"`
CreatedAt time.Time `json:"created_at"`
SiteName string `json:"site_name"`
Amount int64 `json:"amount"`
PartnerName string `json:"partner_name"`
}
type TransactionListResponse struct {
Transactions []TransactionListItem `json:"transactions"`
TotalCount int `json:"total_count"`
Limit int `json:"limit"`
Offset int `json:"offset"`
}

View File

@ -15,6 +15,7 @@ import (
"furtuna-be/internal/repository/products" "furtuna-be/internal/repository/products"
"furtuna-be/internal/repository/sites" "furtuna-be/internal/repository/sites"
"furtuna-be/internal/repository/studios" "furtuna-be/internal/repository/studios"
transactions "furtuna-be/internal/repository/transaction"
"furtuna-be/internal/repository/trx" "furtuna-be/internal/repository/trx"
"furtuna-be/internal/repository/users" "furtuna-be/internal/repository/users"
repository "furtuna-be/internal/repository/wallet" repository "furtuna-be/internal/repository/wallet"
@ -47,6 +48,7 @@ type RepoManagerImpl struct {
Payment Payment Payment Payment
EmailService EmailService EmailService EmailService
License License License License
Transaction TransactionRepository
} }
func NewRepoManagerImpl(db *gorm.DB, cfg *config.Config) *RepoManagerImpl { func NewRepoManagerImpl(db *gorm.DB, cfg *config.Config) *RepoManagerImpl {
@ -68,6 +70,7 @@ func NewRepoManagerImpl(db *gorm.DB, cfg *config.Config) *RepoManagerImpl {
Payment: payment.NewPaymentRepository(db), Payment: payment.NewPaymentRepository(db),
EmailService: brevo.New(&cfg.Brevo), EmailService: brevo.New(&cfg.Brevo),
License: license.NewLicenseRepository(db), License: license.NewLicenseRepository(db),
Transaction: transactions.NewTransactionRepository(db),
} }
} }
@ -200,3 +203,8 @@ type License interface {
FindByID(ctx context.Context, id string) (*entity.LicenseDB, error) FindByID(ctx context.Context, id string) (*entity.LicenseDB, error)
GetAll(ctx context.Context, limit, offset int, statusFilter string) ([]*entity.LicenseGetAll, int64, error) GetAll(ctx context.Context, limit, offset int, statusFilter string) ([]*entity.LicenseGetAll, int64, error)
} }
type TransactionRepository interface {
Create(ctx context.Context, transaction *entity.Transaction) (*entity.Transaction, error)
GetTransactionList(ctx mycontext.Context, req entity.TransactionSearch) ([]*entity.TransactionList, int, error)
}

View File

@ -0,0 +1,163 @@
package transactions
import (
"context"
"furtuna-be/internal/common/mycontext"
"furtuna-be/internal/entity"
"go.uber.org/zap"
"gorm.io/gorm"
)
// TransactionRepository provides methods to perform CRUD operations on transactions.
type TransactionRepository struct {
db *gorm.DB
}
func NewTransactionRepository(db *gorm.DB) *TransactionRepository {
return &TransactionRepository{
db: db,
}
}
// Create creates a new transaction in the database.
func (r *TransactionRepository) Create(ctx context.Context, transaction *entity.Transaction) (*entity.Transaction, error) {
if err := r.db.WithContext(ctx).Create(transaction).Error; err != nil {
zap.L().Error("error when creating transaction", zap.Error(err))
return nil, err
}
return r.FindByID(ctx, transaction.ID)
}
// Update updates an existing transaction in the database.
func (r *TransactionRepository) Update(ctx context.Context, transaction *entity.Transaction) (*entity.Transaction, error) {
if err := r.db.WithContext(ctx).Save(transaction).Error; err != nil {
zap.L().Error("error when updating transaction", zap.Error(err))
return nil, err
}
return transaction, nil
}
func (r *TransactionRepository) FindByID(ctx context.Context, id string) (*entity.Transaction, error) {
var transaction entity.Transaction
if err := r.db.WithContext(ctx).First(&transaction, "id = ?", id).Error; err != nil {
zap.L().Error("error when finding transaction by ID", zap.Error(err))
return nil, err
}
return &transaction, nil
}
func (r *TransactionRepository) Delete(ctx context.Context, id string) error {
if err := r.db.WithContext(ctx).Delete(&entity.Transaction{}, "id = ?", id).Error; err != nil {
zap.L().Error("error when deleting transaction", zap.Error(err))
return err
}
return nil
}
func (r *TransactionRepository) FindByPartnerID(ctx context.Context, partnerID int64) ([]entity.Transaction, error) {
var transactions []entity.Transaction
if err := r.db.WithContext(ctx).Where("partner_id = ?", partnerID).Find(&transactions).Error; err != nil {
zap.L().Error("error when finding transactions by partner ID", zap.Error(err))
return nil, err
}
return transactions, nil
}
func (r *TransactionRepository) FindByStatus(ctx context.Context, status string) ([]entity.Transaction, error) {
var transactions []entity.Transaction
if err := r.db.WithContext(ctx).Where("status = ?", status).Find(&transactions).Error; err != nil {
zap.L().Error("error when finding transactions by status", zap.Error(err))
return nil, err
}
return transactions, nil
}
// UpdateStatus updates the status of a transaction by its ID.
func (r *TransactionRepository) UpdateStatus(ctx context.Context, id string, status string) (*entity.Transaction, error) {
transaction, err := r.FindByID(ctx, id)
if err != nil {
return nil, err
}
transaction.Status = status
if err := r.db.WithContext(ctx).Save(transaction).Error; err != nil {
zap.L().Error("error when updating transaction status", zap.Error(err))
return nil, err
}
return transaction, nil
}
// ListTransactions retrieves a list of transactions with optional filters for pagination and sorting.
func (r *TransactionRepository) ListTransactions(ctx context.Context, offset int, limit int, status string, transactionType string) ([]entity.Transaction, int64, error) {
var transactions []entity.Transaction
var total int64
query := r.db.WithContext(ctx).Model(&entity.Transaction{}).Order("created_at DESC")
if status != "" {
query = query.Where("status = ?", status)
}
if transactionType != "" {
query = query.Where("transaction_type = ?", transactionType)
}
if err := query.Count(&total).Error; err != nil {
zap.L().Error("error when counting transactions", zap.Error(err))
return nil, 0, err
}
if offset >= 0 {
query = query.Offset(offset)
}
if limit > 0 {
query = query.Limit(limit)
}
if err := query.Find(&transactions).Error; err != nil {
zap.L().Error("error when listing transactions", zap.Error(err))
return nil, 0, err
}
return transactions, total, nil
}
func (r *TransactionRepository) GetTransactionList(ctx mycontext.Context, req entity.TransactionSearch) ([]*entity.TransactionList, int, error) {
var transactions []*entity.TransactionList
var total int64
query := r.db.Table("transaction t").
Select("t.id, t.transaction_type, t.status, t.created_at, s.name as site_name, p.name as partner_name, t.amount").
Joins("left join sites s on t.site_id = s.id").
Joins("left join partners p on t.partner_id = p.id").
Where("t.partner_id = ?", req.PartnerID)
if req.SiteID != nil {
query = query.Where("t.site_id = ?", req.SiteID)
}
if req.Type != "" {
query = query.Where("t.transaction_type = ?", req.Type)
}
if req.Status != "" {
query = query.Where("t.status = ?", req.Status)
}
if req.Date != "" {
query = query.Where("DATE(t.created_at) = ?", req.Date)
}
query = query.Count(&total)
if req.Offset > 0 {
query = query.Offset(req.Offset)
}
if req.Limit > 0 {
query = query.Limit(req.Limit)
}
err := query.Find(&transactions).Error
if err != nil {
return nil, 0, err
}
return transactions, int(total), nil
}

View File

@ -10,6 +10,7 @@ import (
"furtuna-be/internal/handlers/http/product" "furtuna-be/internal/handlers/http/product"
site "furtuna-be/internal/handlers/http/sites" site "furtuna-be/internal/handlers/http/sites"
"furtuna-be/internal/handlers/http/studio" "furtuna-be/internal/handlers/http/studio"
"furtuna-be/internal/handlers/http/transaction"
"furtuna-be/internal/handlers/http/user" "furtuna-be/internal/handlers/http/user"
swaggerFiles "github.com/swaggo/files" swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger" ginSwagger "github.com/swaggo/gin-swagger"
@ -58,6 +59,7 @@ func RegisterPrivateRoutes(app *app.Server, serviceManager *services.ServiceMana
site.NewHandler(serviceManager.SiteSvc), site.NewHandler(serviceManager.SiteSvc),
mdtrns.NewHandler(serviceManager.OrderSvc), mdtrns.NewHandler(serviceManager.OrderSvc),
license.NewHandler(serviceManager.LicenseSvc), license.NewHandler(serviceManager.LicenseSvc),
transaction.New(serviceManager.Transaction),
} }
for _, handler := range serverRoutes { for _, handler := range serverRoutes {

View File

@ -11,6 +11,7 @@ import (
"furtuna-be/internal/services/product" "furtuna-be/internal/services/product"
site "furtuna-be/internal/services/sites" site "furtuna-be/internal/services/sites"
"furtuna-be/internal/services/studio" "furtuna-be/internal/services/studio"
"furtuna-be/internal/services/transaction"
"furtuna-be/internal/services/users" "furtuna-be/internal/services/users"
"gorm.io/gorm" "gorm.io/gorm"
@ -23,17 +24,18 @@ import (
) )
type ServiceManagerImpl struct { type ServiceManagerImpl struct {
AuthSvc Auth AuthSvc Auth
EventSvc Event EventSvc Event
UserSvc User UserSvc User
BranchSvc Branch BranchSvc Branch
StudioSvc Studio StudioSvc Studio
ProductSvc Product ProductSvc Product
OrderSvc Order OrderSvc Order
OSSSvc OSSService OSSSvc OSSService
PartnerSvc Partner PartnerSvc Partner
SiteSvc Site SiteSvc Site
LicenseSvc License LicenseSvc License
Transaction Transaction
} }
func NewServiceManagerImpl(cfg *config.Config, repo *repository.RepoManagerImpl) *ServiceManagerImpl { func NewServiceManagerImpl(cfg *config.Config, repo *repository.RepoManagerImpl) *ServiceManagerImpl {
@ -48,8 +50,9 @@ func NewServiceManagerImpl(cfg *config.Config, repo *repository.RepoManagerImpl)
OSSSvc: oss.NewOSSService(repo.OSS), OSSSvc: oss.NewOSSService(repo.OSS),
PartnerSvc: partner.NewPartnerService( PartnerSvc: partner.NewPartnerService(
repo.Partner, users.NewUserService(repo.User, repo.Branch), repo.Trx, repo.Wallet), repo.Partner, users.NewUserService(repo.User, repo.Branch), repo.Trx, repo.Wallet),
SiteSvc: site.NewSiteService(repo.Site), SiteSvc: site.NewSiteService(repo.Site),
LicenseSvc: service.NewLicenseService(repo.License), LicenseSvc: service.NewLicenseService(repo.License),
Transaction: transaction.New(repo.Transaction),
} }
} }
@ -137,3 +140,7 @@ type License interface {
GetByID(ctx context.Context, id string) (*entity.License, error) GetByID(ctx context.Context, id string) (*entity.License, error)
GetAll(ctx context.Context, limit, offset int, status string) ([]*entity.LicenseGetAll, int64, error) GetAll(ctx context.Context, limit, offset int, status string) ([]*entity.LicenseGetAll, int64, error)
} }
type Transaction interface {
GetTransactionList(ctx mycontext.Context, req entity.TransactionSearch) ([]*entity.TransactionList, int, error)
}

View File

@ -0,0 +1,31 @@
package transaction
import (
"furtuna-be/internal/common/logger"
"furtuna-be/internal/common/mycontext"
"furtuna-be/internal/entity"
"furtuna-be/internal/repository"
"go.uber.org/zap"
)
type TransactionService struct {
repo repository.TransactionRepository
}
func New(repo repository.TransactionRepository) *TransactionService {
return &TransactionService{
repo: repo,
}
}
func (s *TransactionService) GetTransactionList(ctx mycontext.Context,
req entity.TransactionSearch) ([]*entity.TransactionList, int, error) {
transactions, total, err := s.repo.GetTransactionList(ctx, req)
if err != nil {
logger.ContextLogger(ctx).Error("error when get all products", zap.Error(err))
return nil, 0, err
}
return transactions, total, nil
}

View File

@ -0,0 +1 @@
DROP TABLE transaction cascade;

View File

@ -0,0 +1,13 @@
CREATE TABLE transaction
(
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
partner_id NUMERIC NOT NULL,
transaction_type varchar not null,
reference_id varchar,
status varchar,
created_by numeric not null,
updated_by numeric not null,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);