feat[BE}: change get penjualan repport dto an add more params

This commit is contained in:
aguhh18
2025-12-16 21:10:48 +07:00
parent 062a7937e2
commit afe4b2ffe3
10 changed files with 741 additions and 247 deletions
@@ -245,3 +245,25 @@ func (u *ClosingController) GetSapronakByKandang(c *fiber.Ctx) error {
Data: payload,
})
}
func (u *ClosingController) GetClosingKeuangan(c *fiber.Ctx) error {
param := c.Params("project_flock_id")
projectFlockID, err := strconv.Atoi(param)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, "Invalid Project Flock Id")
}
result, err := u.ClosingService.GetClosingKeuangan(c, uint(projectFlockID))
if err != nil {
return err
}
return c.Status(fiber.StatusOK).
JSON(response.Success{
Code: fiber.StatusOK,
Status: "success",
Message: "Get closing keuangan successfully",
Data: result,
})
}
@@ -0,0 +1,186 @@
package dto
// === BASE METRICS ===
type FinancialMetrics struct {
RpPerBird float64 `json:"rp_per_bird"`
RpPerKg float64 `json:"rp_per_kg"`
Amount float64 `json:"amount"`
}
type Comparison struct {
Budgeting FinancialMetrics `json:"budgeting"`
Realization FinancialMetrics `json:"realization"`
}
// === HPP PURCHASES PACKAGE ===
type HppItem struct {
Type string `json:"type"`
Comparison
}
type HppGroup struct {
GroupName string `json:"group_name"`
Data []HppItem `json:"data"`
}
type SummaryHpp struct {
Label string `json:"label"`
Comparison
}
// Ini adalah struct mandiri untuk bagian HPP Purchases
type HppPurchasesSection struct {
Title string `json:"title"`
Hpp []HppGroup `json:"hpp"`
SummaryHpp SummaryHpp `json:"summary_hpp"`
}
// === PROFIT LOSS PACKAGE ===
type PLItem struct {
Type string `json:"type"`
FinancialMetrics
}
type PLSummaryItem struct {
Label string `json:"label"`
FinancialMetrics
}
type PLSummaryGroup struct {
GrossProfit PLSummaryItem `json:"gross_profit"`
SubTotal PLSummaryItem `json:"sub_total"`
NetProfit PLSummaryItem `json:"net_profit"`
}
type ProfitLossData struct {
Penjualan []PLItem `json:"penjualan"`
Pembelian []PLItem `json:"pembelian"`
Summary PLSummaryGroup `json:"summary"`
}
// Ini adalah struct mandiri untuk bagian Profit Loss
type ProfitLossSection struct {
Title string `json:"title"`
Data ProfitLossData `json:"data"`
}
// === RESPONSE DTO (ROOT) ===
// Sekarang Root-nya terlihat sangat bersih dan tidak "janggal" lagi
type ReportResponse struct {
HppPurchases HppPurchasesSection `json:"hpp_purchases"`
ProfitLoss ProfitLossSection `json:"profit_loss"`
}
// === MAPPER FUNCTIONS ===
// FinancialMetrics Mappers
func ToFinancialMetrics(rpPerBird, rpPerKg, amount float64) FinancialMetrics {
return FinancialMetrics{
RpPerBird: rpPerBird,
RpPerKg: rpPerKg,
Amount: amount,
}
}
// Comparison Mappers
func ToComparison(budgeting, realization FinancialMetrics) Comparison {
return Comparison{
Budgeting: budgeting,
Realization: realization,
}
}
// HppItem Mappers
func ToHppItem(itemType string, comparison Comparison) HppItem {
return HppItem{
Type: itemType,
Comparison: comparison,
}
}
// HppGroup Mappers
func ToHppGroup(groupName string, items []HppItem) HppGroup {
return HppGroup{
GroupName: groupName,
Data: items,
}
}
// SummaryHpp Mappers
func ToSummaryHpp(label string, comparison Comparison) SummaryHpp {
return SummaryHpp{
Label: label,
Comparison: comparison,
}
}
// HppPurchasesSection Mappers
func ToHppPurchasesSection(title string, hppGroups []HppGroup, summaryHpp SummaryHpp) HppPurchasesSection {
return HppPurchasesSection{
Title: title,
Hpp: hppGroups,
SummaryHpp: summaryHpp,
}
}
// PLItem Mappers
func ToPLItem(itemType string, metrics FinancialMetrics) PLItem {
return PLItem{
Type: itemType,
FinancialMetrics: metrics,
}
}
// PLSummaryItem Mappers
func ToPLSummaryItem(label string, metrics FinancialMetrics) PLSummaryItem {
return PLSummaryItem{
Label: label,
FinancialMetrics: metrics,
}
}
// PLSummaryGroup Mappers
func ToPLSummaryGroup(grossProfit, subTotal, netProfit PLSummaryItem) PLSummaryGroup {
return PLSummaryGroup{
GrossProfit: grossProfit,
SubTotal: subTotal,
NetProfit: netProfit,
}
}
// ProfitLossData Mappers
func ToProfitLossData(penjualan, pembelian []PLItem, summary PLSummaryGroup) ProfitLossData {
return ProfitLossData{
Penjualan: penjualan,
Pembelian: pembelian,
Summary: summary,
}
}
// ProfitLossSection Mappers
func ToProfitLossSection(title string, data ProfitLossData) ProfitLossSection {
return ProfitLossSection{
Title: title,
Data: data,
}
}
// ReportResponse Mappers
func ToReportResponse(hppPurchases HppPurchasesSection, profitLoss ProfitLossSection) ReportResponse {
return ReportResponse{
HppPurchases: hppPurchases,
ProfitLoss: profitLoss,
}
}
// Helper function to create a complete financial report
func BuildFinancialReport(
hppGroups []HppGroup,
summaryHpp SummaryHpp,
penjualan, pembelian []PLItem,
plSummary PLSummaryGroup,
) ReportResponse {
hppSection := ToHppPurchasesSection("HPP Pembelian", hppGroups, summaryHpp)
plSection := ToProfitLossSection("Laporan Laba Rugi", ToProfitLossData(penjualan, pembelian, plSummary))
return ToReportResponse(hppSection, plSection)
}
+2
View File
@@ -25,6 +25,8 @@ func ClosingRoutes(v1 fiber.Router, u user.UserService, s closing.ClosingService
route.Get("/:project_flock_id/overhead", ctrl.GetOverhead)
route.Get("/:project_flock_id/:project_flock_kandang_id/perhitungan_sapronak", ctrl.GetSapronakByKandang)
route.Get("/:project_flock_id/perhitungan_sapronak", ctrl.GetSapronakByProject)
route.Get("/:project_flock_id/keuangan", ctrl.GetClosingKeuangan)
route.Get("/:projectFlockId", ctrl.GetClosingSummary)
route.Get("/:projectFlockId/sapronak", ctrl.GetClosingSapronak)
}
@@ -31,6 +31,7 @@ type ClosingService interface {
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.ClosingSapronakQuery) ([]dto.ClosingSapronakItemDTO, int64, error)
GetClosingKeuangan(ctx *fiber.Ctx, projectFlockID uint) (*dto.ReportResponse, error)
}
type closingService struct {
@@ -379,3 +380,237 @@ func (s closingService) GetOverhead(c *fiber.Ctx, projectFlockID uint) (*dto.Ove
return &result, nil
}
func (s closingService) GetClosingKeuangan(c *fiber.Ctx, projectFlockID uint) (*dto.ReportResponse, error) {
if projectFlockID == 0 {
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid project flock id")
}
_, err := s.Repository.GetByID(c.Context(), projectFlockID, nil)
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, fiber.NewError(fiber.StatusNotFound, "Project flock not found")
}
if err != nil {
s.Log.Errorf("Failed to get project flock %d for closing keuangan: %+v", projectFlockID, err)
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch project flock")
}
budgets, err := s.ProjectBudgetRepo.GetByProjectFlockID(c.Context(), projectFlockID)
if err != nil {
s.Log.Errorf("Failed to get budgets for project flock %d: %+v", projectFlockID, err)
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch budgets")
}
realizations, err := s.ExpenseRealizationRepo.GetByProjectFlockID(c.Context(), projectFlockID)
if err != nil {
s.Log.Errorf("Failed to get realizations for project flock %d: %+v", projectFlockID, err)
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch realizations")
}
deliveryProducts, err := s.MarketingDeliveryProductRepo.GetDeliveryProductsByProjectFlockID(c.Context(), projectFlockID, func(db *gorm.DB) *gorm.DB {
return db.Preload("MarketingProduct").
Preload("MarketingProduct.ProductWarehouse").
Preload("MarketingProduct.ProductWarehouse.Product")
})
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
s.Log.Errorf("Failed to get delivery products for project flock %d: %+v", projectFlockID, err)
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch delivery products")
}
chickins, err := s.ChickinRepo.GetByProjectFlockID(c.Context(), projectFlockID)
if err != nil {
s.Log.Errorf("Failed to get chickins for project flock %d: %+v", projectFlockID, err)
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch chickins")
}
var totalPopulation float64
for _, chickin := range chickins {
totalPopulation += chickin.UsageQty
}
var totalWeightSold float64
for _, delivery := range deliveryProducts {
totalWeightSold += delivery.TotalWeight
}
hppItems := s.buildHppItems(budgets, realizations, totalWeightSold, totalPopulation)
hppGroups := []dto.HppGroup{
dto.ToHppGroup("Input Produksi", hppItems),
}
summaryHpp := s.calculateHppSummary(budgets, realizations, totalWeightSold, totalPopulation)
penjualanItems := s.buildPenjualanItems(deliveryProducts, totalPopulation, totalWeightSold)
pembelianItems := s.buildPembelianItems(budgets, realizations, totalPopulation, totalWeightSold)
plSummary := s.calculatePLSummary(penjualanItems, pembelianItems)
hppSection := dto.ToHppPurchasesSection("HPP Pembelian", hppGroups, summaryHpp)
plSection := dto.ToProfitLossSection("Laporan Laba Rugi", dto.ToProfitLossData(penjualanItems, pembelianItems, plSummary))
report := dto.ToReportResponse(hppSection, plSection)
return &report, nil
}
func (s closingService) buildHppItems(budgets []entity.ProjectBudget, realizations []entity.ExpenseRealization, totalWeightSold, totalPopulation float64) []dto.HppItem {
var totalBudgetAmount float64
var totalRealizationAmount float64
for _, budget := range budgets {
totalBudgetAmount += budget.Price * budget.Qty
}
for _, realization := range realizations {
totalRealizationAmount += realization.Price * realization.Qty
}
budgetRpPerBird := 0.0
budgetRpPerKg := 0.0
if totalPopulation > 0 {
budgetRpPerBird = totalBudgetAmount / totalPopulation
}
if totalWeightSold > 0 {
budgetRpPerKg = totalBudgetAmount / totalWeightSold
}
realizationRpPerBird := 0.0
realizationRpPerKg := 0.0
if totalPopulation > 0 {
realizationRpPerBird = totalRealizationAmount / totalPopulation
}
if totalWeightSold > 0 {
realizationRpPerKg = totalRealizationAmount / totalWeightSold
}
items := []dto.HppItem{
dto.ToHppItem("Total HPP Produksi", dto.ToComparison(
dto.ToFinancialMetrics(budgetRpPerBird, budgetRpPerKg, totalBudgetAmount),
dto.ToFinancialMetrics(realizationRpPerBird, realizationRpPerKg, totalRealizationAmount),
)),
}
return items
}
func (s closingService) calculateHppSummary(budgets []entity.ProjectBudget, realizations []entity.ExpenseRealization, totalWeightSold, totalPopulation float64) dto.SummaryHpp {
var totalBudget float64
var totalRealization float64
for _, budget := range budgets {
totalBudget += budget.Price * budget.Qty
}
for _, realization := range realizations {
totalRealization += realization.Price * realization.Qty
}
budgetRpPerBird := 0.0
budgetRpPerKg := 0.0
if totalPopulation > 0 {
budgetRpPerBird = totalBudget / totalPopulation
}
if totalWeightSold > 0 {
budgetRpPerKg = totalBudget / totalWeightSold
}
realizationRpPerBird := 0.0
realizationRpPerKg := 0.0
if totalPopulation > 0 {
realizationRpPerBird = totalRealization / totalPopulation
}
if totalWeightSold > 0 {
realizationRpPerKg = totalRealization / totalWeightSold
}
return dto.ToSummaryHpp("Total HPP", dto.ToComparison(
dto.ToFinancialMetrics(budgetRpPerBird, budgetRpPerKg, totalBudget),
dto.ToFinancialMetrics(realizationRpPerBird, realizationRpPerKg, totalRealization),
))
}
func (s closingService) buildPenjualanItems(deliveryProducts []entity.MarketingDeliveryProduct, totalPopulation, totalWeightSold float64) []dto.PLItem {
var totalAmount float64
for _, delivery := range deliveryProducts {
totalAmount += delivery.TotalPrice
}
rpPerBird := 0.0
rpPerKg := 0.0
if totalPopulation > 0 {
rpPerBird = totalAmount / totalPopulation
}
if totalWeightSold > 0 {
rpPerKg = totalAmount / totalWeightSold
}
items := []dto.PLItem{
dto.ToPLItem("Penjualan", dto.ToFinancialMetrics(rpPerBird, rpPerKg, totalAmount)),
}
return items
}
func (s closingService) buildPembelianItems(budgets []entity.ProjectBudget, realizations []entity.ExpenseRealization, totalPopulation, totalWeightSold float64) []dto.PLItem {
var totalBudget float64
var totalRealization float64
for _, budget := range budgets {
totalBudget += budget.Price * budget.Qty
}
for _, realization := range realizations {
totalRealization += realization.Price * realization.Qty
}
budgetRpPerBird := 0.0
budgetRpPerKg := 0.0
if totalPopulation > 0 {
budgetRpPerBird = totalBudget / totalPopulation
}
if totalWeightSold > 0 {
budgetRpPerKg = totalBudget / totalWeightSold
}
realizationRpPerBird := 0.0
realizationRpPerKg := 0.0
if totalPopulation > 0 {
realizationRpPerBird = totalRealization / totalPopulation
}
if totalWeightSold > 0 {
realizationRpPerKg = totalRealization / totalWeightSold
}
items := []dto.PLItem{
dto.ToPLItem("Beban Pokok Produksi", dto.ToFinancialMetrics(budgetRpPerBird, budgetRpPerKg, totalBudget)),
dto.ToPLItem("Realisasi Beban Pokok", dto.ToFinancialMetrics(realizationRpPerBird, realizationRpPerKg, totalRealization)),
}
return items
}
func (s closingService) calculatePLSummary(penjualanItems, pembelianItems []dto.PLItem) dto.PLSummaryGroup {
var totalPenjualan float64
var totalPenjualanPerBird float64
var totalPembelian float64
var totalPembelianPerBird float64
for _, item := range penjualanItems {
totalPenjualan += item.Amount
totalPenjualanPerBird += item.RpPerBird
}
for _, item := range pembelianItems {
totalPembelian += item.Amount
totalPembelianPerBird += item.RpPerBird
}
grossProfit := totalPenjualan - totalPembelian
grossProfitPerBird := totalPenjualanPerBird - totalPembelianPerBird
return dto.ToPLSummaryGroup(
dto.ToPLSummaryItem("Laba Kotor", dto.ToFinancialMetrics(grossProfitPerBird, 0, grossProfit)),
dto.ToPLSummaryItem("Sub Total", dto.ToFinancialMetrics(grossProfitPerBird, 0, grossProfit)),
dto.ToPLSummaryItem("Laba Bersih", dto.ToFinancialMetrics(grossProfitPerBird, 0, grossProfit)),
)
}