2
0
mirror of https://github.com/frappe/books.git synced 2024-12-23 11:29:03 +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'); export default async function initLibs(frappe) {
const format = require('../utils/format'); const utils = await import('../utils');
const errors = require('./errors'); const format = await import('../utils/format');
const BaseDocument = require('frappe/model/document'); const errors = await import('./errors');
const BaseMeta = require('frappe/model/meta'); const BaseMeta = await import('frappe/model/meta');
const BaseDocument = await import('frappe/model/document');
module.exports = { Object.assign(frappe, utils.default);
initLibs(frappe) { Object.assign(frappe, format.default);
Object.assign(frappe, utils); frappe.errors = errors.default;
Object.assign(frappe, format); frappe.BaseDocument = BaseDocument.default;
frappe.errors = errors; frappe.BaseMeta = BaseMeta.default;
frappe.BaseDocument = BaseDocument; }
frappe.BaseMeta = BaseMeta;
},
};

View File

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

View File

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

View File

@ -1,16 +1,16 @@
const BaseDocument = require('./document'); import frappe from 'frappe';
const frappe = require('frappe'); import { indicators as indicatorColor } from '../../src/colors';
const model = require('./index'); import Document from './document';
const { indicators: indicatorColor } = require('../../src/colors'); import model from './index';
module.exports = class BaseMeta extends BaseDocument { export default class BaseMeta extends Document {
constructor(data) { constructor(data) {
if (data.basedOn) { if (data.basedOn) {
let config = frappe.models[data.basedOn]; let config = frappe.models[data.basedOn];
Object.assign(data, config, { Object.assign(data, config, {
name: data.name, name: data.name,
label: data.label, label: data.label,
filters: data.filters filters: data.filters,
}); });
} }
super(data); super(data);
@ -30,7 +30,7 @@ module.exports = class BaseMeta extends BaseDocument {
processFields() { processFields() {
// add name field // 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 = [ this.fields = [
{ {
label: frappe.t`ID`, label: frappe.t`ID`,
@ -42,7 +42,7 @@ module.exports = class BaseMeta extends BaseDocument {
].concat(this.fields); ].concat(this.fields);
} }
this.fields = this.fields.map(df => { this.fields = this.fields.map((df) => {
// name field is always required // name field is always required
if (df.fieldname === 'name') { if (df.fieldname === 'name') {
df.required = 1; df.required = 1;
@ -75,7 +75,7 @@ module.exports = class BaseMeta extends BaseDocument {
* dataFields = meta.getFieldsWith({ fieldtype: 'Data' }) * dataFields = meta.getFieldsWith({ fieldtype: 'Data' })
*/ */
getFieldsWith(filters) { getFieldsWith(filters) {
return this.fields.filter(df => { return this.fields.filter((df) => {
let match = true; let match = true;
for (const key in filters) { for (const key in filters) {
const value = filters[key]; const value = filters[key];
@ -93,7 +93,7 @@ module.exports = class BaseMeta extends BaseDocument {
getTableFields() { getTableFields() {
if (this._tableFields === undefined) { if (this._tableFields === undefined) {
this._tableFields = this.fields.filter( this._tableFields = this.fields.filter(
field => field.fieldtype === 'Table' (field) => field.fieldtype === 'Table'
); );
} }
return this._tableFields; return this._tableFields;
@ -101,7 +101,7 @@ module.exports = class BaseMeta extends BaseDocument {
getFormulaFields() { getFormulaFields() {
if (this._formulaFields === undefined) { if (this._formulaFields === undefined) {
this._formulaFields = this.fields.filter(field => field.formula); this._formulaFields = this.fields.filter((field) => field.formula);
} }
return this._formulaFields; return this._formulaFields;
} }
@ -141,7 +141,7 @@ module.exports = class BaseMeta extends BaseDocument {
this._validFields = []; this._validFields = [];
this._validFieldsWithChildren = []; this._validFieldsWithChildren = [];
const _add = field => { const _add = (field) => {
this._validFields.push(field); this._validFields.push(field);
this._validFieldsWithChildren.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 // standard fields
for (let field of model.commonFields) { for (let field of model.commonFields) {
@ -176,7 +176,7 @@ module.exports = class BaseMeta extends BaseDocument {
_add({ _add({
fieldtype: 'Check', fieldtype: 'Check',
fieldname: 'submitted', fieldname: 'submitted',
label: frappe.t`Submitted` label: frappe.t`Submitted`,
}); });
} }
@ -241,8 +241,8 @@ module.exports = class BaseMeta extends BaseDocument {
this._keywordFields = this.keywordFields; this._keywordFields = this.keywordFields;
if (!(this._keywordFields && this._keywordFields.length && this.fields)) { if (!(this._keywordFields && this._keywordFields.length && this.fields)) {
this._keywordFields = this.fields this._keywordFields = this.fields
.filter(field => field.fieldtype !== 'Table' && field.required) .filter((field) => field.fieldtype !== 'Table' && field.required)
.map(field => field.fieldname); .map((field) => field.fieldname);
} }
if (!(this._keywordFields && this._keywordFields.length)) { if (!(this._keywordFields && this._keywordFields.length)) {
this._keywordFields = ['name']; this._keywordFields = ['name'];
@ -253,7 +253,7 @@ module.exports = class BaseMeta extends BaseDocument {
getQuickEditFields() { getQuickEditFields() {
if (this.quickEditFields) { if (this.quickEditFields) {
return this.quickEditFields.map(fieldname => this.getField(fieldname)); return this.quickEditFields.map((fieldname) => this.getField(fieldname));
} }
return this.getFieldsWith({ required: 1 }); return this.getFieldsWith({ required: 1 });
} }
@ -271,10 +271,12 @@ module.exports = class BaseMeta extends BaseDocument {
// values given as string // values given as string
validValues = options.split('\n'); validValues = options.split('\n');
} }
if (typeof options[0] === 'object') { if (typeof options[0] === 'object') {
// options as array of {label, value} pairs // options as array of {label, value} pairs
validValues = options.map(o => o.value); validValues = options.map((o) => o.value);
} }
if (!validValues.includes(value)) { if (!validValues.includes(value)) {
throw new frappe.errors.ValueError( throw new frappe.errors.ValueError(
// prettier-ignore // prettier-ignore
@ -287,7 +289,7 @@ module.exports = class BaseMeta extends BaseDocument {
async trigger(event, params = {}) { async trigger(event, params = {}) {
Object.assign(params, { Object.assign(params, {
doc: this, doc: this,
name: event name: event,
}); });
await super.trigger(event, params); await super.trigger(event, params);
@ -300,8 +302,8 @@ module.exports = class BaseMeta extends BaseDocument {
key: 'submitted', key: 'submitted',
colors: { colors: {
0: indicatorColor.GRAY, 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'); import frappe from 'frappe';
const frappe = require('frappe'); import { getRandomString } from 'frappe/utils';
const { getRandomString } = require('frappe/utils');
module.exports = { export async function isNameAutoSet(doctype) {
async isNameAutoSet(doctype) { const doc = frappe.getNewDoc(doctype);
const doc = frappe.getNewDoc(doctype); if (doc.meta.naming === 'autoincrement') {
if (doc.meta.naming === 'autoincrement') { return true;
return true; }
}
if (!doc.meta.settings) {
return false;
}
const { numberSeries } = await doc.getSettings();
if (numberSeries) {
return true;
}
if (!doc.meta.settings) {
return false; return false;
}, }
async setName(doc) { const { numberSeries } = await doc.getSettings();
if (frappe.isServer) { if (numberSeries) {
// if is server, always name again if autoincrement or other return true;
if (doc.meta.naming === 'autoincrement') { }
doc.name = await this.getNextId(doc.doctype);
return;
}
// Current, per doc number series return false;
if (doc.numberSeries) { }
doc.name = await this.getSeriesNext(doc.numberSeries, doc.doctype);
return;
}
// Legacy, using doc settings for number series export async function setName(doc) {
if (doc.meta.settings) { if (frappe.isServer) {
const numberSeries = (await doc.getSettings()).numberSeries; // if is server, always name again if autoincrement or other
if (!numberSeries) { if (doc.meta.naming === 'autoincrement') {
return; doc.name = await getNextId(doc.doctype);
}
doc.name = await this.getSeriesNext(numberSeries, doc.doctype);
return;
}
}
if (doc.name) {
return; return;
} }
// name === doctype for Single // Current, per doc number series
if (doc.meta.isSingle) { if (doc.numberSeries) {
doc.name = doc.meta.name; doc.name = await getSeriesNext(doc.numberSeries, doc.doctype);
return; return;
} }
// assign a random name by default // Legacy, using doc settings for number series
// override doc to set a name if (doc.meta.settings) {
if (!doc.name) { const numberSeries = (await doc.getSettings()).numberSeries;
doc.name = getRandomString(); if (!numberSeries) {
} return;
},
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;
} }
await this.createNumberSeries(prefix, doctype); doc.name = await getSeriesNext(numberSeries, doc.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) {
return; return;
} }
}
const series = frappe.newDoc({ if (doc.name) {
doctype: 'NumberSeries', return;
name: prefix, }
start,
referenceType,
});
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 = ( const patchesAlreadyRun = (
await frappe.db.knex('PatchRun').select('name') await frappe.db.knex('PatchRun').select('name')
).map(({ name }) => name); ).map(({ name }) => name);
@ -12,7 +12,7 @@ module.exports = async function runPatches(patchList) {
await runPatch(patch); await runPatch(patch);
} }
}; }
async function runPatch({ patchName, patchFunction }) { async function runPatch({ patchName, patchFunction }) {
try { try {

View File

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

View File

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

View File

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

View File

@ -1,8 +1,8 @@
const luxon = require('luxon'); import frappe from 'frappe';
const frappe = require('frappe'); import { DateTime } from 'luxon';
const { DEFAULT_DISPLAY_PRECISION, DEFAULT_LOCALE } = require('./consts'); import { DEFAULT_DISPLAY_PRECISION, DEFAULT_LOCALE } from './consts';
module.exports = { export default {
format(value, df, doc) { format(value, df, doc) {
if (!df) { if (!df) {
return value; return value;
@ -25,10 +25,10 @@ module.exports = {
if (typeof value === 'string') { if (typeof value === 'string') {
// ISO String // ISO String
value = luxon.DateTime.fromISO(value); value = DateTime.fromISO(value);
} else if (Object.prototype.toString.call(value) === '[object Date]') { } else if (Object.prototype.toString.call(value) === '[object Date]') {
// JS Date // JS Date
value = luxon.DateTime.fromJSDate(value); value = DateTime.fromJSDate(value);
} }
value = value.toFormat(dateFormat); 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 frappe from 'frappe';
import naming from 'frappe/model/naming'; import { createNumberSeries } from 'frappe/model/naming';
import GSTR3BServer from '../models/doctype/GSTR3B/GSTR3BServer.js'; import GSTR3BServer from '../models/doctype/GSTR3B/GSTR3BServer.js';
import JournalEntryServer from '../models/doctype/JournalEntry/JournalEntryServer.js'; import JournalEntryServer from '../models/doctype/JournalEntry/JournalEntryServer.js';
import PartyServer from '../models/doctype/Party/PartyServer.js'; import PartyServer from '../models/doctype/Party/PartyServer.js';
@ -20,10 +20,10 @@ export default async function postStart() {
frappe.metaCache = {}; frappe.metaCache = {};
// init naming series if missing // init naming series if missing
await naming.createNumberSeries('SINV-', 'SalesInvoice'); await createNumberSeries('SINV-', 'SalesInvoice');
await naming.createNumberSeries('PINV-', 'PurchaseInvoice'); await createNumberSeries('PINV-', 'PurchaseInvoice');
await naming.createNumberSeries('PAY-', 'Payment'); await createNumberSeries('PAY-', 'Payment');
await naming.createNumberSeries('JV-', 'JournalEntry'); await createNumberSeries('JV-', 'JournalEntry');
// await naming.createNumberSeries('QTN-', 'QuotationSettings'); // await naming.createNumberSeries('QTN-', 'QuotationSettings');
// await naming.createNumberSeries('SO-', 'SalesOrderSettings'); // await naming.createNumberSeries('SO-', 'SalesOrderSettings');
// await naming.createNumberSeries('OF-', 'FulfillmentSettings'); // await naming.createNumberSeries('OF-', 'FulfillmentSettings');

View File

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

View File

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

View File

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

View File

@ -89,25 +89,23 @@
</template> </template>
<script> <script>
import frappe from 'frappe';
import TwoColumnForm from '@/components/TwoColumnForm';
import FormControl from '@/components/Controls/FormControl'; 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 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 { 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 { export default {
name: 'SetupWizard', name: 'SetupWizard',
@ -209,7 +207,7 @@ export default {
const filePath = config.get('lastSelectedFilePath'); const filePath = config.get('lastSelectedFilePath');
renameDbFile(filePath); renameDbFile(filePath);
purgeCache(); await purgeCache();
const { connectionSuccess, reason } = await connectToLocalDatabase( const { connectionSuccess, reason } = await connectToLocalDatabase(
filePath filePath