mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
Merge branch 'feat/BE/Sprint-6' of https://gitlab.com/mbugroup/lti-api into feat/BE/US-279/closing-produksi
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
-- Drop function and sequence for sales order numbers
|
||||
DROP FUNCTION IF EXISTS generate_so_number();
|
||||
DROP SEQUENCE IF EXISTS so_number_seq;
|
||||
@@ -0,0 +1,12 @@
|
||||
-- Create sequence for sales order numbers
|
||||
CREATE SEQUENCE so_number_seq START WITH 1 INCREMENT BY 1;
|
||||
|
||||
CREATE OR REPLACE FUNCTION generate_so_number()
|
||||
RETURNS VARCHAR AS $$
|
||||
DECLARE
|
||||
next_val INTEGER;
|
||||
BEGIN
|
||||
next_val := nextval('so_number_seq');
|
||||
RETURN 'SO-' || LPAD(next_val::TEXT, 5, '0');
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
@@ -20,5 +20,6 @@ type Kandang struct {
|
||||
CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"`
|
||||
Location Location `gorm:"foreignKey:LocationId;references:Id"`
|
||||
Pic User `gorm:"foreignKey:PicId;references:Id"`
|
||||
Warehouses []Warehouse `gorm:"foreignKey:KandangId;references:Id"`
|
||||
ProjectFlockKandangs []ProjectFlockKandang `gorm:"foreignKey:KandangId;references:Id" json:"-"`
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package controller
|
||||
import (
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/modules/closings/dto"
|
||||
service "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/services"
|
||||
@@ -39,17 +40,17 @@ func (u *ClosingController) GetAll(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).
|
||||
JSON(response.SuccessWithPaginate[dto.ClosingListDTO]{
|
||||
JSON(response.SuccessWithPaginate[dto.ClosingListItemDTO]{
|
||||
Code: fiber.StatusOK,
|
||||
Status: "success",
|
||||
Message: "Get all closings successfully",
|
||||
Message: "Retrieved closing projects list successfully",
|
||||
Meta: response.Meta{
|
||||
Page: query.Page,
|
||||
Limit: query.Limit,
|
||||
TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))),
|
||||
TotalResults: totalResults,
|
||||
},
|
||||
Data: dto.ToClosingListDTOs(result),
|
||||
Data: result,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -123,3 +124,67 @@ func (u *ClosingController) GetPenjualan(c *fiber.Ctx) error {
|
||||
Data: dto.ToPenjualanRealisasiResponseDTO(projectFlock.Category, uint(projectFlockID), result),
|
||||
})
|
||||
}
|
||||
|
||||
func (u *ClosingController) GetOverhead(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.GetOverhead(c, uint(projectFlockID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).
|
||||
JSON(response.Success{
|
||||
Code: fiber.StatusOK,
|
||||
Status: "success",
|
||||
Message: "Get overhead successfully",
|
||||
Data: result,
|
||||
})
|
||||
}
|
||||
|
||||
func (u *ClosingController) GetClosingSapronak(c *fiber.Ctx) error {
|
||||
param := c.Params("projectFlockId")
|
||||
|
||||
id, err := strconv.Atoi(param)
|
||||
if err != nil || id <= 0 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid projectFlockId")
|
||||
}
|
||||
|
||||
query := &validation.SapronakQuery{
|
||||
Type: strings.ToLower(c.Query("type")),
|
||||
Page: c.QueryInt("page", 1),
|
||||
Limit: c.QueryInt("limit", 10),
|
||||
}
|
||||
|
||||
if query.Page < 1 || query.Limit < 1 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0")
|
||||
}
|
||||
|
||||
if query.Type != validation.SapronakTypeIncoming && query.Type != validation.SapronakTypeOutgoing {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "type must be either incoming or outgoing")
|
||||
}
|
||||
|
||||
result, totalResults, err := u.ClosingService.GetClosingSapronak(c, uint(id), query)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).
|
||||
JSON(response.SuccessWithPaginate[dto.ClosingSapronakItemDTO]{
|
||||
Code: fiber.StatusOK,
|
||||
Status: "success",
|
||||
Message: "Retrieved closing report (sapronak) successfully",
|
||||
Meta: response.Meta{
|
||||
Page: query.Page,
|
||||
Limit: query.Limit,
|
||||
TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))),
|
||||
TotalResults: totalResults,
|
||||
},
|
||||
Data: result,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -27,20 +27,35 @@ type ClosingDetailDTO struct {
|
||||
ClosingListDTO
|
||||
}
|
||||
|
||||
type ClosingSummaryDTO struct {
|
||||
type ClosingListItemDTO struct {
|
||||
Id uint `json:"id"`
|
||||
LocationID uint `json:"location_id"`
|
||||
Periode int `json:"periode"`
|
||||
JenisProduk string `json:"jenis_produk"`
|
||||
LabelPopulasi string `json:"label_populasi"`
|
||||
JumlahPopulasi int `json:"jumlah_populasi"`
|
||||
JumlahPopulasiFormatted string `json:"jumlah_populasi_formatted"`
|
||||
JenisProject string `json:"jenis_project"`
|
||||
KandangAktif int `json:"kandang_aktif"`
|
||||
KandangAktifFormatted string `json:"kandang_aktif_formatted"`
|
||||
StatusPembayaranPenjualan string `json:"status_pembayaran_penjualan"`
|
||||
StatusPembayaranMitra string `json:"status_pembayaran_mitra"`
|
||||
StatusProject string `json:"status_project"`
|
||||
StatusClosing string `json:"status_closing"`
|
||||
LocationName string `json:"location_name"`
|
||||
ProjectCategory string `json:"project_category"`
|
||||
Period int `json:"period"`
|
||||
ClosingDate string `json:"closing_date"`
|
||||
ShedLabel string `json:"shed_label"`
|
||||
ShedCount int `json:"shed_count"`
|
||||
SalesPaidAmount int64 `json:"sales_paid_amount"`
|
||||
SalesRemainingAmount int64 `json:"sales_remaining_amount"`
|
||||
SalesPaymentStatus string `json:"sales_payment_status"`
|
||||
ProjectStatus string `json:"project_status"`
|
||||
}
|
||||
|
||||
type ClosingSummaryDTO struct {
|
||||
FlockID uint `json:"flock_id"`
|
||||
Period int `json:"period"`
|
||||
// JenisProduk string `json:"jenis_produk"`
|
||||
// LabelPopulasi string `json:"label_populasi"`
|
||||
Population int `json:"population"`
|
||||
PopulationFormatted string `json:"population_formatted"`
|
||||
ProjectType string `json:"project_type"`
|
||||
ActiveHouseCount int `json:"active_house_count"`
|
||||
ActiveHouseLabel string `json:"active_house_label"`
|
||||
SalesPaymentStatus string `json:"sales_payment_status"`
|
||||
// StatusPembayaranMitra string `json:"status_pembayaran_mitra"`
|
||||
StatusProject string `json:"project_status"`
|
||||
StatusClosing string `json:"closing_status"`
|
||||
}
|
||||
|
||||
func ToClosingSummaryDTO(project entity.ProjectFlock, statusProject, statusClosing string) ClosingSummaryDTO {
|
||||
@@ -52,22 +67,41 @@ func ToClosingSummaryDTO(project entity.ProjectFlock, statusProject, statusClosi
|
||||
populationInt := int(population)
|
||||
|
||||
return ClosingSummaryDTO{
|
||||
LocationID: project.LocationId,
|
||||
Periode: period,
|
||||
JenisProduk: project.Category,
|
||||
LabelPopulasi: "",
|
||||
JumlahPopulasi: populationInt,
|
||||
JumlahPopulasiFormatted: fmt.Sprintf("%d Ekor", populationInt),
|
||||
JenisProject: "",
|
||||
KandangAktif: kandangCount,
|
||||
KandangAktifFormatted: fmt.Sprintf("%d Kandang", kandangCount),
|
||||
StatusPembayaranPenjualan: "Tempo",
|
||||
StatusPembayaranMitra: "",
|
||||
FlockID: project.Id,
|
||||
Period: period,
|
||||
// JenisProduk: project.Category,
|
||||
// LabelPopulasi: "",
|
||||
Population: populationInt,
|
||||
PopulationFormatted: fmt.Sprintf("%d Ekor", populationInt),
|
||||
ProjectType: project.Category,
|
||||
ActiveHouseCount: kandangCount,
|
||||
ActiveHouseLabel: fmt.Sprintf("%d Kandang", kandangCount),
|
||||
SalesPaymentStatus: "Tempo",
|
||||
// StatusPembayaranMitra: "",
|
||||
StatusProject: statusProject,
|
||||
StatusClosing: statusClosing,
|
||||
}
|
||||
}
|
||||
|
||||
func ToClosingListItemDTO(project entity.ProjectFlock, projectStatus string) ClosingListItemDTO {
|
||||
shedCount := len(project.KandangHistory)
|
||||
|
||||
return ClosingListItemDTO{
|
||||
Id: project.Id,
|
||||
LocationID: project.LocationId,
|
||||
LocationName: project.Location.Name,
|
||||
ProjectCategory: project.Category,
|
||||
Period: maxPeriod(project.KandangHistory),
|
||||
ClosingDate: "17-Nov-2025",
|
||||
ShedLabel: fmt.Sprintf("%d Kandang", shedCount),
|
||||
ShedCount: shedCount,
|
||||
SalesPaidAmount: 21993726,
|
||||
SalesRemainingAmount: 11075919,
|
||||
SalesPaymentStatus: "Lunas",
|
||||
ProjectStatus: projectStatus,
|
||||
}
|
||||
}
|
||||
|
||||
func maxPeriod(history []entity.ProjectFlockKandang) int {
|
||||
max := 0
|
||||
for _, h := range history {
|
||||
|
||||
@@ -4,14 +4,13 @@ import (
|
||||
"time"
|
||||
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
deliveryOrdersDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/delivery-orderss/dto"
|
||||
deliveryOrdersDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/dto"
|
||||
customerDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/customers/dto"
|
||||
kandangDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/dto"
|
||||
productDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/products/dto"
|
||||
)
|
||||
|
||||
// === Response DTO ===
|
||||
|
||||
type SalesDTO struct {
|
||||
Id uint `json:"id"`
|
||||
RealizationDate time.Time `json:"realization_date"`
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
)
|
||||
|
||||
// === DTO Structs ===
|
||||
|
||||
type OverheadDTO struct {
|
||||
ItemName string `json:"item_name"`
|
||||
UOMName string `json:"uom_name"`
|
||||
BudgetQuantity float64 `json:"budget_quantity"`
|
||||
BudgetUnitPrice float64 `json:"budget_unit_price"`
|
||||
BudgetTotalAmount float64 `json:"budget_total_amount"`
|
||||
ActualDate string `json:"actual_date"`
|
||||
ActualQuantity float64 `json:"actual_quantity"`
|
||||
ActualUnitPrice float64 `json:"actual_unit_price"`
|
||||
ActualTotalAmount float64 `json:"actual_total_amount"`
|
||||
CostPerBird float64 `json:"cost_per_bird"`
|
||||
}
|
||||
|
||||
type TotalDTO struct {
|
||||
BudgetQuantity float64 `json:"budget_quantity"`
|
||||
BudgetTotalAmount float64 `json:"budget_total_amount"`
|
||||
ActualQuantity float64 `json:"actual_quantity"`
|
||||
ActualTotalAmount float64 `json:"actual_total_amount"`
|
||||
CostPerBird float64 `json:"cost_per_bird"`
|
||||
}
|
||||
|
||||
type OverheadListDTO struct {
|
||||
Total TotalDTO `json:"total"`
|
||||
Overheads []OverheadDTO `json:"overheads"`
|
||||
}
|
||||
|
||||
// === Mapper Functions ===
|
||||
|
||||
func ToOverheadDTO(budget *entity.ProjectBudget, realization *entity.ExpenseRealization) OverheadDTO {
|
||||
if budget == nil && realization == nil {
|
||||
return OverheadDTO{}
|
||||
}
|
||||
|
||||
var itemName, itemUOM string
|
||||
if budget != nil {
|
||||
itemName, itemUOM = getItemInfo(budget.Nonstock)
|
||||
}
|
||||
|
||||
if itemName == "" && realization != nil && realization.ExpenseNonstock != nil {
|
||||
itemName, itemUOM = getItemInfo(realization.ExpenseNonstock.Nonstock)
|
||||
}
|
||||
|
||||
dto := OverheadDTO{
|
||||
ItemName: itemName,
|
||||
UOMName: itemUOM,
|
||||
}
|
||||
|
||||
if budget != nil {
|
||||
dto.BudgetQuantity = budget.Qty
|
||||
dto.BudgetUnitPrice = budget.Price
|
||||
dto.BudgetTotalAmount = calculateTotal(budget.Qty, budget.Price)
|
||||
}
|
||||
|
||||
if realization != nil {
|
||||
dto.ActualQuantity = realization.Qty
|
||||
dto.ActualUnitPrice = realization.Price
|
||||
dto.ActualTotalAmount = calculateTotal(realization.Qty, realization.Price)
|
||||
dto.ActualDate = formatRealizationDate(realization)
|
||||
}
|
||||
|
||||
return dto
|
||||
}
|
||||
|
||||
func ToOverheadListDTOs(budgets []entity.ProjectBudget, realizations []entity.ExpenseRealization, totalChickinQty float64) OverheadListDTO {
|
||||
overheadsByNonstockID := make(map[uint]*OverheadDTO)
|
||||
latestDateByNonstockID := make(map[uint]string)
|
||||
|
||||
for i := range budgets {
|
||||
nonstockID := budgets[i].NonstockId
|
||||
if overheadsByNonstockID[nonstockID] == nil {
|
||||
overheadsByNonstockID[nonstockID] = &OverheadDTO{}
|
||||
}
|
||||
|
||||
itemName, itemUOM := getItemInfo(budgets[i].Nonstock)
|
||||
overheadsByNonstockID[nonstockID].ItemName = itemName
|
||||
overheadsByNonstockID[nonstockID].UOMName = itemUOM
|
||||
overheadsByNonstockID[nonstockID].BudgetQuantity = budgets[i].Qty
|
||||
overheadsByNonstockID[nonstockID].BudgetUnitPrice = budgets[i].Price
|
||||
overheadsByNonstockID[nonstockID].BudgetTotalAmount = calculateTotal(budgets[i].Qty, budgets[i].Price)
|
||||
}
|
||||
|
||||
for i := range realizations {
|
||||
if realizations[i].ExpenseNonstock == nil || realizations[i].ExpenseNonstock.NonstockId == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
nonstockID := uint(*realizations[i].ExpenseNonstock.NonstockId)
|
||||
if overheadsByNonstockID[nonstockID] == nil {
|
||||
overheadsByNonstockID[nonstockID] = &OverheadDTO{}
|
||||
}
|
||||
|
||||
overheadsByNonstockID[nonstockID].ActualQuantity += realizations[i].Qty
|
||||
overheadsByNonstockID[nonstockID].ActualTotalAmount += calculateTotal(realizations[i].Qty, realizations[i].Price)
|
||||
|
||||
if overheadsByNonstockID[nonstockID].ItemName == "" {
|
||||
itemName, itemUOM := getItemInfo(realizations[i].ExpenseNonstock.Nonstock)
|
||||
overheadsByNonstockID[nonstockID].ItemName = itemName
|
||||
overheadsByNonstockID[nonstockID].UOMName = itemUOM
|
||||
}
|
||||
|
||||
realizationDateStr := formatRealizationDate(&realizations[i])
|
||||
if realizationDateStr != "" {
|
||||
if latestDateByNonstockID[nonstockID] == "" || realizationDateStr > latestDateByNonstockID[nonstockID] {
|
||||
latestDateByNonstockID[nonstockID] = realizationDateStr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var totalBudgetQuantity, totalBudgetAmount, totalActualQuantity, totalActualAmount float64
|
||||
overheadItems := make([]OverheadDTO, 0, len(overheadsByNonstockID))
|
||||
|
||||
for nonstockID, overhead := range overheadsByNonstockID {
|
||||
overhead.ActualDate = latestDateByNonstockID[nonstockID]
|
||||
overhead.CostPerBird = calculateCostPerBird(overhead.ActualTotalAmount, totalChickinQty)
|
||||
|
||||
if overhead.ActualQuantity > 0 {
|
||||
overhead.ActualUnitPrice = overhead.ActualTotalAmount / overhead.ActualQuantity
|
||||
}
|
||||
|
||||
totalBudgetQuantity += overhead.BudgetQuantity
|
||||
totalBudgetAmount += overhead.BudgetTotalAmount
|
||||
totalActualQuantity += overhead.ActualQuantity
|
||||
totalActualAmount += overhead.ActualTotalAmount
|
||||
|
||||
overheadItems = append(overheadItems, *overhead)
|
||||
}
|
||||
|
||||
return OverheadListDTO{
|
||||
Total: TotalDTO{
|
||||
BudgetQuantity: totalBudgetQuantity,
|
||||
BudgetTotalAmount: totalBudgetAmount,
|
||||
ActualQuantity: totalActualQuantity,
|
||||
ActualTotalAmount: totalActualAmount,
|
||||
CostPerBird: calculateCostPerBird(totalActualAmount, totalChickinQty),
|
||||
},
|
||||
Overheads: overheadItems,
|
||||
}
|
||||
}
|
||||
|
||||
// === Helper Functions ===
|
||||
|
||||
func getItemInfo(nonstock *entity.Nonstock) (string, string) {
|
||||
if nonstock != nil && nonstock.Id != 0 {
|
||||
return nonstock.Name, nonstock.Uom.Name
|
||||
}
|
||||
return "", ""
|
||||
}
|
||||
|
||||
func calculateTotal(qty, price float64) float64 {
|
||||
return qty * price
|
||||
}
|
||||
|
||||
func calculateCostPerBird(totalPrice, totalChickinQty float64) float64 {
|
||||
if totalChickinQty > 0 {
|
||||
return totalPrice / totalChickinQty
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func formatRealizationDate(realization *entity.ExpenseRealization) string {
|
||||
if realization != nil && realization.ExpenseNonstock != nil && realization.ExpenseNonstock.Expense != nil {
|
||||
if !realization.ExpenseNonstock.Expense.RealizationDate.IsZero() {
|
||||
return realization.ExpenseNonstock.Expense.RealizationDate.Format("2006-01-02T15:04:05Z07:00")
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package dto
|
||||
|
||||
import "time"
|
||||
|
||||
type ClosingSapronakItemDTO struct {
|
||||
Id uint64 `json:"id"`
|
||||
Date string `json:"date"`
|
||||
ReferenceNumber string `json:"reference_number"`
|
||||
TransactionType string `json:"transaction_type"`
|
||||
ProductName string `json:"product_name"`
|
||||
ProductCategory string `json:"product_category"`
|
||||
ProductSubCategory string `json:"product_sub_category"`
|
||||
SourceWarehouse string `json:"source_warehouse"`
|
||||
DestinationWarehouse string `json:"destination_warehouse,omitempty"`
|
||||
// Destination string `json:"destination,omitempty"`
|
||||
Quantity float64 `json:"quantity"`
|
||||
Unit string `json:"unit"`
|
||||
FormattedQuantity string `json:"formatted_quantity"`
|
||||
Notes string `json:"notes"`
|
||||
SortDate time.Time `json:"-"`
|
||||
}
|
||||
|
||||
type ClosingSapronakDTO struct {
|
||||
IncomingSapronak []ClosingSapronakItemDTO `json:"incoming_sapronak"`
|
||||
OutgoingSapronak []ClosingSapronakItemDTO `json:"outgoing_sapronak"`
|
||||
}
|
||||
@@ -9,7 +9,9 @@ import (
|
||||
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
||||
rClosing "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/repositories"
|
||||
sClosing "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/services"
|
||||
rMarketings "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/repositories"
|
||||
rExpenseRealization "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/repositories"
|
||||
rMarketings "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/repositories"
|
||||
rChickin "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/repositories"
|
||||
rProjectFlock "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
|
||||
|
||||
rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories"
|
||||
@@ -22,12 +24,15 @@ func (ClosingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *
|
||||
closingRepo := rClosing.NewClosingRepository(db)
|
||||
userRepo := rUser.NewUserRepository(db)
|
||||
projectFlockRepo := rProjectFlock.NewProjectflockRepository(db)
|
||||
projectBudgetRepo := rProjectFlock.NewProjectBudgetRepository(db)
|
||||
marketingRepo := rMarketings.NewMarketingRepository(db)
|
||||
marketingDeliveryProductRepo := rMarketings.NewMarketingDeliveryProductRepository(db)
|
||||
expenseRealizationRepo := rExpenseRealization.NewExpenseRealizationRepository(db)
|
||||
chickinRepo := rChickin.NewChickinRepository(db)
|
||||
approvalRepo := commonRepo.NewApprovalRepository(db)
|
||||
approvalService := commonSvc.NewApprovalService(approvalRepo)
|
||||
|
||||
closingService := sClosing.NewClosingService(closingRepo, projectFlockRepo, marketingRepo, marketingDeliveryProductRepo, approvalService, validate)
|
||||
closingService := sClosing.NewClosingService(closingRepo, projectFlockRepo, marketingRepo, marketingDeliveryProductRepo, approvalService, expenseRealizationRepo, projectBudgetRepo, chickinRepo, validate)
|
||||
userService := sUser.NewUserService(userRepo, validate)
|
||||
|
||||
ClosingRoutes(router, userService, closingService)
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/validations"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type ClosingRepository interface {
|
||||
repository.BaseRepository[entity.ProjectFlock]
|
||||
GetSapronak(ctx context.Context, params SapronakQueryParams) ([]SapronakRow, int64, error)
|
||||
}
|
||||
|
||||
type ClosingRepositoryImpl struct {
|
||||
@@ -19,3 +26,199 @@ func NewClosingRepository(db *gorm.DB) ClosingRepository {
|
||||
BaseRepositoryImpl: repository.NewBaseRepository[entity.ProjectFlock](db),
|
||||
}
|
||||
}
|
||||
|
||||
type SapronakRow struct {
|
||||
Id uint64 `gorm:"column:id"`
|
||||
SortDate time.Time `gorm:"column:sort_date"`
|
||||
DateText string `gorm:"column:date_text"`
|
||||
ReferenceNumber string `gorm:"column:reference_number"`
|
||||
TransactionType string `gorm:"column:transaction_type"`
|
||||
ProductName string `gorm:"column:product_name"`
|
||||
ProductCategory string `gorm:"column:product_category"`
|
||||
ProductSubCategory string `gorm:"column:product_sub_category"`
|
||||
SourceWarehouse string `gorm:"column:source_warehouse"`
|
||||
DestinationWarehouse string `gorm:"column:destination_warehouse"`
|
||||
Destination string `gorm:"column:destination"`
|
||||
Quantity float64 `gorm:"column:quantity"`
|
||||
Unit string `gorm:"column:unit"`
|
||||
Notes string `gorm:"column:notes"`
|
||||
}
|
||||
|
||||
type SapronakQueryParams struct {
|
||||
Type string
|
||||
WarehouseIDs []uint
|
||||
ProjectFlockKandangIDs []uint
|
||||
Limit int
|
||||
Offset int
|
||||
}
|
||||
|
||||
func (r *ClosingRepositoryImpl) GetSapronak(ctx context.Context, params SapronakQueryParams) ([]SapronakRow, int64, error) {
|
||||
db := r.DB().WithContext(ctx)
|
||||
|
||||
var (
|
||||
unionParts []string
|
||||
args []any
|
||||
)
|
||||
|
||||
switch params.Type {
|
||||
case validation.SapronakTypeIncoming:
|
||||
if len(params.WarehouseIDs) == 0 {
|
||||
return []SapronakRow{}, 0, nil
|
||||
}
|
||||
unionParts = append(unionParts, sapronakIncomingPurchasesSQL, sapronakIncomingTransfersSQL)
|
||||
args = append(args, params.WarehouseIDs, params.WarehouseIDs)
|
||||
case validation.SapronakTypeOutgoing:
|
||||
if len(params.WarehouseIDs) > 0 {
|
||||
unionParts = append(unionParts, sapronakOutgoingTransfersSQL)
|
||||
args = append(args, params.WarehouseIDs)
|
||||
}
|
||||
if len(params.ProjectFlockKandangIDs) > 0 {
|
||||
unionParts = append(unionParts, sapronakOutgoingMarketingsSQL)
|
||||
args = append(args, params.ProjectFlockKandangIDs)
|
||||
}
|
||||
if len(unionParts) == 0 {
|
||||
return []SapronakRow{}, 0, nil
|
||||
}
|
||||
default:
|
||||
return nil, 0, fmt.Errorf("invalid sapronak type: %s", params.Type)
|
||||
}
|
||||
|
||||
unionSQL := strings.Join(unionParts, " UNION ALL ")
|
||||
|
||||
var totalResults int64
|
||||
countSQL := fmt.Sprintf("SELECT COUNT(*) FROM (%s) AS combined", unionSQL)
|
||||
if err := db.Raw(countSQL, args...).Scan(&totalResults).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
dataArgs := append(append([]any{}, args...), params.Limit, params.Offset)
|
||||
dataSQL := fmt.Sprintf("SELECT * FROM (%s) AS combined ORDER BY sort_date ASC, id ASC LIMIT ? OFFSET ?", unionSQL)
|
||||
|
||||
var rows []SapronakRow
|
||||
if err := db.Raw(dataSQL, dataArgs...).Scan(&rows).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return rows, totalResults, nil
|
||||
}
|
||||
|
||||
const (
|
||||
sapronakIncomingPurchasesSQL = `
|
||||
SELECT
|
||||
CAST(pi.id AS BIGINT) AS id,
|
||||
COALESCE(pi.received_date, '1970-01-01') AS sort_date,
|
||||
COALESCE(TO_CHAR(pi.received_date, 'DD-Mon-YYYY'), '') AS date_text,
|
||||
COALESCE(p.po_number, '') AS reference_number,
|
||||
'Purchase' AS transaction_type,
|
||||
prod.name AS product_name,
|
||||
pc.name AS product_category,
|
||||
COALESCE((
|
||||
SELECT string_agg(f.name, ' ')
|
||||
FROM flags f
|
||||
WHERE f.flagable_type = 'products' AND f.flagable_id = prod.id
|
||||
), '') AS product_sub_category,
|
||||
'External Supplier' AS source_warehouse,
|
||||
w.name AS destination_warehouse,
|
||||
'' AS destination,
|
||||
pi.total_qty AS quantity,
|
||||
u.name AS unit,
|
||||
COALESCE(p.notes, '') AS notes
|
||||
FROM purchase_items pi
|
||||
JOIN purchases p ON p.id = pi.purchase_id
|
||||
JOIN products prod ON prod.id = pi.product_id
|
||||
JOIN product_categories pc ON pc.id = prod.product_category_id
|
||||
JOIN uoms u ON u.id = prod.uom_id
|
||||
JOIN warehouses w ON w.id = pi.warehouse_id
|
||||
WHERE pi.warehouse_id IN ?
|
||||
`
|
||||
|
||||
sapronakIncomingTransfersSQL = `
|
||||
SELECT
|
||||
CAST(st.id AS BIGINT) AS id,
|
||||
st.transfer_date AS sort_date,
|
||||
TO_CHAR(st.transfer_date, 'DD-Mon-YYYY') AS date_text,
|
||||
st.movement_number AS reference_number,
|
||||
'Internal Transfer In' AS transaction_type,
|
||||
prod.name AS product_name,
|
||||
pc.name AS product_category,
|
||||
COALESCE((
|
||||
SELECT string_agg(f.name, ' ')
|
||||
FROM flags f
|
||||
WHERE f.flagable_type = 'products' AND f.flagable_id = prod.id
|
||||
), '') AS product_sub_category,
|
||||
COALESCE(fw.name, '') AS source_warehouse,
|
||||
COALESCE(tw.name, '') AS destination_warehouse,
|
||||
'' AS destination,
|
||||
std.quantity AS quantity,
|
||||
u.name AS unit,
|
||||
'Stock Refill' AS notes
|
||||
FROM stock_transfer_details std
|
||||
JOIN stock_transfers st ON st.id = std.stock_transfer_id
|
||||
LEFT JOIN warehouses fw ON fw.id = st.from_warehouse_id
|
||||
LEFT JOIN warehouses tw ON tw.id = st.to_warehouse_id
|
||||
JOIN products prod ON prod.id = std.product_id
|
||||
JOIN product_categories pc ON pc.id = prod.product_category_id
|
||||
JOIN uoms u ON u.id = prod.uom_id
|
||||
WHERE st.to_warehouse_id IN ?
|
||||
`
|
||||
|
||||
sapronakOutgoingTransfersSQL = `
|
||||
SELECT
|
||||
CAST(st.id AS BIGINT) AS id,
|
||||
st.transfer_date AS sort_date,
|
||||
TO_CHAR(st.transfer_date, 'DD-Mon-YYYY') AS date_text,
|
||||
st.movement_number AS reference_number,
|
||||
'Internal Transfer Out' AS transaction_type,
|
||||
prod.name AS product_name,
|
||||
pc.name AS product_category,
|
||||
COALESCE((
|
||||
SELECT string_agg(f.name, ' ')
|
||||
FROM flags f
|
||||
WHERE f.flagable_type = 'products' AND f.flagable_id = prod.id
|
||||
), '') AS product_sub_category,
|
||||
COALESCE(fw.name, '') AS source_warehouse,
|
||||
'' AS destination_warehouse,
|
||||
COALESCE(tw.name, '') AS destination,
|
||||
std.quantity AS quantity,
|
||||
u.name AS unit,
|
||||
'Transfer to other unit' AS notes
|
||||
FROM stock_transfer_details std
|
||||
JOIN stock_transfers st ON st.id = std.stock_transfer_id
|
||||
LEFT JOIN warehouses fw ON fw.id = st.from_warehouse_id
|
||||
LEFT JOIN warehouses tw ON tw.id = st.to_warehouse_id
|
||||
JOIN products prod ON prod.id = std.product_id
|
||||
JOIN product_categories pc ON pc.id = prod.product_category_id
|
||||
JOIN uoms u ON u.id = prod.uom_id
|
||||
WHERE st.from_warehouse_id IN ?
|
||||
`
|
||||
|
||||
sapronakOutgoingMarketingsSQL = `
|
||||
SELECT
|
||||
CAST(mp.id AS BIGINT) AS id,
|
||||
m.so_date AS sort_date,
|
||||
TO_CHAR(m.so_date, 'DD-Mon-YYYY') AS date_text,
|
||||
m.so_number AS reference_number,
|
||||
'Trading Sales' AS transaction_type,
|
||||
prod.name AS product_name,
|
||||
pc.name AS product_category,
|
||||
COALESCE((
|
||||
SELECT string_agg(f.name, ' ')
|
||||
FROM flags f
|
||||
WHERE f.flagable_type = 'products' AND f.flagable_id = prod.id
|
||||
), '') AS product_sub_category,
|
||||
w.name AS source_warehouse,
|
||||
'' AS destination_warehouse,
|
||||
'RETAIL CUSTOMER' AS destination,
|
||||
mp.qty AS quantity,
|
||||
u.name AS unit,
|
||||
m.notes AS notes
|
||||
FROM marketing_products mp
|
||||
JOIN marketings m ON m.id = mp.marketing_id
|
||||
JOIN product_warehouses pw ON pw.id = mp.product_warehouse_id
|
||||
JOIN products prod ON prod.id = pw.product_id
|
||||
JOIN product_categories pc ON pc.id = prod.product_category_id
|
||||
JOIN uoms u ON u.id = prod.uom_id
|
||||
JOIN warehouses w ON w.id = pw.warehouse_id
|
||||
WHERE pw.project_flock_kandang_id IN ?
|
||||
`
|
||||
)
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
func ClosingRoutes(v1 fiber.Router, u user.UserService, s closing.ClosingService) {
|
||||
ctrl := controller.NewClosingController(s)
|
||||
|
||||
route := v1.Group("/closing")
|
||||
route := v1.Group("/closings")
|
||||
|
||||
// route.Get("/", m.Auth(u), ctrl.GetAll)
|
||||
// route.Post("/", m.Auth(u), ctrl.CreateOne)
|
||||
@@ -22,5 +22,7 @@ func ClosingRoutes(v1 fiber.Router, u user.UserService, s closing.ClosingService
|
||||
|
||||
route.Get("/", ctrl.GetAll)
|
||||
route.Get("/:project_flock_id/penjualan", ctrl.GetPenjualan)
|
||||
route.Get("/:project_flock_id/overhead", ctrl.GetOverhead)
|
||||
route.Get("/:projectFlockId", ctrl.GetClosingSummary)
|
||||
route.Get("/:projectFlockId/sapronak", ctrl.GetClosingSapronak)
|
||||
}
|
||||
|
||||
@@ -3,14 +3,17 @@ package service
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strconv"
|
||||
|
||||
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
||||
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"
|
||||
marketingDeliveryProductRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/repositories"
|
||||
marketingRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/repositories"
|
||||
expenseRealizationRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/repositories"
|
||||
marketingDeliveryProductRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/repositories"
|
||||
marketingRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/repositories"
|
||||
chickinRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/repositories"
|
||||
projectflockRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||
approvalutils "gitlab.com/mbugroup/lti-api.git/internal/utils/approvals"
|
||||
@@ -22,10 +25,12 @@ import (
|
||||
)
|
||||
|
||||
type ClosingService interface {
|
||||
GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.ProjectFlock, int64, error)
|
||||
GetAll(ctx *fiber.Ctx, params *validation.Query) ([]dto.ClosingListItemDTO, int64, error)
|
||||
GetProjectFlockByID(ctx *fiber.Ctx, id uint) (*entity.ProjectFlock, error)
|
||||
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)
|
||||
}
|
||||
|
||||
type closingService struct {
|
||||
@@ -36,9 +41,12 @@ type closingService struct {
|
||||
MarketingRepo marketingRepository.MarketingRepository
|
||||
MarketingDeliveryProductRepo marketingDeliveryProductRepository.MarketingDeliveryProductRepository
|
||||
ApprovalSvc commonSvc.ApprovalService
|
||||
ExpenseRealizationRepo expenseRealizationRepository.ExpenseRealizationRepository
|
||||
ProjectBudgetRepo projectflockRepository.ProjectBudgetRepository
|
||||
ChickinRepo chickinRepository.ProjectChickinRepository
|
||||
}
|
||||
|
||||
func NewClosingService(repo repository.ClosingRepository, projectFlockRepo projectflockRepository.ProjectflockRepository, marketingRepo marketingRepository.MarketingRepository, marketingDeliveryProductRepo marketingDeliveryProductRepository.MarketingDeliveryProductRepository, approvalSvc commonSvc.ApprovalService, validate *validator.Validate) ClosingService {
|
||||
func NewClosingService(repo repository.ClosingRepository, projectFlockRepo projectflockRepository.ProjectflockRepository, marketingRepo marketingRepository.MarketingRepository, marketingDeliveryProductRepo marketingDeliveryProductRepository.MarketingDeliveryProductRepository, approvalSvc commonSvc.ApprovalService, expenseRealizationRepo expenseRealizationRepository.ExpenseRealizationRepository, projectBudgetRepo projectflockRepository.ProjectBudgetRepository, chickinRepo chickinRepository.ProjectChickinRepository, validate *validator.Validate) ClosingService {
|
||||
return &closingService{
|
||||
Log: utils.Log,
|
||||
Validate: validate,
|
||||
@@ -47,6 +55,9 @@ func NewClosingService(repo repository.ClosingRepository, projectFlockRepo proje
|
||||
MarketingRepo: marketingRepo,
|
||||
MarketingDeliveryProductRepo: marketingDeliveryProductRepo,
|
||||
ApprovalSvc: approvalSvc,
|
||||
ExpenseRealizationRepo: expenseRealizationRepo,
|
||||
ProjectBudgetRepo: projectBudgetRepo,
|
||||
ChickinRepo: chickinRepo,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,11 +67,12 @@ func (s closingService) withRelations(db *gorm.DB) *gorm.DB {
|
||||
|
||||
func (s closingService) withClosingRelations(db *gorm.DB) *gorm.DB {
|
||||
return s.withRelations(db).
|
||||
Preload("Location").
|
||||
Preload("KandangHistory").
|
||||
Preload("KandangHistory.Chickins")
|
||||
}
|
||||
|
||||
func (s closingService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.ProjectFlock, int64, error) {
|
||||
func (s closingService) GetAll(c *fiber.Ctx, params *validation.Query) ([]dto.ClosingListItemDTO, int64, error) {
|
||||
if err := s.Validate.Struct(params); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
@@ -68,9 +80,9 @@ func (s closingService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity
|
||||
offset := (params.Page - 1) * params.Limit
|
||||
|
||||
closings, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB {
|
||||
db = s.withRelations(db)
|
||||
db = s.withClosingRelations(db)
|
||||
if params.Search != "" {
|
||||
return db.Where("name LIKE ?", "%"+params.Search+"%")
|
||||
return db.Where("flock_name LIKE ?", "%"+params.Search+"%")
|
||||
}
|
||||
return db.Order("created_at DESC").Order("updated_at DESC")
|
||||
})
|
||||
@@ -79,7 +91,19 @@ func (s closingService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity
|
||||
s.Log.Errorf("Failed to get closings: %+v", err)
|
||||
return nil, 0, err
|
||||
}
|
||||
return closings, total, nil
|
||||
|
||||
result := make([]dto.ClosingListItemDTO, 0, len(closings))
|
||||
for _, closing := range closings {
|
||||
statusProject, _, err := s.getApprovalStatuses(c.Context(), closing.Id)
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to retrieve approval statuses for project flock %d: %+v", closing.Id, err)
|
||||
return nil, 0, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch approval status")
|
||||
}
|
||||
|
||||
result = append(result, dto.ToClosingListItemDTO(closing, statusProject))
|
||||
}
|
||||
|
||||
return result, total, nil
|
||||
}
|
||||
|
||||
func (s closingService) GetProjectFlockByID(c *fiber.Ctx, id uint) (*entity.ProjectFlock, error) {
|
||||
@@ -144,6 +168,147 @@ 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) {
|
||||
if projectFlockID == 0 {
|
||||
return nil, 0, fiber.NewError(fiber.StatusBadRequest, "Invalid project flock id")
|
||||
}
|
||||
|
||||
if params == nil {
|
||||
params = &validation.SapronakQuery{}
|
||||
}
|
||||
|
||||
if params.Page == 0 {
|
||||
params.Page = 1
|
||||
}
|
||||
if params.Limit == 0 {
|
||||
params.Limit = 10
|
||||
}
|
||||
|
||||
if err := s.Validate.Struct(params); err != nil {
|
||||
return nil, 0, fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
if params.Type != validation.SapronakTypeIncoming && params.Type != validation.SapronakTypeOutgoing {
|
||||
return nil, 0, fiber.NewError(fiber.StatusBadRequest, "type must be either incoming or outgoing")
|
||||
}
|
||||
|
||||
if _, err := s.Repository.GetByID(c.Context(), projectFlockID, nil); err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, 0, fiber.NewError(fiber.StatusNotFound, "Project flock tidak ditemukan")
|
||||
}
|
||||
s.Log.Errorf("Failed get project flock %d for sapronak closing: %+v", projectFlockID, err)
|
||||
return nil, 0, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch project flock")
|
||||
}
|
||||
|
||||
warehouseIDs, err := s.getWarehouseIDsByProjectFlock(c.Context(), projectFlockID)
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to fetch warehouses for project flock %d: %+v", projectFlockID, err)
|
||||
return nil, 0, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch warehouses for project flock")
|
||||
}
|
||||
|
||||
var projectFlockKandangIDs []uint
|
||||
if params.Type == validation.SapronakTypeOutgoing {
|
||||
projectFlockKandangIDs, err = s.getProjectFlockKandangIDs(c.Context(), projectFlockID)
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to fetch project flock kandang IDs for project flock %d: %+v", projectFlockID, err)
|
||||
return nil, 0, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch project flock kandang")
|
||||
}
|
||||
}
|
||||
|
||||
offset := (params.Page - 1) * params.Limit
|
||||
rows, totalResults, err := s.Repository.GetSapronak(c.Context(), repository.SapronakQueryParams{
|
||||
Type: params.Type,
|
||||
WarehouseIDs: warehouseIDs,
|
||||
ProjectFlockKandangIDs: projectFlockKandangIDs,
|
||||
Limit: params.Limit,
|
||||
Offset: offset,
|
||||
})
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to fetch sapronak %s for project flock %d: %+v", params.Type, projectFlockID, err)
|
||||
return nil, 0, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch sapronak data")
|
||||
}
|
||||
|
||||
items := make([]dto.ClosingSapronakItemDTO, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
dateStr := row.DateText
|
||||
if dateStr == "" && !row.SortDate.IsZero() {
|
||||
dateStr = row.SortDate.Format("02-Jan-2006")
|
||||
}
|
||||
items = append(items, dto.ClosingSapronakItemDTO{
|
||||
Id: row.Id,
|
||||
Date: dateStr,
|
||||
ReferenceNumber: row.ReferenceNumber,
|
||||
TransactionType: row.TransactionType,
|
||||
ProductName: row.ProductName,
|
||||
ProductCategory: row.ProductCategory,
|
||||
ProductSubCategory: row.ProductSubCategory,
|
||||
SourceWarehouse: row.SourceWarehouse,
|
||||
DestinationWarehouse: row.DestinationWarehouse,
|
||||
// Destination: row.Destination,
|
||||
Quantity: row.Quantity,
|
||||
Unit: row.Unit,
|
||||
FormattedQuantity: formatQuantity(row.Quantity, row.Unit),
|
||||
Notes: row.Notes,
|
||||
SortDate: row.SortDate,
|
||||
})
|
||||
}
|
||||
|
||||
return items, totalResults, nil
|
||||
}
|
||||
|
||||
func (s closingService) getWarehouseIDsByProjectFlock(ctx context.Context, projectFlockID uint) ([]uint, error) {
|
||||
var kandangIDs []uint
|
||||
db := s.Repository.DB().WithContext(ctx)
|
||||
|
||||
if err := db.Model(&entity.ProjectFlockKandang{}).
|
||||
Where("project_flock_id = ?", projectFlockID).
|
||||
Pluck("kandang_id", &kandangIDs).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(kandangIDs) == 0 {
|
||||
return []uint{}, nil
|
||||
}
|
||||
|
||||
var warehouses []entity.Warehouse
|
||||
if err := db.Where("kandang_id IN ?", kandangIDs).Find(&warehouses).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
unique := make(map[uint]struct{})
|
||||
for _, warehouse := range warehouses {
|
||||
unique[warehouse.Id] = struct{}{}
|
||||
}
|
||||
|
||||
ids := make([]uint, 0, len(unique))
|
||||
for id := range unique {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
func (s closingService) getProjectFlockKandangIDs(ctx context.Context, projectFlockID uint) ([]uint, error) {
|
||||
var ids []uint
|
||||
err := s.Repository.DB().WithContext(ctx).
|
||||
Model(&entity.ProjectFlockKandang{}).
|
||||
Where("project_flock_id = ?", projectFlockID).
|
||||
Pluck("id", &ids).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
func formatQuantity(qty float64, uom string) string {
|
||||
qtyStr := strconv.FormatFloat(qty, 'f', -1, 64)
|
||||
if uom == "" {
|
||||
return qtyStr
|
||||
}
|
||||
return qtyStr + " " + uom
|
||||
}
|
||||
|
||||
func (s closingService) getApprovalStatuses(ctx context.Context, projectFlockID uint) (string, string, error) {
|
||||
if s.ApprovalSvc == nil {
|
||||
return "", "Belum Selesai", nil
|
||||
@@ -188,3 +353,29 @@ func (s closingService) getApprovalStatuses(ctx context.Context, projectFlockID
|
||||
|
||||
return statusProject, statusClosing, nil
|
||||
}
|
||||
|
||||
func (s closingService) GetOverhead(c *fiber.Ctx, projectFlockID uint) (*dto.OverheadListDTO, error) {
|
||||
budgets, err := s.ProjectBudgetRepo.GetByProjectFlockID(c.Context(), projectFlockID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
realizations, err := s.ExpenseRealizationRepo.GetByProjectFlockID(c.Context(), projectFlockID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
chickins, err := s.ChickinRepo.GetByProjectFlockID(c.Context(), projectFlockID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var totalChickinQty float64
|
||||
for _, chickin := range chickins {
|
||||
totalChickinQty += chickin.UsageQty
|
||||
}
|
||||
|
||||
result := dto.ToOverheadListDTOs(budgets, realizations, totalChickinQty)
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
@@ -13,3 +13,14 @@ type Query struct {
|
||||
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100,gt=0"`
|
||||
Search string `query:"search" validate:"omitempty,max=50"`
|
||||
}
|
||||
|
||||
const (
|
||||
SapronakTypeIncoming = "incoming"
|
||||
SapronakTypeOutgoing = "outgoing"
|
||||
)
|
||||
|
||||
type SapronakQuery struct {
|
||||
Type string `query:"type" validate:"required,oneof=incoming outgoing"`
|
||||
Page int `query:"page" validate:"omitempty,number,min=1,gt=0"`
|
||||
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100,gt=0"`
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ type ExpenseRealizationRepository interface {
|
||||
repository.BaseRepository[entity.ExpenseRealization]
|
||||
IdExists(ctx context.Context, id uint64) (bool, error)
|
||||
GetByExpenseNonstockID(ctx context.Context, expenseNonstockID uint64) (*entity.ExpenseRealization, error)
|
||||
GetByProjectFlockID(ctx context.Context, projectFlockID uint) ([]entity.ExpenseRealization, error)
|
||||
}
|
||||
|
||||
type ExpenseRealizationRepositoryImpl struct {
|
||||
@@ -30,11 +31,22 @@ func (r *ExpenseRealizationRepositoryImpl) IdExists(ctx context.Context, id uint
|
||||
|
||||
func (r *ExpenseRealizationRepositoryImpl) GetByExpenseNonstockID(ctx context.Context, expenseNonstockID uint64) (*entity.ExpenseRealization, error) {
|
||||
var realization entity.ExpenseRealization
|
||||
err := r.DB().WithContext(ctx).Where("expense_nonstock_id = ?", expenseNonstockID).First(&realization).Error
|
||||
return &realization, err
|
||||
}
|
||||
|
||||
func (r *ExpenseRealizationRepositoryImpl) GetByProjectFlockID(ctx context.Context, projectFlockID uint) ([]entity.ExpenseRealization, error) {
|
||||
var realizations []entity.ExpenseRealization
|
||||
err := r.DB().WithContext(ctx).
|
||||
Where("expense_nonstock_id = ?", expenseNonstockID).
|
||||
First(&realization).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &realization, nil
|
||||
Preload("ExpenseNonstock").
|
||||
Preload("ExpenseNonstock.Nonstock").
|
||||
Preload("ExpenseNonstock.Nonstock.Uom").
|
||||
Preload("ExpenseNonstock.Expense").
|
||||
Joins("JOIN expense_nonstocks ON expense_nonstocks.id = expense_realizations.expense_nonstock_id").
|
||||
Joins("JOIN project_flock_kandangs ON project_flock_kandangs.id = expense_nonstocks.project_flock_kandang_id").
|
||||
Joins("JOIN expenses ON expenses.id = expense_nonstocks.expense_id").
|
||||
Where("project_flock_kandangs.project_flock_id = ?", projectFlockID).
|
||||
Where("expenses.category = ?", "BOP").
|
||||
Find(&realizations).Error
|
||||
return realizations, err
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
middleware "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||
expenseDto "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/dto"
|
||||
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/repositories"
|
||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/validations"
|
||||
@@ -188,7 +189,11 @@ func (s *expenseService) CreateOne(c *fiber.Ctx, req *validation.Create) (*expen
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to generate reference number")
|
||||
}
|
||||
|
||||
createdBy := uint64(1) //todo get from auth
|
||||
actorID, err := middleware.ActorIDFromContext(c)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusUnauthorized, "Failed to get actor ID from context")
|
||||
}
|
||||
createdBy := uint64(actorID)
|
||||
expense = &entity.Expense{
|
||||
ReferenceNumber: referenceNumber,
|
||||
PoNumber: req.PoNumber,
|
||||
@@ -502,7 +507,10 @@ func (s expenseService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint)
|
||||
}
|
||||
}
|
||||
|
||||
actorID := uint(1) // TODO: replace with authenticated user id
|
||||
actorID, err := middleware.ActorIDFromContext(c)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusUnauthorized, "Failed to get actor ID from context")
|
||||
}
|
||||
if *latestApproval.Action != entity.ApprovalActionUpdated {
|
||||
|
||||
approvalAction := entity.ApprovalActionUpdated
|
||||
@@ -689,7 +697,10 @@ func (s *expenseService) CompleteExpense(c *fiber.Ctx, id uint, notes *string) (
|
||||
return nil, err
|
||||
}
|
||||
|
||||
actorID := uint(1) // TODO: replace with authenticated user id
|
||||
actorID, err := middleware.ActorIDFromContext(c)
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusUnauthorized, "Failed to get actor ID from context")
|
||||
}
|
||||
|
||||
latestApproval, err := s.ApprovalSvc.LatestByTarget(c.Context(), utils.ApprovalWorkflowExpense, id, nil)
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
@@ -1006,11 +1017,14 @@ func (s *expenseService) Approval(c *fiber.Ctx, req *validation.ApprovalRequest,
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "No expense IDs provided")
|
||||
}
|
||||
|
||||
actorID := uint(1) // TODO: replace with authenticated user id
|
||||
actorID, err := middleware.ActorIDFromContext(c)
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusUnauthorized, "Failed to get actor ID from context")
|
||||
}
|
||||
|
||||
var results []expenseDto.ExpenseDetailDTO
|
||||
|
||||
err := s.Repository.DB().WithContext(c.Context()).Transaction(func(tx *gorm.DB) error {
|
||||
err = s.Repository.DB().WithContext(c.Context()).Transaction(func(tx *gorm.DB) error {
|
||||
|
||||
approvalSvcTx := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(tx))
|
||||
expenseRepoTx := repository.NewExpenseRepository(tx)
|
||||
|
||||
@@ -21,10 +21,11 @@ func (AdjustmentModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validat
|
||||
stockLogsRepo := rStockLogs.NewStockLogRepository(db)
|
||||
warehouseRepo := rWarehouse.NewWarehouseRepository(db)
|
||||
productWarehouseRepo := rProductWarehouse.NewProductWarehouseRepository(db)
|
||||
projectFlockKandangRepo := rProjectFlockKandang.NewProjectFlockKandangRepository(db)
|
||||
userRepo := rUser.NewUserRepository(db)
|
||||
productRepo := rproduct.NewProductRepository(db)
|
||||
projectFlockKandangRepo := rProjectFlockKandang.NewProjectFlockKandangRepository(db)
|
||||
adjustmentService := sAdjustment.NewAdjustmentService(productRepo, stockLogsRepo, warehouseRepo, productWarehouseRepo, projectFlockKandangRepo, validate)
|
||||
|
||||
adjustmentService := sAdjustment.NewAdjustmentService(productRepo, stockLogsRepo, warehouseRepo, productWarehouseRepo, validate, projectFlockKandangRepo)
|
||||
userService := sUser.NewUserService(userRepo, validate)
|
||||
|
||||
AdjustmentRoutes(router, userService, adjustmentService)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package adjustments
|
||||
|
||||
import (
|
||||
// m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/adjustments/controllers"
|
||||
adjustment "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/adjustments/services"
|
||||
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||
@@ -13,7 +13,7 @@ func AdjustmentRoutes(v1 fiber.Router, u user.UserService, s adjustment.Adjustme
|
||||
ctrl := controller.NewAdjustmentController(s)
|
||||
|
||||
route := v1.Group("/adjustments")
|
||||
|
||||
route.Use(m.Auth(u))
|
||||
// Standard CRUD routes following master data pattern
|
||||
route.Get("/", ctrl.AdjustmentHistory) // Get all with pagination and filters
|
||||
route.Post("/", ctrl.Adjustment) // Create adjustment
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
@@ -36,8 +38,7 @@ type adjustmentService struct {
|
||||
ProjectFlockKandangRepo projectFlockKandangRepo.ProjectFlockKandangRepository
|
||||
}
|
||||
|
||||
func NewAdjustmentService(productRepo productRepo.ProductRepository, stockLogsRepo stockLogsRepo.StockLogRepository, warehouseRepo warehouseRepo.WarehouseRepository, productWarehouseRepo ProductWarehouse.ProductWarehouseRepository, projectFlockKandangRepo projectFlockKandangRepo.ProjectFlockKandangRepository,
|
||||
validate *validator.Validate) AdjustmentService {
|
||||
func NewAdjustmentService(productRepo productRepo.ProductRepository, stockLogsRepo stockLogsRepo.StockLogRepository, warehouseRepo warehouseRepo.WarehouseRepository, productWarehouseRepo ProductWarehouse.ProductWarehouseRepository, validate *validator.Validate, projectFlockKandangRepo projectFlockKandangRepo.ProjectFlockKandangRepository) AdjustmentService {
|
||||
return &adjustmentService{
|
||||
Log: utils.Log,
|
||||
Validate: validate,
|
||||
@@ -108,11 +109,15 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to validate product warehouse")
|
||||
}
|
||||
if !isProductWarehouseExist {
|
||||
|
||||
projectFlockKandangID, err := s.getActiveProjectFlockKandangID(ctx, uint(req.WarehouseID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newPW := &entity.ProductWarehouse{
|
||||
ProductId: uint(req.ProductID),
|
||||
WarehouseId: uint(req.WarehouseID),
|
||||
Quantity: 0,
|
||||
ProjectFlockKandangId: &projectFlockKandangID,
|
||||
// CreatedBy: 1, // TODO: should Get from auth middleware
|
||||
}
|
||||
|
||||
@@ -190,6 +195,32 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e
|
||||
return s.GetOne(c, createdLogId)
|
||||
}
|
||||
|
||||
func (s *adjustmentService) getActiveProjectFlockKandangID(ctx context.Context, warehouseID uint) (uint, error) {
|
||||
warehouse, err := s.WarehouseRepo.GetByID(ctx, warehouseID, nil)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return 0, fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Gudang dengan ID %d tidak ditemukan", warehouseID))
|
||||
}
|
||||
s.Log.Errorf("Failed to get warehouse %d: %+v", warehouseID, err)
|
||||
return 0, fiber.NewError(fiber.StatusInternalServerError, "Gagal mengambil data gudang")
|
||||
}
|
||||
|
||||
if warehouse.KandangId == nil || *warehouse.KandangId == 0 {
|
||||
return 0, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Gudang %d belum terhubung ke kandang", warehouseID))
|
||||
}
|
||||
|
||||
projectFlockKandang, err := s.ProjectFlockKandangRepo.GetActiveByKandangID(ctx, uint(*warehouse.KandangId))
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return 0, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Kandang %d belum memiliki project flock aktif", *warehouse.KandangId))
|
||||
}
|
||||
s.Log.Errorf("Failed to get active project flock for kandang %d: %+v", *warehouse.KandangId, err)
|
||||
return 0, fiber.NewError(fiber.StatusInternalServerError, "Gagal mengambil project flock kandang")
|
||||
}
|
||||
|
||||
return uint(projectFlockKandang.Id), nil
|
||||
}
|
||||
|
||||
func (s *adjustmentService) AdjustmentHistory(c *fiber.Ctx, query *validation.Query) ([]*entity.StockLog, int64, error) {
|
||||
if err := s.Validate.Struct(query); err != nil {
|
||||
return nil, 0, err
|
||||
|
||||
@@ -9,11 +9,11 @@ import (
|
||||
rStockTransfer "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/transfers/repositories"
|
||||
sTransfer "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/transfers/services"
|
||||
rSupplier "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers/repositories"
|
||||
rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories"
|
||||
rProjectFlockKandang "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
|
||||
rStockLogs "gitlab.com/mbugroup/lti-api.git/internal/modules/shared/repositories"
|
||||
rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories"
|
||||
sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||
rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories"
|
||||
)
|
||||
|
||||
type TransferModule struct{}
|
||||
@@ -26,9 +26,10 @@ func (TransferModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate
|
||||
stockLogsRepo := rStockLogs.NewStockLogRepository(db)
|
||||
supplierRepo := rSupplier.NewSupplierRepository(db)
|
||||
productWarehouseRepo := rProductWarehouse.NewProductWarehouseRepository(db)
|
||||
warehouseRepo := rWarehouse.NewWarehouseRepository(db)
|
||||
userRepo := rUser.NewUserRepository(db)
|
||||
warehouseRepo := rWarehouse.NewWarehouseRepository(db)
|
||||
projectFlockKandangRepo := rProjectFlockKandang.NewProjectFlockKandangRepository(db)
|
||||
|
||||
transferService := sTransfer.NewTransferService(validate, stockTransferRepo, stockTransferDetailRepo, stockTransferDeliveryRepo, StockTransferDeliveryItemRepo, stockLogsRepo, productWarehouseRepo, supplierRepo, warehouseRepo, projectFlockKandangRepo)
|
||||
userService := sUser.NewUserService(userRepo, validate)
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
@@ -15,7 +16,7 @@ import (
|
||||
rStockTransfer "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/transfers/repositories"
|
||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/transfers/validations"
|
||||
rSupplier "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers/repositories"
|
||||
rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories"
|
||||
warehouseRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories"
|
||||
projectFlockKandangRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
|
||||
rStockLogs "gitlab.com/mbugroup/lti-api.git/internal/modules/shared/repositories"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||
@@ -39,12 +40,11 @@ type transferService struct {
|
||||
StockLogsRepository rStockLogs.StockLogRepository
|
||||
ProductWarehouseRepo rProductWarehouse.ProductWarehouseRepository
|
||||
SupplierRepo rSupplier.SupplierRepository
|
||||
WarehouseRepo rWarehouse.WarehouseRepository
|
||||
projectFlockKandangRepo projectFlockKandangRepo.ProjectFlockKandangRepository
|
||||
WarehouseRepo warehouseRepo.WarehouseRepository
|
||||
ProjectFlockKandangRepo projectFlockKandangRepo.ProjectFlockKandangRepository
|
||||
}
|
||||
|
||||
func NewTransferService(validate *validator.Validate, stockTransferRepo rStockTransfer.StockTransferRepository, stockTransferDetailRepo rStockTransfer.StockTransferDetailRepository, stockTransferDeliveryRepo rStockTransfer.StockTransferDeliveryRepository, stockTransferDeliveryItemRepo rStockTransfer.StockTransferDeliveryItemRepository, stockLogsRepo rStockLogs.StockLogRepository, productWarehouseRepo rProductWarehouse.ProductWarehouseRepository, supplierRepo rSupplier.SupplierRepository, warehouseRepo rWarehouse.WarehouseRepository,
|
||||
projectFlockKandangRepo projectFlockKandangRepo.ProjectFlockKandangRepository) TransferService {
|
||||
func NewTransferService(validate *validator.Validate, stockTransferRepo rStockTransfer.StockTransferRepository, stockTransferDetailRepo rStockTransfer.StockTransferDetailRepository, stockTransferDeliveryRepo rStockTransfer.StockTransferDeliveryRepository, stockTransferDeliveryItemRepo rStockTransfer.StockTransferDeliveryItemRepository, stockLogsRepo rStockLogs.StockLogRepository, productWarehouseRepo rProductWarehouse.ProductWarehouseRepository, supplierRepo rSupplier.SupplierRepository, warehouseRepo warehouseRepo.WarehouseRepository, projectFlockKandangRepo projectFlockKandangRepo.ProjectFlockKandangRepository) TransferService {
|
||||
return &transferService{
|
||||
Log: utils.Log,
|
||||
Validate: validate,
|
||||
@@ -56,7 +56,7 @@ func NewTransferService(validate *validator.Validate, stockTransferRepo rStockTr
|
||||
ProductWarehouseRepo: productWarehouseRepo,
|
||||
SupplierRepo: supplierRepo,
|
||||
WarehouseRepo: warehouseRepo,
|
||||
projectFlockKandangRepo: projectFlockKandangRepo,
|
||||
ProjectFlockKandangRepo: projectFlockKandangRepo,
|
||||
}
|
||||
}
|
||||
func (s transferService) withRelations(db *gorm.DB) *gorm.DB {
|
||||
@@ -321,10 +321,16 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
|
||||
}
|
||||
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
// Jika belum ada record untuk produk di gudang tujuan, buat baru
|
||||
ctx := c.Context()
|
||||
projectFlockKandangID, err := s.getActiveProjectFlockKandangID(ctx, uint(req.DestinationWarehouseID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
destPW = &entity.ProductWarehouse{
|
||||
ProductId: uint(product.ProductID),
|
||||
WarehouseId: uint(req.DestinationWarehouseID),
|
||||
Quantity: 0,
|
||||
ProjectFlockKandangId: &projectFlockKandangID,
|
||||
// CreatedBy: 1, // TODO: should Get from auth middleware
|
||||
}
|
||||
if err := s.ProductWarehouseRepo.WithTx(tx).CreateOne(c.Context(), destPW, nil); err != nil {
|
||||
@@ -377,3 +383,29 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *transferService) getActiveProjectFlockKandangID(ctx context.Context, warehouseID uint) (uint, error) {
|
||||
warehouse, err := s.WarehouseRepo.GetByID(ctx, warehouseID, nil)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return 0, fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Gudang dengan ID %d tidak ditemukan", warehouseID))
|
||||
}
|
||||
s.Log.Errorf("Failed to get warehouse %d: %+v", warehouseID, err)
|
||||
return 0, fiber.NewError(fiber.StatusInternalServerError, "Gagal mengambil data gudang")
|
||||
}
|
||||
|
||||
if warehouse.KandangId == nil || *warehouse.KandangId == 0 {
|
||||
return 0, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Gudang %d belum terhubung ke kandang", warehouseID))
|
||||
}
|
||||
|
||||
projectFlockKandang, err := s.ProjectFlockKandangRepo.GetActiveByKandangID(ctx, uint(*warehouse.KandangId))
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return 0, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Kandang %d belum memiliki project flock aktif", *warehouse.KandangId))
|
||||
}
|
||||
s.Log.Errorf("Failed to get active project flock for kandang %d: %+v", *warehouse.KandangId, err)
|
||||
return 0, fiber.NewError(fiber.StatusInternalServerError, "Gagal mengambil project flock kandang")
|
||||
}
|
||||
|
||||
return uint(projectFlockKandang.Id), nil
|
||||
}
|
||||
|
||||
+6
-6
@@ -4,9 +4,9 @@ import (
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/delivery-orderss/dto"
|
||||
service "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/delivery-orderss/services"
|
||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/delivery-orderss/validations"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/dto"
|
||||
service "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/services"
|
||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/validations"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/response"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
@@ -23,7 +23,7 @@ func NewDeliveryOrdersController(deliveryOrdersService service.DeliveryOrdersSer
|
||||
}
|
||||
|
||||
func (u *DeliveryOrdersController) GetAll(c *fiber.Ctx) error {
|
||||
query := &validation.Query{
|
||||
query := &validation.DeliveryOrderQuery{
|
||||
Page: c.QueryInt("page", 1),
|
||||
Limit: c.QueryInt("limit", 10),
|
||||
MarketingId: uint(c.QueryInt("marketing_id", 0)),
|
||||
@@ -76,7 +76,7 @@ func (u *DeliveryOrdersController) GetOne(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
func (u *DeliveryOrdersController) CreateOne(c *fiber.Ctx) error {
|
||||
req := new(validation.Create)
|
||||
req := new(validation.DeliveryOrderCreate)
|
||||
|
||||
if err := c.BodyParser(req); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||
@@ -97,7 +97,7 @@ func (u *DeliveryOrdersController) CreateOne(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
func (u *DeliveryOrdersController) UpdateOne(c *fiber.Ctx) error {
|
||||
req := new(validation.Update)
|
||||
req := new(validation.DeliveryOrderUpdate)
|
||||
param := c.Params("id")
|
||||
|
||||
id, err := strconv.Atoi(param)
|
||||
+3
-3
@@ -3,9 +3,9 @@ package controller
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/dto"
|
||||
service "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/services"
|
||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/validations"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/dto"
|
||||
service "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/services"
|
||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/validations"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/response"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
@@ -1,38 +0,0 @@
|
||||
package delivery_orderss
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"gorm.io/gorm"
|
||||
|
||||
commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
||||
sDeliveryOrders "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/delivery-orderss/services"
|
||||
rMarketing "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/repositories"
|
||||
rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories"
|
||||
sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||
)
|
||||
|
||||
type DeliveryOrdersModule struct{}
|
||||
|
||||
func (DeliveryOrdersModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) {
|
||||
marketingRepo := rMarketing.NewMarketingRepository(db)
|
||||
marketingProductRepo := rMarketing.NewMarketingProductRepository(db)
|
||||
marketingDeliveryProductRepo := rMarketing.NewMarketingDeliveryProductRepository(db)
|
||||
userRepo := rUser.NewUserRepository(db)
|
||||
approvalRepo := commonRepo.NewApprovalRepository(db)
|
||||
approvalSvc := commonSvc.NewApprovalService(approvalRepo)
|
||||
|
||||
// Register workflow steps for MARKETINGS approval
|
||||
if err := approvalSvc.RegisterWorkflowSteps(utils.ApprovalWorkflowMarketing, utils.MarketingApprovalSteps); err != nil {
|
||||
panic(fmt.Sprintf("failed to register marketing approval workflow: %v", err))
|
||||
}
|
||||
|
||||
deliveryOrdersService := sDeliveryOrders.NewDeliveryOrdersService(marketingRepo, marketingProductRepo, marketingDeliveryProductRepo, approvalSvc, validate)
|
||||
userService := sUser.NewUserService(userRepo, validate)
|
||||
|
||||
DeliveryOrdersRoutes(router, userService, deliveryOrdersService)
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package delivery_orderss
|
||||
|
||||
import (
|
||||
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/delivery-orderss/controllers"
|
||||
deliveryOrders "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/delivery-orderss/services"
|
||||
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func DeliveryOrdersRoutes(v1 fiber.Router, u user.UserService, s deliveryOrders.DeliveryOrdersService) {
|
||||
ctrl := controller.NewDeliveryOrdersController(s)
|
||||
|
||||
v1.Get("/", ctrl.GetAll)
|
||||
v1.Get("/:id", ctrl.GetOne)
|
||||
|
||||
// Sisanya di group /delivery-orders
|
||||
route := v1.Group("/delivery-orders")
|
||||
route.Use(m.Auth(u))
|
||||
|
||||
// route.Get("/", m.Auth(u), ctrl.GetAll)
|
||||
// route.Post("/", m.Auth(u), ctrl.CreateOne)
|
||||
// route.Get("/:id", m.Auth(u), ctrl.GetOne)
|
||||
// route.Patch("/:id", m.Auth(u), ctrl.UpdateOne)
|
||||
// route.Delete("/:id", m.Auth(u), ctrl.DeleteOne)
|
||||
|
||||
route.Post("/", ctrl.CreateOne)
|
||||
route.Patch("/:id", ctrl.UpdateOne)
|
||||
|
||||
}
|
||||
+12
-11
@@ -24,7 +24,7 @@ type MarketingListDTO struct {
|
||||
Customer customerDTO.CustomerRelationDTO `json:"customer"`
|
||||
SalesPerson userDTO.UserRelationDTO `json:"sales_person"`
|
||||
SoDocs string `json:"so_docs"`
|
||||
SalesOrder []MarketingProductDTO `json:"sales_order"`
|
||||
SalesOrder []DeliveryMarketingProductDTO `json:"sales_order"`
|
||||
CreatedUser userDTO.UserRelationDTO `json:"created_user"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
@@ -36,13 +36,14 @@ type MarketingDetailDTO struct {
|
||||
Customer customerDTO.CustomerRelationDTO `json:"customer"`
|
||||
SalesPerson userDTO.UserRelationDTO `json:"sales_person"`
|
||||
SoDocs string `json:"so_docs"`
|
||||
SalesOrder []MarketingProductDTO `json:"sales_order"`
|
||||
SalesOrder []DeliveryMarketingProductDTO `json:"sales_order"`
|
||||
DeliveryOrder []DeliveryGroupDTO `json:"delivery_order"`
|
||||
CreatedUser userDTO.UserRelationDTO `json:"created_user"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
LatestApproval approvalDTO.ApprovalRelationDTO `json:"latest_approval"`
|
||||
}
|
||||
|
||||
type MarketingDeliveryProductDTO struct {
|
||||
Id uint `json:"id"`
|
||||
MarketingProductId uint `json:"marketing_product_id"`
|
||||
@@ -73,7 +74,7 @@ type DeliveryGroupDTO struct {
|
||||
Deliveries []DeliveryItemDTO `json:"deliveries"`
|
||||
}
|
||||
|
||||
type MarketingProductDTO struct {
|
||||
type DeliveryMarketingProductDTO struct {
|
||||
Id uint `json:"id"`
|
||||
MarketingId uint `json:"marketing_id"`
|
||||
ProductWarehouseId uint `json:"product_warehouse_id"`
|
||||
@@ -95,14 +96,14 @@ func ToMarketingRelationDTO(marketing *entity.Marketing) MarketingRelationDTO {
|
||||
}
|
||||
}
|
||||
|
||||
func ToMarketingProductDTO(e entity.MarketingProduct) MarketingProductDTO {
|
||||
func ToDeliveryMarketingProductDTO(e entity.MarketingProduct) DeliveryMarketingProductDTO {
|
||||
var productWarehouse *productwarehouseDTO.ProductWarehousNestedDTO
|
||||
if e.ProductWarehouse.Id != 0 {
|
||||
mapped := productwarehouseDTO.ToProductWarehouseNestedDTO(e.ProductWarehouse)
|
||||
productWarehouse = &mapped
|
||||
}
|
||||
|
||||
return MarketingProductDTO{
|
||||
return DeliveryMarketingProductDTO{
|
||||
Id: e.Id,
|
||||
MarketingId: e.MarketingId,
|
||||
ProductWarehouseId: e.ProductWarehouseId,
|
||||
@@ -155,11 +156,11 @@ func ToMarketingListDTO(marketing *entity.Marketing, deliveryProducts []entity.M
|
||||
latestApproval = mapped
|
||||
}
|
||||
|
||||
var salesOrderProducts []MarketingProductDTO
|
||||
var salesOrderProducts []DeliveryMarketingProductDTO
|
||||
if len(marketing.Products) > 0 {
|
||||
salesOrderProducts = make([]MarketingProductDTO, len(marketing.Products))
|
||||
salesOrderProducts = make([]DeliveryMarketingProductDTO, len(marketing.Products))
|
||||
for i, product := range marketing.Products {
|
||||
salesOrderProducts[i] = ToMarketingProductDTO(product)
|
||||
salesOrderProducts[i] = ToDeliveryMarketingProductDTO(product)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,11 +196,11 @@ func ToMarketingDetailDTO(marketing *entity.Marketing, deliveryProducts []entity
|
||||
salesPerson = mapped
|
||||
}
|
||||
|
||||
var salesOrderProducts []MarketingProductDTO
|
||||
var salesOrderProducts []DeliveryMarketingProductDTO
|
||||
if len(marketing.Products) > 0 {
|
||||
salesOrderProducts = make([]MarketingProductDTO, len(marketing.Products))
|
||||
salesOrderProducts = make([]DeliveryMarketingProductDTO, len(marketing.Products))
|
||||
for i, product := range marketing.Products {
|
||||
salesOrderProducts[i] = ToMarketingProductDTO(product)
|
||||
salesOrderProducts[i] = ToDeliveryMarketingProductDTO(product)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,52 @@
|
||||
package marketing
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"gorm.io/gorm"
|
||||
|
||||
commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
||||
rProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
||||
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/repositories"
|
||||
service "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/services"
|
||||
rCustomer "gitlab.com/mbugroup/lti-api.git/internal/modules/master/customers/repositories"
|
||||
rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories"
|
||||
sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||
rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories"
|
||||
rProjectFlockKandang "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||
)
|
||||
|
||||
type MarketingModule struct{}
|
||||
|
||||
func (MarketingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) {
|
||||
RegisterRoutes(router, db, validate)
|
||||
// Initialize repositories
|
||||
marketingRepo := repository.NewMarketingRepository(db)
|
||||
marketingProductRepo := repository.NewMarketingProductRepository(db)
|
||||
marketingDeliveryProductRepo := repository.NewMarketingDeliveryProductRepository(db)
|
||||
userRepo := rUser.NewUserRepository(db)
|
||||
customerRepo := rCustomer.NewCustomerRepository(db)
|
||||
productWarehouseRepo := rProductWarehouse.NewProductWarehouseRepository(db)
|
||||
|
||||
// Initialize approval service
|
||||
approvalRepo := commonRepo.NewApprovalRepository(db)
|
||||
approvalSvc := commonSvc.NewApprovalService(approvalRepo)
|
||||
|
||||
// Register workflow steps for marketing approval
|
||||
if err := approvalSvc.RegisterWorkflowSteps(utils.ApprovalWorkflowMarketing, utils.MarketingApprovalSteps); err != nil {
|
||||
panic(fmt.Sprintf("failed to register marketing approval workflow: %v", err))
|
||||
}
|
||||
|
||||
warehouseRepo := rWarehouse.NewWarehouseRepository(db)
|
||||
projectFlockKandangRepo := rProjectFlockKandang.NewProjectFlockKandangRepository(db)
|
||||
// Initialize services
|
||||
salesOrdersService := service.NewSalesOrdersService(marketingRepo, customerRepo, productWarehouseRepo, userRepo, approvalSvc,warehouseRepo,projectFlockKandangRepo, validate)
|
||||
deliveryOrdersService := service.NewDeliveryOrdersService(marketingRepo, marketingProductRepo, marketingDeliveryProductRepo, approvalSvc, validate)
|
||||
userService := sUser.NewUserService(userRepo, validate)
|
||||
|
||||
// Register routes
|
||||
RegisterRoutes(router, userService, salesOrdersService, deliveryOrdersService)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type MarketingRepository interface {
|
||||
repository.BaseRepository[entity.Marketing]
|
||||
IdExists(ctx context.Context, id uint) (bool, error)
|
||||
GetNextSequence(ctx context.Context) (uint, error)
|
||||
NextSoNumber(ctx context.Context, tx *gorm.DB) (string, error)
|
||||
}
|
||||
|
||||
type MarketingRepositoryImpl struct {
|
||||
*repository.BaseRepositoryImpl[entity.Marketing]
|
||||
}
|
||||
|
||||
func NewMarketingRepository(db *gorm.DB) MarketingRepository {
|
||||
return &MarketingRepositoryImpl{
|
||||
BaseRepositoryImpl: repository.NewBaseRepository[entity.Marketing](db),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *MarketingRepositoryImpl) IdExists(ctx context.Context, id uint) (bool, error) {
|
||||
return repository.Exists[entity.Marketing](ctx, r.DB(), id)
|
||||
}
|
||||
|
||||
func (r *MarketingRepositoryImpl) GetNextSequence(ctx context.Context) (uint, error) {
|
||||
var maxID uint
|
||||
if err := r.DB().WithContext(ctx).Model(&entity.Marketing{}).Select("COALESCE(MAX(id), 0)").Scan(&maxID).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return maxID + 1, nil
|
||||
}
|
||||
|
||||
func (r *MarketingRepositoryImpl) NextSoNumber(ctx context.Context, tx *gorm.DB) (string, error) {
|
||||
db := tx
|
||||
if db == nil {
|
||||
db = r.DB()
|
||||
}
|
||||
|
||||
var soNumber string
|
||||
err := db.WithContext(ctx).
|
||||
Raw("SELECT generate_so_number()").
|
||||
Scan(&soNumber).Error
|
||||
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to generate SO number: %w", err)
|
||||
}
|
||||
|
||||
return soNumber, nil
|
||||
}
|
||||
@@ -1,27 +1,31 @@
|
||||
package marketing
|
||||
|
||||
import (
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/modules"
|
||||
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/controllers"
|
||||
service "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/services"
|
||||
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"gorm.io/gorm"
|
||||
|
||||
salesOrderss "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders"
|
||||
deliveryOrderss "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/delivery-orderss"
|
||||
// MODULE IMPORTS
|
||||
)
|
||||
|
||||
func RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) {
|
||||
group := router.Group("/marketing")
|
||||
func RegisterRoutes(router fiber.Router, userService user.UserService, salesOrdersService service.SalesOrdersService, deliveryOrdersService service.DeliveryOrdersService) {
|
||||
salesOrdersCtrl := controller.NewSalesOrdersController(salesOrdersService)
|
||||
deliveryOrdersCtrl := controller.NewDeliveryOrdersController(deliveryOrdersService)
|
||||
|
||||
allModules := []modules.Module{
|
||||
salesOrderss.SalesOrdersModule{},
|
||||
deliveryOrderss.DeliveryOrdersModule{},
|
||||
// MODULE REGISTRY
|
||||
}
|
||||
route := router.Group("/marketing")
|
||||
route.Use(m.Auth(userService))
|
||||
|
||||
for _, m := range allModules {
|
||||
m.RegisterRoutes(group, db, validate)
|
||||
}
|
||||
route.Get("/", deliveryOrdersCtrl.GetAll)
|
||||
route.Get("/:id", deliveryOrdersCtrl.GetOne)
|
||||
route.Delete("/:id", salesOrdersCtrl.DeleteOne)
|
||||
|
||||
route.Post("/sales-orders", salesOrdersCtrl.CreateOne)
|
||||
route.Patch("/sales-orders/:id", salesOrdersCtrl.UpdateOne)
|
||||
route.Post("/sales-orders/approvals", salesOrdersCtrl.Approval)
|
||||
|
||||
route.Get("/delivery-orders", deliveryOrdersCtrl.GetAll)
|
||||
route.Get("/delivery-orders/:id", deliveryOrdersCtrl.GetOne)
|
||||
route.Post("/delivery-orders", deliveryOrdersCtrl.CreateOne)
|
||||
route.Patch("/delivery-orders/:id", deliveryOrdersCtrl.UpdateOne)
|
||||
}
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
package sales_orders
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"gorm.io/gorm"
|
||||
|
||||
commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
||||
rProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
||||
rSalesOrders "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/repositories"
|
||||
sSalesOrders "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/services"
|
||||
rCustomer "gitlab.com/mbugroup/lti-api.git/internal/modules/master/customers/repositories"
|
||||
rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories"
|
||||
rProjectFlockKandang "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
|
||||
rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories"
|
||||
sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||
)
|
||||
|
||||
type SalesOrdersModule struct{}
|
||||
|
||||
func (SalesOrdersModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) {
|
||||
marketingRepo := rSalesOrders.NewMarketingRepository(db)
|
||||
userRepo := rUser.NewUserRepository(db)
|
||||
customerRepo := rCustomer.NewCustomerRepository(db)
|
||||
productWarehouseRepo := rProductWarehouse.NewProductWarehouseRepository(db)
|
||||
|
||||
approvalSvc := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(db))
|
||||
warehouseRepo := rWarehouse.NewWarehouseRepository(db)
|
||||
projectFlockKandangRepo := rProjectFlockKandang.NewProjectFlockKandangRepository(db)
|
||||
if err := approvalSvc.RegisterWorkflowSteps(utils.ApprovalWorkflowMarketing, utils.MarketingApprovalSteps); err != nil {
|
||||
panic(fmt.Sprintf("failed to register marketing approval workflow: %v", err))
|
||||
}
|
||||
|
||||
salesOrdersService := sSalesOrders.NewSalesOrdersService(marketingRepo, customerRepo, productWarehouseRepo, userRepo, approvalSvc, warehouseRepo, projectFlockKandangRepo,validate)
|
||||
userService := sUser.NewUserService(userRepo, validate)
|
||||
|
||||
SalesOrdersRoutes(router, userService, salesOrdersService)
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
type MarketingRepository interface {
|
||||
repository.BaseRepository[entity.Marketing]
|
||||
IdExists(ctx context.Context, id uint) (bool, error)
|
||||
GetNextSequence(ctx context.Context) (uint, error)
|
||||
NextSoNumber(ctx context.Context, tx *gorm.DB) (string, error)
|
||||
}
|
||||
|
||||
type MarketingRepositoryImpl struct {
|
||||
*repository.BaseRepositoryImpl[entity.Marketing]
|
||||
}
|
||||
|
||||
func NewMarketingRepository(db *gorm.DB) MarketingRepository {
|
||||
return &MarketingRepositoryImpl{
|
||||
BaseRepositoryImpl: repository.NewBaseRepository[entity.Marketing](db),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *MarketingRepositoryImpl) IdExists(ctx context.Context, id uint) (bool, error) {
|
||||
return repository.Exists[entity.Marketing](ctx, r.DB(), id)
|
||||
}
|
||||
|
||||
func (r *MarketingRepositoryImpl) GetNextSequence(ctx context.Context) (uint, error) {
|
||||
var maxID uint
|
||||
if err := r.DB().WithContext(ctx).Model(&entity.Marketing{}).Select("COALESCE(MAX(id), 0)").Scan(&maxID).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return maxID + 1, nil
|
||||
}
|
||||
|
||||
func (r *MarketingRepositoryImpl) NextSoNumber(ctx context.Context, tx *gorm.DB) (string, error) {
|
||||
return r.generateSequentialNumber(ctx, tx, "so_number", utils.MarketingSoNumberPrefix, utils.MarketingNumberPadding)
|
||||
}
|
||||
|
||||
func parseNumericSuffix(value, prefix string) (int, bool) {
|
||||
if !strings.HasPrefix(value, prefix) {
|
||||
return 0, false
|
||||
}
|
||||
suffix := strings.TrimPrefix(value, prefix)
|
||||
if suffix == "" {
|
||||
return 0, false
|
||||
}
|
||||
trimmed := strings.TrimLeft(suffix, "0")
|
||||
if trimmed == "" {
|
||||
trimmed = "0"
|
||||
}
|
||||
number, err := strconv.Atoi(trimmed)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
return number, true
|
||||
}
|
||||
|
||||
func (r *MarketingRepositoryImpl) numberExists(ctx context.Context, db *gorm.DB, column, value string) (bool, error) {
|
||||
var count int64
|
||||
if err := db.WithContext(ctx).
|
||||
Model(&entity.Marketing{}).
|
||||
Where(fmt.Sprintf("%s = ?", column), value).
|
||||
Count(&count).Error; err != nil {
|
||||
return false, err
|
||||
}
|
||||
return count > 0, nil
|
||||
}
|
||||
|
||||
func (r *MarketingRepositoryImpl) generateSequentialNumber(ctx context.Context, tx *gorm.DB, column, prefix string, padding int) (string, error) {
|
||||
|
||||
db := tx
|
||||
if db == nil {
|
||||
db = r.DB()
|
||||
}
|
||||
|
||||
var values []string
|
||||
err := db.WithContext(ctx).
|
||||
Model(&entity.Marketing{}).
|
||||
Where(fmt.Sprintf("%s LIKE ?", column), prefix+"%").
|
||||
Select(column).
|
||||
Order(fmt.Sprintf("%s DESC", column)).
|
||||
Limit(20).
|
||||
Clauses(clause.Locking{Strength: "UPDATE"}).
|
||||
Pluck(column, &values).Error
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
next := 1
|
||||
for _, value := range values {
|
||||
if number, ok := parseNumericSuffix(value, prefix); ok {
|
||||
next = number + 1
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const maxAttempts = 20
|
||||
for attempt := 0; attempt < maxAttempts; attempt++ {
|
||||
candidate := fmt.Sprintf("%s%0*d", prefix, padding, next)
|
||||
exists, err := r.numberExists(ctx, db, column, candidate)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if !exists {
|
||||
return candidate, nil
|
||||
}
|
||||
next++
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("unable to generate unique %s", column)
|
||||
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package sales_orders
|
||||
|
||||
import (
|
||||
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/controllers"
|
||||
salesOrders "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/services"
|
||||
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func SalesOrdersRoutes(v1 fiber.Router, u user.UserService, s salesOrders.SalesOrdersService) {
|
||||
ctrl := controller.NewSalesOrdersController(s)
|
||||
|
||||
v1.Delete("/:id", ctrl.DeleteOne)
|
||||
route := v1.Group("/sales-orders")
|
||||
route.Use(m.Auth(u))
|
||||
|
||||
// route.Post("/", m.Auth(u), ctrl.CreateOne)
|
||||
// route.Patch("/:id", m.Auth(u), ctrl.UpdateOne)
|
||||
// route.Delete("/:id", m.Auth(u), ctrl.DeleteOne)
|
||||
|
||||
route.Post("/", ctrl.CreateOne)
|
||||
route.Patch("/:id", ctrl.UpdateOne)
|
||||
|
||||
route.Post("/approvals", ctrl.Approval)
|
||||
}
|
||||
+9
-9
@@ -11,9 +11,9 @@ import (
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||
productWarehouseRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/delivery-orderss/dto"
|
||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/delivery-orderss/validations"
|
||||
marketingRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/repositories"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/dto"
|
||||
marketingRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/repositories"
|
||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/validations"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
@@ -23,10 +23,10 @@ import (
|
||||
)
|
||||
|
||||
type DeliveryOrdersService interface {
|
||||
GetAll(ctx *fiber.Ctx, params *validation.Query) ([]dto.MarketingListDTO, int64, error)
|
||||
GetAll(ctx *fiber.Ctx, params *validation.DeliveryOrderQuery) ([]dto.MarketingListDTO, int64, error)
|
||||
GetOne(ctx *fiber.Ctx, id uint) (*dto.MarketingDetailDTO, error)
|
||||
CreateOne(ctx *fiber.Ctx, req *validation.Create) (*dto.MarketingDetailDTO, error)
|
||||
UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*dto.MarketingDetailDTO, error)
|
||||
CreateOne(ctx *fiber.Ctx, req *validation.DeliveryOrderCreate) (*dto.MarketingDetailDTO, error)
|
||||
UpdateOne(ctx *fiber.Ctx, req *validation.DeliveryOrderUpdate, id uint) (*dto.MarketingDetailDTO, error)
|
||||
}
|
||||
|
||||
type deliveryOrdersService struct {
|
||||
@@ -85,7 +85,7 @@ func (s deliveryOrdersService) getMarketingWithDeliveries(c *fiber.Ctx, marketin
|
||||
return &responseDTO, nil
|
||||
}
|
||||
|
||||
func (s deliveryOrdersService) GetAll(c *fiber.Ctx, params *validation.Query) ([]dto.MarketingListDTO, int64, error) {
|
||||
func (s deliveryOrdersService) GetAll(c *fiber.Ctx, params *validation.DeliveryOrderQuery) ([]dto.MarketingListDTO, int64, error) {
|
||||
if err := s.Validate.Struct(params); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
@@ -164,7 +164,7 @@ func (s deliveryOrdersService) GetOne(c *fiber.Ctx, id uint) (*dto.MarketingDeta
|
||||
return &responseDTO, nil
|
||||
}
|
||||
|
||||
func (s *deliveryOrdersService) CreateOne(c *fiber.Ctx, req *validation.Create) (*dto.MarketingDetailDTO, error) {
|
||||
func (s *deliveryOrdersService) CreateOne(c *fiber.Ctx, req *validation.DeliveryOrderCreate) (*dto.MarketingDetailDTO, error) {
|
||||
if err := s.Validate.Struct(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -293,7 +293,7 @@ func (s *deliveryOrdersService) CreateOne(c *fiber.Ctx, req *validation.Create)
|
||||
return s.getMarketingWithDeliveries(c, req.MarketingId)
|
||||
}
|
||||
|
||||
func (s deliveryOrdersService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*dto.MarketingDetailDTO, error) {
|
||||
func (s deliveryOrdersService) UpdateOne(c *fiber.Ctx, req *validation.DeliveryOrderUpdate, id uint) (*dto.MarketingDetailDTO, error) {
|
||||
if err := s.Validate.Struct(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
+2
-2
@@ -11,8 +11,8 @@ import (
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||
productWarehouseRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
||||
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/repositories"
|
||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/validations"
|
||||
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/repositories"
|
||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/validations"
|
||||
customerRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/master/customers/repositories"
|
||||
warehouseRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories"
|
||||
projectFlockKandangRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
|
||||
+4
-4
@@ -11,22 +11,22 @@ type DeliveryProduct struct {
|
||||
VehicleNumber string `json:"vehicle_number" validate:"omitempty,max=50"`
|
||||
}
|
||||
|
||||
type Create struct {
|
||||
type DeliveryOrderCreate struct {
|
||||
MarketingId uint `json:"marketing_id" validate:"required,gt=0"`
|
||||
DeliveryProducts []DeliveryProduct `json:"delivery_products" validate:"required,min=1,dive"`
|
||||
}
|
||||
|
||||
type Update struct {
|
||||
type DeliveryOrderUpdate struct {
|
||||
DeliveryProducts []DeliveryProduct `json:"delivery_products" validate:"omitempty,min=1,dive"`
|
||||
}
|
||||
|
||||
type Query struct {
|
||||
type DeliveryOrderQuery struct {
|
||||
Page int `query:"page" validate:"omitempty,number,min=1,gt=0"`
|
||||
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100,gt=0"`
|
||||
MarketingId uint `query:"marketing_id" validate:"omitempty,gt=0"`
|
||||
}
|
||||
|
||||
type Approve struct {
|
||||
type DeliveryOrderApprove struct {
|
||||
Action string `json:"action" validate:"required_strict"`
|
||||
ApprovableIds []uint `json:"approvable_ids" validate:"required_strict,min=1,dive,gt=0"`
|
||||
Notes *string `json:"notes,omitempty" validate:"omitempty,max=500"`
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
type ProjectChickinRepository interface {
|
||||
repository.BaseRepository[entity.ProjectChickin]
|
||||
GetFirstByProjectFlockID(ctx context.Context, projectFlockID uint) (*entity.ProjectChickin, error)
|
||||
GetByProjectFlockID(ctx context.Context, projectFlockID uint) ([]entity.ProjectChickin, error)
|
||||
GetByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) ([]entity.ProjectChickin, error)
|
||||
GetPendingByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) ([]entity.ProjectChickin, error)
|
||||
GetTotalPendingUsageQtyByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) (float64, error)
|
||||
@@ -40,6 +41,16 @@ func (r *ChickinRepositoryImpl) GetFirstByProjectFlockID(ctx context.Context, pr
|
||||
return &chickin, nil
|
||||
}
|
||||
|
||||
func (r *ChickinRepositoryImpl) GetByProjectFlockID(ctx context.Context, projectFlockID uint) ([]entity.ProjectChickin, error) {
|
||||
var chickins []entity.ProjectChickin
|
||||
err := r.db.WithContext(ctx).
|
||||
Joins("JOIN project_flock_kandangs ON project_flock_kandangs.id = project_chickins.project_flock_kandang_id").
|
||||
Where("project_flock_kandangs.project_flock_id = ?", projectFlockID).
|
||||
Order("project_chickins.created_at DESC").
|
||||
Find(&chickins).Error
|
||||
return chickins, err
|
||||
}
|
||||
|
||||
func (r *ChickinRepositoryImpl) GetByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) ([]entity.ProjectChickin, error) {
|
||||
var chickins []entity.ProjectChickin
|
||||
err := r.db.WithContext(ctx).
|
||||
|
||||
@@ -197,7 +197,7 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) ([]enti
|
||||
|
||||
if category == string(utils.ProjectFlockCategoryLaying) {
|
||||
for _, chickin := range newChikins {
|
||||
updates := map[string]any{"quantity": gorm.Expr("quantity - ?", chickin.PendingUsageQty)}
|
||||
updates := map[string]any{"qty": gorm.Expr("qty - ?", chickin.PendingUsageQty)}
|
||||
|
||||
if err := productWarehouseTx.PatchOne(c.Context(), chickin.ProductWarehouseId, updates, nil); err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
@@ -498,7 +498,7 @@ func (s chickinService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entit
|
||||
for _, chickin := range chickins {
|
||||
|
||||
if categoryForRejection == string(utils.ProjectFlockCategoryGrowing) {
|
||||
updates := map[string]any{"quantity": gorm.Expr("quantity + ?", chickin.PendingUsageQty)}
|
||||
updates := map[string]any{"qty": gorm.Expr("qty + ?", chickin.PendingUsageQty)}
|
||||
|
||||
if err := productWarehouseTx.PatchOne(c.Context(), chickin.ProductWarehouseId, updates, nil); err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
@@ -600,7 +600,7 @@ func (s *chickinService) convertChickinsToTarget(ctx *fiber.Ctx, chickins []enti
|
||||
|
||||
if chickin.ProductWarehouseId != targetPW.Id {
|
||||
if err := productWarehouseTx.PatchOne(ctx.Context(), chickin.ProductWarehouseId, map[string]any{
|
||||
"quantity": gorm.Expr("quantity - ?", quantityToConvert),
|
||||
"qty": gorm.Expr("qty - ?", quantityToConvert),
|
||||
}, nil); err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Source product warehouse %d not found", chickin.ProductWarehouseId))
|
||||
@@ -610,7 +610,7 @@ func (s *chickinService) convertChickinsToTarget(ctx *fiber.Ctx, chickins []enti
|
||||
}
|
||||
|
||||
if err := productWarehouseTx.PatchOne(ctx.Context(), targetPW.Id, map[string]any{
|
||||
"quantity": gorm.Expr("quantity + ?", quantityToConvert),
|
||||
"qty": gorm.Expr("qty + ?", quantityToConvert),
|
||||
}, nil); err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Target product warehouse %d not found", targetPW.Id))
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
"gorm.io/gorm"
|
||||
@@ -8,6 +10,7 @@ import (
|
||||
|
||||
type ProjectBudgetRepository interface {
|
||||
repository.BaseRepository[entity.ProjectBudget]
|
||||
GetByProjectFlockID(ctx context.Context, projectFlockID uint) ([]entity.ProjectBudget, error)
|
||||
}
|
||||
|
||||
type ProjectBudgetRepositoryImpl struct {
|
||||
@@ -21,3 +24,13 @@ func NewProjectBudgetRepository(db *gorm.DB) ProjectBudgetRepository {
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ProjectBudgetRepositoryImpl) GetByProjectFlockID(ctx context.Context, projectFlockID uint) ([]entity.ProjectBudget, error) {
|
||||
var budgets []entity.ProjectBudget
|
||||
err := r.db.WithContext(ctx).
|
||||
Where("project_flock_id = ?", projectFlockID).
|
||||
Preload("Nonstock").
|
||||
Preload("Nonstock.Uom").
|
||||
Find(&budgets).Error
|
||||
return budgets, err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/modules/repports/dto"
|
||||
service "gitlab.com/mbugroup/lti-api.git/internal/modules/repports/services"
|
||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/repports/validations"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/response"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
type RepportController struct {
|
||||
RepportService service.RepportService
|
||||
}
|
||||
|
||||
func NewRepportController(repportService service.RepportService) *RepportController {
|
||||
return &RepportController{
|
||||
RepportService: repportService,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *RepportController) GetAll(ctx *fiber.Ctx) error {
|
||||
query := &validation.Query{
|
||||
Page: ctx.QueryInt("page", 1),
|
||||
Limit: ctx.QueryInt("limit", 10),
|
||||
Search: ctx.Query("search", ""),
|
||||
}
|
||||
|
||||
if query.Page < 1 || query.Limit < 1 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0")
|
||||
}
|
||||
|
||||
result, totalResults, err := c.RepportService.GetAll(ctx, query)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ctx.Status(fiber.StatusOK).
|
||||
JSON(response.SuccessWithPaginate[dto.RepportListDTO]{
|
||||
Code: fiber.StatusOK,
|
||||
Status: "success",
|
||||
Message: "Get all reports successfully",
|
||||
Meta: response.Meta{
|
||||
Page: query.Page,
|
||||
Limit: query.Limit,
|
||||
TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))),
|
||||
TotalResults: totalResults,
|
||||
},
|
||||
Data: result,
|
||||
})
|
||||
}
|
||||
|
||||
func (c *RepportController) GetOne(ctx *fiber.Ctx) error {
|
||||
param := ctx.Params("id")
|
||||
|
||||
id, err := strconv.Atoi(param)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid Id")
|
||||
}
|
||||
|
||||
result, err := c.RepportService.GetOne(ctx, uint(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ctx.Status(fiber.StatusOK).
|
||||
JSON(response.Success{
|
||||
Code: fiber.StatusOK,
|
||||
Status: "success",
|
||||
Message: "Get report successfully",
|
||||
Data: result,
|
||||
})
|
||||
}
|
||||
|
||||
func (c *RepportController) GetExpense(ctx *fiber.Ctx) error {
|
||||
param := ctx.Params("id")
|
||||
|
||||
id, err := strconv.Atoi(param)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid Id")
|
||||
}
|
||||
|
||||
result, err := c.RepportService.GetOne(ctx, uint(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ctx.Status(fiber.StatusOK).
|
||||
JSON(response.Success{
|
||||
Code: fiber.StatusOK,
|
||||
Status: "success",
|
||||
Message: "Get report successfully",
|
||||
Data: result,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package dto
|
||||
|
||||
import "time"
|
||||
|
||||
// === DTO Structs ===
|
||||
|
||||
type RepportListDTO struct {
|
||||
Id uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type RepportDetailDTO struct {
|
||||
RepportListDTO
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package repports
|
||||
|
||||
import (
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"gorm.io/gorm"
|
||||
|
||||
sRepport "gitlab.com/mbugroup/lti-api.git/internal/modules/repports/services"
|
||||
|
||||
expenseRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/repositories"
|
||||
)
|
||||
|
||||
type RepportModule struct{}
|
||||
|
||||
func (RepportModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) {
|
||||
// Initialize expense realization repository
|
||||
expRealizationRepo := expenseRepo.NewExpenseRealizationRepository(db)
|
||||
|
||||
// Initialize report service with expense realization repo
|
||||
repportService := sRepport.NewRepportService(validate, expRealizationRepo)
|
||||
|
||||
RepportRoutes(router, repportService)
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package repports
|
||||
|
||||
import (
|
||||
|
||||
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/repports/controllers"
|
||||
repport "gitlab.com/mbugroup/lti-api.git/internal/modules/repports/services"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func RepportRoutes(v1 fiber.Router, s repport.RepportService) {
|
||||
ctrl := controller.NewRepportController(s)
|
||||
|
||||
route := v1.Group("/repports")
|
||||
|
||||
route.Get("/", ctrl.GetAll)
|
||||
route.Get("/:id", ctrl.GetOne)
|
||||
|
||||
route.Get("expense", ctrl.GetExpense)
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/modules/repports/dto"
|
||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/repports/validations"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||
|
||||
expenseRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/repositories"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type RepportService interface {
|
||||
GetAll(ctx *fiber.Ctx, params *validation.Query) ([]dto.RepportListDTO, int64, error)
|
||||
GetOne(ctx *fiber.Ctx, id uint) (*dto.RepportListDTO, error)
|
||||
GetExpense(ctx *fiber.Ctx, id uint) (*dto.RepportListDTO, error)
|
||||
}
|
||||
|
||||
type repportService struct {
|
||||
Log *logrus.Logger
|
||||
Validate *validator.Validate
|
||||
dummyData map[uint]dto.RepportListDTO
|
||||
ExpenseRealizationRepo expenseRepo.ExpenseRealizationRepository
|
||||
}
|
||||
|
||||
func NewRepportService(validate *validator.Validate, expenseRealizationRepo expenseRepo.ExpenseRealizationRepository) RepportService {
|
||||
// Initialize with dummy data
|
||||
now := time.Now()
|
||||
dummyData := map[uint]dto.RepportListDTO{
|
||||
1: {
|
||||
Id: 1,
|
||||
Name: "Sales Report",
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
},
|
||||
2: {
|
||||
Id: 2,
|
||||
Name: "Inventory Report",
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
},
|
||||
3: {
|
||||
Id: 3,
|
||||
Name: "Production Report",
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
},
|
||||
}
|
||||
|
||||
return &repportService{
|
||||
Log: utils.Log,
|
||||
Validate: validate,
|
||||
dummyData: dummyData,
|
||||
ExpenseRealizationRepo: expenseRealizationRepo,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *repportService) GetAll(c *fiber.Ctx, params *validation.Query) ([]dto.RepportListDTO, int64, error) {
|
||||
if err := s.Validate.Struct(params); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// Convert map to slice
|
||||
var results []dto.RepportListDTO
|
||||
for _, v := range s.dummyData {
|
||||
// Apply search filter if provided
|
||||
if params.Search != "" && !strings.Contains(strings.ToLower(v.Name), strings.ToLower(params.Search)) {
|
||||
continue
|
||||
}
|
||||
results = append(results, v)
|
||||
}
|
||||
|
||||
// Apply pagination
|
||||
total := int64(len(results))
|
||||
offset := (params.Page - 1) * params.Limit
|
||||
|
||||
if offset >= int(total) {
|
||||
return []dto.RepportListDTO{}, total, nil
|
||||
}
|
||||
|
||||
end := offset + params.Limit
|
||||
if end > int(total) {
|
||||
end = int(total)
|
||||
}
|
||||
|
||||
return results[offset:end], total, nil
|
||||
}
|
||||
|
||||
func (s *repportService) GetOne(c *fiber.Ctx, id uint) (*dto.RepportListDTO, error) {
|
||||
if data, ok := s.dummyData[id]; ok {
|
||||
return &data, nil
|
||||
}
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "Report not found")
|
||||
}
|
||||
|
||||
func (s *repportService) GetExpense(c *fiber.Ctx, id uint) (*dto.RepportListDTO, error) {
|
||||
if data, ok := s.dummyData[id]; ok {
|
||||
return &data, nil
|
||||
}
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "Report not found")
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package validation
|
||||
|
||||
type Query struct {
|
||||
Page int `query:"page" validate:"omitempty,number,min=1,gt=0"`
|
||||
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100,gt=0"`
|
||||
Search string `query:"search" validate:"omitempty,max=50"`
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
purchases "gitlab.com/mbugroup/lti-api.git/internal/modules/purchases"
|
||||
ssoModule "gitlab.com/mbugroup/lti-api.git/internal/modules/sso"
|
||||
users "gitlab.com/mbugroup/lti-api.git/internal/modules/users"
|
||||
repports "gitlab.com/mbugroup/lti-api.git/internal/modules/repports"
|
||||
// MODULE IMPORTS
|
||||
)
|
||||
|
||||
@@ -42,6 +43,7 @@ func Routes(app *fiber.App, db *gorm.DB) {
|
||||
expenses.ExpenseModule{},
|
||||
ssoModule.Module{},
|
||||
closings.ClosingModule{},
|
||||
repports.RepportModule{},
|
||||
// MODULE REGISTRY
|
||||
}
|
||||
|
||||
|
||||
@@ -263,6 +263,7 @@ func createProductWarehouseRow(t *testing.T, db *gorm.DB, qty float64) entity.Pr
|
||||
ProductId: 1,
|
||||
WarehouseId: 1,
|
||||
Quantity: qty,
|
||||
// CreatedBy: 1,
|
||||
}
|
||||
if err := db.Create(&pw).Error; err != nil {
|
||||
t.Fatalf("create product warehouse: %v", err)
|
||||
|
||||
Reference in New Issue
Block a user