888 lines
25 KiB
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
|
|
}
|