2
0
mirror of https://github.com/frappe/books.git synced 2024-09-20 11:29:00 +00:00

incr: type observable

- convert several to ES6
This commit is contained in:
18alantom 2022-03-17 17:16:49 +05:30
parent 507a500edb
commit 47e6493699
18 changed files with 452 additions and 407 deletions

View File

@ -1,15 +1,13 @@
const utils = require('../utils');
const format = require('../utils/format');
const errors = require('./errors');
const BaseDocument = require('frappe/model/document');
const BaseMeta = require('frappe/model/meta');
export default async function initLibs(frappe) {
const utils = await import('../utils');
const format = await import('../utils/format');
const errors = await import('./errors');
const BaseMeta = await import('frappe/model/meta');
const BaseDocument = await import('frappe/model/document');
module.exports = {
initLibs(frappe) {
Object.assign(frappe, utils);
Object.assign(frappe, format);
frappe.errors = errors;
frappe.BaseDocument = BaseDocument;
frappe.BaseMeta = BaseMeta;
},
};
Object.assign(frappe, utils.default);
Object.assign(frappe, format.default);
frappe.errors = errors.default;
frappe.BaseDocument = BaseDocument.default;
frappe.BaseMeta = BaseMeta.default;
}

View File

@ -1,25 +1,25 @@
const Observable = require('./utils/observable');
const { T, t } = require('./utils/translation');
const utils = require('./utils');
const { getMoneyMaker } = require('pesa');
const {
DEFAULT_INTERNAL_PRECISION,
import initLibs from 'frappe/common';
import { getMoneyMaker } from 'pesa';
import { markRaw } from 'vue';
import utils from './utils';
import {
DEFAULT_DISPLAY_PRECISION,
} = require('./utils/consts');
const { markRaw } = require('vue');
DEFAULT_INTERNAL_PRECISION,
} from './utils/consts';
import Observable from './utils/observable';
import { t, T } from './utils/translation';
module.exports = {
isElectron: false,
isServer: false,
class Frappe {
isElectron = false;
isServer = false;
initializeAndRegister(customModels = {}, force = false) {
async initializeAndRegister(customModels = {}, force = false) {
this.init(force);
const common = require('frappe/common');
this.registerLibs(common);
const coreModels = require('frappe/models');
this.registerModels(coreModels);
await initLibs(this);
const coreModels = await import('frappe/models');
this.registerModels(coreModels.default);
this.registerModels(customModels);
},
}
async initializeMoneyMaker(currency) {
currency ??= 'XXX';
@ -65,26 +65,19 @@ module.exports = {
display,
wrapper: markRaw,
});
},
}
init(force) {
if (this._initialized && !force) return;
this.initConfig();
this.initGlobals();
this.docs = new Observable();
this.events = new Observable();
this._initialized = true;
},
initConfig() {
// Initialize Config
this.config = {
serverURL: '',
backend: 'sqlite',
port: 8000,
};
},
initGlobals() {
// Initialize Globals
this.metaCache = {};
this.models = {};
this.forms = {};
@ -92,15 +85,15 @@ module.exports = {
this.flags = {};
this.methods = {};
this.errorLog = [];
// temp params while calling routes
this.temp = {};
this.params = {};
},
registerLibs(common) {
// add standard libs and utils to frappe
common.initLibs(this);
},
this.docs = new Observable();
this.events = new Observable();
this._initialized = true;
}
registerModels(models) {
// register models from app/models/index.js
@ -126,7 +119,7 @@ module.exports = {
this.models[doctype] = metaDefinition;
}
},
}
getModels(filterFunction) {
let models = [];
@ -134,12 +127,12 @@ module.exports = {
models.push(this.models[doctype]);
}
return filterFunction ? models.filter(filterFunction) : models;
},
}
registerView(view, name, module) {
if (!this.views[view]) this.views[view] = {};
this.views[view][name] = module;
},
}
registerMethod({ method, handler }) {
this.methods[method] = handler;
@ -156,7 +149,7 @@ module.exports = {
})
);
}
},
}
async call({ method, args }) {
if (this.isServer) {
@ -177,7 +170,7 @@ module.exports = {
body: JSON.stringify(args || {}),
});
return await response.json();
},
}
addToCache(doc) {
if (!this.docs) return;
@ -199,7 +192,7 @@ module.exports = {
this.docs.trigger('change', params);
});
}
},
}
removeFromCache(doctype, name) {
try {
@ -207,7 +200,7 @@ module.exports = {
} catch (e) {
console.warn(`Document ${doctype} ${name} does not exist`);
}
},
}
isDirty(doctype, name) {
return (
@ -217,13 +210,13 @@ module.exports = {
this.docs[doctype][name]._dirty) ||
false
);
},
}
getDocFromCache(doctype, name) {
if (this.docs && this.docs[doctype] && this.docs[doctype][name]) {
return this.docs[doctype][name];
}
},
}
getMeta(doctype) {
if (!this.metaCache[doctype]) {
@ -231,12 +224,13 @@ module.exports = {
if (!model) {
throw new Error(`${doctype} is not a registered doctype`);
}
let metaClass = model.metaClass || this.BaseMeta;
this.metaCache[doctype] = new metaClass(model);
}
return this.metaCache[doctype];
},
}
async getDoc(doctype, name, options = { skipDocumentCache: false }) {
let doc = options.skipDocumentCache
@ -251,16 +245,16 @@ module.exports = {
this.addToCache(doc);
}
return doc;
},
}
getDocumentClass(doctype) {
const meta = this.getMeta(doctype);
return meta.documentClass || this.BaseDocument;
},
}
async getSingle(doctype) {
return await this.getDoc(doctype, doctype);
},
}
async getDuplicate(doc) {
const newDoc = await this.getNewDoc(doc.doctype);
@ -277,7 +271,7 @@ module.exports = {
}
}
return newDoc;
},
}
getNewDoc(doctype, cacheDoc = true) {
let doc = this.newDoc({ doctype: doctype });
@ -287,7 +281,7 @@ module.exports = {
this.addToCache(doc);
}
return doc;
},
}
async newCustomDoc(fields) {
let doc = new this.BaseDocument({ isCustom: 1, fields });
@ -295,22 +289,22 @@ module.exports = {
doc.name = this.getRandomString();
this.addToCache(doc);
return doc;
},
}
createMeta(fields) {
let meta = new this.BaseMeta({ isCustom: 1, fields });
return meta;
},
}
newDoc(data) {
let doc = new (this.getDocumentClass(data.doctype))(data);
doc.setDefaults();
return doc;
},
}
async insert(data) {
return await this.newDoc(data).insert();
},
}
async syncDoc(data) {
let doc;
@ -322,7 +316,7 @@ module.exports = {
doc = this.newDoc(data);
await doc.insert();
}
},
}
// only for client side
async login(email, password) {
@ -354,7 +348,7 @@ module.exports = {
}
return response;
},
}
async signup(email, fullName, password) {
let response = await fetch(this.getServerURL() + '/api/signup', {
@ -371,11 +365,11 @@ module.exports = {
}
return response;
},
}
getServerURL() {
return this.config.serverURL || '';
},
}
close() {
this.db.close();
@ -383,11 +377,15 @@ module.exports = {
if (this.server) {
this.server.close();
}
},
t,
T,
store: {
}
store = {
isDevelopment: false,
appVersion: '',
},
};
};
t = t;
T = T;
}
export { T, t };
export default new Frappe();

