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:
parent
82e67a8874
commit
d394db817e
@ -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,
|
||||||
|
@ -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;
|
|
||||||
}
|
}
|
||||||
|
@ -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 = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 };
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
@ -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 };
|
|
||||||
|
@ -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',
|
||||||
|
@ -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
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
@ -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,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user