mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 21:41:55 +00:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 09c951c16a | |||
| b615570036 | |||
| c793c3cf9a | |||
| b3e0410f5a | |||
| a882d5a687 | |||
| c0848b6d2d | |||
| b240478ed5 | |||
| 3052497fc0 | |||
| 71c62c5e02 | |||
| 768961d7d6 | |||
| 8cd9627a51 | |||
| 378d633ea4 | |||
| fb193fc61f | |||
| af7aabdec8 | |||
| bac36b4f00 | |||
| 687d02313b | |||
| 7d3602d829 | |||
| 533e9aca6f | |||
| dcfb5e10b4 | |||
| fbeccf4cdc |
+4
@@ -0,0 +1,4 @@
|
|||||||
|
-- Rollback: Remove requested_qty column from laying_transfer_sources table
|
||||||
|
|
||||||
|
ALTER TABLE laying_transfer_sources
|
||||||
|
DROP COLUMN IF EXISTS requested_qty;
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
-- Add requested_qty column to laying_transfer_sources table
|
||||||
|
-- This field stores the quantity requested by user during create/update
|
||||||
|
-- Separate from UsageQty (FIFO consumed) and PendingUsageQty (FIFO pending)
|
||||||
|
|
||||||
|
ALTER TABLE laying_transfer_sources
|
||||||
|
ADD COLUMN requested_qty NUMERIC(15,3) DEFAULT 0 NOT NULL;
|
||||||
|
|
||||||
|
-- Add comment for documentation
|
||||||
|
COMMENT ON COLUMN laying_transfer_sources.requested_qty IS 'Quantity requested by user during create/update';
|
||||||
@@ -11,6 +11,7 @@ type LayingTransferSource struct {
|
|||||||
LayingTransferId uint `gorm:"index;not null"`
|
LayingTransferId uint `gorm:"index;not null"`
|
||||||
SourceProjectFlockKandangId uint `gorm:"not null"`
|
SourceProjectFlockKandangId uint `gorm:"not null"`
|
||||||
ProductWarehouseId *uint `gorm:""`
|
ProductWarehouseId *uint `gorm:""`
|
||||||
|
RequestedQty float64 `gorm:"type:numeric(15,3);default:0;not null"` // Quantity requested by user
|
||||||
UsageQty float64 `gorm:"type:numeric(15,3);default:0;not null"` // FIFO USABLE field
|
UsageQty float64 `gorm:"type:numeric(15,3);default:0;not null"` // FIFO USABLE field
|
||||||
PendingUsageQty float64 `gorm:"type:numeric(15,3);default:0;not null"` // FIFO USABLE field
|
PendingUsageQty float64 `gorm:"type:numeric(15,3);default:0;not null"` // FIFO USABLE field
|
||||||
Note string `gorm:"type:text"`
|
Note string `gorm:"type:text"`
|
||||||
|
|||||||
@@ -534,6 +534,13 @@ JOIN products prod ON prod.id = pw.product_id
|
|||||||
JOIN uoms u ON u.id = prod.uom_id
|
JOIN uoms u ON u.id = prod.uom_id
|
||||||
JOIN warehouses w ON w.id = pw.warehouse_id
|
JOIN warehouses w ON w.id = pw.warehouse_id
|
||||||
WHERE pw.project_flock_kandang_id IN ?
|
WHERE pw.project_flock_kandang_id IN ?
|
||||||
|
AND EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM flags f
|
||||||
|
WHERE f.flagable_id = pw.product_id
|
||||||
|
AND f.flagable_type = 'products'
|
||||||
|
AND UPPER(f.name) NOT IN ('DOC', 'LAYER', 'PULLET')
|
||||||
|
)
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -396,6 +396,10 @@ func (s expenseService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint)
|
|||||||
updateBody["supplier_id"] = *req.SupplierID
|
updateBody["supplier_id"] = *req.SupplierID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if req.Notes != nil {
|
||||||
|
updateBody["notes"] = *req.Notes
|
||||||
|
}
|
||||||
|
|
||||||
if req.LocationID != nil {
|
if req.LocationID != nil {
|
||||||
locationID := uint(*req.LocationID)
|
locationID := uint(*req.LocationID)
|
||||||
updateBody["location_id"] = locationID
|
updateBody["location_id"] = locationID
|
||||||
@@ -568,20 +572,28 @@ func (s expenseService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fiber.NewError(fiber.StatusUnauthorized, "Failed to get actor ID from context")
|
return fiber.NewError(fiber.StatusUnauthorized, "Failed to get actor ID from context")
|
||||||
}
|
}
|
||||||
if *latestApproval.Action != entity.ApprovalActionUpdated {
|
|
||||||
|
if *latestApproval.Action != entity.ApprovalActionUpdated && latestApproval.StepNumber > uint16(utils.ExpenseStepPengajuan) {
|
||||||
|
|
||||||
approvalAction := entity.ApprovalActionUpdated
|
approvalAction := entity.ApprovalActionUpdated
|
||||||
|
|
||||||
|
previousStep := approvalutils.ApprovalStep(latestApproval.StepNumber) - 1
|
||||||
|
|
||||||
|
if previousStep < utils.ExpenseStepPengajuan {
|
||||||
|
previousStep = utils.ExpenseStepPengajuan
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := approvalSvcTx.CreateApproval(
|
if _, err := approvalSvcTx.CreateApproval(
|
||||||
c.Context(),
|
c.Context(),
|
||||||
utils.ApprovalWorkflowExpense,
|
utils.ApprovalWorkflowExpense,
|
||||||
id,
|
id,
|
||||||
utils.ExpenseStepPengajuan,
|
previousStep,
|
||||||
&approvalAction,
|
&approvalAction,
|
||||||
actorID,
|
actorID,
|
||||||
nil); err != nil {
|
nil); err != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to reset approval step")
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to reset approval step")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.DocumentSvc != nil && len(req.Documents) > 0 {
|
if s.DocumentSvc != nil && len(req.Documents) > 0 {
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ type Update struct {
|
|||||||
Category *string `form:"category" json:"category" validate:"omitempty,oneof=BOP NON-BOP"`
|
Category *string `form:"category" json:"category" validate:"omitempty,oneof=BOP NON-BOP"`
|
||||||
SupplierID *uint64 `form:"supplier_id" json:"supplier_id" validate:"omitempty,gt=0"`
|
SupplierID *uint64 `form:"supplier_id" json:"supplier_id" validate:"omitempty,gt=0"`
|
||||||
LocationID *uint64 `form:"location_id" json:"location_id" validate:"omitempty,gt=0"`
|
LocationID *uint64 `form:"location_id" json:"location_id" validate:"omitempty,gt=0"`
|
||||||
|
Notes *string `form:"notes" json:"notes" validate:"omitempty,max=500"`
|
||||||
Documents []*multipart.FileHeader `form:"documents" json:"documents" validate:"omitempty,dive"`
|
Documents []*multipart.FileHeader `form:"documents" json:"documents" validate:"omitempty,dive"`
|
||||||
ExpenseNonstocks *[]ExpenseNonstock `form:"expense_nonstocks" json:"expense_nonstocks" validate:"omitempty,min=1,dive"`
|
ExpenseNonstocks *[]ExpenseNonstock `form:"expense_nonstocks" json:"expense_nonstocks" validate:"omitempty,min=1,dive"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,20 +4,17 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
|
warehouseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/dto"
|
||||||
userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
|
userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TransferRelationDTO struct {
|
type TransferRelationDTO struct {
|
||||||
Id uint64 `json:"id"`
|
Id uint64 `json:"id"`
|
||||||
|
MovementNumber string `json:"movement_number"`
|
||||||
TransferReason string `json:"transfer_reason"`
|
TransferReason string `json:"transfer_reason"`
|
||||||
TransferDate string `json:"transfer_date"`
|
TransferDate string `json:"transfer_date"`
|
||||||
SourceWarehouse *WarehouseDetailDTO `json:"source_warehouse,omitempty"`
|
SourceWarehouse *warehouseDTO.WarehouseRelationDTO `json:"source_warehouse,omitempty"`
|
||||||
DestinationWarehouse *WarehouseDetailDTO `json:"destination_warehouse,omitempty"`
|
DestinationWarehouse *warehouseDTO.WarehouseRelationDTO `json:"destination_warehouse,omitempty"`
|
||||||
}
|
|
||||||
|
|
||||||
type WarehouseSimpleDTO struct {
|
|
||||||
Id uint `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProductSimpleDTO struct {
|
type ProductSimpleDTO struct {
|
||||||
@@ -25,16 +22,6 @@ type ProductSimpleDTO struct {
|
|||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AreaDTO struct {
|
|
||||||
Id uint `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type LocationDTO struct {
|
|
||||||
Id uint `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type SupplierSimpleDTO struct {
|
type SupplierSimpleDTO struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
@@ -48,13 +35,6 @@ type DocumentDTO struct {
|
|||||||
Size float64 `json:"size"`
|
Size float64 `json:"size"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type WarehouseDetailDTO struct {
|
|
||||||
Id uint `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Location *LocationDTO `json:"location"`
|
|
||||||
Area *AreaDTO `json:"area"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type TransferListDTO struct {
|
type TransferListDTO struct {
|
||||||
TransferRelationDTO
|
TransferRelationDTO
|
||||||
CreatedUser *userDTO.UserRelationDTO `json:"created_user,omitempty"`
|
CreatedUser *userDTO.UserRelationDTO `json:"created_user,omitempty"`
|
||||||
@@ -97,16 +77,19 @@ type TransferDeliveryItemDTO struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ToTransferRelationDTO(e entity.StockTransfer) TransferRelationDTO {
|
func ToTransferRelationDTO(e entity.StockTransfer) TransferRelationDTO {
|
||||||
var sourceWarehouse *WarehouseDetailDTO
|
var sourceWarehouse *warehouseDTO.WarehouseRelationDTO
|
||||||
if e.FromWarehouse != nil && e.FromWarehouse.Id != 0 {
|
if e.FromWarehouse != nil && e.FromWarehouse.Id != 0 {
|
||||||
sourceWarehouse = toWarehouseDetailDTO(e.FromWarehouse)
|
mapped := warehouseDTO.ToWarehouseRelationDTO(*e.FromWarehouse)
|
||||||
|
sourceWarehouse = &mapped
|
||||||
}
|
}
|
||||||
var destinationWarehouse *WarehouseDetailDTO
|
var destinationWarehouse *warehouseDTO.WarehouseRelationDTO
|
||||||
if e.ToWarehouse != nil && e.ToWarehouse.Id != 0 {
|
if e.ToWarehouse != nil && e.ToWarehouse.Id != 0 {
|
||||||
destinationWarehouse = toWarehouseDetailDTO(e.ToWarehouse)
|
mapped := warehouseDTO.ToWarehouseRelationDTO(*e.ToWarehouse)
|
||||||
|
destinationWarehouse = &mapped
|
||||||
}
|
}
|
||||||
return TransferRelationDTO{
|
return TransferRelationDTO{
|
||||||
Id: e.Id,
|
Id: e.Id,
|
||||||
|
MovementNumber: e.MovementNumber,
|
||||||
TransferReason: e.Reason,
|
TransferReason: e.Reason,
|
||||||
TransferDate: e.CreatedAt.Format("2006-01-02"),
|
TransferDate: e.CreatedAt.Format("2006-01-02"),
|
||||||
SourceWarehouse: sourceWarehouse,
|
SourceWarehouse: sourceWarehouse,
|
||||||
@@ -114,38 +97,6 @@ func ToTransferRelationDTO(e entity.StockTransfer) TransferRelationDTO {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func toAreaDTO(a *entity.Area) *AreaDTO {
|
|
||||||
if a == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return &AreaDTO{
|
|
||||||
Id: a.Id,
|
|
||||||
Name: a.Name,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func toLocationDTO(l *entity.Location) *LocationDTO {
|
|
||||||
if l == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return &LocationDTO{
|
|
||||||
Id: l.Id,
|
|
||||||
Name: l.Name,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func toWarehouseDetailDTO(w *entity.Warehouse) *WarehouseDetailDTO {
|
|
||||||
if w == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return &WarehouseDetailDTO{
|
|
||||||
Id: w.Id,
|
|
||||||
Name: w.Name,
|
|
||||||
Location: toLocationDTO(w.Location),
|
|
||||||
Area: toAreaDTO(&w.Area),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ToTransferListDTO(e entity.StockTransfer) TransferListDTO {
|
func ToTransferListDTO(e entity.StockTransfer) TransferListDTO {
|
||||||
var createdUser *userDTO.UserRelationDTO
|
var createdUser *userDTO.UserRelationDTO
|
||||||
if e.CreatedUser != nil {
|
if e.CreatedUser != nil {
|
||||||
|
|||||||
@@ -99,7 +99,11 @@ func (s transferService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entit
|
|||||||
transfers, total, err := s.StockTransferRepo.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB {
|
transfers, total, err := s.StockTransferRepo.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB {
|
||||||
db = s.withRelations(db)
|
db = s.withRelations(db)
|
||||||
if params.Search != "" {
|
if params.Search != "" {
|
||||||
db = db.Where("movement_number ILIKE ?", "%"+strings.TrimSpace(params.Search)+"%")
|
searchTerm := "%" + strings.TrimSpace(params.Search) + "%"
|
||||||
|
db = db.Joins("LEFT JOIN warehouses AS from_warehouses ON from_warehouses.id = stock_transfers.from_warehouse_id").
|
||||||
|
Joins("LEFT JOIN warehouses AS to_warehouses ON to_warehouses.id = stock_transfers.to_warehouse_id").
|
||||||
|
Where("movement_number ILIKE ? OR from_warehouses.name ILIKE ? OR to_warehouses.name ILIKE ?",
|
||||||
|
searchTerm, searchTerm, searchTerm)
|
||||||
}
|
}
|
||||||
return db.Order("created_at DESC").Order("updated_at DESC")
|
return db.Order("created_at DESC").Order("updated_at DESC")
|
||||||
})
|
})
|
||||||
@@ -118,9 +122,9 @@ func (s transferService) GetOne(c *fiber.Ctx, id uint) (*entity.StockTransfer, e
|
|||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return nil, fiber.NewError(fiber.StatusNotFound, "Transfer not found")
|
return nil, fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Transfer dengan ID %d tidak ditemukan", id))
|
||||||
}
|
}
|
||||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to get transfer")
|
return nil, fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal mengambil data transfer dengan ID %d", id))
|
||||||
}
|
}
|
||||||
|
|
||||||
return transferPtr, nil
|
return transferPtr, nil
|
||||||
@@ -136,12 +140,12 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
|
|||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Stok produk %d tidak tersedia di gudang asal", product.ProductID))
|
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Produk dengan ID %d tidak ditemukan di gudang asal (ID: %d)", product.ProductID, req.SourceWarehouseID))
|
||||||
}
|
}
|
||||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Gagal cek stok produk di gudang asal")
|
return nil, fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal mengecek stok produk %d di gudang asal", product.ProductID))
|
||||||
}
|
}
|
||||||
if sourcePW.Quantity < product.ProductQty {
|
if sourcePW.Quantity < product.ProductQty {
|
||||||
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Stok produk %d di gudang asal tidak cukup", product.ProductID))
|
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Stok produk %d di gudang asal tidak mencukupi. Tersedia: %.2f, Diminta: %.2f", product.ProductID, sourcePW.Quantity, product.ProductQty))
|
||||||
}
|
}
|
||||||
pwIDs = append(pwIDs, sourcePW.Id)
|
pwIDs = append(pwIDs, sourcePW.Id)
|
||||||
}
|
}
|
||||||
@@ -161,10 +165,10 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
|
|||||||
|
|
||||||
projectFlockKandang, err := s.ProjectFlockKandangRepo.GetByID(c.Context(), destPfkID)
|
projectFlockKandang, err := s.ProjectFlockKandangRepo.GetByID(c.Context(), destPfkID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Gagal mengambil data project flock")
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Gagal mengambil data project flock untuk gudang tujuan")
|
||||||
}
|
}
|
||||||
if projectFlockKandang.ClosedAt != nil {
|
if projectFlockKandang.ClosedAt != nil {
|
||||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Project flock tujuan sudah closing")
|
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Project flock untuk gudang tujuan sudah ditutup (closing) pada %s", projectFlockKandang.ClosedAt.Format("2006-01-02")))
|
||||||
}
|
}
|
||||||
|
|
||||||
actorID, err := m.ActorIDFromContext(c)
|
actorID, err := m.ActorIDFromContext(c)
|
||||||
@@ -192,16 +196,16 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
|
|||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Supplier dengan ID %d tidak ditemukan", delivery.SupplierID))
|
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Supplier dengan ID %d tidak ditemukan", delivery.SupplierID))
|
||||||
}
|
}
|
||||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Gagal cek data supplier")
|
return nil, fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal mengambil data supplier dengan ID %d", delivery.SupplierID))
|
||||||
}
|
}
|
||||||
if supplier.Category != string(utils.SupplierCategoryBOP) {
|
if supplier.Category != string(utils.SupplierCategoryBOP) {
|
||||||
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Supplier dengan ID %d bukan kategori BOP", delivery.SupplierID))
|
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Supplier '%s' (ID: %d) bukan kategori BOP. Kategori saat ini: %s", supplier.Name, delivery.SupplierID, supplier.Category))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
movementNumber, err := s.StockTransferRepo.GenerateMovementNumber(c.Context())
|
movementNumber, err := s.StockTransferRepo.GenerateMovementNumber(c.Context())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to generate movement number")
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Gagal membuat nomor movement transfer")
|
||||||
}
|
}
|
||||||
|
|
||||||
transferDate, _ := utils.ParseDateString(req.TransferDate)
|
transferDate, _ := utils.ParseDateString(req.TransferDate)
|
||||||
@@ -239,16 +243,16 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
|
|||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Stok produk %d tidak tersedia di gudang asal", product.ProductID))
|
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Produk %d tidak ditemukan di gudang asal (ID: %d)", product.ProductID, req.SourceWarehouseID))
|
||||||
}
|
}
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Gagal mengambil data product warehouse source")
|
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal mengambil data product warehouse untuk produk %d di gudang asal", product.ProductID))
|
||||||
}
|
}
|
||||||
|
|
||||||
destPW, err := productWarehouseRepoTX.GetProductWarehouseByProductAndWarehouseID(
|
destPW, err := productWarehouseRepoTX.GetProductWarehouseByProductAndWarehouseID(
|
||||||
c.Context(), uint(product.ProductID), uint(req.DestinationWarehouseID),
|
c.Context(), uint(product.ProductID), uint(req.DestinationWarehouseID),
|
||||||
)
|
)
|
||||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Gagal mengambil data product warehouse destination")
|
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal mengambil data product warehouse untuk produk %d di gudang tujuan", product.ProductID))
|
||||||
}
|
}
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
ctx := c.Context()
|
ctx := c.Context()
|
||||||
@@ -256,14 +260,21 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set ProjectFlockKandangId hanya jika ada kandang
|
||||||
|
var pfkID *uint
|
||||||
|
if projectFlockKandangID > 0 {
|
||||||
|
pfkID = &projectFlockKandangID
|
||||||
|
}
|
||||||
|
|
||||||
destPW = &entity.ProductWarehouse{
|
destPW = &entity.ProductWarehouse{
|
||||||
ProductId: uint(product.ProductID),
|
ProductId: uint(product.ProductID),
|
||||||
WarehouseId: uint(req.DestinationWarehouseID),
|
WarehouseId: uint(req.DestinationWarehouseID),
|
||||||
Quantity: 0,
|
Quantity: 0,
|
||||||
ProjectFlockKandangId: &projectFlockKandangID,
|
ProjectFlockKandangId: pfkID,
|
||||||
}
|
}
|
||||||
if err := productWarehouseRepoTX.CreateOne(c.Context(), destPW, nil); err != nil {
|
if err := productWarehouseRepoTX.CreateOne(c.Context(), destPW, nil); err != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Gagal membuat product warehouse destination")
|
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal membuat product warehouse untuk produk %d di gudang tujuan", product.ProductID))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,7 +320,7 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
|
|||||||
for _, prod := range item.Products {
|
for _, prod := range item.Products {
|
||||||
detail, ok := detailMap[uint64(prod.ProductID)]
|
detail, ok := detailMap[uint64(prod.ProductID)]
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("produk %d tidak ditemukan di detail", prod.ProductID)
|
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Produk %d tidak ditemukan dalam daftar transfer untuk delivery #%d", prod.ProductID, i+1))
|
||||||
}
|
}
|
||||||
deliveryItems = append(deliveryItems, &entity.StockTransferDeliveryItem{
|
deliveryItems = append(deliveryItems, &entity.StockTransferDeliveryItem{
|
||||||
StockTransferDeliveryId: delivery.Id,
|
StockTransferDeliveryId: delivery.Id,
|
||||||
@@ -372,7 +383,7 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
|
|||||||
Tx: tx,
|
Tx: tx,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Stok tidak cukup di gudang asal untuk produk %d: %v", product.ProductID, err))
|
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Stok tidak mencukupi untuk produk %d di gudang asal. Error: %v", product.ProductID, err))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tx.Model(&entity.StockTransferDetail{}).
|
if err := tx.Model(&entity.StockTransferDetail{}).
|
||||||
@@ -381,7 +392,7 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
|
|||||||
"usage_qty": consumeResult.UsageQuantity,
|
"usage_qty": consumeResult.UsageQuantity,
|
||||||
"pending_qty": consumeResult.PendingQuantity,
|
"pending_qty": consumeResult.PendingQuantity,
|
||||||
}).Error; err != nil {
|
}).Error; err != nil {
|
||||||
return fmt.Errorf("gagal update usage tracking: %w", err)
|
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal mengupdate tracking usage untuk produk %d", product.ProductID))
|
||||||
}
|
}
|
||||||
|
|
||||||
note := fmt.Sprintf("Transfer #%s", entityTransfer.MovementNumber)
|
note := fmt.Sprintf("Transfer #%s", entityTransfer.MovementNumber)
|
||||||
@@ -394,7 +405,7 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
|
|||||||
Tx: tx,
|
Tx: tx,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal menambah stok di gudang tujuan untuk produk %d: %v", product.ProductID, err))
|
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal menambah stok untuk produk %d di gudang tujuan. Error: %v", product.ProductID, err))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tx.Model(&entity.StockTransferDetail{}).
|
if err := tx.Model(&entity.StockTransferDetail{}).
|
||||||
@@ -402,7 +413,7 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
|
|||||||
Updates(map[string]interface{}{
|
Updates(map[string]interface{}{
|
||||||
"total_qty": replenishResult.AddedQuantity,
|
"total_qty": replenishResult.AddedQuantity,
|
||||||
}).Error; err != nil {
|
}).Error; err != nil {
|
||||||
return fmt.Errorf("gagal update total tracking: %w", err)
|
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal mengupdate tracking total untuk produk %d", product.ProductID))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -436,7 +447,7 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
|
|||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to process transfer transaction: %v", err))
|
return nil, fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal memproses transfer. Error: %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := s.GetOne(c, uint(entityTransfer.Id))
|
result, err := s.GetOne(c, uint(entityTransfer.Id))
|
||||||
@@ -446,8 +457,7 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
|
|||||||
|
|
||||||
if len(expensePayloads) > 0 {
|
if len(expensePayloads) > 0 {
|
||||||
if err := s.notifyExpenseItemsDelivered(c, entityTransfer.Id, expensePayloads); err != nil {
|
if err := s.notifyExpenseItemsDelivered(c, entityTransfer.Id, expensePayloads); err != nil {
|
||||||
s.Log.Errorf("Failed to sync expense for transfer %d: %+v", entityTransfer.Id, err)
|
return nil, fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal sinkronisasi data expense untuk transfer %s. Silakan cek manual di module expense", entityTransfer.MovementNumber))
|
||||||
return nil, fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to sync expense: %v", err))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -461,32 +471,26 @@ func (s *transferService) notifyExpenseItemsDelivered(c *fiber.Ctx, transferID u
|
|||||||
return s.ExpenseBridge.OnItemsDelivered(c, transferID, payloads)
|
return s.ExpenseBridge.OnItemsDelivered(c, transferID, payloads)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *transferService) notifyExpenseDetailsDeleted(ctx context.Context, transferID uint64, items []entity.StockTransferDetail) error {
|
|
||||||
if s.ExpenseBridge == nil || transferID == 0 || len(items) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return s.ExpenseBridge.OnItemsDeleted(ctx, transferID, items)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *transferService) getActiveProjectFlockKandangID(ctx context.Context, warehouseID uint) (uint, error) {
|
func (s *transferService) getActiveProjectFlockKandangID(ctx context.Context, warehouseID uint) (uint, error) {
|
||||||
warehouse, err := s.WarehouseRepo.GetByID(ctx, warehouseID, nil)
|
warehouse, err := s.WarehouseRepo.GetByID(ctx, warehouseID, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return 0, fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Gudang dengan ID %d tidak ditemukan", warehouseID))
|
return 0, fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Gudang dengan ID %d tidak ditemukan", warehouseID))
|
||||||
}
|
}
|
||||||
return 0, fiber.NewError(fiber.StatusInternalServerError, "Gagal mengambil data gudang")
|
return 0, fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal mengambil data gudang dengan ID %d", warehouseID))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Jika warehouse tidak punya kandang_id, return 0 tanpa error
|
||||||
if warehouse.KandangId == nil || *warehouse.KandangId == 0 {
|
if warehouse.KandangId == nil || *warehouse.KandangId == 0 {
|
||||||
return 0, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Gudang %d belum terhubung ke kandang", warehouseID))
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
projectFlockKandang, err := s.ProjectFlockKandangRepo.GetActiveByKandangID(ctx, uint(*warehouse.KandangId))
|
projectFlockKandang, err := s.ProjectFlockKandangRepo.GetActiveByKandangID(ctx, uint(*warehouse.KandangId))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return 0, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Kandang %d belum memiliki project flock aktif", *warehouse.KandangId))
|
return 0, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Tidak ada project flock aktif untuk kandang %d", *warehouse.KandangId))
|
||||||
}
|
}
|
||||||
return 0, fiber.NewError(fiber.StatusInternalServerError, "Gagal mengambil project flock kandang")
|
return 0, fiber.NewError(fiber.StatusInternalServerError, "Gagal mengambil data project flock kandang yang aktif")
|
||||||
}
|
}
|
||||||
|
|
||||||
return uint(projectFlockKandang.Id), nil
|
return uint(projectFlockKandang.Id), nil
|
||||||
|
|||||||
@@ -140,13 +140,22 @@ func (b *transferExpenseBridge) markExpensesUpdated(ctx context.Context, expense
|
|||||||
if actorID == 0 {
|
if actorID == 0 {
|
||||||
actorID = 1
|
actorID = 1
|
||||||
}
|
}
|
||||||
svc := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(b.db))
|
approvalRepo := commonRepo.NewApprovalRepository(b.db)
|
||||||
action := entity.ApprovalActionUpdated
|
svc := commonSvc.NewApprovalService(approvalRepo)
|
||||||
|
action := entity.ApprovalActionCreated
|
||||||
|
|
||||||
for id := range expenseIDs {
|
for id := range expenseIDs {
|
||||||
|
latestApproval, err := approvalRepo.LatestByTarget(ctx, string(utils.ApprovalWorkflowExpense), uint(id), nil)
|
||||||
|
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if latestApproval == nil {
|
||||||
if _, err := svc.CreateApproval(ctx, utils.ApprovalWorkflowExpense, uint(id), utils.ExpenseStepFinance, &action, actorID, nil); err != nil {
|
if _, err := svc.CreateApproval(ctx, utils.ApprovalWorkflowExpense, uint(id), utils.ExpenseStepFinance, &action, actorID, nil); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ func (r *MarketingDeliveryProductRepositoryImpl) GetAllWithFilters(ctx context.C
|
|||||||
Joins("JOIN marketings ON marketings.id = marketing_products.marketing_id").
|
Joins("JOIN marketings ON marketings.id = marketing_products.marketing_id").
|
||||||
Where("marketing_delivery_products.delivery_date IS NOT NULL")
|
Where("marketing_delivery_products.delivery_date IS NOT NULL")
|
||||||
|
|
||||||
if filters.ProductId > 0 || filters.WarehouseId > 0 || filters.Search != "" || filters.MarketingType != "" {
|
if filters.ProductId > 0 || filters.WarehouseId > 0 || filters.AreaId > 0 || filters.LocationId > 0 || filters.Search != "" || filters.MarketingType != "" {
|
||||||
db = db.Joins("LEFT JOIN product_warehouses ON product_warehouses.id = marketing_products.product_warehouse_id")
|
db = db.Joins("LEFT JOIN product_warehouses ON product_warehouses.id = marketing_products.product_warehouse_id")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,18 +148,30 @@ func (r *MarketingDeliveryProductRepositoryImpl) GetAllWithFilters(ctx context.C
|
|||||||
db = db.Joins("LEFT JOIN products ON products.id = product_warehouses.product_id")
|
db = db.Joins("LEFT JOIN products ON products.id = product_warehouses.product_id")
|
||||||
}
|
}
|
||||||
|
|
||||||
if filters.WarehouseId > 0 {
|
if filters.WarehouseId > 0 || filters.Search != "" {
|
||||||
db = db.Joins("LEFT JOIN warehouses ON warehouses.id = product_warehouses.warehouse_id")
|
db = db.Joins("LEFT JOIN warehouses ON warehouses.id = product_warehouses.warehouse_id")
|
||||||
}
|
}
|
||||||
|
|
||||||
if filters.Search != "" {
|
if filters.Search != "" {
|
||||||
db = db.Joins("LEFT JOIN customers ON customers.id = marketings.customer_id")
|
db = db.Joins("LEFT JOIN customers ON customers.id = marketings.customer_id")
|
||||||
}
|
db = db.Joins("LEFT JOIN users AS sales_users ON sales_users.id = marketings.sales_person_id")
|
||||||
|
|
||||||
if filters.Search != "" {
|
|
||||||
searchPattern := "%" + filters.Search + "%"
|
searchPattern := "%" + filters.Search + "%"
|
||||||
db = db.Where("marketing_delivery_products.vehicle_number ILIKE ? OR marketings.so_number ILIKE ? OR customers.name ILIKE ? OR products.name ILIKE ?",
|
db = db.Where(`(
|
||||||
searchPattern, searchPattern, searchPattern, searchPattern)
|
marketing_delivery_products.vehicle_number ILIKE ? OR
|
||||||
|
customers.name ILIKE ? OR
|
||||||
|
warehouses.name ILIKE ? OR
|
||||||
|
products.name ILIKE ? OR
|
||||||
|
sales_users.name ILIKE ? OR
|
||||||
|
CONCAT(
|
||||||
|
marketings.so_number,
|
||||||
|
'-',
|
||||||
|
COALESCE(TO_CHAR(marketing_delivery_products.delivery_date, 'YYYYMMDD'), ''),
|
||||||
|
'-',
|
||||||
|
COALESCE(product_warehouses.warehouse_id::text, '')
|
||||||
|
) ILIKE ?
|
||||||
|
)`,
|
||||||
|
searchPattern, searchPattern, searchPattern, searchPattern, searchPattern, searchPattern)
|
||||||
}
|
}
|
||||||
|
|
||||||
if filters.CustomerId > 0 {
|
if filters.CustomerId > 0 {
|
||||||
@@ -178,6 +190,19 @@ func (r *MarketingDeliveryProductRepositoryImpl) GetAllWithFilters(ctx context.C
|
|||||||
db = db.Where("product_warehouses.warehouse_id = ?", filters.WarehouseId)
|
db = db.Where("product_warehouses.warehouse_id = ?", filters.WarehouseId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if filters.AreaId > 0 || filters.LocationId > 0 {
|
||||||
|
db = db.Joins("LEFT JOIN project_flock_kandangs ON project_flock_kandangs.id = product_warehouses.project_flock_kandang_id").
|
||||||
|
Joins("LEFT JOIN project_flocks ON project_flocks.id = project_flock_kandangs.project_flock_id")
|
||||||
|
|
||||||
|
if filters.AreaId > 0 {
|
||||||
|
db = db.Where("project_flocks.area_id = ?", filters.AreaId)
|
||||||
|
}
|
||||||
|
|
||||||
|
if filters.LocationId > 0 {
|
||||||
|
db = db.Where("project_flocks.location_id = ?", filters.LocationId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if filters.MarketingType != "" {
|
if filters.MarketingType != "" {
|
||||||
db = db.Joins("LEFT JOIN flags ON flags.flagable_id = products.id AND flags.flagable_type = 'products'").
|
db = db.Joins("LEFT JOIN flags ON flags.flagable_id = products.id AND flags.flagable_type = 'products'").
|
||||||
Group("marketing_delivery_products.id")
|
Group("marketing_delivery_products.id")
|
||||||
@@ -186,7 +211,6 @@ func (r *MarketingDeliveryProductRepositoryImpl) GetAllWithFilters(ctx context.C
|
|||||||
case "ayam":
|
case "ayam":
|
||||||
db = db.Where("flags.name IN (?)", []string{
|
db = db.Where("flags.name IN (?)", []string{
|
||||||
string(utils.FlagDOC), string(utils.FlagPullet), string(utils.FlagLayer),
|
string(utils.FlagDOC), string(utils.FlagPullet), string(utils.FlagLayer),
|
||||||
string(utils.FlagAyamAfkir), string(utils.FlagAyamCulling), string(utils.FlagAyamMati),
|
|
||||||
})
|
})
|
||||||
case "telur":
|
case "telur":
|
||||||
db = db.Where("flags.name IN (?)", []string{
|
db = db.Where("flags.name IN (?)", []string{
|
||||||
|
|||||||
@@ -200,9 +200,9 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) ([]enti
|
|||||||
|
|
||||||
newChikins = append(newChikins, newChickin)
|
newChikins = append(newChikins, newChickin)
|
||||||
|
|
||||||
totalPopulationQty, err := s.ProjectflockPopulationRepo.GetTotalQtyByProjectFlockKandangID(c.Context(), req.ProjectFlockKandangId)
|
totalPopulationQty, err := s.ProjectflockPopulationRepo.GetTotalQtyByProductWarehouseID(c.Context(), chickinReq.ProductWarehouseId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to get total population quantity for project_flock_kandang %d", req.ProjectFlockKandangId))
|
return nil, fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to get total population quantity for product warehouse %d", chickinReq.ProductWarehouseId))
|
||||||
}
|
}
|
||||||
|
|
||||||
availableQty := productWarehouse.Quantity - totalPopulationQty
|
availableQty := productWarehouse.Quantity - totalPopulationQty
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package dto
|
package dto
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
@@ -53,6 +54,7 @@ type ProjectFlockKandangListDTO struct {
|
|||||||
ProjectFlockKandangRelationDTO
|
ProjectFlockKandangRelationDTO
|
||||||
ProjectFlock *ProjectFlockDTO `json:"project_flock,omitempty"`
|
ProjectFlock *ProjectFlockDTO `json:"project_flock,omitempty"`
|
||||||
Kandang *kandangDTO.KandangRelationDTO `json:"kandang,omitempty"`
|
Kandang *kandangDTO.KandangRelationDTO `json:"kandang,omitempty"`
|
||||||
|
NameWithPeriod string `json:"name_with_period"`
|
||||||
CreatedUser *userDTO.UserRelationDTO `json:"created_user,omitempty"`
|
CreatedUser *userDTO.UserRelationDTO `json:"created_user,omitempty"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
Approval *approvalDTO.ApprovalRelationDTO `json:"approval,omitempty"`
|
Approval *approvalDTO.ApprovalRelationDTO `json:"approval,omitempty"`
|
||||||
@@ -104,6 +106,7 @@ func ToProjectFlockKandangDetailDTOWithAvailableQty(e entity.ProjectFlockKandang
|
|||||||
ProjectFlockKandangRelationDTO: ToProjectFlockKandangRelationDTO(e),
|
ProjectFlockKandangRelationDTO: ToProjectFlockKandangRelationDTO(e),
|
||||||
ProjectFlock: toProjectFlockDTO(projectFlockSummary),
|
ProjectFlock: toProjectFlockDTO(projectFlockSummary),
|
||||||
Kandang: toKandangRelation(e.Kandang),
|
Kandang: toKandangRelation(e.Kandang),
|
||||||
|
NameWithPeriod: toNameWithPeriod(e.Kandang, e.Period),
|
||||||
CreatedAt: e.CreatedAt,
|
CreatedAt: e.CreatedAt,
|
||||||
CreatedUser: toCreatedUserDTO(e.ProjectFlock),
|
CreatedUser: toCreatedUserDTO(e.ProjectFlock),
|
||||||
Approval: toApprovalDTOSelector(e, func(x entity.ProjectFlockKandang) *entity.Approval { return x.LatestProjectFlockApproval }),
|
Approval: toApprovalDTOSelector(e, func(x entity.ProjectFlockKandang) *entity.Approval { return x.LatestProjectFlockApproval }),
|
||||||
@@ -126,6 +129,16 @@ func toKandangRelation(kandang entity.Kandang) *kandangDTO.KandangRelationDTO {
|
|||||||
return &mapped
|
return &mapped
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func toNameWithPeriod(kandang entity.Kandang, period int) string {
|
||||||
|
if kandang.Name == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if period == 0 {
|
||||||
|
return kandang.Name
|
||||||
|
}
|
||||||
|
return kandang.Name + " Period " + strconv.Itoa(period)
|
||||||
|
}
|
||||||
|
|
||||||
func toApprovalDTOSelector(
|
func toApprovalDTOSelector(
|
||||||
e entity.ProjectFlockKandang, selector func(entity.ProjectFlockKandang) *entity.Approval) *approvalDTO.ApprovalRelationDTO {
|
e entity.ProjectFlockKandang, selector func(entity.ProjectFlockKandang) *entity.Approval) *approvalDTO.ApprovalRelationDTO {
|
||||||
approval := selector(e)
|
approval := selector(e)
|
||||||
@@ -147,6 +160,7 @@ func ToProjectFlockKandangListDTO(e entity.ProjectFlockKandang) ProjectFlockKand
|
|||||||
ProjectFlockKandangRelationDTO: ToProjectFlockKandangRelationDTO(e),
|
ProjectFlockKandangRelationDTO: ToProjectFlockKandangRelationDTO(e),
|
||||||
ProjectFlock: toProjectFlockDTO(projectFlockSummary),
|
ProjectFlock: toProjectFlockDTO(projectFlockSummary),
|
||||||
Kandang: toKandangRelation(e.Kandang),
|
Kandang: toKandangRelation(e.Kandang),
|
||||||
|
NameWithPeriod: toNameWithPeriod(e.Kandang, e.Period),
|
||||||
CreatedAt: e.CreatedAt,
|
CreatedAt: e.CreatedAt,
|
||||||
CreatedUser: toCreatedUserDTO(e.ProjectFlock),
|
CreatedUser: toCreatedUserDTO(e.ProjectFlock),
|
||||||
Approval: toApprovalDTOSelector(e, func(x entity.ProjectFlockKandang) *entity.Approval { return x.LatestProjectFlockApproval }),
|
Approval: toApprovalDTOSelector(e, func(x entity.ProjectFlockKandang) *entity.Approval { return x.LatestProjectFlockApproval }),
|
||||||
|
|||||||
+22
-1
@@ -2,6 +2,7 @@ package repository
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"math"
|
||||||
|
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
@@ -16,6 +17,7 @@ type ProjectFlockPopulationRepository interface {
|
|||||||
GetTotalQtyByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) (float64, error)
|
GetTotalQtyByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) (float64, error)
|
||||||
GetTotalQtyByProductWarehouseID(ctx context.Context, productWarehouseID uint) (float64, error)
|
GetTotalQtyByProductWarehouseID(ctx context.Context, productWarehouseID uint) (float64, error)
|
||||||
GetAvailableQtyByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) (float64, error)
|
GetAvailableQtyByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) (float64, error)
|
||||||
|
GetTotalChickInByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) (int64, error)
|
||||||
|
|
||||||
CreateOne(ctx context.Context, entity *entity.ProjectFlockPopulation, modifier func(*gorm.DB) *gorm.DB) error
|
CreateOne(ctx context.Context, entity *entity.ProjectFlockPopulation, modifier func(*gorm.DB) *gorm.DB) error
|
||||||
PatchOne(ctx context.Context, id uint, updates map[string]any, modifier func(*gorm.DB) *gorm.DB) error
|
PatchOne(ctx context.Context, id uint, updates map[string]any, modifier func(*gorm.DB) *gorm.DB) error
|
||||||
@@ -111,7 +113,7 @@ func (r *projectFlockPopulationRepositoryImpl) GetTotalQtyByProductWarehouseID(c
|
|||||||
err := r.DB().WithContext(ctx).
|
err := r.DB().WithContext(ctx).
|
||||||
Model(&entity.ProjectFlockPopulation{}).
|
Model(&entity.ProjectFlockPopulation{}).
|
||||||
Where("product_warehouse_id = ?", productWarehouseID).
|
Where("product_warehouse_id = ?", productWarehouseID).
|
||||||
Select("COALESCE(SUM(total_qty), 0)").
|
Select("COALESCE(SUM(total_qty - total_used_qty), 0)").
|
||||||
Scan(&total).Error
|
Scan(&total).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
@@ -135,3 +137,22 @@ func (r *projectFlockPopulationRepositoryImpl) GetAvailableQtyByProjectFlockKand
|
|||||||
}
|
}
|
||||||
return total, nil
|
return total, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *projectFlockPopulationRepositoryImpl) GetTotalChickInByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) (int64, error) {
|
||||||
|
var total float64
|
||||||
|
err := r.DB().WithContext(ctx).
|
||||||
|
Table("project_flock_populations").
|
||||||
|
Select("COALESCE(SUM(project_flock_populations.total_qty - project_flock_populations.total_used_qty), 0) AS total_qty").
|
||||||
|
Joins("JOIN project_chickins ON project_chickins.id = project_flock_populations.project_chickin_id").
|
||||||
|
Where("project_chickins.project_flock_kandang_id = ?", projectFlockKandangID).
|
||||||
|
Scan(&total).Error
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if total < 0 {
|
||||||
|
total = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return int64(math.Round(total)), nil
|
||||||
|
}
|
||||||
|
|||||||
+4
-1
@@ -27,6 +27,10 @@ func (u *TransferLayingController) GetAll(c *fiber.Ctx) error {
|
|||||||
query := &validation.Query{
|
query := &validation.Query{
|
||||||
Page: c.QueryInt("page", 1),
|
Page: c.QueryInt("page", 1),
|
||||||
Limit: c.QueryInt("limit", 10),
|
Limit: c.QueryInt("limit", 10),
|
||||||
|
Search: c.Query("search", ""),
|
||||||
|
TransferDate: c.Query("transfer_date", ""),
|
||||||
|
FlockSource: uint(c.QueryInt("flock_source", 0)),
|
||||||
|
FlockDestination: uint(c.QueryInt("flock_destination", 0)),
|
||||||
}
|
}
|
||||||
|
|
||||||
if query.Page < 1 || query.Limit < 1 {
|
if query.Page < 1 || query.Limit < 1 {
|
||||||
@@ -179,7 +183,6 @@ func (u *TransferLayingController) Approval(c *fiber.Ctx) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func (u *TransferLayingController) GetAvailableQtyPerKandang(c *fiber.Ctx) error {
|
func (u *TransferLayingController) GetAvailableQtyPerKandang(c *fiber.Ctx) error {
|
||||||
projectFlockID, err := strconv.ParseUint(c.Params("project_flock_id"), 10, 32)
|
projectFlockID, err := strconv.ParseUint(c.Params("project_flock_id"), 10, 32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -162,9 +162,19 @@ func ToProductWarehouseSummaryDTO(pw *entity.ProductWarehouse) *ProductWarehouse
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ToLayingTransferSourceDTO(source entity.LayingTransferSource) LayingTransferSourceDTO {
|
func ToLayingTransferSourceDTO(source entity.LayingTransferSource) LayingTransferSourceDTO {
|
||||||
|
// Tampilkan requested qty sebelum approve, consumed qty setelah approve
|
||||||
|
var displayQty float64
|
||||||
|
if source.UsageQty > 0 {
|
||||||
|
// Sudah di-approve dan di-consume, tampilkan actual consumed quantity
|
||||||
|
displayQty = source.UsageQty
|
||||||
|
} else {
|
||||||
|
// Belum di-approve, tampilkan requested quantity
|
||||||
|
displayQty = source.RequestedQty
|
||||||
|
}
|
||||||
|
|
||||||
return LayingTransferSourceDTO{
|
return LayingTransferSourceDTO{
|
||||||
SourceProjectFlockKandang: ToProjectFlockKandangSummaryDTO(source.SourceProjectFlockKandang),
|
SourceProjectFlockKandang: ToProjectFlockKandangSummaryDTO(source.SourceProjectFlockKandang),
|
||||||
Qty: source.UsageQty, // Ambil dari UsageQty (FIFO consumed quantity)
|
Qty: displayQty,
|
||||||
ProductWarehouse: ToProductWarehouseSummaryDTO(source.ProductWarehouse),
|
ProductWarehouse: ToProductWarehouseSummaryDTO(source.ProductWarehouse),
|
||||||
Note: source.Note,
|
Note: source.Note,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,8 +110,31 @@ func (s transferLayingService) GetAll(c *fiber.Ctx, params *validation.Query) ([
|
|||||||
offset := (params.Page - 1) * params.Limit
|
offset := (params.Page - 1) * params.Limit
|
||||||
|
|
||||||
transferLayings, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB {
|
transferLayings, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB {
|
||||||
db = s.withRelations(db)
|
// Apply search and filters
|
||||||
|
if params.Search != "" {
|
||||||
|
searchPattern := "%" + params.Search + "%"
|
||||||
|
db = db.Joins("LEFT JOIN project_flocks AS pf_from ON laying_transfers.from_project_flock_id = pf_from.id").
|
||||||
|
Joins("LEFT JOIN project_flocks AS pf_to ON laying_transfers.to_project_flock_id = pf_to.id").
|
||||||
|
Where("laying_transfers.transfer_number ILIKE ? OR laying_transfers.notes ILIKE ? OR pf_from.flock_name ILIKE ? OR pf_to.flock_name ILIKE ?",
|
||||||
|
searchPattern, searchPattern, searchPattern, searchPattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.TransferDate != "" {
|
||||||
|
db = db.Where("transfer_date::date = ?::date", params.TransferDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.FlockSource > 0 {
|
||||||
|
db = db.Where("from_project_flock_id = ?", params.FlockSource)
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.FlockDestination > 0 {
|
||||||
|
db = db.Where("to_project_flock_id = ?", params.FlockDestination)
|
||||||
|
}
|
||||||
|
|
||||||
db = db.Order("created_at DESC")
|
db = db.Order("created_at DESC")
|
||||||
|
|
||||||
|
db = s.withRelations(db)
|
||||||
|
|
||||||
return db
|
return db
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -216,7 +239,7 @@ func (s *transferLayingService) CreateOne(c *fiber.Ctx, req *validation.Create)
|
|||||||
|
|
||||||
for _, sourceDetail := range req.SourceKandangs {
|
for _, sourceDetail := range req.SourceKandangs {
|
||||||
if sourceDetail.Quantity <= 0 {
|
if sourceDetail.Quantity <= 0 {
|
||||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Jumlah kandang sumber harus lebih dari 0")
|
continue
|
||||||
}
|
}
|
||||||
totalSourceQty += sourceDetail.Quantity
|
totalSourceQty += sourceDetail.Quantity
|
||||||
|
|
||||||
@@ -247,11 +270,18 @@ func (s *transferLayingService) CreateOne(c *fiber.Ctx, req *validation.Create)
|
|||||||
|
|
||||||
for _, targetDetail := range req.TargetKandangs {
|
for _, targetDetail := range req.TargetKandangs {
|
||||||
if targetDetail.Quantity <= 0 {
|
if targetDetail.Quantity <= 0 {
|
||||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Jumlah kandang tujuan harus lebih dari 0")
|
continue
|
||||||
}
|
}
|
||||||
totalTargetQty += targetDetail.Quantity
|
totalTargetQty += targetDetail.Quantity
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if totalSourceQty == 0 {
|
||||||
|
return nil, fiber.NewError(fiber.StatusBadRequest, "Minimal harus ada 1 kandang sumber dengan jumlah lebih dari 0")
|
||||||
|
}
|
||||||
|
if totalTargetQty == 0 {
|
||||||
|
return nil, fiber.NewError(fiber.StatusBadRequest, "Minimal harus ada 1 kandang tujuan dengan jumlah lebih dari 0")
|
||||||
|
}
|
||||||
|
|
||||||
if totalSourceQty != totalTargetQty {
|
if totalSourceQty != totalTargetQty {
|
||||||
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Jumlah total sumber (%.0f) harus sama dengan jumlah total tujuan (%.0f)", totalSourceQty, totalTargetQty))
|
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Jumlah total sumber (%.0f) harus sama dengan jumlah total tujuan (%.0f)", totalSourceQty, totalTargetQty))
|
||||||
}
|
}
|
||||||
@@ -279,11 +309,16 @@ func (s *transferLayingService) CreateOne(c *fiber.Ctx, req *validation.Create)
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, sourceDetail := range req.SourceKandangs {
|
for _, sourceDetail := range req.SourceKandangs {
|
||||||
|
if sourceDetail.Quantity == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
productWarehouseId := sourceWarehouseMap[sourceDetail.ProjectFlockKandangId]
|
productWarehouseId := sourceWarehouseMap[sourceDetail.ProjectFlockKandangId]
|
||||||
|
|
||||||
source := entity.LayingTransferSource{
|
source := entity.LayingTransferSource{
|
||||||
LayingTransferId: createBody.Id,
|
LayingTransferId: createBody.Id,
|
||||||
SourceProjectFlockKandangId: sourceDetail.ProjectFlockKandangId,
|
SourceProjectFlockKandangId: sourceDetail.ProjectFlockKandangId,
|
||||||
|
RequestedQty: sourceDetail.Quantity, // Quantity yang diminta user
|
||||||
UsageQty: 0,
|
UsageQty: 0,
|
||||||
PendingUsageQty: 0, // Di-set 0, biarkan FIFO Consume yang handle saat Approval
|
PendingUsageQty: 0, // Di-set 0, biarkan FIFO Consume yang handle saat Approval
|
||||||
ProductWarehouseId: &productWarehouseId,
|
ProductWarehouseId: &productWarehouseId,
|
||||||
@@ -295,6 +330,9 @@ func (s *transferLayingService) CreateOne(c *fiber.Ctx, req *validation.Create)
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, targetDetail := range req.TargetKandangs {
|
for _, targetDetail := range req.TargetKandangs {
|
||||||
|
if targetDetail.Quantity == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
targetprojectFlockKandang, err := s.ProjectFlockKandangRepo.GetByID(c.Context(), targetDetail.ProjectFlockKandangId)
|
targetprojectFlockKandang, err := s.ProjectFlockKandangRepo.GetByID(c.Context(), targetDetail.ProjectFlockKandangId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -463,8 +501,9 @@ func (s *transferLayingService) UpdateOne(c *fiber.Ctx, req *validation.Update,
|
|||||||
source := entity.LayingTransferSource{
|
source := entity.LayingTransferSource{
|
||||||
LayingTransferId: id,
|
LayingTransferId: id,
|
||||||
SourceProjectFlockKandangId: sourceDetail.ProjectFlockKandangId,
|
SourceProjectFlockKandangId: sourceDetail.ProjectFlockKandangId,
|
||||||
|
RequestedQty: sourceDetail.Quantity, // Quantity yang diminta user
|
||||||
UsageQty: 0,
|
UsageQty: 0,
|
||||||
PendingUsageQty: sourceDetail.Quantity,
|
PendingUsageQty: 0, // Di-set 0, biarkan FIFO Consume yang handle saat Approval
|
||||||
ProductWarehouseId: &productWarehouseId,
|
ProductWarehouseId: &productWarehouseId,
|
||||||
}
|
}
|
||||||
if err := sourceRepo.CreateOne(c.Context(), &source, nil); err != nil {
|
if err := sourceRepo.CreateOne(c.Context(), &source, nil); err != nil {
|
||||||
@@ -700,7 +739,7 @@ func (s transferLayingService) Approval(c *fiber.Ctx, req *validation.Approve) (
|
|||||||
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Target product warehouse tidak ditemukan untuk transfer %d", approvableID))
|
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Target product warehouse tidak ditemukan untuk transfer %d", approvableID))
|
||||||
}
|
}
|
||||||
|
|
||||||
note := fmt.Sprintf("Transfer to Laying #%s - Target Kandang", transfer.TransferNumber)
|
note := fmt.Sprintf("Transfer to Laying #%s", transfer.TransferNumber)
|
||||||
replenishResult, err := s.FifoSvc.Replenish(c.Context(), commonSvc.StockReplenishRequest{
|
replenishResult, err := s.FifoSvc.Replenish(c.Context(), commonSvc.StockReplenishRequest{
|
||||||
StockableKey: fifo.StockableKeyTransferToLayingIn,
|
StockableKey: fifo.StockableKeyTransferToLayingIn,
|
||||||
StockableID: target.Id,
|
StockableID: target.Id,
|
||||||
@@ -814,15 +853,15 @@ func (s transferLayingService) GetAvailableQtyPerKandang(ctx *fiber.Ctx, project
|
|||||||
|
|
||||||
kandangAvailableQty := make(map[uint]float64)
|
kandangAvailableQty := make(map[uint]float64)
|
||||||
for _, kandang := range kandangs {
|
for _, kandang := range kandangs {
|
||||||
|
// Gunakan fungsi repository yang sama dengan recording service
|
||||||
totalQty, err := s.ProjectFlockPopulationRepo.GetTotalQtyByProjectFlockKandangID(ctx.Context(), kandang.Id)
|
totalAvailable, err := s.ProjectFlockPopulationRepo.GetAvailableQtyByProjectFlockKandangID(ctx.Context(), kandang.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Log.Warnf("Failed to get total qty for kandang %d: %+v", kandang.Id, err)
|
s.Log.Warnf("Failed to get available qty for kandang %d: %+v", kandang.Id, err)
|
||||||
kandangAvailableQty[kandang.Id] = 0
|
kandangAvailableQty[kandang.Id] = 0
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
kandangAvailableQty[kandang.Id] = totalQty
|
kandangAvailableQty[kandang.Id] = totalAvailable
|
||||||
}
|
}
|
||||||
|
|
||||||
return pf, kandangAvailableQty, nil
|
return pf, kandangAvailableQty, nil
|
||||||
|
|||||||
+6
-2
@@ -2,12 +2,12 @@ package validation
|
|||||||
|
|
||||||
type SourceKandangDetail struct {
|
type SourceKandangDetail struct {
|
||||||
ProjectFlockKandangId uint `json:"project_flock_kandang_id" validate:"required"`
|
ProjectFlockKandangId uint `json:"project_flock_kandang_id" validate:"required"`
|
||||||
Quantity float64 `json:"quantity" validate:"required,gt=0"`
|
Quantity float64 `json:"quantity"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TargetKandangDetail struct {
|
type TargetKandangDetail struct {
|
||||||
ProjectFlockKandangId uint `json:"project_flock_kandang_id" validate:"required"`
|
ProjectFlockKandangId uint `json:"project_flock_kandang_id" validate:"required"`
|
||||||
Quantity float64 `json:"quantity" validate:"required,gt=0"`
|
Quantity float64 `json:"quantity"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Create struct {
|
type Create struct {
|
||||||
@@ -31,6 +31,10 @@ type Update struct {
|
|||||||
type Query struct {
|
type Query struct {
|
||||||
Page int `query:"page" validate:"omitempty,number,min=1,gt=0"`
|
Page int `query:"page" validate:"omitempty,number,min=1,gt=0"`
|
||||||
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100,gt=0"`
|
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100,gt=0"`
|
||||||
|
Search string `query:"search" validate:"omitempty"`
|
||||||
|
TransferDate string `query:"transfer_date" validate:"omitempty"`
|
||||||
|
FlockSource uint `query:"flock_source" validate:"omitempty,number"`
|
||||||
|
FlockDestination uint `query:"flock_destination" validate:"omitempty,number"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Approve struct {
|
type Approve struct {
|
||||||
|
|||||||
@@ -167,13 +167,22 @@ func (b *expenseBridge) markExpensesUpdated(ctx context.Context, expenseIDs map[
|
|||||||
if actorID == 0 {
|
if actorID == 0 {
|
||||||
actorID = 1
|
actorID = 1
|
||||||
}
|
}
|
||||||
svc := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(b.db))
|
approvalRepo := commonRepo.NewApprovalRepository(b.db)
|
||||||
action := entity.ApprovalActionUpdated
|
svc := commonSvc.NewApprovalService(approvalRepo)
|
||||||
|
action := entity.ApprovalActionCreated
|
||||||
|
|
||||||
for id := range expenseIDs {
|
for id := range expenseIDs {
|
||||||
|
latestApproval, err := approvalRepo.LatestByTarget(ctx, string(utils.ApprovalWorkflowExpense), uint(id), nil)
|
||||||
|
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if latestApproval == nil {
|
||||||
if _, err := svc.CreateApproval(ctx, utils.ApprovalWorkflowExpense, uint(id), utils.ExpenseStepFinance, &action, actorID, nil); err != nil {
|
if _, err := svc.CreateApproval(ctx, utils.ApprovalWorkflowExpense, uint(id), utils.ExpenseStepFinance, &action, actorID, nil); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -82,6 +82,8 @@ func (c *RepportController) GetMarketing(ctx *fiber.Ctx) error {
|
|||||||
ProductId: int64(ctx.QueryInt("product_id", 0)),
|
ProductId: int64(ctx.QueryInt("product_id", 0)),
|
||||||
WarehouseId: int64(ctx.QueryInt("warehouse_id", 0)),
|
WarehouseId: int64(ctx.QueryInt("warehouse_id", 0)),
|
||||||
SalesPersonId: int64(ctx.QueryInt("sales_person_id", 0)),
|
SalesPersonId: int64(ctx.QueryInt("sales_person_id", 0)),
|
||||||
|
AreaId: int64(ctx.QueryInt("area_id", 0)),
|
||||||
|
LocationId: int64(ctx.QueryInt("location_id", 0)),
|
||||||
MarketingType: ctx.Query("marketing_type", ""),
|
MarketingType: ctx.Query("marketing_type", ""),
|
||||||
FilterBy: ctx.Query("filter_by", ""),
|
FilterBy: ctx.Query("filter_by", ""),
|
||||||
StartDate: ctx.Query("start_date", ""),
|
StartDate: ctx.Query("start_date", ""),
|
||||||
|
|||||||
@@ -27,10 +27,13 @@ type HppPerKandangResponseData struct {
|
|||||||
type HppPerKandangRowDTO struct {
|
type HppPerKandangRowDTO struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
Kandang HppPerKandangRowKandangDTO `json:"kandang"`
|
Kandang HppPerKandangRowKandangDTO `json:"kandang"`
|
||||||
|
NameWithPeriode string `json:"name_with_periode"`
|
||||||
WeightRange HppPerKandangWeightRangeDTO `json:"weight_range"`
|
WeightRange HppPerKandangWeightRangeDTO `json:"weight_range"`
|
||||||
AvgWeightKg float64 `json:"avg_weight_kg"`
|
AvgWeightKg float64 `json:"avg_weight_kg"`
|
||||||
EggProductionPieces int64 `json:"egg_production_pieces"`
|
EggProductionPieces int64 `json:"egg_production_pieces"`
|
||||||
EggProductionKg float64 `json:"egg_production_kg"`
|
EggProductionKg float64 `json:"egg_production_kg"`
|
||||||
|
// EggProductionTotalWeightKg float64 `json:"egg_production_total_weight_kg"`
|
||||||
|
// EggProductionTotalPieces int64 `json:"egg_production_total_pieces"`
|
||||||
// FeedCostRp float64 `json:"feed_cost_rp"`
|
// FeedCostRp float64 `json:"feed_cost_rp"`
|
||||||
// OvkCostRp float64 `json:"ovk_cost_rp"`
|
// OvkCostRp float64 `json:"ovk_cost_rp"`
|
||||||
EggHppRpPerKg float64 `json:"egg_hpp_rp_per_kg"`
|
EggHppRpPerKg float64 `json:"egg_hpp_rp_per_kg"`
|
||||||
@@ -38,8 +41,8 @@ type HppPerKandangRowDTO struct {
|
|||||||
FeedSuppliers []HppPerKandangSupplierDTO `json:"feed_suppliers"`
|
FeedSuppliers []HppPerKandangSupplierDTO `json:"feed_suppliers"`
|
||||||
DocSuppliers []HppPerKandangSupplierDTO `json:"doc_suppliers"`
|
DocSuppliers []HppPerKandangSupplierDTO `json:"doc_suppliers"`
|
||||||
AverageDocPriceRp int64 `json:"average_doc_price_rp"`
|
AverageDocPriceRp int64 `json:"average_doc_price_rp"`
|
||||||
HppRp float64 `json:"hpp_rp"`
|
// HppRp float64 `json:"hpp_rp"`
|
||||||
RemainingValueRp int64 `json:"remaining_value_rp"`
|
// RemainingValueRp int64 `json:"remaining_value_rp"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type HppPerKandangRowKandangDTO struct {
|
type HppPerKandangRowKandangDTO struct {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
|
|
||||||
type HppPerKandangRow struct {
|
type HppPerKandangRow struct {
|
||||||
ProjectFlockKandangID uint
|
ProjectFlockKandangID uint
|
||||||
|
ProjectFlockPeriod int
|
||||||
KandangID uint
|
KandangID uint
|
||||||
KandangName string
|
KandangName string
|
||||||
KandangStatus string
|
KandangStatus string
|
||||||
@@ -20,14 +21,16 @@ type HppPerKandangRow struct {
|
|||||||
PicID uint
|
PicID uint
|
||||||
PicName string
|
PicName string
|
||||||
RecordingCount int64
|
RecordingCount int64
|
||||||
RemainingChickenBirds float64
|
// RemainingChickenBirds float64
|
||||||
RemainingChickenWeight float64
|
// RemainingChickenWeight float64
|
||||||
EggProductionWeightKg float64
|
EggProductionWeightKgRemaining float64
|
||||||
EggProductionPieces float64
|
EggProductionPiecesRemaining float64
|
||||||
|
EggProductionTotalWeightKg float64
|
||||||
|
EggProductionTotalPieces float64
|
||||||
}
|
}
|
||||||
|
|
||||||
type HppPerKandangCostRow struct {
|
type HppPerKandangCostRow struct {
|
||||||
KandangID uint
|
ProjectFlockKandangID uint
|
||||||
FeedCost float64
|
FeedCost float64
|
||||||
OvkCost float64
|
OvkCost float64
|
||||||
DocCost float64
|
DocCost float64
|
||||||
@@ -37,7 +40,7 @@ type HppPerKandangCostRow struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type HppPerKandangSupplierRow struct {
|
type HppPerKandangSupplierRow struct {
|
||||||
KandangID uint
|
ProjectFlockKandangID uint
|
||||||
SupplierID uint
|
SupplierID uint
|
||||||
SupplierName string
|
SupplierName string
|
||||||
SupplierAlias string
|
SupplierAlias string
|
||||||
@@ -86,6 +89,7 @@ func (r *hppPerKandangRepository) GetRowsByPeriod(ctx context.Context, start, en
|
|||||||
Table("project_flocks AS pf").
|
Table("project_flocks AS pf").
|
||||||
Select(`
|
Select(`
|
||||||
pfk.id AS project_flock_kandang_id,
|
pfk.id AS project_flock_kandang_id,
|
||||||
|
pfk.period AS project_flock_period,
|
||||||
k.id AS kandang_id,
|
k.id AS kandang_id,
|
||||||
k.name AS kandang_name,
|
k.name AS kandang_name,
|
||||||
k.status AS kandang_status,
|
k.status AS kandang_status,
|
||||||
@@ -97,18 +101,27 @@ func (r *hppPerKandangRepository) GetRowsByPeriod(ctx context.Context, start, en
|
|||||||
COALESCE(MAX(vr.total_chick_qty), 0) AS remaining_chicken_birds,
|
COALESCE(MAX(vr.total_chick_qty), 0) AS remaining_chicken_birds,
|
||||||
0 AS remaining_chicken_weight,
|
0 AS remaining_chicken_weight,
|
||||||
0 AS egg_production_weight_kg,
|
0 AS egg_production_weight_kg,
|
||||||
0 AS egg_production_pieces`).
|
0 AS egg_production_pieces,
|
||||||
|
0 AS egg_production_total_weight_kg,
|
||||||
|
0 AS egg_production_total_pieces`).
|
||||||
Joins("JOIN project_flock_kandangs AS pfk ON pfk.project_flock_id = pf.id").
|
Joins("JOIN project_flock_kandangs AS pfk ON pfk.project_flock_id = pf.id").
|
||||||
|
Joins(`
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT project_flock_kandang_id, MIN(chick_in_date) AS chick_in_date
|
||||||
|
FROM project_chickins
|
||||||
|
GROUP BY project_flock_kandang_id
|
||||||
|
) AS pc ON pc.project_flock_kandang_id = pfk.id`).
|
||||||
Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id").
|
Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id").
|
||||||
Joins("JOIN locations AS loc ON loc.id = k.location_id").
|
Joins("JOIN locations AS loc ON loc.id = k.location_id").
|
||||||
Joins("JOIN users AS pic ON pic.id = k.pic_id").
|
Joins("JOIN users AS pic ON pic.id = k.pic_id").
|
||||||
Joins("LEFT JOIN (?) AS vr ON vr.project_flock_kandangs_id = pfk.id", validRecordings).
|
Joins("LEFT JOIN (?) AS vr ON vr.project_flock_kandangs_id = pfk.id", validRecordings).
|
||||||
Where("pfk.closed_at IS NULL")
|
Where("pf.category = ?", utils.ProjectFlockCategoryLaying).
|
||||||
|
Where("(pfk.closed_at IS NULL OR ? BETWEEN pc.chick_in_date AND pfk.closed_at)", start)
|
||||||
|
|
||||||
query = applyLocationFilters(query, areaIDs, locationIDs, kandangIDs)
|
query = applyLocationFilters(query, areaIDs, locationIDs, kandangIDs)
|
||||||
|
|
||||||
query = query.Group("pfk.id, k.id, k.name, k.status, loc.id, loc.name, pic.id, pic.name").
|
query = query.Group("pfk.id, pfk.period, k.id, k.name, k.status, loc.id, loc.name, pic.id, pic.name").
|
||||||
Order("k.id ASC")
|
Order("pfk.id ASC")
|
||||||
|
|
||||||
if err := query.Scan(&rows).Error; err != nil {
|
if err := query.Scan(&rows).Error; err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -139,7 +152,7 @@ func (r *hppPerKandangRepository) GetFeedOvkDocCostByPeriod(ctx context.Context,
|
|||||||
query := r.db.WithContext(ctx).
|
query := r.db.WithContext(ctx).
|
||||||
Table("recordings AS r").
|
Table("recordings AS r").
|
||||||
Select(`
|
Select(`
|
||||||
k.id AS kandang_id,
|
pfk.id AS project_flock_kandang_id,
|
||||||
COALESCE(SUM(CASE
|
COALESCE(SUM(CASE
|
||||||
WHEN f.name = ? THEN COALESCE(sa.qty, 0) * COALESCE(pi.price, 0)
|
WHEN f.name = ? THEN COALESCE(sa.qty, 0) * COALESCE(pi.price, 0)
|
||||||
WHEN sa.stockable_type = ? AND tf.name = ? THEN COALESCE(std.total_qty, 0) * COALESCE(tpi.price, 0)
|
WHEN sa.stockable_type = ? AND tf.name = ? THEN COALESCE(std.total_qty, 0) * COALESCE(tpi.price, 0)
|
||||||
@@ -164,18 +177,18 @@ func (r *hppPerKandangRepository) GetFeedOvkDocCostByPeriod(ctx context.Context,
|
|||||||
Joins("LEFT JOIN flags AS f ON f.flagable_id = pi.product_id AND f.flagable_type = ?", entity.FlagableTypeProduct).
|
Joins("LEFT JOIN flags AS f ON f.flagable_id = pi.product_id AND f.flagable_type = ?", entity.FlagableTypeProduct).
|
||||||
Joins("LEFT JOIN flags AS tf ON tf.flagable_id = std.product_id AND tf.flagable_type = ?", entity.FlagableTypeProduct).
|
Joins("LEFT JOIN flags AS tf ON tf.flagable_id = std.product_id AND tf.flagable_type = ?", entity.FlagableTypeProduct).
|
||||||
Where("r.project_flock_kandangs_id IN ?", projectFlockKandangIDs).
|
Where("r.project_flock_kandangs_id IN ?", projectFlockKandangIDs).
|
||||||
// Where("r.record_datetime >= ? AND r.record_datetime < ?", start, end).
|
Where("r.record_datetime < ?", end).
|
||||||
Where("r.deleted_at IS NULL").
|
Where("r.deleted_at IS NULL").
|
||||||
Where("(la.action IS NULL OR la.action != ?)", string(entity.ApprovalActionRejected))
|
Where("(la.action IS NULL OR la.action != ?)", string(entity.ApprovalActionRejected))
|
||||||
|
|
||||||
query = query.Group("k.id").Order("k.id ASC")
|
query = query.Group("pfk.id").Order("pfk.id ASC")
|
||||||
|
|
||||||
if err := query.Scan(&rows).Error; err != nil {
|
if err := query.Scan(&rows).Error; err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
docRows := make([]struct {
|
docRows := make([]struct {
|
||||||
KandangID uint
|
ProjectFlockKandangID uint
|
||||||
DocCost float64
|
DocCost float64
|
||||||
DocQty float64
|
DocQty float64
|
||||||
SupplierID *uint
|
SupplierID *uint
|
||||||
@@ -186,7 +199,7 @@ func (r *hppPerKandangRepository) GetFeedOvkDocCostByPeriod(ctx context.Context,
|
|||||||
docQuery := r.db.WithContext(ctx).
|
docQuery := r.db.WithContext(ctx).
|
||||||
Table("project_chickins AS pc").
|
Table("project_chickins AS pc").
|
||||||
Select(`
|
Select(`
|
||||||
pfk.kandang_id AS kandang_id,
|
pfk.id AS project_flock_kandang_id,
|
||||||
COALESCE(SUM(pc.usage_qty * COALESCE(pi.price, 0)), 0) AS doc_cost,
|
COALESCE(SUM(pc.usage_qty * COALESCE(pi.price, 0)), 0) AS doc_cost,
|
||||||
COALESCE(SUM(pc.usage_qty), 0) AS doc_qty,
|
COALESCE(SUM(pc.usage_qty), 0) AS doc_qty,
|
||||||
s.id AS supplier_id,
|
s.id AS supplier_id,
|
||||||
@@ -199,7 +212,7 @@ func (r *hppPerKandangRepository) GetFeedOvkDocCostByPeriod(ctx context.Context,
|
|||||||
Joins("LEFT JOIN purchases AS pur ON pur.id = pi.purchase_id").
|
Joins("LEFT JOIN purchases AS pur ON pur.id = pi.purchase_id").
|
||||||
Joins("LEFT JOIN suppliers AS s ON s.id = pur.supplier_id").
|
Joins("LEFT JOIN suppliers AS s ON s.id = pur.supplier_id").
|
||||||
Where("pc.project_flock_kandang_id IN ?", projectFlockKandangIDs).
|
Where("pc.project_flock_kandang_id IN ?", projectFlockKandangIDs).
|
||||||
Group("pfk.kandang_id, s.id, s.name, s.alias")
|
Group("pfk.id, s.id, s.name, s.alias")
|
||||||
|
|
||||||
if err := docQuery.Scan(&docRows).Error; err != nil {
|
if err := docQuery.Scan(&docRows).Error; err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
@@ -208,28 +221,28 @@ func (r *hppPerKandangRepository) GetFeedOvkDocCostByPeriod(ctx context.Context,
|
|||||||
costMap := make(map[uint]*HppPerKandangCostRow, len(rows))
|
costMap := make(map[uint]*HppPerKandangCostRow, len(rows))
|
||||||
for i := range rows {
|
for i := range rows {
|
||||||
row := rows[i]
|
row := rows[i]
|
||||||
costMap[row.KandangID] = &rows[i]
|
costMap[row.ProjectFlockKandangID] = &rows[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
docSuppliers := make([]HppPerKandangSupplierRow, 0)
|
docSuppliers := make([]HppPerKandangSupplierRow, 0)
|
||||||
docSeen := make(map[uint]map[uint]bool)
|
docSeen := make(map[uint]map[uint]bool)
|
||||||
for _, doc := range docRows {
|
for _, doc := range docRows {
|
||||||
entry, ok := costMap[doc.KandangID]
|
entry, ok := costMap[doc.ProjectFlockKandangID]
|
||||||
if !ok {
|
if !ok {
|
||||||
rows = append(rows, HppPerKandangCostRow{
|
rows = append(rows, HppPerKandangCostRow{
|
||||||
KandangID: doc.KandangID,
|
ProjectFlockKandangID: doc.ProjectFlockKandangID,
|
||||||
})
|
})
|
||||||
entry = &rows[len(rows)-1]
|
entry = &rows[len(rows)-1]
|
||||||
costMap[doc.KandangID] = entry
|
costMap[doc.ProjectFlockKandangID] = entry
|
||||||
}
|
}
|
||||||
entry.DocCost += doc.DocCost
|
entry.DocCost += doc.DocCost
|
||||||
entry.DocQty += doc.DocQty
|
entry.DocQty += doc.DocQty
|
||||||
if doc.SupplierID != nil {
|
if doc.SupplierID != nil {
|
||||||
if docSeen[doc.KandangID] == nil {
|
if docSeen[doc.ProjectFlockKandangID] == nil {
|
||||||
docSeen[doc.KandangID] = make(map[uint]bool)
|
docSeen[doc.ProjectFlockKandangID] = make(map[uint]bool)
|
||||||
}
|
}
|
||||||
if !docSeen[doc.KandangID][*doc.SupplierID] {
|
if !docSeen[doc.ProjectFlockKandangID][*doc.SupplierID] {
|
||||||
docSeen[doc.KandangID][*doc.SupplierID] = true
|
docSeen[doc.ProjectFlockKandangID][*doc.SupplierID] = true
|
||||||
supplierName := ""
|
supplierName := ""
|
||||||
if doc.SupplierName != nil {
|
if doc.SupplierName != nil {
|
||||||
supplierName = *doc.SupplierName
|
supplierName = *doc.SupplierName
|
||||||
@@ -239,7 +252,7 @@ func (r *hppPerKandangRepository) GetFeedOvkDocCostByPeriod(ctx context.Context,
|
|||||||
supplierAlias = *doc.SupplierAlias
|
supplierAlias = *doc.SupplierAlias
|
||||||
}
|
}
|
||||||
docSuppliers = append(docSuppliers, HppPerKandangSupplierRow{
|
docSuppliers = append(docSuppliers, HppPerKandangSupplierRow{
|
||||||
KandangID: doc.KandangID,
|
ProjectFlockKandangID: doc.ProjectFlockKandangID,
|
||||||
SupplierID: *doc.SupplierID,
|
SupplierID: *doc.SupplierID,
|
||||||
SupplierName: supplierName,
|
SupplierName: supplierName,
|
||||||
SupplierAlias: supplierAlias,
|
SupplierAlias: supplierAlias,
|
||||||
@@ -250,7 +263,7 @@ func (r *hppPerKandangRepository) GetFeedOvkDocCostByPeriod(ctx context.Context,
|
|||||||
}
|
}
|
||||||
|
|
||||||
budgetRows := make([]struct {
|
budgetRows := make([]struct {
|
||||||
KandangID uint
|
ProjectFlockKandangID uint
|
||||||
BudgetCost float64
|
BudgetCost float64
|
||||||
}, 0)
|
}, 0)
|
||||||
|
|
||||||
@@ -272,7 +285,7 @@ func (r *hppPerKandangRepository) GetFeedOvkDocCostByPeriod(ctx context.Context,
|
|||||||
budgetQuery := r.db.WithContext(ctx).
|
budgetQuery := r.db.WithContext(ctx).
|
||||||
Table("project_flock_kandangs AS pfk").
|
Table("project_flock_kandangs AS pfk").
|
||||||
Select(`
|
Select(`
|
||||||
k.id AS kandang_id,
|
pfk.id AS project_flock_kandang_id,
|
||||||
COALESCE(SUM((pb.qty * pb.price) * COALESCE(k_usage.kandang_usage_qty, 0) / NULLIF(p_usage.project_usage_qty, 0)), 0) AS budget_cost`).
|
COALESCE(SUM((pb.qty * pb.price) * COALESCE(k_usage.kandang_usage_qty, 0) / NULLIF(p_usage.project_usage_qty, 0)), 0) AS budget_cost`).
|
||||||
Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id").
|
Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id").
|
||||||
Joins("JOIN locations AS loc ON loc.id = k.location_id").
|
Joins("JOIN locations AS loc ON loc.id = k.location_id").
|
||||||
@@ -280,7 +293,7 @@ func (r *hppPerKandangRepository) GetFeedOvkDocCostByPeriod(ctx context.Context,
|
|||||||
Joins("LEFT JOIN (?) AS k_usage ON k_usage.project_flock_kandang_id = pfk.id", pfkUsageSub).
|
Joins("LEFT JOIN (?) AS k_usage ON k_usage.project_flock_kandang_id = pfk.id", pfkUsageSub).
|
||||||
Joins("LEFT JOIN (?) AS p_usage ON p_usage.project_flock_id = pfk.project_flock_id", projectUsageSub).
|
Joins("LEFT JOIN (?) AS p_usage ON p_usage.project_flock_id = pfk.project_flock_id", projectUsageSub).
|
||||||
Where("pfk.id IN (?)", projectFlockKandangIDs).
|
Where("pfk.id IN (?)", projectFlockKandangIDs).
|
||||||
Group("k.id")
|
Group("pfk.id")
|
||||||
// budgetQuery = applyLocationFilters(budgetQuery, areaIDs, locationIDs, kandangIDs)
|
// budgetQuery = applyLocationFilters(budgetQuery, areaIDs, locationIDs, kandangIDs)
|
||||||
|
|
||||||
if err := budgetQuery.Scan(&budgetRows).Error; err != nil {
|
if err := budgetQuery.Scan(&budgetRows).Error; err != nil {
|
||||||
@@ -288,33 +301,33 @@ func (r *hppPerKandangRepository) GetFeedOvkDocCostByPeriod(ctx context.Context,
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, budget := range budgetRows {
|
for _, budget := range budgetRows {
|
||||||
entry, ok := costMap[budget.KandangID]
|
entry, ok := costMap[budget.ProjectFlockKandangID]
|
||||||
if !ok {
|
if !ok {
|
||||||
rows = append(rows, HppPerKandangCostRow{
|
rows = append(rows, HppPerKandangCostRow{
|
||||||
KandangID: budget.KandangID,
|
ProjectFlockKandangID: budget.ProjectFlockKandangID,
|
||||||
})
|
})
|
||||||
entry = &rows[len(rows)-1]
|
entry = &rows[len(rows)-1]
|
||||||
costMap[budget.KandangID] = entry
|
costMap[budget.ProjectFlockKandangID] = entry
|
||||||
}
|
}
|
||||||
entry.BudgetCost += budget.BudgetCost
|
entry.BudgetCost += budget.BudgetCost
|
||||||
}
|
}
|
||||||
|
|
||||||
expenseRows := make([]struct {
|
expenseRows := make([]struct {
|
||||||
KandangID uint
|
ProjectFlockKandangID uint
|
||||||
ExpenseCost float64
|
ExpenseCost float64
|
||||||
}, 0)
|
}, 0)
|
||||||
|
|
||||||
expenseQuery := r.db.WithContext(ctx).
|
expenseQuery := r.db.WithContext(ctx).
|
||||||
Table("project_flock_kandangs AS pfk").
|
Table("project_flock_kandangs AS pfk").
|
||||||
Select(`
|
Select(`
|
||||||
k.id AS kandang_id,
|
pfk.id AS project_flock_kandang_id,
|
||||||
COALESCE(SUM(er.qty * er.price), 0) AS expense_cost`).
|
COALESCE(SUM(er.qty * er.price), 0) AS expense_cost`).
|
||||||
Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id").
|
Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id").
|
||||||
Joins("JOIN locations AS loc ON loc.id = k.location_id").
|
Joins("JOIN locations AS loc ON loc.id = k.location_id").
|
||||||
Joins("JOIN expense_nonstocks AS en ON en.project_flock_kandang_id = pfk.id").
|
Joins("JOIN expense_nonstocks AS en ON en.project_flock_kandang_id = pfk.id").
|
||||||
Joins("JOIN expense_realizations AS er ON er.expense_nonstock_id = en.id").
|
Joins("JOIN expense_realizations AS er ON er.expense_nonstock_id = en.id").
|
||||||
Where("pfk.id IN (?)", projectFlockKandangIDs).
|
Where("pfk.id IN (?)", projectFlockKandangIDs).
|
||||||
Group("k.id")
|
Group("pfk.id")
|
||||||
// expenseQuery = applyLocationFilters(expenseQuery, areaIDs, locationIDs, kandangIDs)
|
// expenseQuery = applyLocationFilters(expenseQuery, areaIDs, locationIDs, kandangIDs)
|
||||||
|
|
||||||
if err := expenseQuery.Scan(&expenseRows).Error; err != nil {
|
if err := expenseQuery.Scan(&expenseRows).Error; err != nil {
|
||||||
@@ -322,13 +335,13 @@ func (r *hppPerKandangRepository) GetFeedOvkDocCostByPeriod(ctx context.Context,
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, exp := range expenseRows {
|
for _, exp := range expenseRows {
|
||||||
entry, ok := costMap[exp.KandangID]
|
entry, ok := costMap[exp.ProjectFlockKandangID]
|
||||||
if !ok {
|
if !ok {
|
||||||
rows = append(rows, HppPerKandangCostRow{
|
rows = append(rows, HppPerKandangCostRow{
|
||||||
KandangID: exp.KandangID,
|
ProjectFlockKandangID: exp.ProjectFlockKandangID,
|
||||||
})
|
})
|
||||||
entry = &rows[len(rows)-1]
|
entry = &rows[len(rows)-1]
|
||||||
costMap[exp.KandangID] = entry
|
costMap[exp.ProjectFlockKandangID] = entry
|
||||||
}
|
}
|
||||||
entry.ExpenseCost += exp.ExpenseCost
|
entry.ExpenseCost += exp.ExpenseCost
|
||||||
}
|
}
|
||||||
@@ -337,7 +350,7 @@ func (r *hppPerKandangRepository) GetFeedOvkDocCostByPeriod(ctx context.Context,
|
|||||||
|
|
||||||
feedQuery := r.db.WithContext(ctx).
|
feedQuery := r.db.WithContext(ctx).
|
||||||
Table("recordings AS r").
|
Table("recordings AS r").
|
||||||
Select("DISTINCT k.id AS kandang_id, s.id AS supplier_id, s.name AS supplier_name, s.alias AS supplier_alias").
|
Select("DISTINCT pfk.id AS project_flock_kandang_id, s.id AS supplier_id, s.name AS supplier_name, s.alias AS supplier_alias").
|
||||||
Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = r.project_flock_kandangs_id").
|
Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = r.project_flock_kandangs_id").
|
||||||
Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id").
|
Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id").
|
||||||
Joins("JOIN locations AS loc ON loc.id = k.location_id").
|
Joins("JOIN locations AS loc ON loc.id = k.location_id").
|
||||||
@@ -349,7 +362,7 @@ func (r *hppPerKandangRepository) GetFeedOvkDocCostByPeriod(ctx context.Context,
|
|||||||
Joins("LEFT JOIN flags AS f ON f.flagable_id = pi.product_id AND f.flagable_type = ?", entity.FlagableTypeProduct).
|
Joins("LEFT JOIN flags AS f ON f.flagable_id = pi.product_id AND f.flagable_type = ?", entity.FlagableTypeProduct).
|
||||||
Where("f.name IN ?", []utils.FlagType{utils.FlagPakan, utils.FlagOVK}).
|
Where("f.name IN ?", []utils.FlagType{utils.FlagPakan, utils.FlagOVK}).
|
||||||
Where("r.project_flock_kandangs_id IN (?)", projectFlockKandangIDs).
|
Where("r.project_flock_kandangs_id IN (?)", projectFlockKandangIDs).
|
||||||
// Where("r.record_datetime >= ? AND r.record_datetime < ?", start, end).
|
Where("r.record_datetime < ?", end).
|
||||||
Where("r.deleted_at IS NULL")
|
Where("r.deleted_at IS NULL")
|
||||||
// feedQuery = applyLocationFilters(feedQuery, areaIDs, locationIDs, kandangIDs)
|
// feedQuery = applyLocationFilters(feedQuery, areaIDs, locationIDs, kandangIDs)
|
||||||
|
|
||||||
@@ -358,11 +371,11 @@ func (r *hppPerKandangRepository) GetFeedOvkDocCostByPeriod(ctx context.Context,
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i := range feedSuppliers {
|
for i := range feedSuppliers {
|
||||||
if _, exists := costMap[feedSuppliers[i].KandangID]; !exists {
|
if _, exists := costMap[feedSuppliers[i].ProjectFlockKandangID]; !exists {
|
||||||
rows = append(rows, HppPerKandangCostRow{
|
rows = append(rows, HppPerKandangCostRow{
|
||||||
KandangID: feedSuppliers[i].KandangID,
|
ProjectFlockKandangID: feedSuppliers[i].ProjectFlockKandangID,
|
||||||
})
|
})
|
||||||
costMap[feedSuppliers[i].KandangID] = &rows[len(rows)-1]
|
costMap[feedSuppliers[i].ProjectFlockKandangID] = &rows[len(rows)-1]
|
||||||
}
|
}
|
||||||
feedSuppliers[i].Category = "FEED"
|
feedSuppliers[i].Category = "FEED"
|
||||||
}
|
}
|
||||||
@@ -392,8 +405,10 @@ func (r *hppPerKandangRepository) GetEggProductionByProjectFlockKandangIDs(ctx c
|
|||||||
|
|
||||||
type eggRow struct {
|
type eggRow struct {
|
||||||
ProjectFlockKandangID uint
|
ProjectFlockKandangID uint
|
||||||
EggProductionWeightKg float64
|
EggProductionWeightKgRemaining float64
|
||||||
EggProductionPieces float64
|
EggProductionPiecesRemaining float64
|
||||||
|
EggProductionTotalWeightKg float64
|
||||||
|
EggProductionTotalPieces float64
|
||||||
}
|
}
|
||||||
|
|
||||||
eggRows := make([]eggRow, 0)
|
eggRows := make([]eggRow, 0)
|
||||||
@@ -401,12 +416,14 @@ func (r *hppPerKandangRepository) GetEggProductionByProjectFlockKandangIDs(ctx c
|
|||||||
Table("recordings AS r").
|
Table("recordings AS r").
|
||||||
Select(`
|
Select(`
|
||||||
r.project_flock_kandangs_id AS project_flock_kandang_id,
|
r.project_flock_kandangs_id AS project_flock_kandang_id,
|
||||||
COALESCE(SUM(re.weight), 0) AS egg_production_weight_kg,
|
COALESCE(SUM((re.total_qty - re.total_used) * re.weight / 1000), 0) AS egg_production_weight_kg_remaining,
|
||||||
COALESCE(SUM(re.qty), 0) AS egg_production_pieces`).
|
COALESCE(SUM(re.total_qty - re.total_used), 0) AS egg_production_pieces_remaining,
|
||||||
|
COALESCE(SUM(re.weight / 1000), 0) AS egg_production_total_weight_kg,
|
||||||
|
COALESCE(SUM(re.total_qty), 0) AS egg_production_total_pieces`).
|
||||||
Joins("LEFT JOIN (?) AS la ON la.approvable_id = r.id", latestApproval).
|
Joins("LEFT JOIN (?) AS la ON la.approvable_id = r.id", latestApproval).
|
||||||
Joins("LEFT JOIN recording_eggs AS re ON re.recording_id = r.id").
|
Joins("LEFT JOIN recording_eggs AS re ON re.recording_id = r.id").
|
||||||
Where("r.project_flock_kandangs_id IN ?", projectFlockKandangIDs).
|
Where("r.project_flock_kandangs_id IN ?", projectFlockKandangIDs).
|
||||||
// Where("r.record_datetime >= ? AND r.record_datetime < ?", start, end).
|
Where("r.record_datetime < ?", end).
|
||||||
Where("r.deleted_at IS NULL").
|
Where("r.deleted_at IS NULL").
|
||||||
Where("(la.action IS NULL OR la.action != ?)", string(entity.ApprovalActionRejected)).
|
Where("(la.action IS NULL OR la.action != ?)", string(entity.ApprovalActionRejected)).
|
||||||
Group("r.project_flock_kandangs_id")
|
Group("r.project_flock_kandangs_id")
|
||||||
@@ -419,8 +436,10 @@ func (r *hppPerKandangRepository) GetEggProductionByProjectFlockKandangIDs(ctx c
|
|||||||
for _, row := range eggRows {
|
for _, row := range eggRows {
|
||||||
result[row.ProjectFlockKandangID] = HppPerKandangRow{
|
result[row.ProjectFlockKandangID] = HppPerKandangRow{
|
||||||
ProjectFlockKandangID: row.ProjectFlockKandangID,
|
ProjectFlockKandangID: row.ProjectFlockKandangID,
|
||||||
EggProductionWeightKg: row.EggProductionWeightKg,
|
EggProductionWeightKgRemaining: row.EggProductionWeightKgRemaining,
|
||||||
EggProductionPieces: row.EggProductionPieces,
|
EggProductionPiecesRemaining: row.EggProductionPiecesRemaining,
|
||||||
|
EggProductionTotalWeightKg: row.EggProductionTotalWeightKg,
|
||||||
|
EggProductionTotalPieces: row.EggProductionTotalPieces,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -435,7 +454,7 @@ func applyLocationFilters(query *gorm.DB, areaIDs, locationIDs, kandangIDs []int
|
|||||||
query = query.Where("k.location_id IN ?", locationIDs)
|
query = query.Where("k.location_id IN ?", locationIDs)
|
||||||
}
|
}
|
||||||
if len(kandangIDs) > 0 {
|
if len(kandangIDs) > 0 {
|
||||||
query = query.Where("k.id IN ?", kandangIDs)
|
query = query.Where("pfk.id IN ?", kandangIDs)
|
||||||
}
|
}
|
||||||
return query
|
return query
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -464,9 +464,13 @@ func (s *repportService) GetCustomerPayment(ctx *fiber.Ctx, params *validation.C
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(item.Rows) > 0 {
|
||||||
result = append(result, item)
|
result = append(result, item)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
totalCustomers = int64(len(result))
|
||||||
return result, totalCustomers, nil
|
return result, totalCustomers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -503,14 +507,8 @@ func (s *repportService) processCustomerPayment(ctx context.Context, customerID
|
|||||||
row.Status = status
|
row.Status = status
|
||||||
|
|
||||||
if status == "LUNAS" {
|
if status == "LUNAS" {
|
||||||
if previousBalance >= tx.TotalPrice {
|
if paymentDate != nil {
|
||||||
days := 0
|
|
||||||
row.AgingDay = &days
|
|
||||||
} else if paymentDate != nil {
|
|
||||||
days := int(paymentDate.Sub(tx.TransDate).Hours() / 24)
|
days := int(paymentDate.Sub(tx.TransDate).Hours() / 24)
|
||||||
if days < 0 {
|
|
||||||
days = 0
|
|
||||||
}
|
|
||||||
row.AgingDay = &days
|
row.AgingDay = &days
|
||||||
} else {
|
} else {
|
||||||
days := 0
|
days := 0
|
||||||
@@ -518,9 +516,6 @@ func (s *repportService) processCustomerPayment(ctx context.Context, customerID
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
days := int(time.Since(tx.TransDate).Hours() / 24)
|
days := int(time.Since(tx.TransDate).Hours() / 24)
|
||||||
if days < 0 {
|
|
||||||
days = 0
|
|
||||||
}
|
|
||||||
row.AgingDay = &days
|
row.AgingDay = &days
|
||||||
}
|
}
|
||||||
} else if tx.TransactionType == "PAYMENT" {
|
} else if tx.TransactionType == "PAYMENT" {
|
||||||
@@ -586,6 +581,67 @@ func (s *repportService) determineSalesStatusAndPaymentDate(transactions []reppo
|
|||||||
// 4. BELUM LUNAS: no payment at all
|
// 4. BELUM LUNAS: no payment at all
|
||||||
|
|
||||||
if previousBalance >= currentSales.TotalPrice {
|
if previousBalance >= currentSales.TotalPrice {
|
||||||
|
// Cari payment yang digunakan untuk melunasi sales ini dengan FIFO
|
||||||
|
// Track payment allocations that are consumed by previous sales
|
||||||
|
type paymentAllocation struct {
|
||||||
|
date time.Time
|
||||||
|
amount float64
|
||||||
|
consumed float64
|
||||||
|
}
|
||||||
|
allocations := []paymentAllocation{}
|
||||||
|
runningBalance := 0.0
|
||||||
|
|
||||||
|
// Process all transactions before current sales to build allocation map
|
||||||
|
for i := 0; i < currentIndex; i++ {
|
||||||
|
if transactions[i].TransactionType == "PAYMENT" {
|
||||||
|
allocations = append(allocations, paymentAllocation{
|
||||||
|
date: transactions[i].TransDate,
|
||||||
|
amount: transactions[i].PaymentAmount,
|
||||||
|
consumed: 0,
|
||||||
|
})
|
||||||
|
runningBalance += transactions[i].PaymentAmount
|
||||||
|
} else if transactions[i].TransactionType == "SALES" {
|
||||||
|
salesAmount := transactions[i].TotalPrice
|
||||||
|
remainingToConsume := salesAmount
|
||||||
|
|
||||||
|
// Consume from oldest allocations first (FIFO)
|
||||||
|
for j := range allocations {
|
||||||
|
if remainingToConsume <= 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
available := allocations[j].amount - allocations[j].consumed
|
||||||
|
if available > 0 {
|
||||||
|
consume := available
|
||||||
|
if consume > remainingToConsume {
|
||||||
|
consume = remainingToConsume
|
||||||
|
}
|
||||||
|
allocations[j].consumed += consume
|
||||||
|
remainingToConsume -= consume
|
||||||
|
}
|
||||||
|
}
|
||||||
|
runningBalance -= salesAmount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now find which allocation covers the current sales
|
||||||
|
amountNeeded := currentSales.TotalPrice
|
||||||
|
for _, alloc := range allocations {
|
||||||
|
available := alloc.amount - alloc.consumed
|
||||||
|
if available > 0 {
|
||||||
|
if amountNeeded <= available {
|
||||||
|
// This allocation fully covers the sales
|
||||||
|
return "LUNAS", &alloc.date
|
||||||
|
} else {
|
||||||
|
// This allocation partially covers, continue to next
|
||||||
|
amountNeeded -= available
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we get here, use the oldest allocation
|
||||||
|
if len(allocations) > 0 {
|
||||||
|
return "LUNAS", &allocations[0].date
|
||||||
|
}
|
||||||
return "LUNAS", nil
|
return "LUNAS", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1370,15 +1426,17 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes
|
|||||||
}
|
}
|
||||||
for pfkID, egg := range eggMap {
|
for pfkID, egg := range eggMap {
|
||||||
if rowIdx, ok := pfkIndex[pfkID]; ok {
|
if rowIdx, ok := pfkIndex[pfkID]; ok {
|
||||||
repoRows[rowIdx].EggProductionWeightKg = egg.EggProductionWeightKg
|
repoRows[rowIdx].EggProductionWeightKgRemaining = egg.EggProductionWeightKgRemaining
|
||||||
repoRows[rowIdx].EggProductionPieces = egg.EggProductionPieces
|
repoRows[rowIdx].EggProductionPiecesRemaining = egg.EggProductionPiecesRemaining
|
||||||
|
repoRows[rowIdx].EggProductionTotalWeightKg = egg.EggProductionTotalWeightKg
|
||||||
|
repoRows[rowIdx].EggProductionTotalPieces = egg.EggProductionTotalPieces
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
costMap := make(map[uint]HppCostAggregate, len(costRows))
|
costMap := make(map[uint]HppCostAggregate, len(costRows))
|
||||||
for _, row := range costRows {
|
for _, row := range costRows {
|
||||||
costMap[row.KandangID] = HppCostAggregate{
|
costMap[row.ProjectFlockKandangID] = HppCostAggregate{
|
||||||
FeedCost: row.FeedCost,
|
FeedCost: row.FeedCost,
|
||||||
OvkCost: row.OvkCost,
|
OvkCost: row.OvkCost,
|
||||||
DocCost: row.DocCost,
|
DocCost: row.DocCost,
|
||||||
@@ -1407,15 +1465,15 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes
|
|||||||
category = "DOC"
|
category = "DOC"
|
||||||
}
|
}
|
||||||
|
|
||||||
if seen[sup.KandangID] == nil {
|
if seen[sup.ProjectFlockKandangID] == nil {
|
||||||
seen[sup.KandangID] = make(map[uint]bool)
|
seen[sup.ProjectFlockKandangID] = make(map[uint]bool)
|
||||||
}
|
}
|
||||||
if seen[sup.KandangID][sup.SupplierID] {
|
if seen[sup.ProjectFlockKandangID][sup.SupplierID] {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
seen[sup.KandangID][sup.SupplierID] = true
|
seen[sup.ProjectFlockKandangID][sup.SupplierID] = true
|
||||||
|
|
||||||
targetMap[sup.KandangID] = append(targetMap[sup.KandangID], dto.HppPerKandangSupplierDTO{
|
targetMap[sup.ProjectFlockKandangID] = append(targetMap[sup.ProjectFlockKandangID], dto.HppPerKandangSupplierDTO{
|
||||||
ID: int64(sup.SupplierID),
|
ID: int64(sup.SupplierID),
|
||||||
Name: sup.SupplierName,
|
Name: sup.SupplierName,
|
||||||
Alias: sup.SupplierAlias,
|
Alias: sup.SupplierAlias,
|
||||||
@@ -1442,12 +1500,12 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes
|
|||||||
dataRows := make([]dto.HppPerKandangRowDTO, 0, len(repoRows))
|
dataRows := make([]dto.HppPerKandangRowDTO, 0, len(repoRows))
|
||||||
perRangeMap := make(map[weightRangeKey]*weightRangeAggregate)
|
perRangeMap := make(map[weightRangeKey]*weightRangeAggregate)
|
||||||
var totalBirds int64
|
var totalBirds int64
|
||||||
var totalWeight float64
|
// var totalWeight float64
|
||||||
var totalEggPieces int64
|
var totalEggPieces int64
|
||||||
var totalEggKg float64
|
var totalEggKg float64
|
||||||
var totalRemainingValueRp int64
|
// var totalRemainingValueRp int64
|
||||||
var totalEggValueRp int64
|
var totalEggValueRp int64
|
||||||
var totalHppSum float64
|
// var totalHppSum float64
|
||||||
var totalHppCount int
|
var totalHppCount int
|
||||||
var totalDocPriceSum float64
|
var totalDocPriceSum float64
|
||||||
var totalDocPriceCount int
|
var totalDocPriceCount int
|
||||||
@@ -1461,26 +1519,34 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
birdsFloat := row.RemainingChickenBirds
|
// birdsFloat := row.RemainingChickenBirds
|
||||||
if math.IsNaN(birdsFloat) || math.IsInf(birdsFloat, 0) {
|
// if math.IsNaN(birdsFloat) || math.IsInf(birdsFloat, 0) {
|
||||||
birdsFloat = 0
|
// birdsFloat = 0
|
||||||
|
// }
|
||||||
|
// weightFloat := row.RemainingChickenWeight
|
||||||
|
// if math.IsNaN(weightFloat) || math.IsInf(weightFloat, 0) {
|
||||||
|
// weightFloat = 0
|
||||||
|
// }
|
||||||
|
eggPiecesFloatRemaining := row.EggProductionPiecesRemaining
|
||||||
|
if math.IsNaN(eggPiecesFloatRemaining) || math.IsInf(eggPiecesFloatRemaining, 0) {
|
||||||
|
eggPiecesFloatRemaining = 0
|
||||||
}
|
}
|
||||||
weightFloat := row.RemainingChickenWeight
|
eggTotalPiecesFloat := row.EggProductionTotalPieces
|
||||||
if math.IsNaN(weightFloat) || math.IsInf(weightFloat, 0) {
|
if math.IsNaN(eggTotalPiecesFloat) || math.IsInf(eggTotalPiecesFloat, 0) {
|
||||||
weightFloat = 0
|
eggTotalPiecesFloat = 0
|
||||||
}
|
}
|
||||||
eggPiecesFloat := row.EggProductionPieces
|
eggRemainingWeightFloatRemaining := row.EggProductionWeightKgRemaining
|
||||||
if math.IsNaN(eggPiecesFloat) || math.IsInf(eggPiecesFloat, 0) {
|
if math.IsNaN(eggRemainingWeightFloatRemaining) || math.IsInf(eggRemainingWeightFloatRemaining, 0) {
|
||||||
eggPiecesFloat = 0
|
eggRemainingWeightFloatRemaining = 0
|
||||||
}
|
}
|
||||||
eggWeightFloat := row.EggProductionWeightKg
|
eggWeightFloat := row.EggProductionTotalWeightKg
|
||||||
if math.IsNaN(eggWeightFloat) || math.IsInf(eggWeightFloat, 0) {
|
if math.IsNaN(eggWeightFloat) || math.IsInf(eggWeightFloat, 0) {
|
||||||
eggWeightFloat = 0
|
eggWeightFloat = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
avgWeight := 0.0
|
avgWeight := 0.0
|
||||||
if eggPiecesFloat > 0 {
|
if eggTotalPiecesFloat > 0 {
|
||||||
avgWeight = eggWeightFloat / eggPiecesFloat
|
avgWeight = eggWeightFloat / eggTotalPiecesFloat
|
||||||
}
|
}
|
||||||
if params.WeightMin != nil && avgWeight < *params.WeightMin {
|
if params.WeightMin != nil && avgWeight < *params.WeightMin {
|
||||||
continue
|
continue
|
||||||
@@ -1496,28 +1562,30 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes
|
|||||||
weightMax := weightMin + 0.09
|
weightMax := weightMin + 0.09
|
||||||
rangeKey := weightRangeKey{Min: weightMin, Max: weightMax}
|
rangeKey := weightRangeKey{Min: weightMin, Max: weightMax}
|
||||||
|
|
||||||
rowBirds := int64(math.Round(birdsFloat))
|
// rowBirds := int64(math.Round(birdsFloat))
|
||||||
costEntry := costMap[row.KandangID]
|
costEntry := costMap[row.ProjectFlockKandangID]
|
||||||
totalCost := costEntry.FeedCost + costEntry.OvkCost + costEntry.DocCost + costEntry.BudgetCost + costEntry.ExpenseCost
|
totalCost := costEntry.FeedCost + costEntry.OvkCost + costEntry.DocCost + costEntry.BudgetCost + costEntry.ExpenseCost
|
||||||
hppRp := 0.0
|
// hppRp := 0.0
|
||||||
if weightFloat > 0 {
|
// if weightFloat > 0 {
|
||||||
hppRp = totalCost / weightFloat
|
// hppRp = totalCost / weightFloat
|
||||||
}
|
// }
|
||||||
eggHpp := 0.0
|
eggHpp := 0.0
|
||||||
if eggWeightFloat > 0 {
|
if eggWeightFloat > 0 {
|
||||||
eggHpp = totalCost / eggWeightFloat
|
eggHpp = (totalCost / eggWeightFloat) / 1000
|
||||||
}
|
}
|
||||||
|
|
||||||
rowEggPieces := int64(math.Round(eggPiecesFloat))
|
rowEggPieces := int64(math.Round(eggPiecesFloatRemaining))
|
||||||
rowEggValue := int64(eggHpp * eggWeightFloat)
|
rowEggValue := int64(eggHpp * eggRemainingWeightFloatRemaining)
|
||||||
rowRemainingValue := int64(hppRp * weightFloat)
|
// rowRemainingValue := int64(hppRp * weightFloat)
|
||||||
avgDocPrice := int64(0)
|
avgDocPrice := int64(0)
|
||||||
if costEntry.DocQty > 0 {
|
if costEntry.DocQty > 0 {
|
||||||
avgDocPrice = int64(math.Round(costEntry.DocCost / costEntry.DocQty))
|
avgDocPrice = int64(math.Round(costEntry.DocCost / costEntry.DocQty))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nameWithPeriod := fmt.Sprintf("%s Period %d", row.KandangName, row.ProjectFlockPeriod)
|
||||||
|
|
||||||
dataRows = append(dataRows, dto.HppPerKandangRowDTO{
|
dataRows = append(dataRows, dto.HppPerKandangRowDTO{
|
||||||
ID: int(row.KandangID),
|
ID: int(row.ProjectFlockKandangID),
|
||||||
Kandang: dto.HppPerKandangRowKandangDTO{
|
Kandang: dto.HppPerKandangRowKandangDTO{
|
||||||
ID: int64(row.KandangID),
|
ID: int64(row.KandangID),
|
||||||
Name: row.KandangName,
|
Name: row.KandangName,
|
||||||
@@ -1536,31 +1604,34 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes
|
|||||||
WeightMax: weightMax,
|
WeightMax: weightMax,
|
||||||
},
|
},
|
||||||
AvgWeightKg: avgWeight,
|
AvgWeightKg: avgWeight,
|
||||||
|
NameWithPeriode: nameWithPeriod,
|
||||||
// FeedCostRp: costEntry.FeedCost,
|
// FeedCostRp: costEntry.FeedCost,
|
||||||
// OvkCostRp: costEntry.OvkCost,
|
// OvkCostRp: costEntry.OvkCost,
|
||||||
DocSuppliers: docSupplierMap[row.KandangID],
|
DocSuppliers: docSupplierMap[row.ProjectFlockKandangID],
|
||||||
FeedSuppliers: feedSupplierMap[row.KandangID],
|
FeedSuppliers: feedSupplierMap[row.ProjectFlockKandangID],
|
||||||
EggProductionPieces: rowEggPieces,
|
EggProductionPieces: int64(math.Round(eggPiecesFloatRemaining)),
|
||||||
EggProductionKg: eggWeightFloat,
|
EggProductionKg: eggRemainingWeightFloatRemaining,
|
||||||
|
// EggProductionTotalWeightKg: eggWeightFloat,
|
||||||
|
// EggProductionTotalPieces: int64(math.Round(eggTotalPiecesFloat)),
|
||||||
AverageDocPriceRp: avgDocPrice,
|
AverageDocPriceRp: avgDocPrice,
|
||||||
// HppRp: hppRp,
|
// HppRp: hppRp,
|
||||||
EggHppRpPerKg: eggHpp,
|
EggHppRpPerKg: eggHpp,
|
||||||
RemainingValueRp: rowRemainingValue,
|
// RemainingValueRp: rowRemainingValue,
|
||||||
EggValueRp: rowEggValue,
|
EggValueRp: rowEggValue,
|
||||||
})
|
})
|
||||||
|
|
||||||
totalBirds += rowBirds
|
// totalBirds += rowBirds
|
||||||
totalWeight += weightFloat
|
// totalWeight += weightFloat
|
||||||
totalEggPieces += rowEggPieces
|
totalEggPieces += rowEggPieces
|
||||||
totalEggKg += eggWeightFloat
|
totalEggKg += eggRemainingWeightFloatRemaining
|
||||||
totalRemainingValueRp += rowRemainingValue
|
// totalRemainingValueRp += rowRemainingValue
|
||||||
totalEggValueRp += rowEggValue
|
totalEggValueRp += rowEggValue
|
||||||
totalAvgWeightSum += avgWeight
|
totalAvgWeightSum += avgWeight
|
||||||
totalAvgWeightCount++
|
totalAvgWeightCount++
|
||||||
if weightFloat > 0 {
|
// if weightFloat > 0 {
|
||||||
totalHppSum += hppRp
|
// totalHppSum += hppRp
|
||||||
totalHppCount++
|
// totalHppCount++
|
||||||
}
|
// }
|
||||||
if avgDocPrice > 0 {
|
if avgDocPrice > 0 {
|
||||||
totalDocPriceSum += float64(avgDocPrice)
|
totalDocPriceSum += float64(avgDocPrice)
|
||||||
totalDocPriceCount++
|
totalDocPriceCount++
|
||||||
@@ -1587,23 +1658,23 @@ func (s *repportService) GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangRes
|
|||||||
}
|
}
|
||||||
|
|
||||||
rangeSummary := rangeAgg.Summary
|
rangeSummary := rangeAgg.Summary
|
||||||
rangeAgg.RemainingBirds += rowBirds
|
// rangeAgg.RemainingBirds += rowBirds
|
||||||
rangeAgg.RemainingWeightKg += row.RemainingChickenWeight
|
// rangeAgg.RemainingWeightKg += row.RemainingChickenWeight
|
||||||
rangeAgg.AvgWeightSum += avgWeight
|
rangeAgg.AvgWeightSum += avgWeight
|
||||||
rangeAgg.AvgWeightCount++
|
rangeAgg.AvgWeightCount++
|
||||||
for _, supplier := range feedSupplierMap[row.KandangID] {
|
for _, supplier := range feedSupplierMap[row.ProjectFlockKandangID] {
|
||||||
if _, ok := rangeAgg.FeedSuppliers[supplier.ID]; !ok {
|
if _, ok := rangeAgg.FeedSuppliers[supplier.ID]; !ok {
|
||||||
rangeAgg.FeedSuppliers[supplier.ID] = supplier
|
rangeAgg.FeedSuppliers[supplier.ID] = supplier
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, supplier := range docSupplierMap[row.KandangID] {
|
for _, supplier := range docSupplierMap[row.ProjectFlockKandangID] {
|
||||||
if _, ok := rangeAgg.DocSuppliers[supplier.ID]; !ok {
|
if _, ok := rangeAgg.DocSuppliers[supplier.ID]; !ok {
|
||||||
rangeAgg.DocSuppliers[supplier.ID] = supplier
|
rangeAgg.DocSuppliers[supplier.ID] = supplier
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rangeSummary.EggProductionPieces += rowEggPieces
|
rangeSummary.EggProductionPieces += rowEggPieces
|
||||||
rangeSummary.EggProductionKg += eggWeightFloat
|
rangeSummary.EggProductionKg += eggRemainingWeightFloatRemaining
|
||||||
rangeSummary.RemainingValueRp += rowRemainingValue
|
// rangeSummary.RemainingValueRp += rowRemainingValue
|
||||||
rangeSummary.EggValueRp += rowEggValue
|
rangeSummary.EggValueRp += rowEggValue
|
||||||
if eggWeightFloat > 0 {
|
if eggWeightFloat > 0 {
|
||||||
rangeAgg.EggHppSum += eggHpp
|
rangeAgg.EggHppSum += eggHpp
|
||||||
@@ -1750,6 +1821,9 @@ func (s *repportService) parseHppPerKandangQuery(ctx *fiber.Ctx) (*validation.Hp
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, dto.HppPerKandangFiltersDTO{}, fiber.NewError(fiber.StatusBadRequest, err.Error())
|
return nil, dto.HppPerKandangFiltersDTO{}, fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||||
}
|
}
|
||||||
|
if weightMin != nil && weightMax != nil && *weightMin > *weightMax {
|
||||||
|
return nil, dto.HppPerKandangFiltersDTO{}, fiber.NewError(fiber.StatusBadRequest, "weight_min must be less than or equal to weight_max")
|
||||||
|
}
|
||||||
|
|
||||||
params := &validation.HppPerKandangQuery{
|
params := &validation.HppPerKandangQuery{
|
||||||
Page: page,
|
Page: page,
|
||||||
|
|||||||
@@ -17,12 +17,14 @@ type ExpenseQuery struct {
|
|||||||
|
|
||||||
type MarketingQuery struct {
|
type MarketingQuery 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=100,gt=0"`
|
Limit int `query:"limit" validate:"omitempty,min=1,gt=0"`
|
||||||
Search string `query:"search" validate:"omitempty,max=100"`
|
Search string `query:"search" validate:"omitempty,max=100"`
|
||||||
CustomerId int64 `query:"customer_id" validate:"omitempty"`
|
CustomerId int64 `query:"customer_id" validate:"omitempty"`
|
||||||
ProductId int64 `query:"product_id" validate:"omitempty"`
|
ProductId int64 `query:"product_id" validate:"omitempty"`
|
||||||
WarehouseId int64 `query:"warehouse_id" validate:"omitempty"`
|
WarehouseId int64 `query:"warehouse_id" validate:"omitempty"`
|
||||||
SalesPersonId int64 `query:"sales_person_id" validate:"omitempty"`
|
SalesPersonId int64 `query:"sales_person_id" validate:"omitempty"`
|
||||||
|
AreaId int64 `query:"area_id" validate:"omitempty"`
|
||||||
|
LocationId int64 `query:"location_id" validate:"omitempty"`
|
||||||
MarketingType string `query:"marketing_type" validate:"omitempty,oneof=ayam telur trading"`
|
MarketingType string `query:"marketing_type" validate:"omitempty,oneof=ayam telur trading"`
|
||||||
FilterBy string `query:"filter_by" validate:"omitempty,oneof=so_date realization_date"`
|
FilterBy string `query:"filter_by" validate:"omitempty,oneof=so_date realization_date"`
|
||||||
StartDate string `query:"start_date" validate:"omitempty,datetime=2006-01-02"`
|
StartDate string `query:"start_date" validate:"omitempty,datetime=2006-01-02"`
|
||||||
@@ -56,7 +58,7 @@ type DebtSupplierQuery struct {
|
|||||||
|
|
||||||
type HppPerKandangQuery struct {
|
type HppPerKandangQuery 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=100,gt=0"`
|
Limit int `query:"limit" validate:"omitempty,min=1,max=1000,gt=0"`
|
||||||
Period string `query:"period" validate:"required"`
|
Period string `query:"period" validate:"required"`
|
||||||
ShowUnrecorded bool `query:"show_unrecorded"`
|
ShowUnrecorded bool `query:"show_unrecorded"`
|
||||||
AreaIDs []int64 `query:"-"`
|
AreaIDs []int64 `query:"-"`
|
||||||
@@ -68,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=100,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"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user