diff --git a/backend/patches/updateSchemas.ts b/backend/patches/updateSchemas.ts
index e883de26..1ad11bae 100644
--- a/backend/patches/updateSchemas.ts
+++ b/backend/patches/updateSchemas.ts
@@ -21,6 +21,7 @@ const defaultNumberSeriesMap = {
[ModelNameEnum.JournalEntry]: 'JV-',
[ModelNameEnum.SalesInvoice]: 'SINV-',
[ModelNameEnum.PurchaseInvoice]: 'PINV-',
+ [ModelNameEnum.SalesQuote]: 'SQUOT-',
} as Record;
async function execute(dm: DatabaseManager) {
@@ -209,6 +210,7 @@ async function copyTransactionalTables(
ModelNameEnum.Payment,
ModelNameEnum.SalesInvoice,
ModelNameEnum.PurchaseInvoice,
+ ModelNameEnum.SalesQuote,
];
for (const sn of schemaNames) {
diff --git a/models/baseModels/Defaults/Defaults.ts b/models/baseModels/Defaults/Defaults.ts
index 2515d35e..d590193c 100644
--- a/models/baseModels/Defaults/Defaults.ts
+++ b/models/baseModels/Defaults/Defaults.ts
@@ -14,6 +14,7 @@ export class Defaults extends Doc {
purchaseReceiptLocation?: string;
// Number Series
+ salesQuoteNumberSeries?: string;
salesInvoiceNumberSeries?: string;
purchaseInvoiceNumberSeries?: string;
journalEntryNumberSeries?: string;
@@ -29,6 +30,7 @@ export class Defaults extends Doc {
purchaseReceiptTerms?: string;
// Print Templates
+ salesQuotePrintTemplate?: string;
salesInvoicePrintTemplate?: string;
purchaseInvoicePrintTemplate?: string;
journalEntryPrintTemplate?: string;
@@ -46,6 +48,9 @@ export class Defaults extends Doc {
salesPaymentAccount: () => ({ isGroup: false, accountType: 'Cash' }),
purchasePaymentAccount: () => ({ isGroup: false, accountType: 'Cash' }),
// Number Series
+ salesQuoteNumberSeries: () => ({
+ referenceType: ModelNameEnum.SalesQuote,
+ }),
salesInvoiceNumberSeries: () => ({
referenceType: ModelNameEnum.SalesInvoice,
}),
@@ -68,6 +73,7 @@ export class Defaults extends Doc {
referenceType: ModelNameEnum.PurchaseReceipt,
}),
// Print Templates
+ salesQuotePrintTemplate: () => ({ type: ModelNameEnum.SalesQuote }),
salesInvoicePrintTemplate: () => ({ type: ModelNameEnum.SalesInvoice }),
purchaseInvoicePrintTemplate: () => ({
type: ModelNameEnum.PurchaseInvoice,
@@ -118,4 +124,5 @@ export const numberSeriesDefaultsMap: Record<
[ModelNameEnum.StockMovement]: 'stockMovementNumberSeries',
[ModelNameEnum.Shipment]: 'shipmentNumberSeries',
[ModelNameEnum.PurchaseReceipt]: 'purchaseReceiptNumberSeries',
+ [ModelNameEnum.SalesQuote]: 'salesQuoteNumberSeries',
};
diff --git a/models/baseModels/Invoice/Invoice.ts b/models/baseModels/Invoice/Invoice.ts
index 04679097..879bd641 100644
--- a/models/baseModels/Invoice/Invoice.ts
+++ b/models/baseModels/Invoice/Invoice.ts
@@ -71,7 +71,11 @@ export abstract class Invoice extends Transactional {
returnAgainst?: string;
get isSales() {
- return this.schemaName === 'SalesInvoice';
+ return this.schemaName === 'SalesInvoice' || this.schemaName == 'SalesQuote';
+ }
+
+ get isQuote() {
+ return this.schemaName == 'SalesQuote';
}
get enableDiscounting() {
@@ -493,7 +497,7 @@ export abstract class Invoice extends Transactional {
}
async _updateIsItemsReturned() {
- if (!this.isReturn || !this.returnAgainst) {
+ if (!this.isReturn || !this.returnAgainst || this.isQuote) {
return;
}
@@ -515,7 +519,7 @@ export abstract class Invoice extends Transactional {
}
async _validateHasLinkedReturnInvoices() {
- if (!this.name || this.isReturn) {
+ if (!this.name || this.isReturn || this.isQuote) {
return;
}
@@ -685,6 +689,7 @@ export abstract class Invoice extends Transactional {
attachment: () =>
!(this.attachment || !(this.isSubmitted || this.isCancelled)),
backReference: () => !this.backReference,
+ quote: () => !this.quote,
priceList: () => !this.fyo.singles.AccountingSettings?.enablePriceList,
returnAgainst: () =>
(this.isSubmitted || this.isCancelled) && !this.returnAgainst,
diff --git a/models/baseModels/InvoiceItem/InvoiceItem.ts b/models/baseModels/InvoiceItem/InvoiceItem.ts
index 9c39993d..95560849 100644
--- a/models/baseModels/InvoiceItem/InvoiceItem.ts
+++ b/models/baseModels/InvoiceItem/InvoiceItem.ts
@@ -47,7 +47,7 @@ export abstract class InvoiceItem extends Doc {
itemTaxedTotal?: Money;
get isSales() {
- return this.schemaName === 'SalesInvoiceItem';
+ return this.schemaName === 'SalesInvoiceItem' || this.schemaName === 'SalesQuoteItem';
}
get date() {
diff --git a/models/baseModels/PrintTemplate.ts b/models/baseModels/PrintTemplate.ts
index 58750643..145401be 100644
--- a/models/baseModels/PrintTemplate.ts
+++ b/models/baseModels/PrintTemplate.ts
@@ -55,6 +55,7 @@ export class PrintTemplate extends Doc {
const models = [
ModelNameEnum.SalesInvoice,
+ ModelNameEnum.SalesQuote,
ModelNameEnum.PurchaseInvoice,
ModelNameEnum.JournalEntry,
ModelNameEnum.Payment,
diff --git a/models/baseModels/SalesQuote/SalesQuote.ts b/models/baseModels/SalesQuote/SalesQuote.ts
new file mode 100644
index 00000000..8ac10b3f
--- /dev/null
+++ b/models/baseModels/SalesQuote/SalesQuote.ts
@@ -0,0 +1,65 @@
+import { Fyo } from 'fyo';
+import { Action, ListViewSettings } from 'fyo/model/types';
+import { LedgerPosting } from 'models/Transactional/LedgerPosting';
+import { ModelNameEnum } from 'models/types';
+import { getQuoteActions, getTransactionStatusColumn } from '../../helpers';
+import { Invoice } from '../Invoice/Invoice';
+import { SalesQuoteItem } from '../SalesQuoteItem/SalesQuoteItem';
+
+export class SalesQuote extends Invoice {
+ items?: SalesQuoteItem[];
+
+ async getPosting() {
+ return null;
+ }
+
+ async getInvoice(): Promise {
+ if (!this.isSubmitted) {
+ return null;
+ }
+
+ const schemaName = ModelNameEnum.SalesInvoice;
+ const defaults = (this.fyo.singles.Defaults as Defaults) ?? {};
+ const terms = defaults.salesInvoiceTerms ?? '';
+ const numberSeries = defaults.salesInvoiceNumberSeries ?? undefined;
+
+ const data = {
+ ...this,
+ date: new Date().toISOString(),
+ terms,
+ numberSeries,
+ quote: this.name,
+ items: []
+ };
+
+ const invoice = this.fyo.doc.getNewDoc(schemaName, data) as Invoice;
+ for (const row of this.items ?? []) {
+ await invoice.append('items', {
+ ...row
+ });
+ }
+
+ if (!invoice.items?.length) {
+ return null;
+ }
+
+ return invoice;
+ }
+
+ static getListViewSettings(): ListViewSettings {
+ return {
+ columns: [
+ 'name',
+ getTransactionStatusColumn(),
+ 'party',
+ 'date',
+ 'baseGrandTotal',
+ 'outstandingAmount',
+ ],
+ };
+ }
+
+ static getActions(fyo: Fyo): Action[] {
+ return getQuoteActions(fyo, ModelNameEnum.SalesQuote);
+ }
+}
diff --git a/models/baseModels/SalesQuoteItem/SalesQuoteItem.ts b/models/baseModels/SalesQuoteItem/SalesQuoteItem.ts
new file mode 100644
index 00000000..a2f2133c
--- /dev/null
+++ b/models/baseModels/SalesQuoteItem/SalesQuoteItem.ts
@@ -0,0 +1,3 @@
+import { InvoiceItem } from '../InvoiceItem/InvoiceItem';
+
+export class SalesQuoteItem extends InvoiceItem {}
diff --git a/models/helpers.ts b/models/helpers.ts
index 19f32bda..4e0ec45c 100644
--- a/models/helpers.ts
+++ b/models/helpers.ts
@@ -15,6 +15,15 @@ import { StockMovement } from './inventory/StockMovement';
import { StockTransfer } from './inventory/StockTransfer';
import { InvoiceStatus, ModelNameEnum } from './types';
+export function getQuoteActions(
+ fyo: Fyo,
+ schemaName: ModelNameEnum.SalesQuote
+): Action[] {
+ return [
+ getMakeInvoiceAction(fyo, schemaName),
+ ];
+}
+
export function getInvoiceActions(
fyo: Fyo,
schemaName: ModelNameEnum.SalesInvoice | ModelNameEnum.PurchaseInvoice
@@ -67,7 +76,7 @@ export function getMakeStockTransferAction(
export function getMakeInvoiceAction(
fyo: Fyo,
- schemaName: ModelNameEnum.Shipment | ModelNameEnum.PurchaseReceipt
+ schemaName: ModelNameEnum.Shipment | ModelNameEnum.PurchaseReceipt | ModelNameEnum.SalesQuote
): Action {
let label = fyo.t`Sales Invoice`;
if (schemaName === ModelNameEnum.PurchaseReceipt) {
@@ -77,9 +86,15 @@ export function getMakeInvoiceAction(
return {
label,
group: fyo.t`Create`,
- condition: (doc: Doc) => doc.isSubmitted && !doc.backReference,
+ condition: (doc: Doc) => {
+ if (schemaName === ModelNameEnum.SalesQuote) {
+ return doc.isSubmitted
+ } else {
+ return doc.isSubmitted && !doc.backReference
+ }
+ },
action: async (doc: Doc) => {
- const invoice = await (doc as StockTransfer).getInvoice();
+ let invoice = await (doc as SalesQuote | StockTransfer).getInvoice();
if (!invoice || !invoice.name) {
return;
}
diff --git a/models/index.ts b/models/index.ts
index 51c1c672..77edb626 100644
--- a/models/index.ts
+++ b/models/index.ts
@@ -19,6 +19,8 @@ import { PurchaseInvoice } from './baseModels/PurchaseInvoice/PurchaseInvoice';
import { PurchaseInvoiceItem } from './baseModels/PurchaseInvoiceItem/PurchaseInvoiceItem';
import { SalesInvoice } from './baseModels/SalesInvoice/SalesInvoice';
import { SalesInvoiceItem } from './baseModels/SalesInvoiceItem/SalesInvoiceItem';
+import { SalesQuote } from './baseModels/SalesQuote/SalesQuote';
+import { SalesQuoteItem } from './baseModels/SalesQuoteItem/SalesQuoteItem';
import { SetupWizard } from './baseModels/SetupWizard/SetupWizard';
import { Tax } from './baseModels/Tax/Tax';
import { TaxSummary } from './baseModels/TaxSummary/TaxSummary';
@@ -61,6 +63,8 @@ export const models = {
PurchaseInvoiceItem,
SalesInvoice,
SalesInvoiceItem,
+ SalesQuote,
+ SalesQuoteItem,
SerialNumber,
SetupWizard,
PrintTemplate,
diff --git a/models/types.ts b/models/types.ts
index b3fdf730..3a83e4bf 100644
--- a/models/types.ts
+++ b/models/types.ts
@@ -27,6 +27,8 @@ export enum ModelNameEnum {
PurchaseInvoiceItem = 'PurchaseInvoiceItem',
SalesInvoice = 'SalesInvoice',
SalesInvoiceItem = 'SalesInvoiceItem',
+ SalesQuote = 'SalesQuote',
+ SalesQuoteItem = 'SalesQuoteItem',
SerialNumber = 'SerialNumber',
SetupWizard = 'SetupWizard',
Tax = 'Tax',
diff --git a/schemas/app/Defaults.json b/schemas/app/Defaults.json
index ba920134..714253cb 100644
--- a/schemas/app/Defaults.json
+++ b/schemas/app/Defaults.json
@@ -92,6 +92,14 @@
"create": true,
"section": "Number Series"
},
+ {
+ "fieldname": "salesQuoteNumberSeries",
+ "label": "Sales Quote Number Series",
+ "fieldtype": "Link",
+ "target": "NumberSeries",
+ "create": true,
+ "section": "Number Series"
+ },
{
"fieldname": "salesInvoiceTerms",
"label": "Sales Invoice Terms",
@@ -116,6 +124,13 @@
"fieldtype": "Text",
"section": "Terms"
},
+ {
+ "fieldname": "salesQuotePrintTemplate",
+ "label": "Sales Quote Print Template",
+ "fieldtype": "Link",
+ "target": "PrintTemplate",
+ "section": "Print Templates"
+ },
{
"fieldname": "salesInvoicePrintTemplate",
"label": "Sales Invoice Print Template",
diff --git a/schemas/app/NumberSeries.json b/schemas/app/NumberSeries.json
index 497b2cc2..0526e587 100644
--- a/schemas/app/NumberSeries.json
+++ b/schemas/app/NumberSeries.json
@@ -35,6 +35,10 @@
"value": "SalesInvoice",
"label": "Sales Invoice"
},
+ {
+ "value": "SalesQuote",
+ "label": "Sales Quote"
+ },
{
"value": "PurchaseInvoice",
"label": "Purchase Invoice"
diff --git a/schemas/app/SalesInvoice.json b/schemas/app/SalesInvoice.json
index d520d0e5..84e0dfd0 100644
--- a/schemas/app/SalesInvoice.json
+++ b/schemas/app/SalesInvoice.json
@@ -31,6 +31,14 @@
"target": "Shipment",
"section": "References"
},
+ {
+ "fieldname": "quote",
+ "label": "Quote Reference",
+ "fieldtype": "Link",
+ "target": "SalesQuote",
+ "section": "References",
+ "required": false
+ },
{
"fieldname": "makeAutoStockTransfer",
"label": "Make Shipment On Submit",
diff --git a/schemas/app/SalesQuote.json b/schemas/app/SalesQuote.json
new file mode 100644
index 00000000..2d3f7540
--- /dev/null
+++ b/schemas/app/SalesQuote.json
@@ -0,0 +1,58 @@
+{
+ "name": "SalesQuote",
+ "label": "Quote",
+ "extends": "Invoice",
+ "naming": "numberSeries",
+ "showTitle": true,
+ "fields": [
+ {
+ "fieldname": "numberSeries",
+ "label": "Number Series",
+ "fieldtype": "Link",
+ "target": "NumberSeries",
+ "create": true,
+ "required": true,
+ "default": "SQUOT-",
+ "section": "Default"
+ },
+ {
+ "fieldname": "account",
+ "drop": true
+ },
+ {
+ "fieldname": "stockNotTransferred",
+ "drop": true
+ },
+ {
+ "fieldname": "backReference",
+ "drop": true
+ },
+ {
+ "fieldname": "makeAutoStockTransfer",
+ "drop": true
+ },
+ {
+ "fieldname": "returnAgainst",
+ "drop": true
+ },
+ {
+ "fieldname": "party",
+ "label": "Customer",
+ "fieldtype": "Link",
+ "target": "Party",
+ "create": true,
+ "required": true,
+ "section": "Default"
+ },
+ {
+ "fieldname": "items",
+ "label": "Items",
+ "fieldtype": "Table",
+ "target": "SalesQuoteItem",
+ "required": true,
+ "edit": true,
+ "section": "Items"
+ }
+ ],
+ "keywordFields": ["name", "party"]
+}
diff --git a/schemas/app/SalesQuoteItem.json b/schemas/app/SalesQuoteItem.json
new file mode 100644
index 00000000..41231a8d
--- /dev/null
+++ b/schemas/app/SalesQuoteItem.json
@@ -0,0 +1,5 @@
+{
+ "name": "SalesQuoteItem",
+ "label": "Sales Quote Item",
+ "extends": "InvoiceItem"
+}
diff --git a/schemas/index.ts b/schemas/index.ts
index c7c2f0b8..f5e15b02 100644
--- a/schemas/index.ts
+++ b/schemas/index.ts
@@ -197,7 +197,11 @@ function getCombined(
const combined = Object.assign(abstractSchema, extendingSchema);
for (const fieldname in extendingFields) {
- abstractFields[fieldname] = extendingFields[fieldname];
+ if (extendingFields[fieldname].drop) {
+ delete abstractFields[fieldname]
+ } else {
+ abstractFields[fieldname] = extendingFields[fieldname];
+ }
}
combined.fields = getListFromMap(abstractFields);
diff --git a/schemas/schemas.ts b/schemas/schemas.ts
index 1e92f56c..54f4a81a 100644
--- a/schemas/schemas.ts
+++ b/schemas/schemas.ts
@@ -25,6 +25,8 @@ import PurchaseInvoice from './app/PurchaseInvoice.json';
import PurchaseInvoiceItem from './app/PurchaseInvoiceItem.json';
import SalesInvoice from './app/SalesInvoice.json';
import SalesInvoiceItem from './app/SalesInvoiceItem.json';
+import SalesQuote from './app/SalesQuote.json';
+import SalesQuoteItem from './app/SalesQuoteItem.json';
import SetupWizard from './app/SetupWizard.json';
import Tax from './app/Tax.json';
import TaxDetail from './app/TaxDetail.json';
@@ -108,10 +110,12 @@ export const appSchemas: Schema[] | SchemaStub[] = [
Invoice as Schema,
SalesInvoice as Schema,
PurchaseInvoice as Schema,
+ SalesQuote as Schema,
InvoiceItem as Schema,
SalesInvoiceItem as SchemaStub,
PurchaseInvoiceItem as SchemaStub,
+ SalesQuoteItem as SchemaStub,
PriceList as Schema,
PriceListItem as SchemaStub,
diff --git a/src/components/Sidebar.vue b/src/components/Sidebar.vue
index 581eb87a..2793958b 100644
--- a/src/components/Sidebar.vue
+++ b/src/components/Sidebar.vue
@@ -150,6 +150,7 @@
v-if="showDevMode"
class="text-xs text-gray-500 select-none cursor-pointer"
@click="showDevMode = false"
+ title="Open dev tools with Ctrl+Shift+I"
>
dev mode
diff --git a/src/pages/TemplateBuilder/SetType.vue b/src/pages/TemplateBuilder/SetType.vue
new file mode 100644
index 00000000..dd70df3d
--- /dev/null
+++ b/src/pages/TemplateBuilder/SetType.vue
@@ -0,0 +1,71 @@
+
+
+
+
diff --git a/src/pages/TemplateBuilder/TemplateBuilder.vue b/src/pages/TemplateBuilder/TemplateBuilder.vue
index 6d09ca00..6737ce36 100644
--- a/src/pages/TemplateBuilder/TemplateBuilder.vue
+++ b/src/pages/TemplateBuilder/TemplateBuilder.vue
@@ -213,6 +213,13 @@
>
+
+
+