update core calc

-
 avatar
unknown
golang
3 months ago
55 kB
8
Indexable
package usecase

import (
	"context"
	"database/sql"
	"errors"
	"fmt"
	"sf-payroll-th/pkg/consts"
	"sf-payroll-th/pkg/helper"
	"sf-payroll-th/pkg/util"
	"time"

	"github.com/doug-martin/goqu/v9"
	"github.com/doug-martin/goqu/v9/exp"
	"github.com/jmoiron/sqlx"
	"github.com/spf13/cast"

	componentstdconst "gitlab.dataon.com/gophers/sf-payroll/modules/core/component/constant"
	paymentprocessstd "gitlab.dataon.com/gophers/sf-payroll/modules/core/payment"
	"gitlab.dataon.com/gophers/sf-payroll/modules/core/payment/entity"
	paymententitystd "gitlab.dataon.com/gophers/sf-payroll/modules/core/payment/entity"
	periodentitystd "gitlab.dataon.com/gophers/sf-payroll/modules/core/period/entity"
	salaryparamconst "gitlab.dataon.com/gophers/sf-payroll/modules/core/salary-param/constant"
	employeentitystd "gitlab.dataon.com/gophers/sf-payroll/modules/general/employee/entity"
	helperstd "gitlab.dataon.com/gophers/sf-payroll/pkg/helper"
	"gitlab.dataon.com/gophers/sf7-lib/v2/log"
	"gitlab.dataon.com/gophers/sf7-sdk/database"
	"gitlab.dataon.com/gophers/sf7-sdk/rbac"
	"gitlab.dataon.com/gophers/sf7-sdk/sfquery"
)

type coreCalculationLogic struct {
	companyID     int
	currentTime   time.Time
	employeeData  employeentitystd.EmployeePersonalCompanySalary
	req           paymentprocessstd.PaymentProcessRequest
	period        periodentitystd.MasterPayrollPeriod
	procYtdhID    string
	procMtdhID    string
	userLoginData rbac.AuthUser
	vp            *ThPayment

	Salary       float64
	SalaryTax    float64
	SalaryPeriod float64
	RateTax      float64
	RatePeriod   float64
}

func (vp *ThPayment) CoreCalculation(ctx context.Context, db *sqlx.DB, req paymentprocessstd.PaymentProcessRequest) (err error) {
	wr := log.FromCtx(ctx)
	wr.Dbg("Custom Core Calculation")

	var l coreCalculationLogic
	l.companyID = cast.ToInt(ctx.Value(consts.CompanyId))
	l.currentTime = time.Now().In(util.LoadTimeLocation())
	l.userLoginData = util.GetFromContext[rbac.AuthUser](ctx, consts.AuthUser)
	l.req = req
	l.vp = vp

	db, err = database.ClientConnectionFromCtx(ctx, "PAYROLL")
	if err != nil {
		wr.Err(consts.LogMessageDBFailedToConnect, log.Error(err), log.StackTrace())
		return err
	}

	tx, err := db.BeginTxx(ctx, &sql.TxOptions{Isolation: sql.LevelReadUncommitted})
	if err != nil {
		return fmt.Errorf("failed to begin transaction for CoreCalculation: %w", err)
	}
	defer func() {
		helper.CommitOrRollback(ctx, tx, err)
	}()

	period, _ := vp.Core.Period.GetPeriod(ctx, tx, helperstd.ParamRepository{
		Columns: []any{
			goqu.I("period_code"),
			goqu.I("period_type"),
			goqu.I("paydate"),
			goqu.I("taxdate"),
			goqu.I("usesalary"),
			goqu.I("salarystartdate"),
			goqu.I("salaryenddate"),
			goqu.I("currency_code"),
		},
		Conditions: goqu.And(
			goqu.I("period_code").Eq(req.PeriodCode),
			goqu.I("company_id").Eq(l.companyID),
		),
	})
	l.period = period

	l.employeeData, _ = l.vp.Core.Employee.GetEmployeePersonalCompanySalary(ctx, tx, helperstd.ParamRepository{
		Columns: []any{
			// salary param
			goqu.T(salaryparamconst.TableNameTPYDEmpSalaryParam).Col("currency_code"),
			goqu.T(salaryparamconst.TableNameTPYDEmpSalaryParam).Col("effective_date"),
			goqu.T(salaryparamconst.TableNameTPYDEmpSalaryParam).Col("formula"),
			goqu.T(salaryparamconst.TableNameTPYDEmpSalaryParam).Col("taxstatus"),
			goqu.T(salaryparamconst.TableNameTPYDEmpSalaryParam).Col("numdependent"),
			goqu.T(salaryparamconst.TableNameTPYDEmpSalaryParam).Col("salnet"),
			goqu.T(salaryparamconst.TableNameTPYDEmpSalaryParam).Col("taxed"),
			goqu.T(salaryparamconst.TableNameTPYDEmpSalaryParam).Col("taxbornebycomp_dec"),
			goqu.T(salaryparamconst.TableNameTPYDEmpSalaryParam).Col("taxfilenumber"),
			goqu.T(salaryparamconst.TableNameTPYDEmpSalaryParam).Col("tax_type"),
			goqu.T(salaryparamconst.TableNameTPYDEmpSalaryParam).Col("taxlocation_code"),
			goqu.T(salaryparamconst.TableNameTPYDEmpSalaryParam).Col("taxpenaltybornebycomp_dec"),

			// emp personal
			goqu.T(consts.TableNameTEOMEmpPersonal).Col("first_name"),
			goqu.T(consts.TableNameTEOMEmpPersonal).Col("middle_name"),
			goqu.T(consts.TableNameTEOMEmpPersonal).Col("last_name"),
			goqu.T(consts.TableNameTEODEmpCompany).Col("emp_id"),

			// emp company
			goqu.T(consts.TableNameTEODEmpCompany).Col("work_location_code"),
			goqu.T(consts.TableNameTEODEmpCompany).Col("grade_code"),
			goqu.T(consts.TableNameTEODEmpCompany).Col("employ_code"),
			goqu.T(consts.TableNameTEODEmpCompany).Col("job_status_code"),
			goqu.T(consts.TableNameTEODEmpCompany).Col("position_id"),
			goqu.T(consts.TableNameTEODEmpCompany).Col("cost_code"),
			goqu.T(consts.TableNameTEODEmpCompany).Col("company_id"),
		},
		Conditions: goqu.And(
			goqu.C("company_id").Eq(l.companyID),
			goqu.C("emp_id").Eq(req.EmpID),
		),
	})

	err = l.processYtdh(ctx, tx)
	if err != nil {
		errMsg := fmt.Sprintf("failed to process ytdh: %s", req.EmpID)
		wr.Err(errMsg, log.Error(err), log.StackTrace())
		return errors.New(errMsg)
	}

	err = l.processMtdh(ctx, tx)
	if err != nil {
		errMsg := fmt.Sprintf("failed to process mtdh: %s", req.EmpID)
		wr.Err(errMsg, log.Error(err), log.StackTrace())
		return errors.New(errMsg)
	}

	err = l.processMtdcc(ctx, tx)
	if err != nil {
		errMsg := fmt.Sprintf("failed to process mtdcc: %s", req.EmpID)
		wr.Err(errMsg, log.Error(err), log.StackTrace())
		return errors.New(errMsg)
	}

	err = l.processMtdhInsurance(ctx, tx)
	if err != nil {
		errMsg := fmt.Sprintf("failed to process mtdh insurance: %s", req.EmpID)
		wr.Err(errMsg, log.Error(err), log.StackTrace())
		return errors.New(errMsg)
	}

	err = l.processMtddYtdd(ctx, tx)
	if err != nil {
		errMsg := fmt.Sprintf("failed to process mtddYtdd: %s", req.EmpID)
		wr.Err(errMsg, log.Error(err), log.StackTrace())
		return errors.New(errMsg)
	}

	return
}

