mirror of
https://github.com/frappe/books.git
synced 2024-11-08 14:50:56 +00:00
Merge pull request #568 from frappe/navigable-link-fields
feat: navigable link fields
This commit is contained in:
commit
205ef4aa6a
@ -87,7 +87,7 @@ export interface ColumnConfig {
|
|||||||
|
|
||||||
export type ListViewColumn = string | ColumnConfig;
|
export type ListViewColumn = string | ColumnConfig;
|
||||||
export interface ListViewSettings {
|
export interface ListViewSettings {
|
||||||
formRoute?: (doc: Doc) => RouteLocationRaw;
|
formRoute?: (name: string) => RouteLocationRaw;
|
||||||
columns?: ListViewColumn[];
|
columns?: ListViewColumn[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,7 +112,7 @@ export class Item extends Doc {
|
|||||||
|
|
||||||
static getListViewSettings(): ListViewSettings {
|
static getListViewSettings(): ListViewSettings {
|
||||||
return {
|
return {
|
||||||
formRoute: ({ name }) => `/edit/Item/${name}`,
|
formRoute: (name) => `/edit/Item/${name}`,
|
||||||
columns: ['name', 'unit', 'tax', 'rate'],
|
columns: ['name', 'unit', 'tax', 'rate'],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,7 @@ export class JournalEntry extends Transactional {
|
|||||||
|
|
||||||
static getListViewSettings(): ListViewSettings {
|
static getListViewSettings(): ListViewSettings {
|
||||||
return {
|
return {
|
||||||
formRoute: ({ name }) => `/edit/JournalEntry/${name}`,
|
formRoute: (name) => `/edit/JournalEntry/${name}`,
|
||||||
columns: [
|
columns: [
|
||||||
'name',
|
'name',
|
||||||
{
|
{
|
||||||
|
@ -617,7 +617,7 @@ export class Payment extends Transactional {
|
|||||||
|
|
||||||
static getListViewSettings(fyo: Fyo): ListViewSettings {
|
static getListViewSettings(fyo: Fyo): ListViewSettings {
|
||||||
return {
|
return {
|
||||||
formRoute: ({ name }) => `/edit/Payment/${name}`,
|
formRoute: (name) => `/edit/Payment/${name}`,
|
||||||
columns: ['name', getDocStatusListColumn(), 'party', 'date', 'amount'],
|
columns: ['name', getDocStatusListColumn(), 'party', 'date', 'amount'],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,7 @@ export class PurchaseInvoice extends Invoice {
|
|||||||
|
|
||||||
static getListViewSettings(): ListViewSettings {
|
static getListViewSettings(): ListViewSettings {
|
||||||
return {
|
return {
|
||||||
formRoute: ({ name }) => `/edit/PurchaseInvoice/${name}`,
|
formRoute: (name) => `/edit/PurchaseInvoice/${name}`,
|
||||||
columns: [
|
columns: [
|
||||||
'name',
|
'name',
|
||||||
getTransactionStatusColumn(),
|
getTransactionStatusColumn(),
|
||||||
|
@ -37,7 +37,7 @@ export class SalesInvoice extends Invoice {
|
|||||||
|
|
||||||
static getListViewSettings(): ListViewSettings {
|
static getListViewSettings(): ListViewSettings {
|
||||||
return {
|
return {
|
||||||
formRoute: ({ name }) => `/edit/SalesInvoice/${name}`,
|
formRoute: (name) => `/edit/SalesInvoice/${name}`,
|
||||||
columns: [
|
columns: [
|
||||||
'name',
|
'name',
|
||||||
getTransactionStatusColumn(),
|
getTransactionStatusColumn(),
|
||||||
|
@ -8,7 +8,7 @@ export class PurchaseReceipt extends StockTransfer {
|
|||||||
|
|
||||||
static getListViewSettings(): ListViewSettings {
|
static getListViewSettings(): ListViewSettings {
|
||||||
return {
|
return {
|
||||||
formRoute: ({ name }) => `/edit/PurchaseReceipt/${name}`,
|
formRoute: (name) => `/edit/PurchaseReceipt/${name}`,
|
||||||
columns: [
|
columns: [
|
||||||
'name',
|
'name',
|
||||||
getTransactionStatusColumn(),
|
getTransactionStatusColumn(),
|
||||||
|
@ -8,7 +8,7 @@ export class Shipment extends StockTransfer {
|
|||||||
|
|
||||||
static getListViewSettings(): ListViewSettings {
|
static getListViewSettings(): ListViewSettings {
|
||||||
return {
|
return {
|
||||||
formRoute: ({ name }) => `/edit/Shipment/${name}`,
|
formRoute: (name) => `/edit/Shipment/${name}`,
|
||||||
columns: [
|
columns: [
|
||||||
'name',
|
'name',
|
||||||
getTransactionStatusColumn(),
|
getTransactionStatusColumn(),
|
||||||
|
@ -81,7 +81,7 @@ export class StockMovement extends Transfer {
|
|||||||
|
|
||||||
static getListViewSettings(fyo: Fyo): ListViewSettings {
|
static getListViewSettings(fyo: Fyo): ListViewSettings {
|
||||||
return {
|
return {
|
||||||
formRoute: ({ name }) => `/edit/StockMovement/${name}`,
|
formRoute: (name) => `/edit/StockMovement/${name}`,
|
||||||
columns: [
|
columns: [
|
||||||
'name',
|
'name',
|
||||||
getDocStatusListColumn(),
|
getDocStatusListColumn(),
|
||||||
|
@ -50,7 +50,7 @@
|
|||||||
:class="[!isReadOnly ? 'group-hover:flex' : '']"
|
:class="[!isReadOnly ? 'group-hover:flex' : '']"
|
||||||
style="background: rgba(0, 0, 0, 0.2); backdrop-filter: blur(2px)"
|
style="background: rgba(0, 0, 0, 0.2); backdrop-filter: blur(2px)"
|
||||||
>
|
>
|
||||||
<button class="bg-gray-300 p-0.5 rounded mb-1" @click="handleClick">
|
<button class="bg-gray-100 p-0.5 rounded mb-1" @click="handleClick">
|
||||||
<FeatherIcon
|
<FeatherIcon
|
||||||
:name="shouldClear ? 'x' : 'upload'"
|
:name="shouldClear ? 'x' : 'upload'"
|
||||||
class="w-4 h-4 text-gray-600"
|
class="w-4 h-4 text-gray-600"
|
||||||
|
@ -21,25 +21,21 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Buttons -->
|
<!-- Buttons -->
|
||||||
<div class="me-2 flex gap-2">
|
<div class="me-2 flex gap-1">
|
||||||
<!-- Upload Button -->
|
<!-- Upload Button -->
|
||||||
<button v-if="!value" class="bg-gray-300 p-0.5 rounded" @click="upload">
|
<button v-if="!value" class="p-0.5 rounded" @click="upload">
|
||||||
<FeatherIcon name="upload" class="h-4 w-4 text-gray-600" />
|
<FeatherIcon name="upload" class="h-4 w-4 text-gray-600" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- Download Button -->
|
<!-- Download Button -->
|
||||||
<button
|
<button v-if="value" class="p-0.5 rounded" @click="download">
|
||||||
v-if="value"
|
|
||||||
class="bg-gray-300 p-0.5 rounded"
|
|
||||||
@click="download"
|
|
||||||
>
|
|
||||||
<FeatherIcon name="download" class="h-4 w-4 text-gray-600" />
|
<FeatherIcon name="download" class="h-4 w-4 text-gray-600" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- Clear Button -->
|
<!-- Clear Button -->
|
||||||
<button
|
<button
|
||||||
v-if="value && !isReadOnly"
|
v-if="value && !isReadOnly"
|
||||||
class="bg-gray-300 p-0.5 rounded"
|
class="p-0.5 rounded"
|
||||||
@click="clear"
|
@click="clear"
|
||||||
>
|
>
|
||||||
<FeatherIcon name="x" class="h-4 w-4 text-gray-600" />
|
<FeatherIcon name="x" class="h-4 w-4 text-gray-600" />
|
||||||
|
@ -35,7 +35,7 @@
|
|||||||
@keydown.esc="toggleDropdown(false)"
|
@keydown.esc="toggleDropdown(false)"
|
||||||
/>
|
/>
|
||||||
<svg
|
<svg
|
||||||
v-if="!isReadOnly"
|
v-if="!isReadOnly && !canLink"
|
||||||
class="w-3 h-3"
|
class="w-3 h-3"
|
||||||
style="background: inherit; margin-right: -3px"
|
style="background: inherit; margin-right: -3px"
|
||||||
viewBox="0 0 5 10"
|
viewBox="0 0 5 10"
|
||||||
@ -45,21 +45,30 @@
|
|||||||
<path
|
<path
|
||||||
d="M1 2.636L2.636 1l1.637 1.636M1 7.364L2.636 9l1.637-1.636"
|
d="M1 2.636L2.636 1l1.637 1.636M1 7.364L2.636 9l1.637-1.636"
|
||||||
class="stroke-current"
|
class="stroke-current"
|
||||||
:class="showMandatory ? 'text-red-500' : 'text-gray-500'"
|
:class="showMandatory ? 'text-red-400' : 'text-gray-400'"
|
||||||
fill="none"
|
fill="none"
|
||||||
fill-rule="evenodd"
|
fill-rule="evenodd"
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-linejoin="round"
|
stroke-linejoin="round"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
|
<button
|
||||||
|
v-if="canLink"
|
||||||
|
class="p-0.5 rounded -me1 bg-transparent"
|
||||||
|
@click="routeToLinkedDoc"
|
||||||
|
>
|
||||||
|
<FeatherIcon name="chevron-right" class="w-4 h-4 text-gray-600" />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import { getOptionList } from 'fyo/utils';
|
import { getOptionList } from 'fyo/utils';
|
||||||
|
import { FieldTypeEnum } from 'schemas/types';
|
||||||
import Dropdown from 'src/components/Dropdown.vue';
|
import Dropdown from 'src/components/Dropdown.vue';
|
||||||
import { fuzzyMatch } from 'src/utils';
|
import { fuzzyMatch } from 'src/utils';
|
||||||
|
import { getFormRoute, routeTo } from 'src/utils/ui';
|
||||||
import Base from './Base.vue';
|
import Base from './Base.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -100,8 +109,52 @@ export default {
|
|||||||
|
|
||||||
return getOptionList(this.df, this.doc);
|
return getOptionList(this.df, this.doc);
|
||||||
},
|
},
|
||||||
|
canLink() {
|
||||||
|
if (!this.value || !this.df) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fieldtype = this.df?.fieldtype;
|
||||||
|
const isLink = fieldtype === FieldTypeEnum.Link;
|
||||||
|
const isDynamicLink = fieldtype === FieldTypeEnum.DynamicLink;
|
||||||
|
|
||||||
|
if (!isLink && !isDynamicLink) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLink && this.df.target) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const references = this.df.references;
|
||||||
|
if (!references) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.doc?.[references]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
async routeToLinkedDoc() {
|
||||||
|
let schemaName = this.df?.target;
|
||||||
|
const name = this.value;
|
||||||
|
|
||||||
|
if (!schemaName) {
|
||||||
|
const references = this.df?.references ?? '';
|
||||||
|
schemaName = this.doc?.[references];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!schemaName || !name) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const route = getFormRoute(schemaName, name);
|
||||||
|
await routeTo(route);
|
||||||
|
},
|
||||||
setLinkValue(value) {
|
setLinkValue(value) {
|
||||||
this.linkValue = value;
|
this.linkValue = value;
|
||||||
},
|
},
|
||||||
|
@ -21,12 +21,7 @@
|
|||||||
{{ inputPlaceholder }}
|
{{ inputPlaceholder }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<button
|
<button v-if="!isReadOnly" class="p-0.5 rounded -me-1 ms-1">
|
||||||
v-if="!isReadOnly"
|
|
||||||
class="p-0.5 rounded -me-1 ms-1"
|
|
||||||
:class="showMandatory ? 'bg-red-300' : 'bg-gray-300'"
|
|
||||||
@click="togglePopover"
|
|
||||||
>
|
|
||||||
<FeatherIcon
|
<FeatherIcon
|
||||||
name="calendar"
|
name="calendar"
|
||||||
class="w-4 h-4"
|
class="w-4 h-4"
|
||||||
|
@ -50,7 +50,7 @@
|
|||||||
<path
|
<path
|
||||||
d="M1 2.636L2.636 1l1.637 1.636M1 7.364L2.636 9l1.637-1.636"
|
d="M1 2.636L2.636 1l1.637 1.636M1 7.364L2.636 9l1.637-1.636"
|
||||||
class="stroke-current"
|
class="stroke-current"
|
||||||
:class="showMandatory ? 'text-red-500' : 'text-gray-500'"
|
:class="showMandatory ? 'text-red-400' : 'text-gray-400'"
|
||||||
fill="none"
|
fill="none"
|
||||||
fill-rule="evenodd"
|
fill-rule="evenodd"
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
|
@ -55,8 +55,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Money } from 'pesa';
|
import { Money } from 'pesa';
|
||||||
import { getCreateRoute } from 'src/router';
|
import { getFormRoute, routeTo } from 'src/utils/ui';
|
||||||
import { routeTo } from 'src/utils/ui';
|
|
||||||
import { defineComponent, PropType } from 'vue';
|
import { defineComponent, PropType } from 'vue';
|
||||||
import Button from '../Button.vue';
|
import Button from '../Button.vue';
|
||||||
import StatusBadge from '../StatusBadge.vue';
|
import StatusBadge from '../StatusBadge.vue';
|
||||||
@ -90,7 +89,7 @@ export default defineComponent({
|
|||||||
return 'Saved';
|
return 'Saved';
|
||||||
},
|
},
|
||||||
async openEntry(name: string) {
|
async openEntry(name: string) {
|
||||||
const route = getCreateRoute(this.linked.schemaName, name);
|
const route = getFormRoute(this.linked.schemaName, name);
|
||||||
await routeTo(route);
|
await routeTo(route);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -100,7 +100,7 @@ export default {
|
|||||||
const doc = await this.fyo.doc.getDoc(this.schemaName, name);
|
const doc = await this.fyo.doc.getDoc(this.schemaName, name);
|
||||||
|
|
||||||
if (this.listConfig.formRoute) {
|
if (this.listConfig.formRoute) {
|
||||||
return await routeTo(this.listConfig.formRoute(doc));
|
return await routeTo(this.listConfig.formRoute(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
const { routeFilter } = getRouteData({ doc });
|
const { routeFilter } = getRouteData({ doc });
|
||||||
@ -137,7 +137,7 @@ export default {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (this.listConfig.formRoute) {
|
if (this.listConfig.formRoute) {
|
||||||
path = this.listConfig.formRoute(doc);
|
path = this.listConfig.formRoute(doc.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof path === 'object') {
|
if (typeof path === 'object') {
|
||||||
|
@ -141,28 +141,6 @@ const routes: RouteRecordRaw[] = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export function getCreateRoute(
|
|
||||||
schemaName: string,
|
|
||||||
name: string
|
|
||||||
): RouteLocationRaw {
|
|
||||||
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}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const router = createRouter({ routes, history: createWebHistory() });
|
const router = createRouter({ routes, history: createWebHistory() });
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -4,13 +4,12 @@ import { groupBy } from 'lodash';
|
|||||||
import { ModelNameEnum } from 'models/types';
|
import { ModelNameEnum } from 'models/types';
|
||||||
import { reports } from 'reports';
|
import { reports } from 'reports';
|
||||||
import { OptionField } from 'schemas/types';
|
import { OptionField } from 'schemas/types';
|
||||||
import { getCreateRoute } from 'src/router';
|
|
||||||
import { createFilters, routeFilters } from 'src/utils/filters';
|
import { createFilters, routeFilters } from 'src/utils/filters';
|
||||||
import { GetAllOptions } from 'utils/db/types';
|
import { GetAllOptions } from 'utils/db/types';
|
||||||
import { safeParseFloat } from 'utils/index';
|
import { safeParseFloat } from 'utils/index';
|
||||||
import { RouteLocationRaw } from 'vue-router';
|
import { RouteLocationRaw } from 'vue-router';
|
||||||
import { fuzzyMatch } from '.';
|
import { fuzzyMatch } from '.';
|
||||||
import { routeTo } from './ui';
|
import { getFormRoute, routeTo } from './ui';
|
||||||
|
|
||||||
export const searchGroups = ['Docs', 'List', 'Create', 'Report', 'Page'];
|
export const searchGroups = ['Docs', 'List', 'Create', 'Report', 'Page'];
|
||||||
enum SearchGroupEnum {
|
enum SearchGroupEnum {
|
||||||
@ -73,17 +72,6 @@ export function getGroupLabelMap() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function openQuickEditDoc(schemaName: string, fyo: Fyo) {
|
|
||||||
await routeTo(`/list/${schemaName}`);
|
|
||||||
const doc = await fyo.doc.getNewDoc(schemaName);
|
|
||||||
const { openQuickEdit } = await import('src/utils/ui');
|
|
||||||
|
|
||||||
await openQuickEdit({
|
|
||||||
schemaName,
|
|
||||||
name: doc.name as string,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function openFormEditDoc(schemaName: string, fyo: Fyo) {
|
async function openFormEditDoc(schemaName: string, fyo: Fyo) {
|
||||||
const doc = fyo.doc.getNewDoc(schemaName);
|
const doc = fyo.doc.getNewDoc(schemaName);
|
||||||
const name = doc.name;
|
const name = doc.name;
|
||||||
@ -735,10 +723,10 @@ export class Search {
|
|||||||
_getRouteFromKeyword(keyword: Keyword): RouteLocationRaw {
|
_getRouteFromKeyword(keyword: Keyword): RouteLocationRaw {
|
||||||
const { parent, parentSchemaName, schemaName } = keyword.meta;
|
const { parent, parentSchemaName, schemaName } = keyword.meta;
|
||||||
if (parent && parentSchemaName) {
|
if (parent && parentSchemaName) {
|
||||||
return getCreateRoute(parentSchemaName as string, parent as string);
|
return getFormRoute(parentSchemaName as string, parent as string);
|
||||||
}
|
}
|
||||||
|
|
||||||
return getCreateRoute(schemaName as string, keyword.values[0]);
|
return getFormRoute(schemaName as string, keyword.values[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
_getGroupedKeywords() {
|
_getGroupedKeywords() {
|
||||||
|
@ -420,3 +420,33 @@ export function getFieldsGroupedByTabAndSection(
|
|||||||
|
|
||||||
return grouped;
|
return grouped;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getFormRoute(
|
||||||
|
schemaName: string,
|
||||||
|
name: string
|
||||||
|
): RouteLocationRaw {
|
||||||
|
const route = fyo.models[schemaName]
|
||||||
|
?.getListViewSettings(fyo)
|
||||||
|
?.formRoute?.(name);
|
||||||
|
|
||||||
|
if (typeof route === 'string') {
|
||||||
|
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}`;
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user