add orders

This commit is contained in:
Aditya Siregar 2025-08-06 00:42:57 +07:00
parent 0a44135fb6
commit 3696451dc6
6 changed files with 326 additions and 82 deletions

View File

@ -20,7 +20,7 @@ type Database struct {
} }
func (c Database) DSN() string { func (c Database) DSN() string {
return fmt.Sprintf("host=%s port=%s dbname=%s user=%s password=%s sslmode=%s TimeZone=UTC", c.Host, c.Port, c.DB, c.Username, c.Password, c.SslMode) return fmt.Sprintf("host=%s port=%s dbname=%s user=%s password=%s sslmode=%s TimeZone=Asia/Jakarta", c.Host, c.Port, c.DB, c.Username, c.Password, c.SslMode)
} }
func (c Database) ConnectionMaxLifetime() time.Duration { func (c Database) ConnectionMaxLifetime() time.Duration {

View File

@ -3,6 +3,7 @@ package db
import ( import (
"apskel-pos-be/config" "apskel-pos-be/config"
"fmt" "fmt"
_ "github.com/lib/pq" _ "github.com/lib/pq"
"go.uber.org/zap" "go.uber.org/zap"
_ "gopkg.in/yaml.v3" _ "gopkg.in/yaml.v3"

View File

@ -3,30 +3,22 @@ package transformer
import ( import (
"apskel-pos-be/internal/contract" "apskel-pos-be/internal/contract"
"apskel-pos-be/internal/models" "apskel-pos-be/internal/models"
"apskel-pos-be/internal/util"
"fmt" "fmt"
"time" "time"
) )
const ddmmyyyy = "02-01-2006"
// PaymentMethodAnalyticsContractToModel converts contract request to model // PaymentMethodAnalyticsContractToModel converts contract request to model
func PaymentMethodAnalyticsContractToModel(req *contract.PaymentMethodAnalyticsRequest) *models.PaymentMethodAnalyticsRequest { func PaymentMethodAnalyticsContractToModel(req *contract.PaymentMethodAnalyticsRequest) *models.PaymentMethodAnalyticsRequest {
var dateFrom, dateTo time.Time var dateFrom, dateTo time.Time
if req.DateFrom != "" {
df, err := time.Parse(ddmmyyyy, req.DateFrom)
if err == nil {
dateFrom = df
}
}
if req.DateTo != "" {
dt, err := time.Parse(ddmmyyyy, req.DateTo)
if err == nil {
dateTo = dt
}
}
if req.DateFrom == req.DateTo { if fromTime, toTime, err := util.ParseDateRangeToJakartaTime(req.DateFrom, req.DateTo); err == nil {
dateTo = dateTo.AddDate(0, 0, 1) if fromTime != nil {
dateFrom = *fromTime
}
if toTime != nil {
dateTo = *toTime
}
} }
return &models.PaymentMethodAnalyticsRequest{ return &models.PaymentMethodAnalyticsRequest{
@ -75,21 +67,14 @@ func PaymentMethodAnalyticsModelToContract(resp *models.PaymentMethodAnalyticsRe
func SalesAnalyticsContractToModel(req *contract.SalesAnalyticsRequest) *models.SalesAnalyticsRequest { func SalesAnalyticsContractToModel(req *contract.SalesAnalyticsRequest) *models.SalesAnalyticsRequest {
var dateFrom, dateTo time.Time var dateFrom, dateTo time.Time
if req.DateFrom != "" {
df, err := time.Parse(ddmmyyyy, req.DateFrom)
if err == nil {
dateFrom = df
}
}
if req.DateTo != "" {
dt, err := time.Parse(ddmmyyyy, req.DateTo)
if err == nil {
dateTo = dt
}
}
if req.DateFrom == req.DateTo { if fromTime, toTime, err := util.ParseDateRangeToJakartaTime(req.DateFrom, req.DateTo); err == nil {
dateTo = dateTo.AddDate(0, 0, 1) if fromTime != nil {
dateFrom = *fromTime
}
if toTime != nil {
dateTo = *toTime
}
} }
return &models.SalesAnalyticsRequest{ return &models.SalesAnalyticsRequest{
@ -142,21 +127,14 @@ func SalesAnalyticsModelToContract(resp *models.SalesAnalyticsResponse) *contrac
// ProductAnalyticsContractToModel converts contract request to model // ProductAnalyticsContractToModel converts contract request to model
func ProductAnalyticsContractToModel(req *contract.ProductAnalyticsRequest) *models.ProductAnalyticsRequest { func ProductAnalyticsContractToModel(req *contract.ProductAnalyticsRequest) *models.ProductAnalyticsRequest {
var dateFrom, dateTo time.Time var dateFrom, dateTo time.Time
if req.DateFrom != "" {
df, err := time.Parse(ddmmyyyy, req.DateFrom)
if err == nil {
dateFrom = df
}
}
if req.DateTo != "" {
dt, err := time.Parse(ddmmyyyy, req.DateTo)
if err == nil {
dateTo = dt
}
}
if req.DateFrom == req.DateTo { if fromTime, toTime, err := util.ParseDateRangeToJakartaTime(req.DateFrom, req.DateTo); err == nil {
dateTo = dateTo.AddDate(0, 0, 1) if fromTime != nil {
dateFrom = *fromTime
}
if toTime != nil {
dateTo = *toTime
}
} }
return &models.ProductAnalyticsRequest{ return &models.ProductAnalyticsRequest{
@ -200,21 +178,15 @@ func ProductAnalyticsModelToContract(resp *models.ProductAnalyticsResponse) *con
// DashboardAnalyticsContractToModel converts contract request to model // DashboardAnalyticsContractToModel converts contract request to model
func DashboardAnalyticsContractToModel(req *contract.DashboardAnalyticsRequest) *models.DashboardAnalyticsRequest { func DashboardAnalyticsContractToModel(req *contract.DashboardAnalyticsRequest) *models.DashboardAnalyticsRequest {
var dateFrom, dateTo time.Time var dateFrom, dateTo time.Time
if req.DateFrom != "" {
df, err := time.Parse(ddmmyyyy, req.DateFrom)
if err == nil {
dateFrom = df
}
}
if req.DateTo != "" {
dt, err := time.Parse(ddmmyyyy, req.DateTo)
if err == nil {
dateTo = dt
}
}
if req.DateFrom == req.DateTo { // Parse date range using utility function
dateTo = dateTo.AddDate(0, 0, 1) if fromTime, toTime, err := util.ParseDateRangeToJakartaTime(req.DateFrom, req.DateTo); err == nil {
if fromTime != nil {
dateFrom = *fromTime
}
if toTime != nil {
dateTo = *toTime
}
} }
return &models.DashboardAnalyticsRequest{ return &models.DashboardAnalyticsRequest{
@ -296,21 +268,21 @@ func ProfitLossAnalyticsContractToModel(req *contract.ProfitLossAnalyticsRequest
return nil, fmt.Errorf("request cannot be nil") return nil, fmt.Errorf("request cannot be nil")
} }
dateFrom, err := time.Parse("02-01-2006", req.DateFrom) // Parse date range using utility function
dateFrom, dateTo, err := util.ParseDateRangeToJakartaTime(req.DateFrom, req.DateTo)
if err != nil { if err != nil {
return nil, fmt.Errorf("invalid date_from format: %w", err) return nil, fmt.Errorf("invalid date format: %w", err)
} }
dateTo, err := time.Parse("02-01-2006", req.DateTo) if dateFrom == nil || dateTo == nil {
if err != nil { return nil, fmt.Errorf("both date_from and date_to are required")
return nil, fmt.Errorf("invalid date_to format: %w", err)
} }
return &models.ProfitLossAnalyticsRequest{ return &models.ProfitLossAnalyticsRequest{
OrganizationID: req.OrganizationID, OrganizationID: req.OrganizationID,
OutletID: req.OutletID, OutletID: req.OutletID,
DateFrom: dateFrom, DateFrom: *dateFrom,
DateTo: dateTo, DateTo: *dateTo,
GroupBy: req.GroupBy, GroupBy: req.GroupBy,
}, nil }, nil
} }

View File

@ -4,8 +4,8 @@ import (
"apskel-pos-be/internal/constants" "apskel-pos-be/internal/constants"
"apskel-pos-be/internal/contract" "apskel-pos-be/internal/contract"
"apskel-pos-be/internal/models" "apskel-pos-be/internal/models"
"apskel-pos-be/internal/util"
"strconv" "strconv"
"time"
"github.com/google/uuid" "github.com/google/uuid"
) )
@ -239,21 +239,9 @@ func ListOrdersQueryToModel(query *contract.ListOrdersQuery) *models.ListOrdersR
} }
} }
if query.DateFrom != "" { if dateFrom, dateTo, err := util.ParseDateRangeToJakartaTime(query.DateFrom, query.DateTo); err == nil {
if dateFrom, err := time.Parse(ddmmyyyy, query.DateFrom); err == nil { req.DateFrom = dateFrom
req.DateFrom = &dateFrom req.DateTo = dateTo
}
}
if query.DateTo != "" {
if dateTo, err := time.Parse(ddmmyyyy, query.DateTo); err == nil {
req.DateTo = &dateTo
}
}
if query.DateFrom != "" && query.DateFrom == query.DateTo {
newDate := req.DateTo.AddDate(0, 0, 1)
req.DateTo = &newDate
} }
return req return req

View File

@ -0,0 +1,72 @@
package util
import (
"time"
)
const DateFormatDDMMYYYY = "02-01-2006"
// ParseDateToJakartaTime parses a date string in DD-MM-YYYY format and converts it to Jakarta timezone
// Returns start of day (00:00:00) in Jakarta timezone
func ParseDateToJakartaTime(dateStr string) (*time.Time, error) {
if dateStr == "" {
return nil, nil
}
date, err := time.Parse(DateFormatDDMMYYYY, dateStr)
if err != nil {
return nil, err
}
jakartaLoc, err := time.LoadLocation("Asia/Jakarta")
if err != nil {
return nil, err
}
jakartaTime := time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, jakartaLoc)
return &jakartaTime, nil
}
// ParseDateToJakartaTimeEndOfDay parses a date string in DD-MM-YYYY format and converts it to Jakarta timezone
// Returns end of day (23:59:59.999999999) in Jakarta timezone
func ParseDateToJakartaTimeEndOfDay(dateStr string) (*time.Time, error) {
if dateStr == "" {
return nil, nil
}
date, err := time.Parse(DateFormatDDMMYYYY, dateStr)
if err != nil {
return nil, err
}
jakartaLoc, err := time.LoadLocation("Asia/Jakarta")
if err != nil {
return nil, err
}
jakartaTime := time.Date(date.Year(), date.Month(), date.Day(), 23, 59, 59, 999999999, jakartaLoc)
return &jakartaTime, nil
}
// ParseDateRangeToJakartaTime parses date_from and date_to strings and returns them in Jakarta timezone
// date_from will be start of day (00:00:00), date_to will be end of day (23:59:59.999999999)
func ParseDateRangeToJakartaTime(dateFrom, dateTo string) (*time.Time, *time.Time, error) {
var fromTime, toTime *time.Time
var err error
if dateFrom != "" {
fromTime, err = ParseDateToJakartaTime(dateFrom)
if err != nil {
return nil, nil, err
}
}
if dateTo != "" {
toTime, err = ParseDateToJakartaTimeEndOfDay(dateTo)
if err != nil {
return nil, nil, err
}
}
return fromTime, toTime, nil
}

View File

@ -0,0 +1,211 @@
package util
import (
"testing"
"time"
)
func TestParseDateToJakartaTime(t *testing.T) {
tests := []struct {
name string
dateStr string
expected *time.Time
hasError bool
}{
{
name: "valid date",
dateStr: "06-08-2025",
expected: nil, // Will be set during test
hasError: false,
},
{
name: "empty string",
dateStr: "",
expected: nil,
hasError: false,
},
{
name: "invalid date format",
dateStr: "2025-08-06",
hasError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := ParseDateToJakartaTime(tt.dateStr)
if tt.hasError {
if err == nil {
t.Errorf("Expected error but got none")
}
return
}
if err != nil {
t.Errorf("Unexpected error: %v", err)
return
}
if tt.expected == nil && tt.dateStr == "" {
if result != nil {
t.Errorf("Expected nil but got %v", result)
}
return
}
if result == nil && tt.dateStr != "" {
t.Errorf("Expected time but got nil")
return
}
// Check if it's in Jakarta timezone
jakartaLoc, _ := time.LoadLocation("Asia/Jakarta")
if result.Location().String() != jakartaLoc.String() {
t.Errorf("Expected Jakarta timezone but got %v", result.Location())
}
// Check if it's start of day
if result.Hour() != 0 || result.Minute() != 0 || result.Second() != 0 {
t.Errorf("Expected start of day but got %v", result.Format("15:04:05"))
}
})
}
}
func TestParseDateToJakartaTimeEndOfDay(t *testing.T) {
tests := []struct {
name string
dateStr string
expected *time.Time
hasError bool
}{
{
name: "valid date",
dateStr: "06-08-2025",
expected: nil, // Will be set during test
hasError: false,
},
{
name: "empty string",
dateStr: "",
expected: nil,
hasError: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := ParseDateToJakartaTimeEndOfDay(tt.dateStr)
if tt.hasError {
if err == nil {
t.Errorf("Expected error but got none")
}
return
}
if err != nil {
t.Errorf("Unexpected error: %v", err)
return
}
if tt.expected == nil && tt.dateStr == "" {
if result != nil {
t.Errorf("Expected nil but got %v", result)
}
return
}
if result == nil && tt.dateStr != "" {
t.Errorf("Expected time but got nil")
return
}
// Check if it's in Jakarta timezone
jakartaLoc, _ := time.LoadLocation("Asia/Jakarta")
if result.Location().String() != jakartaLoc.String() {
t.Errorf("Expected Jakarta timezone but got %v", result.Location())
}
// Check if it's end of day
if result.Hour() != 23 || result.Minute() != 59 || result.Second() != 59 {
t.Errorf("Expected end of day but got %v", result.Format("15:04:05"))
}
})
}
}
func TestParseDateRangeToJakartaTime(t *testing.T) {
tests := []struct {
name string
dateFrom string
dateTo string
hasError bool
}{
{
name: "valid date range",
dateFrom: "06-08-2025",
dateTo: "06-08-2025",
hasError: false,
},
{
name: "empty strings",
dateFrom: "",
dateTo: "",
hasError: false,
},
{
name: "only date_from",
dateFrom: "06-08-2025",
dateTo: "",
hasError: false,
},
{
name: "only date_to",
dateFrom: "",
dateTo: "06-08-2025",
hasError: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
fromTime, toTime, err := ParseDateRangeToJakartaTime(tt.dateFrom, tt.dateTo)
if tt.hasError {
if err == nil {
t.Errorf("Expected error but got none")
}
return
}
if err != nil {
t.Errorf("Unexpected error: %v", err)
return
}
// If dateFrom is provided, check it's start of day
if tt.dateFrom != "" && fromTime != nil {
jakartaLoc, _ := time.LoadLocation("Asia/Jakarta")
if fromTime.Location().String() != jakartaLoc.String() {
t.Errorf("Expected Jakarta timezone for date_from but got %v", fromTime.Location())
}
if fromTime.Hour() != 0 || fromTime.Minute() != 0 || fromTime.Second() != 0 {
t.Errorf("Expected start of day for date_from but got %v", fromTime.Format("15:04:05"))
}
}
// If dateTo is provided, check it's end of day
if tt.dateTo != "" && toTime != nil {
jakartaLoc, _ := time.LoadLocation("Asia/Jakarta")
if toTime.Location().String() != jakartaLoc.String() {
t.Errorf("Expected Jakarta timezone for date_to but got %v", toTime.Location())
}
if toTime.Hour() != 23 || toTime.Minute() != 59 || toTime.Second() != 59 {
t.Errorf("Expected end of day for date_to but got %v", toTime.Format("15:04:05"))
}
}
})
}
}