mirror of
https://github.com/frappe/books.git
synced 2024-12-22 10:58:59 +00:00
incr: get setupwizard to display
- make converter more explicit
This commit is contained in:
parent
b3533698c0
commit
76c61a5c29
@ -1,7 +1,11 @@
|
||||
import { Fyo } from 'fyo';
|
||||
import Doc from 'fyo/model/doc';
|
||||
import { isPesa } from 'fyo/utils';
|
||||
import { ValueError } from 'fyo/utils/errors';
|
||||
import { DateTime } from 'luxon';
|
||||
import Money from 'pesa/dist/types/src/money';
|
||||
import { FieldType, FieldTypeEnum, RawValue } from 'schemas/types';
|
||||
import { Field, FieldTypeEnum, RawValue } from 'schemas/types';
|
||||
import { getIsNullOrUndef } from 'utils';
|
||||
import { DatabaseHandler } from './dbHandler';
|
||||
import { DocValue, DocValueMap, RawValueMap } from './types';
|
||||
|
||||
@ -51,55 +55,41 @@ export class Converter {
|
||||
}
|
||||
}
|
||||
|
||||
static toDocValue(
|
||||
value: RawValue,
|
||||
fieldtype: FieldType,
|
||||
fyo: Fyo
|
||||
): DocValue {
|
||||
switch (fieldtype) {
|
||||
static toDocValue(value: RawValue, field: Field, fyo: Fyo): DocValue {
|
||||
switch (field.fieldtype) {
|
||||
case FieldTypeEnum.Currency:
|
||||
return fyo.pesa((value ?? 0) as string | number);
|
||||
return toDocCurrency(value, field, fyo);
|
||||
case FieldTypeEnum.Date:
|
||||
return new Date(value as string);
|
||||
return toDocDate(value, field);
|
||||
case FieldTypeEnum.Datetime:
|
||||
return new Date(value as string);
|
||||
return toDocDate(value, field);
|
||||
case FieldTypeEnum.Int:
|
||||
return +(value as string | number);
|
||||
return toDocInt(value, field);
|
||||
case FieldTypeEnum.Float:
|
||||
return +(value as string | number);
|
||||
return toDocFloat(value, field);
|
||||
case FieldTypeEnum.Check:
|
||||
return Boolean(value as number);
|
||||
return toDocCheck(value, field);
|
||||
default:
|
||||
return String(value);
|
||||
}
|
||||
}
|
||||
|
||||
static toRawValue(value: DocValue, fieldtype: FieldType): RawValue {
|
||||
switch (fieldtype) {
|
||||
static toRawValue(value: DocValue, field: Field, fyo: Fyo): RawValue {
|
||||
switch (field.fieldtype) {
|
||||
case FieldTypeEnum.Currency:
|
||||
return (value as Money).store;
|
||||
return toRawCurrency(value, fyo, field);
|
||||
case FieldTypeEnum.Date:
|
||||
return (value as Date).toISOString().split('T')[0];
|
||||
return toRawDate(value, field);
|
||||
case FieldTypeEnum.Datetime:
|
||||
return (value as Date).toISOString();
|
||||
case FieldTypeEnum.Int: {
|
||||
if (typeof value === 'string') {
|
||||
return parseInt(value);
|
||||
}
|
||||
|
||||
return Math.floor(value as number);
|
||||
}
|
||||
case FieldTypeEnum.Float: {
|
||||
if (typeof value === 'string') {
|
||||
return parseFloat(value);
|
||||
}
|
||||
|
||||
return value as number;
|
||||
}
|
||||
return toRawDateTime(value, field);
|
||||
case FieldTypeEnum.Int:
|
||||
return toRawInt(value, field);
|
||||
case FieldTypeEnum.Float:
|
||||
return toRawFloat(value, field);
|
||||
case FieldTypeEnum.Check:
|
||||
return Number(value);
|
||||
return toRawCheck(value, field);
|
||||
default:
|
||||
return String(value);
|
||||
return toRawString(value, field);
|
||||
}
|
||||
}
|
||||
|
||||
@ -118,7 +108,7 @@ export class Converter {
|
||||
} else {
|
||||
docValueMap[fieldname] = Converter.toDocValue(
|
||||
rawValue,
|
||||
field.fieldtype,
|
||||
field,
|
||||
this.fyo
|
||||
);
|
||||
}
|
||||
@ -146,7 +136,8 @@ export class Converter {
|
||||
} else {
|
||||
rawValueMap[fieldname] = Converter.toRawValue(
|
||||
docValue,
|
||||
field.fieldtype
|
||||
field,
|
||||
this.fyo
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -154,3 +145,181 @@ export class Converter {
|
||||
return rawValueMap;
|
||||
}
|
||||
}
|
||||
|
||||
function toDocDate(value: RawValue, field: Field) {
|
||||
if (typeof value !== 'number' && typeof value !== 'string') {
|
||||
throwError(value, field, 'doc');
|
||||
}
|
||||
|
||||
const date = new Date(value);
|
||||
if (date.toString() === 'Invalid Date') {
|
||||
throwError(value, field, 'doc');
|
||||
}
|
||||
|
||||
return date;
|
||||
}
|
||||
|
||||
function toDocCurrency(value: RawValue, field: Field, fyo: Fyo) {
|
||||
if (typeof value === 'string') {
|
||||
return fyo.pesa(value);
|
||||
}
|
||||
|
||||
if (typeof value === 'number') {
|
||||
return fyo.pesa(value);
|
||||
}
|
||||
|
||||
if (typeof value === 'boolean') {
|
||||
return fyo.pesa(Number(value));
|
||||
}
|
||||
|
||||
throwError(value, field, 'doc');
|
||||
}
|
||||
|
||||
function toDocInt(value: RawValue, field: Field): number {
|
||||
if (typeof value === 'string') {
|
||||
value = parseInt(value);
|
||||
}
|
||||
|
||||
return toDocFloat(value, field);
|
||||
}
|
||||
|
||||
function toDocFloat(value: RawValue, field: Field): number {
|
||||
if (typeof value === 'boolean') {
|
||||
return Number(value);
|
||||
}
|
||||
|
||||
if (typeof value === 'string') {
|
||||
value = parseFloat(value);
|
||||
}
|
||||
|
||||
if (typeof value === 'number' && !Number.isNaN(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
throwError(value, field, 'doc');
|
||||
}
|
||||
|
||||
function toDocCheck(value: RawValue, field: Field): boolean {
|
||||
if (typeof value === 'boolean') {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (typeof value === 'string') {
|
||||
return value === '1';
|
||||
}
|
||||
|
||||
if (typeof value === 'number') {
|
||||
return Boolean(value);
|
||||
}
|
||||
|
||||
throwError(value, field, 'doc');
|
||||
}
|
||||
|
||||
function toRawCurrency(value: DocValue, fyo: Fyo, field: Field): string {
|
||||
if (isPesa(value)) {
|
||||
return (value as Money).store;
|
||||
}
|
||||
|
||||
if (getIsNullOrUndef(value)) {
|
||||
return fyo.pesa(0).store;
|
||||
}
|
||||
|
||||
if (typeof value === 'number') {
|
||||
return fyo.pesa(value).store;
|
||||
}
|
||||
|
||||
if (typeof value === 'string') {
|
||||
return fyo.pesa(value).store;
|
||||
}
|
||||
|
||||
throwError(value, field, 'raw');
|
||||
}
|
||||
|
||||
function toRawInt(value: DocValue, field: Field): number {
|
||||
if (typeof value === 'string') {
|
||||
return parseInt(value);
|
||||
}
|
||||
|
||||
if (getIsNullOrUndef(value)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (typeof value === 'number') {
|
||||
return Math.floor(value as number);
|
||||
}
|
||||
|
||||
throwError(value, field, 'raw');
|
||||
}
|
||||
|
||||
function toRawFloat(value: DocValue, field: Field): number {
|
||||
if (typeof value === 'string') {
|
||||
return parseFloat(value);
|
||||
}
|
||||
|
||||
if (getIsNullOrUndef(value)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (typeof value === 'number') {
|
||||
return value;
|
||||
}
|
||||
|
||||
throwError(value, field, 'raw');
|
||||
}
|
||||
|
||||
function toRawDate(value: DocValue, field: Field): string {
|
||||
const dateTime = toRawDateTime(value, field);
|
||||
return dateTime.split('T')[0];
|
||||
}
|
||||
|
||||
function toRawDateTime(value: DocValue, field: Field): string {
|
||||
if (getIsNullOrUndef(value)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (typeof value === 'string') {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (value instanceof Date) {
|
||||
return (value as Date).toISOString();
|
||||
}
|
||||
|
||||
if (value instanceof DateTime) {
|
||||
return (value as DateTime).toISO();
|
||||
}
|
||||
|
||||
throwError(value, field, 'raw');
|
||||
}
|
||||
|
||||
function toRawCheck(value: DocValue, field: Field): number {
|
||||
if (typeof value === 'number') {
|
||||
value = Boolean(value);
|
||||
}
|
||||
|
||||
if (typeof value === 'boolean') {
|
||||
return Number(value);
|
||||
}
|
||||
|
||||
throwError(value, field, 'raw');
|
||||
}
|
||||
|
||||
function toRawString(value: DocValue, field: Field): string {
|
||||
if (getIsNullOrUndef(value)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (typeof value === 'string') {
|
||||
return value;
|
||||
}
|
||||
|
||||
throwError(value, field, 'raw');
|
||||
}
|
||||
|
||||
function throwError<T>(value: T, field: Field, type: 'raw' | 'doc'): never {
|
||||
throw new ValueError(
|
||||
`invalid ${type} conversion '${value}' of type ${typeof value} found, field: ${JSON.stringify(
|
||||
field
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
@ -129,8 +129,8 @@ export class DatabaseHandler extends DatabaseBase {
|
||||
|
||||
const docSingleValue: SingleValue<DocValue> = [];
|
||||
for (const sv of rawSingleValue) {
|
||||
const fieldtype = this.fieldValueMap[sv.parent][sv.fieldname].fieldtype;
|
||||
const value = Converter.toDocValue(sv.value, fieldtype, this.#fyo);
|
||||
const field = this.fieldValueMap[sv.parent][sv.fieldname];
|
||||
const value = Converter.toDocValue(sv.value, field, this.#fyo);
|
||||
|
||||
docSingleValue.push({
|
||||
value,
|
||||
|
118
fyo/model/doc.ts
118
fyo/model/doc.ts
@ -1,6 +1,7 @@
|
||||
import { Fyo } from 'fyo';
|
||||
import { DocValue, DocValueMap } from 'fyo/core/types';
|
||||
import { Verb } from 'fyo/telemetry/types';
|
||||
import { DEFAULT_USER } from 'fyo/utils/consts';
|
||||
import {
|
||||
Conflict,
|
||||
MandatoryError,
|
||||
@ -123,7 +124,7 @@ export default class Doc extends Observable<DocValue | Doc[]> {
|
||||
}
|
||||
}
|
||||
|
||||
setDirty(value: boolean) {
|
||||
_setDirty(value: boolean) {
|
||||
this._dirty = value;
|
||||
if (this.schema.isChild && this.parentdoc) {
|
||||
this.parentdoc._dirty = value;
|
||||
@ -133,27 +134,15 @@ export default class Doc extends Observable<DocValue | Doc[]> {
|
||||
// set value and trigger change
|
||||
async set(fieldname: string | DocValueMap, value?: DocValue | Doc[]) {
|
||||
if (typeof fieldname === 'object') {
|
||||
this.setMultiple(fieldname as DocValueMap);
|
||||
await this.setMultiple(fieldname as DocValueMap);
|
||||
return;
|
||||
}
|
||||
|
||||
if (fieldname === 'numberSeries' && !this._notInserted) {
|
||||
if (!this._canSet(fieldname, value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (value === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
this.fieldMap[fieldname] === undefined ||
|
||||
(this[fieldname] !== undefined &&
|
||||
areDocValuesEqual(this[fieldname] as DocValue, value as DocValue))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setDirty(true);
|
||||
this._setDirty(true);
|
||||
if (Array.isArray(value)) {
|
||||
this[fieldname] = value.map((row, i) => {
|
||||
row.idx = i;
|
||||
@ -161,16 +150,16 @@ export default class Doc extends Observable<DocValue | Doc[]> {
|
||||
});
|
||||
} else {
|
||||
const field = this.fieldMap[fieldname];
|
||||
await this.validateField(field, value);
|
||||
await this._validateField(field, value);
|
||||
this[fieldname] = value;
|
||||
}
|
||||
|
||||
// always run applyChange from the parentdoc
|
||||
if (this.schema.isChild && this.parentdoc) {
|
||||
await this.applyChange(fieldname);
|
||||
await this.parentdoc.applyChange(this.parentfield as string);
|
||||
await this._applyChange(fieldname);
|
||||
await this.parentdoc._applyChange(this.parentfield as string);
|
||||
} else {
|
||||
await this.applyChange(fieldname);
|
||||
await this._applyChange(fieldname);
|
||||
}
|
||||
}
|
||||
|
||||
@ -180,8 +169,29 @@ export default class Doc extends Observable<DocValue | Doc[]> {
|
||||
}
|
||||
}
|
||||
|
||||
async applyChange(fieldname: string) {
|
||||
await this.applyFormula(fieldname);
|
||||
_canSet(fieldname: string, value?: DocValue | Doc[]): boolean {
|
||||
if (fieldname === 'numberSeries' && !this._notInserted) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (value === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.fieldMap[fieldname] === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const currentValue = this.get(fieldname);
|
||||
if (currentValue === undefined) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return !areDocValuesEqual(currentValue as DocValue, value as DocValue);
|
||||
}
|
||||
|
||||
async _applyChange(fieldname: string) {
|
||||
await this._applyFormula(fieldname);
|
||||
await this.trigger('change', {
|
||||
doc: this,
|
||||
changed: fieldname,
|
||||
@ -218,7 +228,7 @@ export default class Doc extends Observable<DocValue | Doc[]> {
|
||||
// push child row and trigger change
|
||||
this.push(fieldname, docValueMap);
|
||||
this._dirty = true;
|
||||
this.applyChange(fieldname);
|
||||
this._applyChange(fieldname);
|
||||
}
|
||||
|
||||
push(fieldname: string, docValueMap: Doc | DocValueMap = {}) {
|
||||
@ -256,11 +266,11 @@ export default class Doc extends Observable<DocValue | Doc[]> {
|
||||
}
|
||||
|
||||
async validateInsert() {
|
||||
this.validateMandatory();
|
||||
await this.validateFields();
|
||||
this._validateMandatory();
|
||||
await this._validateFields();
|
||||
}
|
||||
|
||||
validateMandatory() {
|
||||
_validateMandatory() {
|
||||
const checkForMandatory: Doc[] = [this];
|
||||
const tableFields = this.schema.fields.filter(
|
||||
(f) => f.fieldtype === FieldTypeEnum.Table
|
||||
@ -282,7 +292,7 @@ export default class Doc extends Observable<DocValue | Doc[]> {
|
||||
}
|
||||
}
|
||||
|
||||
async validateFields() {
|
||||
async _validateFields() {
|
||||
const fields = this.schema.fields;
|
||||
for (const field of fields) {
|
||||
if (field.fieldtype === FieldTypeEnum.Table) {
|
||||
@ -290,11 +300,11 @@ export default class Doc extends Observable<DocValue | Doc[]> {
|
||||
}
|
||||
|
||||
const value = this.get(field.fieldname) as DocValue;
|
||||
await this.validateField(field, value);
|
||||
await this._validateField(field, value);
|
||||
}
|
||||
}
|
||||
|
||||
async validateField(field: Field, value: DocValue) {
|
||||
async _validateField(field: Field, value: DocValue) {
|
||||
if (field.fieldtype == 'Select') {
|
||||
validateSelect(field as OptionField, value as string);
|
||||
}
|
||||
@ -329,25 +339,25 @@ export default class Doc extends Observable<DocValue | Doc[]> {
|
||||
return data;
|
||||
}
|
||||
|
||||
setBaseMetaValues() {
|
||||
_setBaseMetaValues() {
|
||||
if (this.schema.isSubmittable && typeof this.submitted !== 'boolean') {
|
||||
this.submitted = false;
|
||||
this.cancelled = false;
|
||||
}
|
||||
|
||||
if (!this.createdBy) {
|
||||
this.createdBy = this.fyo.auth.session.user;
|
||||
this.createdBy = this.fyo.auth.session.user || DEFAULT_USER;
|
||||
}
|
||||
|
||||
if (!this.created) {
|
||||
this.created = new Date();
|
||||
}
|
||||
|
||||
this.updateModified();
|
||||
this._updateModified();
|
||||
}
|
||||
|
||||
updateModified() {
|
||||
this.modifiedBy = this.fyo.auth.session.user;
|
||||
_updateModified() {
|
||||
this.modifiedBy = this.fyo.auth.session.user || DEFAULT_USER;
|
||||
this.modified = new Date();
|
||||
}
|
||||
|
||||
@ -474,11 +484,7 @@ export default class Doc extends Observable<DocValue | Doc[]> {
|
||||
}
|
||||
}
|
||||
|
||||
async applyFormula(fieldname?: string) {
|
||||
if (fieldname && this.formulas[fieldname] === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
async _applyFormula(fieldname?: string) {
|
||||
const doc = this;
|
||||
let changed = false;
|
||||
|
||||
@ -492,7 +498,7 @@ export default class Doc extends Observable<DocValue | Doc[]> {
|
||||
(fn) => this.fieldMap[fn]
|
||||
);
|
||||
|
||||
changed ||= await this.applyFormulaForFields(
|
||||
changed ||= await this._applyFormulaForFields(
|
||||
formulaFields,
|
||||
row,
|
||||
fieldname
|
||||
@ -503,22 +509,27 @@ export default class Doc extends Observable<DocValue | Doc[]> {
|
||||
const formulaFields = Object.keys(this.formulas).map(
|
||||
(fn) => this.fieldMap[fn]
|
||||
);
|
||||
changed ||= await this.applyFormulaForFields(formulaFields, doc, fieldname);
|
||||
changed ||= await this._applyFormulaForFields(
|
||||
formulaFields,
|
||||
doc,
|
||||
fieldname
|
||||
);
|
||||
return changed;
|
||||
}
|
||||
|
||||
async applyFormulaForFields(
|
||||
async _applyFormulaForFields(
|
||||
formulaFields: Field[],
|
||||
doc: Doc,
|
||||
fieldname?: string
|
||||
) {
|
||||
let changed = false;
|
||||
for (const field of formulaFields) {
|
||||
if (!shouldApplyFormula(field, doc, fieldname)) {
|
||||
const shouldApply = shouldApplyFormula(field, doc, fieldname);
|
||||
if (!shouldApply) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const newVal = await this.getValueFromFormula(field, doc);
|
||||
const newVal = await this._getValueFromFormula(field, doc);
|
||||
const previousVal = doc.get(field.fieldname);
|
||||
const isSame = areDocValuesEqual(newVal as DocValue, previousVal);
|
||||
if (newVal === undefined || isSame) {
|
||||
@ -532,15 +543,18 @@ export default class Doc extends Observable<DocValue | Doc[]> {
|
||||
return changed;
|
||||
}
|
||||
|
||||
async getValueFromFormula(field: Field, doc: Doc) {
|
||||
let value: FormulaReturn;
|
||||
|
||||
const formula = doc.formulas[field.fieldtype];
|
||||
async _getValueFromFormula(field: Field, doc: Doc) {
|
||||
const formula = doc.formulas[field.fieldname];
|
||||
if (formula === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
value = await formula();
|
||||
let value: FormulaReturn;
|
||||
try {
|
||||
value = await formula();
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
if (Array.isArray(value) && field.fieldtype === FieldTypeEnum.Table) {
|
||||
value = value.map((row) => this._initChild(row, field.fieldname));
|
||||
}
|
||||
@ -551,13 +565,13 @@ export default class Doc extends Observable<DocValue | Doc[]> {
|
||||
async commit() {
|
||||
// re-run triggers
|
||||
this.setChildIdx();
|
||||
await this.applyFormula();
|
||||
await this._applyFormula();
|
||||
await this.trigger('validate', null);
|
||||
}
|
||||
|
||||
async insert() {
|
||||
await setName(this, this.fyo);
|
||||
this.setBaseMetaValues();
|
||||
this._setBaseMetaValues();
|
||||
await this.commit();
|
||||
await this.validateInsert();
|
||||
await this.trigger('beforeInsert', null);
|
||||
@ -587,7 +601,7 @@ export default class Doc extends Observable<DocValue | Doc[]> {
|
||||
if (this.flags.revertAction) await this.trigger('beforeRevert');
|
||||
|
||||
// update modifiedBy and modified
|
||||
this.updateModified();
|
||||
this._updateModified();
|
||||
|
||||
const data = this.getValidDict();
|
||||
await this.fyo.db.update(this.schemaName, data);
|
||||
|
@ -88,7 +88,7 @@ function getMandatory(doc: Doc): Field[] {
|
||||
}
|
||||
|
||||
export function shouldApplyFormula(field: Field, doc: Doc, fieldname?: string) {
|
||||
if (!doc.formulas[field.fieldtype]) {
|
||||
if (!doc.formulas[field.fieldname]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,7 @@ export function validateSelect(field: OptionField, value: string) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!field.required && (value === null || value === undefined)) {
|
||||
if (!field.required && !value) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,9 @@
|
||||
export const DEFAULT_INTERNAL_PRECISION = 11;
|
||||
export const DEFAULT_DISPLAY_PRECISION = 2;
|
||||
export const DEFAULT_DATE_FORMAT = 'yyyy-MM-dd';
|
||||
export const DEFAULT_DATE_FORMAT = 'MMM d, y';
|
||||
export const DEFAULT_LOCALE = 'en-IN';
|
||||
export const DEFAULT_COUNTRY_CODE = 'in';
|
||||
export const DEFAULT_CURRENCY = 'INR';
|
||||
export const DEFAULT_LANGUAGE = 'English';
|
||||
export const DEFAULT_SERIES_START = 1001;
|
||||
export const DEFAULT_SERIES_START = 1001;
|
||||
export const DEFAULT_USER = 'Admin';
|
@ -129,12 +129,14 @@ function getNumberFormatter(fyo: Fyo) {
|
||||
}
|
||||
|
||||
function getCurrency(field: Field, doc: Doc | null, fyo: Fyo): string {
|
||||
if (doc && doc.getCurrencies[field.fieldname]) {
|
||||
return doc.getCurrencies[field.fieldname]();
|
||||
let getCurrency = doc?.getCurrencies[field.fieldname];
|
||||
if (getCurrency !== undefined) {
|
||||
return getCurrency();
|
||||
}
|
||||
|
||||
if (doc && doc.parentdoc?.getCurrencies[field.fieldname]) {
|
||||
return doc.parentdoc.getCurrencies[field.fieldname]();
|
||||
getCurrency = doc?.parentdoc?.getCurrencies[field.fieldname];
|
||||
if (getCurrency !== undefined) {
|
||||
return getCurrency();
|
||||
}
|
||||
|
||||
return (fyo.singles.SystemSettings?.currency as string) ?? DEFAULT_CURRENCY;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import Doc from 'fyo/model/doc';
|
||||
import { FiltersMap, ListsMap, ValidationMap } from 'fyo/model/types';
|
||||
import { validateEmail } from 'fyo/model/validationFunction';
|
||||
import countryInfo from '../../../fixtures/countryInfo.json';
|
||||
import { getCountryInfo } from 'utils/misc';
|
||||
|
||||
export class AccountingSettings extends Doc {
|
||||
static filters: FiltersMap = {
|
||||
@ -20,6 +20,6 @@ export class AccountingSettings extends Doc {
|
||||
};
|
||||
|
||||
static lists: ListsMap = {
|
||||
country: () => Object.keys(countryInfo),
|
||||
country: () => Object.keys(getCountryInfo()),
|
||||
};
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import Doc from 'fyo/model/doc';
|
||||
import { EmptyMessageMap, FormulaMap, ListsMap } from 'fyo/model/types';
|
||||
import { stateCodeMap } from 'regional/in';
|
||||
import { titleCase } from 'utils';
|
||||
import countryInfo from '../../../fixtures/countryInfo.json';
|
||||
import { getCountryInfo } from 'utils/misc';
|
||||
|
||||
export class Address extends Doc {
|
||||
formulas: FormulaMap = {
|
||||
@ -32,7 +32,7 @@ export class Address extends Doc {
|
||||
}
|
||||
},
|
||||
country() {
|
||||
return Object.keys(countryInfo).sort();
|
||||
return Object.keys(getCountryInfo()).sort();
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -1,9 +1,14 @@
|
||||
import { t } from 'fyo';
|
||||
import Doc from 'fyo/model/doc';
|
||||
import { FormulaMap, ListsMap, ValidationMap } from 'fyo/model/types';
|
||||
import {
|
||||
DependsOnMap,
|
||||
FormulaMap,
|
||||
ListsMap,
|
||||
ValidationMap,
|
||||
} from 'fyo/model/types';
|
||||
import { validateEmail } from 'fyo/model/validationFunction';
|
||||
import { DateTime } from 'luxon';
|
||||
import countryInfo from '../../../fixtures/countryInfo.json';
|
||||
import { getCountryInfo } from 'utils/misc';
|
||||
|
||||
export function getCOAList() {
|
||||
return [
|
||||
@ -26,14 +31,21 @@ export function getCOAList() {
|
||||
}
|
||||
|
||||
export class SetupWizard extends Doc {
|
||||
dependsOn: DependsOnMap = {
|
||||
fiscalYearStart: ['country'],
|
||||
fiscalYearEnd: ['country'],
|
||||
currency: ['country'],
|
||||
chartOfAccounts: ['country'],
|
||||
};
|
||||
|
||||
formulas: FormulaMap = {
|
||||
fiscalYearStart: async () => {
|
||||
if (!this.country) return;
|
||||
|
||||
const today = DateTime.local();
|
||||
|
||||
// @ts-ignore
|
||||
const fyStart = countryInfo[this.country].fiscal_year_start as
|
||||
const countryInfo = getCountryInfo();
|
||||
const fyStart = countryInfo[this.country as string]?.fiscal_year_start as
|
||||
| string
|
||||
| undefined;
|
||||
|
||||
@ -50,8 +62,8 @@ export class SetupWizard extends Doc {
|
||||
|
||||
const today = DateTime.local();
|
||||
|
||||
// @ts-ignore
|
||||
const fyEnd = countryInfo[this.country].fiscal_year_end as
|
||||
const countryInfo = getCountryInfo();
|
||||
const fyEnd = countryInfo[this.country as string]?.fiscal_year_end as
|
||||
| string
|
||||
| undefined;
|
||||
if (fyEnd) {
|
||||
@ -64,8 +76,8 @@ export class SetupWizard extends Doc {
|
||||
if (!this.country) {
|
||||
return;
|
||||
}
|
||||
// @ts-ignore
|
||||
return countryInfo[this.country].currency;
|
||||
const countryInfo = getCountryInfo();
|
||||
return countryInfo[this.country as string]?.currency;
|
||||
},
|
||||
chartOfAccounts: async () => {
|
||||
const country = this.get('country') as string | undefined;
|
||||
@ -73,7 +85,7 @@ export class SetupWizard extends Doc {
|
||||
return;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
const countryInfo = getCountryInfo();
|
||||
const code = (countryInfo[country] as undefined | { code: string })?.code;
|
||||
if (code === undefined) {
|
||||
return;
|
||||
@ -94,7 +106,7 @@ export class SetupWizard extends Doc {
|
||||
};
|
||||
|
||||
static lists: ListsMap = {
|
||||
country: () => Object.keys(countryInfo),
|
||||
country: () => Object.keys(getCountryInfo()),
|
||||
chartOfAccounts: () => getCOAList().map(({ name }) => name),
|
||||
};
|
||||
}
|
||||
|
81
src/App.vue
81
src/App.vue
@ -10,6 +10,13 @@
|
||||
v-if="activeScreen === 'Desk'"
|
||||
@change-db-file="changeDbFile"
|
||||
/>-->
|
||||
<div
|
||||
v-if="activeScreen === 'Desk'"
|
||||
class="h-screen w-screen flex justify-center items-center bg-white"
|
||||
>
|
||||
<h1>Desk</h1>
|
||||
</div>
|
||||
|
||||
<DatabaseSelector
|
||||
v-if="activeScreen === 'DatabaseSelector'"
|
||||
@file-selected="fileSelected"
|
||||
@ -17,7 +24,7 @@
|
||||
<SetupWizard
|
||||
v-if="activeScreen === 'SetupWizard'"
|
||||
@setup-complete="setupComplete"
|
||||
@setup-canceled="setupCanceled"
|
||||
@setup-canceled="changeDbFile"
|
||||
/>
|
||||
<div
|
||||
id="toast-container"
|
||||
@ -26,17 +33,23 @@
|
||||
>
|
||||
<div id="toast-target" />
|
||||
</div>
|
||||
<!-- TODO: check this and uncomment
|
||||
<TelemetryModal />-->
|
||||
<TelemetryModal />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import fs from 'fs/promises';
|
||||
import { ConfigKeys } from 'fyo/core/types';
|
||||
import {
|
||||
getSetupComplete,
|
||||
incrementOpenCount,
|
||||
startTelemetry
|
||||
} from 'src/utils/misc';
|
||||
import TelemetryModal from './components/once/TelemetryModal.vue';
|
||||
import WindowsTitleBar from './components/WindowsTitleBar.vue';
|
||||
import { fyo, initializeInstance } from './initFyo';
|
||||
import DatabaseSelector from './pages/DatabaseSelector.vue';
|
||||
import SetupWizard from './pages/SetupWizard/SetupWizard.vue';
|
||||
import setupInstance from './setup/setupInstance';
|
||||
import './styles/index.css';
|
||||
import { checkForUpdates } from './utils/ipcCalls';
|
||||
import { routeTo } from './utils/ui';
|
||||
@ -53,48 +66,45 @@ export default {
|
||||
SetupWizard,
|
||||
DatabaseSelector,
|
||||
WindowsTitleBar,
|
||||
// TelemetryModal,
|
||||
TelemetryModal,
|
||||
},
|
||||
async mounted() {
|
||||
fyo.telemetry.platform = this.platform;
|
||||
/*
|
||||
const lastSelectedFilePath = fyo.config.get('lastSelectedFilePath', null);
|
||||
const { connectionSuccess, reason } = await connectToLocalDatabase(
|
||||
lastSelectedFilePath
|
||||
|
||||
const lastSelectedFilePath = fyo.config.get(
|
||||
ConfigKeys.LastSelectedFilePath,
|
||||
null
|
||||
);
|
||||
|
||||
if (connectionSuccess) {
|
||||
this.showSetupWizardOrDesk(false);
|
||||
if (lastSelectedFilePath) {
|
||||
await this.fileSelected(lastSelectedFilePath, false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (lastSelectedFilePath) {
|
||||
const title = this.t`DB Connection Error`;
|
||||
const content = `reason: ${reason}, filePath: ${lastSelectedFilePath}`;
|
||||
|
||||
await showErrorDialog(title, content);
|
||||
}
|
||||
*/
|
||||
|
||||
// this.activeScreen = 'DatabaseSelector';
|
||||
this.activeScreen = 'SetupWizard';
|
||||
this.activeScreen = 'DatabaseSelector';
|
||||
},
|
||||
methods: {
|
||||
async setupComplete() {
|
||||
// TODO: Complete this
|
||||
// await postSetup();
|
||||
// await this.showSetupWizardOrDesk(true);
|
||||
async setDesk() {
|
||||
this.activeScreen = 'Desk';
|
||||
incrementOpenCount();
|
||||
await startTelemetry();
|
||||
await checkForUpdates(false);
|
||||
await this.setDeskRoute();
|
||||
},
|
||||
async fileSelected(filePath, isNew) {
|
||||
console.log('from App.vue', filePath, isNew);
|
||||
fyo.config.set(ConfigKeys.LastSelectedFilePath, filePath);
|
||||
if (isNew) {
|
||||
this.activeScreen = 'SetupWizard';
|
||||
return;
|
||||
}
|
||||
|
||||
await this.showSetupWizardOrDesk(filePath);
|
||||
},
|
||||
async showSetupWizardOrDesk(filePath, resetRoute = false) {
|
||||
async setupComplete(setupWizardOptions) {
|
||||
const filePath = fyo.config.get(ConfigKeys.LastSelectedFilePath);
|
||||
await setupInstance(filePath, setupWizardOptions);
|
||||
await this.setDesk();
|
||||
},
|
||||
async showSetupWizardOrDesk(filePath) {
|
||||
const countryCode = await fyo.db.connectToDatabase(filePath);
|
||||
const setupComplete = await getSetupComplete();
|
||||
|
||||
@ -104,13 +114,7 @@ export default {
|
||||
}
|
||||
|
||||
await initializeInstance(filePath, false, countryCode);
|
||||
this.activeScreen = 'Desk';
|
||||
await checkForUpdates(false);
|
||||
if (!resetRoute) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.setDeskRoute();
|
||||
await this.setDesk();
|
||||
},
|
||||
async setDeskRoute() {
|
||||
const { onboardingComplete } = await fyo.doc.getSingle('GetStarted');
|
||||
@ -128,13 +132,6 @@ export default {
|
||||
fyo.purgeCache();
|
||||
this.activeScreen = 'DatabaseSelector';
|
||||
},
|
||||
async setupCanceled() {
|
||||
const filePath = fyo.config.get('lastSelectedFilePath');
|
||||
if (filePath) {
|
||||
await fs.unlink(filePath);
|
||||
}
|
||||
this.changeDbFile();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -2,12 +2,18 @@
|
||||
|
||||
This is where all the frontend code lives
|
||||
|
||||
## Initialization
|
||||
New Instance
|
||||
1. Run _Setup Wizard_ for initialization values (eg: `countryCode`).
|
||||
2.
|
||||
## Fyo Initialization
|
||||
|
||||
Existing Instance
|
||||
1. Connect to db
|
||||
The initialization flows are different when the instance is new or is existing.
|
||||
All of them are triggered from `src/App.vue`.
|
||||
|
||||
**New Instance**
|
||||
|
||||
1. Run _Setup Wizard_ for init values (eg: `country`).
|
||||
2. Call `setupInstance.ts/setupInstance` using init values.
|
||||
|
||||
**Existing Instance**
|
||||
|
||||
1. Connect to db.
|
||||
2. Check if _Setup Wizard_ has been completed, if not, jump to **New Instance**
|
||||
3. Call `initFyo/initializeInstance` with `dbPath` and `countryCode`
|
||||
3. Call `initFyo/initializeInstance` with `dbPath` and `countryCode`
|
||||
|
@ -22,19 +22,19 @@
|
||||
<script>
|
||||
export default {
|
||||
name: 'Base',
|
||||
props: [
|
||||
'df',
|
||||
'value',
|
||||
'inputClass',
|
||||
'placeholder',
|
||||
'size',
|
||||
'showLabel',
|
||||
'readOnly',
|
||||
'autofocus',
|
||||
],
|
||||
props: {
|
||||
df: Object,
|
||||
value: [String, Number, Boolean, Object],
|
||||
inputClass: [Function, String],
|
||||
placeholder: String,
|
||||
size: String,
|
||||
showLabel: Boolean,
|
||||
readOnly: Boolean,
|
||||
autofocus: Boolean,
|
||||
},
|
||||
emits: ['focus', 'input', 'change'],
|
||||
inject: {
|
||||
doctype: {
|
||||
schemaName: {
|
||||
default: null,
|
||||
},
|
||||
name: {
|
||||
|
@ -3,6 +3,7 @@
|
||||
<div class="text-gray-600 text-sm mb-1" v-if="showLabel">
|
||||
{{ df.label }}
|
||||
</div>
|
||||
|
||||
<DatePicker
|
||||
ref="input"
|
||||
:input-class="[inputClasses, 'cursor-text']"
|
||||
@ -10,31 +11,31 @@
|
||||
:placeholder="inputPlaceholder"
|
||||
:readonly="isReadOnly"
|
||||
:format-value="formatValue"
|
||||
@change="value => triggerChange(value)"
|
||||
@change="(value) => triggerChange(value)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { fyo } from 'src/initFyo';
|
||||
import Base from './Base';
|
||||
import DatePicker from '../DatePicker/DatePicker';
|
||||
import Base from './Base';
|
||||
|
||||
export default {
|
||||
name: 'Date',
|
||||
extends: Base,
|
||||
components: {
|
||||
DatePicker
|
||||
DatePicker,
|
||||
},
|
||||
computed: {
|
||||
inputType() {
|
||||
return 'date';
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
formatValue(value) {
|
||||
return fyo.format(value, this.df);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -1,19 +1,19 @@
|
||||
<script>
|
||||
import { h } from 'vue';
|
||||
import AttachImage from './AttachImage';
|
||||
import AutoComplete from './AutoComplete';
|
||||
import Check from './Check';
|
||||
import Color from './Color';
|
||||
import Currency from './Currency';
|
||||
import Data from './Data';
|
||||
import Date from './Date';
|
||||
import DynamicLink from './DynamicLink';
|
||||
import Float from './Float';
|
||||
import Int from './Int';
|
||||
import Link from './Link';
|
||||
import Select from './Select';
|
||||
import Table from './Table';
|
||||
import Text from './Text';
|
||||
import AttachImage from './AttachImage.vue';
|
||||
import AutoComplete from './AutoComplete.vue';
|
||||
import Check from './Check.vue';
|
||||
import Color from './Color.vue';
|
||||
import Currency from './Currency.vue';
|
||||
import Data from './Data.vue';
|
||||
import Date from './Date.vue';
|
||||
import DynamicLink from './DynamicLink.vue';
|
||||
import Float from './Float.vue';
|
||||
import Int from './Int.vue';
|
||||
import Link from './Link.vue';
|
||||
import Select from './Select.vue';
|
||||
import Table from './Table.vue';
|
||||
import Text from './Text.vue';
|
||||
|
||||
export default {
|
||||
name: 'FormControl',
|
||||
|
@ -2,7 +2,7 @@
|
||||
import { t } from 'fyo';
|
||||
import Badge from 'src/components/Badge';
|
||||
import { fyo } from 'src/initFyo';
|
||||
import { openQuickEdit } from 'src/utils';
|
||||
import { openQuickEdit } from 'src/utils/ui';
|
||||
import { markRaw } from 'vue';
|
||||
import AutoComplete from './AutoComplete';
|
||||
|
||||
|
@ -109,7 +109,7 @@
|
||||
togglePopover();
|
||||
"
|
||||
>
|
||||
Clear
|
||||
{{ t`Clear` }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -73,8 +73,8 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Popover from './Popover';
|
||||
import uniq from 'lodash/uniq';
|
||||
import Popover from './Popover.vue';
|
||||
|
||||
export default {
|
||||
name: 'Dropdown',
|
||||
|
@ -45,7 +45,7 @@ import { t } from 'fyo';
|
||||
import reports from 'reports/view';
|
||||
import Dropdown from 'src/components/Dropdown';
|
||||
import { fyo } from 'src/initFyo';
|
||||
import { routeTo } from 'src/utils';
|
||||
import { routeTo } from 'src/utils/ui';
|
||||
|
||||
|
||||
export default {
|
||||
|
@ -36,6 +36,7 @@
|
||||
</template>
|
||||
<script>
|
||||
import { getColorClass } from 'src/utils/colors';
|
||||
import FeatherIcon from './FeatherIcon.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
@ -170,17 +170,16 @@ export default {
|
||||
return evaluateReadOnly(df, this.doc);
|
||||
},
|
||||
onChange(df, value) {
|
||||
if (value == null || df.inline) {
|
||||
if (df.inline) {
|
||||
return;
|
||||
}
|
||||
|
||||
let oldValue = this.doc.get(df.fieldname);
|
||||
|
||||
this.errors[df.fieldname] = null;
|
||||
if (oldValue === value) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.errors[df.fieldname] = null;
|
||||
if (this.emitChange) {
|
||||
this.$emit('change', df, value, oldValue);
|
||||
}
|
||||
|
@ -31,7 +31,7 @@
|
||||
|
||||
<script>
|
||||
import { ipcRenderer } from 'electron';
|
||||
import { runWindowAction } from 'src/utils';
|
||||
import { runWindowAction } from 'src/utils/ipcCalls';
|
||||
import { IPC_MESSAGES } from 'utils/messages';
|
||||
|
||||
export default {
|
||||
|
@ -1,5 +1,8 @@
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
class="py-10 flex-1 bg-white flex justify-center items-center window-drag"
|
||||
>
|
||||
<!-- 0: Language Selection Slide -->
|
||||
<Slide
|
||||
@primary-clicked="handlePrimary"
|
||||
@secondary-clicked="handleSecondary"
|
||||
@ -26,6 +29,8 @@
|
||||
{{ t`Next` }}
|
||||
</template>
|
||||
</Slide>
|
||||
|
||||
<!-- 1: Setup Wizard Slide -->
|
||||
<Slide
|
||||
:primary-disabled="!valuesFilled || loading"
|
||||
@primary-clicked="handlePrimary"
|
||||
@ -35,51 +40,48 @@
|
||||
<template #title>
|
||||
{{ t`Setup your organization` }}
|
||||
</template>
|
||||
|
||||
<template #content>
|
||||
<div v-if="doc">
|
||||
<div
|
||||
class="flex items-center px-6 py-5 mb-4 border bg-brand rounded-xl"
|
||||
>
|
||||
<div class="flex items-center px-6 py-5 mb-8 bg-brand rounded-xl">
|
||||
<FormControl
|
||||
:df="meta.getField('companyLogo')"
|
||||
:df="getField('companyLogo')"
|
||||
:value="doc.companyLogo"
|
||||
@change="(value) => setValue('companyLogo', value)"
|
||||
/>
|
||||
<div class="ml-2">
|
||||
<FormControl
|
||||
ref="companyField"
|
||||
:df="meta.getField('companyName')"
|
||||
:df="getField('companyName')"
|
||||
:value="doc.companyName"
|
||||
@change="(value) => setValue('companyName', value)"
|
||||
:input-class="
|
||||
(classes) => [
|
||||
() => [
|
||||
'bg-transparent font-semibold text-xl text-white placeholder-blue-200 focus:outline-none focus:bg-blue-600 px-3 rounded py-1',
|
||||
]
|
||||
"
|
||||
:autofocus="true"
|
||||
/>
|
||||
<Popover placement="auto" :show-popup="Boolean(emailError)">
|
||||
<template #target>
|
||||
<FormControl
|
||||
:df="meta.getField('email')"
|
||||
:value="doc.email"
|
||||
@change="(value) => setValue('email', value)"
|
||||
:input-class="
|
||||
(classes) => [
|
||||
'text-base bg-transparent text-white placeholder-blue-200 focus:bg-blue-600 focus:outline-none rounded px-3 py-1',
|
||||
]
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
<template #content>
|
||||
<div class="p-2 text-sm">
|
||||
{{ emailError }}
|
||||
</div>
|
||||
</template>
|
||||
</Popover>
|
||||
<FormControl
|
||||
:df="getField('email')"
|
||||
:value="doc.email"
|
||||
@change="(value) => setValue('email', value)"
|
||||
:input-class="
|
||||
() => [
|
||||
'text-base bg-transparent text-white placeholder-blue-200 focus:bg-blue-600 focus:outline-none rounded px-3 py-1',
|
||||
]
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<TwoColumnForm :fields="fields" :doc="doc" />
|
||||
<p
|
||||
class="px-3 -mt-6 text-sm absolute text-red-400 w-full"
|
||||
v-if="emailError"
|
||||
>
|
||||
{{ emailError }}
|
||||
</p>
|
||||
|
||||
<TwoColumnForm :doc="doc" />
|
||||
</div>
|
||||
</template>
|
||||
<template #secondaryButton>{{ t`Back` }}</template>
|
||||
@ -90,22 +92,14 @@
|
||||
|
||||
<script>
|
||||
import { ipcRenderer } from 'electron';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import FormControl from 'src/components/Controls/FormControl';
|
||||
import FormControl from 'src/components/Controls/FormControl.vue';
|
||||
import LanguageSelector from 'src/components/Controls/LanguageSelector.vue';
|
||||
import Popover from 'src/components/Popover';
|
||||
import TwoColumnForm from 'src/components/TwoColumnForm';
|
||||
import { getErrorMessage } from 'src/errorHandling';
|
||||
import { fyo } from 'src/initFyo';
|
||||
import { connectToLocalDatabase, purgeCache } from 'src/initialization';
|
||||
import { setupInstance } from 'src/setup/setupInstance';
|
||||
import { setLanguageMap, showMessageDialog } from 'src/utils';
|
||||
import { getSetupWizardDoc } from 'src/utils/misc';
|
||||
import { showMessageDialog } from 'src/utils/ui';
|
||||
import { IPC_MESSAGES } from 'utils/messages';
|
||||
import {
|
||||
getErrorMessage,
|
||||
handleErrorWithDialog,
|
||||
showErrorDialog
|
||||
} from '../../errorHandling';
|
||||
import Slide from './Slide.vue';
|
||||
|
||||
export default {
|
||||
@ -122,14 +116,13 @@ export default {
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
doctype: 'SetupWizard',
|
||||
schemaName: 'SetupWizard',
|
||||
name: 'SetupWizard',
|
||||
};
|
||||
},
|
||||
components: {
|
||||
TwoColumnForm,
|
||||
FormControl,
|
||||
Popover,
|
||||
Slide,
|
||||
LanguageSelector,
|
||||
},
|
||||
@ -138,12 +131,15 @@ export default {
|
||||
this.index = 1;
|
||||
}
|
||||
|
||||
this.doc = await fyo.doc.getNewDoc('SetupWizard');
|
||||
this.doc = await getSetupWizardDoc();
|
||||
this.doc.on('change', () => {
|
||||
this.valuesFilled = this.allValuesFilled();
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
getField(fieldname) {
|
||||
return this.doc.schema?.fields.find((f) => f.fieldname === fieldname);
|
||||
},
|
||||
openContributingTranslations() {
|
||||
ipcRenderer.send(
|
||||
IPC_MESSAGES.OPEN_EXTERNAL,
|
||||
@ -164,10 +160,6 @@ export default {
|
||||
this.$emit('setup-canceled');
|
||||
}
|
||||
},
|
||||
async selectLanguage(value) {
|
||||
const success = await setLanguageMap(value);
|
||||
this.setValue('language', value);
|
||||
},
|
||||
setValue(fieldname, value) {
|
||||
this.emailError = null;
|
||||
this.doc.set(fieldname, value).catch((e) => {
|
||||
@ -178,7 +170,7 @@ export default {
|
||||
});
|
||||
},
|
||||
allValuesFilled() {
|
||||
let values = this.meta.quickEditFields.map(
|
||||
const values = this.doc.schema.quickEditFields.map(
|
||||
(fieldname) => this.doc[fieldname]
|
||||
);
|
||||
return values.every(Boolean);
|
||||
@ -189,61 +181,18 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.loading = true;
|
||||
await setupInstance(this.doc);
|
||||
this.$emit('setup-complete');
|
||||
} catch (e) {
|
||||
this.loading = false;
|
||||
if (e.type === fyo.errors.DuplicateEntryError) {
|
||||
console.log(e);
|
||||
console.log('retrying');
|
||||
await this.renameDbFileAndRerunSetup();
|
||||
} else {
|
||||
handleErrorWithDialog(e, this.doc);
|
||||
}
|
||||
}
|
||||
},
|
||||
async renameDbFileAndRerunSetup() {
|
||||
const filePath = fyo.config.get('lastSelectedFilePath');
|
||||
renameDbFile(filePath);
|
||||
|
||||
await purgeCache();
|
||||
|
||||
const { connectionSuccess, reason } = await connectToLocalDatabase(
|
||||
filePath
|
||||
);
|
||||
|
||||
if (connectionSuccess) {
|
||||
await setupInstance(this.doc);
|
||||
this.$emit('setup-complete');
|
||||
} else {
|
||||
const title = this.t`DB Connection Error`;
|
||||
const content = `reason: ${reason}, filePath: ${filePath}`;
|
||||
await showErrorDialog(title, content);
|
||||
}
|
||||
this.loading = true;
|
||||
this.$emit('setup-complete', this.doc.getValidDict());
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
meta() {
|
||||
return fyo.getMeta('SetupWizard');
|
||||
},
|
||||
fields() {
|
||||
return this.meta.getQuickEditFields();
|
||||
},
|
||||
buttonText() {
|
||||
return this.loading ? this.t`Setting Up...` : this.t`Submit`;
|
||||
if (this.loading) {
|
||||
return this.t`Submit`;
|
||||
}
|
||||
|
||||
return this.t`Setting Up...`;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
function renameDbFile(filePath) {
|
||||
const dirname = path.dirname(filePath);
|
||||
const basename = path.basename(filePath);
|
||||
const backupPath = path.join(dirname, `_${basename}`);
|
||||
if (fs.existsSync(backupPath)) {
|
||||
fs.unlinkSync(backupPath);
|
||||
}
|
||||
fs.renameSync(filePath, backupPath);
|
||||
}
|
||||
</script>
|
||||
|
@ -7,7 +7,7 @@ import Badge from './components/Badge.vue';
|
||||
import FeatherIcon from './components/FeatherIcon.vue';
|
||||
import { getErrorHandled, handleError } from './errorHandling';
|
||||
import { fyo } from './initFyo';
|
||||
import { incrementOpenCount, outsideClickDirective } from './renderer/helpers';
|
||||
import { outsideClickDirective } from './renderer/helpers';
|
||||
import registerIpcRendererListeners from './renderer/registerIpcRendererListeners';
|
||||
import router from './router';
|
||||
import { stringifyCircular } from './utils';
|
||||
@ -33,7 +33,7 @@ import { setLanguageMap } from './utils/language';
|
||||
|
||||
app.use(router);
|
||||
app.component('App', App);
|
||||
app.component('feather-icon', FeatherIcon);
|
||||
app.component('FeatherIcon', FeatherIcon);
|
||||
app.component('Badge', Badge);
|
||||
|
||||
app.directive('on-outside-click', outsideClickDirective);
|
||||
@ -62,8 +62,6 @@ import { setLanguageMap } from './utils/language';
|
||||
});
|
||||
|
||||
fyo.store.appVersion = await ipcRenderer.invoke(IPC_ACTIONS.GET_VERSION);
|
||||
|
||||
incrementOpenCount();
|
||||
app.mount('body');
|
||||
})();
|
||||
|
||||
|
@ -1,5 +1,3 @@
|
||||
import { ConfigKeys } from 'fyo/core/types';
|
||||
import { fyo } from 'src/initFyo';
|
||||
import { Directive } from 'vue';
|
||||
|
||||
const instances: OutsideClickCallback[] = [];
|
||||
@ -35,14 +33,3 @@ function onDocumentClick(e: Event, el: HTMLElement, fn: OutsideClickCallback) {
|
||||
fn(e);
|
||||
}
|
||||
}
|
||||
|
||||
export function incrementOpenCount() {
|
||||
let openCount = fyo.config.get(ConfigKeys.OpenCount);
|
||||
if (typeof openCount !== 'number') {
|
||||
openCount = 1;
|
||||
} else {
|
||||
openCount += 1;
|
||||
}
|
||||
|
||||
fyo.config.set(ConfigKeys.OpenCount, openCount);
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { ipcRenderer } from 'electron';
|
||||
import { handleError } from 'src/errorHandling';
|
||||
import { fyo } from 'src/initFyo';
|
||||
import { startTelemetry } from 'src/utils/misc';
|
||||
import { showToast } from 'src/utils/ui';
|
||||
import { IPC_CHANNELS, IPC_MESSAGES } from 'utils/messages';
|
||||
|
||||
@ -56,7 +57,7 @@ export default function registerIpcRendererListeners() {
|
||||
document.addEventListener('visibilitychange', function () {
|
||||
const { visibilityState } = document;
|
||||
if (visibilityState === 'visible' && !fyo.telemetry.started) {
|
||||
fyo.telemetry.start();
|
||||
startTelemetry();
|
||||
}
|
||||
|
||||
if (visibilityState !== 'hidden') {
|
||||
|
@ -1,4 +1,3 @@
|
||||
import countryInfo from 'fixtures/countryInfo.json';
|
||||
import { ConfigFile, DocValueMap } from 'fyo/core/types';
|
||||
import Doc from 'fyo/model/doc';
|
||||
import { createNumberSeries } from 'fyo/model/naming';
|
||||
@ -9,16 +8,21 @@ import {
|
||||
DEFAULT_SERIES_START,
|
||||
} from 'fyo/utils/consts';
|
||||
import { AccountingSettings } from 'models/baseModels/AccountingSettings/AccountingSettings';
|
||||
import { fyo } from 'src/initFyo';
|
||||
import { fyo, initializeInstance } from 'src/initFyo';
|
||||
import { createRegionalRecords } from 'src/regional';
|
||||
import { getCountryCodeFromCountry, getCountryInfo } from 'utils/misc';
|
||||
import { CountryInfo } from 'utils/types';
|
||||
import { createCOA } from './createCOA';
|
||||
import { CountrySettings, SetupWizardOptions } from './types';
|
||||
import { SetupWizardOptions } from './types';
|
||||
|
||||
export default async function setupInstance(
|
||||
dbPath: string,
|
||||
setupWizardOptions: SetupWizardOptions
|
||||
) {
|
||||
const { companyName, country, bankName, chartOfAccounts } =
|
||||
setupWizardOptions;
|
||||
|
||||
await initializeDatabase(dbPath, country);
|
||||
await updateSystemSettings(setupWizardOptions);
|
||||
await updateAccountingSettings(setupWizardOptions);
|
||||
await updatePrintSettings(setupWizardOptions);
|
||||
@ -31,10 +35,15 @@ export default async function setupInstance(
|
||||
await completeSetup(companyName);
|
||||
}
|
||||
|
||||
async function initializeDatabase(dbPath: string, country: string) {
|
||||
const countryCode = getCountryCodeFromCountry(country);
|
||||
await initializeInstance(dbPath, true, countryCode);
|
||||
}
|
||||
|
||||
async function updateAccountingSettings({
|
||||
companyName,
|
||||
country,
|
||||
name,
|
||||
fullname,
|
||||
email,
|
||||
bankName,
|
||||
fiscalYearStart,
|
||||
@ -46,7 +55,7 @@ async function updateAccountingSettings({
|
||||
await accountingSettings.setAndUpdate({
|
||||
companyName,
|
||||
country,
|
||||
fullname: name,
|
||||
fullname,
|
||||
email,
|
||||
bankName,
|
||||
fiscalYearStart,
|
||||
@ -73,8 +82,8 @@ async function updateSystemSettings({
|
||||
country,
|
||||
currency: companyCurrency,
|
||||
}: SetupWizardOptions) {
|
||||
// @ts-ignore
|
||||
const countryOptions = countryInfo[country] as CountrySettings;
|
||||
const countryInfo = getCountryInfo();
|
||||
const countryOptions = countryInfo[country] as CountryInfo;
|
||||
const currency =
|
||||
companyCurrency ?? countryOptions.currency ?? DEFAULT_CURRENCY;
|
||||
const locale = countryOptions.locale ?? DEFAULT_LOCALE;
|
||||
@ -88,10 +97,7 @@ async function updateSystemSettings({
|
||||
async function createCurrencyRecords() {
|
||||
const promises: Promise<Doc | undefined>[] = [];
|
||||
const queue: string[] = [];
|
||||
const countrySettings: CountrySettings[] = Object.values(
|
||||
// @ts-ignore
|
||||
countryInfo as Record<string, CountrySettings>
|
||||
);
|
||||
const countrySettings = Object.values(getCountryInfo()) as CountryInfo[];
|
||||
|
||||
for (const country of countrySettings) {
|
||||
const {
|
||||
|
@ -3,7 +3,6 @@ export interface SetupWizardOptions {
|
||||
companyName: string;
|
||||
country: string;
|
||||
fullname: string;
|
||||
name: string;
|
||||
email: string;
|
||||
bankName: string;
|
||||
currency: string;
|
||||
@ -11,15 +10,3 @@ export interface SetupWizardOptions {
|
||||
fiscalYearEnd: string;
|
||||
chartOfAccounts: string;
|
||||
}
|
||||
|
||||
export interface CountrySettings {
|
||||
code: string;
|
||||
currency: string;
|
||||
fiscal_year_start: string;
|
||||
fiscal_year_end: string;
|
||||
locale: string;
|
||||
currency_fraction?: string;
|
||||
currency_fraction_units?: number;
|
||||
smallest_currency_fraction_value?: number;
|
||||
currency_symbol?: string;
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
import { ConfigKeys } from 'fyo/core/types';
|
||||
import { getSingleValue } from 'fyo/utils';
|
||||
import { DateTime } from 'luxon';
|
||||
import { SetupWizard } from 'models/baseModels/SetupWizard/SetupWizard';
|
||||
@ -51,3 +52,31 @@ export async function getSetupComplete(): Promise<boolean> {
|
||||
fyo
|
||||
));
|
||||
}
|
||||
|
||||
export function incrementOpenCount() {
|
||||
let openCount = fyo.config.get(ConfigKeys.OpenCount);
|
||||
if (typeof openCount !== 'number') {
|
||||
openCount = 1;
|
||||
} else {
|
||||
openCount += 1;
|
||||
}
|
||||
|
||||
fyo.config.set(ConfigKeys.OpenCount, openCount);
|
||||
}
|
||||
|
||||
export async function startTelemetry() {
|
||||
fyo.telemetry.interestingDocs = [
|
||||
ModelNameEnum.Payment,
|
||||
ModelNameEnum.PaymentFor,
|
||||
ModelNameEnum.SalesInvoice,
|
||||
ModelNameEnum.SalesInvoiceItem,
|
||||
ModelNameEnum.PurchaseInvoice,
|
||||
ModelNameEnum.PurchaseInvoiceItem,
|
||||
ModelNameEnum.JournalEntry,
|
||||
ModelNameEnum.JournalEntryAccount,
|
||||
ModelNameEnum.Party,
|
||||
ModelNameEnum.Account,
|
||||
ModelNameEnum.Tax,
|
||||
];
|
||||
await fyo.telemetry.start();
|
||||
}
|
||||
|
17
utils/misc.ts
Normal file
17
utils/misc.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import countryInfo from 'fixtures/countryInfo.json';
|
||||
import { CountryInfoMap } from './types';
|
||||
|
||||
export function getCountryInfo(): CountryInfoMap {
|
||||
// @ts-ignore
|
||||
return countryInfo as CountryInfoMap;
|
||||
}
|
||||
|
||||
export function getCountryCodeFromCountry(countryName: string): string {
|
||||
const countryInfoMap = getCountryInfo();
|
||||
const countryInfo = countryInfoMap[countryName];
|
||||
if (countryInfo === undefined) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return countryInfo.code;
|
||||
}
|
@ -1,2 +1,16 @@
|
||||
export type Translation = { translation: string; context?: string };
|
||||
export type LanguageMap = Record<string, Translation>;
|
||||
|
||||
export type CountryInfoMap = Record<string, CountryInfo | undefined>;
|
||||
export interface CountryInfo {
|
||||
code: string;
|
||||
currency: string;
|
||||
currency_fraction?: string;
|
||||
currency_fraction_units?: number;
|
||||
smallest_currency_fraction_value?: number;
|
||||
currency_symbol?: string;
|
||||
timezones?: string[];
|
||||
fiscal_year_start: string;
|
||||
fiscal_year_end: string;
|
||||
locale: string;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user