Compare commits

..

2 Commits

Author SHA1 Message Date
Aditya Siregar
3696451dc6 add orders 2025-08-06 00:42:57 +07:00
Aditya Siregar
0a44135fb6 Update Order Response Ingredient 2025-08-06 00:23:03 +07:00
7 changed files with 330 additions and 89 deletions

View File

@ -20,7 +20,7 @@ type Database struct {
}
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 {

View File

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

View File

@ -1,14 +1,12 @@
package models
// Pagination represents pagination information
type Pagination struct {
Page int `json:"page"`
Limit int `json:"limit"`
Total int64 `json:"total"`
TotalPages int `json:"total_pages"`
Page int `json:"page"`
Limit int `json:"limit"`
Total int64 `json:"total_count"`
TotalPages int `json:"total_pages"`
}
// PaginatedResponse represents a paginated response
type PaginatedResponse[T any] struct {
Data []T `json:"data"`
Pagination Pagination `json:"pagination"`

View File

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

View File

@ -4,8 +4,8 @@ import (
"apskel-pos-be/internal/constants"
"apskel-pos-be/internal/contract"
"apskel-pos-be/internal/models"
"apskel-pos-be/internal/util"
"strconv"
"time"
"github.com/google/uuid"
)
@ -212,7 +212,6 @@ func ListOrdersQueryToModel(query *contract.ListOrdersQuery) *models.ListOrdersR
}
}
// Parse enum fields
if query.OrderType != "" {
orderType := constants.OrderType(query.OrderType)
req.OrderType = &orderType
@ -240,21 +239,9 @@ func ListOrdersQueryToModel(query *contract.ListOrdersQuery) *models.ListOrdersR
}
}
if query.DateFrom != "" {
if dateFrom, err := time.Parse(ddmmyyyy, query.DateFrom); err == nil {
req.DateFrom = &dateFrom
}
}
if query.DateTo != "" {
if dateTo, err := time.Parse(ddmmyyyy, query.DateTo); err == nil {
req.DateTo = &dateTo
}
}
if query.DateFrom == query.DateTo {
newDate := req.DateTo.AddDate(0, 0, 1)
req.DateTo = &newDate
if dateFrom, dateTo, err := util.ParseDateRangeToJakartaTime(query.DateFrom, query.DateTo); err == nil {
req.DateFrom = dateFrom
req.DateTo = dateTo
}
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"))
}
}
})
}
}