first init

This commit is contained in:
Firman Syah 2025-11-18 15:32:20 +07:00
commit d3a93ee716
9 changed files with 903 additions and 0 deletions

2
Makefile Normal file
View File

@ -0,0 +1,2 @@
build:
env GOOS=linux GOARCH=amd64 go build -o genTransitPoint.linux main.go

29
go.mod Normal file
View File

@ -0,0 +1,29 @@
module gen_transit_point
go 1.25.0
require (
github.com/lib/pq v1.10.9
github.com/opentracing/opentracing-go v1.2.0
github.com/xuri/excelize/v2 v2.10.0
gorm.io/driver/postgres v1.6.0
gorm.io/gorm v1.31.1
)
require (
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.6.0 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/richardlehane/mscfb v1.0.4 // indirect
github.com/richardlehane/msoleps v1.0.4 // indirect
github.com/tiendc/go-deepcopy v1.7.1 // indirect
github.com/xuri/efp v0.0.1 // indirect
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 // indirect
golang.org/x/crypto v0.43.0 // indirect
golang.org/x/net v0.46.0 // indirect
golang.org/x/sync v0.17.0 // indirect
golang.org/x/text v0.30.0 // indirect
)

57
go.sum Normal file
View File

@ -0,0 +1,57 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY=
github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM=
github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk=
github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
github.com/richardlehane/msoleps v1.0.4 h1:WuESlvhX3gH2IHcd8UqyCuFY5yiq/GR/yqaSM/9/g00=
github.com/richardlehane/msoleps v1.0.4/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tiendc/go-deepcopy v1.7.1 h1:LnubftI6nYaaMOcaz0LphzwraqN8jiWTwm416sitff4=
github.com/tiendc/go-deepcopy v1.7.1/go.mod h1:4bKjNC2r7boYOkD2IOuZpYjmlDdzjbpTRyCx+goBCJQ=
github.com/xuri/efp v0.0.1 h1:fws5Rv3myXyYni8uwj2qKjVaRP30PdjeYe2Y6FDsCL8=
github.com/xuri/efp v0.0.1/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
github.com/xuri/excelize/v2 v2.10.0 h1:8aKsP7JD39iKLc6dH5Tw3dgV3sPRh8uRVXu/fMstfW4=
github.com/xuri/excelize/v2 v2.10.0/go.mod h1:SC5TzhQkaOsTWpANfm+7bJCldzcnU/jrhqkTi/iBHBU=
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 h1:+C0TIdyyYmzadGaL/HBLbf3WdLgC29pgyhTjAT/0nuE=
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ=
golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs=
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=

318
main.go Normal file
View File

