2
0
mirror of https://github.com/frappe/books.git synced 2025-02-02 12:08:27 +00:00

Merge pull request #479 from 18alantom/defaults

feat: Defaults
This commit is contained in:
Alan 2022-10-12 03:21:38 -07:00 committed by GitHub
commit 5de0f6c8d8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 281 additions and 82 deletions

View File

@ -284,7 +284,7 @@ export class Doc extends Observable<DocValue | Doc[]> {
const defaultFunction =
this.fyo.models[this.schemaName]?.defaults?.[field.fieldname];
if (defaultFunction !== undefined) {
defaultValue = defaultFunction();
defaultValue = defaultFunction(this);
} else if (field.default !== undefined) {
defaultValue = field.default;
}

View File

@ -20,7 +20,7 @@ import { Doc } from './doc';
export type FormulaReturn = DocValue | DocValueMap[] | undefined | Doc[];
export type Formula = (fieldname?: string) => Promise<FormulaReturn> | FormulaReturn;
export type FormulaConfig = { dependsOn?: string[]; formula: Formula };
export type Default = () => DocValue;
export type Default = (doc: Doc) => DocValue;
export type Validation = (value: DocValue) => Promise<void> | void;
export type Required = () => boolean;
export type Hidden = () => boolean;

View File

@ -0,0 +1,51 @@
import { Doc } from 'fyo/model/doc';
import { FiltersMap } from 'fyo/model/types';
import { ModelNameEnum } from 'models/types';
export class Defaults extends Doc {
salesInvoiceNumberSeries?: string;
purchaseInvoiceNumberSeries?: string;
journalEntryNumberSeries?: string;
paymentNumberSeries?: string;
salesInvoiceTerms?: string;
purchaseInvoiceTerms?: string;
static filters: FiltersMap = {
salesInvoiceNumberSeries: () => ({
referenceType: ModelNameEnum.SalesInvoice,
}),
purchaseInvoiceNumberSeries: () => ({
referenceType: ModelNameEnum.PurchaseInvoice,
}),
journalEntryNumberSeries: () => ({
referenceType: ModelNameEnum.JournalEntry,
}),
paymentNumberSeries: () => ({ referenceType: ModelNameEnum.Payment }),
};
static createFilters: FiltersMap = {
salesInvoiceNumberSeries: () => ({
referenceType: ModelNameEnum.SalesInvoice,
}),
purchaseInvoiceNumberSeries: () => ({
referenceType: ModelNameEnum.PurchaseInvoice,
}),
journalEntryNumberSeries: () => ({
referenceType: ModelNameEnum.JournalEntry,
}),
paymentNumberSeries: () => ({
referenceType: ModelNameEnum.Payment,
}),
};
}
export const numberSeriesDefaultsMap: Record<
string,
keyof Defaults | undefined
> = {
[ModelNameEnum.SalesInvoice]: 'salesInvoiceNumberSeries',
[ModelNameEnum.PurchaseInvoice]: 'purchaseInvoiceNumberSeries',
[ModelNameEnum.JournalEntry]: 'journalEntryNumberSeries',
[ModelNameEnum.Payment]: 'paymentNumberSeries',
};

View File

@ -2,20 +2,20 @@ import { Fyo } from 'fyo';
import { DocValue, DocValueMap } from 'fyo/core/types';
import { Doc } from 'fyo/model/doc';
import {
CurrenciesMap,
DefaultMap,
CurrenciesMap, DefaultMap,
FiltersMap,
FormulaMap,
HiddenMap
} from 'fyo/model/types';
import { DEFAULT_CURRENCY } from 'fyo/utils/consts';
import { ValidationError } from 'fyo/utils/errors';
import { getExchangeRate } from 'models/helpers';
import { getExchangeRate, getNumberSeries } from 'models/helpers';
import { Transactional } from 'models/Transactional/Transactional';
import { ModelNameEnum } from 'models/types';
import { Money } from 'pesa';
import { FieldTypeEnum, Schema } from 'schemas/types';
import { getIsNullOrUndef } from 'utils';
import { Defaults } from '../Defaults/Defaults';
import { InvoiceItem } from '../InvoiceItem/InvoiceItem';
import { Party } from '../Party/Party';
import { Payment } from '../Payment/Payment';
@ -363,6 +363,15 @@ export abstract class Invoice extends Transactional {
};
static defaults: DefaultMap = {
numberSeries: (doc) => getNumberSeries(doc.schemaName, doc.fyo),
terms: (doc) => {
const defaults = doc.fyo.singles.Defaults as Defaults | undefined;
if (doc.schemaName === ModelNameEnum.SalesInvoice) {
return defaults?.salesInvoiceTerms ?? '';
}
return defaults?.purchaseInvoiceTerms ?? '';
},
date: () => new Date().toISOString().slice(0, 10),
};

View File

@ -4,14 +4,15 @@ import {
Action,
DefaultMap,
FiltersMap,
ListViewSettings,
ListViewSettings
} from 'fyo/model/types';
import { DateTime } from 'luxon';
import {
getDocStatus,
getLedgerLinkAction,
getNumberSeries,
getStatusMap,
statusColor,
statusColor
} from 'models/helpers';
import { Transactional } from 'models/Transactional/Transactional';
import { Money } from 'pesa';
@ -39,6 +40,7 @@ export class JournalEntry extends Transactional {
}
static defaults: DefaultMap = {
numberSeries: (doc) => getNumberSeries(doc.schemaName, doc.fyo),
date: () => DateTime.local().toISODate(),
};

View File

@ -16,6 +16,7 @@ import { ValidationError } from 'fyo/utils/errors';
import {
getDocStatus,
getLedgerLinkAction,
getNumberSeries,
getStatusMap,
statusColor
} from 'models/helpers';
@ -361,7 +362,10 @@ export class Payment extends Transactional {
await partyDoc.updateOutstandingAmount();
}
static defaults: DefaultMap = { date: () => new Date().toISOString() };
static defaults: DefaultMap = {
numberSeries: (doc) => getNumberSeries(doc.schemaName, doc.fyo),
date: () => new Date().toISOString(),
};
async _getAccountsMap(): Promise<AccountTypeMap> {
if (this._accountsMap) {

View File

@ -8,6 +8,10 @@ import {
AccountRootType,
AccountRootTypeEnum
} from './baseModels/Account/types';
import {
Defaults,
numberSeriesDefaultsMap
} from './baseModels/Defaults/Defaults';
import { InvoiceStatus, ModelNameEnum } from './types';
export function getInvoiceActions(
@ -250,3 +254,15 @@ export function isCredit(rootType: AccountRootType) {
return true;
}
}
export function getNumberSeries(schemaName: string, fyo: Fyo) {
const numberSeriesKey = numberSeriesDefaultsMap[schemaName];
if (!numberSeriesKey) {
return undefined;
}
const defaults = fyo.singles.Defaults as Defaults | undefined;
const field = fyo.getField(schemaName, 'numberSeries');
const value = defaults?.[numberSeriesKey] as string | undefined;
return value ?? (field?.default as string | undefined);
}

View File

@ -3,6 +3,7 @@ import { Account } from './baseModels/Account/Account';
import { AccountingLedgerEntry } from './baseModels/AccountingLedgerEntry/AccountingLedgerEntry';
import { AccountingSettings } from './baseModels/AccountingSettings/AccountingSettings';
import { Address } from './baseModels/Address/Address';
import { Defaults } from './baseModels/Defaults/Defaults';
import { Item } from './baseModels/Item/Item';
import { JournalEntry } from './baseModels/JournalEntry/JournalEntry';
import { JournalEntryAccount } from './baseModels/JournalEntryAccount/JournalEntryAccount';
@ -22,6 +23,7 @@ export const models = {
AccountingLedgerEntry,
AccountingSettings,
Address,
Defaults,
Item,
JournalEntry,
JournalEntryAccount,

View File

@ -8,6 +8,7 @@ export enum ModelNameEnum {
CompanySettings = 'CompanySettings',
Currency = 'Currency',
GetStarted = 'GetStarted',
Defaults = 'Defaults',
Item = 'Item',
UOM = 'UOM',
JournalEntry = 'JournalEntry',

48
schemas/app/Defaults.json Normal file
View File

@ -0,0 +1,48 @@
{
"name": "Defaults",
"label": "Defaults",
"isSingle": true,
"isChild": false,
"fields": [
{
"fieldname": "salesInvoiceNumberSeries",
"label": "Sales Invoice Number Series",
"fieldtype": "Link",
"target": "NumberSeries",
"create": true
},
{
"fieldname": "purchaseInvoiceNumberSeries",
"label": "Purchase Invoice Number Series",
"fieldtype": "Link",
"target": "NumberSeries",
"create": true
},
{
"fieldname": "journalEntryNumberSeries",
"label": "Journal Entry Number Series",
"fieldtype": "Link",
"target": "NumberSeries",
"create": true
},
{
"fieldname": "paymentNumberSeries",
"label": "Payment Number Series",
"fieldtype": "Link",
"target": "NumberSeries",
"create": true
},
{
"fieldname": "salesInvoiceTerms",
"label": "Sales Invoice Terms",
"fieldtype": "Text",
"target": "NumberSeries"
},
{
"fieldname": "purchaseInvoiceTerms",
"label": "Purchase Invoice Terms",
"fieldtype": "Text",
"target": "NumberSeries"
}
]
}

View File

@ -33,11 +33,11 @@
"options": [
{
"value": "SalesInvoice",
"label": "Invoice"
"label": "Sales Invoice"
},
{
"value": "PurchaseInvoice",
"label": "Bill"
"label": "Purchase Invoice"
},
{
"value": "Payment",

View File

@ -1,17 +1,18 @@
import Account from './app/Account.json';
import AccountingLedgerEntry from './app/AccountingLedgerEntry.json';
import AccountingSettings from './app/AccountingSettings.json';
import Misc from './app/Misc.json';
import Address from './app/Address.json';
import Color from './app/Color.json';
import CompanySettings from './app/CompanySettings.json';
import Currency from './app/Currency.json';
import Defaults from './app/Defaults.json';
import GetStarted from './app/GetStarted.json';
import Invoice from './app/Invoice.json';
import InvoiceItem from './app/InvoiceItem.json';
import Item from './app/Item.json';
import JournalEntry from './app/JournalEntry.json';
import JournalEntryAccount from './app/JournalEntryAccount.json';
import Misc from './app/Misc.json';
import NumberSeries from './app/NumberSeries.json';
import Party from './app/Party.json';
import Payment from './app/Payment.json';
@ -55,6 +56,7 @@ export const appSchemas: Schema[] | SchemaStub[] = [
Color as Schema,
Currency as Schema,
Defaults as Schema,
NumberSeries as Schema,
PrintSettings as Schema,

View File

@ -82,7 +82,7 @@ export default {
value: {
immediate: true,
handler(newValue) {
this.linkValue = this.getLabel(newValue);
this.linkValue = this.getLinkValue(newValue);
},
},
},
@ -90,7 +90,7 @@ export default {
doc: { default: null },
},
mounted() {
this.linkValue = this.getLabel(this.linkValue || this.value);
this.linkValue = this.getLinkValue(this.linkValue || this.value);
},
computed: {
options() {
@ -102,7 +102,7 @@ export default {
},
},
methods: {
getLabel(value) {
getLinkValue(value) {
const oldValue = this.linkValue;
let option = this.options.find((o) => o.value === value);
if (option === undefined) {
@ -111,14 +111,6 @@ export default {
return option?.label ?? oldValue;
},
getValue(label) {
let option = this.options.find((o) => o.label === label);
if (option === undefined) {
option = this.options.find((o) => o.value === label);
}
return option?.value ?? label;
},
async updateSuggestions(keyword) {
if (typeof keyword === 'string') {
this.linkValue = keyword;
@ -156,6 +148,11 @@ export default {
.map(({ item }) => item);
},
setSuggestion(suggestion) {
if (suggestion?.actionOnly) {
this.linkValue = this.value;
return;
}
if (suggestion) {
this.linkValue = suggestion.label;
this.triggerChange(suggestion.value);

View File

@ -17,10 +17,6 @@ export default {
if (this.value) {
this.linkValue = this.value;
}
if (this.df.fieldname === 'incomeAccount') {
window.l = this;
}
},
watch: {
value: {
@ -89,6 +85,7 @@ export default {
'<span class="text-gray-600">{{ t`No results found` }}</span>',
}),
action: () => {},
actionOnly: true,
},
];
}
@ -98,8 +95,8 @@ export default {
getCreateNewOption() {
return {
label: t`Create`,
value: 'Create',
action: () => this.openNewDoc(),
actionOnly: true,
component: markRaw({
template:
'<div class="flex items-center font-semibold">{{ t`Create` }}' +

View File

@ -42,6 +42,7 @@ export async function initializeInstance(
async function setSingles(fyo: Fyo) {
await fyo.doc.getDoc(ModelNameEnum.AccountingSettings);
await fyo.doc.getDoc(ModelNameEnum.GetStarted);
await fyo.doc.getDoc(ModelNameEnum.Defaults);
await fyo.doc.getDoc(ModelNameEnum.Misc);
}

View File

@ -2,27 +2,33 @@
<FormContainer :title="t`Settings`" :searchborder="false">
<template #body>
<!-- Icon Tab Bar -->
<div class="flex justify-around mb-2 mt-4">
<div
<div class="flex m-4 mb-0 gap-8">
<button
v-for="(tab, i) in tabs"
:key="tab.label"
class="
p-2
rounded-md
hover:bg-white
flex flex-col
items-center
justify-center
cursor-pointer
text-sm
"
:class="i === activeTab && 'text-blue-500'"
:class="
i === activeTab &&
'text-blue-500 font-semibold border-b-2 border-blue-500'
"
:style="{
paddingBottom: i === activeTab ? 'calc(1rem - 2px)' : '1rem',
}"
@click="activeTab = i"
>
<component :is="getIconComponent(tab)" :active="i === activeTab" />
<div class="mt-2 text-xs">{{ tab.label }}</div>
</div>
{{ tab.label }}
</button>
</div>
<div class="flex-1 overflow-y-auto">
<!-- Component -->
<div class="flex-1 overflow-y-auto custom-scroll">
<component :is="activeTabComponent" @change="handleChange" />
</div>
</template>
@ -41,6 +47,7 @@ import { docsPathMap } from 'src/utils/misc';
import { docsPath, showToast } from 'src/utils/ui';
import { IPC_MESSAGES } from 'utils/messages';
import { h, markRaw } from 'vue';
import TabDefaults from './TabDefaults.vue';
import TabGeneral from './TabGeneral.vue';
import TabInvoice from './TabInvoice.vue';
import TabSystem from './TabSystem.vue';
@ -63,19 +70,21 @@ export default {
{
key: 'Invoice',
label: t`Invoice`,
icon: 'invoice',
component: markRaw(TabInvoice),
},
{
key: 'General',
label: t`General`,
icon: 'general',
component: markRaw(TabGeneral),
},
{
key: 'Defaults',
label: t`Defaults`,
component: markRaw(TabDefaults),
},
{
key: 'System',
label: t`System`,
icon: 'system',
component: markRaw(TabSystem),
},
],

View File

@ -0,0 +1,35 @@
<template>
<div>
<TwoColumnForm
v-if="doc"
:doc="doc"
:fields="fields"
:autosave="true"
:emit-change="true"
@change="(...args:unknown[])=>$emit('change', ...args)"
/>
</div>
</template>
<script lang="ts">
import { Doc } from 'fyo/model/doc';
import { Field } from 'schemas/types';
import TwoColumnForm from 'src/components/TwoColumnForm.vue';
export default {
name: 'TabGeneral',
emits: ['change'],
components: {
TwoColumnForm,
},
data() {
return {
doc: undefined,
} as { doc?: Doc };
},
computed: {
fields() {
return [] as Field[];
},
},
};
</script>

View File

@ -0,0 +1,20 @@
<script lang="ts">
import { fyo } from 'src/initFyo';
import { defineComponent } from 'vue';
import TabBase from './TabBase.vue';
export default defineComponent({
extends: TabBase,
name: 'TabDefaults',
async mounted() {
this.doc = await fyo.doc.getDoc('Defaults', 'Defaults', {
skipDocumentCache: true,
});
},
computed: {
fields() {
return this.doc?.schema.fields;
},
},
});
</script>

View File

@ -1,31 +1,12 @@
<template>
<div>
<TwoColumnForm
v-if="doc"
:doc="doc"
:fields="fields"
:autosave="true"
:emit-change="true"
@change="forwardChangeEvent"
/>
</div>
</template>
<script>
import TwoColumnForm from 'src/components/TwoColumnForm';
<script lang="ts">
import { Field } from 'schemas/types';
import { fyo } from 'src/initFyo';
import { defineComponent } from 'vue';
import TabBase from './TabBase.vue';
export default {
export default defineComponent({
extends: TabBase,
name: 'TabGeneral',
emits: ['change'],
components: {
TwoColumnForm,
},
data() {
return {
doc: null,
};
},
async mounted() {
this.doc = await fyo.doc.getDoc(
'AccountingSettings',
@ -49,27 +30,22 @@ export default {
'roundOffAccount',
];
if (!this.doc.enableDiscounting) {
if (!this.doc?.enableDiscounting) {
fields.push('enableDiscounting');
}
if (this.doc.enableDiscounting) {
if (this.doc?.enableDiscounting) {
fields.push('discountAccount');
}
if (fyo.singles.SystemSettings.countryCode === 'in') {
if (fyo.singles.SystemSettings?.countryCode === 'in') {
fields.push('gstin');
}
return fields.map((fieldname) =>
fyo.getField('AccountingSettings', fieldname)
);
return fields
.map((fieldname) => fyo.getField('AccountingSettings', fieldname))
.filter(Boolean) as Field[];
},
},
methods: {
forwardChangeEvent(...args) {
this.$emit('change', ...args);
},
},
};
});
</script>

View File

@ -112,8 +112,14 @@ const routes: RouteRecordRaw[] = [
{
path: '/settings',
name: 'Settings',
component: Settings,
props: true,
components: {
default: Settings,
edit: QuickEditForm,
},
props: {
default: true,
edit: (route) => route.query,
},
},
];

View File

@ -9,6 +9,7 @@ import {
} from 'fyo/utils/consts';
import { AccountRootTypeEnum } from 'models/baseModels/Account/types';
import { AccountingSettings } from 'models/baseModels/AccountingSettings/AccountingSettings';
import { numberSeriesDefaultsMap } from 'models/baseModels/Defaults/Defaults';
import { ModelNameEnum } from 'models/types';
import { initializeInstance, setCurrencySymbols } from 'src/initFyo';
import { createRegionalRecords } from 'src/regional';
@ -301,8 +302,30 @@ async function getBankAccountParentName(country: string, fyo: Fyo) {
}
async function createDefaultNumberSeries(fyo: Fyo) {
await createNumberSeries('SINV-', 'SalesInvoice', DEFAULT_SERIES_START, fyo);
await createNumberSeries('PINV-', 'PurchaseInvoice', DEFAULT_SERIES_START, fyo);
await createNumberSeries('PAY-', 'Payment', DEFAULT_SERIES_START, fyo);
await createNumberSeries('JV-', 'JournalEntry', DEFAULT_SERIES_START, fyo);
const numberSeriesFields = Object.values(fyo.schemaMap)
.map((f) => f?.fields)
.flat()
.filter((f) => f?.fieldname === 'numberSeries');
for (const field of numberSeriesFields) {
const defaultValue = field?.default as string | undefined;
const schemaName = field?.schemaName;
if (!defaultValue || !schemaName) {
continue;
}
await createNumberSeries(
defaultValue,
schemaName,
DEFAULT_SERIES_START,
fyo
);
const defaultKey = numberSeriesDefaultsMap[schemaName];
if (!defaultKey || fyo.singles.Defaults?.[defaultKey]) {
continue;
}
await fyo.singles.Defaults?.setAndSync(defaultKey as string, defaultValue);
}
}