2
0
mirror of https://github.com/frappe/books.git synced 2024-11-08 14:50:56 +00:00

Merge pull request #566 from frappe/redo-settings

fix(ui): make settings use common form format
This commit is contained in:
Alan 2023-03-01 00:55:46 -08:00 committed by GitHub
commit 51ce718d01
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 445 additions and 558 deletions

View File

@ -177,11 +177,12 @@ export class Doc extends Observable<DocValue | Doc[]> {
} }
get canSave() { get canSave() {
if (!!this.submitted) { const isSubmittable = this.schema.isSubmittable;
if (isSubmittable && !!this.submitted) {
return false; return false;
} }
if (!!this.cancelled) { if (isSubmittable && !!this.cancelled) {
return false; return false;
} }
@ -189,10 +190,6 @@ export class Doc extends Observable<DocValue | Doc[]> {
return false; return false;
} }
if (this.schema.isSingle) {
return false;
}
if (this.schema.isChild) { if (this.schema.isChild) {
return false; return false;
} }

View File

@ -2,6 +2,7 @@ import { Doc } from 'fyo/model/doc';
import { import {
ChangeArg, ChangeArg,
FiltersMap, FiltersMap,
HiddenMap,
ListsMap, ListsMap,
ReadOnlyMap, ReadOnlyMap,
ValidationMap, ValidationMap,
@ -46,6 +47,11 @@ export class AccountingSettings extends Doc {
}, },
}; };
override hidden: HiddenMap = {
discountAccount: () => !this.enableDiscounting,
gstin: () => this.fyo.singles.SystemSettings?.countryCode !== 'in',
};
async change(ch: ChangeArg) { async change(ch: ChangeArg) {
const discountingEnabled = const discountingEnabled =
ch.changed === 'enableDiscounting' && this.enableDiscounting; ch.changed === 'enableDiscounting' && this.enableDiscounting;

View File

@ -11,7 +11,6 @@ import {
ValidationMap, ValidationMap,
} from 'fyo/model/types'; } from 'fyo/model/types';
import { ValidationError } from 'fyo/utils/errors'; import { ValidationError } from 'fyo/utils/errors';
import { ModelNameEnum } from 'models/types';
import { Money } from 'pesa'; import { Money } from 'pesa';
import { AccountRootTypeEnum, AccountTypeEnum } from '../Account/types'; import { AccountRootTypeEnum, AccountTypeEnum } from '../Account/types';

View File

@ -0,0 +1,8 @@
import { Doc } from 'fyo/model/doc';
import { HiddenMap } from 'fyo/model/types';
export class PrintSettings extends Doc {
override hidden: HiddenMap = {
displayBatch: () => !this.fyo.singles.InventorySettings?.enableBatches,
};
}

View File

@ -10,6 +10,7 @@ import { JournalEntryAccount } from './baseModels/JournalEntryAccount/JournalEnt
import { Party } from './baseModels/Party/Party'; import { Party } from './baseModels/Party/Party';
import { Payment } from './baseModels/Payment/Payment'; import { Payment } from './baseModels/Payment/Payment';
import { PaymentFor } from './baseModels/PaymentFor/PaymentFor'; import { PaymentFor } from './baseModels/PaymentFor/PaymentFor';
import { PrintSettings } from './baseModels/PrintSettings/PrintSettings';
import { PurchaseInvoice } from './baseModels/PurchaseInvoice/PurchaseInvoice'; import { PurchaseInvoice } from './baseModels/PurchaseInvoice/PurchaseInvoice';
import { PurchaseInvoiceItem } from './baseModels/PurchaseInvoiceItem/PurchaseInvoiceItem'; import { PurchaseInvoiceItem } from './baseModels/PurchaseInvoiceItem/PurchaseInvoiceItem';
import { SalesInvoice } from './baseModels/SalesInvoice/SalesInvoice'; import { SalesInvoice } from './baseModels/SalesInvoice/SalesInvoice';
@ -17,6 +18,7 @@ import { SalesInvoiceItem } from './baseModels/SalesInvoiceItem/SalesInvoiceItem
import { SetupWizard } from './baseModels/SetupWizard/SetupWizard'; import { SetupWizard } from './baseModels/SetupWizard/SetupWizard';
import { Tax } from './baseModels/Tax/Tax'; import { Tax } from './baseModels/Tax/Tax';
import { TaxSummary } from './baseModels/TaxSummary/TaxSummary'; import { TaxSummary } from './baseModels/TaxSummary/TaxSummary';
import { Batch } from './inventory/Batch';
import { InventorySettings } from './inventory/InventorySettings'; import { InventorySettings } from './inventory/InventorySettings';
import { Location } from './inventory/Location'; import { Location } from './inventory/Location';
import { PurchaseReceipt } from './inventory/PurchaseReceipt'; import { PurchaseReceipt } from './inventory/PurchaseReceipt';
@ -26,7 +28,6 @@ import { ShipmentItem } from './inventory/ShipmentItem';
import { StockLedgerEntry } from './inventory/StockLedgerEntry'; import { StockLedgerEntry } from './inventory/StockLedgerEntry';
import { StockMovement } from './inventory/StockMovement'; import { StockMovement } from './inventory/StockMovement';
import { StockMovementItem } from './inventory/StockMovementItem'; import { StockMovementItem } from './inventory/StockMovementItem';
import { Batch } from './inventory/Batch';
export const models = { export const models = {
Account, Account,
@ -41,6 +42,7 @@ export const models = {
Party, Party,
Payment, Payment,
PaymentFor, PaymentFor,
PrintSettings,
PurchaseInvoice, PurchaseInvoice,
PurchaseInvoiceItem, PurchaseInvoiceItem,
SalesInvoice, SalesInvoice,

View File

@ -5,87 +5,100 @@
"isChild": false, "isChild": false,
"isSubmittable": false, "isSubmittable": false,
"fields": [ "fields": [
{
"label": "Full Name",
"fieldname": "fullname",
"fieldtype": "Data",
"required": true,
"section": "Default"
},
{ {
"label": "Company Name", "label": "Company Name",
"fieldname": "companyName", "fieldname": "companyName",
"fieldtype": "Data", "fieldtype": "Data",
"readOnly": true, "readOnly": true,
"required": true "required": true,
"section": "Default"
}, },
{ {
"label": "Write Off Account", "label": "Bank Name",
"fieldname": "writeOffAccount", "fieldname": "bankName",
"fieldtype": "Link", "fieldtype": "Data",
"target": "Account" "readOnly": true,
"required": true,
"section": "Default"
}, },
{ {
"label": "Round Off Account",
"fieldname": "roundOffAccount",
"fieldtype": "Link",
"target": "Account"
},
{
"fieldname": "country",
"label": "Country", "label": "Country",
"fieldname": "country",
"fieldtype": "AutoComplete", "fieldtype": "AutoComplete",
"placeholder": "Select Country", "placeholder": "Select Country",
"readOnly": true, "readOnly": true,
"required": true "required": true,
}, "section": "Default"
{
"fieldname": "fullname",
"label": "Full Name",
"fieldtype": "Data",
"required": true
}, },
{ {
"fieldname": "email", "fieldname": "email",
"label": "Email", "label": "Email",
"fieldtype": "Data", "fieldtype": "Data",
"required": true "required": true,
"section": "Default"
}, },
{ {
"fieldname": "bankName", "label": "Write Off Account",
"label": "Bank Name", "fieldname": "writeOffAccount",
"fieldtype": "Data", "fieldtype": "Link",
"readOnly": true, "target": "Account",
"required": true "section": "Accounts"
}, },
{ {
"fieldname": "fiscalYearStart", "label": "Round Off Account",
"label": "Fiscal Year Start Date", "fieldname": "roundOffAccount",
"fieldtype": "Date", "fieldtype": "Link",
"required": true "target": "Account",
"section": "Accounts"
}, },
{ {
"fieldname": "fiscalYearEnd", "label": "Discount Account",
"label": "Fiscal Year End Date", "fieldname": "discountAccount",
"fieldtype": "Date", "fieldtype": "Link",
"required": true "target": "Account",
"section": "Accounts"
}, },
{ {
"fieldname": "enableDiscounting", "fieldname": "enableDiscounting",
"label": "Enable Discount Accounting", "label": "Enable Discount Accounting",
"fieldtype": "Check", "fieldtype": "Check",
"default": false "default": false,
}, "section": "Feature Flags"
{
"fieldname": "discountAccount",
"label": "Discount Account",
"fieldtype": "Link",
"target": "Account"
}, },
{ {
"fieldname": "enableInventory", "fieldname": "enableInventory",
"label": "Enable Inventory", "label": "Enable Inventory",
"fieldtype": "Check", "fieldtype": "Check",
"default": false "default": false,
"section": "Feature Flags"
},
{
"fieldname": "fiscalYearStart",
"label": "Fiscal Year Start Date",
"fieldtype": "Date",
"required": true,
"section": "Fiscal Year"
},
{
"fieldname": "fiscalYearEnd",
"label": "Fiscal Year End Date",
"fieldtype": "Date",
"required": true,
"section": "Fiscal Year"
}, },
{ {
"fieldname": "setupComplete", "fieldname": "setupComplete",
"label": "Setup Complete", "label": "Setup Complete",
"fieldtype": "Check", "fieldtype": "Check",
"default": false "default": false,
"hidden": true
} }
], ],
"quickEditFields": [ "quickEditFields": [

View File

@ -9,73 +9,80 @@
"label": "Sales Invoice Number Series", "label": "Sales Invoice Number Series",
"fieldtype": "Link", "fieldtype": "Link",
"target": "NumberSeries", "target": "NumberSeries",
"create": true "create": true,
"section": "Number Series"
}, },
{ {
"fieldname": "purchaseInvoiceNumberSeries", "fieldname": "purchaseInvoiceNumberSeries",
"label": "Purchase Invoice Number Series", "label": "Purchase Invoice Number Series",
"fieldtype": "Link", "fieldtype": "Link",
"target": "NumberSeries", "target": "NumberSeries",
"create": true "create": true,
"section": "Number Series"
}, },
{ {
"fieldname": "journalEntryNumberSeries", "fieldname": "journalEntryNumberSeries",
"label": "Journal Entry Number Series", "label": "Journal Entry Number Series",
"fieldtype": "Link", "fieldtype": "Link",
"target": "NumberSeries", "target": "NumberSeries",
"create": true "create": true,
"section": "Number Series"
}, },
{ {
"fieldname": "paymentNumberSeries", "fieldname": "paymentNumberSeries",
"label": "Payment Number Series", "label": "Payment Number Series",
"fieldtype": "Link", "fieldtype": "Link",
"target": "NumberSeries", "target": "NumberSeries",
"create": true "create": true,
"section": "Number Series"
}, },
{ {
"fieldname": "stockMovementNumberSeries", "fieldname": "stockMovementNumberSeries",
"label": "Stock Movement Number Series", "label": "Stock Movement Number Series",
"fieldtype": "Link", "fieldtype": "Link",
"target": "NumberSeries", "target": "NumberSeries",
"create": true "create": true,
"section": "Number Series"
}, },
{ {
"fieldname": "shipmentNumberSeries", "fieldname": "shipmentNumberSeries",
"label": "Shipment Number Series", "label": "Shipment Number Series",
"fieldtype": "Link", "fieldtype": "Link",
"target": "NumberSeries", "target": "NumberSeries",
"create": true "create": true,
"section": "Number Series"
}, },
{ {
"fieldname": "purchaseReceiptNumberSeries", "fieldname": "purchaseReceiptNumberSeries",
"label": "Purchase Receipt Number Series", "label": "Purchase Receipt Number Series",
"fieldtype": "Link", "fieldtype": "Link",
"target": "NumberSeries", "target": "NumberSeries",
"create": true "create": true,
"section": "Number Series"
}, },
{ {
"fieldname": "salesInvoiceTerms", "fieldname": "salesInvoiceTerms",
"label": "Sales Invoice Terms", "label": "Sales Invoice Terms",
"fieldtype": "Text", "fieldtype": "Text",
"target": "NumberSeries" "section": "Terms"
}, },
{ {
"fieldname": "purchaseInvoiceTerms", "fieldname": "purchaseInvoiceTerms",
"label": "Purchase Invoice Terms", "label": "Purchase Invoice Terms",
"fieldtype": "Text", "fieldtype": "Text",
"target": "NumberSeries" "section": "Terms"
}, },
{ {
"fieldname": "shipmentTerms", "fieldname": "shipmentTerms",
"label": "Shipment Terms", "label": "Shipment Terms",
"fieldtype": "Text", "fieldtype": "Text",
"target": "NumberSeries" "section": "Terms"
}, },
{ {
"fieldname": "purchaseReceiptTerms", "fieldname": "purchaseReceiptTerms",
"label": "Purchase Receipt Terms", "label": "Purchase Receipt Terms",
"fieldtype": "Text", "fieldtype": "Text",
"target": "NumberSeries" "section": "Terms"
} }
] ]
} }

