mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
Merge branch 'development' into 'production'
Development See merge request mbugroup/lti-api!406
This commit is contained in:
@@ -35,6 +35,7 @@ type UniformityWeeklyMetric struct {
|
||||
Week int
|
||||
Uniformity float64
|
||||
AverageWeight float64
|
||||
UniformDate time.Time
|
||||
}
|
||||
|
||||
type StandardWeeklyMetric struct {
|
||||
@@ -144,7 +145,8 @@ func (r *DashboardRepositoryImpl) GetUniformityWeeklyMetrics(ctx context.Context
|
||||
Table("project_flock_kandang_uniformity AS u").
|
||||
Select(`u.week AS week,
|
||||
COALESCE(AVG(u.uniformity), 0) AS uniformity,
|
||||
COALESCE(AVG((u.chart_data->'statistics'->>'average_weight')::numeric), 0) AS average_weight`).
|
||||
COALESCE(AVG((u.chart_data->'statistics'->>'average_weight')::numeric), 0) AS average_weight,
|
||||
MAX(u.uniform_date) AS uniform_date`).
|
||||
Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = u.project_flock_kandang_id").
|
||||
Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id").
|
||||
Where("u.uniform_date IS NOT NULL").
|
||||
|
||||
@@ -265,6 +265,7 @@ func (s dashboardService) buildPerformanceCharts(ctx context.Context, params *va
|
||||
}
|
||||
|
||||
bodyWeightDataset := make([]map[string]interface{}, 0, len(weeks))
|
||||
bodyWeightDatasetIndexByWeek := make(map[int]int, len(weeks))
|
||||
performanceDataset := make([]map[string]interface{}, 0, len(weeks))
|
||||
fcrDataset := make([]map[string]interface{}, 0, len(weeks))
|
||||
deplesiDataset := make([]map[string]interface{}, 0, len(weeks))
|
||||
@@ -298,6 +299,7 @@ func (s dashboardService) buildPerformanceCharts(ctx context.Context, params *va
|
||||
"body_weight": roundTo(uni.AverageWeight, 2),
|
||||
"std_body_weight": roundTo(std.StdBodyWeight, 2),
|
||||
})
|
||||
bodyWeightDatasetIndexByWeek[week] = len(bodyWeightDataset) - 1
|
||||
|
||||
performanceDataset = append(performanceDataset, map[string]interface{}{
|
||||
"week": week,
|
||||
@@ -326,6 +328,15 @@ func (s dashboardService) buildPerformanceCharts(ctx context.Context, params *va
|
||||
})
|
||||
}
|
||||
|
||||
bodyWeightDataset = extendBodyWeightDatasetUntilEndDate(
|
||||
bodyWeightDataset,
|
||||
bodyWeightDatasetIndexByWeek,
|
||||
uniformities,
|
||||
uniformityMap,
|
||||
standardMap,
|
||||
params.PeriodEnd,
|
||||
)
|
||||
|
||||
qualityRows, err := s.Repository.GetEggQualityWeeklyMetrics(ctx, startDate, endExclusive, filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -1049,6 +1060,69 @@ func (s dashboardService) avgSellingPrice(ctx context.Context, filter *validatio
|
||||
return result.TotalPrice / result.TotalWeight, nil
|
||||
}
|
||||
|
||||
func extendBodyWeightDatasetUntilEndDate(
|
||||
dataset []map[string]interface{},
|
||||
indexByWeek map[int]int,
|
||||
uniformities []repository.UniformityWeeklyMetric,
|
||||
uniformityMap map[int]repository.UniformityWeeklyMetric,
|
||||
standardMap map[int]repository.StandardWeeklyMetric,
|
||||
periodEnd time.Time,
|
||||
) []map[string]interface{} {
|
||||
latestUniformityWeek := 0
|
||||
var latestUniformityDate time.Time
|
||||
for _, row := range uniformities {
|
||||
if row.Week <= 0 || row.UniformDate.IsZero() {
|
||||
continue
|
||||
}
|
||||
if latestUniformityDate.IsZero() || row.UniformDate.After(latestUniformityDate) || (row.UniformDate.Equal(latestUniformityDate) && row.Week > latestUniformityWeek) {
|
||||
latestUniformityDate = row.UniformDate
|
||||
latestUniformityWeek = row.Week
|
||||
}
|
||||
}
|
||||
|
||||
if latestUniformityWeek <= 0 || latestUniformityDate.IsZero() || periodEnd.IsZero() || !periodEnd.After(latestUniformityDate) {
|
||||
return dataset
|
||||
}
|
||||
|
||||
additionalWeeks := int(math.Ceil(periodEnd.Sub(latestUniformityDate).Hours() / (24 * 7)))
|
||||
if additionalWeeks <= 0 {
|
||||
return dataset
|
||||
}
|
||||
|
||||
lastUniformity := uniformityMap[latestUniformityWeek]
|
||||
lastStandard := standardMap[latestUniformityWeek]
|
||||
latestBodyWeight := roundTo(lastUniformity.AverageWeight, 2)
|
||||
latestStdBodyWeight := roundTo(lastStandard.StdBodyWeight, 2)
|
||||
|
||||
targetWeek := latestUniformityWeek + additionalWeeks
|
||||
for week := latestUniformityWeek + 1; week <= targetWeek; week++ {
|
||||
row := map[string]interface{}{
|
||||
"week": week,
|
||||
"body_weight": latestBodyWeight,
|
||||
"std_body_weight": latestStdBodyWeight,
|
||||
}
|
||||
|
||||
if idx, ok := indexByWeek[week]; ok {
|
||||
dataset[idx] = row
|
||||
continue
|
||||
}
|
||||
|
||||
dataset = append(dataset, row)
|
||||
indexByWeek[week] = len(dataset) - 1
|
||||
}
|
||||
|
||||
sort.Slice(dataset, func(i, j int) bool {
|
||||
return datasetWeek(dataset[i]) < datasetWeek(dataset[j])
|
||||
})
|
||||
|
||||
return dataset
|
||||
}
|
||||
|
||||
func datasetWeek(row map[string]interface{}) int {
|
||||
week, _ := row["week"].(int)
|
||||
return week
|
||||
}
|
||||
|
||||
func feedUsageToGrams(rows []repository.FeedUsageByUom) float64 {
|
||||
total := 0.0
|
||||
for _, row := range rows {
|
||||
|
||||
@@ -31,6 +31,8 @@ type MarketingDeliveryProductRepositoryImpl struct {
|
||||
*commonRepo.BaseRepositoryImpl[entity.MarketingDeliveryProduct]
|
||||
}
|
||||
|
||||
const marketingDeliveryProductSelectWithNullAttributed = "marketing_delivery_products.*, NULL AS attributed_project_flock_kandang_id"
|
||||
|
||||
func NewMarketingDeliveryProductRepository(db *gorm.DB) MarketingDeliveryProductRepository {
|
||||
return &MarketingDeliveryProductRepositoryImpl{
|
||||
BaseRepositoryImpl: commonRepo.NewBaseRepository[entity.MarketingDeliveryProduct](db),
|
||||
@@ -43,9 +45,9 @@ func (r *MarketingDeliveryProductRepositoryImpl) GetDeliveryProductsByProjectFlo
|
||||
attributionQuery := commonRepo.MarketingDeliveryAttributionRowsQuery(r.DB().WithContext(ctx))
|
||||
|
||||
db := r.DB().WithContext(ctx).
|
||||
Select("DISTINCT "+marketingDeliveryProductSelectWithNullAttributed).
|
||||
Joins("JOIN (?) AS mda ON mda.marketing_delivery_product_id = marketing_delivery_products.id", attributionQuery).
|
||||
Where("mda.project_flock_id = ?", projectFlockID).
|
||||
Distinct("marketing_delivery_products.*")
|
||||
Where("mda.project_flock_id = ?", projectFlockID)
|
||||
|
||||
if callback != nil {
|
||||
db = callback(db)
|
||||
@@ -110,6 +112,7 @@ func (r *MarketingDeliveryProductRepositoryImpl) GetByMarketingId(ctx context.Co
|
||||
|
||||
// JOIN untuk filter by marketing_id yang ada di related table
|
||||
db := r.DB().WithContext(ctx).
|
||||
Select(marketingDeliveryProductSelectWithNullAttributed).
|
||||
Joins("JOIN marketing_products ON marketing_products.id = marketing_delivery_products.marketing_product_id").
|
||||
Where("marketing_products.marketing_id = ?", marketingId)
|
||||
|
||||
@@ -124,6 +127,8 @@ func (r *MarketingDeliveryProductRepositoryImpl) GetByMarketingProductID(ctx con
|
||||
var deliveryProduct entity.MarketingDeliveryProduct
|
||||
|
||||
if err := r.DB().WithContext(ctx).
|
||||
Model(&entity.MarketingDeliveryProduct{}).
|
||||
Select(marketingDeliveryProductSelectWithNullAttributed).
|
||||
Where("marketing_product_id = ?", marketingProductID).
|
||||
First(&deliveryProduct).Error; err != nil {
|
||||
return nil, err
|
||||
@@ -132,6 +137,27 @@ func (r *MarketingDeliveryProductRepositoryImpl) GetByMarketingProductID(ctx con
|
||||
return &deliveryProduct, nil
|
||||
}
|
||||
|
||||
func (r *MarketingDeliveryProductRepositoryImpl) GetByID(
|
||||
ctx context.Context,
|
||||
id uint,
|
||||
modifier func(*gorm.DB) *gorm.DB,
|
||||
) (*entity.MarketingDeliveryProduct, error) {
|
||||
var deliveryProduct entity.MarketingDeliveryProduct
|
||||
|
||||
q := r.DB().WithContext(ctx).
|
||||
Model(&entity.MarketingDeliveryProduct{}).
|
||||
Select(marketingDeliveryProductSelectWithNullAttributed)
|
||||
if modifier != nil {
|
||||
q = modifier(q)
|
||||
}
|
||||
|
||||
if err := q.First(&deliveryProduct, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &deliveryProduct, nil
|
||||
}
|
||||
|
||||
func (r *MarketingDeliveryProductRepositoryImpl) GetAttributionRowsByDeliveryProductIDs(ctx context.Context, deliveryProductIDs []uint) ([]commonRepo.MarketingDeliveryAttributionRow, error) {
|
||||
if len(deliveryProductIDs) == 0 {
|
||||
return []commonRepo.MarketingDeliveryAttributionRow{}, nil
|
||||
@@ -211,6 +237,7 @@ func (r *MarketingDeliveryProductRepositoryImpl) fetchClosingDeliveryProducts(
|
||||
}
|
||||
|
||||
query := r.closingDeliveryProductsQuery(ctx).
|
||||
Select(marketingDeliveryProductSelectWithNullAttributed).
|
||||
Where("marketing_delivery_products.id IN ?", deliveryIDs).
|
||||
Order("marketing_delivery_products.delivery_date DESC")
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ func NewRecordingController(recordingService service.RecordingService) *Recordin
|
||||
|
||||
func (u *RecordingController) GetAll(c *fiber.Ctx) error {
|
||||
projectFlockID := c.QueryInt("project_flock_kandang_id", 0)
|
||||
exportType := strings.TrimSpace(c.Query("export"))
|
||||
|
||||
page := c.QueryInt("page", 1)
|
||||
limit := c.QueryInt("limit", 10)
|
||||
@@ -46,6 +47,11 @@ func (u *RecordingController) GetAll(c *fiber.Ctx) error {
|
||||
return err
|
||||
}
|
||||
|
||||
listDTO := dto.ToRecordingListDTOs(result)
|
||||
if strings.EqualFold(exportType, "excel") {
|
||||
return exportRecordingListExcel(c, listDTO)
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).
|
||||
JSON(response.SuccessWithPaginate[dto.RecordingListDTO]{
|
||||
Code: fiber.StatusOK,
|
||||
@@ -57,7 +63,7 @@ func (u *RecordingController) GetAll(c *fiber.Ctx) error {
|
||||
TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))),
|
||||
TotalResults: totalResults,
|
||||
},
|
||||
Data: dto.ToRecordingListDTOs(result),
|
||||
Data: listDTO,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,517 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/dto"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/xuri/excelize/v2"
|
||||
)
|
||||
|
||||
func exportRecordingListExcel(c *fiber.Ctx, items []dto.RecordingListDTO) error {
|
||||
file := excelize.NewFile()
|
||||
defer file.Close()
|
||||
|
||||
const sheetName = "Recordings"
|
||||
defaultSheet := file.GetSheetName(file.GetActiveSheetIndex())
|
||||
if defaultSheet != sheetName {
|
||||
if err := file.SetSheetName(defaultSheet, sheetName); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "failed to prepare excel sheet")
|
||||
}
|
||||
}
|
||||
|
||||
if err := setRecordingExportColumns(file, sheetName); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "failed to prepare excel columns")
|
||||
}
|
||||
if err := setRecordingExportHeaders(file, sheetName); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "failed to prepare excel headers")
|
||||
}
|
||||
if err := setRecordingExportRows(file, sheetName, items); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "failed to prepare excel rows")
|
||||
}
|
||||
|
||||
buffer, err := file.WriteToBuffer()
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "failed to generate excel file")
|
||||
}
|
||||
|
||||
filename := fmt.Sprintf("recordings_%s.xlsx", time.Now().Format("20060102_150405"))
|
||||
c.Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||
c.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, filename))
|
||||
|
||||
return c.Status(fiber.StatusOK).Send(buffer.Bytes())
|
||||
}
|
||||
|
||||
func setRecordingExportColumns(file *excelize.File, sheet string) error {
|
||||
columnWidths := map[string]float64{
|
||||
"A": 6,
|
||||
"B": 18,
|
||||
"C": 24,
|
||||
"D": 18,
|
||||
"E": 10,
|
||||
"F": 12,
|
||||
"G": 20,
|
||||
"H": 18,
|
||||
"I": 16,
|
||||
"J": 12,
|
||||
"K": 12,
|
||||
"L": 16,
|
||||
"M": 16,
|
||||
"N": 18,
|
||||
"O": 18,
|
||||
"P": 16,
|
||||
"Q": 16,
|
||||
"R": 16,
|
||||
"S": 16,
|
||||
"T": 16,
|
||||
"U": 16,
|
||||
"V": 16,
|
||||
"W": 18,
|
||||
"X": 18,
|
||||
"Y": 18,
|
||||
"Z": 22,
|
||||
"AA": 16,
|
||||
"AB": 18,
|
||||
}
|
||||
|
||||
for col, width := range columnWidths {
|
||||
if err := file.SetColWidth(sheet, col, col, width); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := file.SetRowHeight(sheet, 1, 30); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.SetRowHeight(sheet, 2, 30); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setRecordingExportHeaders(file *excelize.File, sheet string) error {
|
||||
verticalHeaderCols := []string{"A", "B", "C", "D", "E", "F", "G", "H", "I", "Y", "Z", "AA", "AB"}
|
||||
for _, col := range verticalHeaderCols {
|
||||
if err := file.MergeCell(sheet, col+"1", col+"2"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
headerValues := map[string]string{
|
||||
"A1": "No",
|
||||
"B1": "Lokasi",
|
||||
"C1": "Flock",
|
||||
"D1": "Kandang",
|
||||
"E1": "Periode",
|
||||
"F1": "Kategori",
|
||||
"G1": "Umur (hari)",
|
||||
"H1": "Waktu Recording",
|
||||
"I1": "Populasi Akhir",
|
||||
"Y1": "Status Approval",
|
||||
"Z1": "Catatan Approval",
|
||||
"AA1": "Dibuat Oleh",
|
||||
"AB1": "Tanggal Submit",
|
||||
}
|
||||
for cell, value := range headerValues {
|
||||
if err := file.SetCellValue(sheet, cell, value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := file.MergeCell(sheet, "J1", "K1"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.SetCellValue(sheet, "J1", "FCR"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.SetCellValue(sheet, "J2", "Actual"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.SetCellValue(sheet, "K2", "Standard"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := file.MergeCell(sheet, "L1", "M1"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.SetCellValue(sheet, "L1", "Feed Intake (KG)"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.SetCellValue(sheet, "L2", "Actual"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.SetCellValue(sheet, "M2", "Standard"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := file.MergeCell(sheet, "N1", "P1"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.SetCellValue(sheet, "N1", "Mortality"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.SetCellValue(sheet, "N2", "Cum Depletion Rate"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.SetCellValue(sheet, "O2", "Max Depletion Std"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.SetCellValue(sheet, "P2", "Total Depletion"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := file.MergeCell(sheet, "Q1", "T1"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.SetCellValue(sheet, "Q1", "Egg Production"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.SetCellValue(sheet, "Q2", "Egg Mass Actual"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.SetCellValue(sheet, "R2", "Egg Mass Standar"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.SetCellValue(sheet, "S2", "Egg Weight Actual"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.SetCellValue(sheet, "T2", "Egg Weight Standar"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := file.MergeCell(sheet, "U1", "X1"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.SetCellValue(sheet, "U1", "Hen Performance"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.SetCellValue(sheet, "U2", "Hen Day Actual"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.SetCellValue(sheet, "V2", "Hen Day Standar"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.SetCellValue(sheet, "W2", "Hen House Actual"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.SetCellValue(sheet, "X2", "Hen House Standar"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
headerStyle, err := file.NewStyle(&excelize.Style{
|
||||
Font: &excelize.Font{
|
||||
Bold: true,
|
||||
Color: "7A7A7A",
|
||||
},
|
||||
Fill: excelize.Fill{
|
||||
Type: "pattern",
|
||||
Pattern: 1,
|
||||
Color: []string{"F5F5F5"},
|
||||
},
|
||||
Alignment: &excelize.Alignment{
|
||||
Horizontal: "center",
|
||||
Vertical: "center",
|
||||
WrapText: true,
|
||||
},
|
||||
Border: []excelize.Border{
|
||||
{Type: "left", Color: "DDDDDD", Style: 1},
|
||||
{Type: "top", Color: "DDDDDD", Style: 1},
|
||||
{Type: "bottom", Color: "DDDDDD", Style: 1},
|
||||
{Type: "right", Color: "DDDDDD", Style: 1},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return file.SetCellStyle(sheet, "A1", "AB2", headerStyle)
|
||||
}
|
||||
|
||||
func setRecordingExportRows(file *excelize.File, sheet string, items []dto.RecordingListDTO) error {
|
||||
if len(items) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
columns := []string{
|
||||
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N",
|
||||
"O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "AA", "AB",
|
||||
}
|
||||
|
||||
for i, item := range items {
|
||||
rowNumber := i + 3
|
||||
|
||||
fcrStd := 0.0
|
||||
if item.ProjectFlock.Fcr != nil {
|
||||
fcrStd = item.ProjectFlock.Fcr.FcrStd
|
||||
}
|
||||
|
||||
maxDepletionStd := 0.0
|
||||
eggMassStd := 0.0
|
||||
eggWeightStd := 0.0
|
||||
henDayStd := 0.0
|
||||
henHouseStd := 0.0
|
||||
feedIntakeStd := 0.0
|
||||
if item.ProjectFlock.ProductionStandart != nil {
|
||||
maxDepletionStd = item.ProjectFlock.ProductionStandart.MaxDepletionStd
|
||||
eggMassStd = item.ProjectFlock.ProductionStandart.EggMassStd
|
||||
eggWeightStd = item.ProjectFlock.ProductionStandart.EggWeightStd
|
||||
henDayStd = item.ProjectFlock.ProductionStandart.HenDayStd
|
||||
henHouseStd = item.ProjectFlock.ProductionStandart.HenHouseStd
|
||||
feedIntakeStd = item.ProjectFlock.ProductionStandart.FeedIntakeStd
|
||||
}
|
||||
|
||||
locationName := "-"
|
||||
if item.Location != nil {
|
||||
locationName = safeExportText(item.Location.Name)
|
||||
}
|
||||
|
||||
kandangName := "-"
|
||||
if item.Kandang != nil {
|
||||
kandangName = safeExportText(item.Kandang.Name)
|
||||
}
|
||||
|
||||
createdBy := "-"
|
||||
if item.CreatedUser != nil {
|
||||
createdBy = safeExportText(item.CreatedUser.Name)
|
||||
} else if strings.TrimSpace(item.Approval.ActionBy.Name) != "" {
|
||||
createdBy = safeExportText(item.Approval.ActionBy.Name)
|
||||
}
|
||||
|
||||
rowValues := []interface{}{
|
||||
i + 1,
|
||||
locationName,
|
||||
safeExportText(item.ProjectFlock.FlockName),
|
||||
kandangName,
|
||||
item.ProjectFlock.Period,
|
||||
formatCategoryLabel(item.ProjectFlock.ProjectFlockCategory),
|
||||
formatAgeLabel(item),
|
||||
formatDateIndonesian(item.RecordDatetime),
|
||||
formatNumberID(item.ProjectFlock.TotalChickQty, 0, false),
|
||||
formatNumberID(item.FcrValue, 2, true),
|
||||
formatNumberID(fcrStd, 2, true),
|
||||
formatNumberID(item.FeedIntake, 2, true),
|
||||
formatNumberID(feedIntakeStd, 2, true),
|
||||
formatPercentID(item.CumDepletionRate, 2),
|
||||
formatPercentID(maxDepletionStd, 2),
|
||||
formatNumberID(item.TotalDepletionQty, 2, true),
|
||||
formatNumberID(item.EggMass, 2, true),
|
||||
formatNumberID(eggMassStd, 2, true),
|
||||
formatNumberID(item.EggWeight, 2, true),
|
||||
formatNumberID(eggWeightStd, 2, true),
|
||||
formatPercentID(item.HenDay, 2),
|
||||
formatPercentID(henDayStd, 2),
|
||||
formatPercentID(item.HenHouse, 2),
|
||||
formatPercentID(henHouseStd, 2),
|
||||
formatApprovalStatus(item),
|
||||
safeExportText(pointerString(item.Approval.Notes)),
|
||||
createdBy,
|
||||
formatDateIndonesian(item.CreatedAt),
|
||||
}
|
||||
|
||||
for idx, col := range columns {
|
||||
cell := fmt.Sprintf("%s%d", col, rowNumber)
|
||||
if err := file.SetCellValue(sheet, cell, rowValues[idx]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lastRow := len(items) + 2
|
||||
dataCenterStyle, err := file.NewStyle(&excelize.Style{
|
||||
Alignment: &excelize.Alignment{
|
||||
Horizontal: "center",
|
||||
Vertical: "center",
|
||||
WrapText: true,
|
||||
},
|
||||
Border: []excelize.Border{
|
||||
{Type: "left", Color: "E6E6E6", Style: 1},
|
||||
{Type: "top", Color: "E6E6E6", Style: 1},
|
||||
{Type: "bottom", Color: "E6E6E6", Style: 1},
|
||||
{Type: "right", Color: "E6E6E6", Style: 1},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.SetCellStyle(sheet, "A3", fmt.Sprintf("AB%d", lastRow), dataCenterStyle); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dataLeftStyle, err := file.NewStyle(&excelize.Style{
|
||||
Alignment: &excelize.Alignment{
|
||||
Horizontal: "left",
|
||||
Vertical: "center",
|
||||
WrapText: true,
|
||||
},
|
||||
Border: []excelize.Border{
|
||||
{Type: "left", Color: "E6E6E6", Style: 1},
|
||||
{Type: "top", Color: "E6E6E6", Style: 1},
|
||||
{Type: "bottom", Color: "E6E6E6", Style: 1},
|
||||
{Type: "right", Color: "E6E6E6", Style: 1},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
leftColumns := []string{"B", "C", "D", "F", "G", "H", "Y", "Z", "AA", "AB"}
|
||||
for _, col := range leftColumns {
|
||||
if err := file.SetCellStyle(sheet, col+"3", fmt.Sprintf("%s%d", col, lastRow), dataLeftStyle); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func formatAgeLabel(item dto.RecordingListDTO) string {
|
||||
if item.Day <= 0 {
|
||||
return "-"
|
||||
}
|
||||
|
||||
week := 0
|
||||
if item.ProjectFlock.ProductionStandart != nil && item.ProjectFlock.ProductionStandart.Week > 0 {
|
||||
week = item.ProjectFlock.ProductionStandart.Week
|
||||
} else {
|
||||
week = ((item.Day - 1) / 7) + 1
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%d (Minggu ke-%d)", item.Day, week)
|
||||
}
|
||||
|
||||
func formatDateIndonesian(t time.Time) string {
|
||||
if t.IsZero() {
|
||||
return "-"
|
||||
}
|
||||
|
||||
loc, err := time.LoadLocation("Asia/Jakarta")
|
||||
if err == nil {
|
||||
t = t.In(loc)
|
||||
}
|
||||
|
||||
monthNames := []string{
|
||||
"",
|
||||
"Januari",
|
||||
"Februari",
|
||||
"Maret",
|
||||
"April",
|
||||
"Mei",
|
||||
"Juni",
|
||||
"Juli",
|
||||
"Agustus",
|
||||
"September",
|
||||
"Oktober",
|
||||
"November",
|
||||
"Desember",
|
||||
}
|
||||
|
||||
month := int(t.Month())
|
||||
monthLabel := strconv.Itoa(month)
|
||||
if month > 0 && month < len(monthNames) {
|
||||
monthLabel = monthNames[month]
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%02d %s %d", t.Day(), monthLabel, t.Year())
|
||||
}
|
||||
|
||||
func formatCategoryLabel(value string) string {
|
||||
normalized := strings.TrimSpace(strings.ReplaceAll(value, "_", " "))
|
||||
if normalized == "" {
|
||||
return "-"
|
||||
}
|
||||
|
||||
parts := strings.Fields(strings.ToLower(normalized))
|
||||
for i, part := range parts {
|
||||
if len(part) == 0 {
|
||||
continue
|
||||
}
|
||||
parts[i] = strings.ToUpper(part[:1]) + part[1:]
|
||||
}
|
||||
|
||||
return strings.Join(parts, " ")
|
||||
}
|
||||
|
||||
func formatPercentID(value float64, decimals int) string {
|
||||
return fmt.Sprintf("%s%%", formatNumberID(value, decimals, false))
|
||||
}
|
||||
|
||||
func formatNumberID(value float64, decimals int, trim bool) string {
|
||||
if math.IsNaN(value) || math.IsInf(value, 0) {
|
||||
return "0"
|
||||
}
|
||||
if decimals < 0 {
|
||||
decimals = 0
|
||||
}
|
||||
|
||||
raw := strconv.FormatFloat(value, 'f', decimals, 64)
|
||||
if trim && strings.Contains(raw, ".") {
|
||||
raw = strings.TrimRight(raw, "0")
|
||||
raw = strings.TrimRight(raw, ".")
|
||||
}
|
||||
|
||||
parts := strings.SplitN(raw, ".", 2)
|
||||
intPart := parts[0]
|
||||
sign := ""
|
||||
if strings.HasPrefix(intPart, "-") {
|
||||
sign = "-"
|
||||
intPart = strings.TrimPrefix(intPart, "-")
|
||||
}
|
||||
if intPart == "" {
|
||||
intPart = "0"
|
||||
}
|
||||
|
||||
var grouped strings.Builder
|
||||
rem := len(intPart) % 3
|
||||
if rem > 0 {
|
||||
grouped.WriteString(intPart[:rem])
|
||||
if len(intPart) > rem {
|
||||
grouped.WriteString(".")
|
||||
}
|
||||
}
|
||||
for i := rem; i < len(intPart); i += 3 {
|
||||
grouped.WriteString(intPart[i : i+3])
|
||||
if i+3 < len(intPart) {
|
||||
grouped.WriteString(".")
|
||||
}
|
||||
}
|
||||
|
||||
result := sign + grouped.String()
|
||||
if len(parts) == 2 && parts[1] != "" {
|
||||
result += "," + parts[1]
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func safeExportText(value string) string {
|
||||
normalized := strings.TrimSpace(value)
|
||||
if normalized == "" {
|
||||
return "-"
|
||||
}
|
||||
return normalized
|
||||
}
|
||||
|
||||
func pointerString(value *string) string {
|
||||
if value == nil {
|
||||
return ""
|
||||
}
|
||||
return *value
|
||||
}
|
||||
|
||||
func formatApprovalStatus(item dto.RecordingListDTO) string {
|
||||
action := strings.ToUpper(strings.TrimSpace(pointerString(item.Approval.Action)))
|
||||
switch action {
|
||||
case "UPDATED":
|
||||
return "Diperbarui"
|
||||
case "CREATED":
|
||||
return safeExportText(item.Approval.StepName)
|
||||
default:
|
||||
return safeExportText(item.Approval.StepName)
|
||||
}
|
||||
}
|
||||
@@ -406,9 +406,9 @@ func (s *uniformityService) CreateOne(c *fiber.Ctx, req *validation.Create, file
|
||||
}
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "week must start from 1 for growing projects")
|
||||
}
|
||||
if latestWeek > 0 && req.Week > latestWeek+1 {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "week must be sequential without skipping")
|
||||
}
|
||||
// if latestWeek > 0 && req.Week > latestWeek+1 {
|
||||
// return nil, fiber.NewError(fiber.StatusBadRequest, "week must be sequential without skipping")
|
||||
// }
|
||||
|
||||
if err := s.ensureUniqueUniformity(c.Context(), 0, req.ProjectFlockKandangId, req.Week); err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -169,6 +169,7 @@ type PurchaseReceivingUpdate struct {
|
||||
TravelNumber *string
|
||||
TravelDocumentPath *string
|
||||
VehicleNumber *string
|
||||
ClearVehicleNumber bool
|
||||
ReceivedQty *float64
|
||||
WarehouseID *uint
|
||||
ProductWarehouseID *uint
|
||||
@@ -246,6 +247,8 @@ func (r *PurchaseRepositoryImpl) UpdateReceivingDetails(
|
||||
}
|
||||
if upd.VehicleNumber != nil {
|
||||
data["vehicle_number"] = upd.VehicleNumber
|
||||
} else if upd.ClearVehicleNumber {
|
||||
data["vehicle_number"] = gorm.Expr("NULL")
|
||||
}
|
||||
if upd.WarehouseID != nil && *upd.WarehouseID != 0 {
|
||||
data["warehouse_id"] = upd.WarehouseID
|
||||
|
||||
@@ -183,15 +183,102 @@ func (b *expenseBridge) markExpensesUpdated(ctx context.Context, expenseIDs map[
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *expenseBridge) clearExpenseLinksForItems(ctx context.Context, itemIDs []uint) error {
|
||||
if len(itemIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
unique := make(map[uint]struct{}, len(itemIDs))
|
||||
normalized := make([]uint, 0, len(itemIDs))
|
||||
for _, id := range itemIDs {
|
||||
if id == 0 {
|
||||
continue
|
||||
}
|
||||
if _, exists := unique[id]; exists {
|
||||
continue
|
||||
}
|
||||
unique[id] = struct{}{}
|
||||
normalized = append(normalized, id)
|
||||
}
|
||||
if len(normalized) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
rows := make([]struct {
|
||||
ItemID uint
|
||||
ExpenseNonstockID *uint64
|
||||
ExpenseID *uint64
|
||||
}, 0, len(normalized))
|
||||
if err := b.db.WithContext(ctx).
|
||||
Table("purchase_items pi").
|
||||
Select("pi.id as item_id, pi.expense_nonstock_id, en.expense_id").
|
||||
Joins("LEFT JOIN expense_nonstocks en ON en.id = pi.expense_nonstock_id").
|
||||
Where("pi.id IN ?", normalized).
|
||||
Scan(&rows).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
expenseNonstockIDs := make([]uint64, 0, len(rows))
|
||||
expenseIDs := make(map[uint64]struct{})
|
||||
for _, row := range rows {
|
||||
if row.ExpenseNonstockID != nil && *row.ExpenseNonstockID != 0 {
|
||||
expenseNonstockIDs = append(expenseNonstockIDs, *row.ExpenseNonstockID)
|
||||
}
|
||||
if row.ExpenseID != nil && *row.ExpenseID != 0 {
|
||||
expenseIDs[*row.ExpenseID] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
return b.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Model(&entity.PurchaseItem{}).
|
||||
Where("id IN ?", normalized).
|
||||
Update("expense_nonstock_id", gorm.Expr("NULL")).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(expenseNonstockIDs) > 0 {
|
||||
if err := tx.Where("id IN ?", expenseNonstockIDs).Delete(&entity.ExpenseNonstock{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
approvalRepoTx := commonRepo.NewApprovalRepository(tx)
|
||||
for expenseID := range expenseIDs {
|
||||
var count int64
|
||||
if err := tx.Model(&entity.ExpenseNonstock{}).
|
||||
Where("expense_id = ?", expenseID).
|
||||
Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count > 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := approvalRepoTx.DeleteByTarget(ctx, utils.ApprovalWorkflowExpense.String(), uint(expenseID)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Delete(&entity.Expense{}, expenseID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (b *expenseBridge) OnItemsReceived(c *fiber.Ctx, purchaseID uint, updates []ExpenseReceivingPayload) error {
|
||||
if purchaseID == 0 || len(updates) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
ctx := c.Context()
|
||||
clearExpenseLinks := make([]uint, 0, len(updates))
|
||||
filtered := make([]ExpenseReceivingPayload, 0, len(updates))
|
||||
for _, upd := range updates {
|
||||
if upd.PurchaseItemID == 0 {
|
||||
continue
|
||||
}
|
||||
if upd.SupplierID == 0 {
|
||||
clearExpenseLinks = append(clearExpenseLinks, upd.PurchaseItemID)
|
||||
continue
|
||||
}
|
||||
if upd.TransportPerItem == nil || *upd.TransportPerItem <= 0 {
|
||||
@@ -202,6 +289,11 @@ func (b *expenseBridge) OnItemsReceived(c *fiber.Ctx, purchaseID uint, updates [
|
||||
}
|
||||
filtered = append(filtered, upd)
|
||||
}
|
||||
if len(clearExpenseLinks) > 0 {
|
||||
if err := b.clearExpenseLinksForItems(ctx, clearExpenseLinks); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(filtered) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -195,9 +195,14 @@ func (s *purchaseService) GetAll(c *fiber.Ctx, params *validation.Query) ([]enti
|
||||
db = db.Where("purchases.po_date >= ?", *poDateStart)
|
||||
}
|
||||
|
||||
if poDateStart != nil {
|
||||
db = db.Where("purchases.po_date >= ?", *poDateStart)
|
||||
}
|
||||
|
||||
if poDateEnd != nil {
|
||||
db = db.Where("purchases.po_date < ?", *poDateEnd)
|
||||
}
|
||||
|
||||
if scope.Restrict {
|
||||
if len(scope.IDs) == 0 {
|
||||
return db.Where("1 = 0")
|
||||
@@ -311,12 +316,119 @@ func (s *purchaseService) GetAll(c *fiber.Ctx, params *validation.Query) ([]enti
|
||||
WHERE pi.purchase_id = purchases.id
|
||||
AND LOWER(COALESCE(p.name, '')) LIKE ?
|
||||
)
|
||||
OR EXISTS (
|
||||
SELECT 1
|
||||
FROM purchase_items pi
|
||||
JOIN warehouses w ON w.id = pi.warehouse_id
|
||||
JOIN locations l ON l.id = w.location_id
|
||||
WHERE pi.purchase_id = purchases.id
|
||||
AND LOWER(COALESCE(l.name, '')) LIKE ?
|
||||
)
|
||||
OR EXISTS (
|
||||
SELECT 1
|
||||
FROM purchase_items pi
|
||||
JOIN expense_nonstocks en ON en.id = pi.expense_nonstock_id
|
||||
JOIN expenses e ON e.id = en.expense_id
|
||||
WHERE pi.purchase_id = purchases.id
|
||||
AND LOWER(COALESCE(e.reference_number, '')) LIKE ?
|
||||
)
|
||||
)`,
|
||||
like,
|
||||
like,
|
||||
like,
|
||||
like,
|
||||
like,
|
||||
like,
|
||||
like,
|
||||
)
|
||||
}
|
||||
|
||||
if len(approvalStatuses) > 0 {
|
||||
approvalConditions := make([]string, 0, len(approvalStatuses))
|
||||
approvalArgs := make([]any, 0, 2+(len(approvalStatuses)*3))
|
||||
approvalArgs = append(approvalArgs, utils.ApprovalWorkflowPurchase.String(), utils.ApprovalWorkflowPurchase.String())
|
||||
for _, status := range approvalStatuses {
|
||||
if status == "" {
|
||||
continue
|
||||
}
|
||||
like := "%" + status + "%"
|
||||
approvalConditions = append(approvalConditions, `(LOWER(COALESCE(a.step_name, '')) LIKE ? OR LOWER(COALESCE(CAST(a.action AS TEXT), '')) LIKE ? OR CAST(a.step_number AS TEXT) = ?)`)
|
||||
approvalArgs = append(approvalArgs, like, like, status)
|
||||
}
|
||||
|
||||
if len(approvalConditions) > 0 {
|
||||
approvalClause := strings.Join(approvalConditions, " OR ")
|
||||
approvalQuery := fmt.Sprintf(
|
||||
`EXISTS (
|
||||
SELECT 1
|
||||
FROM approvals a
|
||||
WHERE a.approvable_type = ?
|
||||
AND a.approvable_id = purchases.id
|
||||
AND a.id = (
|
||||
SELECT a2.id
|
||||
FROM approvals a2
|
||||
WHERE a2.approvable_type = ?
|
||||
AND a2.approvable_id = purchases.id
|
||||
ORDER BY a2.action_at DESC, a2.id DESC
|
||||
LIMIT 1
|
||||
)
|
||||
AND (%s)
|
||||
)`,
|
||||
approvalClause,
|
||||
)
|
||||
db = db.Where(approvalQuery, approvalArgs...)
|
||||
}
|
||||
}
|
||||
|
||||
if search != "" {
|
||||
like := "%" + search + "%"
|
||||
db = db.Where(
|
||||
`(
|
||||
LOWER(COALESCE(purchases.pr_number, '')) LIKE ?
|
||||
OR LOWER(COALESCE(purchases.po_number, '')) LIKE ?
|
||||
OR EXISTS (
|
||||
SELECT 1
|
||||
FROM suppliers s
|
||||
WHERE s.id = purchases.supplier_id
|
||||
AND LOWER(COALESCE(s.name, '')) LIKE ?
|
||||
)
|
||||
OR EXISTS (
|
||||
SELECT 1
|
||||
FROM users u
|
||||
WHERE u.id = purchases.created_by
|
||||
AND LOWER(COALESCE(u.name, '')) LIKE ?
|
||||
)
|
||||
OR EXISTS (
|
||||
SELECT 1
|
||||
FROM purchase_items pi
|
||||
JOIN products p ON p.id = pi.product_id
|
||||
WHERE pi.purchase_id = purchases.id
|
||||
AND LOWER(COALESCE(p.name, '')) LIKE ?
|
||||
)
|
||||
OR EXISTS (
|
||||
SELECT 1
|
||||
FROM purchase_items pi
|
||||
JOIN warehouses w ON w.id = pi.warehouse_id
|
||||
JOIN locations l ON l.id = w.location_id
|
||||
WHERE pi.purchase_id = purchases.id
|
||||
AND LOWER(COALESCE(l.name, '')) LIKE ?
|
||||
)
|
||||
OR EXISTS (
|
||||
SELECT 1
|
||||
FROM purchase_items pi
|
||||
JOIN expense_nonstocks en ON en.id = pi.expense_nonstock_id
|
||||
JOIN expenses e ON e.id = en.expense_id
|
||||
WHERE pi.purchase_id = purchases.id
|
||||
AND LOWER(COALESCE(e.reference_number, '')) LIKE ?
|
||||
)
|
||||
)`,
|
||||
like,
|
||||
like,
|
||||
like,
|
||||
like,
|
||||
like,
|
||||
like,
|
||||
like,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -949,6 +1061,7 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
|
||||
supplierID uint
|
||||
transportPerItem *float64
|
||||
vehicleNumber *string
|
||||
clearVehicle bool
|
||||
overrideWarehouse bool
|
||||
receivedQty float64
|
||||
}
|
||||
@@ -1041,12 +1154,16 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
|
||||
}
|
||||
|
||||
var vehicleNumber *string
|
||||
if payload.VehicleNumber != nil && strings.TrimSpace(*payload.VehicleNumber) != "" {
|
||||
clearVehicle := false
|
||||
if payload.VehicleNumber != nil {
|
||||
val := strings.TrimSpace(*payload.VehicleNumber)
|
||||
vehicleNumber = &val
|
||||
} else if item.VehicleNumber != nil && strings.TrimSpace(*item.VehicleNumber) != "" {
|
||||
val := strings.TrimSpace(*item.VehicleNumber)
|
||||
vehicleNumber = &val
|
||||
if val != "" {
|
||||
vehicleNumber = &val
|
||||
} else {
|
||||
clearVehicle = true
|
||||
}
|
||||
} else {
|
||||
clearVehicle = true
|
||||
}
|
||||
|
||||
prepared = append(prepared, preparedReceiving{
|
||||
@@ -1057,6 +1174,7 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
|
||||
supplierID: supplierID,
|
||||
transportPerItem: transportPerItem,
|
||||
vehicleNumber: vehicleNumber,
|
||||
clearVehicle: clearVehicle,
|
||||
overrideWarehouse: overrideWarehouse,
|
||||
receivedQty: receivedQty,
|
||||
})
|
||||
@@ -1170,7 +1288,8 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
|
||||
ReceivedDate: &dateCopy,
|
||||
TravelNumber: prep.payload.TravelNumber,
|
||||
TravelDocumentPath: prep.payload.TravelDocumentPath,
|
||||
VehicleNumber: prep.payload.VehicleNumber,
|
||||
VehicleNumber: prep.vehicleNumber,
|
||||
ClearVehicleNumber: prep.clearVehicle,
|
||||
ReceivedQty: &qtyCopy,
|
||||
ProductWarehouseID: newPWID,
|
||||
ClearProductWarehouse: false,
|
||||
|
||||
@@ -432,6 +432,7 @@ func (c *RepportController) GetProductionResult(ctx *fiber.Ctx) error {
|
||||
func parseCommaSeparatedInt64s(raw string) ([]int64, error) {
|
||||
return parseCommaSeparatedInt64sWithField(raw, "supplier_ids")
|
||||
}
|
||||
|
||||
func parseCommaSeparatedInt64sWithField(raw, field string) ([]int64, error) {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
|
||||
@@ -71,7 +71,7 @@ func (r *purchaseSupplierRepositoryImpl) baseSupplierQuery(ctx context.Context,
|
||||
if len(filters.ProductCategoryIDs) > 0 {
|
||||
db = db.
|
||||
Joins("JOIN products ON products.id = purchase_items.product_id").
|
||||
Where("products.product_category_id IN ?", filters.ProductCategoryIDs)
|
||||
Where("products.product_category_id IN ?", filters.ProductCategoryIDs)
|
||||
}
|
||||
|
||||
if len(filters.AreaIDs) > 0 || filters.AllowedAreaIDs != nil {
|
||||
@@ -194,7 +194,7 @@ func (r *purchaseSupplierRepositoryImpl) GetItemsBySuppliers(ctx context.Context
|
||||
if len(filters.ProductCategoryIDs) > 0 {
|
||||
db = db.
|
||||
Joins("JOIN products ON products.id = purchase_items.product_id").
|
||||
Where("products.product_category_id IN ?", filters.ProductCategoryIDs)
|
||||
Where("products.product_category_id IN ?", filters.ProductCategoryIDs)
|
||||
}
|
||||
if len(filters.AreaIDs) > 0 || filters.AllowedAreaIDs != nil {
|
||||
db = db.Joins("JOIN warehouses ON warehouses.id = purchase_items.warehouse_id")
|
||||
|
||||
Reference in New Issue
Block a user