func (l *coreCalculationLogic) processYtdh(ctx context.Context, tx sfquery.DBExecutor) (err error) {
	wr := log.FromCtx(ctx)

	wr.Dbg("Process Ytdh Function Called")
	//
	// Check Proc Ytdh ID on Mtdh based on taxdate
	//
	l.procYtdhID = ""
	getExistYtdhAnotherPeriod, _ := l.vp.Core.Payment.GetProcMTDH(ctx, tx, helperstd.ParamRepository{
		Columns: []any{
			goqu.I("procytdh_id"),
		},
		Conditions: goqu.And(
			goqu.I("emp_id").Eq(l.req.EmpID),
			goqu.L("MONTH(?)", goqu.C("taxdate")).Eq(l.period.Taxdate.Month()),
			goqu.L("YEAR(?)", goqu.C("taxdate")).Eq(l.period.Taxdate.Year()),
			goqu.I("company_id").Eq(l.companyID),
			goqu.I("period_code").Neq(l.req.PeriodCode),
			goqu.Or(
				goqu.I("terminate_process").Lt(2),
				goqu.I("terminate_process").IsNull(),
			),
		),
	})

	if util.PointerValue(getExistYtdhAnotherPeriod.ProcytdhID, "") != "" {
		l.procYtdhID = util.PointerValue(getExistYtdhAnotherPeriod.ProcytdhID, "")
	}

	//
	// Step 2: Get YTDH data based on conditions
	//
	//
	existYtdh, err := l.getExistYtdh(ctx, tx)
	if err != nil {
		wr.Err("failed to get exist ytdh", log.Error(err), log.StackTrace())
	}

	//
	// Step 3: Get previous MTDH data if exists (for this period)
	//
	prevMtdh, err := l.getPrevMtdh(ctx, tx)
	if err != nil {
		wr.Err("failed to get prev mtdh data", log.Error(err))
	}

	// Step 4: Check if tax location changed (will be used later)
	var taxLocationChanged bool
	if existYtdh.ytdTaxLocationCode != l.employeeData.TaxlocationCode {
		taxLocationChanged = true
		wr.Dbg("tax location changed",
			log.Any("tax location status", taxLocationChanged),
			log.Any("old tax location code", existYtdh.ytdTaxLocationCode),
			log.Any("new tax location code", l.employeeData.TaxlocationCode))
	}

	//
	// Step 5: Calculate Salary
	//
	err = l.calculateSalaryTax(ctx, tx)
	if err != nil {
		wr.Err("failed to calculate salary", log.Error(err))
	}

	needInsertNew := false
	var newProcYtdhID string

	switch {
	case existYtdh.procYtdhID == "":
		needInsertNew = true
		wr.Dbg("case A: no existing ytdh, will insert new")

		// Generate new ProcYtdhID
		newProcYtdhID, _ = helperstd.SfGenLogicKey(ctx, tx, helperstd.ParamGenLogicKey{
			KeyType:     "PROCYTDHID",
			EmpId:       l.req.EmpID,
			CompanyId:   l.companyID,
			TrxDate:     l.period.Taxdate,
			PeriodCode:  l.period.PeriodCode,
			ProcessFlag: "0",
			StartPeriod: int(l.period.Taxdate.Month()),
		})

		// Insert new YTDH with zero values
		zeroValueEncrypt := helperstd.SfEncryptCol(ctx, tx, 0, l.req.EmpID)
		newYtdh := paymententitystd.ProcYtdh{
			ProcytdhID:            newProcYtdhID,
			Currentyear:           int(l.period.Taxdate.Year()),
			EmpID:                 l.req.EmpID,
			Taxstatus:             util.ToPointer(l.employeeData.Taxstatus),
			Numdependent:          l.employeeData.Numdependent,
			Salnet:                l.employeeData.Salnet,
			Taxed:                 util.ToPointer(l.employeeData.Taxed),
			TaxbornebycompDec:     util.ToPointer(l.employeeData.TaxbornebycompDec),
			Taxfilenumber:         l.employeeData.Taxfilenumber,
			Tax:                   util.ToPointer(zeroValueEncrypt),
			Taxallow:              util.ToPointer(zeroValueEncrypt),
			Taxbornebycomp:        util.ToPointer(zeroValueEncrypt),
			Taxbornebygov:         util.ToPointer(zeroValueEncrypt),
			Taxpenalty:            util.ToPointer(zeroValueEncrypt),
			Taxpenaltybornebycomp: util.ToPointer(zeroValueEncrypt),
			SalaryTax:             util.ToPointer(helperstd.SfEncryptCol(ctx, tx, l.SalaryTax, l.req.EmpID)),
			CurrencyCodeTax:       "THB",
			TaxType:               l.employeeData.TaxType,
			TaxlocationCode:       l.employeeData.TaxlocationCode,
			CompanyID:             l.companyID,
			CreatedBy:             l.userLoginData.UNAME,
			ModifiedBy:            l.userLoginData.UNAME,
			CreatedDate:           l.currentTime,
			ModifiedDate:          l.currentTime,
		}

		err = l.vp.Core.Payment.InsertProcYtdh(ctx, tx, []paymententitystd.ProcYtdh{newYtdh})
		if err != nil {
			return err
		}

		// Set final ProcYtdhID
		l.procYtdhID = newProcYtdhID
	case existYtdh.procYtdhID != "" && prevMtdh.procmtdhID != "":
		wr.Dbg("case B: Theres existing ytdh and prev mtdh, will update ytdh",
			log.Any("exist ytdh", existYtdh),
			log.Any("prev mtdh", prevMtdh),
			log.Any("struct logic", l),
		)
		salaryTaxUpdated := existYtdh.SalaryTax - prevMtdh.prevSalaryTax
		taxUpdated := existYtdh.Tax - prevMtdh.prevTax
		taxAllowUpdated := existYtdh.TaxAllow - prevMtdh.prevTaxAllow
		taxBorneByCompUpdated := existYtdh.TaxBorneByComp - prevMtdh.prevTaxBorneByComp
		taxBorneByGovUpdated := existYtdh.TaxBorneByGov - prevMtdh.prevTaxBorneByGov
		taxPenaltyBorneByComp := existYtdh.TaxPenaltyBorneByComp - prevMtdh.prevTaxPenaltyBorneByComp
		taxPenaltyUpdated := existYtdh.TaxPenalty - prevMtdh.prevTaxPenalty

		updateYtdh := paymententitystd.ProcYtdh{
			Salnet:                   l.employeeData.Salnet,
			Taxed:                    util.ToPointer(l.employeeData.Taxed),
			TaxbornebycompDec:        util.ToPointer(l.employeeData.TaxbornebycompDec),
			TaxpenaltybornebycompDec: util.ToPointer(l.employeeData.TaxpenaltybornebycompDec),
			Taxfilenumber:            l.employeeData.Taxfilenumber,
			SalaryTax:                util.ToPointer(helperstd.SfEncryptCol(ctx, tx, salaryTaxUpdated, l.req.EmpID)),
			Tax:                      util.ToPointer(helperstd.SfEncryptCol(ctx, tx, taxUpdated, l.req.EmpID)),
			Taxallow:                 util.ToPointer(helperstd.SfEncryptCol(ctx, tx, taxAllowUpdated, l.req.EmpID)),
			Taxbornebycomp:           util.ToPointer(helperstd.SfEncryptCol(ctx, tx, taxBorneByCompUpdated, l.req.EmpID)),
			Taxbornebygov:            util.ToPointer(helperstd.SfEncryptCol(ctx, tx, taxBorneByGovUpdated, l.req.EmpID)),
			Taxpenalty:               util.ToPointer(helperstd.SfEncryptCol(ctx, tx, taxPenaltyUpdated, l.req.EmpID)),
			Taxpenaltybornebycomp:    util.ToPointer(helperstd.SfEncryptCol(ctx, tx, taxPenaltyBorneByComp, l.req.EmpID)),
			ModifiedBy:               l.userLoginData.UNAME,
			ModifiedDate:             l.currentTime,
		}

		updateYtdhConds := goqu.And(
			goqu.I("procytdh_id").Eq(existYtdh.procYtdhID),
			goqu.I("emp_id").Eq(l.req.EmpID),
			goqu.I("company_id").Eq(l.companyID),
		)

		err = l.vp.Core.Payment.UpdateProcYtdh(ctx, tx, updateYtdh, updateYtdhConds)
		if err != nil {
			wr.Err("failed update proc ytdh with existing ytdh and prev mtdh", log.Error(err), log.StackTrace())
		}
	case taxLocationChanged == true:
		wr.Dbg("case C: Theres existing ytdh but tax location changed, will insert new ytdh")
		// Generate new ProcYtdhID
		newProcYtdhId, _ := helperstd.SfGenLogicKey(ctx, tx, helperstd.ParamGenLogicKey{
			KeyType:     "PROCYTDHID",
			EmpId:       l.req.EmpID,
			CompanyId:   l.companyID,
			TrxDate:     l.period.Taxdate,
			PeriodCode:  l.period.PeriodCode,
			ProcessFlag: "0",
			StartPeriod: int(l.period.Taxdate.Month()),
		})
		l.procYtdhID = newProcYtdhId
	}

	if l.procYtdhID != "" {
		summationSalaryTax := existYtdh.SalaryTax + l.SalaryTax
		updateYtdh := paymententitystd.ProcYtdh{
			Salnet:       l.employeeData.Salnet,
			SalaryTax:    util.ToPointer(helperstd.SfEncryptCol(ctx, tx, summationSalaryTax, l.req.EmpID)),
			ModifiedBy:   l.userLoginData.UNAME,
			ModifiedDate: l.currentTime,
		}

		updateYtdhConds := goqu.And(
			goqu.I("procytdh_id").Eq(existYtdh.procYtdhID),
			goqu.I("emp_id").Eq(l.req.EmpID),
			goqu.I("company_id").Eq(l.companyID),
		)

		err = l.vp.Core.Payment.UpdateProcYtdh(ctx, tx, updateYtdh, updateYtdhConds)
		if err != nil {
			wr.Err("failed update proc ytdh with existing ytdh and prev mtdh", log.Error(err), log.StackTrace())
		}
	}
	wr.Dbg("processYtdh completed",
		log.Any("finalProcYtdhID", l.procYtdhID),
		log.Any("needInsertNew", needInsertNew),
		log.Any("taxLocationChanged", taxLocationChanged))

	return
}

