Merge pull request #25254 from CaseSolvedUK/taxdetail-v13
feat: Tax Detail Report
This commit is contained in:
commit
d15d0d8568
0
erpnext/accounts/report/tax_detail/__init__.py
Normal file
0
erpnext/accounts/report/tax_detail/__init__.py
Normal file
451
erpnext/accounts/report/tax_detail/tax_detail.js
Normal file
451
erpnext/accounts/report/tax_detail/tax_detail.js
Normal file
@ -0,0 +1,451 @@
|
||||
// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
// Contributed by Case Solved and sponsored by Nulight Studios
|
||||
/* eslint-disable */
|
||||
|
||||
frappe.provide('frappe.query_reports');
|
||||
|
||||
frappe.query_reports["Tax Detail"] = {
|
||||
filters: [
|
||||
{
|
||||
fieldname: "company",
|
||||
label: __("Company"),
|
||||
fieldtype: "Link",
|
||||
options: "Company",
|
||||
default: frappe.defaults.get_user_default("company"),
|
||||
reqd: 1
|
||||
},
|
||||
{
|
||||
fieldname: "from_date",
|
||||
label: __("From Date"),
|
||||
fieldtype: "Date",
|
||||
default: frappe.datetime.month_start(frappe.datetime.get_today()),
|
||||
reqd: 1,
|
||||
width: "60px"
|
||||
},
|
||||
{
|
||||
fieldname: "to_date",
|
||||
label: __("To Date"),
|
||||
fieldtype: "Date",
|
||||
default: frappe.datetime.month_end(frappe.datetime.get_today()),
|
||||
reqd: 1,
|
||||
width: "60px"
|
||||
},
|
||||
{
|
||||
fieldname: "report_name",
|
||||
label: __("Report Name"),
|
||||
fieldtype: "Read Only",
|
||||
default: frappe.query_report.report_name,
|
||||
hidden: 1,
|
||||
reqd: 1
|
||||
},
|
||||
{
|
||||
fieldname: "mode",
|
||||
label: __("Mode"),
|
||||
fieldtype: "Read Only",
|
||||
default: "edit",
|
||||
hidden: 1,
|
||||
reqd: 1
|
||||
}
|
||||
],
|
||||
onload: function onload(report) {
|
||||
// Remove Add Column and Save from menu
|
||||
report.page.add_inner_button(__("New Report"), () => new_report(), __("Custom Report"));
|
||||
report.page.add_inner_button(__("Load Report"), () => load_report(), __("Custom Report"));
|
||||
hide_filters(report);
|
||||
}
|
||||
};
|
||||
|
||||
function hide_filters(report) {
|
||||
report.page.page_form[0].querySelectorAll('.form-group.frappe-control').forEach(function setHidden(field) {
|
||||
if (field.dataset.fieldtype == "Read Only") {
|
||||
field.classList.add("hidden");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
erpnext.TaxDetail = class TaxDetail {
|
||||
constructor() {
|
||||
this.patch();
|
||||
this.load_report();
|
||||
}
|
||||
// Monkey patch the QueryReport class
|
||||
patch() {
|
||||
this.qr = frappe.query_report;
|
||||
this.super = {
|
||||
refresh_report: this.qr.refresh_report,
|
||||
show_footer_message: this.qr.show_footer_message
|
||||
}
|
||||
this.qr.refresh_report = () => this.refresh_report();
|
||||
this.qr.show_footer_message = () => this.show_footer_message();
|
||||
}
|
||||
show_footer_message() {
|
||||
// The last thing to run after datatable_render in refresh()
|
||||
this.super.show_footer_message.apply(this.qr);
|
||||
if (this.qr.report_name !== 'Tax Detail') {
|
||||
this.show_help();
|
||||
if (this.loading) {
|
||||
this.set_section('');
|
||||
} else {
|
||||
this.reload_component('');
|
||||
}
|
||||
}
|
||||
this.loading = false;
|
||||
}
|
||||
refresh_report() {
|
||||
// Infrequent report build (onload), load filters & data
|
||||
// super function runs a refresh() serially
|
||||
// already run within frappe.run_serially
|
||||
this.loading = true;
|
||||
this.super.refresh_report.apply(this.qr);
|
||||
if (this.qr.report_name !== 'Tax Detail') {
|
||||
frappe.call({
|
||||
method: 'erpnext.accounts.report.tax_detail.tax_detail.get_custom_reports',
|
||||
args: {name: this.qr.report_name}
|
||||
}).then((r) => {
|
||||
const data = JSON.parse(r.message[this.qr.report_name]['json']);
|
||||
this.create_controls();
|
||||
this.sections = data.sections || {};
|
||||
this.controls['show_detail'].set_input(data.show_detail);
|
||||
});
|
||||
}
|
||||
}
|
||||
load_report() {
|
||||
// One-off report build like titles, menu, etc
|
||||
// Run when this object is created which happens in qr.load_report
|
||||
this.qr.menu_items = this.get_menu_items();
|
||||
}
|
||||
get_menu_items() {
|
||||
// Replace Save action
|
||||
let new_items = [];
|
||||
const save = __('Save');
|
||||
|
||||
for (let item of this.qr.menu_items) {
|
||||
if (item.label === save) {
|
||||
new_items.push({
|
||||
label: save,
|
||||
action: () => this.save_report(),
|
||||
standard: false
|
||||
});
|
||||
} else {
|
||||
new_items.push(item);
|
||||
}
|
||||
}
|
||||
return new_items;
|
||||
}
|
||||
save_report() {
|
||||
this.check_datatable();
|
||||
if (this.qr.report_name !== 'Tax Detail') {
|
||||
frappe.call({
|
||||
method:'erpnext.accounts.report.tax_detail.tax_detail.save_custom_report',
|
||||
args: {
|
||||
reference_report: 'Tax Detail',
|
||||
report_name: this.qr.report_name,
|
||||
data: {
|
||||
columns: this.qr.get_visible_columns(),
|
||||
sections: this.sections,
|
||||
show_detail: this.controls['show_detail'].get_input_value()
|
||||
}
|
||||
},
|
||||
freeze: true
|
||||
}).then((r) => {
|
||||
this.set_section('');
|
||||
});
|
||||
}
|
||||
}
|
||||
check_datatable() {
|
||||
if (!this.qr.datatable) {
|
||||
frappe.throw(__('Please change the date range to load data first'));
|
||||
}
|
||||
}
|
||||
set_section(name) {
|
||||
// Sets the given section name and then reloads the data
|
||||
if (name && !this.sections[name]) {
|
||||
this.sections[name] = {};
|
||||
}
|
||||
let options = Object.keys(this.sections);
|
||||
options.unshift('');
|
||||
this.controls['section_name'].$wrapper.find("select").empty().add_options(options);
|
||||
const org_mode = this.qr.get_filter_value('mode');
|
||||
let refresh = false;
|
||||
if (name) {
|
||||
this.controls['section_name'].set_input(name);
|
||||
this.qr.set_filter_value('mode', 'edit');
|
||||
if (org_mode === 'run') {
|
||||
refresh = true;
|
||||
}
|
||||
} else {
|
||||
this.controls['section_name'].set_input('');
|
||||
this.qr.set_filter_value('mode', 'run');
|
||||
if (org_mode === 'edit') {
|
||||
refresh = true;
|
||||
}
|
||||
}
|
||||
if (refresh) {
|
||||
this.qr.refresh();
|
||||
}
|
||||
this.reload_component('');
|
||||
}
|
||||
reload_component(component_name) {
|
||||
const section_name = this.controls['section_name'].get_input_value();
|
||||
if (section_name) {
|
||||
const section = this.sections[section_name];
|
||||
const component_names = Object.keys(section);
|
||||
component_names.unshift('');
|
||||
this.controls['component'].$wrapper.find("select").empty().add_options(component_names);
|
||||
this.controls['component'].set_input(component_name);
|
||||
if (component_name) {
|
||||
this.controls['component_type'].set_input(section[component_name].type);
|
||||
}
|
||||
} else {
|
||||
this.controls['component'].$wrapper.find("select").empty();
|
||||
this.controls['component'].set_input('');
|
||||
}
|
||||
this.set_table_filters();
|
||||
}
|
||||
set_table_filters() {
|
||||
let filters = {};
|
||||
const section_name = this.controls['section_name'].get_input_value();
|
||||
const component_name = this.controls['component'].get_input_value();
|
||||
if (section_name && component_name) {
|
||||
const component_type = this.sections[section_name][component_name].type;
|
||||
if (component_type === 'filter') {
|
||||
filters = this.sections[section_name][component_name]['filters'];
|
||||
}
|
||||
}
|
||||
this.setAppliedFilters(filters);
|
||||
}
|
||||
setAppliedFilters(filters) {
|
||||
if (this.qr.datatable) {
|
||||
Array.from(this.qr.datatable.header.querySelectorAll('.dt-filter')).map(function setFilters(input) {
|
||||
let idx = input.dataset.colIndex;
|
||||
if (filters[idx]) {
|
||||
input.value = filters[idx];
|
||||
} else {
|
||||
input.value = null;
|
||||
}
|
||||
});
|
||||
this.qr.datatable.columnmanager.applyFilter(filters);
|
||||
}
|
||||
}
|
||||
delete(name, type) {
|
||||
if (type === 'section') {
|
||||
delete this.sections[name];
|
||||
const new_section = Object.keys(this.sections)[0] || '';
|
||||
this.set_section(new_section);
|
||||
}
|
||||
if (type === 'component') {
|
||||
const cur_section = this.controls['section_name'].get_input_value();
|
||||
delete this.sections[cur_section][name];
|
||||
this.reload_component('');
|
||||
}
|
||||
}
|
||||
create_controls() {
|
||||
let controls = {};
|
||||
// SELECT in data.js
|
||||
controls['section_name'] = this.qr.page.add_field({
|
||||
label: __('Section'),
|
||||
fieldtype: 'Select',
|
||||
fieldname: 'section_name',
|
||||
change: (e) => {
|
||||
this.set_section(this.controls['section_name'].get_input_value());
|
||||
}
|
||||
});
|
||||
// BUTTON in button.js
|
||||
controls['new_section'] = this.qr.page.add_field({
|
||||
label: __('New Section'),
|
||||
fieldtype: 'Button',
|
||||
fieldname: 'new_section',
|
||||
click: () => {
|
||||
frappe.prompt({
|
||||
label: __('Section Name'),
|
||||
fieldname: 'name',
|
||||
fieldtype: 'Data'
|
||||
}, (values) => {
|
||||
this.set_section(values.name);
|
||||
});
|
||||
}
|
||||
});
|
||||
controls['delete_section'] = this.qr.page.add_field({
|
||||
label: __('Delete Section'),
|
||||
fieldtype: 'Button',
|
||||
fieldname: 'delete_section',
|
||||
click: () => {
|
||||
let cur_section = this.controls['section_name'].get_input_value();
|
||||
if (cur_section) {
|
||||
frappe.confirm(__('Are you sure you want to delete section') + ' ' + cur_section + '?',
|
||||
() => {this.delete(cur_section, 'section')});
|
||||
}
|
||||
}
|
||||
});
|
||||
controls['component'] = this.qr.page.add_field({
|
||||
label: __('Component'),
|
||||
fieldtype: 'Select',
|
||||
fieldname: 'component',
|
||||
change: (e) => {
|
||||
this.reload_component(this.controls['component'].get_input_value());
|
||||
}
|
||||
});
|
||||
controls['component_type'] = this.qr.page.add_field({
|
||||
label: __('Component Type'),
|
||||
fieldtype: 'Select',
|
||||
fieldname: 'component_type',
|
||||
default: 'filter',
|
||||
options: [
|
||||
{label: __('Filtered Row Subtotal'), value: 'filter'},
|
||||
{label: __('Section Subtotal'), value: 'section'}
|
||||
]
|
||||
});
|
||||
controls['add_component'] = this.qr.page.add_field({
|
||||
label: __('Add Component'),
|
||||
fieldtype: 'Button',
|
||||
fieldname: 'add_component',
|
||||
click: () => {
|
||||
this.check_datatable();
|
||||
let section_name = this.controls['section_name'].get_input_value();
|
||||
if (section_name) {
|
||||
const component_type = this.controls['component_type'].get_input_value();
|
||||
let idx = 0;
|
||||
const names = Object.keys(this.sections[section_name]);
|
||||
if (names.length > 0) {
|
||||
const idxs = names.map((key) => parseInt(key.match(/\d+$/)) || 0);
|
||||
idx = Math.max(...idxs) + 1;
|
||||
}
|
||||
const filters = this.qr.datatable.columnmanager.getAppliedFilters();
|
||||
if (component_type === 'filter') {
|
||||
const name = 'Filter' + idx.toString();
|
||||
let data = {
|
||||
type: component_type,
|
||||
filters: filters
|
||||
}
|
||||
this.sections[section_name][name] = data;
|
||||
this.reload_component(name);
|
||||
} else if (component_type === 'section') {
|
||||
if (filters && Object.keys(filters).length !== 0) {
|
||||
frappe.show_alert({
|
||||
message: __('Column filters ignored'),
|
||||
indicator: 'yellow'
|
||||
});
|
||||
}
|
||||
let data = {
|
||||
type: component_type
|
||||
}
|
||||
frappe.prompt({
|
||||
label: __('Section'),
|
||||
fieldname: 'section',
|
||||
fieldtype: 'Select',
|
||||
options: Object.keys(this.sections)
|
||||
}, (values) => {
|
||||
this.sections[section_name][values.section] = data;
|
||||
this.reload_component(values.section);
|
||||
});
|
||||
} else {
|
||||
frappe.throw(__('Please select the Component Type first'));
|
||||
}
|
||||
} else {
|
||||
frappe.throw(__('Please select the Section first'));
|
||||
}
|
||||
}
|
||||
});
|
||||
controls['delete_component'] = this.qr.page.add_field({
|
||||
label: __('Delete Component'),
|
||||
fieldtype: 'Button',
|
||||
fieldname: 'delete_component',
|
||||
click: () => {
|
||||
const component = this.controls['component'].get_input_value();
|
||||
if (component) {
|
||||
frappe.confirm(__('Are you sure you want to delete component') + ' ' + component + '?',
|
||||
() => {this.delete(component, 'component')});
|
||||
}
|
||||
}
|
||||
});
|
||||
controls['save'] = this.qr.page.add_field({
|
||||
label: __('Save & Run'),
|
||||
fieldtype: 'Button',
|
||||
fieldname: 'save',
|
||||
click: () => {
|
||||
this.save_report();
|
||||
}
|
||||
});
|
||||
controls['show_detail'] = this.qr.page.add_field({
|
||||
label: __('Show Detail'),
|
||||
fieldtype: 'Check',
|
||||
fieldname: 'show_detail',
|
||||
default: 1
|
||||
});
|
||||
this.controls = controls;
|
||||
}
|
||||
show_help() {
|
||||
const help = __('Your custom report is built from General Ledger Entries within the date range. You can add multiple sections to the report using the New Section button. Each component added to a section adds a subset of the data into the specified section. Beware of duplicated data rows. The Filtered Row component type saves the datatable column filters to specify the added data. The Section component type refers to the data in a previously defined section, but it cannot refer to its parent section. The Amount column is summed to give the section subtotal. Use the Show Detail box to see the data rows included in each section in the final report. Once finished, hit Save & Run. Report contributed by');
|
||||
this.qr.$report_footer.append('<div class="col-md-12"><strong>' + __('Help') + `: </strong>${help}<a href="https://www.casesolved.co.uk"> Case Solved</a></div>`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!window.taxdetail) {
|
||||
window.taxdetail = new erpnext.TaxDetail();
|
||||
}
|
||||
|
||||
function get_reports(cb) {
|
||||
frappe.call({
|
||||
method: 'erpnext.accounts.report.tax_detail.tax_detail.get_custom_reports',
|
||||
freeze: true
|
||||
}).then((r) => {
|
||||
cb(r.message);
|
||||
})
|
||||
}
|
||||
|
||||
function new_report() {
|
||||
const dialog = new frappe.ui.Dialog({
|
||||
title: __('New Report'),
|
||||
fields: [
|
||||
{
|
||||
fieldname: 'report_name',
|
||||
label: __('Report Name'),
|
||||
fieldtype: 'Data',
|
||||
default: 'VAT Return'
|
||||
}
|
||||
],
|
||||
primary_action_label: __('Create'),
|
||||
primary_action: function new_report_pa(values) {
|
||||
frappe.call({
|
||||
method:'erpnext.accounts.report.tax_detail.tax_detail.save_custom_report',
|
||||
args: {
|
||||
reference_report: 'Tax Detail',
|
||||
report_name: values.report_name,
|
||||
data: {
|
||||
columns: [],
|
||||
sections: {},
|
||||
show_detail: 1
|
||||
}
|
||||
},
|
||||
freeze: true
|
||||
}).then((r) => {
|
||||
frappe.set_route('query-report', values.report_name);
|
||||
});
|
||||
dialog.hide();
|
||||
}
|
||||
});
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
function load_report() {
|
||||
get_reports(function load_report_cb(reports) {
|
||||
const dialog = new frappe.ui.Dialog({
|
||||
title: __('Load Report'),
|
||||
fields: [
|
||||
{
|
||||
fieldname: 'report_name',
|
||||
label: __('Report Name'),
|
||||
fieldtype: 'Select',
|
||||
options: Object.keys(reports)
|
||||
}
|
||||
],
|
||||
primary_action_label: __('Load'),
|
||||
primary_action: function load_report_pa(values) {
|
||||
dialog.hide();
|
||||
frappe.set_route('query-report', values.report_name);
|
||||
}
|
||||
});
|
||||
dialog.show();
|
||||
});
|
||||
}
|
32
erpnext/accounts/report/tax_detail/tax_detail.json
Normal file
32
erpnext/accounts/report/tax_detail/tax_detail.json
Normal file
@ -0,0 +1,32 @@
|
||||
{
|
||||
"add_total_row": 0,
|
||||
"columns": [],
|
||||
"creation": "2021-02-19 16:44:21.175113",
|
||||
"disable_prepared_report": 0,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"filters": [],
|
||||
"idx": 0,
|
||||
"is_standard": "Yes",
|
||||
"modified": "2021-02-19 16:44:21.175113",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Tax Detail",
|
||||
"owner": "Administrator",
|
||||
"prepared_report": 0,
|
||||
"ref_doctype": "GL Entry",
|
||||
"report_name": "Tax Detail",
|
||||
"report_type": "Script Report",
|
||||
"roles": [
|
||||
{
|
||||
"role": "Accounts User"
|
||||
},
|
||||
{
|
||||
"role": "Accounts Manager"
|
||||
},
|
||||
{
|
||||
"role": "Auditor"
|
||||
}
|
||||
]
|
||||
}
|
296
erpnext/accounts/report/tax_detail/tax_detail.py
Normal file
296
erpnext/accounts/report/tax_detail/tax_detail.py
Normal file
@ -0,0 +1,296 @@
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
# Contributed by Case Solved and sponsored by Nulight Studios
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
import json
|
||||
from frappe import _
|
||||
|
||||
# NOTE: Payroll is implemented using Journal Entries which are included as GL Entries
|
||||
|
||||
# field lists in multiple doctypes will be coalesced
|
||||
required_sql_fields = {
|
||||
("GL Entry", 1): ["posting_date"],
|
||||
("Account",): ["root_type", "account_type"],
|
||||
("GL Entry", 2): ["account", "voucher_type", "voucher_no", "debit", "credit"],
|
||||
("Purchase Invoice Item", "Sales Invoice Item"): ["base_net_amount", "item_tax_rate", "item_tax_template", "item_group", "item_name"],
|
||||
("Purchase Invoice", "Sales Invoice"): ["taxes_and_charges", "tax_category"],
|
||||
}
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
if not filters:
|
||||
return [], []
|
||||
|
||||
fieldlist = required_sql_fields
|
||||
fieldstr = get_fieldstr(fieldlist)
|
||||
|
||||
gl_entries = frappe.db.sql("""
|
||||
select {fieldstr}
|
||||
from `tabGL Entry` ge
|
||||
inner join `tabAccount` a on
|
||||
ge.account=a.name and ge.company=a.company
|
||||
left join `tabSales Invoice` si on
|
||||
ge.company=si.company and ge.voucher_type='Sales Invoice' and ge.voucher_no=si.name
|
||||
left join `tabSales Invoice Item` sii on
|
||||
a.root_type='Income' and si.name=sii.parent
|
||||
left join `tabPurchase Invoice` pi on
|
||||
ge.company=pi.company and ge.voucher_type='Purchase Invoice' and ge.voucher_no=pi.name
|
||||
left join `tabPurchase Invoice Item` pii on
|
||||
a.root_type='Expense' and pi.name=pii.parent
|
||||
where
|
||||
ge.company=%(company)s and
|
||||
ge.posting_date>=%(from_date)s and
|
||||
ge.posting_date<=%(to_date)s
|
||||
order by ge.posting_date, ge.voucher_no
|
||||
""".format(fieldstr=fieldstr), filters, as_dict=1)
|
||||
|
||||
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 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)
|
||||
report = {}
|
||||
new_data = []
|
||||
summary = []
|
||||
for section_name, section in sections.items():
|
||||
report[section_name] = {'rows': [], 'subtotal': 0.0}
|
||||
for component_name, component in section.items():
|
||||
if component['type'] == 'filter':
|
||||
for row in data:
|
||||
matched = True
|
||||
for colno, filter_string in component['filters'].items():
|
||||
filter_field = columns[int(colno) - 1]['fieldname']
|
||||
if not filter_match(row[filter_field], filter_string):
|
||||
matched = False
|
||||
break
|
||||
if matched:
|
||||
report[section_name]['rows'] += [row]
|
||||
report[section_name]['subtotal'] += row['amount']
|
||||
if component['type'] == 'section':
|
||||
if component_name == section_name:
|
||||
frappe.throw(_("A report component cannot refer to its parent section") + ": " + section_name)
|
||||
try:
|
||||
report[section_name]['rows'] += report[component_name]['rows']
|
||||
report[section_name]['subtotal'] += report[component_name]['subtotal']
|
||||
except KeyError:
|
||||
frappe.throw(_("A report component can only refer to an earlier section") + ": " + section_name)
|
||||
|
||||
if show_detail:
|
||||
new_data += report[section_name]['rows']
|
||||
new_data += [{'voucher_no': section_name, 'amount': report[section_name]['subtotal']}]
|
||||
summary += [{'label': section_name, 'datatype': 'Currency', 'value': report[section_name]['subtotal']}]
|
||||
if show_detail:
|
||||
new_data += [{}]
|
||||
return new_data or data, summary or None
|
||||
|
||||
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 frappe.safe_eval(f'{value} {operator} {num}')
|
||||
except ValueError:
|
||||
if operator == '<':
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def abbrev(dt):
|
||||
return ''.join(l[0].lower() for l in dt.split(' ')) + '.'
|
||||
|
||||
def doclist(dt, dfs):
|
||||
return [abbrev(dt) + f for f in dfs]
|
||||
|
||||
def as_split(fields):
|
||||
for field in fields:
|
||||
split = field.split(' as ')
|
||||
yield (split[0], split[1] if len(split) > 1 else split[0])
|
||||
|
||||
def coalesce(doctypes, fields):
|
||||
coalesce = []
|
||||
for name, new_name in as_split(fields):
|
||||
sharedfields = ', '.join(abbrev(dt) + name for dt in doctypes)
|
||||
coalesce += [f'coalesce({sharedfields}) as {new_name}']
|
||||
return coalesce
|
||||
|
||||
def get_fieldstr(fieldlist):
|
||||
fields = []
|
||||
for doctypes, docfields in fieldlist.items():
|
||||
if len(doctypes) == 1 or isinstance(doctypes[1], int):
|
||||
fields += doclist(doctypes[0], docfields)
|
||||
else:
|
||||
fields += coalesce(doctypes, docfields)
|
||||
return ', '.join(fields)
|
||||
|
||||
def get_columns(fieldlist):
|
||||
columns = {}
|
||||
for doctypes, docfields in fieldlist.items():
|
||||
fieldmap = {name: new_name for name, new_name in as_split(docfields)}
|
||||
for doctype in doctypes:
|
||||
if isinstance(doctype, int):
|
||||
break
|
||||
meta = frappe.get_meta(doctype)
|
||||
# get column field metadata from the db
|
||||
fieldmeta = {}
|
||||
for field in meta.get('fields'):
|
||||
if field.fieldname in fieldmap.keys():
|
||||
new_name = fieldmap[field.fieldname]
|
||||
fieldmeta[new_name] = {
|
||||
"label": _(field.label),
|
||||
"fieldname": new_name,
|
||||
"fieldtype": field.fieldtype,
|
||||
"options": field.options
|
||||
}
|
||||
# edit the columns to match the modified data
|
||||
for field in fieldmap.values():
|
||||
col = modify_report_columns(doctype, field, fieldmeta[field])
|
||||
if col:
|
||||
columns[col["fieldname"]] = col
|
||||
# use of a dict ensures duplicate columns are removed
|
||||
return list(columns.values())
|
||||
|
||||
def modify_report_columns(doctype, field, column):
|
||||
"Because data is rearranged into other columns"
|
||||
if doctype in ["Sales Invoice Item", "Purchase Invoice Item"]:
|
||||
if field in ["item_tax_rate", "base_net_amount"]:
|
||||
return None
|
||||
|
||||
if doctype == "GL Entry" and field in ["debit", "credit"]:
|
||||
column.update({"label": _("Amount"), "fieldname": "amount"})
|
||||
|
||||
if field == "taxes_and_charges":
|
||||
column.update({"label": _("Taxes and Charges Template")})
|
||||
return column
|
||||
|
||||
def modify_report_data(data):
|
||||
import json
|
||||
new_data = []
|
||||
for line in data:
|
||||
if line.debit:
|
||||
line.amount = -line.debit
|
||||
else:
|
||||
line.amount = line.credit
|
||||
# Remove Invoice GL Tax Entries and generate Tax entries from the invoice lines
|
||||
if "Invoice" in line.voucher_type:
|
||||
if line.account_type not in ("Tax", "Round Off"):
|
||||
new_data += [line]
|
||||
if line.item_tax_rate:
|
||||
tax_rates = json.loads(line.item_tax_rate)
|
||||
for account, rate in tax_rates.items():
|
||||
tax_line = line.copy()
|
||||
tax_line.account_type = "Tax"
|
||||
tax_line.account = account
|
||||
if line.voucher_type == "Sales Invoice":
|
||||
line.amount = line.base_net_amount
|
||||
tax_line.amount = line.base_net_amount * (rate / 100)
|
||||
if line.voucher_type == "Purchase Invoice":
|
||||
line.amount = -line.base_net_amount
|
||||
tax_line.amount = -line.base_net_amount * (rate / 100)
|
||||
new_data += [tax_line]
|
||||
else:
|
||||
new_data += [line]
|
||||
return new_data
|
||||
|
||||
|
||||
# JS client utilities
|
||||
|
||||
custom_report_dict = {
|
||||
'ref_doctype': 'GL Entry',
|
||||
'report_type': 'Custom Report',
|
||||
'reference_report': 'Tax Detail'
|
||||
}
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_custom_reports(name=None):
|
||||
filters = custom_report_dict.copy()
|
||||
if name:
|
||||
filters['name'] = name
|
||||
reports = frappe.get_list('Report',
|
||||
filters = filters,
|
||||
fields = ['name', 'json'],
|
||||
as_list=False
|
||||
)
|
||||
reports_dict = {rep.pop('name'): rep for rep in reports}
|
||||
# Prevent custom reports with the same name
|
||||
reports_dict['Tax Detail'] = {'json': None}
|
||||
return reports_dict
|
||||
|
||||
@frappe.whitelist()
|
||||
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."))
|
||||
|
||||
doc = {
|
||||
'doctype': 'Report',
|
||||
'report_name': report_name,
|
||||
'is_standard': 'No',
|
||||
'module': 'Accounts',
|
||||
'json': data
|
||||
}
|
||||
doc.update(custom_report_dict)
|
||||
|
||||
try:
|
||||
newdoc = frappe.get_doc(doc)
|
||||
newdoc.insert()
|
||||
frappe.msgprint(_("Report created successfully"))
|
||||
except frappe.exceptions.DuplicateEntryError:
|
||||
dbdoc = frappe.get_doc('Report', report_name)
|
||||
dbdoc.update(doc)
|
||||
dbdoc.save()
|
||||
frappe.msgprint(_("Report updated successfully"))
|
||||
return report_name
|
840
erpnext/accounts/report/tax_detail/test_tax_detail.json
Normal file
840
erpnext/accounts/report/tax_detail/test_tax_detail.json
Normal file
@ -0,0 +1,840 @@
|
||||
[
|
||||
{
|
||||
"account_manager": null,
|
||||
"accounts": [],
|
||||
"companies": [],
|
||||
"credit_limits": [],
|
||||
"customer_details": null,
|
||||
"customer_group": "All Customer Groups",
|
||||
"customer_name": "_Test Customer",
|
||||
"customer_pos_id": null,
|
||||
"customer_primary_address": null,
|
||||
"customer_primary_contact": null,
|
||||
"customer_type": "Company",
|
||||
"default_bank_account": null,
|
||||
"default_commission_rate": 0.0,
|
||||
"default_currency": null,
|
||||
"default_price_list": null,
|
||||
"default_sales_partner": null,
|
||||
"disabled": 0,
|
||||
"dn_required": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Customer",
|
||||
"email_id": null,
|
||||
"gender": null,
|
||||
"image": null,
|
||||
"industry": null,
|
||||
"is_frozen": 0,
|
||||
"is_internal_customer": 0,
|
||||
"language": "en",
|
||||
"lead_name": null,
|
||||
"loyalty_program": null,
|
||||
"loyalty_program_tier": null,
|
||||
"market_segment": null,
|
||||
"mobile_no": null,
|
||||
"modified": "2021-02-15 05:18:03.624724",
|
||||
"name": "_Test Customer",
|
||||
"naming_series": "CUST-.YYYY.-",
|
||||
"pan": null,
|
||||
"parent": null,
|
||||
"parentfield": null,
|
||||
"parenttype": null,
|
||||
"payment_terms": null,
|
||||
"primary_address": null,
|
||||
"represents_company": "",
|
||||
"sales_team": [],
|
||||
"salutation": null,
|
||||
"so_required": 0,
|
||||
"tax_category": null,
|
||||
"tax_id": null,
|
||||
"tax_withholding_category": null,
|
||||
"territory": "All Territories",
|
||||
"website": null
|
||||
},{
|
||||
"accounts": [],
|
||||
"allow_purchase_invoice_creation_without_purchase_order": 0,
|
||||
"allow_purchase_invoice_creation_without_purchase_receipt": 0,
|
||||
"companies": [],
|
||||
"country": "United Kingdom",
|
||||
"default_bank_account": null,
|
||||
"default_currency": null,
|
||||
"default_price_list": null,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Supplier",
|
||||
"hold_type": "",
|
||||
"image": null,
|
||||
"is_frozen": 0,
|
||||
"is_internal_supplier": 0,
|
||||
"is_transporter": 0,
|
||||
"language": "en",
|
||||
"modified": "2021-03-31 16:47:10.109316",
|
||||
"name": "_Test Supplier",
|
||||
"naming_series": "SUP-.YYYY.-",
|
||||
"on_hold": 0,
|
||||
"pan": null,
|
||||
"parent": null,
|
||||
"parentfield": null,
|
||||
"parenttype": null,
|
||||
"payment_terms": null,
|
||||
"prevent_pos": 0,
|
||||
"prevent_rfqs": 0,
|
||||
"release_date": null,
|
||||
"represents_company": null,
|
||||
"supplier_details": null,
|
||||
"supplier_group": "Raw Material",
|
||||
"supplier_name": "_Test Supplier",
|
||||
"supplier_type": "Company",
|
||||
"tax_category": null,
|
||||
"tax_id": null,
|
||||
"tax_withholding_category": null,
|
||||
"warn_pos": 0,
|
||||
"warn_rfqs": 0,
|
||||
"website": null
|
||||
},{
|
||||
"account_currency": "GBP",
|
||||
"account_name": "Debtors",
|
||||
"account_number": "",
|
||||
"account_type": "Receivable",
|
||||
"balance_must_be": "",
|
||||
"company": "_T",
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Account",
|
||||
"freeze_account": "No",
|
||||
"include_in_gross": 0,
|
||||
"inter_company_account": 0,
|
||||
"is_group": 0,
|
||||
"lft": 58,
|
||||
"modified": "2021-03-26 04:44:19.955468",
|
||||
"name": "Debtors - _T",
|
||||
"old_parent": null,
|
||||
"parent": null,
|
||||
"parent_account": "Application of Funds (Assets) - _T",
|
||||
"parentfield": null,
|
||||
"parenttype": null,
|
||||
"report_type": "Balance Sheet",
|
||||
"rgt": 59,
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 0.0
|
||||
},{
|
||||
"account_currency": "GBP",
|
||||
"account_name": "Sales",
|
||||
"account_number": "",
|
||||
"account_type": "Income Account",
|
||||
"balance_must_be": "",
|
||||
"company": "_T",
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Account",
|
||||
"freeze_account": "No",
|
||||
"include_in_gross": 0,
|
||||
"inter_company_account": 0,
|
||||
"is_group": 0,
|
||||
"lft": 291,
|
||||
"modified": "2021-03-26 04:50:21.697703",
|
||||
"name": "Sales - _T",
|
||||
"old_parent": null,
|
||||
"parent": null,
|
||||
"parent_account": "Income - _T",
|
||||
"parentfield": null,
|
||||
"parenttype": null,
|
||||
"report_type": "Profit and Loss",
|
||||
"rgt": 292,
|
||||
"root_type": "Income",
|
||||
"tax_rate": 0.0
|
||||
},{
|
||||
"account_currency": "GBP",
|
||||
"account_name": "VAT on Sales",
|
||||
"account_number": "",
|
||||
"account_type": "Tax",
|
||||
"balance_must_be": "",
|
||||
"company": "_T",
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Account",
|
||||
"freeze_account": "No",
|
||||
"include_in_gross": 0,
|
||||
"inter_company_account": 0,
|
||||
"is_group": 0,
|
||||
"lft": 317,
|
||||
"modified": "2021-03-26 04:50:21.697703",
|
||||
"name": "VAT on Sales - _T",
|
||||
"old_parent": null,
|
||||
"parent": null,
|
||||
"parent_account": "Source of Funds (Liabilities) - _T",
|
||||
"parentfield": null,
|
||||
"parenttype": null,
|
||||
"report_type": "Balance Sheet",
|
||||
"rgt": 318,
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 0.0
|
||||
},{
|
||||
"account_currency": "GBP",
|
||||
"account_name": "Cost of Goods Sold",
|
||||
"account_number": "",
|
||||
"account_type": "Cost of Goods Sold",
|
||||
"balance_must_be": "",
|
||||
"company": "_T",
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Account",
|
||||
"freeze_account": "No",
|
||||
"include_in_gross": 0,
|
||||
"inter_company_account": 0,
|
||||
"is_group": 0,
|
||||
"lft": 171,
|
||||
"modified": "2021-03-26 04:44:19.994857",
|
||||
"name": "Cost of Goods Sold - _T",
|
||||
"old_parent": null,
|
||||
"parent": null,
|
||||
"parent_account": "Expenses - _T",
|
||||
"parentfield": null,
|
||||
"parenttype": null,
|
||||
"report_type": "Profit and Loss",
|
||||
"rgt": 172,
|
||||
"root_type": "Expense",
|
||||
"tax_rate": 0.0
|
||||
},{
|
||||
"account_currency": "GBP",
|
||||
"account_name": "VAT on Purchases",
|
||||
"account_number": "",
|
||||
"account_type": "Tax",
|
||||
"balance_must_be": "",
|
||||
"company": "_T",
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Account",
|
||||
"freeze_account": "No",
|
||||
"include_in_gross": 0,
|
||||
"inter_company_account": 0,
|
||||
"is_group": 0,
|
||||
"lft": 80,
|
||||
"modified": "2021-03-26 04:44:19.961983",
|
||||
"name": "VAT on Purchases - _T",
|
||||
"old_parent": null,
|
||||
"parent": null,
|
||||
"parent_account": "Application of Funds (Assets) - _T",
|
||||
"parentfield": null,
|
||||
"parenttype": null,
|
||||
"report_type": "Balance Sheet",
|
||||
"rgt": 81,
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 0.0
|
||||
},{
|
||||
"account_currency": "GBP",
|
||||
"account_name": "Creditors",
|
||||
"account_number": "",
|
||||
"account_type": "Payable",
|
||||
"balance_must_be": "",
|
||||
"company": "_T",
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Account",
|
||||
"freeze_account": "No",
|
||||
"include_in_gross": 0,
|
||||
"inter_company_account": 0,
|
||||
"is_group": 0,
|
||||
"lft": 302,
|
||||
"modified": "2021-03-26 04:50:21.697703",
|
||||
"name": "Creditors - _T",
|
||||
"old_parent": null,
|
||||
"parent": null,
|
||||
"parent_account": "Source of Funds (Liabilities) - _T",
|
||||
"parentfield": null,
|
||||
"parenttype": null,
|
||||
"report_type": "Balance Sheet",
|
||||
"rgt": 303,
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 0.0
|
||||
},{
|
||||
"additional_discount_percentage": 0.0,
|
||||
"address_display": null,
|
||||
"adjust_advance_taxes": 0,
|
||||
"advances": [],
|
||||
"against_expense_account": "Cost of Goods Sold - _T",
|
||||
"allocate_advances_automatically": 0,
|
||||
"amended_from": null,
|
||||
"apply_discount_on": "Grand Total",
|
||||
"apply_tds": 0,
|
||||
"auto_repeat": null,
|
||||
"base_discount_amount": 0.0,
|
||||
"base_grand_total": 511.68,
|
||||
"base_in_words": "GBP Five Hundred And Eleven and Sixty Eight Pence only.",
|
||||
"base_net_total": 426.4,
|
||||
"base_paid_amount": 0.0,
|
||||
"base_rounded_total": 511.68,
|
||||
"base_rounding_adjustment": 0.0,
|
||||
"base_taxes_and_charges_added": 85.28,
|
||||
"base_taxes_and_charges_deducted": 0.0,
|
||||
"base_total": 426.4,
|
||||
"base_total_taxes_and_charges": 85.28,
|
||||
"base_write_off_amount": 0.0,
|
||||
"bill_date": null,
|
||||
"bill_no": null,
|
||||
"billing_address": null,
|
||||
"billing_address_display": null,
|
||||
"buying_price_list": "Standard Buying",
|
||||
"cash_bank_account": null,
|
||||
"clearance_date": null,
|
||||
"company": "_T",
|
||||
"contact_display": null,
|
||||
"contact_email": null,
|
||||
"contact_mobile": null,
|
||||
"contact_person": null,
|
||||
"conversion_rate": 1.0,
|
||||
"cost_center": null,
|
||||
"credit_to": "Creditors - _T",
|
||||
"currency": "GBP",
|
||||
"disable_rounded_total": 0,
|
||||
"discount_amount": 0.0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Purchase Invoice",
|
||||
"due_date": null,
|
||||
"from_date": null,
|
||||
"grand_total": 511.68,
|
||||
"group_same_items": 0,
|
||||
"hold_comment": null,
|
||||
"ignore_pricing_rule": 0,
|
||||
"in_words": "GBP Five Hundred And Eleven and Sixty Eight Pence only.",
|
||||
"inter_company_invoice_reference": null,
|
||||
"is_internal_supplier": 0,
|
||||
"is_opening": "No",
|
||||
"is_paid": 0,
|
||||
"is_return": 0,
|
||||
"is_subcontracted": "No",
|
||||
"items": [
|
||||
{
|
||||
"allow_zero_valuation_rate": 0,
|
||||
"amount": 426.4,
|
||||
"asset_category": null,
|
||||
"asset_location": null,
|
||||
"base_amount": 426.4,
|
||||
"base_net_amount": 426.4,
|
||||
"base_net_rate": 5.33,
|
||||
"base_price_list_rate": 5.33,
|
||||
"base_rate": 5.33,
|
||||
"base_rate_with_margin": 0.0,
|
||||
"batch_no": null,
|
||||
"bom": null,
|
||||
"brand": null,
|
||||
"conversion_factor": 0.0,
|
||||
"cost_center": "Main - _T",
|
||||
"deferred_expense_account": null,
|
||||
"description": "<div class=\"ql-editor read-mode\"><p>Fluid to make widgets</p></div>",
|
||||
"discount_amount": 0.0,
|
||||
"discount_percentage": 0.0,
|
||||
"enable_deferred_expense": 0,
|
||||
"expense_account": "Cost of Goods Sold - _T",
|
||||
"from_warehouse": null,
|
||||
"image": null,
|
||||
"include_exploded_items": 0,
|
||||
"is_fixed_asset": 0,
|
||||
"is_free_item": 0,
|
||||
"item_code": null,
|
||||
"item_group": null,
|
||||
"item_name": "Widget Fluid 1Litre",
|
||||
"item_tax_amount": 0.0,
|
||||
"item_tax_rate": "{\"VAT on Purchases - _T\": 20.0}",
|
||||
"item_tax_template": null,
|
||||
"landed_cost_voucher_amount": 0.0,
|
||||
"manufacturer": null,
|
||||
"manufacturer_part_no": null,
|
||||
"margin_rate_or_amount": 0.0,
|
||||
"margin_type": "",
|
||||
"net_amount": 426.4,
|
||||
"net_rate": 5.33,
|
||||
"page_break": 0,
|
||||
"parent": null,
|
||||
"parentfield": "items",
|
||||
"parenttype": "Purchase Invoice",
|
||||
"po_detail": null,
|
||||
"pr_detail": null,
|
||||
"price_list_rate": 5.33,
|
||||
"pricing_rules": null,
|
||||
"project": null,
|
||||
"purchase_invoice_item": null,
|
||||
"purchase_order": null,
|
||||
"purchase_receipt": null,
|
||||
"qty": 80.0,
|
||||
"quality_inspection": null,
|
||||
"rate": 5.33,
|
||||
"rate_with_margin": 0.0,
|
||||
"received_qty": 0.0,
|
||||
"rejected_qty": 0.0,
|
||||
"rejected_serial_no": null,
|
||||
"rejected_warehouse": null,
|
||||
"rm_supp_cost": 0.0,
|
||||
"sales_invoice_item": null,
|
||||
"serial_no": null,
|
||||
"service_end_date": null,
|
||||
"service_start_date": null,
|
||||
"service_stop_date": null,
|
||||
"stock_qty": 0.0,
|
||||
"stock_uom": "Nos",
|
||||
"stock_uom_rate": 0.0,
|
||||
"total_weight": 0.0,
|
||||
"uom": "Nos",
|
||||
"valuation_rate": 0.0,
|
||||
"warehouse": null,
|
||||
"weight_per_unit": 0.0,
|
||||
"weight_uom": null
|
||||
}
|
||||
],
|
||||
"language": "en",
|
||||
"letter_head": null,
|
||||
"mode_of_payment": null,
|
||||
"modified": "2021-04-03 03:33:09.180453",
|
||||
"name": null,
|
||||
"naming_series": "ACC-PINV-.YYYY.-",
|
||||
"net_total": 426.4,
|
||||
"on_hold": 0,
|
||||
"other_charges_calculation": "<div class=\"tax-break-up\" style=\"overflow-x: auto;\">\n\t<table class=\"table table-bordered table-hover\">\n\t\t<thead>\n\t\t\t<tr>\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t<th class=\"text-left\">Item</th>\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t<th class=\"text-right\">Taxable Amount</th>\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t<th class=\"text-right\">VAT on Purchases</th>\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t</tr>\n\t\t</thead>\n\t\t<tbody>\n\t\t\t\n\t\t\t\t<tr>\n\t\t\t\t\t<td>Widget Fluid 1Litre</td>\n\t\t\t\t\t<td class='text-right'>\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\u00a3 426.40\n\t\t\t\t\t\t\n\t\t\t\t\t</td>\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t<td class='text-right'>\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t(20.0%)\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\u00a3 85.28\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t</tr>\n\t\t\t\n\t\t</tbody>\n\t</table>\n</div>",
|
||||
"outstanding_amount": 511.68,
|
||||
"paid_amount": 0.0,
|
||||
"parent": null,
|
||||
"parentfield": null,
|
||||
"parenttype": null,
|
||||
"party_account_currency": "GBP",
|
||||
"payment_schedule": [],
|
||||
"payment_terms_template": null,
|
||||
"plc_conversion_rate": 1.0,
|
||||
"posting_date": null,
|
||||
"posting_time": "16:59:56.789522",
|
||||
"price_list_currency": "GBP",
|
||||
"pricing_rules": [],
|
||||
"project": null,
|
||||
"rejected_warehouse": null,
|
||||
"release_date": null,
|
||||
"remarks": "No Remarks",
|
||||
"represents_company": null,
|
||||
"return_against": null,
|
||||
"rounded_total": 511.68,
|
||||
"rounding_adjustment": 0.0,
|
||||
"scan_barcode": null,
|
||||
"select_print_heading": null,
|
||||
"set_from_warehouse": null,
|
||||
"set_posting_time": 0,
|
||||
"set_warehouse": null,
|
||||
"shipping_address": null,
|
||||
"shipping_address_display": "",
|
||||
"shipping_rule": null,
|
||||
"status": "Unpaid",
|
||||
"supplied_items": [],
|
||||
"supplier": "_Test Supplier",
|
||||
"supplier_address": null,
|
||||
"supplier_name": "_Test Supplier",
|
||||
"supplier_warehouse": "Stores - _T",
|
||||
"tax_category": null,
|
||||
"tax_id": null,
|
||||
"tax_withholding_category": null,
|
||||
"taxes": [
|
||||
{
|
||||
"account_head": "VAT on Purchases - _T",
|
||||
"add_deduct_tax": "Add",
|
||||
"base_tax_amount": 85.28,
|
||||
"base_tax_amount_after_discount_amount": 85.28,
|
||||
"base_total": 511.68,
|
||||
"category": "Total",
|
||||
"charge_type": "On Net Total",
|
||||
"cost_center": "Main - _T",
|
||||
"description": "VAT on Purchases",
|
||||
"included_in_print_rate": 0,
|
||||
"item_wise_tax_detail": "{\"Widget Fluid 1Litre\":[20.0,85.28]}",
|
||||
"parent": null,
|
||||
"parentfield": "taxes",
|
||||
"parenttype": "Purchase Invoice",
|
||||
"rate": 0.0,
|
||||
"row_id": null,
|
||||
"tax_amount": 85.28,
|
||||
"tax_amount_after_discount_amount": 85.28,
|
||||
"total": 511.68
|
||||
}
|
||||
],
|
||||
"taxes_and_charges": null,
|
||||
"taxes_and_charges_added": 85.28,
|
||||
"taxes_and_charges_deducted": 0.0,
|
||||
"tc_name": null,
|
||||
"terms": null,
|
||||
"title": "_Purchase Invoice",
|
||||
"to_date": null,
|
||||
"total": 426.4,
|
||||
"total_advance": 0.0,
|
||||
"total_net_weight": 0.0,
|
||||
"total_qty": 80.0,
|
||||
"total_taxes_and_charges": 85.28,
|
||||
"unrealized_profit_loss_account": null,
|
||||
"update_stock": 0,
|
||||
"write_off_account": null,
|
||||
"write_off_amount": 0.0,
|
||||
"write_off_cost_center": null
|
||||
},{
|
||||
"account_for_change_amount": null,
|
||||
"additional_discount_percentage": 0.0,
|
||||
"address_display": null,
|
||||
"advances": [],
|
||||
"against_income_account": "Sales - _T",
|
||||
"allocate_advances_automatically": 0,
|
||||
"amended_from": null,
|
||||
"apply_discount_on": "Grand Total",
|
||||
"auto_repeat": null,
|
||||
"base_change_amount": 0.0,
|
||||
"base_discount_amount": 0.0,
|
||||
"base_grand_total": 868.25,
|
||||
"base_in_words": "GBP Eight Hundred And Sixty Eight and Twenty Five Pence only.",
|
||||
"base_net_total": 825.0,
|
||||
"base_paid_amount": 0.0,
|
||||
"base_rounded_total": 868.25,
|
||||
"base_rounding_adjustment": 0.0,
|
||||
"base_total": 825.0,
|
||||
"base_total_taxes_and_charges": 43.25,
|
||||
"base_write_off_amount": 0.0,
|
||||
"c_form_applicable": "No",
|
||||
"c_form_no": null,
|
||||
"campaign": null,
|
||||
"cash_bank_account": null,
|
||||
"change_amount": 0.0,
|
||||
"commission_rate": 0.0,
|
||||
"company": "_T",
|
||||
"company_address": null,
|
||||
"company_address_display": null,
|
||||
"company_tax_id": null,
|
||||
"contact_display": null,
|
||||
"contact_email": null,
|
||||
"contact_mobile": null,
|
||||
"contact_person": null,
|
||||
"conversion_rate": 1.0,
|
||||
"cost_center": null,
|
||||
"currency": "GBP",
|
||||
"customer": "_Test Customer",
|
||||
"customer_address": null,
|
||||
"customer_group": "All Customer Groups",
|
||||
"customer_name": "_Test Customer",
|
||||
"debit_to": "Debtors - _T",
|
||||
"discount_amount": 0.0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Sales Invoice",
|
||||
"due_date": null,
|
||||
"from_date": null,
|
||||
"grand_total": 868.25,
|
||||
"group_same_items": 0,
|
||||
"ignore_pricing_rule": 0,
|
||||
"in_words": "GBP Eight Hundred And Sixty Eight and Twenty Five Pence only.",
|
||||
"inter_company_invoice_reference": null,
|
||||
"is_consolidated": 0,
|
||||
"is_discounted": 0,
|
||||
"is_internal_customer": 0,
|
||||
"is_opening": "No",
|
||||
"is_pos": 0,
|
||||
"is_return": 0,
|
||||
"items": [
|
||||
{
|
||||
"actual_batch_qty": 0.0,
|
||||
"actual_qty": 0.0,
|
||||
"allow_zero_valuation_rate": 0,
|
||||
"amount": 200.0,
|
||||
"asset": null,
|
||||
"barcode": null,
|
||||
"base_amount": 200.0,
|
||||
"base_net_amount": 200.0,
|
||||
"base_net_rate": 50.0,
|
||||
"base_price_list_rate": 0.0,
|
||||
"base_rate": 50.0,
|
||||
"base_rate_with_margin": 0.0,
|
||||
"batch_no": null,
|
||||
"brand": null,
|
||||
"conversion_factor": 1.0,
|
||||
"cost_center": "Main - _T",
|
||||
"customer_item_code": null,
|
||||
"deferred_revenue_account": null,
|
||||
"delivered_by_supplier": 0,
|
||||
"delivered_qty": 0.0,
|
||||
"delivery_note": null,
|
||||
"description": "<div class=\"ql-editor read-mode\"><p>Used</p></div>",
|
||||
"discount_amount": 0.0,
|
||||
"discount_percentage": 0.0,
|
||||
"dn_detail": null,
|
||||
"enable_deferred_revenue": 0,
|
||||
"expense_account": null,
|
||||
"finance_book": null,
|
||||
"image": null,
|
||||
"income_account": "Sales - _T",
|
||||
"incoming_rate": 0.0,
|
||||
"is_fixed_asset": 0,
|
||||
"is_free_item": 0,
|
||||
"item_code": null,
|
||||
"item_group": null,
|
||||
"item_name": "Dunlop tyres",
|
||||
"item_tax_rate": "{\"VAT on Sales - _T\": 20.0}",
|
||||
"item_tax_template": null,
|
||||
"margin_rate_or_amount": 0.0,
|
||||
"margin_type": "",
|
||||
"net_amount": 200.0,
|
||||
"net_rate": 50.0,
|
||||
"page_break": 0,
|
||||
"parent": null,
|
||||
"parentfield": "items",
|
||||
"parenttype": "Sales Invoice",
|
||||
"price_list_rate": 0.0,
|
||||
"pricing_rules": null,
|
||||
"project": null,
|
||||
"qty": 4.0,
|
||||
"quality_inspection": null,
|
||||
"rate": 50.0,
|
||||
"rate_with_margin": 0.0,
|
||||
"sales_invoice_item": null,
|
||||
"sales_order": null,
|
||||
"serial_no": null,
|
||||
"service_end_date": null,
|
||||
"service_start_date": null,
|
||||
"service_stop_date": null,
|
||||
"so_detail": null,
|
||||
"stock_qty": 4.0,
|
||||
"stock_uom": "Nos",
|
||||
"stock_uom_rate": 50.0,
|
||||
"target_warehouse": null,
|
||||
"total_weight": 0.0,
|
||||
"uom": "Nos",
|
||||
"warehouse": null,
|
||||
"weight_per_unit": 0.0,
|
||||
"weight_uom": null
|
||||
},
|
||||
{
|
||||
"actual_batch_qty": 0.0,
|
||||
"actual_qty": 0.0,
|
||||
"allow_zero_valuation_rate": 0,
|
||||
"amount": 65.0,
|
||||
"asset": null,
|
||||
"barcode": null,
|
||||
"base_amount": 65.0,
|
||||
"base_net_amount": 65.0,
|
||||
"base_net_rate": 65.0,
|
||||
"base_price_list_rate": 0.0,
|
||||
"base_rate": 65.0,
|
||||
"base_rate_with_margin": 0.0,
|
||||
"batch_no": null,
|
||||
"brand": null,
|
||||
"conversion_factor": 1.0,
|
||||
"cost_center": "Main - _T",
|
||||
"customer_item_code": null,
|
||||
"deferred_revenue_account": null,
|
||||
"delivered_by_supplier": 0,
|
||||
"delivered_qty": 0.0,
|
||||
"delivery_note": null,
|
||||
"description": "<div class=\"ql-editor read-mode\"><p>Used</p></div>",
|
||||
"discount_amount": 0.0,
|
||||
"discount_percentage": 0.0,
|
||||
"dn_detail": null,
|
||||
"enable_deferred_revenue": 0,
|
||||
"expense_account": null,
|
||||
"finance_book": null,
|
||||
"image": null,
|
||||
"income_account": "Sales - _T",
|
||||
"incoming_rate": 0.0,
|
||||
"is_fixed_asset": 0,
|
||||
"is_free_item": 0,
|
||||
"item_code": "",
|
||||
"item_group": null,
|
||||
"item_name": "Continental tyres",
|
||||
"item_tax_rate": "{\"VAT on Sales - _T\": 5.0}",
|
||||
"item_tax_template": null,
|
||||
"margin_rate_or_amount": 0.0,
|
||||
"margin_type": "",
|
||||
"net_amount": 65.0,
|
||||
"net_rate": 65.0,
|
||||
"page_break": 0,
|
||||
"parent": null,
|
||||
"parentfield": "items",
|
||||
"parenttype": "Sales Invoice",
|
||||
"price_list_rate": 0.0,
|
||||
"pricing_rules": null,
|
||||
"project": null,
|
||||
"qty": 1.0,
|
||||
"quality_inspection": null,
|
||||
"rate": 65.0,
|
||||
"rate_with_margin": 0.0,
|
||||
"sales_invoice_item": null,
|
||||
"sales_order": null,
|
||||
"serial_no": null,
|
||||
"service_end_date": null,
|
||||
"service_start_date": null,
|
||||
"service_stop_date": null,
|
||||
"so_detail": null,
|
||||
"stock_qty": 1.0,
|
||||
"stock_uom": null,
|
||||
"stock_uom_rate": 65.0,
|
||||
"target_warehouse": null,
|
||||
"total_weight": 0.0,
|
||||
"uom": "Nos",
|
||||
"warehouse": null,
|
||||
"weight_per_unit": 0.0,
|
||||
"weight_uom": null
|
||||
},
|
||||
{
|
||||
"actual_batch_qty": 0.0,
|
||||
"actual_qty": 0.0,
|
||||
"allow_zero_valuation_rate": 0,
|
||||
"amount": 560.0,
|
||||
"asset": null,
|
||||
"barcode": null,
|
||||
"base_amount": 560.0,
|
||||
"base_net_amount": 560.0,
|
||||
"base_net_rate": 70.0,
|
||||
"base_price_list_rate": 0.0,
|
||||
"base_rate": 70.0,
|
||||
"base_rate_with_margin": 0.0,
|
||||
"batch_no": null,
|
||||
"brand": null,
|
||||
"conversion_factor": 1.0,
|
||||
"cost_center": "Main - _T",
|
||||
"customer_item_code": null,
|
||||
"deferred_revenue_account": null,
|
||||
"delivered_by_supplier": 0,
|
||||
"delivered_qty": 0.0,
|
||||
"delivery_note": null,
|
||||
"description": "<div class=\"ql-editor read-mode\"><p>New</p></div>",
|
||||
"discount_amount": 0.0,
|
||||
"discount_percentage": 0.0,
|
||||
"dn_detail": null,
|
||||
"enable_deferred_revenue": 0,
|
||||
"expense_account": null,
|
||||
"finance_book": null,
|
||||
"image": null,
|
||||
"income_account": "Sales - _T",
|
||||
"incoming_rate": 0.0,
|
||||
"is_fixed_asset": 0,
|
||||
"is_free_item": 0,
|
||||
"item_code": null,
|
||||
"item_group": null,
|
||||
"item_name": "Toyo tyres",
|
||||
"item_tax_rate": "{\"VAT on Sales - _T\": 0.0}",
|
||||
"item_tax_template": null,
|
||||
"margin_rate_or_amount": 0.0,
|
||||
"margin_type": "",
|
||||
"net_amount": 560.0,
|
||||
"net_rate": 70.0,
|
||||
"page_break": 0,
|
||||
"parent": null,
|
||||
"parentfield": "items",
|
||||
"parenttype": "Sales Invoice",
|
||||
"price_list_rate": 0.0,
|
||||
"pricing_rules": null,
|
||||
"project": null,
|
||||
"qty": 8.0,
|
||||
"quality_inspection": null,
|
||||
"rate": 70.0,
|
||||
"rate_with_margin": 0.0,
|
||||
"sales_invoice_item": null,
|
||||
"sales_order": null,
|
||||
"serial_no": null,
|
||||
"service_end_date": null,
|
||||
"service_start_date": null,
|
||||
"service_stop_date": null,
|
||||
"so_detail": null,
|
||||
"stock_qty": 8.0,
|
||||
"stock_uom": null,
|
||||
"stock_uom_rate": 70.0,
|
||||
"target_warehouse": null,
|
||||
"total_weight": 0.0,
|
||||
"uom": "Nos",
|
||||
"warehouse": null,
|
||||
"weight_per_unit": 0.0,
|
||||
"weight_uom": null
|
||||
}
|
||||
],
|
||||
"language": "en",
|
||||
"letter_head": null,
|
||||
"loyalty_amount": 0.0,
|
||||
"loyalty_points": 0,
|
||||
"loyalty_program": null,
|
||||
"loyalty_redemption_account": null,
|
||||
"loyalty_redemption_cost_center": null,
|
||||
"modified": "2021-02-16 05:18:59.755144",
|
||||
"name": null,
|
||||
"naming_series": "ACC-SINV-.YYYY.-",
|
||||
"net_total": 825.0,
|
||||
"other_charges_calculation": "<div class=\"tax-break-up\" style=\"overflow-x: auto;\">\n\t<table class=\"table table-bordered table-hover\">\n\t\t<thead>\n\t\t\t<tr>\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t<th class=\"text-left\">Item</th>\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t<th class=\"text-right\">Taxable Amount</th>\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t<th class=\"text-right\">VAT on Sales</th>\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t</tr>\n\t\t</thead>\n\t\t<tbody>\n\t\t\t\n\t\t\t\t<tr>\n\t\t\t\t\t<td>Dunlop tyres</td>\n\t\t\t\t\t<td class='text-right'>\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\u00a3 200.00\n\t\t\t\t\t\t\n\t\t\t\t\t</td>\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t<td class='text-right'>\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t(20.0%)\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\u00a3 40.00\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t</tr>\n\t\t\t\n\t\t\t\t<tr>\n\t\t\t\t\t<td>Continental tyres</td>\n\t\t\t\t\t<td class='text-right'>\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\u00a3 65.00\n\t\t\t\t\t\t\n\t\t\t\t\t</td>\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t<td class='text-right'>\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t(5.0%)\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\u00a3 3.25\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t</tr>\n\t\t\t\n\t\t\t\t<tr>\n\t\t\t\t\t<td>Toyo tyres</td>\n\t\t\t\t\t<td class='text-right'>\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\u00a3 560.00\n\t\t\t\t\t\t\n\t\t\t\t\t</td>\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t<td class='text-right'>\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t(0.0%)\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\u00a3 0.00\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t</tr>\n\t\t\t\n\t\t</tbody>\n\t</table>\n</div>",
|
||||
"outstanding_amount": 868.25,
|
||||
"packed_items": [],
|
||||
"paid_amount": 0.0,
|
||||
"parent": null,
|
||||
"parentfield": null,
|
||||
"parenttype": null,
|
||||
"party_account_currency": "GBP",
|
||||
"payment_schedule": [],
|
||||
"payment_terms_template": null,
|
||||
"payments": [],
|
||||
"plc_conversion_rate": 1.0,
|
||||
"po_date": null,
|
||||
"po_no": "",
|
||||
"pos_profile": null,
|
||||
"posting_date": null,
|
||||
"posting_time": "5:19:02.994077",
|
||||
"price_list_currency": "GBP",
|
||||
"pricing_rules": [],
|
||||
"project": null,
|
||||
"redeem_loyalty_points": 0,
|
||||
"remarks": "No Remarks",
|
||||
"represents_company": "",
|
||||
"return_against": null,
|
||||
"rounded_total": 868.25,
|
||||
"rounding_adjustment": 0.0,
|
||||
"sales_partner": null,
|
||||
"sales_team": [],
|
||||
"scan_barcode": null,
|
||||
"select_print_heading": null,
|
||||
"selling_price_list": "Standard Selling",
|
||||
"set_posting_time": 0,
|
||||
"set_target_warehouse": null,
|
||||
"set_warehouse": null,
|
||||
"shipping_address": null,
|
||||
"shipping_address_name": "",
|
||||
"shipping_rule": null,
|
||||
"source": null,
|
||||
"status": "Overdue",
|
||||
"tax_category": "",
|
||||
"tax_id": null,
|
||||
"taxes": [
|
||||
{
|
||||
"account_head": "VAT on Sales - _T",
|
||||
"base_tax_amount": 43.25,
|
||||
"base_tax_amount_after_discount_amount": 43.25,
|
||||
"base_total": 868.25,
|
||||
"charge_type": "On Net Total",
|
||||
"cost_center": "Main - _T",
|
||||
"description": "VAT on Sales",
|
||||
"included_in_print_rate": 0,
|
||||
"item_wise_tax_detail": "{\"Dunlop tyres\":[20.0,40.0],\"Continental tyres\":[5.0,3.25],\"Toyo tyres\":[0.0,0.0]}",
|
||||
"parent": null,
|
||||
"parentfield": "taxes",
|
||||
"parenttype": "Sales Invoice",
|
||||
"rate": 0.0,
|
||||
"row_id": null,
|
||||
"tax_amount": 43.25,
|
||||
"tax_amount_after_discount_amount": 43.25,
|
||||
"total": 868.25
|
||||
}
|
||||
],
|
||||
"taxes_and_charges": null,
|
||||
"tc_name": null,
|
||||
"terms": null,
|
||||
"territory": "All Territories",
|
||||
"timesheets": [],
|
||||
"title": "_Sales Invoice",
|
||||
"to_date": null,
|
||||
"total": 825.0,
|
||||
"total_advance": 0.0,
|
||||
"total_billing_amount": 0.0,
|
||||
"total_commission": 0.0,
|
||||
"total_net_weight": 0.0,
|
||||
"total_qty": 13.0,
|
||||
"total_taxes_and_charges": 43.25,
|
||||
"unrealized_profit_loss_account": null,
|
||||
"update_billed_amount_in_sales_order": 0,
|
||||
"update_stock": 0,
|
||||
"write_off_account": null,
|
||||
"write_off_amount": 0.0,
|
||||
"write_off_cost_center": null,
|
||||
"write_off_outstanding_amount_automatically": 0
|
||||
}
|
||||
]
|
178
erpnext/accounts/report/tax_detail/test_tax_detail.py
Normal file
178
erpnext/accounts/report/tax_detail/test_tax_detail.py
Normal file
@ -0,0 +1,178 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
import unittest
|
||||
import datetime
|
||||
import json
|
||||
import os
|
||||
from frappe.utils import getdate, add_to_date, get_first_day, get_last_day, get_year_start, get_year_ending
|
||||
from .tax_detail import filter_match, save_custom_report
|
||||
|
||||
class TestTaxDetail(unittest.TestCase):
|
||||
def load_testdocs(self):
|
||||
from erpnext.accounts.utils import get_fiscal_year, FiscalYearError
|
||||
datapath, _ = os.path.splitext(os.path.realpath(__file__))
|
||||
with open(datapath + '.json', 'r') as fp:
|
||||
docs = json.load(fp)
|
||||
|
||||
now = getdate()
|
||||
self.from_date = get_first_day(now)
|
||||
self.to_date = get_last_day(now)
|
||||
|
||||
try:
|
||||
get_fiscal_year(now, company="_T")
|
||||
except FiscalYearError:
|
||||
docs = [{
|
||||
"companies": [{
|
||||
"company": "_T",
|
||||
"parent": "_Test Fiscal",
|
||||
"parentfield": "companies",
|
||||
"parenttype": "Fiscal Year"
|
||||
}],
|
||||
"doctype": "Fiscal Year",
|
||||
"year": "_Test Fiscal",
|
||||
"year_end_date": get_year_ending(now),
|
||||
"year_start_date": get_year_start(now)
|
||||
}] + docs
|
||||
|
||||
docs = [{
|
||||
"abbr": "_T",
|
||||
"company_name": "_T",
|
||||
"country": "United Kingdom",
|
||||
"default_currency": "GBP",
|
||||
"doctype": "Company",
|
||||
"name": "_T"
|
||||
}] + docs
|
||||
|
||||
for doc in docs:
|
||||
try:
|
||||
db_doc = frappe.get_doc(doc)
|
||||
if 'Invoice' in db_doc.doctype:
|
||||
db_doc.due_date = add_to_date(now, days=1)
|
||||
db_doc.insert()
|
||||
# Create GL Entries:
|
||||
db_doc.submit()
|
||||
else:
|
||||
db_doc.insert()
|
||||
except frappe.exceptions.DuplicateEntryError:
|
||||
pass
|
||||
|
||||
def load_defcols(self):
|
||||
self.company = frappe.get_doc('Company', '_T')
|
||||
custom_report = frappe.get_doc('Report', 'Tax Detail')
|
||||
self.default_columns, _ = custom_report.run_query_report(
|
||||
filters={
|
||||
'from_date': '2021-03-01',
|
||||
'to_date': '2021-03-31',
|
||||
'company': self.company.name,
|
||||
'mode': 'run',
|
||||
'report_name': 'Tax Detail'
|
||||
}, user=frappe.session.user)
|
||||
|
||||
def rm_testdocs(self):
|
||||
"Remove the Company and all data"
|
||||
from erpnext.setup.doctype.company.delete_company_transactions import delete_company_transactions
|
||||
delete_company_transactions(self.company.name)
|
||||
self.company.delete()
|
||||
|
||||
|
||||
def test_report(self):
|
||||
self.load_testdocs()
|
||||
self.load_defcols()
|
||||
report_name = save_custom_report(
|
||||
'Tax Detail',
|
||||
'_Test Tax Detail',
|
||||
json.dumps({
|
||||
'columns': self.default_columns,
|
||||
'sections': {
|
||||
'Box1':{'Filter0':{'type':'filter','filters':{'4':'VAT on Sales'}}},
|
||||
'Box2':{'Filter0':{'type':'filter','filters':{'4':'Acquisition'}}},
|
||||
'Box3':{'Box1':{'type':'section'},'Box2':{'type':'section'}},
|
||||
'Box4':{'Filter0':{'type':'filter','filters':{'4':'VAT on Purchases'}}},
|
||||
'Box5':{'Box3':{'type':'section'},'Box4':{'type':'section'}},
|
||||
'Box6':{'Filter0':{'type':'filter','filters':{'3':'!=Tax','4':'Sales'}}},
|
||||
'Box7':{'Filter0':{'type':'filter','filters':{'2':'Expense','3':'!=Tax'}}},
|
||||
'Box8':{'Filter0':{'type':'filter','filters':{'3':'!=Tax','4':'Sales','12':'EU'}}},
|
||||
'Box9':{'Filter0':{'type':'filter','filters':{'2':'Expense','3':'!=Tax','12':'EU'}}}
|
||||
},
|
||||
'show_detail': 1
|
||||
}))
|
||||
data = frappe.desk.query_report.run(report_name,
|
||||
filters={
|
||||
'from_date': self.from_date,
|
||||
'to_date': self.to_date,
|
||||
'company': self.company.name,
|
||||
'mode': 'run',
|
||||
'report_name': report_name
|
||||
}, user=frappe.session.user)
|
||||
|
||||
self.assertListEqual(data.get('columns'), self.default_columns)
|
||||
expected = (('Box1', 43.25), ('Box2', 0.0), ('Box3', 43.25), ('Box4', -85.28), ('Box5', -42.03),
|
||||
('Box6', 825.0), ('Box7', -426.40), ('Box8', 0.0), ('Box9', 0.0))
|
||||
exrow = iter(expected)
|
||||
for row in data.get('result'):
|
||||
if row.get('voucher_no') and not row.get('posting_date'):
|
||||
label, value = next(exrow)
|
||||
self.assertDictEqual(row, {'voucher_no': label, 'amount': value})
|
||||
self.assertListEqual(data.get('report_summary'),
|
||||
[{'label': label, 'datatype': 'Currency', 'value': value} for label, value in expected])
|
||||
|
||||
self.rm_testdocs()
|
||||
|
||||
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 '))
|
@ -434,6 +434,16 @@
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "GL Entry",
|
||||
"hidden": 0,
|
||||
"is_query_report": 1,
|
||||
"label": "Tax Detail",
|
||||
"link_to": "Tax Detail",
|
||||
"link_type": "Report",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "GL Entry",
|
||||
"hidden": 0,
|
||||
@ -442,6 +452,7 @@
|
||||
"link_to": "DATEV",
|
||||
"link_type": "Report",
|
||||
"onboard": 0,
|
||||
"only_for": "Germany",
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
@ -452,6 +463,7 @@
|
||||
"link_to": "UAE VAT 201",
|
||||
"link_type": "Report",
|
||||
"onboard": 0,
|
||||
"only_for": "United Arab Emirates",
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
@ -1052,7 +1064,7 @@
|
||||
"type": "Link"
|
||||
}
|
||||
],
|
||||
"modified": "2021-05-12 11:48:01.905144",
|
||||
"modified": "2021-05-13 13:44:56.249888",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounting",
|
||||
|
Loading…
Reference in New Issue
Block a user