2
0
mirror of https://github.com/frappe/books.git synced 2024-12-22 19:09:01 +00:00

incr: user common form for journal entries

This commit is contained in:
18alantom 2023-02-21 11:51:32 +05:30
parent e0141b70d8
commit 332307bdd1
9 changed files with 126 additions and 361 deletions

View File

@ -99,7 +99,7 @@ export class DocHandler {
} }
const doc = new Model!(schema, data, this.fyo, isRawValueMap); const doc = new Model!(schema, data, this.fyo, isRawValueMap);
doc.name ??= this.#getTemporaryName(schema); doc.name ??= this.getTemporaryName(schema);
if (cacheDoc) { if (cacheDoc) {
this.#addToCache(doc); this.#addToCache(doc);
} }
@ -107,7 +107,7 @@ export class DocHandler {
return doc; return doc;
} }
#getTemporaryName(schema: Schema): string { getTemporaryName(schema: Schema): string {
if (schema.naming === 'random') { if (schema.naming === 'random') {
return getRandomString(); return getRandomString();
} }

View File

@ -454,7 +454,7 @@ export class Doc extends Observable<DocValue | Doc[]> {
convertToDocValue: boolean = false convertToDocValue: boolean = false
): Doc { ): Doc {
if (!this.name && this.schema.naming !== 'manual') { if (!this.name && this.schema.naming !== 'manual') {
this.name = getRandomString(); this.name = this.fyo.doc.getTemporaryName(this.schema);
} }
docValueMap.name ??= getRandomString(); docValueMap.name ??= getRandomString();

View File

@ -4,9 +4,9 @@ import {
Action, Action,
DefaultMap, DefaultMap,
FiltersMap, FiltersMap,
HiddenMap,
ListViewSettings, ListViewSettings,
} from 'fyo/model/types'; } from 'fyo/model/types';
import { DateTime } from 'luxon';
import { import {
getDocStatus, getDocStatus,
getLedgerLinkAction, getLedgerLinkAction,
@ -39,6 +39,17 @@ export class JournalEntry extends Transactional {
return posting; return posting;
} }
hidden: HiddenMap = {
referenceNumber: () =>
!(this.referenceNumber || !(this.isSubmitted || this.isCancelled)),
referenceDate: () =>
!(this.referenceDate || !(this.isSubmitted || this.isCancelled)),
userRemark: () =>
!(this.userRemark || !(this.isSubmitted || this.isCancelled)),
attachment: () =>
!(this.attachment || !(this.isSubmitted || this.isCancelled)),
};
static defaults: DefaultMap = { static defaults: DefaultMap = {
numberSeries: (doc) => getNumberSeries(doc.schemaName, doc.fyo), numberSeries: (doc) => getNumberSeries(doc.schemaName, doc.fyo),
date: () => new Date(), date: () => new Date(),

View File

@ -4,6 +4,24 @@
"naming": "numberSeries", "naming": "numberSeries",
"isSubmittable": true, "isSubmittable": true,
"fields": [ "fields": [
{
"label": "Entry No",
"fieldname": "name",
"fieldtype": "Data",
"required": true,
"readOnly": true,
"section": "Default"
},
{
"fieldname": "numberSeries",
"label": "Number Series",
"fieldtype": "Link",
"target": "NumberSeries",
"create": true,
"required": true,
"default": "JV-",
"section": "Default"
},
{ {
"fieldname": "entryType", "fieldname": "entryType",
"label": "Entry Type", "label": "Entry Type",
@ -55,58 +73,49 @@
"label": "Depreciation Entry" "label": "Depreciation Entry"
} }
], ],
"required": true
},
{
"label": "Entry No",
"fieldname": "name",
"fieldtype": "Data",
"required": true, "required": true,
"readOnly": true "section": "Default"
}, },
{ {
"fieldname": "date", "fieldname": "date",
"label": "Date", "label": "Date",
"fieldtype": "Date", "fieldtype": "Date",
"required": true "required": true,
"section": "Default"
}, },
{ {
"fieldname": "accounts", "fieldname": "accounts",
"label": "Account Entries", "label": "Account Entries",
"fieldtype": "Table", "fieldtype": "Table",
"target": "JournalEntryAccount", "target": "JournalEntryAccount",
"required": true "required": true,
"section": "Accounts"
}, },
{ {
"fieldname": "referenceNumber", "fieldname": "referenceNumber",
"label": "Reference Number", "label": "Reference Number",
"fieldtype": "Data" "fieldtype": "Data",
"section": "References"
}, },
{ {
"fieldname": "referenceDate", "fieldname": "referenceDate",
"label": "Reference Date", "label": "Reference Date",
"fieldtype": "Date" "fieldtype": "Date",
"section": "References"
}, },
{ {
"fieldname": "userRemark", "fieldname": "userRemark",
"label": "User Remark", "label": "User Remark",
"fieldtype": "Text", "fieldtype": "Text",
"placeholder": "Add a remark" "placeholder": "Add a remark",
}, "section": "References"
{
"fieldname": "numberSeries",
"label": "Number Series",
"fieldtype": "Link",
"target": "NumberSeries",
"create": true,
"required": true,
"default": "JV-"
}, },
{ {
"fieldname": "attachment", "fieldname": "attachment",
"placeholder": "Add attachment", "placeholder": "Add attachment",
"label": "Attachment", "label": "Attachment",
"fieldtype": "Attachment" "fieldtype": "Attachment",
"section": "References"
} }
], ],
"keywordFields": ["name", "entryType"] "keywordFields": ["name", "entryType"]

