From afac4c7937ee8d6b60629d4dd853f1e1af870ef4 Mon Sep 17 00:00:00 2001 From: jiahui Date: Fri, 9 Aug 2024 17:16:03 +0800 Subject: [PATCH 01/12] add invoice service --- .../pkg/database/cockroach/accountv2.go | 90 +++++- controllers/pkg/database/interface.go | 2 + controllers/pkg/types/global.go | 34 +++ service/account/api/api.go | 108 +++++++ service/account/dao/init.go | 1 + service/account/dao/interface.go | 72 +++++ service/account/docs/docs.go | 267 ++++++++++++++++++ service/account/docs/swagger.json | 267 ++++++++++++++++++ service/account/docs/swagger.yaml | 209 ++++++++++++++ service/account/helper/common.go | 3 + service/account/helper/request.go | 73 +++++ service/account/router/router.go | 5 +- 12 files changed, 1122 insertions(+), 9 deletions(-) diff --git a/controllers/pkg/database/cockroach/accountv2.go b/controllers/pkg/database/cockroach/accountv2.go index ebc0e22defd..3b467c9c831 100644 --- a/controllers/pkg/database/cockroach/accountv2.go +++ b/controllers/pkg/database/cockroach/accountv2.go @@ -643,20 +643,94 @@ func (c *Cockroach) GetPayment(ops *types.UserQueryOpts, startTime, endTime time return payment, nil } +func (c *Cockroach) GetPaymentListWithIds(ids []string, status types.Payment) ([]types.Payment, error) { + var payment []types.Payment + if err := c.DB.Where(status).Where("id IN ?", ids).Find(&payment).Error; err != nil { + return nil, fmt.Errorf("failed to get payment: %w", err) + } + return payment, nil +} + func (c *Cockroach) SetPaymentInvoice(ops *types.UserQueryOpts, paymentIDList []string) error { userUID, err := c.GetUserUID(ops) if err != nil { return fmt.Errorf("failed to get user uid: %v", err) } - var payment []types.Payment - if err := c.DB.Where(types.Payment{PaymentRaw: types.PaymentRaw{UserUID: userUID}}).Where("id IN ?", paymentIDList).Find(&payment).Error; err != nil { - return fmt.Errorf("failed to get payment: %w", err) + if err := c.DB.Where(types.Payment{PaymentRaw: types.PaymentRaw{UserUID: userUID}}).Where("id IN ?", paymentIDList).Update("invoiced_at", true).Error; err != nil { + return fmt.Errorf("failed to save payment: %v", err) + } + return nil +} + +func (c *Cockroach) GetInvoice(userID string, req types.LimitReq) ([]types.Invoice, types.LimitResp, error) { + var invoices []types.Invoice + var total int64 + var limitResp types.LimitResp + + query := c.DB.Model(&types.Invoice{}).Where("user_id = ?", userID) + + if !req.StartTime.IsZero() { + query = query.Where("created_at >= ?", req.StartTime) + } + if !req.EndTime.IsZero() { + query = query.Where("created_at <= ?", req.EndTime) + } + + if req.Page < 1 { + req.Page = 1 + } + if req.PageSize < 1 { + req.PageSize = 10 + } + + if err := query.Count(&total).Error; err != nil { + return nil, limitResp, fmt.Errorf("failed to get total count: %v", err) + } + + totalPage := (total + int64(req.PageSize) - 1) / int64(req.PageSize) + + if err := query.Limit(req.PageSize). + Offset((req.Page - 1) * req.PageSize). + Find(&invoices).Error; err != nil { + return nil, limitResp, fmt.Errorf("failed to get invoices: %v", err) } - for i := range payment { - payment[i].InvoicedAt = true - if err := c.DB.Save(&payment[i]).Error; err != nil { - return fmt.Errorf("failed to save payment: %v", err) + + limitResp = types.LimitResp{ + Total: total, + TotalPage: totalPage, + } + + return invoices, limitResp, nil +} + +func (c *Cockroach) CreateInvoice(i *types.Invoice) error { + if i.ID == "" { + id, err := gonanoid.New(12) + if err != nil { + return fmt.Errorf("failed to generate invoice id: %v", err) } + i.ID = id + } + if i.CreatedAt.IsZero() { + i.CreatedAt = time.Now() + } + if err := c.DB.Create(i).Error; err != nil { + return fmt.Errorf("failed to save invoice: %v", err) + } + return nil +} + +// create invoicePayments +func (c *Cockroach) CreateInvoicePayments(invoicePayments []types.InvoicePayment) error { + if err := c.DB.Create(invoicePayments).Error; err != nil { + return fmt.Errorf("failed to save invoice payments: %v", err) + } + return nil +} + +func (c *Cockroach) SetInvoiceStatus(ids []string, stats string) error { + if err := c.DB.Model(&types.Invoice{}).Where("id IN ?", ids).Update("status", stats).Error; err != nil { + return fmt.Errorf("failed to update invoice status: %v", err) } return nil } @@ -818,7 +892,7 @@ func (c *Cockroach) transferAccount(from, to *types.UserQueryOpts, amount int64, } func (c *Cockroach) InitTables() error { - return CreateTableIfNotExist(c.DB, types.Account{}, types.ErrorAccountCreate{}, types.ErrorPaymentCreate{}, types.Payment{}, types.Transfer{}, types.Region{}) + return CreateTableIfNotExist(c.DB, types.Account{}, types.ErrorAccountCreate{}, types.ErrorPaymentCreate{}, types.Payment{}, types.Transfer{}, types.Region{}, types.Invoice{}, types.InvoicePayment{}) } func NewCockRoach(globalURI, localURI string) (*Cockroach, error) { diff --git a/controllers/pkg/database/interface.go b/controllers/pkg/database/interface.go index 6f364ac4ba1..a7c49800747 100644 --- a/controllers/pkg/database/interface.go +++ b/controllers/pkg/database/interface.go @@ -103,6 +103,8 @@ type AccountV2 interface { NewAccount(user *types.UserQueryOpts) (*types.Account, error) Payment(payment *types.Payment) error SavePayment(payment *types.Payment) error + CreateInvoice(i *types.Invoice) error + GetPaymentListWithIds(ids []string, status types.Payment) ([]types.Payment, error) CreateErrorPaymentCreate(payment types.Payment, errorMsg string) error CreateAccount(ops *types.UserQueryOpts, account *types.Account) (*types.Account, error) CreateErrorAccountCreate(account *types.Account, owner, errorMsg string) error diff --git a/controllers/pkg/types/global.go b/controllers/pkg/types/global.go index 4d22bc23036..2df526e3df8 100644 --- a/controllers/pkg/types/global.go +++ b/controllers/pkg/types/global.go @@ -246,3 +246,37 @@ type Payment struct { func (Payment) TableName() string { return "Payment" } + +type InvoiceStatus string + +const ( + PendingInvoiceStatus = "PENDING" + CompletedInvoiceStatus = "COMPLETED" + RejectedInvoiceStatus = "REJECTED" +) + +type Invoice struct { + ID string `gorm:"type:text;primary_key"` + UserID string `gorm:"type:text;not null"` + CreatedAt time.Time `gorm:"type:timestamp(3) with time zone;default:current_timestamp()"` + UpdatedAt time.Time `gorm:"type:timestamp(3) with time zone"` + Detail string `gorm:"type:text;not null"` + Remark string `gorm:"type:text"` + TotalAmount int64 `gorm:"type:bigint;not null"` + // Pending, Completed, Rejected + Status InvoiceStatus `gorm:"type:text;not null"` +} + +type InvoicePayment struct { + InvoiceID string `gorm:"type:text"` + PaymentID string `gorm:"type:text;primary_key"` + Amount int64 `gorm:"type:bigint;not null"` +} + +func (Invoice) TableName() string { + return "Invoice" +} + +func (InvoicePayment) TableName() string { + return "InvoicePayment" +} diff --git a/service/account/api/api.go b/service/account/api/api.go index f4a0b508617..fe3a32a9b82 100644 --- a/service/account/api/api.go +++ b/service/account/api/api.go @@ -709,3 +709,111 @@ func CalibrateRegionAuth(auth *helper.Auth, kcHost string) error { } return fmt.Errorf("failed to calibrate region auth") } + +func checkInvoiceToken(token string) error { + if token != dao.Cfg.InvoiceToken || token == "" { + return fmt.Errorf("invalid invoice token: %s", token) + } + return nil +} + +// ApplyInvoice +// @Summary Apply invoice +// @Description Apply invoice +// @Tags ApplyInvoice +// @Accept json +// @Produce json +// @Param request body helper.ApplyInvoiceReq true "Apply invoice request" +// @Success 200 {object} map[string]interface{} "successfully apply invoice" +// @Failure 400 {object} map[string]interface{} "failed to parse apply invoice request" +// @Failure 401 {object} map[string]interface{} "authenticate error" +// @Failure 500 {object} map[string]interface{} "failed to apply invoice" +// @Router /account/v1alpha1/invoice/apply [post] +func ApplyInvoice(c *gin.Context) { + req, err := helper.ParseApplyInvoiceReq(c) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("failed to parse apply invoice request: %v", err)}) + return + } + if err := CheckAuthAndCalibrate(req.Auth); err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": fmt.Sprintf("authenticate error : %v", err)}) + return + } + if err := dao.DBClient.ApplyInvoice(req); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to apply invoice : %v", err)}) + return + } + c.JSON(http.StatusOK, gin.H{ + "message": "successfully apply invoice", + }) +} + +// GetInvoice +// @Summary Get invoice +// @Description Get invoice +// @Tags GetInvoice +// @Accept json +// @Produce json +// @Param request body helper.GetInvoiceReq true "Get invoice request" +// @Success 200 {object} map[string]interface{} "successfully get invoice" +// @Failure 400 {object} map[string]interface{} "failed to parse get invoice request" +// @Failure 401 {object} map[string]interface{} "authenticate error" +// @Failure 500 {object} map[string]interface{} "failed to get invoice" +// @Router /account/v1alpha1/invoice/get [post] +func GetInvoice(c *gin.Context) { + req, err := helper.ParseGetInvoiceReq(c) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("failed to parse get invoice request: %v", err)}) + return + } + if err := CheckAuthAndCalibrate(req.Auth); err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": fmt.Sprintf("authenticate error : %v", err)}) + return + } + invoices, limits, err := dao.DBClient.GetInvoice(req) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to get invoice : %v", err)}) + return + } + c.JSON(http.StatusOK, gin.H{ + "data": struct { + Invoices []types.Invoice `json:"invoices"` + Limits types.LimitResp `json:",inline" bson:",inline"` + }{ + Invoices: invoices, + Limits: limits, + }, + }) +} + +// SetStatusInvoice +// @Summary Set status invoice +// @Description Set status invoice +// @Tags SetStatusInvoice +// @Accept json +// @Produce json +// @Param request body helper.SetInvoiceStatusReq true "Set status invoice request" +// @Success 200 {object} map[string]interface{} "successfully set status invoice" +// @Failure 400 {object} map[string]interface{} "failed to parse set status invoice request" +// @Failure 401 {object} map[string]interface{} "authenticate error" +// @Failure 500 {object} map[string]interface{} "failed to set status invoice" +// @Router /account/v1alpha1/invoice/set-status [post] +func SetStatusInvoice(c *gin.Context) { + req, err := helper.ParseSetInvoiceStatusReq(c) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("failed to parse set status invoice request: %v", err)}) + return + } + if err = checkInvoiceToken(req.Token); err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": fmt.Sprintf("authenticate error : %v", err)}) + return + } + + if err := dao.DBClient.SetStatusInvoice(req); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to set status invoice : %v", err)}) + return + } + c.JSON(http.StatusOK, gin.H{ + "message": "successfully set status invoice", + }) +} diff --git a/service/account/dao/init.go b/service/account/dao/init.go index 52d155016cd..71302ef2ec9 100644 --- a/service/account/dao/init.go +++ b/service/account/dao/init.go @@ -12,6 +12,7 @@ import ( type Config struct { LocalRegionDomain string `json:"localRegionDomain"` Regions []Region `json:"regions"` + InvoiceToken string `json:"invoiceToken"` } type Region struct { diff --git a/service/account/dao/interface.go b/service/account/dao/interface.go index 3d33be2bb43..26c333fe595 100644 --- a/service/account/dao/interface.go +++ b/service/account/dao/interface.go @@ -7,6 +7,10 @@ import ( "strings" "time" + "gorm.io/gorm" + + gonanoid "github.com/matoous/go-nanoid/v2" + "go.mongodb.org/mongo-driver/bson/primitive" "github.com/labring/sealos/controllers/pkg/database/cockroach" @@ -39,6 +43,9 @@ type Interface interface { GetPropertiesUsedAmount(user string, startTime, endTime time.Time) (map[string]int64, error) GetAccount(ops types.UserQueryOpts) (*types.Account, error) GetPayment(ops types.UserQueryOpts, startTime, endTime time.Time) ([]types.Payment, error) + ApplyInvoice(req *helper.ApplyInvoiceReq) error + GetInvoice(req *helper.GetInvoiceReq) ([]types.Invoice, types.LimitResp, error) + SetStatusInvoice(req *helper.SetInvoiceStatusReq) error GetWorkspaceName(namespaces []string) ([][]string, error) SetPaymentInvoice(req *helper.SetPaymentInvoiceReq) error Transfer(req *helper.TransferAmountReq) error @@ -1249,3 +1256,68 @@ func (m *Account) GetBillingHistoryNamespaceList(req *helper.NamespaceBillingHis func (m *MongoDB) getBillingCollection() *mongo.Collection { return m.Client.Database(m.AccountDBName).Collection(m.BillingConn) } + +func (m *Account) ApplyInvoice(req *helper.ApplyInvoiceReq) error { + payments, err := m.ck.GetPaymentListWithIds(req.PaymentIDList, types.Payment{PaymentRaw: types.PaymentRaw{InvoicedAt: false}}) + if err != nil { + return fmt.Errorf("failed to get payment list: %v", err) + } + if len(payments) == 0 { + return fmt.Errorf("no payment record was found for invoicing") + } + amount := int64(0) + var paymentIds []string + var invoicePayments []types.InvoicePayment + for i := range payments { + amount += payments[i].Amount + paymentIds = append(paymentIds, payments[i].ID) + invoicePayments = append(invoicePayments, types.InvoicePayment{ + PaymentID: payments[i].ID, + Amount: payments[i].Amount, + }) + } + id, err := gonanoid.New(12) + if err != nil { + return fmt.Errorf("failed to generate payment id: %v", err) + } + invoice := &types.Invoice{ + ID: id, + UserID: req.UserID, + CreatedAt: time.Now().UTC(), + UpdatedAt: time.Now().UTC(), + Detail: req.Detail, + TotalAmount: amount, + Status: types.PendingInvoiceStatus, + } + if err = m.ck.DB.Transaction( + func(tx *gorm.DB) error { + if err = m.ck.SetPaymentInvoice(&types.UserQueryOpts{Owner: req.Auth.Owner}, paymentIds); err != nil { + return fmt.Errorf("failed to set payment invoice: %v", err) + } + if err = m.ck.CreateInvoice(invoice); err != nil { + return fmt.Errorf("failed to create invoice: %v", err) + } + if err = m.ck.CreateInvoicePayments(invoicePayments); err != nil { + return fmt.Errorf("failed to create invoice payments: %v", err) + } + return nil + }); err != nil { + return fmt.Errorf("failed to apply invoice: %v", err) + } + return nil +} + +func (m *Account) GetInvoice(req *helper.GetInvoiceReq) ([]types.Invoice, types.LimitResp, error) { + return m.ck.GetInvoice(req.UserID, types.LimitReq{ + Page: req.Page, + PageSize: req.PageSize, + TimeRange: types.TimeRange{ + StartTime: req.StartTime, + EndTime: req.EndTime, + }, + }) +} + +func (m *Account) SetStatusInvoice(req *helper.SetInvoiceStatusReq) error { + return m.ck.SetInvoiceStatus(req.InvoiceIDList, req.Status) +} diff --git a/service/account/docs/docs.go b/service/account/docs/docs.go index 55344611e08..6a00dcb8c65 100644 --- a/service/account/docs/docs.go +++ b/service/account/docs/docs.go @@ -761,6 +761,174 @@ const docTemplate = `{ } } }, + "/account/v1alpha1/invoice/apply": { + "post": { + "description": "Apply invoice", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "ApplyInvoice" + ], + "summary": "Apply invoice", + "parameters": [ + { + "description": "Apply invoice request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/helper.ApplyInvoiceReq" + } + } + ], + "responses": { + "200": { + "description": "successfully apply invoice", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "400": { + "description": "failed to parse apply invoice request", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "401": { + "description": "authenticate error", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "500": { + "description": "failed to apply invoice", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, + "/account/v1alpha1/invoice/get": { + "post": { + "description": "Get invoice", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "GetInvoice" + ], + "summary": "Get invoice", + "parameters": [ + { + "description": "Get invoice request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/helper.GetInvoiceReq" + } + } + ], + "responses": { + "200": { + "description": "successfully get invoice", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "400": { + "description": "failed to parse get invoice request", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "401": { + "description": "authenticate error", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "500": { + "description": "failed to get invoice", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, + "/account/v1alpha1/invoice/set-status": { + "post": { + "description": "Set status invoice", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SetStatusInvoice" + ], + "summary": "Set status invoice", + "parameters": [ + { + "description": "Set status invoice request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/helper.SetInvoiceStatusReq" + } + } + ], + "responses": { + "200": { + "description": "successfully set status invoice", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "400": { + "description": "failed to parse set status invoice request", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "401": { + "description": "authenticate error", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "500": { + "description": "failed to set status invoice", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, "/account/v1alpha1/namespaces": { "post": { "description": "Get the billing history namespace list from the database", @@ -1079,6 +1247,42 @@ const docTemplate = `{ } } }, + "helper.ApplyInvoiceReq": { + "type": "object", + "required": [ + "detail", + "paymentIDList" + ], + "properties": { + "detail": { + "description": "invoice detail information json\n@Summary Invoice detail information\n@Description Invoice detail information\n@JSONSchema required", + "type": "string", + "example": "{\"title\":\"title\",\"amount\":100,\"taxRate\":0.06,\"tax\":6,\"total\":106,\"invoiceType\":1,\"invoiceContent\":1,\"invoiceStatus\":1,\"invoiceTime\":\"2021-01-01T00:00:00Z\",\"invoiceNumber\":\"invoice-number-1\",\"invoiceCode\":\"invoice-code-1\",\"invoiceFile\":\"invoice-file-1\"}" + }, + "kubeConfig": { + "type": "string" + }, + "owner": { + "type": "string", + "example": "admin" + }, + "paymentIDList": { + "description": "payment id list\n@Summary Payment ID list\n@Description Payment ID list\n@JSONSchema required", + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "[\"payment-id-1\"", + "\"payment-id-2\"]" + ] + }, + "userID": { + "type": "string", + "example": "admin" + } + } + }, "helper.Auth": { "type": "object", "properties": { @@ -1269,6 +1473,38 @@ const docTemplate = `{ } } }, + "helper.GetInvoiceReq": { + "type": "object", + "properties": { + "endTime": { + "type": "string", + "example": "2021-12-01T00:00:00Z" + }, + "kubeConfig": { + "type": "string" + }, + "owner": { + "type": "string", + "example": "admin" + }, + "page": { + "description": "@Summary Page\n@Description Page", + "type": "integer" + }, + "pageSize": { + "description": "@Summary Page Size\n@Description Page Size", + "type": "integer" + }, + "startTime": { + "type": "string", + "example": "2021-01-01T00:00:00Z" + }, + "userID": { + "type": "string", + "example": "admin" + } + } + }, "helper.GetPropertiesResp": { "type": "object", "properties": { @@ -1379,6 +1615,37 @@ const docTemplate = `{ } } }, + "helper.SetInvoiceStatusReq": { + "type": "object", + "required": [ + "invoiceIDList", + "status", + "token" + ], + "properties": { + "invoiceIDList": { + "description": "Invoice id list\n@Summary Invoice ID list\n@Description Invoice ID list\n@JSONSchema required", + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "[\"invoice-id-1\"", + "\"invoice-id-2\"]" + ] + }, + "status": { + "description": "Invoice status\n@Summary Invoice status\n@Description Invoice status\n@JSONSchema required", + "type": "string", + "example": "COMPLETED,REJECTED,PENDING" + }, + "token": { + "description": "@Summary Authentication token\n@Description Authentication token\n@JSONSchema required", + "type": "string", + "example": "token" + } + } + }, "helper.SetPaymentInvoiceReq": { "type": "object", "required": [ diff --git a/service/account/docs/swagger.json b/service/account/docs/swagger.json index 827b9687097..99e0869cfae 100644 --- a/service/account/docs/swagger.json +++ b/service/account/docs/swagger.json @@ -754,6 +754,174 @@ } } }, + "/account/v1alpha1/invoice/apply": { + "post": { + "description": "Apply invoice", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "ApplyInvoice" + ], + "summary": "Apply invoice", + "parameters": [ + { + "description": "Apply invoice request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/helper.ApplyInvoiceReq" + } + } + ], + "responses": { + "200": { + "description": "successfully apply invoice", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "400": { + "description": "failed to parse apply invoice request", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "401": { + "description": "authenticate error", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "500": { + "description": "failed to apply invoice", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, + "/account/v1alpha1/invoice/get": { + "post": { + "description": "Get invoice", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "GetInvoice" + ], + "summary": "Get invoice", + "parameters": [ + { + "description": "Get invoice request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/helper.GetInvoiceReq" + } + } + ], + "responses": { + "200": { + "description": "successfully get invoice", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "400": { + "description": "failed to parse get invoice request", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "401": { + "description": "authenticate error", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "500": { + "description": "failed to get invoice", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, + "/account/v1alpha1/invoice/set-status": { + "post": { + "description": "Set status invoice", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SetStatusInvoice" + ], + "summary": "Set status invoice", + "parameters": [ + { + "description": "Set status invoice request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/helper.SetInvoiceStatusReq" + } + } + ], + "responses": { + "200": { + "description": "successfully set status invoice", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "400": { + "description": "failed to parse set status invoice request", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "401": { + "description": "authenticate error", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "500": { + "description": "failed to set status invoice", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, "/account/v1alpha1/namespaces": { "post": { "description": "Get the billing history namespace list from the database", @@ -1072,6 +1240,42 @@ } } }, + "helper.ApplyInvoiceReq": { + "type": "object", + "required": [ + "detail", + "paymentIDList" + ], + "properties": { + "detail": { + "description": "invoice detail information json\n@Summary Invoice detail information\n@Description Invoice detail information\n@JSONSchema required", + "type": "string", + "example": "{\"title\":\"title\",\"amount\":100,\"taxRate\":0.06,\"tax\":6,\"total\":106,\"invoiceType\":1,\"invoiceContent\":1,\"invoiceStatus\":1,\"invoiceTime\":\"2021-01-01T00:00:00Z\",\"invoiceNumber\":\"invoice-number-1\",\"invoiceCode\":\"invoice-code-1\",\"invoiceFile\":\"invoice-file-1\"}" + }, + "kubeConfig": { + "type": "string" + }, + "owner": { + "type": "string", + "example": "admin" + }, + "paymentIDList": { + "description": "payment id list\n@Summary Payment ID list\n@Description Payment ID list\n@JSONSchema required", + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "[\"payment-id-1\"", + "\"payment-id-2\"]" + ] + }, + "userID": { + "type": "string", + "example": "admin" + } + } + }, "helper.Auth": { "type": "object", "properties": { @@ -1262,6 +1466,38 @@ } } }, + "helper.GetInvoiceReq": { + "type": "object", + "properties": { + "endTime": { + "type": "string", + "example": "2021-12-01T00:00:00Z" + }, + "kubeConfig": { + "type": "string" + }, + "owner": { + "type": "string", + "example": "admin" + }, + "page": { + "description": "@Summary Page\n@Description Page", + "type": "integer" + }, + "pageSize": { + "description": "@Summary Page Size\n@Description Page Size", + "type": "integer" + }, + "startTime": { + "type": "string", + "example": "2021-01-01T00:00:00Z" + }, + "userID": { + "type": "string", + "example": "admin" + } + } + }, "helper.GetPropertiesResp": { "type": "object", "properties": { @@ -1372,6 +1608,37 @@ } } }, + "helper.SetInvoiceStatusReq": { + "type": "object", + "required": [ + "invoiceIDList", + "status", + "token" + ], + "properties": { + "invoiceIDList": { + "description": "Invoice id list\n@Summary Invoice ID list\n@Description Invoice ID list\n@JSONSchema required", + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "[\"invoice-id-1\"", + "\"invoice-id-2\"]" + ] + }, + "status": { + "description": "Invoice status\n@Summary Invoice status\n@Description Invoice status\n@JSONSchema required", + "type": "string", + "example": "COMPLETED,REJECTED,PENDING" + }, + "token": { + "description": "@Summary Authentication token\n@Description Authentication token\n@JSONSchema required", + "type": "string", + "example": "token" + } + } + }, "helper.SetPaymentInvoiceReq": { "type": "object", "required": [ diff --git a/service/account/docs/swagger.yaml b/service/account/docs/swagger.yaml index 7e2cd6f3d87..1bf87dfd6ef 100644 --- a/service/account/docs/swagger.yaml +++ b/service/account/docs/swagger.yaml @@ -68,6 +68,40 @@ definitions: example: admin type: string type: object + helper.ApplyInvoiceReq: + properties: + detail: + description: |- + invoice detail information json + @Summary Invoice detail information + @Description Invoice detail information + @JSONSchema required + example: '{"title":"title","amount":100,"taxRate":0.06,"tax":6,"total":106,"invoiceType":1,"invoiceContent":1,"invoiceStatus":1,"invoiceTime":"2021-01-01T00:00:00Z","invoiceNumber":"invoice-number-1","invoiceCode":"invoice-code-1","invoiceFile":"invoice-file-1"}' + type: string + kubeConfig: + type: string + owner: + example: admin + type: string + paymentIDList: + description: |- + payment id list + @Summary Payment ID list + @Description Payment ID list + @JSONSchema required + example: + - '["payment-id-1"' + - '"payment-id-2"]' + items: + type: string + type: array + userID: + example: admin + type: string + required: + - detail + - paymentIDList + type: object helper.Auth: properties: kubeConfig: @@ -246,6 +280,33 @@ definitions: example: admin type: string type: object + helper.GetInvoiceReq: + properties: + endTime: + example: "2021-12-01T00:00:00Z" + type: string + kubeConfig: + type: string + owner: + example: admin + type: string + page: + description: |- + @Summary Page + @Description Page + type: integer + pageSize: + description: |- + @Summary Page Size + @Description Page Size + type: integer + startTime: + example: "2021-01-01T00:00:00Z" + type: string + userID: + example: admin + type: string + type: object helper.GetPropertiesResp: properties: data: @@ -336,6 +397,40 @@ definitions: type: string type: array type: object + helper.SetInvoiceStatusReq: + properties: + invoiceIDList: + description: |- + Invoice id list + @Summary Invoice ID list + @Description Invoice ID list + @JSONSchema required + example: + - '["invoice-id-1"' + - '"invoice-id-2"]' + items: + type: string + type: array + status: + description: |- + Invoice status + @Summary Invoice status + @Description Invoice status + @JSONSchema required + example: COMPLETED,REJECTED,PENDING + type: string + token: + description: |- + @Summary Authentication token + @Description Authentication token + @JSONSchema required + example: token + type: string + required: + - invoiceIDList + - status + - token + type: object helper.SetPaymentInvoiceReq: properties: kubeConfig: @@ -921,6 +1016,120 @@ paths: summary: Get transfer tags: - Transfer + /account/v1alpha1/invoice/apply: + post: + consumes: + - application/json + description: Apply invoice + parameters: + - description: Apply invoice request + in: body + name: request + required: true + schema: + $ref: '#/definitions/helper.ApplyInvoiceReq' + produces: + - application/json + responses: + "200": + description: successfully apply invoice + schema: + additionalProperties: true + type: object + "400": + description: failed to parse apply invoice request + schema: + additionalProperties: true + type: object + "401": + description: authenticate error + schema: + additionalProperties: true + type: object + "500": + description: failed to apply invoice + schema: + additionalProperties: true + type: object + summary: Apply invoice + tags: + - ApplyInvoice + /account/v1alpha1/invoice/get: + post: + consumes: + - application/json + description: Get invoice + parameters: + - description: Get invoice request + in: body + name: request + required: true + schema: + $ref: '#/definitions/helper.GetInvoiceReq' + produces: + - application/json + responses: + "200": + description: successfully get invoice + schema: + additionalProperties: true + type: object + "400": + description: failed to parse get invoice request + schema: + additionalProperties: true + type: object + "401": + description: authenticate error + schema: + additionalProperties: true + type: object + "500": + description: failed to get invoice + schema: + additionalProperties: true + type: object + summary: Get invoice + tags: + - GetInvoice + /account/v1alpha1/invoice/set-status: + post: + consumes: + - application/json + description: Set status invoice + parameters: + - description: Set status invoice request + in: body + name: request + required: true + schema: + $ref: '#/definitions/helper.SetInvoiceStatusReq' + produces: + - application/json + responses: + "200": + description: successfully set status invoice + schema: + additionalProperties: true + type: object + "400": + description: failed to parse set status invoice request + schema: + additionalProperties: true + type: object + "401": + description: authenticate error + schema: + additionalProperties: true + type: object + "500": + description: failed to set status invoice + schema: + additionalProperties: true + type: object + summary: Set status invoice + tags: + - SetStatusInvoice /account/v1alpha1/namespaces: post: consumes: diff --git a/service/account/helper/common.go b/service/account/helper/common.go index 326135f122f..7451ea61e88 100644 --- a/service/account/helper/common.go +++ b/service/account/helper/common.go @@ -21,6 +21,9 @@ const ( GetBasicCostDistribution = "/cost-basic-distribution" GetAppCostTimeRange = "/cost-app-time-range" CheckPermission = "/check-permission" + GetInvoice = "/invoice/get" + ApplyInvoice = "/invoice/apply" + SetStatusInvoice = "/invoice/set-status" ) // env diff --git a/service/account/helper/request.go b/service/account/helper/request.go index 99f2acd923f..a8180d60106 100644 --- a/service/account/helper/request.go +++ b/service/account/helper/request.go @@ -366,3 +366,76 @@ type LimitResp struct { // @Description Total page TotalPage int64 `json:"totalPage" bson:"totalPage"` } + +type ApplyInvoiceReq struct { + // @Summary Authentication information + // @Description Authentication information + // @JSONSchema required + *Auth `json:",inline" bson:",inline"` + + // payment id list + // @Summary Payment ID list + // @Description Payment ID list + // @JSONSchema required + PaymentIDList []string `json:"paymentIDList" bson:"paymentIDList" binding:"required" example:"[\"payment-id-1\",\"payment-id-2\"]"` + + // invoice detail information json + // @Summary Invoice detail information + // @Description Invoice detail information + // @JSONSchema required + Detail string `json:"detail" bson:"detail" binding:"required" example:"{\"title\":\"title\",\"amount\":100,\"taxRate\":0.06,\"tax\":6,\"total\":106,\"invoiceType\":1,\"invoiceContent\":1,\"invoiceStatus\":1,\"invoiceTime\":\"2021-01-01T00:00:00Z\",\"invoiceNumber\":\"invoice-number-1\",\"invoiceCode\":\"invoice-code-1\",\"invoiceFile\":\"invoice-file-1\"}"` +} + +func ParseApplyInvoiceReq(c *gin.Context) (*ApplyInvoiceReq, error) { + applyInvoice := &ApplyInvoiceReq{} + if err := c.ShouldBindJSON(applyInvoice); err != nil { + return nil, fmt.Errorf("bind json error: %v", err) + } + return applyInvoice, nil +} + +type GetInvoiceReq struct { + // @Summary Authentication information + // @Description Authentication information + // @JSONSchema required + *Auth `json:",inline" bson:",inline"` + + // @Summary Limit request + // @Description Limit request + LimitReq `json:",inline" bson:",inline"` +} + +type SetInvoiceStatusReq struct { + // Invoice id list + // @Summary Invoice ID list + // @Description Invoice ID list + // @JSONSchema required + InvoiceIDList []string `json:"invoiceIDList" bson:"invoiceIDList" binding:"required" example:"[\"invoice-id-1\",\"invoice-id-2\"]"` + + // Invoice status + // @Summary Invoice status + // @Description Invoice status + // @JSONSchema required + Status string `json:"status" bson:"status" binding:"required" example:"COMPLETED,REJECTED,PENDING"` + + // @Summary Authentication token + // @Description Authentication token + // @JSONSchema required + Token string `json:"token" bson:"token" binding:"required" example:"token"` +} + +func ParseGetInvoiceReq(c *gin.Context) (*GetInvoiceReq, error) { + invoiceList := &GetInvoiceReq{} + if err := c.ShouldBindJSON(invoiceList); err != nil { + return nil, fmt.Errorf("bind json error: %v", err) + } + return invoiceList, nil +} + +func ParseSetInvoiceStatusReq(c *gin.Context) (*SetInvoiceStatusReq, error) { + invoiceStatus := &SetInvoiceStatusReq{} + if err := c.ShouldBindJSON(invoiceStatus); err != nil { + return nil, fmt.Errorf("bind json error: %v", err) + } + return invoiceStatus, nil +} diff --git a/service/account/router/router.go b/service/account/router/router.go index 750a0a66b06..3d27d71b1e4 100644 --- a/service/account/router/router.go +++ b/service/account/router/router.go @@ -49,7 +49,10 @@ func RegisterPayRouter() { POST(helper.GetAppList, api.GetCostAppList). POST(helper.GetAppTypeList, api.GetAppTypeList). POST(helper.GetBasicCostDistribution, api.GetBasicCostDistribution). - POST(helper.GetAppCostTimeRange, api.GetAppCostTimeRange) + POST(helper.GetAppCostTimeRange, api.GetAppCostTimeRange). + POST(helper.GetInvoice, api.GetInvoice). + POST(helper.ApplyInvoice, api.ApplyInvoice). + POST(helper.SetStatusInvoice, api.SetStatusInvoice) docs.SwaggerInfo.Host = env.GetEnvWithDefault("SWAGGER_HOST", "localhost:2333") router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler)) From 42c70282dad62163b74aeab203614627fee2e1a0 Mon Sep 17 00:00:00 2001 From: jiahui Date: Mon, 12 Aug 2024 14:34:11 +0800 Subject: [PATCH 02/12] add invoice api service --- .../pkg/database/cockroach/accountv2.go | 67 +++++++++-------- controllers/pkg/database/interface.go | 3 +- service/account/api/api.go | 1 + service/account/dao/interface.go | 21 +++--- service/account/dao/interface_test.go | 72 +++++++++++++++++++ service/account/router/router.go | 2 +- 6 files changed, 127 insertions(+), 39 deletions(-) diff --git a/controllers/pkg/database/cockroach/accountv2.go b/controllers/pkg/database/cockroach/accountv2.go index 3b467c9c831..30f84662407 100644 --- a/controllers/pkg/database/cockroach/accountv2.go +++ b/controllers/pkg/database/cockroach/accountv2.go @@ -643,9 +643,9 @@ func (c *Cockroach) GetPayment(ops *types.UserQueryOpts, startTime, endTime time return payment, nil } -func (c *Cockroach) GetPaymentListWithIds(ids []string, status types.Payment) ([]types.Payment, error) { +func (c *Cockroach) GetUnInvoicedPaymentListWithIds(ids []string) ([]types.Payment, error) { var payment []types.Payment - if err := c.DB.Where(status).Where("id IN ?", ids).Find(&payment).Error; err != nil { + if err := c.DB.Where("id IN ?", ids).Where("invoiced_at = ?", false).Find(&payment).Error; err != nil { return nil, fmt.Errorf("failed to get payment: %w", err) } return payment, nil @@ -656,12 +656,48 @@ func (c *Cockroach) SetPaymentInvoice(ops *types.UserQueryOpts, paymentIDList [] if err != nil { return fmt.Errorf("failed to get user uid: %v", err) } - if err := c.DB.Where(types.Payment{PaymentRaw: types.PaymentRaw{UserUID: userUID}}).Where("id IN ?", paymentIDList).Update("invoiced_at", true).Error; err != nil { + if err := c.DB.Model(&types.Payment{}).Where(types.Payment{PaymentRaw: types.PaymentRaw{UserUID: userUID}}).Where("id IN ?", paymentIDList).Update("invoiced_at", true).Error; err != nil { return fmt.Errorf("failed to save payment: %v", err) } return nil } +func (c *Cockroach) SetPaymentInvoiceWithDB(ops *types.UserQueryOpts, paymentIDList []string, DB *gorm.DB) error { + userUID, err := c.GetUserUID(ops) + if err != nil { + return fmt.Errorf("failed to get user uid: %v", err) + } + if err := DB.Model(&types.Payment{}).Where(types.Payment{PaymentRaw: types.PaymentRaw{UserUID: userUID}}).Where("id IN ?", paymentIDList).Update("invoiced_at", true).Error; err != nil { + return fmt.Errorf("failed to save payment: %v", err) + } + return nil +} + +func (c *Cockroach) CreateInvoiceWithDB(i *types.Invoice, DB *gorm.DB) error { + if i.ID == "" { + id, err := gonanoid.New(12) + if err != nil { + return fmt.Errorf("failed to generate invoice id: %v", err) + } + i.ID = id + } + if i.CreatedAt.IsZero() { + i.CreatedAt = time.Now() + } + if err := DB.Create(i).Error; err != nil { + return fmt.Errorf("failed to save invoice: %v", err) + } + return nil +} + +// create invoicePayments +func (c *Cockroach) CreateInvoicePaymentsWithDB(invoicePayments []types.InvoicePayment, DB *gorm.DB) error { + if err := DB.Create(invoicePayments).Error; err != nil { + return fmt.Errorf("failed to save invoice payments: %v", err) + } + return nil +} + func (c *Cockroach) GetInvoice(userID string, req types.LimitReq) ([]types.Invoice, types.LimitResp, error) { var invoices []types.Invoice var total int64 @@ -703,31 +739,6 @@ func (c *Cockroach) GetInvoice(userID string, req types.LimitReq) ([]types.Invoi return invoices, limitResp, nil } -func (c *Cockroach) CreateInvoice(i *types.Invoice) error { - if i.ID == "" { - id, err := gonanoid.New(12) - if err != nil { - return fmt.Errorf("failed to generate invoice id: %v", err) - } - i.ID = id - } - if i.CreatedAt.IsZero() { - i.CreatedAt = time.Now() - } - if err := c.DB.Create(i).Error; err != nil { - return fmt.Errorf("failed to save invoice: %v", err) - } - return nil -} - -// create invoicePayments -func (c *Cockroach) CreateInvoicePayments(invoicePayments []types.InvoicePayment) error { - if err := c.DB.Create(invoicePayments).Error; err != nil { - return fmt.Errorf("failed to save invoice payments: %v", err) - } - return nil -} - func (c *Cockroach) SetInvoiceStatus(ids []string, stats string) error { if err := c.DB.Model(&types.Invoice{}).Where("id IN ?", ids).Update("status", stats).Error; err != nil { return fmt.Errorf("failed to update invoice status: %v", err) diff --git a/controllers/pkg/database/interface.go b/controllers/pkg/database/interface.go index a7c49800747..517a95df7a6 100644 --- a/controllers/pkg/database/interface.go +++ b/controllers/pkg/database/interface.go @@ -103,8 +103,7 @@ type AccountV2 interface { NewAccount(user *types.UserQueryOpts) (*types.Account, error) Payment(payment *types.Payment) error SavePayment(payment *types.Payment) error - CreateInvoice(i *types.Invoice) error - GetPaymentListWithIds(ids []string, status types.Payment) ([]types.Payment, error) + GetUnInvoicedPaymentListWithIds(ids []string) ([]types.Payment, error) CreateErrorPaymentCreate(payment types.Payment, errorMsg string) error CreateAccount(ops *types.UserQueryOpts, account *types.Account) (*types.Account, error) CreateErrorAccountCreate(account *types.Account, owner, errorMsg string) error diff --git a/service/account/api/api.go b/service/account/api/api.go index fe3a32a9b82..272e0e8e58e 100644 --- a/service/account/api/api.go +++ b/service/account/api/api.go @@ -287,6 +287,7 @@ func GetAccount(c *gin.Context) { } // SetPaymentInvoice +// TODO will be deprecated // @Summary Set payment invoice // @Description Set payment invoice // @Tags PaymentInvoice diff --git a/service/account/dao/interface.go b/service/account/dao/interface.go index 26c333fe595..2f3d0ea8dc3 100644 --- a/service/account/dao/interface.go +++ b/service/account/dao/interface.go @@ -1258,7 +1258,10 @@ func (m *MongoDB) getBillingCollection() *mongo.Collection { } func (m *Account) ApplyInvoice(req *helper.ApplyInvoiceReq) error { - payments, err := m.ck.GetPaymentListWithIds(req.PaymentIDList, types.Payment{PaymentRaw: types.PaymentRaw{InvoicedAt: false}}) + if len(req.PaymentIDList) == 0 { + return nil + } + payments, err := m.ck.GetUnInvoicedPaymentListWithIds(req.PaymentIDList) if err != nil { return fmt.Errorf("failed to get payment list: %v", err) } @@ -1268,18 +1271,19 @@ func (m *Account) ApplyInvoice(req *helper.ApplyInvoiceReq) error { amount := int64(0) var paymentIds []string var invoicePayments []types.InvoicePayment + id, err := gonanoid.New(12) + if err != nil { + return fmt.Errorf("failed to generate payment id: %v", err) + } for i := range payments { amount += payments[i].Amount paymentIds = append(paymentIds, payments[i].ID) invoicePayments = append(invoicePayments, types.InvoicePayment{ PaymentID: payments[i].ID, Amount: payments[i].Amount, + InvoiceID: id, }) } - id, err := gonanoid.New(12) - if err != nil { - return fmt.Errorf("failed to generate payment id: %v", err) - } invoice := &types.Invoice{ ID: id, UserID: req.UserID, @@ -1289,15 +1293,16 @@ func (m *Account) ApplyInvoice(req *helper.ApplyInvoiceReq) error { TotalAmount: amount, Status: types.PendingInvoiceStatus, } + // save invoice with transaction if err = m.ck.DB.Transaction( func(tx *gorm.DB) error { - if err = m.ck.SetPaymentInvoice(&types.UserQueryOpts{Owner: req.Auth.Owner}, paymentIds); err != nil { + if err = m.ck.SetPaymentInvoiceWithDB(&types.UserQueryOpts{ID: req.UserID}, paymentIds, tx); err != nil { return fmt.Errorf("failed to set payment invoice: %v", err) } - if err = m.ck.CreateInvoice(invoice); err != nil { + if err = m.ck.CreateInvoiceWithDB(invoice, tx); err != nil { return fmt.Errorf("failed to create invoice: %v", err) } - if err = m.ck.CreateInvoicePayments(invoicePayments); err != nil { + if err = m.ck.CreateInvoicePaymentsWithDB(invoicePayments, tx); err != nil { return fmt.Errorf("failed to create invoice payments: %v", err) } return nil diff --git a/service/account/dao/interface_test.go b/service/account/dao/interface_test.go index 07dd14d92d0..32c4da9f7ff 100644 --- a/service/account/dao/interface_test.go +++ b/service/account/dao/interface_test.go @@ -510,7 +510,79 @@ func TestMongoDB_GetAppCost1(t *testing.T) { t.Logf("costAppList: %#+v", appList) } +func TestAccount_ApplyInvoice(t *testing.T) { + dbCTX := context.Background() + m, err := newAccountForTest(os.Getenv("MONGO_URI"), os.Getenv("GLOBAL_COCKROACH_URI"), os.Getenv("LOCAL_COCKROACH_URI")) + if err != nil { + t.Fatalf("NewAccountInterface() error = %v", err) + return + } + defer func() { + if err = m.Disconnect(dbCTX); err != nil { + t.Errorf("failed to disconnect mongo: error = %v", err) + } + }() + req := &helper.ApplyInvoiceReq{ + Auth: &helper.Auth{ + Owner: "uy771xun", + UserID: "Vobqe43JUs", + }, + PaymentIDList: []string{ + "vrCBsLt1oIf0", + "uro7KQmUp1bD", + "85M_N42NID_S", + "o6qZ7qk6wNRl", + }, + Detail: "jsonxxxx", + } + err = m.ApplyInvoice(req) + if err != nil { + t.Fatalf("failed to apply invoice: %v", err) + } + t.Logf("success to apply invoice") + + invoice, resp, err := m.GetInvoice(&helper.GetInvoiceReq{ + Auth: &helper.Auth{ + Owner: "uy771xun", + UserID: "Vobqe43JUs", + }, + LimitReq: helper.LimitReq{ + Page: 1, + PageSize: 10, + }, + }) + if err != nil { + t.Fatalf("failed to get invoice: %v", err) + } + t.Logf("invoice: %#+v", invoice) + t.Logf("resp: %#+v", resp) +} + +func TestAccount_SetStatusInvoice(t *testing.T) { + dbCTX := context.Background() + m, err := newAccountForTest(os.Getenv("MONGO_URI"), os.Getenv("GLOBAL_COCKROACH_URI"), os.Getenv("LOCAL_COCKROACH_URI")) + if err != nil { + t.Fatalf("NewAccountInterface() error = %v", err) + return + } + defer func() { + if err = m.Disconnect(dbCTX); err != nil { + t.Errorf("failed to disconnect mongo: error = %v", err) + } + }() + err = m.SetStatusInvoice(&helper.SetInvoiceStatusReq{ + InvoiceIDList: []string{"8WKSA0ECYhSr", "M0Y0dyUhdX9S"}, + Status: types.PendingInvoiceStatus, + }) + if err != nil { + t.Fatalf("failed to set status invoice: %v", err) + } +} + func init() { // set env os.Setenv("MONGO_URI", "") + os.Setenv("GLOBAL_COCKROACH_URI", "") + os.Setenv("LOCAL_COCKROACH_URI", "") + os.Setenv("LOCAL_REGION", "") } diff --git a/service/account/router/router.go b/service/account/router/router.go index 3d27d71b1e4..8eefc97290e 100644 --- a/service/account/router/router.go +++ b/service/account/router/router.go @@ -40,7 +40,7 @@ func RegisterPayRouter() { POST(helper.GetRechargeAmount, api.GetRechargeAmount). POST(helper.GetConsumptionAmount, api.GetConsumptionAmount). POST(helper.GetPropertiesUsed, api.GetPropertiesUsedAmount). - POST(helper.SetPaymentInvoice, api.SetPaymentInvoice). + POST(helper.SetPaymentInvoice, api.SetPaymentInvoice). // will be deprecated POST(helper.SetTransfer, api.TransferAmount). POST(helper.GetTransfer, api.GetTransfer). POST(helper.CheckPermission, api.CheckPermission). From c7469d0a6c736d7f07fb779dd4ae3d43864c68cd Mon Sep 17 00:00:00 2001 From: jiahui Date: Mon, 12 Aug 2024 14:47:28 +0800 Subject: [PATCH 03/12] add invoice api service --- controllers/pkg/types/global.go | 16 ++++++++-------- service/account/api/api.go | 13 +++++++------ 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/controllers/pkg/types/global.go b/controllers/pkg/types/global.go index 2df526e3df8..69fd3232fc9 100644 --- a/controllers/pkg/types/global.go +++ b/controllers/pkg/types/global.go @@ -256,15 +256,15 @@ const ( ) type Invoice struct { - ID string `gorm:"type:text;primary_key"` - UserID string `gorm:"type:text;not null"` - CreatedAt time.Time `gorm:"type:timestamp(3) with time zone;default:current_timestamp()"` - UpdatedAt time.Time `gorm:"type:timestamp(3) with time zone"` - Detail string `gorm:"type:text;not null"` - Remark string `gorm:"type:text"` - TotalAmount int64 `gorm:"type:bigint;not null"` + ID string `gorm:"type:text;primary_key" json:"id" bson:"id"` + UserID string `gorm:"type:text;not null" json:"userID" bson:"userID"` + CreatedAt time.Time `gorm:"type:timestamp(3) with time zone;default:current_timestamp()" bson:"createdAt" json:"createdAt"` + UpdatedAt time.Time `gorm:"type:timestamp(3) with time zone;default:current_timestamp()" bson:"updatedAt" json:"updatedAt"` + Detail string `gorm:"type:text;not null" json:"detail" bson:"detail"` + Remark string `gorm:"type:text" json:"remark" bson:"remark"` + TotalAmount int64 `gorm:"type:bigint;not null" json:"totalAmount" bson:"totalAmount"` // Pending, Completed, Rejected - Status InvoiceStatus `gorm:"type:text;not null"` + Status InvoiceStatus `gorm:"type:text;not null" json:"status" bson:"status"` } type InvoicePayment struct { diff --git a/service/account/api/api.go b/service/account/api/api.go index 272e0e8e58e..aebcbe1d3ec 100644 --- a/service/account/api/api.go +++ b/service/account/api/api.go @@ -776,13 +776,14 @@ func GetInvoice(c *gin.Context) { c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to get invoice : %v", err)}) return } + type resp struct { + Invoices []types.Invoice `json:"invoices"` + LimitResp types.LimitResp `json:",inline" bson:",inline"` + } c.JSON(http.StatusOK, gin.H{ - "data": struct { - Invoices []types.Invoice `json:"invoices"` - Limits types.LimitResp `json:",inline" bson:",inline"` - }{ - Invoices: invoices, - Limits: limits, + "data": resp{ + Invoices: invoices, + LimitResp: limits, }, }) } From 8b334ea488cdc857f800bc2f7f574a7ce905bcdb Mon Sep 17 00:00:00 2001 From: jiahui Date: Tue, 13 Aug 2024 10:29:15 +0800 Subject: [PATCH 04/12] optimize resp --- service/account/api/api.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/service/account/api/api.go b/service/account/api/api.go index aebcbe1d3ec..3ede7f11892 100644 --- a/service/account/api/api.go +++ b/service/account/api/api.go @@ -777,8 +777,8 @@ func GetInvoice(c *gin.Context) { return } type resp struct { - Invoices []types.Invoice `json:"invoices"` - LimitResp types.LimitResp `json:",inline" bson:",inline"` + Invoices []types.Invoice `json:"invoices"` + types.LimitResp `json:",inline" bson:",inline"` } c.JSON(http.StatusOK, gin.H{ "data": resp{ From de605376823250e97188d97bd95f4420b26eb6a3 Mon Sep 17 00:00:00 2001 From: jiahui Date: Tue, 13 Aug 2024 17:27:31 +0800 Subject: [PATCH 05/12] optimize get payment api --- .../pkg/database/cockroach/accountv2.go | 33 ++++++++++++++++++ service/account/api/api.go | 22 +++++++++--- service/account/dao/interface.go | 8 ++--- service/account/dao/interface_test.go | 6 +++- service/account/docs/docs.go | 34 ++++++++++++++++++- service/account/docs/swagger.json | 34 ++++++++++++++++++- service/account/docs/swagger.yaml | 29 +++++++++++++++- service/account/helper/request.go | 25 ++++++++++++++ 8 files changed, 179 insertions(+), 12 deletions(-) diff --git a/controllers/pkg/database/cockroach/accountv2.go b/controllers/pkg/database/cockroach/accountv2.go index 30f84662407..f269be2a3be 100644 --- a/controllers/pkg/database/cockroach/accountv2.go +++ b/controllers/pkg/database/cockroach/accountv2.go @@ -643,6 +643,39 @@ func (c *Cockroach) GetPayment(ops *types.UserQueryOpts, startTime, endTime time return payment, nil } +func (c *Cockroach) GetPaymentWithLimit(ops *types.UserQueryOpts, req types.LimitReq) ([]types.Payment, types.LimitResp, error) { + var payment []types.Payment + var total int64 + var limitResp types.LimitResp + page, pageSize := req.Page, req.PageSize + userUID, err := c.GetUserUID(ops) + if err != nil { + return nil, limitResp, fmt.Errorf("failed to get user uid: %v", err) + } + + query := c.DB.Model(&types.Payment{}).Where(types.Payment{PaymentRaw: types.PaymentRaw{UserUID: userUID}}) + if !req.StartTime.IsZero() { + query = query.Where("created_at >= ?", req.StartTime) + } + if !req.EndTime.IsZero() { + query = query.Where("created_at <= ?", req.EndTime) + } + if err := query.Count(&total).Error; err != nil { + return nil, limitResp, fmt.Errorf("failed to get total count: %v", err) + } + totalPage := (total + int64(pageSize) - 1) / int64(pageSize) + if err := query.Limit(pageSize). + Offset((page - 1) * pageSize). + Find(&payment).Error; err != nil { + return nil, limitResp, fmt.Errorf("failed to get payment: %v", err) + } + limitResp = types.LimitResp{ + Total: total, + TotalPage: totalPage, + } + return payment, limitResp, nil +} + func (c *Cockroach) GetUnInvoicedPaymentListWithIds(ids []string) ([]types.Payment, error) { var payment []types.Payment if err := c.DB.Where("id IN ?", ids).Where("invoiced_at = ?", false).Find(&payment).Error; err != nil { diff --git a/service/account/api/api.go b/service/account/api/api.go index 3ede7f11892..ec7f01ffabe 100644 --- a/service/account/api/api.go +++ b/service/account/api/api.go @@ -124,14 +124,14 @@ func GetConsumptionAmount(c *gin.Context) { // @Tags Payment // @Accept json // @Produce json -// @Param request body helper.UserBaseReq true "User payment request" +// @Param request body helper.GetPaymentReq true "User payment request" // @Success 200 {object} map[string]interface{} "successfully retrieved user payment" // @Failure 400 {object} map[string]interface{} "failed to parse user payment request" // @Failure 401 {object} map[string]interface{} "authenticate error" // @Failure 500 {object} map[string]interface{} "failed to get user payment" // @Router /account/v1alpha1/costs/payment [post] func GetPayment(c *gin.Context) { - req, err := helper.ParseUserBaseReq(c) + req, err := helper.ParsePaymentReq(c) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("failed to parse user payment request: %v", err)}) return @@ -140,13 +140,27 @@ func GetPayment(c *gin.Context) { c.JSON(http.StatusUnauthorized, gin.H{"error": fmt.Sprintf("authenticate error : %v", err)}) return } - payment, err := dao.DBClient.GetPayment(types.UserQueryOpts{Owner: req.Owner}, req.TimeRange.StartTime, req.TimeRange.EndTime) + payment, limitResp, err := dao.DBClient.GetPayment(&types.UserQueryOpts{Owner: req.Owner}, types.LimitReq{ + Page: req.Page, + PageSize: req.PageSize, + TimeRange: types.TimeRange{ + StartTime: req.StartTime, + EndTime: req.EndTime, + }, + }) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to get payment : %v", err)}) return } + type PaymentResp struct { + Payment []types.Payment `json:"payments"` + types.LimitResp `json:",inline" bson:",inline"` + } c.JSON(http.StatusOK, gin.H{ - "payment": payment, + "data": PaymentResp{ + Payment: payment, + LimitResp: limitResp, + }, }) } diff --git a/service/account/dao/interface.go b/service/account/dao/interface.go index 2f3d0ea8dc3..d46dc486372 100644 --- a/service/account/dao/interface.go +++ b/service/account/dao/interface.go @@ -42,7 +42,7 @@ type Interface interface { GetRechargeAmount(ops types.UserQueryOpts, startTime, endTime time.Time) (int64, error) GetPropertiesUsedAmount(user string, startTime, endTime time.Time) (map[string]int64, error) GetAccount(ops types.UserQueryOpts) (*types.Account, error) - GetPayment(ops types.UserQueryOpts, startTime, endTime time.Time) ([]types.Payment, error) + GetPayment(ops *types.UserQueryOpts, req types.LimitReq) ([]types.Payment, types.LimitResp, error) ApplyInvoice(req *helper.ApplyInvoiceReq) error GetInvoice(req *helper.GetInvoiceReq) ([]types.Invoice, types.LimitResp, error) SetStatusInvoice(req *helper.SetInvoiceStatusReq) error @@ -109,8 +109,8 @@ func (g *Cockroach) GetUserCrName(ops types.UserQueryOpts) (string, error) { return user.CrName, nil } -func (g *Cockroach) GetPayment(ops types.UserQueryOpts, startTime, endTime time.Time) ([]types.Payment, error) { - return g.ck.GetPayment(&ops, startTime, endTime) +func (g *Cockroach) GetPayment(ops *types.UserQueryOpts, req types.LimitReq) ([]types.Payment, types.LimitResp, error) { + return g.ck.GetPaymentWithLimit(ops, req) } func (g *Cockroach) SetPaymentInvoice(req *helper.SetPaymentInvoiceReq) error { @@ -137,7 +137,7 @@ func (g *Cockroach) GetLocalRegion() types.Region { } func (g *Cockroach) GetRechargeAmount(ops types.UserQueryOpts, startTime, endTime time.Time) (int64, error) { - payment, err := g.GetPayment(ops, startTime, endTime) + payment, err := g.ck.GetPayment(&ops, startTime, endTime) if err != nil { return 0, fmt.Errorf("failed to get payment: %v", err) } diff --git a/service/account/dao/interface_test.go b/service/account/dao/interface_test.go index 32c4da9f7ff..4d5a6e1f38f 100644 --- a/service/account/dao/interface_test.go +++ b/service/account/dao/interface_test.go @@ -19,12 +19,16 @@ func TestCockroach_GetPayment(t *testing.T) { t.Fatalf("NewAccountInterface() error = %v", err) return } - got, err := db.GetPayment(types.UserQueryOpts{Owner: "1fgtm0mn"}, time.Time{}, time.Time{}) + got, resp, err := db.GetPayment(&types.UserQueryOpts{Owner: "1fgtm0mn"}, types.LimitReq{ + Page: 1, + PageSize: 10, + }) if err != nil { t.Fatalf("GetPayment() error = %v", err) return } t.Logf("got = %+v", got) + t.Logf("limit resp = %+v", resp) } func TestMongoDB_GetAppCosts(t *testing.T) { diff --git a/service/account/docs/docs.go b/service/account/docs/docs.go index 6a00dcb8c65..b3161e03602 100644 --- a/service/account/docs/docs.go +++ b/service/account/docs/docs.go @@ -564,7 +564,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/helper.UserBaseReq" + "$ref": "#/definitions/helper.GetPaymentReq" } } ], @@ -1505,6 +1505,38 @@ const docTemplate = `{ } } }, + "helper.GetPaymentReq": { + "type": "object", + "properties": { + "endTime": { + "type": "string", + "example": "2021-12-01T00:00:00Z" + }, + "kubeConfig": { + "type": "string" + }, + "owner": { + "type": "string", + "example": "admin" + }, + "page": { + "description": "@Summary Page\n@Description Page", + "type": "integer" + }, + "pageSize": { + "description": "@Summary Page Size\n@Description Page Size", + "type": "integer" + }, + "startTime": { + "type": "string", + "example": "2021-01-01T00:00:00Z" + }, + "userID": { + "type": "string", + "example": "admin" + } + } + }, "helper.GetPropertiesResp": { "type": "object", "properties": { diff --git a/service/account/docs/swagger.json b/service/account/docs/swagger.json index 99e0869cfae..f36c95fcb81 100644 --- a/service/account/docs/swagger.json +++ b/service/account/docs/swagger.json @@ -557,7 +557,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/helper.UserBaseReq" + "$ref": "#/definitions/helper.GetPaymentReq" } } ], @@ -1498,6 +1498,38 @@ } } }, + "helper.GetPaymentReq": { + "type": "object", + "properties": { + "endTime": { + "type": "string", + "example": "2021-12-01T00:00:00Z" + }, + "kubeConfig": { + "type": "string" + }, + "owner": { + "type": "string", + "example": "admin" + }, + "page": { + "description": "@Summary Page\n@Description Page", + "type": "integer" + }, + "pageSize": { + "description": "@Summary Page Size\n@Description Page Size", + "type": "integer" + }, + "startTime": { + "type": "string", + "example": "2021-01-01T00:00:00Z" + }, + "userID": { + "type": "string", + "example": "admin" + } + } + }, "helper.GetPropertiesResp": { "type": "object", "properties": { diff --git a/service/account/docs/swagger.yaml b/service/account/docs/swagger.yaml index 1bf87dfd6ef..2d3495a997e 100644 --- a/service/account/docs/swagger.yaml +++ b/service/account/docs/swagger.yaml @@ -307,6 +307,33 @@ definitions: example: admin type: string type: object + helper.GetPaymentReq: + properties: + endTime: + example: "2021-12-01T00:00:00Z" + type: string + kubeConfig: + type: string + owner: + example: admin + type: string + page: + description: |- + @Summary Page + @Description Page + type: integer + pageSize: + description: |- + @Summary Page Size + @Description Page Size + type: integer + startTime: + example: "2021-01-01T00:00:00Z" + type: string + userID: + example: admin + type: string + type: object helper.GetPropertiesResp: properties: data: @@ -880,7 +907,7 @@ paths: name: request required: true schema: - $ref: '#/definitions/helper.UserBaseReq' + $ref: '#/definitions/helper.GetPaymentReq' produces: - application/json responses: diff --git a/service/account/helper/request.go b/service/account/helper/request.go index a8180d60106..28e720fa47f 100644 --- a/service/account/helper/request.go +++ b/service/account/helper/request.go @@ -181,6 +181,20 @@ func ParseUserBaseReq(c *gin.Context) (*UserBaseReq, error) { return userCosts, nil } +func ParsePaymentReq(c *gin.Context) (*GetPaymentReq, error) { + payment := &GetPaymentReq{} + if err := c.ShouldBindJSON(payment); err != nil { + return nil, fmt.Errorf("bind json error: %v", err) + } + if payment.Page <= 0 { + payment.Page = 1 + } + if payment.PageSize <= 0 { + payment.PageSize = 10 + } + return payment, nil +} + type AppCostsReq struct { // @Summary Order ID // @Description Order ID @@ -268,6 +282,17 @@ func setDefaultTimeRange(timeRange *TimeRange) { } } +type GetPaymentReq struct { + // @Summary Authentication information + // @Description Authentication information + // @JSONSchema required + *Auth `json:",inline" bson:",inline"` + + // @Summary Limit request + // @Description Limit request + LimitReq `json:",inline" bson:",inline"` +} + type CostOverviewResp struct { // @Summary Cost overview // @Description Cost overview From f34367d9537cfaa0648ec7c87f488b8073c1fab3 Mon Sep 17 00:00:00 2001 From: jiahui Date: Tue, 13 Aug 2024 17:38:34 +0800 Subject: [PATCH 06/12] optimize get payment api --- .../pkg/database/cockroach/accountv2.go | 8 ++++++++ service/account/api/api.go | 9 +-------- service/account/dao/interface.go | 20 ++++++++++++++++--- service/account/dao/interface_test.go | 5 +---- service/account/helper/request.go | 5 +++++ 5 files changed, 32 insertions(+), 15 deletions(-) diff --git a/controllers/pkg/database/cockroach/accountv2.go b/controllers/pkg/database/cockroach/accountv2.go index f269be2a3be..53b9282225b 100644 --- a/controllers/pkg/database/cockroach/accountv2.go +++ b/controllers/pkg/database/cockroach/accountv2.go @@ -643,6 +643,14 @@ func (c *Cockroach) GetPayment(ops *types.UserQueryOpts, startTime, endTime time return payment, nil } +func (c *Cockroach) GetPaymentWithID(paymentID string) (*types.Payment, error) { + var payment types.Payment + if err := c.DB.Where(types.Payment{ID: paymentID}).First(&payment).Error; err != nil { + return nil, fmt.Errorf("failed to get payment: %w", err) + } + return &payment, nil +} + func (c *Cockroach) GetPaymentWithLimit(ops *types.UserQueryOpts, req types.LimitReq) ([]types.Payment, types.LimitResp, error) { var payment []types.Payment var total int64 diff --git a/service/account/api/api.go b/service/account/api/api.go index ec7f01ffabe..594d1a9b246 100644 --- a/service/account/api/api.go +++ b/service/account/api/api.go @@ -140,14 +140,7 @@ func GetPayment(c *gin.Context) { c.JSON(http.StatusUnauthorized, gin.H{"error": fmt.Sprintf("authenticate error : %v", err)}) return } - payment, limitResp, err := dao.DBClient.GetPayment(&types.UserQueryOpts{Owner: req.Owner}, types.LimitReq{ - Page: req.Page, - PageSize: req.PageSize, - TimeRange: types.TimeRange{ - StartTime: req.StartTime, - EndTime: req.EndTime, - }, - }) + payment, limitResp, err := dao.DBClient.GetPayment(&types.UserQueryOpts{Owner: req.Owner}, req) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to get payment : %v", err)}) return diff --git a/service/account/dao/interface.go b/service/account/dao/interface.go index d46dc486372..86882b02388 100644 --- a/service/account/dao/interface.go +++ b/service/account/dao/interface.go @@ -42,7 +42,7 @@ type Interface interface { GetRechargeAmount(ops types.UserQueryOpts, startTime, endTime time.Time) (int64, error) GetPropertiesUsedAmount(user string, startTime, endTime time.Time) (map[string]int64, error) GetAccount(ops types.UserQueryOpts) (*types.Account, error) - GetPayment(ops *types.UserQueryOpts, req types.LimitReq) ([]types.Payment, types.LimitResp, error) + GetPayment(ops *types.UserQueryOpts, req *helper.GetPaymentReq) ([]types.Payment, types.LimitResp, error) ApplyInvoice(req *helper.ApplyInvoiceReq) error GetInvoice(req *helper.GetInvoiceReq) ([]types.Invoice, types.LimitResp, error) SetStatusInvoice(req *helper.SetInvoiceStatusReq) error @@ -109,8 +109,22 @@ func (g *Cockroach) GetUserCrName(ops types.UserQueryOpts) (string, error) { return user.CrName, nil } -func (g *Cockroach) GetPayment(ops *types.UserQueryOpts, req types.LimitReq) ([]types.Payment, types.LimitResp, error) { - return g.ck.GetPaymentWithLimit(ops, req) +func (g *Cockroach) GetPayment(ops *types.UserQueryOpts, req *helper.GetPaymentReq) ([]types.Payment, types.LimitResp, error) { + if req.PaymentID != "" { + payment, err := g.ck.GetPaymentWithID(req.PaymentID) + if err != nil { + return nil, types.LimitResp{}, fmt.Errorf("failed to get payment with id: %v", err) + } + return []types.Payment{*payment}, types.LimitResp{Total: 1, TotalPage: 1}, nil + } + return g.ck.GetPaymentWithLimit(ops, types.LimitReq{ + Page: req.Page, + PageSize: req.PageSize, + TimeRange: types.TimeRange{ + StartTime: req.StartTime, + EndTime: req.EndTime, + }, + }) } func (g *Cockroach) SetPaymentInvoice(req *helper.SetPaymentInvoiceReq) error { diff --git a/service/account/dao/interface_test.go b/service/account/dao/interface_test.go index 4d5a6e1f38f..d7b8ffe4ced 100644 --- a/service/account/dao/interface_test.go +++ b/service/account/dao/interface_test.go @@ -19,10 +19,7 @@ func TestCockroach_GetPayment(t *testing.T) { t.Fatalf("NewAccountInterface() error = %v", err) return } - got, resp, err := db.GetPayment(&types.UserQueryOpts{Owner: "1fgtm0mn"}, types.LimitReq{ - Page: 1, - PageSize: 10, - }) + got, resp, err := db.GetPayment(&types.UserQueryOpts{Owner: "1fgtm0mn"}, &helper.GetPaymentReq{}) if err != nil { t.Fatalf("GetPayment() error = %v", err) return diff --git a/service/account/helper/request.go b/service/account/helper/request.go index 28e720fa47f..bbae877e1c4 100644 --- a/service/account/helper/request.go +++ b/service/account/helper/request.go @@ -283,6 +283,11 @@ func setDefaultTimeRange(timeRange *TimeRange) { } type GetPaymentReq struct { + // @Summary Payment ID + // @Description Payment ID + // @JSONSchema + PaymentID string `json:"paymentID,omitempty" bson:"paymentID" example:"payment-id-1"` + // @Summary Authentication information // @Description Authentication information // @JSONSchema required From f6db3cdb8949718f35c6b991fe38b90650445513 Mon Sep 17 00:00:00 2001 From: jiahui Date: Tue, 13 Aug 2024 17:41:42 +0800 Subject: [PATCH 07/12] optimize get invoice api --- controllers/pkg/database/cockroach/accountv2.go | 9 +++++++++ service/account/dao/interface.go | 7 +++++++ service/account/docs/docs.go | 10 ++++++++++ service/account/docs/swagger.json | 10 ++++++++++ service/account/docs/swagger.yaml | 14 ++++++++++++++ service/account/helper/request.go | 5 +++++ 6 files changed, 55 insertions(+) diff --git a/controllers/pkg/database/cockroach/accountv2.go b/controllers/pkg/database/cockroach/accountv2.go index 53b9282225b..68500692850 100644 --- a/controllers/pkg/database/cockroach/accountv2.go +++ b/controllers/pkg/database/cockroach/accountv2.go @@ -739,6 +739,15 @@ func (c *Cockroach) CreateInvoicePaymentsWithDB(invoicePayments []types.InvoiceP return nil } +// GetInvoiceWithID +func (c *Cockroach) GetInvoiceWithID(invoiceID string) (*types.Invoice, error) { + var invoice types.Invoice + if err := c.DB.Where(types.Invoice{ID: invoiceID}).First(&invoice).Error; err != nil { + return nil, fmt.Errorf("failed to get invoice: %v", err) + } + return &invoice, nil +} + func (c *Cockroach) GetInvoice(userID string, req types.LimitReq) ([]types.Invoice, types.LimitResp, error) { var invoices []types.Invoice var total int64 diff --git a/service/account/dao/interface.go b/service/account/dao/interface.go index 86882b02388..d177babd9f9 100644 --- a/service/account/dao/interface.go +++ b/service/account/dao/interface.go @@ -1327,6 +1327,13 @@ func (m *Account) ApplyInvoice(req *helper.ApplyInvoiceReq) error { } func (m *Account) GetInvoice(req *helper.GetInvoiceReq) ([]types.Invoice, types.LimitResp, error) { + if req.InvoiceID != "" { + invoice, err := m.ck.GetInvoiceWithID(req.InvoiceID) + if err != nil { + return nil, types.LimitResp{}, fmt.Errorf("failed to get invoice: %v", err) + } + return []types.Invoice{*invoice}, types.LimitResp{Total: 1, TotalPage: 1}, nil + } return m.ck.GetInvoice(req.UserID, types.LimitReq{ Page: req.Page, PageSize: req.PageSize, diff --git a/service/account/docs/docs.go b/service/account/docs/docs.go index b3161e03602..a6a4b8d5a62 100644 --- a/service/account/docs/docs.go +++ b/service/account/docs/docs.go @@ -1480,6 +1480,11 @@ const docTemplate = `{ "type": "string", "example": "2021-12-01T00:00:00Z" }, + "invoiceID": { + "description": "@Summary Invoice ID\n@Description Invoice ID\n@JSONSchema", + "type": "string", + "example": "invoice-id-1" + }, "kubeConfig": { "type": "string" }, @@ -1527,6 +1532,11 @@ const docTemplate = `{ "description": "@Summary Page Size\n@Description Page Size", "type": "integer" }, + "paymentID": { + "description": "@Summary Payment ID\n@Description Payment ID\n@JSONSchema", + "type": "string", + "example": "payment-id-1" + }, "startTime": { "type": "string", "example": "2021-01-01T00:00:00Z" diff --git a/service/account/docs/swagger.json b/service/account/docs/swagger.json index f36c95fcb81..cebd7ff9173 100644 --- a/service/account/docs/swagger.json +++ b/service/account/docs/swagger.json @@ -1473,6 +1473,11 @@ "type": "string", "example": "2021-12-01T00:00:00Z" }, + "invoiceID": { + "description": "@Summary Invoice ID\n@Description Invoice ID\n@JSONSchema", + "type": "string", + "example": "invoice-id-1" + }, "kubeConfig": { "type": "string" }, @@ -1520,6 +1525,11 @@ "description": "@Summary Page Size\n@Description Page Size", "type": "integer" }, + "paymentID": { + "description": "@Summary Payment ID\n@Description Payment ID\n@JSONSchema", + "type": "string", + "example": "payment-id-1" + }, "startTime": { "type": "string", "example": "2021-01-01T00:00:00Z" diff --git a/service/account/docs/swagger.yaml b/service/account/docs/swagger.yaml index 2d3495a997e..d270ab35910 100644 --- a/service/account/docs/swagger.yaml +++ b/service/account/docs/swagger.yaml @@ -285,6 +285,13 @@ definitions: endTime: example: "2021-12-01T00:00:00Z" type: string + invoiceID: + description: |- + @Summary Invoice ID + @Description Invoice ID + @JSONSchema + example: invoice-id-1 + type: string kubeConfig: type: string owner: @@ -327,6 +334,13 @@ definitions: @Summary Page Size @Description Page Size type: integer + paymentID: + description: |- + @Summary Payment ID + @Description Payment ID + @JSONSchema + example: payment-id-1 + type: string startTime: example: "2021-01-01T00:00:00Z" type: string diff --git a/service/account/helper/request.go b/service/account/helper/request.go index bbae877e1c4..e808479aee3 100644 --- a/service/account/helper/request.go +++ b/service/account/helper/request.go @@ -425,6 +425,11 @@ func ParseApplyInvoiceReq(c *gin.Context) (*ApplyInvoiceReq, error) { } type GetInvoiceReq struct { + // @Summary Invoice ID + // @Description Invoice ID + // @JSONSchema + InvoiceID string `json:"invoiceID,omitempty" bson:"invoiceID" example:"invoice-id-1"` + // @Summary Authentication information // @Description Authentication information // @JSONSchema required From 6b553a59dca059e0dc1a431e5733584d1af1f651 Mon Sep 17 00:00:00 2001 From: jiahui Date: Fri, 16 Aug 2024 11:09:21 +0800 Subject: [PATCH 08/12] optimize apply invoice return message --- service/account/api/api.go | 16 +++++++- service/account/dao/interface.go | 25 ++++++------ service/account/dao/interface_test.go | 2 +- service/account/docs/docs.go | 55 +++++++++++++++++++++++++++ service/account/docs/swagger.json | 55 +++++++++++++++++++++++++++ service/account/docs/swagger.yaml | 41 ++++++++++++++++++++ service/account/helper/request.go | 1 + 7 files changed, 181 insertions(+), 14 deletions(-) diff --git a/service/account/api/api.go b/service/account/api/api.go index 594d1a9b246..2cbfd6afce2 100644 --- a/service/account/api/api.go +++ b/service/account/api/api.go @@ -735,6 +735,7 @@ func checkInvoiceToken(token string) error { // @Success 200 {object} map[string]interface{} "successfully apply invoice" // @Failure 400 {object} map[string]interface{} "failed to parse apply invoice request" // @Failure 401 {object} map[string]interface{} "authenticate error" +// @Failure 403 {object} map[string]interface{} "no payment can be applied to the invoice" // @Failure 500 {object} map[string]interface{} "failed to apply invoice" // @Router /account/v1alpha1/invoice/apply [post] func ApplyInvoice(c *gin.Context) { @@ -747,11 +748,17 @@ func ApplyInvoice(c *gin.Context) { c.JSON(http.StatusUnauthorized, gin.H{"error": fmt.Sprintf("authenticate error : %v", err)}) return } - if err := dao.DBClient.ApplyInvoice(req); err != nil { + invoice, payments, err := dao.DBClient.ApplyInvoice(req) + if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to apply invoice : %v", err)}) return } + if len(payments) == 0 { + c.JSON(http.StatusForbidden, gin.H{"error": "no payment can be applied to the invoice"}) + return + } c.JSON(http.StatusOK, gin.H{ + "data": map[string]interface{}{"invoice": invoice, "payments": payments}, "message": "successfully apply invoice", }) } @@ -774,7 +781,12 @@ func GetInvoice(c *gin.Context) { c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("failed to parse get invoice request: %v", err)}) return } - if err := CheckAuthAndCalibrate(req.Auth); err != nil { + if req.Token != "" { + err = checkInvoiceToken(req.Token) + } else { + err = CheckAuthAndCalibrate(req.Auth) + } + if err != nil { c.JSON(http.StatusUnauthorized, gin.H{"error": fmt.Sprintf("authenticate error : %v", err)}) return } diff --git a/service/account/dao/interface.go b/service/account/dao/interface.go index d177babd9f9..9e88bd21661 100644 --- a/service/account/dao/interface.go +++ b/service/account/dao/interface.go @@ -43,7 +43,7 @@ type Interface interface { GetPropertiesUsedAmount(user string, startTime, endTime time.Time) (map[string]int64, error) GetAccount(ops types.UserQueryOpts) (*types.Account, error) GetPayment(ops *types.UserQueryOpts, req *helper.GetPaymentReq) ([]types.Payment, types.LimitResp, error) - ApplyInvoice(req *helper.ApplyInvoiceReq) error + ApplyInvoice(req *helper.ApplyInvoiceReq) (invoice types.Invoice, payments []types.Payment, err error) GetInvoice(req *helper.GetInvoiceReq) ([]types.Invoice, types.LimitResp, error) SetStatusInvoice(req *helper.SetInvoiceStatusReq) error GetWorkspaceName(namespaces []string) ([][]string, error) @@ -1271,23 +1271,25 @@ func (m *MongoDB) getBillingCollection() *mongo.Collection { return m.Client.Database(m.AccountDBName).Collection(m.BillingConn) } -func (m *Account) ApplyInvoice(req *helper.ApplyInvoiceReq) error { +func (m *Account) ApplyInvoice(req *helper.ApplyInvoiceReq) (invoice types.Invoice, payments []types.Payment, err error) { if len(req.PaymentIDList) == 0 { - return nil + return } - payments, err := m.ck.GetUnInvoicedPaymentListWithIds(req.PaymentIDList) + payments, err = m.ck.GetUnInvoicedPaymentListWithIds(req.PaymentIDList) if err != nil { - return fmt.Errorf("failed to get payment list: %v", err) + err = fmt.Errorf("failed to get payment list: %v", err) + return } if len(payments) == 0 { - return fmt.Errorf("no payment record was found for invoicing") + return } amount := int64(0) var paymentIds []string var invoicePayments []types.InvoicePayment id, err := gonanoid.New(12) if err != nil { - return fmt.Errorf("failed to generate payment id: %v", err) + err = fmt.Errorf("failed to generate payment id: %v", err) + return } for i := range payments { amount += payments[i].Amount @@ -1298,7 +1300,7 @@ func (m *Account) ApplyInvoice(req *helper.ApplyInvoiceReq) error { InvoiceID: id, }) } - invoice := &types.Invoice{ + invoice = types.Invoice{ ID: id, UserID: req.UserID, CreatedAt: time.Now().UTC(), @@ -1313,7 +1315,7 @@ func (m *Account) ApplyInvoice(req *helper.ApplyInvoiceReq) error { if err = m.ck.SetPaymentInvoiceWithDB(&types.UserQueryOpts{ID: req.UserID}, paymentIds, tx); err != nil { return fmt.Errorf("failed to set payment invoice: %v", err) } - if err = m.ck.CreateInvoiceWithDB(invoice, tx); err != nil { + if err = m.ck.CreateInvoiceWithDB(&invoice, tx); err != nil { return fmt.Errorf("failed to create invoice: %v", err) } if err = m.ck.CreateInvoicePaymentsWithDB(invoicePayments, tx); err != nil { @@ -1321,9 +1323,10 @@ func (m *Account) ApplyInvoice(req *helper.ApplyInvoiceReq) error { } return nil }); err != nil { - return fmt.Errorf("failed to apply invoice: %v", err) + err = fmt.Errorf("failed to apply invoice: %v", err) + return } - return nil + return } func (m *Account) GetInvoice(req *helper.GetInvoiceReq) ([]types.Invoice, types.LimitResp, error) { diff --git a/service/account/dao/interface_test.go b/service/account/dao/interface_test.go index d7b8ffe4ced..22b0c6d3aea 100644 --- a/service/account/dao/interface_test.go +++ b/service/account/dao/interface_test.go @@ -536,7 +536,7 @@ func TestAccount_ApplyInvoice(t *testing.T) { }, Detail: "jsonxxxx", } - err = m.ApplyInvoice(req) + _, _, err = m.ApplyInvoice(req) if err != nil { t.Fatalf("failed to apply invoice: %v", err) } diff --git a/service/account/docs/docs.go b/service/account/docs/docs.go index a6a4b8d5a62..0002f8a4f96 100644 --- a/service/account/docs/docs.go +++ b/service/account/docs/docs.go @@ -807,6 +807,13 @@ const docTemplate = `{ "additionalProperties": true } }, + "403": { + "description": "no payment can be applied to the invoice", + "schema": { + "type": "object", + "additionalProperties": true + } + }, "500": { "description": "failed to apply invoice", "schema": { @@ -1241,6 +1248,10 @@ const docTemplate = `{ "type": "string", "example": "2021-01-01T00:00:00Z" }, + "token": { + "type": "string", + "example": "token" + }, "userID": { "type": "string", "example": "admin" @@ -1277,6 +1288,10 @@ const docTemplate = `{ "\"payment-id-2\"]" ] }, + "token": { + "type": "string", + "example": "token" + }, "userID": { "type": "string", "example": "admin" @@ -1293,6 +1308,10 @@ const docTemplate = `{ "type": "string", "example": "admin" }, + "token": { + "type": "string", + "example": "token" + }, "userID": { "type": "string", "example": "admin" @@ -1332,6 +1351,10 @@ const docTemplate = `{ "type": "string", "example": "2021-01-01T00:00:00Z" }, + "token": { + "type": "string", + "example": "token" + }, "userID": { "type": "string", "example": "admin" @@ -1467,6 +1490,10 @@ const docTemplate = `{ "type": "string", "example": "2021-01-01T00:00:00Z" }, + "token": { + "type": "string", + "example": "token" + }, "userID": { "type": "string", "example": "admin" @@ -1504,6 +1531,10 @@ const docTemplate = `{ "type": "string", "example": "2021-01-01T00:00:00Z" }, + "token": { + "type": "string", + "example": "token" + }, "userID": { "type": "string", "example": "admin" @@ -1541,6 +1572,10 @@ const docTemplate = `{ "type": "string", "example": "2021-01-01T00:00:00Z" }, + "token": { + "type": "string", + "example": "token" + }, "userID": { "type": "string", "example": "admin" @@ -1598,6 +1633,10 @@ const docTemplate = `{ "type": "string", "example": "2021-01-01T00:00:00Z" }, + "token": { + "type": "string", + "example": "token" + }, "transferID": { "description": "@Summary Transfer ID\n@Description Transfer ID", "type": "string", @@ -1632,6 +1671,10 @@ const docTemplate = `{ "type": "string", "example": "2021-01-01T00:00:00Z" }, + "token": { + "type": "string", + "example": "token" + }, "type": { "description": "@Summary Type of the request (optional)\n@Description Type of the request (optional)\n@JSONSchema", "type": "integer" @@ -1712,6 +1755,10 @@ const docTemplate = `{ "\"payment-id-2\"]" ] }, + "token": { + "type": "string", + "example": "token" + }, "userID": { "type": "string", "example": "admin" @@ -1741,6 +1788,10 @@ const docTemplate = `{ "type": "string", "example": "admin" }, + "token": { + "type": "string", + "example": "token" + }, "transferAll": { "description": "@Summary Transfer all\n@Description Transfer all amount", "type": "boolean" @@ -1769,6 +1820,10 @@ const docTemplate = `{ "type": "string", "example": "2021-01-01T00:00:00Z" }, + "token": { + "type": "string", + "example": "token" + }, "userID": { "type": "string", "example": "admin" diff --git a/service/account/docs/swagger.json b/service/account/docs/swagger.json index cebd7ff9173..4a6e4d20620 100644 --- a/service/account/docs/swagger.json +++ b/service/account/docs/swagger.json @@ -800,6 +800,13 @@ "additionalProperties": true } }, + "403": { + "description": "no payment can be applied to the invoice", + "schema": { + "type": "object", + "additionalProperties": true + } + }, "500": { "description": "failed to apply invoice", "schema": { @@ -1234,6 +1241,10 @@ "type": "string", "example": "2021-01-01T00:00:00Z" }, + "token": { + "type": "string", + "example": "token" + }, "userID": { "type": "string", "example": "admin" @@ -1270,6 +1281,10 @@ "\"payment-id-2\"]" ] }, + "token": { + "type": "string", + "example": "token" + }, "userID": { "type": "string", "example": "admin" @@ -1286,6 +1301,10 @@ "type": "string", "example": "admin" }, + "token": { + "type": "string", + "example": "token" + }, "userID": { "type": "string", "example": "admin" @@ -1325,6 +1344,10 @@ "type": "string", "example": "2021-01-01T00:00:00Z" }, + "token": { + "type": "string", + "example": "token" + }, "userID": { "type": "string", "example": "admin" @@ -1460,6 +1483,10 @@ "type": "string", "example": "2021-01-01T00:00:00Z" }, + "token": { + "type": "string", + "example": "token" + }, "userID": { "type": "string", "example": "admin" @@ -1497,6 +1524,10 @@ "type": "string", "example": "2021-01-01T00:00:00Z" }, + "token": { + "type": "string", + "example": "token" + }, "userID": { "type": "string", "example": "admin" @@ -1534,6 +1565,10 @@ "type": "string", "example": "2021-01-01T00:00:00Z" }, + "token": { + "type": "string", + "example": "token" + }, "userID": { "type": "string", "example": "admin" @@ -1591,6 +1626,10 @@ "type": "string", "example": "2021-01-01T00:00:00Z" }, + "token": { + "type": "string", + "example": "token" + }, "transferID": { "description": "@Summary Transfer ID\n@Description Transfer ID", "type": "string", @@ -1625,6 +1664,10 @@ "type": "string", "example": "2021-01-01T00:00:00Z" }, + "token": { + "type": "string", + "example": "token" + }, "type": { "description": "@Summary Type of the request (optional)\n@Description Type of the request (optional)\n@JSONSchema", "type": "integer" @@ -1705,6 +1748,10 @@ "\"payment-id-2\"]" ] }, + "token": { + "type": "string", + "example": "token" + }, "userID": { "type": "string", "example": "admin" @@ -1734,6 +1781,10 @@ "type": "string", "example": "admin" }, + "token": { + "type": "string", + "example": "token" + }, "transferAll": { "description": "@Summary Transfer all\n@Description Transfer all amount", "type": "boolean" @@ -1762,6 +1813,10 @@ "type": "string", "example": "2021-01-01T00:00:00Z" }, + "token": { + "type": "string", + "example": "token" + }, "userID": { "type": "string", "example": "admin" diff --git a/service/account/docs/swagger.yaml b/service/account/docs/swagger.yaml index d270ab35910..97b84d5c450 100644 --- a/service/account/docs/swagger.yaml +++ b/service/account/docs/swagger.yaml @@ -64,6 +64,9 @@ definitions: startTime: example: "2021-01-01T00:00:00Z" type: string + token: + example: token + type: string userID: example: admin type: string @@ -95,6 +98,9 @@ definitions: items: type: string type: array + token: + example: token + type: string userID: example: admin type: string @@ -109,6 +115,9 @@ definitions: owner: example: admin type: string + token: + example: token + type: string userID: example: admin type: string @@ -144,6 +153,9 @@ definitions: startTime: example: "2021-01-01T00:00:00Z" type: string + token: + example: token + type: string userID: example: admin type: string @@ -276,6 +288,9 @@ definitions: startTime: example: "2021-01-01T00:00:00Z" type: string + token: + example: token + type: string userID: example: admin type: string @@ -310,6 +325,9 @@ definitions: startTime: example: "2021-01-01T00:00:00Z" type: string + token: + example: token + type: string userID: example: admin type: string @@ -344,6 +362,9 @@ definitions: startTime: example: "2021-01-01T00:00:00Z" type: string + token: + example: token + type: string userID: example: admin type: string @@ -388,6 +409,9 @@ definitions: startTime: example: "2021-01-01T00:00:00Z" type: string + token: + example: token + type: string transferID: description: |- @Summary Transfer ID @@ -418,6 +442,9 @@ definitions: startTime: example: "2021-01-01T00:00:00Z" type: string + token: + example: token + type: string type: description: |- @Summary Type of the request (optional) @@ -490,6 +517,9 @@ definitions: items: type: string type: array + token: + example: token + type: string userID: example: admin type: string @@ -517,6 +547,9 @@ definitions: @JSONSchema required example: admin type: string + token: + example: token + type: string transferAll: description: |- @Summary Transfer all @@ -541,6 +574,9 @@ definitions: startTime: example: "2021-01-01T00:00:00Z" type: string + token: + example: token + type: string userID: example: admin type: string @@ -1087,6 +1123,11 @@ paths: schema: additionalProperties: true type: object + "403": + description: no payment can be applied to the invoice + schema: + additionalProperties: true + type: object "500": description: failed to apply invoice schema: diff --git a/service/account/helper/request.go b/service/account/helper/request.go index e808479aee3..8031c7ccfed 100644 --- a/service/account/helper/request.go +++ b/service/account/helper/request.go @@ -118,6 +118,7 @@ type Auth struct { Owner string `json:"owner" bson:"owner" example:"admin"` UserID string `json:"userID" bson:"userID" example:"admin"` KubeConfig string `json:"kubeConfig" bson:"kubeConfig"` + Token string `json:"token" bson:"token" example:"token"` } func ParseNamespaceBillingHistoryReq(c *gin.Context) (*NamespaceBillingHistoryReq, error) { From 360050147118f4caaeb91c0dae367e7a3ec923f4 Mon Sep 17 00:00:00 2001 From: jiahui Date: Fri, 16 Aug 2024 14:36:20 +0800 Subject: [PATCH 09/12] add sort get cost overview --- service/account/dao/interface.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/service/account/dao/interface.go b/service/account/dao/interface.go index 9e88bd21661..748be6255e4 100644 --- a/service/account/dao/interface.go +++ b/service/account/dao/interface.go @@ -649,18 +649,22 @@ func (m *MongoDB) GetCostAppList(req helper.GetCostAppListReq) (resp helper.Cost "$gte": req.StartTime, "$lte": req.EndTime, } - + sort := bson.E{Key: "$sort", Value: bson.D{ + {Key: "time", Value: -1}, + }} pipeline := mongo.Pipeline{ {{Key: "$match", Value: match}}, {{Key: "$unwind", Value: "$app_costs"}}, {{Key: "$match", Value: bson.D{ {Key: "app_costs.name", Value: req.AppName}, }}}, + {sort}, } if req.AppName == "" { pipeline = mongo.Pipeline{ {{Key: "$match", Value: match}}, {{Key: "$unwind", Value: "$app_costs"}}, + {sort}, } } @@ -993,6 +997,9 @@ func (m *MongoDB) getAppPipeLine(req helper.GetCostAppListReq) []bson.M { pipeline := []bson.M{ {"$match": match}, {"$unwind": "$app_costs"}, + {"$sort": bson.M{ + "time": -1, + }}, {"$group": bson.M{ "_id": bson.M{ "namespace": "$namespace", From 34a64aa8125e6c8b84a1f3de919a96788f46faf8 Mon Sep 17 00:00:00 2001 From: jiahui Date: Tue, 20 Aug 2024 11:03:09 +0800 Subject: [PATCH 10/12] get payment by invoice --- .../pkg/database/cockroach/accountv2.go | 26 +++++++++ service/account/api/api.go | 37 ++++++++++++ service/account/dao/interface.go | 5 ++ service/account/docs/docs.go | 56 +++++++++++++++++++ service/account/docs/swagger.json | 56 +++++++++++++++++++ service/account/docs/swagger.yaml | 38 +++++++++++++ service/account/helper/common.go | 1 + service/account/router/router.go | 3 +- 8 files changed, 221 insertions(+), 1 deletion(-) diff --git a/controllers/pkg/database/cockroach/accountv2.go b/controllers/pkg/database/cockroach/accountv2.go index 68500692850..fc8a3b7cdab 100644 --- a/controllers/pkg/database/cockroach/accountv2.go +++ b/controllers/pkg/database/cockroach/accountv2.go @@ -789,6 +789,32 @@ func (c *Cockroach) GetInvoice(userID string, req types.LimitReq) ([]types.Invoi return invoices, limitResp, nil } +func (c *Cockroach) GetInvoicePayments(invoiceID string) ([]types.InvoicePayment, error) { + var invoicePayments []types.InvoicePayment + query := c.DB.Model(&types.InvoicePayment{}).Where("invoice_id = ?", invoiceID) + if err := query.Find(&invoicePayments).Error; err != nil { + return nil, fmt.Errorf("failed to get invoice payments: %v", err) + } + + return invoicePayments, nil +} + +func (c *Cockroach) GetPaymentWithInvoice(invoiceID string) ([]types.Payment, error) { + invoicePayments, err := c.GetInvoicePayments(invoiceID) + if err != nil { + return nil, fmt.Errorf("failed to get invoice payments: %v", err) + } + var paymentIDs []string + for _, invoicePayment := range invoicePayments { + paymentIDs = append(paymentIDs, invoicePayment.PaymentID) + } + var payments []types.Payment + if err := c.DB.Where("id IN ?", paymentIDs).Find(&payments).Error; err != nil { + return nil, fmt.Errorf("failed to get payments: %v", err) + } + return payments, nil +} + func (c *Cockroach) SetInvoiceStatus(ids []string, stats string) error { if err := c.DB.Model(&types.Invoice{}).Where("id IN ?", ids).Update("status", stats).Error; err != nil { return fmt.Errorf("failed to update invoice status: %v", err) diff --git a/service/account/api/api.go b/service/account/api/api.go index 2cbfd6afce2..d65dfa9f7af 100644 --- a/service/account/api/api.go +++ b/service/account/api/api.go @@ -838,3 +838,40 @@ func SetStatusInvoice(c *gin.Context) { "message": "successfully set status invoice", }) } + +// GetInvoicePayment +// @Summary Get invoice payment +// @Description Get invoice payment +// @Tags GetInvoicePayment +// @Accept json +// @Produce json +// @Param request body helper.GetInvoiceReq true "Get invoice payment request" +// @Success 200 {object} map[string]interface{} "successfully get invoice payment" +// @Failure 400 {object} map[string]interface{} "failed to parse get invoice payment request" +// @Failure 401 {object} map[string]interface{} "authenticate error" +// @Failure 500 {object} map[string]interface{} "failed to get invoice payment" +// @Router /account/v1alpha1/invoice/get-payment [post] +func GetInvoicePayment(c *gin.Context) { + req, err := helper.ParseGetInvoiceReq(c) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("failed to parse get invoice payment request: %v", err)}) + return + } + if req.Token != "" { + err = checkInvoiceToken(req.Token) + } else { + err = CheckAuthAndCalibrate(req.Auth) + } + if err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": fmt.Sprintf("authenticate error : %v", err)}) + return + } + payments, err := dao.DBClient.GetInvoicePayments(req.InvoiceID) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to get invoice payment : %v", err)}) + return + } + c.JSON(http.StatusOK, gin.H{ + "data": payments, + }) +} diff --git a/service/account/dao/interface.go b/service/account/dao/interface.go index 748be6255e4..ece8b0041e2 100644 --- a/service/account/dao/interface.go +++ b/service/account/dao/interface.go @@ -45,6 +45,7 @@ type Interface interface { GetPayment(ops *types.UserQueryOpts, req *helper.GetPaymentReq) ([]types.Payment, types.LimitResp, error) ApplyInvoice(req *helper.ApplyInvoiceReq) (invoice types.Invoice, payments []types.Payment, err error) GetInvoice(req *helper.GetInvoiceReq) ([]types.Invoice, types.LimitResp, error) + GetInvoicePayments(invoiceID string) ([]types.Payment, error) SetStatusInvoice(req *helper.SetInvoiceStatusReq) error GetWorkspaceName(namespaces []string) ([][]string, error) SetPaymentInvoice(req *helper.SetPaymentInvoiceReq) error @@ -1354,6 +1355,10 @@ func (m *Account) GetInvoice(req *helper.GetInvoiceReq) ([]types.Invoice, types. }) } +func (m *Account) GetInvoicePayments(invoiceID string) ([]types.Payment, error) { + return m.ck.GetPaymentWithInvoice(invoiceID) +} + func (m *Account) SetStatusInvoice(req *helper.SetInvoiceStatusReq) error { return m.ck.SetInvoiceStatus(req.InvoiceIDList, req.Status) } diff --git a/service/account/docs/docs.go b/service/account/docs/docs.go index 0002f8a4f96..3d03f3c9d21 100644 --- a/service/account/docs/docs.go +++ b/service/account/docs/docs.go @@ -880,6 +880,62 @@ const docTemplate = `{ } } }, + "/account/v1alpha1/invoice/get-payment": { + "post": { + "description": "Get invoice payment", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "GetInvoicePayment" + ], + "summary": "Get invoice payment", + "parameters": [ + { + "description": "Get invoice payment request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/helper.GetInvoiceReq" + } + } + ], + "responses": { + "200": { + "description": "successfully get invoice payment", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "400": { + "description": "failed to parse get invoice payment request", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "401": { + "description": "authenticate error", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "500": { + "description": "failed to get invoice payment", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, "/account/v1alpha1/invoice/set-status": { "post": { "description": "Set status invoice", diff --git a/service/account/docs/swagger.json b/service/account/docs/swagger.json index 4a6e4d20620..43ac3efc1c5 100644 --- a/service/account/docs/swagger.json +++ b/service/account/docs/swagger.json @@ -873,6 +873,62 @@ } } }, + "/account/v1alpha1/invoice/get-payment": { + "post": { + "description": "Get invoice payment", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "GetInvoicePayment" + ], + "summary": "Get invoice payment", + "parameters": [ + { + "description": "Get invoice payment request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/helper.GetInvoiceReq" + } + } + ], + "responses": { + "200": { + "description": "successfully get invoice payment", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "400": { + "description": "failed to parse get invoice payment request", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "401": { + "description": "authenticate error", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "500": { + "description": "failed to get invoice payment", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, "/account/v1alpha1/invoice/set-status": { "post": { "description": "Set status invoice", diff --git a/service/account/docs/swagger.yaml b/service/account/docs/swagger.yaml index 97b84d5c450..4e9e0ffadc8 100644 --- a/service/account/docs/swagger.yaml +++ b/service/account/docs/swagger.yaml @@ -1174,6 +1174,44 @@ paths: summary: Get invoice tags: - GetInvoice + /account/v1alpha1/invoice/get-payment: + post: + consumes: + - application/json + description: Get invoice payment + parameters: + - description: Get invoice payment request + in: body + name: request + required: true + schema: + $ref: '#/definitions/helper.GetInvoiceReq' + produces: + - application/json + responses: + "200": + description: successfully get invoice payment + schema: + additionalProperties: true + type: object + "400": + description: failed to parse get invoice payment request + schema: + additionalProperties: true + type: object + "401": + description: authenticate error + schema: + additionalProperties: true + type: object + "500": + description: failed to get invoice payment + schema: + additionalProperties: true + type: object + summary: Get invoice payment + tags: + - GetInvoicePayment /account/v1alpha1/invoice/set-status: post: consumes: diff --git a/service/account/helper/common.go b/service/account/helper/common.go index 7451ea61e88..90d9a8b946c 100644 --- a/service/account/helper/common.go +++ b/service/account/helper/common.go @@ -24,6 +24,7 @@ const ( GetInvoice = "/invoice/get" ApplyInvoice = "/invoice/apply" SetStatusInvoice = "/invoice/set-status" + GetInvoicePayment = "/invoice/get-payment" ) // env diff --git a/service/account/router/router.go b/service/account/router/router.go index 8eefc97290e..ea1aabf8378 100644 --- a/service/account/router/router.go +++ b/service/account/router/router.go @@ -52,7 +52,8 @@ func RegisterPayRouter() { POST(helper.GetAppCostTimeRange, api.GetAppCostTimeRange). POST(helper.GetInvoice, api.GetInvoice). POST(helper.ApplyInvoice, api.ApplyInvoice). - POST(helper.SetStatusInvoice, api.SetStatusInvoice) + POST(helper.SetStatusInvoice, api.SetStatusInvoice). + POST(helper.GetInvoicePayment, api.GetInvoicePayment) docs.SwaggerInfo.Host = env.GetEnvWithDefault("SWAGGER_HOST", "localhost:2333") router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler)) From aa8af25fcf50b96411eb1e913246d90a6a216fa2 Mon Sep 17 00:00:00 2001 From: jiahui Date: Tue, 20 Aug 2024 17:02:57 +0800 Subject: [PATCH 11/12] payment order by created_at DESC --- controllers/pkg/database/cockroach/accountv2.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/controllers/pkg/database/cockroach/accountv2.go b/controllers/pkg/database/cockroach/accountv2.go index fc8a3b7cdab..8b8ba95da5e 100644 --- a/controllers/pkg/database/cockroach/accountv2.go +++ b/controllers/pkg/database/cockroach/accountv2.go @@ -672,7 +672,8 @@ func (c *Cockroach) GetPaymentWithLimit(ops *types.UserQueryOpts, req types.Limi return nil, limitResp, fmt.Errorf("failed to get total count: %v", err) } totalPage := (total + int64(pageSize) - 1) / int64(pageSize) - if err := query.Limit(pageSize). + if err := query.Order("created_at DESC"). + Limit(pageSize). Offset((page - 1) * pageSize). Find(&payment).Error; err != nil { return nil, limitResp, fmt.Errorf("failed to get payment: %v", err) From 7a6ce69333510a946641bea0a9aa060f44a4b79c Mon Sep 17 00:00:00 2001 From: jiahui Date: Tue, 20 Aug 2024 17:38:31 +0800 Subject: [PATCH 12/12] rollout properties unit price --- service/account/dao/interface.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/service/account/dao/interface.go b/service/account/dao/interface.go index ece8b0041e2..f4fd86ea790 100644 --- a/service/account/dao/interface.go +++ b/service/account/dao/interface.go @@ -173,13 +173,9 @@ func (m *MongoDB) GetProperties() ([]common.PropertyQuery, error) { m.Properties = properties } for _, types := range m.Properties.Types { - price := types.ViewPrice - if price == 0 { - price = types.UnitPrice - } property := common.PropertyQuery{ Name: types.Name, - UnitPrice: price, + UnitPrice: types.UnitPrice, Unit: types.UnitString, Alias: types.Alias, }