mirror of
https://github.com/frappe/books.git
synced 2024-11-08 14:50:56 +00:00
commit
7205f5aa42
@ -117,3 +117,13 @@ export type DocStatus =
|
||||
| 'NotSaved'
|
||||
| 'Submitted'
|
||||
| 'Cancelled';
|
||||
|
||||
export type LeadStatus =
|
||||
| ''
|
||||
| 'Open'
|
||||
| 'Replied'
|
||||
| 'Interested'
|
||||
| 'Opportunity'
|
||||
| 'Converted'
|
||||
| 'Quotation'
|
||||
| 'DonotContact'
|
@ -15,6 +15,7 @@ export class AccountingSettings extends Doc {
|
||||
enableDiscounting?: boolean;
|
||||
enableInventory?: boolean;
|
||||
enablePriceList?: boolean;
|
||||
enableLead?: boolean;
|
||||
enableFormCustomization?: boolean;
|
||||
enableInvoiceReturns?: boolean;
|
||||
|
||||
@ -48,6 +49,9 @@ export class AccountingSettings extends Doc {
|
||||
enableInventory: () => {
|
||||
return !!this.enableInventory;
|
||||
},
|
||||
enableLead: () => {
|
||||
return !!this.enableLead;
|
||||
},
|
||||
enableInvoiceReturns: () => {
|
||||
return !!this.enableInvoiceReturns;
|
||||
},
|
||||
|
@ -165,6 +165,10 @@ export abstract class Invoice extends Transactional {
|
||||
async afterSubmit() {
|
||||
await super.afterSubmit();
|
||||
|
||||
if (this.schemaName === ModelNameEnum.SalesQuote) {
|
||||
return;
|
||||
}
|
||||
|
||||
// update outstanding amounts
|
||||
await this.fyo.db.update(this.schemaName, {
|
||||
name: this.name as string,
|
||||
|
51
models/baseModels/Lead/Lead.ts
Normal file
51
models/baseModels/Lead/Lead.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { Fyo } from 'fyo';
|
||||
import { Doc } from 'fyo/model/doc';
|
||||
import {
|
||||
Action,
|
||||
LeadStatus,
|
||||
ListViewSettings,
|
||||
ValidationMap,
|
||||
} from 'fyo/model/types';
|
||||
import { getLeadActions, getLeadStatusColumn } from 'models/helpers';
|
||||
import {
|
||||
validateEmail,
|
||||
validatePhoneNumber,
|
||||
} from 'fyo/model/validationFunction';
|
||||
import { ModelNameEnum } from 'models/types';
|
||||
|
||||
export class Lead extends Doc {
|
||||
status?: LeadStatus;
|
||||
|
||||
validations: ValidationMap = {
|
||||
email: validateEmail,
|
||||
mobile: validatePhoneNumber,
|
||||
};
|
||||
|
||||
createCustomer() {
|
||||
return this.fyo.doc.getNewDoc(ModelNameEnum.Party, {
|
||||
...this.getValidDict(),
|
||||
fromLead: this.name,
|
||||
phone: this.mobile as string,
|
||||
role: 'Customer',
|
||||
});
|
||||
}
|
||||
|
||||
createSalesQuote() {
|
||||
const data: { party: string | undefined; referenceType: string } = {
|
||||
party: this.name,
|
||||
referenceType: ModelNameEnum.Lead,
|
||||
};
|
||||
|
||||
return this.fyo.doc.getNewDoc(ModelNameEnum.SalesQuote, data);
|
||||
}
|
||||
|
||||
static getActions(fyo: Fyo): Action[] {
|
||||
return getLeadActions(fyo);
|
||||
}
|
||||
|
||||
static getListViewSettings(): ListViewSettings {
|
||||
return {
|
||||
columns: ['name', getLeadStatusColumn(), 'email', 'mobile'],
|
||||
};
|
||||
}
|
||||
}
|
@ -13,9 +13,12 @@ import {
|
||||
} from 'fyo/model/validationFunction';
|
||||
import { Money } from 'pesa';
|
||||
import { PartyRole } from './types';
|
||||
import { ModelNameEnum } from 'models/types';
|
||||
|
||||
export class Party extends Doc {
|
||||
role?: PartyRole;
|
||||
party?: string;
|
||||
fromLead?: string;
|
||||
defaultAccount?: string;
|
||||
outstandingAmount?: Money;
|
||||
async updateOutstandingAmount() {
|
||||
@ -125,6 +128,25 @@ export class Party extends Doc {
|
||||
};
|
||||
}
|
||||
|
||||
async afterDelete() {
|
||||
await super.afterDelete();
|
||||
if (!this.fromLead) {
|
||||
return;
|
||||
}
|
||||
const leadData = await this.fyo.doc.getDoc(ModelNameEnum.Lead, this.name);
|
||||
await leadData.setAndSync('status', 'Interested');
|
||||
}
|
||||
|
||||
async afterSync() {
|
||||
await super.afterSync();
|
||||
if (!this.fromLead) {
|
||||
return;
|
||||
}
|
||||
|
||||
const leadData = await this.fyo.doc.getDoc(ModelNameEnum.Lead, this.name);
|
||||
await leadData.setAndSync('status', 'Converted');
|
||||
}
|
||||
|
||||
static getActions(fyo: Fyo): Action[] {
|
||||
return [
|
||||
{
|
||||
|
@ -1,14 +1,22 @@
|
||||
import { Fyo } from 'fyo';
|
||||
import { DocValueMap } from 'fyo/core/types';
|
||||
import { Action, ListViewSettings } from 'fyo/model/types';
|
||||
import { Action, FiltersMap, ListViewSettings } from 'fyo/model/types';
|
||||
import { ModelNameEnum } from 'models/types';
|
||||
import { getQuoteActions, getTransactionStatusColumn } from '../../helpers';
|
||||
import { Invoice } from '../Invoice/Invoice';
|
||||
import { SalesQuoteItem } from '../SalesQuoteItem/SalesQuoteItem';
|
||||
import { Defaults } from '../Defaults/Defaults';
|
||||
import { Doc } from 'fyo/model/doc';
|
||||
import { Party } from '../Party/Party';
|
||||
|
||||
export class SalesQuote extends Invoice {
|
||||
items?: SalesQuoteItem[];
|
||||
party?: string;
|
||||
name?: string;
|
||||
referenceType?:
|
||||
| ModelNameEnum.SalesInvoice
|
||||
| ModelNameEnum.PurchaseInvoice
|
||||
| ModelNameEnum.Lead;
|
||||
|
||||
// This is an inherited method and it must keep the async from the parent
|
||||
// class
|
||||
@ -49,6 +57,20 @@ export class SalesQuote extends Invoice {
|
||||
return invoice;
|
||||
}
|
||||
|
||||
static filters: FiltersMap = {
|
||||
numberSeries: (doc: Doc) => ({ referenceType: doc.schemaName }),
|
||||
};
|
||||
|
||||
async afterSubmit(): Promise<void> {
|
||||
await super.afterSubmit();
|
||||
|
||||
if (this.referenceType == ModelNameEnum.Lead) {
|
||||
const partyDoc = (await this.loadAndGetLink('party')) as Party;
|
||||
|
||||
await partyDoc.setAndSync('status', 'Quotation');
|
||||
}
|
||||
}
|
||||
|
||||
static getListViewSettings(): ListViewSettings {
|
||||
return {
|
||||
columns: [
|
||||
|
121
models/baseModels/tests/testLead.spec.ts
Normal file
121
models/baseModels/tests/testLead.spec.ts
Normal file
@ -0,0 +1,121 @@
|
||||
import test from 'tape';
|
||||
import { closeTestFyo, getTestFyo, setupTestFyo } from 'tests/helpers';
|
||||
import { ModelNameEnum } from 'models/types';
|
||||
import { Lead } from '../Lead/Lead';
|
||||
import { Party } from '../Party/Party';
|
||||
|
||||
const fyo = getTestFyo();
|
||||
setupTestFyo(fyo, __filename);
|
||||
|
||||
const leadData = {
|
||||
name: 'name2',
|
||||
status: 'Open',
|
||||
email: 'sample@gmail.com',
|
||||
mobile: '1234567890',
|
||||
};
|
||||
|
||||
const itemData: { name: string; rate: number } = {
|
||||
name: 'Pen',
|
||||
rate: 100,
|
||||
};
|
||||
|
||||
test('create test docs for Lead', async (t) => {
|
||||
await fyo.doc.getNewDoc(ModelNameEnum.Item, itemData).sync();
|
||||
|
||||
t.ok(
|
||||
fyo.db.exists(ModelNameEnum.Item, itemData.name),
|
||||
`dummy item ${itemData.name} exists`
|
||||
);
|
||||
});
|
||||
|
||||
test('create a Lead doc', async (t) => {
|
||||
await fyo.doc.getNewDoc(ModelNameEnum.Lead, leadData).sync();
|
||||
|
||||
t.ok(
|
||||
fyo.db.exists(ModelNameEnum.Lead, leadData.name),
|
||||
`${leadData.name} exists`
|
||||
);
|
||||
});
|
||||
|
||||
test('create Customer from Lead', async (t) => {
|
||||
const leadDoc = (await fyo.doc.getDoc(ModelNameEnum.Lead, 'name2')) as Lead;
|
||||
|
||||
const newCustomer = leadDoc.createCustomer();
|
||||
|
||||
t.equals(
|
||||
leadDoc.status,
|
||||
'Open',
|
||||
'status must be Open before Customer is created'
|
||||
);
|
||||
|
||||
await newCustomer.sync();
|
||||
|
||||
t.equals(
|
||||
leadDoc.status,
|
||||
'Converted',
|
||||
'status should change to Converted after Customer is created'
|
||||
);
|
||||
|
||||
t.ok(
|
||||
await fyo.db.exists(ModelNameEnum.Party, newCustomer.name),
|
||||
'Customer created from Lead'
|
||||
);
|
||||
});
|
||||
|
||||
test('create SalesQuote', async (t) => {
|
||||
const leadDoc = (await fyo.doc.getDoc(ModelNameEnum.Lead, 'name2')) as Lead;
|
||||
|
||||
const newSalesQuote = leadDoc.createSalesQuote();
|
||||
|
||||
newSalesQuote.items = [];
|
||||
newSalesQuote.append('items', {
|
||||
item: itemData.name,
|
||||
quantity: 1,
|
||||
rate: itemData.rate,
|
||||
});
|
||||
|
||||
t.equals(
|
||||
leadDoc.status,
|
||||
'Converted',
|
||||
'status must be Open before SQUOT is created'
|
||||
);
|
||||
|
||||
await newSalesQuote.sync();
|
||||
await newSalesQuote.submit();
|
||||
|
||||
t.equals(
|
||||
leadDoc.status,
|
||||
'Quotation',
|
||||
'status should change to Quotation after SQUOT submission'
|
||||
);
|
||||
|
||||
t.ok(
|
||||
await fyo.db.exists(ModelNameEnum.SalesQuote, newSalesQuote.name),
|
||||
'SalesQuote Created from Lead'
|
||||
);
|
||||
});
|
||||
|
||||
test('delete Customer then lead status changes to Interested', async (t) => {
|
||||
const partyDoc = (await fyo.doc.getDoc(
|
||||
ModelNameEnum.Party,
|
||||
'name2'
|
||||
)) as Party;
|
||||
|
||||
await partyDoc.delete();
|
||||
|
||||
t.equals(
|
||||
await fyo.db.exists(ModelNameEnum.Party, 'name2'),
|
||||
false,
|
||||
'Customer deleted'
|
||||
);
|
||||
|
||||
const leadDoc = (await fyo.doc.getDoc(ModelNameEnum.Lead, 'name2')) as Lead;
|
||||
|
||||
t.equals(
|
||||
leadDoc.status,
|
||||
'Interested',
|
||||
'status should change to Interested after Customer is deleted'
|
||||
);
|
||||
});
|
||||
|
||||
closeTestFyo(fyo, __filename);
|
@ -1,6 +1,12 @@
|
||||
import { Fyo, t } from 'fyo';
|
||||
import { Doc } from 'fyo/model/doc';
|
||||
import { Action, ColumnConfig, DocStatus, RenderData } from 'fyo/model/types';
|
||||
import {
|
||||
Action,
|
||||
ColumnConfig,
|
||||
DocStatus,
|
||||
LeadStatus,
|
||||
RenderData,
|
||||
} from 'fyo/model/types';
|
||||
import { DateTime } from 'luxon';
|
||||
import { Money } from 'pesa';
|
||||
import { safeParseFloat } from 'utils/index';
|
||||
@ -15,6 +21,7 @@ import { SalesQuote } from './baseModels/SalesQuote/SalesQuote';
|
||||
import { StockMovement } from './inventory/StockMovement';
|
||||
import { StockTransfer } from './inventory/StockTransfer';
|
||||
import { InvoiceStatus, ModelNameEnum } from './types';
|
||||
import { Lead } from './baseModels/Lead/Lead';
|
||||
|
||||
export function getQuoteActions(
|
||||
fyo: Fyo,
|
||||
@ -23,6 +30,10 @@ export function getQuoteActions(
|
||||
return [getMakeInvoiceAction(fyo, schemaName)];
|
||||
}
|
||||
|
||||
export function getLeadActions(fyo: Fyo): Action[] {
|
||||
return [getCreateCustomerAction(fyo), getSalesQuoteAction(fyo)];
|
||||
}
|
||||
|
||||
export function getInvoiceActions(
|
||||
fyo: Fyo,
|
||||
schemaName: ModelNameEnum.SalesInvoice | ModelNameEnum.PurchaseInvoice
|
||||
@ -108,6 +119,35 @@ export function getMakeInvoiceAction(
|
||||
};
|
||||
}
|
||||
|
||||
export function getCreateCustomerAction(fyo: Fyo): Action {
|
||||
return {
|
||||
group: fyo.t`Create`,
|
||||
label: fyo.t`Customer`,
|
||||
action: async (doc: Doc, router) => {
|
||||
const customerData = (doc as Lead).createCustomer();
|
||||
|
||||
if (!customerData.name) {
|
||||
return;
|
||||
}
|
||||
await router.push(`/edit/Party/${customerData.name}`);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function getSalesQuoteAction(fyo: Fyo): Action {
|
||||
return {
|
||||
group: fyo.t`Create`,
|
||||
label: fyo.t`Sales Quote`,
|
||||
action: async (doc, router) => {
|
||||
const salesQuoteData = (doc as Lead).createSalesQuote();
|
||||
if (!salesQuoteData.name) {
|
||||
return;
|
||||
}
|
||||
await router.push(`/edit/SalesQuote/${salesQuoteData.name}`);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function getMakePaymentAction(fyo: Fyo): Action {
|
||||
return {
|
||||
label: fyo.t`Payment`,
|
||||
@ -230,18 +270,42 @@ export function getTransactionStatusColumn(): ColumnConfig {
|
||||
};
|
||||
}
|
||||
|
||||
export function getLeadStatusColumn(): ColumnConfig {
|
||||
return {
|
||||
label: t`Status`,
|
||||
fieldname: 'status',
|
||||
fieldtype: 'Select',
|
||||
render(doc) {
|
||||
const status = getLeadStatus(doc) as LeadStatus;
|
||||
const color = statusColor[status] ?? 'gray';
|
||||
const label = getStatusTextOfLead(status);
|
||||
|
||||
return {
|
||||
template: `<Badge class="text-xs" color="${color}">${label}</Badge>`,
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const statusColor: Record<
|
||||
DocStatus | InvoiceStatus,
|
||||
DocStatus | InvoiceStatus | LeadStatus,
|
||||
string | undefined
|
||||
> = {
|
||||
'': 'gray',
|
||||
Draft: 'gray',
|
||||
Open: 'gray',
|
||||
Replied: 'yellow',
|
||||
Opportunity: 'yellow',
|
||||
Unpaid: 'orange',
|
||||
Paid: 'green',
|
||||
Interested: 'yellow',
|
||||
Converted: 'green',
|
||||
Quotation: 'green',
|
||||
Saved: 'gray',
|
||||
NotSaved: 'gray',
|
||||
Submitted: 'green',
|
||||
Cancelled: 'red',
|
||||
DonotContact: 'red',
|
||||
Return: 'green',
|
||||
ReturnIssued: 'green',
|
||||
};
|
||||
@ -271,6 +335,37 @@ export function getStatusText(status: DocStatus | InvoiceStatus): string {
|
||||
}
|
||||
}
|
||||
|
||||
export function getStatusTextOfLead(status: LeadStatus): string {
|
||||
switch (status) {
|
||||
case 'Open':
|
||||
return t`Open`;
|
||||
case 'Replied':
|
||||
return t`Replied`;
|
||||
case 'Opportunity':
|
||||
return t`Opportunity`;
|
||||
case 'Interested':
|
||||
return t`Interested`;
|
||||
case 'Converted':
|
||||
return t`Converted`;
|
||||
case 'Quotation':
|
||||
return t`Quotation`;
|
||||
case 'DonotContact':
|
||||
return t`Do not Contact`;
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
export function getLeadStatus(
|
||||
doc?: Lead | Doc | RenderData
|
||||
): LeadStatus | DocStatus {
|
||||
if (!doc) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return doc.status as LeadStatus;
|
||||
}
|
||||
|
||||
export function getDocStatus(
|
||||
doc?: RenderData | Doc
|
||||
): DocStatus | InvoiceStatus {
|
||||
|
@ -9,6 +9,7 @@ import { JournalEntry } from './baseModels/JournalEntry/JournalEntry';
|
||||
import { JournalEntryAccount } from './baseModels/JournalEntryAccount/JournalEntryAccount';
|
||||
import { Misc } from './baseModels/Misc';
|
||||
import { Party } from './baseModels/Party/Party';
|
||||
import { Lead } from './baseModels/Lead/Lead';
|
||||
import { Payment } from './baseModels/Payment/Payment';
|
||||
import { PaymentFor } from './baseModels/PaymentFor/PaymentFor';
|
||||
import { PriceList } from './baseModels/PriceList/PriceList';
|
||||
@ -53,6 +54,7 @@ export const models = {
|
||||
JournalEntry,
|
||||
JournalEntryAccount,
|
||||
Misc,
|
||||
Lead,
|
||||
Party,
|
||||
Payment,
|
||||
PaymentFor,
|
||||
|
@ -4,6 +4,7 @@ import { GSTType } from './types';
|
||||
|
||||
export class Party extends BaseParty {
|
||||
gstin?: string;
|
||||
fromLead?: string;
|
||||
gstType?: GSTType;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/require-await
|
||||
@ -18,5 +19,6 @@ export class Party extends BaseParty {
|
||||
|
||||
hidden: HiddenMap = {
|
||||
gstin: () => (this.gstType as GSTType) !== 'Registered Regular',
|
||||
fromLead: () => !this.fromLead,
|
||||
};
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ export enum ModelNameEnum {
|
||||
JournalEntryAccount = 'JournalEntryAccount',
|
||||
Misc = 'Misc',
|
||||
NumberSeries = 'NumberSeries',
|
||||
Lead = 'Lead',
|
||||
Party = 'Party',
|
||||
Payment = 'Payment',
|
||||
PaymentFor = 'PaymentFor',
|
||||
|
@ -100,6 +100,13 @@
|
||||
"default": false,
|
||||
"section": "Features"
|
||||
},
|
||||
{
|
||||
"fieldname": "enableLead",
|
||||
"label": "Enable Lead",
|
||||
"fieldtype": "Check",
|
||||
"default": false,
|
||||
"section": "Features"
|
||||
},
|
||||
{
|
||||
"fieldname": "fiscalYearStart",
|
||||
"label": "Fiscal Year Start Date",
|
||||
|
76
schemas/app/Lead.json
Normal file
76
schemas/app/Lead.json
Normal file
@ -0,0 +1,76 @@
|
||||
{
|
||||
"name": "Lead",
|
||||
"label": "Lead",
|
||||
"naming": "manual",
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "name",
|
||||
"label": "Name",
|
||||
"fieldtype": "Data",
|
||||
"required": true,
|
||||
"placeholder": "Full Name",
|
||||
"section": "Default"
|
||||
},
|
||||
{
|
||||
"fieldname": "status",
|
||||
"label": "Status",
|
||||
"fieldtype": "Select",
|
||||
"default": "Open",
|
||||
"options": [
|
||||
{
|
||||
"value": "Open",
|
||||
"label": "Open"
|
||||
},
|
||||
{
|
||||
"value": "Replied",
|
||||
"label": "Replied"
|
||||
},
|
||||
{
|
||||
"value": "Interested",
|
||||
"label": "Interested"
|
||||
},
|
||||
{
|
||||
"value": "Opportunity",
|
||||
"label": "Opportunity"
|
||||
},
|
||||
{
|
||||
"value": "Converted",
|
||||
"label": "Converted"
|
||||
},
|
||||
{
|
||||
"value": "Quotation",
|
||||
"label": "Quotation"
|
||||
},
|
||||
{
|
||||
"value": "DonotContact",
|
||||
"label": "Do not Contact"
|
||||
}
|
||||
],
|
||||
"required": true,
|
||||
"section": "Default"
|
||||
},
|
||||
{
|
||||
"fieldname": "email",
|
||||
"label": "Email",
|
||||
"fieldtype": "Data",
|
||||
"placeholder": "john@doe.com",
|
||||
"section": "Contacts"
|
||||
},
|
||||
{
|
||||
"fieldname": "mobile",
|
||||
"label": "Mobile",
|
||||
"fieldtype": "Data",
|
||||
"placeholder": "Mobile",
|
||||
"section": "Contacts"
|
||||
},
|
||||
{
|
||||
"fieldname": "address",
|
||||
"label": "Address",
|
||||
"fieldtype": "Link",
|
||||
"target": "Address",
|
||||
"create": true,
|
||||
"section": "Contacts"
|
||||
}
|
||||
],
|
||||
"keywordFields": ["name", "email", "mobile"]
|
||||
}
|
@ -78,6 +78,14 @@
|
||||
"create": true,
|
||||
"section": "Billing"
|
||||
},
|
||||
{
|
||||
"fieldname": "fromLead",
|
||||
"label": "From Lead",
|
||||
"fieldtype": "Link",
|
||||
"target": "Lead",
|
||||
"readOnly": true,
|
||||
"section": "References"
|
||||
},
|
||||
{
|
||||
"fieldname": "taxId",
|
||||
"label": "Tax ID",
|
||||
|
@ -15,11 +15,29 @@
|
||||
"default": "SQUOT-",
|
||||
"section": "Default"
|
||||
},
|
||||
{
|
||||
"fieldname": "referenceType",
|
||||
"label": "Type",
|
||||
"placeholder": "Type",
|
||||
"fieldtype": "Select",
|
||||
"default": "Party",
|
||||
"options": [
|
||||
{
|
||||
"value": "Party",
|
||||
"label": "Party"
|
||||
},
|
||||
{
|
||||
"value": "Lead",
|
||||
"label": "Lead"
|
||||
}
|
||||
],
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"fieldname": "party",
|
||||
"label": "Customer",
|
||||
"fieldtype": "Link",
|
||||
"target": "Party",
|
||||
"fieldtype": "DynamicLink",
|
||||
"references": "referenceType",
|
||||
"create": true,
|
||||
"required": true,
|
||||
"section": "Default"
|
||||
|
@ -15,6 +15,7 @@ import JournalEntryAccount from './app/JournalEntryAccount.json';
|
||||
import Misc from './app/Misc.json';
|
||||
import NumberSeries from './app/NumberSeries.json';
|
||||
import Party from './app/Party.json';
|
||||
import Lead from './app/Lead.json';
|
||||
import Payment from './app/Payment.json';
|
||||
import PaymentFor from './app/PaymentFor.json';
|
||||
import PriceList from './app/PriceList.json';
|
||||
@ -96,6 +97,7 @@ export const appSchemas: Schema[] | SchemaStub[] = [
|
||||
AccountingLedgerEntry as Schema,
|
||||
|
||||
Party as Schema,
|
||||
Lead as Schema,
|
||||
Address as Schema,
|
||||
Item as Schema,
|
||||
UOM as Schema,
|
||||
|
@ -202,6 +202,13 @@ function getCompleteSidebar(): SidebarConfig {
|
||||
schemaName: 'Item',
|
||||
filters: routeFilters.SalesItems,
|
||||
},
|
||||
{
|
||||
label: t`Lead`,
|
||||
name: 'lead',
|
||||
route: '/list/Lead',
|
||||
schemaName: 'Lead',
|
||||
hidden: () => !fyo.singles.AccountingSettings?.enableLead,
|
||||
},
|
||||
] as SidebarItem[],
|
||||
},
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user