Files
cpone_midleware/internal/repository/tarif_kerjasama.go
2026-09-25 08:35:49 +07:00

382 lines
14 KiB
Go

package repository
import (
"context"
"database/sql"
"errors"
"fmt"
"strconv"
"strings"
"github.com/go-sql-driver/mysql"
)
type TarifKerjasamaListParams struct {
Search string
PerusahaanID string
TarifID *int64
Page int
PerPage int
}
type TarifKerjasamaDetailParams struct {
GroupTarifID *int64
Active string
EffectiveDate string
}
type TarifKerjasamaPage struct {
Items []TarifKerjasamaSummary
Total int
}
type TarifKerjasamaSummary struct {
PerusahaanTarifID int64 `json:"PerusahaanTarifID"`
PerusahaanID string `json:"PerusahaanID"`
KodePerusahaan *string `json:"KodePerusahaan"`
NamaPerusahaan string `json:"NamaPerusahaan"`
PIC *string `json:"PIC"`
Alamat *string `json:"Alamat"`
Telepon *string `json:"Telepon"`
Email *string `json:"Email"`
JumlahGroupTarif int `json:"JumlahGroupTarif"`
}
type TarifKerjasamaDetail struct {
TarifKerjasamaSummary
GroupTarif []TarifKerjasamaGroup `json:"GroupTarif"`
}
type TarifKerjasamaGroup struct {
GroupTarifID int64 `json:"GroupTarifID"`
PerusahaanTarifID int64 `json:"PerusahaanTarifID"`
NamaGroupTarif string `json:"NamaGroupTarif"`
TanggalBerlaku string `json:"TanggalBerlaku"`
TanggalBerakhir string `json:"TanggalBerakhir"`
PeriodeTarifID *int64 `json:"PeriodeTarifID"`
KelasDefault *string `json:"KelasDefault"`
SemuaLayanan *string `json:"SemuaLayanan"`
DiskonSemuaLayananRJ *float64 `json:"DiskonSemuaLayananRJ"`
DiskonSemuaLayananRI *float64 `json:"DiskonSemuaLayananRI"`
Keterangan *string `json:"Keterangan"`
NA string `json:"NA"`
Aktif bool `json:"Aktif"`
JumlahLayanan int `json:"JumlahLayanan"`
Layanan []TarifKerjasamaLayanan `json:"Layanan"`
}
type TarifKerjasamaLayanan struct {
LayananID *string `json:"LayananID"`
NamaLayanan *string `json:"NamaLayanan"`
JenisLayananID *string `json:"JenisLayananID"`
NamaJenisLayanan *string `json:"NamaJenisLayanan"`
JumlahDetail int `json:"JumlahDetail"`
Detail []TarifKerjasamaLayananDetail `json:"Detail"`
}
type TarifKerjasamaLayananDetail struct {
TarifLayananID int64 `json:"TarifLayananID"`
KelasID *string `json:"KelasID"`
NamaKelas *string `json:"NamaKelas"`
Harga float64 `json:"Harga"`
TipeDiskonRJ *string `json:"TipeDiskonRJ"`
DiskonRJ float64 `json:"DiskonRJ"`
TipeDiskonRI *string `json:"TipeDiskonRI"`
DiskonRI float64 `json:"DiskonRI"`
TipeShare *string `json:"TipeShare"`
ShareDokter float64 `json:"ShareDokter"`
ShareRS float64 `json:"ShareRS"`
ShareExternal float64 `json:"ShareExternal"`
ShareLain float64 `json:"ShareLain"`
AsalTarif *string `json:"AsalTarif"`
}
func (r MySQLLayananRepository) ListTarifKerjasama(ctx context.Context, params TarifKerjasamaListParams) (TarifKerjasamaPage, error) {
where, args := tarifKerjasamaListWhere(params)
var total int
countQuery := `SELECT COUNT(*) FROM perusahaan_tarif pt
INNER JOIN perusahaan p ON p.PerusahaanID = pt.PerusahaanID ` + where
if err := r.db.QueryRowContext(ctx, countQuery, args...).Scan(&total); err != nil {
return TarifKerjasamaPage{}, err
}
query := `SELECT pt.Perusahaan_TarifID, pt.PerusahaanID, p.KodePerusahaan,
p.Nama, pt.PenanggungJawab, p.Alamat, pt.TeleponPenanggungJawab,
pt.EmailPenanggungJawab,
COALESCE(pg.JumlahGroupTarif, 0)
FROM perusahaan_tarif pt
INNER JOIN perusahaan p ON p.PerusahaanID = pt.PerusahaanID
LEFT JOIN (
SELECT Perusahaan_TarifID, COUNT(*) AS JumlahGroupTarif
FROM perusahaan_grouptarif
WHERE NA = 'N'
GROUP BY Perusahaan_TarifID
) pg ON pg.Perusahaan_TarifID = pt.Perusahaan_TarifID ` + where + `
ORDER BY p.Nama, pt.Perusahaan_TarifID
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 TarifKerjasamaPage{}, err
}
defer rows.Close()
items := make([]TarifKerjasamaSummary, 0)
for rows.Next() {
item, err := scanTarifKerjasamaSummary(rows)
if err != nil {
return TarifKerjasamaPage{}, err
}
items = append(items, item)
}
if err := rows.Err(); err != nil {
return TarifKerjasamaPage{}, err
}
return TarifKerjasamaPage{Items: items, Total: total}, nil
}
func tarifKerjasamaListWhere(params TarifKerjasamaListParams) (string, []any) {
conditions := []string{"p.NA = 'N'"}
args := make([]any, 0, 6)
if params.Search != "" {
like := "%" + params.Search + "%"
conditions = append(conditions, `(p.Nama LIKE ? OR p.KodePerusahaan LIKE ?
OR pt.PerusahaanID LIKE ? OR pt.PenanggungJawab LIKE ?)`)
args = append(args, like, like, like, like)
}
if params.PerusahaanID != "" {
conditions = append(conditions, "pt.PerusahaanID = ?")
args = append(args, params.PerusahaanID)
}
if params.TarifID != nil {
conditions = append(conditions, "pt.Perusahaan_TarifID = ?")
args = append(args, *params.TarifID)
}
return "WHERE " + strings.Join(conditions, " AND "), args
}
func (r MySQLLayananRepository) GetTarifKerjasama(ctx context.Context, id int64, params TarifKerjasamaDetailParams) (*TarifKerjasamaDetail, error) {
row := r.db.QueryRowContext(ctx, `SELECT pt.Perusahaan_TarifID, pt.PerusahaanID,
p.KodePerusahaan, p.Nama, pt.PenanggungJawab, p.Alamat,
pt.TeleponPenanggungJawab, pt.EmailPenanggungJawab, 0
FROM perusahaan_tarif pt
INNER JOIN perusahaan p ON p.PerusahaanID = pt.PerusahaanID
WHERE pt.Perusahaan_TarifID = ? AND p.NA = 'N' LIMIT 1`, id)
master, err := scanTarifKerjasamaSummary(row)
if err == sql.ErrNoRows {
return nil, nil
}
if err != nil {
return nil, err
}
groups, err := r.tarifKerjasamaGroups(ctx, id, params)
if err != nil {
return nil, err
}
master.JumlahGroupTarif = len(groups)
return &TarifKerjasamaDetail{TarifKerjasamaSummary: master, GroupTarif: groups}, nil
}
func scanTarifKerjasamaSummary(row rowScanner) (TarifKerjasamaSummary, error) {
var item TarifKerjasamaSummary
var kode, pic, alamat, telepon, email sql.NullString
err := row.Scan(&item.PerusahaanTarifID, &item.PerusahaanID, &kode,
&item.NamaPerusahaan, &pic, &alamat, &telepon, &email, &item.JumlahGroupTarif)
item.KodePerusahaan = nullStringPtr(kode)
item.PIC = nullStringPtr(pic)
item.Alamat = nullStringPtr(alamat)
item.Telepon = nullStringPtr(telepon)
item.Email = nullStringPtr(email)
return item, err
}
func (r MySQLLayananRepository) tarifKerjasamaGroups(ctx context.Context, id int64, params TarifKerjasamaDetailParams) ([]TarifKerjasamaGroup, error) {
if params.Active == "" {
params.Active = "N"
}
conditions := []string{"pg.Perusahaan_TarifID = ?", "pg.NA = ?"}
args := []any{id, params.Active}
if params.GroupTarifID != nil {
conditions = append(conditions, "pg.GroupTarifID = ?")
args = append(args, *params.GroupTarifID)
}
if params.EffectiveDate != "" {
conditions = append(conditions, "DATE(pg.TanggalBerlaku) <= ?", "DATE(pg.TanggalBerakhir) >= ?")
args = append(args, params.EffectiveDate, params.EffectiveDate)
}
query := `SELECT pg.GroupTarifID, pg.Perusahaan_TarifID, pg.Keterangan,
CAST(pg.TanggalBerlaku AS CHAR), CAST(pg.TanggalBerakhir AS CHAR),
pg.PeriodeTarifID, pg.KelasDefault, pg.SemuaLayanan,
CAST(pg.HargaLayanan AS CHAR), CAST(pg.HargaLayananRI AS CHAR), pg.NA
FROM perusahaan_grouptarif pg WHERE ` + strings.Join(conditions, " AND ") + `
ORDER BY pg.TanggalBerlaku DESC, pg.GroupTarifID DESC`
rows, err := r.db.QueryContext(ctx, query, args...)
if err != nil {
return nil, err
}
defer rows.Close()
groups := make([]TarifKerjasamaGroup, 0)
for rows.Next() {
var group TarifKerjasamaGroup
var keterangan, kelasDefault, semuaLayanan, hargaRJ, hargaRI sql.NullString
var periodeTarifID sql.NullInt64
if err := rows.Scan(
&group.GroupTarifID, &group.PerusahaanTarifID, &keterangan,
&group.TanggalBerlaku, &group.TanggalBerakhir, &periodeTarifID,
&kelasDefault, &semuaLayanan, &hargaRJ, &hargaRI, &group.NA,
); err != nil {
return nil, err
}
group.Keterangan = nullStringPtr(keterangan)
group.KelasDefault = nullStringPtr(kelasDefault)
group.SemuaLayanan = nullStringPtr(semuaLayanan)
group.PeriodeTarifID = nullInt64Ptr(periodeTarifID)
group.DiskonSemuaLayananRJ = nullDecimalPtr(hargaRJ)
group.DiskonSemuaLayananRI = nullDecimalPtr(hargaRI)
group.NamaGroupTarif = "Group Tarif " + strconv.FormatInt(group.GroupTarifID, 10)
if group.Keterangan != nil && *group.Keterangan != "" {
group.NamaGroupTarif = *group.Keterangan
}
group.Aktif = group.NA == "N"
groups = append(groups, group)
}
if err := rows.Err(); err != nil {
_ = rows.Close()
return nil, err
}
if err := rows.Close(); err != nil {
return nil, err
}
for index := range groups {
groups[index].Layanan, err = r.tarifKerjasamaLayanan(ctx, groups[index].GroupTarifID)
if err != nil {
return nil, err
}
groups[index].JumlahLayanan = len(groups[index].Layanan)
}
return groups, nil
}
type tarifKerjasamaLayananRow struct {
TarifLayananID int64
LayananID sql.NullString
NamaLayanan sql.NullString
JenisLayananID sql.NullString
NamaJenis sql.NullString
KelasID sql.NullString
NamaKelas sql.NullString
Harga sql.NullString
TipeDiskonRJ sql.NullString
DiskonRJ sql.NullString
TipeDiskonRI sql.NullString
DiskonRI sql.NullString
TipeShare sql.NullString
ShareDokter sql.NullString
ShareRS sql.NullString
ShareExternal sql.NullString
ShareLain sql.NullString
AsalTarif sql.NullString
}
func (r MySQLLayananRepository) tarifKerjasamaLayanan(ctx context.Context, groupID int64) ([]TarifKerjasamaLayanan, error) {
tableName := "perusahaan_tarif_layanan_" + strconv.FormatInt(groupID, 10)
query := fmt.Sprintf(`SELECT tarif.TarifLayananID, CAST(tarif.LayananID AS CHAR),
layanan.Nama, CAST(tarif.JenisLayananID AS CHAR), jenis.Nama,
CAST(tarif.KelasID AS CHAR), kelas.Nama, CAST(tarif.Harga AS CHAR),
tarif.TipeDiskon, CAST(tarif.Diskon AS CHAR), '' AS TipeDiskonRI,
'0' AS DiskonRI, tarif.TipeShare, CAST(tarif.ShareDokter AS CHAR),
CAST(tarif.ShareRS AS CHAR), CAST(tarif.ShareExternal AS CHAR),
CAST(tarif.ShareLain AS CHAR), tarif.AsalTarif
FROM %s tarif
LEFT JOIN masterlayanan layanan ON layanan.LayananID = tarif.LayananID
LEFT JOIN jenislayanan jenis ON jenis.JenisLayananID = tarif.JenisLayananID
LEFT JOIN kelas kelas ON kelas.KelasID = tarif.KelasID
WHERE tarif.NA = 'N'
ORDER BY layanan.Nama, tarif.LayananID, tarif.KelasID`, tableName)
rows, err := r.db.QueryContext(ctx, query)
if err != nil {
if isMySQLMissingTable(err) {
return []TarifKerjasamaLayanan{}, nil
}
return nil, err
}
defer rows.Close()
flatRows := make([]tarifKerjasamaLayananRow, 0)
for rows.Next() {
var row tarifKerjasamaLayananRow
if err := rows.Scan(
&row.TarifLayananID, &row.LayananID, &row.NamaLayanan,
&row.JenisLayananID, &row.NamaJenis, &row.KelasID, &row.NamaKelas,
&row.Harga, &row.TipeDiskonRJ, &row.DiskonRJ, &row.TipeDiskonRI,
&row.DiskonRI, &row.TipeShare, &row.ShareDokter, &row.ShareRS,
&row.ShareExternal, &row.ShareLain, &row.AsalTarif,
); err != nil {
return nil, err
}
flatRows = append(flatRows, row)
}
if err := rows.Err(); err != nil {
return nil, err
}
return groupTarifKerjasamaLayanan(flatRows), nil
}
func isMySQLMissingTable(err error) bool {
var mysqlError *mysql.MySQLError
return errors.As(err, &mysqlError) && mysqlError.Number == 1146
}
func groupTarifKerjasamaLayanan(rows []tarifKerjasamaLayananRow) []TarifKerjasamaLayanan {
result := make([]TarifKerjasamaLayanan, 0)
indexes := make(map[string]int)
for _, row := range rows {
key := row.LayananID.String + "\x1f" + row.JenisLayananID.String
index, exists := indexes[key]
if !exists {
index = len(result)
indexes[key] = index
result = append(result, TarifKerjasamaLayanan{
LayananID: nullStringPtr(row.LayananID), NamaLayanan: nullStringPtr(row.NamaLayanan),
JenisLayananID: nullStringPtr(row.JenisLayananID), NamaJenisLayanan: nullStringPtr(row.NamaJenis),
Detail: make([]TarifKerjasamaLayananDetail, 0),
})
}
namaKelas := row.NamaKelas
if !namaKelas.Valid || namaKelas.String == "" {
namaKelas = row.KelasID
}
result[index].Detail = append(result[index].Detail, TarifKerjasamaLayananDetail{
TarifLayananID: row.TarifLayananID, KelasID: nullStringPtr(row.KelasID),
NamaKelas: nullStringPtr(namaKelas), Harga: decimalValue(row.Harga),
TipeDiskonRJ: nullStringPtr(row.TipeDiskonRJ), DiskonRJ: decimalValue(row.DiskonRJ),
TipeDiskonRI: nullStringPtr(row.TipeDiskonRI), DiskonRI: decimalValue(row.DiskonRI),
TipeShare: nullStringPtr(row.TipeShare), ShareDokter: decimalValue(row.ShareDokter),
ShareRS: decimalValue(row.ShareRS), ShareExternal: decimalValue(row.ShareExternal),
ShareLain: decimalValue(row.ShareLain), AsalTarif: nullStringPtr(row.AsalTarif),
})
result[index].JumlahDetail = len(result[index].Detail)
}
return result
}
func nullDecimalPtr(value sql.NullString) *float64 {
if !value.Valid {
return nil
}
parsed := decimalValue(value)
return &parsed
}
func decimalValue(value sql.NullString) float64 {
if !value.Valid {
return 0
}
parsed, _ := strconv.ParseFloat(value.String, 64)
return parsed
}