@ -0,0 +1,318 @@
package main
import (
"context"
"database/sql"
"fmt"
"gen_transit_point/model"
"gen_transit_point/utils"
"log"
"strings"
"time"
_ "github.com/lib/pq"
"github.com/opentracing/opentracing-go"
"github.com/xuri/excelize/v2"
"gorm.io/driver/postgres"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"gorm.io/gorm/logger"
)
func InitGormDB(host string, port int, user, pass, dbname string) (*gorm.DB, error) {
dsn := fmt.Sprintf(
"host=%s port=%d user=%s password=%s dbname=%s sslmode=disable",
host, port, user, pass, dbname,
)
gormDB, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
PrepareStmt: true,
Logger: logger.Default.LogMode(logger.Warn),
})
if err != nil {
return nil, err
}
// Setup connection pooling
sqlDB, err := gormDB.DB()
if err != nil {
return nil, err
}
sqlDB.SetMaxIdleConns(10)
sqlDB.SetMaxOpenConns(100)
sqlDB.SetConnMaxLifetime(time.Hour)
return gormDB, nil
}
func main() {
ctx := context.Background()
now := time.Now()
dbhost := "127.0.0.1"
dbport := 5432
dbuser := "postgres"
dbpass := "postgres"
dbname := "oslogweb"
//prod
/* dbhost := "38.47.91.219"
dbport := 5454
dbuser := "postgres"
dbpass := "0n35p1r1t2025"
dbname := "oslogweb" */
gormDB, err := InitGormDB(dbhost, dbport, dbuser, dbpass, dbname)
if err != nil {
log.Fatalf("Gagal init GORM: %v", err)
}
sqlDB, err := gormDB.DB()
if err != nil {
log.Fatalf("Gagal ambil sql.DB: %v", err)
}
if err := sqlDB.Ping(); err != nil {
log.Fatalf("Gagal ping database: %v", err)
}
fmt.Println("Koneksi ke DB berhasil ✅")
filename := "transit_point_indosps.xlsx"
f, err := excelize.OpenFile(filename)
if err != nil {
log.Fatalf("Gagal membuka file Excel: %v", err)
}
defer f.Close()
sheets := f.GetSheetList()
if len(sheets) == 0 {
log.Fatal("File Excel tidak memiliki sheet.")
}
sheetName := sheets[0]
fmt.Printf("Membaca sheet: %s\n", sheetName)
rows, err := f.GetRows(sheetName)
if err != nil {
log.Fatalf("Gagal membaca baris: %v", err)
}
excelData := []model.TransitPoint{}
//skip header nya jadi init dari 1 bukan 0
for i := 1; i < len(rows); i++ {
row := rows[i]
if len(row) < 5 {
continue
}
name := strings.TrimSpace(row[1])
if name == "" {
continue
}
address := strings.TrimSpace(row[2])
lat := strings.TrimSpace(row[3])
lon := strings.TrimSpace(row[4])
var latF, lonF float64
fmt.Sscanf(lat, "%f", &latF)
fmt.Sscanf(lon, "%f", &lonF)
excelData = append(excelData, model.TransitPoint{
CompanyID: 784,
Name: name,
Address: address,
Latitude: latF,
Longitude: lonF,
CreatedDate: utils.FormatToWithoutTZ(ctx, now),
CreatedBy: "system",
})
}
names := []string{}
for _, p := range excelData {
names = append(names, fmt.Sprintf("'%s'", strings.ReplaceAll(p.Name, "'", "''")))
}
filter := fmt.Sprintf("AND a.company_id=784 AND a.name IN (%s)", strings.Join(names, ","))
data, err := GetTransitPoints(ctx, gormDB, filter)
if err != nil {
log.Fatalf("Gagal GetTransitPoints: %v", err)
}
dbMap := map[string]model.TransitPoint{}
for _, d := range data {
dbMap[d.Name] = *d
}
for _, p := range excelData {
geomWkt := GenerateGeomWkt(ctx, gormDB, p.Longitude, p.Latitude, float64(100))
p.GeomWkt = geomWkt
p.ModifiedBy = "system"
p.ModifiedDate = utils.FormatToWithoutTZ(ctx, now)
if existing, exists := dbMap[p.Name]; exists {
if existing.Address != p.Address || existing.Latitude != p.Latitude || existing.Longitude != p.Longitude {
_, err := UpdateTransitPointDB(ctx, gormDB, &p)
if err != nil {
log.Printf("Gagal update %s: %v", p.Name, err)
} else {
fmt.Printf("Berhasil update: %s\n", p.Name)
}
}
} else {
_, err := InsertTransitPointDB(ctx, gormDB, &p)
if err != nil {
log.Printf("Gagal insert %s: %v", p.Name, err)
} else {
fmt.Printf("Berhasil insert: %s\n", p.Name)
}
}
}
}
func InsertTransitPointDB(ctx context.Context, db *gorm.DB, data *model.TransitPoint) (*model.TransitPoint, error) {
span, spanCtx := opentracing.StartSpanFromContext(ctx, "insertTransitPointDB")
defer span.Finish()
payload := data.BeforeCheck(spanCtx)
tx := db.WithContext(spanCtx).Begin().Scopes(payload.TableName(spanCtx))
if err := tx.Error; err != nil {
return nil, err
}
if payload.GeomWkt.String != "" {
tx.Clauses(clause.Expr{SQL: "ST_GeomFromText(?, 4326)", Vars: []interface{}{payload.GeomWkt}})
}
tx.Create(&payload)
if err := tx.Error; err != nil {
tx.Rollback()
return nil, err
}
if err := tx.Commit().Error; err != nil {
tx.Rollback()
return nil, err
}
return payload.Convert(spanCtx), nil
}
func UpdateTransitPointDB(ctx context.Context, db *gorm.DB, data *model.TransitPoint) (*model.TransitPoint, error) {
span, spanCtx := opentracing.StartSpanFromContext(ctx, "updateTransitPointDB")
defer span.Finish()
payload := data.BeforeCheck(spanCtx)
tx := db.WithContext(spanCtx).Begin().Scopes(payload.TableName(spanCtx))
if err := tx.Error; err != nil {
return nil, err
}
// Update GeomWkt jika ada
if payload.GeomWkt.String != "" {
tx = tx.Model(&payload).Clauses(clause.Expr{
SQL: "geom = ST_GeomFromText(?, 4326)",
Vars: []interface{}{payload.GeomWkt},
})
}
tx = tx.Model(&payload).Where("id = ?", payload.ID).Updates(map[string]interface{}{
"address": payload.Address,
"latitude": payload.Latitude,
"longitude": payload.Longitude,
"modified_by": payload.ModifiedBy,
"modified_date": payload.ModifiedDate,
})
if tx.Error != nil {
tx.Rollback()
return nil, tx.Error
}
if err := tx.Commit().Error; err != nil {
tx.Rollback()
return nil, err
}
return payload.Convert(spanCtx), nil
}
func GenerateGeomWkt(ctx context.Context, db *gorm.DB, lon, lat, bufferRadius float64) string {
var value sql.NullString
tx := db.WithContext(ctx)
query := fmt.Sprintf("SELECT ST_AsText(ST_Buffer(ST_GeomFromText('POINT(%0.7f %0.7f)', 4326), 0.00001 * %v, 'quad_segs=2')) AS geom_text LIMIT 1", lon, lat, bufferRadius)
tx = tx.Raw(query)
tx.Row().Scan(&value)
return strings.TrimSpace(value.String)
}
func GetTransitPoints(ctx context.Context, db *gorm.DB, filter string) ([]*model.TransitPoint, error) {
tx := db.WithContext(ctx)
query := fmt.Sprintf(`SELECT a.id,a.company_id,a.customer_id,a.customer_ids,a.trip_group_transit_point_id,a.status,a.blacklist
,a.latitude,a.longitude,a.register_date,a.unregister_date,a.created_date,a.modified_date
,a.name,a.code,a.geocode,a.address,a.created_by,a.modified_by,a.pic_name,a.pic_phone,a.pic_mobile
,a.transit_point_type,a.remark,a.coords,a.fax,a.mobile_no,a.phone,a.image,a.zip_code,a.geom
FROM m_transit_point AS a
WHERE a.name IS NOT NULL AND a.name <> ''
%v`, filter)
fmt.Printf("query : %v\n", query)
rows, err := tx.Raw(query).Rows()
if err != nil {
return nil, err
}
defer rows.Close()
data := []*model.TransitPoint{}
for rows.Next() {
dataScan := model.TransitPointDB{}
err := rows.Scan(
&dataScan.ID,
&dataScan.CompanyID,
&dataScan.CustomerID,
&dataScan.CustomerIDs,
&dataScan.TripGroupTransitPointID,
&dataScan.Status,
&dataScan.Blacklist,
&dataScan.Latitude,
&dataScan.Longitude,
&dataScan.RegisterDate,
&dataScan.UnregisterDate,
&dataScan.CreatedDate,
&dataScan.ModifiedDate,
&dataScan.Name,
&dataScan.Code,
&dataScan.Geocode,
&dataScan.Address,
&dataScan.CreatedBy,
&dataScan.ModifiedBy,
&dataScan.PicName,
&dataScan.PicPhone,
&dataScan.PicMobile,
&dataScan.TransitPointType,
&dataScan.Remark,
&dataScan.Coords,
&dataScan.Fax,
&dataScan.MobileNo,
&dataScan.Phone,
&dataScan.Image,
&dataScan.ZipCode,
&dataScan.GeomWkb,
)
if err != nil {
return nil, err
}
result := dataScan.Convert(ctx)
data = append(data, result)
}
return data, nil
}

