پرش به محتوای اصلی
فین‌تک و حسابداری
۱ دقیقه مطالعه

کاردکس کالا و روش FIFO در نرم‌افزار انبارداری: از تئوری تا کوئری

پیاده‌سازی بهای تمام‌شده کالا به روش FIFO در یک نرم‌افزار انبارداری: لایه‌های موجودی، مصرف لایه‌ها هنگام فروش و ثبت سند حسابداری بهای کالای فروش‌رفته.

سید محمد بهبهانی‌زاده
توسعه‌دهنده فول‌استک و معمار نرم‌افزار

وقتی یک کالا را در زمان‌های مختلف با قیمت‌های متفاوت خریده‌اید، سود واقعی فروش به این بستگی دارد که بهای تمام‌شده را چطور حساب کنید. در روش FIFO (اولین ورودی، اولین خروجی) فرض می‌شود قدیمی‌ترین موجودی زودتر فروخته می‌شود.

لایه‌های موجودی

هر رسید خرید یک «لایه» با مقدار باقی‌مانده و بهای واحد می‌سازد. موجودی یک کالا جمع مقدار باقی‌مانده همه لایه‌های آن است. این مدل، کاردکس کالا را دقیق و قابل حسابرسی نگه می‌دارد.

sql
CREATE TABLE stock_layer (
  id           BIGSERIAL PRIMARY KEY,
  item_id      BIGINT NOT NULL REFERENCES item(id),
  warehouse_id BIGINT NOT NULL REFERENCES warehouse(id),
  received_at  TIMESTAMPTZ NOT NULL,
  qty_in       NUMERIC(18, 3) NOT NULL,
  qty_left     NUMERIC(18, 3) NOT NULL CHECK (qty_left >= 0),
  unit_cost    NUMERIC(20, 2) NOT NULL
);

CREATE INDEX idx_layer_fifo
  ON stock_layer (item_id, warehouse_id, received_at)
  WHERE qty_left > 0;

مصرف لایه‌ها هنگام فروش

هنگام فروش، از قدیمی‌ترین لایه شروع کرده و مقدار را کم می‌کنیم تا مقدار فروش پوشش داده شود. جمع «مقدار × بهای واحد» لایه‌های مصرف‌شده، بهای کالای فروش‌رفته است. همه این کار باید داخل یک تراکنش و با قفل ردیف‌ها انجام شود تا دو فروش هم‌زمان یک لایه را دوبار مصرف نکنند.

typescript
async function consumeFifo(tx: Tx, itemId: bigint, whId: bigint, qty: Decimal) {
  const layers = await tx.query(
    `SELECT id, qty_left, unit_cost FROM stock_layer
      WHERE item_id = $1 AND warehouse_id = $2 AND qty_left > 0
      ORDER BY received_at, id
      FOR UPDATE`, [itemId, whId]);

  let remaining = qty;
  let cost = new Decimal(0);
  for (const l of layers.rows) {
    if (remaining.isZero()) break;
    const take = Decimal.min(remaining, l.qty_left);
    await tx.query('UPDATE stock_layer SET qty_left = qty_left - $1 WHERE id = $2',
                   [take.toString(), l.id]);
    cost = cost.plus(take.times(l.unit_cost));
    remaining = remaining.minus(take);
  }
  if (!remaining.isZero()) throw new Error('موجودی کافی نیست');
  return cost; // بهای کالای فروش‌رفته
}

نکات کلیدی

  • FOR UPDATE از مصرف هم‌زمان یک لایه جلوگیری می‌کند.
  • بهای تمام‌شده با Decimal حساب می‌شود، نه Float.
  • سند حسابداری بهای کالای فروش‌رفته در همان تراکنش ثبت شود.

جمع‌بندی

FIFO وقتی درست پیاده شود، هم کاردکس کالا را قابل اعتماد می‌کند و هم سود واقعی هر فروش را نشان می‌دهد. کلید کار، لایه‌های صریح، تراکنش و قفل ردیف است.

داده و دیتابیسخبر
۴ دقیقه مطالعهtheregister.com

تعویق PostgreSQL 19؛ بازبینی کد با هوش مصنوعی بیش از توان توسعه‌دهندگان باگ یافت

انتشار PostgreSQL 19 به حدود ۱۹ نوامبر عقب افتاد. بازبینی کد با هوش مصنوعی آن‌قدر باگ پیدا کرد که بین ۸ تا ۱۰ قابلیت مهم، از جمله SQL/PGQ، از این نسخه کنار رفت.

خواندن خبر#PostgreSQL