feat: add package base price fallbacks

This commit is contained in:
2026-08-22 13:29:57 +07:00
parent 412e926a65
commit 30ddf25012
3 changed files with 196 additions and 27 deletions

View File

@@ -84,19 +84,20 @@ type PaketPeriodeTarif struct {
}
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"`
Diskon string `json:"Diskon"`
TipeDiskon string `json:"TipeDiskon"`
ShareRS string `json:"ShareRS"`
ShareDokter string `json:"ShareDokter"`
Subtotal string `json:"Subtotal"`
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 {
@@ -257,7 +258,7 @@ func (r MySQLLayananRepository) GetPaket(ctx context.Context, id string) (*Paket
}
result.PeriodeTarif = periodeTarif
pricing := newPaketBasePricing()
if result.Layanan, err = r.paketLayanan(ctx, id, result.KelasID, tariffTable, pricing); err != nil {
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 {
@@ -321,13 +322,14 @@ func (r MySQLLayananRepository) paketLayanan(
ctx context.Context,
id string,
classID string,
periodeTarif *PaketPeriodeTarif,
tariffTable string,
pricing *paketBasePricing,
) ([]PaketLayanan, error) {
basePriceSelect := "COALESCE(ml.DefaultHarga, 0)"
tariffPriceSelect := "NULL"
args := make([]any, 0, 3)
if tariffTable != "" {
basePriceSelect = fmt.Sprintf(`COALESCE((
tariffPriceSelect = fmt.Sprintf(`(
SELECT tarif.Harga
FROM %s tarif
INNER JOIN layanandepartemen ld
@@ -336,6 +338,7 @@ func (r MySQLLayananRepository) paketLayanan(
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
@@ -343,7 +346,7 @@ func (r MySQLLayananRepository) paketLayanan(
ELSE 4
END, tarif.HargaLayananID
LIMIT 1
), ml.DefaultHarga, 0)`, tariffTable)
)`, tariffTable)
args = append(args, classID, classID, classID)
}
args = append(args, id)
@@ -352,44 +355,150 @@ func (r MySQLLayananRepository) paketLayanan(
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, CAST(pl.HeaderLayanan 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`, basePriceSelect)
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 basePrice float64
var operationHeader sql.NullString
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, &basePrice, &operationHeader,
&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.HargaDasar = phpNumberFormat(basePrice, 0)
item.ShareRS = phpNumberFormat(parseDecimal(shareRS), 2)
item.ShareDokter = phpNumberFormat(parseDecimal(shareDokter), 0)
item.Subtotal = phpNumberFormat(parseDecimal(subtotal), 0)
if operationHeader.Valid {
pricing.layananByOperation[operationHeader.String] += basePrice * parseDecimal(item.Jumlah)
if servicePrice.tariffPrice.Float64 <= 0 && servicePrice.defaultPrice <= 0 && isAdministrationService(servicePrice.serviceCode) {
needsAdministrationPrice = true
}
result = append(result, item)
pricingRows = append(pricingRows, servicePrice)
}
return result, rows.Err()
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) {