feat: add package base price fallbacks
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -94,3 +94,62 @@ func TestPaketItemBasePriceFallbackParity(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPaketServiceBasePriceFallbackParity(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
tariffPrice sql.NullFloat64
|
||||
defaultPrice float64
|
||||
serviceCode string
|
||||
administrationPrice float64
|
||||
administrationSource string
|
||||
expectedPrice float64
|
||||
expectedSource string
|
||||
}{
|
||||
{
|
||||
name: "positive period tariff has highest priority",
|
||||
tariffPrice: sql.NullFloat64{Float64: 150000, Valid: true},
|
||||
defaultPrice: 100000, serviceCode: "LAB001",
|
||||
expectedPrice: 150000, expectedSource: "TARIF_PERIODE",
|
||||
},
|
||||
{
|
||||
name: "zero period tariff does not override master default",
|
||||
tariffPrice: sql.NullFloat64{Float64: 0, Valid: true},
|
||||
defaultPrice: 100000, serviceCode: "LAB001",
|
||||
expectedPrice: 100000, expectedSource: "MASTER_DEFAULT",
|
||||
},
|
||||
{
|
||||
name: "AN001 uses administration period fallback",
|
||||
serviceCode: "an001", administrationPrice: 75000,
|
||||
administrationSource: "ADM_PERIODE",
|
||||
expectedPrice: 75000, expectedSource: "ADM_PERIODE",
|
||||
},
|
||||
{
|
||||
name: "AD001 uses registration master fallback",
|
||||
serviceCode: " AD001 ", administrationPrice: 50000,
|
||||
administrationSource: "BIAYA_ADMIN",
|
||||
expectedPrice: 50000, expectedSource: "BIAYA_ADMIN",
|
||||
},
|
||||
{
|
||||
name: "non administration service does not use administration fallback",
|
||||
serviceCode: "LAB001", administrationPrice: 50000,
|
||||
administrationSource: "BIAYA_ADMIN",
|
||||
expectedPrice: 0, expectedSource: "NOT_AVAILABLE",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
price, source := paketServiceBasePrice(
|
||||
test.tariffPrice,
|
||||
test.defaultPrice,
|
||||
test.serviceCode,
|
||||
test.administrationPrice,
|
||||
test.administrationSource,
|
||||
)
|
||||
if price != test.expectedPrice || source != test.expectedSource {
|
||||
t.Fatalf("price=%v source=%q want price=%v source=%q", price, source, test.expectedPrice, test.expectedSource)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user