feat: add run mode, add tests, various fixes
This commit is contained in:
parent
ef8ab135c9
commit
dba4b3cd13
@ -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();
|
||||
|
@ -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()
|
||||
|
67
erpnext/accounts/report/tax_detail/test_tax_detail.py
Normal file
67
erpnext/accounts/report/tax_detail/test_tax_detail.py
Normal 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 '))
|
Loading…
x
Reference in New Issue
Block a user