2
0
mirror of https://github.com/frappe/books.git synced 2025-02-02 12:08:27 +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 { StockLedgerEntry } from './inventory/StockLedgerEntry';
import { StockMovement } from './inventory/StockMovement'; import { StockMovement } from './inventory/StockMovement';
import { StockMovementItem } from './inventory/StockMovementItem'; import { StockMovementItem } from './inventory/StockMovementItem';
import { StockQueue } from './inventory/StockQueue';
export const models = { export const models = {
Account, Account,
@ -43,7 +42,6 @@ export const models = {
Tax, Tax,
TaxSummary, TaxSummary,
// Inventory Models // Inventory Models
StockQueue,
StockMovement, StockMovement,
StockMovementItem, StockMovementItem,
StockLedgerEntry, StockLedgerEntry,

View File

@ -10,6 +10,4 @@ export class StockLedgerEntry extends Doc {
location?: string; location?: string;
referenceName?: string; referenceName?: string;
referenceType?: 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 { ValidationError } from 'fyo/utils/errors';
import { ModelNameEnum } from 'models/types'; import { ModelNameEnum } from 'models/types';
import { Money } from 'pesa'; import { Money } from 'pesa';
import { getStockQueue } from './helpers';
import { StockLedgerEntry } from './StockLedgerEntry'; import { StockLedgerEntry } from './StockLedgerEntry';
import { StockQueue } from './StockQueue';
import { SMDetails, SMIDetails, SMTransferDetails } from './types'; import { SMDetails, SMIDetails, SMTransferDetails } from './types';
export class StockManager { export class StockManager {
@ -51,11 +49,7 @@ export class StockManagerItem {
* updates the Stock Queue and creates Stock Ledger Entries. * updates the Stock Queue and creates Stock Ledger Entries.
* *
* 1. Get existing stock Queue * 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 * 5. Create Stock Ledger Entry
* 6. Save Stock Queue
* 7. Insert Stock Ledger Entry * 7. Insert Stock Ledger Entry
*/ */
@ -68,7 +62,6 @@ export class StockManagerItem {
fromLocation?: string; fromLocation?: string;
toLocation?: string; toLocation?: string;
stockQueues?: StockQueue[];
stockLedgerEntries?: StockLedgerEntry[]; stockLedgerEntries?: StockLedgerEntry[];
fyo: Fyo; fyo: Fyo;
@ -93,10 +86,6 @@ export class StockManagerItem {
} }
async sync() { async sync() {
for (const sq of this.stockQueues ?? []) {
await sq.sync();
}
for (const sle of this.stockLedgerEntries ?? []) { for (const sle of this.stockLedgerEntries ?? []) {
await sle.sync(); await sle.sync();
} }
@ -134,69 +123,26 @@ export class StockManagerItem {
quantity = -quantity; quantity = -quantity;
} }
// Stock Queue Changes
const { stockQueue, stockValueBefore, stockValueAfter } =
await this.#makeStockQueueChange(location, isOutward);
this.stockQueues?.push(stockQueue);
// Stock Ledger Entry // Stock Ledger Entry
if (!isCancelled) { if (!isCancelled) {
const stockLedgerEntry = this.#getStockLedgerEntry( const stockLedgerEntry = this.#getStockLedgerEntry(location, quantity);
location,
quantity,
stockValueBefore,
stockValueAfter
);
this.stockLedgerEntries?.push(stockLedgerEntry); this.stockLedgerEntries?.push(stockLedgerEntry);
} }
} }
#getStockLedgerEntry( #getStockLedgerEntry(location: string, quantity: number) {
location: string,
quantity: number,
stockValueBefore: Money,
stockValueAfter: Money
) {
return this.fyo.doc.getNewDoc(ModelNameEnum.StockLedgerEntry, { return this.fyo.doc.getNewDoc(ModelNameEnum.StockLedgerEntry, {
date: this.date, date: this.date,
item: this.item, item: this.item,
rate: this.rate, rate: this.rate,
quantity, quantity,
location, location,
stockValueBefore,
stockValueAfter,
referenceName: this.referenceName, referenceName: this.referenceName,
referenceType: this.referenceType, referenceType: this.referenceType,
}) as StockLedgerEntry; }) 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() { #clear() {
this.stockQueues = [];
this.stockLedgerEntries = []; 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 interface SMIDetails extends SMDetails, SMTransferDetails {}
export type StockQueueItem = { rate: Money; quantity: number };

View File

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

View File

@ -49,18 +49,6 @@
"label": "Ref. Type", "label": "Ref. Type",
"fieldtype": "Data", "fieldtype": "Data",
"readOnly": true "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 StockLedgerEntry from './app/inventory/StockLedgerEntry.json';
import StockMovement from './app/inventory/StockMovement.json'; import StockMovement from './app/inventory/StockMovement.json';
import StockMovementItem from './app/inventory/StockMovementItem.json'; import StockMovementItem from './app/inventory/StockMovementItem.json';
import StockQueue from './app/inventory/StockQueue.json';
import Invoice from './app/Invoice.json'; import Invoice from './app/Invoice.json';
import InvoiceItem from './app/InvoiceItem.json'; import InvoiceItem from './app/InvoiceItem.json';
import Item from './app/Item.json'; import Item from './app/Item.json';
@ -95,7 +94,6 @@ export const appSchemas: Schema[] | SchemaStub[] = [
TaxSummary as Schema, TaxSummary as Schema,
Location as Schema, Location as Schema,
StockQueue as Schema,
StockLedgerEntry as Schema, StockLedgerEntry as Schema,
StockMovement as Schema, StockMovement as Schema,
StockMovementItem as Schema, StockMovementItem as Schema,