2
0
mirror of https://github.com/frappe/books.git synced 2025-02-04 04:58:30 +00:00
books/src/setup/setupInstance.ts
18alantom 497d2bbd34 fix: test, run updatetemplates only if electron
- fix prop of null not found error
2023-05-30 05:04:08 -07:00

386 lines
10 KiB
TypeScript

import { Fyo } from 'fyo';
import { DocValueMap } from 'fyo/core/types';
import { Doc } from 'fyo/model/doc';
import { createNumberSeries } from 'fyo/model/naming';
import {
DEFAULT_CURRENCY,
DEFAULT_LOCALE,
DEFAULT_SERIES_START,
} from 'fyo/utils/consts';
import {
AccountRootTypeEnum,
AccountTypeEnum,
} from 'models/baseModels/Account/types';
import { AccountingSettings } from 'models/baseModels/AccountingSettings/AccountingSettings';
import { numberSeriesDefaultsMap } from 'models/baseModels/Defaults/Defaults';
import { InventorySettings } from 'models/inventory/InventorySettings';
import { ValuationMethod } from 'models/inventory/types';
import { ModelNameEnum } from 'models/types';
import { createRegionalRecords } from 'src/regional';
import {
initializeInstance,
setCurrencySymbols,
} from 'src/utils/initialization';
import { getRandomString } from 'utils';
import { getDefaultLocations, getDefaultUOMs } from 'utils/defaults';
import { getCountryCodeFromCountry, getCountryInfo } from 'utils/misc';
import { CountryInfo } from 'utils/types';
import { CreateCOA } from './createCOA';
import { SetupWizardOptions } from './types';
export default async function setupInstance(
dbPath: string,
setupWizardOptions: SetupWizardOptions,
fyo: Fyo
) {
const { companyName, country, bankName, chartOfAccounts } =
setupWizardOptions;
fyo.store.skipTelemetryLogging = true;
await initializeDatabase(dbPath, country, fyo);
await updateSystemSettings(setupWizardOptions, fyo);
await updateAccountingSettings(setupWizardOptions, fyo);
await updatePrintSettings(setupWizardOptions, fyo);
await createCurrencyRecords(fyo);
await createAccountRecords(bankName, country, chartOfAccounts, fyo);
await createRegionalRecords(country, fyo);
await createDefaultEntries(fyo);
await createDefaultNumberSeries(fyo);
await updateInventorySettings(fyo);
if (fyo.isElectron) {
const { updatePrintTemplates } = await import('src/utils/printTemplates');
await updatePrintTemplates(fyo);
}
await completeSetup(companyName, fyo);
if (!Object.keys(fyo.currencySymbols).length) {
await setCurrencySymbols(fyo);
}
fyo.store.skipTelemetryLogging = false;
}
async function createDefaultEntries(fyo: Fyo) {
/**
* Create default UOM entries
*/
for (const uom of getDefaultUOMs(fyo)) {
await checkAndCreateDoc(ModelNameEnum.UOM, uom, fyo);
}
for (const loc of getDefaultLocations(fyo)) {
await checkAndCreateDoc(ModelNameEnum.Location, loc, fyo);
}
}
async function initializeDatabase(dbPath: string, country: string, fyo: Fyo) {
const countryCode = getCountryCodeFromCountry(country);
await initializeInstance(dbPath, true, countryCode, fyo);
}
async function updateAccountingSettings(
{
companyName,
country,
fullname,
email,
bankName,
fiscalYearStart,
fiscalYearEnd,
}: SetupWizardOptions,
fyo: Fyo
) {
const accountingSettings = (await fyo.doc.getDoc(
'AccountingSettings'
)) as AccountingSettings;
await accountingSettings.setAndSync({
companyName,
country,
fullname,
email,
bankName,
fiscalYearStart,
fiscalYearEnd,
});
return accountingSettings;
}
async function updatePrintSettings(
{ logo, companyName, email }: SetupWizardOptions,
fyo: Fyo
) {
const printSettings = await fyo.doc.getDoc('PrintSettings');
await printSettings.setAndSync({
logo,
companyName,
email,
displayLogo: logo ? true : false,
});
}
async function updateSystemSettings(
{ country, currency: companyCurrency }: SetupWizardOptions,
fyo: Fyo
) {
const countryInfo = getCountryInfo();
const countryOptions = countryInfo[country] as CountryInfo;
const currency =
companyCurrency ?? countryOptions.currency ?? DEFAULT_CURRENCY;
const locale = countryOptions.locale ?? DEFAULT_LOCALE;
const countryCode = getCountryCodeFromCountry(country);
const systemSettings = await fyo.doc.getDoc('SystemSettings');
const instanceId = getRandomString();
systemSettings.setAndSync({
locale,
currency,
instanceId,
countryCode,
});
}
async function createCurrencyRecords(fyo: Fyo) {
const promises: Promise<Doc | undefined>[] = [];
const queue: string[] = [];
const countrySettings = Object.values(getCountryInfo()) as CountryInfo[];
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 ?? '',
};
const doc = checkAndCreateDoc('Currency', docObject, fyo);
if (doc) {
promises.push(doc);
queue.push(currency);
}
}
return Promise.all(promises);
}
async function createAccountRecords(
bankName: string,
country: string,
chartOfAccounts: string,
fyo: Fyo
) {
const createCOA = new CreateCOA(chartOfAccounts, fyo);
await createCOA.run();
const parentAccount = await getBankAccountParentName(country, fyo);
const bankAccountDoc = {
name: bankName,
rootType: AccountRootTypeEnum.Asset,
parentAccount,
accountType: 'Bank',
isGroup: false,
};
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) {
await setDefaultAccount('writeOffAccount', fyo.t`Write Off`, fyo);
const isSet = await setDefaultAccount(
'roundOffAccount',
fyo.t`Rounded Off`,
fyo
);
if (!isSet) {
await setDefaultAccount('roundOffAccount', fyo.t`Round Off`, fyo);
}
}
async function setDefaultAccount(key: string, accountName: string, fyo: Fyo) {
const accountExists = await fyo.db.exists(ModelNameEnum.Account, accountName);
if (!accountExists) {
return false;
}
await fyo.singles.AccountingSettings!.setAndSync(key, accountName);
return true;
}
async function completeSetup(companyName: string, fyo: Fyo) {
await fyo.singles.AccountingSettings!.setAndSync('setupComplete', true);
}
async function checkAndCreateDoc(
schemaName: string,
docObject: DocValueMap,
fyo: Fyo
) {
const canCreate = await checkIfExactRecordAbsent(schemaName, docObject, fyo);
if (!canCreate) {
return;
}
const doc = await fyo.doc.getNewDoc(schemaName, docObject);
return doc.sync();
}
async function checkIfExactRecordAbsent(
schemaName: string,
docMap: DocValueMap,
fyo: Fyo
) {
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;
}
async function getBankAccountParentName(country: string, fyo: Fyo) {
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;
}
async function createDefaultNumberSeries(fyo: Fyo) {
const numberSeriesFields = Object.values(fyo.schemaMap)
.map((f) => f?.fields)
.flat()
.filter((f) => f?.fieldname === 'numberSeries');
for (const field of numberSeriesFields) {
const defaultValue = field?.default as string | undefined;
const schemaName = field?.schemaName;
if (!defaultValue || !schemaName) {
continue;
}
await createNumberSeries(
defaultValue,
schemaName,
DEFAULT_SERIES_START,
fyo
);
const defaultKey = numberSeriesDefaultsMap[schemaName];
if (!defaultKey || fyo.singles.Defaults?.[defaultKey]) {
continue;
}
await fyo.singles.Defaults?.setAndSync(defaultKey as string, defaultValue);
}
}
async function updateInventorySettings(fyo: Fyo) {
const inventorySettings = (await fyo.doc.getDoc(
ModelNameEnum.InventorySettings
)) as InventorySettings;
if (!inventorySettings.valuationMethod) {
await inventorySettings.set('valuationMethod', ValuationMethod.FIFO);
}
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;
}
const settingName = accountTypeDefaultMap[accountType]!;
inventorySettings.set(settingName, accounts[0].name);
}
const location = fyo.t`Stores`;
if (await fyo.db.exists(ModelNameEnum.Location, location)) {
inventorySettings.set('defaultLocation', location);
}
await inventorySettings.sync();
}