type procYtdhLogic struct {
	procYtdhID         string
	ytdTaxLocationCode string
	ytdTaxStatus       int
	ytdNumDependent    float64
	ytdTaxType         string
	ytdCurrNetIncome   float64
	ytdSalaryTax       float64

	SalaryTax             float64
	Tax                   float64
	TaxAllow              float64
	TaxBorneByComp        float64
	TaxBorneByGov         float64
	TaxPenaltyBorneByComp float64
	TaxPenalty            float64
	TaxFix                float64
	TaxFixNet             float64
}

type prevMtdhValues struct {
	procmtdhID                      string
	procytdhID                      string
	prevSalary                      float64
	prevSalaryTax                   float64
	prevSalaryPeriod                float64
	prevTax                         float64
	prevTaxPeriod                   float64
	prevTaxAllow                    float64
	prevTaxAllowPeriod              float64
	prevTaxBorneByComp              float64
	prevTaxBorneByCompPeriod        float64
	prevTaxBorneByGov               float64
	prevTaxBorneByGovPeriod         float64
	prevTaxPenaltyBorneByComp       float64
	prevTaxPenaltyBorneByCompPeriod float64
	prevTaxPenalty                  float64
	prevTaxPenaltyPeriod            float64
}

func (l *coreCalculationLogic) getExistYtdh(ctx context.Context, tx sfquery.DBExecutor) (res procYtdhLogic, err error) {
	wr := log.FromCtx(ctx)

	wr.Dbg("getExistYtdh Called - Sub Function of Process YTDH ")

	ytdhSelectCol := []any{
		goqu.I("procytdh_id"),
		goqu.I("taxlocation_code"),
		goqu.I("taxstatus"),
		goqu.I("numdependent"),
		goqu.I("tax_type"),
		goqu.I("curr_netincome"),
	}

	var getExistYtdh paymententitystd.ProcYtdh
	if l.procYtdhID != "" {
		wr.Dbg("found existing process in same month, using that ytdh", log.Any("procytdh_id", l.procYtdhID))

		// CASE: Already processed in same month, follow that YTDH
		getExistYtdh, _ = l.vp.Core.Payment.GetProcYTDH(ctx, tx, helperstd.ParamRepository{
			Columns: ytdhSelectCol,
			Conditions: goqu.And(
				goqu.I("emp_id").Eq(l.req.EmpID),
				goqu.I("company_id").Eq(l.companyID),
				goqu.I("currentyear").Eq(l.period.Taxdate.Year()),
				goqu.I("procytdh_id").Eq(l.procYtdhID),
				goqu.Or(
					goqu.I("terminate_process").Lt(2),
					goqu.I("terminate_process").IsNull(),
				),
			),
		})

		// -- Append Proc Ytdh ID --
		if getExistYtdh.ProcytdhID != "" {
			l.procYtdhID = getExistYtdh.ProcytdhID
		}
	} else {
		wr.Dbg("no existing process in same month, get latest ytdh for the year")

		// CASE: No process in same month, get the latest YTDH for the year
		// This is the subquery logic: select max(procytdh_id)
		getExistYtdh, _ = l.vp.Core.Payment.GetProcYTDH(ctx, tx, helperstd.ParamRepository{
			Columns: ytdhSelectCol,
			Conditions: goqu.And(
				goqu.I("emp_id").Eq(l.req.EmpID),
				goqu.I("company_id").Eq(l.companyID),
				goqu.I("currentyear").Eq(l.period.Taxdate.Year()),
				goqu.Or(
					goqu.I("terminate_process").Lt(2),
					goqu.I("terminate_process").IsNull(),
				),
			),
			Order: []exp.OrderedExpression{goqu.I("procytdh_id").Desc()},
			Limit: 1,
		})

		// -- Append Proc Ytdh ID --
		if getExistYtdh.ProcytdhID != "" {
			l.procYtdhID = getExistYtdh.ProcytdhID
		}
	}

	if getExistYtdh.ProcytdhID == "" {
		wr.Inf("proc ytdh not found", log.Error(err))
		return res, nil
	}

	res.procYtdhID = getExistYtdh.ProcytdhID
	res.ytdTaxLocationCode = getExistYtdh.TaxlocationCode
	res.ytdTaxStatus = util.PointerValue(getExistYtdh.Taxstatus, 0)
	res.ytdNumDependent = util.PointerValue(getExistYtdh.Numdependent, 0.0)
	res.ytdTaxType = util.PointerValue(getExistYtdh.TaxType, "")
	res.ytdCurrNetIncome = cast.ToFloat64(util.PointerValue(getExistYtdh.CurrNetincome, ""))

	// Decrypt columns for detailed values
	salaryTaxDecrypt := helperstd.SfDecryptCol(ctx, "salary_tax", l.req.EmpID, "salary_tax")
	taxDecrypt := helperstd.SfDecryptCol(ctx, "tax", l.req.EmpID, "tax")
	taxAllowDecrypt := helperstd.SfDecryptCol(ctx, "taxallow", l.req.EmpID, "taxallow")
	taxBorneByCompDecrypt := helperstd.SfDecryptCol(ctx, "taxbornebycomp", l.req.EmpID, "taxbornebycomp")
	taxBorneByGovDecrypt := helperstd.SfDecryptCol(ctx, "taxbornebygov", l.req.EmpID, "taxbornebygov")
	taxPenaltyBorneByCompDecrypt := helperstd.SfDecryptCol(ctx, "taxpenaltybornebycomp", l.req.EmpID, "taxpenaltybornebycomp")
	taxPenaltyDecrypt := helperstd.SfDecryptCol(ctx, "taxpenalty", l.req.EmpID, "taxpenalty")
	taxFixDecypt := helperstd.SfDecryptCol(ctx, "taxfix", l.req.EmpID, "taxfix")
	taxFixNetDecypt := helperstd.SfDecryptCol(ctx, "taxfixnet", l.req.EmpID, "taxfixnet")

	selectCols := []any{
		goqu.I("procytdh_id"),
		goqu.I("startp"),
		goqu.I("salnet"),
		goqu.I("taxstatus"),
		goqu.I("tax_type"),
		goqu.I("numdependent"),
		salaryTaxDecrypt,
		taxDecrypt,
		taxAllowDecrypt,
		taxBorneByCompDecrypt,
		taxBorneByGovDecrypt,
		taxPenaltyBorneByCompDecrypt,
		taxPenaltyDecrypt,
		taxFixDecypt,
		taxFixNetDecypt,
	}

	detailProcYtdh, _ := l.vp.Core.Payment.GetProcYTDH(ctx, tx, helperstd.ParamRepository{
		Columns: selectCols,
		Conditions: goqu.And(
			goqu.I("procytdh_id").Eq(l.procYtdhID),
			goqu.I("company_id").Eq(l.companyID),
		),
	})

	if detailProcYtdh.ProcytdhID != "" {
		res.SalaryTax = cast.ToFloat64(util.PointerValue(detailProcYtdh.SalaryTax, ""))
		res.Tax = cast.ToFloat64(util.PointerValue(detailProcYtdh.Tax, ""))
		res.TaxAllow = cast.ToFloat64(util.PointerValue(detailProcYtdh.Taxallow, ""))
		res.TaxBorneByComp = cast.ToFloat64(util.PointerValue(detailProcYtdh.Taxbornebycomp, ""))
		res.TaxBorneByGov = cast.ToFloat64(util.PointerValue(detailProcYtdh.Taxbornebygov, ""))
		res.TaxPenaltyBorneByComp = cast.ToFloat64(util.PointerValue(detailProcYtdh.Taxpenaltybornebycomp, ""))
		res.TaxPenalty = cast.ToFloat64(util.PointerValue(detailProcYtdh.Taxpenalty, ""))
		res.TaxFix = cast.ToFloat64(util.PointerValue(detailProcYtdh.Taxfix, ""))
		res.TaxFixNet = cast.ToFloat64(util.PointerValue(detailProcYtdh.TaxFixNet, ""))
	}

	wr.Dbg("ytdh data retrieved successfully",
		log.Any("procYtdhID", res.procYtdhID),
		log.Any("taxLocation", res.ytdTaxLocationCode),
		log.Any("salaryTax", res.SalaryTax),
		log.Any("tax", res.Tax))

	return
}

