mirror of
https://github.com/frappe/books.git
synced 2024-12-22 19:09:01 +00:00
feat: add view transfer/payments on invoices
This commit is contained in:
parent
1f259524b9
commit
bea9d86b91
@ -7,14 +7,14 @@ import {
|
|||||||
DefaultMap,
|
DefaultMap,
|
||||||
FiltersMap,
|
FiltersMap,
|
||||||
FormulaMap,
|
FormulaMap,
|
||||||
HiddenMap,
|
HiddenMap
|
||||||
} from 'fyo/model/types';
|
} from 'fyo/model/types';
|
||||||
import { DEFAULT_CURRENCY } from 'fyo/utils/consts';
|
import { DEFAULT_CURRENCY } from 'fyo/utils/consts';
|
||||||
import { ValidationError } from 'fyo/utils/errors';
|
import { ValidationError } from 'fyo/utils/errors';
|
||||||
import {
|
import {
|
||||||
getExchangeRate,
|
getExchangeRate,
|
||||||
getInvoiceActions,
|
getInvoiceActions,
|
||||||
getNumberSeries,
|
getNumberSeries
|
||||||
} from 'models/helpers';
|
} from 'models/helpers';
|
||||||
import { InventorySettings } from 'models/inventory/InventorySettings';
|
import { InventorySettings } from 'models/inventory/InventorySettings';
|
||||||
import { StockTransfer } from 'models/inventory/StockTransfer';
|
import { StockTransfer } from 'models/inventory/StockTransfer';
|
||||||
@ -22,7 +22,10 @@ import { Transactional } from 'models/Transactional/Transactional';
|
|||||||
import { ModelNameEnum } from 'models/types';
|
import { ModelNameEnum } from 'models/types';
|
||||||
import { Money } from 'pesa';
|
import { Money } from 'pesa';
|
||||||
import { FieldTypeEnum, Schema } from 'schemas/types';
|
import { FieldTypeEnum, Schema } from 'schemas/types';
|
||||||
import { getIsNullOrUndef, safeParseFloat } from 'utils';
|
import {
|
||||||
|
getIsNullOrUndef, joinMapLists,
|
||||||
|
safeParseFloat
|
||||||
|
} from 'utils';
|
||||||
import { Defaults } from '../Defaults/Defaults';
|
import { Defaults } from '../Defaults/Defaults';
|
||||||
import { InvoiceItem } from '../InvoiceItem/InvoiceItem';
|
import { InvoiceItem } from '../InvoiceItem/InvoiceItem';
|
||||||
import { Item } from '../Item/Item';
|
import { Item } from '../Item/Item';
|
||||||
@ -79,6 +82,22 @@ export abstract class Invoice extends Transactional {
|
|||||||
: ModelNameEnum.PurchaseReceipt;
|
: ModelNameEnum.PurchaseReceipt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get hasLinkedTransfers() {
|
||||||
|
if (!this.submitted) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.getStockTransferred() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
get hasLinkedPayments() {
|
||||||
|
if (!this.submitted) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !this.baseGrandTotal?.eq(this.outstandingAmount!);
|
||||||
|
}
|
||||||
|
|
||||||
constructor(schema: Schema, data: DocValueMap, fyo: Fyo) {
|
constructor(schema: Schema, data: DocValueMap, fyo: Fyo) {
|
||||||
super(schema, data, fyo);
|
super(schema, data, fyo);
|
||||||
this._setGetCurrencies();
|
this._setGetCurrencies();
|
||||||
@ -369,6 +388,14 @@ export abstract class Invoice extends Transactional {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
getStockTransferred() {
|
||||||
|
return (this.items ?? []).reduce(
|
||||||
|
(acc, item) =>
|
||||||
|
(item.quantity ?? 0) - (item.stockNotTransferred ?? 0) + acc,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
getStockNotTransferred() {
|
getStockNotTransferred() {
|
||||||
return (this.items ?? []).reduce(
|
return (this.items ?? []).reduce(
|
||||||
(acc, item) => (item.stockNotTransferred ?? 0) + acc,
|
(acc, item) => (item.stockNotTransferred ?? 0) + acc,
|
||||||
@ -597,4 +624,78 @@ export abstract class Invoice extends Transactional {
|
|||||||
})) as { name: string }[];
|
})) as { name: string }[];
|
||||||
return transfers;
|
return transfers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getLinkedPayments() {
|
||||||
|
if (!this.hasLinkedPayments) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const paymentFors = (await this.fyo.db.getAllRaw('PaymentFor', {
|
||||||
|
fields: ['parent', 'amount'],
|
||||||
|
filters: { referenceName: this.name!, referenceType: this.schemaName },
|
||||||
|
})) as { parent: string; amount: string }[];
|
||||||
|
|
||||||
|
const payments = (await this.fyo.db.getAllRaw('Payment', {
|
||||||
|
fields: ['name', 'date', 'submitted', 'cancelled'],
|
||||||
|
filters: { name: ['in', paymentFors.map((p) => p.parent)] },
|
||||||
|
})) as {
|
||||||
|
name: string;
|
||||||
|
date: string;
|
||||||
|
submitted: number;
|
||||||
|
cancelled: number;
|
||||||
|
}[];
|
||||||
|
|
||||||
|
return joinMapLists(payments, paymentFors, 'name', 'parent')
|
||||||
|
.map((j) => ({
|
||||||
|
name: j.name,
|
||||||
|
date: new Date(j.date),
|
||||||
|
submitted: !!j.submitted,
|
||||||
|
cancelled: !!j.cancelled,
|
||||||
|
amount: this.fyo.pesa(j.amount),
|
||||||
|
}))
|
||||||
|
.sort((a, b) => a.date.valueOf() - b.date.valueOf());
|
||||||
|
}
|
||||||
|
|
||||||
|
async getLinkedStockTransfers() {
|
||||||
|
if (!this.hasLinkedTransfers) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const schemaName = this.stockTransferSchemaName;
|
||||||
|
const transfers = (await this.fyo.db.getAllRaw(schemaName, {
|
||||||
|
fields: ['name', 'date', 'submitted', 'cancelled'],
|
||||||
|
filters: { backReference: this.name! },
|
||||||
|
})) as {
|
||||||
|
name: string;
|
||||||
|
date: string;
|
||||||
|
submitted: number;
|
||||||
|
cancelled: number;
|
||||||
|
}[];
|
||||||
|
|
||||||
|
const itemSchemaName = schemaName + 'Item';
|
||||||
|
const transferItems = (await this.fyo.db.getAllRaw(itemSchemaName, {
|
||||||
|
fields: ['parent', 'quantity', 'location', 'amount'],
|
||||||
|
filters: {
|
||||||
|
parent: ['in', transfers.map((t) => t.name)],
|
||||||
|
item: ['in', this.items!.map((i) => i.item!)],
|
||||||
|
},
|
||||||
|
})) as {
|
||||||
|
parent: string;
|
||||||
|
quantity: number;
|
||||||
|
location: string;
|
||||||
|
amount: string;
|
||||||
|
}[];
|
||||||
|
|
||||||
|
return joinMapLists(transfers, transferItems, 'name', 'parent')
|
||||||
|
.map((j) => ({
|
||||||
|
name: j.name,
|
||||||
|
date: new Date(j.date),
|
||||||
|
submitted: !!j.submitted,
|
||||||
|
cancelled: !!j.cancelled,
|
||||||
|
amount: this.fyo.pesa(j.amount),
|
||||||
|
location: j.location,
|
||||||
|
quantity: j.quantity,
|
||||||
|
}))
|
||||||
|
.sort((a, b) => a.date.valueOf() - b.date.valueOf());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,8 +10,7 @@ import { DateTime } from 'luxon';
|
|||||||
import {
|
import {
|
||||||
getDocStatus,
|
getDocStatus,
|
||||||
getLedgerLinkAction,
|
getLedgerLinkAction,
|
||||||
getNumberSeries,
|
getNumberSeries, getStatusText,
|
||||||
getStatusMap,
|
|
||||||
statusColor
|
statusColor
|
||||||
} from 'models/helpers';
|
} from 'models/helpers';
|
||||||
import { Transactional } from 'models/Transactional/Transactional';
|
import { Transactional } from 'models/Transactional/Transactional';
|
||||||
@ -64,7 +63,7 @@ export class JournalEntry extends Transactional {
|
|||||||
render(doc) {
|
render(doc) {
|
||||||
const status = getDocStatus(doc);
|
const status = getDocStatus(doc);
|
||||||
const color = statusColor[status] ?? 'gray';
|
const color = statusColor[status] ?? 'gray';
|
||||||
const label = getStatusMap()[status];
|
const label = getStatusText(status);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
template: `<Badge class="text-xs" color="${color}">${label}</Badge>`,
|
template: `<Badge class="text-xs" color="${color}">${label}</Badge>`,
|
||||||
|
@ -99,8 +99,6 @@ export function getLedgerLinkAction(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getTransactionStatusColumn(): ColumnConfig {
|
export function getTransactionStatusColumn(): ColumnConfig {
|
||||||
const statusMap = getStatusMap();
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
label: t`Status`,
|
label: t`Status`,
|
||||||
fieldname: 'status',
|
fieldname: 'status',
|
||||||
@ -108,7 +106,7 @@ export function getTransactionStatusColumn(): ColumnConfig {
|
|||||||
render(doc) {
|
render(doc) {
|
||||||
const status = getDocStatus(doc) as InvoiceStatus;
|
const status = getDocStatus(doc) as InvoiceStatus;
|
||||||
const color = statusColor[status];
|
const color = statusColor[status];
|
||||||
const label = statusMap[status];
|
const label = getStatusText(status);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
template: `<Badge class="text-xs" color="${color}">${label}</Badge>`,
|
template: `<Badge class="text-xs" color="${color}">${label}</Badge>`,
|
||||||
@ -131,17 +129,25 @@ export const statusColor: Record<
|
|||||||
Cancelled: 'red',
|
Cancelled: 'red',
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getStatusMap(): Record<DocStatus | InvoiceStatus, string> {
|
export function getStatusText(status: DocStatus | InvoiceStatus): string {
|
||||||
return {
|
switch (status) {
|
||||||
'': '',
|
case 'Draft':
|
||||||
Draft: t`Draft`,
|
return t`Draft`;
|
||||||
Unpaid: t`Unpaid`,
|
case 'Saved':
|
||||||
Paid: t`Paid`,
|
return t`Saved`;
|
||||||
Saved: t`Saved`,
|
case 'NotSaved':
|
||||||
NotSaved: t`Not Saved`,
|
return t`NotSaved`;
|
||||||
Submitted: t`Submitted`,
|
case 'Submitted':
|
||||||
Cancelled: t`Cancelled`,
|
return t`Submitted`;
|
||||||
};
|
case 'Cancelled':
|
||||||
|
return t`Cancelled`;
|
||||||
|
case 'Paid':
|
||||||
|
return t`Paid`;
|
||||||
|
case 'Unpaid':
|
||||||
|
return t`Unpaid`;
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDocStatus(
|
export function getDocStatus(
|
||||||
@ -296,7 +302,7 @@ export function getDocStatusListColumn(): ColumnConfig {
|
|||||||
render(doc) {
|
render(doc) {
|
||||||
const status = getDocStatus(doc);
|
const status = getDocStatus(doc);
|
||||||
const color = statusColor[status] ?? 'gray';
|
const color = statusColor[status] ?? 'gray';
|
||||||
const label = getStatusMap()[status];
|
const label = getStatusText(status);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
template: `<Badge class="text-xs" color="${color}">${label}</Badge>`,
|
template: `<Badge class="text-xs" color="${color}">${label}</Badge>`,
|
||||||
|
@ -1,22 +1,25 @@
|
|||||||
<template>
|
<template>
|
||||||
<Badge class="text-sm flex-center px-3 ml-2" :color="color" v-if="status">{{
|
<Badge
|
||||||
statusLabel
|
class="flex-center"
|
||||||
}}</Badge>
|
:color="color"
|
||||||
|
v-if="status"
|
||||||
|
:class="defaultSize ? 'text-sm px-3' : ''"
|
||||||
|
>{{ statusLabel }}</Badge
|
||||||
|
>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import { getStatusMap, statusColor } from 'models/helpers';
|
import { getStatusText, statusColor } from 'models/helpers';
|
||||||
import Badge from './Badge.vue';
|
import Badge from './Badge.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'StatusBadge',
|
name: 'StatusBadge',
|
||||||
props: ['status'],
|
props: { status: String, defaultSize: { type: Boolean, default: true } },
|
||||||
computed: {
|
computed: {
|
||||||
color() {
|
color() {
|
||||||
return statusColor[this.status];
|
return statusColor[this.status];
|
||||||
},
|
},
|
||||||
statusLabel() {
|
statusLabel() {
|
||||||
const statusMap = getStatusMap();
|
return getStatusText(this.status) || this.status;
|
||||||
return statusMap[this.status] ?? this.status;
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
components: { Badge },
|
components: { Badge },
|
||||||
|
90
src/components/Widgets/LinkedEntryWidget.vue
Normal file
90
src/components/Widgets/LinkedEntryWidget.vue
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
<template>
|
||||||
|
<div class="w-quick-edit border-l 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 { getEntryRoute } from 'src/router';
|
||||||
|
import { getStatus, 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,
|
||||||
|
async openEntry(name: string) {
|
||||||
|
console.log('op', name);
|
||||||
|
const route = getEntryRoute(this.linked.schemaName, name);
|
||||||
|
await routeTo(route);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: { Button, StatusBadge },
|
||||||
|
});
|
||||||
|
</script>
|
@ -4,7 +4,7 @@
|
|||||||
<template #header v-if="doc">
|
<template #header v-if="doc">
|
||||||
<StatusBadge :status="status" />
|
<StatusBadge :status="status" />
|
||||||
<DropdownWithActions
|
<DropdownWithActions
|
||||||
v-for="group of groupedActions()"
|
v-for="group of groupedActions"
|
||||||
:key="group.label"
|
:key="group.label"
|
||||||
:type="group.type"
|
:type="group.type"
|
||||||
:actions="group.actions"
|
:actions="group.actions"
|
||||||
@ -163,8 +163,7 @@ import { fyo } from 'src/initFyo';
|
|||||||
import { docsPathMap } from 'src/utils/misc';
|
import { docsPathMap } from 'src/utils/misc';
|
||||||
import {
|
import {
|
||||||
docsPath,
|
docsPath,
|
||||||
getActionsForDocument,
|
getGroupedActionsForDoc,
|
||||||
getGroupedActionsForDocument,
|
|
||||||
routeTo,
|
routeTo,
|
||||||
showMessageDialog,
|
showMessageDialog,
|
||||||
} from 'src/utils/ui';
|
} from 'src/utils/ui';
|
||||||
@ -209,6 +208,9 @@ export default {
|
|||||||
this.chstatus;
|
this.chstatus;
|
||||||
return getDocStatus(this.doc);
|
return getDocStatus(this.doc);
|
||||||
},
|
},
|
||||||
|
groupedActions() {
|
||||||
|
return getGroupedActionsForDoc(this.doc);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
activated() {
|
activated() {
|
||||||
docsPath.value = docsPathMap[this.schemaName];
|
docsPath.value = docsPathMap[this.schemaName];
|
||||||
@ -248,9 +250,6 @@ export default {
|
|||||||
this.quickEditDoc = doc;
|
this.quickEditDoc = doc;
|
||||||
this.quickEditFields = fields;
|
this.quickEditFields = fields;
|
||||||
},
|
},
|
||||||
groupedActions() {
|
|
||||||
return getGroupedActionsForDocument(this.doc);
|
|
||||||
},
|
|
||||||
getField(fieldname) {
|
getField(fieldname) {
|
||||||
return fyo.getField(this.schemaName, fieldname);
|
return fyo.getField(this.schemaName, fieldname);
|
||||||
},
|
},
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
<feather-icon name="settings" class="w-4 h-4" />
|
<feather-icon name="settings" class="w-4 h-4" />
|
||||||
</Button>
|
</Button>
|
||||||
<DropdownWithActions
|
<DropdownWithActions
|
||||||
v-for="group of groupedActions()"
|
v-for="group of groupedActions"
|
||||||
:key="group.label"
|
:key="group.label"
|
||||||
:type="group.type"
|
:type="group.type"
|
||||||
:actions="group.actions"
|
:actions="group.actions"
|
||||||
@ -267,8 +267,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #quickedit v-if="quickEditDoc">
|
<template #quickedit v-if="quickEditDoc || linked">
|
||||||
<QuickEditForm
|
<QuickEditForm
|
||||||
|
v-if="quickEditDoc && !linked"
|
||||||
class="w-quick-edit"
|
class="w-quick-edit"
|
||||||
:name="quickEditDoc.name"
|
:name="quickEditDoc.name"
|
||||||
:show-name="false"
|
:show-name="false"
|
||||||
@ -281,6 +282,12 @@
|
|||||||
:load-on-close="false"
|
:load-on-close="false"
|
||||||
@close="toggleQuickEditDoc(null)"
|
@close="toggleQuickEditDoc(null)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<LinkedEntryWidget
|
||||||
|
v-if="linked && !quickEditDoc"
|
||||||
|
:linked="linked"
|
||||||
|
@close-widget="linked = null"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
</FormContainer>
|
</FormContainer>
|
||||||
</template>
|
</template>
|
||||||
@ -296,11 +303,13 @@ import DropdownWithActions from 'src/components/DropdownWithActions.vue';
|
|||||||
import FormContainer from 'src/components/FormContainer.vue';
|
import FormContainer from 'src/components/FormContainer.vue';
|
||||||
import FormHeader from 'src/components/FormHeader.vue';
|
import FormHeader from 'src/components/FormHeader.vue';
|
||||||
import StatusBadge from 'src/components/StatusBadge.vue';
|
import StatusBadge from 'src/components/StatusBadge.vue';
|
||||||
|
import LinkedEntryWidget from 'src/components/Widgets/LinkedEntryWidget.vue';
|
||||||
import { fyo } from 'src/initFyo';
|
import { fyo } from 'src/initFyo';
|
||||||
import { docsPathMap } from 'src/utils/misc';
|
import { docsPathMap } from 'src/utils/misc';
|
||||||
import {
|
import {
|
||||||
docsPath,
|
docsPath,
|
||||||
getGroupedActionsForDocument,
|
getGroupedActionsForDoc,
|
||||||
|
getStatus,
|
||||||
routeTo,
|
routeTo,
|
||||||
showMessageDialog,
|
showMessageDialog,
|
||||||
} from 'src/utils/ui';
|
} from 'src/utils/ui';
|
||||||
@ -321,6 +330,7 @@ export default {
|
|||||||
QuickEditForm,
|
QuickEditForm,
|
||||||
ExchangeRate,
|
ExchangeRate,
|
||||||
FormHeader,
|
FormHeader,
|
||||||
|
LinkedEntryWidget,
|
||||||
},
|
},
|
||||||
provide() {
|
provide() {
|
||||||
return {
|
return {
|
||||||
@ -338,12 +348,42 @@ export default {
|
|||||||
color: null,
|
color: null,
|
||||||
printSettings: null,
|
printSettings: null,
|
||||||
companyName: null,
|
companyName: null,
|
||||||
|
linked: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
updated() {
|
updated() {
|
||||||
this.chstatus = !this.chstatus;
|
this.chstatus = !this.chstatus;
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
groupedActions() {
|
||||||
|
const actions = getGroupedActionsForDoc(this.doc);
|
||||||
|
const group = this.t`View`;
|
||||||
|
const viewgroup = actions.find((f) => f.group === group);
|
||||||
|
|
||||||
|
if (viewgroup && this.doc?.hasLinkedPayments) {
|
||||||
|
viewgroup.actions.push({
|
||||||
|
label: this.t`Payments`,
|
||||||
|
group,
|
||||||
|
condition: (doc) => doc.hasLinkedPayments,
|
||||||
|
action: async () => this.setlinked(ModelNameEnum.Payment),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (viewgroup && this.doc?.hasLinkedTransfers) {
|
||||||
|
const label = this.doc.isSales
|
||||||
|
? this.t`Shipments`
|
||||||
|
: this.t`Purchase Receipts`;
|
||||||
|
|
||||||
|
viewgroup.actions.push({
|
||||||
|
label,
|
||||||
|
group,
|
||||||
|
condition: (doc) => doc.hasLinkedTransfers,
|
||||||
|
action: async () => this.setlinked(this.doc.stockTransferSchemaName),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return actions;
|
||||||
|
},
|
||||||
address() {
|
address() {
|
||||||
return this.printSettings && this.printSettings.getLink('address');
|
return this.printSettings && this.printSettings.getLink('address');
|
||||||
},
|
},
|
||||||
@ -416,6 +456,26 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
routeTo,
|
routeTo,
|
||||||
|
async setlinked(schemaName) {
|
||||||
|
let entries = [];
|
||||||
|
let title = '';
|
||||||
|
|
||||||
|
if (schemaName === ModelNameEnum.Payment) {
|
||||||
|
title = this.t`Payments`;
|
||||||
|
entries = await this.doc.getLinkedPayments();
|
||||||
|
} else {
|
||||||
|
title = this.doc.isSales
|
||||||
|
? this.t`Shipments`
|
||||||
|
: this.t`Purchase Receipts`;
|
||||||
|
entries = await this.doc.getLinkedStockTransfers();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.quickEditDoc) {
|
||||||
|
this.toggleQuickEditDoc(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.linked = { entries, schemaName, title };
|
||||||
|
},
|
||||||
toggleInvoiceSettings() {
|
toggleInvoiceSettings() {
|
||||||
if (!this.schemaName) {
|
if (!this.schemaName) {
|
||||||
return;
|
return;
|
||||||
@ -446,9 +506,6 @@ export default {
|
|||||||
|
|
||||||
this.quickEditFields = fields;
|
this.quickEditFields = fields;
|
||||||
},
|
},
|
||||||
groupedActions() {
|
|
||||||
return getGroupedActionsForDocument(this.doc);
|
|
||||||
},
|
|
||||||
getField(fieldname) {
|
getField(fieldname) {
|
||||||
return fyo.getField(this.schemaName, fieldname);
|
return fyo.getField(this.schemaName, fieldname);
|
||||||
},
|
},
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<template #header v-if="doc">
|
<template #header v-if="doc">
|
||||||
<StatusBadge :status="status" />
|
<StatusBadge :status="status" />
|
||||||
<DropdownWithActions
|
<DropdownWithActions
|
||||||
v-for="group of groupedActions()"
|
v-for="group of groupedActions"
|
||||||
:key="group.label"
|
:key="group.label"
|
||||||
:type="group.type"
|
:type="group.type"
|
||||||
:actions="group.actions"
|
:actions="group.actions"
|
||||||
@ -150,8 +150,7 @@ import { fyo } from 'src/initFyo';
|
|||||||
import { docsPathMap } from 'src/utils/misc';
|
import { docsPathMap } from 'src/utils/misc';
|
||||||
import {
|
import {
|
||||||
docsPath,
|
docsPath,
|
||||||
getActionsForDocument,
|
getGroupedActionsForDoc,
|
||||||
getGroupedActionsForDocument,
|
|
||||||
routeTo,
|
routeTo,
|
||||||
showMessageDialog,
|
showMessageDialog,
|
||||||
} from 'src/utils/ui';
|
} from 'src/utils/ui';
|
||||||
@ -222,14 +221,14 @@ export default {
|
|||||||
}
|
}
|
||||||
return fyo.format(value, 'Currency');
|
return fyo.format(value, 'Currency');
|
||||||
},
|
},
|
||||||
|
groupedActions() {
|
||||||
|
return getGroupedActionsForDoc(this.doc);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getField(fieldname) {
|
getField(fieldname) {
|
||||||
return fyo.getField(ModelNameEnum.JournalEntry, fieldname);
|
return fyo.getField(ModelNameEnum.JournalEntry, fieldname);
|
||||||
},
|
},
|
||||||
groupedActions() {
|
|
||||||
return getGroupedActionsForDocument(this.doc);
|
|
||||||
},
|
|
||||||
async sync() {
|
async sync() {
|
||||||
try {
|
try {
|
||||||
await this.doc.sync();
|
await this.doc.sync();
|
||||||
|
@ -110,7 +110,7 @@ import StatusBadge from 'src/components/StatusBadge.vue';
|
|||||||
import TwoColumnForm from 'src/components/TwoColumnForm.vue';
|
import TwoColumnForm from 'src/components/TwoColumnForm.vue';
|
||||||
import { fyo } from 'src/initFyo';
|
import { fyo } from 'src/initFyo';
|
||||||
import { getQuickEditWidget } from 'src/utils/quickEditWidgets';
|
import { getQuickEditWidget } from 'src/utils/quickEditWidgets';
|
||||||
import { getActionsForDocument, openQuickEdit } from 'src/utils/ui';
|
import { getActionsForDoc, openQuickEdit } from 'src/utils/ui';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'QuickEditForm',
|
name: 'QuickEditForm',
|
||||||
@ -198,7 +198,7 @@ export default {
|
|||||||
return fieldnames.map((f) => fyo.getField(this.schemaName, f));
|
return fieldnames.map((f) => fyo.getField(this.schemaName, f));
|
||||||
},
|
},
|
||||||
actions() {
|
actions() {
|
||||||
return getActionsForDocument(this.doc);
|
return getActionsForDoc(this.doc);
|
||||||
},
|
},
|
||||||
quickEditWidget() {
|
quickEditWidget() {
|
||||||
if (this.doc?.notInserted ?? true) {
|
if (this.doc?.notInserted ?? true) {
|
||||||
|
@ -103,7 +103,7 @@ export default defineComponent({
|
|||||||
acc[ac.group] ??= {
|
acc[ac.group] ??= {
|
||||||
group: ac.group,
|
group: ac.group,
|
||||||
label: ac.label ?? '',
|
label: ac.label ?? '',
|
||||||
type: ac.type,
|
e: ac.type,
|
||||||
actions: [],
|
actions: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -156,6 +156,8 @@ export function getEntryRoute(schemaName: string, name: string) {
|
|||||||
ModelNameEnum.SalesInvoice,
|
ModelNameEnum.SalesInvoice,
|
||||||
ModelNameEnum.PurchaseInvoice,
|
ModelNameEnum.PurchaseInvoice,
|
||||||
ModelNameEnum.JournalEntry,
|
ModelNameEnum.JournalEntry,
|
||||||
|
ModelNameEnum.Shipment,
|
||||||
|
ModelNameEnum.PurchaseReceipt,
|
||||||
].includes(schemaName as ModelNameEnum)
|
].includes(schemaName as ModelNameEnum)
|
||||||
) {
|
) {
|
||||||
return `/edit/${schemaName}/${name}`;
|
return `/edit/${schemaName}/${name}`;
|
||||||
|
@ -257,7 +257,7 @@ export async function cancelDocWithPrompt(doc: Doc) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getActionsForDocument(doc?: Doc): Action[] {
|
export function getActionsForDoc(doc?: Doc): Action[] {
|
||||||
if (!doc) return [];
|
if (!doc) return [];
|
||||||
|
|
||||||
const actions: Action[] = [
|
const actions: Action[] = [
|
||||||
@ -279,7 +279,7 @@ export function getActionsForDocument(doc?: Doc): Action[] {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getGroupedActionsForDocument(doc?: Doc) {
|
export function getGroupedActionsForDoc(doc?: Doc) {
|
||||||
type Group = {
|
type Group = {
|
||||||
group: string;
|
group: string;
|
||||||
label: string;
|
label: string;
|
||||||
@ -287,7 +287,7 @@ export function getGroupedActionsForDocument(doc?: Doc) {
|
|||||||
actions: Action[];
|
actions: Action[];
|
||||||
};
|
};
|
||||||
|
|
||||||
const actions = getActionsForDocument(doc);
|
const actions = getActionsForDoc(doc);
|
||||||
const actionsMap = actions.reduce((acc, ac) => {
|
const actionsMap = actions.reduce((acc, ac) => {
|
||||||
if (!ac.group) {
|
if (!ac.group) {
|
||||||
ac.group = '';
|
ac.group = '';
|
||||||
@ -309,7 +309,7 @@ export function getGroupedActionsForDocument(doc?: Doc) {
|
|||||||
.sort()
|
.sort()
|
||||||
.map((k) => actionsMap[k]);
|
.map((k) => actionsMap[k]);
|
||||||
|
|
||||||
return [grouped, actionsMap['']].flat();
|
return [grouped, actionsMap['']].flat().filter(Boolean);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCancelAction(doc: Doc): Action {
|
function getCancelAction(doc: Doc): Action {
|
||||||
@ -397,3 +397,15 @@ function getDuplicateAction(doc: Doc): Action {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getStatus(entry: { cancelled?: boolean; submitted?: boolean }) {
|
||||||
|
if (entry.cancelled) {
|
||||||
|
return 'Cancelled';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.submitted) {
|
||||||
|
return 'Submitted';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'Saved';
|
||||||
|
}
|
||||||
|
@ -186,3 +186,34 @@ export function safeParseFloat(value: unknown): number {
|
|||||||
export function safeParseInt(value: unknown): number {
|
export function safeParseInt(value: unknown): number {
|
||||||
return safeParseNumber(value, parseInt);
|
return safeParseNumber(value, parseInt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function joinMapLists<A, B>(
|
||||||
|
listA: A[],
|
||||||
|
listB: B[],
|
||||||
|
keyA: keyof A,
|
||||||
|
keyB: keyof B
|
||||||
|
): (A & B)[] {
|
||||||
|
const mapA = getMapFromList(listA, keyA);
|
||||||
|
const mapB = getMapFromList(listB, keyB);
|
||||||
|
|
||||||
|
const keyListA = listA
|
||||||
|
.map((i) => i[keyA])
|
||||||
|
.filter((k) => (k as unknown as string) in mapB);
|
||||||
|
|
||||||
|
const keyListB = listB
|
||||||
|
.map((i) => i[keyB])
|
||||||
|
.filter((k) => (k as unknown as string) in mapA);
|
||||||
|
|
||||||
|
const keys = new Set([keyListA, keyListB].flat().sort());
|
||||||
|
|
||||||
|
const joint: (A & B)[] = [];
|
||||||
|
for (const k of keys) {
|
||||||
|
const a = mapA[k as unknown as string];
|
||||||
|
const b = mapB[k as unknown as string];
|
||||||
|
const c = { ...a, ...b };
|
||||||
|
|
||||||
|
joint.push(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
return joint;
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user