mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-25 07:45:44 +00:00
Merge branch 'development' into 'staging'
Development See merge request mbugroup/lti-api!217
This commit is contained in:
@@ -0,0 +1,13 @@
|
|||||||
|
# .air.toml
|
||||||
|
root = "."
|
||||||
|
tmp_dir = "tmp"
|
||||||
|
|
||||||
|
[build]
|
||||||
|
cmd = "go build -buildvcs=false -o ./tmp/main ./cmd/api"
|
||||||
|
bin = "tmp/main"
|
||||||
|
full_bin = "APP_ENV=dev ./tmp/main"
|
||||||
|
include_ext = ["go", "tpl", "tmpl", "html"]
|
||||||
|
exclude_dir = ["vendor", "tmp"]
|
||||||
|
|
||||||
|
[log]
|
||||||
|
time = true
|
||||||
+81
-164
@@ -1,173 +1,90 @@
|
|||||||
stages:
|
stages:
|
||||||
- build
|
|
||||||
- migrate
|
|
||||||
- deploy
|
- deploy
|
||||||
- seed
|
|
||||||
|
|
||||||
default:
|
deploy-dev:
|
||||||
tags:
|
|
||||||
- self-hosted-stg
|
|
||||||
|
|
||||||
workflow:
|
|
||||||
rules:
|
|
||||||
- if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "staging"'
|
|
||||||
when: always
|
|
||||||
- when: never
|
|
||||||
|
|
||||||
variables:
|
|
||||||
DOCKER_BUILDKIT: "1"
|
|
||||||
|
|
||||||
IMAGE_TAG: "staging_${CI_COMMIT_SHORT_SHA}"
|
|
||||||
IMAGE_NAME: "${CI_REGISTRY_IMAGE}:${IMAGE_TAG}"
|
|
||||||
IMAGE_LATEST: "${CI_REGISTRY_IMAGE}:staging_latest"
|
|
||||||
|
|
||||||
DEPLOY_DIR: "/opt/deploy/stg-lti-api"
|
|
||||||
COMPOSE_FILE: "docker-compose.yaml"
|
|
||||||
|
|
||||||
# =========================
|
|
||||||
# BUILD (AUTO)
|
|
||||||
# =========================
|
|
||||||
build_staging:
|
|
||||||
stage: build
|
|
||||||
rules:
|
|
||||||
- if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "staging"'
|
|
||||||
script: |
|
|
||||||
set -e
|
|
||||||
docker info
|
|
||||||
|
|
||||||
echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY"
|
|
||||||
|
|
||||||
echo "✅ Build image: $IMAGE_NAME"
|
|
||||||
docker build -t "$IMAGE_NAME" -f Dockerfile .
|
|
||||||
|
|
||||||
echo "✅ Push image: $IMAGE_NAME"
|
|
||||||
docker push "$IMAGE_NAME"
|
|
||||||
|
|
||||||
echo "✅ Tag latest: $IMAGE_LATEST"
|
|
||||||
docker tag "$IMAGE_NAME" "$IMAGE_LATEST"
|
|
||||||
docker push "$IMAGE_LATEST"
|
|
||||||
|
|
||||||
|
|
||||||
# =========================
|
|
||||||
# MIGRATE (AUTO)
|
|
||||||
# =========================
|
|
||||||
migrate_staging:
|
|
||||||
stage: migrate
|
|
||||||
rules:
|
|
||||||
- if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "staging"'
|
|
||||||
needs:
|
|
||||||
- job: build_staging
|
|
||||||
artifacts: false
|
|
||||||
script: |
|
|
||||||
set -e
|
|
||||||
echo "✅ Running migrations (staging) ..."
|
|
||||||
|
|
||||||
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
|
|
||||||
. ./.env
|
|
||||||
set +a
|
|
||||||
|
|
||||||
# ✅ validasi
|
|
||||||
test -n "$DB_HOST" || (echo "❌ DB_HOST empty" && exit 1)
|
|
||||||
test -n "$DB_PORT" || (echo "❌ DB_PORT empty" && exit 1)
|
|
||||||
test -n "$DB_USER" || (echo "❌ DB_USER empty" && exit 1)
|
|
||||||
test -n "$DB_PASSWORD" || (echo "❌ DB_PASSWORD empty" && exit 1)
|
|
||||||
test -n "$DB_NAME" || (echo "❌ DB_NAME empty" && exit 1)
|
|
||||||
|
|
||||||
export DATABASE_URL="postgres://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}?sslmode=${DB_SSLMODE:-disable}"
|
|
||||||
echo "✅ DATABASE_URL=$DATABASE_URL"
|
|
||||||
|
|
||||||
# ✅ Pastikan postgres & redis ON (sesuaikan nama service compose kamu!)
|
|
||||||
echo "✅ Ensuring postgres & redis running ..."
|
|
||||||
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 \
|
|
||||||
-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"
|
|
||||||
|
|
||||||
|
|
||||||
# =========================
|
|
||||||
# DEPLOY (AUTO)
|
|
||||||
# =========================
|
|
||||||
deploy_staging:
|
|
||||||
stage: deploy
|
stage: deploy
|
||||||
rules:
|
image: alpine:3.20
|
||||||
- if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "staging"'
|
variables:
|
||||||
needs:
|
DEPLOY_APP: "LTI-MBUGROUP"
|
||||||
- job: migrate_staging
|
# Opsional: kalau pakai submodule, ini bikin clone submodule pakai SSH juga
|
||||||
artifacts: false
|
GIT_SUBMODULE_STRATEGY: recursive
|
||||||
- job: build_staging
|
GIT_DEPTH: "1"
|
||||||
artifacts: false
|
|
||||||
script: |
|
|
||||||
set -e
|
|
||||||
docker info
|
|
||||||
echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY"
|
|
||||||
|
|
||||||
cd "$DEPLOY_DIR"
|
before_script:
|
||||||
test -f "$COMPOSE_FILE" || (echo "❌ $COMPOSE_FILE not found in $DEPLOY_DIR" && exit 1)
|
- echo "🧰 Installing dependencies..."
|
||||||
test -f .env || (echo "❌ .env not found in $DEPLOY_DIR" && exit 1)
|
- apk update && apk add --no-cache openssh git curl bash
|
||||||
|
|
||||||
docker compose -f "$COMPOSE_FILE" pull
|
# Setup SSH di runner
|
||||||
docker compose -f "$COMPOSE_FILE" up -d --force-recreate
|
- mkdir -p ~/.ssh
|
||||||
docker image prune -f
|
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' > ~/.ssh/id_rsa
|
||||||
|
- chmod 600 ~/.ssh/id_rsa
|
||||||
|
- eval "$(ssh-agent -s)"
|
||||||
|
- ssh-add ~/.ssh/id_rsa
|
||||||
|
|
||||||
|
# Trust host keys (server + gitlab) biar SSH gak nanya interaktif
|
||||||
|
- ssh-keyscan -H "$SERVER_IP" >> ~/.ssh/known_hosts
|
||||||
|
- ssh-keyscan -H gitlab.com >> ~/.ssh/known_hosts
|
||||||
|
|
||||||
# =========================
|
script:
|
||||||
# SEED (MANUAL)
|
- echo "🚀 Deploying latest code to $SERVER_USER@$SERVER_IP"
|
||||||
# =========================
|
|
||||||
seed_staging:
|
|
||||||
stage: seed
|
|
||||||
rules:
|
|
||||||
- if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "staging"'
|
|
||||||
needs:
|
|
||||||
- job: deploy_staging
|
|
||||||
artifacts: false
|
|
||||||
when: manual
|
|
||||||
allow_failure: false
|
|
||||||
script: |
|
|
||||||
set -e
|
|
||||||
cd "$DEPLOY_DIR"
|
|
||||||
test -f "$COMPOSE_FILE" || (echo "❌ $COMPOSE_FILE not found" && exit 1)
|
|
||||||
test -f .env || (echo "❌ .env not found" && exit 1)
|
|
||||||
|
|
||||||
docker compose -f "$COMPOSE_FILE" pull seed || true
|
- >
|
||||||
docker compose -f "$COMPOSE_FILE" run --rm seed
|
if ssh -o StrictHostKeyChecking=no "$SERVER_USER@$SERVER_IP" "
|
||||||
|
set -e
|
||||||
|
|
||||||
|
cd /home/devops/docker/deployment/development/lti-api
|
||||||
|
|
||||||
|
# Pastikan remote origin SSH (antisipasi kalau pernah ke-set HTTPS)
|
||||||
|
git remote set-url origin git@gitlab.com:mbugroup/lti-api.git
|
||||||
|
|
||||||
|
# Pastikan server percaya gitlab.com juga (untuk git fetch via SSH)
|
||||||
|
mkdir -p ~/.ssh
|
||||||
|
ssh-keyscan -H gitlab.com >> ~/.ssh/known_hosts
|
||||||
|
|
||||||
|
# Fetch/reset pakai SSH
|
||||||
|
GIT_SSH_COMMAND='ssh -o StrictHostKeyChecking=no' git fetch origin development
|
||||||
|
git reset --hard origin/development
|
||||||
|
|
||||||
|
docker compose restart dev-api-lti || docker compose up -d dev-api-lti
|
||||||
|
"; then
|
||||||
|
STATUS='success';
|
||||||
|
else
|
||||||
|
STATUS='failed';
|
||||||
|
fi;
|
||||||
|
|
||||||
|
RUN_URL="${CI_PROJECT_URL}/-/pipelines/${CI_PIPELINE_ID}";
|
||||||
|
|
||||||
|
if [ "$STATUS" = "success" ]; then
|
||||||
|
COLOR=3066993;
|
||||||
|
TITLE="✅ Deployment API Succeeded";
|
||||||
|
DESC="Deployment job on branch \`${CI_COMMIT_REF_NAME}\` completed successfully.";
|
||||||
|
else
|
||||||
|
COLOR=15158332;
|
||||||
|
TITLE="❌ Deployment API Failed Gaes";
|
||||||
|
DESC="Deployment job on branch \`${CI_COMMIT_REF_NAME}\` failed.";
|
||||||
|
fi;
|
||||||
|
|
||||||
|
echo "{
|
||||||
|
\"username\": \"CI Bot\",
|
||||||
|
\"embeds\": [{
|
||||||
|
\"title\": \"$TITLE\",
|
||||||
|
\"description\": \"$DESC\",
|
||||||
|
\"color\": $COLOR,
|
||||||
|
\"fields\": [
|
||||||
|
{\"name\": \"Repository\", \"value\": \"${CI_PROJECT_PATH}\", \"inline\": true},
|
||||||
|
{\"name\": \"Actor\", \"value\": \"${GITLAB_USER_LOGIN}\", \"inline\": true},
|
||||||
|
{\"name\": \"Commit\", \"value\": \"${CI_COMMIT_SHA}\", \"inline\": false},
|
||||||
|
{\"name\": \"Pipeline\", \"value\": \"[Open run](${RUN_URL})\", \"inline\": false}
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
}" > payload.json;
|
||||||
|
|
||||||
|
echo "📡 Sending notification to Discord...";
|
||||||
|
curl -sS -H "Content-Type: application/json" \
|
||||||
|
-d @payload.json "$DISCORD_WEBHOOK_URL";
|
||||||
|
|
||||||
|
only:
|
||||||
|
- development
|
||||||
|
|
||||||
|
environment:
|
||||||
|
name: development
|
||||||
@@ -237,6 +237,8 @@ func (u *ClosingController) GetClosingSapronak(c *fiber.Ctx) error {
|
|||||||
|
|
||||||
query := &validation.ClosingSapronakQuery{
|
query := &validation.ClosingSapronakQuery{
|
||||||
Type: strings.ToLower(c.Query("type")),
|
Type: strings.ToLower(c.Query("type")),
|
||||||
|
Page: c.QueryInt("page", 1),
|
||||||
|
Limit: c.QueryInt("limit", 10),
|
||||||
Search: c.Query("search"),
|
Search: c.Query("search"),
|
||||||
}
|
}
|
||||||
if raw := c.Query("kandang_id"); raw != "" {
|
if raw := c.Query("kandang_id"); raw != "" {
|
||||||
@@ -248,6 +250,10 @@ func (u *ClosingController) GetClosingSapronak(c *fiber.Ctx) error {
|
|||||||
query.KandangID = &kandangUint
|
query.KandangID = &kandangUint
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if query.Page < 1 || query.Limit < 1 {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0")
|
||||||
|
}
|
||||||
|
|
||||||
if query.Type != validation.SapronakTypeIncoming && query.Type != validation.SapronakTypeOutgoing {
|
if query.Type != validation.SapronakTypeIncoming && query.Type != validation.SapronakTypeOutgoing {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "type must be either incoming or outgoing")
|
return fiber.NewError(fiber.StatusBadRequest, "type must be either incoming or outgoing")
|
||||||
}
|
}
|
||||||
@@ -282,8 +288,6 @@ func (u *ClosingController) GetClosingSapronakSummary(c *fiber.Ctx) error {
|
|||||||
|
|
||||||
query := &validation.ClosingSapronakQuery{
|
query := &validation.ClosingSapronakQuery{
|
||||||
Type: strings.ToLower(c.Query("type")),
|
Type: strings.ToLower(c.Query("type")),
|
||||||
Page: c.QueryInt("page", 1),
|
|
||||||
Limit: c.QueryInt("limit", 10),
|
|
||||||
Search: c.Query("search"),
|
Search: c.Query("search"),
|
||||||
}
|
}
|
||||||
if raw := c.Query("kandang_id"); raw != "" {
|
if raw := c.Query("kandang_id"); raw != "" {
|
||||||
@@ -295,10 +299,6 @@ func (u *ClosingController) GetClosingSapronakSummary(c *fiber.Ctx) error {
|
|||||||
query.KandangID = &kandangUint
|
query.KandangID = &kandangUint
|
||||||
}
|
}
|
||||||
|
|
||||||
if query.Page < 1 || query.Limit < 1 {
|
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0")
|
|
||||||
}
|
|
||||||
|
|
||||||
if query.Type != validation.SapronakTypeIncoming && query.Type != validation.SapronakTypeOutgoing {
|
if query.Type != validation.SapronakTypeIncoming && query.Type != validation.SapronakTypeOutgoing {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "type must be either incoming or outgoing")
|
return fiber.NewError(fiber.StatusBadRequest, "type must be either incoming or outgoing")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ func ClosingRoutes(v1 fiber.Router, u user.UserService, s closing.ClosingService
|
|||||||
route.Get("/:project_flock_id/:project_flock_kandang_id/perhitungan_sapronak", m.RequirePermissions(m.P_ClosingDetail), ctrl.GetSapronakByKandang)
|
route.Get("/:project_flock_id/:project_flock_kandang_id/perhitungan_sapronak", m.RequirePermissions(m.P_ClosingDetail), ctrl.GetSapronakByKandang)
|
||||||
route.Get("/:project_flock_id/perhitungan_sapronak", m.RequirePermissions(m.P_ClosingDetail), ctrl.GetSapronakByProject)
|
route.Get("/:project_flock_id/perhitungan_sapronak", m.RequirePermissions(m.P_ClosingDetail), ctrl.GetSapronakByProject)
|
||||||
route.Get("/:projectFlockId/sapronak", m.RequirePermissions(m.P_ClosingDetail), ctrl.GetClosingSapronak)
|
route.Get("/:projectFlockId/sapronak", m.RequirePermissions(m.P_ClosingDetail), ctrl.GetClosingSapronak)
|
||||||
|
route.Get("/:projectFlockId/sapronak/summary", m.RequirePermissions(m.P_ClosingDetail), ctrl.GetClosingSapronakSummary)
|
||||||
route.Get("/:project_flock_id/expedition-hpp", m.RequirePermissions(m.P_ClosingDetail), ctrl.GetExpeditionHPP)
|
route.Get("/:project_flock_id/expedition-hpp", m.RequirePermissions(m.P_ClosingDetail), ctrl.GetExpeditionHPP)
|
||||||
route.Get("/:project_flock_id/:project_flock_kandang_id/expedition-hpp", m.RequirePermissions(m.P_ClosingDetail), ctrl.GetExpeditionHPPByKandang)
|
route.Get("/:project_flock_id/:project_flock_kandang_id/expedition-hpp", m.RequirePermissions(m.P_ClosingDetail), ctrl.GetExpeditionHPPByKandang)
|
||||||
route.Get("/:projectFlockId/production-data", m.RequirePermissions(m.P_ClosingDetail), ctrl.GetClosingDataProduksi)
|
route.Get("/:projectFlockId/production-data", m.RequirePermissions(m.P_ClosingDetail), ctrl.GetClosingDataProduksi)
|
||||||
|
|||||||
@@ -751,6 +751,9 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
|
|||||||
if receivedQty > item.SubQty {
|
if receivedQty > item.SubQty {
|
||||||
return nil, utils.BadRequest(fmt.Sprintf("Received quantity for item %d cannot exceed ordered quantity (%.3f)", payload.PurchaseItemID, item.SubQty))
|
return nil, utils.BadRequest(fmt.Sprintf("Received quantity for item %d cannot exceed ordered quantity (%.3f)", payload.PurchaseItemID, item.SubQty))
|
||||||
}
|
}
|
||||||
|
if receivedQty < item.TotalUsed {
|
||||||
|
return nil, utils.BadRequest(fmt.Sprintf("Received quantity for item %d cannot be lower than used amount (%.3f)", payload.PurchaseItemID, item.TotalUsed))
|
||||||
|
}
|
||||||
|
|
||||||
if _, dup := visitedItems[payload.PurchaseItemID]; dup {
|
if _, dup := visitedItems[payload.PurchaseItemID]; dup {
|
||||||
return nil, utils.BadRequest(fmt.Sprintf("Duplicate receiving data for item %d", payload.PurchaseItemID))
|
return nil, utils.BadRequest(fmt.Sprintf("Duplicate receiving data for item %d", payload.PurchaseItemID))
|
||||||
@@ -835,6 +838,7 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
|
|||||||
affected := make(map[uint]struct{})
|
affected := make(map[uint]struct{})
|
||||||
updates := make([]rPurchase.PurchaseReceivingUpdate, 0, len(prepared))
|
updates := make([]rPurchase.PurchaseReceivingUpdate, 0, len(prepared))
|
||||||
priceUpdates := make([]rPurchase.PurchasePricingUpdate, 0, len(prepared))
|
priceUpdates := make([]rPurchase.PurchasePricingUpdate, 0, len(prepared))
|
||||||
|
totalQtyDeltas := make(map[uint]float64)
|
||||||
fifoAdds := make([]struct {
|
fifoAdds := make([]struct {
|
||||||
itemID uint
|
itemID uint
|
||||||
pwID uint
|
pwID uint
|
||||||
@@ -862,14 +866,20 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
|
|||||||
deltaQty := prep.receivedQty - item.TotalQty
|
deltaQty := prep.receivedQty - item.TotalQty
|
||||||
switch {
|
switch {
|
||||||
case deltaQty > 0 && newPWID != nil:
|
case deltaQty > 0 && newPWID != nil:
|
||||||
fifoAdds = append(fifoAdds, struct {
|
if s.FifoSvc != nil {
|
||||||
itemID uint
|
fifoAdds = append(fifoAdds, struct {
|
||||||
pwID uint
|
itemID uint
|
||||||
qty float64
|
pwID uint
|
||||||
}{itemID: item.Id, pwID: *newPWID, qty: deltaQty})
|
qty float64
|
||||||
|
}{itemID: item.Id, pwID: *newPWID, qty: deltaQty})
|
||||||
|
} else {
|
||||||
|
deltas[*newPWID] += deltaQty
|
||||||
|
totalQtyDeltas[item.Id] += deltaQty
|
||||||
|
}
|
||||||
case deltaQty < 0 && newPWID != nil:
|
case deltaQty < 0 && newPWID != nil:
|
||||||
deltas[*newPWID] += deltaQty // negative
|
deltas[*newPWID] += deltaQty // negative
|
||||||
affected[*newPWID] = struct{}{}
|
affected[*newPWID] = struct{}{}
|
||||||
|
totalQtyDeltas[item.Id] += deltaQty
|
||||||
}
|
}
|
||||||
|
|
||||||
dateCopy := prep.receivedDate
|
dateCopy := prep.receivedDate
|
||||||
@@ -892,7 +902,7 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
|
|||||||
|
|
||||||
updates = append(updates, update)
|
updates = append(updates, update)
|
||||||
|
|
||||||
if item.Price > 0 && prep.receivedQty >= 0 {
|
if prep.receivedQty >= 0 {
|
||||||
priceUpdates = append(priceUpdates, rPurchase.PurchasePricingUpdate{
|
priceUpdates = append(priceUpdates, rPurchase.PurchasePricingUpdate{
|
||||||
ItemID: item.Id,
|
ItemID: item.Id,
|
||||||
Price: item.Price,
|
Price: item.Price,
|
||||||
@@ -919,6 +929,19 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(totalQtyDeltas) > 0 {
|
||||||
|
for itemID, delta := range totalQtyDeltas {
|
||||||
|
if delta == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := tx.Model(&entity.PurchaseItem{}).
|
||||||
|
Where("purchase_id = ? AND id = ?", purchase.Id, itemID).
|
||||||
|
Update("total_qty", gorm.Expr("COALESCE(total_qty,0) + ?", delta)).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Update due_date based on earliest received date when receiving approved.
|
// Update due_date based on earliest received date when receiving approved.
|
||||||
if earliestReceived != nil {
|
if earliestReceived != nil {
|
||||||
due := earliestReceived.AddDate(0, 0, purchase.CreditTerm)
|
due := earliestReceived.AddDate(0, 0, purchase.CreditTerm)
|
||||||
@@ -1371,10 +1394,6 @@ func (s *purchaseService) buildStaffAdjustmentPayload(
|
|||||||
qtyCopy := effectiveQty
|
qtyCopy := effectiveQty
|
||||||
update.Quantity = &qtyCopy
|
update.Quantity = &qtyCopy
|
||||||
}
|
}
|
||||||
if syncReceiving {
|
|
||||||
qtyCopy := effectiveQty
|
|
||||||
update.TotalQty = &qtyCopy
|
|
||||||
}
|
|
||||||
|
|
||||||
updates = append(updates, update)
|
updates = append(updates, update)
|
||||||
delete(requestItems, item.Id)
|
delete(requestItems, item.Id)
|
||||||
|
|||||||
Reference in New Issue
Block a user