View File

@ -6,46 +6,36 @@
{ {
"fieldname": "logo", "fieldname": "logo",
"label": "Logo", "label": "Logo",
"fieldtype": "AttachImage" "fieldtype": "AttachImage",
"section": "Default"
}, },
{ {
"fieldname": "companyName", "fieldname": "companyName",
"label": "Company Name", "label": "Company Name",
"fieldtype": "Data" "fieldtype": "Data",
"section": "Default"
}, },
{ {
"fieldname": "email", "fieldname": "email",
"label": "Email", "label": "Email",
"fieldtype": "Data", "fieldtype": "Data",
"placeholder": "john@doe.com" "placeholder": "john@doe.com",
}, "section": "Contacts"
{
"fieldname": "displayLogo",
"label": "Display Logo in Invoice",
"fieldtype": "Check"
},
{
"fieldname": "displayTaxInvoice",
"label": "Display Tax Invoice",
"fieldtype": "Check"
},
{
"fieldname": "displayBatch",
"label": "Display Batch",
"fieldtype": "Check"
}, },
{ {
"fieldname": "phone", "fieldname": "phone",
"label": "Phone", "label": "Phone",
"fieldtype": "Data", "fieldtype": "Data",
"placeholder": "9888900000" "placeholder": "9888900000",
"section": "Contacts"
}, },
{ {
"fieldname": "address", "fieldname": "address",
"label": "Address", "label": "Address",
"fieldtype": "Link", "fieldtype": "Link",
"target": "Address", "target": "Address",
"inline": true "inline": true,
"section": "Contacts"
}, },
{ {
"fieldname": "template", "fieldname": "template",
@ -66,7 +56,8 @@
"label": "Business" "label": "Business"
} }
], ],
"default": "Basic" "default": "Basic",
"section": "Customizations"
}, },
{ {
"fieldname": "color", "fieldname": "color",
@ -115,7 +106,8 @@
"label": "Black", "label": "Black",
"value": "#112B42" "value": "#112B42"
} }
] ],
"section": "Customizations"
}, },
{ {
"fieldname": "font", "fieldname": "font",
@ -136,7 +128,26 @@
"label": "Courier" "label": "Courier"
} }
], ],
"default": "Arial" "default": "Arial",
"section": "Customizations"
},
{
"fieldname": "displayLogo",
"label": "Display Logo in Invoice",
"fieldtype": "Check",
"section": "Customizations"
},
{
"fieldname": "displayTaxInvoice",
"label": "Display Tax Invoice",
"fieldtype": "Check",
"section": "Customizations"
},
{
"fieldname": "displayBatch",
"label": "Display Batch",
"fieldtype": "Check",
"section": "Customizations"
} }
], ],
"quickEditFields": [ "quickEditFields": [

View File

@ -19,47 +19,55 @@
} }
], ],
"default": "FIFO", "default": "FIFO",
"required": true "required": true,
"section": "Default"
}, },
{ {
"fieldname": "defaultLocation", "fieldname": "defaultLocation",
"label": "Default Location", "label": "Default Location",
"fieldtype": "Link", "fieldtype": "Link",
"target": "Location", "target": "Location",
"create": true "create": true,
"section": "Default"
}, },
{ {
"fieldname": "stockInHand", "fieldname": "stockInHand",
"label": "Stock In Hand Acc.", "label": "Stock In Hand Acc.",
"fieldtype": "Link", "fieldtype": "Link",
"target": "Account" "target": "Account",
"section": "Accounts"
}, },
{ {
"fieldname": "stockReceivedButNotBilled", "fieldname": "stockReceivedButNotBilled",
"label": "Stock Received But Not Billed Acc.", "label": "Stock Received But Not Billed Acc.",
"fieldtype": "Link", "fieldtype": "Link",
"target": "Account" "target": "Account",
"section": "Accounts"
}, },
{ {
"fieldname": "costOfGoodsSold", "fieldname": "costOfGoodsSold",
"label": "Cost Of Goods Sold Acc.", "label": "Cost Of Goods Sold Acc.",
"fieldtype": "Link", "fieldtype": "Link",
"target": "Account" "target": "Account",
"section": "Accounts"
}, },
{ {
"fieldname": "enableBarcodes", "fieldname": "enableBarcodes",
"label": "Enable Barcodes", "label": "Enable Barcodes",
"fieldtype": "Check" "fieldtype": "Check",
"section": "Feature Flags"
}, },
{ {
"fieldname": "enableBatches", "fieldname": "enableBatches",
"label": "Enable Batches", "label": "Enable Batches",
"fieldtype": "Check" "fieldtype": "Check",
"section": "Feature Flags"
}, },
{ {
"fieldname": "enableUomConversions", "fieldname": "enableUomConversions",
"label": "Enable UOM Conversion", "label": "Enable UOM Conversion",
"fieldtype": "Check" "fieldtype": "Check",
"section": "Feature Flags"
} }
] ]
} }

