mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
Merge branch 'development' of https://gitlab.com/mbugroup/lti-api into development
This commit is contained in:
@@ -15,6 +15,7 @@ type SalesDTO struct {
|
|||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
RealizationDate time.Time `json:"realization_date"`
|
RealizationDate time.Time `json:"realization_date"`
|
||||||
Age int `json:"age"`
|
Age int `json:"age"`
|
||||||
|
Week int `json:"week"`
|
||||||
DoNumber string `json:"do_number"`
|
DoNumber string `json:"do_number"`
|
||||||
Product *productDTO.ProductRelationDTO `json:"product,omitempty"`
|
Product *productDTO.ProductRelationDTO `json:"product,omitempty"`
|
||||||
Customer *customerDTO.CustomerRelationDTO `json:"customer,omitempty"`
|
Customer *customerDTO.CustomerRelationDTO `json:"customer,omitempty"`
|
||||||
@@ -43,7 +44,7 @@ type PenjualanRealisasiResponseDTO struct {
|
|||||||
|
|
||||||
func ToSalesDTO(e entity.MarketingDeliveryProduct) SalesDTO {
|
func ToSalesDTO(e entity.MarketingDeliveryProduct) SalesDTO {
|
||||||
|
|
||||||
age := calculateAgeFromChickin(e.MarketingProduct.ProductWarehouse.ProjectFlockKandang, e.DeliveryDate)
|
ageInDay, ageInWeeks := calculateAgeFromChickin(e.MarketingProduct.ProductWarehouse.ProjectFlockKandang, e.DeliveryDate)
|
||||||
|
|
||||||
var product *productDTO.ProductRelationDTO
|
var product *productDTO.ProductRelationDTO
|
||||||
if e.MarketingProduct.ProductWarehouse.Product.Id != 0 {
|
if e.MarketingProduct.ProductWarehouse.Product.Id != 0 {
|
||||||
@@ -73,7 +74,8 @@ func ToSalesDTO(e entity.MarketingDeliveryProduct) SalesDTO {
|
|||||||
return SalesDTO{
|
return SalesDTO{
|
||||||
Id: e.Id,
|
Id: e.Id,
|
||||||
RealizationDate: realizationDate,
|
RealizationDate: realizationDate,
|
||||||
Age: age,
|
Age: ageInDay,
|
||||||
|
Week: ageInWeeks,
|
||||||
DoNumber: doNumber,
|
DoNumber: doNumber,
|
||||||
Product: product,
|
Product: product,
|
||||||
Customer: customer,
|
Customer: customer,
|
||||||
@@ -124,9 +126,9 @@ func ToPenjualanRealisasiResponseDTO(e []entity.MarketingDeliveryProduct) Penjua
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func calculateAgeFromChickin(projectFlockKandang *entity.ProjectFlockKandang, deliveryDate *time.Time) int {
|
func calculateAgeFromChickin(projectFlockKandang *entity.ProjectFlockKandang, deliveryDate *time.Time) (int, int) {
|
||||||
if projectFlockKandang == nil || deliveryDate == nil || len(projectFlockKandang.Chickins) == 0 {
|
if projectFlockKandang == nil || deliveryDate == nil || len(projectFlockKandang.Chickins) == 0 {
|
||||||
return 0
|
return 0, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
earliestChickinDate := projectFlockKandang.Chickins[0].ChickInDate
|
earliestChickinDate := projectFlockKandang.Chickins[0].ChickInDate
|
||||||
@@ -136,7 +138,16 @@ func calculateAgeFromChickin(projectFlockKandang *entity.ProjectFlockKandang, de
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ageInDays := int(deliveryDate.Sub(earliestChickinDate).Hours() / 24)
|
diff := deliveryDate.Sub(earliestChickinDate)
|
||||||
ageInWeeks := ageInDays / 7
|
ageInDays := int(diff.Hours() / 24)
|
||||||
return ageInWeeks
|
|
||||||
|
var ageInWeeks int
|
||||||
|
if ageInDays <= 0 {
|
||||||
|
ageInWeeks = 0
|
||||||
|
} else {
|
||||||
|
|
||||||
|
ageInWeeks = ((ageInDays - 1) / 7) + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return ageInDays, ageInWeeks
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ func (r *ConstantRepositoryImpl) GetConstants() map[string]interface{} {
|
|||||||
"KANDANG",
|
"KANDANG",
|
||||||
},
|
},
|
||||||
"stock_log": map[string][]string{
|
"stock_log": map[string][]string{
|
||||||
"log_types": []string{"TRANSFER", "ADJUSTMENT"},
|
"log_types": []string{"TRANSFER", "ADJUSTMENT", "MARKETING", "CHICKIN", "PURCHASE", "RECORDING"},
|
||||||
"transaction_types": []string{"INCREASE", "DECREASE"},
|
"transaction_types": []string{"INCREASE", "DECREASE"},
|
||||||
},
|
},
|
||||||
"supplier_categories": []string{
|
"supplier_categories": []string{
|
||||||
|
|||||||
@@ -70,7 +70,8 @@ func (r *ExpenseRealizationRepositoryImpl) GetClosingOverhead(ctx context.Contex
|
|||||||
Joins("JOIN expenses ON expenses.id = expense_nonstocks.expense_id").
|
Joins("JOIN expenses ON expenses.id = expense_nonstocks.expense_id").
|
||||||
Joins("LEFT JOIN project_flock_kandangs ON project_flock_kandangs.id = expense_nonstocks.project_flock_kandang_id").
|
Joins("LEFT JOIN project_flock_kandangs ON project_flock_kandangs.id = expense_nonstocks.project_flock_kandang_id").
|
||||||
Joins("LEFT JOIN kandangs ON kandangs.id = expense_nonstocks.kandang_id").
|
Joins("LEFT JOIN kandangs ON kandangs.id = expense_nonstocks.kandang_id").
|
||||||
Where("expenses.realization_date IS NOT NULL")
|
Where("expenses.realization_date IS NOT NULL").
|
||||||
|
Where("expenses.category = ?", "BOP")
|
||||||
|
|
||||||
if projectFlockKandangID != nil {
|
if projectFlockKandangID != nil {
|
||||||
db = db.Where(`(
|
db = db.Where(`(
|
||||||
|
|||||||
@@ -26,7 +26,10 @@ func NewMarketingProductRepository(db *gorm.DB) MarketingProductRepository {
|
|||||||
|
|
||||||
func (r *MarketingProductRepositoryImpl) GetByMarketingID(ctx context.Context, marketingID uint) ([]entity.MarketingProduct, error) {
|
func (r *MarketingProductRepositoryImpl) GetByMarketingID(ctx context.Context, marketingID uint) ([]entity.MarketingProduct, error) {
|
||||||
var products []entity.MarketingProduct
|
var products []entity.MarketingProduct
|
||||||
if err := r.DB().WithContext(ctx).Where("marketing_id = ?", marketingID).Find(&products).Error; err != nil {
|
if err := r.DB().WithContext(ctx).
|
||||||
|
Preload("ProductWarehouse.Product.Flags").
|
||||||
|
Where("marketing_id = ?", marketingID).
|
||||||
|
Find(&products).Error; err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(products) == 0 {
|
if len(products) == 0 {
|
||||||
|
|||||||
@@ -247,9 +247,27 @@ func (s *deliveryOrdersService) CreateOne(c *fiber.Ctx, req *validation.Delivery
|
|||||||
itemDeliveryDate = &parsedDate
|
itemDeliveryDate = &parsedDate
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hitung total_weight dan total_price otomatis
|
// Cek apakah product punya flag PAKAN atau OVK
|
||||||
|
isPakanOrOVK := false
|
||||||
|
if foundMarketingProduct.ProductWarehouse.Product.Id != 0 && len(foundMarketingProduct.ProductWarehouse.Product.Flags) > 0 {
|
||||||
|
for _, flag := range foundMarketingProduct.ProductWarehouse.Product.Flags {
|
||||||
|
if flag.Name == string(utils.FlagPakan) || flag.Name == string(utils.FlagOVK) {
|
||||||
|
isPakanOrOVK = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hitung total_weight dan total_price berdasarkan flag
|
||||||
totalWeight := requestedProduct.Qty * requestedProduct.AvgWeight
|
totalWeight := requestedProduct.Qty * requestedProduct.AvgWeight
|
||||||
totalPrice := requestedProduct.UnitPrice * totalWeight
|
var totalPrice float64
|
||||||
|
if isPakanOrOVK {
|
||||||
|
// PAKAN atau OVK: qty × unit_price
|
||||||
|
totalPrice = requestedProduct.Qty * requestedProduct.UnitPrice
|
||||||
|
} else {
|
||||||
|
// Produk lain: total_weight × unit_price
|
||||||
|
totalPrice = totalWeight * requestedProduct.UnitPrice
|
||||||
|
}
|
||||||
|
|
||||||
deliveryProduct.ProductWarehouseId = foundMarketingProduct.ProductWarehouseId
|
deliveryProduct.ProductWarehouseId = foundMarketingProduct.ProductWarehouseId
|
||||||
deliveryProduct.UnitPrice = requestedProduct.UnitPrice
|
deliveryProduct.UnitPrice = requestedProduct.UnitPrice
|
||||||
@@ -361,9 +379,27 @@ func (s deliveryOrdersService) UpdateOne(c *fiber.Ctx, req *validation.DeliveryO
|
|||||||
|
|
||||||
oldRequestedQty := deliveryProduct.UsageQty + deliveryProduct.PendingQty
|
oldRequestedQty := deliveryProduct.UsageQty + deliveryProduct.PendingQty
|
||||||
|
|
||||||
// Hitung total_weight dan total_price otomatis
|
// Cek apakah product punya flag PAKAN atau OVK
|
||||||
|
isPakanOrOVK := false
|
||||||
|
if foundMarketingProduct.ProductWarehouse.Product.Id != 0 && len(foundMarketingProduct.ProductWarehouse.Product.Flags) > 0 {
|
||||||
|
for _, flag := range foundMarketingProduct.ProductWarehouse.Product.Flags {
|
||||||
|
if flag.Name == string(utils.FlagPakan) || flag.Name == string(utils.FlagOVK) {
|
||||||
|
isPakanOrOVK = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hitung total_weight dan total_price berdasarkan flag
|
||||||
totalWeight := requestedProduct.Qty * requestedProduct.AvgWeight
|
totalWeight := requestedProduct.Qty * requestedProduct.AvgWeight
|
||||||
totalPrice := requestedProduct.UnitPrice * totalWeight
|
var totalPrice float64
|
||||||
|
if isPakanOrOVK {
|
||||||
|
// PAKAN atau OVK: qty × unit_price
|
||||||
|
totalPrice = requestedProduct.Qty * requestedProduct.UnitPrice
|
||||||
|
} else {
|
||||||
|
// Produk lain: total_weight × unit_price
|
||||||
|
totalPrice = totalWeight * requestedProduct.UnitPrice
|
||||||
|
}
|
||||||
|
|
||||||
deliveryProduct.ProductWarehouseId = foundMarketingProduct.ProductWarehouseId
|
deliveryProduct.ProductWarehouseId = foundMarketingProduct.ProductWarehouseId
|
||||||
deliveryProduct.UnitPrice = requestedProduct.UnitPrice
|
deliveryProduct.UnitPrice = requestedProduct.UnitPrice
|
||||||
@@ -435,7 +471,13 @@ func (s deliveryOrdersService) consumeDeliveryStock(ctx context.Context, tx *gor
|
|||||||
}
|
}
|
||||||
|
|
||||||
if pw == nil || pw.Quantity < requestedQty {
|
if pw == nil || pw.Quantity < requestedQty {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Insufficient stock. Available: %.2f, Requested: %.2f", func() float64 { if pw != nil { return pw.Quantity } else { return 0 } }(), requestedQty))
|
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Insufficient stock. Available: %.2f, Requested: %.2f", func() float64 {
|
||||||
|
if pw != nil {
|
||||||
|
return pw.Quantity
|
||||||
|
} else {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}(), requestedQty))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := deliveryProductRepo.UpdateFifoFields(ctx, deliveryProduct.Id, requestedQty, 0); err != nil {
|
if err := deliveryProductRepo.UpdateFifoFields(ctx, deliveryProduct.Id, requestedQty, 0); err != nil {
|
||||||
|
|||||||
@@ -292,9 +292,35 @@ func (s salesOrdersService) UpdateOne(c *fiber.Ctx, req *validation.Update, id u
|
|||||||
for _, rp := range req.MarketingProducts {
|
for _, rp := range req.MarketingProducts {
|
||||||
if old, ok := oldByPW[rp.ProductWarehouseId]; ok {
|
if old, ok := oldByPW[rp.ProductWarehouseId]; ok {
|
||||||
|
|
||||||
// Hitung total_weight dan total_price otomatis
|
// Get product untuk cek flag PAKAN atau OVK
|
||||||
|
productWarehouse, err := s.ProductWarehouseRepo.GetByID(c.Context(), rp.ProductWarehouseId, func(db *gorm.DB) *gorm.DB {
|
||||||
|
return db.Preload("Product.Flags")
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cek apakah product punya flag PAKAN atau OVK
|
||||||
|
isPakanOrOVK := false
|
||||||
|
if productWarehouse.Product.Id != 0 && len(productWarehouse.Product.Flags) > 0 {
|
||||||
|
for _, flag := range productWarehouse.Product.Flags {
|
||||||
|
if flag.Name == string(utils.FlagPakan) || flag.Name == string(utils.FlagOVK) {
|
||||||
|
isPakanOrOVK = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hitung total_weight dan total_price berdasarkan flag
|
||||||
totalWeight := rp.Qty * rp.AvgWeight
|
totalWeight := rp.Qty * rp.AvgWeight
|
||||||
totalPrice := rp.UnitPrice * totalWeight
|
var totalPrice float64
|
||||||
|
if isPakanOrOVK {
|
||||||
|
// PAKAN atau OVK: qty × unit_price
|
||||||
|
totalPrice = rp.Qty * rp.UnitPrice
|
||||||
|
} else {
|
||||||
|
// Produk lain: total_weight × unit_price
|
||||||
|
totalPrice = totalWeight * rp.UnitPrice
|
||||||
|
}
|
||||||
|
|
||||||
updateBody := map[string]any{
|
updateBody := map[string]any{
|
||||||
"product_warehouse_id": rp.ProductWarehouseId,
|
"product_warehouse_id": rp.ProductWarehouseId,
|
||||||
@@ -592,9 +618,34 @@ func (s salesOrdersService) Approval(c *fiber.Ctx, req *validation.Approve) ([]e
|
|||||||
|
|
||||||
func (s *salesOrdersService) createMarketingProductWithDelivery(ctx context.Context, marketingId uint, rp validation.CreateMarketingProduct, marketingProductRepo repository.MarketingProductRepository, invDeliveryRepo repository.MarketingDeliveryProductRepository) error {
|
func (s *salesOrdersService) createMarketingProductWithDelivery(ctx context.Context, marketingId uint, rp validation.CreateMarketingProduct, marketingProductRepo repository.MarketingProductRepository, invDeliveryRepo repository.MarketingDeliveryProductRepository) error {
|
||||||
|
|
||||||
// Hitung total_weight dan total_price otomatis
|
// Get product untuk cek flag PAKAN atau OVK
|
||||||
|
productWarehouse, err := s.ProductWarehouseRepo.GetByID(ctx, rp.ProductWarehouseId, func(db *gorm.DB) *gorm.DB {
|
||||||
|
return db.Preload("Product.Flags")
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cek apakah product punya flag PAKAN atau OVK
|
||||||
|
isPakanOrOVK := false
|
||||||
|
if productWarehouse.Product.Id != 0 && len(productWarehouse.Product.Flags) > 0 {
|
||||||
|
for _, flag := range productWarehouse.Product.Flags {
|
||||||
|
if flag.Name == string(utils.FlagPakan) || flag.Name == string(utils.FlagOVK) {
|
||||||
|
isPakanOrOVK = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
totalWeight := rp.Qty * rp.AvgWeight
|
totalWeight := rp.Qty * rp.AvgWeight
|
||||||
totalPrice := rp.UnitPrice * totalWeight
|
var totalPrice float64
|
||||||
|
if isPakanOrOVK {
|
||||||
|
// PAKAN atau OVK: qty × unit_price
|
||||||
|
totalPrice = rp.Qty * rp.UnitPrice
|
||||||
|
} else {
|
||||||
|
// Produk lain: total_weight × unit_price
|
||||||
|
totalPrice = totalWeight * rp.UnitPrice
|
||||||
|
}
|
||||||
|
|
||||||
marketingProduct := &entity.MarketingProduct{
|
marketingProduct := &entity.MarketingProduct{
|
||||||
MarketingId: marketingId,
|
MarketingId: marketingId,
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import (
|
|||||||
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"
|
||||||
rRecording "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/repositories"
|
rRecording "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/repositories"
|
||||||
sRecording "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/services"
|
sRecording "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/services"
|
||||||
|
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"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils/fifo"
|
"gitlab.com/mbugroup/lti-api.git/internal/utils/fifo"
|
||||||
|
|
||||||
@@ -31,6 +32,7 @@ func (RecordingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate
|
|||||||
projectFlockPopulationRepo := rProjectFlock.NewProjectFlockPopulationRepository(db)
|
projectFlockPopulationRepo := rProjectFlock.NewProjectFlockPopulationRepository(db)
|
||||||
productWarehouseRepo := rProductWarehouse.NewProductWarehouseRepository(db)
|
productWarehouseRepo := rProductWarehouse.NewProductWarehouseRepository(db)
|
||||||
stockAllocationRepo := commonRepo.NewStockAllocationRepository(db)
|
stockAllocationRepo := commonRepo.NewStockAllocationRepository(db)
|
||||||
|
stockLogRepo := rStockLogs.NewStockLogRepository(db)
|
||||||
productionStandardRepo := rProductionStandard.NewProductionStandardRepository(db)
|
productionStandardRepo := rProductionStandard.NewProductionStandardRepository(db)
|
||||||
productionStandardDetailRepo := rProductionStandard.NewProductionStandardDetailRepository(db)
|
productionStandardDetailRepo := rProductionStandard.NewProductionStandardDetailRepository(db)
|
||||||
standardGrowthDetailRepo := rProductionStandard.NewStandardGrowthDetailRepository(db)
|
standardGrowthDetailRepo := rProductionStandard.NewStandardGrowthDetailRepository(db)
|
||||||
@@ -113,6 +115,7 @@ func (RecordingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate
|
|||||||
approvalRepo,
|
approvalRepo,
|
||||||
approvalService,
|
approvalService,
|
||||||
fifoService,
|
fifoService,
|
||||||
|
stockLogRepo,
|
||||||
productionStandardService,
|
productionStandardService,
|
||||||
validate,
|
validate,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
sProductionStandard "gitlab.com/mbugroup/lti-api.git/internal/modules/master/production-standards/services"
|
sProductionStandard "gitlab.com/mbugroup/lti-api.git/internal/modules/master/production-standards/services"
|
||||||
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"
|
||||||
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/repositories"
|
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/repositories"
|
||||||
|
rStockLogs "gitlab.com/mbugroup/lti-api.git/internal/modules/shared/repositories"
|
||||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/validations"
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/validations"
|
||||||
"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"
|
||||||
@@ -39,8 +40,8 @@ type RecordingService interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type RecordingFIFOIntegrationService interface {
|
type RecordingFIFOIntegrationService interface {
|
||||||
ConsumeRecordingStocks(ctx context.Context, tx *gorm.DB, stocks []entity.RecordingStock) error
|
ConsumeRecordingStocks(ctx context.Context, tx *gorm.DB, stocks []entity.RecordingStock, note string, actorID uint) error
|
||||||
ReleaseRecordingStocks(ctx context.Context, tx *gorm.DB, stocks []entity.RecordingStock) error
|
ReleaseRecordingStocks(ctx context.Context, tx *gorm.DB, stocks []entity.RecordingStock, note string, actorID uint) error
|
||||||
}
|
}
|
||||||
|
|
||||||
var recordingStockUsableKey = fifo.UsableKeyRecordingStock
|
var recordingStockUsableKey = fifo.UsableKeyRecordingStock
|
||||||
@@ -57,6 +58,7 @@ type recordingService struct {
|
|||||||
ApprovalSvc commonSvc.ApprovalService
|
ApprovalSvc commonSvc.ApprovalService
|
||||||
ProductionStandardSvc sProductionStandard.ProductionStandardService
|
ProductionStandardSvc sProductionStandard.ProductionStandardService
|
||||||
FifoSvc commonSvc.FifoService
|
FifoSvc commonSvc.FifoService
|
||||||
|
StockLogRepo rStockLogs.StockLogRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRecordingService(
|
func NewRecordingService(
|
||||||
@@ -67,6 +69,7 @@ func NewRecordingService(
|
|||||||
approvalRepo commonRepo.ApprovalRepository,
|
approvalRepo commonRepo.ApprovalRepository,
|
||||||
approvalSvc commonSvc.ApprovalService,
|
approvalSvc commonSvc.ApprovalService,
|
||||||
fifoSvc commonSvc.FifoService,
|
fifoSvc commonSvc.FifoService,
|
||||||
|
stockLogRepo rStockLogs.StockLogRepository,
|
||||||
productionStandardSvc sProductionStandard.ProductionStandardService,
|
productionStandardSvc sProductionStandard.ProductionStandardService,
|
||||||
validate *validator.Validate,
|
validate *validator.Validate,
|
||||||
) RecordingService {
|
) RecordingService {
|
||||||
@@ -81,6 +84,7 @@ func NewRecordingService(
|
|||||||
ApprovalSvc: approvalSvc,
|
ApprovalSvc: approvalSvc,
|
||||||
ProductionStandardSvc: productionStandardSvc,
|
ProductionStandardSvc: productionStandardSvc,
|
||||||
FifoSvc: fifoSvc,
|
FifoSvc: fifoSvc,
|
||||||
|
StockLogRepo: stockLogRepo,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,12 +92,14 @@ func NewRecordingFIFOIntegrationService(
|
|||||||
repo repository.RecordingRepository,
|
repo repository.RecordingRepository,
|
||||||
productWarehouseRepo rProductWarehouse.ProductWarehouseRepository,
|
productWarehouseRepo rProductWarehouse.ProductWarehouseRepository,
|
||||||
fifoSvc commonSvc.FifoService,
|
fifoSvc commonSvc.FifoService,
|
||||||
|
stockLogRepo rStockLogs.StockLogRepository,
|
||||||
) RecordingFIFOIntegrationService {
|
) RecordingFIFOIntegrationService {
|
||||||
return &recordingService{
|
return &recordingService{
|
||||||
Log: utils.Log,
|
Log: utils.Log,
|
||||||
Repository: repo,
|
Repository: repo,
|
||||||
ProductWarehouseRepo: productWarehouseRepo,
|
ProductWarehouseRepo: productWarehouseRepo,
|
||||||
FifoSvc: fifoSvc,
|
FifoSvc: fifoSvc,
|
||||||
|
StockLogRepo: stockLogRepo,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,14 +165,13 @@ func (s recordingService) GetNextDay(c *fiber.Ctx, projectFlockKandangId uint) (
|
|||||||
return 0, fiber.NewError(fiber.StatusBadRequest, "project_flock_kandang_id is required")
|
return 0, fiber.NewError(fiber.StatusBadRequest, "project_flock_kandang_id is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
db := s.Repository.DB().WithContext(c.Context())
|
day, err := s.computeRecordingDay(c.Context(), projectFlockKandangId, time.Now().UTC())
|
||||||
next, err := s.Repository.GenerateNextDay(db, projectFlockKandangId)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Log.Errorf("Failed to compute next recording day for project_flock_kandang_id=%d: %+v", projectFlockKandangId, err)
|
s.Log.Errorf("Failed to compute recording day for project_flock_kandang_id=%d: %+v", projectFlockKandangId, err)
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return next, nil
|
return day, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entity.Recording, error) {
|
func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entity.Recording, error) {
|
||||||
@@ -208,6 +213,11 @@ func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
day, err := s.computeRecordingDay(ctx, pfk.Id, recordTime)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
if !isLaying && len(req.Eggs) > 0 {
|
if !isLaying && len(req.Eggs) > 0 {
|
||||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Egg details permitted only for laying project flocks")
|
return nil, fiber.NewError(fiber.StatusBadRequest, "Egg details permitted only for laying project flocks")
|
||||||
}
|
}
|
||||||
@@ -221,13 +231,8 @@ func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent
|
|||||||
}
|
}
|
||||||
var createdRecording entity.Recording
|
var createdRecording entity.Recording
|
||||||
transactionErr := s.Repository.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
transactionErr := s.Repository.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||||
nextDay, err := s.Repository.GenerateNextDay(tx, req.ProjectFlockKandangId)
|
|
||||||
if err != nil {
|
|
||||||
s.Log.Errorf("Failed to determine recording day: %+v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if s.ProductionStandardSvc != nil {
|
if s.ProductionStandardSvc != nil {
|
||||||
if err := s.ProductionStandardSvc.EnsureWeekAvailable(ctx, pfk.ProjectFlock.ProductionStandardId, category, nextDay); err != nil {
|
if err := s.ProductionStandardSvc.EnsureWeekAvailable(ctx, pfk.ProjectFlock.ProductionStandardId, category, day); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -241,7 +246,6 @@ func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent
|
|||||||
return fiber.NewError(fiber.StatusBadRequest, "Recording for this project flock today already exists")
|
return fiber.NewError(fiber.StatusBadRequest, "Recording for this project flock today already exists")
|
||||||
}
|
}
|
||||||
|
|
||||||
day := nextDay
|
|
||||||
createdRecording = entity.Recording{
|
createdRecording = entity.Recording{
|
||||||
ProjectFlockKandangId: req.ProjectFlockKandangId,
|
ProjectFlockKandangId: req.ProjectFlockKandangId,
|
||||||
RecordDatetime: recordTime,
|
RecordDatetime: recordTime,
|
||||||
@@ -274,7 +278,8 @@ func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent
|
|||||||
}
|
}
|
||||||
|
|
||||||
applyStockDesiredQuantities(mappedStocks, stockDesired, s.FifoSvc != nil)
|
applyStockDesiredQuantities(mappedStocks, stockDesired, s.FifoSvc != nil)
|
||||||
if err := s.consumeRecordingStocks(ctx, tx, mappedStocks); err != nil {
|
note := fmt.Sprintf("Recording-Create#%d", createdRecording.Id)
|
||||||
|
if err := s.consumeRecordingStocks(ctx, tx, mappedStocks, note, actorID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,7 +298,8 @@ func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if s.FifoSvc != nil {
|
if s.FifoSvc != nil {
|
||||||
if err := s.consumeRecordingDepletions(ctx, tx, mappedDepletions); err != nil {
|
note := fmt.Sprintf("Recording-Create#%d", createdRecording.Id)
|
||||||
|
if err := s.consumeRecordingDepletions(ctx, tx, mappedDepletions, note, actorID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -304,7 +310,8 @@ func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if s.FifoSvc != nil {
|
if s.FifoSvc != nil {
|
||||||
if err := s.replenishRecordingEggs(ctx, tx, mappedEggs); err != nil {
|
note := fmt.Sprintf("Recording-Create#%d", createdRecording.Id)
|
||||||
|
if err := s.replenishRecordingEggs(ctx, tx, mappedEggs, note, actorID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -346,6 +353,10 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx := c.Context()
|
ctx := c.Context()
|
||||||
|
actorID, err := m.ActorIDFromContext(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
var recordingEntity *entity.Recording
|
var recordingEntity *entity.Recording
|
||||||
var updatedRecording *entity.Recording
|
var updatedRecording *entity.Recording
|
||||||
@@ -431,14 +442,16 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
|
|||||||
}
|
}
|
||||||
|
|
||||||
if hasStockChanges {
|
if hasStockChanges {
|
||||||
if err := s.syncRecordingStocks(ctx, tx, recordingEntity.Id, existingStocks, req.Stocks); err != nil {
|
note := fmt.Sprintf("Recording-Edit#%d", recordingEntity.Id)
|
||||||
|
if err := s.syncRecordingStocks(ctx, tx, recordingEntity.Id, existingStocks, req.Stocks, note, actorID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if hasDepletionChanges {
|
if hasDepletionChanges {
|
||||||
if s.FifoSvc != nil {
|
if s.FifoSvc != nil {
|
||||||
if err := s.releaseRecordingDepletions(ctx, tx, existingDepletions); err != nil {
|
note := fmt.Sprintf("Recording-Edit#%d", recordingEntity.Id)
|
||||||
|
if err := s.releaseRecordingDepletions(ctx, tx, existingDepletions, note, actorID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -464,7 +477,8 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
|
|||||||
}
|
}
|
||||||
|
|
||||||
if s.FifoSvc != nil {
|
if s.FifoSvc != nil {
|
||||||
if err := s.consumeRecordingDepletions(ctx, tx, mappedDepletions); err != nil {
|
note := fmt.Sprintf("Recording-Edit#%d", recordingEntity.Id)
|
||||||
|
if err := s.consumeRecordingDepletions(ctx, tx, mappedDepletions, note, actorID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -480,6 +494,28 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
|
|||||||
if err := ensureRecordingEggsUnused(existingEggs); err != nil {
|
if err := ensureRecordingEggsUnused(existingEggs); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if s.StockLogRepo != nil {
|
||||||
|
note := fmt.Sprintf("Recording-Edit#%d", recordingEntity.Id)
|
||||||
|
logs := make([]*entity.StockLog, 0, len(existingEggs))
|
||||||
|
for _, egg := range existingEggs {
|
||||||
|
if egg.ProductWarehouseId == 0 || egg.Qty <= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
logs = append(logs, &entity.StockLog{
|
||||||
|
ProductWarehouseId: egg.ProductWarehouseId,
|
||||||
|
CreatedBy: actorID,
|
||||||
|
Decrease: float64(egg.Qty),
|
||||||
|
LoggableType: string(utils.StockLogTypeRecording),
|
||||||
|
LoggableId: recordingEntity.Id,
|
||||||
|
Notes: note,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if len(logs) > 0 {
|
||||||
|
if err := s.StockLogRepo.WithTx(tx).CreateMany(ctx, logs, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if err := s.adjustProductWarehouseQuantities(ctx, tx, buildWarehouseDeltas(nil, nil, existingEggs, nil)); err != nil {
|
if err := s.adjustProductWarehouseQuantities(ctx, tx, buildWarehouseDeltas(nil, nil, existingEggs, nil)); err != nil {
|
||||||
s.Log.Errorf("Failed to adjust product warehouses for eggs: %+v", err)
|
s.Log.Errorf("Failed to adjust product warehouses for eggs: %+v", err)
|
||||||
return err
|
return err
|
||||||
@@ -498,7 +534,8 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
|
|||||||
}
|
}
|
||||||
|
|
||||||
if s.FifoSvc != nil {
|
if s.FifoSvc != nil {
|
||||||
if err := s.replenishRecordingEggs(ctx, tx, mappedEggs); err != nil {
|
note := fmt.Sprintf("Recording-Edit#%d", recordingEntity.Id)
|
||||||
|
if err := s.replenishRecordingEggs(ctx, tx, mappedEggs, note, actorID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -675,7 +712,7 @@ func (s recordingService) DeleteOne(c *fiber.Ctx, id uint) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if s.FifoSvc != nil {
|
if s.FifoSvc != nil {
|
||||||
if err := s.releaseRecordingDepletions(ctx, tx, oldDepletions); err != nil {
|
if err := s.releaseRecordingDepletions(ctx, tx, oldDepletions, "", 0); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -697,7 +734,7 @@ func (s recordingService) DeleteOne(c *fiber.Ctx, id uint) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.releaseRecordingStocks(ctx, tx, oldStocks); err != nil {
|
if err := s.releaseRecordingStocks(ctx, tx, oldStocks, "", 0); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -756,10 +793,19 @@ func (s *recordingService) ensureProductWarehousesExist(c *fiber.Ctx, stocks []v
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *recordingService) consumeRecordingStocks(ctx context.Context, tx *gorm.DB, stocks []entity.RecordingStock) error {
|
func (s *recordingService) consumeRecordingStocks(
|
||||||
|
ctx context.Context,
|
||||||
|
tx *gorm.DB,
|
||||||
|
stocks []entity.RecordingStock,
|
||||||
|
note string,
|
||||||
|
actorID uint,
|
||||||
|
) error {
|
||||||
if len(stocks) == 0 || s.FifoSvc == nil {
|
if len(stocks) == 0 || s.FifoSvc == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
if strings.TrimSpace(note) != "" && s.StockLogRepo == nil {
|
||||||
|
return errors.New("stock log repository is not available")
|
||||||
|
}
|
||||||
|
|
||||||
for _, stock := range stocks {
|
for _, stock := range stocks {
|
||||||
if stock.Id == 0 {
|
if stock.Id == 0 {
|
||||||
@@ -792,15 +838,42 @@ func (s *recordingService) consumeRecordingStocks(ctx context.Context, tx *gorm.
|
|||||||
if err := s.Repository.UpdateStockUsage(tx, stock.Id, result.UsageQuantity, result.PendingQuantity); err != nil {
|
if err := s.Repository.UpdateStockUsage(tx, stock.Id, result.UsageQuantity, result.PendingQuantity); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logDecrease := result.UsageQuantity
|
||||||
|
if result.PendingQuantity > 0 {
|
||||||
|
logDecrease += result.PendingQuantity
|
||||||
|
}
|
||||||
|
if logDecrease > 0 && strings.TrimSpace(note) != "" && actorID != 0 {
|
||||||
|
log := &entity.StockLog{
|
||||||
|
ProductWarehouseId: stock.ProductWarehouseId,
|
||||||
|
CreatedBy: actorID,
|
||||||
|
Decrease: logDecrease,
|
||||||
|
LoggableType: string(utils.StockLogTypeRecording),
|
||||||
|
LoggableId: stock.RecordingId,
|
||||||
|
Notes: note,
|
||||||
|
}
|
||||||
|
if err := s.StockLogRepo.WithTx(tx).CreateOne(ctx, log, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *recordingService) consumeRecordingDepletions(ctx context.Context, tx *gorm.DB, depletions []entity.RecordingDepletion) error {
|
func (s *recordingService) consumeRecordingDepletions(
|
||||||
|
ctx context.Context,
|
||||||
|
tx *gorm.DB,
|
||||||
|
depletions []entity.RecordingDepletion,
|
||||||
|
note string,
|
||||||
|
actorID uint,
|
||||||
|
) error {
|
||||||
if len(depletions) == 0 || s.FifoSvc == nil {
|
if len(depletions) == 0 || s.FifoSvc == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
if strings.TrimSpace(note) != "" && s.StockLogRepo == nil {
|
||||||
|
return errors.New("stock log repository is not available")
|
||||||
|
}
|
||||||
|
|
||||||
for _, depletion := range depletions {
|
for _, depletion := range depletions {
|
||||||
if depletion.Id == 0 {
|
if depletion.Id == 0 {
|
||||||
@@ -832,19 +905,67 @@ func (s *recordingService) consumeRecordingDepletions(ctx context.Context, tx *g
|
|||||||
if err := s.Repository.UpdateDepletionPending(tx, depletion.Id, result.PendingQuantity); err != nil {
|
if err := s.Repository.UpdateDepletionPending(tx, depletion.Id, result.PendingQuantity); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logDecrease := result.UsageQuantity
|
||||||
|
if result.PendingQuantity > 0 {
|
||||||
|
logDecrease += result.PendingQuantity
|
||||||
|
}
|
||||||
|
if logDecrease > 0 && strings.TrimSpace(note) != "" && actorID != 0 {
|
||||||
|
log := &entity.StockLog{
|
||||||
|
ProductWarehouseId: sourceWarehouseID,
|
||||||
|
CreatedBy: actorID,
|
||||||
|
Decrease: logDecrease,
|
||||||
|
LoggableType: string(utils.StockLogTypeRecording),
|
||||||
|
LoggableId: depletion.RecordingId,
|
||||||
|
Notes: note,
|
||||||
|
}
|
||||||
|
if err := s.StockLogRepo.WithTx(tx).CreateOne(ctx, log, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
destDelta := depletion.Qty + depletion.PendingQty
|
||||||
|
if depletion.ProductWarehouseId != 0 && destDelta > 0 && strings.TrimSpace(note) != "" && actorID != 0 {
|
||||||
|
log := &entity.StockLog{
|
||||||
|
ProductWarehouseId: depletion.ProductWarehouseId,
|
||||||
|
CreatedBy: actorID,
|
||||||
|
Increase: destDelta,
|
||||||
|
LoggableType: string(utils.StockLogTypeRecording),
|
||||||
|
LoggableId: depletion.RecordingId,
|
||||||
|
Notes: note,
|
||||||
|
}
|
||||||
|
if err := s.StockLogRepo.WithTx(tx).CreateOne(ctx, log, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *recordingService) ConsumeRecordingStocks(ctx context.Context, tx *gorm.DB, stocks []entity.RecordingStock) error {
|
func (s *recordingService) ConsumeRecordingStocks(
|
||||||
return s.consumeRecordingStocks(ctx, tx, stocks)
|
ctx context.Context,
|
||||||
|
tx *gorm.DB,
|
||||||
|
stocks []entity.RecordingStock,
|
||||||
|
note string,
|
||||||
|
actorID uint,
|
||||||
|
) error {
|
||||||
|
return s.consumeRecordingStocks(ctx, tx, stocks, note, actorID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *recordingService) releaseRecordingStocks(ctx context.Context, tx *gorm.DB, stocks []entity.RecordingStock) error {
|
func (s *recordingService) releaseRecordingStocks(
|
||||||
|
ctx context.Context,
|
||||||
|
tx *gorm.DB,
|
||||||
|
stocks []entity.RecordingStock,
|
||||||
|
note string,
|
||||||
|
actorID uint,
|
||||||
|
) error {
|
||||||
if len(stocks) == 0 || s.FifoSvc == nil {
|
if len(stocks) == 0 || s.FifoSvc == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
if strings.TrimSpace(note) != "" && s.StockLogRepo == nil {
|
||||||
|
return errors.New("stock log repository is not available")
|
||||||
|
}
|
||||||
|
|
||||||
for _, stock := range stocks {
|
for _, stock := range stocks {
|
||||||
if stock.Id == 0 {
|
if stock.Id == 0 {
|
||||||
@@ -863,15 +984,38 @@ func (s *recordingService) releaseRecordingStocks(ctx context.Context, tx *gorm.
|
|||||||
if err := s.Repository.UpdateStockUsage(tx, stock.Id, 0, 0); err != nil {
|
if err := s.Repository.UpdateStockUsage(tx, stock.Id, 0, 0); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if stock.UsageQty != nil && *stock.UsageQty > 0 && strings.TrimSpace(note) != "" && actorID != 0 {
|
||||||
|
log := &entity.StockLog{
|
||||||
|
ProductWarehouseId: stock.ProductWarehouseId,
|
||||||
|
CreatedBy: actorID,
|
||||||
|
Increase: *stock.UsageQty,
|
||||||
|
LoggableType: string(utils.StockLogTypeRecording),
|
||||||
|
LoggableId: stock.RecordingId,
|
||||||
|
Notes: note,
|
||||||
|
}
|
||||||
|
if err := s.StockLogRepo.WithTx(tx).CreateOne(ctx, log, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *recordingService) releaseRecordingDepletions(ctx context.Context, tx *gorm.DB, depletions []entity.RecordingDepletion) error {
|
func (s *recordingService) releaseRecordingDepletions(
|
||||||
|
ctx context.Context,
|
||||||
|
tx *gorm.DB,
|
||||||
|
depletions []entity.RecordingDepletion,
|
||||||
|
note string,
|
||||||
|
actorID uint,
|
||||||
|
) error {
|
||||||
if len(depletions) == 0 || s.FifoSvc == nil {
|
if len(depletions) == 0 || s.FifoSvc == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
if strings.TrimSpace(note) != "" && s.StockLogRepo == nil {
|
||||||
|
return errors.New("stock log repository is not available")
|
||||||
|
}
|
||||||
|
|
||||||
for _, depletion := range depletions {
|
for _, depletion := range depletions {
|
||||||
if depletion.Id == 0 {
|
if depletion.Id == 0 {
|
||||||
@@ -898,13 +1042,52 @@ func (s *recordingService) releaseRecordingDepletions(ctx context.Context, tx *g
|
|||||||
if err := s.Repository.UpdateDepletionPending(tx, depletion.Id, 0); err != nil {
|
if err := s.Repository.UpdateDepletionPending(tx, depletion.Id, 0); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logIncrease := depletion.Qty
|
||||||
|
if depletion.PendingQty > 0 {
|
||||||
|
logIncrease += depletion.PendingQty
|
||||||
|
}
|
||||||
|
if logIncrease > 0 && strings.TrimSpace(note) != "" && actorID != 0 {
|
||||||
|
log := &entity.StockLog{
|
||||||
|
ProductWarehouseId: sourceWarehouseID,
|
||||||
|
CreatedBy: actorID,
|
||||||
|
Increase: logIncrease,
|
||||||
|
LoggableType: string(utils.StockLogTypeRecording),
|
||||||
|
LoggableId: depletion.RecordingId,
|
||||||
|
Notes: note,
|
||||||
|
}
|
||||||
|
if err := s.StockLogRepo.WithTx(tx).CreateOne(ctx, log, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
destDelta := depletion.Qty + depletion.PendingQty
|
||||||
|
if depletion.ProductWarehouseId != 0 && destDelta > 0 && strings.TrimSpace(note) != "" && actorID != 0 {
|
||||||
|
log := &entity.StockLog{
|
||||||
|
ProductWarehouseId: depletion.ProductWarehouseId,
|
||||||
|
CreatedBy: actorID,
|
||||||
|
Decrease: destDelta,
|
||||||
|
LoggableType: string(utils.StockLogTypeRecording),
|
||||||
|
LoggableId: depletion.RecordingId,
|
||||||
|
Notes: note,
|
||||||
|
}
|
||||||
|
if err := s.StockLogRepo.WithTx(tx).CreateOne(ctx, log, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *recordingService) ReleaseRecordingStocks(ctx context.Context, tx *gorm.DB, stocks []entity.RecordingStock) error {
|
func (s *recordingService) ReleaseRecordingStocks(
|
||||||
return s.releaseRecordingStocks(ctx, tx, stocks)
|
ctx context.Context,
|
||||||
|
tx *gorm.DB,
|
||||||
|
stocks []entity.RecordingStock,
|
||||||
|
note string,
|
||||||
|
actorID uint,
|
||||||
|
) error {
|
||||||
|
return s.releaseRecordingStocks(ctx, tx, stocks, note, actorID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *recordingService) resolvePopulationWarehouseID(ctx context.Context, projectFlockKandangID uint) (uint, error) {
|
func (s *recordingService) resolvePopulationWarehouseID(ctx context.Context, projectFlockKandangID uint) (uint, error) {
|
||||||
@@ -929,6 +1112,40 @@ func (s *recordingService) resolvePopulationWarehouseID(ctx context.Context, pro
|
|||||||
return 0, fiber.NewError(fiber.StatusBadRequest, "Source product warehouse populasi tidak ditemukan")
|
return 0, fiber.NewError(fiber.StatusBadRequest, "Source product warehouse populasi tidak ditemukan")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *recordingService) computeRecordingDay(ctx context.Context, projectFlockKandangID uint, recordTime time.Time) (int, error) {
|
||||||
|
if projectFlockKandangID == 0 {
|
||||||
|
return 0, fiber.NewError(fiber.StatusBadRequest, "Project flock kandang tidak valid")
|
||||||
|
}
|
||||||
|
|
||||||
|
populations, err := s.ProjectFlockPopulationRepo.GetByProjectFlockKandangID(ctx, projectFlockKandangID)
|
||||||
|
if err != nil {
|
||||||
|
s.Log.Errorf("Failed to fetch populations for project_flock_kandang_id=%d: %+v", projectFlockKandangID, err)
|
||||||
|
return 0, fiber.NewError(fiber.StatusInternalServerError, "Gagal mengambil data populasi")
|
||||||
|
}
|
||||||
|
|
||||||
|
var chickinDate time.Time
|
||||||
|
for _, pop := range populations {
|
||||||
|
if pop.ProjectChickin == nil || pop.ProjectChickin.ChickInDate.IsZero() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if chickinDate.IsZero() || pop.ProjectChickin.ChickInDate.Before(chickinDate) {
|
||||||
|
chickinDate = pop.ProjectChickin.ChickInDate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if chickinDate.IsZero() {
|
||||||
|
return 0, fiber.NewError(fiber.StatusBadRequest, "Tanggal chick in tidak ditemukan")
|
||||||
|
}
|
||||||
|
|
||||||
|
chickinDay := time.Date(chickinDate.Year(), chickinDate.Month(), chickinDate.Day(), 0, 0, 0, 0, time.UTC)
|
||||||
|
recordDay := time.Date(recordTime.Year(), recordTime.Month(), recordTime.Day(), 0, 0, 0, 0, time.UTC)
|
||||||
|
diff := int(recordDay.Sub(chickinDay).Hours() / 24)
|
||||||
|
if diff < 0 {
|
||||||
|
return 0, fiber.NewError(fiber.StatusBadRequest, "Record date tidak boleh sebelum tanggal chick in")
|
||||||
|
}
|
||||||
|
|
||||||
|
return diff + 1, nil
|
||||||
|
}
|
||||||
|
|
||||||
func buildWarehouseDeltas(
|
func buildWarehouseDeltas(
|
||||||
oldDepletions, newDepletions []entity.RecordingDepletion,
|
oldDepletions, newDepletions []entity.RecordingDepletion,
|
||||||
oldEggs, newEggs []entity.RecordingEgg,
|
oldEggs, newEggs []entity.RecordingEgg,
|
||||||
@@ -963,27 +1180,48 @@ func (s *recordingService) adjustProductWarehouseQuantities(ctx context.Context,
|
|||||||
return s.ProductWarehouseRepo.AdjustQuantities(ctx, deltas, func(*gorm.DB) *gorm.DB { return tx })
|
return s.ProductWarehouseRepo.AdjustQuantities(ctx, deltas, func(*gorm.DB) *gorm.DB { return tx })
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *recordingService) replenishRecordingEggs(ctx context.Context, tx *gorm.DB, eggs []entity.RecordingEgg) error {
|
func (s *recordingService) replenishRecordingEggs(
|
||||||
|
ctx context.Context,
|
||||||
|
tx *gorm.DB,
|
||||||
|
eggs []entity.RecordingEgg,
|
||||||
|
note string,
|
||||||
|
actorID uint,
|
||||||
|
) error {
|
||||||
if len(eggs) == 0 || s.FifoSvc == nil {
|
if len(eggs) == 0 || s.FifoSvc == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
if strings.TrimSpace(note) != "" && s.StockLogRepo == nil {
|
||||||
|
return errors.New("stock log repository is not available")
|
||||||
|
}
|
||||||
|
|
||||||
for _, egg := range eggs {
|
for _, egg := range eggs {
|
||||||
if egg.Id == 0 || egg.ProductWarehouseId == 0 || egg.Qty <= 0 {
|
if egg.Id == 0 || egg.ProductWarehouseId == 0 || egg.Qty <= 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
note := fmt.Sprintf("Recording egg #%d", egg.Id)
|
|
||||||
if _, err := s.FifoSvc.Replenish(ctx, commonSvc.StockReplenishRequest{
|
if _, err := s.FifoSvc.Replenish(ctx, commonSvc.StockReplenishRequest{
|
||||||
StockableKey: fifo.StockableKeyRecordingEgg,
|
StockableKey: fifo.StockableKeyRecordingEgg,
|
||||||
StockableID: egg.Id,
|
StockableID: egg.Id,
|
||||||
ProductWarehouseID: egg.ProductWarehouseId,
|
ProductWarehouseID: egg.ProductWarehouseId,
|
||||||
Quantity: float64(egg.Qty),
|
Quantity: float64(egg.Qty),
|
||||||
Note: ¬e,
|
|
||||||
Tx: tx,
|
Tx: tx,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
s.Log.Errorf("Failed to replenish FIFO stock for recording egg %d: %+v", egg.Id, err)
|
s.Log.Errorf("Failed to replenish FIFO stock for recording egg %d: %+v", egg.Id, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strings.TrimSpace(note) != "" && actorID != 0 {
|
||||||
|
log := &entity.StockLog{
|
||||||
|
ProductWarehouseId: egg.ProductWarehouseId,
|
||||||
|
CreatedBy: actorID,
|
||||||
|
Increase: float64(egg.Qty),
|
||||||
|
LoggableType: string(utils.StockLogTypeRecording),
|
||||||
|
LoggableId: egg.RecordingId,
|
||||||
|
Notes: note,
|
||||||
|
}
|
||||||
|
if err := s.StockLogRepo.WithTx(tx).CreateOne(ctx, log, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -1034,6 +1272,8 @@ func (s *recordingService) syncRecordingStocks(
|
|||||||
recordingID uint,
|
recordingID uint,
|
||||||
existing []entity.RecordingStock,
|
existing []entity.RecordingStock,
|
||||||
incoming []validation.Stock,
|
incoming []validation.Stock,
|
||||||
|
note string,
|
||||||
|
actorID uint,
|
||||||
) error {
|
) error {
|
||||||
if s.FifoSvc == nil {
|
if s.FifoSvc == nil {
|
||||||
if err := s.Repository.DeleteStocks(tx, recordingID); err != nil {
|
if err := s.Repository.DeleteStocks(tx, recordingID); err != nil {
|
||||||
@@ -1080,7 +1320,7 @@ func (s *recordingService) syncRecordingStocks(
|
|||||||
leftovers = append(leftovers, list...)
|
leftovers = append(leftovers, list...)
|
||||||
}
|
}
|
||||||
if len(leftovers) > 0 {
|
if len(leftovers) > 0 {
|
||||||
if err := s.releaseRecordingStocks(ctx, tx, leftovers); err != nil {
|
if err := s.releaseRecordingStocks(ctx, tx, leftovers, note, actorID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ids := make([]uint, 0, len(leftovers))
|
ids := make([]uint, 0, len(leftovers))
|
||||||
@@ -1099,7 +1339,7 @@ func (s *recordingService) syncRecordingStocks(
|
|||||||
if len(stocksToConsume) == 0 {
|
if len(stocksToConsume) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return s.consumeRecordingStocks(ctx, tx, stocksToConsume)
|
return s.consumeRecordingStocks(ctx, tx, stocksToConsume, note, actorID)
|
||||||
}
|
}
|
||||||
|
|
||||||
type eggTotals struct {
|
type eggTotals struct {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import (
|
|||||||
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"
|
rWarehouse "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"
|
||||||
rPurchase "gitlab.com/mbugroup/lti-api.git/internal/modules/purchases/repositories"
|
rPurchase "gitlab.com/mbugroup/lti-api.git/internal/modules/purchases/repositories"
|
||||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/purchases/validations"
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/purchases/validations"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
@@ -830,9 +831,16 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
|
|||||||
receivingAction = entity.ApprovalActionUpdated
|
receivingAction = entity.ApprovalActionUpdated
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
noteSuffix := "receive"
|
||||||
|
if receivingAction == entity.ApprovalActionUpdated {
|
||||||
|
noteSuffix = "edit-receive"
|
||||||
|
}
|
||||||
|
receiveNote := fmt.Sprintf("%s#%s", strings.TrimSpace(*purchase.PoNumber), noteSuffix)
|
||||||
|
|
||||||
transactionErr := s.PurchaseRepo.DB().WithContext(c.Context()).Transaction(func(tx *gorm.DB) error {
|
transactionErr := s.PurchaseRepo.DB().WithContext(c.Context()).Transaction(func(tx *gorm.DB) error {
|
||||||
repoTx := rPurchase.NewPurchaseRepository(tx)
|
repoTx := rPurchase.NewPurchaseRepository(tx)
|
||||||
pwRepoTx := rProductWarehouse.NewProductWarehouseRepository(tx)
|
pwRepoTx := rProductWarehouse.NewProductWarehouseRepository(tx)
|
||||||
|
stockLogRepoTx := rStockLogs.NewStockLogRepository(tx)
|
||||||
|
|
||||||
deltas := make(map[uint]float64)
|
deltas := make(map[uint]float64)
|
||||||
affected := make(map[uint]struct{})
|
affected := make(map[uint]struct{})
|
||||||
@@ -849,6 +857,11 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
|
|||||||
pwID uint
|
pwID uint
|
||||||
qty float64
|
qty float64
|
||||||
}, 0, len(prepared))
|
}, 0, len(prepared))
|
||||||
|
logEntries := make([]struct {
|
||||||
|
itemID uint
|
||||||
|
pwID uint
|
||||||
|
delta float64
|
||||||
|
}, 0, len(prepared))
|
||||||
|
|
||||||
for _, prep := range prepared {
|
for _, prep := range prepared {
|
||||||
item := prep.item
|
item := prep.item
|
||||||
@@ -869,6 +882,13 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
|
|||||||
newPWID = &pwID
|
newPWID = &pwID
|
||||||
|
|
||||||
deltaQty := prep.receivedQty - item.TotalQty
|
deltaQty := prep.receivedQty - item.TotalQty
|
||||||
|
if newPWID != nil && deltaQty != 0 {
|
||||||
|
logEntries = append(logEntries, struct {
|
||||||
|
itemID uint
|
||||||
|
pwID uint
|
||||||
|
delta float64
|
||||||
|
}{itemID: item.Id, pwID: *newPWID, delta: deltaQty})
|
||||||
|
}
|
||||||
switch {
|
switch {
|
||||||
case deltaQty > 0 && newPWID != nil:
|
case deltaQty > 0 && newPWID != nil:
|
||||||
if s.FifoSvc != nil {
|
if s.FifoSvc != nil {
|
||||||
@@ -993,6 +1013,33 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(logEntries) > 0 {
|
||||||
|
logs := make([]*entity.StockLog, 0, len(logEntries))
|
||||||
|
for _, entry := range logEntries {
|
||||||
|
if entry.pwID == 0 || entry.delta == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
log := &entity.StockLog{
|
||||||
|
ProductWarehouseId: entry.pwID,
|
||||||
|
CreatedBy: actorID,
|
||||||
|
LoggableType: string(utils.StockLogTypePurchase),
|
||||||
|
LoggableId: purchase.Id,
|
||||||
|
Notes: receiveNote,
|
||||||
|
}
|
||||||
|
if entry.delta > 0 {
|
||||||
|
log.Increase = entry.delta
|
||||||
|
} else {
|
||||||
|
log.Decrease = -entry.delta
|
||||||
|
}
|
||||||
|
logs = append(logs, log)
|
||||||
|
}
|
||||||
|
if len(logs) > 0 {
|
||||||
|
if err := stockLogRepoTx.CreateMany(c.Context(), logs, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if len(affected) > 0 {
|
if len(affected) > 0 {
|
||||||
if err := pwRepoTx.CleanupEmpty(c.Context(), affected); err != nil {
|
if err := pwRepoTx.CleanupEmpty(c.Context(), affected); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -37,6 +37,21 @@ func NewDebtSupplierRepository(db *gorm.DB) DebtSupplierRepository {
|
|||||||
return &debtSupplierRepositoryImpl{db: db}
|
return &debtSupplierRepositoryImpl{db: db}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *debtSupplierRepositoryImpl) latestPurchaseApproval(ctx context.Context) *gorm.DB {
|
||||||
|
return r.db.WithContext(ctx).
|
||||||
|
Table("approvals AS a").
|
||||||
|
Select("a.approvable_id, a.step_number, a.action").
|
||||||
|
Joins(`
|
||||||
|
JOIN (
|
||||||
|
SELECT approvable_id, MAX(action_at) AS latest_action_at
|
||||||
|
FROM approvals
|
||||||
|
WHERE approvable_type = ?
|
||||||
|
GROUP BY approvable_id
|
||||||
|
) AS la ON la.approvable_id = a.approvable_id AND la.latest_action_at = a.action_at`,
|
||||||
|
string(utils.ApprovalWorkflowPurchase),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
func resolveDebtSupplierDateColumn(filterBy string) string {
|
func resolveDebtSupplierDateColumn(filterBy string) string {
|
||||||
switch strings.ToLower(strings.TrimSpace(filterBy)) {
|
switch strings.ToLower(strings.TrimSpace(filterBy)) {
|
||||||
case "po_date":
|
case "po_date":
|
||||||
@@ -54,7 +69,11 @@ func (r *debtSupplierRepositoryImpl) baseSupplierQuery(ctx context.Context, filt
|
|||||||
db := r.db.WithContext(ctx).
|
db := r.db.WithContext(ctx).
|
||||||
Model(&entity.Supplier{}).
|
Model(&entity.Supplier{}).
|
||||||
Joins("JOIN purchases ON purchases.supplier_id = suppliers.id").
|
Joins("JOIN purchases ON purchases.supplier_id = suppliers.id").
|
||||||
Joins("JOIN purchase_items ON purchase_items.purchase_id = purchases.id")
|
Joins("JOIN purchase_items ON purchase_items.purchase_id = purchases.id").
|
||||||
|
Joins("JOIN (?) AS la ON la.approvable_id = purchases.id", r.latestPurchaseApproval(ctx)).
|
||||||
|
Where("la.step_number >= ?", uint16(utils.PurchaseStepReceiving)).
|
||||||
|
Where("(la.action IS NULL OR la.action != ?)", string(entity.ApprovalActionRejected)).
|
||||||
|
Where("purchase_items.received_date IS NOT NULL")
|
||||||
|
|
||||||
if len(filters.SupplierIDs) > 0 {
|
if len(filters.SupplierIDs) > 0 {
|
||||||
db = db.Where("suppliers.id IN ?", filters.SupplierIDs)
|
db = db.Where("suppliers.id IN ?", filters.SupplierIDs)
|
||||||
@@ -207,7 +226,11 @@ func (r *debtSupplierRepositoryImpl) getPurchaseIDs(ctx context.Context, supplie
|
|||||||
Table("purchases").
|
Table("purchases").
|
||||||
Select("DISTINCT purchases.id").
|
Select("DISTINCT purchases.id").
|
||||||
Joins("JOIN purchase_items ON purchase_items.purchase_id = purchases.id").
|
Joins("JOIN purchase_items ON purchase_items.purchase_id = purchases.id").
|
||||||
Where("purchases.supplier_id IN ?", supplierIDs)
|
Joins("JOIN (?) AS la ON la.approvable_id = purchases.id", r.latestPurchaseApproval(ctx)).
|
||||||
|
Where("purchases.supplier_id IN ?", supplierIDs).
|
||||||
|
Where("la.step_number >= ?", uint16(utils.PurchaseStepReceiving)).
|
||||||
|
Where("(la.action IS NULL OR la.action != ?)", string(entity.ApprovalActionRejected)).
|
||||||
|
Where("purchase_items.received_date IS NOT NULL")
|
||||||
|
|
||||||
if filters.StartDate != "" {
|
if filters.StartDate != "" {
|
||||||
if dateFrom, err := utils.ParseDateString(filters.StartDate); err == nil {
|
if dateFrom, err := utils.ParseDateString(filters.StartDate); err == nil {
|
||||||
@@ -355,7 +378,11 @@ func (r *debtSupplierRepositoryImpl) GetPurchaseTotalsBeforeDate(ctx context.Con
|
|||||||
Table("purchases").
|
Table("purchases").
|
||||||
Select("purchases.supplier_id AS supplier_id, SUM(purchase_items.total_price) AS total").
|
Select("purchases.supplier_id AS supplier_id, SUM(purchase_items.total_price) AS total").
|
||||||
Joins("JOIN purchase_items ON purchase_items.purchase_id = purchases.id").
|
Joins("JOIN purchase_items ON purchase_items.purchase_id = purchases.id").
|
||||||
|
Joins("JOIN (?) AS la ON la.approvable_id = purchases.id", r.latestPurchaseApproval(ctx)).
|
||||||
Where("purchases.supplier_id IN ?", supplierIDs).
|
Where("purchases.supplier_id IN ?", supplierIDs).
|
||||||
|
Where("la.step_number >= ?", uint16(utils.PurchaseStepReceiving)).
|
||||||
|
Where("(la.action IS NULL OR la.action != ?)", string(entity.ApprovalActionRejected)).
|
||||||
|
Where("purchase_items.received_date IS NOT NULL").
|
||||||
Where(fmt.Sprintf("DATE(%s) < ?", dateColumn), dateFrom).
|
Where(fmt.Sprintf("DATE(%s) < ?", dateColumn), dateFrom).
|
||||||
Group("purchases.supplier_id").
|
Group("purchases.supplier_id").
|
||||||
Scan(&rows).Error; err != nil {
|
Scan(&rows).Error; err != nil {
|
||||||
|
|||||||
@@ -860,7 +860,7 @@ func (s *repportService) getUniformityByWeek(ctx context.Context, projectFlockKa
|
|||||||
var rows []entity.ProjectFlockKandangUniformity
|
var rows []entity.ProjectFlockKandangUniformity
|
||||||
if err := s.DB.WithContext(ctx).
|
if err := s.DB.WithContext(ctx).
|
||||||
Model(&entity.ProjectFlockKandangUniformity{}).
|
Model(&entity.ProjectFlockKandangUniformity{}).
|
||||||
Select("week, uniformity, uniform_date, id").
|
Select("week, uniformity, uniform_date, id, chart_data").
|
||||||
Where("project_flock_kandang_id = ?", projectFlockKandangID).
|
Where("project_flock_kandang_id = ?", projectFlockKandangID).
|
||||||
Where("week IN ?", weeks).
|
Where("week IN ?", weeks).
|
||||||
Order("uniform_date DESC").
|
Order("uniform_date DESC").
|
||||||
@@ -1156,12 +1156,6 @@ func (s *repportService) GetDebtSupplier(c *fiber.Ctx, params *validation.DebtSu
|
|||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
references := collectDebtSupplierReferences(purchases)
|
|
||||||
paymentSummaries, err := s.DebtSupplierRepo.GetPaymentSummariesByReferences(c.Context(), supplierIDs, references)
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
location, err := time.LoadLocation("Asia/Jakarta")
|
location, err := time.LoadLocation("Asia/Jakarta")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, fiber.NewError(fiber.StatusInternalServerError, "failed to load timezone configuration")
|
return nil, 0, fiber.NewError(fiber.StatusInternalServerError, "failed to load timezone configuration")
|
||||||
@@ -1176,6 +1170,16 @@ func (s *repportService) GetDebtSupplier(c *fiber.Ctx, params *validation.DebtSu
|
|||||||
DeltaBalance float64
|
DeltaBalance float64
|
||||||
CountTotals bool
|
CountTotals bool
|
||||||
}
|
}
|
||||||
|
type debtSupplierAllocation struct {
|
||||||
|
RowIndex int
|
||||||
|
SortTime time.Time
|
||||||
|
Amount float64
|
||||||
|
Purchase entity.Purchase
|
||||||
|
}
|
||||||
|
type paymentAllocation struct {
|
||||||
|
Date time.Time
|
||||||
|
Amount float64
|
||||||
|
}
|
||||||
|
|
||||||
for _, supplierID := range supplierIDs {
|
for _, supplierID := range supplierIDs {
|
||||||
supplier, exists := supplierMap[supplierID]
|
supplier, exists := supplierMap[supplierID]
|
||||||
@@ -1189,19 +1193,11 @@ func (s *repportService) GetDebtSupplier(c *fiber.Ctx, params *validation.DebtSu
|
|||||||
total := dto.DebtSupplierTotalDTO{}
|
total := dto.DebtSupplierTotalDTO{}
|
||||||
|
|
||||||
combinedRows := make([]debtSupplierRowItem, 0, len(items)+len(paymentItems))
|
combinedRows := make([]debtSupplierRowItem, 0, len(items)+len(paymentItems))
|
||||||
|
purchaseAllocations := make([]debtSupplierAllocation, 0, len(items))
|
||||||
for _, purchase := range items {
|
for _, purchase := range items {
|
||||||
row := buildDebtSupplierRow(purchase, now, location)
|
row := buildDebtSupplierRow(purchase, now, location)
|
||||||
if reference := resolveDebtSupplierReference(purchase); reference != "" {
|
|
||||||
if summary, ok := paymentSummaries[reference]; ok {
|
|
||||||
if isDebtSupplierPaid(row.TotalPrice, summary.Total) {
|
|
||||||
row.Status = "Lunas"
|
|
||||||
if !summary.LatestPaymentDate.IsZero() {
|
|
||||||
row.Aging = calculateDebtSupplierAging(purchase, summary.LatestPaymentDate, location)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sortTime := resolveDebtSupplierSortTime(purchase, params.FilterBy, location)
|
sortTime := resolveDebtSupplierSortTime(purchase, params.FilterBy, location)
|
||||||
|
rowIndex := len(combinedRows)
|
||||||
combinedRows = append(combinedRows, debtSupplierRowItem{
|
combinedRows = append(combinedRows, debtSupplierRowItem{
|
||||||
Row: row,
|
Row: row,
|
||||||
SortTime: sortTime,
|
SortTime: sortTime,
|
||||||
@@ -1209,6 +1205,24 @@ func (s *repportService) GetDebtSupplier(c *fiber.Ctx, params *validation.DebtSu
|
|||||||
DeltaBalance: -row.TotalPrice,
|
DeltaBalance: -row.TotalPrice,
|
||||||
CountTotals: true,
|
CountTotals: true,
|
||||||
})
|
})
|
||||||
|
purchaseAllocations = append(purchaseAllocations, debtSupplierAllocation{
|
||||||
|
RowIndex: rowIndex,
|
||||||
|
SortTime: sortTime,
|
||||||
|
Amount: row.TotalPrice,
|
||||||
|
Purchase: purchase,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
paymentAllocations := make([]paymentAllocation, 0, len(paymentItems)+1)
|
||||||
|
initialAllocation := initialBalanceTotals[supplierID] + initialPaymentTotals[supplierID] - initialPurchaseTotals[supplierID]
|
||||||
|
paymentCarry := 0.0
|
||||||
|
if initialAllocation > 0 && len(purchaseAllocations) > 0 {
|
||||||
|
paymentAllocations = append(paymentAllocations, paymentAllocation{
|
||||||
|
Date: purchaseAllocations[0].SortTime,
|
||||||
|
Amount: initialAllocation,
|
||||||
|
})
|
||||||
|
} else if initialAllocation < 0 {
|
||||||
|
paymentCarry = -initialAllocation
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, payment := range paymentItems {
|
for _, payment := range paymentItems {
|
||||||
@@ -1221,6 +1235,53 @@ func (s *repportService) GetDebtSupplier(c *fiber.Ctx, params *validation.DebtSu
|
|||||||
DeltaBalance: payment.Nominal,
|
DeltaBalance: payment.Nominal,
|
||||||
CountTotals: false,
|
CountTotals: false,
|
||||||
})
|
})
|
||||||
|
paymentAllocations = append(paymentAllocations, paymentAllocation{
|
||||||
|
Date: sortTime,
|
||||||
|
Amount: payment.Nominal,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(purchaseAllocations) > 0 && len(paymentAllocations) > 0 {
|
||||||
|
sort.SliceStable(purchaseAllocations, func(i, j int) bool {
|
||||||
|
return purchaseAllocations[i].SortTime.Before(purchaseAllocations[j].SortTime)
|
||||||
|
})
|
||||||
|
sort.SliceStable(paymentAllocations, func(i, j int) bool {
|
||||||
|
return paymentAllocations[i].Date.Before(paymentAllocations[j].Date)
|
||||||
|
})
|
||||||
|
remaining := make([]float64, len(purchaseAllocations))
|
||||||
|
for i := range purchaseAllocations {
|
||||||
|
remaining[i] = purchaseAllocations[i].Amount
|
||||||
|
}
|
||||||
|
purchaseIndex := 0
|
||||||
|
for _, pay := range paymentAllocations {
|
||||||
|
amount := pay.Amount
|
||||||
|
if amount <= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if paymentCarry > 0 {
|
||||||
|
used := math.Min(amount, paymentCarry)
|
||||||
|
paymentCarry -= used
|
||||||
|
amount -= used
|
||||||
|
}
|
||||||
|
for amount > 0 && purchaseIndex < len(remaining) {
|
||||||
|
if remaining[purchaseIndex] <= 0 {
|
||||||
|
purchaseIndex++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
used := math.Min(amount, remaining[purchaseIndex])
|
||||||
|
remaining[purchaseIndex] -= used
|
||||||
|
amount -= used
|
||||||
|
if remaining[purchaseIndex] <= 0.000001 {
|
||||||
|
allocation := purchaseAllocations[purchaseIndex]
|
||||||
|
combinedRows[allocation.RowIndex].Row.Status = "Lunas"
|
||||||
|
combinedRows[allocation.RowIndex].Row.Aging = calculateDebtSupplierAging(allocation.Purchase, pay.Date, location)
|
||||||
|
purchaseIndex++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if purchaseIndex >= len(remaining) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.SliceStable(combinedRows, func(i, j int) bool {
|
sort.SliceStable(combinedRows, func(i, j int) bool {
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ type HppPerKandangQuery struct {
|
|||||||
|
|
||||||
type ProductionResultQuery struct {
|
type ProductionResultQuery struct {
|
||||||
Page int `query:"page" validate:"omitempty,min=1,gt=0"`
|
Page int `query:"page" validate:"omitempty,min=1,gt=0"`
|
||||||
Limit int `query:"limit" validate:"omitempty,min=1,max=1000,gt=0"`
|
Limit int `query:"limit" validate:"omitempty,min=1,gt=0"`
|
||||||
ProjectFlockKandangID uint `query:"-" validate:"required,gt=0"`
|
ProjectFlockKandangID uint `query:"-" validate:"required,gt=0"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -113,6 +113,8 @@ const (
|
|||||||
StockLogTypeTransfer StockLogType = "TRANSFER"
|
StockLogTypeTransfer StockLogType = "TRANSFER"
|
||||||
StockLogTypeMarketing StockLogType = "MARKETING"
|
StockLogTypeMarketing StockLogType = "MARKETING"
|
||||||
StockLogTypeChikin StockLogType = "CHICKIN"
|
StockLogTypeChikin StockLogType = "CHICKIN"
|
||||||
|
StockLogTypePurchase StockLogType = "PURCHASE"
|
||||||
|
StockLogTypeRecording StockLogType = "RECORDING"
|
||||||
)
|
)
|
||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user