mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
feat[BE]: enhance expense management with location and project flock integration, including updates to migrations, entities, services, and validations
This commit is contained in:
@@ -0,0 +1,24 @@
|
|||||||
|
-- Rollback: Update expense and expense_nonstocks tables
|
||||||
|
|
||||||
|
-- Drop indexes
|
||||||
|
DROP INDEX IF EXISTS idx_expenses_project_flock_id;
|
||||||
|
DROP INDEX IF EXISTS idx_expenses_location_id;
|
||||||
|
|
||||||
|
-- Drop Foreign Key constraint
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (
|
||||||
|
SELECT 1 FROM pg_constraint
|
||||||
|
WHERE conname = 'fk_expenses_location_id'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE expenses
|
||||||
|
DROP CONSTRAINT fk_expenses_location_id;
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
-- Drop columns from expenses table
|
||||||
|
ALTER TABLE expenses
|
||||||
|
DROP COLUMN IF EXISTS project_flock_id;
|
||||||
|
|
||||||
|
ALTER TABLE expenses
|
||||||
|
DROP COLUMN IF EXISTS location_id;
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
-- Migration: Update expense and expense_nonstocks tables
|
||||||
|
|
||||||
|
-- Add location_id column to expenses table
|
||||||
|
ALTER TABLE expenses
|
||||||
|
ADD COLUMN IF NOT EXISTS location_id BIGINT NOT NULL DEFAULT 1;
|
||||||
|
|
||||||
|
-- Add project_flock_id column to expenses table (JSON type)
|
||||||
|
ALTER TABLE expenses
|
||||||
|
ADD COLUMN IF NOT EXISTS project_flock_id JSON NULL;
|
||||||
|
|
||||||
|
-- Add Foreign Key constraint to locations table
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'locations') THEN
|
||||||
|
ALTER TABLE expenses
|
||||||
|
ADD CONSTRAINT fk_expenses_location_id
|
||||||
|
FOREIGN KEY (location_id) REFERENCES locations(id) ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
-- Create index for location_id
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_expenses_location_id ON expenses (location_id);
|
||||||
|
|
||||||
|
-- Create index for project_flock_id
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_expenses_project_flock_id ON expenses ((project_flock_id::text));
|
||||||
|
|
||||||
|
-- Ensure kandang_id is nullable in expense_nonstocks table
|
||||||
|
ALTER TABLE expense_nonstocks
|
||||||
|
ALTER COLUMN kandang_id DROP NOT NULL;
|
||||||
@@ -12,6 +12,8 @@ type Expense struct {
|
|||||||
SupplierId uint64 `gorm:""`
|
SupplierId uint64 `gorm:""`
|
||||||
Category string `gorm:"type:varchar(50);not null"`
|
Category string `gorm:"type:varchar(50);not null"`
|
||||||
PoNumber string `gorm:"type:varchar(50)"`
|
PoNumber string `gorm:"type:varchar(50)"`
|
||||||
|
LocationId uint64 `gorm:"not null"`
|
||||||
|
ProjectFlockId *string `gorm:"type:json"`
|
||||||
RealizationDate time.Time `gorm:"type:date;column:realization_date"`
|
RealizationDate time.Time `gorm:"type:date;column:realization_date"`
|
||||||
TransactionDate time.Time `gorm:"type:date;not null"`
|
TransactionDate time.Time `gorm:"type:date;not null"`
|
||||||
Notes string `gorm:"type:text;column:notes"`
|
Notes string `gorm:"type:text;column:notes"`
|
||||||
@@ -21,6 +23,7 @@ type Expense struct {
|
|||||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||||
|
|
||||||
Supplier *Supplier `gorm:"foreignKey:SupplierId;references:Id"`
|
Supplier *Supplier `gorm:"foreignKey:SupplierId;references:Id"`
|
||||||
|
Location *Location `gorm:"foreignKey:LocationId;references:Id"`
|
||||||
CreatedUser *User `gorm:"foreignKey:CreatedBy;references:Id"`
|
CreatedUser *User `gorm:"foreignKey:CreatedBy;references:Id"`
|
||||||
Nonstocks []ExpenseNonstock `gorm:"foreignKey:ExpenseId;references:Id"`
|
Nonstocks []ExpenseNonstock `gorm:"foreignKey:ExpenseId;references:Id"`
|
||||||
Documents []Document `gorm:"foreignKey:DocumentableId;references:Id"`
|
Documents []Document `gorm:"foreignKey:DocumentableId;references:Id"`
|
||||||
|
|||||||
@@ -90,6 +90,12 @@ func (u *ExpenseController) CreateOne(c *fiber.Ctx) error {
|
|||||||
}
|
}
|
||||||
req.SupplierID = supplierID
|
req.SupplierID = supplierID
|
||||||
|
|
||||||
|
locationID, err := strconv.ParseUint(c.FormValue("location_id"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid location_id format")
|
||||||
|
}
|
||||||
|
req.LocationID = locationID
|
||||||
|
|
||||||
form, err := c.MultipartForm()
|
form, err := c.MultipartForm()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid multipart form")
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid multipart form")
|
||||||
@@ -106,17 +112,7 @@ func (u *ExpenseController) CreateOne(c *fiber.Ctx) error {
|
|||||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Invalid expense_nonstocks JSON: %v", err))
|
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Invalid expense_nonstocks JSON: %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
if singleExpenseNonstock.KandangID == 0 {
|
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "Field KandangID is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
req.ExpenseNonstocks = []validation.ExpenseNonstock{singleExpenseNonstock}
|
req.ExpenseNonstocks = []validation.ExpenseNonstock{singleExpenseNonstock}
|
||||||
} else {
|
|
||||||
for i, expenseNonstock := range req.ExpenseNonstocks {
|
|
||||||
if expenseNonstock.KandangID == 0 {
|
|
||||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Field KandangID is required for expense_nonstocks[%d]", i))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "Field expense_nonstocks is required")
|
return fiber.NewError(fiber.StatusBadRequest, "Field expense_nonstocks is required")
|
||||||
@@ -171,6 +167,15 @@ func (u *ExpenseController) UpdateOne(c *fiber.Ctx) error {
|
|||||||
req.SupplierID = &supplierID
|
req.SupplierID = &supplierID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
locationIDVal := c.FormValue("location_id")
|
||||||
|
if locationIDVal != "" {
|
||||||
|
locationID, err := strconv.ParseUint(locationIDVal, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid location_id format")
|
||||||
|
}
|
||||||
|
req.LocationID = &locationID
|
||||||
|
}
|
||||||
|
|
||||||
expenseNonstocksJSON := c.FormValue("expense_nonstocks")
|
expenseNonstocksJSON := c.FormValue("expense_nonstocks")
|
||||||
if expenseNonstocksJSON != "" {
|
if expenseNonstocksJSON != "" {
|
||||||
var expenseNonstocks []validation.ExpenseNonstock
|
var expenseNonstocks []validation.ExpenseNonstock
|
||||||
@@ -178,12 +183,6 @@ func (u *ExpenseController) UpdateOne(c *fiber.Ctx) error {
|
|||||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Invalid expense_nonstocks JSON: %v", err))
|
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Invalid expense_nonstocks JSON: %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, expenseNonstock := range expenseNonstocks {
|
|
||||||
if expenseNonstock.KandangID == 0 {
|
|
||||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Field KandangID is required for expense_nonstocks[%d]", i))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
req.ExpenseNonstocks = &expenseNonstocks
|
req.ExpenseNonstocks = &expenseNonstocks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -76,7 +76,6 @@ type ExpenseRealizationDTO struct {
|
|||||||
|
|
||||||
type KandangGroupDTO struct {
|
type KandangGroupDTO struct {
|
||||||
Id uint64 `json:"id"`
|
Id uint64 `json:"id"`
|
||||||
KandangId uint64 `json:"kandang_id"`
|
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
Pengajuans []ExpenseNonstockDTO `json:"pengajuans,omitempty"`
|
Pengajuans []ExpenseNonstockDTO `json:"pengajuans,omitempty"`
|
||||||
Realisasi []ExpenseRealizationDTO `json:"realisasi,omitempty"`
|
Realisasi []ExpenseRealizationDTO `json:"realisasi,omitempty"`
|
||||||
@@ -178,7 +177,6 @@ func ToExpenseDetailDTO(e *entity.Expense) ExpenseDetailDTO {
|
|||||||
var pengajuans []ExpenseNonstockDTO
|
var pengajuans []ExpenseNonstockDTO
|
||||||
var realisasi []ExpenseRealizationDTO
|
var realisasi []ExpenseRealizationDTO
|
||||||
|
|
||||||
// Map documents from Document service
|
|
||||||
for _, doc := range e.Documents {
|
for _, doc := range e.Documents {
|
||||||
documents = append(documents, DocumentDTO{
|
documents = append(documents, DocumentDTO{
|
||||||
ID: uint64(doc.Id),
|
ID: uint64(doc.Id),
|
||||||
@@ -186,7 +184,6 @@ func ToExpenseDetailDTO(e *entity.Expense) ExpenseDetailDTO {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map realization documents from Document service
|
|
||||||
for _, doc := range e.RealizationDocuments {
|
for _, doc := range e.RealizationDocuments {
|
||||||
realizationDocs = append(realizationDocs, DocumentDTO{
|
realizationDocs = append(realizationDocs, DocumentDTO{
|
||||||
ID: uint64(doc.Id),
|
ID: uint64(doc.Id),
|
||||||
@@ -271,6 +268,8 @@ func ToExpenseNonstockDTO(ns entity.ExpenseNonstock) ExpenseNonstockDTO {
|
|||||||
|
|
||||||
func ToKandangGroupDTO(pengajuans []ExpenseNonstockDTO, realisasi []ExpenseRealizationDTO, nonstocks []entity.ExpenseNonstock) []KandangGroupDTO {
|
func ToKandangGroupDTO(pengajuans []ExpenseNonstockDTO, realisasi []ExpenseRealizationDTO, nonstocks []entity.ExpenseNonstock) []KandangGroupDTO {
|
||||||
kandangMap := make(map[uint64]*KandangGroupDTO)
|
kandangMap := make(map[uint64]*KandangGroupDTO)
|
||||||
|
var directPengajuans []ExpenseNonstockDTO
|
||||||
|
var directRealisasi []ExpenseRealizationDTO
|
||||||
|
|
||||||
for _, p := range pengajuans {
|
for _, p := range pengajuans {
|
||||||
var kandangId uint64
|
var kandangId uint64
|
||||||
@@ -287,16 +286,19 @@ func ToKandangGroupDTO(pengajuans []ExpenseNonstockDTO, realisasi []ExpenseReali
|
|||||||
}
|
}
|
||||||
|
|
||||||
if kandangId > 0 {
|
if kandangId > 0 {
|
||||||
|
|
||||||
if kandangMap[kandangId] == nil {
|
if kandangMap[kandangId] == nil {
|
||||||
kandangMap[kandangId] = &KandangGroupDTO{
|
kandangMap[kandangId] = &KandangGroupDTO{
|
||||||
Id: kandangId,
|
Id: kandangId,
|
||||||
KandangId: kandangId,
|
|
||||||
Name: kandangName,
|
Name: kandangName,
|
||||||
Pengajuans: []ExpenseNonstockDTO{},
|
Pengajuans: []ExpenseNonstockDTO{},
|
||||||
Realisasi: []ExpenseRealizationDTO{},
|
Realisasi: []ExpenseRealizationDTO{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
kandangMap[kandangId].Pengajuans = append(kandangMap[kandangId].Pengajuans, p)
|
kandangMap[kandangId].Pengajuans = append(kandangMap[kandangId].Pengajuans, p)
|
||||||
|
} else {
|
||||||
|
|
||||||
|
directPengajuans = append(directPengajuans, p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -316,13 +318,24 @@ func ToKandangGroupDTO(pengajuans []ExpenseNonstockDTO, realisasi []ExpenseReali
|
|||||||
if kandangMap[kandangId] == nil {
|
if kandangMap[kandangId] == nil {
|
||||||
kandangMap[kandangId] = &KandangGroupDTO{
|
kandangMap[kandangId] = &KandangGroupDTO{
|
||||||
Id: kandangId,
|
Id: kandangId,
|
||||||
KandangId: kandangId,
|
|
||||||
Name: kandangName,
|
Name: kandangName,
|
||||||
Pengajuans: []ExpenseNonstockDTO{},
|
Pengajuans: []ExpenseNonstockDTO{},
|
||||||
Realisasi: []ExpenseRealizationDTO{},
|
Realisasi: []ExpenseRealizationDTO{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
kandangMap[kandangId].Realisasi = append(kandangMap[kandangId].Realisasi, r)
|
kandangMap[kandangId].Realisasi = append(kandangMap[kandangId].Realisasi, r)
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there are direct expenses (without kandang), add them as a special entry with id=0
|
||||||
|
if len(directPengajuans) > 0 || len(directRealisasi) > 0 {
|
||||||
|
kandangMap[0] = &KandangGroupDTO{
|
||||||
|
Id: 0,
|
||||||
|
|
||||||
|
Name: "",
|
||||||
|
Pengajuans: directPengajuans,
|
||||||
|
Realisasi: directRealisasi,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
@@ -144,11 +145,8 @@ func (s *expenseService) CreateOne(c *fiber.Ctx, req *validation.Create) (*expen
|
|||||||
|
|
||||||
supplierID := uint(req.SupplierID)
|
supplierID := uint(req.SupplierID)
|
||||||
|
|
||||||
supplierExistsFunc := func(ctx context.Context, id uint) (bool, error) {
|
|
||||||
return commonRepo.Exists[entity.Supplier](ctx, s.SupplierRepo.DB(), id)
|
|
||||||
}
|
|
||||||
if err := commonSvc.EnsureRelations(c.Context(),
|
if err := commonSvc.EnsureRelations(c.Context(),
|
||||||
commonSvc.RelationCheck{Name: "Supplier", ID: &supplierID, Exists: supplierExistsFunc},
|
commonSvc.RelationCheck{Name: "Supplier", ID: &supplierID, Exists: s.SupplierRepo.IdExists},
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -199,11 +197,47 @@ func (s *expenseService) CreateOne(c *fiber.Ctx, req *validation.Create) (*expen
|
|||||||
return fiber.NewError(fiber.StatusUnauthorized, "Failed to get actor ID from context")
|
return fiber.NewError(fiber.StatusUnauthorized, "Failed to get actor ID from context")
|
||||||
}
|
}
|
||||||
createdBy := uint64(actorID)
|
createdBy := uint64(actorID)
|
||||||
|
|
||||||
|
hasKandang := false
|
||||||
|
for _, ens := range req.ExpenseNonstocks {
|
||||||
|
if ens.KandangID != nil {
|
||||||
|
hasKandang = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var projectFlockIdJSON *string
|
||||||
|
if !hasKandang && req.Category == string(utils.ExpenseCategoryBOP) {
|
||||||
|
projectFlockRepoTx := projectFlockKandangRepo.NewProjectflockRepository(dbTransaction)
|
||||||
|
activeProjectFlocks, err := projectFlockRepoTx.GetActiveByLocationID(c.Context(), req.LocationID)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get active project flocks for location")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(activeProjectFlocks) == 0 {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "No active project flocks found for this location")
|
||||||
|
}
|
||||||
|
|
||||||
|
projectFlockIDs := make([]uint64, len(activeProjectFlocks))
|
||||||
|
for i, pf := range activeProjectFlocks {
|
||||||
|
projectFlockIDs[i] = uint64(pf.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
projectFlockIdsJSON, err := json.Marshal(projectFlockIDs)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to marshal project_flock_ids")
|
||||||
|
}
|
||||||
|
jsonStr := string(projectFlockIdsJSON)
|
||||||
|
projectFlockIdJSON = &jsonStr
|
||||||
|
}
|
||||||
|
|
||||||
expense = &entity.Expense{
|
expense = &entity.Expense{
|
||||||
ReferenceNumber: referenceNumber,
|
ReferenceNumber: referenceNumber,
|
||||||
PoNumber: req.PoNumber,
|
PoNumber: req.PoNumber,
|
||||||
Category: req.Category,
|
Category: req.Category,
|
||||||
SupplierId: req.SupplierID,
|
SupplierId: req.SupplierID,
|
||||||
|
LocationId: req.LocationID,
|
||||||
|
ProjectFlockId: projectFlockIdJSON,
|
||||||
TransactionDate: expenseDate,
|
TransactionDate: expenseDate,
|
||||||
CreatedBy: createdBy,
|
CreatedBy: createdBy,
|
||||||
}
|
}
|
||||||
@@ -216,35 +250,36 @@ func (s *expenseService) CreateOne(c *fiber.Ctx, req *validation.Create) (*expen
|
|||||||
|
|
||||||
for _, expenseNonstock := range req.ExpenseNonstocks {
|
for _, expenseNonstock := range req.ExpenseNonstocks {
|
||||||
|
|
||||||
|
isAttachingToKandang := (expenseNonstock.KandangID != nil)
|
||||||
|
|
||||||
var projectFlockKandangId *uint64
|
var projectFlockKandangId *uint64
|
||||||
|
var kandangId *uint64
|
||||||
|
|
||||||
if req.Category == string(utils.ExpenseCategoryBOP) {
|
if isAttachingToKandang {
|
||||||
|
kandangId = expenseNonstock.KandangID
|
||||||
|
|
||||||
projectFlockKandang, err := projectFlockKandangRepoTx.GetActiveByKandangID(c.Context(), uint(expenseNonstock.KandangID))
|
if req.Category == string(utils.ExpenseCategoryBOP) {
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
projectFlockKandang, err := projectFlockKandangRepoTx.GetActiveByKandangID(c.Context(), uint(*kandangId))
|
||||||
return fiber.NewError(fiber.StatusNotFound, "No active project flock kandang found for this kandang")
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return fiber.NewError(fiber.StatusNotFound, "No active project flock kandang found for this kandang")
|
||||||
|
}
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to find project flock kandang for this kandang")
|
||||||
}
|
}
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to find project flock kandang for this kandang")
|
id := uint64(projectFlockKandang.Id)
|
||||||
|
projectFlockKandangId = &id
|
||||||
}
|
}
|
||||||
id := uint64(projectFlockKandang.Id)
|
|
||||||
projectFlockKandangId = &id
|
} else {
|
||||||
|
kandangId = nil
|
||||||
|
projectFlockKandangId = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, costItem := range expenseNonstock.CostItems {
|
for _, costItem := range expenseNonstock.CostItems {
|
||||||
|
|
||||||
nonstockId := costItem.NonstockID
|
nonstockId := costItem.NonstockID
|
||||||
var kandangId *uint64
|
newExpenseNonstock := &entity.ExpenseNonstock{
|
||||||
if req.Category == string(utils.ExpenseCategoryNonBOP) {
|
|
||||||
id := uint64(expenseNonstock.KandangID)
|
|
||||||
kandangId = &id
|
|
||||||
} else if req.Category == string(utils.ExpenseCategoryBOP) {
|
|
||||||
if projectFlockKandangId != nil {
|
|
||||||
kandangId = &expenseNonstock.KandangID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
expenseNonstock := &entity.ExpenseNonstock{
|
|
||||||
ExpenseId: &expense.Id,
|
ExpenseId: &expense.Id,
|
||||||
ProjectFlockKandangId: projectFlockKandangId,
|
ProjectFlockKandangId: projectFlockKandangId,
|
||||||
KandangId: kandangId,
|
KandangId: kandangId,
|
||||||
@@ -254,7 +289,7 @@ func (s *expenseService) CreateOne(c *fiber.Ctx, req *validation.Create) (*expen
|
|||||||
Notes: costItem.Notes,
|
Notes: costItem.Notes,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := expenseNonstockRepoTx.CreateOne(c.Context(), expenseNonstock, nil); err != nil {
|
if err := expenseNonstockRepoTx.CreateOne(c.Context(), newExpenseNonstock, nil); err != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create expense cost item")
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create expense cost item")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -361,6 +396,11 @@ func (s expenseService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint)
|
|||||||
updateBody["supplier_id"] = *req.SupplierID
|
updateBody["supplier_id"] = *req.SupplierID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if req.LocationID != nil {
|
||||||
|
locationID := uint(*req.LocationID)
|
||||||
|
updateBody["location_id"] = locationID
|
||||||
|
}
|
||||||
|
|
||||||
if len(updateBody) == 0 && req.ExpenseNonstocks == nil && len(req.Documents) == 0 {
|
if len(updateBody) == 0 && req.ExpenseNonstocks == nil && len(req.Documents) == 0 {
|
||||||
|
|
||||||
responseDTO, err := s.GetOne(c, id)
|
responseDTO, err := s.GetOne(c, id)
|
||||||
@@ -475,18 +515,26 @@ func (s expenseService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint)
|
|||||||
|
|
||||||
for _, expenseNonstock := range *req.ExpenseNonstocks {
|
for _, expenseNonstock := range *req.ExpenseNonstocks {
|
||||||
var projectFlockKandangId *uint64
|
var projectFlockKandangId *uint64
|
||||||
|
var kandangId *uint64
|
||||||
|
|
||||||
if updatedExpense.Category == string(utils.ExpenseCategoryBOP) {
|
// Check if attaching to kandang
|
||||||
projectFlockKandangRepoTx := projectFlockKandangRepo.NewProjectFlockKandangRepository(tx)
|
if expenseNonstock.KandangID != nil {
|
||||||
projectFlockKandang, err := projectFlockKandangRepoTx.GetActiveByKandangID(c.Context(), uint(expenseNonstock.KandangID))
|
kandangId = expenseNonstock.KandangID
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if updatedExpense.Category == string(utils.ExpenseCategoryBOP) {
|
||||||
return fiber.NewError(fiber.StatusNotFound, "No active project flock kandang found for this kandang")
|
// BOP with kandang: Get active project flock kandang
|
||||||
|
projectFlockKandangRepoTx := projectFlockKandangRepo.NewProjectFlockKandangRepository(tx)
|
||||||
|
projectFlockKandang, err := projectFlockKandangRepoTx.GetActiveByKandangID(c.Context(), uint(*kandangId))
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return fiber.NewError(fiber.StatusNotFound, "No active project flock kandang found for this kandang")
|
||||||
|
}
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to find project flock kandang for this kandang")
|
||||||
}
|
}
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to find project flock kandang for this kandang")
|
id := uint64(projectFlockKandang.Id)
|
||||||
|
projectFlockKandangId = &id
|
||||||
}
|
}
|
||||||
id := uint64(projectFlockKandang.Id)
|
// NON-BOP: projectFlockKandangId stays nil
|
||||||
projectFlockKandangId = &id
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, costItem := range expenseNonstock.CostItems {
|
for _, costItem := range expenseNonstock.CostItems {
|
||||||
@@ -498,18 +546,8 @@ func (s expenseService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint)
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var kandangId *uint64
|
|
||||||
if updatedExpense.Category == string(utils.ExpenseCategoryNonBOP) {
|
|
||||||
id := uint64(expenseNonstock.KandangID)
|
|
||||||
kandangId = &id
|
|
||||||
} else if updatedExpense.Category == string(utils.ExpenseCategoryBOP) {
|
|
||||||
if projectFlockKandangId != nil {
|
|
||||||
kandangId = &expenseNonstock.KandangID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
expenseId := uint64(id)
|
expenseId := uint64(id)
|
||||||
expenseNonstock := &entity.ExpenseNonstock{
|
newExpenseNonstock := &entity.ExpenseNonstock{
|
||||||
ExpenseId: &expenseId,
|
ExpenseId: &expenseId,
|
||||||
ProjectFlockKandangId: projectFlockKandangId,
|
ProjectFlockKandangId: projectFlockKandangId,
|
||||||
KandangId: kandangId,
|
KandangId: kandangId,
|
||||||
@@ -519,7 +557,7 @@ func (s expenseService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint)
|
|||||||
Notes: costItem.Notes,
|
Notes: costItem.Notes,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := expenseNonstockRepoTx.CreateOne(c.Context(), expenseNonstock, nil); err != nil {
|
if err := expenseNonstockRepoTx.CreateOne(c.Context(), newExpenseNonstock, nil); err != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create expense cost item")
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create expense cost item")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,12 +9,13 @@ type Create struct {
|
|||||||
TransactionDate string `form:"transaction_date" json:"transaction_date" validate:"required,datetime=2006-01-02"`
|
TransactionDate string `form:"transaction_date" json:"transaction_date" validate:"required,datetime=2006-01-02"`
|
||||||
Category string `form:"category" json:"category" validate:"required,oneof=BOP NON-BOP"`
|
Category string `form:"category" json:"category" validate:"required,oneof=BOP NON-BOP"`
|
||||||
SupplierID uint64 `form:"supplier_id" json:"supplier_id" validate:"required,gt=0"`
|
SupplierID uint64 `form:"supplier_id" json:"supplier_id" validate:"required,gt=0"`
|
||||||
|
LocationID uint64 `form:"location_id" json:"location_id" validate:"required,gt=0"`
|
||||||
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:"required,min=1,dive"`
|
ExpenseNonstocks []ExpenseNonstock `form:"expense_nonstocks" json:"expense_nonstocks" validate:"required,min=1,dive"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExpenseNonstock struct {
|
type ExpenseNonstock struct {
|
||||||
KandangID uint64 `form:"kandang_id" json:"kandang_id" validate:"required,gt=0"`
|
KandangID *uint64 `form:"kandang_id" json:"kandang_id" validate:"omitempty"`
|
||||||
CostItems []CostItem `form:"cost_items" json:"cost_items" validate:"required,min=1,dive"`
|
CostItems []CostItem `form:"cost_items" json:"cost_items" validate:"required,min=1,dive"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,13 +23,14 @@ type CostItem struct {
|
|||||||
NonstockID uint64 `form:"nonstock_id" json:"nonstock_id" validate:"required,gt=0"`
|
NonstockID uint64 `form:"nonstock_id" json:"nonstock_id" validate:"required,gt=0"`
|
||||||
Quantity float64 `form:"quantity" json:"quantity" validate:"required,gt=0"`
|
Quantity float64 `form:"quantity" json:"quantity" validate:"required,gt=0"`
|
||||||
Price float64 `form:"price" json:"price" validate:"required,gt=0"`
|
Price float64 `form:"price" json:"price" validate:"required,gt=0"`
|
||||||
Notes string `form:"notes" json:"notes" validate:"required,max=500"`
|
Notes string `form:"notes" json:"notes" validate:"omitempty,max=500"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Update struct {
|
type Update struct {
|
||||||
TransactionDate *string `form:"transaction_date" json:"transaction_date" validate:"omitempty,datetime=2006-01-02"`
|
TransactionDate *string `form:"transaction_date" json:"transaction_date" validate:"omitempty,datetime=2006-01-02"`
|
||||||
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"`
|
||||||
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"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ type SupplierRepository interface {
|
|||||||
repository.BaseRepository[entity.Supplier]
|
repository.BaseRepository[entity.Supplier]
|
||||||
NameExists(ctx context.Context, name string, excludeID *uint) (bool, error)
|
NameExists(ctx context.Context, name string, excludeID *uint) (bool, error)
|
||||||
AliasExists(ctx context.Context, alias string, excludeID *uint) (bool, error)
|
AliasExists(ctx context.Context, alias string, excludeID *uint) (bool, error)
|
||||||
|
IdExists(ctx context.Context, id uint) (bool, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type SupplierRepositoryImpl struct {
|
type SupplierRepositoryImpl struct {
|
||||||
@@ -33,3 +34,7 @@ func (r *SupplierRepositoryImpl) NameExists(ctx context.Context, name string, ex
|
|||||||
func (r *SupplierRepositoryImpl) AliasExists(ctx context.Context, alias string, excludeID *uint) (bool, error) {
|
func (r *SupplierRepositoryImpl) AliasExists(ctx context.Context, alias string, excludeID *uint) (bool, error) {
|
||||||
return repository.ExistsByField[entity.Supplier](ctx, r.db, "alias", alias, excludeID)
|
return repository.ExistsByField[entity.Supplier](ctx, r.db, "alias", alias, excludeID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *SupplierRepositoryImpl) IdExists(ctx context.Context, id uint) (bool, error) {
|
||||||
|
return repository.Exists[entity.Supplier](ctx, r.db, id)
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ type ProjectflockRepository interface {
|
|||||||
GetNextPeriodsForKandangs(ctx context.Context, kandangIDs []uint) (map[uint]int, error)
|
GetNextPeriodsForKandangs(ctx context.Context, kandangIDs []uint) (map[uint]int, error)
|
||||||
GetCurrentProjectPeriod(ctx context.Context, projectFlockID uint) (int, error)
|
GetCurrentProjectPeriod(ctx context.Context, projectFlockID uint) (int, error)
|
||||||
GetKandangPeriodSummaryRows(ctx context.Context, locationID uint) ([]KandangPeriodRow, error)
|
GetKandangPeriodSummaryRows(ctx context.Context, locationID uint) ([]KandangPeriodRow, error)
|
||||||
|
GetActiveByLocationID(ctx context.Context, locationID uint64) ([]entity.ProjectFlock, error)
|
||||||
AreaExists(ctx context.Context, id uint) (bool, error)
|
AreaExists(ctx context.Context, id uint) (bool, error)
|
||||||
FcrExists(ctx context.Context, id uint) (bool, error)
|
FcrExists(ctx context.Context, id uint) (bool, error)
|
||||||
LocationExists(ctx context.Context, id uint) (bool, error)
|
LocationExists(ctx context.Context, id uint) (bool, error)
|
||||||
@@ -295,3 +296,17 @@ func (r *ProjectflockRepositoryImpl) ExistsByFlockName(ctx context.Context, floc
|
|||||||
}
|
}
|
||||||
return count > 0, nil
|
return count > 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *ProjectflockRepositoryImpl) GetActiveByLocationID(ctx context.Context, locationID uint64) ([]entity.ProjectFlock, error) {
|
||||||
|
var projectFlocks []entity.ProjectFlock
|
||||||
|
err := r.DB().WithContext(ctx).
|
||||||
|
Joins("JOIN project_flock_kandangs ON project_flock_kandangs.project_flock_id = project_flocks.id").
|
||||||
|
Where("project_flocks.location_id = ?", locationID).
|
||||||
|
Where("project_flock_kandangs.closed_at IS NULL").
|
||||||
|
Group("project_flocks.id").
|
||||||
|
Find(&projectFlocks).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return projectFlocks, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -571,7 +571,7 @@ func (b *expenseBridge) createExpenseViaService(
|
|||||||
Category: "BOP",
|
Category: "BOP",
|
||||||
SupplierID: uint64(supplierID),
|
SupplierID: uint64(supplierID),
|
||||||
ExpenseNonstocks: []expenseValidation.ExpenseNonstock{{
|
ExpenseNonstocks: []expenseValidation.ExpenseNonstock{{
|
||||||
KandangID: uint64(*kandangID),
|
KandangID: func() *uint64 { id := uint64(*kandangID); return &id }(),
|
||||||
CostItems: costItems,
|
CostItems: costItems,
|
||||||
}},
|
}},
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user