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

incr: search improvements

- get form to render
- minor ui fixes
This commit is contained in:
18alantom 2022-04-29 18:30:24 +05:30
parent ecfae41635
commit 3e466c647c
40 changed files with 679 additions and 397 deletions

View File

@ -31,7 +31,6 @@ import {
Action,
CurrenciesMap,
DefaultMap,
DependsOnMap,
EmptyMessageMap,
FiltersMap,
FormulaMap,
@ -42,7 +41,7 @@ import {
ReadOnlyMap,
RequiredMap,
TreeViewSettings,
ValidationMap
ValidationMap,
} from './types';
import { validateOptions, validateRequired } from './validationFunction';
@ -583,7 +582,7 @@ export class Doc extends Observable<DocValue | Doc[]> {
}
async _getValueFromFormula(field: Field, doc: Doc) {
const formula = doc.formulas[field.fieldname];
const { formula } = doc.formulas[field.fieldname] ?? {};
if (formula === undefined) {
return;
}
@ -594,6 +593,7 @@ export class Doc extends Observable<DocValue | Doc[]> {
} catch {
return;
}
if (Array.isArray(value) && field.fieldtype === FieldTypeEnum.Table) {
value = value.map((row) => this._getChildDoc(row, field.fieldname));
}
@ -796,7 +796,6 @@ export class Doc extends Observable<DocValue | Doc[]> {
required: RequiredMap = {};
hidden: HiddenMap = {};
readOnly: ReadOnlyMap = {};
dependsOn: DependsOnMap = {};
getCurrencies: CurrenciesMap = {};
static lists: ListsMap = {};

View File

@ -92,7 +92,15 @@ export function shouldApplyFormula(field: Field, doc: Doc, fieldname?: string) {
return true;
}
const dependsOn = doc.dependsOn[field.fieldname] ?? [];
const { dependsOn } = doc.formulas[field.fieldname] ?? {};
if (dependsOn === undefined) {
return true;
}
if (dependsOn.length === 0) {
return false;
}
if (fieldname && dependsOn.includes(fieldname)) {
return true;
}

View File

@ -28,7 +28,10 @@ export function isNameAutoSet(schemaName: string, fyo: Fyo): boolean {
}
export async function setName(doc: Doc, fyo: Fyo) {
// if is server, always name again if autoincrement or other
if (doc.schema.naming === 'manual') {
return;
}
if (doc.schema.naming === 'autoincrement') {
doc.name = await getNextId(doc.schemaName, fyo);
return;

View File

@ -19,6 +19,7 @@ import { Doc } from './doc';
*/
export type FormulaReturn = DocValue | DocValueMap[] | undefined | Doc[];
export type Formula = () => Promise<FormulaReturn> | FormulaReturn;
export type FormulaConfig = { dependsOn?: string[]; formula: Formula };
export type Default = () => DocValue;
export type Validation = (value: DocValue) => Promise<void> | void;
export type Required = () => boolean;
@ -26,14 +27,13 @@ export type Hidden = () => boolean;
export type ReadOnly = () => boolean;
export type GetCurrency = () => string;
export type FormulaMap = Record<string, Formula | undefined>;
export type FormulaMap = Record<string, FormulaConfig | undefined>;
export type DefaultMap = Record<string, Default | undefined>;
export type ValidationMap = Record<string, Validation | undefined>;
export type RequiredMap = Record<string, Required | undefined>;
export type CurrenciesMap = Record<string, GetCurrency | undefined>;
export type HiddenMap = Record<string, Hidden | undefined>;
export type ReadOnlyMap = Record<string, ReadOnly | undefined>;
export type DependsOnMap = Record<string, string[]>;
/**
* Should add this for hidden too

View File

@ -1,31 +1,35 @@
export type AccountType =
| 'Accumulated Depreciation'
| 'Bank'
| 'Cash'
| 'Chargeable'
| 'Cost of Goods Sold'
| 'Depreciation'
| 'Equity'
| 'Expense Account'
| 'Expenses Included In Valuation'
| 'Fixed Asset'
| 'Income Account'
| 'Payable'
| 'Receivable'
| 'Round Off'
| 'Stock'
| 'Stock Adjustment'
| 'Stock Received But Not Billed'
| 'Tax'
| 'Temporary';
export enum AccountRootTypeEnum {
'Asset'='Asset',
'Liability'='Liability',
'Equity'='Equity',
'Income'='Income',
'Expense'='Expense',
}
export type AccountRootType =
| 'Asset'
| 'Liability'
| 'Equity'
| 'Income'
| 'Expense';
export enum AccountTypeEnum {
'Accumulated Depreciation' = 'Accumulated Depreciation',
'Bank' = 'Bank',
'Cash' = 'Cash',
'Chargeable' = 'Chargeable',
'Cost of Goods Sold' = 'Cost of Goods Sold',
'Depreciation' = 'Depreciation',
'Equity' = 'Equity',
'Expense Account' = 'Expense Account',
'Expenses Included In Valuation' = 'Expenses Included In Valuation',
'Fixed Asset' = 'Fixed Asset',
'Income Account' = 'Income Account',
'Payable' = 'Payable',
'Receivable' = 'Receivable',
'Round Off' = 'Round Off',
'Stock' = 'Stock',
'Stock Adjustment' = 'Stock Adjustment',
'Stock Received But Not Billed' = 'Stock Received But Not Billed',
'Tax' = 'Tax',
'Temporary' = 'Temporary',
}
export type AccountType = keyof typeof AccountTypeEnum;
export type AccountRootType = keyof typeof AccountRootTypeEnum
export interface COARootAccount {
rootType: AccountRootType;

View File

@ -1,42 +1,36 @@
import { t } from 'fyo';
import { Doc } from 'fyo/model/doc';
import {
DependsOnMap,
EmptyMessageMap,
FormulaMap,
ListsMap,
} from 'fyo/model/types';
import { EmptyMessageMap, FormulaMap, ListsMap } from 'fyo/model/types';
import { stateCodeMap } from 'regional/in';
import { titleCase } from 'utils';
import { getCountryInfo } from 'utils/misc';
export class Address extends Doc {
formulas: FormulaMap = {
addressDisplay: async () => {
return [
this.addressLine1,
this.addressLine2,
this.city,
this.state,
this.country,
this.postalCode,
]
.filter(Boolean)
.join(', ');
addressDisplay: {
formula: async () => {
return [
this.addressLine1,
this.addressLine2,
this.city,
this.state,
this.country,
this.postalCode,
]
.filter(Boolean)
.join(', ');
},
dependsOn: [
'addressLine1',
'addressLine2',
'city',
'state',
'country',
'postalCode',
],
},
};
dependsOn: DependsOnMap = {
addressDisplay: [
'addressLine1',
'addressLine2',
'city',
'state',
'country',
'postalCode',
],
};
static lists: ListsMap = {
state(doc?: Doc) {
const country = doc?.country as string | undefined;

View File

@ -162,24 +162,35 @@ export abstract class Invoice extends Doc {
}
formulas: FormulaMap = {
account: async () =>
this.getFrom('Party', this.party!, 'defaultAccount') as string,
currency: async () =>
(this.getFrom('Party', this.party!, 'currency') as string) ||
(this.fyo.singles.AccountingSettings!.currency as string),
exchangeRate: async () => await this.getExchangeRate(),
netTotal: async () => this.getSum('items', 'amount', false),
baseNetTotal: async () => this.netTotal!.mul(this.exchangeRate!),
taxes: async () => await this.getTaxSummary(),
grandTotal: async () => await this.getGrandTotal(),
baseGrandTotal: async () =>
(this.grandTotal as Money).mul(this.exchangeRate!),
outstandingAmount: async () => {
if (this.submitted) {
return;
}
account: {
formula: async () =>
this.getFrom('Party', this.party!, 'defaultAccount') as string,
dependsOn: ['party'],
},
currency: {
formula: async () =>
(this.getFrom('Party', this.party!, 'currency') as string) ||
(this.fyo.singles.AccountingSettings!.currency as string),
dependsOn: ['party'],
},
exchangeRate: { formula: async () => await this.getExchangeRate() },
netTotal: { formula: async () => this.getSum('items', 'amount', false) },
baseNetTotal: {
formula: async () => this.netTotal!.mul(this.exchangeRate!),
},
taxes: { formula: async () => await this.getTaxSummary() },
grandTotal: { formula: async () => await this.getGrandTotal() },
baseGrandTotal: {
formula: async () => (this.grandTotal as Money).mul(this.exchangeRate!),
},
outstandingAmount: {
formula: async () => {
if (this.submitted) {
return;
}
return this.baseGrandTotal!;
return this.baseGrandTotal!;
},
},
};

View File

@ -1,11 +1,6 @@
import { DocValue } from 'fyo/core/types';
import { Doc } from 'fyo/model/doc';
import {
DependsOnMap,
FiltersMap,
FormulaMap,
ValidationMap,
} from 'fyo/model/types';
import { FiltersMap, FormulaMap, ValidationMap } from 'fyo/model/types';
import { ValidationError } from 'fyo/utils/errors';
import Money from 'pesa/dist/types/src/money';
import { Invoice } from '../Invoice/Invoice';
@ -21,50 +16,74 @@ export abstract class InvoiceItem extends Doc {
}
formulas: FormulaMap = {
description: () =>
this.parentdoc!.getFrom(
'Item',
this.item as string,
'description'
) as string,
rate: async () => {
const baseRate = ((await this.parentdoc!.getFrom(
'Item',
this.item as string,
'rate'
)) || this.fyo.pesa(0)) as Money;
return baseRate.div(this.exchangeRate!);
description: {
formula: () =>
this.parentdoc!.getFrom(
'Item',
this.item as string,
'description'
) as string,
dependsOn: ['item'],
},
baseRate: () =>
(this.rate as Money).mul(this.parentdoc!.exchangeRate as number),
account: () => {
let accountType = 'expenseAccount';
if (this.isSales) {
accountType = 'incomeAccount';
}
return this.parentdoc!.getFrom('Item', this.item as string, accountType);
},
tax: () => {
if (this.tax) {
return this.tax as string;
}
rate: {
formula: async () => {
const baseRate = ((await this.parentdoc!.getFrom(
'Item',
this.item as string,
'rate'
)) || this.fyo.pesa(0)) as Money;
return this.parentdoc!.getFrom(
'Item',
this.item as string,
'tax'
) as string;
return baseRate.div(this.exchangeRate!);
},
dependsOn: ['item'],
},
amount: () => (this.rate as Money).mul(this.quantity as number),
baseAmount: () =>
(this.amount as Money).mul(this.parentdoc!.exchangeRate as number),
hsnCode: () =>
this.parentdoc!.getFrom('Item', this.item as string, 'hsnCode'),
};
baseRate: {
formula: () =>
(this.rate as Money).mul(this.parentdoc!.exchangeRate as number),
dependsOn: ['item', 'rate'],
},
account: {
formula: () => {
let accountType = 'expenseAccount';
if (this.isSales) {
accountType = 'incomeAccount';
}
return this.parentdoc!.getFrom(
'Item',
this.item as string,
accountType
);
},
dependsOn: ['item'],
},
tax: {
formula: () => {
if (this.tax) {
return this.tax as string;
}
dependsOn: DependsOnMap = {
hsnCode: ['item'],
return this.parentdoc!.getFrom(
'Item',
this.item as string,
'tax'
) as string;
},
dependsOn: ['item'],
},
amount: {
formula: () => (this.rate as Money).mul(this.quantity as number),
dependsOn: ['item', 'rate', 'quantity'],
},
baseAmount: {
formula: () =>
(this.amount as Money).mul(this.parentdoc!.exchangeRate as number),
dependsOn: ['item', 'amount', 'rate', 'quantity'],
},
hsnCode: {
formula: () =>
this.parentdoc!.getFrom('Item', this.item as string, 'hsnCode'),
dependsOn: ['item'],
},
};
validations: ValidationMap = {

View File

@ -3,7 +3,6 @@ import { DocValue } from 'fyo/core/types';
import { Doc } from 'fyo/model/doc';
import {
Action,
DependsOnMap,
FiltersMap,
FormulaMap,
ListViewSettings,
@ -11,49 +10,51 @@ import {
} from 'fyo/model/types';
import { ValidationError } from 'fyo/utils/errors';
import Money from 'pesa/dist/types/src/money';
import { AccountRootTypeEnum, AccountTypeEnum } from '../Account/types';
export class Item extends Doc {
formulas: FormulaMap = {
incomeAccount: async () => {
let accountName = 'Service';
if (this.itemType === 'Product') {
accountName = 'Sales';
}
incomeAccount: {
formula: async () => {
let accountName = 'Service';
if (this.itemType === 'Product') {
accountName = 'Sales';
}
const accountExists = await this.fyo.db.exists('Account', accountName);
return accountExists ? accountName : '';
const accountExists = await this.fyo.db.exists('Account', accountName);
return accountExists ? accountName : '';
},
dependsOn: ['itemType'],
},
expenseAccount: async () => {
const cogs = await this.fyo.db.getAllRaw('Account', {
filters: {
accountType: 'Cost of Goods Sold',
},
});
expenseAccount: {
formula: async () => {
const cogs = await this.fyo.db.getAllRaw('Account', {
filters: {
accountType: AccountTypeEnum['Cost of Goods Sold'],
},
});
if (cogs.length === 0) {
return '';
} else {
return cogs[0].name as string;
}
if (cogs.length === 0) {
return '';
} else {
return cogs[0].name as string;
}
},
dependsOn: ['itemType'],
},
};
static filters: FiltersMap = {
incomeAccount: () => ({
isGroup: false,
rootType: 'Income',
rootType: AccountRootTypeEnum.Income,
}),
expenseAccount: () => ({
isGroup: false,
rootType: 'Expense',
rootType: AccountRootTypeEnum.Expense,
}),
};
dependsOn: DependsOnMap = {
incomeAccount: ['itemType'],
expenseAccount: ['itemType'],
};
validations: ValidationMap = {
rate: async (value: DocValue) => {
if ((value as Money).isNegative()) {

View File

@ -24,8 +24,14 @@ export class JournalEntryAccount extends Doc {
}
formulas: FormulaMap = {
debit: async () => this.getAutoDebitCredit('debit'),
credit: async () => this.getAutoDebitCredit('credit'),
debit: {
formula: async () => this.getAutoDebitCredit('debit'),
dependsOn: ['credit'],
},
credit: {
formula: async () => this.getAutoDebitCredit('credit'),
dependsOn: ['debit'],
},
};
static filters: FiltersMap = {

View File

@ -51,28 +51,35 @@ export class Party extends Doc {
}
formulas: FormulaMap = {
defaultAccount: async () => {
const role = this.role as PartyRole;
if (role === 'Both') {
return '';
}
defaultAccount: {
formula: async () => {
const role = this.role as PartyRole;
if (role === 'Both') {
return '';
}
let accountName = 'Debtors';
if (role === 'Supplier') {
accountName = 'Creditors';
}
let accountName = 'Debtors';
if (role === 'Supplier') {
accountName = 'Creditors';
}
const accountExists = await this.fyo.db.exists('Account', accountName);
return accountExists ? accountName : '';
const accountExists = await this.fyo.db.exists('Account', accountName);
return accountExists ? accountName : '';
},
dependsOn: ['role'],
},
currency: async () =>
this.fyo.singles.AccountingSettings!.currency as string,
address: async () => {
const address = this.address as string | undefined;
if (address) {
return this.getFrom('Address', address, 'addressDisplay') as string;
}
return '';
currency: {
formula: async () =>
this.fyo.singles.AccountingSettings!.currency as string,
},
address: {
formula: async () => {
const address = this.address as string | undefined;
if (address) {
return this.getFrom('Address', address, 'addressDisplay') as string;
}
return '';
},
},
};

View File

@ -38,7 +38,8 @@ export class Payment extends Doc {
async updateDetailsOnReferenceUpdate() {
const forReferences = (this.for ?? []) as Doc[];
const { referenceType, referenceName } = forReferences[0];
const { referenceType, referenceName } = forReferences[0] ?? {};
if (
forReferences.length !== 1 ||
this.party ||
@ -284,17 +285,26 @@ export class Payment extends Doc {
static defaults: DefaultMap = { date: () => new Date().toISOString() };
formulas: FormulaMap = {
account: async () => {
if (this.paymentMethod === 'Cash' && this.paymentType === 'Pay') {
return 'Cash';
}
account: {
formula: async () => {
if (this.paymentMethod === 'Cash' && this.paymentType === 'Pay') {
return 'Cash';
}
},
dependsOn: ['paymentMethod', 'paymentType'],
},
paymentAccount: async () => {
if (this.paymentMethod === 'Cash' && this.paymentType === 'Receive') {
return 'Cash';
}
paymentAccount: {
formula: async () => {
if (this.paymentMethod === 'Cash' && this.paymentType === 'Receive') {
return 'Cash';
}
},
dependsOn: ['paymentMethod', 'paymentType'],
},
amount: {
formula: async () => this.getSum('for', 'amount', false),
dependsOn: ['for'],
},
amount: async () => this.getSum('for', 'amount', false),
};
validations: ValidationMap = {

View File

@ -4,18 +4,25 @@ import Money from 'pesa/dist/types/src/money';
export class PaymentFor extends Doc {
formulas: FormulaMap = {
amount: async () => {
const outstandingAmount = this.parentdoc!.getFrom(
this.referenceType as string,
this.referenceName as string,
'outstandingAmount'
) as Money;
amount: {
formula: async () => {
if (!this.referenceName) {
return this.fyo.pesa(0);
}
if (outstandingAmount) {
return outstandingAmount;
}
const outstandingAmount = this.parentdoc!.getFrom(
this.referenceType as string,
this.referenceName as string,
'outstandingAmount'
) as Money;
return this.fyo.pesa(0);
if (outstandingAmount) {
return outstandingAmount;
}
return this.fyo.pesa(0);
},
dependsOn: ['referenceName'],
},
};

View File

@ -1,11 +1,6 @@
import { t } from 'fyo';
import { Doc } from 'fyo/model/doc';
import {
DependsOnMap,
FormulaMap,
ListsMap,
ValidationMap,
} from 'fyo/model/types';
import { FormulaMap, ListsMap, ValidationMap } from 'fyo/model/types';
import { validateEmail } from 'fyo/model/validationFunction';
import { getCountryInfo, getFiscalYear } from 'utils/misc';
@ -30,57 +25,64 @@ export function getCOAList() {
}
export class SetupWizard extends Doc {
dependsOn: DependsOnMap = {
fiscalYearStart: ['country'],
fiscalYearEnd: ['country'],
currency: ['country'],
chartOfAccounts: ['country'],
};
formulas: FormulaMap = {
fiscalYearStart: async () => {
if (!this.country) return;
fiscalYearStart: {
formula: async () => {
if (!this.country) return;
const countryInfo = getCountryInfo();
const fyStart =
countryInfo[this.country as string]?.fiscal_year_start ?? '';
return getFiscalYear(fyStart, true);
const countryInfo = getCountryInfo();
const fyStart =
countryInfo[this.country as string]?.fiscal_year_start ?? '';
return getFiscalYear(fyStart, true);
},
dependsOn: ['country'],
},
fiscalYearEnd: async () => {
if (!this.country) {
return;
}
fiscalYearEnd: {
formula: async () => {
if (!this.country) {
return;
}
const countryInfo = getCountryInfo();
const fyEnd = countryInfo[this.country as string]?.fiscal_year_end ?? '';
return getFiscalYear(fyEnd, false);
const countryInfo = getCountryInfo();
const fyEnd =
countryInfo[this.country as string]?.fiscal_year_end ?? '';
return getFiscalYear(fyEnd, false);
},
dependsOn: ['country'],
},
currency: async () => {
if (!this.country) {
return;
}
const countryInfo = getCountryInfo();
return countryInfo[this.country as string]?.currency;
currency: {
formula: async () => {
if (!this.country) {
return;
}
const countryInfo = getCountryInfo();
return countryInfo[this.country as string]?.currency;
},
dependsOn: ['country'],
},
chartOfAccounts: async () => {
const country = this.get('country') as string | undefined;
if (country === undefined) {
return;
}
chartOfAccounts: {
formula: async () => {
const country = this.get('country') as string | undefined;
if (country === undefined) {
return;
}
const countryInfo = getCountryInfo();
const code = (countryInfo[country] as undefined | { code: string })?.code;
if (code === undefined) {
return;
}
const countryInfo = getCountryInfo();
const code = (countryInfo[country] as undefined | { code: string })
?.code;
if (code === undefined) {
return;
}
const coaList = getCOAList();
const coa = coaList.find(({ countryCode }) => countryCode === code);
const coaList = getCOAList();
const coa = coaList.find(({ countryCode }) => countryCode === code);
if (coa === undefined) {
return coaList[0].name;
}
return coa.name;
if (coa === undefined) {
return coaList[0].name;
}
return coa.name;
},
dependsOn: ['country'],
},
};

View File

@ -9,10 +9,13 @@ export class TaxSummary extends Doc {
baseAmount?: Money;
formulas: FormulaMap = {
baseAmount: async () => {
const amount = this.amount as Money;
const exchangeRate = (this.parentdoc?.exchangeRate ?? 1) as number;
return amount.mul(exchangeRate);
baseAmount: {
formula: async () => {
const amount = this.amount as Money;
const exchangeRate = (this.parentdoc?.exchangeRate ?? 1) as number;
return amount.mul(exchangeRate);
},
dependsOn: ['amount'],
},
};
}

View File

@ -5,26 +5,39 @@ import { titleCase } from 'utils';
export class Address extends BaseAddress {
formulas: FormulaMap = {
addressDisplay: async () => {
return [
this.addressLine1,
this.addressLine2,
this.city,
this.state,
this.country,
this.postalCode,
]
.filter(Boolean)
.join(', ');
addressDisplay: {
formula: async () => {
return [
this.addressLine1,
this.addressLine2,
this.city,
this.state,
this.country,
this.postalCode,
]
.filter(Boolean)
.join(', ');
},
dependsOn: [
'addressLine1',
'addressLine2',
'city',
'state',
'country',
'postalCode',
],
},
pos: async () => {
const stateList = Object.keys(stateCodeMap).map(titleCase).sort();
const state = this.state as string;
if (stateList.includes(state)) {
return state;
}
return '';
pos: {
formula: async () => {
const stateList = Object.keys(stateCodeMap).map(titleCase).sort();
const state = this.state as string;
if (stateList.includes(state)) {
return state;
}
return '';
},
dependsOn: ['state'],
},
};

View File

@ -18,4 +18,9 @@
// 'gstr-2': GoodsAndServiceTaxGSTR2View,
// };
export default {};
interface ReportView {
title: string;
method: string;
}
export default {} as Record<string, ReportView>;

View File

@ -2,6 +2,7 @@
"name": "Item",
"label": "Item",
"isSingle": false,
"naming": "manual",
"fields": [
{
"fieldname": "name",
@ -30,11 +31,11 @@
"options": [
{
"value": "Unit",
"name": "Unit"
"label": "Unit"
},
{
"value": "Kg",
"name": "Kg"
"label": "Kg"
},
{
"value": "Gram",

View File

@ -59,6 +59,6 @@
"readOnly": true
}
],
"quickEditFields": ["start", "padZeros", "referenceType"],
"quickEditFields": ["referenceType", "start", "padZeros"],
"keywordFields": []
}

View File

@ -1,6 +1,7 @@
{
"name": "Party",
"label": "Party",
"naming": "manual",
"fields": [
{
"fieldname": "name",
@ -33,6 +34,7 @@
"label": "Customer"
}
],
"readOnly": true,
"required": true
},
{
@ -85,6 +87,7 @@
"address",
"defaultAccount",
"currency",
"role",
"taxId"
],
"keywordFields": ["name"]

View File

@ -6,7 +6,7 @@
"fields": [
{
"fieldname": "referenceType",
"label": "Reference Type",
"label": "Type",
"placeholder": "Type",
"fieldtype": "Select",
"options": [
@ -23,7 +23,7 @@
},
{
"fieldname": "referenceName",
"label": "Reference Name",
"label": "Name",
"fieldtype": "DynamicLink",
"references": "referenceType",
"placeholder": "Name",
@ -31,9 +31,10 @@
},
{
"fieldname": "amount",
"label": "Allocated Amount",
"label": "Amount",
"fieldtype": "Currency",
"required": true
}
]
],
"tableFields": ["referenceType", "referenceName", "amount"]
}

View File

@ -34,6 +34,7 @@
"address",
"defaultAccount",
"currency",
"role",
"gstType",
"gstin"
],

View File

@ -68,7 +68,7 @@ export type Field =
| DynamicLinkField
| NumberField;
export type Naming = 'autoincrement' | 'random' | 'numberSeries'
export type Naming = 'autoincrement' | 'random' | 'numberSeries' | 'manual';
export interface Schema {
name: string; // Table name

View File

@ -13,6 +13,18 @@ export default {
if (this.value) {
this.linkValue = this.value;
}
if (this.df.fieldname === 'incomeAccount') {
window.l = this;
}
},
watch: {
value: {
immediate: true,
handler(newValue) {
this.linkValue = newValue;
},
},
},
methods: {
async getSuggestions(keyword = '') {

View File

@ -14,7 +14,13 @@
:class="inputClasses"
>
<select
class="appearance-none bg-transparent focus:outline-none w-11/12 cursor-pointer"
class="
appearance-none
bg-transparent
focus:outline-none
w-11/12
cursor-pointer
"
:class="{
'pointer-events-none': isReadOnly,
'text-gray-400': !value,
@ -36,6 +42,7 @@
</option>
</select>
<svg
v-if="!isReadOnly"
class="w-3 h-3"
style="background: inherit; margin-right: -3px"
viewBox="0 0 5 10"

View File

@ -1,5 +1,5 @@
<template>
<div>
<div v-if="tableFields?.length">
<div class="text-gray-600 text-sm mb-1" v-if="showLabel">
{{ df.label }}
</div>
@ -90,6 +90,9 @@ export default {
TableRow,
},
inject: ['doc'],
mounted(){
window.tab = this
},
data: () => ({ rowContainerHeight: null }),
watch: {
value: {

View File

@ -1,3 +1,6 @@
<script setup>
const keys = useKeys();
</script>
<template>
<div v-on-outside-click="clearInput" class="relative">
<Dropdown :items="suggestions" class="text-sm h-full">
@ -13,21 +16,30 @@
class="rounded-md relative flex items-center overflow-hidden h-full"
>
<div class="absolute flex justify-center w-8">
<feather-icon name="search" class="w-3 h-3 text-gray-700" />
<feather-icon name="search" class="w-3 h-3 text-gray-800" />
</div>
<input
type="search"
class="bg-gray-200 text-sm pl-8 focus:outline-none h-full w-56 placeholder-gray-700"
class="
bg-gray-100
text-sm
pl-7
focus:outline-none
h-full
w-56
placeholder-gray-800
"
:placeholder="t`Search...`"
autocomplete="off"
spellcheck="false"
v-model="inputValue"
@input="
@focus="
() => {
search();
toggleDropdown(true);
}
"
@input="search"
ref="input"
@keydown.up="highlightItemUp"
@keydown.down="highlightItemDown"
@ -35,6 +47,12 @@
@keydown.tab="toggleDropdown(false)"
@keydown.esc="toggleDropdown(false)"
/>
<div
v-if="!inputValue"
class="absolute justify-center right-1.5 text-gray-500 px-1.5"
>
{{ platform === 'Mac' ? '⌘ K' : 'Ctrl K' }}
</div>
</div>
</template>
</Dropdown>
@ -42,10 +60,11 @@
</template>
<script>
import { t } from 'fyo';
import reports from 'reports/view';
import Dropdown from 'src/components/Dropdown';
import { fyo } from 'src/initFyo';
import { getSearchList } from 'src/utils/search';
import { routeTo } from 'src/utils/ui';
import { useKeys } from 'src/utils/vueUtils';
import { watch } from 'vue';
export default {
data() {
@ -61,6 +80,19 @@ export default {
emits: ['change'],
mounted() {
this.makeSearchList();
watch(this.keys, (keys) => {
if (
keys.size === 2 &&
keys.has('KeyK') &&
(keys.has('MetaLeft') || keys.has('ControlLeft'))
) {
this.$refs.input.focus();
}
if (keys.size === 1 && keys.has('Escape')) {
this.$refs.input.blur();
}
});
},
methods: {
async search() {
@ -78,56 +110,17 @@ export default {
this.$emit('change', null);
},
async makeSearchList() {
const schemas = this.getSearchableSchemas();
const reports = this.getReports();
const views = this.getViews();
let searchList = [...schemas, ...reports, ...views];
const searchList = getSearchList();
this.searchList = searchList.map((d) => {
if (d.route) {
d.action = () => routeTo(d.route);
this.inputValue = '';
if (d.route && !d.action) {
d.action = () => {
routeTo(d.route);
this.inputValue = '';
};
}
return d;
});
},
getSearchableSchemas() {
return Object.values(fyo.schemaMap)
.filter((s) => !s.isChild && !s.isSingle)
.map((s) => ({
label: s.label,
route: `/list/${s.name}`,
group: 'List',
}));
},
getReports() {
return Object.values(reports).map((report) => {
return {
label: report.title,
route: `/report/${report.method}`,
group: 'Reports',
};
});
},
getViews() {
return [
{
label: t`Chart of Accounts`,
route: '/chartOfAccounts',
group: 'Setup',
},
{
label: t`Data Import`,
route: '/data_import',
group: 'Setup',
},
{
label: t`Settings`,
route: '/settings',
group: 'Setup',
},
];
},
},
};
</script>

View File

@ -139,9 +139,6 @@ export default {
insertingAccount: false,
};
},
mounted() {
this.fetchAccounts();
},
async activated() {
this.fetchAccounts();
if (fyo.store.isDevelopment) {

View File

@ -15,7 +15,7 @@
flex flex-col
justify-between
h-full
p-6
p-4
border
rounded-lg
cursor-pointer

View File

@ -13,7 +13,9 @@
:key="column.label"
class="py-4 truncate"
:class="
['Float', 'Currency'].includes(column.fieldtype) ? 'text-right' : ''
['Int', 'Float', 'Currency'].includes(column.fieldtype)
? 'text-right'
: ''
"
>
{{ column.label }}
@ -88,9 +90,16 @@ export default {
data: [],
};
},
mounted() {},
computed: {
columns() {
const columns = this.listConfig?.columns ?? [];
let columns = this.listConfig?.columns ?? [];
if (columns.length === 0) {
columns = fyo.schemaMap[this.schemaName].quickEditFields ?? [];
columns = [...new Set(['name', ...columns])];
}
return columns
.map((fieldname) => fyo.getField(this.schemaName, fieldname))
.filter(Boolean);
@ -123,6 +132,7 @@ export default {
routeTo(this.listConfig.formRoute(doc.name));
return;
}
openQuickEdit({
schemaName: this.schemaName,
name: doc.name,

View File

@ -12,16 +12,14 @@
</Button>
</template>
</PageHeader>
<div class="flex-1 flex h-full">
<List
ref="list"
:schemaName="schemaName"
:listConfig="listConfig"
:filters="filters"
class="flex-1"
@makeNewDoc="makeNewDoc"
/>
</div>
<List
ref="list"
:schemaName="schemaName"
:listConfig="listConfig"
:filters="filters"
class="flex-1 flex h-full"
@makeNewDoc="makeNewDoc"
/>
</div>
</template>
<script>

View File

@ -1,5 +1,5 @@
<template>
<div class="border-l h-full">
<div class="border-l h-full overflow-auto">
<!-- Quick edit Tool bar -->
<div class="flex items-center justify-between px-4 pt-4">
<!-- Close Button and Status Text -->
@ -27,7 +27,7 @@
</Button>
<Button
:icon="true"
@click="submitDoc"
@click="submit"
type="primary"
v-if="
schema?.isSubmittable &&
@ -43,30 +43,24 @@
</div>
<!-- Name and image -->
<div class="px-4 pt-2 pb-4 flex-center" v-if="doc">
<div class="flex flex-col items-center">
<FormControl
v-if="imageField"
:df="imageField"
:value="doc[imageField.fieldname]"
@change="(value) => valueChange(imageField, value)"
size="small"
class="mb-1"
:letter-placeholder="
// for AttachImage field
doc[titleField.fieldname] ? doc[titleField.fieldname][0] : null
"
/>
<FormControl
input-class="text-center"
ref="titleControl"
v-if="titleField"
:df="titleField"
:value="doc[titleField.fieldname]"
@change="(value) => valueChange(titleField, value)"
@input="setTitleSize"
/>
</div>
<div class="p-4 gap-2 flex-center flex flex-col items-center" v-if="doc">
<FormControl
v-if="imageField"
:df="imageField"
:value="doc[imageField.fieldname]"
@change="(value) => valueChange(imageField, value)"
size="small"
:letter-placeholder="doc[titleField.fieldname]?.[0] ?? null"
/>
<FormControl
input-class="text-center"
ref="titleControl"
v-if="titleField"
:df="titleField"
:value="doc[titleField.fieldname]"
@change="(value) => valueChange(titleField, value)"
@input="setTitleSize"
/>
</div>
<!-- Rest of the form -->
@ -229,41 +223,56 @@ export default {
}, 300);
},
async fetchDoc() {
try {
this.doc = await fyo.doc.getDoc(this.schemaName, this.name);
this.doc.once('afterRename', () => {
openQuickEdit({
schemaName: this.schemaName,
name: this.doc.name,
});
});
this.doc.on('beforeSync', () => {
this.statusText = t`Saving...`;
});
this.doc.on('afterSync', () => {
setTimeout(() => {
this.statusText = null;
}, 500);
});
} catch (e) {
if (!this.schemaName) {
this.$router.back();
}
if (this.name) {
try {
this.doc = await fyo.doc.getDoc(this.schemaName, this.name);
} catch (e) {
this.$router.back();
}
} else {
this.doc = fyo.doc.getNewDoc(this.schemaName);
}
if (this.doc === null) {
return;
}
this.doc.once('afterRename', () => {
openQuickEdit({
schemaName: this.schemaName,
name: this.doc.name,
});
});
},
valueChange(df, value) {
this.$refs.form.onChange(df, value);
},
sync() {
this.$refs.form.sync();
async sync() {
this.statusText = t`Saving`;
try {
await this.$refs.form.sync();
setTimeout(() => {
this.statusText = null;
}, 300);
} catch (err) {
this.statusText = null;
console.error(err);
}
},
async submitDoc() {
async submit() {
this.statusText = t`Submitting`;
try {
await this.$refs.form.submit();
} catch (e) {
setTimeout(() => {
this.statusText = null;
}, 300);
} catch (err) {
this.statusText = null;
console.error(e);
console.error(err);
}
},
routeToPrevious() {

123
src/utils/search.ts Normal file
View File

@ -0,0 +1,123 @@
import { t } from 'fyo';
import { ModelNameEnum } from 'models/types';
import reports from 'reports/view';
import { fyo } from 'src/initFyo';
import { routeTo } from './ui';
enum SearchGroupEnum {
'List' = 'List',
'Report' = 'Report',
'Create' = 'Create',
'Setup' = 'Setup',
}
type SearchGroup = keyof typeof SearchGroupEnum;
interface SearchItem {
label: string;
group: SearchGroup;
route?: string;
action?: () => void;
}
async function openNewDoc(schemaName: string) {
await routeTo(`/list/${schemaName}`);
const doc = await fyo.doc.getNewDoc(schemaName);
const { openQuickEdit } = await import('src/utils/ui');
await openQuickEdit({
schemaName,
name: doc.name as string,
});
}
function getCreateList(): SearchItem[] {
return [
{
label: t`Create Item`,
group: 'Create',
action() {
openNewDoc(ModelNameEnum.Item);
},
},
{
label: t`Create Party`,
group: 'Create',
action() {
openNewDoc(ModelNameEnum.Party);
},
},
{
label: t`Create Payment`,
group: 'Create',
action() {
openNewDoc(ModelNameEnum.Payment);
},
},
];
}
function getReportList(): SearchItem[] {
return Object.values(reports).map((report) => {
return {
label: report.title,
route: `/report/${report.method}`,
group: 'Report',
};
});
}
function getListViewList(): SearchItem[] {
return [
ModelNameEnum.Account,
ModelNameEnum.Party,
ModelNameEnum.Item,
ModelNameEnum.Payment,
ModelNameEnum.JournalEntry,
ModelNameEnum.PurchaseInvoice,
ModelNameEnum.SalesInvoice,
ModelNameEnum.Tax,
]
.map((s) => fyo.schemaMap[s])
.filter((s) => s && !s.isChild && !s.isSingle)
.map((s) => ({
label: s!.label,
route: `/list/${s!.name}`,
group: 'List',
}));
}
function getSetupList(): SearchItem[] {
return [
{
label: t`Chart of Accounts`,
route: '/chartOfAccounts',
group: 'Setup',
},
{
label: t`Data Import`,
route: '/data_import',
group: 'Setup',
},
{
label: t`Settings`,
route: '/settings',
group: 'Setup',
},
];
}
export function getSearchList() {
const group: Record<SearchGroup, string> = {
Create: t`Create`,
List: t`List`,
Report: t`Report`,
Setup: t`Setup`,
};
return [getListViewList(), getCreateList(), getReportList(), getSetupList()]
.flat()
.map((si) => ({
...si,
group: group[si.group],
}));
}

View File

@ -125,7 +125,7 @@ export function openSettings(tab: SettingsTab) {
routeTo({ path: '/settings', query: { tab } });
}
export function routeTo(route: string | RouteLocationRaw) {
export async function routeTo(route: string | RouteLocationRaw) {
let routeOptions = route;
if (
typeof route === 'string' &&
@ -138,7 +138,7 @@ export function routeTo(route: string | RouteLocationRaw) {
routeOptions = { path: route };
}
router.push(routeOptions);
await router.push(routeOptions);
}
export function deleteDocWithPrompt(doc: Doc) {
@ -292,6 +292,7 @@ function getDeleteAction(doc: Doc): Action {
}),
};
}
function getDuplicateAction(doc: Doc): Action {
const isSubmittable = !!doc.schema.isSubmittable;
return {

31
src/utils/vueUtils.ts Normal file
View File

@ -0,0 +1,31 @@
import { onMounted, onUnmounted, Ref, ref } from 'vue';
export function useKeys(callback?: (keys: Set<string>) => void) {
const keys: Ref<Set<string>> = ref(new Set());
const keydownListener = (e: KeyboardEvent) => {
keys.value.add(e.code);
callback?.(keys.value);
};
const keyupListener = (e: KeyboardEvent) => {
keys.value.delete(e.code);
// Key up won't trigger on macOS for other keys.
if (e.code === 'MetaLeft') {
keys.value.clear();
}
};
onMounted(() => {
window.addEventListener('keydown', keydownListener);
window.addEventListener('keyup', keyupListener);
});
onUnmounted(() => {
window.removeEventListener('keydown', keydownListener);
window.removeEventListener('keyup', keyupListener);
});
return keys;
}

View File

@ -415,7 +415,7 @@
`Save Template`,`حفظ النموذج`
`Save as PDF`,`حفظ كملف PDF`
`Save as PDF Successful`,`PDF حفظ كملف ناجح`
`Saving...`,`حفظ ...`
`Saving`,`حفظ`
`Schedule`,`الجدول`
`Secured Loans`,`قروض مضمونة`
`Securities and Deposits`,`الأوراق المالية والودائع`

Can't render this file because it has a wrong number of fields in line 62.

View File

@ -452,7 +452,7 @@
`Save Template`,`Guardar plantilla`
`Save as PDF`,`Guardar com a PDF`
`Save as PDF Successful`,`Guardat com a PDF correctament`
`Saving...`,`Guardant...`
`Saving`,`Guardant`
`Schedule`,`Programar`
`Search...`,`Search...`
`Secured Loans`,`Préstecs `

Can't render this file because it has a wrong number of fields in line 432.

View File

@ -374,7 +374,7 @@
`Save & Close`,`Speichern & Schließen`
`Save as PDF`,`Als PDF speichern`
`Save as PDF Successful`,`PDF gespeichert`
`Saving...`,`Speichern...`
`Saving`,`Speichern`
`Schedule`,`Zeitplan`
`Secured Loans`,`Gesicherte Darlehen`
`Securities and Deposits`,`Wertpapiere und Einlagen`

Can't render this file because it has a wrong number of fields in line 33.

View File

@ -374,7 +374,7 @@
`Save & Close`,`Enregistrer et fermer`
`Save as PDF`,`Enregistrer en PDF`
`Save as PDF Successful`,`Enregistrer en PDF avec succès`
`Saving...`,`Sauver...`
`Saving`,`Sauver`
`Schedule`,`Programme`
`Secured Loans`,`Prêts garantis`
`Securities and Deposits`,`Titres et dépôts`

Can't render this file because it has a wrong number of fields in line 57.

View File

@ -374,7 +374,7 @@
`Save & Close`,`Guardar e Fechar`
`Save as PDF`,`Guardar como PDF`
`Save as PDF Successful`,`Guardar como PDF Bem sucedido`
`Saving...`,`Salvar...`
`Saving`,`Salvar`
`Schedule`,`Horário`
`Secured Loans`,`Empréstimos Garantidos`
`Securities and Deposits`,`Títulos e Depósitos`

Can't render this file because it has a wrong number of fields in line 57.