func (l *coreCalculationLogic) getPrevMtdh(ctx context.Context, tx sfquery.DBExecutor) (res prevMtdhValues, err error) {
	wr := log.FromCtx(ctx)

	wr.Dbg("getPrevMtdh Called - Sub Function of Process YTDH ")

	// -- Mapping Decrypted Column --
	salaryTaxDecrypt := helperstd.SfDecryptCol(ctx, "salary_tax", l.req.EmpID, "salary_tax")
	taxDecrypt := helperstd.SfDecryptCol(ctx, "tax", l.req.EmpID, "tax")
	taxPeriodDecrypt := helperstd.SfDecryptCol(ctx, "tax_period", l.req.EmpID, "tax_period")
	taxAllowDecrypt := helperstd.SfDecryptCol(ctx, "taxallow", l.req.EmpID, "taxallow")
	taxAllowPeriodDecrypt := helperstd.SfDecryptCol(ctx, "taxallow_period", l.req.EmpID, "taxallow_period")
	taxBorneByCompDecrypt := helperstd.SfDecryptCol(ctx, "taxbornebycomp", l.req.EmpID, "taxbornebycomp")
	taxBorneByCompPeriodDecrypt := helperstd.SfDecryptCol(ctx, "taxbornebycomp_period", l.req.EmpID, "taxbornebycomp_period")
	taxBorneByGovDecrypt := helperstd.SfDecryptCol(ctx, "taxbornebygov", l.req.EmpID, "taxbornebygov")
	taxBorneByGovPeriodDecrypt := helperstd.SfDecryptCol(ctx, "taxbornebygov_period", l.req.EmpID, "taxbornebygov_period")
	taxPenaltyBorneByCompDecrypt := helperstd.SfDecryptCol(ctx, "taxpenaltybornebycomp", l.req.EmpID, "taxpenaltybornebycomp")
	taxPenaltyBorneByCompPeriodDecrypt := helperstd.SfDecryptCol(ctx, "taxpenaltybornebycomp_period", l.req.EmpID, "taxpenaltybornebycomp_period")
	taxPenaltyDecrypt := helperstd.SfDecryptCol(ctx, "taxpenalty", l.req.EmpID, "taxpenalty")
	taxPenaltyPeriodDecrypt := helperstd.SfDecryptCol(ctx, "taxpenalty_period", l.req.EmpID, "taxpenalty_period")

	// Get Existing Current Proc Mtdh
	existProcMtdh, _ := l.vp.Core.Payment.GetProcMTDH(ctx, tx, helperstd.ParamRepository{
		Columns: []any{
			goqu.I("procmtdh_id"),
			goqu.I("procytdh_id"),
			salaryTaxDecrypt,
			taxDecrypt,
			taxPeriodDecrypt,
			taxAllowDecrypt,
			taxAllowPeriodDecrypt,
			taxBorneByCompDecrypt,
			taxBorneByCompPeriodDecrypt,
			taxBorneByGovDecrypt,
			taxBorneByGovPeriodDecrypt,
			taxPenaltyBorneByCompDecrypt,
			taxPenaltyBorneByCompPeriodDecrypt,
			taxPenaltyDecrypt,
			taxPenaltyPeriodDecrypt,
		},
		Conditions: goqu.And(
			goqu.I("emp_id").Eq(l.req.EmpID),
			goqu.I("company_id").Eq(l.companyID),
			goqu.I("period_code").Eq(l.req.PeriodCode),
			goqu.L("MONTH(?)", goqu.C("paydate")).Eq(l.period.Paydate.Month()),
			goqu.L("YEAR(?)", goqu.C("paydate")).Eq(l.period.Paydate.Year()),
			goqu.Or(
				goqu.I("terminate_process").Lt(2),
				goqu.I("terminate_process").IsNull(),
			),
		),
	})
	wr.Dbg("existing proc mtdh - on prev", log.Response(existProcMtdh))

	if existProcMtdh.ProcmtdhID == "" {
		// -- Theres no existing Proc Mtdh --
		l.procMtdhID = ""
		return prevMtdhValues{}, nil
	}

	prevData := prevMtdhValues{
		procmtdhID:                      existProcMtdh.ProcmtdhID,
		procytdhID:                      util.PointerValue(existProcMtdh.ProcytdhID, ""),
		prevSalaryTax:                   cast.ToFloat64(util.PointerValue(existProcMtdh.SalaryTax, "")),
		prevTax:                         cast.ToFloat64(util.PointerValue(existProcMtdh.Tax, "")),
		prevTaxPeriod:                   cast.ToFloat64(util.PointerValue(existProcMtdh.TaxPeriod, "")),
		prevTaxAllow:                    cast.ToFloat64(util.PointerValue(existProcMtdh.Taxallow, "")),
		prevTaxAllowPeriod:              cast.ToFloat64(util.PointerValue(existProcMtdh.TaxallowPeriod, "")),
		prevTaxBorneByComp:              cast.ToFloat64(util.PointerValue(existProcMtdh.Taxbornebycomp, "")),
		prevTaxBorneByCompPeriod:        cast.ToFloat64(util.PointerValue(existProcMtdh.TaxbornebycompPeriod, "")),
		prevTaxBorneByGov:               cast.ToFloat64(util.PointerValue(existProcMtdh.Taxbornebygov, "")),
		prevTaxBorneByGovPeriod:         cast.ToFloat64(util.PointerValue(existProcMtdh.TaxbornebygovPeriod, "")),
		prevTaxPenaltyBorneByComp:       cast.ToFloat64(util.PointerValue(existProcMtdh.Taxpenaltybornebycomp, "")),
		prevTaxPenaltyBorneByCompPeriod: cast.ToFloat64(util.PointerValue(existProcMtdh.TaxpenaltybornebycompPeriod, "")),
		prevTaxPenalty:                  cast.ToFloat64(util.PointerValue(existProcMtdh.Taxpenalty, "")),
		prevTaxPenaltyPeriod:            cast.ToFloat64(util.PointerValue(existProcMtdh.TaxpenaltyPeriod, "")),
	}

	// -- Append Current Proc Mtdh ID --
	l.procMtdhID = existProcMtdh.ProcmtdhID

	wr.Dbg("found previous mtdh data",
		log.Any("procmtdh_id", prevData.procmtdhID),
		log.Any("prevTax", prevData.prevTax),
		log.Any("prev data result", prevData),
	)

	return prevData, nil
}

