package repository import ( "context" "database/sql" "errors" "fmt" "math" "strconv" "strings" "time" ) type AddLabServicesParams struct { Layanan []RequestedLabService Channel string } type RequestedLabService struct { LayananID string Jumlah int Cito bool MarkupCito float64 Catatan string } type resolvedLabService struct { LayananID string LayananDepartemenID int64 Satuan sql.NullString UnitCost float64 FlagTipe sql.NullString Jumlah int Cito string MarkupCito float64 Harga float64 HargaDefault float64 Total float64 ShareTipe string ShareRS float64 ShareDokter float64 ShareExternal float64 ShareLain float64 Catatan string } type labRegistrationServiceState struct { Registration dbLabRegistration KelasID sql.NullString BatalDaftar string Pulang string } type labTransactionServiceState struct { Transaction dbLabTransaction DepartemenID sql.NullString DokterID sql.NullString KelasID sql.NullString } type multipleLabDetail struct { LayananID string HeaderDetail sql.NullInt64 Urutan sql.NullInt64 LevelLayanan sql.NullInt64 Satuan sql.NullString UnitCost sql.NullString LayananDepartemenID sql.NullInt64 } func (r MySQLLayananRepository) AddLabServices(ctx context.Context, regID string, params AddLabServicesParams) (LabRegistrationOutcome, error) { var lastErr error for attempt := 0; attempt < 3; attempt++ { outcome, err := r.addLabServicesAttempt(ctx, regID, params) if err == nil { return outcome, nil } lastErr = err if !isConcurrencyError(err) || ctx.Err() != nil { return LabRegistrationOutcome{}, err } } return LabRegistrationOutcome{}, lastErr } func (r MySQLLayananRepository) addLabServicesAttempt(ctx context.Context, regID string, params AddLabServicesParams) (outcome LabRegistrationOutcome, err error) { tx, err := r.db.BeginTx(ctx, nil) if err != nil { return LabRegistrationOutcome{}, err } defer func() { if recovered := recover(); recovered != nil { _ = tx.Rollback() panic(recovered) } if err != nil { _ = tx.Rollback() } }() outcome, err = addLabServicesInTransaction(ctx, tx, regID, params) if err != nil { return LabRegistrationOutcome{}, err } if err = tx.Commit(); err != nil { return LabRegistrationOutcome{}, err } return outcome, nil } func addLabServicesInTransaction(ctx context.Context, tx *sql.Tx, regID string, params AddLabServicesParams) (LabRegistrationOutcome, error) { registration, found, err := findLabRegistrationStateForUpdate(ctx, tx, regID) if err != nil { return LabRegistrationOutcome{}, err } if !found { return labOutcome(404, false, "registration_not_found", "Registrasi laboratorium tidak ditemukan", nil), nil } if registration.Registration.JenisPasienID != 0 { return LabRegistrationOutcome{}, ValidationError{Message: "Penambahan layanan laboratorium API saat ini hanya mendukung pasien umum"} } if registration.BatalDaftar == "Y" { return labOutcome(409, false, "registration_cancelled", "Registrasi laboratorium sudah dibatalkan", nil), nil } if registration.Pulang == "Y" { return labOutcome(409, false, "registration_closed", "Registrasi laboratorium sudah ditutup", nil), nil } transaction, found, err := findLabTransactionStateForUpdate( ctx, tx, registration.Registration.RegID, registration.Registration.DepartemenID, ) if err != nil { return LabRegistrationOutcome{}, err } if !found { return labOutcome(404, false, "transaction_not_found", "Transaksi departemen laboratorium tidak ditemukan", nil), nil } if registration.Registration.PeriodeTarifID < 1 { return LabRegistrationOutcome{}, ValidationError{Message: "Periode tarif registrasi tidak valid"} } classID := "Default" if transaction.KelasID.Valid { classID = transaction.KelasID.String } else if registration.KelasID.Valid { classID = registration.KelasID.String } classID = strings.TrimSpace(classID) if classID == "" { classID = "Default" } requestedIDs := make([]string, len(params.Layanan)) for index, service := range params.Layanan { requestedIDs[index] = strings.TrimSpace(service.LayananID) } existingIDs, err := findExistingLabServiceIDs(ctx, tx, transaction.Transaction.TrxDepartemenID, requestedIDs) if err != nil { return LabRegistrationOutcome{}, err } existingSet := make(map[string]struct{}, len(existingIDs)) for _, serviceID := range existingIDs { existingSet[serviceID] = struct{}{} } servicesToAdd := make([]RequestedLabService, 0, len(params.Layanan)) for _, service := range params.Layanan { if _, exists := existingSet[strings.TrimSpace(service.LayananID)]; !exists { servicesToAdd = append(servicesToAdd, service) } } if len(servicesToAdd) == 0 { data, err := buildLabRegistrationData(ctx, tx, registration.Registration, &transaction.Transaction) if err != nil { return LabRegistrationOutcome{}, err } added := make([]string, 0) data.AddedLayananIDs = &added data.SkippedLayananIDs = &existingIDs return labOutcome( 200, true, "services_already_added", "Seluruh layanan sudah terdaftar pada kunjungan laboratorium ini", &data, ), nil } resolved, err := resolveLabServices( ctx, tx, servicesToAdd, registration.Registration.DepartemenID, classID, registration.Registration.PeriodeTarifID, ) if err != nil { return LabRegistrationOutcome{}, err } jakarta, err := time.LoadLocation("Asia/Jakarta") if err != nil { return LabRegistrationOutcome{}, err } now := time.Now().In(jakarta).Format("2006-01-02 15:04:05") doctorID := registration.Registration.DokterID if transaction.DokterID.Valid { doctorID = transaction.DokterID } doctorValue := "" if doctorID.Valid { doctorValue = doctorID.String } for _, service := range resolved { if err := insertResolvedLabService( ctx, tx, service, registration.Registration.RegID, transaction.Transaction.TrxDepartemenID, registration.Registration.MEDRECID, registration.Registration.DepartemenID, doctorValue, classID, params.Channel, now, ); err != nil { return LabRegistrationOutcome{}, err } } if err := syncLabSpecimens(ctx, tx, transaction.Transaction.TrxDepartemenID, transaction.Transaction.TrxID, params.Channel, now); err != nil { return LabRegistrationOutcome{}, err } if err := updateLabTransactionTotal( ctx, tx, transaction.Transaction.TrxDepartemenID, registration.Registration.RegID, registration.Registration.DepartemenID, ); err != nil { return LabRegistrationOutcome{}, err } data, err := buildLabRegistrationData(ctx, tx, registration.Registration, &transaction.Transaction) if err != nil { return LabRegistrationOutcome{}, err } addedIDs := make([]string, len(resolved)) for index, service := range resolved { addedIDs[index] = service.LayananID } data.AddedLayananIDs = &addedIDs data.SkippedLayananIDs = &existingIDs return labOutcome(201, true, "services_added", "Layanan laboratorium berhasil ditambahkan", &data), nil } func findLabRegistrationStateForUpdate(ctx context.Context, tx *sql.Tx, regID string) (labRegistrationServiceState, bool, error) { var state labRegistrationServiceState err := tx.QueryRowContext(ctx, ` SELECT RegID, MEDRECID, Tanggal, DepartemenID, DokterID, JenisPasienID, JenisRegID, PeriodeTarifID, KelasID, BatalDaftar, Pulang FROM regpas WHERE RegID = ? AND NA = 'N' LIMIT 1 FOR UPDATE`, regID).Scan( &state.Registration.RegID, &state.Registration.MEDRECID, &state.Registration.Tanggal, &state.Registration.DepartemenID, &state.Registration.DokterID, &state.Registration.JenisPasienID, &state.Registration.JenisRegID, &state.Registration.PeriodeTarifID, &state.KelasID, &state.BatalDaftar, &state.Pulang, ) if errors.Is(err, sql.ErrNoRows) { return labRegistrationServiceState{}, false, nil } if err != nil { return labRegistrationServiceState{}, false, err } return state, true, nil } func findLabTransactionStateForUpdate(ctx context.Context, tx *sql.Tx, regID string, departmentID string) (labTransactionServiceState, bool, error) { var state labTransactionServiceState err := tx.QueryRowContext(ctx, ` SELECT TrxDepartemenID, TrxID, DepartemenID, DokterID, KelasID FROM trxdepartemen WHERE RegID = ? AND DepartemenID = ? AND NA = 'N' LIMIT 1 FOR UPDATE`, regID, departmentID).Scan( &state.Transaction.TrxDepartemenID, &state.Transaction.TrxID, &state.DepartemenID, &state.DokterID, &state.KelasID, ) if errors.Is(err, sql.ErrNoRows) { return labTransactionServiceState{}, false, nil } if err != nil { return labTransactionServiceState{}, false, err } return state, true, nil } func findExistingLabServiceIDs(ctx context.Context, tx *sql.Tx, trxDepartmentID int64, serviceIDs []string) ([]string, error) { placeholders := make([]string, len(serviceIDs)) args := make([]any, 0, len(serviceIDs)+1) args = append(args, trxDepartmentID) for index, serviceID := range serviceIDs { placeholders[index] = "?" args = append(args, serviceID) } rows, err := tx.QueryContext(ctx, ` SELECT LayananID FROM trxlayanan WHERE TrxDepartemenID = ? AND NA = 'N' AND LayananID IN (`+strings.Join(placeholders, ",")+`)`, args...) if err != nil { return nil, err } defer rows.Close() result := make([]string, 0) seen := make(map[string]struct{}) for rows.Next() { var serviceID sql.NullString if err := rows.Scan(&serviceID); err != nil { return nil, err } if !serviceID.Valid { continue } if _, exists := seen[serviceID.String]; exists { continue } seen[serviceID.String] = struct{}{} result = append(result, serviceID.String) } return result, rows.Err() } func resolveLabServices( ctx context.Context, tx *sql.Tx, requested []RequestedLabService, departmentID string, classID string, tariffPeriodID int64, ) ([]resolvedLabService, error) { departmentActive, err := exists(ctx, tx, ` SELECT EXISTS(SELECT 1 FROM departemen WHERE DepartemenID = ? AND NamaModulExternal = 'LABORATORIUM' AND NA = 'N')`, departmentID) if err != nil { return nil, err } if !departmentActive { return nil, ValidationError{Message: "Departemen tujuan bukan departemen laboratorium aktif"} } tariffTable := "harga_layanan_" + strconv.FormatInt(tariffPeriodID, 10) tariffTableExists, err := tableExistsInTransaction(ctx, tx, tariffTable) if err != nil { return nil, err } if !tariffTableExists { return nil, ValidationError{Message: "Tabel periode tarif tidak ditemukan: " + tariffTable} } publicDiscountTableExists, err := tableExistsInTransaction(ctx, tx, "harga_layanan_diskon") if err != nil { return nil, err } resolved := make([]resolvedLabService, 0, len(requested)) for _, requestedService := range requested { serviceID := strings.TrimSpace(requestedService.LayananID) service, found, err := findLabServiceMaster(ctx, tx, serviceID, departmentID) if err != nil { return nil, err } if !found { return nil, ValidationError{Message: fmt.Sprintf( "Layanan %s tidak aktif atau tidak terdaftar pada departemen %s", serviceID, departmentID, )} } tariff, found, err := findLabTariff(ctx, tx, tariffTable, service.LayananDepartemenID, classID) if err != nil { return nil, err } if !found && classID != "Default" { tariff, found, err = findLabTariff(ctx, tx, tariffTable, service.LayananDepartemenID, "Default") if err != nil { return nil, err } } if !found { return nil, ValidationError{Message: fmt.Sprintf( "Tarif layanan %s untuk periode %d dan kelas %s tidak ditemukan", serviceID, tariffPeriodID, classID, )} } price := nullDecimal(tariff.Harga) shareType := "Percentage" if tariff.ShareTipe.Valid { shareType = tariff.ShareTipe.String } shares := [4]float64{ nullDecimal(tariff.ShareRS), nullDecimal(tariff.ShareDokter), nullDecimal(tariff.ShareExternal), nullDecimal(tariff.ShareLain), } if publicDiscountTableExists { discount, found, err := findPublicLabDiscount(ctx, tx, tariffPeriodID, classID, serviceID) if err != nil { return nil, err } if found { if strings.EqualFold(shareType, "Absolute") && price > 0 { shares = absoluteSharesToPercentages(shares, price) shareType = "Percentage" } price -= price * discount / 100 } } defaultPrice := math.Max(0, price) markup := 0.0 if requestedService.Cito { markup = requestedService.MarkupCito } finalPrice := defaultPrice + defaultPrice*markup/100 cito := "N" if requestedService.Cito { cito = "Y" } resolved = append(resolved, resolvedLabService{ LayananID: service.LayananID, LayananDepartemenID: service.LayananDepartemenID, Satuan: service.Satuan, UnitCost: nullDecimal(service.UnitCost), FlagTipe: service.FlagTipe, Jumlah: requestedService.Jumlah, Cito: cito, MarkupCito: markup, Harga: finalPrice, HargaDefault: defaultPrice, Total: math.Max(0, finalPrice) * float64(requestedService.Jumlah), ShareTipe: shareType, ShareRS: shares[0], ShareDokter: shares[1], ShareExternal: shares[2], ShareLain: shares[3], Catatan: strings.TrimSpace(requestedService.Catatan), }) } return resolved, nil } type labServiceMaster struct { LayananDepartemenID int64 LayananID string UnitCost sql.NullString Satuan sql.NullString FlagTipe sql.NullString } func findLabServiceMaster(ctx context.Context, tx *sql.Tx, serviceID string, departmentID string) (labServiceMaster, bool, error) { var service labServiceMaster err := tx.QueryRowContext(ctx, ` SELECT ld.LayananDepartemenID, ld.LayananID, ml.UnitCost, ml.Satuan, lml.FlagTipe FROM layanandepartemen AS ld INNER JOIN masterlayanan AS ml ON ml.LayananID = ld.LayananID LEFT JOIN lab_masterlayanan AS lml ON lml.LayananID = ml.LayananID WHERE ld.LayananID = ? AND ld.DepartemenID = ? AND ld.NA = 'N' AND ml.NA = 'N' LIMIT 1`, serviceID, departmentID).Scan( &service.LayananDepartemenID, &service.LayananID, &service.UnitCost, &service.Satuan, &service.FlagTipe, ) if errors.Is(err, sql.ErrNoRows) { return labServiceMaster{}, false, nil } if err != nil { return labServiceMaster{}, false, err } return service, true, nil } type labTariff struct { Harga sql.NullString ShareTipe sql.NullString ShareRS sql.NullString ShareDokter sql.NullString ShareExternal sql.NullString ShareLain sql.NullString } func findLabTariff(ctx context.Context, tx *sql.Tx, table string, serviceDepartmentID int64, classID string) (labTariff, bool, error) { var tariff labTariff err := tx.QueryRowContext(ctx, ` SELECT Harga, ShareTipe, ShareRS, ShareDokter, ShareExternal, ShareLain FROM `+table+` WHERE LayananDepartemenID = ? AND KelasID = ? AND NA = 'N' LIMIT 1`, serviceDepartmentID, classID).Scan( &tariff.Harga, &tariff.ShareTipe, &tariff.ShareRS, &tariff.ShareDokter, &tariff.ShareExternal, &tariff.ShareLain, ) if errors.Is(err, sql.ErrNoRows) { return labTariff{}, false, nil } if err != nil { return labTariff{}, false, err } return tariff, true, nil } func findPublicLabDiscount(ctx context.Context, tx *sql.Tx, tariffPeriodID int64, classID string, serviceID string) (float64, bool, error) { var discount sql.NullString err := tx.QueryRowContext(ctx, ` SELECT DiskonPercentage FROM harga_layanan_diskon WHERE PeriodeTarifID = ? AND KelasID = ? AND LayananID = ? AND NA = 'N' LIMIT 1`, tariffPeriodID, classID, serviceID).Scan(&discount) if errors.Is(err, sql.ErrNoRows) { return 0, false, nil } if err != nil { return 0, false, err } return nullDecimal(discount), true, nil } func insertResolvedLabService( ctx context.Context, tx *sql.Tx, service resolvedLabService, regID string, trxDepartmentID int64, medrecID string, departmentID string, doctorID string, classID string, channel string, now string, ) error { headerDetail := 1 if service.FlagTipe.Valid && service.FlagTipe.String == "M" { headerDetail = 0 } result, err := tx.ExecContext(ctx, ` INSERT INTO trxlayanan ( TrxDepartemenID, RegID, MEDRECID, DepartemenID, DokterID, HeaderDetail, LayananID, LayananDepartemenID, JenisRegID, KelasID, Catatan, Cito, MarkUpCito, Jumlah, Satuan, MataUang, Rate, UnitCost, TipeDiskon, Diskon, HargaDefault, Harga, DitanggungPasien, DitanggungPasienDefault, DitanggungPenjamin, DitanggungPenjaminDefault, ShareTipe, ShareRS, ShareDokter, ShareExternal, ShareLain2, Tagihkan, Tampilkan, LoginBuat, TanggalBuat, NA ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, '3', ?, ?, ?, ?, ?, ?, 'IDR', 1, ?, 'Percentage', 0, ?, ?, ?, ?, 0, 0, ?, ?, ?, ?, ?, 'Y', 'Y', ?, ?, 'N')`, trxDepartmentID, regID, medrecID, departmentID, doctorID, headerDetail, service.LayananID, service.LayananDepartemenID, classID, service.Catatan, service.Cito, service.MarkupCito, service.Jumlah, nullStringValue(service.Satuan), service.UnitCost, service.HargaDefault, service.Harga, service.Total, service.Total, service.ShareTipe, service.ShareRS, service.ShareDokter, service.ShareExternal, service.ShareLain, channel, now, ) if err != nil { return err } trxServiceID, err := result.LastInsertId() if err != nil { return err } if headerDetail != 0 { return nil } if err := insertLabServiceSupport(ctx, tx, trxServiceID, service.LayananID); err != nil { return err } details, err := findMultipleLabDetails(ctx, tx, service.LayananID, departmentID) if err != nil { return err } for _, detail := range details { if err := insertMultipleLabDetail( ctx, tx, detail, service, regID, trxDepartmentID, medrecID, departmentID, doctorID, classID, channel, now, ); err != nil { return err } } return nil } func findMultipleLabDetails(ctx context.Context, tx *sql.Tx, headerServiceID string, departmentID string) ([]multipleLabDetail, error) { rows, err := tx.QueryContext(ctx, ` SELECT lm.LayananID, lm.HeaderDetail, lm.Urutan, lm.LevelLayanan, ml.Satuan, ml.UnitCost, ld.LayananDepartemenID FROM lab_multiple AS lm INNER JOIN masterlayanan AS ml ON ml.LayananID = lm.LayananID LEFT JOIN layanandepartemen AS ld ON ld.LayananID = lm.LayananID AND ld.DepartemenID = ? AND ld.NA = 'N' WHERE lm.LayananIDHeader = ? AND lm.NA = 'N' AND ml.NA = 'N' ORDER BY lm.Urutan`, departmentID, headerServiceID) if err != nil { return nil, err } defer rows.Close() details := make([]multipleLabDetail, 0) for rows.Next() { var detail multipleLabDetail if err := rows.Scan( &detail.LayananID, &detail.HeaderDetail, &detail.Urutan, &detail.LevelLayanan, &detail.Satuan, &detail.UnitCost, &detail.LayananDepartemenID, ); err != nil { return nil, err } details = append(details, detail) } return details, rows.Err() } func insertMultipleLabDetail( ctx context.Context, tx *sql.Tx, detail multipleLabDetail, header resolvedLabService, regID string, trxDepartmentID int64, medrecID string, departmentID string, doctorID string, classID string, channel string, now string, ) error { headerDetail := int64(1) if detail.HeaderDetail.Valid { headerDetail = detail.HeaderDetail.Int64 } serviceDepartmentID := any("-") if detail.LayananDepartemenID.Valid { serviceDepartmentID = detail.LayananDepartemenID.Int64 } result, err := tx.ExecContext(ctx, ` INSERT INTO trxlayanan ( TrxDepartemenID, RegID, MEDRECID, DepartemenID, DokterID, HeaderDetail, Urutan, LevelLayanan, LayananID, LayananDepartemenID, JenisRegID, KelasID, Cito, MarkUpCito, Jumlah, Satuan, MataUang, Rate, UnitCost, TipeDiskon, Diskon, HargaDefault, Harga, DitanggungPasien, DitanggungPasienDefault, DitanggungPenjamin, DitanggungPenjaminDefault, ShareTipe, ShareRS, ShareDokter, ShareExternal, ShareLain2, Tagihkan, Tampilkan, LoginBuat, TanggalBuat, NA ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, '3', ?, ?, ?, ?, ?, 'IDR', 1, ?, 'Percentage', 0, 0, 0, 0, 0, 0, 0, 'Percentage', 0, 0, 0, 0, 'Y', 'Y', ?, ?, 'N')`, trxDepartmentID, regID, medrecID, departmentID, doctorID, headerDetail, nullInt64Value(detail.Urutan), nullInt64Value(detail.LevelLayanan), detail.LayananID, serviceDepartmentID, classID, header.Cito, header.MarkupCito, header.Jumlah, nullStringValue(detail.Satuan), nullDecimal(detail.UnitCost), channel, now, ) if err != nil { return err } trxServiceID, err := result.LastInsertId() if err != nil { return err } return insertLabServiceSupport(ctx, tx, trxServiceID, header.LayananID) } func insertLabServiceSupport(ctx context.Context, tx *sql.Tx, trxServiceID int64, headerServiceID string) error { _, err := tx.ExecContext(ctx, ` INSERT INTO trxlayanan_penunjang (TrxLayananID, LayananIDHeader) VALUES (?, ?)`, trxServiceID, headerServiceID) return err } func syncLabSpecimens(ctx context.Context, tx *sql.Tx, trxDepartmentID int64, trxID string, channel string, now string) error { rows, err := tx.QueryContext(ctx, ` SELECT ls.Lab_JenisSpesimenID, MAX(tl.Jumlah) AS Qty FROM trxlayanan AS tl INNER JOIN lab_spesimentest AS ls ON ls.LayananID = tl.LayananID INNER JOIN lab_jenisspesimen AS lj ON lj.Lab_JenisSpesimenID = ls.Lab_JenisSpesimenID WHERE tl.TrxDepartemenID = ? AND tl.NA = 'N' AND ls.NA = 'N' AND lj.NA = 'N' GROUP BY ls.Lab_JenisSpesimenID`, trxDepartmentID) if err != nil { return err } type specimen struct { ID int64 Qty sql.NullString } specimens := make([]specimen, 0) for rows.Next() { var value specimen if err := rows.Scan(&value.ID, &value.Qty); err != nil { _ = rows.Close() return err } specimens = append(specimens, value) } if err := rows.Close(); err != nil { return err } if err := rows.Err(); err != nil { return err } for _, value := range specimens { present, err := exists(ctx, tx, ` SELECT EXISTS(SELECT 1 FROM lab_trxspesimen WHERE TrxDepartemenID = ? AND Lab_JenisSpesimenID = ? AND NA = 'N')`, trxDepartmentID, value.ID) if err != nil { return err } if present { continue } quantity := int64(nullDecimal(value.Qty)) if quantity < 1 { quantity = 1 } _, err = tx.ExecContext(ctx, ` INSERT INTO lab_trxspesimen ( TrxDepartemenID, TrxID, Lab_JenisSpesimenID, Qty, LoginBuat, TanggalBuat, NA ) VALUES (?, ?, ?, ?, ?, ?, 'N')`, trxDepartmentID, trxID, value.ID, quantity, channel, now) if err != nil { return err } } return nil } func updateLabTransactionTotal(ctx context.Context, tx *sql.Tx, trxDepartmentID int64, regID string, departmentID string) error { var totalValue sql.NullString if err := tx.QueryRowContext(ctx, ` SELECT COALESCE(SUM(DitanggungPasien + DitanggungPenjamin), 0) FROM trxlayanan WHERE TrxDepartemenID = ? AND Tagihkan = 'Y' AND NA = 'N'`, trxDepartmentID).Scan(&totalValue); err != nil { return err } totalServices := nullDecimal(totalValue) var totalItems, totalBeds sql.NullString err := tx.QueryRowContext(ctx, ` SELECT JumlahNominalTrxItem, JumlahNominalTrxTT FROM trxdepartemen_nominal WHERE TrxDepartemenID = ? LIMIT 1 FOR UPDATE`, trxDepartmentID).Scan(&totalItems, &totalBeds) found := err == nil if err != nil && !errors.Is(err, sql.ErrNoRows) { return err } itemValue := nullDecimal(totalItems) bedValue := nullDecimal(totalBeds) total := totalServices + itemValue + bedValue if found { _, err = tx.ExecContext(ctx, ` UPDATE trxdepartemen_nominal SET RegID = ?, DepartemenID = ?, JumlahNominalTrxLayanan = ?, JumlahNominalTrxItem = ?, JumlahNominalTrxTT = ?, JumlahNominalTotalTrx = ? WHERE TrxDepartemenID = ?`, regID, departmentID, totalServices, itemValue, bedValue, total, trxDepartmentID) } else { _, err = tx.ExecContext(ctx, ` INSERT INTO trxdepartemen_nominal ( TrxDepartemenID, RegID, DepartemenID, JumlahNominalTrxLayanan, JumlahNominalTrxItem, JumlahNominalTrxTT, JumlahNominalTotalTrx ) VALUES (?, ?, ?, ?, ?, ?, ?)`, trxDepartmentID, regID, departmentID, totalServices, itemValue, bedValue, total) } return err } func tableExistsInTransaction(ctx context.Context, tx *sql.Tx, table string) (bool, error) { return exists(ctx, tx, ` SELECT EXISTS(SELECT 1 FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = ?)`, table) } func absoluteSharesToPercentages(shares [4]float64, price float64) [4]float64 { if price <= 0 { return shares } largestIndex := 0 sum := 0.0 for index, share := range shares { shares[index] = math.Round((share/price)*100*100) / 100 sum += shares[index] if shares[index] > shares[largestIndex] { largestIndex = index } } difference := math.Round((100-sum)*100) / 100 if difference != 0 { shares[largestIndex] = math.Round((shares[largestIndex]+difference)*100) / 100 } return shares } func nullDecimal(value sql.NullString) float64 { if !value.Valid { return 0 } parsed, _ := strconv.ParseFloat(value.String, 64) return parsed } func nullInt64Value(value sql.NullInt64) any { if !value.Valid { return nil } return value.Int64 }