2
0
mirror of https://github.com/frappe/books.git synced 2025-01-03 15:17:30 +00:00

incr: add StockQueue manager class

This commit is contained in:
18alantom 2022-11-09 19:18:03 +05:30
parent e1123ecc3a
commit a277c748fb
2 changed files with 204 additions and 0 deletions

View File

@ -0,0 +1,88 @@
export class StockQueue {
quantity: number;
value: number;
queue: { rate: number; quantity: number }[];
movingAverage: number;
constructor() {
this.value = 0;
this.quantity = 0;
this.movingAverage = 0;
this.queue = [];
}
get fifo() {
/**
* Stock value maintained is based on the stock queue
* FIFO by default. This returns FIFO valuation rate.
*/
const valuation = this.value / this.quantity;
if (Number.isNaN(valuation)) {
return 0;
}
return valuation;
}
inward(rate: number, quantity: number): null | number {
if (quantity <= 0 || rate < 0) {
return null;
}
const inwardValue = rate * quantity;
/**
* Update Moving Average valuation
*/
this.movingAverage =
(this.movingAverage * this.quantity + inwardValue) /
(this.quantity + quantity);
this.quantity += quantity;
this.value += inwardValue;
const last = this.queue.at(-1);
if (last?.rate !== rate) {
this.queue.push({ rate, quantity });
} else {
last.quantity += quantity;
}
return rate;
}
outward(quantity: number): null | number {
if (this.quantity < quantity || quantity <= 0) {
return null;
}
let incomingRate: number = 0;
this.quantity -= quantity;
let remaining = quantity;
while (remaining > 0) {
const last = this.queue.shift();
if (last === undefined) {
return null;
}
const storedQuantity = last.quantity;
let quantityRemoved = remaining;
const quantityLeft = storedQuantity - remaining;
remaining = remaining - storedQuantity;
if (remaining > 0) {
quantityRemoved = storedQuantity;
}
if (quantityLeft > 0) {
this.queue.unshift({ rate: last.rate, quantity: quantityLeft });
}
this.value -= last.rate * quantityRemoved;
incomingRate += quantityRemoved * last.rate;
}
return incomingRate / quantity;
}
}

View File

@ -0,0 +1,116 @@
import test from 'tape';
import { StockQueue } from '../stockQueue';
test('stockQueue:initialization', (t) => {
const q = new StockQueue();
t.equal(q.quantity, 0);
t.equal(q.value, 0);
t.equal(q.queue.length, 0);
t.end();
});
test('stockQueue:operations', (t) => {
const q = new StockQueue();
t.equal(q.inward(100, 4), 100);
t.equal(q.fifo, 100);
t.equal(q.movingAverage, 100);
t.equal(q.queue.length, 1);
t.equal(q.quantity, 4);
t.equal(q.value, 400);
t.equal(q.inward(200, 8), 200);
t.equal(q.fifo, (400 + 1600) / 12);
t.equal(q.movingAverage, (400 + 1600) / 12);
t.equal(q.queue.length, 2);
t.equal(q.quantity, 4 + 8);
t.equal(q.value, 400 + 1600);
t.equal(q.inward(300, 3), 300);
t.equal(q.fifo, (400 + 1600 + 900) / 15);
t.equal(q.movingAverage, (400 + 1600 + 900) / 15);
t.equal(q.queue.length, 3);
t.equal(q.quantity, 4 + 8 + 3);
t.equal(q.value, 400 + 1600 + 900);
t.equal(q.outward(3), 100);
t.equal(q.fifo, (100 + 1600 + 900) / 12);
t.equal(q.movingAverage, (400 + 1600 + 900) / 15);
t.equal(q.queue.length, 3);
t.equal(q.quantity, 1 + 8 + 3);
t.equal(q.value, 100 + 1600 + 900);
t.equal(q.outward(5), (100 + 800) / 5);
t.equal(q.fifo, (800 + 900) / 7);
t.equal(q.movingAverage, (400 + 1600 + 900) / 15);
t.equal(q.queue.length, 2);
t.equal(q.quantity, 4 + 3);
t.equal(q.value, 800 + 900);
t.equal(q.outward(4), 200);
t.equal(q.fifo, 900 / 3);
t.equal(q.movingAverage, (400 + 1600 + 900) / 15);
t.equal(q.queue.length, 1);
t.equal(q.quantity, 3);
t.equal(q.value, 900);
t.equal(q.outward(3), 300);
t.equal(q.fifo, 0);
t.equal(q.movingAverage, (400 + 1600 + 900) / 15);
t.equal(q.queue.length, 0);
t.equal(q.quantity, 0);
t.equal(q.value, 0);
t.equal(q.inward(100, 1), 100);
t.equal(q.fifo, 100);
t.equal(q.movingAverage, 100);
t.equal(q.queue.length, 1);
t.equal(q.quantity, 1);
t.equal(q.value, 100);
t.equal(q.inward(150, 1), 150);
t.equal(q.fifo, (100 + 150) / 2);
t.equal(q.movingAverage, (100 + 150) / 2);
t.equal(q.queue.length, 2);
t.equal(q.quantity, 2);
t.equal(q.value, 100 + 150);
t.equal(q.inward(100, 1), 100);
t.equal(q.fifo, (100 + 150 + 100) / 3);
t.equal(q.movingAverage, (100 + 150 + 100) / 3);
t.equal(q.queue.length, 3);
t.equal(q.quantity, 3);
t.equal(q.value, 100 + 150 + 100);
t.equal(q.outward(1), 100);
t.equal(q.fifo, (150 + 100) / 2);
t.equal(q.movingAverage, (100 + 150 + 100) / 3);
t.equal(q.queue.length, 2);
t.equal(q.quantity, 2);
t.equal(q.value, 150 + 100);
t.equal(q.outward(2), (150 + 100) / 2);
t.equal(q.fifo, 0);
t.equal(q.movingAverage, (100 + 150 + 100) / 3);
t.equal(q.queue.length, 0);
t.equal(q.quantity, 0);
t.equal(q.value, 0);
t.end();
});
test('stockQueue:invalidOperations', (t) => {
const q = new StockQueue();
t.equal(q.outward(1), null);
t.equal(q.outward(0), null);
t.equal(q.outward(-5), null);
t.equal(q.inward(1000, -1), null);
t.equal(q.inward(0, 0), null);
t.equal(q.inward(-0.1, 5), null);
t.end();
});