mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
Merge branch 'development' of https://gitlab.com/mbugroup/lti-api into Fix/BE/Purchase-rejected
This commit is contained in:
+56
-17
@@ -49,41 +49,80 @@ build_production:
|
|||||||
|
|
||||||
|
|
||||||
# =========================
|
# =========================
|
||||||
# MIGRATE (PRODUCTION - MANUAL)
|
# MIGRATE (PRODUCTION)
|
||||||
# =========================
|
# =========================
|
||||||
migrate_production:
|
migrate_production:
|
||||||
stage: migrate
|
stage: migrate
|
||||||
rules:
|
rules:
|
||||||
- if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "production"'
|
- if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "production"'
|
||||||
when: manual
|
|
||||||
allow_failure: false
|
|
||||||
needs:
|
needs:
|
||||||
- job: build_production
|
- job: build_production
|
||||||
artifacts: false
|
artifacts: false
|
||||||
script: |
|
script: |
|
||||||
set -e
|
set -e
|
||||||
cd /opt/deploy/lti
|
echo "✅ Running migrations (production) ..."
|
||||||
test -f .env || (echo "❌ .env not found" && exit 1)
|
|
||||||
|
|
||||||
|
cd "$DEPLOY_DIR"
|
||||||
|
test -f "$COMPOSE_FILE" || (echo "❌ $COMPOSE_FILE not found in $DEPLOY_DIR" && exit 1)
|
||||||
|
test -f .env || (echo "❌ .env not found in $DEPLOY_DIR" && exit 1)
|
||||||
|
|
||||||
|
# ✅ load env dari server
|
||||||
set -a
|
set -a
|
||||||
. ./.env
|
. ./.env
|
||||||
set +a
|
set +a
|
||||||
|
|
||||||
# Validasi env wajib
|
# ✅ validasi
|
||||||
: "${DB_HOST:?DB_HOST not set}"
|
test -n "$DB_HOST" || (echo "❌ DB_HOST empty" && exit 1)
|
||||||
: "${DB_PORT:?DB_PORT not set}"
|
test -n "$DB_PORT" || (echo "❌ DB_PORT empty" && exit 1)
|
||||||
: "${DB_USER:?DB_USER not set}"
|
test -n "$DB_USER" || (echo "❌ DB_USER empty" && exit 1)
|
||||||
: "${DB_PASSWORD:?DB_PASSWORD not set}"
|
test -n "$DB_PASSWORD" || (echo "❌ DB_PASSWORD empty" && exit 1)
|
||||||
: "${DB_NAME:?DB_NAME not set}"
|
test -n "$DB_NAME" || (echo "❌ DB_NAME empty" && exit 1)
|
||||||
|
|
||||||
DB_SSLMODE="${DB_SSLMODE:-require}"
|
export DATABASE_URL="postgres://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}?sslmode=${DB_SSLMODE:-disable}"
|
||||||
export DATABASE_URL="postgres://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}?sslmode=${DB_SSLMODE}"
|
echo "✅ DATABASE_URL=$DATABASE_URL"
|
||||||
|
|
||||||
echo "✅ Running migrations (production)..."
|
# ✅ Pastikan postgres & redis ON (sesuaikan nama service compose kamu!)
|
||||||
docker run --rm \
|
echo "✅ Ensuring postgres & redis running ..."
|
||||||
-v "/opt/deploy/lti/internal/database/migrations:/migrations:ro" \
|
docker compose -f "$COMPOSE_FILE" up -d stg-postgres-lti stg-redis-lti || true
|
||||||
|
|
||||||
|
# ✅ Ambil network key dari compose
|
||||||
|
COMPOSE_NETWORK_KEY="$(docker compose -f "$COMPOSE_FILE" config | awk '/networks:/ {getline; print $1}' | tr -d ':')"
|
||||||
|
echo "✅ Compose network key: $COMPOSE_NETWORK_KEY"
|
||||||
|
|
||||||
|
# ✅ Cari network name yang dipakai docker
|
||||||
|
NETWORK_NAME="$(docker network ls --format '{{.Name}}' | grep "_${COMPOSE_NETWORK_KEY}$" | head -n 1)"
|
||||||
|
test -n "$NETWORK_NAME" || (echo "❌ Cannot find docker network for compose ($COMPOSE_NETWORK_KEY)" && exit 1)
|
||||||
|
|
||||||
|
echo "✅ Docker network detected: $NETWORK_NAME"
|
||||||
|
|
||||||
|
# ✅ Migrations dari repo (CI workspace)
|
||||||
|
echo "✅ Checking migrations from repo..."
|
||||||
|
ls -lah "$CI_PROJECT_DIR/internal/database/migrations"
|
||||||
|
|
||||||
|
echo "✅ Running migrations via migrate/migrate container"
|
||||||
|
set +e
|
||||||
|
out=$(docker run --rm \
|
||||||
|
--network "$NETWORK_NAME" \
|
||||||
|
-v "$CI_PROJECT_DIR/internal/database/migrations:/migrations:ro" \
|
||||||
migrate/migrate:v4.15.2 \
|
migrate/migrate:v4.15.2 \
|
||||||
-path=/migrations -database "$DATABASE_URL" up
|
-path=/migrations -database "$DATABASE_URL" up 2>&1)
|
||||||
|
code=$?
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "$out"
|
||||||
|
|
||||||
|
# ✅ Handle no change dengan benar (tidak false-success)
|
||||||
|
if echo "$out" | grep -qi "no change"; then
|
||||||
|
echo "✅ No change (already up to date)"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $code -ne 0 ]; then
|
||||||
|
echo "❌ Migration failed with exit code $code"
|
||||||
|
exit $code
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✅ Migration applied successfully"
|
||||||
|
|
||||||
|
|
||||||
# =========================
|
# =========================
|
||||||
|
|||||||
@@ -196,10 +196,10 @@ func (r *HppRepositoryImpl) GetPulletCost(ctx context.Context, projectFlockKanda
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *HppRepositoryImpl) GetEggProduksiPiecesAndWeightKgByProjectFlockKandangIds(ctx context.Context, projectFlockKandangIDs []uint, date *time.Time) (float64, float64, error) {
|
func (r *HppRepositoryImpl) GetEggProduksiPiecesAndWeightKgByProjectFlockKandangIds(ctx context.Context, projectFlockKandangIDs []uint, date *time.Time) (float64, float64, error) {
|
||||||
// if date == nil {
|
if date == nil {
|
||||||
// now := time.Now()
|
now := time.Now()
|
||||||
// date = &now
|
date = &now
|
||||||
// }
|
}
|
||||||
|
|
||||||
var totals struct {
|
var totals struct {
|
||||||
TotalPieces float64
|
TotalPieces float64
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ var (
|
|||||||
SSOCookieDomain string
|
SSOCookieDomain string
|
||||||
SSOCookieSecure bool
|
SSOCookieSecure bool
|
||||||
SSOCookieSameSite string
|
SSOCookieSameSite string
|
||||||
|
SSOAccessTokenMaxBytes int
|
||||||
SSOTokenBlacklistPrefix string
|
SSOTokenBlacklistPrefix string
|
||||||
SSOPKCETTL time.Duration
|
SSOPKCETTL time.Duration
|
||||||
SSOUserSyncDrift time.Duration
|
SSOUserSyncDrift time.Duration
|
||||||
@@ -144,6 +145,10 @@ func init() {
|
|||||||
SSOCookieDomain = viper.GetString("SSO_COOKIE_DOMAIN")
|
SSOCookieDomain = viper.GetString("SSO_COOKIE_DOMAIN")
|
||||||
SSOCookieSecure = viper.GetBool("SSO_COOKIE_SECURE")
|
SSOCookieSecure = viper.GetBool("SSO_COOKIE_SECURE")
|
||||||
SSOCookieSameSite = defaultString(viper.GetString("SSO_COOKIE_SAMESITE"), "Lax")
|
SSOCookieSameSite = defaultString(viper.GetString("SSO_COOKIE_SAMESITE"), "Lax")
|
||||||
|
SSOAccessTokenMaxBytes = viper.GetInt("SSO_ACCESS_TOKEN_MAX_BYTES")
|
||||||
|
if SSOAccessTokenMaxBytes <= 0 {
|
||||||
|
SSOAccessTokenMaxBytes = 4096
|
||||||
|
}
|
||||||
SSOTokenBlacklistPrefix = defaultString(viper.GetString("SSO_TOKEN_BLACKLIST_PREFIX"), "sso:blacklist")
|
SSOTokenBlacklistPrefix = defaultString(viper.GetString("SSO_TOKEN_BLACKLIST_PREFIX"), "sso:blacklist")
|
||||||
if ttl := viper.GetInt("SSO_PKCE_TTL_SECONDS"); ttl > 0 {
|
if ttl := viper.GetInt("SSO_PKCE_TTL_SECONDS"); ttl > 0 {
|
||||||
SSOPKCETTL = time.Duration(ttl) * time.Second
|
SSOPKCETTL = time.Duration(ttl) * time.Second
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE stock_logs
|
||||||
|
DROP COLUMN stock;
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
ALTER TABLE stock_logs
|
||||||
|
ADD COLUMN stock NUMERIC(15, 3) NOT NULL DEFAULT 0;
|
||||||
|
|
||||||
|
WITH calc AS (
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
SUM(COALESCE(increase, 0) - COALESCE(decrease, 0))
|
||||||
|
OVER (
|
||||||
|
PARTITION BY product_warehouse_id
|
||||||
|
ORDER BY id
|
||||||
|
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
|
||||||
|
) AS running_stock
|
||||||
|
FROM stock_logs
|
||||||
|
)
|
||||||
|
UPDATE stock_logs t
|
||||||
|
SET stock = c.running_stock
|
||||||
|
FROM calc c
|
||||||
|
WHERE t.id = c.id;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- Drop transfer laying sequence
|
||||||
|
DROP SEQUENCE IF EXISTS transfer_laying_seq;
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
-- Create sequence for transfer laying movement number
|
||||||
|
CREATE SEQUENCE transfer_laying_seq START
|
||||||
|
WITH
|
||||||
|
1 INCREMENT BY 1 MINVALUE 1 MAXVALUE 99999 NO CYCLE;
|
||||||
|
|
||||||
|
-- Set sequence starting value based on existing data (if any)
|
||||||
|
-- This prevents duplicate movement numbers if there's already data
|
||||||
|
DO $$ DECLARE max_existing INTEGER;
|
||||||
|
|
||||||
|
BEGIN
|
||||||
|
-- Check if table exists and has data
|
||||||
|
IF EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM information_schema.tables
|
||||||
|
WHERE
|
||||||
|
table_schema = 'public'
|
||||||
|
AND table_name = 'transfer_to_layings'
|
||||||
|
) THEN
|
||||||
|
-- Get max ID from existing records
|
||||||
|
SELECT COALESCE(MAX(id), 0) INTO max_existing
|
||||||
|
FROM transfer_to_layings;
|
||||||
|
|
||||||
|
-- Set sequence to start after the highest existing ID
|
||||||
|
IF max_existing > 0 THEN PERFORM setval (
|
||||||
|
'transfer_laying_seq',
|
||||||
|
max_existing
|
||||||
|
);
|
||||||
|
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
END $$;
|
||||||
@@ -9,6 +9,7 @@ type StockLog struct {
|
|||||||
|
|
||||||
Increase float64 `gorm:"column:increase;type:numeric(15,3);default:0"`
|
Increase float64 `gorm:"column:increase;type:numeric(15,3);default:0"`
|
||||||
Decrease float64 `gorm:"column:decrease;type:numeric(15,3);default:0"`
|
Decrease float64 `gorm:"column:decrease;type:numeric(15,3);default:0"`
|
||||||
|
Stock float64 `gorm:"column:stock;type:numeric(15,3);not null;default:0"`
|
||||||
|
|
||||||
LoggableType string `gorm:"column:loggable_type;type:varchar(50);not null"`
|
LoggableType string `gorm:"column:loggable_type;type:varchar(50);not null"`
|
||||||
LoggableId uint `gorm:"column:loggable_id;not null"`
|
LoggableId uint `gorm:"column:loggable_id;not null"`
|
||||||
|
|||||||
@@ -101,17 +101,44 @@ func ToSalesDTO(e entity.MarketingDeliveryProduct) SalesDTO {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ToSummaryDto(e []entity.MarketingDeliveryProduct) SummaryDTO {
|
func ToSalesAgeDTO(e entity.MarketingDeliveryProduct) SalesDTO {
|
||||||
|
|
||||||
|
productFlags := make([]string, len(e.MarketingProduct.ProductWarehouse.Product.Flags))
|
||||||
|
for i, f := range e.MarketingProduct.ProductWarehouse.Product.Flags {
|
||||||
|
productFlags[i] = f.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
var category string
|
||||||
|
if e.MarketingProduct.ProductWarehouse.ProjectFlockKandang != nil {
|
||||||
|
category = e.MarketingProduct.ProductWarehouse.ProjectFlockKandang.ProjectFlock.Category
|
||||||
|
}
|
||||||
|
|
||||||
|
ageInDay, _ := calculateAgeFromChickin(e.MarketingProduct.ProductWarehouse.ProjectFlockKandang, e.DeliveryDate, productFlags, category)
|
||||||
|
|
||||||
|
return SalesDTO{
|
||||||
|
Age: ageInDay,
|
||||||
|
Qty: e.UsageQty,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToSummaryDto(e []entity.MarketingDeliveryProduct) SummaryDTO {
|
||||||
var totalSalesPrice, totalActualPrice, sumSales, sumActual float64
|
var totalSalesPrice, totalActualPrice, sumSales, sumActual float64
|
||||||
count := len(e)
|
count := len(e)
|
||||||
|
|
||||||
|
if count == 0 {
|
||||||
|
return SummaryDTO{
|
||||||
|
TotalSalesPrice: 0,
|
||||||
|
TotalActualPrice: 0,
|
||||||
|
AvgSalesPrice: 0,
|
||||||
|
AvgActualPrice: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, item := range e {
|
for _, item := range e {
|
||||||
totalSalesPrice += item.MarketingProduct.TotalPrice
|
totalSalesPrice += item.MarketingProduct.TotalPrice
|
||||||
totalActualPrice += item.TotalPrice
|
totalActualPrice += item.TotalPrice
|
||||||
sumSales += item.MarketingProduct.UnitPrice
|
sumSales += item.MarketingProduct.UnitPrice
|
||||||
sumActual += item.UnitPrice
|
sumActual += item.UnitPrice
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return SummaryDTO{
|
return SummaryDTO{
|
||||||
|
|||||||
@@ -367,7 +367,7 @@ func (s closingService) GetClosingSapronak(c *fiber.Ctx, projectFlockID uint, pa
|
|||||||
return nil, 0, fiber.NewError(fiber.StatusBadRequest, "type must be either incoming or outgoing")
|
return nil, 0, fiber.NewError(fiber.StatusBadRequest, "type must be either incoming or outgoing")
|
||||||
}
|
}
|
||||||
|
|
||||||
warehouseIDs, err := s.getWarehouseIDsByProjectFlock(c.Context(), projectFlockID)
|
warehouseIDs, err := s.getWarehouseIDsByProjectFlock(c.Context(), projectFlockID, params.KandangID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Log.Errorf("Failed to fetch warehouses for project flock %d: %+v", projectFlockID, err)
|
s.Log.Errorf("Failed to fetch warehouses for project flock %d: %+v", projectFlockID, err)
|
||||||
return nil, 0, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch warehouses for project flock")
|
return nil, 0, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch warehouses for project flock")
|
||||||
@@ -451,7 +451,7 @@ func (s closingService) GetClosingSapronakSummary(c *fiber.Ctx, projectFlockID u
|
|||||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch project flock")
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch project flock")
|
||||||
}
|
}
|
||||||
|
|
||||||
warehouseIDs, err := s.getWarehouseIDsByProjectFlock(c.Context(), projectFlockID)
|
warehouseIDs, err := s.getWarehouseIDsByProjectFlock(c.Context(), projectFlockID, params.KandangID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Log.Errorf("Failed to fetch warehouses for project flock %d: %+v", projectFlockID, err)
|
s.Log.Errorf("Failed to fetch warehouses for project flock %d: %+v", projectFlockID, err)
|
||||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch warehouses for project flock")
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch warehouses for project flock")
|
||||||
@@ -494,13 +494,16 @@ func (s closingService) GetClosingSapronakSummary(c *fiber.Ctx, projectFlockID u
|
|||||||
return items, nil
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s closingService) getWarehouseIDsByProjectFlock(ctx context.Context, projectFlockID uint) ([]uint, error) {
|
func (s closingService) getWarehouseIDsByProjectFlock(ctx context.Context, projectFlockID uint, kandangID *uint) ([]uint, error) {
|
||||||
var kandangIDs []uint
|
var kandangIDs []uint
|
||||||
db := s.Repository.DB().WithContext(ctx)
|
db := s.Repository.DB().WithContext(ctx)
|
||||||
|
|
||||||
if err := db.Model(&entity.ProjectFlockKandang{}).
|
query := db.Model(&entity.ProjectFlockKandang{}).
|
||||||
Where("project_flock_id = ?", projectFlockID).
|
Where("project_flock_id = ?", projectFlockID)
|
||||||
Pluck("kandang_id", &kandangIDs).Error; err != nil {
|
if kandangID != nil && *kandangID > 0 {
|
||||||
|
query = query.Where("id = ?", *kandangID)
|
||||||
|
}
|
||||||
|
if err := query.Pluck("kandang_id", &kandangIDs).Error; err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -841,7 +844,7 @@ func (s closingService) GetClosingDataProduksi(c *fiber.Ctx, projectFlockID uint
|
|||||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch FCR standard data")
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch FCR standard data")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
age, err := s.calculateAverageSalesAge(c.Context(), projectFlockID)
|
age, err := s.calculateAverageSalesAge(c.Context(), projectFlockID, kandangID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Log.Errorf("Failed to calculate sales age for project flock %d: %+v", projectFlockID, err)
|
s.Log.Errorf("Failed to calculate sales age for project flock %d: %+v", projectFlockID, err)
|
||||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch sales age data")
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch sales age data")
|
||||||
@@ -1028,38 +1031,24 @@ func (s closingService) GetClosingDataProduksi(c *fiber.Ctx, projectFlockID uint
|
|||||||
return &result, nil
|
return &result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s closingService) calculateAverageSalesAge(ctx context.Context, projectFlockID uint) (float64, error) {
|
func (s closingService) calculateAverageSalesAge(ctx context.Context, projectFlockID uint, projectFlockKandangID *uint) (float64, error) {
|
||||||
deliveryProducts, err := s.MarketingDeliveryProductRepo.GetDeliveryProductsByProjectFlockID(ctx, projectFlockID, func(db *gorm.DB) *gorm.DB {
|
penjualan, err := s.MarketingDeliveryProductRepo.GetClosingPenjualanForAgeChickDataProduction(ctx, projectFlockID, projectFlockKandangID)
|
||||||
return db.
|
|
||||||
Preload("MarketingProduct").
|
|
||||||
Preload("MarketingProduct.ProductWarehouse").
|
|
||||||
Preload("MarketingProduct.ProductWarehouse.ProjectFlockKandang").
|
|
||||||
Preload("MarketingProduct.ProductWarehouse.ProjectFlockKandang.Chickins")
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
acumulateAgeQty := 0.0
|
||||||
var (
|
totalQty := 0.0
|
||||||
totalQty float64
|
for _, v := range penjualan {
|
||||||
totalAgeWeeks float64
|
sale := dto.ToSalesAgeDTO(v)
|
||||||
)
|
acumulateAgeQty += float64(sale.Age) * sale.Qty
|
||||||
|
totalQty += sale.Qty
|
||||||
for _, product := range deliveryProducts {
|
}
|
||||||
if product.UsageQty == 0 {
|
if totalQty > 0 {
|
||||||
continue
|
averageAge := acumulateAgeQty / totalQty
|
||||||
}
|
return averageAge, nil
|
||||||
projectFlockKandang := product.MarketingProduct.ProductWarehouse.ProjectFlockKandang
|
|
||||||
ageWeeks := dto.CalculateAgeFromChickinDataProduksi(projectFlockKandang, product.DeliveryDate)
|
|
||||||
totalAgeWeeks += float64(ageWeeks) * product.UsageQty
|
|
||||||
totalQty += product.UsageQty
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if totalQty == 0 {
|
return 0, err
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return totalAgeWeeks / totalQty, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s closingService) determineProductionWeek(ctx context.Context, projectFlockKandangIDs []uint) (int, error) {
|
func (s closingService) determineProductionWeek(ctx context.Context, projectFlockKandangIDs []uint) (int, error) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package controller
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
|
"mime/multipart"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/modules/daily-checklists/dto"
|
"gitlab.com/mbugroup/lti-api.git/internal/modules/daily-checklists/dto"
|
||||||
@@ -362,6 +363,9 @@ func (u *DailyChecklistController) UpdateOne(c *fiber.Ctx) error {
|
|||||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid multipart form")
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid multipart form")
|
||||||
}
|
}
|
||||||
req.Documents = form.File["documents"]
|
req.Documents = form.File["documents"]
|
||||||
|
if err := validateDailyChecklistDocumentSizes(req.Documents); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if err := c.BodyParser(req); err != nil {
|
if err := c.BodyParser(req); err != nil {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||||
@@ -381,6 +385,16 @@ func (u *DailyChecklistController) UpdateOne(c *fiber.Ctx) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateDailyChecklistDocumentSizes(files []*multipart.FileHeader) error {
|
||||||
|
const maxDailyChecklistDocumentBytes = 5 * 1024 * 1024 // 5MB
|
||||||
|
for _, file := range files {
|
||||||
|
if file != nil && file.Size > maxDailyChecklistDocumentBytes {
|
||||||
|
return fiber.NewError(fiber.StatusRequestEntityTooLarge, "Document size must be <= 5MB")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (u *DailyChecklistController) DeleteOne(c *fiber.Ctx) error {
|
func (u *DailyChecklistController) DeleteOne(c *fiber.Ctx) error {
|
||||||
param := c.Params("idDailyChecklist")
|
param := c.Params("idDailyChecklist")
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package service
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"math"
|
"math"
|
||||||
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -259,8 +260,9 @@ func (s dailyChecklistService) GetAll(c *fiber.Ctx, params *validation.Query) ([
|
|||||||
}
|
}
|
||||||
|
|
||||||
if params.Search != "" {
|
if params.Search != "" {
|
||||||
like := "%" + params.Search + "%"
|
re := regexp.MustCompile("[^a-zA-Z0-9]")
|
||||||
db = db.Where("(k.name ILIKE ? OR dc.category::text ILIKE ?)", like, like)
|
like := re.ReplaceAll([]byte("%"+params.Search+"%"), []byte(""))
|
||||||
|
db = db.Where("(regexp_replace(k.name, '[^a-zA-Z0-9]', '', 'g') ILIKE ? OR regexp_replace(dc.category::text, '[^a-zA-Z0-9]', '', 'g') ILIKE ?)", string(like), string(like))
|
||||||
}
|
}
|
||||||
|
|
||||||
countDB := db.Session(&gorm.Session{})
|
countDB := db.Session(&gorm.Session{})
|
||||||
|
|||||||
@@ -169,15 +169,30 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e
|
|||||||
CreatedBy: actorID,
|
CreatedBy: actorID,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stockLogs, err := s.StockLogsRepository.GetByProductWarehouse(ctx, productWarehouse.Id, 1)
|
||||||
|
if err != nil {
|
||||||
|
s.Log.Errorf("Failed to get stock logs: %+v", err)
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get stock logs")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(stockLogs) > 0 {
|
||||||
|
latestStockLog := stockLogs[0]
|
||||||
|
newLog.Stock = latestStockLog.Stock
|
||||||
|
} else {
|
||||||
|
newLog.Stock = 0
|
||||||
|
}
|
||||||
|
|
||||||
if transactionType == string(utils.StockLogTransactionTypeIncrease) {
|
if transactionType == string(utils.StockLogTransactionTypeIncrease) {
|
||||||
afterQuantity += req.Quantity
|
afterQuantity += req.Quantity
|
||||||
newLog.Increase = req.Quantity
|
newLog.Increase = req.Quantity
|
||||||
|
newLog.Stock += newLog.Increase
|
||||||
} else {
|
} else {
|
||||||
if productWarehouse.Quantity < req.Quantity {
|
if productWarehouse.Quantity < req.Quantity {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Stok tidak mencukupi untuk pengurangan. Stok saat ini: %.2f, Jumlah yang akan dikurangi: %.2f", productWarehouse.Quantity, req.Quantity))
|
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Stok tidak mencukupi untuk pengurangan. Stok saat ini: %.2f, Jumlah yang akan dikurangi: %.2f", productWarehouse.Quantity, req.Quantity))
|
||||||
}
|
}
|
||||||
afterQuantity -= req.Quantity
|
afterQuantity -= req.Quantity
|
||||||
newLog.Decrease = req.Quantity
|
newLog.Decrease = req.Quantity
|
||||||
|
newLog.Stock -= newLog.Decrease
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.StockLogsRepository.WithTx(tx).CreateOne(ctx, newLog, nil); err != nil {
|
if err := s.StockLogsRepository.WithTx(tx).CreateOne(ctx, newLog, nil); err != nil {
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ type StockLogDetailDTO struct {
|
|||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
Increase float64 `json:"increase"`
|
Increase float64 `json:"increase"`
|
||||||
Decrease float64 `json:"decrease"`
|
Decrease float64 `json:"decrease"`
|
||||||
|
Stock float64 `json:"stock"`
|
||||||
LoggableType string `json:"loggable_type"`
|
LoggableType string `json:"loggable_type"`
|
||||||
LoggableId uint `json:"loggable_id"`
|
LoggableId uint `json:"loggable_id"`
|
||||||
Notes *string `json:"notes"`
|
Notes *string `json:"notes"`
|
||||||
@@ -195,6 +196,7 @@ func mapStockLogs(src []entity.StockLog) []StockLogDetailDTO {
|
|||||||
Id: log.Id,
|
Id: log.Id,
|
||||||
Increase: log.Increase,
|
Increase: log.Increase,
|
||||||
Decrease: log.Decrease,
|
Decrease: log.Decrease,
|
||||||
|
Stock: log.Stock,
|
||||||
LoggableType: log.LoggableType,
|
LoggableType: log.LoggableType,
|
||||||
LoggableId: log.LoggableId,
|
LoggableId: log.LoggableId,
|
||||||
Notes: notes,
|
Notes: notes,
|
||||||
|
|||||||
@@ -232,11 +232,24 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, delivery := range req.Deliveries {
|
for _, delivery := range req.Deliveries {
|
||||||
// Skip supplier validation if SupplierID is 0 (optional)
|
|
||||||
if delivery.SupplierID == 0 {
|
if delivery.SupplierID == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if delivery.VehiclePlate == "" {
|
||||||
|
return nil, fiber.NewError(fiber.StatusBadRequest, "Vehicle plate wajib diisi ketika supplier dipilih")
|
||||||
|
}
|
||||||
|
if delivery.DriverName == "" {
|
||||||
|
return nil, fiber.NewError(fiber.StatusBadRequest, "Driver name wajib diisi ketika supplier dipilih")
|
||||||
|
}
|
||||||
|
if delivery.DeliveryCost <= 0 {
|
||||||
|
return nil, fiber.NewError(fiber.StatusBadRequest, "Delivery cost harus lebih dari 0 ketika supplier dipilih")
|
||||||
|
}
|
||||||
|
if delivery.DeliveryCostPerItem <= 0 {
|
||||||
|
return nil, fiber.NewError(fiber.StatusBadRequest, "Delivery cost per item harus lebih dari 0 ketika supplier dipilih")
|
||||||
|
}
|
||||||
|
|
||||||
supplier, err := s.SupplierRepo.GetByID(c.Context(), uint(delivery.SupplierID), nil)
|
supplier, err := s.SupplierRepo.GetByID(c.Context(), uint(delivery.SupplierID), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
@@ -463,6 +476,18 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
|
|||||||
LoggableId: uint(detail.Id),
|
LoggableId: uint(detail.Id),
|
||||||
Notes: "",
|
Notes: "",
|
||||||
}
|
}
|
||||||
|
stockLogs, err := s.StockLogsRepository.GetByProductWarehouse(c.Context(), uint(*detail.SourceProductWarehouseID), 1)
|
||||||
|
if err != nil {
|
||||||
|
s.Log.Errorf("Failed to get stock logs: %+v", err)
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get stock logs")
|
||||||
|
}
|
||||||
|
if len(stockLogs) > 0 {
|
||||||
|
latestStockLog := stockLogs[0]
|
||||||
|
stockLogDecrease.Stock -= latestStockLog.Stock - stockLogDecrease.Decrease
|
||||||
|
} else {
|
||||||
|
stockLogDecrease.Stock -= stockLogDecrease.Decrease
|
||||||
|
}
|
||||||
|
|
||||||
if err := stocklogsRepoTx.CreateOne(c.Context(), stockLogDecrease, nil); err != nil {
|
if err := stocklogsRepoTx.CreateOne(c.Context(), stockLogDecrease, nil); err != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Gagal membuat log stok keluar")
|
return fiber.NewError(fiber.StatusInternalServerError, "Gagal membuat log stok keluar")
|
||||||
}
|
}
|
||||||
@@ -499,6 +524,17 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
|
|||||||
LoggableId: uint(detail.Id),
|
LoggableId: uint(detail.Id),
|
||||||
Notes: "",
|
Notes: "",
|
||||||
}
|
}
|
||||||
|
stockLogs, err = s.StockLogsRepository.GetByProductWarehouse(c.Context(), uint(*detail.DestProductWarehouseID), 1)
|
||||||
|
if err != nil {
|
||||||
|
s.Log.Errorf("Failed to get stock logs: %+v", err)
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get stock logs")
|
||||||
|
}
|
||||||
|
if len(stockLogs) > 0 {
|
||||||
|
latestStockLog := stockLogs[0]
|
||||||
|
stockLogIncrease.Stock = latestStockLog.Stock + stockLogIncrease.Increase
|
||||||
|
} else {
|
||||||
|
stockLogIncrease.Stock += stockLogIncrease.Increase
|
||||||
|
}
|
||||||
if err := stocklogsRepoTx.CreateOne(c.Context(), stockLogIncrease, nil); err != nil {
|
if err := stocklogsRepoTx.CreateOne(c.Context(), stockLogIncrease, nil); err != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Gagal membuat log stok masuk")
|
return fiber.NewError(fiber.StatusInternalServerError, "Gagal membuat log stok masuk")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,11 +21,11 @@ type TransferDeliveryProduct struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type TransferDelivery struct {
|
type TransferDelivery struct {
|
||||||
DeliveryCost float64 `json:"delivery_cost" validate:"required"`
|
DeliveryCost float64 `json:"delivery_cost"`
|
||||||
DeliveryCostPerItem float64 `json:"delivery_cost_per_item" validate:"required"`
|
DeliveryCostPerItem float64 `json:"delivery_cost_per_item"`
|
||||||
DocumentIndex int `json:"document_index" validate:"omitempty,min=-1" default:"-1"`
|
DocumentIndex int `json:"document_index" validate:"omitempty,min=-1" default:"-1"`
|
||||||
DriverName string `json:"driver_name" validate:"required"`
|
DriverName string `json:"driver_name"`
|
||||||
VehiclePlate string `json:"vehicle_plate" validate:"required"`
|
VehiclePlate string `json:"vehicle_plate"`
|
||||||
SupplierID uint `json:"supplier_id" `
|
SupplierID uint `json:"supplier_id" `
|
||||||
Products []TransferDeliveryProduct `json:"products" validate:"required,dive"`
|
Products []TransferDeliveryProduct `json:"products" validate:"required,dive"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ type MarketingDeliveryProductRepository interface {
|
|||||||
UpdateFifoFields(ctx context.Context, id uint, usageQty, pendingQty float64) error
|
UpdateFifoFields(ctx context.Context, id uint, usageQty, pendingQty float64) error
|
||||||
GetUsageQty(ctx context.Context, id uint) (float64, error)
|
GetUsageQty(ctx context.Context, id uint) (float64, error)
|
||||||
ResetFifoFields(ctx context.Context, id uint) error
|
ResetFifoFields(ctx context.Context, id uint) error
|
||||||
|
GetClosingPenjualanForAgeChickDataProduction(ctx context.Context, projectFlockID uint, projectFlockKandangID *uint) ([]entity.MarketingDeliveryProduct, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type MarketingDeliveryProductRepositoryImpl struct {
|
type MarketingDeliveryProductRepositoryImpl struct {
|
||||||
@@ -93,6 +94,46 @@ func (r *MarketingDeliveryProductRepositoryImpl) GetClosingPenjualan(ctx context
|
|||||||
return deliveryProducts, nil
|
return deliveryProducts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *MarketingDeliveryProductRepositoryImpl) GetClosingPenjualanForAgeChickDataProduction(ctx context.Context, projectFlockID uint, projectFlockKandangID *uint) ([]entity.MarketingDeliveryProduct, error) {
|
||||||
|
var deliveryProducts []entity.MarketingDeliveryProduct
|
||||||
|
|
||||||
|
db := r.DB().WithContext(ctx).
|
||||||
|
Joins("JOIN marketing_products ON marketing_products.id = marketing_delivery_products.marketing_product_id").
|
||||||
|
Joins("JOIN product_warehouses ON product_warehouses.id = marketing_products.product_warehouse_id").
|
||||||
|
Joins("JOIN products ON products.id = product_warehouses.product_id").
|
||||||
|
Joins("JOIN flags ON flags.flagable_id = products.id AND flags.flagable_type = 'products'").
|
||||||
|
Joins("JOIN project_flock_kandangs ON project_flock_kandangs.id = product_warehouses.project_flock_kandang_id").
|
||||||
|
Where("project_flock_kandangs.project_flock_id = ?", projectFlockID).
|
||||||
|
Where("flags.name IN (?)", []string{
|
||||||
|
string(utils.FlagAyamAfkir),
|
||||||
|
string(utils.FlagAyamCulling),
|
||||||
|
string(utils.FlagPullet),
|
||||||
|
string(utils.FlagLayer),
|
||||||
|
}).
|
||||||
|
Where("marketing_delivery_products.delivery_date IS NOT NULL").
|
||||||
|
Distinct("marketing_delivery_products.*")
|
||||||
|
|
||||||
|
if projectFlockKandangID != nil {
|
||||||
|
db = db.Where("product_warehouses.project_flock_kandang_id = ?", *projectFlockKandangID)
|
||||||
|
}
|
||||||
|
|
||||||
|
db = db.
|
||||||
|
Preload("MarketingProduct").
|
||||||
|
Preload("MarketingProduct.ProductWarehouse").
|
||||||
|
Preload("MarketingProduct.ProductWarehouse.Product").
|
||||||
|
Preload("MarketingProduct.ProductWarehouse.Product.ProductCategory").
|
||||||
|
Preload("MarketingProduct.ProductWarehouse.Product.Flags").
|
||||||
|
Preload("MarketingProduct.ProductWarehouse.ProjectFlockKandang").
|
||||||
|
Preload("MarketingProduct.ProductWarehouse.ProjectFlockKandang.Chickins").
|
||||||
|
Order("marketing_delivery_products.delivery_date DESC")
|
||||||
|
|
||||||
|
if err := db.Find(&deliveryProducts).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return deliveryProducts, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *MarketingDeliveryProductRepositoryImpl) GetClosingPenjualanByCategory(ctx context.Context, projectFlockID uint, projectFlockKandangID *uint, category string) ([]entity.MarketingDeliveryProduct, error) {
|
func (r *MarketingDeliveryProductRepositoryImpl) GetClosingPenjualanByCategory(ctx context.Context, projectFlockID uint, projectFlockKandangID *uint, category string) ([]entity.MarketingDeliveryProduct, error) {
|
||||||
var deliveryProducts []entity.MarketingDeliveryProduct
|
var deliveryProducts []entity.MarketingDeliveryProduct
|
||||||
|
|
||||||
|
|||||||
@@ -410,6 +410,7 @@ func (s deliveryOrdersService) UpdateOne(c *fiber.Ctx, req *validation.DeliveryO
|
|||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch delivery product")
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch delivery product")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
oldRequestedQty := deliveryProduct.UsageQty + deliveryProduct.PendingQty
|
||||||
var itemDeliveryDate *time.Time
|
var itemDeliveryDate *time.Time
|
||||||
if requestedProduct.DeliveryDate != "" {
|
if requestedProduct.DeliveryDate != "" {
|
||||||
parsedDate, err := utils.ParseDateString(requestedProduct.DeliveryDate)
|
parsedDate, err := utils.ParseDateString(requestedProduct.DeliveryDate)
|
||||||
@@ -421,11 +422,8 @@ func (s deliveryOrdersService) UpdateOne(c *fiber.Ctx, req *validation.DeliveryO
|
|||||||
itemDeliveryDate = deliveryProduct.DeliveryDate
|
itemDeliveryDate = deliveryProduct.DeliveryDate
|
||||||
}
|
}
|
||||||
|
|
||||||
oldRequestedQty := deliveryProduct.UsageQty + deliveryProduct.PendingQty
|
|
||||||
|
|
||||||
// Cek apakah product punya flag PAKAN atau OVK
|
|
||||||
isPakanOrOVK := false
|
isPakanOrOVK := false
|
||||||
if foundMarketingProduct.ProductWarehouse.Product.Id != 0 && len(foundMarketingProduct.ProductWarehouse.Product.Flags) > 0 {
|
if foundMarketingProduct.ProductWarehouse.Id != 0 && foundMarketingProduct.ProductWarehouse.Product.Id != 0 && len(foundMarketingProduct.ProductWarehouse.Product.Flags) > 0 {
|
||||||
for _, flag := range foundMarketingProduct.ProductWarehouse.Product.Flags {
|
for _, flag := range foundMarketingProduct.ProductWarehouse.Product.Flags {
|
||||||
if flag.Name == string(utils.FlagPakan) || flag.Name == string(utils.FlagOVK) {
|
if flag.Name == string(utils.FlagPakan) || flag.Name == string(utils.FlagOVK) {
|
||||||
isPakanOrOVK = true
|
isPakanOrOVK = true
|
||||||
@@ -506,60 +504,82 @@ func (s deliveryOrdersService) consumeDeliveryStock(ctx context.Context, tx *gor
|
|||||||
|
|
||||||
deliveryProductRepo := marketingRepo.NewMarketingDeliveryProductRepository(tx)
|
deliveryProductRepo := marketingRepo.NewMarketingDeliveryProductRepository(tx)
|
||||||
|
|
||||||
if err == nil && result.UsageQuantity > 0 {
|
totalConsumed := 0.0
|
||||||
if actorID > 0 {
|
var fifoConsumed float64
|
||||||
decreaseLog := &entity.StockLog{
|
var directConsumed float64
|
||||||
Decrease: result.UsageQuantity,
|
|
||||||
LoggableType: string(utils.StockLogTypeMarketing),
|
if result != nil && result.UsageQuantity > 0 {
|
||||||
LoggableId: deliveryProduct.Id,
|
fifoConsumed = result.UsageQuantity
|
||||||
ProductWarehouseId: marketingProduct.ProductWarehouseId,
|
totalConsumed = result.UsageQuantity
|
||||||
CreatedBy: actorID,
|
|
||||||
Notes: "",
|
|
||||||
}
|
|
||||||
s.StockLogRepo.WithTx(tx).CreateOne(ctx, decreaseLog, nil)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil || (totalConsumed < requestedQty) {
|
||||||
|
remainder := requestedQty - totalConsumed
|
||||||
|
|
||||||
pwRepo := productWarehouseRepo.NewProductWarehouseRepository(tx)
|
pwRepo := productWarehouseRepo.NewProductWarehouseRepository(tx)
|
||||||
pw, err2 := pwRepo.GetByID(ctx, marketingProduct.ProductWarehouseId, nil)
|
pw, err2 := pwRepo.GetByID(ctx, marketingProduct.ProductWarehouseId, nil)
|
||||||
if err2 != nil {
|
if err2 != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to check product warehouse stock")
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to check product warehouse stock")
|
||||||
}
|
}
|
||||||
|
|
||||||
if pw == nil || pw.Quantity < requestedQty {
|
if pw == nil || pw.Quantity < remainder {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Insufficient stock. Available: %.2f, Requested: %.2f", func() float64 {
|
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Insufficient stock. FIFO: %.2f, Direct Available: %.2f, Total Needed: %.2f", func() float64 {
|
||||||
if pw != nil {
|
if pw != nil {
|
||||||
return pw.Quantity
|
return pw.Quantity
|
||||||
} else {
|
} else {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
}(), requestedQty))
|
}(), remainder, requestedQty))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := deliveryProductRepo.UpdateFifoFields(ctx, deliveryProduct.Id, requestedQty, 0); err != nil {
|
if err := pwRepo.AdjustQuantities(ctx, map[uint]float64{
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update delivery product")
|
marketingProduct.ProductWarehouseId: -remainder,
|
||||||
|
}, func(db *gorm.DB) *gorm.DB {
|
||||||
|
return tx
|
||||||
|
}); err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to adjust product warehouse quantity")
|
||||||
}
|
}
|
||||||
|
|
||||||
if actorID > 0 {
|
directConsumed = remainder
|
||||||
decreaseLog := &entity.StockLog{
|
totalConsumed += remainder
|
||||||
Decrease: requestedQty,
|
|
||||||
LoggableType: string(utils.StockLogTypeMarketing),
|
|
||||||
LoggableId: deliveryProduct.Id,
|
|
||||||
ProductWarehouseId: marketingProduct.ProductWarehouseId,
|
|
||||||
CreatedBy: actorID,
|
|
||||||
Notes: "",
|
|
||||||
}
|
|
||||||
s.StockLogRepo.WithTx(tx).CreateOne(ctx, decreaseLog, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := deliveryProductRepo.UpdateFifoFields(ctx, deliveryProduct.Id, result.UsageQuantity, result.PendingQuantity); err != nil {
|
if err := deliveryProductRepo.UpdateFifoFields(ctx, deliveryProduct.Id, totalConsumed, 0); err != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update delivery product")
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update delivery product")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if actorID > 0 && totalConsumed > 0 {
|
||||||
|
notes := ""
|
||||||
|
if fifoConsumed > 0 && directConsumed > 0 {
|
||||||
|
notes = fmt.Sprintf("Partial FIFO (%.2f) + Direct (%.2f)", fifoConsumed, directConsumed)
|
||||||
|
} else if fifoConsumed > 0 {
|
||||||
|
notes = fmt.Sprintf("FIFO stock only (%.2f)", fifoConsumed)
|
||||||
|
} else if directConsumed > 0 {
|
||||||
|
notes = fmt.Sprintf("Direct stock only (%.2f)", directConsumed)
|
||||||
|
}
|
||||||
|
|
||||||
|
decreaseLog := &entity.StockLog{
|
||||||
|
Decrease: totalConsumed,
|
||||||
|
LoggableType: string(utils.StockLogTypeMarketing),
|
||||||
|
LoggableId: deliveryProduct.Id,
|
||||||
|
ProductWarehouseId: marketingProduct.ProductWarehouseId,
|
||||||
|
CreatedBy: actorID,
|
||||||
|
Notes: notes,
|
||||||
|
}
|
||||||
|
stockLogs, err := s.StockLogRepo.GetByProductWarehouse(ctx, marketingProduct.ProductWarehouseId, 1)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get stock logs")
|
||||||
|
}
|
||||||
|
if len(stockLogs) > 0 {
|
||||||
|
latestStockLog := stockLogs[0]
|
||||||
|
decreaseLog.Stock = latestStockLog.Stock
|
||||||
|
decreaseLog.Stock -= decreaseLog.Decrease
|
||||||
|
} else {
|
||||||
|
decreaseLog.Stock -= decreaseLog.Decrease
|
||||||
|
}
|
||||||
|
s.StockLogRepo.WithTx(tx).CreateOne(ctx, decreaseLog, nil)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -599,6 +619,18 @@ func (s deliveryOrdersService) releaseDeliveryStock(ctx context.Context, tx *gor
|
|||||||
CreatedBy: actorID,
|
CreatedBy: actorID,
|
||||||
Notes: "",
|
Notes: "",
|
||||||
}
|
}
|
||||||
|
stockLogs, err := s.StockLogRepo.GetByProductWarehouse(ctx, marketingProduct.ProductWarehouseId, 1)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get stock logs")
|
||||||
|
}
|
||||||
|
if len(stockLogs) > 0 {
|
||||||
|
latestStockLog := stockLogs[0]
|
||||||
|
increaseLog.Stock = latestStockLog.Stock
|
||||||
|
increaseLog.Stock += increaseLog.Increase
|
||||||
|
} else {
|
||||||
|
increaseLog.Stock += increaseLog.Increase
|
||||||
|
}
|
||||||
|
|
||||||
s.StockLogRepo.WithTx(tx).CreateOne(ctx, increaseLog, nil)
|
s.StockLogRepo.WithTx(tx).CreateOne(ctx, increaseLog, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -645,6 +645,18 @@ func (s *chickinService) ConsumeChickinStocks(ctx context.Context, tx *gorm.DB,
|
|||||||
CreatedBy: actorID,
|
CreatedBy: actorID,
|
||||||
Notes: fmt.Sprintf("Chickin #%d", chickin.Id),
|
Notes: fmt.Sprintf("Chickin #%d", chickin.Id),
|
||||||
}
|
}
|
||||||
|
stockLogs, err := s.StockLogRepo.GetByProductWarehouse(ctx, chickin.ProductWarehouseId, 1)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get stock logs")
|
||||||
|
}
|
||||||
|
if len(stockLogs) > 0 {
|
||||||
|
latestStockLog := stockLogs[0]
|
||||||
|
decreaseLog.Stock = latestStockLog.Stock
|
||||||
|
decreaseLog.Stock -= decreaseLog.Decrease
|
||||||
|
} else {
|
||||||
|
decreaseLog.Stock -= decreaseLog.Decrease
|
||||||
|
}
|
||||||
|
|
||||||
s.StockLogRepo.CreateOne(ctx, decreaseLog, nil)
|
s.StockLogRepo.CreateOne(ctx, decreaseLog, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -701,6 +713,18 @@ func (s *chickinService) ReleaseChickinStocks(ctx context.Context, tx *gorm.DB,
|
|||||||
CreatedBy: actorID,
|
CreatedBy: actorID,
|
||||||
Notes: fmt.Sprintf("Chickin #%d - Stock released", chickin.Id),
|
Notes: fmt.Sprintf("Chickin #%d - Stock released", chickin.Id),
|
||||||
}
|
}
|
||||||
|
stockLogs, err := s.StockLogRepo.GetByProductWarehouse(ctx, chickin.ProductWarehouseId, 1)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get stock logs")
|
||||||
|
}
|
||||||
|
if len(stockLogs) > 0 {
|
||||||
|
latestStockLog := stockLogs[0]
|
||||||
|
increaseLog.Stock = latestStockLog.Stock
|
||||||
|
increaseLog.Stock += increaseLog.Increase
|
||||||
|
} else {
|
||||||
|
increaseLog.Stock += increaseLog.Increase
|
||||||
|
}
|
||||||
|
|
||||||
s.StockLogRepo.CreateOne(ctx, increaseLog, nil)
|
s.StockLogRepo.CreateOne(ctx, increaseLog, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||||
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
@@ -19,9 +23,6 @@ import (
|
|||||||
approvalutils "gitlab.com/mbugroup/lti-api.git/internal/utils/approvals"
|
approvalutils "gitlab.com/mbugroup/lti-api.git/internal/utils/approvals"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils/fifo"
|
"gitlab.com/mbugroup/lti-api.git/internal/utils/fifo"
|
||||||
recordingutil "gitlab.com/mbugroup/lti-api.git/internal/utils/recording"
|
recordingutil "gitlab.com/mbugroup/lti-api.git/internal/utils/recording"
|
||||||
"math"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
@@ -535,6 +536,17 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
|
|||||||
if egg.ProductWarehouseId == 0 || egg.Qty <= 0 {
|
if egg.ProductWarehouseId == 0 || egg.Qty <= 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
stockLogs, err := s.StockLogRepo.GetByProductWarehouse(ctx, egg.ProductWarehouseId, 1)
|
||||||
|
if err != nil {
|
||||||
|
s.Log.Errorf("Failed to get stock logs: %+v", err)
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get stock logs")
|
||||||
|
}
|
||||||
|
latestStockLog := &entity.StockLog{}
|
||||||
|
if len(stockLogs) > 0 {
|
||||||
|
latestStockLog = stockLogs[0]
|
||||||
|
} else {
|
||||||
|
latestStockLog.Stock = 0
|
||||||
|
}
|
||||||
logs = append(logs, &entity.StockLog{
|
logs = append(logs, &entity.StockLog{
|
||||||
ProductWarehouseId: egg.ProductWarehouseId,
|
ProductWarehouseId: egg.ProductWarehouseId,
|
||||||
CreatedBy: actorID,
|
CreatedBy: actorID,
|
||||||
@@ -542,6 +554,7 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
|
|||||||
LoggableType: string(utils.StockLogTypeRecording),
|
LoggableType: string(utils.StockLogTypeRecording),
|
||||||
LoggableId: recordingEntity.Id,
|
LoggableId: recordingEntity.Id,
|
||||||
Notes: note,
|
Notes: note,
|
||||||
|
Stock: latestStockLog.Stock - float64(egg.Qty),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if len(logs) > 0 {
|
if len(logs) > 0 {
|
||||||
@@ -937,6 +950,18 @@ func (s *recordingService) consumeRecordingStocks(
|
|||||||
LoggableId: stock.RecordingId,
|
LoggableId: stock.RecordingId,
|
||||||
Notes: note,
|
Notes: note,
|
||||||
}
|
}
|
||||||
|
stockLogs, err := s.StockLogRepo.GetByProductWarehouse(ctx, stock.ProductWarehouseId, 1)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get stock logs")
|
||||||
|
}
|
||||||
|
if len(stockLogs) > 0 {
|
||||||
|
latestStockLog := stockLogs[0]
|
||||||
|
log.Stock = latestStockLog.Stock
|
||||||
|
log.Stock -= log.Decrease
|
||||||
|
} else {
|
||||||
|
log.Stock -= log.Decrease
|
||||||
|
}
|
||||||
|
|
||||||
if err := s.StockLogRepo.WithTx(tx).CreateOne(ctx, log, nil); err != nil {
|
if err := s.StockLogRepo.WithTx(tx).CreateOne(ctx, log, nil); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1004,6 +1029,18 @@ func (s *recordingService) consumeRecordingDepletions(
|
|||||||
LoggableId: depletion.RecordingId,
|
LoggableId: depletion.RecordingId,
|
||||||
Notes: note,
|
Notes: note,
|
||||||
}
|
}
|
||||||
|
stockLogs, err := s.StockLogRepo.GetByProductWarehouse(ctx, sourceWarehouseID, 1)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get stock logs")
|
||||||
|
}
|
||||||
|
if len(stockLogs) > 0 {
|
||||||
|
latestStockLog := stockLogs[0]
|
||||||
|
log.Stock = latestStockLog.Stock
|
||||||
|
log.Stock -= log.Decrease
|
||||||
|
} else {
|
||||||
|
log.Stock -= log.Decrease
|
||||||
|
}
|
||||||
|
|
||||||
if err := s.StockLogRepo.WithTx(tx).CreateOne(ctx, log, nil); err != nil {
|
if err := s.StockLogRepo.WithTx(tx).CreateOne(ctx, log, nil); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1022,6 +1059,18 @@ func (s *recordingService) consumeRecordingDepletions(
|
|||||||
LoggableId: depletion.RecordingId,
|
LoggableId: depletion.RecordingId,
|
||||||
Notes: note,
|
Notes: note,
|
||||||
}
|
}
|
||||||
|
stockLogs, err := s.StockLogRepo.GetByProductWarehouse(ctx, depletion.ProductWarehouseId, 1)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get stock logs")
|
||||||
|
}
|
||||||
|
if len(stockLogs) > 0 {
|
||||||
|
latestStockLog := stockLogs[0]
|
||||||
|
log.Stock = latestStockLog.Stock
|
||||||
|
log.Stock += log.Increase
|
||||||
|
} else {
|
||||||
|
log.Stock += log.Increase
|
||||||
|
}
|
||||||
|
|
||||||
if err := s.StockLogRepo.WithTx(tx).CreateOne(ctx, log, nil); err != nil {
|
if err := s.StockLogRepo.WithTx(tx).CreateOne(ctx, log, nil); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1082,6 +1131,18 @@ func (s *recordingService) releaseRecordingStocks(
|
|||||||
LoggableId: stock.RecordingId,
|
LoggableId: stock.RecordingId,
|
||||||
Notes: note,
|
Notes: note,
|
||||||
}
|
}
|
||||||
|
stockLogs, err := s.StockLogRepo.GetByProductWarehouse(ctx, stock.ProductWarehouseId, 1)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get stock logs")
|
||||||
|
}
|
||||||
|
if len(stockLogs) > 0 {
|
||||||
|
latestStockLog := stockLogs[0]
|
||||||
|
log.Stock = latestStockLog.Stock
|
||||||
|
log.Stock += log.Increase
|
||||||
|
} else {
|
||||||
|
log.Stock += log.Increase
|
||||||
|
}
|
||||||
|
|
||||||
if err := s.StockLogRepo.WithTx(tx).CreateOne(ctx, log, nil); err != nil {
|
if err := s.StockLogRepo.WithTx(tx).CreateOne(ctx, log, nil); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1144,6 +1205,18 @@ func (s *recordingService) releaseRecordingDepletions(
|
|||||||
LoggableId: depletion.RecordingId,
|
LoggableId: depletion.RecordingId,
|
||||||
Notes: note,
|
Notes: note,
|
||||||
}
|
}
|
||||||
|
stockLogs, err := s.StockLogRepo.GetByProductWarehouse(ctx, sourceWarehouseID, 1)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get stock logs")
|
||||||
|
}
|
||||||
|
if len(stockLogs) > 0 {
|
||||||
|
latestStockLog := stockLogs[0]
|
||||||
|
log.Stock = latestStockLog.Stock
|
||||||
|
log.Stock += log.Increase
|
||||||
|
} else {
|
||||||
|
log.Stock += log.Increase
|
||||||
|
}
|
||||||
|
|
||||||
if err := s.StockLogRepo.WithTx(tx).CreateOne(ctx, log, nil); err != nil {
|
if err := s.StockLogRepo.WithTx(tx).CreateOne(ctx, log, nil); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1162,6 +1235,18 @@ func (s *recordingService) releaseRecordingDepletions(
|
|||||||
LoggableId: depletion.RecordingId,
|
LoggableId: depletion.RecordingId,
|
||||||
Notes: note,
|
Notes: note,
|
||||||
}
|
}
|
||||||
|
stockLogs, err := s.StockLogRepo.GetByProductWarehouse(ctx, depletion.ProductWarehouseId, 1)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get stock logs")
|
||||||
|
}
|
||||||
|
if len(stockLogs) > 0 {
|
||||||
|
latestStockLog := stockLogs[0]
|
||||||
|
log.Stock = latestStockLog.Stock
|
||||||
|
log.Stock -= log.Decrease
|
||||||
|
} else {
|
||||||
|
log.Stock -= log.Decrease
|
||||||
|
}
|
||||||
|
|
||||||
if err := s.StockLogRepo.WithTx(tx).CreateOne(ctx, log, nil); err != nil {
|
if err := s.StockLogRepo.WithTx(tx).CreateOne(ctx, log, nil); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1309,6 +1394,18 @@ func (s *recordingService) replenishRecordingEggs(
|
|||||||
LoggableId: egg.RecordingId,
|
LoggableId: egg.RecordingId,
|
||||||
Notes: note,
|
Notes: note,
|
||||||
}
|
}
|
||||||
|
stockLogs, err := s.StockLogRepo.GetByProductWarehouse(ctx, egg.ProductWarehouseId, 1)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get stock logs")
|
||||||
|
}
|
||||||
|
if len(stockLogs) > 0 {
|
||||||
|
latestStockLog := stockLogs[0]
|
||||||
|
log.Stock = latestStockLog.Stock
|
||||||
|
log.Stock += log.Increase
|
||||||
|
} else {
|
||||||
|
log.Stock += log.Increase
|
||||||
|
}
|
||||||
|
|
||||||
if err := s.StockLogRepo.WithTx(tx).CreateOne(ctx, log, nil); err != nil {
|
if err := s.StockLogRepo.WithTx(tx).CreateOne(ctx, log, nil); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1692,7 +1789,8 @@ func (s *recordingService) computeAndUpdateMetrics(ctx context.Context, tx *gorm
|
|||||||
|
|
||||||
var eggMass float64
|
var eggMass float64
|
||||||
if remainingChick > 0 && totalEggWeightGrams > 0 {
|
if remainingChick > 0 && totalEggWeightGrams > 0 {
|
||||||
eggMass = (totalEggWeightGrams / remainingChick) / 1000
|
// totalEggWeightGrams is in grams; egg mass is grams per hen.
|
||||||
|
eggMass = totalEggWeightGrams / remainingChick
|
||||||
updates["egg_mass"] = eggMass
|
updates["egg_mass"] = eggMass
|
||||||
recording.EggMass = &eggMass
|
recording.EggMass = &eggMass
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+25
@@ -2,6 +2,7 @@ package repository
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||||
@@ -16,6 +17,10 @@ type TransferLayingRepository interface {
|
|||||||
|
|
||||||
// Tambah method baru untuk query dengan filter lengkap
|
// Tambah method baru untuk query dengan filter lengkap
|
||||||
GetAllWithFilters(ctx context.Context, offset int, limit int, params *GetAllFilterParams) ([]entity.LayingTransfer, int64, error)
|
GetAllWithFilters(ctx context.Context, offset int, limit int, params *GetAllFilterParams) ([]entity.LayingTransfer, int64, error)
|
||||||
|
|
||||||
|
// Get sequence for movement number
|
||||||
|
GetNextMovementNumber(ctx context.Context) (int64, error)
|
||||||
|
GenerateMovementNumber(ctx context.Context) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type TransferLayingRepositoryImpl struct {
|
type TransferLayingRepositoryImpl struct {
|
||||||
@@ -29,6 +34,26 @@ func NewTransferLayingRepository(db *gorm.DB) TransferLayingRepository {
|
|||||||
db: db,
|
db: db,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *TransferLayingRepositoryImpl) GetNextMovementNumber(ctx context.Context) (int64, error) {
|
||||||
|
var seq int64
|
||||||
|
err := r.db.WithContext(ctx).Raw("SELECT nextval('transfer_laying_seq')").Scan(&seq).Error
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return seq, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *TransferLayingRepositoryImpl) GenerateMovementNumber(ctx context.Context) (string, error) {
|
||||||
|
seq, err := r.GetNextMovementNumber(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
movementNumber := fmt.Sprintf("TL-%05d", seq)
|
||||||
|
return movementNumber, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *TransferLayingRepositoryImpl) IdExists(ctx context.Context, id uint) (bool, error) {
|
func (r *TransferLayingRepositoryImpl) IdExists(ctx context.Context, id uint) (bool, error) {
|
||||||
return repository.Exists[entity.LayingTransfer](ctx, r.db, id)
|
return repository.Exists[entity.LayingTransfer](ctx, r.db, id)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -271,7 +271,11 @@ func (s *transferLayingService) CreateOne(c *fiber.Ctx, req *validation.Create)
|
|||||||
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))
|
||||||
}
|
}
|
||||||
|
|
||||||
transferNumber := fmt.Sprintf("TL-%d", time.Now().UnixNano())
|
transferNumber, err := s.Repository.GenerateMovementNumber(c.Context())
|
||||||
|
if err != nil {
|
||||||
|
s.Log.Errorf("Failed to generate movement number: %+v", err)
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Gagal membuat nomor transfer")
|
||||||
|
}
|
||||||
|
|
||||||
createBody := &entity.LayingTransfer{
|
createBody := &entity.LayingTransfer{
|
||||||
TransferNumber: transferNumber,
|
TransferNumber: transferNumber,
|
||||||
@@ -440,15 +444,105 @@ func (s *transferLayingService) UpdateOne(c *fiber.Ctx, req *validation.Update,
|
|||||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Target project flock not found")
|
return nil, fiber.NewError(fiber.StatusBadRequest, "Target project flock not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sourceKandangIDs := make([]uint, len(req.SourceKandangs))
|
||||||
|
for i, detail := range req.SourceKandangs {
|
||||||
|
sourceKandangIDs[i] = detail.ProjectFlockKandangId
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.validateKandangOwnership(
|
||||||
|
c.Context(),
|
||||||
|
req.SourceProjectFlockId,
|
||||||
|
sourceKandangIDs,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
targetKandangIDs := make([]uint, len(req.TargetKandangs))
|
||||||
|
for i, detail := range req.TargetKandangs {
|
||||||
|
targetKandangIDs[i] = detail.ProjectFlockKandangId
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.validateKandangOwnership(
|
||||||
|
c.Context(),
|
||||||
|
req.TargetProjectFlockId,
|
||||||
|
targetKandangIDs,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
transferDate, err := time.Parse("2006-01-02", req.TransferDate)
|
transferDate, err := time.Parse("2006-01-02", req.TransferDate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid transfer date format")
|
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid transfer date format")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var totalSourceQty, totalTargetQty float64
|
||||||
|
sourceWarehouseMap := make(map[uint]uint)
|
||||||
|
|
||||||
|
for _, sourceDetail := range req.SourceKandangs {
|
||||||
|
if sourceDetail.Quantity <= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
totalSourceQty += sourceDetail.Quantity
|
||||||
|
|
||||||
|
populations, err := s.ProjectFlockPopulationRepo.GetByProjectFlockKandangID(c.Context(), sourceDetail.ProjectFlockKandangId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalPopulation float64
|
||||||
|
var productWarehouseId uint
|
||||||
|
for _, pop := range populations {
|
||||||
|
totalPopulation += pop.TotalQty
|
||||||
|
if productWarehouseId == 0 {
|
||||||
|
productWarehouseId = pop.ProductWarehouseId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if totalPopulation == 0 {
|
||||||
|
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Kandang sumber %d tidak memiliki populasi untuk ditransfer", sourceDetail.ProjectFlockKandangId))
|
||||||
|
}
|
||||||
|
|
||||||
|
if totalPopulation < sourceDetail.Quantity {
|
||||||
|
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Kandang sumber %d jumlah tidak mencukupi. Tersedia: %.0f, Diminta: %.0f", sourceDetail.ProjectFlockKandangId, totalPopulation, sourceDetail.Quantity))
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceWarehouseMap[sourceDetail.ProjectFlockKandangId] = productWarehouseId
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, targetDetail := range req.TargetKandangs {
|
||||||
|
if targetDetail.Quantity <= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Jumlah total sumber (%.0f) harus sama dengan jumlah total tujuan (%.0f)", totalSourceQty, totalTargetQty))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ambil productWarehouseId pertama dari source yang valid (quantity > 0)
|
||||||
|
var firstProductWarehouseId uint
|
||||||
|
for _, sourceDetail := range req.SourceKandangs {
|
||||||
|
if sourceDetail.Quantity > 0 {
|
||||||
|
if pwId, ok := sourceWarehouseMap[sourceDetail.ProjectFlockKandangId]; ok {
|
||||||
|
firstProductWarehouseId = pwId
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
||||||
repoTx := s.Repository.WithTx(dbTransaction)
|
repoTx := s.Repository.WithTx(dbTransaction)
|
||||||
sourceRepo := s.LayingTransferSourceRepo.WithTx(dbTransaction)
|
sourceRepo := s.LayingTransferSourceRepo.WithTx(dbTransaction)
|
||||||
targetRepo := s.LayingTransferTargetRepo.WithTx(dbTransaction)
|
targetRepo := s.LayingTransferTargetRepo.WithTx(dbTransaction)
|
||||||
|
pwRepo := rInventory.NewProductWarehouseRepository(dbTransaction)
|
||||||
|
|
||||||
// Hapus old sources dan targets
|
// Hapus old sources dan targets
|
||||||
for _, oldSource := range existingTransfer.Sources {
|
for _, oldSource := range existingTransfer.Sources {
|
||||||
@@ -472,26 +566,11 @@ func (s *transferLayingService) UpdateOne(c *fiber.Ctx, req *validation.Update,
|
|||||||
|
|
||||||
// Create new sources dengan pending quantity
|
// Create new sources dengan pending quantity
|
||||||
for _, sourceDetail := range req.SourceKandangs {
|
for _, sourceDetail := range req.SourceKandangs {
|
||||||
populations, err := s.ProjectFlockPopulationRepo.GetByProjectFlockKandangID(c.Context(), sourceDetail.ProjectFlockKandangId)
|
if sourceDetail.Quantity == 0 {
|
||||||
if err != nil {
|
continue
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get populations")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(populations) == 0 {
|
productWarehouseId := sourceWarehouseMap[sourceDetail.ProjectFlockKandangId]
|
||||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Source kandang %d has no population available", sourceDetail.ProjectFlockKandangId))
|
|
||||||
}
|
|
||||||
|
|
||||||
var productWarehouseId uint
|
|
||||||
for _, pop := range populations {
|
|
||||||
if pop.ProductWarehouseId > 0 {
|
|
||||||
productWarehouseId = pop.ProductWarehouseId
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if productWarehouseId == 0 {
|
|
||||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Source kandang %d has no product warehouse", sourceDetail.ProjectFlockKandangId))
|
|
||||||
}
|
|
||||||
|
|
||||||
source := entity.LayingTransferSource{
|
source := entity.LayingTransferSource{
|
||||||
LayingTransferId: id,
|
LayingTransferId: id,
|
||||||
@@ -506,7 +585,18 @@ func (s *transferLayingService) UpdateOne(c *fiber.Ctx, req *validation.Update,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pwRepo := rInventory.NewProductWarehouseRepository(dbTransaction)
|
// Ambil product ID dari source warehouse pertama yang valid
|
||||||
|
var sourceProductID uint
|
||||||
|
if firstProductWarehouseId > 0 {
|
||||||
|
sourcePW, err := pwRepo.GetByID(c.Context(), firstProductWarehouseId, nil)
|
||||||
|
if err == nil {
|
||||||
|
sourceProductID = sourcePW.ProductId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if sourceProductID == 0 {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get product from source warehouse")
|
||||||
|
}
|
||||||
|
|
||||||
for _, targetDetail := range req.TargetKandangs {
|
for _, targetDetail := range req.TargetKandangs {
|
||||||
targetprojectFlockKandang, err := s.ProjectFlockKandangRepo.GetByID(c.Context(), targetDetail.ProjectFlockKandangId)
|
targetprojectFlockKandang, err := s.ProjectFlockKandangRepo.GetByID(c.Context(), targetDetail.ProjectFlockKandangId)
|
||||||
@@ -522,23 +612,6 @@ func (s *transferLayingService) UpdateOne(c *fiber.Ctx, req *validation.Update,
|
|||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get target warehouse")
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get target warehouse")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ambil product ID dari source yang pertama (semua sources seharusnya product-nya sama)
|
|
||||||
var sourceProductID uint
|
|
||||||
if len(req.SourceKandangs) > 0 {
|
|
||||||
firstSourceKandangID := req.SourceKandangs[0].ProjectFlockKandangId
|
|
||||||
populations, err := s.ProjectFlockPopulationRepo.GetByProjectFlockKandangID(c.Context(), firstSourceKandangID)
|
|
||||||
if err == nil && len(populations) > 0 && populations[0].ProductWarehouseId > 0 {
|
|
||||||
sourcePW, err := pwRepo.GetByID(c.Context(), populations[0].ProductWarehouseId, nil)
|
|
||||||
if err == nil {
|
|
||||||
sourceProductID = sourcePW.ProductId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if sourceProductID == 0 {
|
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get product from source warehouse")
|
|
||||||
}
|
|
||||||
|
|
||||||
targetPW, err := pwRepo.FindByProductWarehouseAndPfk(c.Context(), sourceProductID, targetWarehouse.Id, &targetDetail.ProjectFlockKandangId)
|
targetPW, err := pwRepo.FindByProductWarehouseAndPfk(c.Context(), sourceProductID, targetWarehouse.Id, &targetDetail.ProjectFlockKandangId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
@@ -753,6 +826,18 @@ func (s transferLayingService) Approval(c *fiber.Ctx, req *validation.Approve) (
|
|||||||
LoggableId: approvableID,
|
LoggableId: approvableID,
|
||||||
Notes: fmt.Sprintf("TL #%s", transfer.TransferNumber),
|
Notes: fmt.Sprintf("TL #%s", transfer.TransferNumber),
|
||||||
}
|
}
|
||||||
|
stockLogs, err := stockLogRepoTx.GetByProductWarehouse(c.Context(), *source.ProductWarehouseId, 1)
|
||||||
|
if err != nil {
|
||||||
|
s.Log.Errorf("Failed to get stock logs: %+v", err)
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get stock logs")
|
||||||
|
}
|
||||||
|
if len(stockLogs) > 0 {
|
||||||
|
latestStockLog := stockLogs[0]
|
||||||
|
stockLogDecrease.Stock = latestStockLog.Stock - stockLogDecrease.Decrease
|
||||||
|
} else {
|
||||||
|
stockLogDecrease.Stock -= stockLogDecrease.Decrease
|
||||||
|
}
|
||||||
|
|
||||||
if err := stockLogRepoTx.CreateOne(c.Context(), stockLogDecrease, nil); err != nil {
|
if err := stockLogRepoTx.CreateOne(c.Context(), stockLogDecrease, nil); err != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Gagal membuat log stok keluar")
|
return fiber.NewError(fiber.StatusInternalServerError, "Gagal membuat log stok keluar")
|
||||||
}
|
}
|
||||||
@@ -791,6 +876,18 @@ func (s transferLayingService) Approval(c *fiber.Ctx, req *validation.Approve) (
|
|||||||
LoggableId: approvableID,
|
LoggableId: approvableID,
|
||||||
Notes: fmt.Sprintf("TL #%s", transfer.TransferNumber),
|
Notes: fmt.Sprintf("TL #%s", transfer.TransferNumber),
|
||||||
}
|
}
|
||||||
|
stockLogs, err := stockLogRepoTx.GetByProductWarehouse(c.Context(), *target.ProductWarehouseId, 1)
|
||||||
|
if err != nil {
|
||||||
|
s.Log.Errorf("Failed to get stock logs: %+v", err)
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get stock logs")
|
||||||
|
}
|
||||||
|
if len(stockLogs) > 0 {
|
||||||
|
latestStockLog := stockLogs[0]
|
||||||
|
stockLogIncrease.Stock = latestStockLog.Stock + stockLogIncrease.Increase
|
||||||
|
} else {
|
||||||
|
stockLogIncrease.Stock += stockLogIncrease.Increase
|
||||||
|
}
|
||||||
|
|
||||||
if err := stockLogRepoTx.CreateOne(c.Context(), stockLogIncrease, nil); err != nil {
|
if err := stockLogRepoTx.CreateOne(c.Context(), stockLogIncrease, nil); err != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Gagal membuat log stok masuk")
|
return fiber.NewError(fiber.StatusInternalServerError, "Gagal membuat log stok masuk")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1081,10 +1081,25 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
|
|||||||
LoggableId: purchase.Id,
|
LoggableId: purchase.Id,
|
||||||
Notes: receiveNote,
|
Notes: receiveNote,
|
||||||
}
|
}
|
||||||
|
stockLogs, err := stockLogRepoTx.GetByProductWarehouse(ctx, entry.pwID, 1)
|
||||||
|
if err != nil {
|
||||||
|
s.Log.Errorf("Failed to get stock logs: %+v", err)
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get stock logs")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(stockLogs) > 0 {
|
||||||
|
latestStockLog := stockLogs[0]
|
||||||
|
log.Stock = latestStockLog.Stock
|
||||||
|
} else {
|
||||||
|
log.Stock = 0
|
||||||
|
}
|
||||||
|
|
||||||
if entry.delta > 0 {
|
if entry.delta > 0 {
|
||||||
log.Increase = entry.delta
|
log.Increase = entry.delta
|
||||||
|
log.Stock += log.Increase
|
||||||
} else {
|
} else {
|
||||||
log.Decrease = -entry.delta
|
log.Decrease = -entry.delta
|
||||||
|
log.Stock -= log.Decrease
|
||||||
}
|
}
|
||||||
logs = append(logs, log)
|
logs = append(logs, log)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -200,7 +200,7 @@ func (h *Controller) Refresh(c *fiber.Ctx) error {
|
|||||||
return fiber.NewError(fiber.StatusUnauthorized, "invalid access token")
|
return fiber.NewError(fiber.StatusUnauthorized, "invalid access token")
|
||||||
}
|
}
|
||||||
|
|
||||||
issueCookies(c, struct {
|
if err := issueCookies(c, struct {
|
||||||
AccessToken string `json:"access_token"`
|
AccessToken string `json:"access_token"`
|
||||||
RefreshToken string `json:"refresh_token"`
|
RefreshToken string `json:"refresh_token"`
|
||||||
TokenType string `json:"token_type"`
|
TokenType string `json:"token_type"`
|
||||||
@@ -218,7 +218,9 @@ func (h *Controller) Refresh(c *fiber.Ctx) error {
|
|||||||
IDToken: tokenResp.IDToken,
|
IDToken: tokenResp.IDToken,
|
||||||
Error: tokenResp.Error,
|
Error: tokenResp.Error,
|
||||||
Description: tokenResp.Description,
|
Description: tokenResp.Description,
|
||||||
}, verification)
|
}, verification); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
utils.Log.WithFields(logrus.Fields{
|
utils.Log.WithFields(logrus.Fields{
|
||||||
"user_id": verification.UserID,
|
"user_id": verification.UserID,
|
||||||
@@ -307,7 +309,9 @@ func (h *Controller) Callback(c *fiber.Ctx) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// prepare cookies
|
// prepare cookies
|
||||||
issueCookies(c, tokenResp, verification)
|
if err := issueCookies(c, tokenResp, verification); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
redirectTarget := sessionData.ReturnTo
|
redirectTarget := sessionData.ReturnTo
|
||||||
if redirectTarget == "" {
|
if redirectTarget == "" {
|
||||||
@@ -742,13 +746,21 @@ func issueCookies(c *fiber.Ctx, tokenResp struct {
|
|||||||
IDToken string `json:"id_token"`
|
IDToken string `json:"id_token"`
|
||||||
Error string `json:"error"`
|
Error string `json:"error"`
|
||||||
Description string `json:"error_description"`
|
Description string `json:"error_description"`
|
||||||
}, verification *sso.VerificationResult) {
|
}, verification *sso.VerificationResult) error {
|
||||||
if revoker := session.GetRevocationStore(); revoker != nil && verification != nil {
|
if revoker := session.GetRevocationStore(); revoker != nil && verification != nil {
|
||||||
if err := revoker.ClearUserLogout(c.Context(), verification.UserID); err != nil {
|
if err := revoker.ClearUserLogout(c.Context(), verification.UserID); err != nil {
|
||||||
utils.Log.WithError(err).Warn("failed to clear logout marker")
|
utils.Log.WithError(err).Warn("failed to clear logout marker")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if max := config.SSOAccessTokenMaxBytes; max > 0 && len(tokenResp.AccessToken) > max {
|
||||||
|
utils.Log.WithFields(logrus.Fields{
|
||||||
|
"token_len": len(tokenResp.AccessToken),
|
||||||
|
"max_len": max,
|
||||||
|
}).Warn("sso access token exceeds cookie size limit")
|
||||||
|
return fiber.NewError(fiber.StatusRequestEntityTooLarge, "access token too large")
|
||||||
|
}
|
||||||
|
|
||||||
accessName := resolveSSOCookieName(config.SSOAccessCookieName, "access")
|
accessName := resolveSSOCookieName(config.SSOAccessCookieName, "access")
|
||||||
refreshName := resolveSSOCookieName(config.SSORefreshCookieName, "refresh")
|
refreshName := resolveSSOCookieName(config.SSORefreshCookieName, "refresh")
|
||||||
maxAge := tokenResp.ExpiresIn
|
maxAge := tokenResp.ExpiresIn
|
||||||
@@ -790,6 +802,7 @@ func issueCookies(c *fiber.Ctx, tokenResp struct {
|
|||||||
|
|
||||||
// Optional: expose limited info via headers for FE debugging (avoid tokens)
|
// Optional: expose limited info via headers for FE debugging (avoid tokens)
|
||||||
c.Set("X-Auth-User", fmt.Sprintf("%d", verification.UserID))
|
c.Set("X-Auth-User", fmt.Sprintf("%d", verification.UserID))
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func clearSSOCookie(c *fiber.Ctx, name string) {
|
func clearSSOCookie(c *fiber.Ctx, name string) {
|
||||||
|
|||||||
@@ -291,6 +291,8 @@ func (h *UserSyncController) upsertUser(c *fiber.Ctx, alias string, req *userSyn
|
|||||||
"user_id": req.User.ID,
|
"user_id": req.User.ID,
|
||||||
}).Info("sso user synced")
|
}).Info("sso user synced")
|
||||||
|
|
||||||
|
sso.InvalidateProfileCache(c.Context(), uint(req.User.ID))
|
||||||
|
|
||||||
msg := fmt.Sprintf("User %s successfully", req.Action)
|
msg := fmt.Sprintf("User %s successfully", req.Action)
|
||||||
return c.Status(fiber.StatusOK).JSON(response.Success{
|
return c.Status(fiber.StatusOK).JSON(response.Success{
|
||||||
Code: fiber.StatusOK,
|
Code: fiber.StatusOK,
|
||||||
@@ -318,6 +320,8 @@ func (h *UserSyncController) logoutUser(c *fiber.Ctx, alias string, req *userSyn
|
|||||||
"user_id": req.User.ID,
|
"user_id": req.User.ID,
|
||||||
}).Info("sso user logout enforced")
|
}).Info("sso user logout enforced")
|
||||||
|
|
||||||
|
sso.InvalidateProfileCache(c.Context(), uint(req.User.ID))
|
||||||
|
|
||||||
return c.Status(fiber.StatusOK).JSON(response.Common{
|
return c.Status(fiber.StatusOK).JSON(response.Common{
|
||||||
Code: fiber.StatusOK,
|
Code: fiber.StatusOK,
|
||||||
Status: "success",
|
Status: "success",
|
||||||
@@ -341,6 +345,8 @@ func (h *UserSyncController) removeUser(c *fiber.Ctx, alias string, req *userSyn
|
|||||||
"user_id": req.User.ID,
|
"user_id": req.User.ID,
|
||||||
}).Info("sso user deleted")
|
}).Info("sso user deleted")
|
||||||
|
|
||||||
|
sso.InvalidateProfileCache(c.Context(), uint(req.User.ID))
|
||||||
|
|
||||||
return c.Status(fiber.StatusOK).JSON(response.Common{
|
return c.Status(fiber.StatusOK).JSON(response.Common{
|
||||||
Code: fiber.StatusOK,
|
Code: fiber.StatusOK,
|
||||||
Status: "success",
|
Status: "success",
|
||||||
|
|||||||
@@ -265,24 +265,44 @@ func profileCacheKey(userID uint) string {
|
|||||||
return profileCachePrefix + strconv.FormatUint(uint64(userID), 10)
|
return profileCachePrefix + strconv.FormatUint(uint64(userID), 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InvalidateProfileCache clears cached profile data for the given user in both local and Redis caches.
|
||||||
|
func InvalidateProfileCache(ctx context.Context, userID uint) {
|
||||||
|
if userID == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
key := profileCacheKey(userID)
|
||||||
|
profileLocalCache.Delete(key)
|
||||||
|
|
||||||
|
client := cache.Redis()
|
||||||
|
if client == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ctx == nil {
|
||||||
|
ctx = context.Background()
|
||||||
|
}
|
||||||
|
if err := client.Del(ctx, key).Err(); err != nil && !errors.Is(err, redis.Nil) {
|
||||||
|
utils.Log.WithError(err).Warn("sso profile redis delete failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func canonicalPermissionName(name string) string {
|
func canonicalPermissionName(name string) string {
|
||||||
return strings.ToLower(strings.TrimSpace(name))
|
return strings.ToLower(strings.TrimSpace(name))
|
||||||
}
|
}
|
||||||
|
|
||||||
// userInfoEnvelope handles the varying shapes returned by the SSO userinfo endpoint.
|
// userInfoEnvelope handles the varying shapes returned by the SSO userinfo endpoint.
|
||||||
type userInfoEnvelope struct {
|
type userInfoEnvelope struct {
|
||||||
Roles []userInfoRole `json:"roles"`
|
Roles []userInfoRole `json:"roles"`
|
||||||
AreaIDs []uint `json:"area_ids"`
|
AreaIDs []uint `json:"area_ids"`
|
||||||
LocationIDs []uint `json:"location_ids"`
|
LocationIDs []uint `json:"location_ids"`
|
||||||
AllArea bool `json:"all_area"`
|
AllArea bool `json:"all_area"`
|
||||||
AllLocation bool `json:"all_location"`
|
AllLocation bool `json:"all_location"`
|
||||||
Data *struct {
|
Data *struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
Roles []userInfoRole `json:"roles"`
|
Roles []userInfoRole `json:"roles"`
|
||||||
AreaIDs []uint `json:"area_ids"`
|
AreaIDs []uint `json:"area_ids"`
|
||||||
LocationIDs []uint `json:"location_ids"`
|
LocationIDs []uint `json:"location_ids"`
|
||||||
AllArea bool `json:"all_area"`
|
AllArea bool `json:"all_area"`
|
||||||
AllLocation bool `json:"all_location"`
|
AllLocation bool `json:"all_location"`
|
||||||
} `json:"data"`
|
} `json:"data"`
|
||||||
User *struct {
|
User *struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
|
|||||||
Reference in New Issue
Block a user