215
model/transitPoint.go Normal file
View File

@ -0,0 +1,215 @@
package model
import (
"context"
"database/sql"
"fmt"
"gen_transit_point/utils"
"github.com/lib/pq"
"github.com/opentracing/opentracing-go"
"gorm.io/gorm"
"gorm.io/gorm/schema"
)
type TransitPoint struct {
ID int64 `json:"id"`
CompanyID int64 `json:"company_id"`
CustomerID int64 `json:"customer_id"`
CustomerIDs []int64 `json:"customer_ids"`
TripGroupTransitPointID *int64 `json:"trip_group_transit_point_id"`
Status bool `json:"status"`
Blacklist bool `json:"blacklist"`
Latitude float64 `json:"latitude"`
Longitude float64 `json:"longitude"`
RegisterDate string `json:"register_date"`
UnregisterDate string `json:"unregister_date"`
CreatedDate string `json:"created_date"`
ModifiedDate string `json:"modified_date"`
Name string `json:"name"`
Code string `json:"code"`
Geocode string `json:"geocode"`
Address string `json:"address"`
CreatedBy string `json:"created_by"`
ModifiedBy string `json:"modified_by"`
PicName string `json:"pic_name"`
PicPhone string `json:"pic_phone"`
PicMobile string `json:"pic_mobile"`
TransitPointType string `json:"transit_point_type"`
Remark string `json:"remark"`
Coords string `json:"coords"`
Fax string `json:"fax"`
MobileNo string `json:"mobile_no"`
Phone string `json:"phone"`
Image string `json:"image"`
ZipCode string `json:"zip_code"`
GeomWkt string `json:"geom" gorm:"column:geom" validate:"required"`
GeomWkb string `json:"geom_wkb,omitempty" gorm:"-"`
CentroidLon float64 `json:"centroid_lon,omitempty" gorm:"-"`
CentroidLat float64 `json:"centroid_lat,omitempty" gorm:"-"`
}
type TransitPointDB struct {
ID sql.NullInt64 `json:"id"`
CompanyID sql.NullInt64 `json:"company_id"`
CustomerID sql.NullInt64 `json:"customer_id"`
CustomerIDs pq.Int64Array `json:"customer_ids" gorm:"type:bigint[];default:'{}'::bigint[]"`
TripGroupTransitPointID sql.NullInt64 `json:"trip_group_transit_point_id"`
Status sql.NullBool `json:"status"`
Blacklist sql.NullBool `json:"blacklist"`
Latitude sql.NullFloat64 `json:"latitude"`
Longitude sql.NullFloat64 `json:"longitude"`
RegisterDate sql.NullTime `json:"register_date"`
UnregisterDate sql.NullTime `json:"unregister_date"`
CreatedDate sql.NullTime `json:"created_date"`
ModifiedDate sql.NullTime `json:"modified_date"`
Name sql.NullString `json:"name"`
Code sql.NullString `json:"code"`
Geocode sql.NullString `json:"geocode"`
Address sql.NullString `json:"address"`
CreatedBy sql.NullString `json:"created_by"`
ModifiedBy sql.NullString `json:"modified_by"`
PicName sql.NullString `json:"pic_name"`
PicPhone sql.NullString `json:"pic_phone"`
PicMobile sql.NullString `json:"pic_mobile"`
TransitPointType sql.NullString `json:"transit_point_type"`
Remark sql.NullString `json:"remark"`
Coords sql.NullString `json:"coords"`
Fax sql.NullString `json:"fax"`
MobileNo sql.NullString `json:"mobile_no"`
Phone sql.NullString `json:"phone"`
Image sql.NullString `json:"image"`
ZipCode sql.NullString `json:"zip_code"`
GeomWkt sql.NullString `json:"geom_wkt" gorm:"column:geom"`
GeomWkb sql.NullString `json:"geom_wkb" gorm:"-"`
CentroidLon sql.NullFloat64 `json:"centroid_lon" gorm:"-"`
CentroidLat sql.NullFloat64 `json:"centroid_lat" gorm:"-"`
}
func (t *TransitPoint) BeforeCheck(ctx context.Context) *TransitPointDB {
checkLat := utils.ValidCoords(ctx, t.Latitude, utils.VALID_LATITUDE)
checkLon := utils.ValidCoords(ctx, t.Longitude, utils.VALID_LONGITUDE)
if !checkLat || !checkLon {
lat := t.Longitude
lon := t.Latitude
t.Latitude = lat
t.Longitude = lon
}
data := &TransitPointDB{
ID: utils.NewNullInt64(ctx, &t.ID),
CompanyID: utils.NewNullInt64(ctx, &t.CompanyID),
CustomerID: utils.NewNullInt64(ctx, &t.CustomerID),
CustomerIDs: pq.Int64Array(t.CustomerIDs),
TripGroupTransitPointID: utils.NewNullInt64(ctx, t.TripGroupTransitPointID),
Status: utils.NewNullBool(ctx, &t.Status),
Blacklist: utils.NewNullBool(ctx, &t.Blacklist),
Latitude: utils.NewNullFloat64(ctx, &t.Latitude),
Longitude: utils.NewNullFloat64(ctx, &t.Longitude),
RegisterDate: utils.NewNullStringTime(ctx, &t.RegisterDate),
UnregisterDate: utils.NewNullStringTime(ctx, &t.UnregisterDate),
CreatedDate: utils.NewNullStringTime(ctx, &t.CreatedDate),
ModifiedDate: utils.NewNullStringTime(ctx, &t.ModifiedDate),
Name: utils.NewNullString(ctx, &t.Name),
Code: utils.NewNullString(ctx, &t.Code),
Geocode: utils.NewNullString(ctx, &t.Geocode),
Address: utils.NewNullString(ctx, &t.Address),
CreatedBy: utils.NewNullString(ctx, &t.CreatedBy),
ModifiedBy: utils.NewNullString(ctx, &t.ModifiedBy),
PicName: utils.NewNullString(ctx, &t.PicName),
PicPhone: utils.NewNullString(ctx, &t.PicPhone),
PicMobile: utils.NewNullString(ctx, &t.PicMobile),
TransitPointType: utils.NewNullString(ctx, &t.TransitPointType),
Remark: utils.NewNullString(ctx, &t.Remark),
Coords: utils.NewNullString(ctx, &t.Coords),
Fax: utils.NewNullString(ctx, &t.Fax),
MobileNo: utils.NewNullString(ctx, &t.MobileNo),
Phone: utils.NewNullString(ctx, &t.Phone),
Image: utils.NewNullString(ctx, &t.Image),
ZipCode: utils.NewNullString(ctx, &t.ZipCode),
GeomWkt: utils.NewNullString(ctx, &t.GeomWkt),
GeomWkb: utils.NewNullString(ctx, &t.GeomWkb),
CentroidLon: utils.NewNullFloat64(ctx, &t.CentroidLon),
CentroidLat: utils.NewNullFloat64(ctx, &t.CentroidLat),
}
return data
}
func (t *TransitPointDB) TableName(ctx context.Context, alias ...string) func(db *gorm.DB) *gorm.DB {
span, spanCtx := opentracing.StartSpanFromContext(ctx, "tableName")
defer span.Finish()
return func(db *gorm.DB) *gorm.DB {
db = t.NamingStrategy(spanCtx, db)
table := db.Statement.Config.NamingStrategy.TableName("transit_point")
if len(alias) > 0 && alias[0] != "" {
return db.Table(fmt.Sprintf("%s AS %s", table, alias[0]))
}
return db.Table(table)
}
}
func (t *TransitPointDB) NamingStrategy(ctx context.Context, tx *gorm.DB) *gorm.DB {
span, spanCtx := opentracing.StartSpanFromContext(ctx, "namingStrategy")
defer span.Finish()
tx.Statement.Context = spanCtx
tx.Config.NamingStrategy = schema.NamingStrategy{
TablePrefix: "m_",
SingularTable: true,
}
return tx
}
func (t *TransitPointDB) Convert(ctx context.Context) *TransitPoint {
data := &TransitPoint{
ID: t.ID.Int64,
CompanyID: t.CompanyID.Int64,
CustomerID: t.CustomerID.Int64,
CustomerIDs: []int64(t.CustomerIDs),
TripGroupTransitPointID: &t.TripGroupTransitPointID.Int64,
Status: t.Status.Bool,
Blacklist: t.Blacklist.Bool,
Latitude: t.Latitude.Float64,
Longitude: t.Longitude.Float64,
RegisterDate: utils.FormatToWithoutTZ(ctx, t.RegisterDate.Time),
UnregisterDate: utils.FormatToWithoutTZ(ctx, t.UnregisterDate.Time),
CreatedDate: utils.FormatToWithoutTZ(ctx, t.CreatedDate.Time),
ModifiedDate: utils.FormatToWithoutTZ(ctx, t.ModifiedDate.Time),
Name: t.Name.String,
Code: t.Code.String,
Geocode: t.Geocode.String,
Address: t.Address.String,
CreatedBy: t.CreatedBy.String,
ModifiedBy: t.ModifiedBy.String,
PicName: t.PicName.String,
PicPhone: t.PicPhone.String,
PicMobile: t.PicMobile.String,
TransitPointType: t.TransitPointType.String,
Remark: t.Remark.String,
Coords: t.Coords.String,
Fax: t.Fax.String,
MobileNo: t.MobileNo.String,
Phone: t.Phone.String,
Image: t.Image.String,
ZipCode: t.ZipCode.String,
//GeomWkb: t.GeomWkb.String,
GeomWkt: t.GeomWkt.String,
//CentroidLon: t.CentroidLon.Float64,
//CentroidLat: t.CentroidLat.Float64,
}
checkLat := utils.ValidCoords(ctx, data.Latitude, utils.VALID_LATITUDE)
checkLon := utils.ValidCoords(ctx, data.Longitude, utils.VALID_LONGITUDE)
if !checkLat || !checkLon {
lat := data.Longitude
lon := data.Latitude
data.Latitude = lat
data.Longitude = lon
}
/* if data.Latitude == 0 || data.Longitude == 0 {
data.Latitude = data.CentroidLat
data.Longitude = data.CentroidLon
} */
return data
}