View File

@ -1,12 +1,12 @@
const frappe = require('frappe');
const Observable = require('frappe/utils/observable');
const naming = require('./naming');
const { isPesa } = require('../utils/index');
const { DEFAULT_INTERNAL_PRECISION } = require('../utils/consts');
const { Verb } = require('@/telemetry/types');
const { default: telemetry } = require('@/telemetry/telemetry');
import telemetry from '@/telemetry/telemetry';
import { Verb } from '@/telemetry/types';
import frappe from 'frappe';
import Observable from 'frappe/utils/observable';
import { DEFAULT_INTERNAL_PRECISION } from '../utils/consts';
import { isPesa } from '../utils/index';
import { setName } from './naming';
module.exports = class BaseDocument extends Observable {
export default class Document extends Observable {
constructor(data) {
super();
this.fetchValuesCache = {};
@ -171,7 +171,7 @@ module.exports = class BaseDocument extends Observable {
}
_initChild(data, key) {
if (data instanceof BaseDocument) {
if (data instanceof Document) {
return data;
}
@ -189,7 +189,7 @@ module.exports = class BaseDocument extends Observable {
data.name = frappe.getRandomString();
}
const childDoc = new BaseDocument(data);
const childDoc = new Document(data);
childDoc.setDefaults();
return childDoc;
}
@ -549,10 +549,6 @@ module.exports = class BaseDocument extends Observable {
}
}
async setName() {
await naming.setName(this);
}
async commit() {
// re-run triggers
this.setKeywords();
@ -562,7 +558,7 @@ module.exports = class BaseDocument extends Observable {
}
async insert() {
await this.setName();
await setName(this);
this.setStandardValues();
await this.commit();
await this.validateInsert();
@ -745,7 +741,7 @@ module.exports = class BaseDocument extends Observable {
await doc.set(updateMap);
await doc.insert();
}
};
}
function getPreDefaultValues(fieldtype) {
switch (fieldtype) {

View File

@ -1,16 +1,16 @@
const BaseDocument = require('./document');
const frappe = require('frappe');
const model = require('./index');
const { indicators: indicatorColor } = require('../../src/colors');
import frappe from 'frappe';
import { indicators as indicatorColor } from '../../src/colors';
import Document from './document';
import model from './index';
module.exports = class BaseMeta extends BaseDocument {
export default class BaseMeta extends Document {
constructor(data) {
if (data.basedOn) {
let config = frappe.models[data.basedOn];
Object.assign(data, config, {
name: data.name,
label: data.label,
filters: data.filters
filters: data.filters,
});
}
super(data);
@ -30,7 +30,7 @@ module.exports = class BaseMeta extends BaseDocument {
processFields() {
// add name field
if (!this.fields.find(df => df.fieldname === 'name') && !this.isSingle) {
if (!this.fields.find((df) => df.fieldname === 'name') && !this.isSingle) {
this.fields = [
{
label: frappe.t`ID`,
@ -42,7 +42,7 @@ module.exports = class BaseMeta extends BaseDocument {
].concat(this.fields);
}
this.fields = this.fields.map(df => {
this.fields = this.fields.map((df) => {
// name field is always required
if (df.fieldname === 'name') {
df.required = 1;
@ -75,7 +75,7 @@ module.exports = class BaseMeta extends BaseDocument {
* dataFields = meta.getFieldsWith({ fieldtype: 'Data' })
*/
getFieldsWith(filters) {
return this.fields.filter(df => {
return this.fields.filter((df) => {
let match = true;
for (const key in filters) {
const value = filters[key];
@ -93,7 +93,7 @@ module.exports = class BaseMeta extends BaseDocument {
getTableFields() {
if (this._tableFields === undefined) {
this._tableFields = this.fields.filter(
field => field.fieldtype === 'Table'
(field) => field.fieldtype === 'Table'
);
}
return this._tableFields;
@ -101,7 +101,7 @@ module.exports = class BaseMeta extends BaseDocument {
getFormulaFields() {
if (this._formulaFields === undefined) {
this._formulaFields = this.fields.filter(field => field.formula);
this._formulaFields = this.fields.filter((field) => field.formula);
}
return this._formulaFields;
}
@ -141,7 +141,7 @@ module.exports = class BaseMeta extends BaseDocument {
this._validFields = [];
this._validFieldsWithChildren = [];
const _add = field => {
const _add = (field) => {
this._validFields.push(field);
this._validFieldsWithChildren.push(field);
};
@ -160,7 +160,7 @@ module.exports = class BaseMeta extends BaseDocument {
}
});
const doctypeFields = this.fields.map(field => field.fieldname);
const doctypeFields = this.fields.map((field) => field.fieldname);
// standard fields
for (let field of model.commonFields) {
@ -176,7 +176,7 @@ module.exports = class BaseMeta extends BaseDocument {
_add({
fieldtype: 'Check',
fieldname: 'submitted',
label: frappe.t`Submitted`
label: frappe.t`Submitted`,
});
}
@ -241,8 +241,8 @@ module.exports = class BaseMeta extends BaseDocument {
this._keywordFields = this.keywordFields;
if (!(this._keywordFields && this._keywordFields.length && this.fields)) {
this._keywordFields = this.fields
.filter(field => field.fieldtype !== 'Table' && field.required)
.map(field => field.fieldname);
.filter((field) => field.fieldtype !== 'Table' && field.required)
.map((field) => field.fieldname);
}
if (!(this._keywordFields && this._keywordFields.length)) {
this._keywordFields = ['name'];
@ -253,7 +253,7 @@ module.exports = class BaseMeta extends BaseDocument {
getQuickEditFields() {
if (this.quickEditFields) {
return this.quickEditFields.map(fieldname => this.getField(fieldname));
return this.quickEditFields.map((fieldname) => this.getField(fieldname));
}
return this.getFieldsWith({ required: 1 });
}
@ -271,10 +271,12 @@ module.exports = class BaseMeta extends BaseDocument {
// values given as string
validValues = options.split('\n');
}
if (typeof options[0] === 'object') {
// options as array of {label, value} pairs
validValues = options.map(o => o.value);
validValues = options.map((o) => o.value);
}
if (!validValues.includes(value)) {
throw new frappe.errors.ValueError(
// prettier-ignore
@ -287,7 +289,7 @@ module.exports = class BaseMeta extends BaseDocument {
async trigger(event, params = {}) {
Object.assign(params, {
doc: this,
name: event
name: event,
});
await super.trigger(event, params);
@ -300,8 +302,8 @@ module.exports = class BaseMeta extends BaseDocument {
key: 'submitted',
colors: {
0: indicatorColor.GRAY,
1: indicatorColor.BLUE
}
1: indicatorColor.BLUE,
},
};
}
}
@ -323,4 +325,4 @@ module.exports = class BaseMeta extends BaseDocument {
}
}
}
};
}

View File

@ -1,122 +1,119 @@
const { getPaddedName } = require('@/utils');
const frappe = require('frappe');
const { getRandomString } = require('frappe/utils');
import frappe from 'frappe';
import { getRandomString } from 'frappe/utils';
module.exports = {
async isNameAutoSet(doctype) {
const doc = frappe.getNewDoc(doctype);
if (doc.meta.naming === 'autoincrement') {
return true;
}
if (!doc.meta.settings) {
return false;
}
const { numberSeries } = await doc.getSettings();
if (numberSeries) {
return true;
}
export async function isNameAutoSet(doctype) {
const doc = frappe.getNewDoc(doctype);
if (doc.meta.naming === 'autoincrement') {
return true;
}
if (!doc.meta.settings) {
return false;
},
}
async setName(doc) {
if (frappe.isServer) {
// if is server, always name again if autoincrement or other
if (doc.meta.naming === 'autoincrement') {
doc.name = await this.getNextId(doc.doctype);
return;
}
const { numberSeries } = await doc.getSettings();
if (numberSeries) {
return true;
}
// Current, per doc number series
if (doc.numberSeries) {
doc.name = await this.getSeriesNext(doc.numberSeries, doc.doctype);
return;
}
return false;
}
// Legacy, using doc settings for number series
if (doc.meta.settings) {
const numberSeries = (await doc.getSettings()).numberSeries;
if (!numberSeries) {
return;
}
doc.name = await this.getSeriesNext(numberSeries, doc.doctype);
return;
}
}
if (doc.name) {
export async function setName(doc) {
if (frappe.isServer) {
// if is server, always name again if autoincrement or other
if (doc.meta.naming === 'autoincrement') {
doc.name = await getNextId(doc.doctype);
return;
}
// name === doctype for Single
if (doc.meta.isSingle) {
doc.name = doc.meta.name;
// Current, per doc number series
if (doc.numberSeries) {
doc.name = await getSeriesNext(doc.numberSeries, doc.doctype);
return;
}
// assign a random name by default
// override doc to set a name
if (!doc.name) {
doc.name = getRandomString();
}
},
async getNextId(doctype) {
// get the last inserted row
let lastInserted = await this.getLastInserted(doctype);
let name = 1;
if (lastInserted) {
let lastNumber = parseInt(lastInserted.name);
if (isNaN(lastNumber)) lastNumber = 0;
name = lastNumber + 1;
}
return (name + '').padStart(9, '0');
},
async getLastInserted(doctype) {
const lastInserted = await frappe.db.getAll({
doctype: doctype,
fields: ['name'],
limit: 1,
order_by: 'creation',
order: 'desc',
});
return lastInserted && lastInserted.length ? lastInserted[0] : null;
},
async getSeriesNext(prefix, doctype) {
let series;
try {
series = await frappe.getDoc('NumberSeries', prefix);
} catch (e) {
if (!e.statusCode || e.statusCode !== 404) {
throw e;
// Legacy, using doc settings for number series
if (doc.meta.settings) {
const numberSeries = (await doc.getSettings()).numberSeries;
if (!numberSeries) {
return;
}
await this.createNumberSeries(prefix, doctype);
series = await frappe.getDoc('NumberSeries', prefix);
}
return await series.next(doctype);
},
async createNumberSeries(prefix, referenceType, start = 1001) {
const exists = await frappe.db.exists('NumberSeries', prefix);
if (exists) {
doc.name = await getSeriesNext(numberSeries, doc.doctype);
return;
}
}
const series = frappe.newDoc({
doctype: 'NumberSeries',
name: prefix,
start,
referenceType,
});
if (doc.name) {
return;
}
await series.insert();
},
};
// name === doctype for Single
if (doc.meta.isSingle) {
doc.name = doc.meta.name;
return;
}
// assign a random name by default
// override doc to set a name
if (!doc.name) {
doc.name = getRandomString();
}
}
export async function getNextId(doctype) {
// get the last inserted row
let lastInserted = await getLastInserted(doctype);
let name = 1;
if (lastInserted) {
let lastNumber = parseInt(lastInserted.name);
if (isNaN(lastNumber)) lastNumber = 0;
name = lastNumber + 1;
}
return (name + '').padStart(9, '0');
}
export async function getLastInserted(doctype) {
const lastInserted = await frappe.db.getAll({
doctype: doctype,
fields: ['name'],
limit: 1,
order_by: 'creation',
order: 'desc',
});
return lastInserted && lastInserted.length ? lastInserted[0] : null;
}
export async function getSeriesNext(prefix, doctype) {
let series;
try {
series = await frappe.getDoc('NumberSeries', prefix);
} catch (e) {
if (!e.statusCode || e.statusCode !== 404) {
throw e;
}
await createNumberSeries(prefix, doctype);
series = await frappe.getDoc('NumberSeries', prefix);
}
return await series.next(doctype);
}
export async function createNumberSeries(prefix, referenceType, start = 1001) {
const exists = await frappe.db.exists('NumberSeries', prefix);
if (exists) {
return;
}
const series = frappe.newDoc({
doctype: 'NumberSeries',
name: prefix,
start,
referenceType,
});
await series.insert();
}

View File

@ -1,6 +1,6 @@
const frappe = require('frappe');
import frappe from 'frappe';
module.exports = async function runPatches(patchList) {
export default async function runPatches(patchList) {
const patchesAlreadyRun = (
await frappe.db.knex('PatchRun').select('name')
).map(({ name }) => name);
@ -12,7 +12,7 @@ module.exports = async function runPatches(patchList) {
await runPatch(patch);
}
};
}
async function runPatch({ patchName, patchFunction }) {
try {

View File

@ -1,4 +1,5 @@
const { t } = require('frappe');
import { t } from 'frappe';
import NumberSeries from './NumberSeriesDocument.js';
const referenceTypeMap = {
SalesInvoice: t`Invoice`,
@ -13,9 +14,10 @@ const referenceTypeMap = {
'-': t`None`,
};
module.exports = {
export default {
name: 'NumberSeries',
documentClass: require('./NumberSeriesDocument.js'),
label: t`Number Series`,
documentClass: NumberSeries,
doctype: 'DocType',
isSingle: 0,
isChild: 0,

View File

@ -1,8 +1,8 @@
const { getPaddedName } = require('@/utils');
const frappe = require('frappe');
const BaseDocument = require('frappe/model/document');
import { getPaddedName } from '@/utils';
import frappe from 'frappe';
import BaseDocument from 'frappe/model/document';
module.exports = class NumberSeries extends BaseDocument {
export default class NumberSeries extends BaseDocument {
validate() {
if (!this.current) {
this.current = this.start;
@ -34,4 +34,4 @@ module.exports = class NumberSeries extends BaseDocument {
getPaddedName(next) {
return getPaddedName(this.name, next, this.padZeros);
}
};
}

View File

@ -1,13 +1,25 @@
module.exports = {
NumberSeries: require('./doctype/NumberSeries/NumberSeries.js'),
PrintFormat: require('./doctype/PrintFormat/PrintFormat.js'),
Role: require('./doctype/Role/Role.js'),
Session: require('./doctype/Session/Session.js'),
SingleValue: require('./doctype/SingleValue/SingleValue.js'),
SystemSettings: require('./doctype/SystemSettings/SystemSettings.js'),
ToDo: require('./doctype/ToDo/ToDo.js'),
User: require('./doctype/User/User.js'),
UserRole: require('./doctype/UserRole/UserRole.js'),
File: require('./doctype/File/File.js'),
PatchRun: require('./doctype/PatchRun/PatchRun.js')
import File from './doctype/File/File.js';
import NumberSeries from './doctype/NumberSeries/NumberSeries.js';
import PatchRun from './doctype/PatchRun/PatchRun.js';
import PrintFormat from './doctype/PrintFormat/PrintFormat.js';
import Role from './doctype/Role/Role.js';
import Session from './doctype/Session/Session.js';
import SingleValue from './doctype/SingleValue/SingleValue.js';
import SystemSettings from './doctype/SystemSettings/SystemSettings.js';
import ToDo from './doctype/ToDo/ToDo.js';
import User from './doctype/User/User.js';
import UserRole from './doctype/UserRole/UserRole.js';
export default {
NumberSeries,
PrintFormat,
Role,
Session,
SingleValue,
SystemSettings,
ToDo,
User,
UserRole,
File,
PatchRun,
};

View File

@ -1,8 +1,8 @@
const luxon = require('luxon');
const frappe = require('frappe');
const { DEFAULT_DISPLAY_PRECISION, DEFAULT_LOCALE } = require('./consts');
import frappe from 'frappe';
import { DateTime } from 'luxon';
import { DEFAULT_DISPLAY_PRECISION, DEFAULT_LOCALE } from './consts';
module.exports = {
export default {
format(value, df, doc) {
if (!df) {
return value;
@ -25,10 +25,10 @@ module.exports = {
if (typeof value === 'string') {
// ISO String
value = luxon.DateTime.fromISO(value);
value = DateTime.fromISO(value);
} else if (Object.prototype.toString.call(value) === '[object Date]') {
// JS Date
value = luxon.DateTime.fromJSDate(value);
value = DateTime.fromJSDate(value);
}
value = value.toFormat(dateFormat);

View File

@ -1,128 +0,0 @@
module.exports = class Observable {
constructor() {
this._observable = {
isHot: {},
eventQueue: {},
listeners: {},
onceListeners: {}
}
}
// getter, setter stubs, so Observable can be used as a simple Document
get(key) {
return this[key];
}
set(key, value) {
this[key] = value;
this.trigger('change', {
doc: this,
fieldname: key
});
}
on(event, listener) {
this._addListener('listeners', event, listener);
if (this._observable.socketClient) {
this._observable.socketClient.on(event, listener);
}
}
// remove listener
off(event, listener) {
for (let type of ['listeners', 'onceListeners']) {
let index = this._observable[type][event] && this._observable[type][event].indexOf(listener);
if (index) {
this._observable[type][event].splice(index, 1);
}
}
}
once(event, listener) {
this._addListener('onceListeners', event, listener);
}
async trigger(event, params, throttle = false) {
if (throttle) {
if (this._throttled(event, params, throttle)) return;
params = [params]
}
await this._executeTriggers(event, params);
}
async _executeTriggers(event, params) {
let response = await this._triggerEvent('listeners', event, params);
if (response === false) return false;
response = await this._triggerEvent('onceListeners', event, params);
if (response === false) return false;
// emit via socket
if (this._observable.socketServer) {
this._observable.socketServer.emit(event, params);
}
// clear once-listeners
if (this._observable.onceListeners && this._observable.onceListeners[event]) {
delete this._observable.onceListeners[event];
}
}
clearListeners() {
this._observable.listeners = {};
this._observable.onceListeners = {};
}
bindSocketClient(socket) {
// also send events with sockets
this._observable.socketClient = socket;
}
bindSocketServer(socket) {
// also send events with sockets
this._observable.socketServer = socket;
}
_throttled(event, params, throttle) {
if (this._observable.isHot[event]) {
// hot, add to queue
if (!this._observable.eventQueue[event]) this._observable.eventQueue[event] = [];
this._observable.eventQueue[event].push(params);
// aleady hot, quit
return true;
}
this._observable.isHot[event] = true;
// cool-off
setTimeout(() => {
this._observable.isHot[event] = false;
// flush queue
if (this._observable.eventQueue[event]) {
let _queuedParams = this._observable.eventQueue[event];
this._observable.eventQueue[event] = null;
this._executeTriggers(event, _queuedParams);
}
}, throttle);
return false;
}
_addListener(type, event, listener) {
if (!this._observable[type][event]) {
this._observable[type][event] = [];
}
this._observable[type][event].push(listener);
}
async _triggerEvent(type, event, params) {
if (this._observable[type][event]) {
for (let listener of this._observable[type][event]) {
await listener(params);
}
}
}
}

170
frappe/utils/observable.ts Normal file
View File

@ -0,0 +1,170 @@
enum EventType {
Listeners = '_listeners',
OnceListeners = '_onceListeners',
}
export default class Observable {
[key: string]: unknown;
_isHot: Map<string, boolean>;
_eventQueue: Map<string, unknown[]>;
_map: Map<string, unknown>;
_listeners: Map<string, Function[]>;
_onceListeners: Map<string, Function[]>;
constructor() {
this._map = new Map();
this._isHot = new Map();
this._eventQueue = new Map();
this._listeners = new Map();
this._onceListeners = new Map();
}
/**
* Getter to use Observable as a regular document.
*
* @param key
* @returns
*/
get(key: string): unknown {
return this[key];
}
/**
* Setter to use Observable as a regular document.
*
* @param key
* @param value
*/
set(key: string, value: unknown) {
this[key] = value;
this.trigger('change', {
doc: this,
fieldname: key,
});
}
/**
* Sets a `listener` that executes every time `event` is triggered
*
* @param event : name of the event for which the listener is set
* @param listener : listener that is executed when the event is triggered
*/
on(event: string, listener: Function) {
this._addListener(EventType.Listeners, event, listener);
}
/**
* Sets a `listener` that execture `once`: executes once when `event` is
* triggered then deletes itself
*
* @param event : name of the event for which the listener is set
* @param listener : listener that is executed when the event is triggered
*/
once(event: string, listener: Function) {
this._addListener(EventType.OnceListeners, event, listener);
}
/**
* Remove a listener from an event for both 'on' and 'once'
*
* @param event : name of the event from which to remove the listener
* @param listener : listener that was set for the event
*/
off(event: string, listener: Function) {
this._removeListener(EventType.Listeners, event, listener);
this._removeListener(EventType.OnceListeners, event, listener);
}
/**
* Remove all the listeners.
*/
clear() {
this._listeners.clear();
this._onceListeners.clear();
}
/**
* Triggers the event's listener function.
*
* @param event : name of the event to be triggered.
* @param params : params to pass to the listeners.
* @param throttle : wait time before triggering the event.
*/
async trigger(event: string, params: unknown, throttle: number = 0) {
let isHot = false;
if (throttle > 0) {
isHot = this._throttled(event, params, throttle);
params = [params];
}
if (isHot) {
return;
}
await this._executeTriggers(event, params);
}
_removeListener(type: EventType, event: string, listener: Function) {
const listeners = (this[type].get(event) ?? []).filter(
(l) => l !== listener
);
this[type].set(event, listeners);
}
async _executeTriggers(event: string, params?: unknown) {
await this._triggerEvent(EventType.Listeners, event, params);
await this._triggerEvent(EventType.OnceListeners, event, params);
this._onceListeners.delete(event);
}
_throttled(event: string, params: unknown, throttle: number) {
/**
* Throttled events execute after `throttle` ms, during this period
* isHot is true, i.e it's going to execute.
*/
if (!this._eventQueue.has(event)) {
this._eventQueue.set(event, []);
}
if (this._isHot.get(event)) {
this._eventQueue.get(event)!.push(params);
return true;
}
this._isHot.set(event, true);
setTimeout(() => {
this._isHot.set(event, false);
const params = this._eventQueue.get(event);
if (params !== undefined) {
this._executeTriggers(event, params);
this._eventQueue.delete(event);
}
}, throttle);
return false;
}
_addListener(type: EventType, event: string, listener: Function) {
this._initLiseners(type, event);
this[type].get(event)!.push(listener);
}
_initLiseners(type: EventType, event: string) {
if (this[type].has(event)) {
return;
}
this[type].set(event, []);
}
async _triggerEvent(type: EventType, event: string, params?: unknown) {
const listeners = this[type].get(event) ?? [];
for (const listener of listeners) {
await listener(params);
}
}
}

View File

@ -1,5 +1,5 @@
import frappe from 'frappe';
import naming from 'frappe/model/naming';
import { createNumberSeries } from 'frappe/model/naming';
import GSTR3BServer from '../models/doctype/GSTR3B/GSTR3BServer.js';
import JournalEntryServer from '../models/doctype/JournalEntry/JournalEntryServer.js';
import PartyServer from '../models/doctype/Party/PartyServer.js';
@ -20,10 +20,10 @@ export default async function postStart() {
frappe.metaCache = {};
// init naming series if missing
await naming.createNumberSeries('SINV-', 'SalesInvoice');
await naming.createNumberSeries('PINV-', 'PurchaseInvoice');
await naming.createNumberSeries('PAY-', 'Payment');
await naming.createNumberSeries('JV-', 'JournalEntry');
await createNumberSeries('SINV-', 'SalesInvoice');
await createNumberSeries('PINV-', 'PurchaseInvoice');
await createNumberSeries('PAY-', 'Payment');
await createNumberSeries('JV-', 'JournalEntry');
// await naming.createNumberSeries('QTN-', 'QuotationSettings');
// await naming.createNumberSeries('SO-', 'SalesOrderSettings');
// await naming.createNumberSeries('OF-', 'FulfillmentSettings');

View File

@ -33,9 +33,9 @@
import WindowsTitleBar from '@/components/WindowsTitleBar';
import config from '@/config';
import {
connectToLocalDatabase,
postSetup,
purgeCache
connectToLocalDatabase,
postSetup,
purgeCache,
} from '@/initialization';
import { IPC_ACTIONS, IPC_MESSAGES } from '@/messages';
import { ipcRenderer } from 'electron';
@ -131,10 +131,10 @@ export default {
routeTo('/get-started');
}
},
changeDbFile() {
async changeDbFile() {
config.set('lastSelectedFilePath', null);
telemetry.stop();
purgeCache(true);
await purgeCache(true);
this.activeScreen = 'DatabaseSelector';
},
async setupCanceled() {

View File

@ -6,7 +6,7 @@ import {
MandatoryError,
ValidationError,
} from 'frappe/common/errors';
import BaseDocument from 'frappe/model/document';
import Document from 'frappe/model/document';
import config, { ConfigKeys, TelemetrySetting } from './config';
import { IPC_ACTIONS, IPC_MESSAGES } from './messages';
import telemetry from './telemetry/telemetry';
@ -110,7 +110,7 @@ export function handleError(
}
}
export function getErrorMessage(e: Error, doc?: BaseDocument): string {
export function getErrorMessage(e: Error, doc?: Document): string {
let errorMessage = e.message || t`An error occurred.`;
const { doctype, name }: { doctype?: unknown; name?: unknown } = doc ?? {};
@ -125,7 +125,7 @@ export function getErrorMessage(e: Error, doc?: BaseDocument): string {
return errorMessage;
}
export function handleErrorWithDialog(error: Error, doc?: BaseDocument) {
export function handleErrorWithDialog(error: Error, doc?: Document) {
const errorMessage = getErrorMessage(error, doc);
handleError(false, error, { errorMessage, doc });

View File

@ -106,7 +106,7 @@ export async function connectToLocalDatabase(filePath) {
return { connectionSuccess: true, reason: '' };
}
export function purgeCache(purgeAll = false) {
export async function purgeCache(purgeAll = false) {
const filterFunction = purgeAll
? () => true
: (d) => frappe.docs[d][d] instanceof frappe.BaseMeta;
@ -120,7 +120,7 @@ export function purgeCache(purgeAll = false) {
if (purgeAll) {
delete frappe.db;
frappe.initializeAndRegister(models, true);
await frappe.initializeAndRegister(models, true);
}
}

View File

@ -25,7 +25,7 @@ import { setLanguageMap, stringifyCircular } from './utils';
frappe.isServer = true;
frappe.isElectron = true;
frappe.initializeAndRegister(models);
await frappe.initializeAndRegister(models);
ipcRenderer.send = getErrorHandled(ipcRenderer.send);
ipcRenderer.invoke = getErrorHandled(ipcRenderer.invoke);

View File

@ -89,25 +89,23 @@
</template>
<script>
import frappe from 'frappe';
import TwoColumnForm from '@/components/TwoColumnForm';
import FormControl from '@/components/Controls/FormControl';
import setupCompany from './setupCompany';
import Popover from '@/components/Popover';
import config from '@/config';
import path from 'path';
import fs from 'fs';
import { purgeCache, connectToLocalDatabase } from '@/initialization';
import { setLanguageMap, showMessageDialog } from '@/utils';
import {
handleErrorWithDialog,
getErrorMessage,
showErrorDialog,
} from '../../errorHandling';
import Slide from './Slide.vue';
import LanguageSelector from '@/components/Controls/LanguageSelector.vue';
import { ipcRenderer } from 'electron';
import Popover from '@/components/Popover';
import TwoColumnForm from '@/components/TwoColumnForm';
import config from '@/config';
import { connectToLocalDatabase, purgeCache } from '@/initialization';
import { IPC_MESSAGES } from '@/messages';
import { setLanguageMap, showMessageDialog } from '@/utils';
import { ipcRenderer } from 'electron';
import frappe from 'frappe';
import fs from 'fs';
import path from 'path';
import {
getErrorMessage, handleErrorWithDialog, showErrorDialog
} from '../../errorHandling';
import setupCompany from './setupCompany';
import Slide from './Slide.vue';
export default {
name: 'SetupWizard',
@ -209,7 +207,7 @@ export default {
const filePath = config.get('lastSelectedFilePath');
renameDbFile(filePath);
purgeCache();
await purgeCache();
const { connectionSuccess, reason } = await connectToLocalDatabase(
filePath