import province
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
teguh nugroho 2026-08-17 18:39:41 +07:00
parent 1783bb2e7d
commit fc0d140c5c
9 changed files with 1130 additions and 25 deletions

View File

@ -5,10 +5,34 @@ type CreateProvinceRequest struct {
} }
type CreateProvinceResponse struct { type CreateProvinceResponse struct {
ID string `json:"id"` ID string `json:"id"`
Name string `json:"name"` Name *string `json:"name"`
MerchantID *string `json:"merchant_id"` MerchantID *string `json:"merchant_id"`
} }
type ProvinceFilterAllRequest struct {
ID string `json:"id" form:"id"`
Name string `json:"name" form:"name"`
MerchantID string `json:"merchant_id" form:"merchant_id"`
}
type UpdateProvinceRequest struct { type UpdateProvinceRequest struct {
Name string `json:"name" binding:"required,min=2"` Name string `json:"name" binding:"required,min=2"`
} }
type ImportLog struct {
Row int `json:"row"`
Status string `json:"status"`
Data string `json:"data"`
Message string `json:"message"`
}
type ImportProvinceResponse struct {
JobID string `json:"job_id,omitempty"`
TotalRows int `json:"total_rows"`
Imported int `json:"imported"`
Skipped int `json:"skipped"`
Logs []ImportLog `json:"logs"`
Errors []string `json:"errors"`
IsAsync bool `json:"is_async"`
Message string `json:"message,omitempty"`
}

View File

@ -2,11 +2,12 @@ package province
import ( import (
"cargo-erp-backend/internal/handlers/dto" "cargo-erp-backend/internal/handlers/dto"
provinceimporter "cargo-erp-backend/internal/importers/province"
"cargo-erp-backend/internal/usecases" "cargo-erp-backend/internal/usecases"
"cargo-erp-backend/pkg/helpers" "cargo-erp-backend/pkg/helpers"
"cargo-erp-backend/pkg/importer"
"cargo-erp-backend/pkg/response" "cargo-erp-backend/pkg/response"
"net/http" "net/http"
"strconv"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@ -33,6 +34,9 @@ func (h *ProvinceHandler) Router() {
r.POST("/", h.Create) r.POST("/", h.Create)
r.PUT("/:id", h.Update) r.PUT("/:id", h.Update)
r.DELETE("/:id", h.Delete) r.DELETE("/:id", h.Delete)
r.GET("/template", h.Template)
r.POST("/import", h.Import)
r.GET("/import/status/:jobId", h.ImportStatus)
} }
} }
@ -42,24 +46,24 @@ func (h *ProvinceHandler) GetAll(c *gin.Context) {
response.Error(c, http.StatusUnauthorized, err.Error()) response.Error(c, http.StatusUnauthorized, err.Error())
return return
} }
page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) var req dto.ProvinceFilterAllRequest
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10")) if err := c.ShouldBindQuery(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
if page < 1 { return
page = 1
} }
if limit < 1 {
limit = 10
}
u := usecases.NewProvinceUsecase(h.Helper, *userid) u := usecases.NewProvinceUsecase(h.Helper, *userid)
provinces, total, err := u.GetAll(page, limit) provinces, _, err := u.GetAll(req)
if err != nil { if err != nil {
response.Error(c, http.StatusInternalServerError, err.Error()) response.Error(c, http.StatusInternalServerError, err.Error())
return return
} }
var responseDTO = make([]dto.CreateProvinceResponse, 0)
response.PaginatedResponse(c, provinces, int(total), page, limit) for _, v := range provinces {
var DTO dto.CreateProvinceResponse
h.Helper.MapStructToStruct(v, "json", &DTO, "json")
responseDTO = append(responseDTO, DTO)
}
response.Success(c, responseDTO)
} }
func (h *ProvinceHandler) GetList(c *gin.Context) { func (h *ProvinceHandler) GetList(c *gin.Context) {
@ -70,8 +74,6 @@ func (h *ProvinceHandler) GetList(c *gin.Context) {
} }
var req dto.DataTableRequest var req dto.DataTableRequest
// 1. Bind JSON request otomatis ke struct
if err := c.ShouldBindJSON(&req); err != nil { if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, http.StatusBadRequest, "Request Format request invalid") response.Error(c, http.StatusBadRequest, "Request Format request invalid")
return return
@ -153,8 +155,9 @@ func (h *ProvinceHandler) Update(c *gin.Context) {
response.Error(c, http.StatusInternalServerError, err.Error()) response.Error(c, http.StatusInternalServerError, err.Error())
return return
} }
var responseDTO dto.CreateProvinceResponse
response.Success(c, province) h.Helper.MapStructToStruct(province, "json", &responseDTO, "json")
response.Success(c, responseDTO)
} }
func (h *ProvinceHandler) Delete(c *gin.Context) { func (h *ProvinceHandler) Delete(c *gin.Context) {
@ -172,3 +175,82 @@ func (h *ProvinceHandler) Delete(c *gin.Context) {
response.Success(c, map[string]interface{}{"message": "province deleted"}) response.Success(c, map[string]interface{}{"message": "province deleted"})
} }
func (h *ProvinceHandler) Template(c *gin.Context) {
userid, err := h.Helper.GetAuthInfo(c.GetString("auth"))
if err != nil {
response.Error(c, http.StatusUnauthorized, err.Error())
return
}
handler := provinceimporter.NewProvinceImportHandler(*userid)
imp := importer.NewImporter(h.Helper, handler)
imp.ServeTemplate(c.Writer)
}
func (h *ProvinceHandler) Import(c *gin.Context) {
userid, err := h.Helper.GetAuthInfo(c.GetString("auth"))
if err != nil {
response.Error(c, http.StatusUnauthorized, err.Error())
return
}
file, header, err := c.Request.FormFile("file")
if err != nil {
response.Error(c, http.StatusBadRequest, "File is required")
return
}
defer file.Close()
handler := provinceimporter.NewProvinceImportHandler(*userid)
imp := importer.NewImporter(h.Helper, handler)
result, err := imp.Process(file, userid.ID, header.Filename)
if err != nil {
response.Error(c, http.StatusBadRequest, err.Error())
return
}
var importLogs []dto.ImportLog
for _, l := range result.Logs {
importLogs = append(importLogs, dto.ImportLog{
Row: l.Row,
Status: l.Status,
Data: l.Data,
Message: l.Message,
})
}
response.Success(c, dto.ImportProvinceResponse{
JobID: result.JobID,
TotalRows: result.TotalRows,
Imported: result.Imported,
Skipped: result.Skipped,
Logs: importLogs,
Errors: result.Errors,
IsAsync: result.IsAsync,
Message: result.Message,
})
}
func (h *ProvinceHandler) ImportStatus(c *gin.Context) {
userid, err := h.Helper.GetAuthInfo(c.GetString("auth"))
if err != nil {
response.Error(c, http.StatusUnauthorized, err.Error())
return
}
jobId := c.Param("jobId")
handler := provinceimporter.NewProvinceImportHandler(*userid)
imp := importer.NewImporter(h.Helper, handler)
job, logs, err := imp.GetJobStatus(jobId)
if err != nil {
response.Error(c, http.StatusNotFound, "Job not found")
return
}
response.Success(c, map[string]interface{}{
"job": job,
"logs": logs,
})
}

View File

@ -0,0 +1,84 @@
package province
import (
"cargo-erp-backend/internal/domain"
"cargo-erp-backend/pkg/importer"
"fmt"
"strings"
"time"
"gorm.io/gorm"
)
type ProvinceImportHandler struct {
UserData domain.User
}
func NewProvinceImportHandler(user_data domain.User) importer.ImportHandler {
return &ProvinceImportHandler{
UserData: user_data,
}
}
func (h *ProvinceImportHandler) ValidateRow(row []string, rowNumber int) (importer.RowData, []error) {
var errors []error
if len(row) < 1 {
errors = append(errors, fmt.Errorf("row is empty"))
return nil, errors
}
name := strings.TrimSpace(row[0])
if name == "" {
errors = append(errors, fmt.Errorf("name is required"))
return nil, errors
}
if len(name) < 2 {
errors = append(errors, fmt.Errorf("name must be at least 2 characters"))
return nil, errors
}
if len(name) > 255 {
errors = append(errors, fmt.Errorf("name must be at most 255 characters"))
return nil, errors
}
now := time.Now()
province := &domain.Province{
MerchantID: h.UserData.MerchantID,
Name: &name,
CreatedOn: &now,
UpdatedOn: &now,
}
return province, nil
}
func (h *ProvinceImportHandler) GetDataString(data importer.RowData) string {
province := data.(*domain.Province)
if province.Name != nil {
return *province.Name
}
return ""
}
func (h *ProvinceImportHandler) IsDuplicate(db *gorm.DB, data importer.RowData) bool {
province := data.(*domain.Province)
var count int64
db.Model(&domain.Province{}).Where("UPPER(name) = ?", strings.ToUpper(*province.Name)).Count(&count)
return count > 0
}
func (h *ProvinceImportHandler) GetTemplateHeaders() []string {
return []string{"name"}
}
func (h *ProvinceImportHandler) GetTemplateRows() [][]string {
return [][]string{
{"DKI Jakarta"},
{"Jawa Barat"},
{"Jawa Tengah"},
{"Jawa Timur"},
}
}

View File

@ -5,11 +5,13 @@ import (
"cargo-erp-backend/internal/handlers/dto" "cargo-erp-backend/internal/handlers/dto"
"cargo-erp-backend/pkg/datatable" "cargo-erp-backend/pkg/datatable"
"cargo-erp-backend/pkg/helpers" "cargo-erp-backend/pkg/helpers"
"fmt"
"reflect"
"time" "time"
) )
type ProvinceUsecaseInterface interface { type ProvinceUsecaseInterface interface {
GetAll(page int, limit int) ([]domain.Province, int64, error) GetAll(filter dto.ProvinceFilterAllRequest) ([]domain.Province, int64, error)
GetList(dto.DataTableRequest) (dto.DataTableResponse, error) GetList(dto.DataTableRequest) (dto.DataTableResponse, error)
GetByID(id string) (domain.Province, error) GetByID(id string) (domain.Province, error)
Create(name string, createdBy string, merchantid *string) (domain.Province, error) Create(name string, createdBy string, merchantid *string) (domain.Province, error)
@ -26,21 +28,29 @@ func NewProvinceUsecase(helper helpers.HelperInterface, authUser domain.User) Pr
return &ProvinceUsecase{Helper: helper, AuthUser: authUser} return &ProvinceUsecase{Helper: helper, AuthUser: authUser}
} }
func (u *ProvinceUsecase) GetAll(page int, limit int) ([]domain.Province, int64, error) { func (u *ProvinceUsecase) GetAll(filter dto.ProvinceFilterAllRequest) ([]domain.Province, int64, error) {
var provinces []domain.Province var provinces []domain.Province
var total int64 var total int64
db := u.Helper.GetDB("slave") db := u.Helper.GetDB("slave")
tx := db.Model(&provinces)
if u.AuthUser.UsersRole_Relation.RoleKey != "SPM" { if u.AuthUser.UsersRole_Relation.RoleKey != "SPM" {
db.Where(db.Where("merchant_id IS NULL").Or("merchant_id=?", u.AuthUser.MerchantID)) tx.Where(db.Where("merchant_id IS NULL").Or("merchant_id=?", u.AuthUser.MerchantID))
} }
db.Model(&domain.Province{}).Count(&total) v := reflect.ValueOf(filter)
t := reflect.TypeOf(filter)
offset := (page - 1) * limit for i := 0; i < v.NumField(); i++ {
if err := db.Offset(offset).Limit(limit).Find(&provinces).Error; err != nil { if v.Field(i).String() != "" {
tagKey := t.Field(i).Tag.Get("form")
tx.Where(fmt.Sprintf("%v=?", tagKey), v.Field(i))
}
}
tx.Model(&domain.Province{}).Count(&total)
tx.Order("name ASC")
if err := tx.Find(&provinces).Error; err != nil {
return nil, 0, err return nil, 0, err
} }
return provinces, total, nil return provinces, total, nil
} }

90
pkg/csv/csv.go Normal file
View File

@ -0,0 +1,90 @@
package csv
import (
"encoding/csv"
"io"
"strings"
)
type RowHandler func(row []string, rowNumber int) error
func ReadCSV(file io.Reader, handler RowHandler) (int, []string, error) {
reader := csv.NewReader(file)
reader.LazyQuotes = true
reader.TrimLeadingSpace = true
var errors []string
rowNumber := 0
totalRows := 0
for {
row, err := reader.Read()
if err == io.EOF {
break
}
rowNumber++
if err != nil {
errors = append(errors, "Row "+itoa(rowNumber)+": "+err.Error())
continue
}
// Skip empty rows
if len(row) == 0 || (len(row) == 1 && strings.TrimSpace(row[0]) == "") {
continue
}
// Skip header row (first row)
if rowNumber == 1 {
continue
}
if err := handler(row, rowNumber); err != nil {
errors = append(errors, "Row "+itoa(rowNumber)+": "+err.Error())
}
totalRows++
}
return totalRows, errors, nil
}
func CountRows(file io.Reader) (int, error) {
reader := csv.NewReader(file)
count := 0
for {
_, err := reader.Read()
if err == io.EOF {
break
}
if err != nil {
continue
}
count++
}
return count, nil
}
func WriteCSV(headers []string, rows [][]string) string {
var sb strings.Builder
writer := csv.NewWriter(&sb)
writer.Write(headers)
for _, row := range rows {
writer.Write(row)
}
writer.Flush()
return sb.String()
}
func itoa(i int) string {
if i == 0 {
return "0"
}
result := ""
for i > 0 {
result = string(rune('0'+i%10)) + result
i /= 10
}
return result
}

389
pkg/importer/importer.go Normal file
View File