BIN
transit_point_indosps.xlsx Normal file

Binary file not shown.

6
utils/constant.go Normal file
View File

@ -0,0 +1,6 @@
package utils
const (
VALID_LATITUDE = "latitude"
VALID_LONGITUDE = "longitude"
)

257
utils/convert.go Normal file
View File

@ -0,0 +1,257 @@
package utils
import (
"context"
"crypto/md5"
"database/sql"
"fmt"
"math"
"strings"
"time"
)
const (
TIME_LAYOUT_DATE = "2006-01-02"
TIME_LAYOUT_DB_WITHOUT_TZ = "2006-01-02 15:04:05"
)
type ConvertSecondToDHMS struct {
Days int64 `json:"days"`
Hours int64 `json:"hours"`
Minutes int64 `json:"minutes"`
Seconds int64 `json:"seconds"`
}
func FormatToDate(ctx context.Context, t time.Time) string {
if t.IsZero() {
return "1900-01-01"
}
return t.Format(TIME_LAYOUT_DATE)
}
func FormatToWithoutTZ(ctx context.Context, t time.Time) string {
if t.IsZero() {
return "1900-01-01 00:00:00"
}
return t.Format(TIME_LAYOUT_DB_WITHOUT_TZ)
}
func NewNullFromFormatWithoutTZ(ctx context.Context, v *string) *time.Time {
var value *time.Time
if v == nil {
return nil
}
check := strings.TrimSpace(*v)
if check == "" {
return nil
}
if strings.Contains(check, "1900-01-01") {
return nil
}
switch check {
case "1900-01-01", "0001-01-01", "1900-01-01 00:00:00", "0001-01-01 00:00:00":
return nil
default:
result, _ := time.Parse(time.RFC3339, strings.ReplaceAll(check, " ", "T")+"+07:00")
if !result.IsZero() {
return &result
}
}
return value
}
func NewNullInt64(ctx context.Context, v *int64) sql.NullInt64 {
if v == nil {
return sql.NullInt64{}
}
if *v <= 0 {
return sql.NullInt64{}
}
return sql.NullInt64{Int64: *v, Valid: true}
}
func NewNullInt32(ctx context.Context, v *int32) sql.NullInt32 {
if v == nil {
return sql.NullInt32{}
}
if *v <= 0 {
return sql.NullInt32{}
}
return sql.NullInt32{Int32: *v, Valid: true}
}
func NewNullInt16(ctx context.Context, v *int16) sql.NullInt16 {
if v == nil {
return sql.NullInt16{}
}
if *v <= 0 {
return sql.NullInt16{}
}
return sql.NullInt16{Int16: *v, Valid: true}
}
func NewNullFloat64(ctx context.Context, v *float64) sql.NullFloat64 {
if v == nil {
return sql.NullFloat64{}
}
return sql.NullFloat64{Float64: *v, Valid: true}
}
func NewNullString(ctx context.Context, v *string) sql.NullString {
if v == nil {
return sql.NullString{}
}
s := strings.TrimSpace(*v)
if len(s) <= 0 {
return sql.NullString{}
}
return sql.NullString{String: s, Valid: true}
}
func NewNullTime(ctx context.Context, v *time.Time) sql.NullTime {
if v == nil {
return sql.NullTime{}
}
value := *v
if value.IsZero() {
return sql.NullTime{}
}
return sql.NullTime{Time: value, Valid: true}
}
func NewNullStringTime(ctx context.Context, v *string) sql.NullTime {
if v == nil {
return sql.NullTime{}
}
s := strings.TrimSpace(*v)
result, _ := time.Parse(time.RFC3339, s)
if !result.IsZero() {
return sql.NullTime{Time: result, Valid: true}
}
values := strings.Split(s, " ")
if len(values) <= 0 {
return sql.NullTime{}
} else if len(values) == 2 {
dateTime := strings.ReplaceAll(s, " ", "T")
result, _ = time.Parse(time.RFC3339, dateTime+"+07:00")
} else if len(values) == 1 {
date := strings.TrimSpace(values[0])
result, _ = time.Parse(time.RFC3339, date+"T00:00:00+07:00")
}
if result.IsZero() {
return sql.NullTime{}
}
return sql.NullTime{Time: result, Valid: true}
}
func NewNullBool(ctx context.Context, v *bool) sql.NullBool {
if v == nil {
return sql.NullBool{}
}
check := *v
value := sql.NullBool{Bool: check, Valid: true}
value.Scan(false)
if check {
value.Scan(true)
}
return value
}
func HashToMD5(ctx context.Context, password string) string {
var result string
value := strings.TrimSpace(password)
if value != "" {
hashed := []byte(value)
result = fmt.Sprintf("%x", md5.Sum(hashed))
}
return result
}
func GetConvertSecondToDHMS(ctx context.Context, n int64) ConvertSecondToDHMS {
day := int64(n / (24 * 3600))
n = n % (24 * 3600)
hour := int64(n / 3600)
n %= 3600
minutes := int64(n / 60)
n %= 60
seconds := n
result := ConvertSecondToDHMS{
Days: day,
Hours: hour,
Minutes: minutes,
Seconds: seconds,
}
return result
}
func SetToHMS(ctx context.Context, data ConvertSecondToDHMS) string {
hours := "00"
if data.Hours >= 0 && data.Hours < 10 {
hours = fmt.Sprintf("0%d", data.Hours)
} else {
hours = fmt.Sprintf("%d", data.Hours)
}
minutes := "00"
if data.Minutes >= 0 && data.Minutes < 10 {
minutes = fmt.Sprintf("0%d", data.Minutes)
} else if data.Minutes >= 10 && data.Minutes <= 59 {
minutes = fmt.Sprintf("%d", data.Minutes)
}
seconds := "00"
if data.Seconds >= 0 && data.Seconds < 10 {
seconds = fmt.Sprintf("0%d", data.Seconds)
} else if data.Seconds >= 10 && data.Seconds <= 59 {
seconds = fmt.Sprintf("%d", data.Seconds)
}
return fmt.Sprintf("%s:%s:%s", hours, minutes, seconds)
}
func SetToSummaryHMS(ctx context.Context, data ConvertSecondToDHMS, withFormat bool) string {
result := ""
if data.Days >= 0 {
totalHours := (data.Days * 24) + data.Hours
hours := "00"
if totalHours >= 0 && totalHours < 10 {
hours = fmt.Sprintf("0%d", totalHours)
} else {
hours = fmt.Sprintf("%d", totalHours)
}
minutes := "00"
if data.Minutes >= 0 && data.Minutes < 10 {
minutes = fmt.Sprintf("0%d", data.Minutes)
} else if data.Minutes >= 10 && data.Minutes <= 59 {
minutes = fmt.Sprintf("%d", data.Minutes)
}
seconds := "00"
if data.Seconds >= 0 && data.Seconds < 10 {
seconds = fmt.Sprintf("0%d", data.Seconds)
} else if data.Seconds >= 10 && data.Seconds <= 59 {
seconds = fmt.Sprintf("%d", data.Seconds)
}
result = fmt.Sprintf("%s:%s:%s", hours, minutes, seconds)
if withFormat {
result = fmt.Sprintf("%sH %sM %sS", hours, minutes, seconds)
}
}
return result
}
func ToFixed(num float64, precision int) float64 {
output := math.Pow(10, float64(precision))
return math.Round(num*output) / output
}

19
utils/helper.go Normal file
View File

@ -0,0 +1,19 @@
package utils
import (
"context"
)
func ValidCoords(ctx context.Context, value float64, key string) bool {
switch key {
case VALID_LATITUDE:
if value >= -90 && value <= 90 {
return true
}
case VALID_LONGITUDE:
if value >= -180 && value <= 180 {
return true
}
}
return false
}