mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
(BE-58,,59): extend db schema & build stock transfer api
- Extend DB schema for stock transfers - Build stock transfer API (create,)
This commit is contained in:
@@ -0,0 +1,4 @@
|
|||||||
|
-- DROP TABLE: STOCK_TRANSFERS DAN SEQUENCE-NYA
|
||||||
|
DROP TABLE IF EXISTS stock_transfers CASCADE;
|
||||||
|
|
||||||
|
DROP SEQUENCE IF EXISTS stock_transfer_seq CASCADE;
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
-- ===============================================================
|
||||||
|
-- STOCK TRANSFERS (HEADER)
|
||||||
|
-- ===============================================================
|
||||||
|
|
||||||
|
CREATE SEQUENCE IF NOT EXISTS stock_transfer_seq START 1;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS stock_transfers (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
movement_number VARCHAR(50) UNIQUE NOT NULL,
|
||||||
|
from_warehouse_id BIGINT NOT NULL,
|
||||||
|
to_warehouse_id BIGINT NOT NULL,
|
||||||
|
area_id BIGINT,
|
||||||
|
reason TEXT,
|
||||||
|
transfer_date DATE NOT NULL,
|
||||||
|
created_by BIGINT,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
deleted_at TIMESTAMPTZ
|
||||||
|
);
|
||||||
|
|
||||||
|
-- FOREIGN KEYS (dijalankan setelah semua tabel parent ada)
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'warehouses') THEN
|
||||||
|
ALTER TABLE stock_transfers
|
||||||
|
ADD CONSTRAINT fk_stock_transfers_from_warehouse
|
||||||
|
FOREIGN KEY (from_warehouse_id)
|
||||||
|
REFERENCES warehouses(id)
|
||||||
|
ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
ALTER TABLE stock_transfers
|
||||||
|
ADD CONSTRAINT fk_stock_transfers_to_warehouse
|
||||||
|
FOREIGN KEY (to_warehouse_id)
|
||||||
|
REFERENCES warehouses(id)
|
||||||
|
ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'areas') THEN
|
||||||
|
ALTER TABLE stock_transfers
|
||||||
|
ADD CONSTRAINT fk_stock_transfers_area
|
||||||
|
FOREIGN KEY (area_id)
|
||||||
|
REFERENCES areas(id)
|
||||||
|
ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'users') THEN
|
||||||
|
ALTER TABLE stock_transfers
|
||||||
|
ADD CONSTRAINT fk_stock_transfers_created_by
|
||||||
|
FOREIGN KEY (created_by)
|
||||||
|
REFERENCES users(id)
|
||||||
|
ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
-- INDEXES
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_stock_transfers_from_warehouse_id ON stock_transfers(from_warehouse_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_stock_transfers_to_warehouse_id ON stock_transfers(to_warehouse_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_stock_transfers_transfer_date ON stock_transfers(transfer_date);
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- DROP TABLE: STOCK_TRANSFER_DETAILS
|
||||||
|
DROP TABLE IF EXISTS stock_transfer_details CASCADE;
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
-- ===============================================================
|
||||||
|
-- STOCK TRANSFER DETAILS (PRODUK)
|
||||||
|
-- ===============================================================
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS stock_transfer_details (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
stock_transfer_id BIGINT NOT NULL,
|
||||||
|
product_id BIGINT NOT NULL,
|
||||||
|
quantity NUMERIC(15, 3) NOT NULL CHECK (quantity > 0),
|
||||||
|
before_quantity NUMERIC(15, 3),
|
||||||
|
after_quantity NUMERIC(15, 3),
|
||||||
|
note TEXT,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
deleted_at TIMESTAMPTZ
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ===============================================================
|
||||||
|
-- FOREIGN KEYS (dengan pengecekan tabel agar anti gagal)
|
||||||
|
-- ===============================================================
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'stock_transfers') THEN
|
||||||
|
EXECUTE
|
||||||
|
'ALTER TABLE stock_transfer_details
|
||||||
|
ADD CONSTRAINT fk_stock_transfer_details_transfer
|
||||||
|
FOREIGN KEY (stock_transfer_id)
|
||||||
|
REFERENCES stock_transfers(id)
|
||||||
|
ON DELETE CASCADE ON UPDATE CASCADE';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'products') THEN
|
||||||
|
EXECUTE
|
||||||
|
'ALTER TABLE stock_transfer_details
|
||||||
|
ADD CONSTRAINT fk_stock_transfer_details_product
|
||||||
|
FOREIGN KEY (product_id)
|
||||||
|
REFERENCES products(id)
|
||||||
|
ON DELETE RESTRICT ON UPDATE CASCADE';
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
-- ===============================================================
|
||||||
|
-- INDEXES
|
||||||
|
-- ===============================================================
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_stock_transfer_details_transfer_id ON stock_transfer_details (stock_transfer_id);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_stock_transfer_details_product_id ON stock_transfer_details (product_id);
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- DROP TABLE: STOCK_TRANSFER_DELIVERIES
|
||||||
|
DROP TABLE IF EXISTS stock_transfer_deliveries CASCADE;
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
-- ===============================================================
|
||||||
|
-- STOCK TRANSFER DELIVERIES (EKSPEDISI)
|
||||||
|
-- ===============================================================
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS stock_transfer_deliveries (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
stock_transfer_id BIGINT NOT NULL,
|
||||||
|
supplier_id BIGINT,
|
||||||
|
vehicle_plate VARCHAR(20),
|
||||||
|
driver_name VARCHAR(100),
|
||||||
|
document_number VARCHAR(50),
|
||||||
|
document_path TEXT,
|
||||||
|
shipping_cost_item NUMERIC(15,3),
|
||||||
|
shipping_cost_total NUMERIC(15,3),
|
||||||
|
note TEXT,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
deleted_at TIMESTAMPTZ
|
||||||
|
);
|
||||||
|
|
||||||
|
-- FOREIGN KEYS
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'stock_transfers') THEN
|
||||||
|
ALTER TABLE stock_transfer_deliveries
|
||||||
|
ADD CONSTRAINT fk_stock_transfer_deliveries_transfer
|
||||||
|
FOREIGN KEY (stock_transfer_id)
|
||||||
|
REFERENCES stock_transfers(id)
|
||||||
|
ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'suppliers') THEN
|
||||||
|
ALTER TABLE stock_transfer_deliveries
|
||||||
|
ADD CONSTRAINT fk_stock_transfer_deliveries_supplier
|
||||||
|
FOREIGN KEY (supplier_id)
|
||||||
|
REFERENCES suppliers(id)
|
||||||
|
ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
-- INDEXES
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_stock_transfer_deliveries_transfer_id ON stock_transfer_deliveries(stock_transfer_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_stock_transfer_deliveries_supplier_id ON stock_transfer_deliveries(supplier_id);
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
-- DROP PIVOT TABLE: STOCK_TRANSFER_DELIVERY_ITEMS
|
||||||
|
DROP TABLE IF EXISTS stock_transfer_delivery_items CASCADE;
|
||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
-- ===============================================================
|
||||||
|
-- STOCK TRANSFER DELIVERY ITEMS (PIVOT)
|
||||||
|
-- ===============================================================
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS stock_transfer_delivery_items (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
stock_transfer_delivery_id BIGINT NOT NULL,
|
||||||
|
stock_transfer_detail_id BIGINT NOT NULL,
|
||||||
|
quantity NUMERIC(15, 3) NOT NULL CHECK (quantity > 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- FOREIGN KEYS
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'stock_transfer_deliveries') THEN
|
||||||
|
ALTER TABLE stock_transfer_delivery_items
|
||||||
|
ADD CONSTRAINT fk_delivery_items_delivery
|
||||||
|
FOREIGN KEY (stock_transfer_delivery_id)
|
||||||
|
REFERENCES stock_transfer_deliveries(id)
|
||||||
|
ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'stock_transfer_details') THEN
|
||||||
|
ALTER TABLE stock_transfer_delivery_items
|
||||||
|
ADD CONSTRAINT fk_delivery_items_detail
|
||||||
|
FOREIGN KEY (stock_transfer_detail_id)
|
||||||
|
REFERENCES stock_transfer_details(id)
|
||||||
|
ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
-- INDEXES
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_stock_transfer_delivery_items_delivery_id ON stock_transfer_delivery_items (stock_transfer_delivery_id);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_stock_transfer_delivery_items_detail_id ON stock_transfer_delivery_items (stock_transfer_detail_id);
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package entities
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// HEADER
|
||||||
|
type StockTransfer struct {
|
||||||
|
Id uint64 `gorm:"primaryKey;autoIncrement"`
|
||||||
|
MovementNumber string `gorm:"uniqueIndex;not null"`
|
||||||
|
FromWarehouseId uint64
|
||||||
|
ToWarehouseId uint64
|
||||||
|
TransferDate time.Time
|
||||||
|
Reason string
|
||||||
|
CreatedBy uint64
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
|
DeletedAt *time.Time `gorm:"index"`
|
||||||
|
// Relations
|
||||||
|
FromWarehouse *Warehouse `gorm:"foreignKey:FromWarehouseId"`
|
||||||
|
ToWarehouse *Warehouse `gorm:"foreignKey:ToWarehouseId"`
|
||||||
|
Details []StockTransferDetail `gorm:"foreignKey:StockTransferId"`
|
||||||
|
Deliveries []StockTransferDelivery `gorm:"foreignKey:StockTransferId"`
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
LogTypeAdjustment = "ADJUSTMENT"
|
LogTypeAdjustment = "ADJUSTMENT"
|
||||||
|
LogTypeTransfer = "TRANSFER"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package entities
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// DETAIL EKSPEDISI
|
||||||
|
type StockTransferDelivery struct {
|
||||||
|
Id uint64 `gorm:"primaryKey;autoIncrement"`
|
||||||
|
StockTransferId uint64
|
||||||
|
SupplierId uint64
|
||||||
|
VehiclePlate string
|
||||||
|
DriverName string
|
||||||
|
DocumentNumber string
|
||||||
|
DocumentPath string
|
||||||
|
ShippingCostItem float64
|
||||||
|
ShippingCostTotal float64
|
||||||
|
Note string
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
|
DeletedAt *time.Time `gorm:"index"`
|
||||||
|
// Relations
|
||||||
|
StockTransfer *StockTransfer `gorm:"foreignKey:StockTransferId"`
|
||||||
|
Supplier *Supplier `gorm:"foreignKey:SupplierId"`
|
||||||
|
Items []StockTransferDeliveryItem `gorm:"foreignKey:StockTransferDeliveryId"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package entities
|
||||||
|
|
||||||
|
// PIVOT TABLE TRANSFER
|
||||||
|
type StockTransferDeliveryItem struct {
|
||||||
|
Id uint64 `gorm:"primaryKey;autoIncrement"`
|
||||||
|
StockTransferDeliveryId uint64
|
||||||
|
StockTransferDetailId uint64
|
||||||
|
Quantity float64
|
||||||
|
// Relations
|
||||||
|
StockTransferDelivery *StockTransferDelivery `gorm:"foreignKey:StockTransferDeliveryId"`
|
||||||
|
StockTransferDetail *StockTransferDetail `gorm:"foreignKey:StockTransferDetailId"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package entities
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
|
||||||
|
// DETAIL PRODUK
|
||||||
|
type StockTransferDetail struct {
|
||||||
|
Id uint64 `gorm:"primaryKey;autoIncrement"`
|
||||||
|
StockTransferId uint64
|
||||||
|
ProductId uint64
|
||||||
|
Quantity float64
|
||||||
|
BeforeQuantity float64
|
||||||
|
AfterQuantity float64
|
||||||
|
Note string
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
|
DeletedAt *time.Time `gorm:"index"`
|
||||||
|
// Relations
|
||||||
|
StockTransfer *StockTransfer `gorm:"foreignKey:StockTransferId"`
|
||||||
|
Product *Product `gorm:"foreignKey:ProductId"`
|
||||||
|
DeliveryItems []StockTransferDeliveryItem `gorm:"foreignKey:StockTransferDetailId"`
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
productWarehouses "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses"
|
productWarehouses "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses"
|
||||||
adjustments "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/adjustments"
|
adjustments "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/adjustments"
|
||||||
|
transfers "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/transfers"
|
||||||
// MODULE IMPORTS
|
// MODULE IMPORTS
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -19,6 +20,7 @@ func RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Valida
|
|||||||
productWarehouses.ProductWarehouseModule{},
|
productWarehouses.ProductWarehouseModule{},
|
||||||
|
|
||||||
adjustments.AdjustmentModule{},
|
adjustments.AdjustmentModule{},
|
||||||
|
transfers.TransferModule{},
|
||||||
// MODULE REGISTRY
|
// MODULE REGISTRY
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/transfers/dto"
|
||||||
|
service "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/transfers/services"
|
||||||
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/transfers/validations"
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/response"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TransferController struct {
|
||||||
|
TransferService service.TransferService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTransferController(transferService service.TransferService) *TransferController {
|
||||||
|
return &TransferController{
|
||||||
|
TransferService: transferService,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *TransferController) GetAll(c *fiber.Ctx) error {
|
||||||
|
query := &validation.Query{
|
||||||
|
Page: c.QueryInt("page", 1),
|
||||||
|
Limit: c.QueryInt("limit", 10),
|
||||||
|
Search: c.Query("search", ""),
|
||||||
|
}
|
||||||
|
|
||||||
|
result, totalResults, err := u.TransferService.GetAll(c, query)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).
|
||||||
|
JSON(response.SuccessWithPaginate[dto.TransferListDTO]{
|
||||||
|
Code: fiber.StatusOK,
|
||||||
|
Status: "success",
|
||||||
|
Message: "Get all transfers successfully",
|
||||||
|
Meta: response.Meta{
|
||||||
|
Page: query.Page,
|
||||||
|
Limit: query.Limit,
|
||||||
|
TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))),
|
||||||
|
TotalResults: totalResults,
|
||||||
|
},
|
||||||
|
Data: dto.ToTransferListDTOs(result),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *TransferController) GetOne(c *fiber.Ctx) error {
|
||||||
|
param := c.Params("id")
|
||||||
|
|
||||||
|
id, err := strconv.Atoi(param)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid Id")
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := u.TransferService.GetOne(c, uint(id))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).
|
||||||
|
JSON(response.Success{
|
||||||
|
Code: fiber.StatusOK,
|
||||||
|
Status: "success",
|
||||||
|
Message: "Get transfer successfully",
|
||||||
|
Data: dto.ToTransferListDTO(*result),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *TransferController) CreateOne(c *fiber.Ctx) error {
|
||||||
|
var req validation.TransferRequest
|
||||||
|
if err := c.BodyParser(&req); err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := u.TransferService.CreateOne(c, &req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusCreated).
|
||||||
|
JSON(response.Success{
|
||||||
|
Code: fiber.StatusCreated,
|
||||||
|
Status: "success",
|
||||||
|
Message: "Create transfer successfully",
|
||||||
|
Data: dto.ToTransferListDTO(*result),
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package dto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
|
)
|
||||||
|
|
||||||
|
// === DTO Structs ===
|
||||||
|
|
||||||
|
type TransferBaseDTO struct {
|
||||||
|
Id uint64 `json:"id"`
|
||||||
|
TransferReason string `json:"transfer_reason"`
|
||||||
|
TransferDate string `json:"transfer_date"`
|
||||||
|
SourceWarehouseId uint64 `json:"source_warehouse_id"`
|
||||||
|
DestinationWarehouseId uint64 `json:"destination_warehouse_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TransferListDTO struct {
|
||||||
|
TransferBaseDTO
|
||||||
|
CreatedBy uint64 `json:"created_by"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TransferDetailDTO struct {
|
||||||
|
TransferListDTO
|
||||||
|
// Tambahkan detail produk, deliveries, dsb jika perlu
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Mapper Functions ===
|
||||||
|
|
||||||
|
func ToTransferBaseDTO(e entity.StockTransfer) TransferBaseDTO {
|
||||||
|
return TransferBaseDTO{
|
||||||
|
Id: e.Id,
|
||||||
|
TransferReason: e.Reason, // atau field lain sesuai entity
|
||||||
|
TransferDate: e.CreatedAt.Format("2006-01-02"),
|
||||||
|
SourceWarehouseId: e.FromWarehouseId,
|
||||||
|
DestinationWarehouseId: e.ToWarehouseId,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToTransferListDTO(e entity.StockTransfer) TransferListDTO {
|
||||||
|
return TransferListDTO{
|
||||||
|
TransferBaseDTO: ToTransferBaseDTO(e),
|
||||||
|
CreatedBy: e.CreatedBy,
|
||||||
|
CreatedAt: e.CreatedAt,
|
||||||
|
UpdatedAt: e.UpdatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToTransferListDTOs(e []entity.StockTransfer) []TransferListDTO {
|
||||||
|
result := make([]TransferListDTO, len(e))
|
||||||
|
for i, r := range e {
|
||||||
|
result[i] = ToTransferListDTO(r)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToTransferDetailDTO(e entity.StockTransfer) TransferDetailDTO {
|
||||||
|
return TransferDetailDTO{
|
||||||
|
TransferListDTO: ToTransferListDTO(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package transfers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
|
||||||
|
rProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
||||||
|
rStockTransfer "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/transfers/repositories"
|
||||||
|
sTransfer "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/transfers/services"
|
||||||
|
rStockLogs "gitlab.com/mbugroup/lti-api.git/internal/modules/shared/stock-logs/repositories"
|
||||||
|
rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories"
|
||||||
|
sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TransferModule struct{}
|
||||||
|
|
||||||
|
func (TransferModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) {
|
||||||
|
stockTransferRepo := rStockTransfer.NewStockTransferRepository(db)
|
||||||
|
stockTransferDetailRepo := rStockTransfer.NewStockTransferDetailRepository(db)
|
||||||
|
stockTransferDeliveryRepo := rStockTransfer.NewStockTransferDeliveryRepository(db)
|
||||||
|
StockTransferDeliveryItemRepo := rStockTransfer.NewStockTransferDeliveryItemRepository(db)
|
||||||
|
stockLogsRepo := rStockLogs.NewStockLogRepository(db)
|
||||||
|
productWarehouseRepo := rProductWarehouse.NewProductWarehouseRepository(db)
|
||||||
|
userRepo := rUser.NewUserRepository(db)
|
||||||
|
|
||||||
|
transferService := sTransfer.NewTransferService(validate, stockTransferRepo, stockTransferDetailRepo, stockTransferDeliveryRepo, StockTransferDeliveryItemRepo, stockLogsRepo, productWarehouseRepo)
|
||||||
|
userService := sUser.NewUserService(userRepo, validate)
|
||||||
|
|
||||||
|
TransferRoutes(router, userService, transferService)
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package repositories
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||||
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StockTransferRepository interface {
|
||||||
|
repository.BaseRepository[entity.StockTransfer]
|
||||||
|
// get sequence for movement number
|
||||||
|
GetNextMovementNumber(ctx context.Context) (int64, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type StockTransferRepositoryImpl struct {
|
||||||
|
*repository.BaseRepositoryImpl[entity.StockTransfer]
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStockTransferRepository(db *gorm.DB) StockTransferRepository {
|
||||||
|
return &StockTransferRepositoryImpl{
|
||||||
|
BaseRepositoryImpl: repository.NewBaseRepository[entity.StockTransfer](db),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *StockTransferRepositoryImpl) GetNextMovementNumber(ctx context.Context) (int64, error) {
|
||||||
|
var seq int64
|
||||||
|
err := r.DB().WithContext(ctx).Raw("SELECT nextval('stock_transfer_seq')").Scan(&seq).Error
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return seq, nil
|
||||||
|
}
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
package repositories
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||||
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StockTransferDeliveryRepository interface {
|
||||||
|
repository.BaseRepository[entity.StockTransferDelivery]
|
||||||
|
// Tambahkan custom method jika perlu
|
||||||
|
}
|
||||||
|
|
||||||
|
type StockTransferDeliveryRepositoryImpl struct {
|
||||||
|
*repository.BaseRepositoryImpl[entity.StockTransferDelivery]
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStockTransferDeliveryRepository(db *gorm.DB) StockTransferDeliveryRepository {
|
||||||
|
return &StockTransferDeliveryRepositoryImpl{
|
||||||
|
BaseRepositoryImpl: repository.NewBaseRepository[entity.StockTransferDelivery](db),
|
||||||
|
}
|
||||||
|
}
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
package repositories
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||||
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StockTransferDeliveryItemRepository interface {
|
||||||
|
repository.BaseRepository[entity.StockTransferDeliveryItem]
|
||||||
|
// Tambahkan custom method jika perlu
|
||||||
|
}
|
||||||
|
|
||||||
|
type StockTransferDeliveryItemRepositoryImpl struct {
|
||||||
|
*repository.BaseRepositoryImpl[entity.StockTransferDeliveryItem]
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStockTransferDeliveryItemRepository(db *gorm.DB) StockTransferDeliveryItemRepository {
|
||||||
|
return &StockTransferDeliveryItemRepositoryImpl{
|
||||||
|
BaseRepositoryImpl: repository.NewBaseRepository[entity.StockTransferDeliveryItem](db),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package repositories
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||||
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StockTransferDetailRepository interface {
|
||||||
|
repository.BaseRepository[entity.StockTransferDetail]
|
||||||
|
// Tambahkan custom method jika perlu
|
||||||
|
}
|
||||||
|
|
||||||
|
type StockTransferDetailRepositoryImpl struct {
|
||||||
|
*repository.BaseRepositoryImpl[entity.StockTransferDetail]
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStockTransferDetailRepository(db *gorm.DB) StockTransferDetailRepository {
|
||||||
|
return &StockTransferDetailRepositoryImpl{
|
||||||
|
BaseRepositoryImpl: repository.NewBaseRepository[entity.StockTransferDetail](db),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package transfers
|
||||||
|
|
||||||
|
import (
|
||||||
|
// m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||||
|
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/transfers/controllers"
|
||||||
|
transfer "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/transfers/services"
|
||||||
|
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TransferRoutes(v1 fiber.Router, u user.UserService, s transfer.TransferService) {
|
||||||
|
ctrl := controller.NewTransferController(s)
|
||||||
|
|
||||||
|
route := v1.Group("/transfers")
|
||||||
|
|
||||||
|
// route.Get("/", m.Auth(u), ctrl.GetAll)
|
||||||
|
// route.Post("/", m.Auth(u), ctrl.CreateOne)
|
||||||
|
// route.Get("/:id", m.Auth(u), ctrl.GetOne)
|
||||||
|
// route.Patch("/:id", m.Auth(u), ctrl.UpdateOne)
|
||||||
|
// route.Delete("/:id", m.Auth(u), ctrl.DeleteOne)
|
||||||
|
|
||||||
|
route.Get("/", ctrl.GetAll)
|
||||||
|
route.Post("/", ctrl.CreateOne)
|
||||||
|
route.Get("/:id", ctrl.GetOne)
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,240 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
|
rProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
||||||
|
rStockTransfer "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/transfers/repositories"
|
||||||
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/transfers/validations"
|
||||||
|
rStockLogs "gitlab.com/mbugroup/lti-api.git/internal/modules/shared/stock-logs/repositories"
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
|
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TransferService interface {
|
||||||
|
GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.StockTransfer, int64, error)
|
||||||
|
GetOne(ctx *fiber.Ctx, id uint) (*entity.StockTransfer, error)
|
||||||
|
CreateOne(ctx *fiber.Ctx, req *validation.TransferRequest) (*entity.StockTransfer, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type transferService struct {
|
||||||
|
Log *logrus.Logger
|
||||||
|
Validate *validator.Validate
|
||||||
|
StockTransferRepo rStockTransfer.StockTransferRepository
|
||||||
|
StockTransferDetailRepo rStockTransfer.StockTransferDetailRepository
|
||||||
|
StockTransferDeliveryRepo rStockTransfer.StockTransferDeliveryRepository
|
||||||
|
StockTransferDeliveryItemRepo rStockTransfer.StockTransferDeliveryItemRepository
|
||||||
|
StockLogsRepository rStockLogs.StockLogRepository
|
||||||
|
ProductWarehouseRepo rProductWarehouse.ProductWarehouseRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTransferService(validate *validator.Validate, stockTransferRepo rStockTransfer.StockTransferRepository, stockTransferDetailRepo rStockTransfer.StockTransferDetailRepository, stockTransferDeliveryRepo rStockTransfer.StockTransferDeliveryRepository, stockTransferDeliveryItemRepo rStockTransfer.StockTransferDeliveryItemRepository, stockLogsRepo rStockLogs.StockLogRepository, productWarehouseRepo rProductWarehouse.ProductWarehouseRepository) TransferService {
|
||||||
|
return &transferService{
|
||||||
|
Log: utils.Log,
|
||||||
|
Validate: validate,
|
||||||
|
StockTransferRepo: stockTransferRepo,
|
||||||
|
StockTransferDetailRepo: stockTransferDetailRepo,
|
||||||
|
StockTransferDeliveryRepo: stockTransferDeliveryRepo,
|
||||||
|
StockTransferDeliveryItemRepo: stockTransferDeliveryItemRepo,
|
||||||
|
StockLogsRepository: stockLogsRepo,
|
||||||
|
ProductWarehouseRepo: productWarehouseRepo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s transferService) withRelations(db *gorm.DB) *gorm.DB {
|
||||||
|
return db.Preload("CreatedUser")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s transferService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.StockTransfer, int64, error) {
|
||||||
|
if err := s.Validate.Struct(params); err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// offset := (params.Page - 1) * params.Limit
|
||||||
|
|
||||||
|
// transfers, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB {
|
||||||
|
// db = s.withRelations(db)
|
||||||
|
// if params.Search != "" {
|
||||||
|
// return db.Where("name LIKE ?", "%"+params.Search+"%")
|
||||||
|
// }
|
||||||
|
// return db.Order("created_at DESC").Order("updated_at DESC")
|
||||||
|
// })
|
||||||
|
|
||||||
|
// if err != nil {
|
||||||
|
// s.Log.Errorf("Failed to get transfers: %+v", err)
|
||||||
|
// return nil, 0, err
|
||||||
|
// }
|
||||||
|
return nil, 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s transferService) GetOne(c *fiber.Ctx, id uint) (*entity.StockTransfer, error) {
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferRequest) (*entity.StockTransfer, error) {
|
||||||
|
if err := s.Validate.Struct(req); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validasi stok di gudang asal
|
||||||
|
for _, product := range req.Products {
|
||||||
|
sourcePW, err := s.ProductWarehouseRepo.GetProductWarehouseByProductAndWarehouseID(
|
||||||
|
c.Context(), uint(product.ProductID), uint(req.SourceWarehouseID),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
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.StatusInternalServerError, "Gagal cek stok produk di gudang asal")
|
||||||
|
}
|
||||||
|
if sourcePW.Quantity < product.ProductQty {
|
||||||
|
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Stok produk %d di gudang asal tidak cukup", product.ProductID))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate movement number
|
||||||
|
// Format: PND-MBU-00001
|
||||||
|
seqNum, err := s.StockTransferRepo.GetNextMovementNumber(c.Context())
|
||||||
|
if err != nil {
|
||||||
|
s.Log.Errorf("Failed to get next movement number: %+v", err)
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to generate movement number")
|
||||||
|
}
|
||||||
|
movementNumber := fmt.Sprintf("PND-MBU-%05d", seqNum)
|
||||||
|
transferDate, _ := utils.ParseDateString(req.TransferDate)
|
||||||
|
|
||||||
|
entityTransfer := &entity.StockTransfer{
|
||||||
|
FromWarehouseId: uint64(req.SourceWarehouseID),
|
||||||
|
ToWarehouseId: uint64(req.DestinationWarehouseID),
|
||||||
|
Reason: req.TransferReason,
|
||||||
|
TransferDate: transferDate,
|
||||||
|
MovementNumber: movementNumber,
|
||||||
|
CreatedBy: 1, //todo: get from token
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the transfer entity to the database
|
||||||
|
err = s.StockTransferRepo.DB().WithContext(c.Context()).Transaction(func(tx *gorm.DB) error {
|
||||||
|
|
||||||
|
// Insert header
|
||||||
|
if err := s.StockTransferRepo.WithTx(tx).CreateOne(c.Context(), entityTransfer, nil); err != nil {
|
||||||
|
s.Log.Errorf("Failed to create stock transfer: %+v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.Log.Infof("Stock transfer created: %+v", entityTransfer.Id)
|
||||||
|
|
||||||
|
// insert ke details
|
||||||
|
var details []*entity.StockTransferDetail
|
||||||
|
for _, product := range req.Products {
|
||||||
|
details = append(details, &entity.StockTransferDetail{
|
||||||
|
StockTransferId: entityTransfer.Id,
|
||||||
|
ProductId: uint64(product.ProductID),
|
||||||
|
Quantity: product.ProductQty,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if err := s.StockTransferDetailRepo.WithTx(tx).CreateMany(c.Context(), details, nil); err != nil {
|
||||||
|
s.Log.Errorf("Failed to create stock transfer details: %+v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.Log.Infof("Stock transfer details created for transfer ID: %+v", entityTransfer.Id)
|
||||||
|
|
||||||
|
// Tambahkan proses insert delivery
|
||||||
|
var deliveries []*entity.StockTransferDelivery
|
||||||
|
for _, delivery := range req.Deliveries {
|
||||||
|
deliveries = append(deliveries, &entity.StockTransferDelivery{
|
||||||
|
StockTransferId: entityTransfer.Id,
|
||||||
|
SupplierId: uint64(delivery.SupplierID),
|
||||||
|
VehiclePlate: delivery.VehiclePlate,
|
||||||
|
DriverName: delivery.DriverName,
|
||||||
|
DocumentPath: delivery.Document,
|
||||||
|
ShippingCostItem: delivery.DeliveryCostPerItem,
|
||||||
|
ShippingCostTotal: delivery.DeliveryCost,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if err := s.StockTransferDeliveryRepo.WithTx(tx).CreateMany(c.Context(), deliveries, nil); err != nil {
|
||||||
|
s.Log.Errorf("Failed to create stock transfer deliveries: %+v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// tambahkan insert ke delivery items sebagai fivot
|
||||||
|
var deliveryItems []*entity.StockTransferDeliveryItem
|
||||||
|
for i, delivery := range req.Deliveries {
|
||||||
|
for _, item := range delivery.Products {
|
||||||
|
deliveryItems = append(deliveryItems, &entity.StockTransferDeliveryItem{
|
||||||
|
StockTransferDeliveryId: deliveries[i].Id,
|
||||||
|
StockTransferDetailId: uint64(item.ProductID),
|
||||||
|
Quantity: item.ProductQty,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := s.StockTransferDeliveryItemRepo.WithTx(tx).CreateMany(c.Context(), deliveryItems, nil); err != nil {
|
||||||
|
s.Log.Errorf("Failed to create stock transfer delivery items: %+v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.Log.Infof("Stock transfer delivery items created for transfer ID: %+v", entityTransfer.Id)
|
||||||
|
|
||||||
|
// Proses pengurangan stok di gudang asal dan penambahan stok di gudang tujuan
|
||||||
|
for _, product := range req.Products {
|
||||||
|
// Kurangi stok di gudang asal
|
||||||
|
sourcePW, err := s.ProductWarehouseRepo.GetProductWarehouseByProductAndWarehouseID(c.Context(), uint(product.ProductID), uint(req.SourceWarehouseID))
|
||||||
|
if err != nil {
|
||||||
|
s.Log.Errorf("Failed to get source product warehouse: %+v", err)
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get source product warehouse")
|
||||||
|
}
|
||||||
|
if sourcePW.Quantity < product.ProductQty {
|
||||||
|
s.Log.Errorf("Insufficient stock in source warehouse for product ID: %+v", product.ProductID)
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Insufficient stock in source warehouse for product ID: %d", product.ProductID))
|
||||||
|
}
|
||||||
|
sourcePW.Quantity -= product.ProductQty
|
||||||
|
if err := s.ProductWarehouseRepo.WithTx(tx).UpdateOne(c.Context(), sourcePW.Id, sourcePW, nil); err != nil {
|
||||||
|
s.Log.Errorf("Failed to update source product warehouse: %+v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.Log.Infof("Source product warehouse updated: %+v", sourcePW.Id)
|
||||||
|
|
||||||
|
// Tambah stok di gudang tujuan
|
||||||
|
destPW, err := s.ProductWarehouseRepo.GetProductWarehouseByProductAndWarehouseID(
|
||||||
|
c.Context(), uint(product.ProductID), uint(req.DestinationWarehouseID),
|
||||||
|
)
|
||||||
|
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
s.Log.Errorf("Failed to get destination product warehouse: %+v", err)
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get destination product warehouse")
|
||||||
|
}
|
||||||
|
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
// Jika belum ada record untuk produk di gudang tujuan, buat baru
|
||||||
|
destPW = &entity.ProductWarehouse{
|
||||||
|
ProductId: uint(product.ProductID),
|
||||||
|
WarehouseId: uint(req.DestinationWarehouseID),
|
||||||
|
Quantity: 0,
|
||||||
|
CreatedBy: 1, // TODO: should Get from auth middleware
|
||||||
|
}
|
||||||
|
if err := s.ProductWarehouseRepo.WithTx(tx).CreateOne(c.Context(), destPW, nil); err != nil {
|
||||||
|
s.Log.Errorf("Failed to create destination product warehouse: %+v", err)
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create destination product warehouse")
|
||||||
|
}
|
||||||
|
s.Log.Infof("Destination product warehouse created: %+v", destPW.Id)
|
||||||
|
}
|
||||||
|
destPW.Quantity += product.ProductQty
|
||||||
|
if err := s.ProductWarehouseRepo.WithTx(tx).UpdateOne(c.Context(), destPW.Id, destPW, nil); err != nil {
|
||||||
|
s.Log.Errorf("Failed to update destination product warehouse: %+v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.Log.Infof("Destination product warehouse updated: %+v", destPW.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
s.Log.Errorf("Transaction failed in CreateOne: %+v", err)
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to process transfer transaction")
|
||||||
|
}
|
||||||
|
|
||||||
|
return entityTransfer, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package validation
|
||||||
|
|
||||||
|
type Create struct {
|
||||||
|
Name string `json:"name" validate:"required_strict,min=3"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Query struct {
|
||||||
|
Page int `query:"page" validate:"omitempty,number,min=1"`
|
||||||
|
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100"`
|
||||||
|
Search string `query:"search" validate:"omitempty,max=50"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TransferProduct struct {
|
||||||
|
ProductID uint `json:"product_id" validate:"required"`
|
||||||
|
ProductQty float64 `json:"product_qty" validate:"required,gt=0"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TransferDeliveryProduct struct {
|
||||||
|
ProductID uint `json:"product_id" validate:"required"`
|
||||||
|
ProductQty float64 `json:"product_qty" validate:"required,gt=0"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TransferDelivery struct {
|
||||||
|
DeliveryCost float64 `json:"delivery_cost" validate:"required"`
|
||||||
|
DeliveryCostPerItem float64 `json:"delivery_cost_per_item" validate:"required"`
|
||||||
|
Document string `json:"document"`
|
||||||
|
DriverName string `json:"driver_name" validate:"required"`
|
||||||
|
DeliveryNoteNumber string `json:"delivery_note_number" validate:"required"`
|
||||||
|
VehiclePlate string `json:"vehicle_plate" validate:"required"`
|
||||||
|
SupplierID uint `json:"supplier_id" validate:"required"`
|
||||||
|
Products []TransferDeliveryProduct `json:"products" validate:"required,dive"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TransferRequest struct {
|
||||||
|
TransferReason string `json:"transfer_reason" validate:"required"`
|
||||||
|
TransferDate string `json:"transfer_date" validate:"required,datetime=2006-01-02"`
|
||||||
|
SourceWarehouseID uint `json:"source_warehouse_id" validate:"required"`
|
||||||
|
DestinationWarehouseID uint `json:"destination_warehouse_id" validate:"required"`
|
||||||
|
Products []TransferProduct `json:"products" validate:"required,dive"`
|
||||||
|
Deliveries []TransferDelivery `json:"deliveries" validate:"required,dive"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseDateString mengubah string "YYYY-MM-DD" menjadi time.Time
|
||||||
|
func ParseDateString(dateStr string) (time.Time, error) {
|
||||||
|
if dateStr == "" {
|
||||||
|
return time.Time{}, errors.New("date string is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed, err := time.Parse("2006-01-02", dateStr)
|
||||||
|
if err != nil {
|
||||||
|
return time.Time{}, errors.New("invalid date format, expected YYYY-MM-DD")
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FormatDate mengubah time.Time menjadi string "YYYY-MM-DD"
|
||||||
|
func FormatDate(t time.Time) string {
|
||||||
|
return t.Format("2006-01-02")
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user