mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-23 06:45:43 +00:00
Merge branch 'feat/BE/Sprint-7' of https://gitlab.com/mbugroup/lti-api into feat/BE/US-334-Report-closing-hpp-expedisi
This commit is contained in:
@@ -30,7 +30,7 @@ type ClosingService interface {
|
||||
GetPenjualan(ctx *fiber.Ctx, projectFlockID uint) ([]entity.MarketingDeliveryProduct, error)
|
||||
GetClosingSummary(ctx *fiber.Ctx, projectFlockID uint) (*dto.ClosingSummaryDTO, error)
|
||||
GetOverhead(ctx *fiber.Ctx, projectFlockID uint) (*dto.OverheadListDTO, error)
|
||||
GetClosingSapronak(ctx *fiber.Ctx, projectFlockID uint, params *validation.SapronakQuery) ([]dto.ClosingSapronakItemDTO, int64, error)
|
||||
GetClosingSapronak(ctx *fiber.Ctx, projectFlockID uint, params *validation.ClosingSapronakQuery) ([]dto.ClosingSapronakItemDTO, int64, error)
|
||||
GetExpeditionHPP(ctx *fiber.Ctx, projectFlockID uint, projectFlockKandangID *uint) (*dto.ExpeditionHPPDTO, error)
|
||||
}
|
||||
|
||||
@@ -169,13 +169,13 @@ func (s closingService) GetClosingSummary(c *fiber.Ctx, projectFlockID uint) (*d
|
||||
return &summary, nil
|
||||
}
|
||||
|
||||
func (s closingService) GetClosingSapronak(c *fiber.Ctx, projectFlockID uint, params *validation.SapronakQuery) ([]dto.ClosingSapronakItemDTO, int64, error) {
|
||||
func (s closingService) GetClosingSapronak(c *fiber.Ctx, projectFlockID uint, params *validation.ClosingSapronakQuery) ([]dto.ClosingSapronakItemDTO, int64, error) {
|
||||
if projectFlockID == 0 {
|
||||
return nil, 0, fiber.NewError(fiber.StatusBadRequest, "Invalid project flock id")
|
||||
}
|
||||
|
||||
if params == nil {
|
||||
params = &validation.SapronakQuery{}
|
||||
params = &validation.ClosingSapronakQuery{}
|
||||
}
|
||||
|
||||
if params.Page == 0 {
|
||||
@@ -331,7 +331,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,681 @@
|
||||
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"
|
||||
projectflockRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||
)
|
||||
|
||||
type SapronakService interface {
|
||||
GetSapronakByProject(ctx *fiber.Ctx, projectFlockID uint, flag string) ([]dto.SapronakReportDTO, error)
|
||||
GetSapronakByKandang(ctx *fiber.Ctx, projectFlockID uint, pfkID uint, flag string) (*dto.SapronakReportDTO, error)
|
||||
}
|
||||
|
||||
type sapronakService struct {
|
||||
Log *logrus.Logger
|
||||
Validate *validator.Validate
|
||||
Repository repository.ClosingRepository
|
||||
ProjectFlockKandangRepo projectflockRepository.ProjectFlockKandangRepository
|
||||
}
|
||||
|
||||
func NewSapronakService(
|
||||
repo repository.ClosingRepository,
|
||||
pfkRepo projectflockRepository.ProjectFlockKandangRepository,
|
||||
validate *validator.Validate,
|
||||
) SapronakService {
|
||||
return &sapronakService{
|
||||
Log: utils.Log,
|
||||
Validate: validate,
|
||||
Repository: repo,
|
||||
ProjectFlockKandangRepo: pfkRepo,
|
||||
}
|
||||
}
|
||||
|
||||
func (s sapronakService) GetSapronakByProject(c *fiber.Ctx, projectFlockID uint, flag string) ([]dto.SapronakReportDTO, error) {
|
||||
if projectFlockID == 0 {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "project_flock_id is required")
|
||||
}
|
||||
reports, err := s.computeSapronakReports(c.Context(), &validation.CountSapronakQuery{
|
||||
ProjectFlockID: projectFlockID,
|
||||
Status: "all",
|
||||
Flag: flag,
|
||||
})
|
||||
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, flag string) (*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.CountSapronakQuery{
|
||||
ProjectFlockID: projectFlockID,
|
||||
ProjectFlockKandangID: pfkID,
|
||||
Status: "all",
|
||||
Flag: flag,
|
||||
})
|
||||
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.CountSapronakQuery) ([]dto.SapronakReportDTO, error) {
|
||||
pfks, err := s.loadProjectFlockKandangs(ctx, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(pfks) == 0 {
|
||||
return []dto.SapronakReportDTO{}, nil
|
||||
}
|
||||
|
||||
filterStatus := strings.ToLower(strings.TrimSpace(params.Status))
|
||||
if filterStatus == "" {
|
||||
filterStatus = "all"
|
||||
}
|
||||
|
||||
results := make([]dto.SapronakReportDTO, 0, len(pfks))
|
||||
for _, pfk := range pfks {
|
||||
status := "closing"
|
||||
if pfk.ClosedAt == nil {
|
||||
status = "active"
|
||||
}
|
||||
|
||||
if (filterStatus == "active" && status != "active") || (filterStatus == "closing" && status != "closing") {
|
||||
continue
|
||||
}
|
||||
|
||||
// We no longer filter by date for closing sapronak report; pass nil pointers.
|
||||
items, groups, totalIncoming, totalUsage, err := s.buildSapronakItems(ctx, pfk, nil, nil, params.Flag)
|
||||
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: nil,
|
||||
EndDate: nil,
|
||||
TotalIncomingValue: totalIncoming,
|
||||
TotalUsageValue: totalUsage,
|
||||
Items: items,
|
||||
Groups: groups,
|
||||
})
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s sapronakService) loadProjectFlockKandangs(ctx context.Context, params *validation.CountSapronakQuery) ([]entity.ProjectFlockKandang, error) {
|
||||
db := s.ProjectFlockKandangRepo.DB().WithContext(ctx).
|
||||
Preload("ProjectFlock").
|
||||
Preload("Kandang").
|
||||
Preload("Chickins")
|
||||
|
||||
if params != nil {
|
||||
if params.ProjectFlockID > 0 {
|
||||
db = db.Where("project_flock_kandangs.project_flock_id = ?", params.ProjectFlockID)
|
||||
}
|
||||
if params.KandangID > 0 {
|
||||
db = db.Where("project_flock_kandangs.kandang_id = ?", params.KandangID)
|
||||
}
|
||||
if params.ProjectFlockKandangID > 0 {
|
||||
db = db.Where("project_flock_kandangs.id = ?", params.ProjectFlockKandangID)
|
||||
}
|
||||
}
|
||||
|
||||
var pfks []entity.ProjectFlockKandang
|
||||
if err := db.Find(&pfks).Error; 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) combineSapronakReports(reports []dto.SapronakReportDTO, projectID uint) dto.SapronakReportDTO {
|
||||
if len(reports) == 0 {
|
||||
return dto.SapronakReportDTO{}
|
||||
}
|
||||
|
||||
var (
|
||||
totalIncoming float64
|
||||
totalUsage float64
|
||||
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
|
||||
|
||||
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: nil,
|
||||
TotalIncomingValue: totalIncoming,
|
||||
TotalUsageValue: totalUsage,
|
||||
Items: items,
|
||||
Groups: groups,
|
||||
}
|
||||
}
|
||||
|
||||
func mapIncomingUsage(incomingRows []repository.SapronakIncomingRow, usageRows []repository.SapronakUsageRow) (map[uint]repository.SapronakIncomingRow, map[uint]repository.SapronakUsageRow) {
|
||||
incoming := make(map[uint]repository.SapronakIncomingRow, len(incomingRows))
|
||||
for _, row := range incomingRows {
|
||||
incoming[row.ProductID] = row
|
||||
}
|
||||
usage := make(map[uint]repository.SapronakUsageRow, len(usageRows))
|
||||
for _, row := range usageRows {
|
||||
usage[row.ProductID] = row
|
||||
}
|
||||
return incoming, usage
|
||||
}
|
||||
|
||||
type sapronakDetailMaps struct {
|
||||
Incoming map[uint][]dto.SapronakDetailDTO
|
||||
Usage map[uint][]dto.SapronakDetailDTO
|
||||
AdjIncoming map[uint][]dto.SapronakDetailDTO
|
||||
AdjOutgoing map[uint][]dto.SapronakDetailDTO
|
||||
TransferIn map[uint][]dto.SapronakDetailDTO
|
||||
TransferOut map[uint][]dto.SapronakDetailDTO
|
||||
}
|
||||
|
||||
func buildSapronakDetails(
|
||||
incomingRows map[uint][]repository.SapronakDetailRow,
|
||||
usageRows map[uint][]repository.SapronakDetailRow,
|
||||
adjIncomingRows map[uint][]repository.SapronakDetailRow,
|
||||
adjOutgoingRows map[uint][]repository.SapronakDetailRow,
|
||||
transferInRows map[uint][]repository.SapronakDetailRow,
|
||||
transferOutRows map[uint][]repository.SapronakDetailRow,
|
||||
) sapronakDetailMaps {
|
||||
result := sapronakDetailMaps{
|
||||
Incoming: make(map[uint][]dto.SapronakDetailDTO),
|
||||
Usage: make(map[uint][]dto.SapronakDetailDTO),
|
||||
AdjIncoming: make(map[uint][]dto.SapronakDetailDTO),
|
||||
AdjOutgoing: make(map[uint][]dto.SapronakDetailDTO),
|
||||
TransferIn: make(map[uint][]dto.SapronakDetailDTO),
|
||||
TransferOut: make(map[uint][]dto.SapronakDetailDTO),
|
||||
}
|
||||
|
||||
addRows := func(target map[uint][]dto.SapronakDetailDTO, src map[uint][]repository.SapronakDetailRow, jenis string, masuk bool) {
|
||||
for pid, rows := range src {
|
||||
for _, r := range rows {
|
||||
d := dto.SapronakDetailDTO{
|
||||
ProductID: r.ProductID,
|
||||
ProductName: r.ProductName,
|
||||
Flag: r.Flag,
|
||||
Tanggal: r.Date,
|
||||
NoReferensi: r.Reference,
|
||||
JenisTransaksi: jenis,
|
||||
Harga: r.Price,
|
||||
}
|
||||
if masuk {
|
||||
d.QtyMasuk = r.QtyIn
|
||||
d.Nilai = r.QtyIn * r.Price
|
||||
} else {
|
||||
d.QtyKeluar = r.QtyOut
|
||||
d.Nilai = r.QtyOut * r.Price
|
||||
}
|
||||
target[pid] = append(target[pid], d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addRows(result.Incoming, incomingRows, "Pembelian", true)
|
||||
addRows(result.Usage, usageRows, "Pemakaian", false)
|
||||
addRows(result.AdjIncoming, adjIncomingRows, "Adjustment Masuk", true)
|
||||
addRows(result.AdjOutgoing, adjOutgoingRows, "Adjustment Keluar", false)
|
||||
addRows(result.TransferIn, transferInRows, "Mutasi Masuk", true)
|
||||
addRows(result.TransferOut, transferOutRows, "Mutasi Keluar", false)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (s sapronakService) buildSapronakItems(ctx context.Context, pfk entity.ProjectFlockKandang, start, end *time.Time, flagFilter string) ([]dto.SapronakItemDTO, []dto.SapronakGroupDTO, float64, float64, error) {
|
||||
// For sapronak closing report we intentionally ignore date range
|
||||
// and aggregate all historical transactions for the kandang/project.
|
||||
incomingRows, err := s.Repository.FetchSapronakIncoming(ctx, pfk.KandangId)
|
||||
if err != nil {
|
||||
return nil, nil, 0, 0, err
|
||||
}
|
||||
incomingDetailsRows, err := s.Repository.FetchSapronakIncomingDetails(ctx, pfk.KandangId)
|
||||
if err != nil {
|
||||
return nil, nil, 0, 0, err
|
||||
}
|
||||
usageRows, err := s.Repository.FetchSapronakUsage(ctx, pfk.Id)
|
||||
if err != nil {
|
||||
return nil, nil, 0, 0, err
|
||||
}
|
||||
chickinUsageRows, err := s.Repository.FetchSapronakChickinUsage(ctx, pfk.Id)
|
||||
if err != nil {
|
||||
return nil, nil, 0, 0, err
|
||||
}
|
||||
usageDetailsRows, err := s.Repository.FetchSapronakUsageDetails(ctx, pfk.Id)
|
||||
if err != nil {
|
||||
return nil, nil, 0, 0, err
|
||||
}
|
||||
chickinUsageDetailsRows, err := s.Repository.FetchSapronakChickinUsageDetails(ctx, pfk.Id)
|
||||
if err != nil {
|
||||
return nil, nil, 0, 0, err
|
||||
}
|
||||
adjIncomingRows, adjOutgoingRows, err := s.Repository.FetchSapronakAdjustments(ctx, pfk.KandangId)
|
||||
if err != nil {
|
||||
return nil, nil, 0, 0, err
|
||||
}
|
||||
transIncomingRows, transOutgoingRows, err := s.Repository.FetchSapronakTransfers(ctx, pfk.KandangId)
|
||||
if err != nil {
|
||||
return nil, nil, 0, 0, err
|
||||
}
|
||||
|
||||
filterFlag := strings.ToUpper(strings.TrimSpace(flagFilter))
|
||||
matchesFlag := func(f string) bool {
|
||||
if filterFlag == "" {
|
||||
return true
|
||||
}
|
||||
return strings.ToUpper(f) == filterFlag
|
||||
}
|
||||
|
||||
// For project flocks with category GROWING, pullet usage from chickin
|
||||
// should not be counted yet. Only when category is LAYING we allow
|
||||
// pullet usage to contribute to qty_used.
|
||||
isLaying := strings.EqualFold(string(pfk.ProjectFlock.Category), string(utils.ProjectFlockCategoryLaying))
|
||||
|
||||
if !isLaying {
|
||||
filteredUsage := make([]repository.SapronakUsageRow, 0, len(chickinUsageRows))
|
||||
for _, row := range chickinUsageRows {
|
||||
if strings.ToUpper(row.Flag) == "DOC" {
|
||||
filteredUsage = append(filteredUsage, row)
|
||||
}
|
||||
}
|
||||
chickinUsageRows = filteredUsage
|
||||
|
||||
filteredDetail := make(map[uint][]repository.SapronakDetailRow, len(chickinUsageDetailsRows))
|
||||
for pid, rows := range chickinUsageDetailsRows {
|
||||
for _, d := range rows {
|
||||
if strings.ToUpper(d.Flag) == "DOC" {
|
||||
filteredDetail[pid] = append(filteredDetail[pid], d)
|
||||
}
|
||||
}
|
||||
}
|
||||
chickinUsageDetailsRows = filteredDetail
|
||||
}
|
||||
|
||||
allUsageRows := append(usageRows, chickinUsageRows...)
|
||||
incoming, usage := mapIncomingUsage(incomingRows, allUsageRows)
|
||||
itemMap := make(map[uint]dto.SapronakItemDTO, len(incoming)+len(usage))
|
||||
groupMap := make(map[string]*dto.SapronakGroupDTO)
|
||||
|
||||
for pid, rows := range chickinUsageDetailsRows {
|
||||
if len(rows) == 0 {
|
||||
continue
|
||||
}
|
||||
usageDetailsRows[pid] = append(usageDetailsRows[pid], rows...)
|
||||
}
|
||||
|
||||
detailMaps := buildSapronakDetails(incomingDetailsRows, usageDetailsRows, adjIncomingRows, adjOutgoingRows, transIncomingRows, transOutgoingRows)
|
||||
incomingDetails := detailMaps.Incoming
|
||||
usageDetails := detailMaps.Usage
|
||||
adjIncoming := detailMaps.AdjIncoming
|
||||
adjOutgoing := detailMaps.AdjOutgoing
|
||||
transIncoming := detailMaps.TransferIn
|
||||
transOutgoing := detailMaps.TransferOut
|
||||
|
||||
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 {
|
||||
if !matchesFlag(row.Flag) {
|
||||
continue
|
||||
}
|
||||
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 {
|
||||
if !matchesFlag(row.Flag) {
|
||||
continue
|
||||
}
|
||||
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 {
|
||||
if !matchesFlag(d.Flag) {
|
||||
continue
|
||||
}
|
||||
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 {
|
||||
if !matchesFlag(d.Flag) {
|
||||
continue
|
||||
}
|
||||
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 {
|
||||
if !matchesFlag(d.Flag) {
|
||||
continue
|
||||
}
|
||||
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
|
||||
}
|
||||
if !matchesFlag(flag) {
|
||||
continue
|
||||
}
|
||||
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
|
||||
}
|
||||
if !matchesFlag(flag) {
|
||||
continue
|
||||
}
|
||||
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
|
||||
}
|
||||
if !matchesFlag(flag) {
|
||||
continue
|
||||
}
|
||||
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
|
||||
}
|
||||
if !matchesFlag(flag) {
|
||||
continue
|
||||
}
|
||||
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
|
||||
}
|
||||
if !matchesFlag(flag) {
|
||||
continue
|
||||
}
|
||||
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 transOutgoing {
|
||||
flag := ""
|
||||
name := ""
|
||||
if item, ok := itemMap[productID]; ok {
|
||||
flag = item.Flag
|
||||
name = item.ProductName
|
||||
}
|
||||
if !matchesFlag(flag) {
|
||||
continue
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
groups := make([]dto.SapronakGroupDTO, 0, len(groupMap))
|
||||
for _, g := range groupMap {
|
||||
groups = append(groups, *g)
|
||||
}
|
||||
|
||||
return items, groups, totalIncoming, totalUsage, nil
|
||||
}
|
||||
Reference in New Issue
Block a user