2
0
mirror of https://github.com/frappe/books.git synced 2024-11-09 15:20:56 +00:00

feat: price list

This commit is contained in:
akshayitzme 2023-06-06 14:29:08 +05:30
parent 2a82b021fb
commit 3862fe1006
10 changed files with 241 additions and 1 deletions

View File

@ -14,6 +14,7 @@ import { getCountryInfo } from 'utils/misc';
export class AccountingSettings extends Doc {
enableDiscounting?: boolean;
enableInventory?: boolean;
enablePriceList?: boolean;
static filters: FiltersMap = {
writeOffAccount: () => ({

View File

@ -35,6 +35,7 @@ export abstract class Invoice extends Transactional {
party?: string;
account?: string;
currency?: string;
priceList?: string;
netTotal?: Money;
grandTotal?: Money;
baseGrandTotal?: Money;
@ -514,6 +515,7 @@ export abstract class Invoice extends Transactional {
attachment: () =>
!(this.attachment || !(this.isSubmitted || this.isCancelled)),
backReference: () => !this.backReference,
priceList: () => !this.fyo.singles.AccountingSettings?.enablePriceList,
};
static defaults: DefaultMap = {
@ -544,6 +546,10 @@ export abstract class Invoice extends Transactional {
accountType: doc.isSales ? 'Receivable' : 'Payable',
}),
numberSeries: (doc: Doc) => ({ referenceType: doc.schemaName }),
priceList: (doc: Doc) => ({
enabled: true,
...(doc.isSales ? { selling: true } : { buying: true }),
}),
};
static createFilters: FiltersMap = {

View File

@ -17,6 +17,7 @@ import { safeParseFloat } from 'utils/index';
import { Invoice } from '../Invoice/Invoice';
import { Item } from '../Item/Item';
import { StockTransfer } from 'models/inventory/StockTransfer';
import { getPriceListRate } from 'models/helpers';
export abstract class InvoiceItem extends Doc {
item?: string;
@ -48,6 +49,18 @@ export abstract class InvoiceItem extends Doc {
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() {
return !!this?.parentdoc?.discountAfterTax;
}
@ -101,12 +114,15 @@ export abstract class InvoiceItem extends Doc {
},
rate: {
formula: async (fieldname) => {
const rate = (await this.fyo.getValue(
const priceListRate = await getPriceListRate(this);
const itemRate = (await this.fyo.getValue(
'Item',
this.item as string,
'rate'
)) as undefined | Money;
const rate = priceListRate instanceof Money ? priceListRate : itemRate;
if (!rate?.float && this.rate?.float) {
return this.rate;
}
@ -144,6 +160,9 @@ export abstract class InvoiceItem extends Doc {
return rateFromTotals ?? rate ?? this.fyo.pesa(0);
},
dependsOn: [
'date',
'priceList',
'batch',
'party',
'exchangeRate',
'item',

View 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}`
);
},
};
}

View 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()],
};
}
}

View File

@ -17,6 +17,8 @@ import { Invoice } from './baseModels/Invoice/Invoice';
import { StockMovement } from './inventory/StockMovement';
import { StockTransfer } from './inventory/StockTransfer';
import { InvoiceStatus, ModelNameEnum } from './types';
import { InvoiceItem } from './baseModels/InvoiceItem/InvoiceItem';
import { ItemPrice } from './baseModels/ItemPrice/ItemPrice';
export function getInvoiceActions(
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({
fromCurrency,
toCurrency,

View File

@ -10,6 +10,8 @@ import { JournalEntryAccount } from './baseModels/JournalEntryAccount/JournalEnt
import { Party } from './baseModels/Party/Party';
import { Payment } from './baseModels/Payment/Payment';
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 { PurchaseInvoice } from './baseModels/PurchaseInvoice/PurchaseInvoice';
import { PurchaseInvoiceItem } from './baseModels/PurchaseInvoiceItem/PurchaseInvoiceItem';
@ -45,6 +47,8 @@ export const models = {
Payment,
PaymentFor,
PrintSettings,
PriceList,
ItemPrice,
PurchaseInvoice,
PurchaseInvoiceItem,
SalesInvoice,

View File

@ -10,6 +10,7 @@ export enum ModelNameEnum {
GetStarted = 'GetStarted',
Defaults = 'Defaults',
Item = 'Item',
ItemPrice = 'ItemPrice',
UOM = 'UOM',
UOMConversionItem = 'UOMConversionItem',
JournalEntry = 'JournalEntry',
@ -19,6 +20,7 @@ export enum ModelNameEnum {
Party = 'Party',
Payment = 'Payment',
PaymentFor = 'PaymentFor',
PriceList = 'PriceList',
PrintSettings = 'PrintSettings',
PrintTemplate = 'PrintTemplate',
PurchaseInvoice = 'PurchaseInvoice',

View File

@ -9,6 +9,8 @@ import Defaults from './app/Defaults.json';
import GetStarted from './app/GetStarted.json';
import InventorySettings from './app/inventory/InventorySettings.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 PurchaseReceiptItem from './app/inventory/PurchaseReceiptItem.json';
import SerialNumber from './app/inventory/SerialNumber.json';
@ -100,6 +102,9 @@ export const appSchemas: Schema[] | SchemaStub[] = [
SalesInvoiceItem as SchemaStub,
PurchaseInvoiceItem as SchemaStub,
PriceList as Schema,
ItemPrice as SchemaStub,
Tax as Schema,
TaxDetail as Schema,
TaxSummary as Schema,

View File

@ -245,6 +245,13 @@ async function getCompleteSidebar(): Promise<SidebarConfig> {
schemaName: 'Item',
filters: { for: 'Both' },
},
{
label: t`Price List`,
name: 'price-list',
route: '/list/PriceList',
schemaName: 'PriceList',
hidden: () => !fyo.singles.AccountingSettings?.enablePriceList,
},
] as SidebarItem[],
},
await getReportSidebar(),