mirror of
https://github.com/frappe/books.git
synced 2024-11-13 00:46:28 +00:00
commit
341148e326
@ -14,6 +14,7 @@ import { getCountryInfo } from 'utils/misc';
|
|||||||
export class AccountingSettings extends Doc {
|
export class AccountingSettings extends Doc {
|
||||||
enableDiscounting?: boolean;
|
enableDiscounting?: boolean;
|
||||||
enableInventory?: boolean;
|
enableInventory?: boolean;
|
||||||
|
enablePriceList?: boolean;
|
||||||
|
|
||||||
static filters: FiltersMap = {
|
static filters: FiltersMap = {
|
||||||
writeOffAccount: () => ({
|
writeOffAccount: () => ({
|
||||||
|
@ -35,6 +35,7 @@ export abstract class Invoice extends Transactional {
|
|||||||
party?: string;
|
party?: string;
|
||||||
account?: string;
|
account?: string;
|
||||||
currency?: string;
|
currency?: string;
|
||||||
|
priceList?: string;
|
||||||
netTotal?: Money;
|
netTotal?: Money;
|
||||||
grandTotal?: Money;
|
grandTotal?: Money;
|
||||||
baseGrandTotal?: Money;
|
baseGrandTotal?: Money;
|
||||||
@ -514,6 +515,7 @@ export abstract class Invoice extends Transactional {
|
|||||||
attachment: () =>
|
attachment: () =>
|
||||||
!(this.attachment || !(this.isSubmitted || this.isCancelled)),
|
!(this.attachment || !(this.isSubmitted || this.isCancelled)),
|
||||||
backReference: () => !this.backReference,
|
backReference: () => !this.backReference,
|
||||||
|
priceList: () => !this.fyo.singles.AccountingSettings?.enablePriceList,
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaults: DefaultMap = {
|
static defaults: DefaultMap = {
|
||||||
@ -544,6 +546,10 @@ export abstract class Invoice extends Transactional {
|
|||||||
accountType: doc.isSales ? 'Receivable' : 'Payable',
|
accountType: doc.isSales ? 'Receivable' : 'Payable',
|
||||||
}),
|
}),
|
||||||
numberSeries: (doc: Doc) => ({ referenceType: doc.schemaName }),
|
numberSeries: (doc: Doc) => ({ referenceType: doc.schemaName }),
|
||||||
|
priceList: (doc: Doc) => ({
|
||||||
|
enabled: true,
|
||||||
|
...(doc.isSales ? { selling: true } : { buying: true }),
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
static createFilters: FiltersMap = {
|
static createFilters: FiltersMap = {
|
||||||
|
@ -17,6 +17,7 @@ import { safeParseFloat } from 'utils/index';
|
|||||||
import { Invoice } from '../Invoice/Invoice';
|
import { Invoice } from '../Invoice/Invoice';
|
||||||
import { Item } from '../Item/Item';
|
import { Item } from '../Item/Item';
|
||||||
import { StockTransfer } from 'models/inventory/StockTransfer';
|
import { StockTransfer } from 'models/inventory/StockTransfer';
|
||||||
|
import { getPriceListRate } from 'models/helpers';
|
||||||
|
|
||||||
export abstract class InvoiceItem extends Doc {
|
export abstract class InvoiceItem extends Doc {
|
||||||
item?: string;
|
item?: string;
|
||||||
@ -48,6 +49,18 @@ export abstract class InvoiceItem extends Doc {
|
|||||||
return this.schemaName === 'SalesInvoiceItem';
|
return this.schemaName === 'SalesInvoiceItem';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get date() {
|
||||||
|
return this.parentdoc?.date ?? undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
get party() {
|
||||||
|
return this.parentdoc?.party ?? undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
get priceList() {
|
||||||
|
return this.parentdoc?.priceList ?? undefined;
|
||||||
|
}
|
||||||
|
|
||||||
get discountAfterTax() {
|
get discountAfterTax() {
|
||||||
return !!this?.parentdoc?.discountAfterTax;
|
return !!this?.parentdoc?.discountAfterTax;
|
||||||
}
|
}
|
||||||
@ -101,12 +114,15 @@ export abstract class InvoiceItem extends Doc {
|
|||||||
},
|
},
|
||||||
rate: {
|
rate: {
|
||||||
formula: async (fieldname) => {
|
formula: async (fieldname) => {
|
||||||
const rate = (await this.fyo.getValue(
|
const priceListRate = await getPriceListRate(this);
|
||||||
|
const itemRate = (await this.fyo.getValue(
|
||||||
'Item',
|
'Item',
|
||||||
this.item as string,
|
this.item as string,
|
||||||
'rate'
|
'rate'
|
||||||
)) as undefined | Money;
|
)) as undefined | Money;
|
||||||
|
|
||||||
|
const rate = priceListRate instanceof Money ? priceListRate : itemRate;
|
||||||
|
|
||||||
if (!rate?.float && this.rate?.float) {
|
if (!rate?.float && this.rate?.float) {
|
||||||
return this.rate;
|
return this.rate;
|
||||||
}
|
}
|
||||||
@ -144,6 +160,9 @@ export abstract class InvoiceItem extends Doc {
|
|||||||
return rateFromTotals ?? rate ?? this.fyo.pesa(0);
|
return rateFromTotals ?? rate ?? this.fyo.pesa(0);
|
||||||
},
|
},
|
||||||
dependsOn: [
|
dependsOn: [
|
||||||
|
'date',
|
||||||
|
'priceList',
|
||||||
|
'batch',
|
||||||
'party',
|
'party',
|
||||||
'exchangeRate',
|
'exchangeRate',
|
||||||
'item',
|
'item',
|
||||||
|
60
models/baseModels/ItemPrice/ItemPrice.ts
Normal file
60
models/baseModels/ItemPrice/ItemPrice.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import { t } from 'fyo';
|
||||||
|
import { DocValue } from 'fyo/core/types';
|
||||||
|
import { Doc } from 'fyo/model/doc';
|
||||||
|
import { ValidationMap } from 'fyo/model/types';
|
||||||
|
import { ValidationError } from 'fyo/utils/errors';
|
||||||
|
import { getItemPrice } from 'models/helpers';
|
||||||
|
import { ModelNameEnum } from 'models/types';
|
||||||
|
import { Money } from 'pesa';
|
||||||
|
|
||||||
|
export class ItemPrice extends Doc {
|
||||||
|
item?: string;
|
||||||
|
rate?: Money;
|
||||||
|
validFrom?: Date;
|
||||||
|
validUpto?: Date;
|
||||||
|
|
||||||
|
get isBuying() {
|
||||||
|
return !!this.parentdoc?.buying;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isSelling() {
|
||||||
|
return !!this.parentdoc?.selling;
|
||||||
|
}
|
||||||
|
|
||||||
|
get priceList() {
|
||||||
|
return this.parentdoc?.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
validations: ValidationMap = {
|
||||||
|
validUpto: async (value: DocValue) => {
|
||||||
|
if (!value || !this.validFrom) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (value < this.validFrom) {
|
||||||
|
throw new ValidationError(
|
||||||
|
t`Valid From date can not be greater than Valid To date.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const itemPrice = await getItemPrice(
|
||||||
|
this,
|
||||||
|
this.validFrom,
|
||||||
|
this.validUpto
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!itemPrice) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const priceList = (await this.fyo.getValue(
|
||||||
|
ModelNameEnum.ItemPrice,
|
||||||
|
itemPrice,
|
||||||
|
'parent'
|
||||||
|
)) as string;
|
||||||
|
|
||||||
|
throw new ValidationError(
|
||||||
|
t`an Item Price already exists for the given date in Price List ${priceList}`
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
18
models/baseModels/PriceList/PriceList.ts
Normal file
18
models/baseModels/PriceList/PriceList.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { Doc } from 'fyo/model/doc';
|
||||||
|
import { ListViewSettings } from 'fyo/model/types';
|
||||||
|
import { ItemPrice } from '../ItemPrice/ItemPrice';
|
||||||
|
import { getPriceListStatusColumn } from 'models/helpers';
|
||||||
|
|
||||||
|
export class PriceList extends Doc {
|
||||||
|
enabled?: boolean;
|
||||||
|
buying?: boolean;
|
||||||
|
selling?: boolean;
|
||||||
|
isUomDependent?: boolean;
|
||||||
|
priceListItem?: ItemPrice[];
|
||||||
|
|
||||||
|
static getListViewSettings(): ListViewSettings {
|
||||||
|
return {
|
||||||
|
columns: ['name', getPriceListStatusColumn()],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
220
models/baseModels/tests/testPriceList.spec.ts
Normal file
220
models/baseModels/tests/testPriceList.spec.ts
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
import test from 'tape';
|
||||||
|
import { getDefaultMetaFieldValueMap } from 'backend/helpers';
|
||||||
|
import { closeTestFyo, getTestFyo, setupTestFyo } from 'tests/helpers';
|
||||||
|
import { ModelNameEnum } from 'models/types';
|
||||||
|
import { getItem } from 'models/inventory/tests/helpers';
|
||||||
|
import { getItemPrice } from 'models/helpers';
|
||||||
|
import { SalesInvoiceItem } from '../SalesInvoiceItem/SalesInvoiceItem';
|
||||||
|
import { SalesInvoice } from '../SalesInvoice/SalesInvoice';
|
||||||
|
import { PurchaseInvoiceItem } from '../PurchaseInvoiceItem/PurchaseInvoiceItem';
|
||||||
|
|
||||||
|
const fyo = getTestFyo();
|
||||||
|
setupTestFyo(fyo, __filename);
|
||||||
|
|
||||||
|
const itemMap = {
|
||||||
|
Pen: {
|
||||||
|
name: 'Pen',
|
||||||
|
rate: 100,
|
||||||
|
hasBatch: true,
|
||||||
|
},
|
||||||
|
Ink: {
|
||||||
|
name: 'Ink',
|
||||||
|
rate: 50,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const partyMap = {
|
||||||
|
partyOne: {
|
||||||
|
name: 'John Whoe',
|
||||||
|
email: 'john@whoe.com',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const batchMap = {
|
||||||
|
batchOne: {
|
||||||
|
name: 'PL-AB001',
|
||||||
|
manufactureDate: '2022-11-03T09:57:04.528',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const priceListMap = {
|
||||||
|
PL_SELL: {
|
||||||
|
name: 'PL_SELL',
|
||||||
|
enabled: true,
|
||||||
|
party: 'Shaju',
|
||||||
|
buying: false,
|
||||||
|
selling: true,
|
||||||
|
isUomDependent: false,
|
||||||
|
itemPrice: [
|
||||||
|
{
|
||||||
|
enabled: true,
|
||||||
|
item: itemMap.Pen.name,
|
||||||
|
rate: 101,
|
||||||
|
buying: false,
|
||||||
|
selling: true,
|
||||||
|
party: partyMap.partyOne.name,
|
||||||
|
validFrom: '2023-02-28T18:30:00.678Z',
|
||||||
|
validUpto: '2023-03-30T18:30:00.678Z',
|
||||||
|
...getDefaultMetaFieldValueMap(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
PL_BUY: {
|
||||||
|
name: 'PL_BUY',
|
||||||
|
enabled: true,
|
||||||
|
buying: true,
|
||||||
|
selling: false,
|
||||||
|
isUomDependent: false,
|
||||||
|
itemPrice: [
|
||||||
|
{
|
||||||
|
enabled: true,
|
||||||
|
item: itemMap.Pen.name,
|
||||||
|
rate: 102,
|
||||||
|
buying: true,
|
||||||
|
selling: false,
|
||||||
|
party: partyMap.partyOne.name,
|
||||||
|
validFrom: '2023-02-28T18:30:00.678Z',
|
||||||
|
validUpto: '2023-03-30T18:30:00.678Z',
|
||||||
|
...getDefaultMetaFieldValueMap(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
PL_SB: {
|
||||||
|
name: 'PL_SB',
|
||||||
|
enabled: true,
|
||||||
|
selling: true,
|
||||||
|
buying: true,
|
||||||
|
isUomDependent: false,
|
||||||
|
itemPrice: [
|
||||||
|
{
|
||||||
|
enabled: true,
|
||||||
|
item: itemMap.Pen.name,
|
||||||
|
rate: 104,
|
||||||
|
batch: batchMap.batchOne.name,
|
||||||
|
buying: true,
|
||||||
|
selling: true,
|
||||||
|
party: partyMap.partyOne.name,
|
||||||
|
validFrom: '2023-05-05T18:30:00.000Z',
|
||||||
|
validUpto: '2023-06-05T18:30:00.000Z',
|
||||||
|
...getDefaultMetaFieldValueMap(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
test('Price List: create dummy items, parties and batches', async (t) => {
|
||||||
|
// Create Items
|
||||||
|
for (const { name, rate } of Object.values(itemMap)) {
|
||||||
|
const item = getItem(name, rate, false);
|
||||||
|
await fyo.doc.getNewDoc(ModelNameEnum.Item, item).sync();
|
||||||
|
t.ok(await fyo.db.exists(ModelNameEnum.Item, name), `Item: ${name} exists`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create Parties
|
||||||
|
for (const { name, email } of Object.values(partyMap)) {
|
||||||
|
await fyo.doc.getNewDoc(ModelNameEnum.Party, { name, email }).sync();
|
||||||
|
t.ok(
|
||||||
|
await fyo.db.exists(ModelNameEnum.Party, name),
|
||||||
|
`Party: ${name} exists`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create Batches
|
||||||
|
for (const batch of Object.values(batchMap)) {
|
||||||
|
await fyo.doc.getNewDoc(ModelNameEnum.Batch, batch).sync();
|
||||||
|
t.ok(
|
||||||
|
await fyo.db.exists(ModelNameEnum.Batch, batch.name),
|
||||||
|
`Batch: ${batch.name} exists`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('create Price Lists', async (t) => {
|
||||||
|
for (const priceListItem of Object.values(priceListMap)) {
|
||||||
|
await fyo.doc.getNewDoc(ModelNameEnum.PriceList, priceListItem).sync();
|
||||||
|
t.ok(
|
||||||
|
await fyo.db.exists(ModelNameEnum.PriceList, priceListItem.name),
|
||||||
|
`Price List ${priceListItem.name} exists`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('check item price', async (t) => {
|
||||||
|
// check selling enabled item price
|
||||||
|
const sinv = fyo.doc.getNewDoc(ModelNameEnum.SalesInvoice, {
|
||||||
|
items: [{ item: itemMap.Pen.name, quantity: 1 }],
|
||||||
|
date: priceListMap.PL_SELL.itemPrice[0].validFrom,
|
||||||
|
priceList: priceListMap.PL_SELL.name,
|
||||||
|
party: partyMap.partyOne.name,
|
||||||
|
}) as SalesInvoice;
|
||||||
|
|
||||||
|
const sinvItem = Object.values(sinv.items ?? {})[0];
|
||||||
|
const sellEnabled = await getItemPrice(sinvItem as SalesInvoiceItem);
|
||||||
|
|
||||||
|
const sellEnabledPLName = await fyo.getValue(
|
||||||
|
ModelNameEnum.ItemPrice,
|
||||||
|
sellEnabled as string,
|
||||||
|
'parent'
|
||||||
|
);
|
||||||
|
|
||||||
|
t.equal(sellEnabledPLName, priceListMap.PL_SELL.name);
|
||||||
|
|
||||||
|
// check buying enabled item price
|
||||||
|
const pinv = fyo.doc.getNewDoc(ModelNameEnum.PurchaseInvoice, {
|
||||||
|
items: [{ item: itemMap.Pen.name, quantity: 1 }],
|
||||||
|
date: priceListMap.PL_BUY.itemPrice[0].validFrom,
|
||||||
|
priceList: priceListMap.PL_BUY.name,
|
||||||
|
party: partyMap.partyOne.name,
|
||||||
|
}) as SalesInvoice;
|
||||||
|
|
||||||
|
const pinvItem = Object.values(pinv.items ?? {})[0];
|
||||||
|
const buyEnabled = await getItemPrice(pinvItem as PurchaseInvoiceItem);
|
||||||
|
|
||||||
|
const buyEnabledPLName = await fyo.getValue(
|
||||||
|
ModelNameEnum.ItemPrice,
|
||||||
|
buyEnabled as string,
|
||||||
|
'parent'
|
||||||
|
);
|
||||||
|
|
||||||
|
t.equal(buyEnabledPLName, priceListMap.PL_BUY.name);
|
||||||
|
|
||||||
|
// check sell batch enabled
|
||||||
|
const sinv1 = fyo.doc.getNewDoc(ModelNameEnum.SalesInvoice, {
|
||||||
|
items: [
|
||||||
|
{ item: itemMap.Pen.name, quantity: 1, batch: batchMap.batchOne.name },
|
||||||
|
],
|
||||||
|
date: priceListMap.PL_SB.itemPrice[0].validFrom,
|
||||||
|
priceList: priceListMap.PL_SB.name,
|
||||||
|
party: partyMap.partyOne.name,
|
||||||
|
}) as SalesInvoice;
|
||||||
|
|
||||||
|
const sinv1Item = Object.values(sinv1.items ?? {})[0];
|
||||||
|
const sellBatchEnabled = await getItemPrice(sinv1Item as SalesInvoiceItem);
|
||||||
|
|
||||||
|
const sellBatchEnabledPLName = await fyo.getValue(
|
||||||
|
ModelNameEnum.ItemPrice,
|
||||||
|
sellBatchEnabled as string,
|
||||||
|
'parent'
|
||||||
|
);
|
||||||
|
|
||||||
|
t.equal(sellBatchEnabledPLName, priceListMap.PL_SB.name);
|
||||||
|
|
||||||
|
// undefined returns
|
||||||
|
const sinv2 = fyo.doc.getNewDoc(ModelNameEnum.SalesInvoice, {
|
||||||
|
items: [{ item: itemMap.Ink.name, quantity: 1 }],
|
||||||
|
date: priceListMap.PL_SELL.itemPrice[0].validFrom,
|
||||||
|
priceList: priceListMap.PL_SELL.name,
|
||||||
|
party: partyMap.partyOne.name,
|
||||||
|
}) as SalesInvoice;
|
||||||
|
|
||||||
|
const sinv2Item = Object.values(sinv2.items ?? {})[0];
|
||||||
|
const nonExistItem = await getItemPrice(sinv2Item as SalesInvoiceItem);
|
||||||
|
|
||||||
|
t.equal(
|
||||||
|
nonExistItem,
|
||||||
|
undefined,
|
||||||
|
'itemPrice of non-existing item in price list returns false'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
closeTestFyo(fyo, __filename);
|
@ -17,6 +17,8 @@ import { Invoice } from './baseModels/Invoice/Invoice';
|
|||||||
import { StockMovement } from './inventory/StockMovement';
|
import { StockMovement } from './inventory/StockMovement';
|
||||||
import { StockTransfer } from './inventory/StockTransfer';
|
import { StockTransfer } from './inventory/StockTransfer';
|
||||||
import { InvoiceStatus, ModelNameEnum } from './types';
|
import { InvoiceStatus, ModelNameEnum } from './types';
|
||||||
|
import { InvoiceItem } from './baseModels/InvoiceItem/InvoiceItem';
|
||||||
|
import { ItemPrice } from './baseModels/ItemPrice/ItemPrice';
|
||||||
|
|
||||||
export function getInvoiceActions(
|
export function getInvoiceActions(
|
||||||
fyo: Fyo,
|
fyo: Fyo,
|
||||||
@ -325,6 +327,122 @@ export function getSerialNumberStatusText(status: string): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getPriceListStatusColumn(): ColumnConfig {
|
||||||
|
return {
|
||||||
|
label: t`Enabled For`,
|
||||||
|
fieldname: 'enabledFor',
|
||||||
|
fieldtype: 'Select',
|
||||||
|
render(doc) {
|
||||||
|
let status = 'None';
|
||||||
|
|
||||||
|
if (doc.buying && !doc.selling) {
|
||||||
|
status = 'Buying';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doc.selling && !doc.buying) {
|
||||||
|
status = 'Selling';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doc.buying && doc.selling) {
|
||||||
|
status = 'Buying & Selling';
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
template: `<Badge class="text-xs" color="gray">${status}</Badge>`,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getItemPrice(
|
||||||
|
doc: InvoiceItem | ItemPrice,
|
||||||
|
validFrom?: Date,
|
||||||
|
validUpto?: Date
|
||||||
|
): Promise<string | undefined> {
|
||||||
|
if (!doc.item || !doc.priceList) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isUomDependent = await doc.fyo.getValue(
|
||||||
|
ModelNameEnum.PriceList,
|
||||||
|
doc.priceList,
|
||||||
|
'isUomDependent'
|
||||||
|
);
|
||||||
|
|
||||||
|
const itemPriceQuery = Object.values(
|
||||||
|
await doc.fyo.db.getAll(ModelNameEnum.ItemPrice, {
|
||||||
|
filters: {
|
||||||
|
enabled: true,
|
||||||
|
item: doc.item,
|
||||||
|
...(doc.isSales ? { selling: true } : { buying: true }),
|
||||||
|
...(doc.batch ? { batch: doc.batch as string } : { batch: null }),
|
||||||
|
},
|
||||||
|
fields: ['name', 'unit', 'party', 'batch', 'validFrom', 'validUpto'],
|
||||||
|
})
|
||||||
|
)[0];
|
||||||
|
|
||||||
|
if (!itemPriceQuery) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { name, unit, party } = itemPriceQuery;
|
||||||
|
const validFromDate = validFrom ?? itemPriceQuery.validFrom;
|
||||||
|
const validUptoDate = validFrom ?? itemPriceQuery.validUpto;
|
||||||
|
let date;
|
||||||
|
|
||||||
|
if (doc.date) {
|
||||||
|
date = new Date((doc.date as Date).setHours(0, 0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isUomDependent && unit !== doc.unit) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (party && doc.party !== party) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (date instanceof Date) {
|
||||||
|
if (validFromDate && date < validFromDate) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validUptoDate && date > validUptoDate) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validFrom && validUpto) {
|
||||||
|
if (validFromDate && validFrom < validFromDate) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validUptoDate && validFrom > validUptoDate) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return name as string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getPriceListRate(
|
||||||
|
doc: InvoiceItem
|
||||||
|
): Promise<Money | undefined> {
|
||||||
|
const itemPrice = await getItemPrice(doc);
|
||||||
|
|
||||||
|
if (!itemPrice) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const itemPriceRate = (await doc.fyo.getValue(
|
||||||
|
ModelNameEnum.ItemPrice,
|
||||||
|
itemPrice,
|
||||||
|
'rate'
|
||||||
|
)) as Money;
|
||||||
|
|
||||||
|
return itemPriceRate;
|
||||||
|
}
|
||||||
|
|
||||||
export async function getExchangeRate({
|
export async function getExchangeRate({
|
||||||
fromCurrency,
|
fromCurrency,
|
||||||
toCurrency,
|
toCurrency,
|
||||||
|
@ -10,6 +10,8 @@ import { JournalEntryAccount } from './baseModels/JournalEntryAccount/JournalEnt
|
|||||||
import { Party } from './baseModels/Party/Party';
|
import { Party } from './baseModels/Party/Party';
|
||||||
import { Payment } from './baseModels/Payment/Payment';
|
import { Payment } from './baseModels/Payment/Payment';
|
||||||
import { PaymentFor } from './baseModels/PaymentFor/PaymentFor';
|
import { PaymentFor } from './baseModels/PaymentFor/PaymentFor';
|
||||||
|
import { PriceList } from './baseModels/PriceList/PriceList';
|
||||||
|
import { ItemPrice } from './baseModels/ItemPrice/ItemPrice';
|
||||||
import { PrintSettings } from './baseModels/PrintSettings/PrintSettings';
|
import { PrintSettings } from './baseModels/PrintSettings/PrintSettings';
|
||||||
import { PurchaseInvoice } from './baseModels/PurchaseInvoice/PurchaseInvoice';
|
import { PurchaseInvoice } from './baseModels/PurchaseInvoice/PurchaseInvoice';
|
||||||
import { PurchaseInvoiceItem } from './baseModels/PurchaseInvoiceItem/PurchaseInvoiceItem';
|
import { PurchaseInvoiceItem } from './baseModels/PurchaseInvoiceItem/PurchaseInvoiceItem';
|
||||||
@ -45,6 +47,8 @@ export const models = {
|
|||||||
Payment,
|
Payment,
|
||||||
PaymentFor,
|
PaymentFor,
|
||||||
PrintSettings,
|
PrintSettings,
|
||||||
|
PriceList,
|
||||||
|
ItemPrice,
|
||||||
PurchaseInvoice,
|
PurchaseInvoice,
|
||||||
PurchaseInvoiceItem,
|
PurchaseInvoiceItem,
|
||||||
SalesInvoice,
|
SalesInvoice,
|
||||||
|
@ -10,6 +10,7 @@ export enum ModelNameEnum {
|
|||||||
GetStarted = 'GetStarted',
|
GetStarted = 'GetStarted',
|
||||||
Defaults = 'Defaults',
|
Defaults = 'Defaults',
|
||||||
Item = 'Item',
|
Item = 'Item',
|
||||||
|
ItemPrice = 'ItemPrice',
|
||||||
UOM = 'UOM',
|
UOM = 'UOM',
|
||||||
UOMConversionItem = 'UOMConversionItem',
|
UOMConversionItem = 'UOMConversionItem',
|
||||||
JournalEntry = 'JournalEntry',
|
JournalEntry = 'JournalEntry',
|
||||||
@ -19,6 +20,7 @@ export enum ModelNameEnum {
|
|||||||
Party = 'Party',
|
Party = 'Party',
|
||||||
Payment = 'Payment',
|
Payment = 'Payment',
|
||||||
PaymentFor = 'PaymentFor',
|
PaymentFor = 'PaymentFor',
|
||||||
|
PriceList = 'PriceList',
|
||||||
PrintSettings = 'PrintSettings',
|
PrintSettings = 'PrintSettings',
|
||||||
PrintTemplate = 'PrintTemplate',
|
PrintTemplate = 'PrintTemplate',
|
||||||
PurchaseInvoice = 'PurchaseInvoice',
|
PurchaseInvoice = 'PurchaseInvoice',
|
||||||
|
@ -79,6 +79,13 @@
|
|||||||
"default": false,
|
"default": false,
|
||||||
"section": "Features"
|
"section": "Features"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "enablePriceList",
|
||||||
|
"label": "Enable Price List",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"default": false,
|
||||||
|
"section": "Features"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "fiscalYearStart",
|
"fieldname": "fiscalYearStart",
|
||||||
"label": "Fiscal Year Start Date",
|
"label": "Fiscal Year Start Date",
|
||||||
|
@ -43,6 +43,13 @@
|
|||||||
"required": true,
|
"required": true,
|
||||||
"section": "Default"
|
"section": "Default"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "priceList",
|
||||||
|
"label": "Price List",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"target": "PriceList",
|
||||||
|
"section": "Default"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"abstract": true,
|
"abstract": true,
|
||||||
"fieldname": "items",
|
"fieldname": "items",
|
||||||
|
98
schemas/app/ItemPrice.json
Normal file
98
schemas/app/ItemPrice.json
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
{
|
||||||
|
"name": "ItemPrice",
|
||||||
|
"label": "Item Price",
|
||||||
|
"isChild": true,
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "enabled",
|
||||||
|
"label": "Enabled",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"default": true,
|
||||||
|
"section": "Price List"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "buying",
|
||||||
|
"label": "Buying",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"placeholder": "Buying",
|
||||||
|
"default": false,
|
||||||
|
"section": "Price List"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "selling",
|
||||||
|
"label": "Selling",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"placeholder": "Selling",
|
||||||
|
"default": false,
|
||||||
|
"section": "Price List"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "party",
|
||||||
|
"label": "Party",
|
||||||
|
"placeholder": "Party",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"target": "Party",
|
||||||
|
"create": true,
|
||||||
|
"section": "Price List"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "item",
|
||||||
|
"label": "Item",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"target": "Item",
|
||||||
|
"required": true,
|
||||||
|
"create": true,
|
||||||
|
"section": "Item"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "unit",
|
||||||
|
"label": "Unit Type",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"target": "UOM",
|
||||||
|
"default": "Unit",
|
||||||
|
"section": "Item"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "rate",
|
||||||
|
"label": "Rate",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"required": true,
|
||||||
|
"section": "Item"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "batch",
|
||||||
|
"label": "Batch",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"target": "Batch",
|
||||||
|
"create": true,
|
||||||
|
"section": "Item"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "validFrom",
|
||||||
|
"label": "Valid From",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"placeholder": "Valid From",
|
||||||
|
"section": "Validity"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "validUpto",
|
||||||
|
"label": "Valid Upto",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"placeholder": "Valid Upto",
|
||||||
|
"section": "Validity"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tableFields": ["item", "rate", "enabled"],
|
||||||
|
"quickEditFields": [
|
||||||
|
"enabled",
|
||||||
|
"buying",
|
||||||
|
"selling",
|
||||||
|
"party",
|
||||||
|
"item",
|
||||||
|
"unit",
|
||||||
|
"rate",
|
||||||
|
"batch",
|
||||||
|
"validFrom",
|
||||||
|
"validUpto"
|
||||||
|
]
|
||||||
|
}
|
48
schemas/app/PriceList.json
Normal file
48
schemas/app/PriceList.json
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
{
|
||||||
|
"name": "PriceList",
|
||||||
|
"label": "Price List",
|
||||||
|
"naming": "manual",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "name",
|
||||||
|
"label": "Name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "enabled",
|
||||||
|
"label": "Enabled",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "buying",
|
||||||
|
"label": "Buying",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"default": false,
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "selling",
|
||||||
|
"label": "Selling",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"default": false,
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "isUomDependent",
|
||||||
|
"label": "Is Price UOM Dependent",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "itemPrice",
|
||||||
|
"label": "Item Prices",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"target": "ItemPrice",
|
||||||
|
"edit": true,
|
||||||
|
"required": true,
|
||||||
|
"section": "Item Prices"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -9,6 +9,8 @@ import Defaults from './app/Defaults.json';
|
|||||||
import GetStarted from './app/GetStarted.json';
|
import GetStarted from './app/GetStarted.json';
|
||||||
import InventorySettings from './app/inventory/InventorySettings.json';
|
import InventorySettings from './app/inventory/InventorySettings.json';
|
||||||
import Location from './app/inventory/Location.json';
|
import Location from './app/inventory/Location.json';
|
||||||
|
import PriceList from './app/PriceList.json';
|
||||||
|
import ItemPrice from './app/ItemPrice.json';
|
||||||
import PurchaseReceipt from './app/inventory/PurchaseReceipt.json';
|
import PurchaseReceipt from './app/inventory/PurchaseReceipt.json';
|
||||||
import PurchaseReceiptItem from './app/inventory/PurchaseReceiptItem.json';
|
import PurchaseReceiptItem from './app/inventory/PurchaseReceiptItem.json';
|
||||||
import SerialNumber from './app/inventory/SerialNumber.json';
|
import SerialNumber from './app/inventory/SerialNumber.json';
|
||||||
@ -100,6 +102,9 @@ export const appSchemas: Schema[] | SchemaStub[] = [
|
|||||||
SalesInvoiceItem as SchemaStub,
|
SalesInvoiceItem as SchemaStub,
|
||||||
PurchaseInvoiceItem as SchemaStub,
|
PurchaseInvoiceItem as SchemaStub,
|
||||||
|
|
||||||
|
PriceList as Schema,
|
||||||
|
ItemPrice as SchemaStub,
|
||||||
|
|
||||||
Tax as Schema,
|
Tax as Schema,
|
||||||
TaxDetail as Schema,
|
TaxDetail as Schema,
|
||||||
TaxSummary as Schema,
|
TaxSummary as Schema,
|
||||||
|
@ -245,6 +245,13 @@ async function getCompleteSidebar(): Promise<SidebarConfig> {
|
|||||||
schemaName: 'Item',
|
schemaName: 'Item',
|
||||||
filters: { for: 'Both' },
|
filters: { for: 'Both' },
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: t`Price List`,
|
||||||
|
name: 'price-list',
|
||||||
|
route: '/list/PriceList',
|
||||||
|
schemaName: 'PriceList',
|
||||||
|
hidden: () => !fyo.singles.AccountingSettings?.enablePriceList,
|
||||||
|
},
|
||||||
] as SidebarItem[],
|
] as SidebarItem[],
|
||||||
},
|
},
|
||||||
await getReportSidebar(),
|
await getReportSidebar(),
|
||||||
|
Loading…
Reference in New Issue
Block a user