diff --git a/internal/handlers/dto/postal_code.go b/internal/handlers/dto/postal_code.go new file mode 100644 index 0000000..86dd8c1 --- /dev/null +++ b/internal/handlers/dto/postal_code.go @@ -0,0 +1,48 @@ +package dto + +type CreatePostalCodeRequest struct { + PostalCode string `json:"postal_code" binding:"required"` + ProvinceID string `json:"province_id" binding:"required"` + CityID string `json:"city_id" binding:"required"` + DistrictID string `json:"district_id" binding:"required"` + SubdistrictID string `json:"subdistrict_id" binding:"required"` +} + +type CreatePostalCodeResponse struct { + ID string `json:"id"` + PostalCode *string `json:"postal_code"` + ProvinceID *string `json:"province_id"` + CityID *string `json:"city_id"` + DistrictID *string `json:"district_id"` + SubdistrictID *string `json:"subdistrict_id"` + MerchantID *string `json:"merchant_id"` +} + +type PostalCodeFilterAllRequest struct { + ID string `json:"id" form:"id"` + PostalCode string `json:"postal_code" form:"postal_code"` + ProvinceID string `json:"province_id" form:"province_id"` + CityID string `json:"city_id" form:"city_id"` + DistrictID string `json:"district_id" form:"district_id"` + SubdistrictID string `json:"subdistrict_id" form:"subdistrict_id"` + MerchantID string `json:"merchant_id" form:"merchant_id"` +} + +type UpdatePostalCodeRequest struct { + PostalCode string `json:"postal_code" binding:"required"` + ProvinceID string `json:"province_id" binding:"required"` + CityID string `json:"city_id" binding:"required"` + DistrictID string `json:"district_id" binding:"required"` + SubdistrictID string `json:"subdistrict_id" binding:"required"` +} + +type ImportPostalCodeResponse 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"` +} diff --git a/internal/handlers/dto/province.go b/internal/handlers/dto/province.go index b9af877..ef3119e 100644 --- a/internal/handlers/dto/province.go +++ b/internal/handlers/dto/province.go @@ -27,14 +27,14 @@ type ImportLog struct { } 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"` + 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 StopImportRequest struct { diff --git a/internal/handlers/dto/subdistrict.go b/internal/handlers/dto/subdistrict.go new file mode 100644 index 0000000..f1bbe2b --- /dev/null +++ b/internal/handlers/dto/subdistrict.go @@ -0,0 +1,44 @@ +package dto + +type CreateSubdistrictRequest struct { + Name string `json:"name" binding:"required,min=2"` + ProvinceID string `json:"province_id" binding:"required"` + CityID string `json:"city_id" binding:"required"` + DistrictID string `json:"district_id" binding:"required"` +} + +type CreateSubdistrictResponse struct { + ID string `json:"id"` + Name *string `json:"name"` + ProvinceID *string `json:"province_id"` + CityID *string `json:"city_id"` + DistrictID *string `json:"district_id"` + MerchantID *string `json:"merchant_id"` +} + +type SubdistrictFilterAllRequest struct { + ID string `json:"id" form:"id"` + Name string `json:"name" form:"name"` + ProvinceID string `json:"province_id" form:"province_id"` + CityID string `json:"city_id" form:"city_id"` + DistrictID string `json:"district_id" form:"district_id"` + MerchantID string `json:"merchant_id" form:"merchant_id"` +} + +type UpdateSubdistrictRequest struct { + Name string `json:"name" binding:"required,min=2"` + ProvinceID string `json:"province_id" binding:"required"` + CityID string `json:"city_id" binding:"required"` + DistrictID string `json:"district_id" binding:"required"` +} + +type ImportSubdistrictResponse 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"` +} diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go index 8eced32..8aa12ee 100644 --- a/internal/handlers/handlers.go +++ b/internal/handlers/handlers.go @@ -4,7 +4,9 @@ import ( "cargo-erp-backend/internal/handlers/router/auth" "cargo-erp-backend/internal/handlers/router/city" "cargo-erp-backend/internal/handlers/router/district" + "cargo-erp-backend/internal/handlers/router/postal_code" "cargo-erp-backend/internal/handlers/router/province" + "cargo-erp-backend/internal/handlers/router/subdistrict" "cargo-erp-backend/internal/middleware" "cargo-erp-backend/pkg/helpers" "fmt" @@ -109,6 +111,8 @@ func (h *Handlers) Run() { province.NewProvinceHandler(h.Helper, masterdata).Router() city.NewCityHandler(h.Helper, masterdata).Router() district.NewDistrictHandler(h.Helper, masterdata).Router() + subdistrict.NewSubdistrictHandler(h.Helper, masterdata).Router() + postal_code.NewPostalCodeHandler(h.Helper, masterdata).Router() } h.Helper.Log().Info("Starting Apps") diff --git a/internal/handlers/router/postal_code/postal_code.go b/internal/handlers/router/postal_code/postal_code.go new file mode 100644 index 0000000..42b974f --- /dev/null +++ b/internal/handlers/router/postal_code/postal_code.go @@ -0,0 +1,281 @@ +package postal_code + +import ( + "cargo-erp-backend/internal/handlers/dto" + postal_codeimporter "cargo-erp-backend/internal/importers/postal_code" + "cargo-erp-backend/internal/usecases" + "cargo-erp-backend/pkg/helpers" + "cargo-erp-backend/pkg/importer" + "cargo-erp-backend/pkg/response" + "net/http" + + "github.com/gin-gonic/gin" +) + +type PostalCodeHandlerInterface interface { + Router() +} + +type PostalCodeHandler struct { + Helper helpers.HelperInterface + Group *gin.RouterGroup +} + +func NewPostalCodeHandler(helper helpers.HelperInterface, g *gin.RouterGroup) PostalCodeHandlerInterface { + return &PostalCodeHandler{Helper: helper, Group: g} +} + +func (h *PostalCodeHandler) Router() { + r := h.Group.Group("postal-codes") + { + r.GET("/", h.GetAll) + r.POST("/list", h.GetList) + r.GET("/:id", h.GetByID) + r.POST("/", h.Create) + r.PUT("/:id", h.Update) + r.DELETE("/:id", h.Delete) + r.GET("/template", h.Template) + r.POST("/import", h.Import) + r.POST("/import/stop", h.ImportStop) + r.GET("/import/status/:jobId", h.ImportStatus) + } +} + +func (h *PostalCodeHandler) GetAll(c *gin.Context) { + userid, err := h.Helper.GetAuthInfo(c.GetString("auth")) + if err != nil { + response.Error(c, http.StatusUnauthorized, err.Error()) + return + } + var req dto.PostalCodeFilterAllRequest + if err := c.ShouldBindQuery(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + u := usecases.NewPostalCodeUsecase(h.Helper, *userid) + postalCodes, _, err := u.GetAll(req) + if err != nil { + response.Error(c, http.StatusInternalServerError, err.Error()) + return + } + var responseDTO = make([]dto.CreatePostalCodeResponse, 0) + for _, v := range postalCodes { + var DTO dto.CreatePostalCodeResponse + h.Helper.MapStructToStruct(v, "json", &DTO, "json") + responseDTO = append(responseDTO, DTO) + } + response.Success(c, responseDTO) +} + +func (h *PostalCodeHandler) GetList(c *gin.Context) { + userid, err := h.Helper.GetAuthInfo(c.GetString("auth")) + if err != nil { + response.Error(c, http.StatusUnauthorized, err.Error()) + return + } + + var req dto.DataTableRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.Error(c, http.StatusBadRequest, "Request Format request invalid") + return + } + u := usecases.NewPostalCodeUsecase(h.Helper, *userid) + res, err := u.GetList(req) + if err != nil { + response.Error(c, http.StatusInternalServerError, err.Error()) + return + } + response.Success(c, res) + return +} + +func (h *PostalCodeHandler) GetByID(c *gin.Context) { + userid, err := h.Helper.GetAuthInfo(c.GetString("auth")) + if err != nil { + response.Error(c, http.StatusUnauthorized, err.Error()) + return + } + id := c.Param("id") + + u := usecases.NewPostalCodeUsecase(h.Helper, *userid) + postalCode, err := u.GetByID(id) + if err != nil { + response.Error(c, http.StatusNotFound, "postal code not found") + return + } + + response.Success(c, postalCode) +} + +func (h *PostalCodeHandler) Create(c *gin.Context) { + var req dto.CreatePostalCodeRequest + if err := c.BindJSON(&req); err != nil { + response.Error(c, http.StatusBadRequest, err.Error()) + return + } + userid, err := h.Helper.GetAuthInfo(c.GetString("auth")) + if err != nil { + response.Error(c, http.StatusUnauthorized, err.Error()) + return + } + var merchantid *string = nil + if userid.UsersRole_Relation.RoleKey != "SPM" { + merchantid = userid.MerchantID + } + createdBy := userid.ID + + u := usecases.NewPostalCodeUsecase(h.Helper, *userid) + postalCode, err := u.Create(req.PostalCode, req.ProvinceID, req.CityID, req.DistrictID, req.SubdistrictID, createdBy, merchantid) + if err != nil { + response.Error(c, http.StatusInternalServerError, err.Error()) + return + } + var responseDTO dto.CreatePostalCodeResponse + h.Helper.MapStructToStruct(postalCode, "json", &responseDTO, "json") + response.Created(c, responseDTO) +} + +func (h *PostalCodeHandler) Update(c *gin.Context) { + id := c.Param("id") + + var req dto.UpdatePostalCodeRequest + if err := c.BindJSON(&req); err != nil { + response.Error(c, http.StatusBadRequest, err.Error()) + return + } + userid, err := h.Helper.GetAuthInfo(c.GetString("auth")) + if err != nil { + response.Error(c, http.StatusUnauthorized, err.Error()) + return + } + updatedBy := userid.ID + + u := usecases.NewPostalCodeUsecase(h.Helper, *userid) + postalCode, err := u.Update(id, req.PostalCode, req.ProvinceID, req.CityID, req.DistrictID, req.SubdistrictID, updatedBy) + if err != nil { + response.Error(c, http.StatusInternalServerError, err.Error()) + return + } + var responseDTO dto.CreatePostalCodeResponse + h.Helper.MapStructToStruct(postalCode, "json", &responseDTO, "json") + response.Success(c, responseDTO) +} + +func (h *PostalCodeHandler) Delete(c *gin.Context) { + userid, err := h.Helper.GetAuthInfo(c.GetString("auth")) + if err != nil { + response.Error(c, http.StatusUnauthorized, err.Error()) + return + } + id := c.Param("id") + u := usecases.NewPostalCodeUsecase(h.Helper, *userid) + if err := u.Delete(id); err != nil { + response.Error(c, http.StatusInternalServerError, err.Error()) + return + } + + response.Success(c, map[string]interface{}{"message": "postal code deleted"}) +} + +func (h *PostalCodeHandler) Template(c *gin.Context) { + userid, err := h.Helper.GetAuthInfo(c.GetString("auth")) + if err != nil { + response.Error(c, http.StatusUnauthorized, err.Error()) + return + } + handler := postal_codeimporter.NewPostalCodeImportHandler(*userid, h.Helper) + imp := importer.NewImporter(h.Helper, handler) + imp.ServeTemplate(c.Writer) +} + +func (h *PostalCodeHandler) 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 := postal_codeimporter.NewPostalCodeImportHandler(*userid, h.Helper) + 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.ImportPostalCodeResponse{ + 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 *PostalCodeHandler) 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 := postal_codeimporter.NewPostalCodeImportHandler(*userid, h.Helper) + 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, + }) +} + +func (h *PostalCodeHandler) ImportStop(c *gin.Context) { + userid, err := h.Helper.GetAuthInfo(c.GetString("auth")) + if err != nil { + response.Error(c, http.StatusUnauthorized, err.Error()) + return + } + + var req dto.StopImportRequest + if err := c.BindJSON(&req); err != nil { + response.Error(c, http.StatusBadRequest, "job_id is required") + return + } + + handler := postal_codeimporter.NewPostalCodeImportHandler(*userid, h.Helper) + imp := importer.NewImporter(h.Helper, handler) + + if err := imp.StopJob(req.JobID); err != nil { + response.Error(c, http.StatusBadRequest, err.Error()) + return + } + + response.Success(c, map[string]interface{}{"message": "Job stopped"}) +} diff --git a/internal/handlers/router/subdistrict/subdistrict.go b/internal/handlers/router/subdistrict/subdistrict.go new file mode 100644 index 0000000..a763e92 --- /dev/null +++ b/internal/handlers/router/subdistrict/subdistrict.go @@ -0,0 +1,281 @@ +package subdistrict + +import ( + "cargo-erp-backend/internal/handlers/dto" + subdistrictimporter "cargo-erp-backend/internal/importers/subdistrict" + "cargo-erp-backend/internal/usecases" + "cargo-erp-backend/pkg/helpers" + "cargo-erp-backend/pkg/importer" + "cargo-erp-backend/pkg/response" + "net/http" + + "github.com/gin-gonic/gin" +) + +type SubdistrictHandlerInterface interface { + Router() +} + +type SubdistrictHandler struct { + Helper helpers.HelperInterface + Group *gin.RouterGroup +} + +func NewSubdistrictHandler(helper helpers.HelperInterface, g *gin.RouterGroup) SubdistrictHandlerInterface { + return &SubdistrictHandler{Helper: helper, Group: g} +} + +func (h *SubdistrictHandler) Router() { + r := h.Group.Group("subdistricts") + { + r.GET("/", h.GetAll) + r.POST("/list", h.GetList) + r.GET("/:id", h.GetByID) + r.POST("/", h.Create) + r.PUT("/:id", h.Update) + r.DELETE("/:id", h.Delete) + r.GET("/template", h.Template) + r.POST("/import", h.Import) + r.POST("/import/stop", h.ImportStop) + r.GET("/import/status/:jobId", h.ImportStatus) + } +} + +func (h *SubdistrictHandler) GetAll(c *gin.Context) { + userid, err := h.Helper.GetAuthInfo(c.GetString("auth")) + if err != nil { + response.Error(c, http.StatusUnauthorized, err.Error()) + return + } + var req dto.SubdistrictFilterAllRequest + if err := c.ShouldBindQuery(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + u := usecases.NewSubdistrictUsecase(h.Helper, *userid) + subdistricts, _, err := u.GetAll(req) + if err != nil { + response.Error(c, http.StatusInternalServerError, err.Error()) + return + } + var responseDTO = make([]dto.CreateSubdistrictResponse, 0) + for _, v := range subdistricts { + var DTO dto.CreateSubdistrictResponse + h.Helper.MapStructToStruct(v, "json", &DTO, "json") + responseDTO = append(responseDTO, DTO) + } + response.Success(c, responseDTO) +} + +func (h *SubdistrictHandler) GetList(c *gin.Context) { + userid, err := h.Helper.GetAuthInfo(c.GetString("auth")) + if err != nil { + response.Error(c, http.StatusUnauthorized, err.Error()) + return + } + + var req dto.DataTableRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.Error(c, http.StatusBadRequest, "Request Format request invalid") + return + } + u := usecases.NewSubdistrictUsecase(h.Helper, *userid) + res, err := u.GetList(req) + if err != nil { + response.Error(c, http.StatusInternalServerError, err.Error()) + return + } + response.Success(c, res) + return +} + +func (h *SubdistrictHandler) GetByID(c *gin.Context) { + userid, err := h.Helper.GetAuthInfo(c.GetString("auth")) + if err != nil { + response.Error(c, http.StatusUnauthorized, err.Error()) + return + } + id := c.Param("id") + + u := usecases.NewSubdistrictUsecase(h.Helper, *userid) + subdistrict, err := u.GetByID(id) + if err != nil { + response.Error(c, http.StatusNotFound, "subdistrict not found") + return + } + + response.Success(c, subdistrict) +} + +func (h *SubdistrictHandler) Create(c *gin.Context) { + var req dto.CreateSubdistrictRequest + if err := c.BindJSON(&req); err != nil { + response.Error(c, http.StatusBadRequest, err.Error()) + return + } + userid, err := h.Helper.GetAuthInfo(c.GetString("auth")) + if err != nil { + response.Error(c, http.StatusUnauthorized, err.Error()) + return + } + var merchantid *string = nil + if userid.UsersRole_Relation.RoleKey != "SPM" { + merchantid = userid.MerchantID + } + createdBy := userid.ID + + u := usecases.NewSubdistrictUsecase(h.Helper, *userid) + subdistrict, err := u.Create(req.Name, req.ProvinceID, req.CityID, req.DistrictID, createdBy, merchantid) + if err != nil { + response.Error(c, http.StatusInternalServerError, err.Error()) + return + } + var responseDTO dto.CreateSubdistrictResponse + h.Helper.MapStructToStruct(subdistrict, "json", &responseDTO, "json") + response.Created(c, responseDTO) +} + +func (h *SubdistrictHandler) Update(c *gin.Context) { + id := c.Param("id") + + var req dto.UpdateSubdistrictRequest + if err := c.BindJSON(&req); err != nil { + response.Error(c, http.StatusBadRequest, err.Error()) + return + } + userid, err := h.Helper.GetAuthInfo(c.GetString("auth")) + if err != nil { + response.Error(c, http.StatusUnauthorized, err.Error()) + return + } + updatedBy := userid.ID + + u := usecases.NewSubdistrictUsecase(h.Helper, *userid) + subdistrict, err := u.Update(id, req.Name, req.ProvinceID, req.CityID, req.DistrictID, updatedBy) + if err != nil { + response.Error(c, http.StatusInternalServerError, err.Error()) + return + } + var responseDTO dto.CreateSubdistrictResponse + h.Helper.MapStructToStruct(subdistrict, "json", &responseDTO, "json") + response.Success(c, responseDTO) +} + +func (h *SubdistrictHandler) Delete(c *gin.Context) { + userid, err := h.Helper.GetAuthInfo(c.GetString("auth")) + if err != nil { + response.Error(c, http.StatusUnauthorized, err.Error()) + return + } + id := c.Param("id") + u := usecases.NewSubdistrictUsecase(h.Helper, *userid) + if err := u.Delete(id); err != nil { + response.Error(c, http.StatusInternalServerError, err.Error()) + return + } + + response.Success(c, map[string]interface{}{"message": "subdistrict deleted"}) +} + +func (h *SubdistrictHandler) Template(c *gin.Context) { + userid, err := h.Helper.GetAuthInfo(c.GetString("auth")) + if err != nil { + response.Error(c, http.StatusUnauthorized, err.Error()) + return + } + handler := subdistrictimporter.NewSubdistrictImportHandler(*userid, h.Helper) + imp := importer.NewImporter(h.Helper, handler) + imp.ServeTemplate(c.Writer) +} + +func (h *SubdistrictHandler) 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 := subdistrictimporter.NewSubdistrictImportHandler(*userid, h.Helper) + 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.ImportSubdistrictResponse{ + 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 *SubdistrictHandler) 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 := subdistrictimporter.NewSubdistrictImportHandler(*userid, h.Helper) + 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, + }) +} + +func (h *SubdistrictHandler) ImportStop(c *gin.Context) { + userid, err := h.Helper.GetAuthInfo(c.GetString("auth")) + if err != nil { + response.Error(c, http.StatusUnauthorized, err.Error()) + return + } + + var req dto.StopImportRequest + if err := c.BindJSON(&req); err != nil { + response.Error(c, http.StatusBadRequest, "job_id is required") + return + } + + handler := subdistrictimporter.NewSubdistrictImportHandler(*userid, h.Helper) + imp := importer.NewImporter(h.Helper, handler) + + if err := imp.StopJob(req.JobID); err != nil { + response.Error(c, http.StatusBadRequest, err.Error()) + return + } + + response.Success(c, map[string]interface{}{"message": "Job stopped"}) +} diff --git a/internal/importers/postal_code/importer.go b/internal/importers/postal_code/importer.go new file mode 100644 index 0000000..3e0dd4c --- /dev/null +++ b/internal/importers/postal_code/importer.go @@ -0,0 +1,151 @@ +package postal_code + +import ( + "cargo-erp-backend/internal/domain" + "cargo-erp-backend/pkg/helpers" + "cargo-erp-backend/pkg/importer" + "fmt" + "strings" + "time" + + "gorm.io/gorm" +) + +type PostalCodeImportHandler struct { + UserData domain.User + Helper helpers.HelperInterface + lastProvinceName string + lastCityName string + lastDistrictName string + lastSubdistrictName string +} + +func NewPostalCodeImportHandler(userData domain.User, helper helpers.HelperInterface) importer.ImportHandler { + return &PostalCodeImportHandler{ + UserData: userData, + Helper: helper, + } +} + +func (h *PostalCodeImportHandler) ValidateRow(row []string, rowNumber int) (importer.RowData, []error) { + var errors []error + + if len(row) < 5 { + errors = append(errors, fmt.Errorf("row must have 5 columns: postal_code, subdistrict_name, district_name, city_name, province_name")) + return nil, errors + } + + postalCode := strings.TrimSpace(row[0]) + if postalCode == "" { + errors = append(errors, fmt.Errorf("postal_code is required")) + } else if len(postalCode) > 10 { + errors = append(errors, fmt.Errorf("postal_code must be at most 10 characters")) + } + + subdistrictName := strings.TrimSpace(row[1]) + if subdistrictName == "" { + errors = append(errors, fmt.Errorf("subdistrict_name is required")) + } + + districtName := strings.TrimSpace(row[2]) + if districtName == "" { + errors = append(errors, fmt.Errorf("district_name is required")) + } + + cityName := strings.TrimSpace(row[3]) + if cityName == "" { + errors = append(errors, fmt.Errorf("city_name is required")) + } + + provinceName := strings.TrimSpace(row[4]) + if provinceName == "" { + errors = append(errors, fmt.Errorf("province_name is required")) + } + + if len(errors) > 0 { + return nil, errors + } + + var province domain.Province + db := h.Helper.GetDB("slave") + if err := db.Where("UPPER(name) = ?", strings.ToUpper(provinceName)).First(&province).Error; err != nil { + errors = append(errors, fmt.Errorf("province '%s' not found", provinceName)) + return nil, errors + } + + var city domain.City + if err := db.Where("UPPER(name) = ? AND province_id = ?", strings.ToUpper(cityName), province.ID).First(&city).Error; err != nil { + errors = append(errors, fmt.Errorf("city '%s' not found in province '%s'", cityName, provinceName)) + return nil, errors + } + + var district domain.District + if err := db.Where("UPPER(name) = ? AND city_id = ?", strings.ToUpper(districtName), city.ID).First(&district).Error; err != nil { + errors = append(errors, fmt.Errorf("district '%s' not found in city '%s'", districtName, cityName)) + return nil, errors + } + + var subdistrict domain.Subdistrict + if err := db.Where("UPPER(name) = ? AND district_id = ?", strings.ToUpper(subdistrictName), district.ID).First(&subdistrict).Error; err != nil { + errors = append(errors, fmt.Errorf("subdistrict '%s' not found in district '%s'", subdistrictName, districtName)) + return nil, errors + } + + h.lastProvinceName = provinceName + h.lastCityName = cityName + h.lastDistrictName = districtName + h.lastSubdistrictName = subdistrictName + + now := time.Now() + nowStr := now.Format("2006-01-02 15:04:05") + pc := &domain.PostalCode{ + PostalCode: &postalCode, + ProvinceID: &province.ID, + CityID: &city.ID, + DistrictID: &district.ID, + SubdistrictID: &subdistrict.ID, + MerchantID: h.UserData.MerchantID, + CreatedBy: &h.UserData.ID, + CreatedOn: &now, + UpdatedBy: &h.UserData.ID, + UpdatedOn: &nowStr, + } + + return pc, nil +} + +func (h *PostalCodeImportHandler) GetDataString(data importer.RowData) string { + pc := data.(*domain.PostalCode) + code := "" + if pc.PostalCode != nil { + code = *pc.PostalCode + } + return fmt.Sprintf("%s, %s, %s, %s, %s", code, h.lastSubdistrictName, h.lastDistrictName, h.lastCityName, h.lastProvinceName) +} + +func (h *PostalCodeImportHandler) IsDuplicate(db *gorm.DB, data importer.RowData) bool { + pc := data.(*domain.PostalCode) + var count int64 + db.Model(&domain.PostalCode{}). + Joins("JOIN subdistrict ON postal_code.subdistrict_id = subdistrict.id"). + Joins("JOIN district ON postal_code.district_id = district.id"). + Joins("JOIN city ON postal_code.city_id = city.id"). + Joins("JOIN province ON postal_code.province_id = province.id"). + Where("UPPER(postal_code.postal_code) = ? AND UPPER(subdistrict.name) = ? AND UPPER(district.name) = ? AND UPPER(city.name) = ? AND UPPER(province.name) = ?", + strings.ToUpper(*pc.PostalCode), strings.ToUpper(h.lastSubdistrictName), strings.ToUpper(h.lastDistrictName), strings.ToUpper(h.lastCityName), strings.ToUpper(h.lastProvinceName)). + Count(&count) + return count > 0 +} + +func (h *PostalCodeImportHandler) GetTemplateHeaders() []string { + return []string{"postal_code", "subdistrict_name", "district_name", "city_name", "province_name"} +} + +func (h *PostalCodeImportHandler) GetTemplateRows() [][]string { + return [][]string{ + {"10430", "Kramat Pulo", "Jakarta Pusat", "Jakarta Pusat", "DKI Jakarta"}, + {"10430", "Senen", "Jakarta Pusat", "Jakarta Pusat", "DKI Jakarta"}, + {"10440", "Cempaka Putih", "Jakarta Pusat", "Jakarta Pusat", "DKI Jakarta"}, + {"10510", "Gambir", "Jakarta Pusat", "Jakarta Pusat", "DKI Jakarta"}, + } +} diff --git a/internal/importers/subdistrict/importer.go b/internal/importers/subdistrict/importer.go new file mode 100644 index 0000000..5668dee --- /dev/null +++ b/internal/importers/subdistrict/importer.go @@ -0,0 +1,137 @@ +package subdistrict + +import ( + "cargo-erp-backend/internal/domain" + "cargo-erp-backend/pkg/helpers" + "cargo-erp-backend/pkg/importer" + "fmt" + "strings" + "time" + + "gorm.io/gorm" +) + +type SubdistrictImportHandler struct { + UserData domain.User + Helper helpers.HelperInterface + lastProvinceName string + lastCityName string + lastDistrictName string +} + +func NewSubdistrictImportHandler(userData domain.User, helper helpers.HelperInterface) importer.ImportHandler { + return &SubdistrictImportHandler{ + UserData: userData, + Helper: helper, + } +} + +func (h *SubdistrictImportHandler) ValidateRow(row []string, rowNumber int) (importer.RowData, []error) { + var errors []error + + if len(row) < 4 { + errors = append(errors, fmt.Errorf("row must have 4 columns: name, district_name, city_name, province_name")) + return nil, errors + } + + name := strings.TrimSpace(row[0]) + if name == "" { + errors = append(errors, fmt.Errorf("name is required")) + } else if len(name) < 2 { + errors = append(errors, fmt.Errorf("name must be at least 2 characters")) + } else if len(name) > 255 { + errors = append(errors, fmt.Errorf("name must be at most 255 characters")) + } + + districtName := strings.TrimSpace(row[1]) + if districtName == "" { + errors = append(errors, fmt.Errorf("district_name is required")) + } + + cityName := strings.TrimSpace(row[2]) + if cityName == "" { + errors = append(errors, fmt.Errorf("city_name is required")) + } + + provinceName := strings.TrimSpace(row[3]) + if provinceName == "" { + errors = append(errors, fmt.Errorf("province_name is required")) + } + + if len(errors) > 0 { + return nil, errors + } + + var province domain.Province + db := h.Helper.GetDB("slave") + if err := db.Where("UPPER(name) = ?", strings.ToUpper(provinceName)).First(&province).Error; err != nil { + errors = append(errors, fmt.Errorf("province '%s' not found", provinceName)) + return nil, errors + } + + var city domain.City + if err := db.Where("UPPER(name) = ? AND province_id = ?", strings.ToUpper(cityName), province.ID).First(&city).Error; err != nil { + errors = append(errors, fmt.Errorf("city '%s' not found in province '%s'", cityName, provinceName)) + return nil, errors + } + + var district domain.District + if err := db.Where("UPPER(name) = ? AND city_id = ?", strings.ToUpper(districtName), city.ID).First(&district).Error; err != nil { + errors = append(errors, fmt.Errorf("district '%s' not found in city '%s'", districtName, cityName)) + return nil, errors + } + + h.lastProvinceName = provinceName + h.lastCityName = cityName + h.lastDistrictName = districtName + + now := time.Now() + subdistrict := &domain.Subdistrict{ + Name: &name, + ProvinceID: &province.ID, + CityID: &city.ID, + DistrictID: &district.ID, + MerchantID: h.UserData.MerchantID, + CreatedBy: &h.UserData.ID, + CreatedOn: &now, + UpdatedBy: &h.UserData.ID, + UpdatedOn: &now, + } + + return subdistrict, nil +} + +func (h *SubdistrictImportHandler) GetDataString(data importer.RowData) string { + subdistrict := data.(*domain.Subdistrict) + name := "" + if subdistrict.Name != nil { + name = *subdistrict.Name + } + return fmt.Sprintf("%s, %s, %s, %s", name, h.lastDistrictName, h.lastCityName, h.lastProvinceName) +} + +func (h *SubdistrictImportHandler) IsDuplicate(db *gorm.DB, data importer.RowData) bool { + subdistrict := data.(*domain.Subdistrict) + var count int64 + db.Model(&domain.Subdistrict{}). + Joins("JOIN district ON subdistrict.district_id = district.id"). + Joins("JOIN city ON subdistrict.city_id = city.id"). + Joins("JOIN province ON subdistrict.province_id = province.id"). + Where("UPPER(subdistrict.name) = ? AND UPPER(district.name) = ? AND UPPER(city.name) = ? AND UPPER(province.name) = ?", + strings.ToUpper(*subdistrict.Name), strings.ToUpper(h.lastDistrictName), strings.ToUpper(h.lastCityName), strings.ToUpper(h.lastProvinceName)). + Count(&count) + return count > 0 +} + +func (h *SubdistrictImportHandler) GetTemplateHeaders() []string { + return []string{"name", "district_name", "city_name", "province_name"} +} + +func (h *SubdistrictImportHandler) GetTemplateRows() [][]string { + return [][]string{ + {"Kramat Pulo", "Jakarta Pusat", "Jakarta Pusat", "DKI Jakarta"}, + {"Senen", "Jakarta Pusat", "Jakarta Pusat", "DKI Jakarta"}, + {"Cempaka Putih", "Jakarta Pusat", "Jakarta Pusat", "DKI Jakarta"}, + {"Gambir", "Jakarta Pusat", "Jakarta Pusat", "DKI Jakarta"}, + } +} diff --git a/internal/usecases/postal_code.go b/internal/usecases/postal_code.go new file mode 100644 index 0000000..f43881d --- /dev/null +++ b/internal/usecases/postal_code.go @@ -0,0 +1,184 @@ +package usecases + +import ( + "cargo-erp-backend/internal/domain" + "cargo-erp-backend/internal/handlers/dto" + "cargo-erp-backend/pkg/datatable" + "cargo-erp-backend/pkg/helpers" + "fmt" + "reflect" + "time" +) + +type PostalCodeUsecaseInterface interface { + GetAll(filter dto.PostalCodeFilterAllRequest) ([]domain.PostalCode, int64, error) + GetList(dto.DataTableRequest) (dto.DataTableResponse, error) + GetByID(id string) (domain.PostalCode, error) + Create(postalCode string, provinceID string, cityID string, districtID string, subdistrictID string, createdBy string, merchantid *string) (domain.PostalCode, error) + Update(id string, postalCode string, provinceID string, cityID string, districtID string, subdistrictID string, updatedBy string) (domain.PostalCode, error) + Delete(id string) error +} + +type PostalCodeUsecase struct { + Helper helpers.HelperInterface + AuthUser domain.User +} + +func NewPostalCodeUsecase(helper helpers.HelperInterface, authUser domain.User) PostalCodeUsecaseInterface { + return &PostalCodeUsecase{Helper: helper, AuthUser: authUser} +} + +func (u *PostalCodeUsecase) GetAll(filter dto.PostalCodeFilterAllRequest) ([]domain.PostalCode, int64, error) { + var postalCodes []domain.PostalCode + var total int64 + + db := u.Helper.GetDB("slave") + tx := db.Model(&postalCodes) + if u.AuthUser.UsersRole_Relation.RoleKey != "SPM" { + tx.Where(db.Where("merchant_id IS NULL").Or("merchant_id=?", u.AuthUser.MerchantID)) + } + v := reflect.ValueOf(filter) + t := reflect.TypeOf(filter) + + for i := 0; i < v.NumField(); i++ { + if v.Field(i).String() != "" { + tagKey := t.Field(i).Tag.Get("form") + tx.Where(fmt.Sprintf("%v=?", tagKey), v.Field(i)) + } + } + tx.Model(&domain.PostalCode{}).Count(&total) + tx.Order("postal_code ASC") + if err := tx.Find(&postalCodes).Error; err != nil { + return nil, 0, err + } + return postalCodes, total, nil +} + +func (u *PostalCodeUsecase) GetList(req dto.DataTableRequest) (dto.DataTableResponse, error) { + var response dto.DataTableResponse + db := u.Helper.GetDB("slave") + tx := db.Table("postal_code"). + Joins("LEFT JOIN subdistrict ON postal_code.subdistrict_id=subdistrict.id"). + Joins("LEFT JOIN district ON postal_code.district_id=district.id"). + Joins("LEFT JOIN city ON postal_code.city_id=city.id"). + Joins("LEFT JOIN province ON postal_code.province_id=province.id"). + Joins("LEFT JOIN merchant ON postal_code.merchant_id=merchant.id") + dt := datatable.NewDatatable(tx, req) + coldef := make([]dto.DataTableColDef, 0) + coldef = append(coldef, dto.DataTableColDef{ + Field: "postal_code.postal_code", + Alias: "postal_code", + }) + coldef = append(coldef, dto.DataTableColDef{ + Field: "subdistrict.name", + Alias: "subdistrict_name", + }) + coldef = append(coldef, dto.DataTableColDef{ + Field: "district.name", + Alias: "district_name", + }) + coldef = append(coldef, dto.DataTableColDef{ + Field: "city.name", + Alias: "city_name", + }) + coldef = append(coldef, dto.DataTableColDef{ + Field: "province.name", + Alias: "province_name", + }) + coldef = append(coldef, dto.DataTableColDef{ + Field: "postal_code.province_id", + Alias: "province_id", + }) + coldef = append(coldef, dto.DataTableColDef{ + Field: "postal_code.city_id", + Alias: "city_id", + }) + coldef = append(coldef, dto.DataTableColDef{ + Field: "postal_code.district_id", + Alias: "district_id", + }) + coldef = append(coldef, dto.DataTableColDef{ + Field: "postal_code.subdistrict_id", + Alias: "subdistrict_id", + }) + coldef = append(coldef, dto.DataTableColDef{ + Field: "merchant.id", + Alias: "merchant_id", + }) + coldef = append(coldef, dto.DataTableColDef{ + Field: "postal_code.id", + Alias: "id", + }) + + response = dt.Render(coldef) + return response, nil +} + +func (u *PostalCodeUsecase) GetByID(id string) (domain.PostalCode, error) { + var postalCode domain.PostalCode + db := u.Helper.GetDB("slave") + if err := db.Where("id = ?", id).First(&postalCode).Error; err != nil { + return domain.PostalCode{}, err + } + return postalCode, nil +} + +func (u *PostalCodeUsecase) Create(postalCode string, provinceID string, cityID string, districtID string, subdistrictID string, createdBy string, merchantid *string) (domain.PostalCode, error) { + now := time.Now() + nowStr := now.Format("2006-01-02 15:04:05") + pc := domain.PostalCode{ + PostalCode: &postalCode, + ProvinceID: &provinceID, + CityID: &cityID, + DistrictID: &districtID, + SubdistrictID: &subdistrictID, + MerchantID: merchantid, + CreatedBy: &createdBy, + CreatedOn: &now, + UpdatedBy: &createdBy, + UpdatedOn: &nowStr, + } + + db := u.Helper.GetDB("master") + if err := db.Create(&pc).Error; err != nil { + return domain.PostalCode{}, err + } + return pc, nil +} + +func (u *PostalCodeUsecase) Update(id string, postalCode string, provinceID string, cityID string, districtID string, subdistrictID string, updatedBy string) (domain.PostalCode, error) { + var pc domain.PostalCode + db := u.Helper.GetDB("master") + if u.AuthUser.UsersRole_Relation.RoleKey != "SPM" { + db.Where("merchant_id=?", u.AuthUser.MerchantID) + } + if err := db.Where("id = ?", id).First(&pc).Error; err != nil { + return domain.PostalCode{}, err + } + + now := time.Now() + nowStr := now.Format("2006-01-02 15:04:05") + pc.PostalCode = &postalCode + pc.ProvinceID = &provinceID + pc.CityID = &cityID + pc.DistrictID = &districtID + pc.SubdistrictID = &subdistrictID + pc.UpdatedBy = &updatedBy + pc.UpdatedOn = &nowStr + + if err := db.Save(&pc).Error; err != nil { + return domain.PostalCode{}, err + } + return pc, nil +} + +func (u *PostalCodeUsecase) Delete(id string) error { + db := u.Helper.GetDB("master") + if u.AuthUser.UsersRole_Relation.RoleKey != "SPM" { + db.Where("merchant_id=?", u.AuthUser.MerchantID) + } + if err := db.Delete(&domain.PostalCode{}, "id = ?", id).Error; err != nil { + return err + } + return nil +} diff --git a/internal/usecases/subdistrict.go b/internal/usecases/subdistrict.go new file mode 100644 index 0000000..b9e8eec --- /dev/null +++ b/internal/usecases/subdistrict.go @@ -0,0 +1,171 @@ +package usecases + +import ( + "cargo-erp-backend/internal/domain" + "cargo-erp-backend/internal/handlers/dto" + "cargo-erp-backend/pkg/datatable" + "cargo-erp-backend/pkg/helpers" + "fmt" + "reflect" + "time" +) + +type SubdistrictUsecaseInterface interface { + GetAll(filter dto.SubdistrictFilterAllRequest) ([]domain.Subdistrict, int64, error) + GetList(dto.DataTableRequest) (dto.DataTableResponse, error) + GetByID(id string) (domain.Subdistrict, error) + Create(name string, provinceID string, cityID string, districtID string, createdBy string, merchantid *string) (domain.Subdistrict, error) + Update(id string, name string, provinceID string, cityID string, districtID string, updatedBy string) (domain.Subdistrict, error) + Delete(id string) error +} + +type SubdistrictUsecase struct { + Helper helpers.HelperInterface + AuthUser domain.User +} + +func NewSubdistrictUsecase(helper helpers.HelperInterface, authUser domain.User) SubdistrictUsecaseInterface { + return &SubdistrictUsecase{Helper: helper, AuthUser: authUser} +} + +func (u *SubdistrictUsecase) GetAll(filter dto.SubdistrictFilterAllRequest) ([]domain.Subdistrict, int64, error) { + var subdistricts []domain.Subdistrict + var total int64 + + db := u.Helper.GetDB("slave") + tx := db.Model(&subdistricts) + if u.AuthUser.UsersRole_Relation.RoleKey != "SPM" { + tx.Where(db.Where("merchant_id IS NULL").Or("merchant_id=?", u.AuthUser.MerchantID)) + } + v := reflect.ValueOf(filter) + t := reflect.TypeOf(filter) + + for i := 0; i < v.NumField(); i++ { + if v.Field(i).String() != "" { + tagKey := t.Field(i).Tag.Get("form") + tx.Where(fmt.Sprintf("%v=?", tagKey), v.Field(i)) + } + } + tx.Model(&domain.Subdistrict{}).Count(&total) + tx.Order("name ASC") + if err := tx.Find(&subdistricts).Error; err != nil { + return nil, 0, err + } + return subdistricts, total, nil +} + +func (u *SubdistrictUsecase) GetList(req dto.DataTableRequest) (dto.DataTableResponse, error) { + var response dto.DataTableResponse + db := u.Helper.GetDB("slave") + tx := db.Table("subdistrict"). + Joins("LEFT JOIN district ON subdistrict.district_id=district.id"). + Joins("LEFT JOIN city ON subdistrict.city_id=city.id"). + Joins("LEFT JOIN province ON subdistrict.province_id=province.id"). + Joins("LEFT JOIN merchant ON subdistrict.merchant_id=merchant.id") + dt := datatable.NewDatatable(tx, req) + coldef := make([]dto.DataTableColDef, 0) + coldef = append(coldef, dto.DataTableColDef{ + Field: "subdistrict.name", + Alias: "name", + }) + coldef = append(coldef, dto.DataTableColDef{ + Field: "district.name", + Alias: "district_name", + }) + coldef = append(coldef, dto.DataTableColDef{ + Field: "city.name", + Alias: "city_name", + }) + coldef = append(coldef, dto.DataTableColDef{ + Field: "province.name", + Alias: "province_name", + }) + coldef = append(coldef, dto.DataTableColDef{ + Field: "subdistrict.province_id", + Alias: "province_id", + }) + coldef = append(coldef, dto.DataTableColDef{ + Field: "subdistrict.city_id", + Alias: "city_id", + }) + coldef = append(coldef, dto.DataTableColDef{ + Field: "subdistrict.district_id", + Alias: "district_id", + }) + coldef = append(coldef, dto.DataTableColDef{ + Field: "merchant.id", + Alias: "merchant_id", + }) + coldef = append(coldef, dto.DataTableColDef{ + Field: "subdistrict.id", + Alias: "id", + }) + + response = dt.Render(coldef) + return response, nil +} + +func (u *SubdistrictUsecase) GetByID(id string) (domain.Subdistrict, error) { + var subdistrict domain.Subdistrict + db := u.Helper.GetDB("slave") + if err := db.Where("id = ?", id).First(&subdistrict).Error; err != nil { + return domain.Subdistrict{}, err + } + return subdistrict, nil +} + +func (u *SubdistrictUsecase) Create(name string, provinceID string, cityID string, districtID string, createdBy string, merchantid *string) (domain.Subdistrict, error) { + now := time.Now() + subdistrict := domain.Subdistrict{ + Name: &name, + ProvinceID: &provinceID, + CityID: &cityID, + DistrictID: &districtID, + MerchantID: merchantid, + CreatedBy: &createdBy, + CreatedOn: &now, + UpdatedBy: &createdBy, + UpdatedOn: &now, + } + + db := u.Helper.GetDB("master") + if err := db.Create(&subdistrict).Error; err != nil { + return domain.Subdistrict{}, err + } + return subdistrict, nil +} + +func (u *SubdistrictUsecase) Update(id string, name string, provinceID string, cityID string, districtID string, updatedBy string) (domain.Subdistrict, error) { + var subdistrict domain.Subdistrict + db := u.Helper.GetDB("master") + if u.AuthUser.UsersRole_Relation.RoleKey != "SPM" { + db.Where("merchant_id=?", u.AuthUser.MerchantID) + } + if err := db.Where("id = ?", id).First(&subdistrict).Error; err != nil { + return domain.Subdistrict{}, err + } + + now := time.Now() + subdistrict.Name = &name + subdistrict.ProvinceID = &provinceID + subdistrict.CityID = &cityID + subdistrict.DistrictID = &districtID + subdistrict.UpdatedBy = &updatedBy + subdistrict.UpdatedOn = &now + + if err := db.Save(&subdistrict).Error; err != nil { + return domain.Subdistrict{}, err + } + return subdistrict, nil +} + +func (u *SubdistrictUsecase) Delete(id string) error { + db := u.Helper.GetDB("master") + if u.AuthUser.UsersRole_Relation.RoleKey != "SPM" { + db.Where("merchant_id=?", u.AuthUser.MerchantID) + } + if err := db.Delete(&domain.Subdistrict{}, "id = ?", id).Error; err != nil { + return err + } + return nil +} diff --git a/storage/logs/app.log b/storage/logs/app.log index 6a15071..8eaadd5 100644 --- a/storage/logs/app.log +++ b/storage/logs/app.log @@ -914,3 +914,12 @@ {"level":"info","ts":"2026-08-18T20:24:04.777+0700","caller":"middleware/auth.go:62","msg":"session_login : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiIxOWZmMTA0YzE2NTIyYTIyNTJjYmFjYTI0ZCIsImlzcyI6ImNhcmdvLXBsYXRmb3JtIiwic3ViIjoiMTlmZjEwNGMxNjUyMmEyMjUyY2JhY2EyNGQiLCJleHAiOjE3ODcwNjI5NjYsImlhdCI6MTc4NzA1OTM2Nn0.xctXhU-k0oCqTfjGVUPaCuS0hdyK542X96Za0_Fw4LM"} {"level":"info","ts":"2026-08-18T20:24:04.834+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-18T20:24:05.015+0700","caller":"handlers/handlers.go:68","msg":"Request Success","status":200,"method":"POST","path":"/master-data/cities/list","query":"","ip":"127.0.0.1","latency":0.239231125,"user-agent":"Apidog/1.0.0 (https://apidog.com)"} +{"level":"info","ts":"2026-08-18T20:33:56.318+0700","caller":"middleware/auth.go:62","msg":"session_login : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiIxOWZmMTA0YzE2NTIyYTIyNTJjYmFjYTI0ZCIsImlzcyI6ImNhcmdvLXBsYXRmb3JtIiwic3ViIjoiMTlmZjEwNGMxNjUyMmEyMjUyY2JhY2EyNGQiLCJleHAiOjE3ODcwNjI5NjYsImlhdCI6MTc4NzA1OTM2Nn0.xctXhU-k0oCqTfjGVUPaCuS0hdyK542X96Za0_Fw4LM"} +{"level":"info","ts":"2026-08-18T20:33:57.630+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-18T20:33:58.058+0700","caller":"handlers/handlers.go:68","msg":"Request Success","status":200,"method":"POST","path":"/master-data/cities/list","query":"","ip":"127.0.0.1","latency":1.749137667,"user-agent":"Apidog/1.0.0 (https://apidog.com)"} +{"level":"info","ts":"2026-08-18T20:36:08.722+0700","caller":"middleware/auth.go:62","msg":"session_login : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiIxOWZmMTA0YzE2NTIyYTIyNTJjYmFjYTI0ZCIsImlzcyI6ImNhcmdvLXBsYXRmb3JtIiwic3ViIjoiMTlmZjEwNGMxNjUyMmEyMjUyY2JhY2EyNGQiLCJleHAiOjE3ODcwNjI5NjYsImlhdCI6MTc4NzA1OTM2Nn0.xctXhU-k0oCqTfjGVUPaCuS0hdyK542X96Za0_Fw4LM"} +{"level":"info","ts":"2026-08-18T20:36:09.348+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-18T20:36:09.857+0700","caller":"handlers/handlers.go:68","msg":"Request Success","status":200,"method":"POST","path":"/master-data/districts/list","query":"","ip":"127.0.0.1","latency":1.136347459,"user-agent":"Apidog/1.0.0 (https://apidog.com)"} +{"level":"info","ts":"2026-08-18T20:37:45.299+0700","caller":"middleware/auth.go:62","msg":"session_login : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiIxOWZmMTA0YzE2NTIyYTIyNTJjYmFjYTI0ZCIsImlzcyI6ImNhcmdvLXBsYXRmb3JtIiwic3ViIjoiMTlmZjEwNGMxNjUyMmEyMjUyY2JhY2EyNGQiLCJleHAiOjE3ODcwNjI5NjYsImlhdCI6MTc4NzA1OTM2Nn0.xctXhU-k0oCqTfjGVUPaCuS0hdyK542X96Za0_Fw4LM"} +{"level":"info","ts":"2026-08-18T20:37:45.397+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-18T20:37:45.428+0700","caller":"handlers/handlers.go:68","msg":"Request Success","status":200,"method":"GET","path":"/master-data/districts/template","query":"","ip":"127.0.0.1","latency":0.137240458,"user-agent":"Apidog/1.0.0 (https://apidog.com)"} diff --git a/storage/logs/db.log b/storage/logs/db.log index f194cbf..572daab 100644 --- a/storage/logs/db.log +++ b/storage/logs/db.log @@ -3581,3 +3581,9 @@ {"level":"info","ts":"2026-08-18T20:24:04.900+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/pkg/datatable/datatable.go:30","elapsed":0.053077667,"rows":1,"sql":"SELECT count(*) FROM \"city\" INNER JOIN province ON city.province_id=province.id LEFT JOIN merchant ON city.merchant_id=merchant.id"} {"level":"info","ts":"2026-08-18T20:24:04.957+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/pkg/datatable/datatable.go:81","elapsed":0.054915792,"rows":1,"sql":"SELECT count(*) FROM \"city\" INNER JOIN province ON city.province_id=province.id LEFT JOIN merchant ON city.merchant_id=merchant.id WHERE city.province_id='19fe79fac96c4db9fbb7fe5c52'"} {"level":"info","ts":"2026-08-18T20:24:05.014+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/pkg/datatable/datatable.go:89","elapsed":0.057587875,"rows":10,"sql":"SELECT city.name AS name,province.name AS province_name,city.province_id AS province_id,merchant.id AS merchant_id,city.id AS id FROM \"city\" INNER JOIN province ON city.province_id=province.id LEFT JOIN merchant ON city.merchant_id=merchant.id WHERE city.province_id='19fe79fac96c4db9fbb7fe5c52' ORDER BY city.name ASC LIMIT 10"} +{"level":"info","ts":"2026-08-18T20:33:57.963+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/pkg/datatable/datatable.go:30","elapsed":0.317756709,"rows":1,"sql":"SELECT count(*) FROM \"city\" INNER JOIN province ON city.province_id=province.id LEFT JOIN merchant ON city.merchant_id=merchant.id"} +{"level":"info","ts":"2026-08-18T20:33:58.009+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/pkg/datatable/datatable.go:81","elapsed":0.045027208,"rows":1,"sql":"SELECT count(*) FROM \"city\" INNER JOIN province ON city.province_id=province.id LEFT JOIN merchant ON city.merchant_id=merchant.id"} +{"level":"info","ts":"2026-08-18T20:33:58.055+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/pkg/datatable/datatable.go:89","elapsed":0.04498725,"rows":10,"sql":"SELECT city.name AS name,province.name AS province_name,city.province_id AS province_id,merchant.id AS merchant_id,city.id AS id FROM \"city\" INNER JOIN province ON city.province_id=province.id LEFT JOIN merchant ON city.merchant_id=merchant.id ORDER BY city.name ASC LIMIT 10"} +{"level":"info","ts":"2026-08-18T20:36:09.759+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/pkg/datatable/datatable.go:30","elapsed":0.4042015,"rows":1,"sql":"SELECT count(*) FROM \"district\" LEFT JOIN city ON district.city_id=city.id LEFT JOIN province ON district.province_id=province.id LEFT JOIN merchant ON district.merchant_id=merchant.id"} +{"level":"info","ts":"2026-08-18T20:36:09.792+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/pkg/datatable/datatable.go:81","elapsed":0.032117583,"rows":1,"sql":"SELECT count(*) FROM \"district\" LEFT JOIN city ON district.city_id=city.id LEFT JOIN province ON district.province_id=province.id LEFT JOIN merchant ON district.merchant_id=merchant.id"} +{"level":"info","ts":"2026-08-18T20:36:09.856+0700","caller":"logger/database.go:90","msg":"[GORM] query execution","line":"/Users/teguh/Documents/project/cargo/cargo-erp-deploy/app/backend/pkg/datatable/datatable.go:89","elapsed":0.064178167,"rows":10,"sql":"SELECT district.name AS name,city.name AS city_name,province.name AS province_name,district.province_id AS province_id,district.city_id AS city_id,merchant.id AS merchant_id,district.id AS id FROM \"district\" LEFT JOIN city ON district.city_id=city.id LEFT JOIN province ON district.province_id=province.id LEFT JOIN merchant ON district.merchant_id=merchant.id ORDER BY district.name ASC LIMIT 10"}