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:
commit
21ca586604
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -112,7 +112,6 @@ export class Item extends Doc {
|
||||
|
||||
static getListViewSettings(): ListViewSettings {
|
||||
return {
|
||||
formRoute: (name) => `/edit/Item/${name}`,
|
||||
columns: ['name', 'unit', 'tax', 'rate'],
|
||||
};
|
||||
}
|
||||
|
@ -65,7 +65,6 @@ export class JournalEntry extends Transactional {
|
||||
|
||||
static getListViewSettings(): ListViewSettings {
|
||||
return {
|
||||
formRoute: (name) => `/edit/JournalEntry/${name}`,
|
||||
columns: [
|
||||
'name',
|
||||
{
|
||||
|
@ -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'],
|
||||
};
|
||||
}
|
||||
|
@ -37,7 +37,6 @@ export class PurchaseInvoice extends Invoice {
|
||||
|
||||
static getListViewSettings(): ListViewSettings {
|
||||
return {
|
||||
formRoute: (name) => `/edit/PurchaseInvoice/${name}`,
|
||||
columns: [
|
||||
'name',
|
||||
getTransactionStatusColumn(),
|
||||
|
@ -37,7 +37,6 @@ export class SalesInvoice extends Invoice {
|
||||
|
||||
static getListViewSettings(): ListViewSettings {
|
||||
return {
|
||||
formRoute: (name) => `/edit/SalesInvoice/${name}`,
|
||||
columns: [
|
||||
'name',
|
||||
getTransactionStatusColumn(),
|
||||
|
@ -8,7 +8,6 @@ export class PurchaseReceipt extends StockTransfer {
|
||||
|
||||
static getListViewSettings(): ListViewSettings {
|
||||
return {
|
||||
formRoute: (name) => `/edit/PurchaseReceipt/${name}`,
|
||||
columns: [
|
||||
'name',
|
||||
getTransactionStatusColumn(),
|
||||
|
@ -8,7 +8,6 @@ export class Shipment extends StockTransfer {
|
||||
|
||||
static getListViewSettings(): ListViewSettings {
|
||||
return {
|
||||
formRoute: (name) => `/edit/Shipment/${name}`,
|
||||
columns: [
|
||||
'name',
|
||||
getTransactionStatusColumn(),
|
||||
|
@ -88,7 +88,6 @@ export class StockMovement extends Transfer {
|
||||
};
|
||||
|
||||
return {
|
||||
formRoute: (name) => `/edit/StockMovement/${name}`,
|
||||
columns: [
|
||||
'name',
|
||||
getDocStatusListColumn(),
|
||||
|
@ -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": [
|
||||
|
@ -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": [
|
||||
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -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": [
|
||||
|
@ -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;
|
||||
},
|
||||
|
@ -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() {
|
||||
|
@ -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>
|
||||
|
@ -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>
|
@ -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>
|
@ -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>
|
||||
|
@ -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"
|
||||
|
113
src/pages/CommonForm/RowEditForm.vue
Normal file
113
src/pages/CommonForm/RowEditForm.vue
Normal 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>
|
@ -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>
|
||||
|
@ -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 {
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
@ -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;
|
||||
});
|
||||
|
||||
|
@ -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[];
|
||||
|
@ -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(
|
||||
|
Loading…
x
Reference in New Issue
Block a user