2022-10-28 08:04:08 +00:00
|
|
|
import { Fyo, t } from 'fyo';
|
|
|
|
import { ValidationError } from 'fyo/utils/errors';
|
|
|
|
import { ModelNameEnum } from 'models/types';
|
|
|
|
import { Money } from 'pesa';
|
|
|
|
import { StockLedgerEntry } from './StockLedgerEntry';
|
2022-10-29 07:07:52 +00:00
|
|
|
import { SMDetails, SMIDetails, SMTransferDetails } from './types';
|
2022-10-28 08:04:08 +00:00
|
|
|
|
|
|
|
export class StockManager {
|
|
|
|
/**
|
2022-10-29 07:07:52 +00:00
|
|
|
* 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 transferStock(transferDetails: SMTransferDetails) {
|
|
|
|
const details = this.#getSMIDetails(transferDetails);
|
|
|
|
const item = new StockManagerItem(details, this.fyo);
|
|
|
|
await item.transferStock(this.isCancelled);
|
|
|
|
this.items.push(item);
|
|
|
|
}
|
|
|
|
|
|
|
|
async sync() {
|
|
|
|
for (const item of this.items) {
|
|
|
|
await item.sync();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#getSMIDetails(transferDetails: SMTransferDetails): SMIDetails {
|
|
|
|
return Object.assign({}, this.details, transferDetails);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export class StockManagerItem {
|
|
|
|
/**
|
|
|
|
* The Stock Manager Item is used to move stock to and from a location. It
|
2022-10-28 08:04:08 +00:00
|
|
|
* updates the Stock Queue and creates Stock Ledger Entries.
|
|
|
|
*
|
|
|
|
* 1. Get existing stock Queue
|
|
|
|
* 5. Create Stock Ledger Entry
|
|
|
|
* 7. Insert Stock Ledger Entry
|
|
|
|
*/
|
|
|
|
|
2022-10-29 06:15:23 +00:00
|
|
|
date: Date;
|
|
|
|
item: string;
|
|
|
|
rate: Money;
|
|
|
|
quantity: number;
|
|
|
|
referenceName: string;
|
|
|
|
referenceType: string;
|
2022-10-28 08:04:08 +00:00
|
|
|
fromLocation?: string;
|
|
|
|
toLocation?: string;
|
|
|
|
|
2022-10-29 06:15:23 +00:00
|
|
|
stockLedgerEntries?: StockLedgerEntry[];
|
2022-10-28 08:04:08 +00:00
|
|
|
|
2022-10-29 06:15:23 +00:00
|
|
|
fyo: Fyo;
|
2022-10-28 08:04:08 +00:00
|
|
|
|
2022-10-29 07:07:52 +00:00
|
|
|
constructor(details: SMIDetails, fyo: Fyo) {
|
2022-10-28 08:04:08 +00:00
|
|
|
this.date = details.date;
|
|
|
|
this.item = details.item;
|
2022-10-29 06:15:23 +00:00
|
|
|
this.rate = details.rate;
|
2022-10-28 08:04:08 +00:00
|
|
|
this.quantity = details.quantity;
|
|
|
|
this.fromLocation = details.fromLocation;
|
|
|
|
this.toLocation = details.toLocation;
|
|
|
|
this.referenceName = details.referenceName;
|
|
|
|
this.referenceType = details.referenceType;
|
|
|
|
this.#validate();
|
2022-10-29 06:15:23 +00:00
|
|
|
|
|
|
|
this.fyo = fyo;
|
2022-10-28 08:04:08 +00:00
|
|
|
}
|
|
|
|
|
2022-10-29 06:15:23 +00:00
|
|
|
async transferStock(isCancelled: boolean) {
|
|
|
|
this.#clear();
|
|
|
|
await this.#moveStockForBothLocations(isCancelled);
|
|
|
|
}
|
|
|
|
|
|
|
|
async sync() {
|
|
|
|
for (const sle of this.stockLedgerEntries ?? []) {
|
|
|
|
await sle.sync();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async #moveStockForBothLocations(isCancelled: boolean) {
|
2022-10-28 08:04:08 +00:00
|
|
|
if (this.fromLocation) {
|
2022-10-29 06:15:23 +00:00
|
|
|
await this.#moveStockForSingleLocation(
|
|
|
|
this.fromLocation,
|
|
|
|
isCancelled ? false : true,
|
|
|
|
isCancelled
|
|
|
|
);
|
2022-10-28 08:04:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (this.toLocation) {
|
2022-10-29 06:15:23 +00:00
|
|
|
await this.#moveStockForSingleLocation(
|
|
|
|
this.toLocation,
|
|
|
|
isCancelled ? true : false,
|
|
|
|
isCancelled
|
|
|
|
);
|
2022-10-28 08:04:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-29 06:15:23 +00:00
|
|
|
async #moveStockForSingleLocation(
|
|
|
|
location: string,
|
|
|
|
isOutward: boolean,
|
|
|
|
isCancelled: boolean
|
|
|
|
) {
|
2022-10-28 08:04:08 +00:00
|
|
|
let quantity = this.quantity!;
|
2022-10-29 06:15:23 +00:00
|
|
|
if (quantity === 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-10-28 08:04:08 +00:00
|
|
|
if (isOutward) {
|
|
|
|
quantity = -quantity;
|
|
|
|
}
|
|
|
|
|
2022-10-29 06:15:23 +00:00
|
|
|
// Stock Ledger Entry
|
|
|
|
if (!isCancelled) {
|
2022-11-01 08:27:02 +00:00
|
|
|
const stockLedgerEntry = this.#getStockLedgerEntry(location, quantity);
|
2022-10-29 06:15:23 +00:00
|
|
|
this.stockLedgerEntries?.push(stockLedgerEntry);
|
|
|
|
}
|
2022-10-28 08:04:08 +00:00
|
|
|
}
|
|
|
|
|
2022-11-01 08:27:02 +00:00
|
|
|
#getStockLedgerEntry(location: string, quantity: number) {
|
2022-10-28 08:04:08 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2022-10-29 06:15:23 +00:00
|
|
|
#clear() {
|
|
|
|
this.stockLedgerEntries = [];
|
|
|
|
}
|
|
|
|
|
2022-10-28 08:04:08 +00:00
|
|
|
#validate() {
|
|
|
|
this.#validateRate();
|
2022-10-29 06:15:23 +00:00
|
|
|
this.#validateQuantity();
|
2022-10-28 08:04:08 +00:00
|
|
|
this.#validateLocation();
|
|
|
|
}
|
|
|
|
|
2022-10-29 06:15:23 +00:00
|
|
|
#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`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-28 08:04:08 +00:00
|
|
|
#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`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|