mirror of
https://github.com/frappe/books.git
synced 2024-12-22 19:09:01 +00:00
commit
7205f5aa42
@ -117,3 +117,13 @@ export type DocStatus =
|
|||||||
| 'NotSaved'
|
| 'NotSaved'
|
||||||
| 'Submitted'
|
| 'Submitted'
|
||||||
| 'Cancelled';
|
| 'Cancelled';
|
||||||
|
|
||||||
|
export type LeadStatus =
|
||||||
|
| ''
|
||||||
|
| 'Open'
|
||||||
|
| 'Replied'
|
||||||
|
| 'Interested'
|
||||||
|
| 'Opportunity'
|
||||||
|
| 'Converted'
|
||||||
|
| 'Quotation'
|
||||||
|
| 'DonotContact'
|
@ -15,6 +15,7 @@ export class AccountingSettings extends Doc {
|
|||||||
enableDiscounting?: boolean;
|
enableDiscounting?: boolean;
|
||||||
enableInventory?: boolean;
|
enableInventory?: boolean;
|
||||||
enablePriceList?: boolean;
|
enablePriceList?: boolean;
|
||||||
|
enableLead?: boolean;
|
||||||
enableFormCustomization?: boolean;
|
enableFormCustomization?: boolean;
|
||||||
enableInvoiceReturns?: boolean;
|
enableInvoiceReturns?: boolean;
|
||||||
|
|
||||||
@ -48,6 +49,9 @@ export class AccountingSettings extends Doc {
|
|||||||
enableInventory: () => {
|
enableInventory: () => {
|
||||||
return !!this.enableInventory;
|
return !!this.enableInventory;
|
||||||
},
|
},
|
||||||
|
enableLead: () => {
|
||||||
|
return !!this.enableLead;
|
||||||
|
},
|
||||||
enableInvoiceReturns: () => {
|
enableInvoiceReturns: () => {
|
||||||
return !!this.enableInvoiceReturns;
|
return !!this.enableInvoiceReturns;
|
||||||
},
|
},
|
||||||
|
@ -165,6 +165,10 @@ export abstract class Invoice extends Transactional {
|
|||||||
async afterSubmit() {
|
async afterSubmit() {
|
||||||
await super.afterSubmit();
|
await super.afterSubmit();
|
||||||
|
|
||||||
|
if (this.schemaName === ModelNameEnum.SalesQuote) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// update outstanding amounts
|
// update outstanding amounts
|
||||||
await this.fyo.db.update(this.schemaName, {
|
await this.fyo.db.update(this.schemaName, {
|
||||||
name: this.name as string,
|
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';
|
} from 'fyo/model/validationFunction';
|
||||||
import { Money } from 'pesa';
|
import { Money } from 'pesa';
|
||||||
import { PartyRole } from './types';
|
import { PartyRole } from './types';
|
||||||
|
import { ModelNameEnum } from 'models/types';
|
||||||
|
|
||||||
export class Party extends Doc {
|
export class Party extends Doc {
|
||||||
role?: PartyRole;
|
role?: PartyRole;
|
||||||
|
party?: string;
|
||||||
|
fromLead?: string;
|
||||||
defaultAccount?: string;
|
defaultAccount?: string;
|
||||||
outstandingAmount?: Money;
|
outstandingAmount?: Money;
|
||||||
async updateOutstandingAmount() {
|
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[] {
|
static getActions(fyo: Fyo): Action[] {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
@ -1,14 +1,22 @@
|
|||||||
import { Fyo } from 'fyo';
|
import { Fyo } from 'fyo';
|
||||||
import { DocValueMap } from 'fyo/core/types';
|
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 { ModelNameEnum } from 'models/types';
|
||||||
import { getQuoteActions, getTransactionStatusColumn } from '../../helpers';
|
import { getQuoteActions, getTransactionStatusColumn } from '../../helpers';
|
||||||
import { Invoice } from '../Invoice/Invoice';
|
import { Invoice } from '../Invoice/Invoice';
|
||||||
import { SalesQuoteItem } from '../SalesQuoteItem/SalesQuoteItem';
|
import { SalesQuoteItem } from '../SalesQuoteItem/SalesQuoteItem';
|
||||||
import { Defaults } from '../Defaults/Defaults';
|
import { Defaults } from '../Defaults/Defaults';
|
||||||
|
import { Doc } from 'fyo/model/doc';
|
||||||
|
import { Party } from '../Party/Party';
|
||||||
|
|
||||||
export class SalesQuote extends Invoice {
|
export class SalesQuote extends Invoice {
|
||||||
items?: SalesQuoteItem[];
|
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
|
// This is an inherited method and it must keep the async from the parent
|
||||||
// class
|
// class
|
||||||
@ -49,6 +57,20 @@ export class SalesQuote extends Invoice {
|
|||||||
return 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 {
|
static getListViewSettings(): ListViewSettings {
|
||||||
return {
|
return {
|
||||||
columns: [
|
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 { Fyo, t } from 'fyo';
|
||||||
import { Doc } from 'fyo/model/doc';
|
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 { DateTime } from 'luxon';
|
||||||
import { Money } from 'pesa';
|
import { Money } from 'pesa';
|
||||||
import { safeParseFloat } from 'utils/index';
|
import { safeParseFloat } from 'utils/index';
|
||||||
@ -15,6 +21,7 @@ import { SalesQuote } from './baseModels/SalesQuote/SalesQuote';
|
|||||||
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 { Lead } from './baseModels/Lead/Lead';
|
||||||
|
|
||||||
export function getQuoteActions(
|
export function getQuoteActions(
|
||||||
fyo: Fyo,
|
fyo: Fyo,
|
||||||
@ -23,6 +30,10 @@ export function getQuoteActions(
|
|||||||
return [getMakeInvoiceAction(fyo, schemaName)];
|
return [getMakeInvoiceAction(fyo, schemaName)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getLeadActions(fyo: Fyo): Action[] {
|
||||||
|
return [getCreateCustomerAction(fyo), getSalesQuoteAction(fyo)];
|
||||||
|
}
|
||||||
|
|
||||||
export function getInvoiceActions(
|
export function getInvoiceActions(
|
||||||
fyo: Fyo,
|
fyo: Fyo,
|
||||||
schemaName: ModelNameEnum.SalesInvoice | ModelNameEnum.PurchaseInvoice
|
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 {
|
export function getMakePaymentAction(fyo: Fyo): Action {
|
||||||
return {
|
return {
|
||||||
label: fyo.t`Payment`,
|
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<
|
export const statusColor: Record<
|
||||||
DocStatus | InvoiceStatus,
|
DocStatus | InvoiceStatus | LeadStatus,
|
||||||
string | undefined
|
string | undefined
|
||||||
> = {
|
> = {
|
||||||
'': 'gray',
|
'': 'gray',
|
||||||
Draft: 'gray',
|
Draft: 'gray',
|
||||||
|
Open: 'gray',
|
||||||
|
Replied: 'yellow',
|
||||||
|
Opportunity: 'yellow',
|
||||||
Unpaid: 'orange',
|
Unpaid: 'orange',
|
||||||
Paid: 'green',
|
Paid: 'green',
|
||||||
|
Interested: 'yellow',
|
||||||
|
Converted: 'green',
|
||||||
|
Quotation: 'green',
|
||||||
Saved: 'gray',
|
Saved: 'gray',
|
||||||
NotSaved: 'gray',
|
NotSaved: 'gray',
|
||||||
Submitted: 'green',
|
Submitted: 'green',
|
||||||
Cancelled: 'red',
|
Cancelled: 'red',
|
||||||
|
DonotContact: 'red',
|
||||||
Return: 'green',
|
Return: 'green',
|
||||||
ReturnIssued: '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(
|
export function getDocStatus(
|
||||||
doc?: RenderData | Doc
|
doc?: RenderData | Doc
|
||||||
): DocStatus | InvoiceStatus {
|
): DocStatus | InvoiceStatus {
|
||||||
|
@ -9,6 +9,7 @@ import { JournalEntry } from './baseModels/JournalEntry/JournalEntry';
|
|||||||
import { JournalEntryAccount } from './baseModels/JournalEntryAccount/JournalEntryAccount';
|
import { JournalEntryAccount } from './baseModels/JournalEntryAccount/JournalEntryAccount';
|
||||||
import { Misc } from './baseModels/Misc';
|
import { Misc } from './baseModels/Misc';
|
||||||
import { Party } from './baseModels/Party/Party';
|
import { Party } from './baseModels/Party/Party';
|
||||||
|
import { Lead } from './baseModels/Lead/Lead';
|
||||||
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 { PriceList } from './baseModels/PriceList/PriceList';
|
||||||
@ -53,6 +54,7 @@ export const models = {
|
|||||||
JournalEntry,
|
JournalEntry,
|
||||||
JournalEntryAccount,
|
JournalEntryAccount,
|
||||||
Misc,
|
Misc,
|
||||||
|
Lead,
|
||||||
Party,
|
Party,
|
||||||
Payment,
|
Payment,
|
||||||
PaymentFor,
|
PaymentFor,
|
||||||
|
@ -4,6 +4,7 @@ import { GSTType } from './types';
|
|||||||
|
|
||||||
export class Party extends BaseParty {
|
export class Party extends BaseParty {
|
||||||
gstin?: string;
|
gstin?: string;
|
||||||
|
fromLead?: string;
|
||||||
gstType?: GSTType;
|
gstType?: GSTType;
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/require-await
|
// eslint-disable-next-line @typescript-eslint/require-await
|
||||||
@ -18,5 +19,6 @@ export class Party extends BaseParty {
|
|||||||
|
|
||||||
hidden: HiddenMap = {
|
hidden: HiddenMap = {
|
||||||
gstin: () => (this.gstType as GSTType) !== 'Registered Regular',
|
gstin: () => (this.gstType as GSTType) !== 'Registered Regular',
|
||||||
|
fromLead: () => !this.fromLead,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ export enum ModelNameEnum {
|
|||||||
JournalEntryAccount = 'JournalEntryAccount',
|
JournalEntryAccount = 'JournalEntryAccount',
|
||||||
Misc = 'Misc',
|
Misc = 'Misc',
|
||||||
NumberSeries = 'NumberSeries',
|
NumberSeries = 'NumberSeries',
|
||||||
|
Lead = 'Lead',
|
||||||
Party = 'Party',
|
Party = 'Party',
|
||||||
Payment = 'Payment',
|
Payment = 'Payment',
|
||||||
PaymentFor = 'PaymentFor',
|
PaymentFor = 'PaymentFor',
|
||||||
|
@ -100,6 +100,13 @@
|
|||||||
"default": false,
|
"default": false,
|
||||||
"section": "Features"
|
"section": "Features"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "enableLead",
|
||||||
|
"label": "Enable Lead",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"default": false,
|
||||||
|
"section": "Features"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "fiscalYearStart",
|
"fieldname": "fiscalYearStart",
|
||||||
"label": "Fiscal Year Start Date",
|
"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,
|
"create": true,
|
||||||
"section": "Billing"
|
"section": "Billing"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "fromLead",
|
||||||
|
"label": "From Lead",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"target": "Lead",
|
||||||
|
"readOnly": true,
|
||||||
|
"section": "References"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "taxId",
|
"fieldname": "taxId",
|
||||||
"label": "Tax ID",
|
"label": "Tax ID",
|
||||||
|
@ -15,11 +15,29 @@
|
|||||||
"default": "SQUOT-",
|
"default": "SQUOT-",
|
||||||
"section": "Default"
|
"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",
|
"fieldname": "party",
|
||||||
"label": "Customer",
|
"label": "Customer",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "DynamicLink",
|
||||||
"target": "Party",
|
"references": "referenceType",
|
||||||
"create": true,
|
"create": true,
|
||||||
"required": true,
|
"required": true,
|
||||||
"section": "Default"
|
"section": "Default"
|
||||||
|
@ -15,6 +15,7 @@ import JournalEntryAccount from './app/JournalEntryAccount.json';
|
|||||||
import Misc from './app/Misc.json';
|
import Misc from './app/Misc.json';
|
||||||
import NumberSeries from './app/NumberSeries.json';
|
import NumberSeries from './app/NumberSeries.json';
|
||||||
import Party from './app/Party.json';
|
import Party from './app/Party.json';
|
||||||
|
import Lead from './app/Lead.json';
|
||||||
import Payment from './app/Payment.json';
|
import Payment from './app/Payment.json';
|
||||||
import PaymentFor from './app/PaymentFor.json';
|
import PaymentFor from './app/PaymentFor.json';
|
||||||
import PriceList from './app/PriceList.json';
|
import PriceList from './app/PriceList.json';
|
||||||
@ -96,6 +97,7 @@ export const appSchemas: Schema[] | SchemaStub[] = [
|
|||||||
AccountingLedgerEntry as Schema,
|
AccountingLedgerEntry as Schema,
|
||||||
|
|
||||||
Party as Schema,
|
Party as Schema,
|
||||||
|
Lead as Schema,
|
||||||
Address as Schema,
|
Address as Schema,
|
||||||
Item as Schema,
|
Item as Schema,
|
||||||
UOM as Schema,
|
UOM as Schema,
|
||||||
|
@ -202,6 +202,13 @@ function getCompleteSidebar(): SidebarConfig {
|
|||||||
schemaName: 'Item',
|
schemaName: 'Item',
|
||||||
filters: routeFilters.SalesItems,
|
filters: routeFilters.SalesItems,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: t`Lead`,
|
||||||
|
name: 'lead',
|
||||||
|
route: '/list/Lead',
|
||||||
|
schemaName: 'Lead',
|
||||||
|
hidden: () => !fyo.singles.AccountingSettings?.enableLead,
|
||||||
|
},
|
||||||
] as SidebarItem[],
|
] as SidebarItem[],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user