@ -0,0 +1,389 @@
package importer
import (
csvlib "cargo-erp-backend/pkg/csv"
"cargo-erp-backend/pkg/helpers"
"cargo-erp-backend/pkg/jobqueue"
"context"
"encoding/csv"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
type RowData interface {
TableName() string
}
type ImportHandler interface {
ValidateRow(row []string, rowNumber int) (RowData, []error)
IsDuplicate(db *gorm.DB, data RowData) bool
GetDataString(data RowData) string
GetTemplateHeaders() []string
GetTemplateRows() [][]string
}
type ImportLog struct {
Row int `json:"row"`
Status string `json:"status"`
Data string `json:"data"`
Message string `json:"message"`
}
type ImportResult struct {
JobID string `json:"job_id,omitempty"`
TotalRows int `json:"total_rows"`
Imported int `json:"imported"`
Skipped int `json:"skipped"`
Logs []ImportLog `json:"logs"`
Errors []string `json:"errors"`
IsAsync bool `json:"is_async"`
Message string `json:"message,omitempty"`
}
type Importer struct {
Helper helpers.HelperInterface
Handler ImportHandler
JobQ *jobqueue.JobQueue
}
func NewImporter(helper helpers.HelperInterface, handler ImportHandler) *Importer {
return &Importer{
Helper: helper,
Handler: handler,
JobQ: jobqueue.NewJobQueue(helper.GetRedis("master")),
}
}
func (i *Importer) Process(file io.Reader, userID string, filename string) (*ImportResult, error) {
if !strings.HasSuffix(strings.ToLower(filename), ".csv") {
return nil, fmt.Errorf("file must be .csv format")
}
headerErr := i.validateHeaders(file)
if headerErr != nil {
return nil, headerErr
}
rowCount, err := csvlib.CountRows(file)
if err != nil {
return nil, fmt.Errorf("failed to count rows: %w", err)
}
if rowCount <= 1 {
return &ImportResult{
TotalRows: 0,
Imported: 0,
Skipped: 0,
Logs: []ImportLog{},
Errors: []string{"CSV file is empty or has only headers"},
}, nil
}
totalDataRows := rowCount - 1
if totalDataRows >= 10000 {
return i.processAsync(file, userID, totalDataRows)
}
return i.processSync(file, userID, totalDataRows)
}
func (i *Importer) validateHeaders(file io.Reader) error {
reader := csv.NewReader(file)
header, err := reader.Read()
if err != nil {
return fmt.Errorf("failed to read CSV headers: %w", err)
}
expectedHeaders := i.Handler.GetTemplateHeaders()
if len(header) != len(expectedHeaders) {
return fmt.Errorf("invalid CSV headers. Expected: %s. Got: %s",
strings.Join(expectedHeaders, ", "),
strings.Join(header, ", "))
}
for idx, h := range header {
if strings.TrimSpace(strings.ToLower(h)) != strings.TrimSpace(strings.ToLower(expectedHeaders[idx])) {
return fmt.Errorf("invalid CSV headers. Expected: %s. Got: %s",
strings.Join(expectedHeaders, ", "),
strings.Join(header, ", "))
}
}
return nil
}
func (i *Importer) processSync(file io.Reader, userID string, totalRows int) (*ImportResult, error) {
db := i.Helper.GetDB("master")
var imported, skipped int
var logs []ImportLog
var errors []string
var batch []RowData
batchSize := 500
csvlib.ReadCSV(file, func(row []string, rowNumber int) error {
data, rowErrors := i.Handler.ValidateRow(row, rowNumber)
if len(rowErrors) > 0 {
for _, e := range rowErrors {
errorMsg := fmt.Sprintf("Row %d: %s", rowNumber, e.Error())
errors = append(errors, errorMsg)
logs = append(logs, ImportLog{
Row: rowNumber,
Status: "error",
Data: strings.Join(row, ","),
Message: errorMsg,
})
}
skipped++
return nil
}
if i.Handler.IsDuplicate(db, data) {
dataStr := i.Handler.GetDataString(data)
logs = append(logs, ImportLog{
Row: rowNumber,
Status: "skipped",
Data: dataStr,
Message: fmt.Sprintf("Row %d: '%s' already exists, skipped", rowNumber, dataStr),
})
skipped++
return nil
}
batch = append(batch, data)
if len(batch) >= batchSize {
if err := i.insertBatch(db, batch); err != nil {
errorMsg := fmt.Sprintf("Batch insert error: %v", err)
errors = append(errors, errorMsg)
logs = append(logs, ImportLog{
Row: rowNumber,
Status: "error",
Data: strings.Join(row, ","),
Message: errorMsg,
})
} else {
for _, b := range batch {
dataStr := i.Handler.GetDataString(b)
logs = append(logs, ImportLog{
Row: rowNumber,
Status: "inserted",
Data: dataStr,
Message: fmt.Sprintf("Row %d: '%s' inserted successfully", rowNumber, dataStr),
})
}
imported += len(batch)
}
batch = nil
}
return nil
})
if len(batch) > 0 {
if err := i.insertBatch(db, batch); err != nil {
errorMsg := fmt.Sprintf("Batch insert error: %v", err)
errors = append(errors, errorMsg)
logs = append(logs, ImportLog{
Row: totalRows + 1,
Status: "error",
Data: "",
Message: errorMsg,
})
} else {
for _, b := range batch {
dataStr := i.Handler.GetDataString(b)
logs = append(logs, ImportLog{
Row: imported + 1,
Status: "inserted",
Data: dataStr,
Message: fmt.Sprintf("'%s' inserted successfully", dataStr),
})
}
imported += len(batch)
}
}
return &ImportResult{
TotalRows: totalRows,
Imported: imported,
Skipped: skipped,
Logs: logs,
Errors: errors,
IsAsync: false,
}, nil
}
func (i *Importer) processAsync(file io.Reader, userID string, totalRows int) (*ImportResult, error) {
jobID := uuid.New().String()
ctx := context.Background()
metadata := map[string]string{
"user_id": userID,
"table": i.Handler.GetTemplateHeaders()[0],
}
if err := i.JobQ.CreateJob(ctx, jobID, totalRows, metadata); err != nil {
return nil, fmt.Errorf("failed to create job: %w", err)
}
go i.processAsyncWorker(file, jobID, userID, totalRows)
return &ImportResult{
JobID: jobID,
TotalRows: totalRows,
IsAsync: true,
Message: fmt.Sprintf("Import started. Poll /import/status/%s for progress.", jobID),
}, nil
}
func (i *Importer) processAsyncWorker(file io.Reader, jobID string, userID string, totalRows int) {
db := i.Helper.GetDB("master")
ctx := context.Background()
var imported, skipped int
var logs []ImportLog
var errors []string
var batch []RowData
batchSize := 500
i.JobQ.UpdateProgress(ctx, jobID, 0, 0, nil)
csvlib.ReadCSV(file, func(row []string, rowNumber int) error {
data, rowErrors := i.Handler.ValidateRow(row, rowNumber)
if len(rowErrors) > 0 {
for _, e := range rowErrors {
errorMsg := fmt.Sprintf("Row %d: %s", rowNumber, e.Error())
errors = append(errors, errorMsg)
logs = append(logs, ImportLog{
Row: rowNumber,
Status: "error",
Data: strings.Join(row, ","),
Message: errorMsg,
})
}
skipped++
return nil
}
if i.Handler.IsDuplicate(db, data) {
dataStr := i.Handler.GetDataString(data)
logs = append(logs, ImportLog{
Row: rowNumber,
Status: "skipped",
Data: dataStr,
Message: fmt.Sprintf("Row %d: '%s' already exists, skipped", rowNumber, dataStr),
})
skipped++
return nil
}
batch = append(batch, data)
if len(batch) >= batchSize {
if err := i.insertBatch(db, batch); err != nil {
errorMsg := fmt.Sprintf("Batch insert error: %v", err)
errors = append(errors, errorMsg)
logs = append(logs, ImportLog{
Row: rowNumber,
Status: "error",
Data: strings.Join(row, ","),
Message: errorMsg,
})
} else {
for _, b := range batch {
dataStr := i.Handler.GetDataString(b)
logs = append(logs, ImportLog{
Row: rowNumber,
Status: "inserted",
Data: dataStr,
Message: fmt.Sprintf("Row %d: '%s' inserted successfully", rowNumber, dataStr),
})
}
imported += len(batch)
}
batch = nil
i.saveJobLogs(ctx, jobID, logs, imported, skipped, errors)
}
return nil
})
if len(batch) > 0 {
if err := i.insertBatch(db, batch); err != nil {
errorMsg := fmt.Sprintf("Batch insert error: %v", err)
errors = append(errors, errorMsg)
logs = append(logs, ImportLog{
Row: totalRows + 1,
Status: "error",
Data: "",
Message: errorMsg,
})
} else {
for _, b := range batch {
dataStr := i.Handler.GetDataString(b)
logs = append(logs, ImportLog{
Row: imported + 1,
Status: "inserted",
Data: dataStr,
Message: fmt.Sprintf("'%s' inserted successfully", dataStr),
})
}
imported += len(batch)
}
}
i.JobQ.CompleteJob(ctx, jobID, imported, skipped, errors)
i.saveJobLogs(ctx, jobID, logs, imported, skipped, errors)
}
func (i *Importer) saveJobLogs(ctx context.Context, jobID string, logs []ImportLog, imported int, skipped int, errors []string) {
logsJSON, _ := json.Marshal(logs)
key := fmt.Sprintf("import_job:%s:logs", jobID)
i.Helper.GetRedis("master").Set(ctx, key, logsJSON, 24*time.Hour)
}
func (i *Importer) insertBatch(db *gorm.DB, batch []RowData) error {
if len(batch) == 0 {
return nil
}
return db.CreateInBatches(batch, 500).Error
}
func (i *Importer) GetTemplate() string {
headers := i.Handler.GetTemplateHeaders()
rows := i.Handler.GetTemplateRows()
return csvlib.WriteCSV(headers, rows)
}
func (i *Importer) ServeTemplate(w http.ResponseWriter) {
template := i.GetTemplate()
w.Header().Set("Content-Type", "text/csv")
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s_template.csv", time.Now().Format("20060102")))
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(template)))
w.Write([]byte(template))
}
func (i *Importer) GetJobStatus(jobID string) (*jobqueue.Job, []ImportLog, error) {
ctx := context.Background()
job, err := i.JobQ.GetJob(ctx, jobID)
if err != nil {
return nil, nil, err
}
var logs []ImportLog
key := fmt.Sprintf("import_job:%s:logs", jobID)
logsJSON, err := i.Helper.GetRedis("slave").Get(ctx, key).Result()
if err == nil {
json.Unmarshal([]byte(logsJSON), &logs)
}
return job, logs, nil
}

207
pkg/jobqueue/jobqueue.go Normal file
View File

@ -0,0 +1,207 @@
package jobqueue
import (
"context"
"fmt"
"time"
"github.com/redis/go-redis/v9"
)
type JobStatus string
const (
StatusPending JobStatus = "pending"
StatusProcessing JobStatus = "processing"
StatusCompleted JobStatus = "completed"
StatusFailed JobStatus = "failed"
)
type Job struct {
ID string `json:"id"`
Status JobStatus `json:"status"`
Total int `json:"total"`
Imported int `json:"imported"`
Skipped int `json:"skipped"`
Progress int `json:"progress"`
Errors []string `json:"errors,omitempty"`
CreatedAt time.Time `json:"created_at"`
FinishedAt *time.Time `json:"finished_at,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"`
}
type JobQueue struct {
Redis *redis.Client
}
func NewJobQueue(redis *redis.Client) *JobQueue {
return &JobQueue{Redis: redis}
}
func (jq *JobQueue) CreateJob(ctx context.Context, jobID string, total int, metadata map[string]string) error {
key := fmt.Sprintf("import_job:%s", jobID)
now := time.Now()
job := map[string]interface{}{
"id": jobID,
"status": string(StatusPending),
"total": total,
"imported": 0,
"skipped": 0,
"progress": 0,
"created_at": now.Format(time.RFC3339),
}
if metadata != nil {
for k, v := range metadata {
job["meta_"+k] = v
}
}
if err := jq.Redis.HSet(ctx, key, job).Err(); err != nil {
return err
}
jq.Redis.Expire(ctx, key, 24*time.Hour)
return nil
}
func (jq *JobQueue) UpdateProgress(ctx context.Context, jobID string, imported int, skipped int, errors []string) error {
key := fmt.Sprintf("import_job:%s", jobID)
job, err := jq.Redis.HGetAll(ctx, key).Result()
if err != nil {
return err
}
total := parseInt(job["total"])
progress := 0
if total > 0 {
progress = ((imported + skipped) * 100) / total
}
update := map[string]interface{}{
"status": string(StatusProcessing),
"imported": imported,
"skipped": skipped,
"progress": progress,
}
if len(errors) > 0 {
update["errors"] = joinErrors(errors)
}
return jq.Redis.HSet(ctx, key, update).Err()
}
func (jq *JobQueue) CompleteJob(ctx context.Context, jobID string, imported int, skipped int, errors []string) error {
key := fmt.Sprintf("import_job:%s", jobID)
now := time.Now()
update := map[string]interface{}{
"status": string(StatusCompleted),
"imported": imported,
"skipped": skipped,
"progress": 100,
"finished_at": now.Format(time.RFC3339),
}
if len(errors) > 0 {
update["errors"] = joinErrors(errors)
}
return jq.Redis.HSet(ctx, key, update).Err()
}
func (jq *JobQueue) FailJob(ctx context.Context, jobID string, errMsg string) error {
key := fmt.Sprintf("import_job:%s", jobID)
now := time.Now()
return jq.Redis.HSet(ctx, key, map[string]interface{}{
"status": string(StatusFailed),
"finished_at": now.Format(time.RFC3339),
"errors": errMsg,
}).Err()
}
func (jq *JobQueue) GetJob(ctx context.Context, jobID string) (*Job, error) {
key := fmt.Sprintf("import_job:%s", jobID)
data, err := jq.Redis.HGetAll(ctx, key).Result()
if err != nil {
return nil, err
}
if len(data) == 0 {
return nil, fmt.Errorf("job not found")
}
job := &Job{
ID: data["id"],
Status: JobStatus(data["status"]),
Total: parseInt(data["total"]),
Imported: parseInt(data["imported"]),
Skipped: parseInt(data["skipped"]),
Progress: parseInt(data["progress"]),
}
if data["errors"] != "" {
job.Errors = splitErrors(data["errors"])
}
if t, err := time.Parse(time.RFC3339, data["created_at"]); err == nil {
job.CreatedAt = t
}
if data["finished_at"] != "" {
if t, err := time.Parse(time.RFC3339, data["finished_at"]); err == nil {
job.FinishedAt = &t
}
}
return job, nil
}
func (jq *JobQueue) DeleteJob(ctx context.Context, jobID string) error {
key := fmt.Sprintf("import_job:%s", jobID)
return jq.Redis.Del(ctx, key).Err()
}
func parseInt(s string) int {
n := 0
for _, c := range s {
if c >= '0' && c <= '9' {
n = n*10 + int(c-'0')
}
}
return n
}
func joinErrors(errors []string) string {
result := ""
for i, e := range errors {
if i > 0 {
result += "||"
}
result += e
}
return result
}
func splitErrors(s string) []string {
result := []string{}
current := ""
for _, c := range s {
if c == '|' {
if current != "" {
result = append(result, current)
}
current = ""
} else {
current += string(c)
}
}
if current != "" {
result = append(result, current)
}
return result
}

