2
0
mirror of https://github.com/frappe/books.git synced 2024-09-20 03:29:00 +00:00
books/models/inventory/StockManager.ts
18alantom 43784984c3 fix: stock movement
- test: stock movement create and cancel
2022-11-07 13:28:35 +05:30

195 lines
4.5 KiB
TypeScript

import { Fyo, t } from 'fyo';
import { ValidationError } from 'fyo/utils/errors';
import { ModelNameEnum } from 'models/types';
import { Money } from 'pesa';
import { StockLedgerEntry } from './StockLedgerEntry';
import { SMDetails, SMIDetails, SMTransferDetails } from './types';
export class StockManager {
/**
* The Stock Manager manages a group of Stock Manager Items
* all of which would belong to a single transaction such as a
* single Stock Movement entry.
*/
items: StockManagerItem[];
details: SMDetails;
isCancelled: boolean;
fyo: Fyo;
constructor(details: SMDetails, isCancelled: boolean, fyo: Fyo) {
this.items = [];
this.details = details;
this.isCancelled = isCancelled;
this.fyo = fyo;
}
async createTransfers(transferDetails: SMTransferDetails[]) {
for (const detail of transferDetails) {
await this.#createTransfer(detail);
}
await this.#sync();
}
async cancelTransfers() {
const { referenceName, referenceType } = this.details;
await this.fyo.db.deleteAll(ModelNameEnum.StockLedgerEntry, {
referenceType,
referenceName,
});
}
async #sync() {
for (const item of this.items) {
await item.sync();
}
}
async #createTransfer(transferDetails: SMTransferDetails) {
const details = this.#getSMIDetails(transferDetails);
const item = new StockManagerItem(details, this.fyo);
await item.transferStock();
this.items.push(item);
}
#getSMIDetails(transferDetails: SMTransferDetails): SMIDetails {
return Object.assign({}, this.details, transferDetails);
}
}
class StockManagerItem {
/**
* The Stock Manager Item is used to move stock to and from a location. It
* updates the Stock Queue and creates Stock Ledger Entries.
*
* 1. Get existing stock Queue
* 5. Create Stock Ledger Entry
* 7. Insert Stock Ledger Entry
*/
date: Date;
item: string;
rate: Money;
quantity: number;
referenceName: string;
referenceType: string;
fromLocation?: string;
toLocation?: string;
stockLedgerEntries?: StockLedgerEntry[];
fyo: Fyo;
constructor(details: SMIDetails, fyo: Fyo) {
this.date = details.date;
this.item = details.item;
this.rate = details.rate;
this.quantity = details.quantity;
this.fromLocation = details.fromLocation;
this.toLocation = details.toLocation;
this.referenceName = details.referenceName;
this.referenceType = details.referenceType;
this.#validate();
this.fyo = fyo;
}
transferStock() {
this.#clear();
this.#moveStockForBothLocations();
}
async sync() {
for (const sle of this.stockLedgerEntries ?? []) {
await sle.sync();
}
}
#moveStockForBothLocations() {
if (this.fromLocation) {
this.#moveStockForSingleLocation(this.fromLocation, true);
}
if (this.toLocation) {
this.#moveStockForSingleLocation(this.toLocation, false);
}
}
#moveStockForSingleLocation(location: string, isOutward: boolean) {
let quantity = this.quantity!;
if (quantity === 0) {
return;
}
if (isOutward) {
quantity = -quantity;
}
// Stock Ledger Entry
const stockLedgerEntry = this.#getStockLedgerEntry(location, quantity);
this.stockLedgerEntries?.push(stockLedgerEntry);
}
#getStockLedgerEntry(location: string, quantity: number) {
return this.fyo.doc.getNewDoc(ModelNameEnum.StockLedgerEntry, {
date: this.date,
item: this.item,
rate: this.rate,
quantity,
location,
referenceName: this.referenceName,
referenceType: this.referenceType,
}) as StockLedgerEntry;
}
#clear() {
this.stockLedgerEntries = [];
}
#validate() {
this.#validateRate();
this.#validateQuantity();
this.#validateLocation();
}
#validateQuantity() {
if (!this.quantity) {
throw new ValidationError(t`Stock Manager: quantity needs to be set`);
}
if (this.quantity <= 0) {
throw new ValidationError(
t`Stock Manager: quantity (${this.quantity}) has to be greater than zero`
);
}
}
#validateRate() {
if (!this.rate) {
throw new ValidationError(t`Stock Manager: rate needs to be set`);
}
if (this.rate.lte(0)) {
throw new ValidationError(
t`Stock Manager: rate (${this.rate.float}) has to be greater than zero`
);
}
}
#validateLocation() {
if (this.fromLocation) {
return;
}
if (this.toLocation) {
return;
}
throw new ValidationError(
t`Stock Manager: both From and To Location cannot be undefined`
);
}
}