Backup Get Comp Ins Detail
unknown
coldfusion
a month ago
18 kB
4
Indexable
package usecase
import (
"context"
"fmt"
"strconv"
"strings"
"time"
"github.com/dustin/go-humanize"
"github.com/spf13/cast"
"gitlab.dataon.com/gophers/sf7-lib/v2/log"
"gitlab.dataon.com/gophers/sf7-sdk/sfquery"
employeeentity "sf-payroll-th/internal/employee/entity"
insuranceentity "sf-payroll-th/internal/insurance/entity"
statutory "sf-payroll-th/internal/statutory"
"sf-payroll-th/internal/statutory/entity"
taxlocationentity "sf-payroll-th/internal/tax-location/entity"
"sf-payroll-th/pkg/consts"
"sf-payroll-th/pkg/util"
)
// GetCompanyInsDetail is the function to get detail information company & insurance data
//
// The response contains company insurance detail information such as company zip code, company phone, company fax, branch account, branch name, registration date, address number, village number, alley, street name, sub district, district, city, province and province branch.
//
// The function calls GetCompInsuranceRelation to get company insurance relation
// and then calls GetCompInsuranceFieldWithRelations to get company insurance field with relations
// Finally, it maps company insurance field to the response and returns the response
//
// If there is an error while calling the underlying functions, it logs the error and returns the error
func (u *usecase) GetCompanyInsDetail(ctx context.Context, db sfquery.DBExecutor, SSOCompany string) (res statutory.GetCompanyInsDetailResponse, err error) {
wr := log.FromCtx(ctx)
wr.Dbg("GetCompanyInsDetail Func - called")
companyID := cast.ToInt(ctx.Value(consts.CompanyId))
//
// Get Company Insurance (with institution_code=SSO filter)
// Mirrors Lucee's qSSOBranch query in getDetInsCompany.
//
compInsRel, err := u.employeeRepository.GetCompInsuranceRelation(ctx, db, employeeentity.ParamCompInsurance{
BranchCode: SSOCompany,
InstitutionCode: "SSO",
CompanyID: companyID,
})
if err != nil {
wr.Err("failed to get company insurance relation", log.Error(err), log.StackTrace())
return res, nil
}
// Lucee getDetInsCompany uses insurance_phone (TEORCOMPINSURANCE.insurance_phone) for
// company phone — NOT TEOMCOMPANY.company_phone.
res.CompanyZipCode = util.PointerValue(compInsRel.InsuranceZipcode, "")
res.CompanyPhone = util.PointerValue(compInsRel.InsurancePhone, "")
res.CompanyFax = util.PointerValue(compInsRel.InsuranceFax, "")
res.BranchAccount = compInsRel.BranchAccount
res.BranchName = util.PointerValue(compInsRel.BranchcompanyName, "")
res.RegistrationDate = util.FormatTimePtrToDateString(compInsRel.RegisterDate, time.DateOnly)
//
// Get raw insurance row (no institution_code filter).
// Lucee's getDetInsCompany runs a second JOIN query (qGetCompInfo) against
// TEOMCOMPANY + TEORCOMPINSURANCE without institution_code filter to get:
// - accountno = TEORCOMPINSURANCE.register_no (used for 103/608/609 record lines)
// Also used for sso103SummaryTEXT header via RIGHT(register_no,10) / RIGHT(branch_account,6).
//
rawInsRel, rawErr := u.employeeRepository.GetCompInsuranceRelation(ctx, db, employeeentity.ParamCompInsurance{
BranchCode: SSOCompany,
CompanyID: companyID,
})
if rawErr == nil {
rawRegisterNo := rawInsRel.RegisterNo
if len(rawRegisterNo) > 10 {
rawRegisterNo = rawRegisterNo[len(rawRegisterNo)-10:]
}
rawBranchAccount := rawInsRel.BranchAccount
if len(rawBranchAccount) > 6 {
rawBranchAccount = rawBranchAccount[len(rawBranchAccount)-6:]
}
res.HeaderAccountNo = rawRegisterNo
res.HeaderBranchAccount = rawBranchAccount
}
//
// Get Additional Company Information (address fields).
// addressnumber, alley, streetname, subdistrict, district → from TPYMTAXLOCATION via TaxLocation module.
// villagenumber, city, province, provincebranch → still from TPYRCOMPINSURANCEFIELD key-value store.
//
// --- Part 1: TaxLocation address fields ---
taxLoc, taxLocErr := u.taxLocationRepository.GetTaxLocation(ctx, db, taxlocationentity.TaxLocationFilter{
TaxLocationCode: "",
CompanyID: companyID,
})
if taxLocErr == nil {
res.AddressNumber = util.PointerValue(taxLoc.AddressNumber, "")
res.Alley = util.PointerValue(taxLoc.Alley, "")
res.StreetName = util.PointerValue(taxLoc.StreetName, "")
res.SubDistrict = util.PointerValue(taxLoc.SubDistrict, "")
res.District = util.PointerValue(taxLoc.District, "")
} else {
wr.Dbg("tax location not found or error", log.Error(taxLocErr))
}
// --- Part 2: TPYRCOMPINSURANCEFIELD key-value fields (VillageNumber, City, Province, ProvinceBranch) ---
if util.PointerValue(compInsRel.InstitutionCode, "") != "" {
compInsFields, totalCompInsField, insFieldErr := u.insuranceRepository.GetCompInsuranceFieldWithRelations(ctx, db, insuranceentity.ParamCompInsuranceField{
InstitutionCode: util.PointerValue(compInsRel.InstitutionCode, ""),
CompanyID: companyID,
RegisterNo: compInsRel.RegisterNo,
})
if insFieldErr != nil {
// Log but do NOT return early — Lucee skips gracefully when no field data.
wr.Err("failed to get company insurance field", log.Error(insFieldErr), log.StackTrace())
} else if totalCompInsField > 0 {
for _, v := range compInsFields {
value := util.PointerValue(v.Value, "")
switch v.FieldCode {
case "VILLAGENUMBER":
res.VillageNumber = value
case "CITY":
res.City = value
case "PROVINCE":
res.Province = value
case "PROVINCEBRANCH":
res.ProvinceBranch = value
}
}
}
}
//
// Mapping Several Company Information
// Mirrors Lucee's qGetCompInfo JOIN and subsequent mapping.
//
company, compErr := u.employeeRepository.GetCompany(ctx, db, employeeentity.ParamCompany{
CompanyID: companyID,
})
if compErr != nil {
wr.Err("failed to get company", log.Error(compErr), log.StackTrace())
// Continue — company name falls back to register_name from insurance row if set.
}
// Company name: prefer insurance register_name over TEOMCOMPANY.company_name
companyName := company.CompanyName
if util.PointerValue(compInsRel.RegisterName, "") != "" {
companyName = util.PointerValue(compInsRel.RegisterName, "")
}
// Company address: prefer TEORCOMPINSURANCE.insurance_address over TEOMCOMPANY.company_address2
companyAddr := util.PointerValue(company.CompanyAddress2, "")
if util.PointerValue(compInsRel.InsuranceAddress, "") != "" {
companyAddr = util.PointerValue(compInsRel.InsuranceAddress, "")
}
// Company name split matches Lucee's splitCompanyName behavior:
// - All fits in 60 chars → name1=full name, name2=""
// - Split case → name1=second part, name2=first part (swapped, per companynameposition logic)
companyPart1, companyPart2 := SplitTextByLength(companyName, 60)
companyAddress1, companyAddress2 := SplitTextByLength(companyAddr, 40)
res.CompanyName = companyName
if companyPart2 != "" {
// swap: name1=part2, name2=part1 (matches Lucee companynameposition=true behavior)
res.CompanyName1 = companyPart2
res.CompanyName2 = companyPart1
} else {
res.CompanyName1 = companyPart1
res.CompanyName2 = companyPart2
}
res.CompanyAddr1 = companyAddress1
res.CompanyAddr2 = companyAddress2
// AccountNo: Lucee getDetInsCompany sets accountno = qGetCompInfo.register_no, which is
// TEORCOMPINSURANCE.register_no from the raw JOIN query (no institution_code filter).
// Use rawInsRel.RegisterNo when available; fall back to SSO-filtered value.
if rawErr == nil {
res.AccountNo = rawInsRel.RegisterNo
} else {
res.AccountNo = compInsRel.RegisterNo
}
res.TaxFileNumber = util.PointerValue(company.Taxfilenumber, "")
res.LocalName = util.PointerValue(company.LocalName, "")
// CompanyRegisterNo: TEOMCOMPANY.register_no
res.CompanyRegisterNo = util.PointerValue(company.RegisterNo, "")
res.BranchAccount = compInsRel.BranchAccount
// InsuranceAddress: raw insurance_address from TEORCOMPINSURANCE (may be empty string).
res.InsuranceAddress = util.PointerValue(compInsRel.InsuranceAddress, "")
return
}
// SplitTextByLength splits a given text into two parts based on the maximum length.
// It takes a text and a maximum length as input, and returns two strings.
// The first string will contain as many words as possible without exceeding the maximum length,
// and the second string will contain the remaining words.
func SplitTextByLength(text string, maxLen int) (string, string) {
text = strings.TrimSpace(text)
words := strings.Fields(text)
if len(words) == 0 {
return "", ""
}
var part1 []string
currentLen := 0
for i, word := range words {
wordLen := len([]rune(word))
if currentLen+wordLen+1 <= maxLen {
part1 = append(part1, word)
currentLen += wordLen + 1
} else {
return strings.Join(part1, " "), strings.Join(words[i:], " ")
}
}
return strings.Join(part1, " "), ""
}
// formatIntPart formats a float64 as a string with commas
// separating every three digits. It truncates the decimal part
// and returns the integer part as a string.
func formatIntPart(v float64) string {
return humanize.Comma(int64(v))
}
// formatDecimalPart formats a float64 as a string with two decimal places.
// It truncates the integer part and returns the decimal part as a string.
// If the input is an integer, it returns "00".
func formatDecimalPart(v float64) string {
s := fmt.Sprintf("%.2f", v)
parts := strings.Split(s, ".")
if len(parts) == 2 {
return parts[1]
}
return "00"
}
// mapTerminationReason maps resignation and terminate_reason fields to SSO reason code (1-7).
// Both fields are checked case-insensitively. Returns 0 for unknown combinations.
func mapTerminationReason(resignation, terminateReason string) int {
res := strings.ToUpper(strings.TrimSpace(resignation))
tr := strings.ToUpper(strings.TrimSpace(terminateReason))
switch {
case res == "RESIGN" || res == "RESIGNED" || res == "RELOCATION" || tr == "RESIGNED":
return 1
case res == "CONTRACTEND" || res == "ENDOFCONTRACT" || tr == "CONTACTEXPIRED":
return 2
case res == "TERMINATE" || res == "TERMINATED" || res == "EARLYRETIREMENT" || tr == "TERMINATED" || tr == "EARLYRETIRED":
return 3
case res == "PENSION" || tr == "RETIRED":
return 4
case res == "PERSONALMATTER" || res == "DISMISSED" || res == "DISMISS" || tr == "DISMISSED":
return 5
case res == "DECEASED" || res == "DIE" || tr == "PASSAWAY":
return 6
case res == "MUTATION" || res == "TRANSFER" || tr == "MUTATION" || tr == "TRANSFER":
return 7
default:
return 0
}
}
// formatBuddhistDate formats a time.Time as a Buddhist Era date string.
// Supported formats: "dd/mm/yyyy" (e.g. "15/06/2568"), "ddmmyyyy" (e.g. "15062568").
func formatBuddhistDate(t time.Time, format string) string {
day := fmt.Sprintf("%02d", t.Day())
month := fmt.Sprintf("%02d", int(t.Month()))
year := fmt.Sprintf("%04d", t.Year()+543)
switch format {
case "ddmmyyyy":
return day + month + year
default: // "dd/mm/yyyy"
return day + "/" + month + "/" + year
}
}
// GetSSOCollection queries terminated employee data for SSO 609 TXT report Header I.
// It returns employee count, min/max terminate dates (+1 day), and employee data.
func (u *usecase) GetSSOCollection(ctx context.Context, db sfquery.DBExecutor, req statutory.GetSSORequest) (res statutory.GetSSOCollectionResponse, err error) {
wr := log.FromCtx(ctx)
wr.Dbg("GetSSOCollection Support Func - Called")
companyID := cast.ToInt(ctx.Value(consts.CompanyId))
employees, total, err := u.repository.GetSSO609s(ctx, db, entity.ParamSSO609{
CompanyID: companyID,
InsuranceNo: req.SSOCompany,
EffectiveDateFrom: req.EffectiveDateFrom,
EffectiveDateTo: req.EffectiveDateTo,
Offset: 0,
Limit: 100000,
})
if err != nil {
wr.Err("failed to get SSO 609 data for collection", log.Error(err), log.StackTrace())
return
}
if total == 0 {
return
}
res.TotalHC609 = total
res.EmployeeData = employees
// Compute min/max terminate_date + 1 day
var minDate, maxDate time.Time
first := true
for _, emp := range employees {
if emp.TerminateDate == nil {
continue
}
d := emp.TerminateDate.AddDate(0, 0, 1)
if first {
minDate = d
maxDate = d
first = false
} else {
if d.Before(minDate) {
minDate = d
}
if d.After(maxDate) {
maxDate = d
}
}
}
if !first {
res.FirstJoinDate609 = &minDate
res.LastJoinDate609 = &maxDate
}
return
}
// GetSSO103 returns a list of SSO 103 data based on the given parameters.
// It takes a context, a database executor, and a request object as input, and returns a list of SSO 103 data and an error.
// The request object should contain the company ID, language, tax field code, insurance number, and insurance code.
// The function will return an error if the data is not found or if there is an error while retrieving the data.
// The function will return a list of SSO 103 data if the data is found successfully.
func (u usecase) GetSSO103(ctx context.Context, db sfquery.DBExecutor, req statutory.GetSSORequest) (res []statutory.GetSSO103Response, err error) {
wr := log.FromCtx(ctx)
wr.Dbg("GetSSO103 Support Func - Called ")
companyID := cast.ToInt(ctx.Value(consts.CompanyId))
lang := cast.ToString(ctx.Value(consts.Language))
//
// Get SSO 103 Data
//
sso103s, totalSso103, err := u.repository.GetSSO103s(ctx, db, entity.ParamSSO103{
Lang: lang,
CompanyID: companyID,
TaxfieldCode: "DISABILITY",
InsuranceNo: req.SSOCompany,
InsuranceCode: "SSO",
DataFormat: req.DataFormat,
SocialMember: "Y",
EffectiveDateFrom: req.EffectiveDateFrom,
EffectiveDateTo: req.EffectiveDateTo,
})
wr.Dbg("sso103s - debug",
log.Any("sso103s", sso103s))
if err != nil {
wr.Err("failed to get sso 103 data", log.Error(err), log.StackTrace())
}
if totalSso103 == 0 {
wr.Inf("data sso 103 not found")
return res, nil
}
for _, v := range sso103s {
res = append(res, statutory.GetSSO103Response{
EmpID: v.EmpID,
EmpNo: v.EmpNo,
FullName: util.PointerValue(v.FullName, ""),
FirstName: v.FirstName,
MiddleName: util.PointerValue(v.MiddleName, ""),
LastName: util.PointerValue(v.LastName, ""),
LocalFirstName: util.PointerValue(v.LocalFirstName, ""),
LocalMiddleName: util.PointerValue(v.LocalMiddleName, ""),
LocalLastName: util.PointerValue(v.LocalLastName, ""),
OfficialName: util.PointerValue(v.OfficialName, ""),
Gender: v.Gender,
StartDate: v.StartDate,
IdentityNo: util.PointerValue(v.IDentityNo, ""),
NationalityCode: util.PointerValue(v.NationalityCode, ""),
BirthDate: v.Birthdate,
SalutationCode: util.PointerValue(v.SalutationCode, ""),
MaritalStatus: util.PointerValue(v.Maritalstatus, ""),
SalutationName: util.PointerValue(v.SalutationName, ""),
SalutationCodeMaster: util.PointerValue(v.Code, ""),
CompanyName: util.PointerValue(v.CompanyName, ""),
NationalityName: util.PointerValue(v.NationalityName, ""),
SalaryInsuranceNo: util.PointerValue(v.SalaryInsuranceNo, ""),
InsuranceNoEmp: util.PointerValue(v.SalaryInsuranceNo, ""),
UsedToBeSocialMember: util.PointerValue(v.UsedToBeSocialMember, ""),
JoinDate: v.JoinDt,
EndDate: v.EndDt,
Hospital1: util.PointerValue(v.HospitalDoc1, ""),
Hospital2: util.PointerValue(v.HospitalDoc2, ""),
Hospital3: util.PointerValue(v.HospitalDoc3, ""),
DocHospitalCode1: util.PointerValue(v.Hospital1, ""),
DocHospitalCode2: util.PointerValue(v.Hospital2, ""),
DocHospitalCode3: util.PointerValue(v.Hospital3, ""),
MaritalStatusName: util.PointerValue(v.MaritalNameEn, ""),
DisabilityTax: util.PointerValue(v.Value, ""),
DisabilityCode: util.PointerValue(v.DisabilityCode, ""),
NumOfChild: cast.ToInt(util.PointerValue(v.ChildCountValue, "0")),
})
}
return
}
// ThaiBaht converts a float64 amount to Thai Baht words.
// The decimal part is ignored — only the integer part is converted.
// Example: ThaiBaht(15000) → "หนึ่งหมื่นห้าพันบาท"
func ThaiBaht(amount float64) string {
numWords := []string{"ศูนย์", "หนึ่ง", "สอง", "สาม", "สี่", "ห้า", "หก", "เจ็ด", "แปด", "เก้า"}
posWords := []string{"", "สิบ", "ร้อย", "พัน", "หมื่น", "แสน", "ล้าน", "สิบล้าน", "ร้อยล้าน"}
intPart := int64(amount)
if intPart < 0 {
intPart = -intPart
}
if intPart == 0 {
return "ศูนย์บาท"
}
s := strconv.FormatInt(intPart, 10)
numLen := len(s)
var result string
for idx, ch := range s {
digit := int(ch - '0')
if digit == 0 {
continue
}
posIdx := numLen - 1 - idx // distance from last digit
if posIdx >= len(posWords) {
posIdx = len(posWords) - 1
}
var numWord string
switch {
case digit == 1 && idx == numLen-1 && numLen > 1:
// last digit, not the only digit → เอ็ด
numWord = "เอ็ด"
case digit == 1 && posIdx == 1:
// tens position → suppress numeral, just use สิบ
numWord = ""
case digit == 2 && posIdx == 1:
// tens position, digit 2 → ยี่
numWord = "ยี่"
default:
numWord = numWords[digit]
}
result += numWord + posWords[posIdx]
}
return result + "บาท"
}
// mapSalutationCodeToThaiName maps a salutation code to a Thai prefix name.
// MR → นาย, MS → นางสาว, MRS → นาง; any other code defaults to นาย.
func mapSalutationCodeToThaiName(code string) string {
switch strings.ToUpper(strings.TrimSpace(code)) {
case "MR":
return "นาย"
case "MS":
return "นางสาว"
case "MRS":
return "นาง"
default:
return "นาย"
}
}
// mapSalutationToSSOCode maps a salutation code to the SSO numeric code used in TXT files.
// Handles both text codes (MR/MS/MRS) and numeric codes (003/004/005).
// MR/003 → 003, MS/004 → 004, MRS/005 → 005; others default to 003.
func mapSalutationToSSOCode(code string) string {
switch strings.ToUpper(strings.TrimSpace(code)) {
case "MR", "003":
return "003"
case "MS", "004":
return "004"
case "MRS", "005":
return "005"
default:
return "003"
}
}
Editor is loading...
Leave a Comment