From b6f75082e9ece5821b4494c34bd19a5b3841cd8e Mon Sep 17 00:00:00 2001 From: "aditya.siregar" Date: Tue, 30 Jul 2024 23:39:55 +0700 Subject: [PATCH] Add Transaction --- internal/entity/transaction.go | 42 +++-- .../handlers/http/transaction/transaction.go | 86 +++++++++ internal/handlers/request/transaction.go | 22 +++ internal/handlers/response/transaction.go | 20 +++ internal/repository/repository.go | 8 + .../repository/transaction/transaction.go | 163 ++++++++++++++++++ internal/routes/routes.go | 2 + internal/services/service.go | 33 ++-- internal/services/transaction/transaction.go | 31 ++++ migrations/000012_add-transaction.down.sql | 1 + migrations/000012_add-transaction.up.sql | 13 ++ 11 files changed, 395 insertions(+), 26 deletions(-) create mode 100644 internal/handlers/http/transaction/transaction.go create mode 100644 internal/handlers/response/transaction.go create mode 100644 internal/repository/transaction/transaction.go create mode 100644 internal/services/transaction/transaction.go create mode 100644 migrations/000012_add-transaction.down.sql create mode 100644 migrations/000012_add-transaction.up.sql diff --git a/internal/entity/transaction.go b/internal/entity/transaction.go index 9debf98..12a52bb 100644 --- a/internal/entity/transaction.go +++ b/internal/entity/transaction.go @@ -1,23 +1,19 @@ package entity import ( - "furtuna-be/internal/constants/transaction" "time" ) type Transaction struct { - ID int64 - PartnerID int64 - Status transaction.PaymentStatus - Amount float64 - OrderID int64 - PaymentMethod transaction.PaymentMethod - CustomerName string - CustomerPhone string - CreatedAt time.Time - UpdatedAt time.Time - CreatedBy int64 - UpdatedBy int64 + ID string `gorm:"type:uuid;primaryKey;default:uuid_generate_v4()"` + PartnerID int64 `gorm:"not null"` + TransactionType string `gorm:"not null"` + ReferenceID string `gorm:"size:255"` + Status string `gorm:"size:255"` + CreatedBy int64 `gorm:"not null"` + UpdatedBy int64 `gorm:"not null"` + CreatedAt time.Time `gorm:"autoCreateTime"` + UpdatedAt time.Time `gorm:"autoUpdateTime"` } type TransactionDB struct { @@ -33,3 +29,23 @@ func (b *Transaction) ToTransactionDB() *TransactionDB { func (TransactionDB) TableName() string { 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 +} diff --git a/internal/handlers/http/transaction/transaction.go b/internal/handlers/http/transaction/transaction.go new file mode 100644 index 0000000..055a1ff --- /dev/null +++ b/internal/handlers/http/transaction/transaction.go @@ -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, + } +} diff --git a/internal/handlers/request/transaction.go b/internal/handlers/request/transaction.go index 40a35af..7a1c656 100644 --- a/internal/handlers/request/transaction.go +++ b/internal/handlers/request/transaction.go @@ -1,9 +1,31 @@ package request import ( + "furtuna-be/internal/common/mycontext" "furtuna-be/internal/constants/transaction" + "furtuna-be/internal/entity" ) type Transaction struct { 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, + } +} diff --git a/internal/handlers/response/transaction.go b/internal/handlers/response/transaction.go new file mode 100644 index 0000000..5cdbb96 --- /dev/null +++ b/internal/handlers/response/transaction.go @@ -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"` +} diff --git a/internal/repository/repository.go b/internal/repository/repository.go index 1d5bad8..ac91cf8 100644 --- a/internal/repository/repository.go +++ b/internal/repository/repository.go @@ -15,6 +15,7 @@ import ( "furtuna-be/internal/repository/products" "furtuna-be/internal/repository/sites" "furtuna-be/internal/repository/studios" + transactions "furtuna-be/internal/repository/transaction" "furtuna-be/internal/repository/trx" "furtuna-be/internal/repository/users" repository "furtuna-be/internal/repository/wallet" @@ -47,6 +48,7 @@ type RepoManagerImpl struct { Payment Payment EmailService EmailService License License + Transaction TransactionRepository } 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), EmailService: brevo.New(&cfg.Brevo), License: license.NewLicenseRepository(db), + Transaction: transactions.NewTransactionRepository(db), } } @@ -200,3 +203,8 @@ type License interface { FindByID(ctx context.Context, id string) (*entity.LicenseDB, 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) +} diff --git a/internal/repository/transaction/transaction.go b/internal/repository/transaction/transaction.go new file mode 100644 index 0000000..898f3b6 --- /dev/null +++ b/internal/repository/transaction/transaction.go @@ -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 +} diff --git a/internal/routes/routes.go b/internal/routes/routes.go index d93da3c..6a8b95a 100644 --- a/internal/routes/routes.go +++ b/internal/routes/routes.go @@ -10,6 +10,7 @@ import ( "furtuna-be/internal/handlers/http/product" site "furtuna-be/internal/handlers/http/sites" "furtuna-be/internal/handlers/http/studio" + "furtuna-be/internal/handlers/http/transaction" "furtuna-be/internal/handlers/http/user" swaggerFiles "github.com/swaggo/files" ginSwagger "github.com/swaggo/gin-swagger" @@ -58,6 +59,7 @@ func RegisterPrivateRoutes(app *app.Server, serviceManager *services.ServiceMana site.NewHandler(serviceManager.SiteSvc), mdtrns.NewHandler(serviceManager.OrderSvc), license.NewHandler(serviceManager.LicenseSvc), + transaction.New(serviceManager.Transaction), } for _, handler := range serverRoutes { diff --git a/internal/services/service.go b/internal/services/service.go index 37eb5dc..af0881e 100644 --- a/internal/services/service.go +++ b/internal/services/service.go @@ -11,6 +11,7 @@ import ( "furtuna-be/internal/services/product" site "furtuna-be/internal/services/sites" "furtuna-be/internal/services/studio" + "furtuna-be/internal/services/transaction" "furtuna-be/internal/services/users" "gorm.io/gorm" @@ -23,17 +24,18 @@ import ( ) type ServiceManagerImpl struct { - AuthSvc Auth - EventSvc Event - UserSvc User - BranchSvc Branch - StudioSvc Studio - ProductSvc Product - OrderSvc Order - OSSSvc OSSService - PartnerSvc Partner - SiteSvc Site - LicenseSvc License + AuthSvc Auth + EventSvc Event + UserSvc User + BranchSvc Branch + StudioSvc Studio + ProductSvc Product + OrderSvc Order + OSSSvc OSSService + PartnerSvc Partner + SiteSvc Site + LicenseSvc License + Transaction Transaction } 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), PartnerSvc: partner.NewPartnerService( repo.Partner, users.NewUserService(repo.User, repo.Branch), repo.Trx, repo.Wallet), - SiteSvc: site.NewSiteService(repo.Site), - LicenseSvc: service.NewLicenseService(repo.License), + SiteSvc: site.NewSiteService(repo.Site), + 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) 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) +} diff --git a/internal/services/transaction/transaction.go b/internal/services/transaction/transaction.go new file mode 100644 index 0000000..237f430 --- /dev/null +++ b/internal/services/transaction/transaction.go @@ -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 +} diff --git a/migrations/000012_add-transaction.down.sql b/migrations/000012_add-transaction.down.sql new file mode 100644 index 0000000..24bc1d9 --- /dev/null +++ b/migrations/000012_add-transaction.down.sql @@ -0,0 +1 @@ +DROP TABLE transaction cascade; \ No newline at end of file diff --git a/migrations/000012_add-transaction.up.sql b/migrations/000012_add-transaction.up.sql new file mode 100644 index 0000000..cc6f7ac --- /dev/null +++ b/migrations/000012_add-transaction.up.sql @@ -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 +); \ No newline at end of file