func (l *coreCalculationLogic) processMtdh(ctx context.Context, tx sfquery.DBExecutor) (err error) {
	wr := log.FromCtx(ctx)

	wr.Dbg("Process Mtdh Function Called")
	wr.Dbg("debug struct logic on processMtdh", log.Response(l))

	taxDate, _ := util.ParseDatePointer(l.period.Taxdate.Format(time.DateTime), time.DateTime)
	payDate, _ := util.ParseDatePointer(l.period.Paydate.Format(time.DateTime), time.DateTime)
	zeroValueEncrypt := helperstd.SfEncryptCol(ctx, tx, 0, l.req.EmpID)
	if l.procMtdhID == "" {
		newProcMtdhId, _ := helperstd.SfGenLogicKey(ctx, tx, helperstd.ParamGenLogicKey{
			KeyType:     "PROCMTDHID",
			EmpId:       l.req.EmpID,
			CompanyId:   l.companyID,
			TrxDate:     l.period.Paydate,
			PeriodCode:  l.period.PeriodCode,
			ProcessFlag: "",
			StartPeriod: 0,
		})

		newMtdh := paymententitystd.ProcMtdh{
			ProcmtdhID:               newProcMtdhId,
			ProcytdhID:               util.ToPointer(l.procYtdhID),
			PositionID:               util.ToPointer(l.employeeData.PositionID),
			Salary:                   util.ToPointer(helperstd.SfEncryptCol(ctx, tx, l.Salary, l.req.EmpID)),
			SalaryTax:                util.ToPointer(helperstd.SfEncryptCol(ctx, tx, l.SalaryTax, l.req.EmpID)),
			SalaryPeriod:             util.ToPointer(helperstd.SfEncryptCol(ctx, tx, l.SalaryPeriod, l.req.EmpID)),
			TaxbornebycompDec:        util.ToPointer(cast.ToString(l.employeeData.TaxbornebycompDec)),
			TaxpenaltybornebycompDec: util.ToPointer(cast.ToString(l.employeeData.TaxpenaltybornebycompDec)),
			Taxfilenumber:            l.employeeData.Taxfilenumber,
			CurrencyCode:             util.ToPointer(l.employeeData.CurrencyCode),
			CurrencyCodeTax:          util.ToPointer("THB"),
			CurrencyCodePeriod:       l.period.CurrencyCode,
			Salused:                  util.ToPointer(l.period.Usesalary),
			PeriodCode:               l.req.PeriodCode,
			EmpID:                    l.req.EmpID,
			Taxdate:                  taxDate,
			Paydate:                  payDate,
			TaxType:                  l.employeeData.TaxType,
			Taxstatus:                util.ToPointer(l.employeeData.Taxstatus),
			Numdependent:             util.ToPointer(cast.ToInt(l.employeeData.Numdependent)),
			CompanyID:                l.companyID,
			CostcenterCode:           util.ToPointer(l.employeeData.CostCode),
			FirstName:                util.ToPointer(l.employeeData.FirstName),
			MiddleName:               l.employeeData.MIDdleName,
			LastName:                 l.employeeData.LastName,
			WorklocationCode:         l.employeeData.WorkLocationCode,
			TaxlocationCode:          l.employeeData.TaxlocationCode,
			GradeCode:                util.ToPointer(l.employeeData.GradeCode),
			EmploymentstatusCode:     util.ToPointer(l.employeeData.EmployCode),
			Jobstatuscode:            l.employeeData.JobStatusCode,
			RateTax:                  util.ToPointer(l.RateTax),
			RatePeriod:               util.ToPointer(l.RatePeriod),
			Taxed:                    util.ToPointer(l.employeeData.Taxed),
			Tax:                      util.ToPointer(zeroValueEncrypt),
			Taxallow:                 util.ToPointer(zeroValueEncrypt),
			Taxbornebygov:            util.ToPointer(zeroValueEncrypt),
			Taxfix:                   util.ToPointer(zeroValueEncrypt),
			CreatedBy:                l.userLoginData.UNAME,
			ModifiedBy:               util.ToPointer(l.userLoginData.UNAME),
			CreatedDate:              l.currentTime,
			ModifiedDate:             util.ToPointer(l.currentTime),
		}

		err = l.vp.Core.Payment.InsertProcMtdh(ctx, tx, []paymententitystd.ProcMtdh{newMtdh})
		if err != nil {
			wr.Err("failed to insert new mtdh", log.Error(err), log.StackTrace())
			return nil
		}

		// -- Append Current Mtdh ID --
		l.procMtdhID = newProcMtdhId
	} else {
		updateMtdh := paymententitystd.ProcMtdh{
			ProcytdhID:               util.ToPointer(l.procYtdhID),
			EmpID:                    l.req.EmpID,
			PositionID:               util.ToPointer(l.employeeData.PositionID),
			CostcenterCode:           util.ToPointer(l.employeeData.CostCode),
			Salary:                   util.ToPointer(helperstd.SfEncryptCol(ctx, tx, l.Salary, l.req.EmpID)),
			SalaryTax:                util.ToPointer(helperstd.SfEncryptCol(ctx, tx, l.SalaryTax, l.req.EmpID)),
			SalaryPeriod:             util.ToPointer(helperstd.SfEncryptCol(ctx, tx, l.SalaryPeriod, l.req.EmpID)),
			TaxbornebycompDec:        util.ToPointer(cast.ToString(l.employeeData.TaxbornebycompDec)),
			TaxpenaltybornebycompDec: util.ToPointer(cast.ToString(l.employeeData.TaxpenaltybornebycompDec)),
			CurrencyCode:             util.ToPointer(l.employeeData.CurrencyCode),
			CurrencyCodeTax:          util.ToPointer("THB"),
			CurrencyCodePeriod:       l.period.CurrencyCode,
			Salused:                  util.ToPointer(l.period.Usesalary),
			Taxdate:                  taxDate,
			Paydate:                  payDate,
			TaxType:                  l.employeeData.TaxType,
			Taxfilenumber:            l.employeeData.Taxfilenumber,
			Taxstatus:                util.ToPointer(l.employeeData.Taxstatus),
			Numdependent:             util.ToPointer(cast.ToInt(l.employeeData.Numdependent)),
			FirstName:                util.ToPointer(l.employeeData.FirstName),
			MiddleName:               l.employeeData.MIDdleName,
			LastName:                 l.employeeData.LastName,
			WorklocationCode:         l.employeeData.WorkLocationCode,
			TaxlocationCode:          l.employeeData.TaxlocationCode,
			GradeCode:                util.ToPointer(l.employeeData.GradeCode),
			EmploymentstatusCode:     util.ToPointer(l.employeeData.EmployCode),
			Jobstatuscode:            l.employeeData.JobStatusCode,
			RateTax:                  util.ToPointer(l.RateTax),
			RatePeriod:               util.ToPointer(l.RatePeriod),
			Taxed:                    util.ToPointer(l.employeeData.Taxed),
			Tax:                      util.ToPointer(zeroValueEncrypt),
			Taxallow:                 util.ToPointer(zeroValueEncrypt),
			Taxbornebygov:            util.ToPointer(zeroValueEncrypt),
			Taxfix:                   util.ToPointer(zeroValueEncrypt),
			Taxfixnet:                util.ToPointer(zeroValueEncrypt),
			ModifiedBy:               util.ToPointer(l.userLoginData.UNAME),
			ModifiedDate:             util.ToPointer(l.currentTime),
		}

		updateMtdhConds := goqu.And(
			goqu.I("procmtdh_id").Eq(l.procMtdhID),
			goqu.I("company_id").Eq(l.companyID),
			goqu.I("emp_id").Eq(l.req.EmpID),
		)

		err = l.vp.Core.Payment.UpdateProcMtdh(ctx, tx, updateMtdh, updateMtdhConds)
		if err != nil {
			wr.Err("failed to update proc mtdh", log.Error(err), log.StackTrace())
			return nil
		}

		//
		// Update Proc Ytdh (Recalculate)
		//
		updateYtdh := paymententitystd.ProcYtdh{
			Salnet:                   l.employeeData.Salnet,
			Taxed:                    util.ToPointer(l.employeeData.Taxed),
			TaxbornebycompDec:        util.ToPointer(l.employeeData.TaxbornebycompDec),
			TaxpenaltybornebycompDec: util.ToPointer(l.employeeData.TaxpenaltybornebycompDec),
			Taxfilenumber:            l.employeeData.Taxfilenumber,
			SalaryTax:                util.ToPointer(helperstd.SfEncryptCol(ctx, tx, l.SalaryTax, l.req.EmpID)),
			ModifiedBy:               l.userLoginData.UNAME,
			ModifiedDate:             l.currentTime,
		}
		wr.Dbg("update proc ytdh after prorcess mtdh",
			log.Any("struct update", updateYtdh),
			log.Any("salary tax", l.SalaryTax),
		)

		updateYtdhConds := goqu.And(
			goqu.I("procytdh_id").Eq(l.procYtdhID),
			goqu.I("emp_id").Eq(l.req.EmpID),
			goqu.I("company_id").Eq(l.companyID),
		)

		err = l.vp.Core.Payment.UpdateProcYtdh(ctx, tx, updateYtdh, updateYtdhConds)
		if err != nil {
			wr.Err("failed update proc ytdh after prorcess mtdh", log.Error(err), log.StackTrace())
		}
	}

	return
}

func (l *coreCalculationLogic) calculateSalaryTax(ctx context.Context, tx sfquery.DBExecutor) (err error) {
	wr := log.FromCtx(ctx)

	wr.Dbg("calculateSalaryTax Called - Sub Function of Process YTDH")

	// -- Return Default Value If Not Usesalary --
	l.Salary = 0
	l.SalaryTax = 0
	l.SalaryPeriod = 0
	l.RateTax = 1
	l.RatePeriod = 1
	if l.period.Usesalary != "Y" {
		return nil
	}

	// -- Get New Salary --
	newSalaryDecrypt := helperstd.SfDecryptCol(ctx, "new_salary", l.req.EmpID, "new_salary")
	salaryParamTemp, _ := l.vp.Core.SalaryParam.GetEmpSalaryParamTemp(ctx, tx, helperstd.ParamRepository{
		Columns: []any{
			newSalaryDecrypt,
		},
		Conditions: goqu.And(
			goqu.I("emp_id").Eq(l.req.EmpID),
			goqu.I("company_id").Eq(l.companyID),
			goqu.I("period").Eq(l.req.PeriodCode),
		),
	})

	salaryTemp := cast.ToFloat64(salaryParamTemp.NewSalary)
	l.Salary = salaryTemp
	l.SalaryTax = salaryTemp
	l.SalaryPeriod = salaryTemp
	if l.employeeData.CurrencyCode != "THB" {
		l.Salary, _ = helperstd.SfCurrencyRound(ctx, tx, salaryTemp, "THB")
		convertSalaryTax, _ := helperstd.SfConvertCurrency(ctx, tx, l.Salary, l.employeeData.CurrencyCode, "THB", l.period.Taxdate, "TAX")
		l.SalaryTax, _ = helperstd.SfCurrencyRound(ctx, tx, convertSalaryTax.ConvertResult, "THB")
		l.RateTax = convertSalaryTax.ConvertRate
	}

	if l.employeeData.CurrencyCode != util.PointerValue(l.period.CurrencyCode, "") {
		convertSalaryPeriod, _ := helperstd.SfConvertCurrency(ctx, tx, salaryTemp, l.employeeData.CurrencyCode, *l.period.CurrencyCode, l.period.Paydate, "TRANSFER")
		l.SalaryPeriod, _ = helperstd.SfCurrencyRound(ctx, tx, convertSalaryPeriod.ConvertResult, "THB")
		l.RatePeriod = convertSalaryPeriod.ConvertRate
	}

	wr.Dbg("Salary Param Temp - Final",
		log.Any("final salary", l.Salary),
		log.Any("final salary tax", l.Salary),
		log.Any("final salary period", l.Salary),
	)
	return
}

