mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-23 23:05:44 +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"`
|
CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"`
|
||||||
Location Location `gorm:"foreignKey:LocationId;references:Id"`
|
Location Location `gorm:"foreignKey:LocationId;references:Id"`
|
||||||
Pic User `gorm:"foreignKey:PicId;references:Id"`
|
Pic User `gorm:"foreignKey:PicId;references:Id"`
|
||||||
|
Warehouses []Warehouse `gorm:"foreignKey:KandangId;references:Id"`
|
||||||
ProjectFlockKandangs []ProjectFlockKandang `gorm:"foreignKey:KandangId;references:Id" json:"-"`
|
ProjectFlockKandangs []ProjectFlockKandang `gorm:"foreignKey:KandangId;references:Id" json:"-"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package controller
|
|||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/modules/closings/dto"
|
"gitlab.com/mbugroup/lti-api.git/internal/modules/closings/dto"
|
||||||
service "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/services"
|
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).
|
return c.Status(fiber.StatusOK).
|
||||||
JSON(response.SuccessWithPaginate[dto.ClosingListDTO]{
|
JSON(response.SuccessWithPaginate[dto.ClosingListItemDTO]{
|
||||||
Code: fiber.StatusOK,
|
Code: fiber.StatusOK,
|
||||||
Status: "success",
|
Status: "success",
|
||||||
Message: "Get all closings successfully",
|
Message: "Retrieved closing projects list successfully",
|
||||||
Meta: response.Meta{
|
Meta: response.Meta{
|
||||||
Page: query.Page,
|
Page: query.Page,
|
||||||
Limit: query.Limit,
|
Limit: query.Limit,
|
||||||
TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))),
|
TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))),
|
||||||
TotalResults: totalResults,
|
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),
|
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
|
ClosingListDTO
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ClosingListItemDTO struct {
|
||||||
|
Id uint `json:"id"`
|
||||||
|
LocationID uint `json:"location_id"`
|
||||||
|
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 {
|
type ClosingSummaryDTO struct {
|
||||||
LocationID uint `json:"location_id"`
|
FlockID uint `json:"flock_id"`
|
||||||
Periode int `json:"periode"`
|
Period int `json:"period"`
|
||||||
JenisProduk string `json:"jenis_produk"`
|
// JenisProduk string `json:"jenis_produk"`
|
||||||
LabelPopulasi string `json:"label_populasi"`
|
// LabelPopulasi string `json:"label_populasi"`
|
||||||
JumlahPopulasi int `json:"jumlah_populasi"`
|
Population int `json:"population"`
|
||||||
JumlahPopulasiFormatted string `json:"jumlah_populasi_formatted"`
|
PopulationFormatted string `json:"population_formatted"`
|
||||||
JenisProject string `json:"jenis_project"`
|
ProjectType string `json:"project_type"`
|
||||||
KandangAktif int `json:"kandang_aktif"`
|
ActiveHouseCount int `json:"active_house_count"`
|
||||||
KandangAktifFormatted string `json:"kandang_aktif_formatted"`
|
ActiveHouseLabel string `json:"active_house_label"`
|
||||||
StatusPembayaranPenjualan string `json:"status_pembayaran_penjualan"`
|
SalesPaymentStatus string `json:"sales_payment_status"`
|
||||||
StatusPembayaranMitra string `json:"status_pembayaran_mitra"`
|
// StatusPembayaranMitra string `json:"status_pembayaran_mitra"`
|
||||||
StatusProject string `json:"status_project"`
|
StatusProject string `json:"project_status"`
|
||||||
StatusClosing string `json:"status_closing"`
|
StatusClosing string `json:"closing_status"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func ToClosingSummaryDTO(project entity.ProjectFlock, statusProject, statusClosing string) ClosingSummaryDTO {
|
func ToClosingSummaryDTO(project entity.ProjectFlock, statusProject, statusClosing string) ClosingSummaryDTO {
|
||||||
@@ -52,19 +67,38 @@ func ToClosingSummaryDTO(project entity.ProjectFlock, statusProject, statusClosi
|
|||||||
populationInt := int(population)
|
populationInt := int(population)
|
||||||
|
|
||||||
return ClosingSummaryDTO{
|
return ClosingSummaryDTO{
|
||||||
LocationID: project.LocationId,
|
FlockID: project.Id,
|
||||||
Periode: period,
|
Period: period,
|
||||||
JenisProduk: project.Category,
|
// JenisProduk: project.Category,
|
||||||
LabelPopulasi: "",
|
// LabelPopulasi: "",
|
||||||
JumlahPopulasi: populationInt,
|
Population: populationInt,
|
||||||
JumlahPopulasiFormatted: fmt.Sprintf("%d Ekor", populationInt),
|
PopulationFormatted: fmt.Sprintf("%d Ekor", populationInt),
|
||||||
JenisProject: "",
|
ProjectType: project.Category,
|
||||||
KandangAktif: kandangCount,
|
ActiveHouseCount: kandangCount,
|
||||||
KandangAktifFormatted: fmt.Sprintf("%d Kandang", kandangCount),
|
ActiveHouseLabel: fmt.Sprintf("%d Kandang", kandangCount),
|
||||||
StatusPembayaranPenjualan: "Tempo",
|
SalesPaymentStatus: "Tempo",
|
||||||
StatusPembayaranMitra: "",
|
// StatusPembayaranMitra: "",
|
||||||
StatusProject: statusProject,
|
StatusProject: statusProject,
|
||||||
StatusClosing: statusClosing,
|
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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,14 +4,13 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
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"
|
customerDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/customers/dto"
|
||||||
kandangDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/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"
|
productDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/products/dto"
|
||||||
)
|
)
|
||||||
|
|
||||||
// === Response DTO ===
|
// === Response DTO ===
|
||||||
|
|
||||||
type SalesDTO struct {
|
type SalesDTO struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
RealizationDate time.Time `json:"realization_date"`
|
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"
|
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
||||||
rClosing "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/repositories"
|
rClosing "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/repositories"
|
||||||
sClosing "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/services"
|
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"
|
rProjectFlock "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
|
||||||
|
|
||||||
rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/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)
|
closingRepo := rClosing.NewClosingRepository(db)
|
||||||
userRepo := rUser.NewUserRepository(db)
|
userRepo := rUser.NewUserRepository(db)
|
||||||
projectFlockRepo := rProjectFlock.NewProjectflockRepository(db)
|
projectFlockRepo := rProjectFlock.NewProjectflockRepository(db)
|
||||||
|
projectBudgetRepo := rProjectFlock.NewProjectBudgetRepository(db)
|
||||||
marketingRepo := rMarketings.NewMarketingRepository(db)
|
marketingRepo := rMarketings.NewMarketingRepository(db)
|
||||||
marketingDeliveryProductRepo := rMarketings.NewMarketingDeliveryProductRepository(db)
|
marketingDeliveryProductRepo := rMarketings.NewMarketingDeliveryProductRepository(db)
|
||||||
|
expenseRealizationRepo := rExpenseRealization.NewExpenseRealizationRepository(db)
|
||||||
|
chickinRepo := rChickin.NewChickinRepository(db)
|
||||||
approvalRepo := commonRepo.NewApprovalRepository(db)
|
approvalRepo := commonRepo.NewApprovalRepository(db)
|
||||||
approvalService := commonSvc.NewApprovalService(approvalRepo)
|
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)
|
userService := sUser.NewUserService(userRepo, validate)
|
||||||
|
|
||||||
ClosingRoutes(router, userService, closingService)
|
ClosingRoutes(router, userService, closingService)
|
||||||
|
|||||||
@@ -1,13 +1,20 @@
|
|||||||
package repository
|
package repository
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/validations"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ClosingRepository interface {
|
type ClosingRepository interface {
|
||||||
repository.BaseRepository[entity.ProjectFlock]
|
repository.BaseRepository[entity.ProjectFlock]
|
||||||
|
GetSapronak(ctx context.Context, params SapronakQueryParams) ([]SapronakRow, int64, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClosingRepositoryImpl struct {
|
type ClosingRepositoryImpl struct {
|
||||||
@@ -19,3 +26,199 @@ func NewClosingRepository(db *gorm.DB) ClosingRepository {
|
|||||||
BaseRepositoryImpl: repository.NewBaseRepository[entity.ProjectFlock](db),
|
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) {
|
func ClosingRoutes(v1 fiber.Router, u user.UserService, s closing.ClosingService) {
|
||||||
ctrl := controller.NewClosingController(s)
|
ctrl := controller.NewClosingController(s)
|
||||||
|
|
||||||
route := v1.Group("/closing")
|
route := v1.Group("/closings")
|
||||||
|
|
||||||
// route.Get("/", m.Auth(u), ctrl.GetAll)
|
// route.Get("/", m.Auth(u), ctrl.GetAll)
|
||||||
// route.Post("/", m.Auth(u), ctrl.CreateOne)
|
// 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("/", ctrl.GetAll)
|
||||||
route.Get("/:project_flock_id/penjualan", ctrl.GetPenjualan)
|
route.Get("/:project_flock_id/penjualan", ctrl.GetPenjualan)
|
||||||
|
route.Get("/:project_flock_id/overhead", ctrl.GetOverhead)
|
||||||
route.Get("/:projectFlockId", ctrl.GetClosingSummary)
|
route.Get("/:projectFlockId", ctrl.GetClosingSummary)
|
||||||
|
route.Get("/:projectFlockId/sapronak", ctrl.GetClosingSapronak)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,17 @@ package service
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/modules/closings/dto"
|
"gitlab.com/mbugroup/lti-api.git/internal/modules/closings/dto"
|
||||||
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/repositories"
|
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/repositories"
|
||||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/validations"
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/validations"
|
||||||
marketingDeliveryProductRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/repositories"
|
expenseRealizationRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/repositories"
|
||||||
marketingRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/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"
|
projectflockRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
approvalutils "gitlab.com/mbugroup/lti-api.git/internal/utils/approvals"
|
approvalutils "gitlab.com/mbugroup/lti-api.git/internal/utils/approvals"
|
||||||
@@ -22,10 +25,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type ClosingService interface {
|
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)
|
GetProjectFlockByID(ctx *fiber.Ctx, id uint) (*entity.ProjectFlock, error)
|
||||||
GetPenjualan(ctx *fiber.Ctx, projectFlockID uint) ([]entity.MarketingDeliveryProduct, error)
|
GetPenjualan(ctx *fiber.Ctx, projectFlockID uint) ([]entity.MarketingDeliveryProduct, error)
|
||||||
GetClosingSummary(ctx *fiber.Ctx, projectFlockID uint) (*dto.ClosingSummaryDTO, 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 {
|
type closingService struct {
|
||||||
@@ -36,9 +41,12 @@ type closingService struct {
|
|||||||
MarketingRepo marketingRepository.MarketingRepository
|
MarketingRepo marketingRepository.MarketingRepository
|
||||||
MarketingDeliveryProductRepo marketingDeliveryProductRepository.MarketingDeliveryProductRepository
|
MarketingDeliveryProductRepo marketingDeliveryProductRepository.MarketingDeliveryProductRepository
|
||||||
ApprovalSvc commonSvc.ApprovalService
|
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{
|
return &closingService{
|
||||||
Log: utils.Log,
|
Log: utils.Log,
|
||||||
Validate: validate,
|
Validate: validate,
|
||||||
@@ -47,6 +55,9 @@ func NewClosingService(repo repository.ClosingRepository, projectFlockRepo proje
|
|||||||
MarketingRepo: marketingRepo,
|
MarketingRepo: marketingRepo,
|
||||||
MarketingDeliveryProductRepo: marketingDeliveryProductRepo,
|
MarketingDeliveryProductRepo: marketingDeliveryProductRepo,
|
||||||
ApprovalSvc: approvalSvc,
|
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 {
|
func (s closingService) withClosingRelations(db *gorm.DB) *gorm.DB {
|
||||||
return s.withRelations(db).
|
return s.withRelations(db).
|
||||||
|
Preload("Location").
|
||||||
Preload("KandangHistory").
|
Preload("KandangHistory").
|
||||||
Preload("KandangHistory.Chickins")
|
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 {
|
if err := s.Validate.Struct(params); err != nil {
|
||||||
return nil, 0, err
|
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
|
offset := (params.Page - 1) * params.Limit
|
||||||
|
|
||||||
closings, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB {
|
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 != "" {
|
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")
|
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)
|
s.Log.Errorf("Failed to get closings: %+v", err)
|
||||||
return nil, 0, 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) {
|
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
|
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) {
|
func (s closingService) getApprovalStatuses(ctx context.Context, projectFlockID uint) (string, string, error) {
|
||||||
if s.ApprovalSvc == nil {
|
if s.ApprovalSvc == nil {
|
||||||
return "", "Belum Selesai", nil
|
return "", "Belum Selesai", nil
|
||||||
@@ -188,3 +353,29 @@ func (s closingService) getApprovalStatuses(ctx context.Context, projectFlockID
|
|||||||
|
|
||||||
return statusProject, statusClosing, nil
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
package validation
|
package validation
|
||||||
|
|
||||||
type Create struct {
|
type Create struct {
|
||||||
Name string `json:"name" validate:"required_strict,min=3"`
|
Name string `json:"name" validate:"required_strict,min=3"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Update struct {
|
type Update struct {
|
||||||
Name *string `json:"name,omitempty" validate:"omitempty"`
|
Name *string `json:"name,omitempty" validate:"omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Query struct {
|
type Query struct {
|
||||||
@@ -13,3 +13,14 @@ type Query struct {
|
|||||||
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100,gt=0"`
|
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100,gt=0"`
|
||||||
Search string `query:"search" validate:"omitempty,max=50"`
|
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]
|
repository.BaseRepository[entity.ExpenseRealization]
|
||||||
IdExists(ctx context.Context, id uint64) (bool, error)
|
IdExists(ctx context.Context, id uint64) (bool, error)
|
||||||
GetByExpenseNonstockID(ctx context.Context, expenseNonstockID uint64) (*entity.ExpenseRealization, error)
|
GetByExpenseNonstockID(ctx context.Context, expenseNonstockID uint64) (*entity.ExpenseRealization, error)
|
||||||
|
GetByProjectFlockID(ctx context.Context, projectFlockID uint) ([]entity.ExpenseRealization, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExpenseRealizationRepositoryImpl struct {
|
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) {
|
func (r *ExpenseRealizationRepositoryImpl) GetByExpenseNonstockID(ctx context.Context, expenseNonstockID uint64) (*entity.ExpenseRealization, error) {
|
||||||
var realization entity.ExpenseRealization
|
var realization entity.ExpenseRealization
|
||||||
err := r.DB().WithContext(ctx).
|
err := r.DB().WithContext(ctx).Where("expense_nonstock_id = ?", expenseNonstockID).First(&realization).Error
|
||||||
Where("expense_nonstock_id = ?", expenseNonstockID).
|
return &realization, err
|
||||||
First(&realization).Error
|
}
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
func (r *ExpenseRealizationRepositoryImpl) GetByProjectFlockID(ctx context.Context, projectFlockID uint) ([]entity.ExpenseRealization, error) {
|
||||||
}
|
var realizations []entity.ExpenseRealization
|
||||||
return &realization, nil
|
err := r.DB().WithContext(ctx).
|
||||||
|
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"
|
commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||||
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
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"
|
expenseDto "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/dto"
|
||||||
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/repositories"
|
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/repositories"
|
||||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/validations"
|
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")
|
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{
|
expense = &entity.Expense{
|
||||||
ReferenceNumber: referenceNumber,
|
ReferenceNumber: referenceNumber,
|
||||||
PoNumber: req.PoNumber,
|
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 {
|
if *latestApproval.Action != entity.ApprovalActionUpdated {
|
||||||
|
|
||||||
approvalAction := entity.ApprovalActionUpdated
|
approvalAction := entity.ApprovalActionUpdated
|
||||||
@@ -689,7 +697,10 @@ func (s *expenseService) CompleteExpense(c *fiber.Ctx, id uint, notes *string) (
|
|||||||
return nil, err
|
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)
|
latestApproval, err := s.ApprovalSvc.LatestByTarget(c.Context(), utils.ApprovalWorkflowExpense, id, nil)
|
||||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
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")
|
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
|
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))
|
approvalSvcTx := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(tx))
|
||||||
expenseRepoTx := repository.NewExpenseRepository(tx)
|
expenseRepoTx := repository.NewExpenseRepository(tx)
|
||||||
|
|||||||
@@ -21,10 +21,11 @@ func (AdjustmentModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validat
|
|||||||
stockLogsRepo := rStockLogs.NewStockLogRepository(db)
|
stockLogsRepo := rStockLogs.NewStockLogRepository(db)
|
||||||
warehouseRepo := rWarehouse.NewWarehouseRepository(db)
|
warehouseRepo := rWarehouse.NewWarehouseRepository(db)
|
||||||
productWarehouseRepo := rProductWarehouse.NewProductWarehouseRepository(db)
|
productWarehouseRepo := rProductWarehouse.NewProductWarehouseRepository(db)
|
||||||
|
projectFlockKandangRepo := rProjectFlockKandang.NewProjectFlockKandangRepository(db)
|
||||||
userRepo := rUser.NewUserRepository(db)
|
userRepo := rUser.NewUserRepository(db)
|
||||||
productRepo := rproduct.NewProductRepository(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)
|
userService := sUser.NewUserService(userRepo, validate)
|
||||||
|
|
||||||
AdjustmentRoutes(router, userService, adjustmentService)
|
AdjustmentRoutes(router, userService, adjustmentService)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package adjustments
|
package adjustments
|
||||||
|
|
||||||
import (
|
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"
|
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/adjustments/controllers"
|
||||||
adjustment "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/adjustments/services"
|
adjustment "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/adjustments/services"
|
||||||
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/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)
|
ctrl := controller.NewAdjustmentController(s)
|
||||||
|
|
||||||
route := v1.Group("/adjustments")
|
route := v1.Group("/adjustments")
|
||||||
|
route.Use(m.Auth(u))
|
||||||
// Standard CRUD routes following master data pattern
|
// Standard CRUD routes following master data pattern
|
||||||
route.Get("/", ctrl.AdjustmentHistory) // Get all with pagination and filters
|
route.Get("/", ctrl.AdjustmentHistory) // Get all with pagination and filters
|
||||||
route.Post("/", ctrl.Adjustment) // Create adjustment
|
route.Post("/", ctrl.Adjustment) // Create adjustment
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
@@ -36,8 +38,7 @@ type adjustmentService struct {
|
|||||||
ProjectFlockKandangRepo projectFlockKandangRepo.ProjectFlockKandangRepository
|
ProjectFlockKandangRepo projectFlockKandangRepo.ProjectFlockKandangRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAdjustmentService(productRepo productRepo.ProductRepository, stockLogsRepo stockLogsRepo.StockLogRepository, warehouseRepo warehouseRepo.WarehouseRepository, productWarehouseRepo ProductWarehouse.ProductWarehouseRepository, projectFlockKandangRepo projectFlockKandangRepo.ProjectFlockKandangRepository,
|
func NewAdjustmentService(productRepo productRepo.ProductRepository, stockLogsRepo stockLogsRepo.StockLogRepository, warehouseRepo warehouseRepo.WarehouseRepository, productWarehouseRepo ProductWarehouse.ProductWarehouseRepository, validate *validator.Validate, projectFlockKandangRepo projectFlockKandangRepo.ProjectFlockKandangRepository) AdjustmentService {
|
||||||
validate *validator.Validate) AdjustmentService {
|
|
||||||
return &adjustmentService{
|
return &adjustmentService{
|
||||||
Log: utils.Log,
|
Log: utils.Log,
|
||||||
Validate: validate,
|
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")
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to validate product warehouse")
|
||||||
}
|
}
|
||||||
if !isProductWarehouseExist {
|
if !isProductWarehouseExist {
|
||||||
|
projectFlockKandangID, err := s.getActiveProjectFlockKandangID(ctx, uint(req.WarehouseID))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
newPW := &entity.ProductWarehouse{
|
newPW := &entity.ProductWarehouse{
|
||||||
ProductId: uint(req.ProductID),
|
ProductId: uint(req.ProductID),
|
||||||
WarehouseId: uint(req.WarehouseID),
|
WarehouseId: uint(req.WarehouseID),
|
||||||
Quantity: 0,
|
Quantity: 0,
|
||||||
|
ProjectFlockKandangId: &projectFlockKandangID,
|
||||||
// CreatedBy: 1, // TODO: should Get from auth middleware
|
// 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)
|
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) {
|
func (s *adjustmentService) AdjustmentHistory(c *fiber.Ctx, query *validation.Query) ([]*entity.StockLog, int64, error) {
|
||||||
if err := s.Validate.Struct(query); err != nil {
|
if err := s.Validate.Struct(query); err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
|
|||||||
@@ -9,11 +9,11 @@ import (
|
|||||||
rStockTransfer "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/transfers/repositories"
|
rStockTransfer "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/transfers/repositories"
|
||||||
sTransfer "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/transfers/services"
|
sTransfer "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/transfers/services"
|
||||||
rSupplier "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers/repositories"
|
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"
|
rProjectFlockKandang "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
|
||||||
rStockLogs "gitlab.com/mbugroup/lti-api.git/internal/modules/shared/repositories"
|
rStockLogs "gitlab.com/mbugroup/lti-api.git/internal/modules/shared/repositories"
|
||||||
rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories"
|
rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories"
|
||||||
sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
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{}
|
type TransferModule struct{}
|
||||||
@@ -26,9 +26,10 @@ func (TransferModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate
|
|||||||
stockLogsRepo := rStockLogs.NewStockLogRepository(db)
|
stockLogsRepo := rStockLogs.NewStockLogRepository(db)
|
||||||
supplierRepo := rSupplier.NewSupplierRepository(db)
|
supplierRepo := rSupplier.NewSupplierRepository(db)
|
||||||
productWarehouseRepo := rProductWarehouse.NewProductWarehouseRepository(db)
|
productWarehouseRepo := rProductWarehouse.NewProductWarehouseRepository(db)
|
||||||
warehouseRepo := rWarehouse.NewWarehouseRepository(db)
|
|
||||||
userRepo := rUser.NewUserRepository(db)
|
userRepo := rUser.NewUserRepository(db)
|
||||||
|
warehouseRepo := rWarehouse.NewWarehouseRepository(db)
|
||||||
projectFlockKandangRepo := rProjectFlockKandang.NewProjectFlockKandangRepository(db)
|
projectFlockKandangRepo := rProjectFlockKandang.NewProjectFlockKandangRepository(db)
|
||||||
|
|
||||||
transferService := sTransfer.NewTransferService(validate, stockTransferRepo, stockTransferDetailRepo, stockTransferDeliveryRepo, StockTransferDeliveryItemRepo, stockLogsRepo, productWarehouseRepo, supplierRepo, warehouseRepo, projectFlockKandangRepo)
|
transferService := sTransfer.NewTransferService(validate, stockTransferRepo, stockTransferDetailRepo, stockTransferDeliveryRepo, StockTransferDeliveryItemRepo, stockLogsRepo, productWarehouseRepo, supplierRepo, warehouseRepo, projectFlockKandangRepo)
|
||||||
userService := sUser.NewUserService(userRepo, validate)
|
userService := sUser.NewUserService(userRepo, validate)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -15,7 +16,7 @@ import (
|
|||||||
rStockTransfer "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/transfers/repositories"
|
rStockTransfer "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/transfers/repositories"
|
||||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/transfers/validations"
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/transfers/validations"
|
||||||
rSupplier "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers/repositories"
|
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"
|
projectFlockKandangRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
|
||||||
rStockLogs "gitlab.com/mbugroup/lti-api.git/internal/modules/shared/repositories"
|
rStockLogs "gitlab.com/mbugroup/lti-api.git/internal/modules/shared/repositories"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
@@ -39,12 +40,11 @@ type transferService struct {
|
|||||||
StockLogsRepository rStockLogs.StockLogRepository
|
StockLogsRepository rStockLogs.StockLogRepository
|
||||||
ProductWarehouseRepo rProductWarehouse.ProductWarehouseRepository
|
ProductWarehouseRepo rProductWarehouse.ProductWarehouseRepository
|
||||||
SupplierRepo rSupplier.SupplierRepository
|
SupplierRepo rSupplier.SupplierRepository
|
||||||
WarehouseRepo rWarehouse.WarehouseRepository
|
WarehouseRepo warehouseRepo.WarehouseRepository
|
||||||
projectFlockKandangRepo projectFlockKandangRepo.ProjectFlockKandangRepository
|
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,
|
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 {
|
||||||
projectFlockKandangRepo projectFlockKandangRepo.ProjectFlockKandangRepository) TransferService {
|
|
||||||
return &transferService{
|
return &transferService{
|
||||||
Log: utils.Log,
|
Log: utils.Log,
|
||||||
Validate: validate,
|
Validate: validate,
|
||||||
@@ -56,7 +56,7 @@ func NewTransferService(validate *validator.Validate, stockTransferRepo rStockTr
|
|||||||
ProductWarehouseRepo: productWarehouseRepo,
|
ProductWarehouseRepo: productWarehouseRepo,
|
||||||
SupplierRepo: supplierRepo,
|
SupplierRepo: supplierRepo,
|
||||||
WarehouseRepo: warehouseRepo,
|
WarehouseRepo: warehouseRepo,
|
||||||
projectFlockKandangRepo: projectFlockKandangRepo,
|
ProjectFlockKandangRepo: projectFlockKandangRepo,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func (s transferService) withRelations(db *gorm.DB) *gorm.DB {
|
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) {
|
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
// Jika belum ada record untuk produk di gudang tujuan, buat baru
|
// 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{
|
destPW = &entity.ProductWarehouse{
|
||||||
ProductId: uint(product.ProductID),
|
ProductId: uint(product.ProductID),
|
||||||
WarehouseId: uint(req.DestinationWarehouseID),
|
WarehouseId: uint(req.DestinationWarehouseID),
|
||||||
Quantity: 0,
|
Quantity: 0,
|
||||||
|
ProjectFlockKandangId: &projectFlockKandangID,
|
||||||
// CreatedBy: 1, // TODO: should Get from auth middleware
|
// CreatedBy: 1, // TODO: should Get from auth middleware
|
||||||
}
|
}
|
||||||
if err := s.ProductWarehouseRepo.WithTx(tx).CreateOne(c.Context(), destPW, nil); err != nil {
|
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
|
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"
|
"math"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/delivery-orderss/dto"
|
"gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/dto"
|
||||||
service "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/delivery-orderss/services"
|
service "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/services"
|
||||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/delivery-orderss/validations"
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/validations"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/response"
|
"gitlab.com/mbugroup/lti-api.git/internal/response"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
@@ -23,7 +23,7 @@ func NewDeliveryOrdersController(deliveryOrdersService service.DeliveryOrdersSer
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *DeliveryOrdersController) GetAll(c *fiber.Ctx) error {
|
func (u *DeliveryOrdersController) GetAll(c *fiber.Ctx) error {
|
||||||
query := &validation.Query{
|
query := &validation.DeliveryOrderQuery{
|
||||||
Page: c.QueryInt("page", 1),
|
Page: c.QueryInt("page", 1),
|
||||||
Limit: c.QueryInt("limit", 10),
|
Limit: c.QueryInt("limit", 10),
|
||||||
MarketingId: uint(c.QueryInt("marketing_id", 0)),
|
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 {
|
func (u *DeliveryOrdersController) CreateOne(c *fiber.Ctx) error {
|
||||||
req := new(validation.Create)
|
req := new(validation.DeliveryOrderCreate)
|
||||||
|
|
||||||
if err := c.BodyParser(req); err != nil {
|
if err := c.BodyParser(req); err != nil {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
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 {
|
func (u *DeliveryOrdersController) UpdateOne(c *fiber.Ctx) error {
|
||||||
req := new(validation.Update)
|
req := new(validation.DeliveryOrderUpdate)
|
||||||
param := c.Params("id")
|
param := c.Params("id")
|
||||||
|
|
||||||
id, err := strconv.Atoi(param)
|
id, err := strconv.Atoi(param)
|
||||||
+3
-3
@@ -3,9 +3,9 @@ package controller
|
|||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/dto"
|
"gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/dto"
|
||||||
service "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/services"
|
service "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/services"
|
||||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/validations"
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/validations"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/response"
|
"gitlab.com/mbugroup/lti-api.git/internal/response"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"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"`
|
Customer customerDTO.CustomerRelationDTO `json:"customer"`
|
||||||
SalesPerson userDTO.UserRelationDTO `json:"sales_person"`
|
SalesPerson userDTO.UserRelationDTO `json:"sales_person"`
|
||||||
SoDocs string `json:"so_docs"`
|
SoDocs string `json:"so_docs"`
|
||||||
SalesOrder []MarketingProductDTO `json:"sales_order"`
|
SalesOrder []DeliveryMarketingProductDTO `json:"sales_order"`
|
||||||
CreatedUser userDTO.UserRelationDTO `json:"created_user"`
|
CreatedUser userDTO.UserRelationDTO `json:"created_user"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
@@ -36,13 +36,14 @@ type MarketingDetailDTO struct {
|
|||||||
Customer customerDTO.CustomerRelationDTO `json:"customer"`
|
Customer customerDTO.CustomerRelationDTO `json:"customer"`
|
||||||
SalesPerson userDTO.UserRelationDTO `json:"sales_person"`
|
SalesPerson userDTO.UserRelationDTO `json:"sales_person"`
|
||||||
SoDocs string `json:"so_docs"`
|
SoDocs string `json:"so_docs"`
|
||||||
SalesOrder []MarketingProductDTO `json:"sales_order"`
|
SalesOrder []DeliveryMarketingProductDTO `json:"sales_order"`
|
||||||
DeliveryOrder []DeliveryGroupDTO `json:"delivery_order"`
|
DeliveryOrder []DeliveryGroupDTO `json:"delivery_order"`
|
||||||
CreatedUser userDTO.UserRelationDTO `json:"created_user"`
|
CreatedUser userDTO.UserRelationDTO `json:"created_user"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
LatestApproval approvalDTO.ApprovalRelationDTO `json:"latest_approval"`
|
LatestApproval approvalDTO.ApprovalRelationDTO `json:"latest_approval"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type MarketingDeliveryProductDTO struct {
|
type MarketingDeliveryProductDTO struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
MarketingProductId uint `json:"marketing_product_id"`
|
MarketingProductId uint `json:"marketing_product_id"`
|
||||||
@@ -73,7 +74,7 @@ type DeliveryGroupDTO struct {
|
|||||||
Deliveries []DeliveryItemDTO `json:"deliveries"`
|
Deliveries []DeliveryItemDTO `json:"deliveries"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type MarketingProductDTO struct {
|
type DeliveryMarketingProductDTO struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
MarketingId uint `json:"marketing_id"`
|
MarketingId uint `json:"marketing_id"`
|
||||||
ProductWarehouseId uint `json:"product_warehouse_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
|
var productWarehouse *productwarehouseDTO.ProductWarehousNestedDTO
|
||||||
if e.ProductWarehouse.Id != 0 {
|
if e.ProductWarehouse.Id != 0 {
|
||||||
mapped := productwarehouseDTO.ToProductWarehouseNestedDTO(e.ProductWarehouse)
|
mapped := productwarehouseDTO.ToProductWarehouseNestedDTO(e.ProductWarehouse)
|
||||||
productWarehouse = &mapped
|
productWarehouse = &mapped
|
||||||
}
|
}
|
||||||
|
|
||||||
return MarketingProductDTO{
|
return DeliveryMarketingProductDTO{
|
||||||
Id: e.Id,
|
Id: e.Id,
|
||||||
MarketingId: e.MarketingId,
|
MarketingId: e.MarketingId,
|
||||||
ProductWarehouseId: e.ProductWarehouseId,
|
ProductWarehouseId: e.ProductWarehouseId,
|
||||||
@@ -155,11 +156,11 @@ func ToMarketingListDTO(marketing *entity.Marketing, deliveryProducts []entity.M
|
|||||||
latestApproval = mapped
|
latestApproval = mapped
|
||||||
}
|
}
|
||||||
|
|
||||||
var salesOrderProducts []MarketingProductDTO
|
var salesOrderProducts []DeliveryMarketingProductDTO
|
||||||
if len(marketing.Products) > 0 {
|
if len(marketing.Products) > 0 {
|
||||||
salesOrderProducts = make([]MarketingProductDTO, len(marketing.Products))
|
salesOrderProducts = make([]DeliveryMarketingProductDTO, len(marketing.Products))
|
||||||
for i, product := range 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
|
salesPerson = mapped
|
||||||
}
|
}
|
||||||
|
|
||||||
var salesOrderProducts []MarketingProductDTO
|
var salesOrderProducts []DeliveryMarketingProductDTO
|
||||||
if len(marketing.Products) > 0 {
|
if len(marketing.Products) > 0 {
|
||||||
salesOrderProducts = make([]MarketingProductDTO, len(marketing.Products))
|
salesOrderProducts = make([]DeliveryMarketingProductDTO, len(marketing.Products))
|
||||||
for i, product := range marketing.Products {
|
for i, product := range marketing.Products {
|
||||||
salesOrderProducts[i] = ToMarketingProductDTO(product)
|
salesOrderProducts[i] = ToDeliveryMarketingProductDTO(product)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,13 +1,52 @@
|
|||||||
package marketing
|
package marketing
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
"gorm.io/gorm"
|
"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{}
|
type MarketingModule struct{}
|
||||||
|
|
||||||
func (MarketingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) {
|
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
|
package marketing
|
||||||
|
|
||||||
import (
|
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"
|
"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) {
|
func RegisterRoutes(router fiber.Router, userService user.UserService, salesOrdersService service.SalesOrdersService, deliveryOrdersService service.DeliveryOrdersService) {
|
||||||
group := router.Group("/marketing")
|
salesOrdersCtrl := controller.NewSalesOrdersController(salesOrdersService)
|
||||||
|
deliveryOrdersCtrl := controller.NewDeliveryOrdersController(deliveryOrdersService)
|
||||||
|
|
||||||
allModules := []modules.Module{
|
route := router.Group("/marketing")
|
||||||
salesOrderss.SalesOrdersModule{},
|
route.Use(m.Auth(userService))
|
||||||
deliveryOrderss.DeliveryOrdersModule{},
|
|
||||||
// MODULE REGISTRY
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, m := range allModules {
|
route.Get("/", deliveryOrdersCtrl.GetAll)
|
||||||
m.RegisterRoutes(group, db, validate)
|
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"
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||||
productWarehouseRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
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"
|
"gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/dto"
|
||||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/delivery-orderss/validations"
|
marketingRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/repositories"
|
||||||
marketingRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/repositories"
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/validations"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
|
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
@@ -23,10 +23,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type DeliveryOrdersService interface {
|
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)
|
GetOne(ctx *fiber.Ctx, id uint) (*dto.MarketingDetailDTO, error)
|
||||||
CreateOne(ctx *fiber.Ctx, req *validation.Create) (*dto.MarketingDetailDTO, error)
|
CreateOne(ctx *fiber.Ctx, req *validation.DeliveryOrderCreate) (*dto.MarketingDetailDTO, error)
|
||||||
UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*dto.MarketingDetailDTO, error)
|
UpdateOne(ctx *fiber.Ctx, req *validation.DeliveryOrderUpdate, id uint) (*dto.MarketingDetailDTO, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type deliveryOrdersService struct {
|
type deliveryOrdersService struct {
|
||||||
@@ -85,7 +85,7 @@ func (s deliveryOrdersService) getMarketingWithDeliveries(c *fiber.Ctx, marketin
|
|||||||
return &responseDTO, nil
|
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 {
|
if err := s.Validate.Struct(params); err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
@@ -164,7 +164,7 @@ func (s deliveryOrdersService) GetOne(c *fiber.Ctx, id uint) (*dto.MarketingDeta
|
|||||||
return &responseDTO, nil
|
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 {
|
if err := s.Validate.Struct(req); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -293,7 +293,7 @@ func (s *deliveryOrdersService) CreateOne(c *fiber.Ctx, req *validation.Create)
|
|||||||
return s.getMarketingWithDeliveries(c, req.MarketingId)
|
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 {
|
if err := s.Validate.Struct(req); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
+2
-2
@@ -11,8 +11,8 @@ import (
|
|||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||||
productWarehouseRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
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"
|
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/repositories"
|
||||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/validations"
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/validations"
|
||||||
customerRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/master/customers/repositories"
|
customerRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/master/customers/repositories"
|
||||||
warehouseRepo "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"
|
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"`
|
VehicleNumber string `json:"vehicle_number" validate:"omitempty,max=50"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Create struct {
|
type DeliveryOrderCreate struct {
|
||||||
MarketingId uint `json:"marketing_id" validate:"required,gt=0"`
|
MarketingId uint `json:"marketing_id" validate:"required,gt=0"`
|
||||||
DeliveryProducts []DeliveryProduct `json:"delivery_products" validate:"required,min=1,dive"`
|
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"`
|
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"`
|
Page int `query:"page" validate:"omitempty,number,min=1,gt=0"`
|
||||||
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100,gt=0"`
|
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100,gt=0"`
|
||||||
MarketingId uint `query:"marketing_id" validate:"omitempty,gt=0"`
|
MarketingId uint `query:"marketing_id" validate:"omitempty,gt=0"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Approve struct {
|
type DeliveryOrderApprove struct {
|
||||||
Action string `json:"action" validate:"required_strict"`
|
Action string `json:"action" validate:"required_strict"`
|
||||||
ApprovableIds []uint `json:"approvable_ids" validate:"required_strict,min=1,dive,gt=0"`
|
ApprovableIds []uint `json:"approvable_ids" validate:"required_strict,min=1,dive,gt=0"`
|
||||||
Notes *string `json:"notes,omitempty" validate:"omitempty,max=500"`
|
Notes *string `json:"notes,omitempty" validate:"omitempty,max=500"`
|
||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
type ProjectChickinRepository interface {
|
type ProjectChickinRepository interface {
|
||||||
repository.BaseRepository[entity.ProjectChickin]
|
repository.BaseRepository[entity.ProjectChickin]
|
||||||
GetFirstByProjectFlockID(ctx context.Context, projectFlockID uint) (*entity.ProjectChickin, error)
|
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)
|
GetByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) ([]entity.ProjectChickin, error)
|
||||||
GetPendingByProjectFlockKandangID(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)
|
GetTotalPendingUsageQtyByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) (float64, error)
|
||||||
@@ -40,6 +41,16 @@ func (r *ChickinRepositoryImpl) GetFirstByProjectFlockID(ctx context.Context, pr
|
|||||||
return &chickin, nil
|
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) {
|
func (r *ChickinRepositoryImpl) GetByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) ([]entity.ProjectChickin, error) {
|
||||||
var chickins []entity.ProjectChickin
|
var chickins []entity.ProjectChickin
|
||||||
err := r.db.WithContext(ctx).
|
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) {
|
if category == string(utils.ProjectFlockCategoryLaying) {
|
||||||
for _, chickin := range newChikins {
|
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 err := productWarehouseTx.PatchOne(c.Context(), chickin.ProductWarehouseId, updates, nil); err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
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 {
|
for _, chickin := range chickins {
|
||||||
|
|
||||||
if categoryForRejection == string(utils.ProjectFlockCategoryGrowing) {
|
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 err := productWarehouseTx.PatchOne(c.Context(), chickin.ProductWarehouseId, updates, nil); err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
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 chickin.ProductWarehouseId != targetPW.Id {
|
||||||
if err := productWarehouseTx.PatchOne(ctx.Context(), chickin.ProductWarehouseId, map[string]any{
|
if err := productWarehouseTx.PatchOne(ctx.Context(), chickin.ProductWarehouseId, map[string]any{
|
||||||
"quantity": gorm.Expr("quantity - ?", quantityToConvert),
|
"qty": gorm.Expr("qty - ?", quantityToConvert),
|
||||||
}, nil); err != nil {
|
}, nil); err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Source product warehouse %d not found", chickin.ProductWarehouseId))
|
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{
|
if err := productWarehouseTx.PatchOne(ctx.Context(), targetPW.Id, map[string]any{
|
||||||
"quantity": gorm.Expr("quantity + ?", quantityToConvert),
|
"qty": gorm.Expr("qty + ?", quantityToConvert),
|
||||||
}, nil); err != nil {
|
}, nil); err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Target product warehouse %d not found", targetPW.Id))
|
return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Target product warehouse %d not found", targetPW.Id))
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package repository
|
package repository
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
@@ -8,6 +10,7 @@ import (
|
|||||||
|
|
||||||
type ProjectBudgetRepository interface {
|
type ProjectBudgetRepository interface {
|
||||||
repository.BaseRepository[entity.ProjectBudget]
|
repository.BaseRepository[entity.ProjectBudget]
|
||||||
|
GetByProjectFlockID(ctx context.Context, projectFlockID uint) ([]entity.ProjectBudget, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProjectBudgetRepositoryImpl struct {
|
type ProjectBudgetRepositoryImpl struct {
|
||||||
@@ -21,3 +24,13 @@ func NewProjectBudgetRepository(db *gorm.DB) ProjectBudgetRepository {
|
|||||||
db: db,
|
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"
|
purchases "gitlab.com/mbugroup/lti-api.git/internal/modules/purchases"
|
||||||
ssoModule "gitlab.com/mbugroup/lti-api.git/internal/modules/sso"
|
ssoModule "gitlab.com/mbugroup/lti-api.git/internal/modules/sso"
|
||||||
users "gitlab.com/mbugroup/lti-api.git/internal/modules/users"
|
users "gitlab.com/mbugroup/lti-api.git/internal/modules/users"
|
||||||
|
repports "gitlab.com/mbugroup/lti-api.git/internal/modules/repports"
|
||||||
// MODULE IMPORTS
|
// MODULE IMPORTS
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -42,6 +43,7 @@ func Routes(app *fiber.App, db *gorm.DB) {
|
|||||||
expenses.ExpenseModule{},
|
expenses.ExpenseModule{},
|
||||||
ssoModule.Module{},
|
ssoModule.Module{},
|
||||||
closings.ClosingModule{},
|
closings.ClosingModule{},
|
||||||
|
repports.RepportModule{},
|
||||||
// MODULE REGISTRY
|
// MODULE REGISTRY
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -263,6 +263,7 @@ func createProductWarehouseRow(t *testing.T, db *gorm.DB, qty float64) entity.Pr
|
|||||||
ProductId: 1,
|
ProductId: 1,
|
||||||
WarehouseId: 1,
|
WarehouseId: 1,
|
||||||
Quantity: qty,
|
Quantity: qty,
|
||||||
|
// CreatedBy: 1,
|
||||||
}
|
}
|
||||||
if err := db.Create(&pw).Error; err != nil {
|
if err := db.Create(&pw).Error; err != nil {
|
||||||
t.Fatalf("create product warehouse: %v", err)
|
t.Fatalf("create product warehouse: %v", err)
|
||||||
|
|||||||
Reference in New Issue
Block a user