mirror of
https://github.com/frappe/books.git
synced 2024-12-23 03:19:01 +00:00
261 lines
6.8 KiB
TypeScript
261 lines
6.8 KiB
TypeScript
import { Fyo, t } from 'fyo';
|
|
import { Attachment } from 'fyo/core/types';
|
|
import { Doc } from 'fyo/model/doc';
|
|
import {
|
|
Action,
|
|
DefaultMap,
|
|
FiltersMap,
|
|
FormulaMap,
|
|
HiddenMap,
|
|
} from 'fyo/model/types';
|
|
import { ValidationError } from 'fyo/utils/errors';
|
|
import { Defaults } from 'models/baseModels/Defaults/Defaults';
|
|
import { Invoice } from 'models/baseModels/Invoice/Invoice';
|
|
import { addItem, getLedgerLinkAction, getNumberSeries } from 'models/helpers';
|
|
import { LedgerPosting } from 'models/Transactional/LedgerPosting';
|
|
import { ModelNameEnum } from 'models/types';
|
|
import { Money } from 'pesa';
|
|
import { validateBatch } from './helpers';
|
|
import { StockTransferItem } from './StockTransferItem';
|
|
import { Transfer } from './Transfer';
|
|
|
|
export abstract class StockTransfer extends Transfer {
|
|
name?: string;
|
|
date?: Date;
|
|
party?: string;
|
|
terms?: string;
|
|
attachment?: Attachment;
|
|
grandTotal?: Money;
|
|
backReference?: string;
|
|
items?: StockTransferItem[];
|
|
|
|
get isSales() {
|
|
return this.schemaName === ModelNameEnum.Shipment;
|
|
}
|
|
|
|
formulas: FormulaMap = {
|
|
grandTotal: {
|
|
formula: () => this.getSum('items', 'amount', false),
|
|
dependsOn: ['items'],
|
|
},
|
|
};
|
|
|
|
hidden: HiddenMap = {
|
|
backReference: () =>
|
|
!(this.backReference || !(this.isSubmitted || this.isCancelled)),
|
|
terms: () => !(this.terms || !(this.isSubmitted || this.isCancelled)),
|
|
attachment: () =>
|
|
!(this.attachment || !(this.isSubmitted || this.isCancelled)),
|
|
};
|
|
|
|
static defaults: DefaultMap = {
|
|
numberSeries: (doc) => getNumberSeries(doc.schemaName, doc.fyo),
|
|
terms: (doc) => {
|
|
const defaults = doc.fyo.singles.Defaults as Defaults | undefined;
|
|
if (doc.schemaName === ModelNameEnum.Shipment) {
|
|
return defaults?.shipmentTerms ?? '';
|
|
}
|
|
|
|
return defaults?.purchaseReceiptTerms ?? '';
|
|
},
|
|
date: () => new Date(),
|
|
};
|
|
|
|
static filters: FiltersMap = {
|
|
party: (doc: Doc) => ({
|
|
role: ['in', [doc.isSales ? 'Customer' : 'Supplier', 'Both']],
|
|
}),
|
|
numberSeries: (doc: Doc) => ({ referenceType: doc.schemaName }),
|
|
};
|
|
|
|
override _getTransferDetails() {
|
|
return (this.items ?? []).map((row) => {
|
|
let fromLocation = undefined;
|
|
let toLocation = undefined;
|
|
|
|
if (this.isSales) {
|
|
fromLocation = row.location;
|
|
} else {
|
|
toLocation = row.location;
|
|
}
|
|
|
|
return {
|
|
item: row.item!,
|
|
rate: row.rate!,
|
|
quantity: row.quantity!,
|
|
batch: row.batch!,
|
|
fromLocation,
|
|
toLocation,
|
|
};
|
|
});
|
|
}
|
|
|
|
override async getPosting(): Promise<LedgerPosting | null> {
|
|
await this.validateAccounts();
|
|
const stockInHand = (await this.fyo.getValue(
|
|
ModelNameEnum.InventorySettings,
|
|
'stockInHand'
|
|
)) as string;
|
|
|
|
const amount = this.grandTotal ?? this.fyo.pesa(0);
|
|
const posting = new LedgerPosting(this, this.fyo);
|
|
|
|
if (this.isSales) {
|
|
const costOfGoodsSold = (await this.fyo.getValue(
|
|
ModelNameEnum.InventorySettings,
|
|
'costOfGoodsSold'
|
|
)) as string;
|
|
|
|
await posting.debit(costOfGoodsSold, amount);
|
|
await posting.credit(stockInHand, amount);
|
|
} else {
|
|
const stockReceivedButNotBilled = (await this.fyo.getValue(
|
|
ModelNameEnum.InventorySettings,
|
|
'stockReceivedButNotBilled'
|
|
)) as string;
|
|
|
|
await posting.debit(stockInHand, amount);
|
|
await posting.credit(stockReceivedButNotBilled, amount);
|
|
}
|
|
|
|
await posting.makeRoundOffEntry();
|
|
return posting;
|
|
}
|
|
|
|
async validateAccounts() {
|
|
const settings: string[] = ['stockInHand'];
|
|
if (this.isSales) {
|
|
settings.push('costOfGoodsSold');
|
|
} else {
|
|
settings.push('stockReceivedButNotBilled');
|
|
}
|
|
|
|
const messages: string[] = [];
|
|
for (const setting of settings) {
|
|
const value = this.fyo.singles.InventorySettings?.[setting] as
|
|
| string
|
|
| undefined;
|
|
const field = this.fyo.getField(ModelNameEnum.InventorySettings, setting);
|
|
if (!value) {
|
|
messages.push(t`${field.label} account not set in Inventory Settings.`);
|
|
continue;
|
|
}
|
|
|
|
const exists = await this.fyo.db.exists(ModelNameEnum.Account, value);
|
|
if (!exists) {
|
|
messages.push(t`Account ${value} does not exist.`);
|
|
}
|
|
}
|
|
|
|
if (messages.length) {
|
|
throw new ValidationError(messages.join(' '));
|
|
}
|
|
}
|
|
|
|
override async validate(): Promise<void> {
|
|
await super.validate();
|
|
await validateBatch(this);
|
|
}
|
|
|
|
static getActions(fyo: Fyo): Action[] {
|
|
return [getLedgerLinkAction(fyo, false), getLedgerLinkAction(fyo, true)];
|
|
}
|
|
|
|
async afterSubmit() {
|
|
await super.afterSubmit();
|
|
await this._updateBackReference();
|
|
}
|
|
|
|
async afterCancel(): Promise<void> {
|
|
await super.afterCancel();
|
|
await this._updateBackReference();
|
|
}
|
|
|
|
async _updateBackReference() {
|
|
if (!this.isCancelled && !this.isSubmitted) {
|
|
return;
|
|
}
|
|
|
|
if (!this.backReference) {
|
|
return;
|
|
}
|
|
|
|
const schemaName = this.isSales
|
|
? ModelNameEnum.SalesInvoice
|
|
: ModelNameEnum.PurchaseInvoice;
|
|
|
|
const invoice = (await this.fyo.doc.getDoc(
|
|
schemaName,
|
|
this.backReference
|
|
)) as Invoice;
|
|
const transferMap = this._getTransferMap();
|
|
|
|
for (const row of invoice.items ?? []) {
|
|
const item = row.item!;
|
|
const quantity = row.quantity!;
|
|
const notTransferred = (row.stockNotTransferred as number) ?? 0;
|
|
|
|
const transferred = transferMap[item];
|
|
if (
|
|
typeof transferred !== 'number' ||
|
|
typeof notTransferred !== 'number'
|
|
) {
|
|
continue;
|
|
}
|
|
|
|
if (this.isCancelled) {
|
|
await row.set(
|
|
'stockNotTransferred',
|
|
Math.min(notTransferred + transferred, quantity)
|
|
);
|
|
transferMap[item] = Math.max(
|
|
transferred + notTransferred - quantity,
|
|
0
|
|
);
|
|
} else {
|
|
await row.set(
|
|
'stockNotTransferred',
|
|
Math.max(notTransferred - transferred, 0)
|
|
);
|
|
transferMap[item] = Math.max(transferred - notTransferred, 0);
|
|
}
|
|
}
|
|
|
|
const notTransferred = invoice.getStockNotTransferred();
|
|
await invoice.setAndSync('stockNotTransferred', notTransferred);
|
|
}
|
|
|
|
_getTransferMap() {
|
|
return (this.items ?? []).reduce((acc, item) => {
|
|
if (!item.item) {
|
|
return acc;
|
|
}
|
|
|
|
if (!item.quantity) {
|
|
return acc;
|
|
}
|
|
|
|
acc[item.item] ??= 0;
|
|
acc[item.item] += item.quantity;
|
|
|
|
return acc;
|
|
}, {} as Record<string, number>);
|
|
}
|
|
|
|
override duplicate(): Doc {
|
|
const doc = super.duplicate() as StockTransfer;
|
|
doc.backReference = undefined;
|
|
return doc;
|
|
}
|
|
|
|
static createFilters: FiltersMap = {
|
|
party: (doc: Doc) => ({
|
|
role: doc.isSales ? 'Customer' : 'Supplier',
|
|
}),
|
|
};
|
|
|
|
async addItem(name: string) {
|
|
return await addItem(name, this);
|
|
}
|
|
}
|