func (l *coreCalculationLogic) processMtdcc(ctx context.Context, tx sfquery.DBExecutor) (err error) {
	wr := log.FromCtx(ctx)

	procMtdh, _ := l.vp.Core.Payment.GetProcMTDH(ctx, tx, helperstd.ParamRepository{
		Columns: []any{
			goqu.I("procmtdh_id"),
		},
		Conditions: goqu.And(
			goqu.I("emp_id").Eq(l.req.EmpID),
			goqu.I("company_id").Eq(l.companyID),
			goqu.I("procmtdh_id").Eq(l.procMtdhID),
		),
	})

	//
	// Delete Existing Proc Mtdcc
	//
	if procMtdh.ProcmtdhID != "" {
		deleteConds := goqu.And(
			goqu.I("procmtdh_id").Eq(procMtdh.ProcmtdhID),
			goqu.I("company_id").Eq(l.companyID),
		)

		err = l.vp.Core.Payment.DeleteProcMtdcc(ctx, tx, deleteConds)
		if err != nil {
			wr.Err(consts.LogMessageDBFailedToDelete, log.Error(err), log.StackTrace())
			return err
		}
	}

	empCostCenters, totalEmpCostCenter, _ := l.vp.Core.CostCenter.GetEmpCcAllocations(ctx, tx, helperstd.ParamRepository{
		Columns: []any{
			goqu.I("effective_date"),
			goqu.I("emp_id"),
			goqu.I("company_id"),
		},
		Conditions: goqu.And(
			goqu.I("emp_id").Eq(l.req.EmpID),
			goqu.I("company_id").Eq(l.companyID),
			goqu.I("effective_date").Lte(l.period.Paydate),
		),
		Order: []exp.OrderedExpression{
			goqu.I("effective_date").Desc(),
		},
	})

	costCenterPctToInserted := []paymententitystd.ProcMtdcc{}
	if totalEmpCostCenter > 0 {
		firstEffDate := empCostCenters[0].EffectiveDate
		empCostCenters, totalEmpCostCenter, _ := l.vp.Core.CostCenter.GetEmpCcAllocations(ctx, tx, helperstd.ParamRepository{
			Columns: []any{
				goqu.I("costcenter_code"),
				goqu.I("percentage"),
				goqu.I("emp_id"),
				goqu.I("company_id"),
				goqu.I("effective_date"),
			},
			Conditions: goqu.And(
				goqu.I("emp_id").Eq(l.req.EmpID),
				goqu.I("company_id").Eq(l.companyID),
				goqu.I("effective_date").Eq(firstEffDate),
			),
		})

		if totalEmpCostCenter > 0 {
			for _, v := range empCostCenters {
				costCenterPctToInserted = append(costCenterPctToInserted, paymententitystd.ProcMtdcc{
					ProcmtdhID:     procMtdh.ProcmtdhID,
					CompanyID:      l.companyID,
					CostcenterCode: v.CostcenterCode,
					Percentage:     cast.ToInt(v.Percentage),
					CreatedBy:      l.userLoginData.UNAME,
					CreatedDate:    l.currentTime,
					ModifiedBy:     l.userLoginData.UNAME,
					ModifiedDate:   l.currentTime,
				})
			}
		}
	} else {
		empCompanies, totalEmpCompany, _ := l.vp.Core.Employee.GetEmployeeCompanies(ctx, tx, helperstd.ParamRepository{
			Columns: []any{
				goqu.I("cost_code"),
				goqu.I("emp_id"),
				goqu.I("company_id"),
			},
			Conditions: goqu.And(
				goqu.I("emp_id").Eq(l.req.EmpID),
				goqu.I("company_id").Eq(l.companyID),
			),
		})

		if totalEmpCompany > 0 {
			for _, v := range empCompanies {
				costCenterPctToInserted = append(costCenterPctToInserted, paymententitystd.ProcMtdcc{
					ProcmtdhID:     procMtdh.ProcmtdhID,
					CompanyID:      l.companyID,
					CostcenterCode: v.CostCode,
					Percentage:     100,
					CreatedBy:      l.userLoginData.UNAME,
					CreatedDate:    l.currentTime,
					ModifiedBy:     l.userLoginData.UNAME,
					ModifiedDate:   l.currentTime,
				})
			}
		}
	}

	//
	// Insert Proc Mtdcc
	//
	if len(costCenterPctToInserted) > 0 {
		err = l.vp.Core.Payment.InsertProcMtdcc(ctx, tx, costCenterPctToInserted)
		if err != nil {
			wr.Err(consts.LogMessageDBFailedToInsert, log.Error(err), log.StackTrace())
			return err
		}
	}

	return
}

func (l *coreCalculationLogic) processMtdhInsurance(ctx context.Context, tx sfquery.DBExecutor) (err error) {
	wr := log.FromCtx(ctx)

	procMtdh, _ := l.vp.Core.Payment.GetProcMTDH(ctx, tx, helperstd.ParamRepository{
		Columns: []any{
			goqu.I("procmtdh_id"),
		},
		Conditions: goqu.And(
			goqu.I("emp_id").Eq(l.req.EmpID),
			goqu.I("company_id").Eq(l.companyID),
			goqu.I("procmtdh_id").Eq(l.procMtdhID),
		),
	})

	//
	// Delete Existing Proc Mtdh Insurance
	//
	if procMtdh.ProcmtdhID != "" {
		deleteConds := goqu.And(
			goqu.I("procmtdh_id").Eq(procMtdh.ProcmtdhID),
			goqu.I("company_id").Eq(l.companyID),
		)
		err = l.vp.Core.Payment.DeleteProcMtdhInsurance(ctx, tx, deleteConds)
		if err != nil {
			wr.Err(consts.LogMessageDBFailedToDelete, log.Error(err), log.StackTrace())
			return err
		}
	}

	salaryInsurances, totalSalaryInsurance, _ := l.vp.Core.SalaryParam.GetEmpSalaryInsurance(ctx, tx, helperstd.ParamRepository{
		Columns: []any{
			goqu.I("insurance_code"),
			goqu.I("insurance_no"),
			goqu.I("emp_id"),
			goqu.I("company_id"),
		},
		Conditions: goqu.And(
			goqu.I("emp_id").Eq(l.req.EmpID),
			goqu.I("company_id").Eq(l.companyID),
		),
	})

	if totalSalaryInsurance == 0 {
		return nil
	}

	empSalaryInsuranceToInserted := []paymententitystd.ProcMtdhInsurance{}
	for _, v := range salaryInsurances {
		newProcMtdhInsurance := paymententitystd.ProcMtdhInsurance{
			ProcmtdhID:    procMtdh.ProcmtdhID,
			CompanyID:     l.companyID,
			InsuranceCode: v.InsuranceCode,
			InsuranceNo:   util.PointerValue(v.InsuranceNo, ""),
		}

		empSalaryInsuranceToInserted = append(empSalaryInsuranceToInserted, newProcMtdhInsurance)
	}

	err = l.vp.Core.Payment.InsertProcMtdhInsurance(ctx, tx, empSalaryInsuranceToInserted)
	if err != nil {
		wr.Err(consts.LogMessageDBFailedToInsert, log.Error(err), log.StackTrace())
		return err
	}

	return
}

