mirror of
https://github.com/frappe/books.git
synced 2024-11-10 07:40:55 +00:00
Merge pull request #555 from 18alantom/common-form-view
feat: Common form view
This commit is contained in:
commit
14f832dabf
@ -14,9 +14,11 @@ export class DocHandler {
|
||||
singles: SinglesMap = {};
|
||||
docs: Observable<DocMap | undefined> = new Observable();
|
||||
observer: Observable<never> = new Observable();
|
||||
#temporaryNameCounters: Record<string, number>;
|
||||
|
||||
constructor(fyo: Fyo) {
|
||||
this.fyo = fyo;
|
||||
this.#temporaryNameCounters = {};
|
||||
}
|
||||
|
||||
init() {
|
||||
@ -97,7 +99,7 @@ export class DocHandler {
|
||||
}
|
||||
|
||||
const doc = new Model!(schema, data, this.fyo, isRawValueMap);
|
||||
doc.name ??= getRandomString();
|
||||
doc.name ??= this.getTemporaryName(schema);
|
||||
if (cacheDoc) {
|
||||
this.#addToCache(doc);
|
||||
}
|
||||
@ -105,6 +107,22 @@ export class DocHandler {
|
||||
return doc;
|
||||
}
|
||||
|
||||
getTemporaryName(schema: Schema): string {
|
||||
if (schema.naming === 'random') {
|
||||
return getRandomString();
|
||||
}
|
||||
|
||||
this.#temporaryNameCounters[schema.name] ??= 1;
|
||||
|
||||
const idx = this.#temporaryNameCounters[schema.name];
|
||||
this.#temporaryNameCounters[schema.name] = idx + 1;
|
||||
|
||||
return this.fyo.t`New ${schema.label ?? schema.name} ${String(idx).padStart(
|
||||
2,
|
||||
'0'
|
||||
)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache operations
|
||||
*/
|
||||
|
@ -454,7 +454,7 @@ export class Doc extends Observable<DocValue | Doc[]> {
|
||||
convertToDocValue: boolean = false
|
||||
): Doc {
|
||||
if (!this.name && this.schema.naming !== 'manual') {
|
||||
this.name = getRandomString();
|
||||
this.name = this.fyo.doc.getTemporaryName(this.schema);
|
||||
}
|
||||
|
||||
docValueMap.name ??= getRandomString();
|
||||
@ -868,9 +868,9 @@ export class Doc extends Observable<DocValue | Doc[]> {
|
||||
}
|
||||
|
||||
async _insert() {
|
||||
await setName(this, this.fyo);
|
||||
this._setBaseMetaValues();
|
||||
await this._preSync();
|
||||
await setName(this, this.fyo);
|
||||
|
||||
const validDict = this.getValidDict(false, true);
|
||||
let data: DocValueMap;
|
||||
|
@ -111,6 +111,7 @@ export class Item extends Doc {
|
||||
|
||||
static getListViewSettings(): ListViewSettings {
|
||||
return {
|
||||
formRoute: ({ name }) => `/edit/Item/${name}`,
|
||||
columns: ['name', 'unit', 'tax', 'rate'],
|
||||
};
|
||||
}
|
||||
|
@ -4,9 +4,9 @@ import {
|
||||
Action,
|
||||
DefaultMap,
|
||||
FiltersMap,
|
||||
HiddenMap,
|
||||
ListViewSettings,
|
||||
} from 'fyo/model/types';
|
||||
import { DateTime } from 'luxon';
|
||||
import {
|
||||
getDocStatus,
|
||||
getLedgerLinkAction,
|
||||
@ -39,6 +39,17 @@ export class JournalEntry extends Transactional {
|
||||
return posting;
|
||||
}
|
||||
|
||||
hidden: HiddenMap = {
|
||||
referenceNumber: () =>
|
||||
!(this.referenceNumber || !(this.isSubmitted || this.isCancelled)),
|
||||
referenceDate: () =>
|
||||
!(this.referenceDate || !(this.isSubmitted || this.isCancelled)),
|
||||
userRemark: () =>
|
||||
!(this.userRemark || !(this.isSubmitted || this.isCancelled)),
|
||||
attachment: () =>
|
||||
!(this.attachment || !(this.isSubmitted || this.isCancelled)),
|
||||
};
|
||||
|
||||
static defaults: DefaultMap = {
|
||||
numberSeries: (doc) => getNumberSeries(doc.schemaName, doc.fyo),
|
||||
date: () => new Date(),
|
||||
|
@ -617,6 +617,7 @@ export class Payment extends Transactional {
|
||||
|
||||
static getListViewSettings(fyo: Fyo): ListViewSettings {
|
||||
return {
|
||||
formRoute: ({ name }) => `/edit/Payment/${name}`,
|
||||
columns: ['name', getDocStatusListColumn(), 'party', 'date', 'amount'],
|
||||
};
|
||||
}
|
||||
|
@ -75,6 +75,7 @@ export class StockMovement extends Transfer {
|
||||
|
||||
static getListViewSettings(fyo: Fyo): ListViewSettings {
|
||||
return {
|
||||
formRoute: ({ name }) => `/edit/StockMovement/${name}`,
|
||||
columns: [
|
||||
'name',
|
||||
getDocStatusListColumn(),
|
||||
|
@ -1,7 +1,13 @@
|
||||
import { Fyo, t } from 'fyo';
|
||||
import { Attachment } from 'fyo/core/types';
|
||||
import { Doc } from 'fyo/model/doc';
|
||||
import { Action, DefaultMap, FiltersMap, FormulaMap } from 'fyo/model/types';
|
||||
import {
|
||||
Action,
|
||||
DefaultMap,
|
||||
FiltersMap,
|
||||
FormulaMap,
|
||||
HiddenMap,
|
||||
} from 'fyo/model/types';
|
||||
import { ValidationError } from 'fyo/utils/errors';
|
||||
import { Defaults } from 'models/baseModels/Defaults/Defaults';
|
||||
import { Invoice } from 'models/baseModels/Invoice/Invoice';
|
||||
@ -33,6 +39,14 @@ export abstract class StockTransfer extends Transfer {
|
||||
},
|
||||
};
|
||||
|
||||
hidden: HiddenMap = {
|
||||
backReference: () =>
|
||||
!(this.backReference || !(this.isSubmitted || this.isCancelled)),
|
||||
terms: () => !(this.terms || !(this.isSubmitted || this.isCancelled)),
|
||||
attachment: () =>
|
||||
!(this.attachment || !(this.isSubmitted || this.isCancelled)),
|
||||
};
|
||||
|
||||
static defaults: DefaultMap = {
|
||||
numberSeries: (doc) => getNumberSeries(doc.schemaName, doc.fyo),
|
||||
terms: (doc) => {
|
||||
|
@ -4,50 +4,20 @@
|
||||
"isSingle": false,
|
||||
"naming": "manual",
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "image",
|
||||
"label": "Image",
|
||||
"section": "Default",
|
||||
"fieldtype": "AttachImage"
|
||||
},
|
||||
{
|
||||
"fieldname": "name",
|
||||
"label": "Item Name",
|
||||
"fieldtype": "Data",
|
||||
"placeholder": "Item Name",
|
||||
"section": "Default",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"fieldname": "image",
|
||||
"label": "Image",
|
||||
"fieldtype": "AttachImage"
|
||||
},
|
||||
{
|
||||
"fieldname": "description",
|
||||
"label": "Description",
|
||||
"placeholder": "Item Description",
|
||||
"fieldtype": "Text"
|
||||
},
|
||||
{
|
||||
"fieldname": "unit",
|
||||
"label": "Unit Type",
|
||||
"fieldtype": "Link",
|
||||
"target": "UOM",
|
||||
"create": true,
|
||||
"default": "Unit",
|
||||
"placeholder": "Unit Type"
|
||||
},
|
||||
{
|
||||
"fieldname": "itemType",
|
||||
"label": "Type",
|
||||
"placeholder": "Type",
|
||||
"fieldtype": "Select",
|
||||
"default": "Product",
|
||||
"options": [
|
||||
{
|
||||
"value": "Product",
|
||||
"label": "Product"
|
||||
},
|
||||
{
|
||||
"value": "Service",
|
||||
"label": "Service"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"fieldname": "for",
|
||||
"label": "For",
|
||||
@ -67,14 +37,57 @@
|
||||
}
|
||||
],
|
||||
"required": true,
|
||||
"section": "Default",
|
||||
"default": "Both"
|
||||
},
|
||||
{
|
||||
"fieldname": "itemType",
|
||||
"label": "Type",
|
||||
"placeholder": "Type",
|
||||
"fieldtype": "Select",
|
||||
"default": "Product",
|
||||
"section": "Default",
|
||||
"options": [
|
||||
{
|
||||
"value": "Product",
|
||||
"label": "Product"
|
||||
},
|
||||
{
|
||||
"value": "Service",
|
||||
"label": "Service"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"fieldname": "unit",
|
||||
"label": "Unit Type",
|
||||
"fieldtype": "Link",
|
||||
"target": "UOM",
|
||||
"create": true,
|
||||
"default": "Unit",
|
||||
"section": "Details",
|
||||
"placeholder": "Unit Type"
|
||||
},
|
||||
{
|
||||
"fieldname": "rate",
|
||||
"label": "Rate",
|
||||
"section": "Details",
|
||||
"fieldtype": "Currency"
|
||||
},
|
||||
{
|
||||
"fieldname": "description",
|
||||
"label": "Description",
|
||||
"placeholder": "Item Description",
|
||||
"section": "Details",
|
||||
"fieldtype": "Text"
|
||||
},
|
||||
{
|
||||
"fieldname": "incomeAccount",
|
||||
"label": "Sales Acc.",
|
||||
"fieldtype": "Link",
|
||||
"target": "Account",
|
||||
"placeholder": "Income",
|
||||
"section": "Accounts",
|
||||
"create": true,
|
||||
"required": true
|
||||
},
|
||||
@ -84,6 +97,7 @@
|
||||
"fieldtype": "Link",
|
||||
"target": "Account",
|
||||
"placeholder": "Expense",
|
||||
"section": "Accounts",
|
||||
"create": true,
|
||||
"required": true
|
||||
},
|
||||
@ -92,31 +106,30 @@
|
||||
"label": "Tax",
|
||||
"fieldtype": "Link",
|
||||
"target": "Tax",
|
||||
"section": "Accounts",
|
||||
"create": true,
|
||||
"placeholder": "Tax"
|
||||
},
|
||||
{
|
||||
"fieldname": "rate",
|
||||
"label": "Rate",
|
||||
"fieldtype": "Currency"
|
||||
},
|
||||
{
|
||||
"fieldname": "hsnCode",
|
||||
"label": "HSN/SAC",
|
||||
"fieldtype": "Int",
|
||||
"placeholder": "HSN/SAC Code"
|
||||
"placeholder": "HSN/SAC Code",
|
||||
"section": "Inventory"
|
||||
},
|
||||
{
|
||||
"fieldname": "barcode",
|
||||
"label": "Barcode",
|
||||
"fieldtype": "Data",
|
||||
"placeholder": "Barcode"
|
||||
"placeholder": "Barcode",
|
||||
"section": "Inventory"
|
||||
},
|
||||
{
|
||||
"fieldname": "trackItem",
|
||||
"label": "Track Item",
|
||||
"fieldtype": "Check",
|
||||
"default": false
|
||||
"default": false,
|
||||
"section": "Inventory"
|
||||
}
|
||||
],
|
||||
"quickEditFields": [
|
||||
|
@ -4,6 +4,24 @@
|
||||
"naming": "numberSeries",
|
||||
"isSubmittable": true,
|
||||
"fields": [
|
||||
{
|
||||
"label": "Entry No",
|
||||
"fieldname": "name",
|
||||
"fieldtype": "Data",
|
||||
"required": true,
|
||||
"readOnly": true,
|
||||
"section": "Default"
|
||||
},
|
||||
{
|
||||
"fieldname": "numberSeries",
|
||||
"label": "Number Series",
|
||||
"fieldtype": "Link",
|
||||
"target": "NumberSeries",
|
||||
"create": true,
|
||||
"required": true,
|
||||
"default": "JV-",
|
||||
"section": "Default"
|
||||
},
|
||||
{
|
||||
"fieldname": "entryType",
|
||||
"label": "Entry Type",
|
||||
@ -55,58 +73,49 @@
|
||||
"label": "Depreciation Entry"
|
||||
}
|
||||
],
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"label": "Entry No",
|
||||
"fieldname": "name",
|
||||
"fieldtype": "Data",
|
||||
"required": true,
|
||||
"readOnly": true
|
||||
"section": "Default"
|
||||
},
|
||||
{
|
||||
"fieldname": "date",
|
||||
"label": "Date",
|
||||
"fieldtype": "Date",
|
||||
"required": true
|
||||
"required": true,
|
||||
"section": "Default"
|
||||
},
|
||||
{
|
||||
"fieldname": "accounts",
|
||||
"label": "Account Entries",
|
||||
"fieldtype": "Table",
|
||||
"target": "JournalEntryAccount",
|
||||
"required": true
|
||||
"required": true,
|
||||
"section": "Accounts"
|
||||
},
|
||||
{
|
||||
"fieldname": "referenceNumber",
|
||||
"label": "Reference Number",
|
||||
"fieldtype": "Data"
|
||||
"fieldtype": "Data",
|
||||
"section": "References"
|
||||
},
|
||||
{
|
||||
"fieldname": "referenceDate",
|
||||
"label": "Reference Date",
|
||||
"fieldtype": "Date"
|
||||
"fieldtype": "Date",
|
||||
"section": "References"
|
||||
},
|
||||
{
|
||||
"fieldname": "userRemark",
|
||||
"label": "User Remark",
|
||||
"fieldtype": "Text",
|
||||
"placeholder": "Add a remark"
|
||||
},
|
||||
{
|
||||
"fieldname": "numberSeries",
|
||||
"label": "Number Series",
|
||||
"fieldtype": "Link",
|
||||
"target": "NumberSeries",
|
||||
"create": true,
|
||||
"required": true,
|
||||
"default": "JV-"
|
||||
"placeholder": "Add a remark",
|
||||
"section": "References"
|
||||
},
|
||||
{
|
||||
"fieldname": "attachment",
|
||||
"placeholder": "Add attachment",
|
||||
"label": "Attachment",
|
||||
"fieldtype": "Attachment"
|
||||
"fieldtype": "Attachment",
|
||||
"section": "References"
|
||||
}
|
||||
],
|
||||
"keywordFields": ["name", "entryType"]
|
||||
|
@ -46,7 +46,8 @@
|
||||
{
|
||||
"fieldname": "outstandingAmount",
|
||||
"label": "Outstanding Amount",
|
||||
"fieldtype": "Currency"
|
||||
"fieldtype": "Currency",
|
||||
"hidden": true
|
||||
},
|
||||
{
|
||||
"fieldname": "currency",
|
||||
|
@ -11,7 +11,18 @@
|
||||
"fieldname": "name",
|
||||
"fieldtype": "Data",
|
||||
"required": true,
|
||||
"readOnly": true
|
||||
"readOnly": true,
|
||||
"section": "Default"
|
||||
},
|
||||
{
|
||||
"fieldname": "numberSeries",
|
||||
"label": "Number Series",
|
||||
"fieldtype": "Link",
|
||||
"target": "NumberSeries",
|
||||
"create": true,
|
||||
"required": true,
|
||||
"default": "PAY-",
|
||||
"section": "Default"
|
||||
},
|
||||
{
|
||||
"fieldname": "party",
|
||||
@ -19,13 +30,15 @@
|
||||
"fieldtype": "Link",
|
||||
"target": "Party",
|
||||
"create": true,
|
||||
"required": true
|
||||
"required": true,
|
||||
"section": "Default"
|
||||
},
|
||||
{
|
||||
"fieldname": "date",
|
||||
"label": "Posting Date",
|
||||
"fieldtype": "Date",
|
||||
"required": true
|
||||
"required": true,
|
||||
"section": "Default"
|
||||
},
|
||||
{
|
||||
"fieldname": "paymentType",
|
||||
@ -42,16 +55,27 @@
|
||||
"label": "Pay"
|
||||
}
|
||||
],
|
||||
"required": true
|
||||
"required": true,
|
||||
"section": "Default"
|
||||
},
|
||||
{
|
||||
"fieldname": "numberSeries",
|
||||
"label": "Number Series",
|
||||
"fieldname": "account",
|
||||
"label": "From Account",
|
||||
"fieldtype": "Link",
|
||||
"target": "NumberSeries",
|
||||
"target": "Account",
|
||||
"create": true,
|
||||
"required": true,
|
||||
"default": "PAY-"
|
||||
"section": "Details"
|
||||
},
|
||||
{
|
||||
"fieldname": "paymentAccount",
|
||||
"label": "To Account",
|
||||
"placeholder": "To Account",
|
||||
"fieldtype": "Link",
|
||||
"target": "Account",
|
||||
"create": true,
|
||||
"required": true,
|
||||
"section": "Details"
|
||||
},
|
||||
{
|
||||
"fieldname": "paymentMethod",
|
||||
@ -73,72 +97,64 @@
|
||||
}
|
||||
],
|
||||
"default": "Cash",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"fieldname": "account",
|
||||
"label": "From Account",
|
||||
"fieldtype": "Link",
|
||||
"target": "Account",
|
||||
"create": true,
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"fieldname": "paymentAccount",
|
||||
"label": "To Account",
|
||||
"placeholder": "To Account",
|
||||
"fieldtype": "Link",
|
||||
"target": "Account",
|
||||
"create": true,
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"fieldname": "referenceId",
|
||||
"label": "Ref. / Cheque No.",
|
||||
"placeholder": "Ref. / Cheque No.",
|
||||
"fieldtype": "Data"
|
||||
},
|
||||
{
|
||||
"fieldname": "referenceDate",
|
||||
"label": "Ref. Date",
|
||||
"placeholder": "Ref. Date",
|
||||
"fieldtype": "Date"
|
||||
"required": true,
|
||||
"section": "Details"
|
||||
},
|
||||
{
|
||||
"fieldname": "clearanceDate",
|
||||
"label": "Clearance Date",
|
||||
"placeholder": "Clearance Date",
|
||||
"fieldtype": "Date"
|
||||
"fieldtype": "Date",
|
||||
"section": "Details"
|
||||
},
|
||||
{
|
||||
"fieldname": "referenceId",
|
||||
"label": "Ref. / Cheque No.",
|
||||
"placeholder": "Ref. / Cheque No.",
|
||||
"fieldtype": "Data",
|
||||
"section": "Details"
|
||||
},
|
||||
{
|
||||
"fieldname": "referenceDate",
|
||||
"label": "Reference Date",
|
||||
"placeholder": "Ref. Date",
|
||||
"fieldtype": "Date",
|
||||
"section": "Details"
|
||||
},
|
||||
{
|
||||
"fieldname": "amount",
|
||||
"label": "Amount",
|
||||
"fieldtype": "Currency",
|
||||
"required": true
|
||||
"required": true,
|
||||
"section": "Amounts"
|
||||
},
|
||||
{
|
||||
"fieldname": "writeoff",
|
||||
"label": "Write Off",
|
||||
"fieldtype": "Currency"
|
||||
"fieldtype": "Currency",
|
||||
"section": "Amounts"
|
||||
},
|
||||
{
|
||||
"fieldname": "amountPaid",
|
||||
"label": "Amount Paid",
|
||||
"fieldtype": "Currency",
|
||||
"computed": true
|
||||
"computed": true,
|
||||
"section": "Amounts"
|
||||
},
|
||||
{
|
||||
"fieldname": "for",
|
||||
"label": "Payment Reference",
|
||||
"fieldtype": "Table",
|
||||
"target": "PaymentFor",
|
||||
"required": false
|
||||
"required": false,
|
||||
"section": "References"
|
||||
},
|
||||
{
|
||||
"fieldname": "attachment",
|
||||
"placeholder": "Add attachment",
|
||||
"label": "Attachment",
|
||||
"fieldtype": "Attachment"
|
||||
"fieldtype": "Attachment",
|
||||
"section": "References"
|
||||
}
|
||||
],
|
||||
"quickEditFields": [
|
||||
|
@ -5,14 +5,6 @@
|
||||
"naming": "numberSeries",
|
||||
"showTitle": true,
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "items",
|
||||
"label": "Items",
|
||||
"fieldtype": "Table",
|
||||
"target": "PurchaseReceiptItem",
|
||||
"required": true,
|
||||
"edit": true
|
||||
},
|
||||
{
|
||||
"fieldname": "numberSeries",
|
||||
"label": "Number Series",
|
||||
@ -20,14 +12,24 @@
|
||||
"target": "NumberSeries",
|
||||
"create": true,
|
||||
"required": true,
|
||||
"default": "PREC-"
|
||||
"default": "PREC-",
|
||||
"section": "Default"
|
||||
},
|
||||
{
|
||||
"fieldname": "backReference",
|
||||
"label": "Back Reference",
|
||||
"fieldtype": "Link",
|
||||
"target": "PurchaseInvoice",
|
||||
"readOnly": true
|
||||
"readOnly": true,
|
||||
"section": "References"
|
||||
},
|
||||
{
|
||||
"fieldname": "items",
|
||||
"label": "Items",
|
||||
"fieldtype": "Table",
|
||||
"target": "PurchaseReceiptItem",
|
||||
"required": true,
|
||||
"edit": true
|
||||
}
|
||||
],
|
||||
"keywordFields": ["name", "party"]
|
||||
|
@ -5,14 +5,6 @@
|
||||
"naming": "numberSeries",
|
||||
"showTitle": true,
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "items",
|
||||
"label": "Items",
|
||||
"fieldtype": "Table",
|
||||
"target": "ShipmentItem",
|
||||
"required": true,
|
||||
"edit": true
|
||||
},
|
||||
{
|
||||
"fieldname": "numberSeries",
|
||||
"label": "Number Series",
|
||||
@ -20,14 +12,25 @@
|
||||
"target": "NumberSeries",
|
||||
"create": true,
|
||||
"required": true,
|
||||
"default": "SHPM-"
|
||||
"default": "SHPM-",
|
||||
"section": "Default"
|
||||
},
|
||||
{
|
||||
"fieldname": "backReference",
|
||||
"label": "Back Reference",
|
||||
"fieldtype": "Link",
|
||||
"target": "SalesInvoice",
|
||||
"readOnly": true
|
||||
"readOnly": true,
|
||||
"section": "References"
|
||||
},
|
||||
{
|
||||
"fieldname": "items",
|
||||
"label": "Items",
|
||||
"fieldtype": "Table",
|
||||
"target": "ShipmentItem",
|
||||
"required": true,
|
||||
"edit": true,
|
||||
"section": "Items"
|
||||
}
|
||||
],
|
||||
"keywordFields": ["name", "party"]
|
||||
|
@ -11,13 +11,18 @@
|
||||
"fieldname": "name",
|
||||
"fieldtype": "Data",
|
||||
"required": true,
|
||||
"readOnly": true
|
||||
"readOnly": true,
|
||||
"section": "Default"
|
||||
},
|
||||
{
|
||||
"fieldname": "date",
|
||||
"label": "Date",
|
||||
"fieldtype": "Datetime",
|
||||
"required": true
|
||||
"fieldname": "numberSeries",
|
||||
"label": "Number Series",
|
||||
"fieldtype": "Link",
|
||||
"target": "NumberSeries",
|
||||
"create": true,
|
||||
"required": true,
|
||||
"default": "SMOV-",
|
||||
"section": "Default"
|
||||
},
|
||||
{
|
||||
"fieldname": "movementType",
|
||||
@ -41,29 +46,30 @@
|
||||
"label": "Manufacture"
|
||||
}
|
||||
],
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"fieldname": "numberSeries",
|
||||
"label": "Number Series",
|
||||
"fieldtype": "Link",
|
||||
"target": "NumberSeries",
|
||||
"create": true,
|
||||
"required": true,
|
||||
"default": "SMOV-"
|
||||
"section": "Default"
|
||||
},
|
||||
{
|
||||
"fieldname": "amount",
|
||||
"label": "Total Amount",
|
||||
"fieldtype": "Currency",
|
||||
"readOnly": true
|
||||
"fieldname": "date",
|
||||
"label": "Date",
|
||||
"fieldtype": "Datetime",
|
||||
"required": true,
|
||||
"section": "Default"
|
||||
},
|
||||
{
|
||||
"fieldname": "items",
|
||||
"label": "Items",
|
||||
"fieldtype": "Table",
|
||||
"target": "StockMovementItem",
|
||||
"required": true
|
||||
"required": true,
|
||||
"section": "Items"
|
||||
},
|
||||
{
|
||||
"fieldname": "amount",
|
||||
"label": "Total Amount",
|
||||
"fieldtype": "Currency",
|
||||
"readOnly": true,
|
||||
"section": "Items"
|
||||
}
|
||||
],
|
||||
"quickEditFields": [
|
||||
|
@ -11,13 +11,13 @@
|
||||
"fieldname": "name",
|
||||
"fieldtype": "Data",
|
||||
"required": true,
|
||||
"readOnly": true
|
||||
"readOnly": true,
|
||||
"section": "Default"
|
||||
},
|
||||
{
|
||||
"fieldname": "date",
|
||||
"label": "Date",
|
||||
"fieldtype": "Datetime",
|
||||
"required": true
|
||||
"abstract": true,
|
||||
"fieldname": "numberSeries",
|
||||
"section": "Default"
|
||||
},
|
||||
{
|
||||
"fieldname": "party",
|
||||
@ -25,25 +25,46 @@
|
||||
"fieldtype": "Link",
|
||||
"target": "Party",
|
||||
"create": true,
|
||||
"required": true
|
||||
"required": true,
|
||||
"section": "Default"
|
||||
},
|
||||
{
|
||||
"fieldname": "terms",
|
||||
"label": "Notes",
|
||||
"placeholder": "Add transfer terms",
|
||||
"fieldtype": "Text"
|
||||
"fieldname": "date",
|
||||
"label": "Date",
|
||||
"fieldtype": "Datetime",
|
||||
"required": true,
|
||||
"section": "Default"
|
||||
},
|
||||
{
|
||||
"fieldname": "attachment",
|
||||
"placeholder": "Add attachment",
|
||||
"label": "Attachment",
|
||||
"fieldtype": "Attachment"
|
||||
"abstract": true,
|
||||
"fieldname": "items",
|
||||
"section": "Items"
|
||||
},
|
||||
{
|
||||
"fieldname": "grandTotal",
|
||||
"label": "Grand Total",
|
||||
"fieldtype": "Currency",
|
||||
"readOnly": true
|
||||
"readOnly": true,
|
||||
"section": "Items"
|
||||
},
|
||||
{
|
||||
"fieldname": "terms",
|
||||
"label": "Notes",
|
||||
"placeholder": "Add transfer terms",
|
||||
"fieldtype": "Text",
|
||||
"section": "References"
|
||||
},
|
||||
{
|
||||
"fieldname": "attachment",
|
||||
"placeholder": "Add attachment",
|
||||
"label": "Attachment",
|
||||
"fieldtype": "Attachment",
|
||||
"section": "References"
|
||||
},
|
||||
{
|
||||
"abstract": true,
|
||||
"fieldname": "backReference",
|
||||
"section": "References"
|
||||
}
|
||||
],
|
||||
"keywordFields": ["name", "party"]
|
||||
|
@ -6,28 +6,32 @@
|
||||
"label": "Created By",
|
||||
"fieldtype": "Data",
|
||||
"required": true,
|
||||
"meta": true
|
||||
"meta": true,
|
||||
"section": "System"
|
||||
},
|
||||
{
|
||||
"fieldname": "modifiedBy",
|
||||
"label": "Modified By",
|
||||
"fieldtype": "Data",
|
||||
"required": true,
|
||||
"meta": true
|
||||
"meta": true,
|
||||
"section": "System"
|
||||
},
|
||||
{
|
||||
"fieldname": "created",
|
||||
"label": "Created",
|
||||
"fieldtype": "Datetime",
|
||||
"required": true,
|
||||
"meta": true
|
||||
"meta": true,
|
||||
"section": "System"
|
||||
},
|
||||
{
|
||||
"fieldname": "modified",
|
||||
"label": "Modified",
|
||||
"fieldtype": "Datetime",
|
||||
"required": true,
|
||||
"meta": true
|
||||
"meta": true,
|
||||
"section": "System"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -6,14 +6,16 @@
|
||||
"label": "Submitted",
|
||||
"fieldtype": "Check",
|
||||
"required": true,
|
||||
"meta": true
|
||||
"meta": true,
|
||||
"section": "System"
|
||||
},
|
||||
{
|
||||
"fieldname": "cancelled",
|
||||
"label": "Cancelled",
|
||||
"fieldtype": "Check",
|
||||
"required": true,
|
||||
"meta": true
|
||||
"meta": true,
|
||||
"section": "System"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -64,6 +64,9 @@ export interface BaseField {
|
||||
inline?: boolean; // UI Facing config, whether to display doc inline.
|
||||
filter?: boolean; // UI Facing config, whether to be used to filter the List.
|
||||
computed?: boolean; // Computed values are not stored in the database.
|
||||
section?: string; // UI Facing config, for grouping by sections
|
||||
tab?: string; // UI Facing config, for grouping by tabs
|
||||
abstract?: string; // Uused to mark the location of a field in an Abstract schema
|
||||
}
|
||||
|
||||
export type SelectOption = { value: string; label: string };
|
||||
|
@ -1,41 +1,50 @@
|
||||
<template>
|
||||
<div :class="containerClasses" class="flex gap-2 items-center">
|
||||
<label
|
||||
for="attachment"
|
||||
class="block whitespace-nowrap overflow-auto no-scrollbar"
|
||||
:class="[inputClasses, !value ? 'text-gray-600' : 'cursor-default']"
|
||||
>{{ label }}</label
|
||||
>
|
||||
<input
|
||||
ref="fileInput"
|
||||
id="attachment"
|
||||
type="file"
|
||||
accept="image/*,.pdf"
|
||||
class="hidden"
|
||||
:disabled="!!value"
|
||||
@input="selectFile"
|
||||
/>
|
||||
|
||||
<!-- Buttons -->
|
||||
<div class="me-2 flex gap-2">
|
||||
<!-- Upload Button -->
|
||||
<button v-if="!value" class="bg-gray-300 p-0.5 rounded" @click="upload">
|
||||
<FeatherIcon name="upload" class="h-4 w-4 text-gray-600" />
|
||||
</button>
|
||||
|
||||
<!-- Download Button -->
|
||||
<button v-if="value" class="bg-gray-300 p-0.5 rounded" @click="download">
|
||||
<FeatherIcon name="download" class="h-4 w-4 text-gray-600" />
|
||||
</button>
|
||||
|
||||
<!-- Clear Button -->
|
||||
<button
|
||||
v-if="value && !isReadOnly"
|
||||
class="bg-gray-300 p-0.5 rounded"
|
||||
@click="clear"
|
||||
<div>
|
||||
<div :class="labelClasses" v-if="showLabel && df">
|
||||
{{ df.label }}
|
||||
</div>
|
||||
<div :class="containerClasses" class="flex gap-2 items-center">
|
||||
<label
|
||||
for="attachment"
|
||||
class="block whitespace-nowrap overflow-auto no-scrollbar"
|
||||
:class="[inputClasses, !value ? 'text-gray-600' : 'cursor-default']"
|
||||
>{{ label }}</label
|
||||
>
|
||||
<FeatherIcon name="x" class="h-4 w-4 text-gray-600" />
|
||||
</button>
|
||||
<input
|
||||
ref="fileInput"
|
||||
id="attachment"
|
||||
type="file"
|
||||
accept="image/*,.pdf"
|
||||
class="hidden"
|
||||
:disabled="!!value"
|
||||
@input="selectFile"
|
||||
/>
|
||||
|
||||
<!-- Buttons -->
|
||||
<div class="me-2 flex gap-2">
|
||||
<!-- Upload Button -->
|
||||
<button v-if="!value" class="bg-gray-300 p-0.5 rounded" @click="upload">
|
||||
<FeatherIcon name="upload" class="h-4 w-4 text-gray-600" />
|
||||
</button>
|
||||
|
||||
<!-- Download Button -->
|
||||
<button
|
||||
v-if="value"
|
||||
class="bg-gray-300 p-0.5 rounded"
|
||||
@click="download"
|
||||
>
|
||||
<FeatherIcon name="download" class="h-4 w-4 text-gray-600" />
|
||||
</button>
|
||||
|
||||
<!-- Clear Button -->
|
||||
<button
|
||||
v-if="value && !isReadOnly"
|
||||
class="bg-gray-300 p-0.5 rounded"
|
||||
@click="clear"
|
||||
>
|
||||
<FeatherIcon name="x" class="h-4 w-4 text-gray-600" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -28,7 +28,7 @@
|
||||
<template #content>
|
||||
<div class="text-sm p-2 text-center">
|
||||
<div>
|
||||
<Row class="border-none" :column-count="5" gap="0.5rem">
|
||||
<Row :column-count="5" gap="0.5rem">
|
||||
<div
|
||||
v-for="color in colors"
|
||||
:key="color.value"
|
||||
|
@ -116,7 +116,7 @@ export default {
|
||||
},
|
||||
async openNewDoc() {
|
||||
const schemaName = this.df.target;
|
||||
const doc = await fyo.doc.getNewDoc(schemaName);
|
||||
const doc = fyo.doc.getNewDoc(schemaName);
|
||||
|
||||
const filters = await this.getCreateFilters();
|
||||
|
||||
|
@ -4,79 +4,85 @@
|
||||
{{ df.label }}
|
||||
</div>
|
||||
|
||||
<!-- Title Row -->
|
||||
<Row
|
||||
:ratio="ratio"
|
||||
class="border-b px-2 text-gray-600 w-full flex items-center"
|
||||
>
|
||||
<div class="flex items-center ps-2">#</div>
|
||||
<div
|
||||
class="items-center flex px-2 h-row-mid"
|
||||
:class="{
|
||||
'ms-auto': isNumeric(df),
|
||||
}"
|
||||
v-for="df in tableFields"
|
||||
:key="df.fieldname"
|
||||
:style="{
|
||||
height: ``,
|
||||
}"
|
||||
<div :class="border ? 'border rounded-md' : ''">
|
||||
<!-- Title Row -->
|
||||
<Row
|
||||
:ratio="ratio"
|
||||
class="border-b px-2 text-gray-600 w-full flex items-center"
|
||||
>
|
||||
{{ df.label }}
|
||||
</div>
|
||||
</Row>
|
||||
|
||||
<!-- Data Rows -->
|
||||
<div
|
||||
class="overflow-auto"
|
||||
:style="{ 'max-height': maxHeight }"
|
||||
v-if="value"
|
||||
>
|
||||
<TableRow
|
||||
v-for="row in value"
|
||||
ref="table-row"
|
||||
:key="row.name"
|
||||
v-bind="{ row, tableFields, size, ratio, isNumeric }"
|
||||
:read-only="isReadOnly"
|
||||
@remove="removeRow(row)"
|
||||
:can-edit-row="canEditRow"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Add Row and Row Count -->
|
||||
<Row
|
||||
:ratio="ratio"
|
||||
class="
|
||||
text-gray-500
|
||||
cursor-pointer
|
||||
border-transparent
|
||||
px-2
|
||||
w-full
|
||||
h-row-mid
|
||||
flex
|
||||
items-center
|
||||
"
|
||||
v-if="!isReadOnly"
|
||||
@click="addRow"
|
||||
>
|
||||
<div class="flex items-center ps-1">
|
||||
<feather-icon name="plus" class="w-4 h-4 text-gray-500" />
|
||||
</div>
|
||||
<div class="flex justify-between px-2" :style="`grid-column: 2 / ${ratio.length + 1}`">
|
||||
<p>
|
||||
{{ t`Add Row` }}
|
||||
</p>
|
||||
<p
|
||||
class="text-end px-2"
|
||||
v-if="
|
||||
value &&
|
||||
maxRowsBeforeOverflow &&
|
||||
value.length > maxRowsBeforeOverflow
|
||||
"
|
||||
<div class="flex items-center ps-2">#</div>
|
||||
<div
|
||||
class="items-center flex px-2 h-row-mid"
|
||||
:class="{
|
||||
'ms-auto': isNumeric(df),
|
||||
}"
|
||||
v-for="df in tableFields"
|
||||
:key="df.fieldname"
|
||||
:style="{
|
||||
height: ``,
|
||||
}"
|
||||
>
|
||||
{{ t`${value.length} rows` }}
|
||||
</p>
|
||||
{{ df.label }}
|
||||
</div>
|
||||
</Row>
|
||||
|
||||
<!-- Data Rows -->
|
||||
<div
|
||||
class="overflow-auto"
|
||||
:style="{ 'max-height': maxHeight }"
|
||||
v-if="value"
|
||||
>
|
||||
<TableRow
|
||||
v-for="(row, idx) of value"
|
||||
:class="idx < value.length - 1 ? 'border-b' : ''"
|
||||
ref="table-row"
|
||||
:key="row.name"
|
||||
v-bind="{ row, tableFields, size, ratio, isNumeric }"
|
||||
:read-only="isReadOnly"
|
||||
@remove="removeRow(row)"
|
||||
:can-edit-row="canEditRow"
|
||||
/>
|
||||
</div>
|
||||
</Row>
|
||||
|
||||
<!-- Add Row and Row Count -->
|
||||
<Row
|
||||
:ratio="ratio"
|
||||
class="
|
||||
text-gray-500
|
||||
cursor-pointer
|
||||
px-2
|
||||
w-full
|
||||
h-row-mid
|
||||
flex
|
||||
items-center
|
||||
"
|
||||
:class="value.length > 0 ? 'border-t' : ''"
|
||||
v-if="!isReadOnly"
|
||||
@click="addRow"
|
||||
>
|
||||
<div class="flex items-center ps-1">
|
||||
<feather-icon name="plus" class="w-4 h-4 text-gray-500" />
|
||||
</div>
|
||||
<div
|
||||
class="flex justify-between px-2"
|
||||
:style="`grid-column: 2 / ${ratio.length + 1}`"
|
||||
>
|
||||
<p>
|
||||
{{ t`Add Row` }}
|
||||
</p>
|
||||
<p
|
||||
class="text-end px-2"
|
||||
v-if="
|
||||
value &&
|
||||
maxRowsBeforeOverflow &&
|
||||
value.length > maxRowsBeforeOverflow
|
||||
"
|
||||
>
|
||||
{{ t`${value.length} rows` }}
|
||||
</p>
|
||||
</div>
|
||||
</Row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -101,6 +107,10 @@ export default {
|
||||
type: Number,
|
||||
default: 3,
|
||||
},
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
components: {
|
||||
Row,
|
||||
|
@ -4,7 +4,6 @@
|
||||
class="
|
||||
w-full
|
||||
px-2
|
||||
border-b
|
||||
hover:bg-gray-25
|
||||
group
|
||||
flex
|
||||
|
@ -8,6 +8,7 @@
|
||||
justify-between
|
||||
h-row-large
|
||||
items-center
|
||||
flex-shrink-0
|
||||
"
|
||||
>
|
||||
<h1>{{ formTitle }}</h1>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="inline-grid border-b" :style="style" v-bind="$attrs">
|
||||
<div class="inline-grid" :style="style" v-bind="$attrs">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -56,7 +56,7 @@
|
||||
<script lang="ts">
|
||||
import { Money } from 'pesa';
|
||||
import { getCreateRoute } from 'src/router';
|
||||
import { getStatus, routeTo } from 'src/utils/ui';
|
||||
import { routeTo } from 'src/utils/ui';
|
||||
import { defineComponent, PropType } from 'vue';
|
||||
import Button from '../Button.vue';
|
||||
import StatusBadge from '../StatusBadge.vue';
|
||||
@ -78,7 +78,17 @@ export default defineComponent({
|
||||
linked: { type: Object as PropType<Linked>, required: true },
|
||||
},
|
||||
methods: {
|
||||
getStatus,
|
||||
getStatus(entry: { cancelled?: boolean; submitted?: boolean }) {
|
||||
if (entry.cancelled) {
|
||||
return 'Cancelled';
|
||||
}
|
||||
|
||||
if (entry.submitted) {
|
||||
return 'Submitted';
|
||||
}
|
||||
|
||||
return 'Saved';
|
||||
},
|
||||
async openEntry(name: string) {
|
||||
const route = getCreateRoute(this.linked.schemaName, name);
|
||||
await routeTo(route);
|
||||
|
317
src/pages/CommonForm/CommonForm.vue
Normal file
317
src/pages/CommonForm/CommonForm.vue
Normal file
@ -0,0 +1,317 @@
|
||||
<template>
|
||||
<FormContainer>
|
||||
<template #header v-if="hasDoc">
|
||||
<StatusBadge :status="status" />
|
||||
<DropdownWithActions
|
||||
v-for="group of groupedActions"
|
||||
:key="group.label"
|
||||
:type="group.type"
|
||||
:actions="group.actions"
|
||||
>
|
||||
<p v-if="group.group">
|
||||
{{ group.group }}
|
||||
</p>
|
||||
<feather-icon v-else name="more-horizontal" class="w-4 h-4" />
|
||||
</DropdownWithActions>
|
||||
<Button v-if="doc?.canSave" type="primary" @click="sync">
|
||||
{{ t`Save` }}
|
||||
</Button>
|
||||
<Button v-else-if="doc?.canSubmit" type="primary" @click="submit">{{
|
||||
t`Submit`
|
||||
}}</Button>
|
||||
</template>
|
||||
<template #body>
|
||||
<FormHeader
|
||||
:form-title="title"
|
||||
:form-sub-title="schema.label"
|
||||
class="sticky top-0 bg-white border-b"
|
||||
>
|
||||
</FormHeader>
|
||||
|
||||
<!-- Section Container -->
|
||||
<div v-if="hasDoc" class="overflow-auto custom-scroll">
|
||||
<CommonFormSection
|
||||
v-for="([name, fields], idx) in activeGroup.entries()"
|
||||
@editrow="(doc: Doc) => toggleQuickEditDoc(doc)"
|
||||
:key="name + idx"
|
||||
ref="section"
|
||||
class="p-4"
|
||||
:class="idx !== 0 && activeGroup.size > 1 ? 'border-t' : ''"
|
||||
:show-title="activeGroup.size > 1 && name !== t`Default`"
|
||||
:title="name"
|
||||
:fields="fields"
|
||||
:doc="doc"
|
||||
:errors="errors"
|
||||
@value-change="onValueChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Tab Bar -->
|
||||
<div
|
||||
class="
|
||||
mt-auto
|
||||
px-4
|
||||
pb-4
|
||||
flex
|
||||
gap-8
|
||||
border-t
|
||||
flex-shrink-0
|
||||
sticky
|
||||
bottom-0
|
||||
bg-white
|
||||
"
|
||||
v-if="groupedFields && groupedFields.size > 1"
|
||||
>
|
||||
<div
|
||||
v-for="key of groupedFields.keys()"
|
||||
:key="key"
|
||||
@click="activeTab = key"
|
||||
class="text-sm cursor-pointer"
|
||||
:class="
|
||||
key === activeTab
|
||||
? 'text-blue-500 font-semibold border-t-2 border-blue-500'
|
||||
: ''
|
||||
"
|
||||
:style="{
|
||||
paddingTop: key === activeTab ? 'calc(1rem - 2px)' : '1rem',
|
||||
}"
|
||||
>
|
||||
{{ key }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #quickedit>
|
||||
<Transition name="quickedit">
|
||||
<QuickEditForm
|
||||
v-if="hasQeDoc"
|
||||
:name="qeDoc.name"
|
||||
:show-name="false"
|
||||
:show-save="false"
|
||||
:source-doc="qeDoc"
|
||||
:schema-name="qeDoc.schemaName"
|
||||
:white="true"
|
||||
:route-back="false"
|
||||
:load-on-close="false"
|
||||
@close="() => toggleQuickEditDoc(null)"
|
||||
/>
|
||||
</Transition>
|
||||
</template>
|
||||
</FormContainer>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { DocValue } from 'fyo/core/types';
|
||||
import { Doc } from 'fyo/model/doc';
|
||||
import { ValidationError } from 'fyo/utils/errors';
|
||||
import { getDocStatus } from 'models/helpers';
|
||||
import { ModelNameEnum } from 'models/types';
|
||||
import { Field, Schema } from 'schemas/types';
|
||||
import Button from 'src/components/Button.vue';
|
||||
import DropdownWithActions from 'src/components/DropdownWithActions.vue';
|
||||
import FormContainer from 'src/components/FormContainer.vue';
|
||||
import FormHeader from 'src/components/FormHeader.vue';
|
||||
import StatusBadge from 'src/components/StatusBadge.vue';
|
||||
import { handleErrorWithDialog } from 'src/errorHandling';
|
||||
import { getErrorMessage } from 'src/utils';
|
||||
import { ActionGroup, UIGroupedFields } from 'src/utils/types';
|
||||
import {
|
||||
getFieldsGroupedByTabAndSection,
|
||||
getGroupedActionsForDoc,
|
||||
} from 'src/utils/ui';
|
||||
import { computed, defineComponent, nextTick } from 'vue';
|
||||
import QuickEditForm from '../QuickEditForm.vue';
|
||||
import CommonFormSection from './CommonFormSection.vue';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
name: { type: String, default: '' },
|
||||
schemaName: { type: String, default: ModelNameEnum.SalesInvoice },
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
schemaName: computed(() => this.docOrNull?.schemaName),
|
||||
name: computed(() => this.docOrNull?.name),
|
||||
doc: computed(() => this.docOrNull),
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
errors: {},
|
||||
docOrNull: null,
|
||||
activeTab: 'Default',
|
||||
groupedFields: null,
|
||||
quickEditDoc: null,
|
||||
} as {
|
||||
errors: Record<string, string>;
|
||||
docOrNull: null | Doc;
|
||||
activeTab: string;
|
||||
groupedFields: null | UIGroupedFields;
|
||||
quickEditDoc: null | Doc;
|
||||
};
|
||||
},
|
||||
async mounted() {
|
||||
if (this.fyo.store.isDevelopment) {
|
||||
// @ts-ignore
|
||||
window.cf = this;
|
||||
}
|
||||
|
||||
await this.setDoc();
|
||||
this.updateGroupedFields();
|
||||
},
|
||||
computed: {
|
||||
hasDoc(): boolean {
|
||||
return !!this.docOrNull;
|
||||
},
|
||||
hasQeDoc(): boolean {
|
||||
return !!this.quickEditDoc;
|
||||
},
|
||||
status(): string {
|
||||
if (!this.hasDoc) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return getDocStatus(this.doc);
|
||||
},
|
||||
doc(): Doc {
|
||||
const doc = this.docOrNull as Doc | null;
|
||||
if (!doc) {
|
||||
throw new ValidationError(
|
||||
this.t`Doc ${this.schema.label} ${this.name} not set`
|
||||
);
|
||||
}
|
||||
return doc;
|
||||
},
|
||||
qeDoc(): Doc {
|
||||
const doc = this.quickEditDoc as Doc | null;
|
||||
if (!doc) {
|
||||
throw new ValidationError(
|
||||
this.t`Doc ${this.schema.label} ${this.name} not set`
|
||||
);
|
||||
}
|
||||
return doc;
|
||||
},
|
||||
title(): string {
|
||||
if (this.schema.isSubmittable && this.docOrNull?.notInserted) {
|
||||
return this.t`New Entry`;
|
||||
}
|
||||
|
||||
return this.docOrNull?.name! ?? this.t`New Entry`;
|
||||
},
|
||||
schema(): Schema {
|
||||
const schema = this.fyo.schemaMap[this.schemaName];
|
||||
if (!schema) {
|
||||
throw new ValidationError(`no schema found with ${this.schemaName}`);
|
||||
}
|
||||
|
||||
return schema;
|
||||
},
|
||||
activeGroup(): Map<string, Field[]> {
|
||||
if (!this.groupedFields) {
|
||||
return new Map();
|
||||
}
|
||||
|
||||
const group = this.groupedFields.get(this.activeTab);
|
||||
if (!group) {
|
||||
throw new ValidationError(
|
||||
`Tab group ${this.activeTab} has no value set`
|
||||
);
|
||||
}
|
||||
|
||||
return group;
|
||||
},
|
||||
groupedActions(): ActionGroup[] {
|
||||
if (!this.hasDoc) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return getGroupedActionsForDoc(this.doc);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
updateGroupedFields(): void {
|
||||
if (!this.hasDoc) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.groupedFields = getFieldsGroupedByTabAndSection(
|
||||
this.schema,
|
||||
this.doc
|
||||
);
|
||||
},
|
||||
async sync() {
|
||||
try {
|
||||
await this.doc.sync();
|
||||
this.updateGroupedFields();
|
||||
} catch (err) {
|
||||
if (!(err instanceof Error)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await handleErrorWithDialog(err, this.doc);
|
||||
}
|
||||
},
|
||||
async submit() {
|
||||
try {
|
||||
await this.doc.submit();
|
||||
this.updateGroupedFields();
|
||||
} catch (err) {
|
||||
if (!(err instanceof Error)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await handleErrorWithDialog(err, this.doc);
|
||||
}
|
||||
},
|
||||
async setDoc() {
|
||||
if (this.hasDoc) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.name) {
|
||||
await this.setDocFromName(this.name);
|
||||
} else {
|
||||
this.docOrNull = this.fyo.doc.getNewDoc(this.schemaName);
|
||||
}
|
||||
},
|
||||
async setDocFromName(name: string) {
|
||||
try {
|
||||
this.docOrNull = await this.fyo.doc.getDoc(this.schemaName, name);
|
||||
} catch (err) {
|
||||
this.docOrNull = this.fyo.doc.getNewDoc(this.schemaName);
|
||||
}
|
||||
},
|
||||
async toggleQuickEditDoc(doc: Doc | null) {
|
||||
if (this.quickEditDoc && doc) {
|
||||
this.quickEditDoc = null;
|
||||
await nextTick();
|
||||
}
|
||||
|
||||
this.quickEditDoc = doc;
|
||||
},
|
||||
async onValueChange(field: Field, value: DocValue) {
|
||||
const { fieldname } = field;
|
||||
delete this.errors[fieldname];
|
||||
|
||||
try {
|
||||
await this.doc.set(fieldname, value);
|
||||
} catch (err) {
|
||||
if (!(err instanceof Error)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.errors[fieldname] = getErrorMessage(err, this.doc);
|
||||
}
|
||||
|
||||
this.updateGroupedFields();
|
||||
},
|
||||
},
|
||||
components: {
|
||||
FormContainer,
|
||||
FormHeader,
|
||||
CommonFormSection,
|
||||
StatusBadge,
|
||||
Button,
|
||||
DropdownWithActions,
|
||||
QuickEditForm,
|
||||
},
|
||||
});
|
||||
</script>
|
81
src/pages/CommonForm/CommonFormSection.vue
Normal file
81
src/pages/CommonForm/CommonFormSection.vue
Normal file
@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<div v-if="(fields ?? []).length > 0">
|
||||
<div
|
||||
v-if="showTitle && title"
|
||||
class="flex justify-between items-center cursor-pointer select-none"
|
||||
:class="collapsed ? '' : 'mb-4'"
|
||||
@click="collapsed = !collapsed"
|
||||
>
|
||||
<h2 class="text-base text-gray-900 font-semibold">
|
||||
{{ title }}
|
||||
</h2>
|
||||
<feather-icon
|
||||
:name="collapsed ? 'chevron-up' : 'chevron-down'"
|
||||
class="w-4 h-4 text-gray-600"
|
||||
/>
|
||||
</div>
|
||||
<div class="grid gap-4 gap-x-8 grid-cols-2" v-if="!collapsed">
|
||||
<div
|
||||
v-for="field of fields"
|
||||
:key="field.fieldname"
|
||||
:class="field.fieldtype === 'Table' ? 'col-span-2 text-base' : ''"
|
||||
class="mb-auto"
|
||||
>
|
||||
<FormControl
|
||||
:ref="field.fieldname === 'name' ? 'nameField' : 'fields'"
|
||||
:show-label="true"
|
||||
:border="true"
|
||||
:df="field"
|
||||
:value="doc[field.fieldname]"
|
||||
@editrow="(doc: Doc) => $emit('editrow', doc)"
|
||||
@change="(value: DocValue) => $emit('value-change', field, value)"
|
||||
/>
|
||||
<div v-if="errors?.[field.fieldname]" class="text-sm text-red-600 mt-1">
|
||||
{{ errors[field.fieldname] }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { DocValue } from 'fyo/core/types';
|
||||
import { Doc } from 'fyo/model/doc';
|
||||
import { Field } from 'schemas/types';
|
||||
import FormControl from 'src/components/Controls/FormControl.vue';
|
||||
import { defineComponent, PropType } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
emits: ['editrow', 'value-change'],
|
||||
props: {
|
||||
title: String,
|
||||
errors: Object as PropType<Record<string, string>>,
|
||||
showTitle: Boolean,
|
||||
doc: { type: Object as PropType<Doc>, required: true },
|
||||
fields: Array as PropType<Field[]>,
|
||||
},
|
||||
data() {
|
||||
return { collapsed: false } as {
|
||||
collapsed: boolean;
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.focusOnNameField();
|
||||
},
|
||||
methods: {
|
||||
focusOnNameField() {
|
||||
const naming = this.fyo.schemaMap[this.doc.schemaName]?.naming;
|
||||
if (naming !== 'manual') {
|
||||
return;
|
||||
}
|
||||
|
||||
const nameField = (this.$refs.nameField as { focus: Function }[])?.[0];
|
||||
if (!nameField) {
|
||||
return;
|
||||
}
|
||||
|
||||
nameField.focus();
|
||||
},
|
||||
},
|
||||
components: { FormControl },
|
||||
});
|
||||
</script>
|
@ -1,320 +0,0 @@
|
||||
<template>
|
||||
<FormContainer>
|
||||
<!-- Page Header (Title, Buttons, etc) -->
|
||||
<template #header v-if="doc">
|
||||
<StatusBadge :status="status" />
|
||||
<Barcode
|
||||
v-if="showBarcode"
|
||||
@item-selected="(name) => doc.addItem(name)"
|
||||
/>
|
||||
<DropdownWithActions
|
||||
v-for="group of groupedActions"
|
||||
:key="group.label"
|
||||
:type="group.type"
|
||||
:actions="group.actions"
|
||||
>
|
||||
<p v-if="group.group">
|
||||
{{ group.group }}
|
||||
</p>
|
||||
<feather-icon v-else name="more-horizontal" class="w-4 h-4" />
|
||||
</DropdownWithActions>
|
||||
<Button v-if="doc?.canSave" type="primary" @click="sync">
|
||||
{{ t`Save` }}
|
||||
</Button>
|
||||
<Button v-else-if="doc?.canSubmit" type="primary" @click="submit">{{
|
||||
t`Submit`
|
||||
}}</Button>
|
||||
</template>
|
||||
|
||||
<!-- Form Header -->
|
||||
<template #body v-if="doc">
|
||||
<FormHeader
|
||||
:form-title="doc.notInserted ? t`New Entry` : doc.name"
|
||||
:form-sub-title="doc.schema?.label ?? ''"
|
||||
/>
|
||||
<hr />
|
||||
|
||||
<div>
|
||||
<!-- Form Data Entry -->
|
||||
<div class="m-4 grid grid-cols-3 gap-4">
|
||||
<FormControl
|
||||
input-class="font-semibold"
|
||||
:border="true"
|
||||
:df="getField('party')"
|
||||
:value="doc.party"
|
||||
@change="(value) => doc.set('party', value, true)"
|
||||
@new-doc="(party) => doc.set('party', party.name, true)"
|
||||
:read-only="doc?.submitted"
|
||||
/>
|
||||
<FormControl
|
||||
input-class="text-end"
|
||||
:border="true"
|
||||
:df="getField('date')"
|
||||
:value="doc.date"
|
||||
@change="(value) => doc.set('date', value)"
|
||||
:read-only="doc?.submitted"
|
||||
/>
|
||||
<FormControl
|
||||
input-class="text-end"
|
||||
:border="true"
|
||||
:df="getField('numberSeries')"
|
||||
:value="doc.numberSeries"
|
||||
@change="(value) => doc.set('numberSeries', value)"
|
||||
:read-only="!doc.notInserted || doc?.submitted"
|
||||
/>
|
||||
<FormControl
|
||||
v-if="doc.backReference"
|
||||
:border="true"
|
||||
:df="getField('backReference')"
|
||||
:value="doc.backReference"
|
||||
:read-only="true"
|
||||
/>
|
||||
<FormControl
|
||||
v-if="doc.attachment || !(doc.isSubmitted || doc.isCancelled)"
|
||||
:border="true"
|
||||
:df="getField('attachment')"
|
||||
:value="doc.attachment"
|
||||
@change="(value) => doc.set('attachment', value)"
|
||||
:read-only="doc?.submitted"
|
||||
/>
|
||||
</div>
|
||||
<hr />
|
||||
|
||||
<!-- Items Table -->
|
||||
<Table
|
||||
class="text-base"
|
||||
:df="getField('items')"
|
||||
:value="doc.items"
|
||||
:showHeader="true"
|
||||
:max-rows-before-overflow="4"
|
||||
@change="(value) => doc.set('items', value)"
|
||||
@editrow="toggleQuickEditDoc"
|
||||
:read-only="doc?.submitted"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Form Footer -->
|
||||
<div v-if="doc.items?.length ?? 0" class="mt-auto">
|
||||
<hr />
|
||||
<div class="flex justify-between text-base m-4 gap-12">
|
||||
<div class="w-1/2 flex flex-col justify-between">
|
||||
<!-- Form Terms-->
|
||||
<FormControl
|
||||
:border="true"
|
||||
v-if="!doc?.submitted || doc.terms"
|
||||
:df="getField('terms')"
|
||||
:value="doc.terms"
|
||||
class="mt-auto"
|
||||
@change="(value) => doc.set('terms', value)"
|
||||
:read-only="doc?.submitted"
|
||||
/>
|
||||
</div>
|
||||
<div class="w-1/2" v-if="doc.grandTotal">
|
||||
<!-- Grand Total -->
|
||||
<div
|
||||
class="
|
||||
flex
|
||||
justify-between
|
||||
text-green-600
|
||||
font-semibold
|
||||
text-base
|
||||
"
|
||||
>
|
||||
<div>{{ t`Grand Total` }}</div>
|
||||
<div>{{ formattedValue('grandTotal') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #quickedit v-if="quickEditDoc">
|
||||
<QuickEditForm
|
||||
:name="quickEditDoc.name"
|
||||
:show-name="false"
|
||||
:show-save="false"
|
||||
:source-doc="quickEditDoc"
|
||||
:source-fields="quickEditFields"
|
||||
:schema-name="quickEditDoc.schemaName"
|
||||
:white="true"
|
||||
:route-back="false"
|
||||
:load-on-close="false"
|
||||
@close="toggleQuickEditDoc(null)"
|
||||
/>
|
||||
</template>
|
||||
</FormContainer>
|
||||
</template>
|
||||
<script>
|
||||
import { computed } from '@vue/reactivity';
|
||||
import { t } from 'fyo';
|
||||
import { getDocStatus } from 'models/helpers';
|
||||
import { ModelNameEnum } from 'models/types';
|
||||
import Button from 'src/components/Button.vue';
|
||||
import Barcode from 'src/components/Controls/Barcode.vue';
|
||||
import FormControl from 'src/components/Controls/FormControl.vue';
|
||||
import Table from 'src/components/Controls/Table.vue';
|
||||
import DropdownWithActions from 'src/components/DropdownWithActions.vue';
|
||||
import FormContainer from 'src/components/FormContainer.vue';
|
||||
import FormHeader from 'src/components/FormHeader.vue';
|
||||
import StatusBadge from 'src/components/StatusBadge.vue';
|
||||
import { fyo } from 'src/initFyo';
|
||||
import { docsPathMap } from 'src/utils/misc';
|
||||
import { docsPathRef, focusedDocsRef } from 'src/utils/refs';
|
||||
import {
|
||||
getGroupedActionsForDoc,
|
||||
routeTo,
|
||||
showMessageDialog,
|
||||
} from 'src/utils/ui';
|
||||
import { nextTick } from 'vue';
|
||||
import { handleErrorWithDialog } from '../errorHandling';
|
||||
import QuickEditForm from './QuickEditForm.vue';
|
||||
|
||||
export default {
|
||||
name: 'InvoiceForm',
|
||||
props: { schemaName: String, name: String },
|
||||
components: {
|
||||
StatusBadge,
|
||||
Button,
|
||||
FormControl,
|
||||
DropdownWithActions,
|
||||
Table,
|
||||
FormContainer,
|
||||
QuickEditForm,
|
||||
FormHeader,
|
||||
Barcode,
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
schemaName: this.schemaName,
|
||||
name: this.name,
|
||||
doc: computed(() => this.doc),
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chstatus: false,
|
||||
doc: null,
|
||||
quickEditDoc: null,
|
||||
quickEditFields: [],
|
||||
printSettings: null,
|
||||
};
|
||||
},
|
||||
updated() {
|
||||
this.chstatus = !this.chstatus;
|
||||
},
|
||||
computed: {
|
||||
status() {
|
||||
this.chstatus;
|
||||
return getDocStatus(this.doc);
|
||||
},
|
||||
groupedActions() {
|
||||
return getGroupedActionsForDoc(this.doc);
|
||||
},
|
||||
showBarcode() {
|
||||
if (!this.doc) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.doc.canEdit) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!fyo.singles.InventorySettings?.enableBarcodes) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return [
|
||||
ModelNameEnum.Shipment,
|
||||
ModelNameEnum.PurchaseReceipt,
|
||||
ModelNameEnum.StockMovement,
|
||||
].includes(this.schemaName);
|
||||
},
|
||||
},
|
||||
activated() {
|
||||
docsPathRef.value = docsPathMap[this.schemaName];
|
||||
focusedDocsRef.add(this.doc);
|
||||
},
|
||||
deactivated() {
|
||||
docsPathRef.value = '';
|
||||
focusedDocsRef.delete(this.doc);
|
||||
},
|
||||
async mounted() {
|
||||
try {
|
||||
this.doc = await fyo.doc.getDoc(this.schemaName, this.name);
|
||||
focusedDocsRef.add(this.doc);
|
||||
} catch (error) {
|
||||
if (error instanceof fyo.errors.NotFoundError) {
|
||||
routeTo(`/list/${this.schemaName}`);
|
||||
return;
|
||||
}
|
||||
await this.handleError(error);
|
||||
}
|
||||
|
||||
let query = this.$route.query;
|
||||
if (query.values && query.schemaName === this.schemaName) {
|
||||
this.doc.set(this.$router.currentRoute.value.query.values);
|
||||
}
|
||||
|
||||
if (fyo.store.isDevelopment) {
|
||||
window.frm = this;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
routeTo,
|
||||
async toggleQuickEditDoc(doc, fields = []) {
|
||||
if (this.quickEditDoc && doc) {
|
||||
this.quickEditDoc = null;
|
||||
this.quickEditFields = [];
|
||||
await nextTick();
|
||||
}
|
||||
|
||||
this.quickEditDoc = doc;
|
||||
this.quickEditFields = fields;
|
||||
},
|
||||
getField(fieldname) {
|
||||
return fyo.getField(this.schemaName, fieldname);
|
||||
},
|
||||
async sync() {
|
||||
try {
|
||||
await this.doc.sync();
|
||||
} catch (err) {
|
||||
await this.handleError(err);
|
||||
}
|
||||
},
|
||||
async submit() {
|
||||
const message = t`Submit ${this.doc.name}`;
|
||||
const ref = this;
|
||||
await showMessageDialog({
|
||||
message,
|
||||
buttons: [
|
||||
{
|
||||
label: t`Yes`,
|
||||
async action() {
|
||||
try {
|
||||
await ref.doc.submit();
|
||||
} catch (err) {
|
||||
await ref.handleError(err);
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t`No`,
|
||||
action() {},
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
async handleError(e) {
|
||||
await handleErrorWithDialog(e, this.doc);
|
||||
},
|
||||
formattedValue(fieldname, doc) {
|
||||
if (!doc) {
|
||||
doc = this.doc;
|
||||
}
|
||||
|
||||
const df = this.getField(fieldname);
|
||||
return fyo.format(doc[fieldname], df, doc);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
@ -1,269 +0,0 @@
|
||||
<template>
|
||||
<FormContainer>
|
||||
<!-- Page Header (Title, Buttons, etc) -->
|
||||
<template #header v-if="doc">
|
||||
<StatusBadge :status="status" />
|
||||
<DropdownWithActions
|
||||
v-for="group of groupedActions"
|
||||
:key="group.label"
|
||||
:type="group.type"
|
||||
:actions="group.actions"
|
||||
>
|
||||
<p v-if="group.group">
|
||||
{{ group.group }}
|
||||
</p>
|
||||
<feather-icon v-else name="more-horizontal" class="w-4 h-4" />
|
||||
</DropdownWithActions>
|
||||
<Button
|
||||
v-if="doc?.canSave"
|
||||
type="primary"
|
||||
class="text-white text-xs"
|
||||
@click="sync"
|
||||
>
|
||||
{{ t`Save` }}
|
||||
</Button>
|
||||
<Button
|
||||
v-else-if="doc.canSubmit"
|
||||
type="primary"
|
||||
class="text-white text-xs"
|
||||
@click="submit"
|
||||
>
|
||||
{{ t`Submit` }}
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
<!-- Journal Entry Form -->
|
||||
<template #body v-if="doc">
|
||||
<FormHeader
|
||||
:form-title="doc.notInserted ? t`New Entry` : doc.name"
|
||||
:form-sub-title="t`Journal Entry`"
|
||||
/>
|
||||
<hr />
|
||||
<div>
|
||||
<div class="m-4 grid grid-cols-3 gap-y-4 gap-x-4">
|
||||
<!-- First Column of Fields -->
|
||||
<FormControl
|
||||
:border="true"
|
||||
:df="getField('numberSeries')"
|
||||
:value="doc.numberSeries"
|
||||
@change="(value) => doc.set('numberSeries', value)"
|
||||
:read-only="!doc.notInserted || doc.submitted"
|
||||
/>
|
||||
<FormControl
|
||||
:border="true"
|
||||
:df="getField('date')"
|
||||
:value="doc.date"
|
||||
:placeholder="'Date'"
|
||||
@change="(value) => doc.set('date', value)"
|
||||
:read-only="doc.submitted"
|
||||
/>
|
||||
<FormControl
|
||||
:border="true"
|
||||
:df="getField('entryType')"
|
||||
:value="doc.entryType"
|
||||
placeholder="Entry Type"
|
||||
@change="(value) => doc.set('entryType', value)"
|
||||
:read-only="doc.submitted"
|
||||
/>
|
||||
<FormControl
|
||||
:border="true"
|
||||
:df="getField('referenceNumber')"
|
||||
:value="doc.referenceNumber"
|
||||
:placeholder="'Reference Number'"
|
||||
@change="(value) => doc.set('referenceNumber', value)"
|
||||
:read-only="doc.submitted"
|
||||
/>
|
||||
<FormControl
|
||||
:border="true"
|
||||
:df="getField('referenceDate')"
|
||||
:value="doc.referenceDate"
|
||||
:placeholder="'Reference Date'"
|
||||
@change="(value) => doc.set('referenceDate', value)"
|
||||
:read-only="doc.submitted"
|
||||
/>
|
||||
<FormControl
|
||||
v-if="doc.attachment || !(doc.isSubmitted || doc.isCancelled)"
|
||||
:border="true"
|
||||
:df="getField('attachment')"
|
||||
:value="doc.attachment"
|
||||
@change="(value) => doc.set('attachment', value)"
|
||||
:read-only="doc?.submitted"
|
||||
/>
|
||||
</div>
|
||||
<hr />
|
||||
|
||||
<!-- Account Entries Table -->
|
||||
<Table
|
||||
class="text-base"
|
||||
:df="getField('accounts')"
|
||||
:value="doc.accounts"
|
||||
:showHeader="true"
|
||||
:max-rows-before-overflow="6"
|
||||
:read-only="doc.submitted"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div v-if="doc.accounts?.length ?? 0" class="mt-auto">
|
||||
<hr />
|
||||
<div class="flex justify-between text-base m-4 gap-12">
|
||||
<!-- User Remark -->
|
||||
<FormControl
|
||||
:border="true"
|
||||
v-if="!doc.submitted || doc.userRemark"
|
||||
class="w-1/2 self-end"
|
||||
:df="getField('userRemark')"
|
||||
:value="doc.userRemark"
|
||||
@change="(value) => doc.set('userRemark', value)"
|
||||
:read-only="doc.submitted"
|
||||
/>
|
||||
|
||||
<!-- Debit and Credit -->
|
||||
<div class="w-1/2 gap-2 flex flex-col self-end font-semibold ms-auto">
|
||||
<div class="flex justify-between text-green-600">
|
||||
<div>{{ t`Total Debit` }}</div>
|
||||
<div>{{ totalDebit }}</div>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="flex justify-between text-red-600">
|
||||
<div>{{ t`Total Credit` }}</div>
|
||||
<div>{{ totalCredit }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</FormContainer>
|
||||
</template>
|
||||
<script>
|
||||
import { computed } from '@vue/reactivity';
|
||||
import { getDocStatus } from 'models/helpers';
|
||||
import { ModelNameEnum } from 'models/types';
|
||||
import Button from 'src/components/Button.vue';
|
||||
import FormControl from 'src/components/Controls/FormControl.vue';
|
||||
import Table from 'src/components/Controls/Table.vue';
|
||||
import DropdownWithActions from 'src/components/DropdownWithActions.vue';
|
||||
import FormContainer from 'src/components/FormContainer.vue';
|
||||
import FormHeader from 'src/components/FormHeader.vue';
|
||||
import StatusBadge from 'src/components/StatusBadge.vue';
|
||||
import { fyo } from 'src/initFyo';
|
||||
import { docsPathMap } from 'src/utils/misc';
|
||||
import { docsPathRef, focusedDocsRef } from 'src/utils/refs';
|
||||
import {
|
||||
getGroupedActionsForDoc,
|
||||
routeTo,
|
||||
showMessageDialog,
|
||||
} from 'src/utils/ui';
|
||||
import { handleErrorWithDialog } from '../errorHandling';
|
||||
|
||||
export default {
|
||||
name: 'JournalEntryForm',
|
||||
props: ['name'],
|
||||
components: {
|
||||
Button,
|
||||
DropdownWithActions,
|
||||
StatusBadge,
|
||||
FormControl,
|
||||
Table,
|
||||
FormContainer,
|
||||
FormHeader,
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
schemaName: this.schemaName,
|
||||
name: this.name,
|
||||
doc: computed(() => this.doc),
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
schemaName: ModelNameEnum.JournalEntry,
|
||||
doc: null,
|
||||
};
|
||||
},
|
||||
activated() {
|
||||
docsPathRef.value = docsPathMap.JournalEntry;
|
||||
focusedDocsRef.add(this.doc);
|
||||
},
|
||||
deactivated() {
|
||||
docsPathRef.value = '';
|
||||
focusedDocsRef.delete(this.doc);
|
||||
},
|
||||
async mounted() {
|
||||
try {
|
||||
this.doc = await fyo.doc.getDoc(this.schemaName, this.name);
|
||||
focusedDocsRef.add(this.doc);
|
||||
} catch (error) {
|
||||
if (error instanceof fyo.errors.NotFoundError) {
|
||||
routeTo(`/list/${this.schemaName}`);
|
||||
return;
|
||||
}
|
||||
|
||||
await this.handleError(error);
|
||||
}
|
||||
|
||||
if (fyo.store.isDevelopment) {
|
||||
window.je = this;
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
status() {
|
||||
return getDocStatus(this.doc);
|
||||
},
|
||||
totalDebit() {
|
||||
let value = 0;
|
||||
if (this.doc.accounts) {
|
||||
value = this.doc.getSum('accounts', 'debit');
|
||||
}
|
||||
return fyo.format(value, 'Currency');
|
||||
},
|
||||
totalCredit() {
|
||||
let value = 0;
|
||||
if (this.doc.accounts) {
|
||||
value = this.doc.getSum('accounts', 'credit');
|
||||
}
|
||||
return fyo.format(value, 'Currency');
|
||||
},
|
||||
groupedActions() {
|
||||
return getGroupedActionsForDoc(this.doc);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getField(fieldname) {
|
||||
return fyo.getField(ModelNameEnum.JournalEntry, fieldname);
|
||||
},
|
||||
async sync() {
|
||||
try {
|
||||
await this.doc.sync();
|
||||
} catch (err) {
|
||||
this.handleError(err);
|
||||
}
|
||||
},
|
||||
async submit() {
|
||||
const ref = this;
|
||||
await showMessageDialog({
|
||||
message: this.t`Submit Journal Entry?`,
|
||||
buttons: [
|
||||
{
|
||||
label: this.t`Yes`,
|
||||
async action() {
|
||||
try {
|
||||
await ref.doc.submit();
|
||||
} catch (err) {
|
||||
await ref.handleError(err);
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
label: this.t`No`,
|
||||
action() {},
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
async handleError(e) {
|
||||
await handleErrorWithDialog(e, this.doc);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
@ -9,7 +9,7 @@
|
||||
>
|
||||
<p class="w-8 text-end me-4 text-gray-700">#</p>
|
||||
<Row
|
||||
class="flex-1 text-gray-700 border-none h-row-mid"
|
||||
class="flex-1 text-gray-700 h-row-mid"
|
||||
:columnCount="columns.length"
|
||||
gap="1rem"
|
||||
>
|
||||
@ -45,7 +45,7 @@
|
||||
</p>
|
||||
<Row
|
||||
gap="1rem"
|
||||
class="cursor-pointer text-gray-900 flex-1 border-none h-row-mid"
|
||||
class="cursor-pointer text-gray-900 flex-1 h-row-mid"
|
||||
@click="$emit('openDoc', doc.name)"
|
||||
:columnCount="columns.length"
|
||||
>
|
||||
|
@ -1,11 +1,10 @@
|
||||
import { ModelNameEnum } from 'models/types';
|
||||
import ChartOfAccounts from 'src/pages/ChartOfAccounts.vue';
|
||||
import CommonForm from 'src/pages/CommonForm/CommonForm.vue';
|
||||
import Dashboard from 'src/pages/Dashboard/Dashboard.vue';
|
||||
import ImportWizard from 'src/pages/ImportWizard.vue';
|
||||
import GeneralForm from 'src/pages/GeneralForm.vue';
|
||||
import GetStarted from 'src/pages/GetStarted.vue';
|
||||
import ImportWizard from 'src/pages/ImportWizard.vue';
|
||||
import InvoiceForm from 'src/pages/InvoiceForm.vue';
|
||||
import JournalEntryForm from 'src/pages/JournalEntryForm.vue';
|
||||
import ListView from 'src/pages/ListView/ListView.vue';
|
||||
import PrintView from 'src/pages/PrintView/PrintView.vue';
|
||||
import QuickEditForm from 'src/pages/QuickEditForm.vue';
|
||||
@ -18,29 +17,34 @@ import {
|
||||
RouteRecordRaw,
|
||||
} from 'vue-router';
|
||||
|
||||
function getGeneralFormItems(): RouteRecordRaw[] {
|
||||
return [ModelNameEnum.Shipment, ModelNameEnum.PurchaseReceipt].map(
|
||||
(schemaName) => {
|
||||
return {
|
||||
path: `/edit/${schemaName}/:name`,
|
||||
name: `${schemaName}Form`,
|
||||
components: {
|
||||
default: GeneralForm,
|
||||
edit: QuickEditForm,
|
||||
function getCommonFormItems(): RouteRecordRaw[] {
|
||||
return [
|
||||
ModelNameEnum.Shipment,
|
||||
ModelNameEnum.PurchaseReceipt,
|
||||
ModelNameEnum.JournalEntry,
|
||||
ModelNameEnum.Payment,
|
||||
ModelNameEnum.StockMovement,
|
||||
ModelNameEnum.Item,
|
||||
].map((schemaName) => {
|
||||
return {
|
||||
path: `/edit/${schemaName}/:name`,
|
||||
name: `${schemaName}Form`,
|
||||
components: {
|
||||
default: CommonForm,
|
||||
edit: QuickEditForm,
|
||||
},
|
||||
props: {
|
||||
default: (route) => {
|
||||
route.params.schemaName = schemaName;
|
||||
return {
|
||||
schemaName,
|
||||
name: route.params.name,
|
||||
};
|
||||
},
|
||||
props: {
|
||||
default: (route) => {
|
||||
route.params.schemaName = schemaName;
|
||||
return {
|
||||
schemaName,
|
||||
name: route.params.name,
|
||||
};
|
||||
},
|
||||
edit: (route) => route.query,
|
||||
},
|
||||
};
|
||||
}
|
||||
);
|
||||
edit: (route) => route.query,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const routes: RouteRecordRaw[] = [
|
||||
@ -52,26 +56,7 @@ const routes: RouteRecordRaw[] = [
|
||||
path: '/get-started',
|
||||
component: GetStarted,
|
||||
},
|
||||
...getGeneralFormItems(),
|
||||
{
|
||||
path: '/edit/JournalEntry/:name',
|
||||
name: 'JournalEntryForm',
|
||||
components: {
|
||||
default: JournalEntryForm,
|
||||
edit: QuickEditForm,
|
||||
},
|
||||
props: {
|
||||
default: (route) => {
|
||||
// for sidebar item active state
|
||||
route.params.schemaName = 'JournalEntry';
|
||||
return {
|
||||
schemaName: 'JournalEntry',
|
||||
name: route.params.name,
|
||||
};
|
||||
},
|
||||
edit: (route) => route.query,
|
||||
},
|
||||
},
|
||||
...getCommonFormItems(),
|
||||
{
|
||||
path: '/edit/:schemaName/:name',
|
||||
name: 'InvoiceForm',
|
||||
@ -167,6 +152,9 @@ export function getCreateRoute(
|
||||
ModelNameEnum.JournalEntry,
|
||||
ModelNameEnum.Shipment,
|
||||
ModelNameEnum.PurchaseReceipt,
|
||||
ModelNameEnum.StockMovement,
|
||||
ModelNameEnum.Payment,
|
||||
ModelNameEnum.Item,
|
||||
].includes(schemaName as ModelNameEnum)
|
||||
) {
|
||||
return `/edit/${schemaName}/${name}`;
|
||||
|
@ -93,25 +93,16 @@ async function openFormEditDoc(schemaName: string, fyo: Fyo) {
|
||||
|
||||
function getCreateList(fyo: Fyo): SearchItem[] {
|
||||
const hasInventory = fyo.doc.singles.AccountingSettings?.enableInventory;
|
||||
const quickEditCreateList = [
|
||||
...(hasInventory ? [ModelNameEnum.StockMovement] : []),
|
||||
].map(
|
||||
(schemaName) =>
|
||||
({
|
||||
label: fyo.schemaMap[schemaName]?.label,
|
||||
group: 'Create',
|
||||
action() {
|
||||
openQuickEditDoc(schemaName, fyo);
|
||||
},
|
||||
} as SearchItem)
|
||||
);
|
||||
|
||||
const formEditCreateList = [
|
||||
ModelNameEnum.SalesInvoice,
|
||||
ModelNameEnum.PurchaseInvoice,
|
||||
ModelNameEnum.JournalEntry,
|
||||
...(hasInventory
|
||||
? [ModelNameEnum.Shipment, ModelNameEnum.PurchaseReceipt]
|
||||
? [
|
||||
ModelNameEnum.Shipment,
|
||||
ModelNameEnum.PurchaseReceipt,
|
||||
ModelNameEnum.StockMovement,
|
||||
]
|
||||
: []),
|
||||
].map(
|
||||
(schemaName) =>
|
||||
@ -197,7 +188,7 @@ function getCreateList(fyo: Fyo): SearchItem[] {
|
||||
} as SearchItem;
|
||||
});
|
||||
|
||||
return [quickEditCreateList, formEditCreateList, filteredCreateList].flat();
|
||||
return [formEditCreateList, filteredCreateList].flat();
|
||||
}
|
||||
|
||||
function getReportList(fyo: Fyo): SearchItem[] {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Doc } from "fyo/model/doc";
|
||||
import { FieldType } from "schemas/types";
|
||||
import { QueryFilter } from "utils/db/types";
|
||||
import type { Doc } from 'fyo/model/doc';
|
||||
import type { Action } from 'fyo/model/types';
|
||||
import type { Field, FieldType } from 'schemas/types';
|
||||
import type { QueryFilter } from 'utils/db/types';
|
||||
|
||||
export interface MessageDialogButton {
|
||||
label: string;
|
||||
@ -31,7 +32,7 @@ export interface QuickEditOptions {
|
||||
hideFields?: string[];
|
||||
showFields?: string[];
|
||||
defaults?: Record<string, unknown>;
|
||||
listFilters?: QueryFilter
|
||||
listFilters?: QueryFilter;
|
||||
}
|
||||
|
||||
export type SidebarConfig = SidebarRoot[];
|
||||
@ -44,7 +45,7 @@ export interface SidebarRoot {
|
||||
iconHeight?: string;
|
||||
hidden?: () => boolean;
|
||||
items?: SidebarItem[];
|
||||
filters?: QueryFilter
|
||||
filters?: QueryFilter;
|
||||
}
|
||||
|
||||
export interface SidebarItem {
|
||||
@ -55,7 +56,6 @@ export interface SidebarItem {
|
||||
hidden?: () => boolean;
|
||||
}
|
||||
|
||||
|
||||
export interface ExportField {
|
||||
fieldname: string;
|
||||
fieldtype: FieldType;
|
||||
@ -70,5 +70,13 @@ export interface ExportTableField {
|
||||
fields: ExportField[];
|
||||
}
|
||||
|
||||
export type ActionGroup = {
|
||||
group: string;
|
||||
label: string;
|
||||
type: string;
|
||||
actions: Action[];
|
||||
};
|
||||
|
||||
export type UIGroupedFields = Map<string, Map<string, Field[]>>;
|
||||
export type ExportFormat = 'csv' | 'json';
|
||||
export type PeriodKey = 'This Year' | 'This Quarter' | 'This Month'
|
||||
export type PeriodKey = 'This Year' | 'This Quarter' | 'This Month';
|
||||
|
@ -9,6 +9,7 @@ import { Action } from 'fyo/model/types';
|
||||
import { getActions } from 'fyo/utils';
|
||||
import { getDbError, LinkValidationError, ValueError } from 'fyo/utils/errors';
|
||||
import { ModelNameEnum } from 'models/types';
|
||||
import { Schema } from 'schemas/types';
|
||||
import { handleErrorWithDialog } from 'src/errorHandling';
|
||||
import { fyo } from 'src/initFyo';
|
||||
import router from 'src/router';
|
||||
@ -16,11 +17,14 @@ import { IPC_ACTIONS } from 'utils/messages';
|
||||
import { App, createApp, h, ref } from 'vue';
|
||||
import { RouteLocationRaw } from 'vue-router';
|
||||
import { stringifyCircular } from './';
|
||||
import { evaluateHidden } from './doc';
|
||||
import {
|
||||
ActionGroup,
|
||||
MessageDialogOptions,
|
||||
QuickEditOptions,
|
||||
SettingsTab,
|
||||
ToastOptions,
|
||||
UIGroupedFields,
|
||||
} from './types';
|
||||
|
||||
export async function openQuickEdit({
|
||||
@ -70,6 +74,7 @@ export async function openQuickEdit({
|
||||
if (forWhat[0] === 'not in' && forWhat[1] === 'Sales') {
|
||||
defaults = Object.assign({ for: 'Purchases' });
|
||||
}
|
||||
console.log(method, schemaName, name);
|
||||
|
||||
router[method]({
|
||||
query: {
|
||||
@ -274,14 +279,7 @@ export function getActionsForDoc(doc?: Doc): Action[] {
|
||||
});
|
||||
}
|
||||
|
||||
export function getGroupedActionsForDoc(doc?: Doc) {
|
||||
type Group = {
|
||||
group: string;
|
||||
label: string;
|
||||
type: string;
|
||||
actions: Action[];
|
||||
};
|
||||
|
||||
export function getGroupedActionsForDoc(doc?: Doc): ActionGroup[] {
|
||||
const actions = getActionsForDoc(doc);
|
||||
const actionsMap = actions.reduce((acc, ac) => {
|
||||
if (!ac.group) {
|
||||
@ -297,7 +295,7 @@ export function getGroupedActionsForDoc(doc?: Doc) {
|
||||
|
||||
acc[ac.group].actions.push(ac);
|
||||
return acc;
|
||||
}, {} as Record<string, Group>);
|
||||
}, {} as Record<string, ActionGroup>);
|
||||
|
||||
const grouped = Object.keys(actionsMap)
|
||||
.filter(Boolean)
|
||||
@ -393,14 +391,33 @@ function getDuplicateAction(doc: Doc): Action {
|
||||
};
|
||||
}
|
||||
|
||||
export function getStatus(entry: { cancelled?: boolean; submitted?: boolean }) {
|
||||
if (entry.cancelled) {
|
||||
return 'Cancelled';
|
||||
export function getFieldsGroupedByTabAndSection(
|
||||
schema: Schema,
|
||||
doc: Doc
|
||||
): UIGroupedFields {
|
||||
const grouped: UIGroupedFields = new Map();
|
||||
for (const field of schema?.fields ?? []) {
|
||||
const tab = field.tab ?? 'Default';
|
||||
const section = field.section ?? 'Default';
|
||||
if (!grouped.has(tab)) {
|
||||
grouped.set(tab, new Map());
|
||||
}
|
||||
|
||||
const tabbed = grouped.get(tab)!;
|
||||
if (!tabbed.has(section)) {
|
||||
tabbed.set(section, []);
|
||||
}
|
||||
|
||||
if (field.meta) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (evaluateHidden(field, doc)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
tabbed.get(section)!.push(field);
|
||||
}
|
||||
|
||||
if (entry.submitted) {
|
||||
return 'Submitted';
|
||||
}
|
||||
|
||||
return 'Saved';
|
||||
return grouped;
|
||||
}
|
||||
|
@ -2,7 +2,13 @@
|
||||
* Properties of a schema which are to be translated,
|
||||
* irrespective of nesting.
|
||||
*/
|
||||
export const schemaTranslateables = ['label', 'description', 'placeholder'];
|
||||
export const schemaTranslateables = [
|
||||
'label',
|
||||
'description',
|
||||
'placeholder',
|
||||
'section',
|
||||
'tab',
|
||||
];
|
||||
|
||||
export function getIndexFormat(inp: string | string[]) {
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user