2
0
mirror of https://github.com/frappe/books.git synced 2024-11-09 23:30:56 +00:00

incr: stateless stock-ledger, remove stock queue

This commit is contained in:
18alantom 2022-11-01 13:57:02 +05:30
parent 82e67a8874
commit d394db817e
10 changed files with 2 additions and 264 deletions

View File

@ -21,7 +21,6 @@ import { Location } from './inventory/Location';
import { StockLedgerEntry } from './inventory/StockLedgerEntry';
import { StockMovement } from './inventory/StockMovement';
import { StockMovementItem } from './inventory/StockMovementItem';
import { StockQueue } from './inventory/StockQueue';
export const models = {
Account,
@ -43,7 +42,6 @@ export const models = {
Tax,
TaxSummary,
// Inventory Models
StockQueue,
StockMovement,
StockMovementItem,
StockLedgerEntry,

View File

@ -10,6 +10,4 @@ export class StockLedgerEntry extends Doc {
location?: string;
referenceName?: string;
referenceType?: string;
stockValueBefore?: Money;
stockValueAfter?: Money;
}

View File

@ -2,9 +2,7 @@ import { Fyo, t } from 'fyo';
import { ValidationError } from 'fyo/utils/errors';
import { ModelNameEnum } from 'models/types';
import { Money } from 'pesa';
import { getStockQueue } from './helpers';
import { StockLedgerEntry } from './StockLedgerEntry';
import { StockQueue } from './StockQueue';
import { SMDetails, SMIDetails, SMTransferDetails } from './types';
export class StockManager {
@ -51,11 +49,7 @@ export class StockManagerItem {
* updates the Stock Queue and creates Stock Ledger Entries.
*
* 1. Get existing stock Queue
* 2. Get Stock Value Before from Stock Queue
* 3. Update Stock Queue
* 4. Get Stock Value After from Stock Queue
* 5. Create Stock Ledger Entry
* 6. Save Stock Queue
* 7. Insert Stock Ledger Entry
*/
@ -68,7 +62,6 @@ export class StockManagerItem {
fromLocation?: string;
toLocation?: string;
stockQueues?: StockQueue[];
stockLedgerEntries?: StockLedgerEntry[];
fyo: Fyo;
@ -93,10 +86,6 @@ export class StockManagerItem {
}
async sync() {
for (const sq of this.stockQueues ?? []) {
await sq.sync();
}
for (const sle of this.stockLedgerEntries ?? []) {
await sle.sync();
}
@ -134,69 +123,26 @@ export class StockManagerItem {
quantity = -quantity;
}
// Stock Queue Changes
const { stockQueue, stockValueBefore, stockValueAfter } =
await this.#makeStockQueueChange(location, isOutward);
this.stockQueues?.push(stockQueue);
// Stock Ledger Entry
if (!isCancelled) {
const stockLedgerEntry = this.#getStockLedgerEntry(
location,
quantity,
stockValueBefore,
stockValueAfter
);
const stockLedgerEntry = this.#getStockLedgerEntry(location, quantity);
this.stockLedgerEntries?.push(stockLedgerEntry);
}
}
#getStockLedgerEntry(
location: string,
quantity: number,
stockValueBefore: Money,
stockValueAfter: Money
) {
#getStockLedgerEntry(location: string, quantity: number) {
return this.fyo.doc.getNewDoc(ModelNameEnum.StockLedgerEntry, {
date: this.date,
item: this.item,
rate: this.rate,
quantity,
location,
stockValueBefore,
stockValueAfter,
referenceName: this.referenceName,
referenceType: this.referenceType,
}) as StockLedgerEntry;
}
async #makeStockQueueChange(location: string, isOutward: boolean) {
const stockQueue = await getStockQueue(this.item!, location, this.fyo);
const stockValueBefore = stockQueue.stockValue!;
let isSuccess;
if (isOutward) {
isSuccess = stockQueue.outward(-this.quantity!);
} else {
isSuccess = stockQueue.inward(this.rate!, this.quantity!);
}
if (!isSuccess && isOutward) {
throw new ValidationError(
t`Stock Manager: Insufficient quantity ${
stockQueue.quantity
} at ${location} of ${this
.item!} for outward transaction. Quantity required ${this.quantity!}.`
);
}
const stockValueAfter = stockQueue.stockValue!;
return { stockQueue, stockValueBefore, stockValueAfter };
}
#clear() {
this.stockQueues = [];
this.stockLedgerEntries = [];
}

View File

@ -1,122 +0,0 @@
import { Doc } from 'fyo/model/doc';
import { Money } from 'pesa';
import { StockQueueItem } from './types';
export class StockQueue extends Doc {
item?: string;
location?: string;
queue?: string;
stockValue?: Money;
/**
* Stock Queue
*
* Used to keep track of inward rates for stock
* valuation purposes.
*
* Stock Queue uses autoincrement as PK as opposed
* to (item, location, ...) to prevent NULL value
* primary keys.
*/
get quantity(): number {
return this.stockQueue.reduce((qty, sqi) => {
return qty + sqi.quantity;
}, 0);
}
get stockQueue(): StockQueueItem[] {
const stringifiedRatesQueue = JSON.parse(this.queue ?? '[]') as {
rate: string;
quantity: number;
}[];
return stringifiedRatesQueue.map(({ rate, quantity }) => ({
rate: this.fyo.pesa(rate),
quantity,
}));
}
set stockQueue(stockQueue: StockQueueItem[]) {
const stringifiedRatesQueue = stockQueue.map(({ rate, quantity }) => ({
rate: rate.store,
quantity,
}));
this.queue = JSON.stringify(stringifiedRatesQueue);
}
inward(rate: Money, quantity: number): boolean {
const stockQueue = this.stockQueue;
stockQueue.push({ rate, quantity });
this.stockQueue = stockQueue;
this._updateStockValue(stockQueue);
return true;
}
outward(quantity: number): boolean {
const stockQueue = this.stockQueue;
const outwardQueues = getQueuesPostOutwards(stockQueue, quantity);
if (!outwardQueues.isPossible) {
return false;
}
this.stockQueue = outwardQueues.balanceQueue;
this._updateStockValue(outwardQueues.balanceQueue);
return true;
}
_updateStockValue(stockQueue: StockQueueItem[]) {
this.stockValue = stockQueue.reduce((acc, { rate, quantity }) => {
return acc.add(rate.mul(quantity));
}, this.fyo.pesa(0));
}
}
function getQueuesPostOutwards(
stockQueue: StockQueueItem[],
outwardQuantity: number
) {
const totalQuantity = stockQueue.reduce(
(acc, { quantity }) => acc + quantity,
0
);
const isPossible = outwardQuantity <= totalQuantity;
if (!isPossible) {
return { isPossible };
}
let outwardRemaining = outwardQuantity;
const balanceQueue: StockQueueItem[] = [];
const outwardQueue: StockQueueItem[] = [];
for (let i = stockQueue.length - 1; i >= 0; i--) {
const { quantity, rate } = stockQueue[i];
if (outwardRemaining === 0) {
balanceQueue.unshift({ quantity, rate });
}
const balanceRemaining = quantity - outwardRemaining;
if (balanceRemaining === 0) {
outwardQueue.push({ quantity, rate });
outwardRemaining = 0;
continue;
}
if (balanceRemaining > 0) {
outwardQueue.push({ quantity: outwardRemaining, rate });
balanceQueue.unshift({ quantity: balanceRemaining, rate });
outwardRemaining = 0;
continue;
}
if (balanceRemaining < 0) {
outwardQueue.push({ quantity, rate });
outwardRemaining = +balanceRemaining;
continue;
}
}
return { isPossible, outwardQueue, balanceQueue };
}

View File

@ -1,29 +1 @@
import { Fyo } from 'fyo';
import { ModelNameEnum } from 'models/types';
import { StockQueue } from './StockQueue';
export async function getStockQueue(
item: string,
location: string,
fyo: Fyo
): Promise<StockQueue> {
/**
* Create a new StockQueue if it doesn't exist.
*/
const names = (await fyo.db.getAllRaw(ModelNameEnum.StockQueue, {
filters: { item, location },
fields: ['name'],
limit: 1,
})) as { name: string }[];
const name = names?.[0]?.name;
if (!name) {
return fyo.doc.getNewDoc(ModelNameEnum.StockQueue, {
item,
location,
}) as StockQueue;
}
return (await fyo.doc.getDoc(ModelNameEnum.StockQueue, name)) as StockQueue;
}

View File

@ -22,4 +22,3 @@ export interface SMTransferDetails {
export interface SMIDetails extends SMDetails, SMTransferDetails {}
export type StockQueueItem = { rate: Money; quantity: number };

View File

@ -31,7 +31,6 @@ export enum ModelNameEnum {
SingleValue = 'SingleValue',
SystemSettings = 'SystemSettings',
StockMovement = 'StockMovement',
StockQueue = 'StockQueue',
StockMovementItem = 'StockMovementItem',
StockLedgerEntry = 'StockLedgerEntry',
Location = 'Location',

View File

@ -49,18 +49,6 @@
"label": "Ref. Type",
"fieldtype": "Data",
"readOnly": true
},
{
"fieldname": "stockValueBefore",
"label": "Stock Value Before",
"fieldtype": "Currency",
"readOnly": true
},
{
"fieldname": "stockValueAfter",
"label": "Stock Value After",
"fieldtype": "Currency",
"readOnly": true
}
]
}

