diff --git a/Makefile b/Makefile index 7fbdae017..cb1b551f3 100644 --- a/Makefile +++ b/Makefile @@ -57,7 +57,8 @@ air: cronjob: go run ./cmd/cronjob/main.go -test: +test: + golangci-lint run docker rm --volumes -f ${POSTGRES_TEST_CONTAINER} docker-compose up -d ${POSTGRES_TEST_SERVICE} @while ! docker exec $(POSTGRES_TEST_CONTAINER) pg_isready > /dev/null; do \ diff --git a/docs/docs.go b/docs/docs.go index 19e695f4e..248976428 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -7939,13 +7939,13 @@ const docTemplate = `{ "type": "object", "properties": { "cost": { - "type": "integer" + "type": "number" }, "description": { "type": "string" }, "discount": { - "type": "integer" + "type": "number" }, "isExternal": { "type": "boolean" @@ -7954,7 +7954,7 @@ const docTemplate = `{ "type": "number" }, "unitCost": { - "type": "integer" + "type": "number" } } }, @@ -7992,7 +7992,7 @@ const docTemplate = `{ "type": "string" }, "discount": { - "type": "integer", + "type": "number", "minimum": 0 }, "dueDate": { @@ -8035,15 +8035,15 @@ const docTemplate = `{ "type": "string" }, "subtotal": { - "type": "integer", + "type": "number", "minimum": 0 }, "tax": { - "type": "integer", + "type": "number", "minimum": 0 }, "total": { - "type": "integer", + "type": "number", "minimum": 0 } } @@ -10179,7 +10179,7 @@ const docTemplate = `{ } }, "conversionAmount": { - "type": "integer" + "type": "number" }, "conversionRate": { "type": "number" @@ -10188,7 +10188,7 @@ const docTemplate = `{ "type": "string" }, "discount": { - "type": "integer" + "type": "number" }, "dueAt": { "type": "string" @@ -10239,16 +10239,16 @@ const docTemplate = `{ "type": "string" }, "subTotal": { - "type": "integer" + "type": "number" }, "tax": { - "type": "integer" + "type": "number" }, "threadID": { "type": "string" }, "total": { - "type": "integer" + "type": "number" }, "year": { "type": "integer" @@ -10277,7 +10277,7 @@ const docTemplate = `{ "$ref": "#/definitions/view.CompanyInfo" }, "conversionAmount": { - "type": "integer" + "type": "number" }, "conversionRate": { "type": "number" @@ -10286,7 +10286,7 @@ const docTemplate = `{ "type": "string" }, "discount": { - "type": "integer" + "type": "number" }, "dueAt": { "type": "string" @@ -10340,16 +10340,16 @@ const docTemplate = `{ "type": "string" }, "subTotal": { - "type": "integer" + "type": "number" }, "tax": { - "type": "integer" + "type": "number" }, "threadID": { "type": "string" }, "total": { - "type": "integer" + "type": "number" }, "year": { "type": "integer" @@ -10360,13 +10360,13 @@ const docTemplate = `{ "type": "object", "properties": { "cost": { - "type": "integer" + "type": "number" }, "description": { "type": "string" }, "discount": { - "type": "integer" + "type": "number" }, "isExternal": { "type": "boolean" @@ -10375,7 +10375,7 @@ const docTemplate = `{ "type": "number" }, "unitCost": { - "type": "integer" + "type": "number" } } }, diff --git a/docs/swagger.json b/docs/swagger.json index 9695a62d6..b71e63848 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -7931,13 +7931,13 @@ "type": "object", "properties": { "cost": { - "type": "integer" + "type": "number" }, "description": { "type": "string" }, "discount": { - "type": "integer" + "type": "number" }, "isExternal": { "type": "boolean" @@ -7946,7 +7946,7 @@ "type": "number" }, "unitCost": { - "type": "integer" + "type": "number" } } }, @@ -7984,7 +7984,7 @@ "type": "string" }, "discount": { - "type": "integer", + "type": "number", "minimum": 0 }, "dueDate": { @@ -8027,15 +8027,15 @@ "type": "string" }, "subtotal": { - "type": "integer", + "type": "number", "minimum": 0 }, "tax": { - "type": "integer", + "type": "number", "minimum": 0 }, "total": { - "type": "integer", + "type": "number", "minimum": 0 } } @@ -10171,7 +10171,7 @@ } }, "conversionAmount": { - "type": "integer" + "type": "number" }, "conversionRate": { "type": "number" @@ -10180,7 +10180,7 @@ "type": "string" }, "discount": { - "type": "integer" + "type": "number" }, "dueAt": { "type": "string" @@ -10231,16 +10231,16 @@ "type": "string" }, "subTotal": { - "type": "integer" + "type": "number" }, "tax": { - "type": "integer" + "type": "number" }, "threadID": { "type": "string" }, "total": { - "type": "integer" + "type": "number" }, "year": { "type": "integer" @@ -10269,7 +10269,7 @@ "$ref": "#/definitions/view.CompanyInfo" }, "conversionAmount": { - "type": "integer" + "type": "number" }, "conversionRate": { "type": "number" @@ -10278,7 +10278,7 @@ "type": "string" }, "discount": { - "type": "integer" + "type": "number" }, "dueAt": { "type": "string" @@ -10332,16 +10332,16 @@ "type": "string" }, "subTotal": { - "type": "integer" + "type": "number" }, "tax": { - "type": "integer" + "type": "number" }, "threadID": { "type": "string" }, "total": { - "type": "integer" + "type": "number" }, "year": { "type": "integer" @@ -10352,13 +10352,13 @@ "type": "object", "properties": { "cost": { - "type": "integer" + "type": "number" }, "description": { "type": "string" }, "discount": { - "type": "integer" + "type": "number" }, "isExternal": { "type": "boolean" @@ -10367,7 +10367,7 @@ "type": "number" }, "unitCost": { - "type": "integer" + "type": "number" } } }, diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 58256b225..b3e17826c 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1473,17 +1473,17 @@ definitions: request.InvoiceItem: properties: cost: - type: integer + type: number description: type: string discount: - type: integer + type: number isExternal: type: boolean quantity: type: number unitCost: - type: integer + type: number type: object request.ProjectHeadInput: properties: @@ -1504,7 +1504,7 @@ definitions: type: string discount: minimum: 0 - type: integer + type: number dueDate: type: string email: @@ -1534,13 +1534,13 @@ definitions: type: string subtotal: minimum: 0 - type: integer + type: number tax: minimum: 0 - type: integer + type: number total: minimum: 0 - type: integer + type: number required: - bankID - dueDate @@ -2950,13 +2950,13 @@ definitions: type: string type: array conversionAmount: - type: integer + type: number conversionRate: type: number description: type: string discount: - type: integer + type: number dueAt: type: string email: @@ -2990,13 +2990,13 @@ definitions: status: type: string subTotal: - type: integer + type: number tax: - type: integer + type: number threadID: type: string total: - type: integer + type: number year: type: integer type: object @@ -3015,13 +3015,13 @@ definitions: companyInfo: $ref: '#/definitions/view.CompanyInfo' conversionAmount: - type: integer + type: number conversionRate: type: number description: type: string discount: - type: integer + type: number dueAt: type: string email: @@ -3057,30 +3057,30 @@ definitions: status: type: string subTotal: - type: integer + type: number tax: - type: integer + type: number threadID: type: string total: - type: integer + type: number year: type: integer type: object view.InvoiceItem: properties: cost: - type: integer + type: number description: type: string discount: - type: integer + type: number isExternal: type: boolean quantity: type: number unitCost: - type: integer + type: number type: object view.InvoiceListResponse: properties: diff --git a/go.mod b/go.mod index 137b9e2ee..05f34d81f 100644 --- a/go.mod +++ b/go.mod @@ -23,6 +23,7 @@ require ( github.com/jackc/pgtype v1.12.0 github.com/jinzhu/now v1.1.5 github.com/joho/godotenv v1.4.0 + github.com/k0kubun/pp/v3 v3.2.0 github.com/lib/pq v1.10.6 github.com/matoous/go-nanoid v1.5.0 github.com/patrickmn/go-cache v2.1.0+incompatible diff --git a/go.sum b/go.sum index 63283c082..19125a8e2 100644 --- a/go.sum +++ b/go.sum @@ -387,6 +387,8 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/k0kubun/pp/v3 v3.2.0 h1:h33hNTZ9nVFNP3u2Fsgz8JXiF5JINoZfFq4SvKJwNcs= +github.com/k0kubun/pp/v3 v3.2.0/go.mod h1:ODtJQbQcIRfAD3N+theGCV1m/CBxweERz2dapdz1EwA= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= diff --git a/migrations/schemas/20230419095553-change_invoice_number_data_type.sql b/migrations/schemas/20230419095553-change_invoice_number_data_type.sql new file mode 100644 index 000000000..bab21104e --- /dev/null +++ b/migrations/schemas/20230419095553-change_invoice_number_data_type.sql @@ -0,0 +1,9 @@ +-- +migrate Up +ALTER TABLE invoices ALTER COLUMN discount TYPE DECIMAL USING discount::DECIMAL; +ALTER TABLE invoices ALTER COLUMN tax TYPE DECIMAL USING tax::DECIMAL; +ALTER TABLE invoices ALTER COLUMN sub_total TYPE DECIMAL USING sub_total::DECIMAL; + +-- +migrate Down +ALTER TABLE invoices ALTER COLUMN discount TYPE INTEGER USING discount::INTEGER; +ALTER TABLE invoices ALTER COLUMN tax TYPE INTEGER USING tax::INTEGER; +ALTER TABLE invoices ALTER COLUMN sub_total TYPE INTEGER USING sub_total::INTEGER; diff --git a/pkg/controller/invoice/commission.go b/pkg/controller/invoice/commission.go index e68829862..50c0e5a76 100644 --- a/pkg/controller/invoice/commission.go +++ b/pkg/controller/invoice/commission.go @@ -70,7 +70,7 @@ func (c *controller) calculateCommissionFromInvoice(db *gorm.DB, l logger.Logger if len(pics.devLeads) > 0 { commissionRate := commissionConfigMap[model.HeadPositionTechnicalLead.String()] if commissionRate.GreaterThan(decimal.NewFromInt(0)) { - c, err := c.calculateHeadCommission(commissionRate, pics.devLeads, invoice, float64(invoice.Total)) + c, err := c.calculateHeadCommission(commissionRate, pics.devLeads, invoice, invoice.Total) if err != nil { l.Errorf(err, "failed to calculate dev lead commission rate", "projectID", invoice.ProjectID.String()) return nil, err @@ -82,7 +82,7 @@ func (c *controller) calculateCommissionFromInvoice(db *gorm.DB, l logger.Logger if len(pics.accountManagers) > 0 { commissionRate := commissionConfigMap[model.HeadPositionAccountManager.String()] if commissionRate.GreaterThan(decimal.NewFromInt(0)) { - c, err := c.calculateHeadCommission(commissionRate, pics.accountManagers, invoice, float64(invoice.Total)) + c, err := c.calculateHeadCommission(commissionRate, pics.accountManagers, invoice, invoice.Total) if err != nil { l.Errorf(err, "failed to calculate account manager commission rate", "projectID", invoice.ProjectID.String()) return nil, err @@ -94,7 +94,7 @@ func (c *controller) calculateCommissionFromInvoice(db *gorm.DB, l logger.Logger if len(pics.deliveryManagers) > 0 { commissionRate := commissionConfigMap[model.HeadPositionDeliveryManager.String()] if commissionRate.GreaterThan(decimal.NewFromInt(0)) { - c, err := c.calculateHeadCommission(commissionRate, pics.deliveryManagers, invoice, float64(invoice.Total)) + c, err := c.calculateHeadCommission(commissionRate, pics.deliveryManagers, invoice, invoice.Total) if err != nil { l.Errorf(err, "failed to calculate delivery manager commission rate", "projectID", invoice.ProjectID.String()) return nil, err @@ -106,7 +106,7 @@ func (c *controller) calculateCommissionFromInvoice(db *gorm.DB, l logger.Logger if len(pics.sales) > 0 { commissionRate := commissionConfigMap[model.HeadPositionSalePerson.String()] if commissionRate.GreaterThan(decimal.NewFromInt(0)) { - c, err := c.calculateHeadCommission(commissionRate, pics.sales, invoice, float64(invoice.Total)) + c, err := c.calculateHeadCommission(commissionRate, pics.sales, invoice, invoice.Total) if err != nil { l.Errorf(err, "failed to calculate account manager commission rate", "projectID", invoice.ProjectID.String()) return nil, err @@ -151,28 +151,28 @@ func getPICs(invoice *model.Invoice, projectMembers []*model.ProjectMember) *pic devLeads = append(devLeads, pic{ ID: itm.EmployeeID, CommissionRate: itm.CommissionRate, - ChargeRate: float64(invoice.Total), + ChargeRate: invoice.Total, Note: "Lead", }) case model.HeadPositionAccountManager: accountManagers = append(accountManagers, pic{ ID: itm.EmployeeID, CommissionRate: itm.CommissionRate, - ChargeRate: float64(invoice.Total), + ChargeRate: invoice.Total, Note: "Account Manager", }) case model.HeadPositionDeliveryManager: deliveryManagers = append(deliveryManagers, pic{ ID: itm.EmployeeID, CommissionRate: itm.CommissionRate, - ChargeRate: float64(invoice.Total), + ChargeRate: invoice.Total, Note: "Delivery Manager", }) case model.HeadPositionSalePerson: sales = append(sales, pic{ ID: itm.EmployeeID, CommissionRate: itm.CommissionRate, - ChargeRate: float64(invoice.Total), + ChargeRate: invoice.Total, Note: "Sales", }) } diff --git a/pkg/controller/invoice/send.go b/pkg/controller/invoice/send.go index 265e4098d..0d05cab2b 100644 --- a/pkg/controller/invoice/send.go +++ b/pkg/controller/invoice/send.go @@ -9,7 +9,6 @@ import ( "os" "path/filepath" "strconv" - "strings" "text/template" "time" @@ -23,37 +22,6 @@ import ( "github.com/dwarvesf/fortress-api/pkg/utils/timeutil" ) -// InvoiceItem invoice item -type InvoiceItem struct { - Quantity float64 `json:"quantity"` - UnitCost int64 `json:"unitCost"` - Discount int64 `json:"discount"` - Cost int64 `json:"cost"` - Description string `json:"description"` - IsExternal bool `json:"isExternal"` -} - -type SendInvoiceInput struct { - IsDraft bool `json:"isDraft"` - ProjectID model.UUID `json:"projectID" binding:"required"` - BankID model.UUID `json:"bankID" binding:"required"` - Description string `json:"description"` - Note string `json:"note"` - CC []string `json:"cc"` - LineItems []InvoiceItem `json:"lineItems"` - Email string `json:"email" binding:"required,email"` - Total int `json:"total" binding:"gte=0"` - Discount int `json:"discount" binding:"gte=0"` - Tax int `json:"tax" binding:"gte=0"` - SubTotal int `json:"subtotal" binding:"gte=0"` - InvoiceDate string `json:"invoiceDate" binding:"required"` - DueDate string `json:"dueDate" binding:"required"` - Month int `json:"invoiceMonth" binding:"gte=0,lte=11"` - Year int `json:"invoiceYear" binding:"gte=0"` - SentByID *model.UUID - Number string -} - func (c *controller) Send(iv *model.Invoice) (*model.Invoice, error) { now := time.Now() @@ -113,13 +81,13 @@ func (c *controller) Send(iv *model.Invoice) (*model.Invoice, error) { return nil, err } - temp, rate, err := c.service.Wise.Convert(float64(iv.Total), iv.Bank.Currency.Name, "VND") + conversionAmount, rate, err := c.service.Wise.Convert(iv.Total, iv.Bank.Currency.Name, "VND") if err != nil { l.Error(err, "failed to convert currency") return nil, err } - am := model.NewVietnamDong(int64(temp)) - iv.ConversionAmount = int64(am) + am := model.NewVietnamDong(int64(conversionAmount)) + iv.ConversionAmount = float64(am) iv.ConversionRate = rate savedInvoice, err := c.store.Invoice.Save(c.repo.DB(), iv) @@ -187,7 +155,7 @@ func (c *controller) Send(iv *model.Invoice) (*model.Invoice, error) { } msg := fmt.Sprintf(`#Invoice %v has been sent - + Confirm Command: Paid @Giang #%v`, iv.Number, iv.Number) c.worker.Enqueue(bcModel.BasecampCommentMsg, c.service.Basecamp.BuildCommentMessage(bucketID, todoID, msg, "")) @@ -274,16 +242,11 @@ func (c *controller) generateInvoicePDF(l logger.Logger, invoice *model.Invoice) return timeutil. FormatDatetime(timeutil.LastDayOfMonth(invoice.Month, invoice.Year)) }, - "formatMoney": func(money int64) string { + "formatMoney": func(money float64) string { var result string - formatted := pound. - Multiply(money * int64(math.Pow(10, float64(pound.Currency().Fraction)))). - Display() - result = formatted - parts := strings.Split(formatted, ".00") - if len(parts) > 1 { - result = parts[0] - } + tmpValue := money * math.Pow(10, float64(pound.Currency().Fraction)) + result = pound.Multiply(int64(tmpValue)).Display() + return result }, "haveDescription": func(description string) bool { diff --git a/pkg/controller/invoice/update_status.go b/pkg/controller/invoice/update_status.go index c9bff1d9f..c0a64ffcc 100644 --- a/pkg/controller/invoice/update_status.go +++ b/pkg/controller/invoice/update_status.go @@ -238,7 +238,7 @@ func (c *controller) processPaidInvoiceData(l logger.Logger, wg *sync.WaitGroup, accountingTxn := &model.AccountingTransaction{ Name: req.Invoice.Number, - Amount: float64(req.Invoice.Total), + Amount: req.Invoice.Total, Date: &now, ConversionAmount: model.VietnamDong(req.Invoice.ConversionAmount), Organization: projectOrg, diff --git a/pkg/handler/invoice/request/request.go b/pkg/handler/invoice/request/request.go index faad1ae85..9908d4fb3 100644 --- a/pkg/handler/invoice/request/request.go +++ b/pkg/handler/invoice/request/request.go @@ -2,6 +2,7 @@ package request import ( "encoding/json" + "math" "strings" "time" @@ -70,10 +71,10 @@ type SendInvoiceRequest struct { CC []string `json:"cc"` LineItems []InvoiceItem `json:"lineItems"` Email string `json:"email" binding:"required,email"` - Total int `json:"total" binding:"gte=0"` - Discount int `json:"discount" binding:"gte=0"` - Tax int `json:"tax" binding:"gte=0"` - SubTotal int `json:"subtotal" binding:"gte=0"` + Total float64 `json:"total" binding:"gte=0"` + Discount float64 `json:"discount" binding:"gte=0"` + Tax float64 `json:"tax" binding:"gte=0"` + SubTotal float64 `json:"subtotal" binding:"gte=0"` InvoiceDate string `json:"invoiceDate" binding:"required"` DueDate string `json:"dueDate" binding:"required"` Month int `json:"invoiceMonth" binding:"gte=0,lte=11"` @@ -84,9 +85,9 @@ type SendInvoiceRequest struct { type InvoiceItem struct { Quantity float64 `json:"quantity"` - UnitCost int64 `json:"unitCost"` - Discount int64 `json:"discount"` - Cost int64 `json:"cost"` + UnitCost float64 `json:"unitCost"` + Discount float64 `json:"discount"` + Cost float64 `json:"cost"` Description string `json:"description"` IsExternal bool `json:"isExternal"` } @@ -95,10 +96,10 @@ func toInvoiceItemsModel(lineItems []InvoiceItem) []model.InvoiceItem { var items []model.InvoiceItem for _, item := range lineItems { items = append(items, model.InvoiceItem{ - Quantity: item.Quantity, - UnitCost: item.UnitCost, - Discount: item.Discount, - Cost: item.Cost, + Quantity: math.Round(item.Quantity*100) / 100, + UnitCost: math.Round(item.UnitCost*100) / 100, + Discount: math.Round(item.Discount*100) / 100, + Cost: math.Round(item.Cost*100) / 100, Description: item.Description, IsExternal: item.IsExternal, }) @@ -173,10 +174,10 @@ func (i *SendInvoiceRequest) ToInvoiceModel() (*model.Invoice, error) { LineItems: lineItems, Email: i.Email, CC: cc, - Total: int64(i.Total), - Discount: int64(i.Discount), - Tax: int64(i.Tax), - SubTotal: int64(i.SubTotal), + Total: math.Round(i.Total*100) / 100, + Discount: math.Round(i.Discount*100) / 100, + Tax: i.Tax, + SubTotal: math.Round(i.SubTotal*100) / 100, Month: i.Month + 1, Year: i.Year, Status: defaultStatus, diff --git a/pkg/model/invoice.go b/pkg/model/invoice.go index 0a5286828..ade40ae63 100644 --- a/pkg/model/invoice.go +++ b/pkg/model/invoice.go @@ -49,11 +49,11 @@ type Invoice struct { CC JSON Description string Note string - SubTotal int64 - Tax int64 - Discount int64 - Total int64 - ConversionAmount int64 + SubTotal float64 + Tax float64 + Discount float64 + Total float64 + ConversionAmount float64 InvoiceFileURL string ErrorInvoiceID *UUID LineItems JSON @@ -105,9 +105,9 @@ func GatherAddresses(CCs JSON) (string, error) { type InvoiceItem struct { Quantity float64 `json:"quantity"` - UnitCost int64 `json:"unit_cost"` - Discount int64 `json:"discount"` - Cost int64 `json:"cost"` + UnitCost float64 `json:"unit_cost"` + Discount float64 `json:"discount"` + Cost float64 `json:"cost"` Description string `json:"description"` IsExternal bool `json:"is_external"` } diff --git a/pkg/view/invoice.go b/pkg/view/invoice.go index 84c557e0e..0d8582c5d 100644 --- a/pkg/view/invoice.go +++ b/pkg/view/invoice.go @@ -22,11 +22,11 @@ type Invoice struct { CC []string `json:"cc"` Description string `json:"description"` Note string `json:"note"` - SubTotal int64 `json:"subTotal"` - Tax int64 `json:"tax"` - Discount int64 `json:"discount"` - Total int64 `json:"total"` - ConversionAmount int64 `json:"conversionAmount"` + SubTotal float64 `json:"subTotal"` + Tax float64 `json:"tax"` + Discount float64 `json:"discount"` + Total float64 `json:"total"` + ConversionAmount float64 `json:"conversionAmount"` InvoiceFileURL string `json:"invoiceFileURL"` ErrorInvoiceID string `json:"errorInvoiceID"` LineItems []InvoiceItem `json:"lineItems"` @@ -64,9 +64,9 @@ type CompanyInfo struct { type InvoiceItem struct { Quantity float64 `json:"quantity"` - UnitCost int64 `json:"unitCost"` - Discount int64 `json:"discount"` - Cost int64 `json:"cost"` + UnitCost float64 `json:"unitCost"` + Discount float64 `json:"discount"` + Cost float64 `json:"cost"` Description string `json:"description"` IsExternal bool `json:"isExternal"` }