feat: add run mode, add tests, various fixes

This commit is contained in:
casesolved-co-uk 2021-03-19 23:05:19 +00:00
parent ef8ab135c9
commit dba4b3cd13
3 changed files with 166 additions and 18 deletions

View File

@ -92,11 +92,8 @@ class TaxReport {
freeze: true
}).then((r) => {
const data = JSON.parse(r.message[report_name]['json']);
if (data && data['sections']) {
this.sections = data['sections'];
} else {
this.sections = {};
}
this.sections = data.sections || {};
this.controls['show_detail'].set_input(data.show_detail);
this.set_section();
})
this.loaded = 1;
@ -107,8 +104,11 @@ class TaxReport {
args: {
reference_report: 'Tax Detail',
report_name: this.qr.report_name,
columns: this.qr.get_visible_columns(),
sections: this.sections
data: {
columns: this.qr.get_visible_columns(),
sections: this.sections,
show_detail: this.controls['show_detail'].get_input_value()
}
},
freeze: true
}).then((r) => {
@ -233,7 +233,9 @@ class TaxReport {
reload() {
// Reloads the data. When the datatable is reloaded, load_report()
// will be run by the after_datatable_render event.
// TODO: why does this trigger multiple reloads?
this.qr.refresh();
this.show_help();
if (this.edit_mode()) {
this.reload_filter();
} else {
@ -354,6 +356,12 @@ class TaxReport {
this.save_report();
}
});
controls['show_detail'] = this.page.add_field({
label: __('Show Detail'),
fieldtype: 'Check',
fieldname: 'show_detail',
default: 1
});
this.controls = controls;
this.set_value_options();
this.get_filter_controls();

View File

@ -54,10 +54,89 @@ def execute(filters=None):
order by ge.posting_date, ge.voucher_no
""".format(fieldstr=fieldstr), filters, as_dict=1)
gl_entries = modify_report_data(gl_entries)
report_data = modify_report_data(gl_entries)
summary = None
if filters['mode'] == 'run' and filters['report_name'] != 'Tax Detail':
report_data, summary = run_report(filters['report_name'], report_data)
return get_columns(fieldlist), gl_entries
# return columns, data, message, chart, report_summary
return get_columns(fieldlist), report_data, None, None, summary
def run_report(report_name, data):
"Applies the sections and filters saved in the custom report"
report_config = json.loads(frappe.get_doc('Report', report_name).json)
# Columns indexed from 1 wrt colno
columns = report_config.get('columns')
sections = report_config.get('sections', {})
show_detail = report_config.get('show_detail', 1)
new_data = []
summary = []
for section_name, section in sections.items():
section_total = 0.0
for filt_name, filt in section.items():
value_field = filt['fieldname']
rmidxs = []
for colno, filter_string in filt['filters'].items():
filter_field = columns[int(colno) - 1]['fieldname']
for i, row in enumerate(data):
if not filter_match(row[filter_field], filter_string):
rmidxs += [i]
rows = [row for i, row in enumerate(data) if i not in rmidxs]
section_total += subtotal(rows, value_field)
if show_detail: new_data += rows
new_data += [ {columns[1]['fieldname']: section_name, columns[2]['fieldname']: section_total} ]
summary += [ {'label': section_name, 'datatype': 'Currency', 'value': section_total} ]
if show_detail: new_data += [ {} ]
return new_data if new_data else data, summary
def filter_match(value, string):
"Approximation to datatable filters"
import datetime
if string == '': return True
if value is None: value = -999999999999999
elif isinstance(value, datetime.date): return True
if isinstance(value, str):
value = value.lower()
string = string.lower()
if string[0] == '<': return True if string[1:].strip() else False
elif string[0] == '>': return False if string[1:].strip() else True
elif string[0] == '=': return string[1:] in value if string[1:] else False
elif string[0:2] == '!=': return string[2:] not in value
elif len(string.split(':')) == 2:
pre, post = string.split(':')
return (True if not pre.strip() and post.strip() in value else False)
else:
return string in value
else:
if string[0] in ['<', '>', '=']:
operator = string[0]
if operator == '=': operator = '=='
string = string[1:].strip()
elif string[0:2] == '!=':
operator = '!='
string = string[2:].strip()
elif len(string.split(':')) == 2:
pre, post = string.split(':')
try:
return (True if float(pre) <= value and float(post) >= value else False)
except ValueError:
return (False if pre.strip() else True)
else:
return string in str(value)
try:
num = float(string) if string.strip() else 0
return eval(f'{value} {operator} {num}')
except ValueError:
if operator == '<': return True
return False
def subtotal(data, field):
subtotal = 0.0
for row in data:
subtotal += row[field]
return subtotal
abbrev = lambda dt: ''.join(l[0].lower() for l in dt.split(' ')) + '.'
doclist = lambda dt, dfs: [abbrev(dt) + f for f in dfs]
@ -148,24 +227,18 @@ def get_custom_reports(name=None):
return reports_dict
@frappe.whitelist()
def save_custom_report(reference_report, report_name, columns, sections):
import pymysql
def save_custom_report(reference_report, report_name, data):
if reference_report != 'Tax Detail':
frappe.throw(_("The wrong report is referenced."))
if report_name == 'Tax Detail':
frappe.throw(_("The parent report cannot be overwritten."))
data = {
'columns': json.loads(columns),
'sections': json.loads(sections)
}
doc = {
'doctype': 'Report',
'report_name': report_name,
'is_standard': 'No',
'module': 'Accounts',
'json': json.dumps(data, separators=(',', ':'))
'json': data
}
doc.update(custom_report_dict)
@ -173,7 +246,7 @@ def save_custom_report(reference_report, report_name, columns, sections):
newdoc = frappe.get_doc(doc)
newdoc.insert()
frappe.msgprint(_("Report created successfully"))
except (frappe.exceptions.DuplicateEntryError, pymysql.err.IntegrityError):
except frappe.exceptions.DuplicateEntryError:
dbdoc = frappe.get_doc('Report', report_name)
dbdoc.update(doc)
dbdoc.save()

View File

@ -0,0 +1,67 @@
from __future__ import unicode_literals
import frappe, unittest, datetime
from frappe.utils import getdate
from .tax_detail import execute, filter_match
class TestTaxDetail(unittest.TestCase):
def setup(self):
pass
def test_filter_match(self):
# None - treated as -inf number except range
self.assertTrue(filter_match(None, '!='))
self.assertTrue(filter_match(None, '<'))
self.assertTrue(filter_match(None, '<jjj'))
self.assertTrue(filter_match(None, ' : '))
self.assertTrue(filter_match(None, ':56'))
self.assertTrue(filter_match(None, ':de'))
self.assertFalse(filter_match(None, '3.4'))
self.assertFalse(filter_match(None, '='))
self.assertFalse(filter_match(None, '=3.4'))
self.assertFalse(filter_match(None, '>3.4'))
self.assertFalse(filter_match(None, ' <'))
self.assertFalse(filter_match(None, 'ew'))
self.assertFalse(filter_match(None, ' '))
self.assertFalse(filter_match(None, ' f :'))
# Numbers
self.assertTrue(filter_match(3.4, '3.4'))
self.assertTrue(filter_match(3.4, '.4'))
self.assertTrue(filter_match(3.4, '3'))
self.assertTrue(filter_match(-3.4, '< -3'))
self.assertTrue(filter_match(-3.4, '> -4'))
self.assertTrue(filter_match(3.4, '= 3.4 '))
self.assertTrue(filter_match(3.4, '!=4.5'))
self.assertTrue(filter_match(3.4, ' 3 : 4 '))
self.assertTrue(filter_match(0.0, ' : '))
self.assertFalse(filter_match(3.4, '=4.5'))
self.assertFalse(filter_match(3.4, ' = 3.4 '))
self.assertFalse(filter_match(3.4, '!=3.4'))
self.assertFalse(filter_match(3.4, '>6'))
self.assertFalse(filter_match(3.4, '<-4.5'))
self.assertFalse(filter_match(3.4, '4.5'))
self.assertFalse(filter_match(3.4, '5:9'))
# Strings
self.assertTrue(filter_match('ACC-SINV-2021-00001', 'SINV'))
self.assertTrue(filter_match('ACC-SINV-2021-00001', 'sinv'))
self.assertTrue(filter_match('ACC-SINV-2021-00001', '-2021'))
self.assertTrue(filter_match(' ACC-SINV-2021-00001', ' acc'))
self.assertTrue(filter_match('ACC-SINV-2021-00001', '=2021'))
self.assertTrue(filter_match('ACC-SINV-2021-00001', '!=zz'))
self.assertTrue(filter_match('ACC-SINV-2021-00001', '< zzz '))
self.assertTrue(filter_match('ACC-SINV-2021-00001', ' : sinv '))
self.assertFalse(filter_match('ACC-SINV-2021-00001', ' sinv :'))
self.assertFalse(filter_match('ACC-SINV-2021-00001', ' acc'))
self.assertFalse(filter_match('ACC-SINV-2021-00001', '= 2021 '))
self.assertFalse(filter_match('ACC-SINV-2021-00001', '!=sinv'))
self.assertFalse(filter_match('ACC-SINV-2021-00001', ' >'))
self.assertFalse(filter_match('ACC-SINV-2021-00001', '>aa'))
self.assertFalse(filter_match('ACC-SINV-2021-00001', ' <'))
self.assertFalse(filter_match('ACC-SINV-2021-00001', '< '))
self.assertFalse(filter_match('ACC-SINV-2021-00001', ' ='))
self.assertFalse(filter_match('ACC-SINV-2021-00001', '='))
# Date - always match
self.assertTrue(filter_match(datetime.date(2021, 3, 19), ' kdsjkldfs '))