mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-24 07:15:43 +00:00
fix: reimplement transfer to laying logics separating effective financial date and physical transfer date
This commit is contained in:
+8
@@ -0,0 +1,8 @@
|
|||||||
|
BEGIN;
|
||||||
|
|
||||||
|
DROP INDEX IF EXISTS idx_laying_transfers_economic_cutoff_date;
|
||||||
|
|
||||||
|
ALTER TABLE laying_transfers
|
||||||
|
DROP COLUMN IF EXISTS economic_cutoff_date;
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
BEGIN;
|
||||||
|
|
||||||
|
ALTER TABLE laying_transfers
|
||||||
|
ADD COLUMN IF NOT EXISTS economic_cutoff_date DATE;
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_laying_transfers_economic_cutoff_date
|
||||||
|
ON laying_transfers(economic_cutoff_date);
|
||||||
|
|
||||||
|
UPDATE laying_transfers
|
||||||
|
SET economic_cutoff_date = COALESCE(economic_cutoff_date, effective_move_date, transfer_date)
|
||||||
|
WHERE economic_cutoff_date IS NULL;
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
@@ -12,6 +12,7 @@ type LayingTransfer struct {
|
|||||||
FromProjectFlockId uint `gorm:"not null"`
|
FromProjectFlockId uint `gorm:"not null"`
|
||||||
ToProjectFlockId uint `gorm:"not null"`
|
ToProjectFlockId uint `gorm:"not null"`
|
||||||
TransferDate time.Time `gorm:"type:date;not null"`
|
TransferDate time.Time `gorm:"type:date;not null"`
|
||||||
|
EconomicCutoffDate *time.Time `gorm:"type:date"`
|
||||||
EffectiveMoveDate *time.Time `gorm:"type:date"`
|
EffectiveMoveDate *time.Time `gorm:"type:date"`
|
||||||
ExecutedAt *time.Time `gorm:"type:timestamptz"`
|
ExecutedAt *time.Time `gorm:"type:timestamptz"`
|
||||||
ExecutedBy *uint `gorm:"index"`
|
ExecutedBy *uint `gorm:"index"`
|
||||||
|
|||||||
@@ -293,7 +293,8 @@ func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent
|
|||||||
category := strings.ToUpper(pfk.ProjectFlock.Category)
|
category := strings.ToUpper(pfk.ProjectFlock.Category)
|
||||||
isLaying := category == strings.ToUpper(string(utils.ProjectFlockCategoryLaying))
|
isLaying := category == strings.ToUpper(string(utils.ProjectFlockCategoryLaying))
|
||||||
|
|
||||||
if err := s.enforceTransferRecordingRoute(ctx, pfk, recordTime); err != nil {
|
routePayload := buildRecordingRoutePayloadFromCreate(req)
|
||||||
|
if err := s.enforceTransferRecordingRoute(ctx, pfk, recordTime, routePayload); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -494,6 +495,23 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
|
|||||||
}
|
}
|
||||||
|
|
||||||
recordingEntity = recording
|
recordingEntity = recording
|
||||||
|
pfkForRoute := recordingEntity.ProjectFlockKandang
|
||||||
|
if pfkForRoute == nil || pfkForRoute.Id == 0 {
|
||||||
|
fetchedPfk, fetchErr := s.ProjectFlockKandangRepo.GetByIDLight(ctx, recordingEntity.ProjectFlockKandangId)
|
||||||
|
if fetchErr != nil {
|
||||||
|
if errors.Is(fetchErr, gorm.ErrRecordNotFound) {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Project flock kandang not found")
|
||||||
|
}
|
||||||
|
s.Log.Errorf("Failed to fetch project flock kandang for route validation: %+v", fetchErr)
|
||||||
|
return fetchErr
|
||||||
|
}
|
||||||
|
pfkForRoute = fetchedPfk
|
||||||
|
}
|
||||||
|
routePayload := buildRecordingRoutePayloadFromUpdate(req)
|
||||||
|
if err := s.enforceTransferRecordingRoute(ctx, pfkForRoute, recordingEntity.RecordDatetime, routePayload); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
hasStockChanges := req.Stocks != nil
|
hasStockChanges := req.Stocks != nil
|
||||||
hasDepletionChanges := req.Depletions != nil
|
hasDepletionChanges := req.Depletions != nil
|
||||||
hasEggChanges := req.Eggs != nil
|
hasEggChanges := req.Eggs != nil
|
||||||
@@ -909,6 +927,7 @@ func (s *recordingService) enforceTransferRecordingRoute(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
pfk *entity.ProjectFlockKandang,
|
pfk *entity.ProjectFlockKandang,
|
||||||
recordTime time.Time,
|
recordTime time.Time,
|
||||||
|
payload recordingRoutePayload,
|
||||||
) error {
|
) error {
|
||||||
if pfk == nil || pfk.Id == 0 || s.TransferLayingRepo == nil {
|
if pfk == nil || pfk.Id == 0 || s.TransferLayingRepo == nil {
|
||||||
return nil
|
return nil
|
||||||
@@ -928,22 +947,35 @@ func (s *recordingService) enforceTransferRecordingRoute(
|
|||||||
return fiber.NewError(fiber.StatusInternalServerError, "Gagal memvalidasi transfer laying")
|
return fiber.NewError(fiber.StatusInternalServerError, "Gagal memvalidasi transfer laying")
|
||||||
}
|
}
|
||||||
|
|
||||||
effectiveDate := effectiveTransferDate(transfer)
|
physicalMoveDate, economicCutoffDate := transferRecordingWindow(transfer)
|
||||||
if effectiveDate.IsZero() {
|
if physicalMoveDate.IsZero() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if recordDate.Before(effectiveDate) {
|
if recordDate.Before(physicalMoveDate) {
|
||||||
return fiber.NewError(
|
return fiber.NewError(
|
||||||
fiber.StatusBadRequest,
|
fiber.StatusBadRequest,
|
||||||
fmt.Sprintf("Recording kandang laying hanya bisa dimulai pada %s. Sebelumnya gunakan kandang growing", effectiveDate.Format("2006-01-02")),
|
fmt.Sprintf("Recording kandang laying hanya bisa dimulai pada %s (tanggal pindah fisik). Sebelumnya gunakan kandang growing", physicalMoveDate.Format("2006-01-02")),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if transfer.ExecutedAt == nil || transfer.ExecutedAt.IsZero() {
|
if transfer.ExecutedAt == nil || transfer.ExecutedAt.IsZero() {
|
||||||
return fiber.NewError(
|
return fiber.NewError(
|
||||||
fiber.StatusBadRequest,
|
fiber.StatusBadRequest,
|
||||||
fmt.Sprintf("Transfer laying %s sudah efektif pada %s tetapi belum dieksekusi. Eksekusi transfer terlebih dahulu", transfer.TransferNumber, effectiveDate.Format("2006-01-02")),
|
fmt.Sprintf("Transfer laying %s dengan tanggal pindah fisik %s belum dieksekusi. Eksekusi transfer terlebih dahulu", transfer.TransferNumber, physicalMoveDate.Format("2006-01-02")),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if recordDate.Before(economicCutoffDate) && payload.StockCount > 0 {
|
||||||
|
return fiber.NewError(
|
||||||
|
fiber.StatusBadRequest,
|
||||||
|
fmt.Sprintf(
|
||||||
|
"Periode transisi transfer laying %s (%s s.d. %s): input PAKAN/OVK harus dicatat di kandang growing hingga %s. Recording kandang laying pada periode ini hanya untuk deplesi (dan telur bila ada).",
|
||||||
|
transfer.TransferNumber,
|
||||||
|
physicalMoveDate.Format("2006-01-02"),
|
||||||
|
economicCutoffDate.AddDate(0, 0, -1).Format("2006-01-02"),
|
||||||
|
economicCutoffDate.AddDate(0, 0, -1).Format("2006-01-02"),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -957,22 +989,38 @@ func (s *recordingService) enforceTransferRecordingRoute(
|
|||||||
return fiber.NewError(fiber.StatusInternalServerError, "Gagal memvalidasi transfer laying")
|
return fiber.NewError(fiber.StatusInternalServerError, "Gagal memvalidasi transfer laying")
|
||||||
}
|
}
|
||||||
|
|
||||||
if transfer != nil && transfer.ExecutedAt != nil && !transfer.ExecutedAt.IsZero() {
|
physicalMoveDate, economicCutoffDate := transferRecordingWindow(transfer)
|
||||||
return fiber.NewError(
|
if physicalMoveDate.IsZero() {
|
||||||
fiber.StatusBadRequest,
|
|
||||||
"Project flock kandang sudah dipindahkan ke laying",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
effectiveDate := effectiveTransferDate(transfer)
|
|
||||||
if effectiveDate.IsZero() {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !recordDate.Before(effectiveDate) {
|
if recordDate.Before(physicalMoveDate) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if transfer.ExecutedAt == nil || transfer.ExecutedAt.IsZero() {
|
||||||
return fiber.NewError(
|
return fiber.NewError(
|
||||||
fiber.StatusBadRequest,
|
fiber.StatusBadRequest,
|
||||||
fmt.Sprintf("Recording kandang growing hanya diperbolehkan sampai %s. Gunakan kandang laying mulai %s", effectiveDate.AddDate(0, 0, -1).Format("2006-01-02"), effectiveDate.Format("2006-01-02")),
|
fmt.Sprintf("Transfer laying %s sudah memasuki tanggal pindah fisik %s namun belum dieksekusi. Eksekusi transfer lalu lakukan recording transisi sesuai aturan", transfer.TransferNumber, physicalMoveDate.Format("2006-01-02")),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !recordDate.Before(economicCutoffDate) {
|
||||||
|
return fiber.NewError(
|
||||||
|
fiber.StatusBadRequest,
|
||||||
|
fmt.Sprintf("Recording kandang growing hanya diperbolehkan sampai %s. Gunakan kandang laying mulai %s", economicCutoffDate.AddDate(0, 0, -1).Format("2006-01-02"), economicCutoffDate.Format("2006-01-02")),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if payload.DepletionCount > 0 {
|
||||||
|
return fiber.NewError(
|
||||||
|
fiber.StatusBadRequest,
|
||||||
|
fmt.Sprintf(
|
||||||
|
"Periode transisi transfer laying %s (%s s.d. %s): deplesi harus dicatat di kandang laying tujuan agar mapping tidak ambigu. Kandang growing pada periode ini hanya untuk PAKAN/OVK.",
|
||||||
|
transfer.TransferNumber,
|
||||||
|
physicalMoveDate.Format("2006-01-02"),
|
||||||
|
economicCutoffDate.AddDate(0, 0, -1).Format("2006-01-02"),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -980,19 +1028,93 @@ func (s *recordingService) enforceTransferRecordingRoute(
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func effectiveTransferDate(transfer *entity.LayingTransfer) time.Time {
|
type recordingRoutePayload struct {
|
||||||
|
StockCount int
|
||||||
|
DepletionCount int
|
||||||
|
EggCount int
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildRecordingRoutePayloadFromCreate(req *validation.Create) recordingRoutePayload {
|
||||||
|
payload := recordingRoutePayload{}
|
||||||
|
if req == nil {
|
||||||
|
return payload
|
||||||
|
}
|
||||||
|
for _, stock := range req.Stocks {
|
||||||
|
if stock.Qty > 0 {
|
||||||
|
payload.StockCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, depletion := range req.Depletions {
|
||||||
|
if depletion.Qty > 0 {
|
||||||
|
payload.DepletionCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, egg := range req.Eggs {
|
||||||
|
if egg.Qty > 0 {
|
||||||
|
payload.EggCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return payload
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildRecordingRoutePayloadFromUpdate(req *validation.Update) recordingRoutePayload {
|
||||||
|
payload := recordingRoutePayload{}
|
||||||
|
if req == nil {
|
||||||
|
return payload
|
||||||
|
}
|
||||||
|
for _, stock := range req.Stocks {
|
||||||
|
if stock.Qty > 0 {
|
||||||
|
payload.StockCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, depletion := range req.Depletions {
|
||||||
|
if depletion.Qty > 0 {
|
||||||
|
payload.DepletionCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, egg := range req.Eggs {
|
||||||
|
if egg.Qty > 0 {
|
||||||
|
payload.EggCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return payload
|
||||||
|
}
|
||||||
|
|
||||||
|
func transferPhysicalMoveDate(transfer *entity.LayingTransfer) time.Time {
|
||||||
if transfer == nil {
|
if transfer == nil {
|
||||||
return time.Time{}
|
return time.Time{}
|
||||||
}
|
}
|
||||||
if transfer.EffectiveMoveDate != nil && !transfer.EffectiveMoveDate.IsZero() {
|
|
||||||
return normalizeDateOnlyUTC(*transfer.EffectiveMoveDate)
|
|
||||||
}
|
|
||||||
if !transfer.TransferDate.IsZero() {
|
if !transfer.TransferDate.IsZero() {
|
||||||
return normalizeDateOnlyUTC(transfer.TransferDate)
|
return normalizeDateOnlyUTC(transfer.TransferDate)
|
||||||
}
|
}
|
||||||
return time.Time{}
|
return time.Time{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func transferEconomicCutoffDate(transfer *entity.LayingTransfer) time.Time {
|
||||||
|
if transfer == nil {
|
||||||
|
return time.Time{}
|
||||||
|
}
|
||||||
|
if transfer.EconomicCutoffDate != nil && !transfer.EconomicCutoffDate.IsZero() {
|
||||||
|
return normalizeDateOnlyUTC(*transfer.EconomicCutoffDate)
|
||||||
|
}
|
||||||
|
if transfer.EffectiveMoveDate != nil && !transfer.EffectiveMoveDate.IsZero() {
|
||||||
|
return normalizeDateOnlyUTC(*transfer.EffectiveMoveDate)
|
||||||
|
}
|
||||||
|
return transferPhysicalMoveDate(transfer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func transferRecordingWindow(transfer *entity.LayingTransfer) (time.Time, time.Time) {
|
||||||
|
physicalMoveDate := transferPhysicalMoveDate(transfer)
|
||||||
|
economicCutoffDate := transferEconomicCutoffDate(transfer)
|
||||||
|
if economicCutoffDate.IsZero() {
|
||||||
|
economicCutoffDate = physicalMoveDate
|
||||||
|
}
|
||||||
|
if !physicalMoveDate.IsZero() && economicCutoffDate.Before(physicalMoveDate) {
|
||||||
|
economicCutoffDate = physicalMoveDate
|
||||||
|
}
|
||||||
|
return physicalMoveDate, economicCutoffDate
|
||||||
|
}
|
||||||
|
|
||||||
func normalizeDateOnlyUTC(value time.Time) time.Time {
|
func normalizeDateOnlyUTC(value time.Time) time.Time {
|
||||||
return time.Date(value.UTC().Year(), value.UTC().Month(), value.UTC().Day(), 0, 0, 0, 0, time.UTC)
|
return time.Date(value.UTC().Year(), value.UTC().Month(), value.UTC().Day(), 0, 0, 0, 0, time.UTC)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
|
)
|
||||||
|
|
||||||
|
func mustDate(t *testing.T, value string) time.Time {
|
||||||
|
t.Helper()
|
||||||
|
parsed, err := time.Parse("2006-01-02", value)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed parsing date %s: %v", value, err)
|
||||||
|
}
|
||||||
|
return parsed
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTransferRecordingWindow(t *testing.T) {
|
||||||
|
t.Run("early transfer keeps transition until economic cutoff", func(t *testing.T) {
|
||||||
|
physical := mustDate(t, "2026-04-08")
|
||||||
|
cutoff := mustDate(t, "2026-05-13")
|
||||||
|
transfer := &entity.LayingTransfer{
|
||||||
|
TransferDate: physical,
|
||||||
|
EconomicCutoffDate: &cutoff,
|
||||||
|
}
|
||||||
|
|
||||||
|
gotPhysical, gotCutoff := transferRecordingWindow(transfer)
|
||||||
|
if gotPhysical.Format("2006-01-02") != "2026-04-08" {
|
||||||
|
t.Fatalf("unexpected physical date: %s", gotPhysical.Format("2006-01-02"))
|
||||||
|
}
|
||||||
|
if gotCutoff.Format("2006-01-02") != "2026-05-13" {
|
||||||
|
t.Fatalf("unexpected cutoff date: %s", gotCutoff.Format("2006-01-02"))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("standard transfer has no transition window", func(t *testing.T) {
|
||||||
|
physical := mustDate(t, "2026-05-13")
|
||||||
|
cutoff := mustDate(t, "2026-05-13")
|
||||||
|
transfer := &entity.LayingTransfer{
|
||||||
|
TransferDate: physical,
|
||||||
|
EconomicCutoffDate: &cutoff,
|
||||||
|
}
|
||||||
|
|
||||||
|
gotPhysical, gotCutoff := transferRecordingWindow(transfer)
|
||||||
|
if gotPhysical.Format("2006-01-02") != "2026-05-13" {
|
||||||
|
t.Fatalf("unexpected physical date: %s", gotPhysical.Format("2006-01-02"))
|
||||||
|
}
|
||||||
|
if gotCutoff.Format("2006-01-02") != "2026-05-13" {
|
||||||
|
t.Fatalf("unexpected cutoff date: %s", gotCutoff.Format("2006-01-02"))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("late transfer clamps economic cutoff to physical move", func(t *testing.T) {
|
||||||
|
physical := mustDate(t, "2026-06-03")
|
||||||
|
cutoff := mustDate(t, "2026-05-13")
|
||||||
|
transfer := &entity.LayingTransfer{
|
||||||
|
TransferDate: physical,
|
||||||
|
EconomicCutoffDate: &cutoff,
|
||||||
|
}
|
||||||
|
|
||||||
|
gotPhysical, gotCutoff := transferRecordingWindow(transfer)
|
||||||
|
if gotPhysical.Format("2006-01-02") != "2026-06-03" {
|
||||||
|
t.Fatalf("unexpected physical date: %s", gotPhysical.Format("2006-01-02"))
|
||||||
|
}
|
||||||
|
if gotCutoff.Format("2006-01-02") != "2026-06-03" {
|
||||||
|
t.Fatalf("unexpected cutoff date: %s", gotCutoff.Format("2006-01-02"))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("legacy data falls back to effective move date", func(t *testing.T) {
|
||||||
|
physical := mustDate(t, "2026-04-08")
|
||||||
|
legacyEffective := mustDate(t, "2026-05-13")
|
||||||
|
transfer := &entity.LayingTransfer{
|
||||||
|
TransferDate: physical,
|
||||||
|
EffectiveMoveDate: &legacyEffective,
|
||||||
|
}
|
||||||
|
|
||||||
|
gotPhysical, gotCutoff := transferRecordingWindow(transfer)
|
||||||
|
if gotPhysical.Format("2006-01-02") != "2026-04-08" {
|
||||||
|
t.Fatalf("unexpected physical date: %s", gotPhysical.Format("2006-01-02"))
|
||||||
|
}
|
||||||
|
if gotCutoff.Format("2006-01-02") != "2026-05-13" {
|
||||||
|
t.Fatalf("unexpected cutoff date: %s", gotCutoff.Format("2006-01-02"))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -14,12 +14,13 @@ import (
|
|||||||
// === DTO Structs ===
|
// === DTO Structs ===
|
||||||
|
|
||||||
type TransferLayingRelationDTO struct {
|
type TransferLayingRelationDTO struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
TransferNumber string `json:"transfer_number"`
|
TransferNumber string `json:"transfer_number"`
|
||||||
TransferDate time.Time `json:"transfer_date"`
|
TransferDate time.Time `json:"transfer_date"`
|
||||||
EffectiveMoveDate *time.Time `json:"effective_move_date,omitempty"`
|
EconomicCutoffDate *time.Time `json:"economic_cutoff_date,omitempty"`
|
||||||
ExecutedAt *time.Time `json:"executed_at,omitempty"`
|
EffectiveMoveDate *time.Time `json:"effective_move_date,omitempty"`
|
||||||
Notes string `json:"notes"`
|
ExecutedAt *time.Time `json:"executed_at,omitempty"`
|
||||||
|
Notes string `json:"notes"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProjectFlockKandangWithKandangDTO struct {
|
type ProjectFlockKandangWithKandangDTO struct {
|
||||||
@@ -92,12 +93,13 @@ type MaxTargetQtyForTransferDTO struct {
|
|||||||
|
|
||||||
func ToTransferLayingRelationDTO(e entity.LayingTransfer) TransferLayingRelationDTO {
|
func ToTransferLayingRelationDTO(e entity.LayingTransfer) TransferLayingRelationDTO {
|
||||||
return TransferLayingRelationDTO{
|
return TransferLayingRelationDTO{
|
||||||
Id: e.Id,
|
Id: e.Id,
|
||||||
TransferNumber: e.TransferNumber,
|
TransferNumber: e.TransferNumber,
|
||||||
TransferDate: e.TransferDate,
|
TransferDate: e.TransferDate,
|
||||||
EffectiveMoveDate: e.EffectiveMoveDate,
|
EconomicCutoffDate: e.EconomicCutoffDate,
|
||||||
ExecutedAt: e.ExecutedAt,
|
EffectiveMoveDate: e.EffectiveMoveDate,
|
||||||
Notes: e.Notes,
|
ExecutedAt: e.ExecutedAt,
|
||||||
|
Notes: e.Notes,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -783,15 +783,16 @@ func (s transferLayingService) Approval(c *fiber.Ctx, req *validation.Approve) (
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Gagal mengambil sources transfer")
|
return fiber.NewError(fiber.StatusInternalServerError, "Gagal mengambil sources transfer")
|
||||||
}
|
}
|
||||||
effectiveMoveDate, err := s.calculateEffectiveMoveDate(c.Context(), sources)
|
economicCutoffDate, err := s.calculateEconomicCutoffDate(c.Context(), sources)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := repoTx.PatchOne(c.Context(), approvableID, map[string]any{
|
if err := repoTx.PatchOne(c.Context(), approvableID, map[string]any{
|
||||||
"effective_move_date": effectiveMoveDate,
|
"economic_cutoff_date": economicCutoffDate,
|
||||||
"executed_at": nil,
|
"effective_move_date": economicCutoffDate, // Backward-compatible alias for existing clients.
|
||||||
"executed_by": nil,
|
"executed_at": nil,
|
||||||
|
"executed_by": nil,
|
||||||
}, nil); err != nil {
|
}, nil); err != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Gagal menyimpan tanggal efektif transfer laying")
|
return fiber.NewError(fiber.StatusInternalServerError, "Gagal menyimpan tanggal efektif transfer laying")
|
||||||
}
|
}
|
||||||
@@ -866,23 +867,25 @@ func (s transferLayingService) Execute(c *fiber.Ctx, id uint) (*entity.LayingTra
|
|||||||
return fiber.NewError(fiber.StatusInternalServerError, "Gagal mengambil target transfer laying")
|
return fiber.NewError(fiber.StatusInternalServerError, "Gagal mengambil target transfer laying")
|
||||||
}
|
}
|
||||||
|
|
||||||
if transfer.EffectiveMoveDate == nil || transfer.EffectiveMoveDate.IsZero() {
|
if transfer.EconomicCutoffDate == nil || transfer.EconomicCutoffDate.IsZero() {
|
||||||
effectiveMoveDate, calcErr := s.calculateEffectiveMoveDate(c.Context(), sources)
|
economicCutoffDate, calcErr := s.calculateEconomicCutoffDate(c.Context(), sources)
|
||||||
if calcErr != nil {
|
if calcErr != nil {
|
||||||
return calcErr
|
return calcErr
|
||||||
}
|
}
|
||||||
if patchErr := repoTx.PatchOne(c.Context(), transfer.Id, map[string]any{
|
if patchErr := repoTx.PatchOne(c.Context(), transfer.Id, map[string]any{
|
||||||
"effective_move_date": effectiveMoveDate,
|
"economic_cutoff_date": economicCutoffDate,
|
||||||
|
"effective_move_date": economicCutoffDate, // Keep legacy field in sync.
|
||||||
}, nil); patchErr != nil {
|
}, nil); patchErr != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Gagal menyimpan tanggal efektif transfer laying")
|
return fiber.NewError(fiber.StatusInternalServerError, "Gagal menyimpan tanggal efektif transfer laying")
|
||||||
}
|
}
|
||||||
transfer.EffectiveMoveDate = &effectiveMoveDate
|
transfer.EconomicCutoffDate = &economicCutoffDate
|
||||||
|
transfer.EffectiveMoveDate = &economicCutoffDate
|
||||||
}
|
}
|
||||||
|
|
||||||
effectiveMoveDate := normalizeDateOnlyUTC(*transfer.EffectiveMoveDate)
|
physicalMoveDate := normalizeDateOnlyUTC(transfer.TransferDate)
|
||||||
today := normalizeDateOnlyUTC(time.Now().UTC())
|
today := normalizeDateOnlyUTC(time.Now().UTC())
|
||||||
if today.Before(effectiveMoveDate) {
|
if today.Before(physicalMoveDate) {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Transfer laying baru bisa dieksekusi mulai tanggal %s", effectiveMoveDate.Format("2006-01-02")))
|
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Transfer laying baru bisa dieksekusi mulai tanggal pindah fisik %s", physicalMoveDate.Format("2006-01-02")))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.executeApprovedTransferMovement(c.Context(), dbTransaction, transfer, actorID, sources, targets); err != nil {
|
if err := s.executeApprovedTransferMovement(c.Context(), dbTransaction, transfer, actorID, sources, targets); err != nil {
|
||||||
@@ -978,10 +981,7 @@ func (s *transferLayingService) executeApprovedTransferMovement(
|
|||||||
return fiber.NewError(fiber.StatusInternalServerError, "Gagal update source usage qty")
|
return fiber.NewError(fiber.StatusInternalServerError, "Gagal update source usage qty")
|
||||||
}
|
}
|
||||||
|
|
||||||
asOf := transfer.TransferDate
|
asOf := normalizeDateOnlyUTC(transfer.TransferDate)
|
||||||
if transfer.EffectiveMoveDate != nil && !transfer.EffectiveMoveDate.IsZero() {
|
|
||||||
asOf = *transfer.EffectiveMoveDate
|
|
||||||
}
|
|
||||||
if _, err := s.FifoStockV2Svc.Reflow(ctx, commonSvc.FifoStockV2ReflowRequest{
|
if _, err := s.FifoStockV2Svc.Reflow(ctx, commonSvc.FifoStockV2ReflowRequest{
|
||||||
FlagGroupCode: transferToLayingFlagGroupCode,
|
FlagGroupCode: transferToLayingFlagGroupCode,
|
||||||
ProductWarehouseID: *source.ProductWarehouseId,
|
ProductWarehouseID: *source.ProductWarehouseId,
|
||||||
@@ -1147,7 +1147,7 @@ func (s *transferLayingService) allocatePopulationForTransfer(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *transferLayingService) calculateEffectiveMoveDate(ctx context.Context, sources []entity.LayingTransferSource) (time.Time, error) {
|
func (s *transferLayingService) calculateEconomicCutoffDate(ctx context.Context, sources []entity.LayingTransferSource) (time.Time, error) {
|
||||||
if len(sources) == 0 {
|
if len(sources) == 0 {
|
||||||
return time.Time{}, fiber.NewError(fiber.StatusBadRequest, "Sumber transfer laying tidak ditemukan")
|
return time.Time{}, fiber.NewError(fiber.StatusBadRequest, "Sumber transfer laying tidak ditemukan")
|
||||||
}
|
}
|
||||||
@@ -1172,8 +1172,8 @@ func (s *transferLayingService) calculateEffectiveMoveDate(ctx context.Context,
|
|||||||
return time.Time{}, fiber.NewError(fiber.StatusBadRequest, "Tanggal chick in sumber transfer laying tidak ditemukan")
|
return time.Time{}, fiber.NewError(fiber.StatusBadRequest, "Tanggal chick in sumber transfer laying tidak ditemukan")
|
||||||
}
|
}
|
||||||
|
|
||||||
effectiveMoveDate := baselineChickInDate.AddDate(0, 0, maxGrowingWeek*7)
|
economicCutoffDate := baselineChickInDate.AddDate(0, 0, maxGrowingWeek*7)
|
||||||
return normalizeDateOnlyUTC(effectiveMoveDate), nil
|
return normalizeDateOnlyUTC(economicCutoffDate), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *transferLayingService) resolveSourceChickInDate(ctx context.Context, sourceProjectFlockKandangID uint) (time.Time, error) {
|
func (s *transferLayingService) resolveSourceChickInDate(ctx context.Context, sourceProjectFlockKandangID uint) (time.Time, error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user