mirror of
https://github.com/frappe/books.git
synced 2024-11-08 23:00:56 +00:00
fix(ui): propagate row changes
- type ExchangeRate.vue - add BarCode and ExchangeRate widgets to CommonForm - create (not complete) with LinkedEntries.vue
This commit is contained in:
parent
28158e5add
commit
f0a6e7bf9e
@ -194,7 +194,6 @@ export abstract class Invoice extends Transactional {
|
|||||||
account: string;
|
account: string;
|
||||||
rate: number;
|
rate: number;
|
||||||
amount: Money;
|
amount: Money;
|
||||||
[key: string]: DocValue;
|
|
||||||
}
|
}
|
||||||
> = {};
|
> = {};
|
||||||
|
|
||||||
@ -223,11 +222,23 @@ export abstract class Invoice extends Transactional {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Object.keys(taxes)
|
type Summary = typeof taxes[string] & { idx: number };
|
||||||
.map((account) => {
|
const taxArr: Summary[] = [];
|
||||||
return taxes[account];
|
let idx = 0;
|
||||||
})
|
for (const account in taxes) {
|
||||||
.filter((tax) => !tax.amount.isZero());
|
const tax = taxes[account];
|
||||||
|
if (tax.amount.isZero()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
taxArr.push({
|
||||||
|
...tax,
|
||||||
|
idx,
|
||||||
|
});
|
||||||
|
idx += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return taxArr;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getTax(tax: string) {
|
async getTax(tax: string) {
|
||||||
@ -419,6 +430,19 @@ export abstract class Invoice extends Transactional {
|
|||||||
discountPercent: () =>
|
discountPercent: () =>
|
||||||
true || !(this.enableDiscounting && !this.setDiscountAmount),
|
true || !(this.enableDiscounting && !this.setDiscountAmount),
|
||||||
discountAfterTax: () => !this.enableDiscounting,
|
discountAfterTax: () => !this.enableDiscounting,
|
||||||
|
taxes: () => !this.taxes?.length,
|
||||||
|
baseGrandTotal: () =>
|
||||||
|
this.exchangeRate === 1 || this.baseGrandTotal!.isZero(),
|
||||||
|
grandTotal: () => !this.taxes?.length,
|
||||||
|
entryCurrency: () => !this.isMultiCurrency,
|
||||||
|
currency: () => !this.isMultiCurrency,
|
||||||
|
exchangeRate: () => !this.isMultiCurrency,
|
||||||
|
stockNotTransferred: () => !this.stockNotTransferred,
|
||||||
|
outstandingAmount: () =>
|
||||||
|
!!this.outstandingAmount?.isZero() || !this.isSubmitted,
|
||||||
|
terms: () => !(this.terms || !(this.isSubmitted || this.isCancelled)),
|
||||||
|
attachment: () =>
|
||||||
|
!(this.attachment || !(this.isSubmitted || this.isCancelled)),
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaults: DefaultMap = {
|
static defaults: DefaultMap = {
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
ref="toValue"
|
|
||||||
:value="isSwapped ? fromValue / exchangeRate : exchangeRate * fromValue"
|
:value="isSwapped ? fromValue / exchangeRate : exchangeRate * fromValue"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
min="0"
|
min="0"
|
||||||
@ -34,7 +33,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script lang="ts">
|
||||||
import { safeParseFloat } from 'utils/index';
|
import { safeParseFloat } from 'utils/index';
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
@ -53,9 +52,9 @@ export default defineComponent({
|
|||||||
swap() {
|
swap() {
|
||||||
this.isSwapped = !this.isSwapped;
|
this.isSwapped = !this.isSwapped;
|
||||||
},
|
},
|
||||||
rightChange(e) {
|
rightChange(e: Event) {
|
||||||
let value = this.$refs.toValue.value;
|
let value: string | number = 1;
|
||||||
if (e) {
|
if (e.target instanceof HTMLInputElement) {
|
||||||
value = e.target.value;
|
value = e.target.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,14 +69,14 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
left() {
|
left(): string {
|
||||||
if (this.isSwapped) {
|
if (this.isSwapped) {
|
||||||
return this.toCurrency;
|
return this.toCurrency;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.fromCurrency;
|
return this.fromCurrency;
|
||||||
},
|
},
|
||||||
right() {
|
right(): string {
|
||||||
if (this.isSwapped) {
|
if (this.isSwapped) {
|
||||||
return this.fromCurrency;
|
return this.fromCurrency;
|
||||||
}
|
}
|
||||||
|
@ -39,8 +39,9 @@
|
|||||||
:key="row.name"
|
:key="row.name"
|
||||||
v-bind="{ row, tableFields, size, ratio, isNumeric }"
|
v-bind="{ row, tableFields, size, ratio, isNumeric }"
|
||||||
:read-only="isReadOnly"
|
:read-only="isReadOnly"
|
||||||
@remove="removeRow(row)"
|
|
||||||
:can-edit-row="canEditRow"
|
:can-edit-row="canEditRow"
|
||||||
|
@remove="removeRow(row)"
|
||||||
|
@change="(field, value) => $emit('row-change', field, value, this.df)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -95,7 +96,7 @@ import TableRow from './TableRow.vue';
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Table',
|
name: 'Table',
|
||||||
emits: ['editrow'],
|
emits: ['editrow', 'row-change'],
|
||||||
extends: Base,
|
extends: Base,
|
||||||
props: {
|
props: {
|
||||||
value: { type: Array, default: () => [] },
|
value: { type: Array, default: () => [] },
|
||||||
|
@ -1,16 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<Row
|
<Row
|
||||||
:ratio="ratio"
|
:ratio="ratio"
|
||||||
class="
|
class="w-full px-2 group flex items-center justify-center h-row-mid"
|
||||||
w-full
|
:class="readOnly ? '' : 'hover:bg-gray-25'"
|
||||||
px-2
|
|
||||||
hover:bg-gray-25
|
|
||||||
group
|
|
||||||
flex
|
|
||||||
items-center
|
|
||||||
justify-center
|
|
||||||
h-row-mid
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
<!-- Index or Remove button -->
|
<!-- Index or Remove button -->
|
||||||
<div class="flex items-center ps-2 text-gray-600">
|
<div class="flex items-center ps-2 text-gray-600">
|
||||||
@ -78,7 +70,7 @@ export default {
|
|||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
emits: ['remove'],
|
emits: ['remove', 'change'],
|
||||||
components: {
|
components: {
|
||||||
Row,
|
Row,
|
||||||
FormControl,
|
FormControl,
|
||||||
@ -106,6 +98,7 @@ export default {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await this.row.set(fieldname, value);
|
await this.row.set(fieldname, value);
|
||||||
|
this.$emit('change', df, value);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.errors[fieldname] = getErrorMessage(e, this.row);
|
this.errors[fieldname] = getErrorMessage(e, this.row);
|
||||||
this.row[fieldname] = '';
|
this.row[fieldname] = '';
|
||||||
|
@ -2,15 +2,43 @@
|
|||||||
<FormContainer>
|
<FormContainer>
|
||||||
<template #header-left v-if="hasDoc">
|
<template #header-left v-if="hasDoc">
|
||||||
<StatusBadge :status="status" class="h-8" />
|
<StatusBadge :status="status" class="h-8" />
|
||||||
|
<Barcode
|
||||||
|
class="h-8"
|
||||||
|
v-if="canShowBarcode"
|
||||||
|
@item-selected="(name:string) => {
|
||||||
|
// @ts-ignore
|
||||||
|
doc?.addItem(name);
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
<ExchangeRate
|
||||||
|
v-if="hasDoc && doc.isMultiCurrency"
|
||||||
|
:disabled="doc?.isSubmitted || doc?.isCancelled"
|
||||||
|
:from-currency="fromCurrency"
|
||||||
|
:to-currency="toCurrency"
|
||||||
|
:exchange-rate="exchangeRate"
|
||||||
|
@change="
|
||||||
|
async (exchangeRate: number) =>
|
||||||
|
await doc.set('exchangeRate', exchangeRate)
|
||||||
|
"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template #header v-if="hasDoc">
|
<template #header v-if="hasDoc">
|
||||||
|
<Button
|
||||||
|
v-if="canShowLinks"
|
||||||
|
:icon="true"
|
||||||
|
@click="showLinks = true"
|
||||||
|
:title="t`View linked entries`"
|
||||||
|
>
|
||||||
|
<feather-icon name="link" class="w-4 h-4"></feather-icon>
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
v-if="canPrint"
|
v-if="canPrint"
|
||||||
ref="printButton"
|
ref="printButton"
|
||||||
:icon="true"
|
:icon="true"
|
||||||
@click="routeTo(`/print/${doc.schemaName}/${doc.name}`)"
|
@click="routeTo(`/print/${doc.schemaName}/${doc.name}`)"
|
||||||
|
:title="t`Open Print View`"
|
||||||
>
|
>
|
||||||
{{ t`Print` }}
|
<feather-icon name="printer" class="w-4 h-4"></feather-icon>
|
||||||
</Button>
|
</Button>
|
||||||
<DropdownWithActions
|
<DropdownWithActions
|
||||||
v-for="group of groupedActions"
|
v-for="group of groupedActions"
|
||||||
@ -53,6 +81,7 @@
|
|||||||
:doc="doc"
|
:doc="doc"
|
||||||
:errors="errors"
|
:errors="errors"
|
||||||
@value-change="onValueChange"
|
@value-change="onValueChange"
|
||||||
|
@row-change="updateGroupedFields"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -105,6 +134,13 @@
|
|||||||
@close="() => toggleQuickEditDoc(null)"
|
@close="() => toggleQuickEditDoc(null)"
|
||||||
/>
|
/>
|
||||||
</Transition>
|
</Transition>
|
||||||
|
<Transition name="quickedit">
|
||||||
|
<LinkedEntries
|
||||||
|
v-if="showLinks && !hasQeDoc"
|
||||||
|
:doc="doc"
|
||||||
|
@close="showLinks = false"
|
||||||
|
/>
|
||||||
|
</Transition>
|
||||||
</template>
|
</template>
|
||||||
</FormContainer>
|
</FormContainer>
|
||||||
</template>
|
</template>
|
||||||
@ -140,6 +176,10 @@ import { inject } from 'vue';
|
|||||||
import { shortcutsKey } from 'src/utils/injectionKeys';
|
import { shortcutsKey } from 'src/utils/injectionKeys';
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { useDocShortcuts } from 'src/utils/vueUtils';
|
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 LinkedEntries from './LinkedEntries.vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
@ -173,12 +213,14 @@ export default defineComponent({
|
|||||||
groupedFields: null,
|
groupedFields: null,
|
||||||
quickEditDoc: null,
|
quickEditDoc: null,
|
||||||
isPrintable: false,
|
isPrintable: false,
|
||||||
|
showLinks: false,
|
||||||
} as {
|
} as {
|
||||||
errors: Record<string, string>;
|
errors: Record<string, string>;
|
||||||
activeTab: string;
|
activeTab: string;
|
||||||
groupedFields: null | UIGroupedFields;
|
groupedFields: null | UIGroupedFields;
|
||||||
quickEditDoc: null | Doc;
|
quickEditDoc: null | Doc;
|
||||||
isPrintable: boolean;
|
isPrintable: boolean;
|
||||||
|
showLinks: boolean;
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
@ -208,6 +250,45 @@ export default defineComponent({
|
|||||||
docsPathRef.value = '';
|
docsPathRef.value = '';
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
canShowBarcode(): boolean {
|
||||||
|
if (!this.fyo.singles.InventorySettings?.enableBarcodes) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.hasDoc) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.doc.isSubmitted || this.doc.isCancelled) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
return typeof this.doc?.addItem === 'function';
|
||||||
|
},
|
||||||
|
exchangeRate(): number {
|
||||||
|
if (!this.hasDoc || typeof this.doc.exchangeRate !== 'number') {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.doc.exchangeRate;
|
||||||
|
},
|
||||||
|
fromCurrency(): string {
|
||||||
|
const currency = this.doc?.currency;
|
||||||
|
if (typeof currency !== 'string') {
|
||||||
|
return this.toCurrency;
|
||||||
|
}
|
||||||
|
|
||||||
|
return currency;
|
||||||
|
},
|
||||||
|
toCurrency(): string {
|
||||||
|
const currency = this.fyo.singles.SystemSettings?.currency;
|
||||||
|
if (typeof currency !== 'string') {
|
||||||
|
return DEFAULT_CURRENCY;
|
||||||
|
}
|
||||||
|
|
||||||
|
return currency;
|
||||||
|
},
|
||||||
canPrint(): boolean {
|
canPrint(): boolean {
|
||||||
if (!this.hasDoc) {
|
if (!this.hasDoc) {
|
||||||
return false;
|
return false;
|
||||||
@ -215,6 +296,17 @@ export default defineComponent({
|
|||||||
|
|
||||||
return !this.doc.isCancelled && !this.doc.dirty && this.isPrintable;
|
return !this.doc.isCancelled && !this.doc.dirty && this.isPrintable;
|
||||||
},
|
},
|
||||||
|
canShowLinks(): boolean {
|
||||||
|
if (!this.hasDoc || this.hasQeDoc || this.showLinks) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.doc.schema.isSubmittable && !this.doc.isSubmitted) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.doc.inserted;
|
||||||
|
},
|
||||||
hasDoc(): boolean {
|
hasDoc(): boolean {
|
||||||
return this.docOrNull instanceof Doc;
|
return this.docOrNull instanceof Doc;
|
||||||
},
|
},
|
||||||
@ -321,6 +413,11 @@ export default defineComponent({
|
|||||||
await nextTick();
|
await nextTick();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (doc && this.showLinks) {
|
||||||
|
this.showLinks = false;
|
||||||
|
await nextTick();
|
||||||
|
}
|
||||||
|
|
||||||
this.quickEditDoc = doc;
|
this.quickEditDoc = doc;
|
||||||
},
|
},
|
||||||
async onValueChange(field: Field, value: DocValue) {
|
async onValueChange(field: Field, value: DocValue) {
|
||||||
@ -348,6 +445,9 @@ export default defineComponent({
|
|||||||
Button,
|
Button,
|
||||||
DropdownWithActions,
|
DropdownWithActions,
|
||||||
QuickEditForm,
|
QuickEditForm,
|
||||||
|
Barcode,
|
||||||
|
ExchangeRate,
|
||||||
|
LinkedEntries,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
:value="doc[field.fieldname]"
|
:value="doc[field.fieldname]"
|
||||||
@editrow="(doc: Doc) => $emit('editrow', doc)"
|
@editrow="(doc: Doc) => $emit('editrow', doc)"
|
||||||
@change="(value: DocValue) => $emit('value-change', field, value)"
|
@change="(value: DocValue) => $emit('value-change', field, value)"
|
||||||
|
@row-change="(field:Field, value:DocValue, parentfield:Field) => $emit('row-change',field, value, parentfield)"
|
||||||
/>
|
/>
|
||||||
<div v-if="errors?.[field.fieldname]" class="text-sm text-red-600 mt-1">
|
<div v-if="errors?.[field.fieldname]" class="text-sm text-red-600 mt-1">
|
||||||
{{ errors[field.fieldname] }}
|
{{ errors[field.fieldname] }}
|
||||||
@ -49,7 +50,7 @@ import { focusOrSelectFormControl } from 'src/utils/ui';
|
|||||||
import { defineComponent, PropType } from 'vue';
|
import { defineComponent, PropType } from 'vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
emits: ['editrow', 'value-change'],
|
emits: ['editrow', 'value-change', 'row-change'],
|
||||||
props: {
|
props: {
|
||||||
title: String,
|
title: String,
|
||||||
errors: Object as PropType<Record<string, string>>,
|
errors: Object as PropType<Record<string, string>>,
|
||||||
|
39
src/pages/CommonForm/LinkedEntries.vue
Normal file
39
src/pages/CommonForm/LinkedEntries.vue
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<template>
|
||||||
|
<div class="w-quick-edit bg-white border-l">
|
||||||
|
<div
|
||||||
|
class="
|
||||||
|
flex
|
||||||
|
items-center
|
||||||
|
justify-between
|
||||||
|
px-4
|
||||||
|
h-row-largest
|
||||||
|
sticky
|
||||||
|
top-0
|
||||||
|
border-b
|
||||||
|
"
|
||||||
|
style="z-index: 1"
|
||||||
|
>
|
||||||
|
<div class="flex items-center justify-between w-full">
|
||||||
|
<Button :icon="true" @click="$emit('close')">
|
||||||
|
<feather-icon name="x" class="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
<p class="text-xl font-semibold text-gray-600">
|
||||||
|
{{ t`Linked Entries` }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts">
|
||||||
|
import { Doc } from 'fyo/model/doc';
|
||||||
|
import Button from 'src/components/Button.vue';
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import { PropType } from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
emits: ['close'],
|
||||||
|
props: { doc: { type: Object as PropType<Doc>, required: true } },
|
||||||
|
mounted() {},
|
||||||
|
components: { Button },
|
||||||
|
});
|
||||||
|
</script>
|
Loading…
Reference in New Issue
Block a user