View File

@ -1,38 +0,0 @@
{
"name": "StockQueue",
"label": "Stock Queue",
"isSingle": false,
"isChild": false,
"naming": "autoincrement",
"fields": [
{
"fieldname": "item",
"label": "Item",
"fieldtype": "Link",
"target": "Item",
"required": true,
"readOnly": true
},
{
"fieldname": "location",
"label": "Location",
"fieldtype": "Link",
"target": "Location",
"required": true,
"readOnly": true
},
{
"fieldname": "queue",
"label": "Queue",
"fieldtype": "Data",
"required": true,
"readOnly": true
},
{
"fieldname": "stockValue",
"label": "Stock Value",
"fieldtype": "Currency",
"readOnly": true
}
]
}

View File

@ -11,7 +11,6 @@ import Location from './app/inventory/Location.json';
import StockLedgerEntry from './app/inventory/StockLedgerEntry.json';
import StockMovement from './app/inventory/StockMovement.json';
import StockMovementItem from './app/inventory/StockMovementItem.json';
import StockQueue from './app/inventory/StockQueue.json';
import Invoice from './app/Invoice.json';
import InvoiceItem from './app/InvoiceItem.json';
import Item from './app/Item.json';
@ -95,7 +94,6 @@ export const appSchemas: Schema[] | SchemaStub[] = [
TaxSummary as Schema,
Location as Schema,
StockQueue as Schema,
StockLedgerEntry as Schema,
StockMovement as Schema,
StockMovementItem as Schema,