2
0
mirror of https://github.com/frappe/books.git synced 2025-01-23 07:08:36 +00:00

Merge pull request #595 from frappe/common-form-for-all

fix(ux): use CommonForm for all entries
This commit is contained in:
Alan 2023-04-16 23:58:06 -07:00 committed by GitHub
commit 21ca586604
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 483 additions and 913 deletions

View File

@ -157,18 +157,18 @@ export class DocHandler {
});
doc.on('afterSync', () => {
if (doc.name === name) {
if (doc.name === name && this.#cacheHas(schemaName, name)) {
return;
}
this.#removeFromCache(doc.schemaName, name);
this.removeFromCache(doc.schemaName, name);
this.#addToCache(doc);
});
}
#setCacheUpdationListeners(schemaName: string) {
this.fyo.db.observer.on(`delete:${schemaName}`, (name: string) => {
this.#removeFromCache(schemaName, name);
this.removeFromCache(schemaName, name);
});
this.fyo.db.observer.on(
@ -179,13 +179,13 @@ export class DocHandler {
return;
}
this.#removeFromCache(schemaName, names.oldName);
this.removeFromCache(schemaName, names.oldName);
this.#addToCache(doc);
}
);
}
#removeFromCache(schemaName: string, name: string) {
removeFromCache(schemaName: string, name: string) {
const docMap = this.docs.get(schemaName);
delete docMap?.[name];
}
@ -194,4 +194,8 @@ export class DocHandler {
const docMap = this.docs.get(schemaName);
return docMap?.[name];
}
#cacheHas(schemaName: string, name: string): boolean {
return !!this.#getFromCache(schemaName, name);
}
}

View File

