mirror of
https://github.com/frappe/books.git
synced 2024-11-09 15:20:56 +00:00
Merge pull request #743 from akshayitzme/invoice-return
feat: invoice return
This commit is contained in:
commit
f5304b1331
@ -185,7 +185,7 @@ export class BespokeQueries {
|
|||||||
|
|
||||||
static async getReturnBalanceItemsQty(
|
static async getReturnBalanceItemsQty(
|
||||||
db: DatabaseCore,
|
db: DatabaseCore,
|
||||||
schemaName: string,
|
schemaName: ModelNameEnum,
|
||||||
docName: string
|
docName: string
|
||||||
): Promise<Record<string, ReturnDocItem> | undefined> {
|
): Promise<Record<string, ReturnDocItem> | undefined> {
|
||||||
const returnDocNames = (
|
const returnDocNames = (
|
||||||
@ -200,21 +200,41 @@ export class BespokeQueries {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const returnedItems: DocItem[] = await db.knex!(`${schemaName}Item`)
|
const returnedItemsQuery = db.knex!(`${schemaName}Item`)
|
||||||
.select('item', 'batch', 'serialNumber')
|
|
||||||
.sum({ quantity: 'quantity' })
|
.sum({ quantity: 'quantity' })
|
||||||
.whereIn('parent', returnDocNames)
|
.whereIn('parent', returnDocNames);
|
||||||
.groupBy('item', 'batch', 'serialNumber');
|
|
||||||
|
|
||||||
|
const docItemsQuery = db.knex!(`${schemaName}Item`)
|
||||||
|
.where('parent', docName)
|
||||||
|
.sum({ quantity: 'quantity' });
|
||||||
|
|
||||||
|
if (
|
||||||
|
[ModelNameEnum.SalesInvoice, ModelNameEnum.PurchaseInvoice].includes(
|
||||||
|
schemaName
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
returnedItemsQuery.select('item', 'batch').groupBy('item', 'batch');
|
||||||
|
docItemsQuery.select('name', 'item', 'batch').groupBy('item', 'batch');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
[ModelNameEnum.Shipment, ModelNameEnum.PurchaseReceipt].includes(
|
||||||
|
schemaName
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
returnedItemsQuery
|
||||||
|
.select('item', 'batch', 'serialNumber')
|
||||||
|
.groupBy('item', 'batch', 'serialNumber');
|
||||||
|
docItemsQuery
|
||||||
|
.select('name', 'item', 'batch', 'serialNumber')
|
||||||
|
.groupBy('item', 'batch', 'serialNumber');
|
||||||
|
}
|
||||||
|
|
||||||
|
const returnedItems = (await returnedItemsQuery) as DocItem[];
|
||||||
if (!returnedItems.length) {
|
if (!returnedItems.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const docItems = (await docItemsQuery) as DocItem[];
|
||||||
const docItems: DocItem[] = await db.knex!(`${schemaName}Item`)
|
|
||||||
.select('name', 'item', 'batch', 'serialNumber')
|
|
||||||
.where('parent', docName)
|
|
||||||
.groupBy('item', 'batch', 'serialNumber')
|
|
||||||
.sum({ quantity: 'quantity' });
|
|
||||||
|
|
||||||
const docItemsMap = BespokeQueries.#getDocItemMap(docItems);
|
const docItemsMap = BespokeQueries.#getDocItemMap(docItems);
|
||||||
const returnedItemsMap = BespokeQueries.#getDocItemMap(returnedItems);
|
const returnedItemsMap = BespokeQueries.#getDocItemMap(returnedItems);
|
||||||
@ -223,7 +243,6 @@ export class BespokeQueries {
|
|||||||
docItemsMap,
|
docItemsMap,
|
||||||
returnedItemsMap
|
returnedItemsMap
|
||||||
);
|
);
|
||||||
|
|
||||||
return returnBalanceItems;
|
return returnBalanceItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ export class AccountingSettings extends Doc {
|
|||||||
enableInventory?: boolean;
|
enableInventory?: boolean;
|
||||||
enablePriceList?: boolean;
|
enablePriceList?: boolean;
|
||||||
enableFormCustomization?: boolean;
|
enableFormCustomization?: boolean;
|
||||||
|
enableInvoiceReturns?: boolean;
|
||||||
|
|
||||||
static filters: FiltersMap = {
|
static filters: FiltersMap = {
|
||||||
writeOffAccount: () => ({
|
writeOffAccount: () => ({
|
||||||
@ -47,6 +48,9 @@ export class AccountingSettings extends Doc {
|
|||||||
enableInventory: () => {
|
enableInventory: () => {
|
||||||
return !!this.enableInventory;
|
return !!this.enableInventory;
|
||||||
},
|
},
|
||||||
|
enableInvoiceReturns: () => {
|
||||||
|
return !!this.enableInvoiceReturns;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
override hidden: HiddenMap = {
|
override hidden: HiddenMap = {
|
||||||
|
@ -25,6 +25,8 @@ import { Party } from '../Party/Party';
|
|||||||
import { Payment } from '../Payment/Payment';
|
import { Payment } from '../Payment/Payment';
|
||||||
import { Tax } from '../Tax/Tax';
|
import { Tax } from '../Tax/Tax';
|
||||||
import { TaxSummary } from '../TaxSummary/TaxSummary';
|
import { TaxSummary } from '../TaxSummary/TaxSummary';
|
||||||
|
import { ReturnDocItem } from 'models/inventory/types';
|
||||||
|
import { AccountFieldEnum, PaymentTypeEnum } from '../Payment/types';
|
||||||
|
|
||||||
export abstract class Invoice extends Transactional {
|
export abstract class Invoice extends Transactional {
|
||||||
_taxes: Record<string, Tax> = {};
|
_taxes: Record<string, Tax> = {};
|
||||||
@ -52,6 +54,9 @@ export abstract class Invoice extends Transactional {
|
|||||||
makeAutoPayment?: boolean;
|
makeAutoPayment?: boolean;
|
||||||
makeAutoStockTransfer?: boolean;
|
makeAutoStockTransfer?: boolean;
|
||||||
|
|
||||||
|
isReturned?: boolean;
|
||||||
|
returnAgainst?: string;
|
||||||
|
|
||||||
get isSales() {
|
get isSales() {
|
||||||
return this.schemaName === 'SalesInvoice';
|
return this.schemaName === 'SalesInvoice';
|
||||||
}
|
}
|
||||||
@ -118,6 +123,10 @@ export abstract class Invoice extends Transactional {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isReturn(): boolean {
|
||||||
|
return !!this.returnAgainst;
|
||||||
|
}
|
||||||
|
|
||||||
constructor(schema: Schema, data: DocValueMap, fyo: Fyo) {
|
constructor(schema: Schema, data: DocValueMap, fyo: Fyo) {
|
||||||
super(schema, data, fyo);
|
super(schema, data, fyo);
|
||||||
this._setGetCurrencies();
|
this._setGetCurrencies();
|
||||||
@ -159,12 +168,15 @@ export abstract class Invoice extends Transactional {
|
|||||||
await stockTransfer?.submit();
|
await stockTransfer?.submit();
|
||||||
await this.load();
|
await this.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await this._updateIsItemsReturned();
|
||||||
}
|
}
|
||||||
|
|
||||||
async afterCancel() {
|
async afterCancel() {
|
||||||
await super.afterCancel();
|
await super.afterCancel();
|
||||||
await this._cancelPayments();
|
await this._cancelPayments();
|
||||||
await this._updatePartyOutStanding();
|
await this._updatePartyOutStanding();
|
||||||
|
await this._updateIsItemsReturned();
|
||||||
}
|
}
|
||||||
|
|
||||||
async _cancelPayments() {
|
async _cancelPayments() {
|
||||||
@ -368,6 +380,134 @@ export abstract class Invoice extends Transactional {
|
|||||||
return discountAmount;
|
return discountAmount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getReturnDoc(): Promise<Invoice | undefined> {
|
||||||
|
if (!this.name) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const docData = this.getValidDict(true, true);
|
||||||
|
const docItems = docData.items as DocValueMap[];
|
||||||
|
|
||||||
|
if (!docItems) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let returnDocItems: DocValueMap[] = [];
|
||||||
|
|
||||||
|
const returnBalanceItemsQty = await this.fyo.db.getReturnBalanceItemsQty(
|
||||||
|
this.schemaName,
|
||||||
|
this.name
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const item of docItems) {
|
||||||
|
if (!returnBalanceItemsQty) {
|
||||||
|
returnDocItems = docItems;
|
||||||
|
returnDocItems.map((row) => {
|
||||||
|
row.name = undefined;
|
||||||
|
(row.quantity as number) *= -1;
|
||||||
|
return row;
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isItemExist = !!returnDocItems.filter(
|
||||||
|
(balanceItem) => balanceItem.item === item.item
|
||||||
|
).length;
|
||||||
|
|
||||||
|
if (isItemExist) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const returnedItem: ReturnDocItem | undefined =
|
||||||
|
returnBalanceItemsQty[item.item as string];
|
||||||
|
|
||||||
|
let quantity = returnedItem.quantity;
|
||||||
|
let serialNumber: string | undefined =
|
||||||
|
returnedItem.serialNumbers?.join('\n');
|
||||||
|
|
||||||
|
if (
|
||||||
|
item.batch &&
|
||||||
|
returnedItem.batches &&
|
||||||
|
returnedItem.batches[item.batch as string]
|
||||||
|
) {
|
||||||
|
quantity = returnedItem.batches[item.batch as string].quantity;
|
||||||
|
|
||||||
|
if (returnedItem.batches[item.batch as string].serialNumbers) {
|
||||||
|
serialNumber =
|
||||||
|
returnedItem.batches[item.batch as string].serialNumbers?.join(
|
||||||
|
'\n'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
returnDocItems.push({
|
||||||
|
...item,
|
||||||
|
serialNumber,
|
||||||
|
name: undefined,
|
||||||
|
quantity: quantity,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const returnDocData = {
|
||||||
|
...docData,
|
||||||
|
name: undefined,
|
||||||
|
date: new Date(),
|
||||||
|
items: returnDocItems,
|
||||||
|
returnAgainst: docData.name,
|
||||||
|
} as DocValueMap;
|
||||||
|
|
||||||
|
const newReturnDoc = this.fyo.doc.getNewDoc(
|
||||||
|
this.schema.name,
|
||||||
|
returnDocData
|
||||||
|
) as Invoice;
|
||||||
|
|
||||||
|
await newReturnDoc.runFormulas();
|
||||||
|
return newReturnDoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
async _updateIsItemsReturned() {
|
||||||
|
if (!this.isReturn || !this.returnAgainst) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const returnInvoices = await this.fyo.db.getAll(this.schema.name, {
|
||||||
|
filters: {
|
||||||
|
submitted: true,
|
||||||
|
cancelled: false,
|
||||||
|
returnAgainst: this.returnAgainst,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const isReturned = !!returnInvoices.length;
|
||||||
|
const invoiceDoc = await this.fyo.doc.getDoc(
|
||||||
|
this.schemaName,
|
||||||
|
this.returnAgainst
|
||||||
|
);
|
||||||
|
await invoiceDoc.setAndSync({ isReturned });
|
||||||
|
await invoiceDoc.submit();
|
||||||
|
}
|
||||||
|
|
||||||
|
async _validateHasLinkedReturnInvoices() {
|
||||||
|
if (!this.name || this.isReturn) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const returnInvoices = await this.fyo.db.getAll(this.schemaName, {
|
||||||
|
filters: {
|
||||||
|
returnAgainst: this.name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!returnInvoices.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const names = returnInvoices.map(({ name }) => name).join(', ');
|
||||||
|
throw new ValidationError(
|
||||||
|
this.fyo
|
||||||
|
.t`Cannot cancel ${this.name} because of the following ${this.schema.label}: ${names}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
formulas: FormulaMap = {
|
formulas: FormulaMap = {
|
||||||
account: {
|
account: {
|
||||||
formula: async () => {
|
formula: async () => {
|
||||||
@ -518,6 +658,8 @@ export abstract class Invoice extends Transactional {
|
|||||||
!(this.attachment || !(this.isSubmitted || this.isCancelled)),
|
!(this.attachment || !(this.isSubmitted || this.isCancelled)),
|
||||||
backReference: () => !this.backReference,
|
backReference: () => !this.backReference,
|
||||||
priceList: () => !this.fyo.singles.AccountingSettings?.enablePriceList,
|
priceList: () => !this.fyo.singles.AccountingSettings?.enablePriceList,
|
||||||
|
returnAgainst: () =>
|
||||||
|
(this.isSubmitted || this.isCancelled) && !this.returnAgainst,
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaults: DefaultMap = {
|
static defaults: DefaultMap = {
|
||||||
@ -595,12 +737,29 @@ export abstract class Invoice extends Transactional {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const accountField = this.isSales ? 'account' : 'paymentAccount';
|
let accountField: AccountFieldEnum = AccountFieldEnum.Account;
|
||||||
|
let paymentType: PaymentTypeEnum = PaymentTypeEnum.Receive;
|
||||||
|
|
||||||
|
if (this.isSales && this.isReturn) {
|
||||||
|
accountField = AccountFieldEnum.PaymentAccount;
|
||||||
|
paymentType = PaymentTypeEnum.Pay;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.isSales) {
|
||||||
|
accountField = AccountFieldEnum.PaymentAccount;
|
||||||
|
paymentType = PaymentTypeEnum.Pay;
|
||||||
|
|
||||||
|
if (this.isReturn) {
|
||||||
|
accountField = AccountFieldEnum.Account;
|
||||||
|
paymentType = PaymentTypeEnum.Receive;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
party: this.party,
|
party: this.party,
|
||||||
date: new Date().toISOString().slice(0, 10),
|
date: new Date().toISOString().slice(0, 10),
|
||||||
paymentType: this.isSales ? 'Receive' : 'Pay',
|
paymentType,
|
||||||
amount: this.outstandingAmount,
|
amount: this.outstandingAmount?.abs(),
|
||||||
[accountField]: this.account,
|
[accountField]: this.account,
|
||||||
for: [
|
for: [
|
||||||
{
|
{
|
||||||
@ -720,6 +879,7 @@ export abstract class Invoice extends Transactional {
|
|||||||
async beforeCancel(): Promise<void> {
|
async beforeCancel(): Promise<void> {
|
||||||
await super.beforeCancel();
|
await super.beforeCancel();
|
||||||
await this._validateStockTransferCancelled();
|
await this._validateStockTransferCancelled();
|
||||||
|
await this._validateHasLinkedReturnInvoices();
|
||||||
}
|
}
|
||||||
|
|
||||||
async beforeDelete(): Promise<void> {
|
async beforeDelete(): Promise<void> {
|
||||||
|
@ -86,6 +86,10 @@ export abstract class InvoiceItem extends Doc {
|
|||||||
return this.parentdoc?.isMultiCurrency ?? false;
|
return this.parentdoc?.isMultiCurrency ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isReturn() {
|
||||||
|
return !!this.parentdoc?.isReturn;
|
||||||
|
}
|
||||||
|
|
||||||
constructor(schema: Schema, data: DocValueMap, fyo: Fyo) {
|
constructor(schema: Schema, data: DocValueMap, fyo: Fyo) {
|
||||||
super(schema, data, fyo);
|
super(schema, data, fyo);
|
||||||
this._setGetCurrencies();
|
this._setGetCurrencies();
|
||||||
@ -210,6 +214,15 @@ export abstract class InvoiceItem extends Doc {
|
|||||||
const unitDoc = itemDoc.getLink('uom');
|
const unitDoc = itemDoc.getLink('uom');
|
||||||
|
|
||||||
let quantity: number = this.quantity ?? 1;
|
let quantity: number = this.quantity ?? 1;
|
||||||
|
|
||||||
|
if (this.isReturn && quantity > 0) {
|
||||||
|
quantity *= -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.isReturn && quantity < 0) {
|
||||||
|
quantity *= -1;
|
||||||
|
}
|
||||||
|
|
||||||
if (fieldname === 'transferQuantity') {
|
if (fieldname === 'transferQuantity') {
|
||||||
quantity = this.transferQuantity! * this.unitConversionFactor!;
|
quantity = this.transferQuantity! * this.unitConversionFactor!;
|
||||||
}
|
}
|
||||||
@ -225,6 +238,8 @@ export abstract class InvoiceItem extends Doc {
|
|||||||
'transferQuantity',
|
'transferQuantity',
|
||||||
'transferUnit',
|
'transferUnit',
|
||||||
'unitConversionFactor',
|
'unitConversionFactor',
|
||||||
|
'item',
|
||||||
|
'isReturn',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
unitConversionFactor: {
|
unitConversionFactor: {
|
||||||
|
@ -36,6 +36,7 @@ export class Payment extends Transactional {
|
|||||||
amount?: Money;
|
amount?: Money;
|
||||||
writeoff?: Money;
|
writeoff?: Money;
|
||||||
paymentType?: PaymentType;
|
paymentType?: PaymentType;
|
||||||
|
referenceType?: ModelNameEnum.SalesInvoice | ModelNameEnum.PurchaseInvoice;
|
||||||
for?: PaymentFor[];
|
for?: PaymentFor[];
|
||||||
_accountsMap?: AccountTypeMap;
|
_accountsMap?: AccountTypeMap;
|
||||||
|
|
||||||
@ -300,7 +301,7 @@ export class Payment extends Transactional {
|
|||||||
)) as Invoice;
|
)) as Invoice;
|
||||||
|
|
||||||
outstandingAmount = outstandingAmount.add(
|
outstandingAmount = outstandingAmount.add(
|
||||||
referenceDoc.outstandingAmount ?? 0
|
referenceDoc.outstandingAmount?.abs() ?? 0
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -438,6 +439,15 @@ export class Payment extends Transactional {
|
|||||||
'referenceName'
|
'referenceName'
|
||||||
)) as Invoice | null;
|
)) as Invoice | null;
|
||||||
|
|
||||||
|
if (
|
||||||
|
refDoc &&
|
||||||
|
refDoc.schema.name === ModelNameEnum.SalesInvoice &&
|
||||||
|
refDoc.isReturned
|
||||||
|
) {
|
||||||
|
const accountsMap = await this._getAccountsMap();
|
||||||
|
return accountsMap[AccountTypeEnum.Cash]?.[0];
|
||||||
|
}
|
||||||
|
|
||||||
return refDoc?.account ?? null;
|
return refDoc?.account ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -495,13 +505,21 @@ export class Payment extends Transactional {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const partyDoc = (await this.loadAndGetLink('party')) as Party;
|
const partyDoc = (await this.loadAndGetLink('party')) as Party;
|
||||||
|
const outstanding = partyDoc.outstandingAmount as Money;
|
||||||
|
|
||||||
|
if (outstanding.isNegative()) {
|
||||||
|
if (this.referenceType === ModelNameEnum.SalesInvoice) {
|
||||||
|
return 'Pay';
|
||||||
|
}
|
||||||
|
return 'Receive';
|
||||||
|
}
|
||||||
|
|
||||||
if (partyDoc.role === 'Supplier') {
|
if (partyDoc.role === 'Supplier') {
|
||||||
return 'Pay';
|
return 'Pay';
|
||||||
} else if (partyDoc.role === 'Customer') {
|
} else if (partyDoc.role === 'Customer') {
|
||||||
return 'Receive';
|
return 'Receive';
|
||||||
}
|
}
|
||||||
|
|
||||||
const outstanding = partyDoc.outstandingAmount as Money;
|
|
||||||
if (outstanding?.isZero() ?? true) {
|
if (outstanding?.isZero() ?? true) {
|
||||||
return this.paymentType;
|
return this.paymentType;
|
||||||
}
|
}
|
||||||
@ -520,6 +538,14 @@ export class Payment extends Transactional {
|
|||||||
formula: () => this.amount!.sub(this.writeoff!),
|
formula: () => this.amount!.sub(this.writeoff!),
|
||||||
dependsOn: ['amount', 'writeoff', 'for'],
|
dependsOn: ['amount', 'writeoff', 'for'],
|
||||||
},
|
},
|
||||||
|
referenceType: {
|
||||||
|
formula: () => {
|
||||||
|
if (this.referenceType) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return this.for![0].referenceType;
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
validations: ValidationMap = {
|
validations: ValidationMap = {
|
||||||
@ -534,7 +560,7 @@ export class Payment extends Transactional {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const amount = this.getSum('for', 'amount', false);
|
const amount = (this.getSum('for', 'amount', false) as Money).abs();
|
||||||
|
|
||||||
if ((value as Money).gt(amount)) {
|
if ((value as Money).gt(amount)) {
|
||||||
throw new ValidationError(
|
throw new ValidationError(
|
||||||
|
@ -1,2 +1,12 @@
|
|||||||
export type PaymentType = 'Receive' | 'Pay';
|
export type PaymentType = 'Receive' | 'Pay';
|
||||||
export type PaymentMethod = 'Cash' | 'Cheque' | 'Transfer';
|
export type PaymentMethod = 'Cash' | 'Cheque' | 'Transfer';
|
||||||
|
|
||||||
|
export enum PaymentTypeEnum{
|
||||||
|
Receive = 'Receive',
|
||||||
|
Pay = 'Pay'
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum AccountFieldEnum{
|
||||||
|
Account = 'account',
|
||||||
|
PaymentAccount = 'paymentAccount'
|
||||||
|
}
|
||||||
|
@ -12,14 +12,26 @@ export class PurchaseInvoice extends Invoice {
|
|||||||
async getPosting() {
|
async getPosting() {
|
||||||
const exchangeRate = this.exchangeRate ?? 1;
|
const exchangeRate = this.exchangeRate ?? 1;
|
||||||
const posting: LedgerPosting = new LedgerPosting(this, this.fyo);
|
const posting: LedgerPosting = new LedgerPosting(this, this.fyo);
|
||||||
await posting.credit(this.account!, this.baseGrandTotal!);
|
if (this.isReturn) {
|
||||||
|
await posting.debit(this.account!, this.baseGrandTotal!);
|
||||||
|
} else {
|
||||||
|
await posting.credit(this.account!, this.baseGrandTotal!);
|
||||||
|
}
|
||||||
|
|
||||||
for (const item of this.items!) {
|
for (const item of this.items!) {
|
||||||
|
if (this.isReturn) {
|
||||||
|
await posting.credit(item.account!, item.amount!.mul(exchangeRate));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
await posting.debit(item.account!, item.amount!.mul(exchangeRate));
|
await posting.debit(item.account!, item.amount!.mul(exchangeRate));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.taxes) {
|
if (this.taxes) {
|
||||||
for (const tax of this.taxes) {
|
for (const tax of this.taxes) {
|
||||||
|
if (this.isReturn) {
|
||||||
|
await posting.credit(tax.account!, tax.amount!.mul(exchangeRate));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
await posting.debit(tax.account!, tax.amount!.mul(exchangeRate));
|
await posting.debit(tax.account!, tax.amount!.mul(exchangeRate));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -28,7 +40,11 @@ export class PurchaseInvoice extends Invoice {
|
|||||||
const discountAccount = this.fyo.singles.AccountingSettings
|
const discountAccount = this.fyo.singles.AccountingSettings
|
||||||
?.discountAccount as string | undefined;
|
?.discountAccount as string | undefined;
|
||||||
if (discountAccount && discountAmount.isPositive()) {
|
if (discountAccount && discountAmount.isPositive()) {
|
||||||
await posting.credit(discountAccount, discountAmount.mul(exchangeRate));
|
if (this.isReturn) {
|
||||||
|
await posting.debit(discountAccount, discountAmount.mul(exchangeRate));
|
||||||
|
} else {
|
||||||
|
await posting.credit(discountAccount, discountAmount.mul(exchangeRate));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await posting.makeRoundOffEntry();
|
await posting.makeRoundOffEntry();
|
||||||
|
@ -12,14 +12,26 @@ export class SalesInvoice extends Invoice {
|
|||||||
async getPosting() {
|
async getPosting() {
|
||||||
const exchangeRate = this.exchangeRate ?? 1;
|
const exchangeRate = this.exchangeRate ?? 1;
|
||||||
const posting: LedgerPosting = new LedgerPosting(this, this.fyo);
|
const posting: LedgerPosting = new LedgerPosting(this, this.fyo);
|
||||||
await posting.debit(this.account!, this.baseGrandTotal!);
|
if (this.isReturn) {
|
||||||
|
await posting.credit(this.account!, this.baseGrandTotal!);
|
||||||
|
} else {
|
||||||
|
await posting.debit(this.account!, this.baseGrandTotal!);
|
||||||
|
}
|
||||||
|
|
||||||
for (const item of this.items!) {
|
for (const item of this.items!) {
|
||||||
|
if (this.isReturn) {
|
||||||
|
await posting.debit(item.account!, item.amount!.mul(exchangeRate));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
await posting.credit(item.account!, item.amount!.mul(exchangeRate));
|
await posting.credit(item.account!, item.amount!.mul(exchangeRate));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.taxes) {
|
if (this.taxes) {
|
||||||
for (const tax of this.taxes) {
|
for (const tax of this.taxes) {
|
||||||
|
if (this.isReturn) {
|
||||||
|
await posting.debit(tax.account!, tax.amount!.mul(exchangeRate));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
await posting.credit(tax.account!, tax.amount!.mul(exchangeRate));
|
await posting.credit(tax.account!, tax.amount!.mul(exchangeRate));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -28,7 +40,11 @@ export class SalesInvoice extends Invoice {
|
|||||||
const discountAccount = this.fyo.singles.AccountingSettings
|
const discountAccount = this.fyo.singles.AccountingSettings
|
||||||
?.discountAccount as string | undefined;
|
?.discountAccount as string | undefined;
|
||||||
if (discountAccount && discountAmount.isPositive()) {
|
if (discountAccount && discountAmount.isPositive()) {
|
||||||
await posting.debit(discountAccount, discountAmount.mul(exchangeRate));
|
if (this.isReturn) {
|
||||||
|
await posting.credit(discountAccount, discountAmount.mul(exchangeRate));
|
||||||
|
} else {
|
||||||
|
await posting.debit(discountAccount, discountAmount.mul(exchangeRate));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await posting.makeRoundOffEntry();
|
await posting.makeRoundOffEntry();
|
||||||
|
282
models/baseModels/tests/testInvoice.spec.ts
Normal file
282
models/baseModels/tests/testInvoice.spec.ts
Normal file
@ -0,0 +1,282 @@
|
|||||||
|
import test from 'tape';
|
||||||
|
import { closeTestFyo, getTestFyo, setupTestFyo } from 'tests/helpers';
|
||||||
|
import { ModelNameEnum } from 'models/types';
|
||||||
|
import { SalesInvoice } from '../SalesInvoice/SalesInvoice';
|
||||||
|
import { Payment } from '../Payment/Payment';
|
||||||
|
import { PaymentTypeEnum } from '../Payment/types';
|
||||||
|
import {
|
||||||
|
assertDoesNotThrow,
|
||||||
|
assertThrows,
|
||||||
|
} from 'backend/database/tests/helpers';
|
||||||
|
import { PurchaseInvoice } from '../PurchaseInvoice/PurchaseInvoice';
|
||||||
|
|
||||||
|
const fyo = getTestFyo();
|
||||||
|
setupTestFyo(fyo, __filename);
|
||||||
|
|
||||||
|
const itemData = {
|
||||||
|
name: 'Pen',
|
||||||
|
rate: 100,
|
||||||
|
unit: 'Unit',
|
||||||
|
for: 'Both',
|
||||||
|
trackItem: true,
|
||||||
|
hasBatch: true,
|
||||||
|
hasSerialNumber: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const partyData = {
|
||||||
|
name: 'John Whoe',
|
||||||
|
email: 'john@whoe.com',
|
||||||
|
};
|
||||||
|
|
||||||
|
const batchMap = {
|
||||||
|
batchOne: {
|
||||||
|
name: 'PN-AB001',
|
||||||
|
manufactureDate: '2022-11-03T09:57:04.528',
|
||||||
|
},
|
||||||
|
batchTwo: {
|
||||||
|
name: 'PN-AB002',
|
||||||
|
manufactureDate: '2022-10-03T09:57:04.528',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
test('create test docs', async (t) => {
|
||||||
|
await fyo.doc.getNewDoc(ModelNameEnum.Item, itemData).sync();
|
||||||
|
|
||||||
|
t.ok(
|
||||||
|
fyo.db.exists(ModelNameEnum.Item, itemData.name),
|
||||||
|
`dummy item ${itemData.name} exists`
|
||||||
|
);
|
||||||
|
|
||||||
|
await fyo.doc.getNewDoc(ModelNameEnum.Party, partyData).sync();
|
||||||
|
t.ok(
|
||||||
|
fyo.db.exists(ModelNameEnum.Party, partyData.name),
|
||||||
|
`dummy party ${partyData.name} exists`
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const batch of Object.values(batchMap)) {
|
||||||
|
await fyo.doc.getNewDoc(ModelNameEnum.Batch, batch).sync(),
|
||||||
|
t.ok(
|
||||||
|
fyo.db.exists(ModelNameEnum.Batch, batch.name),
|
||||||
|
`batch ${batch.name} exists`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('create SINV with batch then create payment against it', async (t) => {
|
||||||
|
const sinvDoc = fyo.doc.getNewDoc(ModelNameEnum.SalesInvoice, {
|
||||||
|
account: 'Debtors',
|
||||||
|
party: partyData.name,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
item: itemData.name,
|
||||||
|
batch: batchMap.batchOne.name,
|
||||||
|
rate: itemData.rate,
|
||||||
|
quantity: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}) as SalesInvoice;
|
||||||
|
|
||||||
|
await sinvDoc.sync();
|
||||||
|
await sinvDoc.runFormulas();
|
||||||
|
await sinvDoc.submit();
|
||||||
|
|
||||||
|
t.ok(
|
||||||
|
fyo.db.exists(ModelNameEnum.SalesInvoice, sinvDoc.name),
|
||||||
|
`${sinvDoc.name} exists`
|
||||||
|
);
|
||||||
|
|
||||||
|
const paymentDoc = sinvDoc.getPayment();
|
||||||
|
await paymentDoc?.sync();
|
||||||
|
await paymentDoc?.submit();
|
||||||
|
|
||||||
|
t.equals(paymentDoc?.name, 'PAY-1001');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('create SINV return for one qty', async (t) => {
|
||||||
|
const sinvDoc = (await fyo.doc.getDoc(
|
||||||
|
ModelNameEnum.SalesInvoice,
|
||||||
|
'SINV-1001'
|
||||||
|
)) as SalesInvoice;
|
||||||
|
|
||||||
|
let returnDoc = (await sinvDoc?.getReturnDoc()) as SalesInvoice;
|
||||||
|
|
||||||
|
returnDoc.items = [];
|
||||||
|
returnDoc.append('items', {
|
||||||
|
item: itemData.name,
|
||||||
|
batch: batchMap.batchOne.name,
|
||||||
|
quantity: 1,
|
||||||
|
rate: itemData.rate,
|
||||||
|
});
|
||||||
|
|
||||||
|
await returnDoc.runFormulas();
|
||||||
|
await returnDoc.sync();
|
||||||
|
await returnDoc.submit();
|
||||||
|
|
||||||
|
t.ok(
|
||||||
|
await fyo.db.exists(ModelNameEnum.SalesInvoice, returnDoc.name),
|
||||||
|
'SINV return for one qty created'
|
||||||
|
);
|
||||||
|
|
||||||
|
t.equals(
|
||||||
|
returnDoc.outstandingAmount?.float,
|
||||||
|
itemData.rate,
|
||||||
|
'returnDoc outstanding amount matches'
|
||||||
|
);
|
||||||
|
|
||||||
|
const returnSinvAles = await fyo.db.getAllRaw(
|
||||||
|
ModelNameEnum.AccountingLedgerEntry,
|
||||||
|
{
|
||||||
|
fields: ['name', 'account', 'credit', 'debit'],
|
||||||
|
filters: { referenceName: returnDoc.name! },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const ale of returnSinvAles) {
|
||||||
|
if (ale.account === 'Sales') {
|
||||||
|
t.equal(
|
||||||
|
fyo.pesa(ale.debit as string).float,
|
||||||
|
fyo.pesa(itemData.rate).float,
|
||||||
|
`return Invoice debited from ${ale.account}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ale.account === 'Debtors') {
|
||||||
|
t.equal(
|
||||||
|
fyo.pesa(ale.credit as string).float,
|
||||||
|
fyo.pesa(itemData.rate).float,
|
||||||
|
`return Invoice credited to ${ale.account}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await assertThrows(
|
||||||
|
async () => await sinvDoc.cancel(),
|
||||||
|
'can not cancel a SINV when a return invoice is created against it'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('create SINV return for balance qty', async (t) => {
|
||||||
|
const sinvDoc = (await fyo.doc.getDoc(
|
||||||
|
ModelNameEnum.SalesInvoice,
|
||||||
|
'SINV-1001'
|
||||||
|
)) as SalesInvoice;
|
||||||
|
|
||||||
|
const returnDoc = (await sinvDoc?.getReturnDoc()) as SalesInvoice;
|
||||||
|
t.equals(
|
||||||
|
Object.values(returnDoc.items!)[0].quantity,
|
||||||
|
-1,
|
||||||
|
'return doc has 1 qty left to return'
|
||||||
|
);
|
||||||
|
|
||||||
|
await returnDoc.sync();
|
||||||
|
|
||||||
|
await returnDoc.runFormulas();
|
||||||
|
await returnDoc.submit();
|
||||||
|
|
||||||
|
t.ok(
|
||||||
|
await fyo.db.exists(ModelNameEnum.SalesInvoice, returnDoc.name),
|
||||||
|
'SINV return for one qty created'
|
||||||
|
);
|
||||||
|
|
||||||
|
t.equals(
|
||||||
|
returnDoc.outstandingAmount?.float,
|
||||||
|
-itemData.rate,
|
||||||
|
'return doc outstanding amount matches'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('create payment for return invoice', async (t) => {
|
||||||
|
const returnDoc = (await fyo.doc.getDoc(
|
||||||
|
ModelNameEnum.SalesInvoice,
|
||||||
|
'SINV-1002'
|
||||||
|
)) as SalesInvoice;
|
||||||
|
|
||||||
|
t.equals(returnDoc.returnAgainst, 'SINV-1001');
|
||||||
|
|
||||||
|
const paymentDoc = returnDoc.getPayment() as Payment;
|
||||||
|
t.equals(paymentDoc.paymentType, PaymentTypeEnum.Pay, 'payment type is pay');
|
||||||
|
|
||||||
|
t.equals(
|
||||||
|
paymentDoc.amount?.float,
|
||||||
|
itemData.rate,
|
||||||
|
'payment amount for return invoice matches'
|
||||||
|
);
|
||||||
|
|
||||||
|
await paymentDoc.sync();
|
||||||
|
|
||||||
|
t.ok(
|
||||||
|
await fyo.db.exists(ModelNameEnum.Payment, paymentDoc.name),
|
||||||
|
'payment entry created for return invoice'
|
||||||
|
);
|
||||||
|
|
||||||
|
await assertDoesNotThrow(
|
||||||
|
async () => await returnDoc.cancel(),
|
||||||
|
'return invoice cancelled'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('creating PINV return when invoice is not paid', async (t) => {
|
||||||
|
const pinvDoc = fyo.doc.getNewDoc(
|
||||||
|
ModelNameEnum.PurchaseInvoice
|
||||||
|
) as PurchaseInvoice;
|
||||||
|
|
||||||
|
await pinvDoc.set({
|
||||||
|
party: partyData.name,
|
||||||
|
account: 'Creditors',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
item: itemData.name,
|
||||||
|
batch: batchMap.batchOne.name,
|
||||||
|
quantity: 2,
|
||||||
|
rate: itemData.rate,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
await pinvDoc.sync();
|
||||||
|
await pinvDoc.submit();
|
||||||
|
|
||||||
|
t.equals(pinvDoc.name, 'PINV-1001', `${pinvDoc.name} is submitted`);
|
||||||
|
|
||||||
|
const returnDoc = (await pinvDoc.getReturnDoc()) as PurchaseInvoice;
|
||||||
|
await returnDoc.sync();
|
||||||
|
await returnDoc.submit();
|
||||||
|
|
||||||
|
t.equals(
|
||||||
|
returnDoc?.returnAgainst,
|
||||||
|
pinvDoc.name,
|
||||||
|
`return pinv created against ${pinvDoc.name}`
|
||||||
|
);
|
||||||
|
t.equals(
|
||||||
|
Object.values(returnDoc.items!)[0].quantity,
|
||||||
|
-2,
|
||||||
|
'pinv returned qty matches'
|
||||||
|
);
|
||||||
|
|
||||||
|
const returnSinvAles = await fyo.db.getAllRaw(
|
||||||
|
ModelNameEnum.AccountingLedgerEntry,
|
||||||
|
{
|
||||||
|
fields: ['name', 'account', 'credit', 'debit'],
|
||||||
|
filters: { referenceName: returnDoc.name! },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const ale of returnSinvAles) {
|
||||||
|
if (ale.account === 'Creditors') {
|
||||||
|
t.equal(
|
||||||
|
fyo.pesa(ale.debit as string).float,
|
||||||
|
returnDoc.outstandingAmount!.float,
|
||||||
|
`return Invoice debited from ${ale.account}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ale.account === 'Cost of Goods Sold') {
|
||||||
|
t.equal(
|
||||||
|
fyo.pesa(ale.credit as string).float,
|
||||||
|
returnDoc.outstandingAmount!.float,
|
||||||
|
`return Invoice credited to ${ale.account}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
closeTestFyo(fyo, __filename);
|
@ -1,11 +1,8 @@
|
|||||||
import test from 'tape';
|
import test from 'tape';
|
||||||
import { getDefaultMetaFieldValueMap } from 'backend/helpers';
|
|
||||||
import { closeTestFyo, getTestFyo, setupTestFyo } from 'tests/helpers';
|
import { closeTestFyo, getTestFyo, setupTestFyo } from 'tests/helpers';
|
||||||
import { ModelNameEnum } from 'models/types';
|
import { ModelNameEnum } from 'models/types';
|
||||||
import { getItem } from 'models/inventory/tests/helpers';
|
import { getItem } from 'models/inventory/tests/helpers';
|
||||||
import { SalesInvoiceItem } from '../SalesInvoiceItem/SalesInvoiceItem';
|
|
||||||
import { SalesInvoice } from '../SalesInvoice/SalesInvoice';
|
import { SalesInvoice } from '../SalesInvoice/SalesInvoice';
|
||||||
import { PurchaseInvoiceItem } from '../PurchaseInvoiceItem/PurchaseInvoiceItem';
|
|
||||||
|
|
||||||
const fyo = getTestFyo();
|
const fyo = getTestFyo();
|
||||||
setupTestFyo(fyo, __filename);
|
setupTestFyo(fyo, __filename);
|
||||||
|
@ -23,6 +23,7 @@ export function getInvoiceActions(
|
|||||||
getMakePaymentAction(fyo),
|
getMakePaymentAction(fyo),
|
||||||
getMakeStockTransferAction(fyo, schemaName),
|
getMakeStockTransferAction(fyo, schemaName),
|
||||||
getLedgerLinkAction(fyo),
|
getLedgerLinkAction(fyo),
|
||||||
|
getMakeReturnDocAction(fyo),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,11 +98,13 @@ export function getMakePaymentAction(fyo: Fyo): Action {
|
|||||||
condition: (doc: Doc) =>
|
condition: (doc: Doc) =>
|
||||||
doc.isSubmitted && !(doc.outstandingAmount as Money).isZero(),
|
doc.isSubmitted && !(doc.outstandingAmount as Money).isZero(),
|
||||||
action: async (doc, router) => {
|
action: async (doc, router) => {
|
||||||
|
const schemaName = doc.schema.name;
|
||||||
const payment = (doc as Invoice).getPayment();
|
const payment = (doc as Invoice).getPayment();
|
||||||
if (!payment) {
|
if (!payment) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await payment?.set('referenceType', schemaName);
|
||||||
const currentRoute = router.currentRoute.value.fullPath;
|
const currentRoute = router.currentRoute.value.fullPath;
|
||||||
payment.once('afterSync', async () => {
|
payment.once('afterSync', async () => {
|
||||||
await payment.submit();
|
await payment.submit();
|
||||||
@ -109,7 +112,12 @@ export function getMakePaymentAction(fyo: Fyo): Action {
|
|||||||
await router.push(currentRoute);
|
await router.push(currentRoute);
|
||||||
});
|
});
|
||||||
|
|
||||||
const hideFields = ['party', 'paymentType', 'for'];
|
const hideFields = ['party', 'for'];
|
||||||
|
|
||||||
|
if (!fyo.singles.AccountingSettings?.enableInvoiceReturns) {
|
||||||
|
hideFields.push('paymentType');
|
||||||
|
}
|
||||||
|
|
||||||
if (doc.schemaName === ModelNameEnum.SalesInvoice) {
|
if (doc.schemaName === ModelNameEnum.SalesInvoice) {
|
||||||
hideFields.push('account');
|
hideFields.push('account');
|
||||||
} else {
|
} else {
|
||||||
@ -166,11 +174,17 @@ export function getMakeReturnDocAction(fyo: Fyo): Action {
|
|||||||
label: fyo.t`Return`,
|
label: fyo.t`Return`,
|
||||||
group: fyo.t`Create`,
|
group: fyo.t`Create`,
|
||||||
condition: (doc: Doc) =>
|
condition: (doc: Doc) =>
|
||||||
!!fyo.singles.InventorySettings?.enableStockReturns &&
|
(!!fyo.singles.AccountingSettings?.enableInvoiceReturns ||
|
||||||
|
!!fyo.singles.InventorySettings?.enableStockReturns) &&
|
||||||
doc.isSubmitted &&
|
doc.isSubmitted &&
|
||||||
!doc.isReturn,
|
!doc.isReturn,
|
||||||
action: async (doc: Doc) => {
|
action: async (doc: Doc) => {
|
||||||
const returnDoc = await (doc as StockTransfer)?.getReturnDoc();
|
let returnDoc: Invoice | StockTransfer | undefined;
|
||||||
|
|
||||||
|
if (doc instanceof Invoice || doc instanceof StockTransfer) {
|
||||||
|
returnDoc = await doc.getReturnDoc();
|
||||||
|
}
|
||||||
|
|
||||||
if (!returnDoc || !returnDoc.name) {
|
if (!returnDoc || !returnDoc.name) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -297,6 +311,14 @@ function getSubmittableDocStatus(doc: RenderData | Doc) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getInvoiceStatus(doc: RenderData | Doc): InvoiceStatus {
|
export function getInvoiceStatus(doc: RenderData | Doc): InvoiceStatus {
|
||||||
|
if (doc.submitted && !doc.cancelled && doc.returnAgainst) {
|
||||||
|
return 'Return';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doc.submitted && !doc.cancelled && doc.isReturned) {
|
||||||
|
return 'ReturnIssued';
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
doc.submitted &&
|
doc.submitted &&
|
||||||
!doc.cancelled &&
|
!doc.cancelled &&
|
||||||
|
@ -86,6 +86,13 @@
|
|||||||
"default": false,
|
"default": false,
|
||||||
"section": "Features"
|
"section": "Features"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "enableInvoiceReturns",
|
||||||
|
"label": "Enable Invoice Returns",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"default": false,
|
||||||
|
"section": "Features"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "enableFormCustomization",
|
"fieldname": "enableFormCustomization",
|
||||||
"label": "Enable Form Customization",
|
"label": "Enable Form Customization",
|
||||||
|
@ -181,10 +181,21 @@
|
|||||||
"fieldtype": "Attachment",
|
"fieldtype": "Attachment",
|
||||||
"section": "References"
|
"section": "References"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "isReturned",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"hidden": true,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"abstract": true,
|
"abstract": true,
|
||||||
"fieldname": "backReference",
|
"fieldname": "backReference",
|
||||||
"section": "References"
|
"section": "References"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"abstract": true,
|
||||||
|
"fieldname": "returnAgainst",
|
||||||
|
"section": "References"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"keywordFields": ["name", "party"]
|
"keywordFields": ["name", "party"]
|
||||||
|
@ -155,6 +155,24 @@
|
|||||||
"label": "Attachment",
|
"label": "Attachment",
|
||||||
"fieldtype": "Attachment",
|
"fieldtype": "Attachment",
|
||||||
"section": "References"
|
"section": "References"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "referenceType",
|
||||||
|
"label": "Type",
|
||||||
|
"placeholder": "Type",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"value": "SalesInvoice",
|
||||||
|
"label": "Sales"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "PurchaseInvoice",
|
||||||
|
"label": "Purchase"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"hidden": true,
|
||||||
|
"required": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"quickEditFields": [
|
"quickEditFields": [
|
||||||
|
@ -54,6 +54,13 @@
|
|||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"readOnly": true,
|
"readOnly": true,
|
||||||
"section": "Outstanding"
|
"section": "Outstanding"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "returnAgainst",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"target": "PurchaseInvoice",
|
||||||
|
"label": "Return Against",
|
||||||
|
"section": "References"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"keywordFields": ["name", "party"]
|
"keywordFields": ["name", "party"]
|
||||||
|
@ -54,6 +54,13 @@
|
|||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"readOnly": true,
|
"readOnly": true,
|
||||||
"section": "Outstanding"
|
"section": "Outstanding"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "returnAgainst",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"target": "SalesInvoice",
|
||||||
|
"label": "Return Against",
|
||||||
|
"section": "References"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"keywordFields": ["name", "party"]
|
"keywordFields": ["name", "party"]
|
||||||
|
@ -92,6 +92,14 @@ function getSubmittableStatus(doc: Doc) {
|
|||||||
return 'Cancelled';
|
return 'Cancelled';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (doc.returnAgainst && doc.isSubmitted) {
|
||||||
|
return 'Return';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doc.isReturned && doc.isSubmitted) {
|
||||||
|
return 'ReturnIssued';
|
||||||
|
}
|
||||||
|
|
||||||
const isInvoice = doc instanceof Invoice;
|
const isInvoice = doc instanceof Invoice;
|
||||||
if (
|
if (
|
||||||
doc.isSubmitted &&
|
doc.isSubmitted &&
|
||||||
@ -113,14 +121,6 @@ function getSubmittableStatus(doc: Doc) {
|
|||||||
return 'Paid';
|
return 'Paid';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (doc.returnAgainst && doc.isSubmitted) {
|
|
||||||
return 'Return';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (doc.isReturned && doc.isSubmitted) {
|
|
||||||
return 'ReturnIssued';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (doc.isSubmitted) {
|
if (doc.isSubmitted) {
|
||||||
return 'Submitted';
|
return 'Submitted';
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,15 @@
|
|||||||
|
import { ModelNameEnum } from 'models/types';
|
||||||
|
|
||||||
export const routeFilters = {
|
export const routeFilters = {
|
||||||
SalesItems: { for: ['in', ['Sales', 'Both']] },
|
SalesItems: { for: ['in', ['Sales', 'Both']] },
|
||||||
PurchaseItems: { for: ['in', ['Purchases', 'Both']] },
|
PurchaseItems: { for: ['in', ['Purchases', 'Both']] },
|
||||||
Items: { for: 'Both' },
|
Items: { for: 'Both' },
|
||||||
PurchasePayments: { paymentType: 'Pay' },
|
PurchasePayments: {
|
||||||
SalesPayments: { paymentType: 'Receive' },
|
referenceType: ModelNameEnum.PurchaseInvoice,
|
||||||
|
},
|
||||||
|
SalesPayments: {
|
||||||
|
referenceType: ModelNameEnum.SalesInvoice,
|
||||||
|
},
|
||||||
Suppliers: { role: ['in', ['Supplier', 'Both']] },
|
Suppliers: { role: ['in', ['Supplier', 'Both']] },
|
||||||
Customers: { role: ['in', ['Customer', 'Both']] },
|
Customers: { role: ['in', ['Customer', 'Both']] },
|
||||||
Party: { role: 'Both' },
|
Party: { role: 'Both' },
|
||||||
|
Loading…
Reference in New Issue
Block a user