mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-24 15:25:43 +00:00
feat/BE/US-284/TASK-,299-Create API (GET ONE in tab Perhitungan Sapronak)
This commit is contained in:
@@ -165,7 +165,7 @@ func (s closingService) getApprovalStatuses(ctx context.Context, projectFlockID
|
||||
minStep = rec.StepNumber
|
||||
statusProject = rec.StepName
|
||||
}
|
||||
if rec.StepNumber == uint16(utils.ProjectFlockStepSelesai) {
|
||||
if rec.StepNumber == uint16(utils.ProjectFlockStepAktif) {
|
||||
completed++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,565 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/modules/closings/dto"
|
||||
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/repositories"
|
||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/validations"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||
)
|
||||
|
||||
type SapronakService interface {
|
||||
GetSapronakByProject(ctx *fiber.Ctx, projectFlockID uint) ([]dto.SapronakReportDTO, error)
|
||||
GetSapronakByKandang(ctx *fiber.Ctx, projectFlockID uint, pfkID uint) (*dto.SapronakReportDTO, error)
|
||||
GetSapronakReport(ctx *fiber.Ctx, params *validation.SapronakQuery) ([]dto.SapronakReportDTO, error)
|
||||
}
|
||||
|
||||
type sapronakService struct {
|
||||
Log *logrus.Logger
|
||||
Validate *validator.Validate
|
||||
Repository repository.ClosingRepository
|
||||
}
|
||||
|
||||
func NewSapronakService(repo repository.ClosingRepository, validate *validator.Validate) SapronakService {
|
||||
return &sapronakService{
|
||||
Log: utils.Log,
|
||||
Validate: validate,
|
||||
Repository: repo,
|
||||
}
|
||||
}
|
||||
|
||||
func (s sapronakService) GetSapronakReport(c *fiber.Ctx, params *validation.SapronakQuery) ([]dto.SapronakReportDTO, error) {
|
||||
if err := s.Validate.Struct(params); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.computeSapronakReports(c.Context(), params)
|
||||
}
|
||||
|
||||
func (s sapronakService) GetSapronakByProject(c *fiber.Ctx, projectFlockID uint) ([]dto.SapronakReportDTO, error) {
|
||||
if projectFlockID == 0 {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "project_flock_id is required")
|
||||
}
|
||||
reports, err := s.computeSapronakReports(c.Context(), &validation.SapronakQuery{
|
||||
ProjectFlockID: projectFlockID,
|
||||
Status: "all",
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(reports) <= 1 {
|
||||
return reports, nil
|
||||
}
|
||||
|
||||
combined := s.combineSapronakReports(reports, projectFlockID)
|
||||
return []dto.SapronakReportDTO{combined}, nil
|
||||
}
|
||||
|
||||
func (s sapronakService) GetSapronakByKandang(c *fiber.Ctx, projectFlockID uint, pfkID uint) (*dto.SapronakReportDTO, error) {
|
||||
if projectFlockID == 0 || pfkID == 0 {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "project_flock_id and project_flock_kandang_id are required")
|
||||
}
|
||||
|
||||
results, err := s.computeSapronakReports(c.Context(), &validation.SapronakQuery{
|
||||
ProjectFlockID: projectFlockID,
|
||||
ProjectFlockKandangID: pfkID,
|
||||
Status: "all",
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, res := range results {
|
||||
if res.ProjectFlockID == projectFlockID && res.ProjectFlockKandangID == pfkID {
|
||||
return &res, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "Sapronak for kandang not found")
|
||||
}
|
||||
|
||||
func (s sapronakService) computeSapronakReports(ctx context.Context, params *validation.SapronakQuery) ([]dto.SapronakReportDTO, error) {
|
||||
pfks, err := s.loadProjectFlockKandangs(ctx, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(pfks) == 0 {
|
||||
return []dto.SapronakReportDTO{}, nil
|
||||
}
|
||||
|
||||
startMap, err := s.mapStartDates(ctx, pfks)
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to prepare start dates for sapronak report: %+v", err)
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to prepare sapronak report")
|
||||
}
|
||||
statusMap, nextStartMap := s.computeStatusAndNextStart(pfks, startMap)
|
||||
|
||||
filterStatus := strings.ToLower(strings.TrimSpace(params.Status))
|
||||
if filterStatus == "" {
|
||||
filterStatus = "all"
|
||||
}
|
||||
|
||||
results := make([]dto.SapronakReportDTO, 0, len(pfks))
|
||||
for _, pfk := range pfks {
|
||||
status := statusMap[pfk.Id]
|
||||
if status == "" {
|
||||
status = "closing"
|
||||
}
|
||||
|
||||
if (filterStatus == "active" && status != "active") || (filterStatus == "closing" && status != "closing") {
|
||||
continue
|
||||
}
|
||||
|
||||
start := startMap[pfk.Id]
|
||||
var startPtr *time.Time
|
||||
if !start.IsZero() {
|
||||
startCopy := start
|
||||
startPtr = &startCopy
|
||||
}
|
||||
|
||||
var endPtr *time.Time
|
||||
if end, ok := nextStartMap[pfk.Id]; ok {
|
||||
endCopy := end
|
||||
endPtr = &endCopy
|
||||
}
|
||||
|
||||
items, groups, totalIncoming, totalUsage, err := s.buildSapronakItems(ctx, pfk, startPtr, endPtr)
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to build sapronak items for pfk %d: %+v", pfk.Id, err)
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to calculate sapronak report")
|
||||
}
|
||||
|
||||
results = append(results, dto.SapronakReportDTO{
|
||||
ProjectFlockKandangID: pfk.Id,
|
||||
ProjectFlockID: pfk.ProjectFlockId,
|
||||
ProjectName: pfk.ProjectFlock.FlockName,
|
||||
KandangID: pfk.KandangId,
|
||||
KandangName: pfk.Kandang.Name,
|
||||
Period: pfk.Period,
|
||||
Status: status,
|
||||
StartDate: startPtr,
|
||||
EndDate: endPtr,
|
||||
TotalIncomingValue: totalIncoming,
|
||||
TotalUsageValue: totalUsage,
|
||||
Items: items,
|
||||
Groups: groups,
|
||||
})
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s sapronakService) loadProjectFlockKandangs(ctx context.Context, params *validation.SapronakQuery) ([]entity.ProjectFlockKandang, error) {
|
||||
pfks, err := s.Repository.ListProjectFlockKandangsForSapronak(ctx, params)
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to load project flock kandangs for sapronak report: %+v", err)
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to load project flock kandangs")
|
||||
}
|
||||
return pfks, nil
|
||||
}
|
||||
|
||||
func (s sapronakService) mapStartDates(ctx context.Context, pfks []entity.ProjectFlockKandang) (map[uint]time.Time, error) {
|
||||
result := make(map[uint]time.Time, len(pfks))
|
||||
if len(pfks) == 0 {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
ids := make([]uint, len(pfks))
|
||||
for i, pfk := range pfks {
|
||||
ids[i] = pfk.Id
|
||||
}
|
||||
|
||||
startDates, err := s.Repository.MapSapronakStartDates(ctx, ids)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, pfk := range pfks {
|
||||
if start, ok := startDates[pfk.Id]; ok {
|
||||
result[pfk.Id] = start
|
||||
continue
|
||||
}
|
||||
result[pfk.Id] = pfk.CreatedAt.UTC()
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s sapronakService) combineSapronakReports(reports []dto.SapronakReportDTO, projectID uint) dto.SapronakReportDTO {
|
||||
if len(reports) == 0 {
|
||||
return dto.SapronakReportDTO{}
|
||||
}
|
||||
|
||||
var (
|
||||
totalIncoming float64
|
||||
totalUsage float64
|
||||
earliestStart *time.Time
|
||||
projectName = reports[0].ProjectName
|
||||
)
|
||||
|
||||
itemMap := make(map[uint]dto.SapronakItemDTO)
|
||||
groupMap := make(map[string]*dto.SapronakGroupDTO)
|
||||
|
||||
ensureGroup := func(flag string) *dto.SapronakGroupDTO {
|
||||
if g, ok := groupMap[flag]; ok {
|
||||
return g
|
||||
}
|
||||
groupMap[flag] = &dto.SapronakGroupDTO{Flag: flag}
|
||||
return groupMap[flag]
|
||||
}
|
||||
|
||||
for _, r := range reports {
|
||||
totalIncoming += r.TotalIncomingValue
|
||||
totalUsage += r.TotalUsageValue
|
||||
if r.StartDate != nil {
|
||||
if earliestStart == nil || r.StartDate.Before(*earliestStart) {
|
||||
earliestStart = r.StartDate
|
||||
}
|
||||
}
|
||||
|
||||
for _, it := range r.Items {
|
||||
cur := itemMap[it.ProductID]
|
||||
if cur.ProductID == 0 {
|
||||
cur.ProductID = it.ProductID
|
||||
cur.ProductName = it.ProductName
|
||||
cur.Flag = it.Flag
|
||||
}
|
||||
cur.IncomingQty += it.IncomingQty
|
||||
cur.IncomingValue += it.IncomingValue
|
||||
cur.UsageQty += it.UsageQty
|
||||
cur.UsageValue += it.UsageValue
|
||||
if cur.IncomingQty >= cur.UsageQty {
|
||||
cur.RemainingQty = cur.IncomingQty - cur.UsageQty
|
||||
} else {
|
||||
cur.RemainingQty = 0
|
||||
}
|
||||
if cur.IncomingQty > 0 {
|
||||
cur.AveragePrice = cur.IncomingValue / cur.IncomingQty
|
||||
} else {
|
||||
cur.AveragePrice = it.AveragePrice
|
||||
}
|
||||
itemMap[it.ProductID] = cur
|
||||
}
|
||||
|
||||
for _, g := range r.Groups {
|
||||
agg := ensureGroup(g.Flag)
|
||||
agg.TotalMasuk += g.TotalMasuk
|
||||
agg.TotalKeluar += g.TotalKeluar
|
||||
agg.SaldoAkhir += g.SaldoAkhir
|
||||
agg.TotalNilai += g.TotalNilai
|
||||
agg.Items = append(agg.Items, g.Items...)
|
||||
}
|
||||
}
|
||||
|
||||
items := make([]dto.SapronakItemDTO, 0, len(itemMap))
|
||||
for _, it := range itemMap {
|
||||
items = append(items, it)
|
||||
}
|
||||
|
||||
groups := make([]dto.SapronakGroupDTO, 0, len(groupMap))
|
||||
for _, g := range groupMap {
|
||||
groups = append(groups, *g)
|
||||
}
|
||||
|
||||
return dto.SapronakReportDTO{
|
||||
ProjectFlockID: projectID,
|
||||
ProjectName: projectName,
|
||||
Status: "combined",
|
||||
StartDate: earliestStart,
|
||||
TotalIncomingValue: totalIncoming,
|
||||
TotalUsageValue: totalUsage,
|
||||
Items: items,
|
||||
Groups: groups,
|
||||
}
|
||||
}
|
||||
|
||||
func (s sapronakService) computeStatusAndNextStart(pfks []entity.ProjectFlockKandang, startMap map[uint]time.Time) (map[uint]string, map[uint]time.Time) {
|
||||
statusMap := make(map[uint]string, len(pfks))
|
||||
nextStartMap := make(map[uint]time.Time, len(pfks))
|
||||
|
||||
if len(pfks) == 0 {
|
||||
return statusMap, nextStartMap
|
||||
}
|
||||
|
||||
grouped := make(map[uint][]entity.ProjectFlockKandang)
|
||||
for _, pfk := range pfks {
|
||||
grouped[pfk.KandangId] = append(grouped[pfk.KandangId], pfk)
|
||||
}
|
||||
|
||||
for _, list := range grouped {
|
||||
for idx, item := range list {
|
||||
if idx < len(list)-1 {
|
||||
next := list[idx+1]
|
||||
if start, ok := startMap[next.Id]; ok {
|
||||
nextStartMap[item.Id] = start
|
||||
}
|
||||
statusMap[item.Id] = "closing"
|
||||
continue
|
||||
}
|
||||
statusMap[item.Id] = "active"
|
||||
}
|
||||
}
|
||||
|
||||
return statusMap, nextStartMap
|
||||
}
|
||||
|
||||
func (s sapronakService) buildSapronakItems(ctx context.Context, pfk entity.ProjectFlockKandang, start, end *time.Time) ([]dto.SapronakItemDTO, []dto.SapronakGroupDTO, float64, float64, error) {
|
||||
incomingRows, err := s.Repository.FetchSapronakIncoming(ctx, pfk.KandangId, start, end)
|
||||
if err != nil {
|
||||
return nil, nil, 0, 0, err
|
||||
}
|
||||
incomingDetailsRows, err := s.Repository.FetchSapronakIncomingDetails(ctx, pfk.KandangId, start, end)
|
||||
if err != nil {
|
||||
return nil, nil, 0, 0, err
|
||||
}
|
||||
usageRows, err := s.Repository.FetchSapronakUsage(ctx, pfk.Id, start, end)
|
||||
if err != nil {
|
||||
return nil, nil, 0, 0, err
|
||||
}
|
||||
usageDetailsRows, err := s.Repository.FetchSapronakUsageDetails(ctx, pfk.Id, start, end)
|
||||
if err != nil {
|
||||
return nil, nil, 0, 0, err
|
||||
}
|
||||
adjIncomingRows, adjOutgoingRows, err := s.Repository.FetchSapronakAdjustments(ctx, pfk.KandangId, start, end)
|
||||
if err != nil {
|
||||
return nil, nil, 0, 0, err
|
||||
}
|
||||
transIncomingRows, _, err := s.Repository.FetchSapronakTransfers(ctx, pfk.KandangId, start, end)
|
||||
if err != nil {
|
||||
return nil, nil, 0, 0, err
|
||||
}
|
||||
|
||||
incoming, usage := mapIncomingUsage(incomingRows, usageRows)
|
||||
itemMap := make(map[uint]dto.SapronakItemDTO, len(incoming)+len(usage))
|
||||
groupMap := make(map[string]*dto.SapronakGroupDTO)
|
||||
details := buildSapronakDetails(incomingDetailsRows, usageDetailsRows, adjIncomingRows, adjOutgoingRows, transIncomingRows)
|
||||
|
||||
ensureGroup := func(flag string) *dto.SapronakGroupDTO {
|
||||
if g, ok := groupMap[flag]; ok {
|
||||
return g
|
||||
}
|
||||
groupMap[flag] = &dto.SapronakGroupDTO{Flag: flag}
|
||||
return groupMap[flag]
|
||||
}
|
||||
|
||||
for _, row := range incoming {
|
||||
avgPrice := row.DefaultPrice
|
||||
if row.Qty > 0 && row.Value > 0 {
|
||||
avgPrice = row.Value / row.Qty
|
||||
}
|
||||
|
||||
itemMap[row.ProductID] = dto.SapronakItemDTO{
|
||||
ProductID: row.ProductID,
|
||||
ProductName: row.ProductName,
|
||||
Flag: row.Flag,
|
||||
IncomingQty: row.Qty,
|
||||
IncomingValue: row.Value,
|
||||
RemainingQty: row.Qty,
|
||||
AveragePrice: avgPrice,
|
||||
}
|
||||
}
|
||||
|
||||
for _, row := range usage {
|
||||
existing := itemMap[row.ProductID]
|
||||
price := existing.AveragePrice
|
||||
if price == 0 {
|
||||
price = row.DefaultPrice
|
||||
}
|
||||
|
||||
usageValue := row.Qty * price
|
||||
|
||||
existing.ProductID = row.ProductID
|
||||
if existing.ProductName == "" {
|
||||
existing.ProductName = row.ProductName
|
||||
}
|
||||
if existing.Flag == "" {
|
||||
existing.Flag = row.Flag
|
||||
}
|
||||
existing.AveragePrice = price
|
||||
existing.UsageQty += row.Qty
|
||||
existing.UsageValue += usageValue
|
||||
if existing.IncomingQty >= existing.UsageQty {
|
||||
existing.RemainingQty = existing.IncomingQty - existing.UsageQty
|
||||
} else {
|
||||
existing.RemainingQty = 0
|
||||
}
|
||||
|
||||
itemMap[row.ProductID] = existing
|
||||
}
|
||||
|
||||
for productID, details := range adjIncoming {
|
||||
for _, d := range details {
|
||||
existing := itemMap[productID]
|
||||
if existing.Flag == "" {
|
||||
existing.Flag = d.Flag
|
||||
}
|
||||
if existing.ProductName == "" {
|
||||
existing.ProductName = d.ProductName
|
||||
}
|
||||
existing.IncomingQty += d.QtyMasuk
|
||||
existing.IncomingValue += d.Nilai
|
||||
if existing.IncomingQty > 0 {
|
||||
existing.AveragePrice = existing.IncomingValue / existing.IncomingQty
|
||||
}
|
||||
if existing.IncomingQty >= existing.UsageQty {
|
||||
existing.RemainingQty = existing.IncomingQty - existing.UsageQty
|
||||
} else {
|
||||
existing.RemainingQty = 0
|
||||
}
|
||||
itemMap[productID] = existing
|
||||
}
|
||||
}
|
||||
|
||||
for productID, details := range adjOutgoing {
|
||||
for _, d := range details {
|
||||
existing := itemMap[productID]
|
||||
if existing.Flag == "" {
|
||||
existing.Flag = d.Flag
|
||||
}
|
||||
if existing.ProductName == "" {
|
||||
existing.ProductName = d.ProductName
|
||||
}
|
||||
existing.UsageQty += d.QtyKeluar
|
||||
existing.UsageValue += d.Nilai
|
||||
if existing.IncomingQty >= existing.UsageQty {
|
||||
existing.RemainingQty = existing.IncomingQty - existing.UsageQty
|
||||
} else {
|
||||
existing.RemainingQty = 0
|
||||
}
|
||||
itemMap[productID] = existing
|
||||
}
|
||||
}
|
||||
|
||||
for productID, details := range transIncoming {
|
||||
for _, d := range details {
|
||||
existing := itemMap[productID]
|
||||
if existing.Flag == "" {
|
||||
existing.Flag = d.Flag
|
||||
}
|
||||
if existing.ProductName == "" {
|
||||
existing.ProductName = d.ProductName
|
||||
}
|
||||
existing.IncomingQty += d.QtyMasuk
|
||||
existing.IncomingValue += d.Nilai
|
||||
if existing.IncomingQty > 0 {
|
||||
existing.AveragePrice = existing.IncomingValue / existing.IncomingQty
|
||||
}
|
||||
if existing.IncomingQty >= existing.UsageQty {
|
||||
existing.RemainingQty = existing.IncomingQty - existing.UsageQty
|
||||
} else {
|
||||
existing.RemainingQty = 0
|
||||
}
|
||||
itemMap[productID] = existing
|
||||
}
|
||||
}
|
||||
|
||||
items := make([]dto.SapronakItemDTO, 0, len(itemMap))
|
||||
var totalIncoming, totalUsage float64
|
||||
for _, item := range itemMap {
|
||||
totalIncoming += item.IncomingValue
|
||||
totalUsage += item.UsageValue
|
||||
items = append(items, item)
|
||||
}
|
||||
|
||||
for productID, details := range incomingDetails {
|
||||
flag := ""
|
||||
name := ""
|
||||
if item, ok := itemMap[productID]; ok {
|
||||
flag = item.Flag
|
||||
name = item.ProductName
|
||||
}
|
||||
group := ensureGroup(flag)
|
||||
for _, d := range details {
|
||||
d.Flag = flag
|
||||
d.ProductName = name
|
||||
group.Items = append(group.Items, d)
|
||||
group.TotalMasuk += d.QtyMasuk
|
||||
group.TotalNilai += d.Nilai
|
||||
group.SaldoAkhir += d.QtyMasuk
|
||||
}
|
||||
}
|
||||
|
||||
for productID, details := range adjIncoming {
|
||||
flag := ""
|
||||
name := ""
|
||||
if item, ok := itemMap[productID]; ok {
|
||||
flag = item.Flag
|
||||
name = item.ProductName
|
||||
}
|
||||
group := ensureGroup(flag)
|
||||
for _, d := range details {
|
||||
d.Flag = flag
|
||||
d.ProductName = name
|
||||
group.Items = append(group.Items, d)
|
||||
group.TotalMasuk += d.QtyMasuk
|
||||
group.TotalNilai += d.Nilai
|
||||
group.SaldoAkhir += d.QtyMasuk
|
||||
}
|
||||
}
|
||||
|
||||
for productID, details := range usageDetails {
|
||||
flag := ""
|
||||
name := ""
|
||||
if item, ok := itemMap[productID]; ok {
|
||||
flag = item.Flag
|
||||
name = item.ProductName
|
||||
}
|
||||
group := ensureGroup(flag)
|
||||
for _, d := range details {
|
||||
d.Flag = flag
|
||||
d.ProductName = name
|
||||
group.Items = append(group.Items, d)
|
||||
group.TotalKeluar += d.QtyKeluar
|
||||
group.SaldoAkhir -= d.QtyKeluar
|
||||
}
|
||||
}
|
||||
|
||||
for productID, details := range adjOutgoing {
|
||||
flag := ""
|
||||
name := ""
|
||||
if item, ok := itemMap[productID]; ok {
|
||||
flag = item.Flag
|
||||
name = item.ProductName
|
||||
}
|
||||
group := ensureGroup(flag)
|
||||
for _, d := range details {
|
||||
d.Flag = flag
|
||||
d.ProductName = name
|
||||
group.Items = append(group.Items, d)
|
||||
group.TotalKeluar += d.QtyKeluar
|
||||
group.SaldoAkhir -= d.QtyKeluar
|
||||
}
|
||||
}
|
||||
|
||||
for productID, details := range transIncoming {
|
||||
flag := ""
|
||||
name := ""
|
||||
if item, ok := itemMap[productID]; ok {
|
||||
flag = item.Flag
|
||||
name = item.ProductName
|
||||
}
|
||||
group := ensureGroup(flag)
|
||||
for _, d := range details {
|
||||
d.Flag = flag
|
||||
d.ProductName = name
|
||||
group.Items = append(group.Items, d)
|
||||
group.TotalMasuk += d.QtyMasuk
|
||||
group.TotalNilai += d.Nilai
|
||||
group.SaldoAkhir += d.QtyMasuk
|
||||
}
|
||||
}
|
||||
|
||||
groups := make([]dto.SapronakGroupDTO, 0, len(groupMap))
|
||||
for _, g := range groupMap {
|
||||
groups = append(groups, *g)
|
||||
}
|
||||
|
||||
return items, groups, totalIncoming, totalUsage, nil
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/modules/closings/dto"
|
||||
)
|
||||
|
||||
type SapronakFormatter interface {
|
||||
ProjectPayload(reports []dto.SapronakReportDTO) dto.SapronakProjectAggregatedDTO
|
||||
KandangPayload(report *dto.SapronakReportDTO) dto.SapronakProjectAggregatedDTO
|
||||
}
|
||||
|
||||
type sapronakFormatter struct{}
|
||||
|
||||
func NewSapronakFormatter() SapronakFormatter {
|
||||
return &sapronakFormatter{}
|
||||
}
|
||||
|
||||
func (f *sapronakFormatter) ProjectPayload(reports []dto.SapronakReportDTO) dto.SapronakProjectAggregatedDTO {
|
||||
result := dto.SapronakProjectAggregatedDTO{
|
||||
Doc: dto.SapronakCategoryDTO{},
|
||||
Ovk: dto.SapronakCategoryDTO{},
|
||||
Pakan: dto.SapronakCategoryDTO{},
|
||||
}
|
||||
|
||||
if len(reports) == 0 {
|
||||
return result
|
||||
}
|
||||
|
||||
rep := reports[0]
|
||||
return f.mapFromReport(&rep)
|
||||
}
|
||||
|
||||
func (f *sapronakFormatter) KandangPayload(report *dto.SapronakReportDTO) dto.SapronakProjectAggregatedDTO {
|
||||
return f.mapFromReport(report)
|
||||
}
|
||||
|
||||
func (f *sapronakFormatter) mapFromReport(report *dto.SapronakReportDTO) dto.SapronakProjectAggregatedDTO {
|
||||
result := dto.SapronakProjectAggregatedDTO{
|
||||
Doc: dto.SapronakCategoryDTO{},
|
||||
Ovk: dto.SapronakCategoryDTO{},
|
||||
Pakan: dto.SapronakCategoryDTO{},
|
||||
}
|
||||
|
||||
if report == nil {
|
||||
return result
|
||||
}
|
||||
|
||||
byFlag := map[string]*dto.SapronakCategoryDTO{
|
||||
"DOC": &result.Doc,
|
||||
"OVK": &result.Ovk,
|
||||
"PAKAN": &result.Pakan,
|
||||
}
|
||||
|
||||
formatDate := func(t *time.Time) string {
|
||||
if t == nil {
|
||||
return ""
|
||||
}
|
||||
return t.Format("02-Jan-2006")
|
||||
}
|
||||
|
||||
for _, group := range report.Groups {
|
||||
flag := strings.ToUpper(group.Flag)
|
||||
target := byFlag[flag]
|
||||
if target == nil {
|
||||
continue
|
||||
}
|
||||
for idx, item := range group.Items {
|
||||
qtyUsed := item.QtyKeluar
|
||||
if qtyUsed == 0 {
|
||||
qtyUsed = item.QtyMasuk
|
||||
}
|
||||
|
||||
target.Rows = append(target.Rows, dto.SapronakCategoryRowDTO{
|
||||
ID: idx + 1,
|
||||
Date: formatDate(item.Tanggal),
|
||||
ReferenceNumber: item.NoReferensi,
|
||||
QtyIn: item.QtyMasuk,
|
||||
QtyOut: item.QtyKeluar,
|
||||
QtyUsed: qtyUsed,
|
||||
Description: item.ProductName,
|
||||
ProductCategory: item.ProductName,
|
||||
UnitPrice: item.Harga,
|
||||
TotalAmount: item.Nilai,
|
||||
Notes: "-",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
buildTotals := func(cat *dto.SapronakCategoryDTO, label string) {
|
||||
var qtyIn, qtyOut, qtyUsed, total float64
|
||||
for _, r := range cat.Rows {
|
||||
qtyIn += r.QtyIn
|
||||
qtyOut += r.QtyOut
|
||||
qtyUsed += r.QtyUsed
|
||||
total += r.TotalAmount
|
||||
}
|
||||
avg := 0.0
|
||||
if qtyIn > 0 {
|
||||
avg = total / qtyIn
|
||||
}
|
||||
cat.Total = dto.SapronakCategoryTotalDTO{
|
||||
Label: label,
|
||||
QtyIn: qtyIn,
|
||||
QtyOut: qtyOut,
|
||||
QtyUsed: qtyUsed,
|
||||
AvgUnitPrice: avg,
|
||||
TotalAmount: total,
|
||||
}
|
||||
}
|
||||
|
||||
buildTotals(&result.Doc, "TOTAL DOC")
|
||||
buildTotals(&result.Ovk, "TOTAL OVK")
|
||||
buildTotals(&result.Pakan, "TOTAL PAKAN")
|
||||
|
||||
return result
|
||||
}
|
||||
Reference in New Issue
Block a user