Files
cpone_midleware/internal/repository/paket.go

727 lines
23 KiB
Go

package repository
import (
"context"
"database/sql"
"fmt"
"math"
"strconv"
"strings"
"time"
)
type PaketListParams struct {
Search string
Code string
CareType string
Status int
Active string
StartDate string
EndDate string
Page int
PerPage int
}
type PaketPage struct {
Items []PaketSummary
Total int
}
type PaketSummary struct {
PaketID string `json:"PaketID"`
KodeCorp *string `json:"KodeCorp"`
Nama string `json:"Nama"`
JenisPaketID string `json:"JenisPaketID"`
JenisPaket *string `json:"JenisPaket"`
Harga int64 `json:"Harga"`
RawatInap bool `json:"RawatInap"`
RawatJalan bool `json:"RawatJalan"`
MultiVisit bool `json:"MultiVisit"`
TglDari string `json:"TglDari"`
TglSampai string `json:"TglSampai"`
KelasID string `json:"KelasID"`
Kelas *string `json:"Kelas"`
JumlahHari int `json:"JumlahHari"`
StatusID int `json:"StatusID"`
Status *string `json:"Status"`
NA string `json:"NA"`
Aktif bool `json:"Aktif"`
Keterangan string `json:"Keterangan"`
}
type PaketDetail struct {
PaketID string `json:"PaketID"`
Nama string `json:"Nama"`
JenisPaket string `json:"JenisPaket"`
RJ int `json:"RJ"`
RI int `json:"RI"`
TglDari string `json:"TglDari"`
TglSampai string `json:"TglSampai"`
MultiVisit int `json:"MultiVisit"`
HeaderPaket string `json:"HeaderPaket"`
KelasID string `json:"KelasID"`
JumlahHari int `json:"JumlahHari"`
TotalHarga string `json:"TotalHarga"`
Keterangan string `json:"Keterangan"`
StatusID int `json:"StatusID"`
LoginBuat string `json:"LoginBuat"`
TanggalBuat string `json:"TanggalBuat"`
LoginEdit string `json:"LoginEdit"`
TanggalEdit string `json:"TanggalEdit"`
NA string `json:"NA"`
PeriodeTarif *PaketPeriodeTarif `json:"PeriodeTarif"`
Layanan []PaketLayanan `json:"layanan"`
Operasi []PaketOperasi `json:"operasi"`
Item []PaketItem `json:"item"`
Kamar []PaketKamar `json:"kamar"`
}
type PaketPeriodeTarif struct {
PeriodeTarifID int64 `json:"PeriodeTarifID"`
Keterangan *string `json:"Keterangan"`
TanggalBerlaku string `json:"TanggalBerlaku"`
TanggalBerakhir string `json:"TanggalBerakhir"`
}
type PaketLayanan struct {
PaketLayananID int64 `json:"PaketLayananID"`
LayananID string `json:"LayananID"`
NamaLayanan *string `json:"NamaLayanan"`
NamaDepartemen *string `json:"NamaDepartemen"`
Jumlah string `json:"Jumlah"`
JumlahHari int `json:"JumlahHari"`
Harga string `json:"Harga"`
HargaDasar string `json:"HargaDasar"`
HargaDasarSource string `json:"HargaDasarSource"`
Diskon string `json:"Diskon"`
TipeDiskon string `json:"TipeDiskon"`
ShareRS string `json:"ShareRS"`
ShareDokter string `json:"ShareDokter"`
Subtotal string `json:"Subtotal"`
}
type PaketOperasi struct {
PaketOperasiHeaderID int64 `json:"PaketOperasiHeaderID"`
NamaTindakan *string `json:"NamaTindakan"`
NamaGolongan *string `json:"NamaGolongan"`
NamaJenisAnestesi *string `json:"NamaJenisAnestesi"`
NamaKasusOperasi *string `json:"NamaKasusOperasi"`
EstimasiWaktu string `json:"EstimasiWaktu"`
Keterangan string `json:"Keterangan"`
HargaDasar string `json:"HargaDasar"`
}
type PaketItem struct {
PaketItemID int64 `json:"PaketItemID"`
ItemID string `json:"ItemID"`
NamaItem *string `json:"NamaItem"`
NamaDepartemen *string `json:"NamaDepartemen"`
Jumlah string `json:"Jumlah"`
NamaSatuan *string `json:"NamaSatuan"`
Harga string `json:"Harga"`
HargaDasar string `json:"HargaDasar"`
Diskon string `json:"Diskon"`
TipeDiskon string `json:"TipeDiskon"`
SubTotal string `json:"SubTotal"`
}
type PaketKamar struct {
PaketTTID int64 `json:"PaketTTID"`
NamaDepartemen *string `json:"NamaDepartemen"`
NamaKelas *string `json:"NamaKelas"`
Jumlah string `json:"Jumlah"`
Harga string `json:"Harga"`
HargaDasar string `json:"HargaDasar"`
TipeDiskon string `json:"TipeDiskon"`
Diskon float64 `json:"Diskon"`
SubTotal string `json:"SubTotal"`
}
func (r MySQLLayananRepository) ListPaket(ctx context.Context, params PaketListParams) (PaketPage, error) {
where, args := paketListWhere(params)
var total int
if err := r.db.QueryRowContext(ctx, "SELECT COUNT(*) FROM paket_header ph "+where, args...).Scan(&total); err != nil {
return PaketPage{}, err
}
query := `
SELECT ph.PaketID,
(SELECT pch.KodeCorp FROM paket_corp_header pch
WHERE pch.PaketID = ph.PaketID ORDER BY pch.TanggalBuat DESC LIMIT 1),
ph.Nama, ph.JenisPaket, jp.Nama, ph.TotalHarga, ph.RI, ph.RJ,
ph.MultiVisit, CAST(ph.TglDari AS CHAR), CAST(ph.TglSampai AS CHAR),
ph.KelasID, ph.JumlahHari, ph.StatusID, ph.NA, ph.Keterangan
FROM paket_header ph
LEFT JOIN jenispaket jp ON jp.JenisPaketID = ph.JenisPaket
` + where + `
ORDER BY ph.TanggalBuat DESC, ph.PaketID DESC
LIMIT ? OFFSET ?`
queryArgs := append(append([]any{}, args...), params.PerPage, (params.Page-1)*params.PerPage)
rows, err := r.db.QueryContext(ctx, query, queryArgs...)
if err != nil {
return PaketPage{}, err
}
defer rows.Close()
items := make([]PaketSummary, 0)
for rows.Next() {
var item PaketSummary
var kodeCorp, jenisPaket sql.NullString
var harga float64
var ri, rj, multiVisit int
if err := rows.Scan(
&item.PaketID, &kodeCorp, &item.Nama, &item.JenisPaketID, &jenisPaket,
&harga, &ri, &rj, &multiVisit, &item.TglDari, &item.TglSampai,
&item.KelasID, &item.JumlahHari, &item.StatusID, &item.NA, &item.Keterangan,
); err != nil {
return PaketPage{}, err
}
item.KodeCorp = nullStringPtr(kodeCorp)
item.JenisPaket = nullStringPtr(jenisPaket)
item.Harga = int64(math.Round(harga))
item.RawatInap = ri != 0
item.RawatJalan = rj != 0
item.MultiVisit = multiVisit != 0
item.Status = paketStatus(item.StatusID)
item.Aktif = item.NA == "N"
items = append(items, item)
}
if err := rows.Err(); err != nil {
return PaketPage{}, err
}
return PaketPage{Items: items, Total: total}, nil
}
func paketListWhere(params PaketListParams) (string, []any) {
conditions := []string{"ph.StatusID = ?"}
args := []any{params.Status}
if params.Active != "" {
conditions = append(conditions, "ph.NA = ?")
args = append(args, params.Active)
}
if params.Search != "" {
conditions = append(conditions, "ph.Nama LIKE ?")
args = append(args, "%"+params.Search+"%")
}
if params.Code != "" {
conditions = append(conditions, "ph.PaketID LIKE ?")
args = append(args, "%"+params.Code+"%")
}
if params.CareType != "" {
conditions = append(conditions, "ph."+params.CareType+" = 1")
}
if params.StartDate != "" {
conditions = append(conditions, "ph.TglSampai >= ?")
args = append(args, params.StartDate)
}
if params.EndDate != "" {
conditions = append(conditions, "ph.TglDari <= ?")
args = append(args, params.EndDate)
}
return "WHERE " + strings.Join(conditions, " AND "), args
}
func paketStatus(status int) *string {
statuses := map[int]string{-1: "rejected", 0: "draft", 1: "submitted", 2: "approved"}
value, ok := statuses[status]
if !ok {
return nil
}
return &value
}
func (r MySQLLayananRepository) GetPaket(ctx context.Context, id string) (*PaketDetail, error) {
row := r.db.QueryRowContext(ctx, `
SELECT PaketID, Nama, JenisPaket, RJ, RI, CAST(TglDari AS CHAR), CAST(TglSampai AS CHAR),
MultiVisit, HeaderPaket, KelasID, JumlahHari, CAST(TotalHarga AS CHAR), Keterangan,
StatusID, LoginBuat, CAST(TanggalBuat AS CHAR), LoginEdit, CAST(TanggalEdit AS CHAR), NA
FROM paket_header WHERE PaketID = ? LIMIT 1`, id)
var result PaketDetail
var tanggalBuat, tanggalEdit string
if err := row.Scan(
&result.PaketID, &result.Nama, &result.JenisPaket, &result.RJ, &result.RI,
&result.TglDari, &result.TglSampai, &result.MultiVisit, &result.HeaderPaket,
&result.KelasID, &result.JumlahHari, &result.TotalHarga, &result.Keterangan,
&result.StatusID, &result.LoginBuat, &tanggalBuat, &result.LoginEdit, &tanggalEdit, &result.NA,
); err != nil {
if err == sql.ErrNoRows {
return nil, nil
}
return nil, err
}
result.TanggalBuat = laravelDateJSON(tanggalBuat)
result.TanggalEdit = laravelDateJSON(tanggalEdit)
periodeTarif, tariffTable, err := r.activePaketTariff(ctx)
if err != nil {
return nil, err
}
result.PeriodeTarif = periodeTarif
pricing := newPaketBasePricing()
if result.Layanan, err = r.paketLayanan(ctx, id, result.KelasID, periodeTarif, tariffTable, pricing); err != nil {
return nil, err
}
if result.Item, err = r.paketItem(ctx, id, pricing); err != nil {
return nil, err
}
if result.Operasi, err = r.paketOperasi(ctx, id, pricing); err != nil {
return nil, err
}
if result.Kamar, err = r.paketKamar(ctx, id); err != nil {
return nil, err
}
return &result, nil
}
type paketBasePricing struct {
layananByOperation map[string]float64
itemByOperation map[string]float64
}
func newPaketBasePricing() *paketBasePricing {
return &paketBasePricing{
layananByOperation: make(map[string]float64),
itemByOperation: make(map[string]float64),
}
}
func (r MySQLLayananRepository) activePaketTariff(ctx context.Context) (*PaketPeriodeTarif, string, error) {
var period PaketPeriodeTarif
var keterangan sql.NullString
err := r.db.QueryRowContext(ctx, `
SELECT PeriodeTarifID, Keterangan,
CAST(TanggalBerlaku AS CHAR), CAST(TanggalBerakhir AS CHAR)
FROM periode_tarif
WHERE DATE(TanggalBerlaku) <= CURRENT_DATE()
AND DATE(TanggalBerakhir) >= CURRENT_DATE()
AND NA = 'N'
ORDER BY PeriodeTarifID DESC
LIMIT 1`).Scan(
&period.PeriodeTarifID,
&keterangan,
&period.TanggalBerlaku,
&period.TanggalBerakhir,
)
if err == sql.ErrNoRows {
return nil, "", nil
}
if err != nil {
return nil, "", err
}
period.Keterangan = nullStringPtr(keterangan)
tableName := "harga_layanan_" + strconv.FormatInt(period.PeriodeTarifID, 10)
exists, err := r.tableExists(ctx, tableName)
if err != nil || !exists {
return &period, "", err
}
return &period, tableName, nil
}
func (r MySQLLayananRepository) paketLayanan(
ctx context.Context,
id string,
classID string,
periodeTarif *PaketPeriodeTarif,
tariffTable string,
pricing *paketBasePricing,
) ([]PaketLayanan, error) {
tariffPriceSelect := "NULL"
args := make([]any, 0, 3)
if tariffTable != "" {
tariffPriceSelect = fmt.Sprintf(`(
SELECT tarif.Harga
FROM %s tarif
INNER JOIN layanandepartemen ld
ON ld.LayananDepartemenID = tarif.LayananDepartemenID
AND ld.NA = 'N'
WHERE ld.LayananID = pl.LayananID
AND tarif.KelasID IN (?, 'Default')
AND tarif.NA = 'N'
AND tarif.Harga > 0
ORDER BY CASE
WHEN tarif.KelasID = ? AND ld.DepartemenID <=> pl.DepartemenID THEN 1
WHEN tarif.KelasID = ? THEN 2
WHEN tarif.KelasID = 'Default' AND ld.DepartemenID <=> pl.DepartemenID THEN 3
ELSE 4
END, tarif.HargaLayananID
LIMIT 1
)`, tariffTable)
args = append(args, classID, classID, classID)
}
args = append(args, id)
query := fmt.Sprintf(`
SELECT pl.PaketLayananID, pl.LayananID, ml.Nama, d.Nama,
CAST(pl.Jumlah AS CHAR), pl.JumlahHari, CAST(pl.Harga AS CHAR),
CAST(pl.Diskon AS CHAR), pl.TipeDiskon, CAST(pl.ShareRS AS CHAR),
CAST(pl.ShareDokter AS CHAR), CAST(pl.Subtotal AS CHAR),
%s, COALESCE(ml.DefaultHarga, 0), COALESCE(ml.LayananKode, ''),
CAST(pl.HeaderLayanan AS CHAR)
FROM paket_layanan pl
LEFT JOIN masterlayanan ml ON ml.LayananID = pl.LayananID
LEFT JOIN departemen d ON d.DepartemenID = pl.DepartemenID AND d.NA = 'N'
WHERE pl.PaketID = ? AND pl.NA = 'N'
ORDER BY pl.PaketLayananID`, tariffPriceSelect)
rows, err := r.db.QueryContext(ctx, query, args...)
if err != nil {
return nil, err
}
defer rows.Close()
result := make([]PaketLayanan, 0)
type servicePricing struct {
tariffPrice sql.NullFloat64
defaultPrice float64
serviceCode string
operationHeader sql.NullString
}
pricingRows := make([]servicePricing, 0)
needsAdministrationPrice := false
for rows.Next() {
var item PaketLayanan
var namaLayanan, namaDepartemen sql.NullString
var harga, shareRS, shareDokter, subtotal string
var servicePrice servicePricing
if err := rows.Scan(
&item.PaketLayananID, &item.LayananID, &namaLayanan, &namaDepartemen,
&item.Jumlah, &item.JumlahHari, &harga, &item.Diskon, &item.TipeDiskon,
&shareRS, &shareDokter, &subtotal, &servicePrice.tariffPrice,
&servicePrice.defaultPrice, &servicePrice.serviceCode, &servicePrice.operationHeader,
); err != nil {
return nil, err
}
item.NamaLayanan = nullStringPtr(namaLayanan)
item.NamaDepartemen = nullStringPtr(namaDepartemen)
item.Harga = phpNumberFormat(parseDecimal(harga), 0)
item.ShareRS = phpNumberFormat(parseDecimal(shareRS), 2)
item.ShareDokter = phpNumberFormat(parseDecimal(shareDokter), 0)
item.Subtotal = phpNumberFormat(parseDecimal(subtotal), 0)
if servicePrice.tariffPrice.Float64 <= 0 && servicePrice.defaultPrice <= 0 && isAdministrationService(servicePrice.serviceCode) {
needsAdministrationPrice = true
}
result = append(result, item)
pricingRows = append(pricingRows, servicePrice)
}
if err := rows.Err(); err != nil {
return nil, err
}
administrationPrice := 0.0
administrationSource := "NOT_AVAILABLE"
if needsAdministrationPrice {
var err error
administrationPrice, administrationSource, err = r.paketAdministrationBasePrice(ctx, periodeTarif)
if err != nil {
return nil, err
}
}
for index := range result {
servicePrice := pricingRows[index]
basePrice, source := paketServiceBasePrice(
servicePrice.tariffPrice,
servicePrice.defaultPrice,
servicePrice.serviceCode,
administrationPrice,
administrationSource,
)
result[index].HargaDasar = phpNumberFormat(basePrice, 0)
result[index].HargaDasarSource = source
if servicePrice.operationHeader.Valid {
pricing.layananByOperation[servicePrice.operationHeader.String] += basePrice * parseDecimal(result[index].Jumlah)
}
}
return result, nil
}
func paketServiceBasePrice(
tariffPrice sql.NullFloat64,
defaultPrice float64,
serviceCode string,
administrationPrice float64,
administrationSource string,
) (float64, string) {
if tariffPrice.Valid && tariffPrice.Float64 > 0 {
return tariffPrice.Float64, "TARIF_PERIODE"
}
if defaultPrice > 0 {
return defaultPrice, "MASTER_DEFAULT"
}
if isAdministrationService(serviceCode) && administrationPrice > 0 {
return administrationPrice, administrationSource
}
return 0, "NOT_AVAILABLE"
}
func isAdministrationService(serviceCode string) bool {
switch strings.ToUpper(strings.TrimSpace(serviceCode)) {
case "AN001", "AD001":
return true
default:
return false
}
}
func (r MySQLLayananRepository) paketAdministrationBasePrice(
ctx context.Context,
periodeTarif *PaketPeriodeTarif,
) (float64, string, error) {
if periodeTarif != nil {
exists, err := r.tableExists(ctx, "adm_periode")
if err != nil {
return 0, "", err
}
if exists {
var price sql.NullFloat64
err = r.db.QueryRowContext(ctx, `
SELECT NilaiAdministrasi
FROM adm_periode
WHERE JenisRegID = 0 AND PeriodeTarifID = ?
ORDER BY AdmPeriodeID DESC
LIMIT 1`, periodeTarif.PeriodeTarifID).Scan(&price)
if err != nil && err != sql.ErrNoRows {
return 0, "", err
}
if price.Valid && price.Float64 > 0 {
return price.Float64, "ADM_PERIODE", nil
}
}
}
var price sql.NullFloat64
err := r.db.QueryRowContext(ctx, `
SELECT NilaiAdministrasi
FROM jenisreg
WHERE JenisRegID = 0
LIMIT 1`).Scan(&price)
if err != nil && err != sql.ErrNoRows {
return 0, "", err
}
if price.Valid && price.Float64 > 0 {
return price.Float64, "BIAYA_ADMIN", nil
}
return 0, "NOT_AVAILABLE", nil
}
func (r MySQLLayananRepository) paketOperasi(ctx context.Context, id string, pricing *paketBasePricing) ([]PaketOperasi, error) {
rows, err := r.db.QueryContext(ctx, `
SELECT po.PaketOperasiHeaderID, mt.Nama, go.Nama, ja.Nama, ko.Nama,
po.EstimasiWaktu, po.Keterangan
FROM paket_operasi_header po
LEFT JOIN mastertindakan mt ON mt.TindakanID = po.TindakanID
LEFT JOIN golonganoperasi go ON go.GolonganOperasiID = po.GolonganOperasiID
LEFT JOIN jenisanestesi ja ON ja.JenisAnestesiID = po.JenisAnestesiID
LEFT JOIN kasusoperasi ko ON ko.KasusOperasiID = po.KasusOperasiID
WHERE po.PaketID = ? AND po.NA = 'N'
ORDER BY po.PaketOperasiHeaderID`, id)
if err != nil {
return nil, err
}
defer rows.Close()
result := make([]PaketOperasi, 0)
for rows.Next() {
var item PaketOperasi
var tindakan, golongan, anestesi, kasus sql.NullString
if err := rows.Scan(
&item.PaketOperasiHeaderID, &tindakan, &golongan, &anestesi, &kasus,
&item.EstimasiWaktu, &item.Keterangan,
); err != nil {
return nil, err
}
item.NamaTindakan = nullStringPtr(tindakan)
item.NamaGolongan = nullStringPtr(golongan)
item.NamaJenisAnestesi = nullStringPtr(anestesi)
item.NamaKasusOperasi = nullStringPtr(kasus)
headerID := strconv.FormatInt(item.PaketOperasiHeaderID, 10)
item.HargaDasar = phpNumberFormat(
pricing.layananByOperation[headerID]+pricing.itemByOperation[headerID],
2,
)
result = append(result, item)
}
return result, rows.Err()
}
func (r MySQLLayananRepository) paketItem(ctx context.Context, id string, pricing *paketBasePricing) ([]PaketItem, error) {
rows, err := r.db.QueryContext(ctx, `
SELECT pi.PaketItemID, pi.ItemID, mi.Nama, d.Nama, CAST(pi.Jumlah AS CHAR), s.Nama,
CAST(pi.Harga AS CHAR), CAST(pi.Diskon AS CHAR), pi.TipeDiskon,
pi.SatuanID, pi.SatuanDasarID,
COALESCE((SELECT ks.NilaiKonversi FROM konversisatuan ks
WHERE ks.ItemID = pi.ItemID AND ks.SatuanTujuan = pi.SatuanDasarID
AND ks.SatuanAsal = pi.SatuanID AND ks.NA = 'N' LIMIT 1), 1),
COALESCE(mi.HET, 'N'), COALESCE(mi.DefaultHarga, 0),
COALESCE(mi.CostDasar, 0), COALESCE(mi.KategoriItemID, 0),
(SELECT mo.Margin FROM margin_obat mo
WHERE mo.Dari <= mi.CostDasar AND mo.Sampai >= mi.CostDasar
AND mo.JenisReg = 'RJ' AND mo.NA = 'N' LIMIT 1),
(SELECT ss.Value_ FROM sys_setting ss
WHERE ss.Setting = 'Margin_RJ' AND ss.NA = 'N' LIMIT 1),
(SELECT ma.Margin FROM margin_alkes ma
WHERE ma.Dari <= mi.CostDasar AND ma.Sampai >= mi.CostDasar
AND ma.NA = 'N' LIMIT 1),
CAST(pi.HeaderID AS CHAR)
FROM paket_item pi
LEFT JOIN masteritem mi ON mi.ItemID = pi.ItemID AND mi.NA = 'N'
LEFT JOIN departemen d ON d.DepartemenID = pi.DepartemenID AND d.NA = 'N'
LEFT JOIN satuan s ON s.SatuanID = pi.SatuanID
WHERE pi.PaketID = ? AND pi.NA = 'N'
ORDER BY pi.PaketItemID`, id)
if err != nil {
return nil, err
}
defer rows.Close()
result := make([]PaketItem, 0)
for rows.Next() {
var item PaketItem
var namaItem, namaDepartemen, namaSatuan sql.NullString
var harga, satuanID, satuanDasarID string
var konversi float64
var het string
var defaultPrice, baseCost float64
var categoryID int
var medicineMargin, settingMargin, medicalSupplyMargin sql.NullFloat64
var operationHeader sql.NullString
if err := rows.Scan(
&item.PaketItemID, &item.ItemID, &namaItem, &namaDepartemen, &item.Jumlah,
&namaSatuan, &harga, &item.Diskon, &item.TipeDiskon,
&satuanID, &satuanDasarID, &konversi,
&het, &defaultPrice, &baseCost, &categoryID,
&medicineMargin, &settingMargin, &medicalSupplyMargin, &operationHeader,
); err != nil {
return nil, err
}
item.NamaItem = nullStringPtr(namaItem)
item.NamaDepartemen = nullStringPtr(namaDepartemen)
item.NamaSatuan = nullStringPtr(namaSatuan)
hargaValue := parseDecimal(harga)
diskon := parseDecimal(item.Diskon)
nDisc := diskon
if item.TipeDiskon == "Percentage" {
nDisc = hargaValue * (diskon / 100)
}
if satuanDasarID == satuanID {
konversi = 1
}
item.Harga = phpNumberFormat(hargaValue, 2)
basePrice := paketItemBasePrice(
het, defaultPrice, baseCost, categoryID,
medicineMargin, settingMargin, medicalSupplyMargin,
)
item.HargaDasar = phpNumberFormat(basePrice, 2)
item.SubTotal = phpNumberFormat((hargaValue-nDisc)*parseDecimal(item.Jumlah)*konversi, 2)
if operationHeader.Valid {
pricing.itemByOperation[operationHeader.String] += basePrice * parseDecimal(item.Jumlah) * konversi
}
result = append(result, item)
}
return result, rows.Err()
}
func (r MySQLLayananRepository) paketKamar(ctx context.Context, id string) ([]PaketKamar, error) {
rows, err := r.db.QueryContext(ctx, `
SELECT pt.PaketTTID, d.Nama, k.Nama, CAST(pt.Jumlah AS CHAR),
CAST(pt.Harga AS CHAR), pt.TipeDiskon, CAST(pt.Diskon AS CHAR),
COALESCE(k.HargaTT, 0)
FROM paket_tt pt
LEFT JOIN departemen d ON d.DepartemenID = pt.DepartemenID AND d.NA = 'N'
LEFT JOIN kelas k ON k.KelasID = pt.KelasID AND k.NA = 'N'
WHERE pt.PaketID = ?
ORDER BY pt.PaketTTID`, id)
if err != nil {
return nil, err
}
defer rows.Close()
result := make([]PaketKamar, 0)
for rows.Next() {
var item PaketKamar
var namaDepartemen, namaKelas sql.NullString
var harga, diskon string
var basePrice float64
if err := rows.Scan(
&item.PaketTTID, &namaDepartemen, &namaKelas, &item.Jumlah,
&harga, &item.TipeDiskon, &diskon, &basePrice,
); err != nil {
return nil, err
}
item.NamaDepartemen = nullStringPtr(namaDepartemen)
item.NamaKelas = nullStringPtr(namaKelas)
hargaValue := parseDecimal(harga)
diskonValue := parseDecimal(diskon)
item.Diskon = diskonValue
if item.TipeDiskon == "Percentage" {
item.Diskon = hargaValue * (diskonValue / 100)
}
item.Harga = phpNumberFormat(hargaValue, 2)
item.HargaDasar = phpNumberFormat(basePrice, 2)
item.SubTotal = phpNumberFormat((hargaValue-item.Diskon)*parseDecimal(item.Jumlah), 0)
result = append(result, item)
}
return result, rows.Err()
}
func paketItemBasePrice(
het string,
defaultPrice float64,
baseCost float64,
categoryID int,
medicineMargin sql.NullFloat64,
settingMargin sql.NullFloat64,
medicalSupplyMargin sql.NullFloat64,
) float64 {
if het == "Y" {
return defaultPrice
}
switch categoryID {
case 1:
margin := settingMargin.Float64
if medicineMargin.Valid {
margin = medicineMargin.Float64
}
return math.Round(baseCost + baseCost*margin/100)
case 2:
return math.Round(baseCost + baseCost*medicalSupplyMargin.Float64/100)
default:
return math.Round(defaultPrice)
}
}
func parseDecimal(value string) float64 {
parsed, _ := strconv.ParseFloat(value, 64)
return parsed
}
func phpNumberFormat(value float64, decimals int) string {
power := math.Pow10(decimals)
value = math.Round(value*power) / power
formatted := strconv.FormatFloat(value, 'f', decimals, 64)
parts := strings.SplitN(formatted, ".", 2)
integer := parts[0]
sign := ""
if strings.HasPrefix(integer, "-") {
sign = "-"
integer = strings.TrimPrefix(integer, "-")
}
for index := len(integer) - 3; index > 0; index -= 3 {
integer = integer[:index] + "," + integer[index:]
}
if len(parts) == 2 {
return sign + integer + "." + parts[1]
}
return sign + integer
}
func laravelDateJSON(value string) string {
if strings.HasPrefix(value, "0000-00-00") {
return "-000001-11-29T16:52:48.000000Z"
}
location, err := time.LoadLocation("Asia/Jakarta")
if err != nil {
return value
}
parsed, err := time.ParseInLocation("2006-01-02 15:04:05", value, location)
if err != nil {
return value
}
return parsed.UTC().Format("2006-01-02T15:04:05.000000Z")
}