2
0
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:
18alantom 2023-04-13 11:46:51 +05:30
parent 28158e5add
commit f0a6e7bf9e
7 changed files with 185 additions and 28 deletions

View File

@ -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 = {

View File

@ -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;
} }

View File

@ -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: () => [] },

View File

@ -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] = '';

View File

@ -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>

View File

@ -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>>,

View 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>