func (l *coreCalculationLogic) processMtddYtdd(ctx context.Context, tx sfquery.DBExecutor) (err error) {
	wr := log.FromCtx(ctx)

	wr.Dbg("Process Mtdd & Ytdd Caled")

	procMtdh, _ := l.vp.Core.Payment.GetProcMTDH(ctx, tx, helperstd.ParamRepository{
		Columns: []any{
			goqu.I("procmtdh_id"),
			goqu.I("procytdh_id"),
			goqu.I("period_code"),
			goqu.I("currency_code"),
			goqu.I("currency_code_tax"),
			goqu.I("currency_code_period"),
			goqu.I("paydate"),
			goqu.I("taxdate"),
		},
		Conditions: goqu.And(
			goqu.I("emp_id").Eq(l.req.EmpID),
			goqu.I("company_id").Eq(l.companyID),
			goqu.I("procmtdh_id").Eq(l.procMtdhID),
		),
	})
	wr.Dbg("procmtdh in mtdd", log.Response(procMtdh))

	if procMtdh.ProcmtdhID == "" {
		return nil
	}

	compValueTaxDecCol := helperstd.SfDecryptCol(ctx, "comp_value_tax", l.req.EmpID, "comp_value_tax")
	procMtdd, totalProcMtdd, _ := l.vp.Core.Payment.GetProcMTDDs(ctx, tx, helperstd.ParamRepository{
		Columns: []any{
			goqu.I("allowdeduct_code"),
			goqu.I("procmtdh_id"),
			goqu.I("company_id"),
			compValueTaxDecCol,
		},
		Conditions: goqu.And(
			goqu.I("procmtdh_id").Eq(l.procMtdhID),
			goqu.I("company_id").Eq(l.companyID),
		),
	})

	wr.Dbg("existing proc mtdd", log.Response(procMtdd))
	type debugStruct struct {
		ValueMtdd               float64
		ValueYtddExist          float64
		ValueYtddAFterSubstract float64
	}
	var arrDebug []debugStruct
	if totalProcMtdd > 0 {
		// 1. Update YTDD by substraction existing comp value tax with current comp value tax
		// 2. Delete Current Proc MTDD to be re-process
		compsToBeDeleted := []string{}
		for _, v := range procMtdd {
			// -- Append Component MTDD to be deleted --
			compsToBeDeleted = append(compsToBeDeleted, v.AllowdeductCode)

			//
			// Get Existing YTDD
			//
			compValueTaxYtddCol := helperstd.SfDecryptCol(ctx, "comp_value_tax", l.req.EmpID, "comp_value_tax")
			procYtdd, _ := l.vp.Core.Payment.GetProcYTDD(ctx, tx, helperstd.ParamRepository{
				Columns: []any{
					compValueTaxYtddCol,
					goqu.I("procytdh_id"),
					goqu.I("allowdeduct_code"),
					goqu.I("company_id"),
				},
				Conditions: goqu.And(
					goqu.I("procytdh_id").Eq(procMtdh.ProcytdhID),
					goqu.I("allowdeduct_code").Eq(v.AllowdeductCode),
					goqu.I("company_id").Eq(l.companyID),
				),
			})

			if procYtdd.ProcytdhID == "" {
				continue
			}

			//
			// Substract Existing YTDD With Current MTDD comp value tax to be re-processed
			//
			compValueTaxYtdd := cast.ToFloat64(procYtdd.CompValueTax)
			compValueTaxMtdd := cast.ToFloat64(v.CompValueTax)
			sbtrCompValueTaxYtdd := compValueTaxYtdd - compValueTaxMtdd
			sbtrCompValueTaxYtddEnc := helperstd.SfEncryptCol(ctx, tx, sbtrCompValueTaxYtdd, l.req.EmpID)

			arrDebug = append(arrDebug, debugStruct{
				ValueMtdd:               compValueTaxMtdd,
				ValueYtddExist:          compValueTaxYtdd,
				ValueYtddAFterSubstract: sbtrCompValueTaxYtdd,
			})

			//
			// Update Proc YTDD
			//
			updateProcYtdd := entity.ProcYtdd{
				CompValueTax: util.ToPointer(sbtrCompValueTaxYtddEnc),
			}
			updateProcYtddConds := goqu.And(
				goqu.I("procytdh_id").Eq(procYtdd.ProcytdhID),
				goqu.I("allowdeduct_code").Eq(v.AllowdeductCode),
				goqu.I("company_id").Eq(l.companyID),
			)

			err = l.vp.Core.Payment.UpdateProcYtdd(ctx, tx, updateProcYtdd, updateProcYtddConds)
			if err != nil {
				wr.Err(consts.LogMessageDBFailedToUpdate, log.Error(err), log.StackTrace())
				return err
			}
		}
		wr.Dbg("compsToBeDeleted in mtdd", log.Response(compsToBeDeleted))

		//
		// Delete Existing Proc MTDD
		//
		if len(compsToBeDeleted) > 0 {
			wr.Dbg("component to be deleted", log.Response(compsToBeDeleted))
			delExistMtddConds := goqu.And(
				goqu.I("procmtdh_id").Eq(l.procMtdhID),
				goqu.I("allowdeduct_code").In(compsToBeDeleted),
				goqu.I("company_id").Eq(l.companyID),
			)

			err = l.vp.Core.Payment.DeleteProcMtdd(ctx, tx, delExistMtddConds)
			if err != nil {
				wr.Err(consts.LogMessageDBFailedToDelete, log.Error(err), log.StackTrace())
				return err
			}
		}
	}
	wr.Dbg("arrDebug", log.Response(arrDebug))

	// Process MTDD
	// 1. Get Payroll Component From TPYDEMPALLOWDUDCT JOIN TPYMPAYALLOWDEDUCT
	// 2. If Exist Process Comp Value, Comp Value Tax & Comp Value Period Per Component
	// 3. Insert to ProcMTDD, which has been deleted previously
	// 4. Insert to ProcYTDD if not exist / Update If Exist with summation comp value tax existing & current
	// process
	allowDeductValueCol := helperstd.SfDecryptCol(ctx, "allowdeduct_value", l.req.EmpID, "allowdeduct_value")
	formulaResultCol := helperstd.SfDecryptCol(ctx, "formula_result", l.req.EmpID, "formula_result")
	payComps, totalPayComp, _ := l.vp.Core.Component.GetEmpPayAllowDeduct(ctx, tx, helperstd.ParamRepository{
		Columns: []any{
			allowDeductValueCol,
			formulaResultCol,
			goqu.T(componentstdconst.TableNameTPYDEmpAllowDeduct).Col("emp_id"),
			goqu.T(componentstdconst.TableNameTPYDEmpAllowDeduct).Col("period_code"),
			goqu.T(componentstdconst.TableNameTPYDEmpAllowDeduct).Col("company_id"),
			goqu.T(componentstdconst.TableNameTPYDEmpAllowDeduct).Col("allowdeduct_code"),
			goqu.T(componentstdconst.TableNameTPYDEmpAllowDeduct).Col("currency_code"),
			goqu.T(componentstdconst.TableNameTPYDEmpAllowDeduct).Col("taxed"),
			goqu.T(componentstdconst.TableNameTPYDEmpAllowDeduct).Col("net"),
			goqu.T(componentstdconst.TableNameTPYDEmpAllowDeduct).Col("taxclass"),
			goqu.T(componentstdconst.TableNameTPYDEmpAllowDeduct).Col("allowdeduct_formula"),
			goqu.T(componentstdconst.TableNameTPYDEmpAllowDeduct).Col("fixed"),
			goqu.T(componentstdconst.TableNameTPYMPayAllowDeduct).Col("enabled"),
			goqu.T(componentstdconst.TableNameTPYMPayAllowDeduct).Col("allowdeducttype"),
			goqu.T(componentstdconst.TableNameTPYMPayAllowDeduct).Col("allowdeductname_en"),
			goqu.T(componentstdconst.TableNameTPYMPayAllowDeduct).Col("allowdeductname_id"),
			goqu.T(componentstdconst.TableNameTPYMPayAllowDeduct).Col("allowdeductname_my"),
			goqu.T(componentstdconst.TableNameTPYMPayAllowDeduct).Col("allowdeductname_th"),
		},
		Conditions: goqu.And(
			goqu.C("emp_id").Eq(l.req.EmpID),
			goqu.C("period_code").Eq(l.req.PeriodCode),
			goqu.C("company_id").Eq(l.companyID),
			goqu.C("enabled").Eq("Y"),
		),
	})
	wr.Dbg("payroll component", log.Response(payComps))

	if totalPayComp == 0 {
		msg := fmt.Sprintf("no component found for employee %s", l.req.EmpID)
		wr.Inf(msg, log.Any("payroll component", payComps))
		return nil
	}

	var takeHomePayTax, takeHomePayPeriod float64
	mtddToBeInserteds := []entity.ProcMtdd{}
	ytddToBeInserteds := []entity.ProcYtdd{}
	currencyCodeTax := util.PointerValue(procMtdh.CurrencyCodeTax, "")
	currencyPeriod := util.PointerValue(l.period.CurrencyCode, "")

	type DebugProcssMtddYtdd struct {
		AllowdeductCode          string
		CompValueTaxMtdd         float64
		CompValueTaxYtddExisting float64
		SummationForUpdateYtdd   float64
	}
	var arrDebugProcssMtddYtdd []DebugProcssMtddYtdd
	for _, comp := range payComps {
		formulaResult := cast.ToFloat64(comp.FormulaResult)
		currencyComp := util.PointerValue(comp.CurrencyCode, "")
		compValue, _ := helperstd.SfCurrencyRound(ctx, tx, formulaResult, currencyComp)

		// If component value is 0, skip
		if compValue == 0 {
			continue
		}

		//
		// Handle If Currency Code Component Is Different With Currency Code Tax
		//
		var compValueTax float64
		compValueTax = compValue
		if currencyComp != currencyCodeTax {
			// Convert Result Based On Type
			convertResult, err := helperstd.SfConvertCurrency(ctx, tx, formulaResult, currencyComp, currencyCodeTax, *procMtdh.Taxdate, "TAX")
			if err != nil {
				wr.Err("failed to convert currency on value tax", log.Error(err), log.StackTrace())
				return nil
			}
			wr.Dbg("masuk kondisi convert currency", log.Response(convertResult))

			compValueTax = convertResult.ConvertResult
			// rateTax = convertResult.ConvertRate

			// Rounding Comp Value Tax
			compValueTax, err = helperstd.SfCurrencyRound(ctx, tx, compValueTax, currencyCodeTax)
			if err != nil {
				wr.Err("failed to round comp value tax", log.Error(err), log.StackTrace())
				return nil
			}
		}

		//
		// Handle If Currency Code Component Is Differenct With Period Currency Code
		//
		var compValuePeriod float64
		compValuePeriod = compValue
		if currencyComp != currencyPeriod {
			// Convert Result Based On Type
			convertResult, err := helperstd.SfConvertCurrency(ctx, tx, formulaResult, util.PointerValue(comp.CurrencyCode, ""), util.PointerValue(procMtdh.CurrencyCodeTax, ""), *procMtdh.Paydate, "TRANSFER")
			if err != nil {
				wr.Err("failed to convert currency on value period", log.Error(err), log.StackTrace())
				return nil
			}

			compValuePeriod = convertResult.ConvertResult
			// rateTaxPeriod = convertResult.ConvertRate

			// Rounding Comp Value Tax
			compValuePeriod, err = helperstd.SfCurrencyRound(ctx, tx, compValuePeriod, util.PointerValue(procMtdh.CurrencyCodeTax, ""))
			if err != nil {
				wr.Err("failed to round comp value period", log.Error(err), log.StackTrace())
				return nil
			}
		}

		//
		// Append MTDD To Be Inserted
		//
		compValueEnc := helperstd.SfEncryptCol(ctx, tx, compValue, l.req.EmpID)
		compValueTaxEnc := helperstd.SfEncryptCol(ctx, tx, compValueTax, l.req.EmpID)
		compValuePeriodEnc := helperstd.SfEncryptCol(ctx, tx, compValuePeriod, l.req.EmpID)
		newMtdd := entity.ProcMtdd{
			ProcmtdhID:         l.procMtdhID,
			CompanyID:          l.companyID,
			AllowdeductCode:    comp.AllowdeductCode,
			Allowdeducttype:    util.ToPointer(comp.AllowDeductType),
			AllowdeductnameEn:  util.ToPointer(comp.AllowDeductNameEN),
			AllowdeductnameId:  comp.AllowDeductNameID,
			AllowdeductnameMy:  comp.AllowDeductNameMY,
			AllowdeductnameTh:  comp.AllowDeductNameTH,
			CurrencyCode:       util.PointerValue(comp.CurrencyCode, ""),
			CurrencyCodeTax:    util.PointerValue(procMtdh.CurrencyCodeTax, ""),
			CurrencyCodePeriod: util.PointerValue(l.period.CurrencyCode, ""),
			CompValue:          util.ToPointer(compValueEnc),
			CompValueTax:       util.ToPointer(compValueTaxEnc),
			CompValuePeriod:    util.ToPointer(compValuePeriodEnc),
			AllowdeductValue:   util.ToPointer(comp.AllowdeductValue),
			AllowdeductFormula: util.ToPointer(comp.AllowdeductFormula),
			Taxed:              util.ToPointer(comp.Taxed),
			Net:                util.ToPointer(comp.Net),
			Taxclass:           comp.Taxclass,
			Fixed:              comp.Fixed,
		}

		if newMtdd != (entity.ProcMtdd{}) {
			mtddToBeInserteds = append(mtddToBeInserteds, newMtdd)
		}

		//
		// Check Existing YTDD
		//
		compValueTaxYtddCol := helperstd.SfDecryptCol(ctx, "comp_value_tax", l.req.EmpID, "comp_value_tax")
		getYtdd, _ := l.vp.Core.Payment.GetProcYTDD(ctx, tx, helperstd.ParamRepository{
			Columns: []any{
				compValueTaxYtddCol,
				goqu.I("procytdh_id"),
				goqu.I("allowdeduct_code"),
				goqu.I("company_id"),
			},
			Conditions: goqu.And(
				goqu.I("procytdh_id").Eq(procMtdh.ProcytdhID),
				goqu.I("allowdeduct_code").Eq(comp.AllowdeductCode),
				goqu.I("company_id").Eq(l.companyID),
			),
		})

		//
		// Insert / Update YTDD
		//
		if getYtdd.ProcytdhID != "" {
			compValueTaxFloat := cast.ToFloat64(getYtdd.CompValueTax)
			sumCompValueTax := compValueTaxFloat + compValueTax
			compValueTaxYtddEnc := helperstd.SfEncryptCol(ctx, tx, sumCompValueTax, l.req.EmpID)

			arrDebugProcssMtddYtdd = append(arrDebugProcssMtddYtdd, DebugProcssMtddYtdd{
				AllowdeductCode:          comp.AllowdeductCode,
				CompValueTaxMtdd:         compValueTax,
				CompValueTaxYtddExisting: compValueTaxFloat,
				SummationForUpdateYtdd:   sumCompValueTax,
			})
			updateYtdd := entity.ProcYtdd{
				CompValueTax:      util.ToPointer(compValueTaxYtddEnc),
				Taxed:             comp.Taxed,
				Net:               comp.Net,
				Taxclass:          comp.Taxclass,
				CurrencyCodeTax:   util.PointerValue(procMtdh.CurrencyCodeTax, ""),
				AllowdeductnameEn: util.ToPointer(comp.AllowDeductNameEN),
				AllowdeductnameId: comp.AllowDeductNameID,
				AllowdeductnameMy: comp.AllowDeductNameMY,
				AllowdeductnameTh: comp.AllowDeductNameTH,
				Allowdeducttype:   util.ToPointer(comp.AllowDeductType),
				Fixed:             comp.Fixed,
			}
			updateYtddConds := goqu.And(
				goqu.I("procytdh_id").Eq(procMtdh.ProcytdhID),
				goqu.I("allowdeduct_code").Eq(comp.AllowdeductCode),
				goqu.I("company_id").Eq(l.companyID),
			)
			err = l.vp.Core.Payment.UpdateProcYtdd(ctx, tx, updateYtdd, updateYtddConds)
			if err != nil {
				wr.Err("failed to update proc ytdd", log.Error(err), log.StackTrace())
				return nil
			}
		} else {
			//
			// Append YTDD To Be Inserted
			//
			newYtdd := entity.ProcYtdd{
				ProcytdhID:        l.procYtdhID,
				CompanyID:         l.companyID,
				AllowdeductCode:   comp.AllowdeductCode,
				CompValueTax:      util.ToPointer(compValueTaxEnc),
				Taxed:             comp.Taxed,
				Net:               comp.Net,
				Taxclass:          comp.Taxclass,
				CurrencyCodeTax:   util.PointerValue(procMtdh.CurrencyCodeTax, ""),
				AllowdeductnameEn: util.ToPointer(comp.AllowDeductNameEN),
				AllowdeductnameId: comp.AllowDeductNameID,
				AllowdeductnameMy: comp.AllowDeductNameMY,
				AllowdeductnameTh: comp.AllowDeductNameTH,
				Allowdeducttype:   util.ToPointer(comp.AllowDeductType),
				Fixed:             comp.Fixed,
			}
			ytddToBeInserteds = append(ytddToBeInserteds, newYtdd)
		}

		switch comp.AllowDeductType {
		case "A":
			takeHomePayTax += compValueTax
			takeHomePayPeriod += compValuePeriod
		case "D":
			takeHomePayTax -= compValueTax
			takeHomePayPeriod -= compValuePeriod
		}
	}
	wr.Dbg("arrDebugProcssMtddYtdd", log.Response(arrDebugProcssMtddYtdd))
	wr.Dbg(
		"proc mtdd & ytdd to be inserted",
		log.Any("mtdd to be inserted", mtddToBeInserteds),
		log.Any("panjang mtdd", len(mtddToBeInserteds)),
		log.Any("ytdd to be inserted", ytddToBeInserteds),
		log.Any("takeHomePayTax", takeHomePayTax),
		log.Any("takeHomePayPeriod", takeHomePayPeriod),
	)

	if len(mtddToBeInserteds) > 0 {
		for _, v := range mtddToBeInserteds {
			err = l.vp.Core.Payment.InsertProcMtdd(ctx, tx, []entity.ProcMtdd{v})
			if err != nil {
				wr.Err("failed to insert proc mtdd", log.Error(err), log.StackTrace())
				return nil
			}
		}
	}

	if len(ytddToBeInserteds) > 0 {
		for _, v := range ytddToBeInserteds {
			err = l.vp.Core.Payment.InsertProcYtdd(ctx, tx, []entity.ProcYtdd{v})
			if err != nil {
				wr.Err("failed to insert proc ytdd", log.Error(err), log.StackTrace())
				return nil
			}
		}
	}

	///
	// Update THP Tax & Thp Period
	//
	takeHomePayTax += cast.ToFloat64(l.SalaryTax)
	takeHomePayPeriod += cast.ToFloat64(l.SalaryPeriod)
	updateProcMtdh := entity.ProcMtdh{
		ThpTax:     util.ToPointer(helperstd.SfEncryptCol(ctx, tx, takeHomePayTax, l.req.EmpID)),
		ThpPeriod:  util.ToPointer(helperstd.SfEncryptCol(ctx, tx, takeHomePayPeriod, l.req.EmpID)),
		PositionID: util.ToPointer(l.employeeData.PositionID),
	}
	updateMtdhConds := goqu.And(
		goqu.I("procmtdh_id").Eq(l.procMtdhID),
		goqu.I("company_id").Eq(l.companyID),
		goqu.I("emp_id").Eq(l.req.EmpID),
	)
	err = l.vp.Core.Payment.UpdateProcMtdh(ctx, tx, updateProcMtdh, updateMtdhConds)
	if err != nil {
		wr.Err("failed to update proc mtdh", log.Error(err), log.StackTrace())
		return nil
	}

	return
}
Editor is loading...
Leave a Comment