View File

@ -687,3 +687,117 @@
{"level":"info","ts":"2026-08-17T14:21:37.526+0700","caller":"middleware/auth.go:62","msg":"session_login : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiIxOWZmMTA0YzE2NTIyYTIyNTJjYmFjYTI0ZCIsImlzcyI6ImNhcmdvLXBsYXRmb3JtIiwic3ViIjoiMTlmZjEwNGMxNjUyMmEyMjUyY2JhY2EyNGQiLCJleHAiOjE3ODY5NTI0NDksImlhdCI6MTc4Njk0ODg0OX0.Zl6rY1vRR7pf7xkq3YU7L2cfNUsth9ZriJ3ANZVh14g"} {"level":"info","ts":"2026-08-17T14:21:37.526+0700","caller":"middleware/auth.go:62","msg":"session_login : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiIxOWZmMTA0YzE2NTIyYTIyNTJjYmFjYTI0ZCIsImlzcyI6ImNhcmdvLXBsYXRmb3JtIiwic3ViIjoiMTlmZjEwNGMxNjUyMmEyMjUyY2JhY2EyNGQiLCJleHAiOjE3ODY5NTI0NDksImlhdCI6MTc4Njk0ODg0OX0.Zl6rY1vRR7pf7xkq3YU7L2cfNUsth9ZriJ3ANZVh14g"}
{"level":"info","ts":"2026-08-17T14:21:37.550+0700","caller":"middleware/auth.go:90","msg":"Set Auth : {\"blocked\":false,\"blocked_notes\":null,\"citys_relation\":null,\"created_by\":null,\"created_on\":\"2026-08-11T20:30:48.554654Z\",\"districts_relation\":null,\"email\":\"gcx@gmail.com\",\"id\":\"19ff104c16522a2252cbaca24d\",\"is_active\":true,\"merchant_id\":\"19ff111bca6c2c382d9eb43b3c\",\"merchant_relation\":null,\"merchants_relation\":null,\"nick_name\":\"Administrator\",\"postal_codes_relation\":null,\"provinces_relation\":null,\"subdistricts_relation\":null,\"updated_by\":null,\"updated_on\":\"2026-08-11T20:30:48.554654Z\",\"users_role_id\":\"19fe814c5350d67814c3e6057c\",\"users_role_relation\":{\"created_by\":null,\"created_on\":\"2026-08-10T02:51:43.16495Z\",\"id\":\"19fe814c5350d67814c3e6057c\",\"merchant_id\":null,\"merchant_relation\":{\"active\":null,\"additional_information\":null,\"address\":null,\"city_id\":null,\"city_relation\":null,\"citys_relation\":null,\"created_by\":null,\"created_by_relation\":null,\"created_on\":null,\"district_id\":null,\"district_relation\":null,\"districts_relation\":null,\"email\":null,\"id\":\"\",\"modas_relation\":null,\"name\":null,\"package_types_relation\":null,\"postal_code\":null,\"postal_codes_relation\":null,\"province_id\":null,\"province_relation\":null,\"provinces_relation\":null,\"subdistrict_id\":null,\"subdistrict_relation\":null,\"subdistricts_relation\":null,\"telp\":null,\"updated_by\":null,\"updated_on\":null,\"users_roless_relation\":null,\"userss_relation\":null},\"name\":\"Administrator\",\"role_key\":\"ADM_MERCHANT\",\"updated_by\":null,\"updated_on\":\"2026-08-10T02:51:43.16495Z\",\"userss_relation\":null}}"} {"level":"info","ts":"2026-08-17T14:21:37.550+0700","caller":"middleware/auth.go:90","msg":"Set Auth : {\"blocked\":false,\"blocked_notes\":null,\"citys_relation\":null,\"created_by\":null,\"created_on\":\"2026-08-11T20:30:48.554654Z\",\"districts_relation\":null,\"email\":\"gcx@gmail.com\",\"id\":\"19ff104c16522a2252cbaca24d\",\"is_active\":true,\"merchant_id\":\"19ff111bca6c2c382d9eb43b3c\",\"merchant_relation\":null,\"merchants_relation\":null,\"nick_name\":\"Administrator\",\"postal_codes_relation\":null,\"provinces_relation\":null,\"subdistricts_relation\":null,\"updated_by\":null,\"updated_on\":\"2026-08-11T20:30:48.554654Z\",\"users_role_id\":\"19fe814c5350d67814c3e6057c\",\"users_role_relation\":{\"created_by\":null,\"created_on\":\"2026-08-10T02:51:43.16495Z\",\"id\":\"19fe814c5350d67814c3e6057c\",\"merchant_id\":null,\"merchant_relation\":{\"active\":null,\"additional_information\":null,\"address\":null,\"city_id\":null,\"city_relation\":null,\"citys_relation\":null,\"created_by\":null,\"created_by_relation\":null,\"created_on\":null,\"district_id\":null,\"district_relation\":null,\"districts_relation\":null,\"email\":null,\"id\":\"\",\"modas_relation\":null,\"name\":null,\"package_types_relation\":null,\"postal_code\":null,\"postal_codes_relation\":null,\"province_id\":null,\"province_relation\":null,\"provinces_relation\":null,\"subdistrict_id\":null,\"subdistrict_relation\":null,\"subdistricts_relation\":null,\"telp\":null,\"updated_by\":null,\"updated_on\":null,\"users_roless_relation\":null,\"userss_relation\":null},\"name\":\"Administrator\",\"role_key\":\"ADM_MERCHANT\",\"updated_by\":null,\"updated_on\":\"2026-08-10T02:51:43.16495Z\",\"userss_relation\":null}}"}
{"level":"info","ts":"2026-08-17T14:21:37.621+0700","caller":"handlers/handlers.go:66","msg":"Request Success","status":201,"method":"POST","path":"/master-data/provinces/","query":"","ip":"127.0.0.1","latency":0.095641333,"user-agent":"Apidog/1.0.0 (https://apidog.com)"} {"level":"info","ts":"2026-08-17T14:21:37.621+0700","caller":"handlers/handlers.go:66","msg":"Request Success","status":201,"method":"POST","path":"/master-data/provinces/","query":"","ip":"127.0.0.1","latency":0.095641333,"user-agent":"Apidog/1.0.0 (https://apidog.com)"}
{"level":"info","ts":"2026-08-17T14:25:11.586+0700","caller":"database/database.go:53","msg":"Connect To Master DB"}
{"level":"info","ts":"2026-08-17T14:25:11.681+0700","caller":"database/database.go:63","msg":"DB Master Connected"}
{"level":"info","ts":"2026-08-17T14:25:11.681+0700","caller":"database/database.go:76","msg":"Connect To Slave DB"}
{"level":"info","ts":"2026-08-17T14:25:11.752+0700","caller":"database/database.go:86","msg":"DB Connection Done"}
{"level":"info","ts":"2026-08-17T14:25:11.856+0700","caller":"handlers/handlers.go:113","msg":"Starting Apps"}
{"level":"info","ts":"2026-08-17T14:25:13.926+0700","caller":"middleware/auth.go:62","msg":"session_login : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiIxOWZmMTA0YzE2NTIyYTIyNTJjYmFjYTI0ZCIsImlzcyI6ImNhcmdvLXBsYXRmb3JtIiwic3ViIjoiMTlmZjEwNGMxNjUyMmEyMjUyY2JhY2EyNGQiLCJleHAiOjE3ODY5NTI0NDksImlhdCI6MTc4Njk0ODg0OX0.Zl6rY1vRR7pf7xkq3YU7L2cfNUsth9ZriJ3ANZVh14g"}
{"level":"info","ts":"2026-08-17T14:25:13.950+0700","caller":"middleware/auth.go:90","msg":"Set Auth : {\"blocked\":false,\"blocked_notes\":null,\"citys_relation\":null,\"created_by\":null,\"created_on\":\"2026-08-11T20:30:48.554654Z\",\"districts_relation\":null,\"email\":\"gcx@gmail.com\",\"id\":\"19ff104c16522a2252cbaca24d\",\"is_active\":true,\"merchant_id\":\"19ff111bca6c2c382d9eb43b3c\",\"merchant_relation\":null,\"merchants_relation\":null,\"nick_name\":\"Administrator\",\"postal_codes_relation\":null,\"provinces_relation\":null,\"subdistricts_relation\":null,\"updated_by\":null,\"updated_on\":\"2026-08-11T20:30:48.554654Z\",\"users_role_id\":\"19fe814c5350d67814c3e6057c\",\"users_role_relation\":{\"created_by\":null,\"created_on\":\"2026-08-10T02:51:43.16495Z\",\"id\":\"19fe814c5350d67814c3e6057c\",\"merchant_id\":null,\"merchant_relation\":{\"active\":null,\"additional_information\":null,\"address\":null,\"city_id\":null,\"city_relation\":null,\"citys_relation\":null,\"created_by\":null,\"created_by_relation\":null,\"created_on\":null,\"district_id\":null,\"district_relation\":null,\"districts_relation\":null,\"email\":null,\"id\":\"\",\"modas_relation\":null,\"name\":null,\"package_types_relation\":null,\"postal_code\":null,\"postal_codes_relation\":null,\"province_id\":null,\"province_relation\":null,\"provinces_relation\":null,\"subdistrict_id\":null,\"subdistrict_relation\":null,\"subdistricts_relation\":null,\"telp\":null,\"updated_by\":null,\"updated_on\":null,\"users_roless_relation\":null,\"userss_relation\":null},\"name\":\"Administrator\",\"role_key\":\"ADM_MERCHANT\",\"updated_by\":null,\"updated_on\":\"2026-08-10T02:51:43.16495Z\",\"userss_relation\":null}}"}
{"level":"info","ts":"2026-08-17T14:25:14.035+0700","caller":"handlers/handlers.go:66","msg":"Request Success","status":200,"method":"DELETE","path":"/master-data/provinces/1a00e98ea5f87d9a1026ba6441","query":"","ip":"127.0.0.1","latency":0.1097195,"user-agent":"Apidog/1.0.0 (https://apidog.com)"}
{"level":"info","ts":"2026-08-17T14:25:32.443+0700","caller":"middleware/auth.go:62","msg":"session_login : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiIxOWZmMTA0YzE2NTIyYTIyNTJjYmFjYTI0ZCIsImlzcyI6ImNhcmdvLXBsYXRmb3JtIiwic3ViIjoiMTlmZjEwNGMxNjUyMmEyMjUyY2JhY2EyNGQiLCJleHAiOjE3ODY5NTI0NDksImlhdCI6MTc4Njk0ODg0OX0.Zl6rY1vRR7pf7xkq3YU7L2cfNUsth9ZriJ3ANZVh14g"}
{"level":"info","ts":"2026-08-17T14:25:32.474+0700","caller":"middleware/auth.go:90","msg":"Set Auth : {\"blocked\":false,\"blocked_notes\":null,\"citys_relation\":null,\"created_by\":null,\"created_on\":\"2026-08-11T20:30:48.554654Z\",\"districts_relation\":null,\"email\":\"gcx@gmail.com\",\"id\":\"19ff104c16522a2252cbaca24d\",\"is_active\":true,\"merchant_id\":\"19ff111bca6c2c382d9eb43b3c\",\"merchant_relation\":null,\"merchants_relation\":null,\"nick_name\":\"Administrator\",\"postal_codes_relation\":null,\"provinces_relation\":null,\"subdistricts_relation\":null,\"updated_by\":null,\"updated_on\":\"2026-08-11T20:30:48.554654Z\",\"users_role_id\":\"19fe814c5350d67814c3e6057c\",\"users_role_relation\":{\"created_by\":null,\"created_on\":\"2026-08-10T02:51:43.16495Z\",\"id\":\"19fe814c5350d67814c3e6057c\",\"merchant_id\":null,\"merchant_relation\":{\"active\":null,\"additional_information\":null,\"address\":null,\"city_id\":null,\"city_relation\":null,\"citys_relation\":null,\"created_by\":null,\"created_by_relation\":null,\"created_on\":null,\"district_id\":null,\"district_relation\":null,\"districts_relation\":null,\"email\":null,\"id\":\"\",\"modas_relation\":null,\"name\":null,\"package_types_relation\":null,\"postal_code\":null,\"postal_codes_relation\":null,\"province_id\":null,\"province_relation\":null,\"provinces_relation\":null,\"subdistrict_id\":null,\"subdistrict_relation\":null,\"subdistricts_relation\":null,\"telp\":null,\"updated_by\":null,\"updated_on\":null,\"users_roless_relation\":null,\"userss_relation\":null},\"name\":\"Administrator\",\"role_key\":\"ADM_MERCHANT\",\"updated_by\":null,\"updated_on\":\"2026-08-10T02:51:43.16495Z\",\"userss_relation\":null}}"}
{"level":"info","ts":"2026-08-17T14:25:32.600+0700","caller":"handlers/handlers.go:66","msg":"Request Success","status":201,"method":"POST","path":"/master-data/provinces/","query":"","ip":"127.0.0.1","latency":0.157767833,"user-agent":"Apidog/1.0.0 (https://apidog.com)"}
{"level":"info","ts":"2026-08-17T14:29:01.138+0700","caller":"database/database.go:53","msg":"Connect To Master DB"}
{"level":"info","ts":"2026-08-17T14:29:01.281+0700","caller":"database/database.go:63","msg":"DB Master Connected"}
{"level":"info","ts":"2026-08-17T14:29:01.281+0700","caller":"database/database.go:76","msg":"Connect To Slave DB"}
{"level":"info","ts":"2026-08-17T14:29:01.363+0700","caller":"database/database.go:86","msg":"DB Connection Done"}
{"level":"info","ts":"2026-08-17T14:29:01.481+0700","caller":"handlers/handlers.go:113","msg":"Starting Apps"}
{"level":"info","ts":"2026-08-17T14:29:01.947+0700","caller":"middleware/auth.go:62","msg":"session_login : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiIxOWZmMTA0YzE2NTIyYTIyNTJjYmFjYTI0ZCIsImlzcyI6ImNhcmdvLXBsYXRmb3JtIiwic3ViIjoiMTlmZjEwNGMxNjUyMmEyMjUyY2JhY2EyNGQiLCJleHAiOjE3ODY5NTI0NDksImlhdCI6MTc4Njk0ODg0OX0.Zl6rY1vRR7pf7xkq3YU7L2cfNUsth9ZriJ3ANZVh14g"}
{"level":"info","ts":"2026-08-17T14:29:01.968+0700","caller":"middleware/auth.go:90","msg":"Set Auth : {\"blocked\":false,\"blocked_notes\":null,\"citys_relation\":null,\"created_by\":null,\"created_on\":\"2026-08-11T20:30:48.554654Z\",\"districts_relation\":null,\"email\":\"gcx@gmail.com\",\"id\":\"19ff104c16522a2252cbaca24d\",\"is_active\":true,\"merchant_id\":\"19ff111bca6c2c382d9eb43b3c\",\"merchant_relation\":null,\"merchants_relation\":null,\"nick_name\":\"Administrator\",\"postal_codes_relation\":null,\"provinces_relation\":null,\"subdistricts_relation\":null,\"updated_by\":null,\"updated_on\":\"2026-08-11T20:30:48.554654Z\",\"users_role_id\":\"19fe814c5350d67814c3e6057c\",\"users_role_relation\":{\"created_by\":null,\"created_on\":\"2026-08-10T02:51:43.16495Z\",\"id\":\"19fe814c5350d67814c3e6057c\",\"merchant_id\":null,\"merchant_relation\":{\"active\":null,\"additional_information\":null,\"address\":null,\"city_id\":null,\"city_relation\":null,\"citys_relation\":null,\"created_by\":null,\"created_by_relation\":null,\"created_on\":null,\"district_id\":null,\"district_relation\":null,\"districts_relation\":null,\"email\":null,\"id\":\"\",\"modas_relation\":null,\"name\":null,\"package_types_relation\":null,\"postal_code\":null,\"postal_codes_relation\":null,\"province_id\":null,\"province_relation\":null,\"provinces_relation\":null,\"subdistrict_id\":null,\"subdistrict_relation\":null,\"subdistricts_relation\":null,\"telp\":null,\"updated_by\":null,\"updated_on\":null,\"users_roless_relation\":null,\"userss_relation\":null},\"name\":\"Administrator\",\"role_key\":\"ADM_MERCHANT\",\"updated_by\":null,\"updated_on\":\"2026-08-10T02:51:43.16495Z\",\"userss_relation\":null}}"}
{"level":"info","ts":"2026-08-17T14:29:02.064+0700","caller":"handlers/handlers.go:66","msg":"Request Success","status":200,"method":"PUT","path":"/master-data/provinces/1a00e9c80365466b3d2a04918c","query":"","ip":"127.0.0.1","latency":0.117066958,"user-agent":"Apidog/1.0.0 (https://apidog.com)"}
{"level":"info","ts":"2026-08-17T14:30:30.255+0700","caller":"database/database.go:53","msg":"Connect To Master DB"}
{"level":"info","ts":"2026-08-17T14:30:30.350+0700","caller":"database/database.go:63","msg":"DB Master Connected"}
{"level":"info","ts":"2026-08-17T14:30:30.350+0700","caller":"database/database.go:76","msg":"Connect To Slave DB"}
{"level":"info","ts":"2026-08-17T14:30:30.424+0700","caller":"database/database.go:86","msg":"DB Connection Done"}
{"level":"info","ts":"2026-08-17T14:30:30.532+0700","caller":"handlers/handlers.go:113","msg":"Starting Apps"}
{"level":"info","ts":"2026-08-17T14:30:34.315+0700","caller":"middleware/auth.go:62","msg":"session_login : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiIxOWZmMTA0YzE2NTIyYTIyNTJjYmFjYTI0ZCIsImlzcyI6ImNhcmdvLXBsYXRmb3JtIiwic3ViIjoiMTlmZjEwNGMxNjUyMmEyMjUyY2JhY2EyNGQiLCJleHAiOjE3ODY5NTI0NDksImlhdCI6MTc4Njk0ODg0OX0.Zl6rY1vRR7pf7xkq3YU7L2cfNUsth9ZriJ3ANZVh14g"}
{"level":"info","ts":"2026-08-17T14:30:34.340+0700","caller":"middleware/auth.go:90","msg":"Set Auth : {\"blocked\":false,\"blocked_notes\":null,\"citys_relation\":null,\"created_by\":null,\"created_on\":\"2026-08-11T20:30:48.554654Z\",\"districts_relation\":null,\"email\":\"gcx@gmail.com\",\"id\":\"19ff104c16522a2252cbaca24d\",\"is_active\":true,\"merchant_id\":\"19ff111bca6c2c382d9eb43b3c\",\"merchant_relation\":null,\"merchants_relation\":null,\"nick_name\":\"Administrator\",\"postal_codes_relation\":null,\"provinces_relation\":null,\"subdistricts_relation\":null,\"updated_by\":null,\"updated_on\":\"2026-08-11T20:30:48.554654Z\",\"users_role_id\":\"19fe814c5350d67814c3e6057c\",\"users_role_relation\":{\"created_by\":null,\"created_on\":\"2026-08-10T02:51:43.16495Z\",\"id\":\"19fe814c5350d67814c3e6057c\",\"merchant_id\":null,\"merchant_relation\":{\"active\":null,\"additional_information\":null,\"address\":null,\"city_id\":null,\"city_relation\":null,\"citys_relation\":null,\"created_by\":null,\"created_by_relation\":null,\"created_on\":null,\"district_id\":null,\"district_relation\":null,\"districts_relation\":null,\"email\":null,\"id\":\"\",\"modas_relation\":null,\"name\":null,\"package_types_relation\":null,\"postal_code\":null,\"postal_codes_relation\":null,\"province_id\":null,\"province_relation\":null,\"provinces_relation\":null,\"subdistrict_id\":null,\"subdistrict_relation\":null,\"subdistricts_relation\":null,\"telp\":null,\"updated_by\":null,\"updated_on\":null,\"users_roless_relation\":null,\"userss_relation\":null},\"name\":\"Administrator\",\"role_key\":\"ADM_MERCHANT\",\"updated_by\":null,\"updated_on\":\"2026-08-10T02:51:43.16495Z\",\"userss_relation\":null}}"}
{"level":"info","ts":"2026-08-17T14:30:34.428+0700","caller":"handlers/handlers.go:66","msg":"Request Success","status":200,"method":"PUT","path":"/master-data/provinces/1a00e9c80365466b3d2a04918c","query":"","ip":"127.0.0.1","latency":0.113493,"user-agent":"Apidog/1.0.0 (https://apidog.com)"}
{"level":"info","ts":"2026-08-17T14:32:39.551+0700","caller":"middleware/auth.go:62","msg":"session_login : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiIxOWZmMTA0YzE2NTIyYTIyNTJjYmFjYTI0ZCIsImlzcyI6ImNhcmdvLXBsYXRmb3JtIiwic3ViIjoiMTlmZjEwNGMxNjUyMmEyMjUyY2JhY2EyNGQiLCJleHAiOjE3ODY5NTI0NDksImlhdCI6MTc4Njk0ODg0OX0.Zl6rY1vRR7pf7xkq3YU7L2cfNUsth9ZriJ3ANZVh14g"}
{"level":"info","ts":"2026-08-17T14:32:39.592+0700","caller":"middleware/auth.go:90","msg":"Set Auth : {\"blocked\":false,\"blocked_notes\":null,\"citys_relation\":null,\"created_by\":null,\"created_on\":\"2026-08-11T20:30:48.554654Z\",\"districts_relation\":null,\"email\":\"gcx@gmail.com\",\"id\":\"19ff104c16522a2252cbaca24d\",\"is_active\":true,\"merchant_id\":\"19ff111bca6c2c382d9eb43b3c\",\"merchant_relation\":null,\"merchants_relation\":null,\"nick_name\":\"Administrator\",\"postal_codes_relation\":null,\"provinces_relation\":null,\"subdistricts_relation\":null,\"updated_by\":null,\"updated_on\":\"2026-08-11T20:30:48.554654Z\",\"users_role_id\":\"19fe814c5350d67814c3e6057c\",\"users_role_relation\":{\"created_by\":null,\"created_on\":\"2026-08-10T02:51:43.16495Z\",\"id\":\"19fe814c5350d67814c3e6057c\",\"merchant_id\":null,\"merchant_relation\":{\"active\":null,\"additional_information\":null,\"address\":null,\"city_id\":null,\"city_relation\":null,\"citys_relation\":null,\"created_by\":null,\"created_by_relation\":null,\"created_on\":null,\"district_id\":null,\"district_relation\":null,\"districts_relation\":null,\"email\":null,\"id\":\"\",\"modas_relation\":null,\"name\":null,\"package_types_relation\":null,\"postal_code\":null,\"postal_codes_relation\":null,\"province_id\":null,\"province_relation\":null,\"provinces_relation\":null,\"subdistrict_id\":null,\"subdistrict_relation\":null,\"subdistricts_relation\":null,\"telp\":null,\"updated_by\":null,\"updated_on\":null,\"users_roless_relation\":null,\"userss_relation\":null},\"name\":\"Administrator\",\"role_key\":\"ADM_MERCHANT\",\"updated_by\":null,\"updated_on\":\"2026-08-10T02:51:43.16495Z\",\"userss_relation\":null}}"}
{"level":"info","ts":"2026-08-17T14:32:39.657+0700","caller":"handlers/handlers.go:66","msg":"Request Success","status":200,"method":"DELETE","path":"/master-data/provinces/1a00e9c80365466b3d2a04918c","query":"","ip":"127.0.0.1","latency":0.108803959,"user-agent":"Apidog/1.0.0 (https://apidog.com)"}
{"level":"info","ts":"2026-08-17T14:38:27.830+0700","caller":"database/database.go:53","msg":"Connect To Master DB"}
{"level":"info","ts":"2026-08-17T14:38:27.951+0700","caller":"database/database.go:63","msg":"DB Master Connected"}
{"level":"info","ts":"2026-08-17T14:38:27.951+0700","caller":"database/database.go:76","msg":"Connect To Slave DB"}
{"level":"info","ts":"2026-08-17T14:38:28.024+0700","caller":"database/database.go:86","msg":"DB Connection Done"}
{"level":"info","ts":"2026-08-17T14:38:28.135+0700","caller":"handlers/handlers.go:113","msg":"Starting Apps"}
{"level":"info","ts":"2026-08-17T14:39:10.542+0700","caller":"middleware/auth.go:62","msg":"session_login : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiIxOWZmMTA0YzE2NTIyYTIyNTJjYmFjYTI0ZCIsImlzcyI6ImNhcmdvLXBsYXRmb3JtIiwic3ViIjoiMTlmZjEwNGMxNjUyMmEyMjUyY2JhY2EyNGQiLCJleHAiOjE3ODY5NTI0NDksImlhdCI6MTc4Njk0ODg0OX0.Zl6rY1vRR7pf7xkq3YU7L2cfNUsth9ZriJ3ANZVh14g"}
{"level":"info","ts":"2026-08-17T14:39:10.568+0700","caller":"middleware/auth.go:90","msg":"Set Auth : {\"blocked\":false,\"blocked_notes\":null,\"citys_relation\":null,\"created_by\":null,\"created_on\":\"2026-08-11T20:30:48.554654Z\",\"districts_relation\":null,\"email\":\"gcx@gmail.com\",\"id\":\"19ff104c16522a2252cbaca24d\",\"is_active\":true,\"merchant_id\":\"19ff111bca6c2c382d9eb43b3c\",\"merchant_relation\":null,\"merchants_relation\":null,\"nick_name\":\"Administrator\",\"postal_codes_relation\":null,\"provinces_relation\":null,\"subdistricts_relation\":null,\"updated_by\":null,\"updated_on\":\"2026-08-11T20:30:48.554654Z\",\"users_role_id\":\"19fe814c5350d67814c3e6057c\",\"users_role_relation\":{\"created_by\":null,\"created_on\":\"2026-08-10T02:51:43.16495Z\",\"id\":\"19fe814c5350d67814c3e6057c\",\"merchant_id\":null,\"merchant_relation\":{\"active\":null,\"additional_information\":null,\"address\":null,\"city_id\":null,\"city_relation\":null,\"citys_relation\":null,\"created_by\":null,\"created_by_relation\":null,\"created_on\":null,\"district_id\":null,\"district_relation\":null,\"districts_relation\":null,\"email\":null,\"id\":\"\",\"modas_relation\":null,\"name\":null,\"package_types_relation\":null,\"postal_code\":null,\"postal_codes_relation\":null,\"province_id\":null,\"province_relation\":null,\"provinces_relation\":null,\"subdistrict_id\":null,\"subdistrict_relation\":null,\"subdistricts_relation\":null,\"telp\":null,\"updated_by\":null,\"updated_on\":null,\"users_roless_relation\":null,\"userss_relation\":null},\"name\":\"Administrator\",\"role_key\":\"ADM_MERCHANT\",\"updated_by\":null,\"updated_on\":\"2026-08-10T02:51:43.16495Z\",\"userss_relation\":null}}"}
{"level":"info","ts":"2026-08-17T14:39:10.654+0700","caller":"handlers/handlers.go:66","msg":"Request Success","status":200,"method":"GET","path":"/master-data/provinces/","query":"","ip":"127.0.0.1","latency":0.113548917,"user-agent":"Apidog/1.0.0 (https://apidog.com)"}
{"level":"info","ts":"2026-08-17T14:39:47.378+0700","caller":"database/database.go:53","msg":"Connect To Master DB"}
{"level":"info","ts":"2026-08-17T14:39:47.470+0700","caller":"database/database.go:63","msg":"DB Master Connected"}
{"level":"info","ts":"2026-08-17T14:39:47.470+0700","caller":"database/database.go:76","msg":"Connect To Slave DB"}
{"level":"info","ts":"2026-08-17T14:39:47.543+0700","caller":"database/database.go:86","msg":"DB Connection Done"}
{"level":"info","ts":"2026-08-17T14:39:47.656+0700","caller":"handlers/handlers.go:113","msg":"Starting Apps"}
{"level":"info","ts":"2026-08-17T14:39:50.136+0700","caller":"middleware/auth.go:62","msg":"session_login : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiIxOWZmMTA0YzE2NTIyYTIyNTJjYmFjYTI0ZCIsImlzcyI6ImNhcmdvLXBsYXRmb3JtIiwic3ViIjoiMTlmZjEwNGMxNjUyMmEyMjUyY2JhY2EyNGQiLCJleHAiOjE3ODY5NTI0NDksImlhdCI6MTc4Njk0ODg0OX0.Zl6rY1vRR7pf7xkq3YU7L2cfNUsth9ZriJ3ANZVh14g"}
{"level":"info","ts":"2026-08-17T14:39:50.160+0700","caller":"middleware/auth.go:90","msg":"Set Auth : {\"blocked\":false,\"blocked_notes\":null,\"citys_relation\":null,\"created_by\":null,\"created_on\":\"2026-08-11T20:30:48.554654Z\",\"districts_relation\":null,\"email\":\"gcx@gmail.com\",\"id\":\"19ff104c16522a2252cbaca24d\",\"is_active\":true,\"merchant_id\":\"19ff111bca6c2c382d9eb43b3c\",\"merchant_relation\":null,\"merchants_relation\":null,\"nick_name\":\"Administrator\",\"postal_codes_relation\":null,\"provinces_relation\":null,\"subdistricts_relation\":null,\"updated_by\":null,\"updated_on\":\"2026-08-11T20:30:48.554654Z\",\"users_role_id\":\"19fe814c5350d67814c3e6057c\",\"users_role_relation\":{\"created_by\":null,\"created_on\":\"2026-08-10T02:51:43.16495Z\",\"id\":\"19fe814c5350d67814c3e6057c\",\"merchant_id\":null,\"merchant_relation\":{\"active\":null,\"additional_information\":null,\"address\":null,\"city_id\":null,\"city_relation\":null,\"citys_relation\":null,\"created_by\":null,\"created_by_relation\":null,\"created_on\":null,\"district_id\":null,\"district_relation\":null,\"districts_relation\":null,\"email\":null,\"id\":\"\",\"modas_relation\":null,\"name\":null,\"package_types_relation\":null,\"postal_code\":null,\"postal_codes_relation\":null,\"province_id\":null,\"province_relation\":null,\"provinces_relation\":null,\"subdistrict_id\":null,\"subdistrict_relation\":null,\"subdistricts_relation\":null,\"telp\":null,\"updated_by\":null,\"updated_on\":null,\"users_roless_relation\":null,\"userss_relation\":null},\"name\":\"Administrator\",\"role_key\":\"ADM_MERCHANT\",\"updated_by\":null,\"updated_on\":\"2026-08-10T02:51:43.16495Z\",\"userss_relation\":null}}"}
{"level":"info","ts":"2026-08-17T14:39:50.234+0700","caller":"handlers/handlers.go:66","msg":"Request Success","status":200,"method":"GET","path":"/master-data/provinces/","query":"","ip":"127.0.0.1","latency":0.099099792,"user-agent":"Apidog/1.0.0 (https://apidog.com)"}
{"level":"info","ts":"2026-08-17T14:40:13.477+0700","caller":"middleware/auth.go:62","msg":"session_login : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiIxOWZmMTA0YzE2NTIyYTIyNTJjYmFjYTI0ZCIsImlzcyI6ImNhcmdvLXBsYXRmb3JtIiwic3ViIjoiMTlmZjEwNGMxNjUyMmEyMjUyY2JhY2EyNGQiLCJleHAiOjE3ODY5NTI0NDksImlhdCI6MTc4Njk0ODg0OX0.Zl6rY1vRR7pf7xkq3YU7L2cfNUsth9ZriJ3ANZVh14g"}
{"level":"info","ts":"2026-08-17T14:40:13.517+0700","caller":"middleware/auth.go:90","msg":"Set Auth : {\"blocked\":false,\"blocked_notes\":null,\"citys_relation\":null,\"created_by\":null,\"created_on\":\"2026-08-11T20:30:48.554654Z\",\"districts_relation\":null,\"email\":\"gcx@gmail.com\",\"id\":\"19ff104c16522a2252cbaca24d\",\"is_active\":true,\"merchant_id\":\"19ff111bca6c2c382d9eb43b3c\",\"merchant_relation\":null,\"merchants_relation\":null,\"nick_name\":\"Administrator\",\"postal_codes_relation\":null,\"provinces_relation\":null,\"subdistricts_relation\":null,\"updated_by\":null,\"updated_on\":\"2026-08-11T20:30:48.554654Z\",\"users_role_id\":\"19fe814c5350d67814c3e6057c\",\"users_role_relation\":{\"created_by\":null,\"created_on\":\"2026-08-10T02:51:43.16495Z\",\"id\":\"19fe814c5350d67814c3e6057c\",\"merchant_id\":null,\"merchant_relation\":{\"active\":null,\"additional_information\":null,\"address\":null,\"city_id\":null,\"city_relation\":null,\"citys_relation\":null,\"created_by\":null,\"created_by_relation\":null,\"created_on\":null,\"district_id\":null,\"district_relation\":null,\"districts_relation\":null,\"email\":null,\"id\":\"\",\"modas_relation\":null,\"name\":null,\"package_types_relation\":null,\"postal_code\":null,\"postal_codes_relation\":null,\"province_id\":null,\"province_relation\":null,\"provinces_relation\":null,\"subdistrict_id\":null,\"subdistrict_relation\":null,\"subdistricts_relation\":null,\"telp\":null,\"updated_by\":null,\"updated_on\":null,\"users_roless_relation\":null,\"userss_relation\":null},\"name\":\"Administrator\",\"role_key\":\"ADM_MERCHANT\",\"updated_by\":null,\"updated_on\":\"2026-08-10T02:51:43.16495Z\",\"userss_relation\":null}}"}
{"level":"info","ts":"2026-08-17T14:40:13.593+0700","caller":"handlers/handlers.go:66","msg":"Request Success","status":200,"method":"GET","path":"/master-data/provinces/","query":"","ip":"127.0.0.1","latency":0.117934,"user-agent":"Apidog/1.0.0 (https://apidog.com)"}
{"level":"info","ts":"2026-08-17T14:40:47.694+0700","caller":"database/database.go:53","msg":"Connect To Master DB"}
{"level":"info","ts":"2026-08-17T14:40:47.855+0700","caller":"database/database.go:63","msg":"DB Master Connected"}
{"level":"info","ts":"2026-08-17T14:40:47.855+0700","caller":"database/database.go:76","msg":"Connect To Slave DB"}
{"level":"info","ts":"2026-08-17T14:40:47.976+0700","caller":"database/database.go:86","msg":"DB Connection Done"}
{"level":"info","ts":"2026-08-17T14:40:48.089+0700","caller":"handlers/handlers.go:113","msg":"Starting Apps"}
{"level":"warn","ts":"2026-08-17T14:40:52.337+0700","caller":"handlers/handlers.go:64","msg":"Client Error","status":401,"method":"GET","path":"/master-data/provinces/","query":"","ip":"127.0.0.1","latency":0.000458083,"user-agent":"Apidog/1.0.0 (https://apidog.com)"}
{"level":"info","ts":"2026-08-17T14:40:58.800+0700","caller":"handlers/handlers.go:66","msg":"Request Success","status":200,"method":"POST","path":"/login","query":"","ip":"127.0.0.1","latency":0.180353416,"user-agent":"Apidog/1.0.0 (https://apidog.com)"}
{"level":"info","ts":"2026-08-17T14:41:02.287+0700","caller":"middleware/auth.go:62","msg":"session_login : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiIxOWZmMTA0YzE2NTIyYTIyNTJjYmFjYTI0ZCIsImlzcyI6ImNhcmdvLXBsYXRmb3JtIiwic3ViIjoiMTlmZjEwNGMxNjUyMmEyMjUyY2JhY2EyNGQiLCJleHAiOjE3ODY5NTYwNTgsImlhdCI6MTc4Njk1MjQ1OH0.TZXGJLNng6TyfzspOkA6HuEyFi61K-wnie_L6ybYzyY"}
{"level":"info","ts":"2026-08-17T14:41:02.315+0700","caller":"middleware/auth.go:90","msg":"Set Auth : {\"blocked\":false,\"blocked_notes\":null,\"citys_relation\":null,\"created_by\":null,\"created_on\":\"2026-08-11T20:30:48.554654Z\",\"districts_relation\":null,\"email\":\"gcx@gmail.com\",\"id\":\"19ff104c16522a2252cbaca24d\",\"is_active\":true,\"merchant_id\":\"19ff111bca6c2c382d9eb43b3c\",\"merchant_relation\":null,\"merchants_relation\":null,\"nick_name\":\"Administrator\",\"postal_codes_relation\":null,\"provinces_relation\":null,\"subdistricts_relation\":null,\"updated_by\":null,\"updated_on\":\"2026-08-11T20:30:48.554654Z\",\"users_role_id\":\"19fe814c5350d67814c3e6057c\",\"users_role_relation\":{\"created_by\":null,\"created_on\":\"2026-08-10T02:51:43.16495Z\",\"id\":\"19fe814c5350d67814c3e6057c\",\"merchant_id\":null,\"merchant_relation\":{\"active\":null,\"additional_information\":null,\"address\":null,\"city_id\":null,\"city_relation\":null,\"citys_relation\":null,\"created_by\":null,\"created_by_relation\":null,\"created_on\":null,\"district_id\":null,\"district_relation\":null,\"districts_relation\":null,\"email\":null,\"id\":\"\",\"modas_relation\":null,\"name\":null,\"package_types_relation\":null,\"postal_code\":null,\"postal_codes_relation\":null,\"province_id\":null,\"province_relation\":null,\"provinces_relation\":null,\"subdistrict_id\":null,\"subdistrict_relation\":null,\"subdistricts_relation\":null,\"telp\":null,\"updated_by\":null,\"updated_on\":null,\"users_roless_relation\":null,\"userss_relation\":null},\"name\":\"Administrator\",\"role_key\":\"ADM_MERCHANT\",\"updated_by\":null,\"updated_on\":\"2026-08-10T02:51:43.16495Z\",\"userss_relation\":null}}"}
{"level":"info","ts":"2026-08-17T14:41:02.440+0700","caller":"handlers/handlers.go:66","msg":"Request Success","status":200,"method":"GET","path":"/master-data/provinces/","query":"","ip":"127.0.0.1","latency":0.153650875,"user-agent":"Apidog/1.0.0 (https://apidog.com)"}
{"level":"info","ts":"2026-08-17T14:42:37.997+0700","caller":"database/database.go:53","msg":"Connect To Master DB"}
{"level":"info","ts":"2026-08-17T14:42:38.100+0700","caller":"database/database.go:63","msg":"DB Master Connected"}
{"level":"info","ts":"2026-08-17T14:42:38.100+0700","caller":"database/database.go:76","msg":"Connect To Slave DB"}
{"level":"info","ts":"2026-08-17T14:42:38.180+0700","caller":"database/database.go:86","msg":"DB Connection Done"}
{"level":"info","ts":"2026-08-17T14:42:38.286+0700","caller":"handlers/handlers.go:113","msg":"Starting Apps"}
{"level":"info","ts":"2026-08-17T14:42:40.017+0700","caller":"middleware/auth.go:62","msg":"session_login : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiIxOWZmMTA0YzE2NTIyYTIyNTJjYmFjYTI0ZCIsImlzcyI6ImNhcmdvLXBsYXRmb3JtIiwic3ViIjoiMTlmZjEwNGMxNjUyMmEyMjUyY2JhY2EyNGQiLCJleHAiOjE3ODY5NTYwNTgsImlhdCI6MTc4Njk1MjQ1OH0.TZXGJLNng6TyfzspOkA6HuEyFi61K-wnie_L6ybYzyY"}
{"level":"info","ts":"2026-08-17T14:42:40.041+0700","caller":"middleware/auth.go:90","msg":"Set Auth : {\"blocked\":false,\"blocked_notes\":null,\"citys_relation\":null,\"created_by\":null,\"created_on\":\"2026-08-11T20:30:48.554654Z\",\"districts_relation\":null,\"email\":\"gcx@gmail.com\",\"id\":\"19ff104c16522a2252cbaca24d\",\"is_active\":true,\"merchant_id\":\"19ff111bca6c2c382d9eb43b3c\",\"merchant_relation\":null,\"merchants_relation\":null,\"nick_name\":\"Administrator\",\"postal_codes_relation\":null,\"provinces_relation\":null,\"subdistricts_relation\":null,\"updated_by\":null,\"updated_on\":\"2026-08-11T20:30:48.554654Z\",\"users_role_id\":\"19fe814c5350d67814c3e6057c\",\"users_role_relation\":{\"created_by\":null,\"created_on\":\"2026-08-10T02:51:43.16495Z\",\"id\":\"19fe814c5350d67814c3e6057c\",\"merchant_id\":null,\"merchant_relation\":{\"active\":null,\"additional_information\":null,\"address\":null,\"city_id\":null,\"city_relation\":null,\"citys_relation\":null,\"created_by\":null,\"created_by_relation\":null,\"created_on\":null,\"district_id\":null,\"district_relation\":null,\"districts_relation\":null,\"email\":null,\"id\":\"\",\"modas_relation\":null,\"name\":null,\"package_types_relation\":null,\"postal_code\":null,\"postal_codes_relation\":null,\"province_id\":null,\"province_relation\":null,\"provinces_relation\":null,\"subdistrict_id\":null,\"subdistrict_relation\":null,\"subdistricts_relation\":null,\"telp\":null,\"updated_by\":null,\"updated_on\":null,\"users_roless_relation\":null,\"userss_relation\":null},\"name\":\"Administrator\",\"role_key\":\"ADM_MERCHANT\",\"updated_by\":null,\"updated_on\":\"2026-08-10T02:51:43.16495Z\",\"userss_relation\":null}}"}
{"level":"info","ts":"2026-08-17T14:42:40.111+0700","caller":"handlers/handlers.go:66","msg":"Request Success","status":200,"method":"GET","path":"/master-data/provinces/","query":"","ip":"127.0.0.1","latency":0.0938845,"user-agent":"Apidog/1.0.0 (https://apidog.com)"}
{"level":"info","ts":"2026-08-17T14:43:03.312+0700","caller":"database/database.go:53","msg":"Connect To Master DB"}
{"level":"info","ts":"2026-08-17T14:43:03.433+0700","caller":"database/database.go:63","msg":"DB Master Connected"}
{"level":"info","ts":"2026-08-17T14:43:03.433+0700","caller":"database/database.go:76","msg":"Connect To Slave DB"}
{"level":"info","ts":"2026-08-17T14:43:03.513+0700","caller":"database/database.go:86","msg":"DB Connection Done"}
{"level":"info","ts":"2026-08-17T14:43:03.623+0700","caller":"handlers/handlers.go:113","msg":"Starting Apps"}
{"level":"info","ts":"2026-08-17T14:43:03.660+0700","caller":"middleware/auth.go:62","msg":"session_login : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiIxOWZmMTA0YzE2NTIyYTIyNTJjYmFjYTI0ZCIsImlzcyI6ImNhcmdvLXBsYXRmb3JtIiwic3ViIjoiMTlmZjEwNGMxNjUyMmEyMjUyY2JhY2EyNGQiLCJleHAiOjE3ODY5NTYwNTgsImlhdCI6MTc4Njk1MjQ1OH0.TZXGJLNng6TyfzspOkA6HuEyFi61K-wnie_L6ybYzyY"}
{"level":"info","ts":"2026-08-17T14:43:03.681+0700","caller":"middleware/auth.go:90","msg":"Set Auth : {\"blocked\":false,\"blocked_notes\":null,\"citys_relation\":null,\"created_by\":null,\"created_on\":\"2026-08-11T20:30:48.554654Z\",\"districts_relation\":null,\"email\":\"gcx@gmail.com\",\"id\":\"19ff104c16522a2252cbaca24d\",\"is_active\":true,\"merchant_id\":\"19ff111bca6c2c382d9eb43b3c\",\"merchant_relation\":null,\"merchants_relation\":null,\"nick_name\":\"Administrator\",\"postal_codes_relation\":null,\"provinces_relation\":null,\"subdistricts_relation\":null,\"updated_by\":null,\"updated_on\":\"2026-08-11T20:30:48.554654Z\",\"users_role_id\":\"19fe814c5350d67814c3e6057c\",\"users_role_relation\":{\"created_by\":null,\"created_on\":\"2026-08-10T02:51:43.16495Z\",\"id\":\"19fe814c5350d67814c3e6057c\",\"merchant_id\":null,\"merchant_relation\":{\"active\":null,\"additional_information\":null,\"address\":null,\"city_id\":null,\"city_relation\":null,\"citys_relation\":null,\"created_by\":null,\"created_by_relation\":null,\"created_on\":null,\"district_id\":null,\"district_relation\":null,\"districts_relation\":null,\"email\":null,\"id\":\"\",\"modas_relation\":null,\"name\":null,\"package_types_relation\":null,\"postal_code\":null,\"postal_codes_relation\":null,\"province_id\":null,\"province_relation\":null,\"provinces_relation\":null,\"subdistrict_id\":null,\"subdistrict_relation\":null,\"subdistricts_relation\":null,\"telp\":null,\"updated_by\":null,\"updated_on\":null,\"users_roless_relation\":null,\"userss_relation\":null},\"name\":\"Administrator\",\"role_key\":\"ADM_MERCHANT\",\"updated_by\":null,\"updated_on\":\"2026-08-10T02:51:43.16495Z\",\"userss_relation\":null}}"}
{"level":"info","ts":"2026-08-17T14:43:03.742+0700","caller":"handlers/handlers.go:66","msg":"Request Success","status":200,"method":"GET","path":"/master-data/provinces/","query":"","ip":"127.0.0.1","latency":0.082196166,"user-agent":"Apidog/1.0.0 (https://apidog.com)"}
{"level":"info","ts":"2026-08-17T14:45:05.375+0700","caller":"middleware/auth.go:62","msg":"session_login : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiIxOWZmMTA0YzE2NTIyYTIyNTJjYmFjYTI0ZCIsImlzcyI6ImNhcmdvLXBsYXRmb3JtIiwic3ViIjoiMTlmZjEwNGMxNjUyMmEyMjUyY2JhY2EyNGQiLCJleHAiOjE3ODY5NTYwNTgsImlhdCI6MTc4Njk1MjQ1OH0.TZXGJLNng6TyfzspOkA6HuEyFi61K-wnie_L6ybYzyY"}
{"level":"info","ts":"2026-08-17T14:45:05.402+0700","caller":"middleware/auth.go:90","msg":"Set Auth : {\"blocked\":false,\"blocked_notes\":null,\"citys_relation\":null,\"created_by\":null,\"created_on\":\"2026-08-11T20:30:48.554654Z\",\"districts_relation\":null,\"email\":\"gcx@gmail.com\",\"id\":\"19ff104c16522a2252cbaca24d\",\"is_active\":true,\"merchant_id\":\"19ff111bca6c2c382d9eb43b3c\",\"merchant_relation\":null,\"merchants_relation\":null,\"nick_name\":\"Administrator\",\"postal_codes_relation\":null,\"provinces_relation\":null,\"subdistricts_relation\":null,\"updated_by\":null,\"updated_on\":\"2026-08-11T20:30:48.554654Z\",\"users_role_id\":\"19fe814c5350d67814c3e6057c\",\"users_role_relation\":{\"created_by\":null,\"created_on\":\"2026-08-10T02:51:43.16495Z\",\"id\":\"19fe814c5350d67814c3e6057c\",\"merchant_id\":null,\"merchant_relation\":{\"active\":null,\"additional_information\":null,\"address\":null,\"city_id\":null,\"city_relation\":null,\"citys_relation\":null,\"created_by\":null,\"created_by_relation\":null,\"created_on\":null,\"district_id\":null,\"district_relation\":null,\"districts_relation\":null,\"email\":null,\"id\":\"\",\"modas_relation\":null,\"name\":null,\"package_types_relation\":null,\"postal_code\":null,\"postal_codes_relation\":null,\"province_id\":null,\"province_relation\":null,\"provinces_relation\":null,\"subdistrict_id\":null,\"subdistrict_relation\":null,\"subdistricts_relation\":null,\"telp\":null,\"updated_by\":null,\"updated_on\":null,\"users_roless_relation\":null,\"userss_relation\":null},\"name\":\"Administrator\",\"role_key\":\"ADM_MERCHANT\",\"updated_by\":null,\"updated_on\":\"2026-08-10T02:51:43.16495Z\",\"userss_relation\":null}}"}
{"level":"info","ts":"2026-08-17T14:45:05.466+0700","caller":"handlers/handlers.go:66","msg":"Request Success","status":200,"method":"GET","path":"/master-data/provinces/","query":"name=aceh","ip":"127.0.0.1","latency":0.093297541,"user-agent":"Apidog/1.0.0 (https://apidog.com)"}
{"level":"info","ts":"2026-08-17T14:50:33.706+0700","caller":"database/database.go:53","msg":"Connect To Master DB"}
{"level":"info","ts":"2026-08-17T14:50:33.793+0700","caller":"database/database.go:63","msg":"DB Master Connected"}
{"level":"info","ts":"2026-08-17T14:50:33.794+0700","caller":"database/database.go:76","msg":"Connect To Slave DB"}
{"level":"info","ts":"2026-08-17T14:50:33.875+0700","caller":"database/database.go:86","msg":"DB Connection Done"}
{"level":"info","ts":"2026-08-17T14:50:34.004+0700","caller":"handlers/handlers.go:113","msg":"Starting Apps"}
{"level":"info","ts":"2026-08-17T14:50:41.249+0700","caller":"middleware/auth.go:62","msg":"session_login : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiIxOWZmMTA0YzE2NTIyYTIyNTJjYmFjYTI0ZCIsImlzcyI6ImNhcmdvLXBsYXRmb3JtIiwic3ViIjoiMTlmZjEwNGMxNjUyMmEyMjUyY2JhY2EyNGQiLCJleHAiOjE3ODY5NTYwNTgsImlhdCI6MTc4Njk1MjQ1OH0.TZXGJLNng6TyfzspOkA6HuEyFi61K-wnie_L6ybYzyY"}
{"level":"info","ts":"2026-08-17T14:50:41.276+0700","caller":"middleware/auth.go:90","msg":"Set Auth : {\"blocked\":false,\"blocked_notes\":null,\"citys_relation\":null,\"created_by\":null,\"created_on\":\"2026-08-11T20:30:48.554654Z\",\"districts_relation\":null,\"email\":\"gcx@gmail.com\",\"id\":\"19ff104c16522a2252cbaca24d\",\"is_active\":true,\"merchant_id\":\"19ff111bca6c2c382d9eb43b3c\",\"merchant_relation\":null,\"merchants_relation\":null,\"nick_name\":\"Administrator\",\"postal_codes_relation\":null,\"provinces_relation\":null,\"subdistricts_relation\":null,\"updated_by\":null,\"updated_on\":\"2026-08-11T20:30:48.554654Z\",\"users_role_id\":\"19fe814c5350d67814c3e6057c\",\"users_role_relation\":{\"created_by\":null,\"created_on\":\"2026-08-10T02:51:43.16495Z\",\"id\":\"19fe814c5350d67814c3e6057c\",\"merchant_id\":null,\"merchant_relation\":{\"active\":null,\"additional_information\":null,\"address\":null,\"city_id\":null,\"city_relation\":null,\"citys_relation\":null,\"created_by\":null,\"created_by_relation\":null,\"created_on\":null,\"district_id\":null,\"district_relation\":null,\"districts_relation\":null,\"email\":null,\"id\":\"\",\"modas_relation\":null,\"name\":null,\"package_types_relation\":null,\"postal_code\":null,\"postal_codes_relation\":null,\"province_id\":null,\"province_relation\":null,\"provinces_relation\":null,\"subdistrict_id\":null,\"subdistrict_relation\":null,\"subdistricts_relation\":null,\"telp\":null,\"updated_by\":null,\"updated_on\":null,\"users_roless_relation\":null,\"userss_relation\":null},\"name\":\"Administrator\",\"role_key\":\"ADM_MERCHANT\",\"updated_by\":null,\"updated_on\":\"2026-08-10T02:51:43.16495Z\",\"userss_relation\":null}}"}
{"level":"info","ts":"2026-08-17T14:59:52.379+0700","caller":"database/database.go:53","msg":"Connect To Master DB"}
{"level":"info","ts":"2026-08-17T14:59:52.469+0700","caller":"database/database.go:63","msg":"DB Master Connected"}
{"level":"info","ts":"2026-08-17T14:59:52.469+0700","caller":"database/database.go:76","msg":"Connect To Slave DB"}
{"level":"info","ts":"2026-08-17T14:59:52.543+0700","caller":"database/database.go:86","msg":"DB Connection Done"}
{"level":"info","ts":"2026-08-17T14:59:52.653+0700","caller":"handlers/handlers.go:113","msg":"Starting Apps"}
{"level":"info","ts":"2026-08-17T14:59:56.806+0700","caller":"middleware/auth.go:62","msg":"session_login : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiIxOWZmMTA0YzE2NTIyYTIyNTJjYmFjYTI0ZCIsImlzcyI6ImNhcmdvLXBsYXRmb3JtIiwic3ViIjoiMTlmZjEwNGMxNjUyMmEyMjUyY2JhY2EyNGQiLCJleHAiOjE3ODY5NTYwNTgsImlhdCI6MTc4Njk1MjQ1OH0.TZXGJLNng6TyfzspOkA6HuEyFi61K-wnie_L6ybYzyY"}
{"level":"info","ts":"2026-08-17T14:59:56.837+0700","caller":"middleware/auth.go:90","msg":"Set Auth : {\"blocked\":false,\"blocked_notes\":null,\"citys_relation\":null,\"created_by\":null,\"created_on\":\"2026-08-11T20:30:48.554654Z\",\"districts_relation\":null,\"email\":\"gcx@gmail.com\",\"id\":\"19ff104c16522a2252cbaca24d\",\"is_active\":true,\"merchant_id\":\"19ff111bca6c2c382d9eb43b3c\",\"merchant_relation\":null,\"merchants_relation\":null,\"nick_name\":\"Administrator\",\"postal_codes_relation\":null,\"provinces_relation\":null,\"subdistricts_relation\":null,\"updated_by\":null,\"updated_on\":\"2026-08-11T20:30:48.554654Z\",\"users_role_id\":\"19fe814c5350d67814c3e6057c\",\"users_role_relation\":{\"created_by\":null,\"created_on\":\"2026-08-10T02:51:43.16495Z\",\"id\":\"19fe814c5350d67814c3e6057c\",\"merchant_id\":null,\"merchant_relation\":{\"active\":null,\"additional_information\":null,\"address\":null,\"city_id\":null,\"city_relation\":null,\"citys_relation\":null,\"created_by\":null,\"created_by_relation\":null,\"created_on\":null,\"district_id\":null,\"district_relation\":null,\"districts_relation\":null,\"email\":null,\"id\":\"\",\"modas_relation\":null,\"name\":null,\"package_types_relation\":null,\"postal_code\":null,\"postal_codes_relation\":null,\"province_id\":null,\"province_relation\":null,\"provinces_relation\":null,\"subdistrict_id\":null,\"subdistrict_relation\":null,\"subdistricts_relation\":null,\"telp\":null,\"updated_by\":null,\"updated_on\":null,\"users_roless_relation\":null,\"userss_relation\":null},\"name\":\"Administrator\",\"role_key\":\"ADM_MERCHANT\",\"updated_by\":null,\"updated_on\":\"2026-08-10T02:51:43.16495Z\",\"userss_relation\":null}}"}
{"level":"info","ts":"2026-08-17T14:59:56.919+0700","caller":"handlers/handlers.go:66","msg":"Request Success","status":200,"method":"GET","path":"/master-data/provinces/","query":"name=aceh","ip":"127.0.0.1","latency":0.113733667,"user-agent":"Apidog/1.0.0 (https://apidog.com)"}
{"level":"info","ts":"2026-08-17T15:02:00.031+0700","caller":"middleware/auth.go:62","msg":"session_login : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiIxOWZmMTA0YzE2NTIyYTIyNTJjYmFjYTI0ZCIsImlzcyI6ImNhcmdvLXBsYXRmb3JtIiwic3ViIjoiMTlmZjEwNGMxNjUyMmEyMjUyY2JhY2EyNGQiLCJleHAiOjE3ODY5NTYwNTgsImlhdCI6MTc4Njk1MjQ1OH0.TZXGJLNng6TyfzspOkA6HuEyFi61K-wnie_L6ybYzyY"}
{"level":"info","ts":"2026-08-17T15:02:00.064+0700","caller":"middleware/auth.go:90","msg":"Set Auth : {\"blocked\":false,\"blocked_notes\":null,\"citys_relation\":null,\"created_by\":null,\"created_on\":\"2026-08-11T20:30:48.554654Z\",\"districts_relation\":null,\"email\":\"gcx@gmail.com\",\"id\":\"19ff104c16522a2252cbaca24d\",\"is_active\":true,\"merchant_id\":\"19ff111bca6c2c382d9eb43b3c\",\"merchant_relation\":null,\"merchants_relation\":null,\"nick_name\":\"Administrator\",\"postal_codes_relation\":null,\"provinces_relation\":null,\"subdistricts_relation\":null,\"updated_by\":null,\"updated_on\":\"2026-08-11T20:30:48.554654Z\",\"users_role_id\":\"19fe814c5350d67814c3e6057c\",\"users_role_relation\":{\"created_by\":null,\"created_on\":\"2026-08-10T02:51:43.16495Z\",\"id\":\"19fe814c5350d67814c3e6057c\",\"merchant_id\":null,\"merchant_relation\":{\"active\":null,\"additional_information\":null,\"address\":null,\"city_id\":null,\"city_relation\":null,\"citys_relation\":null,\"created_by\":null,\"created_by_relation\":null,\"created_on\":null,\"district_id\":null,\"district_relation\":null,\"districts_relation\":null,\"email\":null,\"id\":\"\",\"modas_relation\":null,\"name\":null,\"package_types_relation\":null,\"postal_code\":null,\"postal_codes_relation\":null,\"province_id\":null,\"province_relation\":null,\"provinces_relation\":null,\"subdistrict_id\":null,\"subdistrict_relation\":null,\"subdistricts_relation\":null,\"telp\":null,\"updated_by\":null,\"updated_on\":null,\"users_roless_relation\":null,\"userss_relation\":null},\"name\":\"Administrator\",\"role_key\":\"ADM_MERCHANT\",\"updated_by\":null,\"updated_on\":\"2026-08-10T02:51:43.16495Z\",\"userss_relation\":null}}"}
{"level":"info","ts":"2026-08-17T15:02:00.145+0700","caller":"handlers/handlers.go:66","msg":"Request Success","status":200,"method":"GET","path":"/master-data/provinces/","query":"name=ACEH","ip":"127.0.0.1","latency":0.116235959,"user-agent":"Apidog/1.0.0 (https://apidog.com)"}
{"level":"info","ts":"2026-08-17T15:28:06.336+0700","caller":"database/database.go:53","msg":"Connect To Master DB"}
{"level":"info","ts":"2026-08-17T15:28:06.443+0700","caller":"database/database.go:63","msg":"DB Master Connected"}
{"level":"info","ts":"2026-08-17T15:28:06.443+0700","caller":"database/database.go:76","msg":"Connect To Slave DB"}
{"level":"info","ts":"2026-08-17T15:28:06.515+0700","caller":"database/database.go:86","msg":"DB Connection Done"}
{"level":"info","ts":"2026-08-17T15:28:06.623+0700","caller":"handlers/handlers.go:113","msg":"Starting Apps"}
{"level":"info","ts":"2026-08-17T15:29:04.258+0700","caller":"middleware/auth.go:62","msg":"session_login : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiIxOWZmMTA0YzE2NTIyYTIyNTJjYmFjYTI0ZCIsImlzcyI6ImNhcmdvLXBsYXRmb3JtIiwic3ViIjoiMTlmZjEwNGMxNjUyMmEyMjUyY2JhY2EyNGQiLCJleHAiOjE3ODY5NTYwNTgsImlhdCI6MTc4Njk1MjQ1OH0.TZXGJLNng6TyfzspOkA6HuEyFi61K-wnie_L6ybYzyY"}
{"level":"info","ts":"2026-08-17T15:29:04.289+0700","caller":"middleware/auth.go:90","msg":"Set Auth : {\"blocked\":false,\"blocked_notes\":null,\"citys_relation\":null,\"created_by\":null,\"created_on\":\"2026-08-11T20:30:48.554654Z\",\"districts_relation\":null,\"email\":\"gcx@gmail.com\",\"id\":\"19ff104c16522a2252cbaca24d\",\"is_active\":true,\"merchant_id\":\"19ff111bca6c2c382d9eb43b3c\",\"merchant_relation\":null,\"merchants_relation\":null,\"nick_name\":\"Administrator\",\"postal_codes_relation\":null,\"provinces_relation\":null,\"subdistricts_relation\":null,\"updated_by\":null,\"updated_on\":\"2026-08-11T20:30:48.554654Z\",\"users_role_id\":\"19fe814c5350d67814c3e6057c\",\"users_role_relation\":{\"created_by\":null,\"created_on\":\"2026-08-10T02:51:43.16495Z\",\"id\":\"19fe814c5350d67814c3e6057c\",\"merchant_id\":null,\"merchant_relation\":{\"active\":null,\"additional_information\":null,\"address\":null,\"city_id\":null,\"city_relation\":null,\"citys_relation\":null,\"created_by\":null,\"created_by_relation\":null,\"created_on\":null,\"district_id\":null,\"district_relation\":null,\"districts_relation\":null,\"email\":null,\"id\":\"\",\"modas_relation\":null,\"name\":null,\"package_types_relation\":null,\"postal_code\":null,\"postal_codes_relation\":null,\"province_id\":null,\"province_relation\":null,\"provinces_relation\":null,\"subdistrict_id\":null,\"subdistrict_relation\":null,\"subdistricts_relation\":null,\"telp\":null,\"updated_by\":null,\"updated_on\":null,\"users_roless_relation\":null,\"userss_relation\":null},\"name\":\"Administrator\",\"role_key\":\"ADM_MERCHANT\",\"updated_by\":null,\"updated_on\":\"2026-08-10T02:51:43.16495Z\",\"userss_relation\":null}}"}
{"level":"info","ts":"2026-08-17T15:29:04.291+0700","caller":"handlers/handlers.go:66","msg":"Request Success","status":200,"method":"GET","path":"/master-data/provinces/template","query":"","ip":"127.0.0.1","latency":0.034353167,"user-agent":"Apidog/1.0.0 (https://apidog.com)"}
{"level":"info","ts":"2026-08-17T18:38:50.151+0700","caller":"database/database.go:53","msg":"Connect To Master DB"}
{"level":"info","ts":"2026-08-17T18:38:50.222+0700","caller":"database/database.go:63","msg":"DB Master Connected"}
{"level":"info","ts":"2026-08-17T18:38:50.223+0700","caller":"database/database.go:76","msg":"Connect To Slave DB"}
{"level":"info","ts":"2026-08-17T18:38:50.274+0700","caller":"database/database.go:86","msg":"DB Connection Done"}
{"level":"info","ts":"2026-08-17T18:38:50.356+0700","caller":"handlers/handlers.go:113","msg":"Starting Apps"}
{"level":"info","ts":"2026-08-17T18:39:33.406+0700","caller":"database/database.go:53","msg":"Connect To Master DB"}
{"level":"info","ts":"2026-08-17T18:39:33.467+0700","caller":"database/database.go:63","msg":"DB Master Connected"}
{"level":"info","ts":"2026-08-17T18:39:33.467+0700","caller":"database/database.go:76","msg":"Connect To Slave DB"}
{"level":"info","ts":"2026-08-17T18:39:33.514+0700","caller":"database/database.go:86","msg":"DB Connection Done"}
{"level":"info","ts":"2026-08-17T18:39:33.609+0700","caller":"handlers/handlers.go:113","msg":"Starting Apps"}

