2022-04-24 12:18:44 +05:30
|
|
|
import { Fyo } from 'fyo';
|
2022-07-20 14:14:36 +05:30
|
|
|
import { DocValueMap } from 'fyo/core/types';
|
2022-04-24 12:18:44 +05:30
|
|
|
import { Doc } from 'fyo/model/doc';
|
2022-04-22 16:32:03 +05:30
|
|
|
import { createNumberSeries } from 'fyo/model/naming';
|
|
|
|
import {
|
|
|
|
DEFAULT_CURRENCY,
|
|
|
|
DEFAULT_LOCALE,
|
2022-11-18 14:14:29 +05:30
|
|
|
DEFAULT_SERIES_START,
|
2022-04-22 16:32:03 +05:30
|
|
|
} from 'fyo/utils/consts';
|
2022-11-18 14:14:29 +05:30
|
|
|
import {
|
|
|
|
AccountRootTypeEnum,
|
|
|
|
AccountTypeEnum,
|
|
|
|
} from 'models/baseModels/Account/types';
|
2022-04-20 16:11:27 +05:30
|
|
|
import { AccountingSettings } from 'models/baseModels/AccountingSettings/AccountingSettings';
|
2022-10-12 14:59:43 +05:30
|
|
|
import { numberSeriesDefaultsMap } from 'models/baseModels/Defaults/Defaults';
|
2022-11-18 14:14:29 +05:30
|
|
|
import { InventorySettings } from 'models/inventory/InventorySettings';
|
2022-11-18 23:01:50 +05:30
|
|
|
import { ValuationMethod } from 'models/inventory/types';
|
2022-05-23 13:39:07 +05:30
|
|
|
import { ModelNameEnum } from 'models/types';
|
2022-04-20 16:11:27 +05:30
|
|
|
import { createRegionalRecords } from 'src/regional';
|
2022-11-18 14:14:29 +05:30
|
|
|
import {
|
|
|
|
initializeInstance,
|
|
|
|
setCurrencySymbols,
|
|
|
|
} from 'src/utils/initialization';
|
2022-05-23 13:39:07 +05:30
|
|
|
import { getRandomString } from 'utils';
|
2022-11-21 12:45:57 +05:30
|
|
|
import { getDefaultLocations, getDefaultUOMs } from 'utils/defaults';
|
2022-04-23 14:53:44 +05:30
|
|
|
import { getCountryCodeFromCountry, getCountryInfo } from 'utils/misc';
|
|
|
|
import { CountryInfo } from 'utils/types';
|
2022-04-24 12:18:44 +05:30
|
|
|
import { CreateCOA } from './createCOA';
|
2022-04-23 14:53:44 +05:30
|
|
|
import { SetupWizardOptions } from './types';
|
2022-04-20 16:11:27 +05:30
|
|
|
|
|
|
|
export default async function setupInstance(
|
2022-04-23 14:53:44 +05:30
|
|
|
dbPath: string,
|
2022-04-24 12:18:44 +05:30
|
|
|
setupWizardOptions: SetupWizardOptions,
|
|
|
|
fyo: Fyo
|
2022-04-20 16:11:27 +05:30
|
|
|
) {
|
|
|
|
const { companyName, country, bankName, chartOfAccounts } =
|
|
|
|
setupWizardOptions;
|
2022-04-23 14:53:44 +05:30
|
|
|
|
2022-07-18 16:43:56 +05:30
|
|
|
fyo.store.skipTelemetryLogging = true;
|
2022-04-24 12:18:44 +05:30
|
|
|
await initializeDatabase(dbPath, country, fyo);
|
|
|
|
await updateSystemSettings(setupWizardOptions, fyo);
|
|
|
|
await updateAccountingSettings(setupWizardOptions, fyo);
|
|
|
|
await updatePrintSettings(setupWizardOptions, fyo);
|
2022-04-20 16:11:27 +05:30
|
|
|
|
2022-04-24 12:18:44 +05:30
|
|
|
await createCurrencyRecords(fyo);
|
|
|
|
await createAccountRecords(bankName, country, chartOfAccounts, fyo);
|
|
|
|
await createRegionalRecords(country, fyo);
|
2022-07-08 23:21:21 +05:30
|
|
|
await createDefaultEntries(fyo);
|
2022-04-24 12:18:44 +05:30
|
|
|
await createDefaultNumberSeries(fyo);
|
2022-11-18 14:14:29 +05:30
|
|
|
await updateInventorySettings(fyo);
|
2023-05-30 10:21:48 +05:30
|
|
|
|
|
|
|
if (fyo.isElectron) {
|
|
|
|
const { updatePrintTemplates } = await import('src/utils/printTemplates');
|
|
|
|
await updatePrintTemplates(fyo);
|
|
|
|
}
|
2022-04-20 16:11:27 +05:30
|
|
|
|
2022-04-24 12:18:44 +05:30
|
|
|
await completeSetup(companyName, fyo);
|
2022-07-28 13:03:25 +05:30
|
|
|
if (!Object.keys(fyo.currencySymbols).length) {
|
|
|
|
await setCurrencySymbols(fyo);
|
|
|
|
}
|
|
|
|
|
2022-07-18 16:43:56 +05:30
|
|
|
fyo.store.skipTelemetryLogging = false;
|
2022-04-20 16:11:27 +05:30
|
|
|
}
|
|
|
|
|
2022-07-08 23:21:21 +05:30
|
|
|
async function createDefaultEntries(fyo: Fyo) {
|
|
|
|
/**
|
|
|
|
* Create default UOM entries
|
|
|
|
*/
|
2022-11-21 12:45:57 +05:30
|
|
|
for (const uom of getDefaultUOMs(fyo)) {
|
2022-07-08 23:21:21 +05:30
|
|
|
await checkAndCreateDoc(ModelNameEnum.UOM, uom, fyo);
|
|
|
|
}
|
2022-11-21 12:45:57 +05:30
|
|
|
|
|
|
|
for (const loc of getDefaultLocations(fyo)) {
|
|
|
|
await checkAndCreateDoc(ModelNameEnum.Location, loc, fyo);
|
|
|
|
}
|
2022-07-08 23:21:21 +05:30
|
|
|
}
|
|
|
|
|
2022-04-24 12:18:44 +05:30
|
|
|
async function initializeDatabase(dbPath: string, country: string, fyo: Fyo) {
|
2022-04-23 14:53:44 +05:30
|
|
|
const countryCode = getCountryCodeFromCountry(country);
|
2022-04-24 12:18:44 +05:30
|
|
|
await initializeInstance(dbPath, true, countryCode, fyo);
|
2022-04-23 14:53:44 +05:30
|
|
|
}
|
|
|
|
|
2022-04-24 12:18:44 +05:30
|
|
|
async function updateAccountingSettings(
|
|
|
|
{
|
|
|
|
companyName,
|
|
|
|
country,
|
|
|
|
fullname,
|
|
|
|
email,
|
|
|
|
bankName,
|
|
|
|
fiscalYearStart,
|
|
|
|
fiscalYearEnd,
|
|
|
|
}: SetupWizardOptions,
|
|
|
|
fyo: Fyo
|
|
|
|
) {
|
2022-05-26 15:41:15 +05:30
|
|
|
const accountingSettings = (await fyo.doc.getDoc(
|
2022-04-20 16:11:27 +05:30
|
|
|
'AccountingSettings'
|
|
|
|
)) as AccountingSettings;
|
2022-04-24 12:18:44 +05:30
|
|
|
await accountingSettings.setAndSync({
|
2022-04-20 16:11:27 +05:30
|
|
|
companyName,
|
|
|
|
country,
|
2022-04-23 14:53:44 +05:30
|
|
|
fullname,
|
2022-04-20 16:11:27 +05:30
|
|
|
email,
|
|
|
|
bankName,
|
|
|
|
fiscalYearStart,
|
|
|
|
fiscalYearEnd,
|
|
|
|
});
|
|
|
|
return accountingSettings;
|
|
|
|
}
|
|
|
|
|
2022-04-24 12:18:44 +05:30
|
|
|
async function updatePrintSettings(
|
2022-04-25 12:03:31 +05:30
|
|
|
{ logo, companyName, email }: SetupWizardOptions,
|
2022-04-24 12:18:44 +05:30
|
|
|
fyo: Fyo
|
|
|
|
) {
|
2022-05-26 15:41:15 +05:30
|
|
|
const printSettings = await fyo.doc.getDoc('PrintSettings');
|
2022-04-24 12:18:44 +05:30
|
|
|
await printSettings.setAndSync({
|
2022-04-25 12:03:31 +05:30
|
|
|
logo,
|
2022-04-20 16:11:27 +05:30
|
|
|
companyName,
|
|
|
|
email,
|
2022-04-25 12:03:31 +05:30
|
|
|
displayLogo: logo ? true : false,
|
2022-04-20 16:11:27 +05:30
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-04-24 12:18:44 +05:30
|
|
|
async function updateSystemSettings(
|
|
|
|
{ country, currency: companyCurrency }: SetupWizardOptions,
|
|
|
|
fyo: Fyo
|
|
|
|
) {
|
2022-04-23 14:53:44 +05:30
|
|
|
const countryInfo = getCountryInfo();
|
|
|
|
const countryOptions = countryInfo[country] as CountryInfo;
|
2022-04-20 16:11:27 +05:30
|
|
|
const currency =
|
|
|
|
companyCurrency ?? countryOptions.currency ?? DEFAULT_CURRENCY;
|
|
|
|
const locale = countryOptions.locale ?? DEFAULT_LOCALE;
|
2022-04-25 12:03:31 +05:30
|
|
|
const countryCode = getCountryCodeFromCountry(country);
|
2022-05-26 15:41:15 +05:30
|
|
|
const systemSettings = await fyo.doc.getDoc('SystemSettings');
|
2022-05-23 13:39:07 +05:30
|
|
|
const instanceId = getRandomString();
|
2022-04-25 12:03:31 +05:30
|
|
|
|
2022-04-24 12:18:44 +05:30
|
|
|
systemSettings.setAndSync({
|
2022-04-20 16:11:27 +05:30
|
|
|
locale,
|
|
|
|
currency,
|
2022-05-23 13:39:07 +05:30
|
|
|
instanceId,
|
2022-04-25 12:03:31 +05:30
|
|
|
countryCode,
|
2022-04-20 16:11:27 +05:30
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-04-24 12:18:44 +05:30
|
|
|
async function createCurrencyRecords(fyo: Fyo) {
|
2022-04-20 16:11:27 +05:30
|
|
|
const promises: Promise<Doc | undefined>[] = [];
|
|
|
|
const queue: string[] = [];
|
2022-04-23 14:53:44 +05:30
|
|
|
const countrySettings = Object.values(getCountryInfo()) as CountryInfo[];
|
2022-04-20 16:11:27 +05:30
|
|
|
|
|
|
|
for (const country of countrySettings) {
|
|
|
|
const {
|
|
|
|
currency,
|
|
|
|
currency_fraction,
|
|
|
|
currency_fraction_units,
|
|
|
|
smallest_currency_fraction_value,
|
|
|
|
currency_symbol,
|
|
|
|
} = country;
|
|
|
|
|
|
|
|
if (!currency || queue.includes(currency)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
const docObject = {
|
|
|
|
name: currency,
|
|
|
|
fraction: currency_fraction ?? '',
|
|
|
|
fractionUnits: currency_fraction_units ?? 100,
|
|
|
|
smallestValue: smallest_currency_fraction_value ?? 0.01,
|
|
|
|
symbol: currency_symbol ?? '',
|
|
|
|
};
|
|
|
|
|
2022-04-24 12:18:44 +05:30
|
|
|
const doc = checkAndCreateDoc('Currency', docObject, fyo);
|
2022-04-20 16:11:27 +05:30
|
|
|
if (doc) {
|
|
|
|
promises.push(doc);
|
|
|
|
queue.push(currency);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return Promise.all(promises);
|
|
|
|
}
|
|
|
|
|
|
|
|
async function createAccountRecords(
|
|
|
|
bankName: string,
|
|
|
|
country: string,
|
2022-04-24 12:18:44 +05:30
|
|
|
chartOfAccounts: string,
|
|
|
|
fyo: Fyo
|
2022-04-20 16:11:27 +05:30
|
|
|
) {
|
2022-04-24 12:18:44 +05:30
|
|
|
const createCOA = new CreateCOA(chartOfAccounts, fyo);
|
|
|
|
await createCOA.run();
|
|
|
|
const parentAccount = await getBankAccountParentName(country, fyo);
|
2022-07-14 18:03:31 +05:30
|
|
|
const bankAccountDoc = {
|
2022-04-20 16:11:27 +05:30
|
|
|
name: bankName,
|
2022-07-14 18:03:31 +05:30
|
|
|
rootType: AccountRootTypeEnum.Asset,
|
2022-04-20 16:11:27 +05:30
|
|
|
parentAccount,
|
|
|
|
accountType: 'Bank',
|
|
|
|
isGroup: false,
|
|
|
|
};
|
2022-07-14 18:03:31 +05:30
|
|
|
|
|
|
|
await checkAndCreateDoc('Account', bankAccountDoc, fyo);
|
|
|
|
await createDiscountAccount(fyo);
|
|
|
|
await setDefaultAccounts(fyo);
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function createDiscountAccount(fyo: Fyo) {
|
|
|
|
const incomeAccountName = fyo.t`Indirect Income`;
|
|
|
|
const accountExists = await fyo.db.exists(
|
|
|
|
ModelNameEnum.Account,
|
|
|
|
incomeAccountName
|
|
|
|
);
|
|
|
|
|
|
|
|
if (!accountExists) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const discountAccountName = fyo.t`Discounts`;
|
|
|
|
const discountAccountDoc = {
|
|
|
|
name: discountAccountName,
|
|
|
|
rootType: AccountRootTypeEnum.Income,
|
|
|
|
parentAccount: incomeAccountName,
|
|
|
|
accountType: 'Income Account',
|
|
|
|
isGroup: false,
|
|
|
|
};
|
|
|
|
|
|
|
|
await checkAndCreateDoc(ModelNameEnum.Account, discountAccountDoc, fyo);
|
|
|
|
await fyo.singles.AccountingSettings!.setAndSync(
|
|
|
|
'discountAccount',
|
|
|
|
discountAccountName
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
async function setDefaultAccounts(fyo: Fyo) {
|
2022-08-10 12:16:59 +05:30
|
|
|
await setDefaultAccount('writeOffAccount', fyo.t`Write Off`, fyo);
|
|
|
|
const isSet = await setDefaultAccount(
|
|
|
|
'roundOffAccount',
|
|
|
|
fyo.t`Rounded Off`,
|
|
|
|
fyo
|
|
|
|
);
|
2022-07-14 18:03:31 +05:30
|
|
|
|
2022-08-10 12:16:59 +05:30
|
|
|
if (!isSet) {
|
|
|
|
await setDefaultAccount('roundOffAccount', fyo.t`Round Off`, fyo);
|
|
|
|
}
|
|
|
|
}
|
2022-07-14 18:03:31 +05:30
|
|
|
|
2022-08-10 12:16:59 +05:30
|
|
|
async function setDefaultAccount(key: string, accountName: string, fyo: Fyo) {
|
|
|
|
const accountExists = await fyo.db.exists(ModelNameEnum.Account, accountName);
|
|
|
|
if (!accountExists) {
|
|
|
|
return false;
|
2022-07-14 18:03:31 +05:30
|
|
|
}
|
2022-08-10 12:16:59 +05:30
|
|
|
|
|
|
|
await fyo.singles.AccountingSettings!.setAndSync(key, accountName);
|
|
|
|
return true;
|
2022-04-20 16:11:27 +05:30
|
|
|
}
|
|
|
|
|
2022-04-24 12:18:44 +05:30
|
|
|
async function completeSetup(companyName: string, fyo: Fyo) {
|
2022-04-25 12:03:31 +05:30
|
|
|
await fyo.singles.AccountingSettings!.setAndSync('setupComplete', true);
|
2022-04-20 16:11:27 +05:30
|
|
|
}
|
|
|
|
|
2022-04-24 12:18:44 +05:30
|
|
|
async function checkAndCreateDoc(
|
|
|
|
schemaName: string,
|
|
|
|
docObject: DocValueMap,
|
|
|
|
fyo: Fyo
|
|
|
|
) {
|
|
|
|
const canCreate = await checkIfExactRecordAbsent(schemaName, docObject, fyo);
|
2022-04-20 16:11:27 +05:30
|
|
|
if (!canCreate) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const doc = await fyo.doc.getNewDoc(schemaName, docObject);
|
2022-04-24 12:18:44 +05:30
|
|
|
return doc.sync();
|
2022-04-20 16:11:27 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
async function checkIfExactRecordAbsent(
|
|
|
|
schemaName: string,
|
2022-04-24 12:18:44 +05:30
|
|
|
docMap: DocValueMap,
|
|
|
|
fyo: Fyo
|
2022-04-20 16:11:27 +05:30
|
|
|
) {
|
|
|
|
const name = docMap.name as string;
|
|
|
|
const newDocObject = Object.assign({}, docMap);
|
|
|
|
|
|
|
|
const rows = await fyo.db.getAllRaw(schemaName, {
|
|
|
|
fields: ['*'],
|
|
|
|
filters: { name },
|
|
|
|
});
|
|
|
|
|
|
|
|
if (rows.length === 0) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
const storedDocObject = rows[0];
|
|
|
|
const matchList = Object.keys(newDocObject).map((key) => {
|
|
|
|
const newValue = newDocObject[key];
|
|
|
|
const storedValue = storedDocObject[key];
|
|
|
|
return newValue == storedValue; // Should not be type sensitive.
|
|
|
|
});
|
|
|
|
|
|
|
|
if (!matchList.every(Boolean)) {
|
|
|
|
await fyo.db.delete(schemaName, name);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-04-24 12:18:44 +05:30
|
|
|
async function getBankAccountParentName(country: string, fyo: Fyo) {
|
2022-04-20 16:11:27 +05:30
|
|
|
const parentBankAccount = await fyo.db.getAllRaw('Account', {
|
|
|
|
fields: ['*'],
|
|
|
|
filters: { isGroup: true, accountType: 'Bank' },
|
|
|
|
});
|
|
|
|
|
|
|
|
if (parentBankAccount.length === 0) {
|
|
|
|
// This should not happen if the fixtures are correct.
|
|
|
|
return 'Bank Accounts';
|
|
|
|
} else if (parentBankAccount.length > 1) {
|
|
|
|
switch (country) {
|
|
|
|
case 'Indonesia':
|
|
|
|
return 'Bank Rupiah - 1121.000';
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return parentBankAccount[0].name;
|
|
|
|
}
|
2022-04-22 16:32:03 +05:30
|
|
|
|
2022-04-24 12:18:44 +05:30
|
|
|
async function createDefaultNumberSeries(fyo: Fyo) {
|
2022-10-12 14:05:18 +05:30
|
|
|
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];
|
2022-10-12 14:59:43 +05:30
|
|
|
if (!defaultKey || fyo.singles.Defaults?.[defaultKey]) {
|
2022-10-12 14:05:18 +05:30
|
|
|
continue;
|
|
|
|
}
|
2022-10-12 14:59:43 +05:30
|
|
|
|
|
|
|
await fyo.singles.Defaults?.setAndSync(defaultKey as string, defaultValue);
|
2022-10-12 14:05:18 +05:30
|
|
|
}
|
2022-04-22 16:32:03 +05:30
|
|
|
}
|
2022-11-18 14:14:29 +05:30
|
|
|
|
|
|
|
async function updateInventorySettings(fyo: Fyo) {
|
|
|
|
const inventorySettings = (await fyo.doc.getDoc(
|
|
|
|
ModelNameEnum.InventorySettings
|
|
|
|
)) as InventorySettings;
|
|
|
|
|
|
|
|
if (!inventorySettings.valuationMethod) {
|
2022-11-18 23:01:50 +05:30
|
|
|
await inventorySettings.set('valuationMethod', ValuationMethod.FIFO);
|
2022-11-18 14:14:29 +05:30
|
|
|
}
|
2022-11-18 23:01:50 +05:30
|
|
|
const accountTypeDefaultMap = {
|
|
|
|
[AccountTypeEnum.Stock]: 'stockInHand',
|
|
|
|
[AccountTypeEnum['Stock Received But Not Billed']]:
|
|
|
|
'stockReceivedButNotBilled',
|
|
|
|
[AccountTypeEnum['Cost of Goods Sold']]: 'costOfGoodsSold',
|
|
|
|
} as Record<string, string>;
|
|
|
|
|
|
|
|
for (const accountType in accountTypeDefaultMap) {
|
|
|
|
const accounts = (await fyo.db.getAllRaw('Account', {
|
|
|
|
filters: { accountType, isGroup: false },
|
|
|
|
})) as { name: string }[];
|
|
|
|
|
|
|
|
if (!accounts.length) {
|
|
|
|
continue;
|
|
|
|
}
|
2022-11-18 14:14:29 +05:30
|
|
|
|
2022-11-18 23:01:50 +05:30
|
|
|
const settingName = accountTypeDefaultMap[accountType]!;
|
|
|
|
inventorySettings.set(settingName, accounts[0].name);
|
2022-11-18 14:14:29 +05:30
|
|
|
}
|
|
|
|
|
2022-11-21 12:45:57 +05:30
|
|
|
const location = fyo.t`Stores`;
|
|
|
|
if (await fyo.db.exists(ModelNameEnum.Location, location)) {
|
|
|
|
inventorySettings.set('defaultLocation', location);
|
|
|
|
}
|
|
|
|
|
2022-11-18 14:14:29 +05:30
|
|
|
await inventorySettings.sync();
|
|
|
|
}
|