View File

@ -1,4 +1,8 @@
<template> <template>
<div>
<div :class="labelClasses" v-if="showLabel && df">
{{ df.label }}
</div>
<div :class="containerClasses" class="flex gap-2 items-center"> <div :class="containerClasses" class="flex gap-2 items-center">
<label <label
for="attachment" for="attachment"
@ -24,7 +28,11 @@
</button> </button>
<!-- Download Button --> <!-- Download Button -->
<button v-if="value" class="bg-gray-300 p-0.5 rounded" @click="download"> <button
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>
@ -38,6 +46,7 @@
</button> </button>
</div> </div>
</div> </div>
</div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { t } from 'fyo'; import { t } from 'fyo';

View File

@ -155,7 +155,7 @@ export default defineComponent({
} }
await this.setDoc(); await this.setDoc();
this.groupedFields = this.getGroupedFields(); this.updateGroupedFields();
}, },
computed: { computed: {
hasDoc(): boolean { hasDoc(): boolean {
@ -227,16 +227,20 @@ export default defineComponent({
}, },
}, },
methods: { methods: {
getGroupedFields(): UIGroupedFields { updateGroupedFields(): void {
if (!this.hasDoc) { if (!this.hasDoc) {
return new Map(); return;
} }
return getFieldsGroupedByTabAndSection(this.schema, this.doc); this.groupedFields = getFieldsGroupedByTabAndSection(
this.schema,
this.doc
);
}, },
async sync() { async sync() {
try { try {
await this.doc.sync(); await this.doc.sync();
this.updateGroupedFields();
} catch (err) { } catch (err) {
if (!(err instanceof Error)) { if (!(err instanceof Error)) {
return; return;
@ -248,6 +252,7 @@ export default defineComponent({
async submit() { async submit() {
try { try {
await this.doc.submit(); await this.doc.submit();
this.updateGroupedFields();
} catch (err) { } catch (err) {
if (!(err instanceof Error)) { if (!(err instanceof Error)) {
return; return;
@ -297,7 +302,7 @@ export default defineComponent({
this.errors[fieldname] = getErrorMessage(err, this.doc); this.errors[fieldname] = getErrorMessage(err, this.doc);
} }
this.groupedFields = this.getGroupedFields(); this.updateGroupedFields();
}, },
}, },
components: { components: {

View File

@ -18,10 +18,11 @@
<div <div
v-for="field of fields" v-for="field of fields"
:key="field.fieldname" :key="field.fieldname"
class="mb-auto"
:class="field.fieldtype === 'Table' ? 'col-span-2 text-base' : ''" :class="field.fieldtype === 'Table' ? 'col-span-2 text-base' : ''"
class="mb-auto"
> >
<FormControl <FormControl
:ref="field.fieldname === 'name' ? 'nameField' : 'fields'"
:show-label="true" :show-label="true"
:border="true" :border="true"
:df="field" :df="field"
@ -29,7 +30,7 @@
@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)"
/> />
<div v-if="errors?.[field.fieldname]" class="text-sm text-red-600"> <div v-if="errors?.[field.fieldname]" class="text-sm text-red-600 mt-1">
{{ errors[field.fieldname] }} {{ errors[field.fieldname] }}
</div> </div>
</div> </div>
@ -57,6 +58,24 @@ export default defineComponent({
collapsed: boolean; collapsed: boolean;
}; };
}, },
mounted() {
this.focusOnNameField();
},
methods: {
focusOnNameField() {
const naming = this.fyo.schemaMap[this.doc.schemaName]?.naming;
if (naming !== 'manual') {
return;
}
const nameField = (this.$refs.nameField as { focus: Function }[])?.[0];
if (!nameField) {
return;
}
nameField.focus();
},
},
components: { FormControl }, components: { FormControl },
}); });
</script> </script>

View File

@ -1,269 +0,0 @@
<template>
<FormContainer>
<!-- Page Header (Title, Buttons, etc) -->
<template #header v-if="doc">
<StatusBadge :status="status" />
<DropdownWithActions
v-for="group of groupedActions"
:key="group.label"
:type="group.type"
:actions="group.actions"
>
<p v-if="group.group">
{{ group.group }}
</p>
<feather-icon v-else name="more-horizontal" class="w-4 h-4" />
</DropdownWithActions>
<Button
v-if="doc?.canSave"
type="primary"
class="text-white text-xs"
@click="sync"
>
{{ t`Save` }}
</Button>
<Button
v-else-if="doc.canSubmit"
type="primary"
class="text-white text-xs"
@click="submit"
>
{{ t`Submit` }}
</Button>
</template>
<!-- Journal Entry Form -->
<template #body v-if="doc">
<FormHeader
:form-title="doc.notInserted ? t`New Entry` : doc.name"
:form-sub-title="t`Journal Entry`"
/>
<hr />
<div>
<div class="m-4 grid grid-cols-3 gap-y-4 gap-x-4">
<!-- First Column of Fields -->
<FormControl
:border="true"
:df="getField('numberSeries')"
:value="doc.numberSeries"
@change="(value) => doc.set('numberSeries', value)"
:read-only="!doc.notInserted || doc.submitted"
/>
<FormControl
:border="true"
:df="getField('date')"
:value="doc.date"
:placeholder="'Date'"
@change="(value) => doc.set('date', value)"
:read-only="doc.submitted"
/>
<FormControl
:border="true"
:df="getField('entryType')"
:value="doc.entryType"
placeholder="Entry Type"
@change="(value) => doc.set('entryType', value)"
:read-only="doc.submitted"
/>
<FormControl
:border="true"
:df="getField('referenceNumber')"
:value="doc.referenceNumber"
:placeholder="'Reference Number'"
@change="(value) => doc.set('referenceNumber', value)"
:read-only="doc.submitted"
/>
<FormControl
:border="true"
:df="getField('referenceDate')"
:value="doc.referenceDate"
:placeholder="'Reference Date'"
@change="(value) => doc.set('referenceDate', value)"
:read-only="doc.submitted"
/>
<FormControl
v-if="doc.attachment || !(doc.isSubmitted || doc.isCancelled)"
:border="true"
:df="getField('attachment')"
:value="doc.attachment"
@change="(value) => doc.set('attachment', value)"
:read-only="doc?.submitted"
/>
</div>
<hr />
<!-- Account Entries Table -->
<Table
class="text-base"
:df="getField('accounts')"
:value="doc.accounts"
:showHeader="true"
:max-rows-before-overflow="6"
:read-only="doc.submitted"
/>
</div>
<!-- Footer -->
<div v-if="doc.accounts?.length ?? 0" class="mt-auto">
<hr />
<div class="flex justify-between text-base m-4 gap-12">
<!-- User Remark -->
<FormControl
:border="true"
v-if="!doc.submitted || doc.userRemark"
class="w-1/2 self-end"
:df="getField('userRemark')"
:value="doc.userRemark"
@change="(value) => doc.set('userRemark', value)"
:read-only="doc.submitted"
/>
<!-- Debit and Credit -->
<div class="w-1/2 gap-2 flex flex-col self-end font-semibold ms-auto">
<div class="flex justify-between text-green-600">
<div>{{ t`Total Debit` }}</div>
<div>{{ totalDebit }}</div>
</div>
<hr />
<div class="flex justify-between text-red-600">
<div>{{ t`Total Credit` }}</div>
<div>{{ totalCredit }}</div>
</div>
</div>
</div>
</div>
</template>
</FormContainer>
</template>
<script>
import { computed } from '@vue/reactivity';
import { getDocStatus } from 'models/helpers';
import { ModelNameEnum } from 'models/types';
import Button from 'src/components/Button.vue';
import FormControl from 'src/components/Controls/FormControl.vue';
import Table from 'src/components/Controls/Table.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 { fyo } from 'src/initFyo';
import { docsPathMap } from 'src/utils/misc';
import { docsPathRef, focusedDocsRef } from 'src/utils/refs';
import {
getGroupedActionsForDoc,
routeTo,
showMessageDialog,
} from 'src/utils/ui';
import { handleErrorWithDialog } from '../errorHandling';
export default {
name: 'JournalEntryForm',
props: ['name'],
components: {
Button,
DropdownWithActions,
StatusBadge,
FormControl,
Table,
FormContainer,
FormHeader,
},
provide() {
return {
schemaName: this.schemaName,
name: this.name,
doc: computed(() => this.doc),
};
},
data() {
return {
schemaName: ModelNameEnum.JournalEntry,
doc: null,
};
},
activated() {
docsPathRef.value = docsPathMap.JournalEntry;
focusedDocsRef.add(this.doc);
},
deactivated() {
docsPathRef.value = '';
focusedDocsRef.delete(this.doc);
},
async mounted() {
try {
this.doc = await fyo.doc.getDoc(this.schemaName, this.name);
focusedDocsRef.add(this.doc);
} catch (error) {
if (error instanceof fyo.errors.NotFoundError) {
routeTo(`/list/${this.schemaName}`);
return;
}
await this.handleError(error);
}
if (fyo.store.isDevelopment) {
window.je = this;
}
},
computed: {
status() {
return getDocStatus(this.doc);
},
totalDebit() {
let value = 0;
if (this.doc.accounts) {
value = this.doc.getSum('accounts', 'debit');
}
return fyo.format(value, 'Currency');
},
totalCredit() {
let value = 0;
if (this.doc.accounts) {
value = this.doc.getSum('accounts', 'credit');
}
return fyo.format(value, 'Currency');
},
groupedActions() {
return getGroupedActionsForDoc(this.doc);
},
},
methods: {
getField(fieldname) {
return fyo.getField(ModelNameEnum.JournalEntry, fieldname);
},
async sync() {
try {
await this.doc.sync();
} catch (err) {
this.handleError(err);
}
},
async submit() {
const ref = this;
await showMessageDialog({
message: this.t`Submit Journal Entry?`,
buttons: [
{
label: this.t`Yes`,
async action() {
try {
await ref.doc.submit();
} catch (err) {
await ref.handleError(err);
}
},
},
{
label: this.t`No`,
action() {},
},
],
});
},
async handleError(e) {
await handleErrorWithDialog(e, this.doc);
},
},
};
</script>

View File

@ -1,22 +1,21 @@
import { ModelNameEnum } from 'models/types'; import { ModelNameEnum } from 'models/types';
import ChartOfAccounts from 'src/pages/ChartOfAccounts.vue'; import ChartOfAccounts from 'src/pages/ChartOfAccounts.vue';
import CommonForm from 'src/pages/CommonForm/CommonForm.vue';
import Dashboard from 'src/pages/Dashboard/Dashboard.vue'; import Dashboard from 'src/pages/Dashboard/Dashboard.vue';
import ImportWizard from 'src/pages/ImportWizard.vue';
import GeneralForm from 'src/pages/GeneralForm.vue'; import GeneralForm from 'src/pages/GeneralForm.vue';
import GetStarted from 'src/pages/GetStarted.vue'; import GetStarted from 'src/pages/GetStarted.vue';
import ImportWizard from 'src/pages/ImportWizard.vue';
import InvoiceForm from 'src/pages/InvoiceForm.vue'; import InvoiceForm from 'src/pages/InvoiceForm.vue';
import JournalEntryForm from 'src/pages/JournalEntryForm.vue';
import ListView from 'src/pages/ListView/ListView.vue'; import ListView from 'src/pages/ListView/ListView.vue';
import PrintView from 'src/pages/PrintView/PrintView.vue'; import PrintView from 'src/pages/PrintView/PrintView.vue';
import QuickEditForm from 'src/pages/QuickEditForm.vue'; import QuickEditForm from 'src/pages/QuickEditForm.vue';
import CommonForm from 'src/pages/CommonForm/CommonForm.vue';
import Report from 'src/pages/Report.vue'; import Report from 'src/pages/Report.vue';
import Settings from 'src/pages/Settings/Settings.vue'; import Settings from 'src/pages/Settings/Settings.vue';
import { import {
createRouter, createRouter,
createWebHistory, createWebHistory,
RouteLocationRaw, RouteLocationRaw,
RouteRecordRaw, RouteRecordRaw
} from 'vue-router'; } from 'vue-router';
function getGeneralFormItems(): RouteRecordRaw[] { function getGeneralFormItems(): RouteRecordRaw[] {
@ -46,6 +45,7 @@ function getGeneralFormItems(): RouteRecordRaw[] {
function getCommonFormItems(): RouteRecordRaw[] { function getCommonFormItems(): RouteRecordRaw[] {
return [ return [
ModelNameEnum.JournalEntry,
ModelNameEnum.Payment, ModelNameEnum.Payment,
ModelNameEnum.StockMovement, ModelNameEnum.StockMovement,
ModelNameEnum.Item, ModelNameEnum.Item,
@ -82,25 +82,6 @@ const routes: RouteRecordRaw[] = [
}, },
...getGeneralFormItems(), ...getGeneralFormItems(),
...getCommonFormItems(), ...getCommonFormItems(),
{
path: '/edit/JournalEntry/:name',
name: 'JournalEntryForm',
components: {
default: JournalEntryForm,
edit: QuickEditForm,
},
props: {
default: (route) => {
// for sidebar item active state
route.params.schemaName = 'JournalEntry';
return {
schemaName: 'JournalEntry',
name: route.params.name,
};
},
edit: (route) => route.query,
},
},
{ {
path: '/edit/:schemaName/:name', path: '/edit/:schemaName/:name',
name: 'InvoiceForm', name: 'InvoiceForm',