View File

@ -3358,3 +3358,108 @@
{"level":"info","ts":"2026-08-17T14:21:30.235+0700","caller":"database/redis.go:49","msg":"Connecting to Redis slave"} {"level":"info","ts":"2026-08-17T14:21:30.235+0700","caller":"database/redis.go:49","msg":"Connecting to Redis slave"}
{"level":"info","ts":"2026-08-17T14:21:30.290+0700","caller":"database/redis.go:62","msg":"Connected"} {"level":"info","ts":"2026-08-17T14:21:30.290+0700","caller":"database/redis.go:62","msg":"Connected"}
{"level":"info","ts":"2026-08-17T14:21:37.620+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/province.go:100","elapsed":0.064875875,"rows":1,"sql":"INSERT INTO \"province\" (\"name\",\"created_by\",\"created_on\",\"updated_by\",\"updated_on\",\"merchant_id\") VALUES ('tambahan','19ff104c16522a2252cbaca24d','2026-08-17 14:21:37.555','19ff104c16522a2252cbaca24d','2026-08-17 14:21:37.555','19ff111bca6c2c382d9eb43b3c') RETURNING \"id\""} {"level":"info","ts":"2026-08-17T14:21:37.620+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/province.go:100","elapsed":0.064875875,"rows":1,"sql":"INSERT INTO \"province\" (\"name\",\"created_by\",\"created_on\",\"updated_by\",\"updated_on\",\"merchant_id\") VALUES ('tambahan','19ff104c16522a2252cbaca24d','2026-08-17 14:21:37.555','19ff104c16522a2252cbaca24d','2026-08-17 14:21:37.555','19ff111bca6c2c382d9eb43b3c') RETURNING \"id\""}
{"level":"info","ts":"2026-08-17T14:25:11.752+0700","caller":"database/redis.go:34","msg":"Parse config slave"}
{"level":"info","ts":"2026-08-17T14:25:11.753+0700","caller":"database/redis.go:49","msg":"Connecting to Redis slave"}
{"level":"info","ts":"2026-08-17T14:25:11.802+0700","caller":"database/redis.go:62","msg":"Connected"}
{"level":"info","ts":"2026-08-17T14:25:11.802+0700","caller":"database/redis.go:34","msg":"Parse config master"}
{"level":"info","ts":"2026-08-17T14:25:11.802+0700","caller":"database/redis.go:49","msg":"Connecting to Redis master"}
{"level":"info","ts":"2026-08-17T14:25:11.856+0700","caller":"database/redis.go:62","msg":"Connected"}
{"level":"info","ts":"2026-08-17T14:25:14.035+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/province.go:135","elapsed":0.062923208,"rows":1,"sql":"DELETE FROM \"province\" WHERE id = '1a00e98ea5f87d9a1026ba6441'"}
{"level":"info","ts":"2026-08-17T14:25:32.594+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/province.go:100","elapsed":0.081571459,"rows":1,"sql":"INSERT INTO \"province\" (\"name\",\"created_by\",\"created_on\",\"updated_by\",\"updated_on\",\"merchant_id\") VALUES ('tambahan','19ff104c16522a2252cbaca24d','2026-08-17 14:25:32.504','19ff104c16522a2252cbaca24d','2026-08-17 14:25:32.504','19ff111bca6c2c382d9eb43b3c') RETURNING \"id\""}
{"level":"info","ts":"2026-08-17T14:29:01.363+0700","caller":"database/redis.go:34","msg":"Parse config master"}
{"level":"info","ts":"2026-08-17T14:29:01.363+0700","caller":"database/redis.go:49","msg":"Connecting to Redis master"}
{"level":"info","ts":"2026-08-17T14:29:01.426+0700","caller":"database/redis.go:62","msg":"Connected"}
{"level":"info","ts":"2026-08-17T14:29:01.426+0700","caller":"database/redis.go:34","msg":"Parse config slave"}
{"level":"info","ts":"2026-08-17T14:29:01.426+0700","caller":"database/redis.go:49","msg":"Connecting to Redis slave"}
{"level":"info","ts":"2026-08-17T14:29:01.481+0700","caller":"database/redis.go:62","msg":"Connected"}
{"level":"info","ts":"2026-08-17T14:29:02.013+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/province.go:114","elapsed":0.044179208,"rows":1,"sql":"SELECT * FROM \"province\" WHERE id = '1a00e9c80365466b3d2a04918c' ORDER BY \"province\".\"id\" LIMIT 1"}
{"level":"info","ts":"2026-08-17T14:29:02.064+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/province.go:123","elapsed":0.050348583,"rows":1,"sql":"UPDATE \"province\" SET \"name\"='tambahan xxx',\"created_by\"='19ff104c16522a2252cbaca24d',\"created_on\"='2026-08-17 14:25:32.504',\"updated_by\"='19ff104c16522a2252cbaca24d',\"updated_on\"='2026-08-17 14:29:02.024',\"merchant_id\"='19ff111bca6c2c382d9eb43b3c' WHERE \"id\" = '1a00e9c80365466b3d2a04918c'"}
{"level":"info","ts":"2026-08-17T14:30:30.424+0700","caller":"database/redis.go:34","msg":"Parse config slave"}
{"level":"info","ts":"2026-08-17T14:30:30.424+0700","caller":"database/redis.go:49","msg":"Connecting to Redis slave"}
{"level":"info","ts":"2026-08-17T14:30:30.477+0700","caller":"database/redis.go:62","msg":"Connected"}
{"level":"info","ts":"2026-08-17T14:30:30.477+0700","caller":"database/redis.go:34","msg":"Parse config master"}
{"level":"info","ts":"2026-08-17T14:30:30.477+0700","caller":"database/redis.go:49","msg":"Connecting to Redis master"}
{"level":"info","ts":"2026-08-17T14:30:30.531+0700","caller":"database/redis.go:62","msg":"Connected"}
{"level":"info","ts":"2026-08-17T14:30:34.384+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/province.go:114","elapsed":0.040815125,"rows":1,"sql":"SELECT * FROM \"province\" WHERE id = '1a00e9c80365466b3d2a04918c' ORDER BY \"province\".\"id\" LIMIT 1"}
{"level":"info","ts":"2026-08-17T14:30:34.428+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/province.go:123","elapsed":0.043398083,"rows":1,"sql":"UPDATE \"province\" SET \"name\"='tambahan xxx',\"created_by\"='19ff104c16522a2252cbaca24d',\"created_on\"='2026-08-17 14:25:32.504',\"updated_by\"='19ff104c16522a2252cbaca24d',\"updated_on\"='2026-08-17 14:30:34.395',\"merchant_id\"='19ff111bca6c2c382d9eb43b3c' WHERE \"id\" = '1a00e9c80365466b3d2a04918c'"}
{"level":"info","ts":"2026-08-17T14:32:39.655+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/province.go:135","elapsed":0.056869167,"rows":1,"sql":"DELETE FROM \"province\" WHERE id = '1a00e9c80365466b3d2a04918c'"}
{"level":"info","ts":"2026-08-17T14:38:28.024+0700","caller":"database/redis.go:34","msg":"Parse config master"}
{"level":"info","ts":"2026-08-17T14:38:28.025+0700","caller":"database/redis.go:49","msg":"Connecting to Redis master"}
{"level":"info","ts":"2026-08-17T14:38:28.076+0700","caller":"database/redis.go:62","msg":"Connected"}
{"level":"info","ts":"2026-08-17T14:38:28.076+0700","caller":"database/redis.go:34","msg":"Parse config slave"}
{"level":"info","ts":"2026-08-17T14:38:28.076+0700","caller":"database/redis.go:49","msg":"Connecting to Redis slave"}
{"level":"info","ts":"2026-08-17T14:38:28.134+0700","caller":"database/redis.go:62","msg":"Connected"}
{"level":"info","ts":"2026-08-17T14:39:10.622+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/province.go:37","elapsed":0.040459209,"rows":1,"sql":"SELECT count(*) FROM \"province\""}
{"level":"info","ts":"2026-08-17T14:39:10.653+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/province.go:39","elapsed":0.030940792,"rows":34,"sql":"SELECT * FROM \"province\""}
{"level":"info","ts":"2026-08-17T14:39:47.543+0700","caller":"database/redis.go:34","msg":"Parse config master"}
{"level":"info","ts":"2026-08-17T14:39:47.543+0700","caller":"database/redis.go:49","msg":"Connecting to Redis master"}
{"level":"info","ts":"2026-08-17T14:39:47.599+0700","caller":"database/redis.go:62","msg":"Connected"}
{"level":"info","ts":"2026-08-17T14:39:47.600+0700","caller":"database/redis.go:34","msg":"Parse config slave"}
{"level":"info","ts":"2026-08-17T14:39:47.600+0700","caller":"database/redis.go:49","msg":"Connecting to Redis slave"}
{"level":"info","ts":"2026-08-17T14:39:47.656+0700","caller":"database/redis.go:62","msg":"Connected"}
{"level":"info","ts":"2026-08-17T14:39:50.204+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/province.go:37","elapsed":0.039307917,"rows":1,"sql":"SELECT count(*) FROM \"province\""}
{"level":"info","ts":"2026-08-17T14:39:50.234+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/province.go:39","elapsed":0.029045833,"rows":34,"sql":"SELECT * FROM \"province\""}
{"level":"info","ts":"2026-08-17T14:40:13.574+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/province.go:37","elapsed":0.028055875,"rows":1,"sql":"SELECT count(*) FROM \"province\""}
{"level":"info","ts":"2026-08-17T14:40:13.588+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/province.go:39","elapsed":0.013114458,"rows":34,"sql":"SELECT * FROM \"province\""}
{"level":"info","ts":"2026-08-17T14:40:47.976+0700","caller":"database/redis.go:34","msg":"Parse config master"}
{"level":"info","ts":"2026-08-17T14:40:47.976+0700","caller":"database/redis.go:49","msg":"Connecting to Redis master"}
{"level":"info","ts":"2026-08-17T14:40:48.031+0700","caller":"database/redis.go:62","msg":"Connected"}
{"level":"info","ts":"2026-08-17T14:40:48.031+0700","caller":"database/redis.go:34","msg":"Parse config slave"}
{"level":"info","ts":"2026-08-17T14:40:48.031+0700","caller":"database/redis.go:49","msg":"Connecting to Redis slave"}
{"level":"info","ts":"2026-08-17T14:40:48.088+0700","caller":"database/redis.go:62","msg":"Connected"}
{"level":"info","ts":"2026-08-17T14:40:58.695+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/auth.go:29","elapsed":0.020184542,"rows":1,"sql":"SELECT * FROM \"users_roles\" WHERE \"users_roles\".\"id\" = '19fe814c5350d67814c3e6057c'"}
{"level":"info","ts":"2026-08-17T14:40:58.697+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/auth.go:29","elapsed":0.072182583,"rows":1,"sql":"SELECT * FROM \"users\" WHERE email='gcx@gmail.com'"}
{"level":"info","ts":"2026-08-17T14:41:02.382+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/province.go:37","elapsed":0.064269583,"rows":1,"sql":"SELECT count(*) FROM \"province\""}
{"level":"info","ts":"2026-08-17T14:41:02.440+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/province.go:39","elapsed":0.056326958,"rows":34,"sql":"SELECT * FROM \"province\""}
{"level":"info","ts":"2026-08-17T14:42:38.180+0700","caller":"database/redis.go:34","msg":"Parse config slave"}
{"level":"info","ts":"2026-08-17T14:42:38.180+0700","caller":"database/redis.go:49","msg":"Connecting to Redis slave"}
{"level":"info","ts":"2026-08-17T14:42:38.234+0700","caller":"database/redis.go:62","msg":"Connected"}
{"level":"info","ts":"2026-08-17T14:42:38.234+0700","caller":"database/redis.go:34","msg":"Parse config master"}
{"level":"info","ts":"2026-08-17T14:42:38.234+0700","caller":"database/redis.go:49","msg":"Connecting to Redis master"}
{"level":"info","ts":"2026-08-17T14:42:38.286+0700","caller":"database/redis.go:62","msg":"Connected"}
{"level":"info","ts":"2026-08-17T14:42:40.080+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/province.go:38","elapsed":0.03832,"rows":1,"sql":"SELECT count(*) FROM \"province\" WHERE merchant_id IS NULL OR merchant_id='19ff111bca6c2c382d9eb43b3c'"}
{"level":"info","ts":"2026-08-17T14:42:40.111+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/province.go:40","elapsed":0.029813584,"rows":34,"sql":"SELECT * FROM \"province\""}
{"level":"info","ts":"2026-08-17T14:43:03.513+0700","caller":"database/redis.go:34","msg":"Parse config slave"}
{"level":"info","ts":"2026-08-17T14:43:03.513+0700","caller":"database/redis.go:49","msg":"Connecting to Redis slave"}
{"level":"info","ts":"2026-08-17T14:43:03.569+0700","caller":"database/redis.go:62","msg":"Connected"}
{"level":"info","ts":"2026-08-17T14:43:03.569+0700","caller":"database/redis.go:34","msg":"Parse config master"}
{"level":"info","ts":"2026-08-17T14:43:03.569+0700","caller":"database/redis.go:49","msg":"Connecting to Redis master"}
{"level":"info","ts":"2026-08-17T14:43:03.623+0700","caller":"database/redis.go:62","msg":"Connected"}
{"level":"info","ts":"2026-08-17T14:43:03.715+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/province.go:38","elapsed":0.033541541,"rows":1,"sql":"SELECT count(*) FROM \"province\" WHERE merchant_id IS NULL OR merchant_id='19ff111bca6c2c382d9eb43b3c'"}
{"level":"info","ts":"2026-08-17T14:43:03.742+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/province.go:40","elapsed":0.026353084,"rows":34,"sql":"SELECT * FROM \"province\" WHERE merchant_id IS NULL OR merchant_id='19ff111bca6c2c382d9eb43b3c' ORDER BY name ASC"}
{"level":"info","ts":"2026-08-17T14:45:05.446+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/province.go:38","elapsed":0.032110583,"rows":1,"sql":"SELECT count(*) FROM \"province\" WHERE merchant_id IS NULL OR merchant_id='19ff111bca6c2c382d9eb43b3c'"}
{"level":"info","ts":"2026-08-17T14:45:05.465+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/province.go:40","elapsed":0.017382292,"rows":34,"sql":"SELECT * FROM \"province\" WHERE merchant_id IS NULL OR merchant_id='19ff111bca6c2c382d9eb43b3c' ORDER BY name ASC"}
{"level":"info","ts":"2026-08-17T14:50:33.875+0700","caller":"database/redis.go:34","msg":"Parse config master"}
{"level":"info","ts":"2026-08-17T14:50:33.876+0700","caller":"database/redis.go:49","msg":"Connecting to Redis master"}
{"level":"info","ts":"2026-08-17T14:50:33.933+0700","caller":"database/redis.go:62","msg":"Connected"}
{"level":"info","ts":"2026-08-17T14:50:33.934+0700","caller":"database/redis.go:34","msg":"Parse config slave"}
{"level":"info","ts":"2026-08-17T14:50:33.934+0700","caller":"database/redis.go:49","msg":"Connecting to Redis slave"}
{"level":"info","ts":"2026-08-17T14:50:34.000+0700","caller":"database/redis.go:62","msg":"Connected"}
{"level":"info","ts":"2026-08-17T14:59:52.543+0700","caller":"database/redis.go:34","msg":"Parse config slave"}
{"level":"info","ts":"2026-08-17T14:59:52.544+0700","caller":"database/redis.go:49","msg":"Connecting to Redis slave"}
{"level":"info","ts":"2026-08-17T14:59:52.598+0700","caller":"database/redis.go:62","msg":"Connected"}
{"level":"info","ts":"2026-08-17T14:59:52.599+0700","caller":"database/redis.go:34","msg":"Parse config master"}
{"level":"info","ts":"2026-08-17T14:59:52.599+0700","caller":"database/redis.go:49","msg":"Connecting to Redis master"}
{"level":"info","ts":"2026-08-17T14:59:52.652+0700","caller":"database/redis.go:62","msg":"Connected"}
{"level":"info","ts":"2026-08-17T14:59:56.871+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/province.go:51","elapsed":0.033292084,"rows":1,"sql":"SELECT count(*) FROM \"province\" WHERE (merchant_id IS NULL OR merchant_id='19ff111bca6c2c382d9eb43b3c') AND name='aceh'"}
{"level":"info","ts":"2026-08-17T14:59:56.918+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/province.go:53","elapsed":0.046337875,"rows":0,"sql":"SELECT * FROM \"province\" WHERE (merchant_id IS NULL OR merchant_id='19ff111bca6c2c382d9eb43b3c') AND name='aceh' ORDER BY name ASC"}
{"level":"info","ts":"2026-08-17T15:02:00.123+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/province.go:51","elapsed":0.03923575,"rows":1,"sql":"SELECT count(*) FROM \"province\" WHERE (merchant_id IS NULL OR merchant_id='19ff111bca6c2c382d9eb43b3c') AND name='ACEH'"}
{"level":"info","ts":"2026-08-17T15:02:00.143+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/internal/usecases/province.go:53","elapsed":0.018068625,"rows":1,"sql":"SELECT * FROM \"province\" WHERE (merchant_id IS NULL OR merchant_id='19ff111bca6c2c382d9eb43b3c') AND name='ACEH' ORDER BY name ASC"}
{"level":"info","ts":"2026-08-17T15:28:06.516+0700","caller":"database/redis.go:34","msg":"Parse config master"}
{"level":"info","ts":"2026-08-17T15:28:06.516+0700","caller":"database/redis.go:49","msg":"Connecting to Redis master"}
{"level":"info","ts":"2026-08-17T15:28:06.572+0700","caller":"database/redis.go:62","msg":"Connected"}
{"level":"info","ts":"2026-08-17T15:28:06.572+0700","caller":"database/redis.go:34","msg":"Parse config slave"}
{"level":"info","ts":"2026-08-17T15:28:06.572+0700","caller":"database/redis.go:49","msg":"Connecting to Redis slave"}
{"level":"info","ts":"2026-08-17T15:28:06.622+0700","caller":"database/redis.go:62","msg":"Connected"}
{"level":"info","ts":"2026-08-17T18:38:50.279+0700","caller":"database/redis.go:34","msg":"Parse config master"}
{"level":"info","ts":"2026-08-17T18:38:50.279+0700","caller":"database/redis.go:49","msg":"Connecting to Redis master"}
{"level":"info","ts":"2026-08-17T18:38:50.317+0700","caller":"database/redis.go:62","msg":"Connected"}
{"level":"info","ts":"2026-08-17T18:38:50.317+0700","caller":"database/redis.go:34","msg":"Parse config slave"}
{"level":"info","ts":"2026-08-17T18:38:50.317+0700","caller":"database/redis.go:49","msg":"Connecting to Redis slave"}
{"level":"info","ts":"2026-08-17T18:38:50.355+0700","caller":"database/redis.go:62","msg":"Connected"}
{"level":"info","ts":"2026-08-17T18:39:33.514+0700","caller":"database/redis.go:34","msg":"Parse config master"}
{"level":"info","ts":"2026-08-17T18:39:33.515+0700","caller":"database/redis.go:49","msg":"Connecting to Redis master"}
{"level":"info","ts":"2026-08-17T18:39:33.550+0700","caller":"database/redis.go:62","msg":"Connected"}
{"level":"info","ts":"2026-08-17T18:39:33.550+0700","caller":"database/redis.go:34","msg":"Parse config slave"}
{"level":"info","ts":"2026-08-17T18:39:33.551+0700","caller":"database/redis.go:49","msg":"Connecting to Redis slave"}
{"level":"info","ts":"2026-08-17T18:39:33.584+0700","caller":"database/redis.go:62","msg":"Connected"}