update ignore
This commit is contained in:
No files matched your search
@@ -0,0 +1,217 @@
|
||||
package kfa
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"service/internal/infrastructure/database"
|
||||
"service/pkg/utils/query"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
type CommandRepository interface {
|
||||
UpsertProductBundle(ctx context.Context, product *KfaProduct, ingredients []KfaProductActiveIngredient, packagings []KfaProductPackaging) error
|
||||
}
|
||||
|
||||
type commandRepository struct {
|
||||
dbManager database.Service
|
||||
connName string
|
||||
}
|
||||
|
||||
func NewCommandRepository(dbManager database.Service, connName string) CommandRepository {
|
||||
return &commandRepository{
|
||||
dbManager: dbManager,
|
||||
connName: connName,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *commandRepository) UpsertProductBundle(ctx context.Context, product *KfaProduct, ingredients []KfaProductActiveIngredient, packagings []KfaProductPackaging) (err error) {
|
||||
dbGorm, err := r.dbManager.GetGormDB(r.connName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sqldb, err := dbGorm.DB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dbx := sqlx.NewDb(sqldb, "postgres")
|
||||
|
||||
// Inisialisasi Query Builder (Nonaktifkan limit sekuritas untuk internal worker)
|
||||
sqlBuilder := query.NewSQLQueryBuilder(query.DBTypePostgreSQL).
|
||||
SetQueryLogging(false).
|
||||
SetSecurityOptions(false, 0)
|
||||
|
||||
// Ekstrak semua kolom yang akan digunakan untuk mendaftarkannya ke whitelist Query Builder
|
||||
productData, productUpdateCols := extractInsertData(product, "kfa_code")
|
||||
var allowedCols []string
|
||||
allowedCols = append(allowedCols, productData.Columns...)
|
||||
if len(ingredients) > 0 {
|
||||
ingData, _ := extractInsertData(ingredients[0], "")
|
||||
allowedCols = append(allowedCols, ingData.Columns...)
|
||||
}
|
||||
if len(packagings) > 0 {
|
||||
pkgData, _ := extractInsertData(packagings[0], "")
|
||||
allowedCols = append(allowedCols, pkgData.Columns...)
|
||||
}
|
||||
allowedCols = append(allowedCols, "product_kfa_code", "kfa_code") // pastikan key relasi / conflict dimasukkan
|
||||
|
||||
// Daftarkan kolom agar terhindar dari error 'disallowed column'
|
||||
sqlBuilder.SetAllowedColumns(allowedCols)
|
||||
|
||||
tx, err := dbx.BeginTxx(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Tangani Rollback jika terjadi kegagalan atau panic
|
||||
defer func() {
|
||||
if p := recover(); p != nil {
|
||||
tx.Rollback()
|
||||
panic(p)
|
||||
} else if err != nil {
|
||||
tx.Rollback()
|
||||
}
|
||||
}()
|
||||
|
||||
// 1. Upsert Induk Product (Manual Check & Update/Insert to avoid PG Constraint Error 42P10)
|
||||
var count int
|
||||
err = tx.GetContext(ctx, &count, "SELECT count(*) FROM master.kfa_products WHERE kfa_code = $1", product.KfaCode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if count > 0 {
|
||||
updateData := query.UpdateData{
|
||||
Columns: productUpdateCols,
|
||||
Values: make([]interface{}, len(productUpdateCols)),
|
||||
}
|
||||
for i, col := range productUpdateCols {
|
||||
for j, insertCol := range productData.Columns {
|
||||
if col == insertCol {
|
||||
updateData.Values[i] = productData.Values[j]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
_, err = sqlBuilder.ExecuteUpdate(ctx, tx, "master.kfa_products", updateData, []query.FilterGroup{{
|
||||
Filters: []query.DynamicFilter{query.CreateEqualFilter("kfa_code", product.KfaCode)},
|
||||
}})
|
||||
} else {
|
||||
_, err = sqlBuilder.ExecuteInsert(ctx, tx, "master.kfa_products", productData)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 2. Hapus Active Ingredients Lama
|
||||
delFilters := []query.FilterGroup{{
|
||||
Filters: []query.DynamicFilter{
|
||||
query.CreateEqualFilter("product_kfa_code", product.KfaCode),
|
||||
},
|
||||
}}
|
||||
_, err = sqlBuilder.ExecuteDelete(ctx, tx, "master.kfa_product_active_ingredients", delFilters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 3. Insert Active Ingredients Baru
|
||||
for _, ing := range ingredients {
|
||||
ingData, _ := extractInsertData(ing, "")
|
||||
_, err = sqlBuilder.ExecuteInsert(ctx, tx, "master.kfa_product_active_ingredients", ingData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Hapus Packagings Lama
|
||||
_, err = sqlBuilder.ExecuteDelete(ctx, tx, "master.kfa_product_packagings", delFilters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 5. Insert Packagings Baru
|
||||
for _, pkg := range packagings {
|
||||
pkgData, _ := extractInsertData(pkg, "")
|
||||
_, err = sqlBuilder.ExecuteInsert(ctx, tx, "master.kfa_product_packagings", pkgData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = tx.Commit()
|
||||
return err
|
||||
}
|
||||
|
||||
// Helper: Ekstrak data model struct menjadi InsertData format untuk QueryBuilder
|
||||
func extractInsertData(obj interface{}, conflictCol string) (query.InsertData, []string) {
|
||||
val := reflect.ValueOf(obj)
|
||||
if val.Kind() == reflect.Ptr {
|
||||
val = val.Elem()
|
||||
}
|
||||
typ := val.Type()
|
||||
|
||||
var cols []string
|
||||
var vals []interface{}
|
||||
var updateCols []string
|
||||
|
||||
for i := 0; i < typ.NumField(); i++ {
|
||||
field := typ.Field(i)
|
||||
if field.PkgPath != "" { // Skip properti yang tidak diekspor
|
||||
continue
|
||||
}
|
||||
|
||||
colName := toSnakeCase(field.Name)
|
||||
if colName == "id" { // ID selalu di-skip pada operasi insert karena dia auto increment
|
||||
continue
|
||||
}
|
||||
|
||||
fieldVal := val.Field(i).Interface()
|
||||
cols = append(cols, colName)
|
||||
|
||||
if b, ok := fieldVal.(json.RawMessage); ok {
|
||||
if len(b) == 0 || string(b) == "null" {
|
||||
vals = append(vals, nil)
|
||||
} else {
|
||||
vals = append(vals, string(b))
|
||||
}
|
||||
} else {
|
||||
vals = append(vals, fieldVal)
|
||||
}
|
||||
|
||||
// Cek apa saja kolom yang valid di-update saat konflik (mengecualikan relasi/kode referensi)
|
||||
if colName != conflictCol && colName != "created_at" && colName != "product_kfa_code" {
|
||||
updateCols = append(updateCols, colName)
|
||||
}
|
||||
}
|
||||
|
||||
return query.InsertData{
|
||||
Columns: cols,
|
||||
Values: vals,
|
||||
}, updateCols
|
||||
}
|
||||
|
||||
// Helper: Ubah PascalCase field Go ke mode snake_case untuk tabel database
|
||||
func toSnakeCase(s string) string {
|
||||
if s == "ID" {
|
||||
return "id"
|
||||
}
|
||||
var result strings.Builder
|
||||
var prevUpper bool
|
||||
for i, r := range s {
|
||||
if unicode.IsUpper(r) {
|
||||
if i > 0 && !prevUpper {
|
||||
result.WriteByte('_')
|
||||
}
|
||||
result.WriteRune(unicode.ToLower(r))
|
||||
prevUpper = true
|
||||
} else {
|
||||
result.WriteRune(r)
|
||||
prevUpper = false
|
||||
}
|
||||
}
|
||||
return result.String()
|
||||
}
|
||||
Reference in New Issue
Block a user