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
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
}

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
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,
}
}

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/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)
}

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"
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 {

View File

@ -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)
}

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
);