@ -915,6 +915,10 @@ export class Doc extends Observable<DocValue | Doc[]> {
}
async delete() {
if (this.notInserted && this.name) {
this.fyo.doc.removeFromCache(this.schemaName, this.name);
}
if (!this.canDelete) {
return;
}

View File

@ -112,7 +112,6 @@ export class Item extends Doc {
static getListViewSettings(): ListViewSettings {
return {
formRoute: (name) => `/edit/Item/${name}`,
columns: ['name', 'unit', 'tax', 'rate'],
};
}

View File

@ -65,7 +65,6 @@ export class JournalEntry extends Transactional {
static getListViewSettings(): ListViewSettings {
return {
formRoute: (name) => `/edit/JournalEntry/${name}`,
columns: [
'name',
{

View File

@ -617,7 +617,6 @@ export class Payment extends Transactional {
static getListViewSettings(fyo: Fyo): ListViewSettings {
return {
formRoute: (name) => `/edit/Payment/${name}`,
columns: ['name', getDocStatusListColumn(), 'party', 'date', 'amount'],
};
}

View File

@ -37,7 +37,6 @@ export class PurchaseInvoice extends Invoice {
static getListViewSettings(): ListViewSettings {
return {
formRoute: (name) => `/edit/PurchaseInvoice/${name}`,
columns: [
'name',
getTransactionStatusColumn(),

View File

@ -37,7 +37,6 @@ export class SalesInvoice extends Invoice {
static getListViewSettings(): ListViewSettings {
return {
formRoute: (name) => `/edit/SalesInvoice/${name}`,
columns: [
'name',
getTransactionStatusColumn(),

View File

@ -8,7 +8,6 @@ export class PurchaseReceipt extends StockTransfer {
static getListViewSettings(): ListViewSettings {
return {
formRoute: (name) => `/edit/PurchaseReceipt/${name}`,
columns: [
'name',
getTransactionStatusColumn(),

View File

@ -8,7 +8,6 @@ export class Shipment extends StockTransfer {
static getListViewSettings(): ListViewSettings {
return {
formRoute: (name) => `/edit/Shipment/${name}`,
columns: [
'name',
getTransactionStatusColumn(),

View File

@ -88,7 +88,6 @@ export class StockMovement extends Transfer {
};
return {
formRoute: (name) => `/edit/StockMovement/${name}`,
columns: [
'name',
getDocStatusListColumn(),

View File

@ -6,11 +6,28 @@
"isChild": false,
"naming": "autoincrement",
"fields": [
{
"label": "Entry No.",
"fieldname": "name",
"fieldtype": "Data",
"required": true,
"readOnly": true,
"section": "Default"
},
{
"fieldname": "date",
"label": "Date",
"fieldtype": "Datetime",
"readOnly": true
"readOnly": true,
"section": "Default"
},
{
"fieldname": "party",
"label": "Party",
"fieldtype": "Link",
"target": "Party",
"readOnly": true,
"section": "Default"
},
{
"fieldname": "account",
@ -18,53 +35,53 @@
"fieldtype": "Link",
"target": "Account",
"required": true,
"readOnly": true
},
{
"fieldname": "party",
"label": "Party",
"fieldtype": "Link",
"target": "Party",
"readOnly": true
"readOnly": true,
"section": "Default"
},
{
"fieldname": "debit",
"label": "Debit",
"fieldtype": "Currency",
"readOnly": true
"readOnly": true,
"section": "Details"
},
{
"fieldname": "credit",
"label": "Credit",
"fieldtype": "Currency",
"readOnly": true
"readOnly": true,
"section": "Details"
},
{
"fieldname": "referenceType",
"label": "Ref. Type",
"fieldtype": "Data",
"readOnly": true
"readOnly": true,
"section": "Reference"
},
{
"fieldname": "referenceName",
"label": "Ref. Name",
"fieldtype": "DynamicLink",
"references": "referenceType",
"readOnly": true
"readOnly": true,
"section": "Reference"
},
{
"fieldname": "reverted",
"label": "Reverted",
"fieldtype": "Check",
"default": false,
"readOnly": true
"readOnly": true,
"section": "Reference"
},
{
"fieldname": "reverts",
"label": "Reverts",
"fieldtype": "Link",
"target": "AccountingLedgerEntry",
"readOnly": true
"readOnly": true,
"section": "Reference"
}
],
"quickEditFields": [

View File

@ -3,17 +3,19 @@
"label": "Party",
"naming": "manual",
"fields": [
{
"fieldname": "image",
"label": "Image",
"fieldtype": "AttachImage",
"section": "Default"
},
{
"fieldname": "name",
"label": "Name",
"fieldtype": "Data",
"required": true,
"placeholder": "Full Name"
},
{
"fieldname": "image",
"label": "Image",
"fieldtype": "AttachImage"
"placeholder": "Full Name",
"section": "Default"
},
{
"fieldname": "role",
@ -34,20 +36,38 @@
"label": "Customer"
}
],
"required": true
"required": true,
"section": "Default"
},
{
"fieldname": "email",
"label": "Email",
"fieldtype": "Data",
"placeholder": "john@doe.com",
"section": "Contacts"
},
{
"fieldname": "phone",
"label": "Phone",
"fieldtype": "Data",
"placeholder": "Phone",
"section": "Contacts"
},
{
"fieldname": "address",
"label": "Address",
"fieldtype": "Link",
"target": "Address",
"create": true,
"section": "Contacts"
},
{
"fieldname": "defaultAccount",
"label": "Default Account",
"fieldtype": "Link",
"target": "Account",
"create": true
},
{
"fieldname": "outstandingAmount",
"label": "Outstanding Amount",
"fieldtype": "Currency",
"hidden": true
"create": true,
"section": "Billing"
},
{
"fieldname": "currency",
@ -55,31 +75,20 @@
"fieldtype": "Link",
"target": "Currency",
"placeholder": "INR",
"create": true
},
{
"fieldname": "email",
"label": "Email",
"fieldtype": "Data",
"placeholder": "john@doe.com"
},
{
"fieldname": "phone",
"label": "Phone",
"fieldtype": "Data",
"placeholder": "Phone"
},
{
"fieldname": "address",
"label": "Address",
"fieldtype": "Link",
"target": "Address",
"create": true
"create": true,
"section": "Billing"
},
{
"fieldname": "taxId",
"label": "Tax ID",
"fieldtype": "Data"
"fieldtype": "Data",
"section": "Billing"
},
{
"fieldname": "outstandingAmount",
"label": "Outstanding Amount",
"fieldtype": "Currency",
"hidden": true
}
],
"quickEditFields": [

View File

@ -6,57 +6,73 @@
"isChild": false,
"naming": "autoincrement",
"fields": [
{
"label": "Entry No.",
"fieldname": "name",
"fieldtype": "Data",
"required": true,
"readOnly": true,
"section": "Default"
},
{
"fieldname": "date",
"label": "Date",
"fieldtype": "Datetime",
"readOnly": true
},
{
"fieldname": "item",
"label": "Item",
"fieldtype": "Link",
"target": "Item",
"readOnly": true
},
{
"fieldname": "rate",
"label": "Rate",
"fieldtype": "Currency",
"readOnly": true
},
{
"fieldname": "quantity",
"label": "Quantity",
"fieldtype": "Float",
"readOnly": true
"readOnly": true,
"section": "Default"
},
{
"fieldname": "location",
"label": "Location",
"fieldtype": "Link",
"target": "Location",
"readOnly": true
},
{
"fieldname": "referenceName",
"label": "Ref. Name",
"fieldtype": "DynamicLink",
"references": "referenceType",
"readOnly": true
},
{
"fieldname": "referenceType",
"label": "Ref. Type",
"fieldtype": "Data",
"readOnly": true
"readOnly": true,
"section": "Details"
},
{
"fieldname": "batch",
"label": "Batch",
"fieldtype": "Link",
"target": "Batch",
"readOnly": true
"readOnly": true,
"section": "Details"
},
{
"fieldname": "item",
"label": "Item",
"fieldtype": "Link",
"target": "Item",
"readOnly": true,
"section": "Details"
},
{
"fieldname": "rate",
"label": "Rate",
"fieldtype": "Currency",
"readOnly": true,
"section": "Details"
},
{
"fieldname": "quantity",
"label": "Quantity",
"fieldtype": "Float",
"readOnly": true,
"section": "Details"
},
{
"fieldname": "referenceType",
"label": "Ref. Type",
"fieldtype": "Data",
"readOnly": true,
"section": "Reference"
},
{
"fieldname": "referenceName",
"label": "Ref. Name",
"fieldtype": "DynamicLink",
"references": "referenceType",
"readOnly": true,
"section": "Reference"
}
]
}

View File

@ -1,11 +1,6 @@
{
"name": "Party",
"fields": [
{
"fieldname": "gstin",
"label": "GSTIN No.",
"fieldtype": "Data"
},
{
"fieldname": "gstType",
"label": "GST Registration",
@ -25,7 +20,14 @@
"value": "Consumer",
"label": "Consumer"
}
]
],
"section": "Billing"
},
{
"fieldname": "gstin",
"label": "GSTIN No.",
"fieldtype": "Data",
"section": "Billing"
}
],
"quickEditFields": [

View File

@ -1,19 +1,13 @@
<template>
<div
class="
relative
bg-white
border
rounded-full
flex-center
overflow-hidden
group
"
class="relative bg-white border flex-center overflow-hidden group"
:class="{
'w-20 h-20': size !== 'small',
'w-12 h-12': size === 'small',
'rounded': size === 'form',
'w-20 h-20 rounded-full': size !== 'small' && size !== 'form',
'w-12 h-12 rounded-full': size === 'small',
}"
:title="df?.label"
:style="imageSizeStyle"
>
<img :src="value" v-if="value" />
<div :class="[!isReadOnly ? 'group-hover:opacity-90' : '']" v-else>
@ -113,6 +107,12 @@ export default defineComponent({
},
},
computed: {
imageSizeStyle() {
if (this.size === 'form') {
return { width: '135px', height: '135px' };
}
return {};
},
shouldClear() {
return !!this.value;
},

View File

@ -131,24 +131,17 @@ export default {
},
async openNewDoc() {
const schemaName = this.df.target;
const linkDoc = fyo.doc.getNewDoc(schemaName);
const name = this.linkValue;
const filters = await this.getCreateFilters();
const { openQuickEdit } = await import('src/utils/ui');
openQuickEdit({
schemaName,
name: linkDoc.name,
defaults: Object.assign({}, filters, {
name: this.linkValue,
}),
});
const doc = fyo.doc.getNewDoc(schemaName, { name, ...filters });
openQuickEdit({ doc });
linkDoc.once('afterSync', () => {
doc.once('afterSync', () => {
this.$router.back();
this.results = [];
this.triggerChange(linkDoc.name);
this.triggerChange(doc.name);
});
},
async getCreateFilters() {

View File

@ -1,5 +1,5 @@
<template>
<div class="text-sm border-t">
<div class="text-sm">
<template v-for="df in formFields">
<!-- Table Field Form (Eg: PaymentFor) -->
<Table
@ -8,7 +8,7 @@
ref="controls"
size="small"
:df="df"
:value="doc[df.fieldname]"
:value="(doc[df.fieldname] ?? []) as unknown[]"
@change="async (value) => await onChange(df, value)"
/>
@ -52,21 +52,25 @@
</template>
</div>
</template>
<script>
<script lang="ts">
import { Doc } from 'fyo/model/doc';
import FormControl from 'src/components/Controls/FormControl.vue';
import { fyo } from 'src/initFyo';
import { getErrorMessage } from 'src/utils';
import { evaluateHidden } from 'src/utils/doc';
import Table from './Controls/Table.vue';
import { defineComponent } from 'vue';
import { Field } from 'schemas/types';
import { PropType } from 'vue';
import { DocValue } from 'fyo/core/types';
export default {
export default defineComponent({
name: 'TwoColumnForm',
props: {
doc: Doc,
fields: { type: Array, default: () => [] },
doc: { type: Doc, required: true },
fields: { type: Array as PropType<Field[]>, default: () => [] },
columnRatio: {
type: Array,
type: Array as PropType<number[]>,
default: () => [1, 1],
},
},
@ -79,7 +83,7 @@ export default {
return {
formFields: [],
errors: {},
};
} as { formFields: Field[]; errors: Record<string, string> };
},
components: {
FormControl,
@ -88,22 +92,23 @@ export default {
mounted() {
this.setFormFields();
if (fyo.store.isDevelopment) {
// @ts-ignore
window.tcf = this;
}
},
methods: {
getFieldHeight(df) {
if (['AttachImage', 'Text'].includes(df.fieldtype)) {
getFieldHeight(field: Field) {
if (['AttachImage', 'Text'].includes(field.fieldtype)) {
return 'calc((var(--h-row-mid) + 1px) * 2)';
}
if (this.errors[df.fieldname]) {
if (this.errors[field.fieldname]) {
return 'calc((var(--h-row-mid) + 1px) * 2)';
}
return 'calc(var(--h-row-mid) + 1px)';
},
async onChange(field, value) {
async onChange(field: Field, value: DocValue) {
const { fieldname } = field;
delete this.errors[fieldname];
@ -148,5 +153,5 @@ export default {
};
},
},
};
});
</script>

View File

@ -1,98 +0,0 @@
<template>
<div class="w-quick-edit border-s bg-white flex flex-col">
<!-- Linked Entry Title -->
<div class="flex items-center justify-between px-4 h-row-largest border-b">
<Button :icon="true" @click="$emit('close-widget')">
<feather-icon name="x" class="w-4 h-4" />
</Button>
<p class="font-semibold text-xl text-gray-600">
{{ linked.title }}
</p>
</div>
<!-- Linked Entry Items -->
<div
v-for="entry in linked.entries"
:key="entry.name"
class="p-4 border-b flex flex-col hover:bg-gray-50 cursor-pointer"
@click="openEntry(entry.name)"
>
<!-- Name And Status -->
<div class="mb-2 flex justify-between items-center">
<p class="font-semibold text-gray-900">
{{ entry.name }}
</p>
<StatusBadge
:status="getStatus(entry)"
:default-size="false"
class="px-0 text-xs"
/>
</div>
<!-- Date and Amount -->
<div class="text-sm flex justify-between items-center">
<p>
{{ fyo.format(entry.date as Date, 'Date') }}
</p>
<p>{{ fyo.format(entry.amount as Money, 'Currency') }}</p>
</div>
<!-- Quantity and Location -->
<div
v-if="['Shipment', 'PurchaseReceipt'].includes(linked.schemaName)"
class="text-sm flex justify-between items-center mt-1"
>
<p>
{{ entry.location }}
</p>
<p>
{{ t`Qty. ${fyo.format(entry.quantity as number, 'Float')}` }}
</p>
</div>
</div>
</div>
</template>
<script lang="ts">
import { Money } from 'pesa';
import { getFormRoute, routeTo } from 'src/utils/ui';
import { defineComponent, PropType } from 'vue';
import Button from '../Button.vue';
import StatusBadge from '../StatusBadge.vue';
interface Linked {
schemaName: string;
title: string;
entries: {
name: string;
cancelled: boolean;
submitted: boolean;
[key: string]: unknown;
}[];
}
export default defineComponent({
emits: ['close-widget'],
props: {
linked: { type: Object as PropType<Linked>, required: true },
},
methods: {
getStatus(entry: { cancelled?: boolean; submitted?: boolean }) {
if (entry.cancelled) {
return 'Cancelled';
}
if (entry.submitted) {
return 'Submitted';
}
return 'Saved';
},
async openEntry(name: string) {
const route = getFormRoute(this.linked.schemaName, name);
await routeTo(route);
},
},
components: { Button, StatusBadge },
});
</script>

View File

@ -1,144 +0,0 @@
<template>
<div v-if="pendingInvoices.length">
<div
class="
px-4
text-sm text-gray-600
border-b
flex
items-center
h-row-smallest
"
>
{{ t`Recent Invoices` }}
</div>
<!-- Invoice List -->
<div
class="px-4 py-4 border-b hover:bg-gray-50 cursor-pointer text-base"
v-for="invoice in pendingInvoices"
:key="invoice.name"
@click="routeToForm(invoice)"
>
<!-- Invoice Name & Status -->
<div class="flex justify-between items-center mb-1">
<span class="font-medium">
{{ invoice.name }}
</span>
<span>
<component :is="getStatusBadge(invoice)" />
</span>
</div>
<!-- Invoice Date & Amount -->
<div class="flex justify-between text-gray-900">
<span>
{{ fyo.format(invoice.date, getInvoiceField(invoice, 'date')) }}
</span>
<div>
<!-- Paid Amount -->
<span>
{{
fyo.format(
amountPaid(invoice),
getInvoiceField(invoice, 'baseGrandTotal')
)
}}
</span>
<!-- Outstanding Amount -->
<span class="text-gray-600 font-medium" v-if="!fullyPaid(invoice)">
({{
fyo.format(
invoice.outstandingAmount,
getInvoiceField(invoice, 'outstandingAmount')
)
}})
</span>
</div>
</div>
</div>
</div>
</template>
<script>
import { Doc } from 'fyo/model/doc';
import { PartyRoleEnum } from 'models/baseModels/Party/types';
import { getTransactionStatusColumn } from 'models/helpers';
import { ModelNameEnum } from 'models/types';
import { fyo } from 'src/initFyo';
import { routeTo } from 'src/utils/ui';
export default {
name: 'PartyWidget',
props: { doc: Doc },
data() {
return {
pendingInvoices: [],
};
},
computed: {
invoiceSchemaNames() {
switch (this.doc.get('role')) {
case PartyRoleEnum.Customer:
return [ModelNameEnum.SalesInvoice];
case PartyRoleEnum.Supplier:
return [ModelNameEnum.PurchaseInvoice];
case PartyRoleEnum.Both:
default:
return [ModelNameEnum.SalesInvoice, ModelNameEnum.PurchaseInvoice];
}
},
},
mounted() {
this.fetchPendingInvoices();
},
methods: {
getInvoiceField(invoice, fieldname) {
return fyo.getField(invoice.schemaName, fieldname);
},
async fetchPendingInvoices() {
const pendingInvoices = [];
for (const schemaName of this.invoiceSchemaNames) {
const invoices = await fyo.db.getAll(schemaName, {
fields: [
'name',
'date',
'outstandingAmount',
'baseGrandTotal',
'submitted',
],
filters: {
party: this.doc.name,
cancelled: false,
},
limit: 3,
orderBy: 'created',
});
invoices.forEach((i) => {
i.schemaName = schemaName;
i.schema = fyo.schemaMap[schemaName];
});
pendingInvoices.push(...invoices);
}
this.pendingInvoices = pendingInvoices;
},
getStatusBadge(doc) {
const statusColumn = getTransactionStatusColumn();
return statusColumn.render(doc);
},
routeToForm(invoice) {
routeTo(`/edit/${invoice.schemaName}/${invoice.name}`);
},
fullyPaid(invoice) {
return invoice.outstandingAmount.isZero();
},
amountPaid(invoice) {
return invoice.baseGrandTotal.sub(invoice.outstandingAmount);
},
},
};
</script>

View File

@ -70,7 +70,7 @@
<div v-if="hasDoc" class="overflow-auto custom-scroll">
<CommonFormSection
v-for="([name, fields], idx) in activeGroup.entries()"
@editrow="(doc: Doc) => toggleQuickEditDoc(doc)"
@editrow="(doc: Doc) => showRowEditForm(doc)"
:key="name + idx"
ref="section"
class="p-4"
@ -121,24 +121,21 @@
</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)"
<LinkedEntries
v-if="showLinks && canShowLinks"
:doc="doc"
@close="showLinks = false"
/>
</Transition>
<Transition name="quickedit">
<LinkedEntries
v-if="showLinks && !hasQeDoc"
<RowEditForm
v-if="row && !showLinks"
:doc="doc"
@close="showLinks = false"
:fieldname="row.fieldname"
:index="row.index"
@previous="(i:number) => row!.index = i"
@next="(i:number) => row!.index = i"
@close="() => (row = null)"
/>
</Transition>
</template>
@ -147,16 +144,20 @@
<script lang="ts">
import { DocValue } from 'fyo/core/types';
import { Doc } from 'fyo/model/doc';
import { DEFAULT_CURRENCY } from 'fyo/utils/consts';
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 Barcode from 'src/components/Controls/Barcode.vue';
import ExchangeRate from 'src/components/Controls/ExchangeRate.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 { getErrorMessage } from 'src/utils';
import { shortcutsKey } from 'src/utils/injectionKeys';
import { docsPathMap } from 'src/utils/misc';
import { docsPathRef } from 'src/utils/refs';
import { ActionGroup, DocRef, UIGroupedFields } from 'src/utils/types';
@ -169,17 +170,11 @@ import {
isPrintable,
routeTo,
} from 'src/utils/ui';
import { computed, defineComponent, nextTick } from 'vue';
import QuickEditForm from '../QuickEditForm.vue';
import CommonFormSection from './CommonFormSection.vue';
import { inject } from 'vue';
import { shortcutsKey } from 'src/utils/injectionKeys';
import { ref } from 'vue';
import { useDocShortcuts } from 'src/utils/vueUtils';
import Barcode from 'src/components/Controls/Barcode.vue';
import { DEFAULT_CURRENCY } from 'fyo/utils/consts';
import ExchangeRate from 'src/components/Controls/ExchangeRate.vue';
import { computed, defineComponent, inject, nextTick, ref } from 'vue';
import CommonFormSection from './CommonFormSection.vue';
import LinkedEntries from './LinkedEntries.vue';
import RowEditForm from './RowEditForm.vue';
export default defineComponent({
props: {
@ -211,16 +206,16 @@ export default defineComponent({
errors: {},
activeTab: this.t`Default`,
groupedFields: null,
quickEditDoc: null,
isPrintable: false,
showLinks: false,
row: null,
} as {
errors: Record<string, string>;
activeTab: string;
groupedFields: null | UIGroupedFields;
quickEditDoc: null | Doc;
isPrintable: boolean;
showLinks: boolean;
row: null | { index: number; fieldname: string };
};
},
async mounted() {
@ -256,6 +251,7 @@ export default defineComponent({
deactivated(): void {
docsPathRef.value = '';
this.showLinks = false;
this.row = null;
},
computed: {
canShowBarcode(): boolean {
@ -305,7 +301,7 @@ export default defineComponent({
return !this.doc.isCancelled && !this.doc.dirty && this.isPrintable;
},
canShowLinks(): boolean {
if (!this.hasDoc || this.hasQeDoc) {
if (!this.hasDoc) {
return false;
}
@ -318,9 +314,6 @@ export default defineComponent({
hasDoc(): boolean {
return this.docOrNull instanceof Doc;
},
hasQeDoc(): boolean {
return this.quickEditDoc instanceof Doc;
},
status(): string {
if (!this.hasDoc) {
return '';
@ -337,15 +330,6 @@ export default defineComponent({
}
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`;
@ -415,18 +399,18 @@ export default defineComponent({
this.name
);
},
async toggleQuickEditDoc(doc: Doc | null) {
if (this.quickEditDoc && doc) {
this.quickEditDoc = null;
await nextTick();
}
if (doc && this.showLinks) {
async showRowEditForm(doc: Doc) {
if (this.showLinks) {
this.showLinks = false;
await nextTick();
}
this.quickEditDoc = doc;
const index = doc.idx;
const fieldname = doc.parentFieldname;
if (typeof index === 'number' && typeof fieldname === 'string') {
this.row = { index, fieldname };
}
},
async onValueChange(field: Field, value: DocValue) {
const { fieldname } = field;
@ -452,10 +436,10 @@ export default defineComponent({
StatusBadge,
Button,
DropdownWithActions,
QuickEditForm,
Barcode,
ExchangeRate,
LinkedEntries,
RowEditForm,
},
});
</script>

View File

@ -21,11 +21,13 @@
:key="field.fieldname"
:class="[
field.fieldtype === 'Table' ? 'col-span-2 text-base' : '',
field.fieldtype === 'AttachImage' ? 'row-span-2' : '',
field.fieldtype === 'Check' ? 'mt-auto' : 'mb-auto',
]"
>
<FormControl
:ref="field.fieldname === 'name' ? 'nameField' : 'fields'"
:size="field.fieldtype === 'AttachImage' ? 'form' : undefined"
:show-label="true"
:border="true"
:df="field"

View File

@ -0,0 +1,113 @@
<template>
<div
class="border-s h-full overflow-auto w-quick-edit bg-white custom-scroll"
>
<!-- Row Edit Tool bar -->
<div class="sticky top-0 border-b bg-white" style="z-index: 1">
<div class="flex items-center justify-between px-4 h-row-largest">
<!-- Close Button -->
<Button :icon="true" @click="$emit('close')">
<feather-icon name="x" class="w-4 h-4" />
</Button>
<!-- Actions, Badge and Status Change Buttons -->
<div class="flex items-stretch gap-2">
<Button
v-if="previous >= 0"
:icon="true"
@click="$emit('previous', previous)"
>
<feather-icon name="chevron-left" class="w-4 h-4" />
</Button>
<Button v-if="next >= 0" :icon="true" @click="$emit('next', next)">
<feather-icon name="chevron-right" class="w-4 h-4" />
</Button>
</div>
</div>
<FormHeader
class="border-t"
:form-title="t`Row ${index + 1}`"
:form-sub-title="fieldlabel"
/>
</div>
<TwoColumnForm
class="w-full"
ref="form"
:doc="row"
:fields="fields"
:column-ratio="[1.1, 2]"
/>
</div>
</template>
<script lang="ts">
import { Doc } from 'fyo/model/doc';
import { ValueError } from 'fyo/utils/errors';
import Button from 'src/components/Button.vue';
import FormHeader from 'src/components/FormHeader.vue';
import TwoColumnForm from 'src/components/TwoColumnForm.vue';
import { shortcutsKey } from 'src/utils/injectionKeys';
import { computed } from 'vue';
import { inject } from 'vue';
import { defineComponent } from 'vue';
const COMPONENT_NAME = 'RowEditForm';
export default defineComponent({
setup() {
return { shortcuts: inject(shortcutsKey) };
},
emits: ['next', 'previous', 'close'],
props: {
doc: { type: Doc, required: true },
index: { type: Number, required: true },
fieldname: { type: String, required: true },
},
provide() {
return {
doc: computed(() => this.row),
};
},
mounted() {
this.shortcuts?.set(COMPONENT_NAME, ['Escape'], () => this.$emit('close'));
},
unmounted() {
this.shortcuts?.delete(COMPONENT_NAME);
},
computed: {
fieldlabel() {
return (
this.fyo.getField(this.doc.schemaName, this.fieldname)?.label ?? ''
);
},
row() {
const rows = this.doc.get(this.fieldname);
if (Array.isArray(rows) && rows[this.index] instanceof Doc) {
return rows[this.index];
}
const label = `${this.doc.name}.${this.fieldname}[${this.index}]`;
throw new ValueError(this.t`Invalid value found for ${label}`);
},
fields() {
const fieldnames = this.row.schema.quickEditFields ?? [];
return fieldnames.map((f) => this.fyo.getField(this.row.schemaName, f));
},
previous(): number {
return this.index - 1;
},
next() {
const rows = this.doc.get(this.fieldname);
if (!Array.isArray(rows)) {
return -1;
}
if (rows.length - 1 === this.index) {
return -1;
}
return this.index + 1;
},
},
components: { Button, TwoColumnForm, FormHeader },
});
</script>

View File

@ -22,12 +22,10 @@ import { toggleSidebar } from 'src/utils/ui';
<router-view name="edit" v-slot="{ Component, route }">
<Transition name="quickedit">
<div v-if="route?.query?.edit">
<keep-alive>
<component
:is="Component"
:key="route.query.schemaName + route.query.name"
/>
</keep-alive>
<component
:is="Component"
:key="route.query.schemaName + route.query.name"
/>
</div>
</Transition>
</router-view>

View File

@ -42,7 +42,6 @@
</div>
</template>
<script lang="ts">
import { Doc } from 'fyo/model/doc';
import { Field } from 'schemas/types';
import Button from 'src/components/Button.vue';
import ExportWizard from 'src/components/ExportWizard.vue';
@ -50,17 +49,15 @@ import FilterDropdown from 'src/components/FilterDropdown.vue';
import Modal from 'src/components/Modal.vue';
import PageHeader from 'src/components/PageHeader.vue';
import { fyo } from 'src/initFyo';
import { getRouteData } from 'src/utils/filters';
import { shortcutsKey } from 'src/utils/injectionKeys';
import {
docsPathMap,
getCreateFiltersFromListViewFilters,
} from 'src/utils/misc';
import { docsPathRef } from 'src/utils/refs';
import { openQuickEdit, routeTo } from 'src/utils/ui';
import { getFormRoute, routeTo } from 'src/utils/ui';
import { QueryFilter } from 'utils/db/types';
import { defineComponent, inject, ref } from 'vue';
import { RouteLocationRaw } from 'vue-router';
import List from './List.vue';
export default defineComponent({
@ -135,18 +132,8 @@ export default defineComponent({
this.listFilters = listFilters;
},
async openDoc(name: string) {
const doc = await this.fyo.doc.getDoc(this.schemaName, name);
if (this.listConfig?.formRoute) {
return await routeTo(this.listConfig.formRoute(name));
}
const { routeFilter } = getRouteData({ doc });
openQuickEdit({
doc,
listFilters: routeFilter,
});
const route = getFormRoute(this.schemaName, name);
await routeTo(route);
},
async makeNewDoc() {
if (!this.canCreate) {
@ -155,43 +142,17 @@ export default defineComponent({
const filters = getCreateFiltersFromListViewFilters(this.filters ?? {});
const doc = fyo.doc.getNewDoc(this.schemaName, filters);
const path = this.getFormPath(doc);
const route = getFormRoute(this.schemaName, doc.name!);
await routeTo(route);
await routeTo(path);
doc.on('afterSync', () => {
const path = this.getFormPath(doc);
this.$router.replace(path);
const route = getFormRoute(this.schemaName, doc.name!);
this.$router.replace(route);
});
},
applyFilter(filters: QueryFilter) {
this.list?.updateData(filters);
},
getFormPath(doc: Doc) {
const { label, routeFilter } = getRouteData({ doc });
const currentPath = this.$router.currentRoute.value.path;
// Maintain filters
let path = `/list/${this.schemaName}/${label}`;
if (currentPath.startsWith(path)) {
path = currentPath;
}
let route: RouteLocationRaw = {
path,
query: {
edit: 1,
schemaName: this.schemaName,
name: doc.name,
filters: JSON.stringify(routeFilter),
},
};
if (this.listConfig?.formRoute) {
route = this.listConfig.formRoute(doc.name!);
}
return route;
},
},
computed: {
context(): string {

View File

@ -6,15 +6,15 @@
<AutoComplete
v-if="templateList.length"
:df="{
fieldtype: 'AutoComplete',
fieldname: 'templateName',
label: t`Template Name`,
target: 'PrintTemplate',
options: templateList,
options: templateList.map((n) => ({ label: n, value: n })),
}"
input-class="text-base py-0 h-8"
class="w-56"
:border="true"
:value="templateName"
:value="templateName ?? ''"
@change="onTemplateNameChange"
/>
</template>

View File

@ -1,8 +1,5 @@
<template>
<div
class="border-s h-full overflow-auto w-quick-edit"
:class="white ? 'bg-white' : 'bg-gray-25'"
>
<div class="border-s h-full overflow-auto w-quick-edit bg-white">
<!-- Quick edit Tool bar -->
<div
class="
@ -13,63 +10,54 @@
h-row-largest
sticky
top-0
border-b
bg-white
"
style="z-index: 1"
:class="{ 'border-b': showName }"
>
<!-- Close Button and Status Text -->
<div class="flex items-center">
<Button :icon="true" @click="routeToPrevious">
<feather-icon name="x" class="w-4 h-4" />
</Button>
<span v-if="statusText" class="ms-2 text-base text-gray-600">{{
statusText
}}</span>
</div>
<!-- Close Button -->
<Button :icon="true" @click="routeToPrevious">
<feather-icon name="x" class="w-4 h-4" />
</Button>
<!-- Actions, Badge and Status Change Buttons -->
<div class="flex items-stretch gap-2" v-if="showSave">
<StatusBadge :status="status" />
<DropdownWithActions :actions="actions" />
<Button
:icon="true"
@click="sync"
type="primary"
v-if="doc?.canSave"
class="text-white text-xs"
>
{{ t`Save` }}
</Button>
<Button
:icon="true"
@click="submit"
type="primary"
v-else-if="doc?.canSubmit"
class="text-white text-xs"
>
{{ t`Submit` }}
</Button>
</div>
<!-- Save & Submit Buttons -->
<Button
:icon="true"
@click="sync"
type="primary"
v-if="doc?.canSave"
class="text-white text-xs"
>
{{ t`Save` }}
</Button>
<Button
:icon="true"
@click="submit"
type="primary"
v-else-if="doc?.canSubmit"
class="text-white text-xs"
>
{{ t`Submit` }}
</Button>
</div>
<!-- Name and image -->
<div
class="items-center"
class="items-center border-b"
:class="imageField ? 'grid' : 'flex justify-center'"
:style="{
height: `calc(var(--h-row-mid) * ${!!imageField ? '2 + 1px' : '1'})`,
gridTemplateColumns: `minmax(0, 1.1fr) minmax(0, 2fr)`,
}"
v-if="doc && showName && (titleField || imageField)"
v-if="doc && (titleField || imageField)"
>
<FormControl
v-if="imageField"
class="ms-4"
:df="imageField"
:value="doc[imageField.fieldname]"
@change="(value) => valueChange(imageField, value)"
:letter-placeholder="doc[titleField.fieldname]?.[0] ?? ''"
@change="(value) => valueChange(imageField as Field, value)"
:letter-placeholder="letterPlaceHolder"
/>
<FormControl
v-if="titleField"
@ -83,7 +71,7 @@
:df="titleField"
:value="doc[titleField.fieldname]"
:read-only="doc.inserted || doc.schema.naming !== 'manual'"
@change="(value) => valueChange(titleField, value)"
@change="(value) => valueChange(titleField as Field, value)"
/>
</div>
@ -96,61 +84,43 @@
:fields="fields"
:column-ratio="[1.1, 2]"
/>
<!-- QuickEdit Widgets -->
<component v-if="quickEditWidget" :is="quickEditWidget" />
</div>
</template>
<script>
<script lang="ts">
import { computed } from '@vue/reactivity';
import { t } from 'fyo';
import { Doc } from 'fyo/model/doc';
import { getDocStatus } from 'models/helpers';
import { DocValue } from 'fyo/core/types';
import { Field, Schema } from 'schemas/types';
import Button from 'src/components/Button.vue';
import FormControl from 'src/components/Controls/FormControl.vue';
import DropdownWithActions from 'src/components/DropdownWithActions.vue';
import StatusBadge from 'src/components/StatusBadge.vue';
import TwoColumnForm from 'src/components/TwoColumnForm.vue';
import { fyo } from 'src/initFyo';
import { shortcutsKey } from 'src/utils/injectionKeys';
import { getQuickEditWidget } from 'src/utils/quickEditWidgets';
import { DocRef } from 'src/utils/types';
import {
commonDocSubmit,
commonDocSync,
focusOrSelectFormControl,
getActionsForDoc,
openQuickEdit,
} from 'src/utils/ui';
import { useDocShortcuts } from 'src/utils/vueUtils';
import { ref, inject } from 'vue';
import { defineComponent, inject, ref } from 'vue';
export default {
export default defineComponent({
name: 'QuickEditForm',
props: {
name: String,
schemaName: String,
defaults: String,
white: { type: Boolean, default: false },
routeBack: { type: Boolean, default: true },
showName: { type: Boolean, default: true },
showSave: { type: Boolean, default: true },
sourceDoc: { type: Doc, default: null },
loadOnClose: { type: Boolean, default: true },
sourceFields: { type: Array, default: () => [] },
name: { type: String, required: true },
schemaName: { type: String, required: true },
hideFields: { type: Array, default: () => [] },
showFields: { type: Array, default: () => [] },
},
components: {
Button,
FormControl,
StatusBadge,
TwoColumnForm,
DropdownWithActions,
},
emits: ['close'],
setup() {
const doc = ref(null);
const doc = ref(null) as DocRef;
const shortcuts = inject(shortcutsKey);
let context = 'QuickEditForm';
@ -159,6 +129,7 @@ export default {
}
return {
form: ref<InstanceType<typeof TwoColumnForm> | null>(null),
doc,
context,
shortcuts,
@ -171,44 +142,44 @@ export default {
},
data() {
return {
values: null,
titleField: null,
imageField: null,
statusText: null,
} as {
titleField: null | Field;
imageField: null | Field;
};
},
activated() {
this.setShortcuts();
},
async mounted() {
if (this.defaults) {
this.values = JSON.parse(this.defaults);
}
await this.fetchFieldsAndDoc();
focusOrSelectFormControl(this.doc, this.$refs.titleControl, false);
await this.initialize();
if (fyo.store.isDevelopment) {
// @ts-ignore
window.qef = this;
}
this.setShortcuts();
},
computed: {
isChild() {
return !!this?.doc?.schema?.isChild;
},
schema() {
return fyo.schemaMap[this.schemaName] ?? null;
},
status() {
return getDocStatus(this.doc);
},
fields() {
if (this.sourceFields?.length) {
return this.sourceFields;
letterPlaceHolder() {
if (!this.doc) {
return '';
}
const fn = this.titleField?.fieldname ?? 'name';
const value = this.doc.get(fn);
if (typeof value === 'string') {
return value[0];
}
return '';
},
schema(): Schema {
return fyo.schemaMap[this.schemaName]!;
},
fields() {
if (!this.schema) {
return [];
}
@ -227,105 +198,66 @@ export default {
return fieldnames.map((f) => fyo.getField(this.schemaName, f));
},
actions() {
return getActionsForDoc(this.doc);
},
quickEditWidget() {
if (this.doc?.notInserted ?? true) {
return null;
}
const widget = getQuickEditWidget(this.schemaName);
if (widget === null) {
return null;
}
return widget(this.doc);
},
},
methods: {
setShortcuts() {
if (this.shortcuts.has(this.context, ['Escape'])) {
return;
}
this.shortcuts.set(this.context, ['Escape'], () => {
this.shortcuts?.set(this.context, ['Escape'], () => {
this.routeToPrevious();
});
},
async fetchFieldsAndDoc() {
async initialize() {
if (!this.schema) {
return;
}
const titleField = this.schema.titleField;
this.titleField = fyo.getField(this.schemaName, titleField);
this.imageField = fyo.getField(this.schemaName, 'image');
await this.fetchDoc();
// set default values
if (this.values) {
this.doc?.set(this.values);
}
},
async fetchDoc() {
if (this.sourceDoc) {
return (this.doc = this.sourceDoc);
}
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) {
this.setFields();
await this.setDoc();
if (!this.doc) {
return;
}
this.doc.once('afterRename', () => {
openQuickEdit({
schemaName: this.schemaName,
name: this.doc.name,
});
});
focusOrSelectFormControl(this.doc, this.$refs.titleControl, false);
},
valueChange(df, value) {
this.$refs.form.onChange(df, value);
setFields() {
const titleFieldName = this.schema.titleField ?? 'name';
this.titleField = fyo.getField(this.schemaName, titleFieldName) ?? null;
this.imageField = fyo.getField(this.schemaName, 'image') ?? null;
},
async setDoc() {
try {
this.doc = await fyo.doc.getDoc(this.schemaName, this.name);
} catch (e) {
return this.$router.back();
}
},
valueChange(field: Field, value: DocValue) {
this.form?.onChange(field, value);
},
async sync() {
this.statusText = t`Saving`;
await commonDocSync(this.doc);
setTimeout(() => {
this.statusText = null;
}, 300);
},
async submit() {
this.statusText = t`Submitting`;
await commonDocSubmit(this.doc);
setTimeout(() => {
this.statusText = null;
}, 300);
},
routeToPrevious() {
if (this.loadOnClose && this.doc.dirty && !this.doc.notInserted) {
this.doc.load();
if (!this.doc) {
return;
}
if (this.routeBack) {
this.$router.back();
} else {
this.$emit('close');
await commonDocSync(this.doc);
},
async submit() {
if (!this.doc) {
return;
}
await commonDocSubmit(this.doc);
},
async routeToPrevious() {
if (this.doc?.dirty && this.doc?.inserted) {
await this.doc.load();
}
if (this.doc && this.doc.notInserted) {
await this.doc.delete();
}
this.$router.back();
},
},
};
});
</script>

View File

@ -1,4 +1,3 @@
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';
@ -12,38 +11,6 @@ import Settings from 'src/pages/Settings/Settings.vue';
import TemplateBuilder from 'src/pages/TemplateBuilder/TemplateBuilder.vue';
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
function getCommonFormItems(): RouteRecordRaw[] {
return [
ModelNameEnum.SalesInvoice,
ModelNameEnum.PurchaseInvoice,
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,
};
},
edit: (route) => route.query,
},
};
});
}
const routes: RouteRecordRaw[] = [
{
path: '/',
@ -53,7 +20,21 @@ const routes: RouteRecordRaw[] = [
path: '/get-started',
component: GetStarted,
},
...getCommonFormItems(),
{
path: `/edit/:schemaName/:name`,
name: `CommonForm`,
components: {
default: CommonForm,
edit: QuickEditForm,
},
props: {
default: (route) => ({
schemaName: route.params.schemaName,
name: route.params.name,
}),
edit: (route) => route.query,
},
},
{
path: '/list/:schemaName/:pageTitle?',
name: 'ListView',
@ -78,9 +59,7 @@ const routes: RouteRecordRaw[] = [
pageTitle,
};
},
edit: (route) => {
return route.query;
},
edit: (route) => route.query,
},
},
{
@ -133,5 +112,4 @@ const routes: RouteRecordRaw[] = [
];
const router = createRouter({ routes, history: createWebHistory() });
export default router;

View File

@ -1,11 +1,3 @@
import { t } from 'fyo';
import type { Doc } from 'fyo/model/doc';
import { ValueError } from 'fyo/utils/errors';
import type { Item } from 'models/baseModels/Item/Item';
import type { Party } from 'models/baseModels/Party/Party';
import type { Payment } from 'models/baseModels/Payment/Payment';
import { ModelNameEnum } from 'models/types';
import { fyo } from 'src/initFyo';
export const routeFilters = {
SalesItems: { for: ['in', ['Sales', 'Both']] },
@ -28,96 +20,3 @@ export const createFilters = {
Customers: { role: 'Customer' },
Party: { role: 'Both' },
};
export function getRouteData({ doc, uiname }: { doc?: Doc; uiname?: string }) {
if (doc) {
uiname = getUiName(doc);
}
if (!uiname) {
throw new ValueError(`Doc and uiname not passed to getFilters`);
}
const routeFilter = routeFilters[uiname as keyof typeof routeFilters] ?? {};
const createFilter =
createFilters[uiname as keyof typeof createFilters] ?? {};
const label = getLabelName(uiname);
return { routeFilter, createFilter, label };
}
function getLabelName(uiname: string) {
const labels = {
SalesItems: t`Sales Items`,
PurchaseItems: t`Purchase Items`,
Items: t`Items`,
Suppliers: t`Suppliers`,
Customers: t`Customers`,
Party: t`Party`,
PurchasePayments: t`Purchase Payments`,
SalesPayments: t`Sales Payments`,
};
return labels[uiname as keyof typeof labels] ?? fyo.schemaMap[uiname]?.label;
}
function getUiName(doc: Doc) {
const isDual = [
ModelNameEnum.Party,
ModelNameEnum.Payment,
ModelNameEnum.Item,
].includes(doc.schemaName as ModelNameEnum);
if (!isDual) {
return doc.schemaName;
}
if (doc.schemaName === ModelNameEnum.Party) {
return getPartyUiName(doc as Party);
}
if (doc.schemaName === ModelNameEnum.Payment) {
return getPaymentUiName(doc as Payment);
}
if (doc.schemaName === ModelNameEnum.Item) {
return getItemUiName(doc as Item);
}
return doc.schemaName;
}
function getPartyUiName(doc: Party) {
switch (doc.role) {
case 'Customer':
return 'Customers';
case 'Supplier':
return 'Suppliers';
default:
return doc.schemaName;
}
}
function getPaymentUiName(doc: Payment) {
switch (doc.paymentType) {
case 'Pay':
return 'PurchasePayments';
case 'Receive':
return 'SalesPayments';
default:
return doc.schemaName;
}
}
function getItemUiName(doc: Item) {
switch (doc.for) {
case 'Purchases':
return 'PurchaseItems';
case 'Sales':
return 'SalesItems';
case 'Both':
return 'Items';
default:
return doc.schemaName;
}
}

View File

@ -1,18 +0,0 @@
import { Doc } from 'fyo/model/doc';
import { ModelNameEnum } from 'models/types';
import PartyWidget from 'src/components/Widgets/PartyWidget.vue';
import { h } from 'vue';
export function getQuickEditWidget(schemaName: string) {
if (schemaName === ModelNameEnum.Party) {
return (doc: Doc) => ({
render() {
return h(PartyWidget, {
doc,
});
},
});
}
return null;
}

View File

@ -104,74 +104,50 @@ function getCreateList(fyo: Fyo): SearchItem[] {
const filteredCreateList = [
{
label: t`Sales Payments`,
label: t`Sales Payment`,
schemaName: ModelNameEnum.Payment,
create: createFilters.SalesPayments,
},
{
label: t`Purchase Payments`,
label: t`Purchase Payment`,
schemaName: ModelNameEnum.Payment,
create: createFilters.PurchasePayments,
},
{
label: t`Customers`,
label: t`Customer`,
schemaName: ModelNameEnum.Party,
create: createFilters.Customers,
filter: routeFilters.Customers,
},
{
label: t`Suppliers`,
label: t`Supplier`,
schemaName: ModelNameEnum.Party,
create: createFilters.Suppliers,
filter: routeFilters.Suppliers,
},
{
label: t`Party`,
schemaName: ModelNameEnum.Party,
create: createFilters.Party,
filter: routeFilters.Party,
},
{
label: t`Sales Items`,
label: t`Sales Item`,
schemaName: ModelNameEnum.Item,
create: createFilters.SalesItems,
},
{
label: t`Purchase Items`,
label: t`Purchase Item`,
schemaName: ModelNameEnum.Item,
create: createFilters.PurchaseItems,
},
{
label: t`Items`,
label: t`Item`,
schemaName: ModelNameEnum.Item,
create: createFilters.Items,
},
].map(({ label, filter, create, schemaName }) => {
let action: Function;
if (!filter) {
action = getCreateAction(fyo, schemaName, create);
} else {
const route = {
path: `/list/${schemaName}/${label}`,
query: { filters: JSON.stringify(filter) },
};
action = async () => {
await routeTo(route);
const doc = fyo.doc.getNewDoc(schemaName, create);
const { openQuickEdit } = await import('src/utils/ui');
await openQuickEdit({
schemaName,
name: doc.name as string,
listFilters: filter,
});
};
}
].map(({ label, create, schemaName }) => {
return {
label,
group: 'Create',
action,
action: getCreateAction(fyo, schemaName, create),
} as SearchItem;
});

View File

@ -38,13 +38,10 @@ export type SettingsTab =
| ModelNameEnum.SystemSettings;
export interface QuickEditOptions {
doc?: Doc;
schemaName?: string;
name?: string;
doc: Doc;
hideFields?: string[];
showFields?: string[];
defaults?: Record<string, unknown>;
listFilters?: QueryFilter;
}
export type SidebarConfig = SidebarRoot[];

View File

@ -35,63 +35,26 @@ export const toastDurationMap = { short: 2_500, long: 5_000 } as const;
export async function openQuickEdit({
doc,
schemaName,
name,
hideFields = [],
showFields = [],
defaults = {},
listFilters = {},
}: QuickEditOptions) {
if (doc) {
schemaName = doc.schemaName;
name = doc.name;
const { schemaName, name } = doc;
if (!name) {
throw new ValueError(t`Quick edit error: ${schemaName} entry has no name.`);
}
if (!doc && (!schemaName || !name)) {
throw new ValueError(t`Schema Name or Name not passed to Open Quick Edit`);
}
const currentRoute = router.currentRoute.value;
const query = currentRoute.query;
let method: 'push' | 'replace' = 'push';
if (query.edit && query.schemaName === schemaName) {
method = 'replace';
}
if (query.name === name) {
if (router.currentRoute.value.query.name === name) {
return;
}
const forWhat = (defaults?.for ?? []) as string[];
if (forWhat[0] === 'not in') {
const purpose = forWhat[1]?.[0];
defaults = Object.assign({
for:
purpose === 'Sales'
? 'Purchases'
: purpose === 'Purchases'
? 'Sales'
: 'Both',
});
}
if (forWhat[0] === 'not in' && forWhat[1] === 'Sales') {
defaults = Object.assign({ for: 'Purchases' });
}
router[method]({
query: {
edit: 1,
schemaName,
name,
showFields,
hideFields,
defaults: stringifyCircular(defaults),
filters: JSON.stringify(listFilters),
},
});
const query = {
edit: 1,
name,
schemaName,
showFields,
hideFields,
};
router.push({ query });
}
export async function openSettings(tab: SettingsTab) {
@ -384,22 +347,7 @@ export function getFormRoute(
return route;
}
if (
[
ModelNameEnum.SalesInvoice,
ModelNameEnum.PurchaseInvoice,
ModelNameEnum.JournalEntry,
ModelNameEnum.Shipment,
ModelNameEnum.PurchaseReceipt,
ModelNameEnum.StockMovement,
ModelNameEnum.Payment,
ModelNameEnum.Item,
].includes(schemaName as ModelNameEnum)
) {
return `/edit/${schemaName}/${name}`;
}
return `/list/${schemaName}?edit=1&schemaName=${schemaName}&name=${name}`;
return `/edit/${schemaName}/${name}`;
}
export async function getDocFromNameIfExistsElseNew(