View File

@ -45,7 +45,23 @@
"default": "MMM d, y", "default": "MMM d, y",
"required": true, "required": true,
"allowCustom": true, "allowCustom": true,
"description": "Sets the app-wide date display format." "description": "Sets the app-wide date display format.",
"section": "Default"
},
{
"fieldname": "hideGetStarted",
"label": "Hide Get Started",
"fieldtype": "Check",
"default": false,
"description": "Hides the Get Started section from the sidebar. Change will be visible on restart or refreshing the app.",
"section": "Default"
},
{
"fieldname": "version",
"label": "Version",
"fieldtype": "Data",
"readOnly": true,
"section": "Default"
}, },
{ {
"fieldname": "locale", "fieldname": "locale",
@ -54,7 +70,8 @@
"default": "en-IN", "default": "en-IN",
"required": true, "required": true,
"allowCustom": true, "allowCustom": true,
"description": "Set the local code. This is used for number formatting." "description": "Set the local code. This is used for number formatting.",
"section": "Number Display"
}, },
{ {
"fieldname": "displayPrecision", "fieldname": "displayPrecision",
@ -64,29 +81,8 @@
"required": true, "required": true,
"minvalue": 0, "minvalue": 0,
"maxvalue": 9, "maxvalue": 9,
"description": "Sets how many digits are shown after the decimal point." "description": "Sets how many digits are shown after the decimal point.",
}, "section": "Number Display"
{
"fieldname": "internalPrecision",
"label": "Internal Precision",
"fieldtype": "Int",
"minvalue": 0,
"default": 11,
"description": "Sets the internal precision used for monetary calculations. Above 6 should be sufficient for most currencies."
},
{
"fieldname": "hideGetStarted",
"label": "Hide Get Started",
"fieldtype": "Check",
"default": false,
"description": "Hides the Get Started section from the sidebar. Change will be visible on restart or refreshing the app."
},
{
"fieldname": "countryCode",
"label": "Country Code",
"fieldtype": "Data",
"default": "in",
"description": "Country code used to initialize regional settings."
}, },
{ {
"fieldname": "currency", "fieldname": "currency",
@ -94,19 +90,32 @@
"fieldtype": "AutoComplete", "fieldtype": "AutoComplete",
"default": "INR", "default": "INR",
"readOnly": true, "readOnly": true,
"required": true "required": true,
"section": "Number Display"
}, },
{ {
"fieldname": "version", "fieldname": "internalPrecision",
"label": "Version", "label": "Internal Precision",
"fieldtype": "Int",
"minvalue": 0,
"default": 11,
"description": "Sets the internal precision used for monetary calculations. Above 6 should be sufficient for most currencies.",
"hidden": true
},
{
"fieldname": "countryCode",
"label": "Country Code",
"fieldtype": "Data", "fieldtype": "Data",
"readOnly": true "default": "in",
"description": "Country code used to initialize regional settings.",
"hidden": true
}, },
{ {
"fieldname": "instanceId", "fieldname": "instanceId",
"label": "Instance Id", "label": "Instance Id",
"fieldtype": "Data", "fieldtype": "Data",
"readOnly": true "readOnly": true,
"hidden": true
} }
], ],
"quickEditFields": [ "quickEditFields": [

View File

@ -5,7 +5,8 @@
"fieldname": "gstin", "fieldname": "gstin",
"label": "GSTIN", "label": "GSTIN",
"fieldtype": "Data", "fieldtype": "Data",
"placeholder": "27AAAAA0000A1Z5" "placeholder": "27AAAAA0000A1Z5",
"section": "Default"
} }
], ],
"quickEditFields": [ "quickEditFields": [

View File

@ -112,6 +112,8 @@ import FormHeader from 'src/components/FormHeader.vue';
import StatusBadge from 'src/components/StatusBadge.vue'; import StatusBadge from 'src/components/StatusBadge.vue';
import { handleErrorWithDialog } from 'src/errorHandling'; import { handleErrorWithDialog } from 'src/errorHandling';
import { getErrorMessage } from 'src/utils'; import { getErrorMessage } from 'src/utils';
import { docsPathMap } from 'src/utils/misc';
import { docsPathRef, focusedDocsRef } from 'src/utils/refs';
import { ActionGroup, UIGroupedFields } from 'src/utils/types'; import { ActionGroup, UIGroupedFields } from 'src/utils/types';
import { import {
getFieldsGroupedByTabAndSection, getFieldsGroupedByTabAndSection,
@ -155,8 +157,17 @@ export default defineComponent({
} }
await this.setDoc(); await this.setDoc();
focusedDocsRef.add(this.docOrNull);
this.updateGroupedFields(); this.updateGroupedFields();
}, },
activated(): void {
docsPathRef.value = docsPathMap[this.schemaName] ?? '';
focusedDocsRef.add(this.docOrNull);
},
deactivated(): void {
docsPathRef.value = '';
focusedDocsRef.add(this.docOrNull);
},
computed: { computed: {
hasDoc(): boolean { hasDoc(): boolean {
return !!this.docOrNull; return !!this.docOrNull;

View File

@ -18,8 +18,10 @@
<div <div
v-for="field of fields" v-for="field of fields"
:key="field.fieldname" :key="field.fieldname"
:class="field.fieldtype === 'Table' ? 'col-span-2 text-base' : ''" :class="[
class="mb-auto" field.fieldtype === 'Table' ? 'col-span-2 text-base' : '',
field.fieldtype === 'Check' ? 'mt-auto' : 'mb-auto',
]"
> >
<FormControl <FormControl
:ref="field.fieldname === 'name' ? 'nameField' : 'fields'" :ref="field.fieldname === 'name' ? 'nameField' : 'fields'"

View File

@ -338,7 +338,6 @@ export default {
LinkedEntryWidget, LinkedEntryWidget,
Barcode, Barcode,
}, },
inject: ['shortcuts'],
provide() { provide() {
return { return {
schemaName: this.schemaName, schemaName: this.schemaName,

View File

@ -132,7 +132,6 @@ export default {
DropdownWithActions, DropdownWithActions,
}, },
emits: ['close'], emits: ['close'],
inject: ['shortcuts'],
provide() { provide() {
return { return {
schemaName: this.schemaName, schemaName: this.schemaName,

View File

@ -1,191 +1,268 @@
<template> <template>
<FormContainer :title="t`Settings`" :searchborder="false"> <FormContainer>
<template #header>
<Button v-if="canSave" type="primary" @click="sync">
{{ t`Save` }}
</Button>
</template>
<template #body> <template #body>
<!-- Icon Tab Bar --> <FormHeader
<div class="flex m-4 mb-0 gap-8"> :form-title="tabLabels[activeTab] ?? ''"
<button :form-sub-title="t`Settings`"
v-for="(tab, i) in tabs" class="sticky top-0 bg-white border-b"
:key="tab.label" >
class=" </FormHeader>
hover:bg-white <!-- Section Container -->
flex flex-col
items-center <div class="overflow-auto custom-scroll" v-if="doc">
justify-center <CommonFormSection
cursor-pointer v-for="([name, fields], idx) in activeGroup.entries()"
text-sm @editrow="(doc: Doc) => toggleQuickEditDoc(doc)"
" :key="name + idx"
:class=" ref="section"
i === activeTab && class="p-4"
'text-blue-500 font-semibold border-b-2 border-blue-500' :class="idx !== 0 && activeGroup.size > 1 ? 'border-t' : ''"
" :show-title="activeGroup.size > 1 && name !== t`Default`"
:style="{ :title="name"
paddingBottom: i === activeTab ? 'calc(1rem - 2px)' : '1rem', :fields="fields"
}" :doc="doc"
@click="activeTab = i" :errors="errors"
> @value-change="onValueChange"
{{ tab.label }} />
</button>
</div> </div>
<!-- Component --> <!-- Tab Bar -->
<div class="flex-1 overflow-y-auto custom-scroll"> <div
<component class="
:is="tabs[activeTab].component" mt-auto
:schema-name="tabs[activeTab].schemaName" px-4
@change="handleChange" pb-4
/> flex
gap-8
border-t
flex-shrink-0
sticky
bottom-0
bg-white
"
v-if="groupedFields && groupedFields.size > 1"
>
<div
v-for="key of groupedFields.keys()"
:key="key"
@click="activeTab = key"
class="text-sm cursor-pointer"
:class="
key === activeTab
? 'text-blue-500 font-semibold border-t-2 border-blue-500'
: ''
"
:style="{
paddingTop: key === activeTab ? 'calc(1rem - 2px)' : '1rem',
}"
>
{{ tabLabels[key] }}
</div>
</div> </div>
</template> </template>
</FormContainer> </FormContainer>
</template> </template>
<script> <script lang="ts">
import { ipcRenderer } from 'electron'; import { DocValue } from 'fyo/core/types';
import { t } from 'fyo'; import { Doc } from 'fyo/model/doc';
import { ValidationError } from 'fyo/utils/errors';
import { ModelNameEnum } from 'models/types';
import { Field, Schema } from 'schemas/types';
import Button from 'src/components/Button.vue'; import Button from 'src/components/Button.vue';
import FormContainer from 'src/components/FormContainer.vue'; import FormContainer from 'src/components/FormContainer.vue';
import Icon from 'src/components/Icon.vue'; import FormHeader from 'src/components/FormHeader.vue';
import PageHeader from 'src/components/PageHeader.vue'; import { handleErrorWithDialog } from 'src/errorHandling';
import Row from 'src/components/Row.vue'; import { getErrorMessage } from 'src/utils';
import StatusBadge from 'src/components/StatusBadge.vue'; import { evaluateHidden } from 'src/utils/doc';
import { fyo } from 'src/initFyo'; import { reloadWindow } from 'src/utils/ipcCalls';
import { docsPathMap } from 'src/utils/misc'; import { docsPathMap } from 'src/utils/misc';
import { docsPathRef } from 'src/utils/refs'; import { docsPathRef } from 'src/utils/refs';
import { UIGroupedFields } from 'src/utils/types';
import { showToast } from 'src/utils/ui'; import { showToast } from 'src/utils/ui';
import { IPC_MESSAGES } from 'utils/messages'; import { computed, defineComponent, nextTick } from 'vue';
import { h, markRaw } from 'vue'; import CommonFormSection from '../CommonForm/CommonFormSection.vue';
import TabBase from './TabBase.vue';
import TabGeneral from './TabGeneral.vue';
import TabInvoice from './TabInvoice.vue';
import TabSystem from './TabSystem.vue';
export default {
name: 'Settings',
components: {
PageHeader,
StatusBadge,
Button,
Row,
FormContainer,
},
data() {
const hasInventory = !!fyo.singles.AccountingSettings?.enableInventory;
export default defineComponent({
components: { FormContainer, Button, FormHeader, CommonFormSection },
data() {
return { return {
activeTab: 0, errors: {},
updated: false, canSave: false,
fieldsChanged: [], activeTab: ModelNameEnum.AccountingSettings,
tabs: [ groupedFields: null,
{ quickEditDoc: null,
key: 'Invoice', } as {
label: t`Invoice`, errors: Record<string, string>;
schemaName: 'PrintSettings', canSave: boolean;
component: markRaw(TabInvoice), activeTab: string;
}, groupedFields: null | UIGroupedFields;
{ quickEditDoc: null | Doc;
key: 'General',
label: t`General`,
schemaName: 'AccountingSettings',
component: markRaw(TabGeneral),
},
{
key: 'Defaults',
label: t`Defaults`,
schemaName: 'Defaults',
component: markRaw(TabBase),
},
...(hasInventory
? [
{
key: 'Inventory',
label: t`Inventory`,
schemaName: 'InventorySettings',
component: markRaw(TabBase),
},
]
: []),
{
key: 'System',
label: t`System`,
schemaName: 'SystemSettings',
component: markRaw(TabSystem),
},
],
}; };
}, },
activated() { provide() {
this.setActiveTab(); return { doc: computed(() => this.doc) };
docsPathRef.value = docsPathMap.Settings;
}, },
deactivated() { mounted() {
if (this.fyo.store.isDevelopment) {
// @ts-ignore
window.settings = this;
}
this.update();
},
activated(): void {
docsPathRef.value = docsPathMap.Settings ?? '';
},
deactivated(): void {
docsPathRef.value = ''; docsPathRef.value = '';
if (this.fieldsChanged.length === 0) {
return;
}
const shouleShowReload = this.fieldsChanged
.map(({ fieldname }) => fieldname)
.some((f) => {
if (f.startsWith('enable')) {
return true;
}
if (f === 'displayPrecision') {
return true;
}
if (f === 'hideGetStarted') {
return true;
}
return false;
});
if (shouleShowReload) {
this.showReloadToast();
}
}, },
methods: { methods: {
showReloadToast() { async sync(): Promise<void> {
showToast({ const syncableDocs = this.schemas
message: t`Settings changes will be visible on reload`, .map(({ name }) => this.fyo.singles[name])
actionText: t`Reload App`, .filter((doc) => doc?.canSave) as Doc[];
type: 'info',
action: async () => { for (const doc of syncableDocs) {
ipcRenderer.send(IPC_MESSAGES.RELOAD_MAIN_WINDOW); await this.syncDoc(doc);
},
});
},
handleChange(df, newValue, oldValue) {
if (!df) {
return;
} }
this.fieldsChanged.push(df); this.update();
await showToast({
message: this.t`Changes will be visible on reload`,
actionText: this.t`Reload App`,
type: 'info',
action: reloadWindow,
});
}, },
setActiveTab() { async syncDoc(doc: Doc): Promise<void> {
const { tab } = this.$route.query; try {
const index = this.tabs.findIndex((i) => i.key === tab); await doc.sync();
if (index !== -1) { this.updateGroupedFields();
this.activeTab = index; } catch (err) {
} else { if (!(err instanceof Error)) {
this.activeTab = 0; return;
}
await handleErrorWithDialog(err, doc);
} }
}, },
getIconComponent(tab) { async toggleQuickEditDoc(doc: Doc | null): Promise<void> {
return { if (this.quickEditDoc && doc) {
render() { this.quickEditDoc = null;
return h(Icon, { await nextTick();
class: 'w-6 h-6', }
...Object.assign(
{ this.quickEditDoc = doc;
name: tab.icon, },
size: '24', async onValueChange(field: Field, value: DocValue): Promise<void> {
}, const { fieldname } = field;
this.$attrs delete this.errors[fieldname];
),
}); try {
}, await this.doc?.set(fieldname, value);
}; } catch (err) {
if (!(err instanceof Error)) {
return;
}
this.errors[fieldname] = getErrorMessage(err, this.doc ?? undefined);
}
this.update();
},
update(): void {
this.updateCanSave();
this.updateGroupedFields();
},
updateCanSave(): void {
this.canSave = this.schemas
.map(({ name }) => this.fyo.singles[name]?.canSave)
.some(Boolean);
},
updateGroupedFields(): void {
const grouped: UIGroupedFields = new Map();
const fields: Field[] = this.schemas.map((s) => s.fields).flat();
for (const field of fields) {
const schemaName = field.schemaName!;
if (!grouped.has(schemaName)) {
grouped.set(schemaName, new Map());
}
const tabbed = grouped.get(schemaName)!;
const section = field.section ?? this.t`Miscellaneous`;
if (!tabbed.has(section)) {
tabbed.set(section, []);
}
if (field.meta) {
continue;
}
const doc = this.fyo.singles[schemaName];
if (evaluateHidden(field, doc)) {
continue;
}
tabbed.get(section)!.push(field);
}
this.groupedFields = grouped;
}, },
}, },
}; computed: {
doc(): Doc | null {
const doc = this.fyo.singles[this.activeTab];
if (!doc) {
return null;
}
return doc;
},
tabLabels(): Record<string, string> {
return {
[ModelNameEnum.AccountingSettings]: this.t`General`,
[ModelNameEnum.PrintSettings]: this.t`Print`,
[ModelNameEnum.InventorySettings]: this.t`Inventory`,
[ModelNameEnum.Defaults]: this.t`Defaults`,
[ModelNameEnum.SystemSettings]: this.t`System`,
};
},
schemas(): Schema[] {
const enableInventory =
!!this.fyo.singles.AccountingSettings?.enableInventory;
return [
ModelNameEnum.AccountingSettings,
ModelNameEnum.InventorySettings,
ModelNameEnum.Defaults,
ModelNameEnum.PrintSettings,
ModelNameEnum.SystemSettings,
]
.filter((s) =>
s === ModelNameEnum.InventorySettings ? enableInventory : true
)
.map((s) => this.fyo.schemaMap[s]!);
},
activeGroup(): Map<string, Field[]> {
if (!this.groupedFields) {
return new Map();
}
const group = this.groupedFields.get(this.activeTab);
if (!group) {
throw new ValidationError(
`Tab group ${this.activeTab} has no value set`
);
}
return group;
},
},
});
</script> </script>

View File

@ -1,59 +0,0 @@
<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 TwoColumnForm from 'src/components/TwoColumnForm.vue';
import { defineComponent } from 'vue';
export default defineComponent({
name: 'TabGeneral',
emits: ['change'],
props: { schemaName: String },
components: {
TwoColumnForm,
},
async mounted() {
await this.setDoc();
},
watch: {
async schemaName() {
await this.setDoc();
},
},
methods: {
async setDoc() {
if (this.doc && this.schemaName === this.doc.schemaName) {
return;
}
if (!this.schemaName) {
return;
}
this.doc = await this.fyo.doc.getDoc(this.schemaName, this.schemaName, {
skipDocumentCache: true,
});
},
},
data() {
return {
doc: undefined,
} as { doc?: Doc };
},
computed: {
fields() {
return this.doc?.schema.fields;
},
},
});
</script>

View File

@ -1,49 +0,0 @@
<script lang="ts">
import { Field } from 'schemas/types';
import { fyo } from 'src/initFyo';
import { defineComponent } from 'vue';
import TabBase from './TabBase.vue';
export default defineComponent({
extends: TabBase,
name: 'TabGeneral',
async mounted() {
this.doc = await fyo.doc.getDoc(
'AccountingSettings',
'AccountingSettings',
{
skipDocumentCache: true,
}
);
},
computed: {
fields() {
const fields = [
'fullname',
'companyName',
'country',
'bankName',
'currency',
'fiscalYearStart',
'fiscalYearEnd',
'writeOffAccount',
'roundOffAccount',
'enableDiscounting',
'enableInventory',
];
if (this.doc?.enableDiscounting) {
fields.push('discountAccount');
}
if (fyo.singles.SystemSettings?.countryCode === 'in') {
fields.push('gstin');
}
return fields
.map((fieldname) => fyo.getField('AccountingSettings', fieldname))
.filter(Boolean) as Field[];
},
},
});
</script>

View File

@ -1,101 +0,0 @@
<template>
<div v-if="doc" class="pb-4">
<hr />
<div class="flex items-center gap-4 p-4">
<FormControl
:df="getField('logo')"
:value="doc.logo"
@change="
(value) => {
doc.setAndSync('logo', value);
forwardChangeEvent(getField('logo'));
}
"
/>
<div class="flex flex-col">
<span
class="bg-transparent font-semibold text-xl text-gray-900 px-3 py-2"
>
{{ companyName }}
</span>
<span class="text-lg text-gray-800 px-3 py-2">
{{ doc.email }}
</span>
</div>
</div>
<TwoColumnForm
:doc="doc"
:fields="fields"
:autosave="true"
:emit-change="true"
@change="forwardChangeEvent"
/>
</div>
</template>
<script>
import { ipcRenderer } from 'electron';
import TwoColumnForm from 'src/components/TwoColumnForm.vue';
import { fyo } from 'src/initFyo';
import { IPC_ACTIONS } from 'utils/messages';
import FormControl from '../../components/Controls/FormControl.vue';
export default {
name: 'TabInvoice',
components: {
TwoColumnForm,
FormControl,
},
emits: ['change'],
provide() {
return {
schemaName: 'PrintSettings',
name: 'PrintSettings',
};
},
data() {
return {
companyName: null,
doc: null,
showEdit: false,
};
},
async mounted() {
this.doc = await fyo.doc.getDoc('PrintSettings');
this.companyName = (await fyo.doc.getDoc('AccountingSettings')).companyName;
},
computed: {
fields() {
const fields = ['template', 'color', 'font', 'email', 'phone', 'address'];
if (this.doc.logo) {
fields.unshift('displayLogo');
}
return fields.map((field) => this.getField(field));
},
},
methods: {
getField(fieldname) {
return fyo.getField('PrintSettings', fieldname);
},
async openFileSelector() {
const options = {
title: t`Select Logo`,
properties: ['openFile'],
filters: [{ name: 'Invoice Logo', extensions: ['png', 'jpg', 'svg'] }],
};
const { filePaths } = await ipcRenderer.invoke(
IPC_ACTIONS.GET_OPEN_FILEPATH,
options
);
if (filePaths[0] !== undefined) {
this.doc.set('logo', `file://${files[0]}`);
this.doc.update;
}
},
forwardChangeEvent(...args) {
this.$emit('change', ...args);
},
},
};
</script>

View File

@ -1,58 +0,0 @@
<template>
<div class="flex flex-col justify-between h-full">
<TwoColumnForm
v-if="doc"
:doc="doc"
:fields="fields"
:autosave="true"
:emit-change="true"
@change="forwardChangeEvent"
/>
<div class="flex p-4 justify-between">
<LanguageSelector class="text-sm w-28" />
<p class="mt-auto text-gray-600 text-base select-none">
{{ `v${fyo.store.appVersion}` }}
</p>
</div>
</div>
</template>
<script>
import { ConfigKeys } from 'fyo/core/types';
import { ModelNameEnum } from 'models/types';
import LanguageSelector from 'src/components/Controls/LanguageSelector.vue';
import TwoColumnForm from 'src/components/TwoColumnForm';
import { fyo } from 'src/initFyo';
export default {
name: 'TabSystem',
components: {
TwoColumnForm,
LanguageSelector,
},
emits: ['change'],
data() {
return {
doc: null,
telemetry: '',
};
},
async mounted() {
this.doc = fyo.singles.SystemSettings;
this.companyName = fyo.singles.AccountingSettings.companyName;
this.telemetry = fyo.config.get(ConfigKeys.Telemetry);
},
computed: {
fields() {
return fyo.schemaMap.SystemSettings.quickEditFields.map((f) =>
fyo.getField(ModelNameEnum.SystemSettings, f)
);
},
},
methods: {
forwardChangeEvent(...args) {
this.$emit('change', ...args);
},
},
};
</script>

View File

@ -10,6 +10,10 @@ import { SelectFileOptions, SelectFileReturn } from 'utils/types';
import { setLanguageMap } from './language'; import { setLanguageMap } from './language';
import { showMessageDialog, showToast } from './ui'; import { showMessageDialog, showToast } from './ui';
export function reloadWindow() {
return ipcRenderer.send(IPC_MESSAGES.RELOAD_MAIN_WINDOW);
}
export async function selectFile( export async function selectFile(
options: SelectFileOptions options: SelectFileOptions
): Promise<SelectFileReturn> { ): Promise<SelectFileReturn> {

View File

@ -2,7 +2,8 @@ import { ipcRenderer } from 'electron';
import { DEFAULT_LANGUAGE } from 'fyo/utils/consts'; import { DEFAULT_LANGUAGE } from 'fyo/utils/consts';
import { setLanguageMapOnTranslationString } from 'fyo/utils/translation'; import { setLanguageMapOnTranslationString } from 'fyo/utils/translation';
import { fyo } from 'src/initFyo'; import { fyo } from 'src/initFyo';
import { IPC_ACTIONS, IPC_MESSAGES } from 'utils/messages'; import { IPC_ACTIONS } from 'utils/messages';
import { reloadWindow } from './ipcCalls';
import { systemLanguageRef } from './refs'; import { systemLanguageRef } from './refs';
import { showToast } from './ui'; import { showToast } from './ui';
@ -47,7 +48,7 @@ export async function setLanguageMap(
} }
if (!dontReload && success && initLanguage !== oldLanguage) { if (!dontReload && success && initLanguage !== oldLanguage) {
await ipcRenderer.send(IPC_MESSAGES.RELOAD_MAIN_WINDOW); reloadWindow();
} }
return success; return success;
} }