mirror of
https://github.com/frappe/books.git
synced 2024-11-10 07:40:55 +00:00
added new controls float, currency and added formatting
This commit is contained in:
parent
a53cf5a264
commit
ec0ebd95a5
@ -51,7 +51,9 @@ module.exports = class Desk {
|
||||
|
||||
frappe.router.add('new/:doctype', async (params) => {
|
||||
let doc = await frappe.get_new_doc(params.doctype);
|
||||
frappe.router.set_route('edit', doc.doctype, doc.name);
|
||||
// unset the name, its local
|
||||
await frappe.router.set_route('edit', doc.doctype, doc.name);
|
||||
await doc.set('name', '');
|
||||
});
|
||||
|
||||
}
|
||||
|
@ -15,11 +15,6 @@ class BaseControl {
|
||||
|
||||
bind(doc) {
|
||||
this.doc = doc;
|
||||
|
||||
this.doc.add_handler(this.fieldname, () => {
|
||||
this.set_doc_value();
|
||||
});
|
||||
|
||||
this.set_doc_value();
|
||||
}
|
||||
|
||||
@ -56,6 +51,7 @@ class BaseControl {
|
||||
|
||||
make_input() {
|
||||
this.input = frappe.ui.add('input', 'form-control', this.form_group);
|
||||
this.input.setAttribute('autocomplete', 'off');
|
||||
}
|
||||
|
||||
set_input_name() {
|
||||
@ -70,16 +66,24 @@ class BaseControl {
|
||||
}
|
||||
|
||||
set_input_value(value) {
|
||||
this.input.value = this.format(value);
|
||||
}
|
||||
|
||||
format(value) {
|
||||
if (value === undefined || value === null) {
|
||||
value = '';
|
||||
}
|
||||
this.input.value = value;
|
||||
return value;
|
||||
}
|
||||
|
||||
async get_input_value() {
|
||||
async get_parsed_value() {
|
||||
return await this.parse(this.input.value);
|
||||
}
|
||||
|
||||
get_input_value() {
|
||||
return this.input.value;
|
||||
}
|
||||
|
||||
async parse(value) {
|
||||
return value;
|
||||
}
|
||||
@ -93,10 +97,12 @@ class BaseControl {
|
||||
}
|
||||
|
||||
async handle_change(e) {
|
||||
let value = await this.get_input_value();
|
||||
let value = await this.parse(this.get_input_value());
|
||||
value = await this.validate(value);
|
||||
if (this.doc[this.fieldname] !== value) {
|
||||
await this.doc.set(this.fieldname, value);
|
||||
}
|
||||
}
|
||||
|
||||
disable() {
|
||||
this.input.setAttribute('disabled', 'disabled');
|
||||
|
13
client/view/controls/currency.js
Normal file
13
client/view/controls/currency.js
Normal file
@ -0,0 +1,13 @@
|
||||
const FloatControl = require('./float');
|
||||
const frappe = require('frappejs');
|
||||
|
||||
class CurrencyControl extends FloatControl {
|
||||
parse(value) {
|
||||
return frappe.parse_number(value);
|
||||
}
|
||||
format(value) {
|
||||
return frappe.format_number(value);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = CurrencyControl;
|
11
client/view/controls/float.js
Normal file
11
client/view/controls/float.js
Normal file
@ -0,0 +1,11 @@
|
||||
const BaseControl = require('./base');
|
||||
|
||||
class FloatControl extends BaseControl {
|
||||
make() {
|
||||
super.make();
|
||||
this.input.setAttribute('type', 'text');
|
||||
this.input.classList.add('text-right');
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = FloatControl;
|
@ -2,10 +2,11 @@ const control_classes = {
|
||||
Data: require('./data'),
|
||||
Text: require('./text'),
|
||||
Select: require('./select'),
|
||||
Link: require('./link')
|
||||
Link: require('./link'),
|
||||
Float: require('./float'),
|
||||
Currency: require('./currency')
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
get_control_class(fieldtype) {
|
||||
return control_classes[fieldtype];
|
||||
|
@ -80,6 +80,14 @@ module.exports = class BaseForm {
|
||||
for (let control of this.controls_list) {
|
||||
control.bind(this.doc);
|
||||
}
|
||||
|
||||
// refresh value in control
|
||||
this.doc.add_handler('change', (params) => {
|
||||
let control = this.controls[params.fieldname];
|
||||
if (control && control.get_input_value() !== control.format(params.fieldname)) {
|
||||
control.set_doc_value();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async submit() {
|
||||
|
@ -119,7 +119,7 @@ module.exports = class BaseList {
|
||||
}
|
||||
|
||||
get_row_html(data) {
|
||||
return `<a href="#edit/${this.doctype}/${data.name}>${data.name}</a>`;
|
||||
return `<a href="#edit/${this.doctype}/${data.name}">${data.name}</a>`;
|
||||
}
|
||||
|
||||
get_row(i) {
|
||||
|
@ -22,13 +22,14 @@ module.exports = class BaseDocument {
|
||||
this.handlers[key].push(method || key);
|
||||
}
|
||||
|
||||
get(key) {
|
||||
return this[key];
|
||||
get(fieldname) {
|
||||
return this[fieldname];
|
||||
}
|
||||
|
||||
set(key, value) {
|
||||
this.validate_field(key, value);
|
||||
this[key] = value;
|
||||
// set value and trigger change
|
||||
async set(fieldname, value) {
|
||||
this[fieldname] = await this.validate_field(fieldname, value);
|
||||
await this.trigger('change', {doc: this, fieldname: fieldname, value: value});
|
||||
}
|
||||
|
||||
set_name() {
|
||||
@ -69,11 +70,12 @@ module.exports = class BaseDocument {
|
||||
}
|
||||
}
|
||||
|
||||
validate_field (key, value) {
|
||||
async validate_field (key, value) {
|
||||
let df = this.meta.get_field(key);
|
||||
if (df.fieldtype=='Select') {
|
||||
this.meta.validate_select(df, value);
|
||||
return this.meta.validate_select(df, value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
get_valid_dict() {
|
||||
@ -110,9 +112,11 @@ module.exports = class BaseDocument {
|
||||
this.set_name();
|
||||
this.set_standard_values();
|
||||
this.set_keywords();
|
||||
await this.trigger('validate', 'before_insert');
|
||||
await this.trigger('validate');
|
||||
await this.trigger('before_insert');
|
||||
await frappe.db.insert(this.doctype, this.get_valid_dict());
|
||||
await this.trigger('after_insert', 'after_save');
|
||||
await this.trigger('after_insert');
|
||||
await this.trigger('after_save');
|
||||
}
|
||||
|
||||
async delete() {
|
||||
@ -121,15 +125,13 @@ module.exports = class BaseDocument {
|
||||
await this.trigger('after_delete');
|
||||
}
|
||||
|
||||
async trigger() {
|
||||
for(var key of arguments) {
|
||||
async trigger(key, params) {
|
||||
if (this.handlers[key]) {
|
||||
for (let method of this.handlers[key]) {
|
||||
if (typeof method === 'string') {
|
||||
await this[method]();
|
||||
await this[method](params);
|
||||
} else {
|
||||
await method(this);
|
||||
}
|
||||
await method(params);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -138,9 +140,11 @@ module.exports = class BaseDocument {
|
||||
async update() {
|
||||
this.set_standard_values();
|
||||
this.set_keywords();
|
||||
await this.trigger('validate', 'before_update');
|
||||
await this.trigger('validate');
|
||||
await this.trigger('before_update');
|
||||
await frappe.db.update(this.doctype, this.get_valid_dict());
|
||||
await this.trigger('after_update', 'after_save');
|
||||
await this.trigger('after_update');
|
||||
await this.trigger('after_save');
|
||||
return this;
|
||||
}
|
||||
};
|
@ -85,10 +85,10 @@ module.exports = class BaseMeta extends BaseDocument {
|
||||
if (!options.includes(value)) {
|
||||
throw new frappe.errors.ValueError(`${value} must be one of ${options.join(", ")}`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
async trigger(key, event = {}) {
|
||||
|
||||
Object.assign(event, {
|
||||
doc: this,
|
||||
name: key
|
||||
|
@ -6,7 +6,8 @@ class ToDoList extends BaseList {
|
||||
return ['name', 'subject', 'status'];
|
||||
}
|
||||
get_row_html(data) {
|
||||
return `<a href="#edit/todo/${data.name}">${data.subject}</a>`;
|
||||
let symbol = data.status=="Closed" ? "✔" : "";
|
||||
return `<a href="#edit/todo/${data.name}">${symbol} ${data.subject}</a>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
35
tests/test_utils.js
Normal file
35
tests/test_utils.js
Normal file
@ -0,0 +1,35 @@
|
||||
const utils = require('frappejs/utils');
|
||||
const assert = require('assert');
|
||||
|
||||
describe('Number Formatting', () => {
|
||||
it('should format numbers', () => {
|
||||
assert.equal(utils.format_number(100), '100.00');
|
||||
assert.equal(utils.format_number(1000), '1,000.00');
|
||||
assert.equal(utils.format_number(10000), '10,000.00');
|
||||
assert.equal(utils.format_number(100000), '100,000.00');
|
||||
assert.equal(utils.format_number(1000000), '1,000,000.00');
|
||||
assert.equal(utils.format_number(100.1234), '100.12');
|
||||
assert.equal(utils.format_number(1000.1234), '1,000.12');
|
||||
});
|
||||
|
||||
it('should parse numbers', () => {
|
||||
assert.equal(utils.parse_number('100.00'), 100);
|
||||
assert.equal(utils.parse_number('1,000.00'), 1000);
|
||||
assert.equal(utils.parse_number('10,000.00'), 10000);
|
||||
assert.equal(utils.parse_number('100,000.00'), 100000);
|
||||
assert.equal(utils.parse_number('1,000,000.00'), 1000000);
|
||||
assert.equal(utils.parse_number('100.1234'), 100.1234);
|
||||
assert.equal(utils.parse_number('1,000.1234'), 1000.1234);
|
||||
});
|
||||
|
||||
it('should format lakhs and crores', () => {
|
||||
assert.equal(utils.format_number(100, '#,##,###.##'), '100.00');
|
||||
assert.equal(utils.format_number(1000, '#,##,###.##'), '1,000.00');
|
||||
assert.equal(utils.format_number(10000, '#,##,###.##'), '10,000.00');
|
||||
assert.equal(utils.format_number(100000, '#,##,###.##'), '1,00,000.00');
|
||||
assert.equal(utils.format_number(1000000, '#,##,###.##'), '10,00,000.00');
|
||||
assert.equal(utils.format_number(10000000, '#,##,###.##'), '1,00,00,000.00');
|
||||
assert.equal(utils.format_number(100.1234, '#,##,###.##'), '100.12');
|
||||
assert.equal(utils.format_number(1000.1234, '#,##,###.##'), '1,000.12');
|
||||
});
|
||||
});
|
@ -1,4 +1,8 @@
|
||||
module.exports = {
|
||||
let utils = {};
|
||||
|
||||
Object.assign(utils, require('./number_format'));
|
||||
|
||||
Object.assign(utils, {
|
||||
slug(text) {
|
||||
return text.toLowerCase().replace(/ /g, '_');
|
||||
},
|
||||
@ -17,4 +21,6 @@ module.exports = {
|
||||
setTimeout(resolve, seconds * 1000);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = utils;
|
104
utils/number_format.js
Normal file
104
utils/number_format.js
Normal file
@ -0,0 +1,104 @@
|
||||
const number_formats = {
|
||||
"#,###.##": { fraction_sep: ".", group_sep: ",", precision: 2 },
|
||||
"#.###,##": { fraction_sep: ",", group_sep: ".", precision: 2 },
|
||||
"# ###.##": { fraction_sep: ".", group_sep: " ", precision: 2 },
|
||||
"# ###,##": { fraction_sep: ",", group_sep: " ", precision: 2 },
|
||||
"#'###.##": { fraction_sep: ".", group_sep: "'", precision: 2 },
|
||||
"#, ###.##": { fraction_sep: ".", group_sep: ", ", precision: 2 },
|
||||
"#,##,###.##": { fraction_sep: ".", group_sep: ",", precision: 2 },
|
||||
"#,###.###": { fraction_sep: ".", group_sep: ",", precision: 3 },
|
||||
"#.###": { fraction_sep: "", group_sep: ".", precision: 0 },
|
||||
"#,###": { fraction_sep: "", group_sep: ",", precision: 0 },
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
// parse a formatted number string
|
||||
// from "4,555,000.34" -> 4555000.34
|
||||
parse_number(number, format='#,###.##') {
|
||||
if (!number) {
|
||||
return 0;
|
||||
}
|
||||
if (typeof number === 'number') {
|
||||
return number;
|
||||
}
|
||||
const info = this.get_format_info(format);
|
||||
return parseFloat(this.remove_separator(number, info.group_sep));
|
||||
},
|
||||
|
||||
format_number(number, format = '#,###.##', precision = null) {
|
||||
if (!number) {
|
||||
number = 0;
|
||||
}
|
||||
let info = this.get_format_info(format);
|
||||
if (precision) {
|
||||
info.precision = precision;
|
||||
}
|
||||
let is_negative = false;
|
||||
|
||||
number = this.parse_number(number);
|
||||
if (number < 0) {
|
||||
is_negative = true;
|
||||
}
|
||||
number = Math.abs(number);
|
||||
number = number.toFixed(info.precision);
|
||||
|
||||
var parts = number.split('.');
|
||||
|
||||
// get group position and parts
|
||||
var group_position = info.group_sep ? 3 : 0;
|
||||
|
||||
if (group_position) {
|
||||
var integer = parts[0];
|
||||
var str = '';
|
||||
var offset = integer.length % group_position;
|
||||
for (var i = integer.length; i >= 0; i--) {
|
||||
var l = this.remove_separator(str, info.group_sep).length;
|
||||
if (format == "#,##,###.##" && str.indexOf(",") != -1) { // INR
|
||||
group_position = 2;
|
||||
l += 1;
|
||||
}
|
||||
|
||||
str += integer.charAt(i);
|
||||
|
||||
if (l && !((l + 1) % group_position) && i != 0) {
|
||||
str += info.group_sep;
|
||||
}
|
||||
}
|
||||
parts[0] = str.split("").reverse().join("");
|
||||
}
|
||||
if (parts[0] + "" == "") {
|
||||
parts[0] = "0";
|
||||
}
|
||||
|
||||
// join decimal
|
||||
parts[1] = (parts[1] && info.fraction_sep) ? (info.fraction_sep + parts[1]) : "";
|
||||
|
||||
// join
|
||||
return (is_negative ? "-" : "") + parts[0] + parts[1];
|
||||
},
|
||||
|
||||
get_format_info(format) {
|
||||
let format_info = number_formats[format];
|
||||
|
||||
if (!format_info) {
|
||||
throw `Unknown number format "${format}"`;
|
||||
}
|
||||
|
||||
return format_info;
|
||||
},
|
||||
|
||||
round(num, precision) {
|
||||
var is_negative = num < 0 ? true : false;
|
||||
var d = parseInt(precision || 0);
|
||||
var m = Math.pow(10, d);
|
||||
var n = +(d ? Math.abs(num) * m : Math.abs(num)).toFixed(8); // Avoid rounding errors
|
||||
var i = Math.floor(n), f = n - i;
|
||||
var r = ((!precision && f == 0.5) ? ((i % 2 == 0) ? i : i + 1) : Math.round(n));
|
||||
r = d ? r / m : r;
|
||||
return is_negative ? -r : r;
|
||||
},
|
||||
|
||||
remove_separator(text, sep) {
|
||||
return text.replace(new RegExp(sep === "." ? "\\." : sep, "g"), '');
|
||||
}
|
||||
};
|
Loading…
Reference in New Issue
Block a user