feat: add package base prices

This commit is contained in:
2026-08-21 10:21:47 +07:00
parent a7102e6b0e
commit b9eb934364
3 changed files with 225 additions and 17 deletions

View File

@@ -3,6 +3,7 @@ package repository
import (
"context"
"database/sql"
"fmt"
"math"
"strconv"
"strings"
@@ -82,6 +83,7 @@ type PaketLayanan struct {
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"`
@@ -97,6 +99,7 @@ type PaketOperasi struct {
NamaKasusOperasi *string `json:"NamaKasusOperasi"`
EstimasiWaktu string `json:"EstimasiWaktu"`
Keterangan string `json:"Keterangan"`
HargaDasar string `json:"HargaDasar"`
}
type PaketItem struct {
@@ -107,6 +110,7 @@ type PaketItem struct {
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"`
@@ -118,6 +122,7 @@ type PaketKamar struct {
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"`
@@ -238,14 +243,18 @@ func (r MySQLLayananRepository) GetPaket(ctx context.Context, id string) (*Paket
result.TanggalBuat = laravelDateJSON(tanggalBuat)
result.TanggalEdit = laravelDateJSON(tanggalEdit)
var err error
if result.Layanan, err = r.paketLayanan(ctx, id); err != nil {
tariffTable, err := r.activePaketTariffTable(ctx)
if err != nil {
return nil, err
}
if result.Operasi, err = r.paketOperasi(ctx, id); err != nil {
pricing := newPaketBasePricing()
if result.Layanan, err = r.paketLayanan(ctx, id, result.KelasID, tariffTable, pricing); err != nil {
return nil, err
}
if result.Item, err = r.paketItem(ctx, id); err != nil {
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 {
@@ -254,17 +263,85 @@ func (r MySQLLayananRepository) GetPaket(ctx context.Context, id string) (*Paket
return &result, nil
}
func (r MySQLLayananRepository) paketLayanan(ctx context.Context, id string) ([]PaketLayanan, error) {
rows, err := r.db.QueryContext(ctx, `
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) activePaketTariffTable(ctx context.Context) (string, error) {
var periodID int64
err := r.db.QueryRowContext(ctx, `
SELECT PeriodeTarifID
FROM periode_tarif
WHERE DATE(TanggalBerlaku) <= CURRENT_DATE()
AND DATE(TanggalBerakhir) >= CURRENT_DATE()
AND NA = 'N'
ORDER BY PeriodeTarifID DESC
LIMIT 1`).Scan(&periodID)
if err == sql.ErrNoRows {
return "", nil
}
if err != nil {
return "", err
}
tableName := "harga_layanan_" + strconv.FormatInt(periodID, 10)
exists, err := r.tableExists(ctx, tableName)
if err != nil || !exists {
return "", err
}
return tableName, nil
}
func (r MySQLLayananRepository) paketLayanan(
ctx context.Context,
id string,
classID string,
tariffTable string,
pricing *paketBasePricing,
) ([]PaketLayanan, error) {
basePriceSelect := "COALESCE(ml.DefaultHarga, 0)"
args := make([]any, 0, 3)
if tariffTable != "" {
basePriceSelect = fmt.Sprintf(`COALESCE((
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'
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
), ml.DefaultHarga, 0)`, 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)
CAST(pl.ShareDokter AS CHAR), CAST(pl.Subtotal AS CHAR),
%s, 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`, id)
ORDER BY pl.PaketLayananID`, basePriceSelect)
rows, err := r.db.QueryContext(ctx, query, args...)
if err != nil {
return nil, err
}
@@ -274,25 +351,31 @@ func (r MySQLLayananRepository) paketLayanan(ctx context.Context, id string) ([]
var item PaketLayanan
var namaLayanan, namaDepartemen sql.NullString
var harga, shareRS, shareDokter, subtotal string
var basePrice float64
var operationHeader sql.NullString
if err := rows.Scan(
&item.PaketLayananID, &item.LayananID, &namaLayanan, &namaDepartemen,
&item.Jumlah, &item.JumlahHari, &harga, &item.Diskon, &item.TipeDiskon,
&shareRS, &shareDokter, &subtotal,
&shareRS, &shareDokter, &subtotal, &basePrice, &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)
}
result = append(result, item)
}
return result, rows.Err()
}
func (r MySQLLayananRepository) paketOperasi(ctx context.Context, id string) ([]PaketOperasi, error) {
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
@@ -321,19 +404,35 @@ func (r MySQLLayananRepository) paketOperasi(ctx context.Context, id string) ([]
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) ([]PaketItem, error) {
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)
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'
@@ -350,10 +449,17 @@ func (r MySQLLayananRepository) paketItem(ctx context.Context, id string) ([]Pak
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
}
@@ -370,7 +476,15 @@ func (r MySQLLayananRepository) paketItem(ctx context.Context, id string) ([]Pak
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()
@@ -379,7 +493,8 @@ func (r MySQLLayananRepository) paketItem(ctx context.Context, id string) ([]Pak
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)
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'
@@ -394,9 +509,10 @@ func (r MySQLLayananRepository) paketKamar(ctx context.Context, id string) ([]Pa
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,
&harga, &item.TipeDiskon, &diskon, &basePrice,
); err != nil {
return nil, err
}
@@ -409,12 +525,40 @@ func (r MySQLLayananRepository) paketKamar(ctx context.Context, id string) ([]Pa
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