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

incr: add button to create a demo instance

This commit is contained in:
18alantom 2022-05-11 18:06:26 +05:30
parent 0701c56429
commit 22b90cd424
7 changed files with 217 additions and 145 deletions

View File

@ -22,11 +22,11 @@ export const purchaseItemPartyMap: Record<string, string> = Object.keys(
return acc;
}, {} as Record<string, string>);
export const flow = [
0.15, 0.1, 0.25, 0.1, 0.01, 0.01, 0.01, 0.05, 0, 0.15, 0.2, 0.4,
];
export function getFlowConstant(months: number) {
// Jan to December
const flow = [
0.15, 0.1, 0.25, 0.1, 0.01, 0.01, 0.01, 0.05, 0, 0.15, 0.2, 0.4,
];
const d = DateTime.now().minus({ months });
return flow[d.month - 1];

View File

@ -1,4 +1,4 @@
import { Fyo } from 'fyo';
import { Fyo, t } from 'fyo';
import { Doc } from 'fyo/model/doc';
import { range, sample } from 'lodash';
import { DateTime } from 'luxon';
@ -11,6 +11,7 @@ import setupInstance from 'src/setup/setupInstance';
import { getMapFromList } from 'utils';
import { getFiscalYear } from 'utils/misc';
import {
flow,
getFlowConstant,
getRandomDates,
purchaseItemPartyMap,
@ -18,7 +19,7 @@ import {
import items from './items.json';
import parties from './parties.json';
type Notifier = (stage: string, count: number, total: number) => void;
type Notifier = (stage: string, percent: number) => void;
export async function setupDummyInstance(
dbPath: string,
@ -27,28 +28,26 @@ export async function setupDummyInstance(
baseCount: number = 1000,
notifier?: Notifier
) {
notifier?.(fyo.t`Setting Up Instance`, 0, 0);
await setupInstance(
dbPath,
{
logo: null,
companyName: "Flo's Clothes",
country: 'India',
fullname: 'Lin Florentine',
email: 'lin@flosclothes.com',
bankName: 'Supreme Bank',
currency: 'INR',
fiscalYearStart: getFiscalYear('04-01', true),
fiscalYearEnd: getFiscalYear('04-01', false),
chartOfAccounts: 'India - Chart of Accounts',
},
fyo
);
notifier?.(fyo.t`Setting Up Instance`, -1);
const options = {
logo: null,
companyName: "Flo's Clothes",
country: 'India',
fullname: 'Lin Florentine',
email: 'lin@flosclothes.com',
bankName: 'Supreme Bank',
currency: 'INR',
fiscalYearStart: getFiscalYear('04-01', true),
fiscalYearEnd: getFiscalYear('04-01', false),
chartOfAccounts: 'India - Chart of Accounts',
};
await setupInstance(dbPath, options, fyo);
years = Math.floor(years);
notifier?.(fyo.t`Creating Items and Parties`, 0, 0);
notifier?.(fyo.t`Creating Items and Parties`, -1);
await generateStaticEntries(fyo);
await generateDynamicEntries(fyo, years, baseCount, notifier);
return options.companyName;
}
/**
@ -61,29 +60,22 @@ async function generateDynamicEntries(
baseCount: number,
notifier?: Notifier
) {
notifier?.(fyo.t`Getting Sales Invoices`, 0, 0);
const salesInvoices = await getSalesInvoices(fyo, years, baseCount);
const salesInvoices = await getSalesInvoices(fyo, years, baseCount, notifier);
notifier?.(fyo.t`Getting Purchase Invoices`, 0, 0);
notifier?.(fyo.t`Creating Purchase Invoices`, -1);
const purchaseInvoices = await getPurchaseInvoices(fyo, years, salesInvoices);
notifier?.(fyo.t`Getting Journal Entries`, 0, 0);
notifier?.(fyo.t`Creating Journal Entries`, -1);
const journalEntries = await getJournalEntries(fyo, salesInvoices);
await syncAndSubmit(journalEntries, (count: number, total: number) =>
notifier?.(fyo.t`Syncing Journal Entries`, count, total)
);
await syncAndSubmit(journalEntries, notifier);
const invoices = ([salesInvoices, purchaseInvoices].flat() as Invoice[]).sort(
(a, b) => +(a.date as Date) - +(b.date as Date)
);
await syncAndSubmit(invoices, (count: number, total: number) =>
notifier?.(fyo.t`Syncing Invoices`, count, total)
);
await syncAndSubmit(invoices, notifier);
const payments = await getPayments(fyo, invoices);
await syncAndSubmit(payments, (count: number, total: number) =>
notifier?.(fyo.t`Syncing Payments`, count, total)
);
await syncAndSubmit(payments, notifier);
}
async function getJournalEntries(fyo: Fyo, salesInvoices: SalesInvoice[]) {
@ -178,7 +170,12 @@ async function getPayments(fyo: Fyo, invoices: Invoice[]) {
return payments;
}
async function getSalesInvoices(fyo: Fyo, years: number, baseCount: number) {
async function getSalesInvoices(
fyo: Fyo,
years: number,
baseCount: number,
notifier?: Notifier
) {
const invoices: SalesInvoice[] = [];
const salesItems = items.filter((i) => i.for !== 'Purchases');
const customers = parties.filter((i) => i.role !== 'Supplier');
@ -187,65 +184,73 @@ async function getSalesInvoices(fyo: Fyo, years: number, baseCount: number) {
* Get certain number of entries for each month of the count
* of years.
*/
let dates: Date[] = [];
for (const months of range(0, years * 12)) {
const flow = getFlowConstant(months);
const count = Math.ceil(flow * baseCount * (Math.random() * 0.25 + 0.75));
const dates = getRandomDates(count, months);
dates = dates.concat(getRandomDates(count, months));
}
/**
* For each date create a Sales Invoice.
*/
/**
* For each date create a Sales Invoice.
*/
for (const date of dates) {
const customer = sample(customers);
for (const d in dates) {
const date = dates[d];
const doc = fyo.doc.getNewDoc(ModelNameEnum.SalesInvoice, {
date,
}) as SalesInvoice;
notifier?.(
`Creating Sales Invoices, ${d} out of ${dates.length}`,
parseInt(d) / dates.length
);
const customer = sample(customers);
await doc.set('party', customer!.name);
if (!doc.account) {
doc.account = 'Debtors';
}
/**
* Add `numItems` number of items to the invoice.
*/
const numItems = Math.ceil(Math.random() * 5);
for (let i = 0; i < numItems; i++) {
const item = sample(salesItems);
if ((doc.items ?? []).find((i) => i.item === item)) {
continue;
}
const doc = fyo.doc.getNewDoc(ModelNameEnum.SalesInvoice, {
date,
}) as SalesInvoice;
let quantity = 1;
/**
* Increase quantity depending on the rate.
*/
if (item!.rate < 100 && Math.random() < 0.4) {
quantity = Math.ceil(Math.random() * 10);
} else if (item!.rate < 1000 && Math.random() < 0.2) {
quantity = Math.ceil(Math.random() * 4);
} else if (Math.random() < 0.01) {
quantity = Math.ceil(Math.random() * 3);
}
const rate = fyo.pesa(item!.rate * flow + 1).clip(0);
await doc.append('items', {});
await doc.items!.at(-1)!.set({
item: item!.name,
rate,
quantity,
account: item!.incomeAccount,
amount: rate.mul(quantity),
tax: item!.tax,
description: item!.description,
hsnCode: item!.hsnCode,
});
}
invoices.push(doc);
await doc.set('party', customer!.name);
if (!doc.account) {
doc.account = 'Debtors';
}
/**
* Add `numItems` number of items to the invoice.
*/
const numItems = Math.ceil(Math.random() * 5);
for (let i = 0; i < numItems; i++) {
const item = sample(salesItems);
if ((doc.items ?? []).find((i) => i.item === item)) {
continue;
}
let quantity = 1;
/**
* Increase quantity depending on the rate.
*/
if (item!.rate < 100 && Math.random() < 0.4) {
quantity = Math.ceil(Math.random() * 10);
} else if (item!.rate < 1000 && Math.random() < 0.2) {
quantity = Math.ceil(Math.random() * 4);
} else if (Math.random() < 0.01) {
quantity = Math.ceil(Math.random() * 3);
}
const fc = flow[date.getMonth()];
const rate = fyo.pesa(item!.rate * (fc + 1)).clip(0);
await doc.append('items', {});
await doc.items!.at(-1)!.set({
item: item!.name,
rate,
quantity,
account: item!.incomeAccount,
amount: rate.mul(quantity),
tax: item!.tax,
description: item!.description,
hsnCode: item!.hsnCode,
});
}
invoices.push(doc);
}
return invoices;
@ -442,14 +447,21 @@ async function generateParties(fyo: Fyo) {
}
}
async function syncAndSubmit(
docs: Doc[],
notifier: (count: number, total: number) => void
) {
async function syncAndSubmit(docs: Doc[], notifier?: Notifier) {
const nameMap: Record<string, string> = {
[ModelNameEnum.PurchaseInvoice]: t`Invoices`,
[ModelNameEnum.SalesInvoice]: t`Invoices`,
[ModelNameEnum.Payment]: t`Payments`,
[ModelNameEnum.JournalEntry]: t`Journal Entries`,
};
const total = docs.length;
for (const i in docs) {
notifier(parseInt(i) + 1, total);
const doc = docs[i];
notifier?.(
`Syncing ${nameMap[doc.schemaName]}, ${i} out of ${total}`,
parseInt(i) / total
);
await doc.sync();
await doc.submit();
}

View File

@ -523,7 +523,7 @@ export class Doc extends Observable<DocValue | Doc[]> {
if (dbValues && docModified !== dbModified) {
throw new ConflictError(
this.fyo
.t`Document ${this.schemaName} ${this.name} has been modified after loading` +
.t`${this.schema.label} ${this.name} has been modified after loading` +
` ${dbModified}, ${docModified}`
);
}

View File

@ -62,7 +62,7 @@ export function getInstanceId(fyo: Fyo): UniqueId {
const file = files.find((f) => f.companyName === companyName);
if (file === undefined) {
return addNewFile(companyName, files, fyo);
return addNewFile(companyName, fyo, files);
}
if (file.id === undefined) {
@ -72,14 +72,18 @@ export function getInstanceId(fyo: Fyo): UniqueId {
return file.id;
}
function addNewFile(
export function addNewFile(
companyName: string,
files: ConfigFile[],
fyo: Fyo
fyo: Fyo,
files?: ConfigFile[],
dbPath?: string
): UniqueId {
files ??= fyo.config.get(ConfigKeys.Files, []) as ConfigFile[];
dbPath ??= fyo.config.get(ConfigKeys.LastSelectedFilePath, '') as string;
const newFile: ConfigFile = {
companyName,
dbPath: fyo.config.get(ConfigKeys.LastSelectedFilePath, '') as string,
dbPath,
id: getId(),
};

View File

@ -1,9 +1,10 @@
<template>
<div
class="absolute bottom-0 flex justify-end pb-6 pr-6"
style="width: calc(100% - 12rem)"
:style="{ width: fullWidth ? '100%' : 'calc(100% - 12rem)' }"
v-if="open && !close"
>
<!-- Loading Continer -->
<div
class="
text-gray-900
@ -16,16 +17,28 @@
bg-white
rounded-lg
"
v-if="true"
>
<p class="text-base text-gray-600 pb-2" v-if="message.length">{{ message }}</p>
<!-- Message -->
<p class="text-base text-gray-600 pb-2" v-if="message?.length">
{{ message }}
</p>
<!-- Loading Bar Container -->
<div class="w-full flex flex-row items-center">
<div class="w-full bg-gray-200 h-3 mr-2 rounded">
<!-- Loading Bar BG -->
<div
class="w-full h-3 mr-2 rounded"
:class="percent >= 0 ? 'bg-gray-200' : 'bg-gray-300'"
>
<!-- Loading Bar -->
<div
v-if="percent >= 0"
class="h-3 rounded bg-blue-400"
:style="{ width: `${percent * 100}%` }"
></div>
</div>
<!-- Close Icon -->
<feather-icon
name="x"
class="
@ -48,12 +61,16 @@ export default {
open: { type: Boolean, default: false },
percent: { type: Number, default: 0.5 },
message: { type: String, default: '' },
fullWidth: { type: Boolean, default: false },
},
data() {
return {
close: false,
};
},
mounted() {
window.l = this;
},
methods: {
closeToast() {
this.close = true;

View File

@ -28,16 +28,8 @@
<!-- New File (Blue Icon) -->
<div
@click="newDatabase"
class="
px-6
h-18
flex flex-row
items-center
cursor-pointer
gap-4
p-2
hover:bg-gray-100
"
class="px-6 h-18 flex flex-row items-center gap-4 p-2"
:class="creatingDemo ? '' : 'hover:bg-gray-100 cursor-pointer'"
>
<div class="w-8 h-8 rounded-full bg-blue-500 relative flex-center">
<feather-icon name="plus" class="text-white w-5 h-5" />
@ -56,16 +48,8 @@
<!-- Existing File (Green Icon) -->
<div
@click="existingDatabase"
class="
px-6
h-18
flex flex-row
items-center
cursor-pointer
gap-4
p-2
hover:bg-gray-100
"
class="px-6 h-18 flex flex-row items-center gap-4 p-2"
:class="creatingDemo ? '' : 'hover:bg-gray-100 cursor-pointer'"
>
<div class="w-8 h-8 rounded-full bg-green-500 relative flex-center">
<feather-icon name="upload" class="w-4 h-4 text-white" />
@ -84,15 +68,8 @@
<!-- File List -->
<div class="overflow-scroll" style="max-height: 340px">
<div
class="
h-18
px-6
flex
gap-4
items-center
hover:bg-gray-100
cursor-pointer
"
class="h-18 px-6 flex gap-4 items-center"
:class="creatingDemo ? '' : 'hover:bg-gray-100 cursor-pointer'"
v-for="(file, i) in files"
:key="file.dbPath"
@click="selectFile(file)"
@ -127,20 +104,43 @@
<!-- Language Selector -->
<div
class="w-full flex justify-end absolute px-6 py-6"
class="w-full flex justify-between items-center absolute px-6 py-6"
style="top: 100%; transform: translateY(-100%)"
>
<LanguageSelector class="w-40" />
<Button
class="text-sm w-40"
@click="createDemo"
:disabled="creatingDemo"
>{{ creatingDemo ? t`Please Wait` : t`Create Demo` }}</Button
>
<LanguageSelector
v-show="!creatingDemo"
class="w-40 bg-gray-100 rounded-md"
input-class="text-sm bg-transparent"
/>
</div>
</div>
<Loading
v-if="creatingDemo"
:open="creatingDemo"
:full-width="true"
:percent="creationPercent"
:message="creationMessage"
/>
</div>
</template>
<script>
import { setupDummyInstance } from 'dummy';
import { ipcRenderer } from 'electron';
import fs from 'fs';
import { ConfigKeys } from 'fyo/core/types';
import { addNewFile } from 'fyo/telemetry/helpers';
import { cloneDeep } from 'lodash';
import { DateTime } from 'luxon';
import Button from 'src/components/Button.vue';
import LanguageSelector from 'src/components/Controls/LanguageSelector.vue';
import Loading from 'src/components/Loading.vue';
import WindowControls from 'src/components/WindowControls.vue';
import { fyo } from 'src/initFyo';
import { getSavePath } from 'src/utils/ipcCalls';
@ -151,16 +151,51 @@ export default {
emits: ['file-selected'],
data() {
return {
creationMessage: '',
creationPercent: 0,
creatingDemo: false,
loadingDatabase: false,
fileSelectedFrom: null,
files: [],
};
},
mounted() {
this.setFiles();
window.ds = this;
if (fyo.store.isDevelopment) {
window.ds = this;
}
},
methods: {
async createDemo() {
const { filePath, canceled } = await getSavePath('demo', 'db');
if (canceled || !filePath) {
return;
}
this.creatingDemo = true;
const companyName = await setupDummyInstance(
filePath,
fyo,
1,
1000,
(message, percent) => {
this.creationMessage = message;
this.creationPercent = percent;
}
);
addNewFile(
companyName,
fyo,
fyo.config.get(ConfigKeys.Files, []),
filePath
);
fyo.purgeCache();
this.setFiles();
this.creatingDemo = false;
},
setFiles() {
const files = cloneDeep(fyo.config.get('files', []));
this.files = files.filter(({ dbPath }) => fs.existsSync(dbPath));
@ -171,7 +206,10 @@ export default {
}
},
async newDatabase() {
this.fileSelectedFrom = 'New File';
if (this.creatingDemo) {
return;
}
const { filePath, canceled } = await getSavePath('books', 'db');
if (canceled || !filePath) {
return;
@ -180,7 +218,10 @@ export default {
this.connectToDatabase(filePath, true);
},
async existingDatabase() {
this.fileSelectedFrom = 'Existing File';
if (this.creatingDemo) {
return;
}
const filePath = (
await ipcRenderer.invoke(IPC_ACTIONS.GET_OPEN_FILEPATH, {
title: this.t`Select file`,
@ -191,7 +232,10 @@ export default {
this.connectToDatabase(filePath);
},
async selectFile(file) {
this.fileSelectedFrom = file;
if (this.creatingDemo) {
return;
}
await this.connectToDatabase(file.dbPath);
},
async connectToDatabase(filePath, isNew) {
@ -207,6 +251,6 @@ export default {
this.$emit('file-selected', filePath, !!isNew);
},
},
components: { LanguageSelector, WindowControls },
components: { LanguageSelector, WindowControls, Button, Loading },
};
</script>

View File

@ -68,16 +68,11 @@ export function incrementOpenCount() {
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,
ModelNameEnum.Item,
];
await fyo.telemetry.start();
}