Files
cpone_midleware/internal/repository/lab_services.go

888 lines
25 KiB
Go

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, PerusahaanID, AsuransiID,
ProviderID, GroupTarifID, NomerPolis, 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.Registration.PerusahaanID,
&state.Registration.AsuransiID,
&state.Registration.ProviderID,
&state.Registration.GroupTarifID,
&state.Registration.NomerPolis,
&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
}