fix: rewrite to allow referring to existing sections and reduce to single amount column
This commit is contained in:
parent
1c37390899
commit
2cb0da8780
@ -43,7 +43,7 @@ frappe.query_reports["Tax Detail"] = {
|
|||||||
fieldname: "mode",
|
fieldname: "mode",
|
||||||
label: __("Mode"),
|
label: __("Mode"),
|
||||||
fieldtype: "Read Only",
|
fieldtype: "Read Only",
|
||||||
default: "run",
|
default: "edit",
|
||||||
hidden: 1,
|
hidden: 1,
|
||||||
reqd: 1
|
reqd: 1
|
||||||
}
|
}
|
||||||
@ -83,12 +83,12 @@ erpnext.TaxDetail = class TaxDetail {
|
|||||||
// The last thing to run after datatable_render in refresh()
|
// The last thing to run after datatable_render in refresh()
|
||||||
this.super.show_footer_message.apply(this.qr);
|
this.super.show_footer_message.apply(this.qr);
|
||||||
if (this.qr.report_name !== 'Tax Detail') {
|
if (this.qr.report_name !== 'Tax Detail') {
|
||||||
this.set_value_options();
|
|
||||||
this.show_help();
|
this.show_help();
|
||||||
if (this.loading) {
|
if (this.loading) {
|
||||||
this.set_section('');
|
this.set_section('');
|
||||||
|
} else {
|
||||||
|
this.reload_component('');
|
||||||
}
|
}
|
||||||
this.reload_filter();
|
|
||||||
}
|
}
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
@ -134,6 +134,7 @@ erpnext.TaxDetail = class TaxDetail {
|
|||||||
return new_items;
|
return new_items;
|
||||||
}
|
}
|
||||||
save_report() {
|
save_report() {
|
||||||
|
this.check_datatable();
|
||||||
if (this.qr.report_name !== 'Tax Detail') {
|
if (this.qr.report_name !== 'Tax Detail') {
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method:'erpnext.accounts.report.tax_detail.tax_detail.save_custom_report',
|
method:'erpnext.accounts.report.tax_detail.tax_detail.save_custom_report',
|
||||||
@ -152,55 +153,13 @@ erpnext.TaxDetail = class TaxDetail {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
set_value_options() {
|
check_datatable() {
|
||||||
// May be run with no columns or data
|
if (!this.qr.datatable) {
|
||||||
if (this.qr.columns) {
|
frappe.throw(__('Please change the date range to load data first'));
|
||||||
this.fieldname_lookup = {};
|
|
||||||
this.label_lookup = {};
|
|
||||||
this.qr.columns.forEach((col, index) => {
|
|
||||||
if (col['fieldtype'] == "Currency") {
|
|
||||||
this.fieldname_lookup[col['label']] = col['fieldname'];
|
|
||||||
this.label_lookup[col['fieldname']] = col['label'];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const options = Object.keys(this.fieldname_lookup);
|
|
||||||
this.controls['value_field'].$wrapper.find("select").empty().add_options(options);
|
|
||||||
this.controls['value_field'].set_input(options[0]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
set_value_label_from_filter() {
|
|
||||||
const section_name = this.controls['section_name'].get_input_value();
|
|
||||||
const fidx = this.controls['filter_index'].get_input_value();
|
|
||||||
if (section_name && fidx) {
|
|
||||||
const fieldname = this.sections[section_name][fidx]['fieldname'];
|
|
||||||
this.controls['value_field'].set_input(this.label_lookup[fieldname]);
|
|
||||||
} else {
|
|
||||||
this.controls['value_field'].set_input(Object.keys(this.fieldname_lookup)[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
get_value_fieldname() {
|
|
||||||
const curlabel = this.controls['value_field'].get_input_value();
|
|
||||||
return this.fieldname_lookup[curlabel];
|
|
||||||
}
|
|
||||||
new_section(label) {
|
|
||||||
const dialog = new frappe.ui.Dialog({
|
|
||||||
title: label,
|
|
||||||
fields: [{
|
|
||||||
fieldname: 'data',
|
|
||||||
label: label,
|
|
||||||
fieldtype: 'Data'
|
|
||||||
}],
|
|
||||||
primary_action_label: label,
|
|
||||||
primary_action: (values) => {
|
|
||||||
dialog.hide();
|
|
||||||
this.set_section(values.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
dialog.show();
|
|
||||||
}
|
|
||||||
set_section(name) {
|
set_section(name) {
|
||||||
// Sets the given section name and then reloads the data
|
// Sets the given section name and then reloads the data
|
||||||
this.controls['filter_index'].set_input('');
|
|
||||||
if (name && !this.sections[name]) {
|
if (name && !this.sections[name]) {
|
||||||
this.sections[name] = {};
|
this.sections[name] = {};
|
||||||
}
|
}
|
||||||
@ -225,43 +184,49 @@ erpnext.TaxDetail = class TaxDetail {
|
|||||||
if (refresh) {
|
if (refresh) {
|
||||||
this.qr.refresh();
|
this.qr.refresh();
|
||||||
}
|
}
|
||||||
this.reload_filter();
|
this.reload_component('');
|
||||||
}
|
}
|
||||||
reload_filter() {
|
reload_component(component_name) {
|
||||||
const section_name = this.controls['section_name'].get_input_value();
|
const section_name = this.controls['section_name'].get_input_value();
|
||||||
if (section_name) {
|
if (section_name) {
|
||||||
let fidx = this.controls['filter_index'].get_input_value();
|
const section = this.sections[section_name];
|
||||||
let section = this.sections[section_name];
|
const component_names = Object.keys(section);
|
||||||
let fidxs = Object.keys(section);
|
component_names.unshift('');
|
||||||
fidxs.unshift('');
|
this.controls['component'].$wrapper.find("select").empty().add_options(component_names);
|
||||||
this.controls['filter_index'].$wrapper.find("select").empty().add_options(fidxs);
|
this.controls['component'].set_input(component_name);
|
||||||
this.controls['filter_index'].set_input(fidx);
|
if (component_name) {
|
||||||
|
this.controls['component_type'].set_input(section[component_name].type);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.controls['filter_index'].$wrapper.find("select").empty();
|
this.controls['component'].$wrapper.find("select").empty();
|
||||||
this.controls['filter_index'].set_input('');
|
this.controls['component'].set_input('');
|
||||||
}
|
}
|
||||||
this.set_table_filters();
|
this.set_table_filters();
|
||||||
}
|
}
|
||||||
set_table_filters() {
|
set_table_filters() {
|
||||||
let filters = {};
|
let filters = {};
|
||||||
const section_name = this.controls['section_name'].get_input_value();
|
const section_name = this.controls['section_name'].get_input_value();
|
||||||
const fidx = this.controls['filter_index'].get_input_value();
|
const component_name = this.controls['component'].get_input_value();
|
||||||
if (section_name && fidx) {
|
if (section_name && component_name) {
|
||||||
filters = this.sections[section_name][fidx]['filters'];
|
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);
|
this.setAppliedFilters(filters);
|
||||||
this.set_value_label_from_filter();
|
|
||||||
}
|
}
|
||||||
setAppliedFilters(filters) {
|
setAppliedFilters(filters) {
|
||||||
Array.from(this.qr.datatable.header.querySelectorAll('.dt-filter')).map(function setFilters(input) {
|
if (this.qr.datatable) {
|
||||||
let idx = input.dataset.colIndex;
|
Array.from(this.qr.datatable.header.querySelectorAll('.dt-filter')).map(function setFilters(input) {
|
||||||
if (filters[idx]) {
|
let idx = input.dataset.colIndex;
|
||||||
input.value = filters[idx];
|
if (filters[idx]) {
|
||||||
} else {
|
input.value = filters[idx];
|
||||||
input.value = null;
|
} else {
|
||||||
}
|
input.value = null;
|
||||||
});
|
}
|
||||||
this.qr.datatable.columnmanager.applyFilter(filters);
|
});
|
||||||
|
this.qr.datatable.columnmanager.applyFilter(filters);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
delete(name, type) {
|
delete(name, type) {
|
||||||
if (type === 'section') {
|
if (type === 'section') {
|
||||||
@ -269,11 +234,10 @@ erpnext.TaxDetail = class TaxDetail {
|
|||||||
const new_section = Object.keys(this.sections)[0] || '';
|
const new_section = Object.keys(this.sections)[0] || '';
|
||||||
this.set_section(new_section);
|
this.set_section(new_section);
|
||||||
}
|
}
|
||||||
if (type === 'filter') {
|
if (type === 'component') {
|
||||||
const cur_section = this.controls['section_name'].get_input_value();
|
const cur_section = this.controls['section_name'].get_input_value();
|
||||||
delete this.sections[cur_section][name];
|
delete this.sections[cur_section][name];
|
||||||
this.controls['filter_index'].set_input('');
|
this.reload_component('');
|
||||||
this.reload_filter();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
create_controls() {
|
create_controls() {
|
||||||
@ -293,7 +257,13 @@ erpnext.TaxDetail = class TaxDetail {
|
|||||||
fieldtype: 'Button',
|
fieldtype: 'Button',
|
||||||
fieldname: 'new_section',
|
fieldname: 'new_section',
|
||||||
click: () => {
|
click: () => {
|
||||||
this.new_section(__('New Section'));
|
frappe.prompt({
|
||||||
|
label: __('Section Name'),
|
||||||
|
fieldname: 'name',
|
||||||
|
fieldtype: 'Data'
|
||||||
|
}, (values) => {
|
||||||
|
this.set_section(values.name);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
controls['delete_section'] = this.qr.page.add_field({
|
controls['delete_section'] = this.qr.page.add_field({
|
||||||
@ -308,61 +278,87 @@ erpnext.TaxDetail = class TaxDetail {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
controls['filter_index'] = this.qr.page.add_field({
|
controls['component'] = this.qr.page.add_field({
|
||||||
label: __('Filter'),
|
label: __('Component'),
|
||||||
fieldtype: 'Select',
|
fieldtype: 'Select',
|
||||||
fieldname: 'filter_index',
|
fieldname: 'component',
|
||||||
change: (e) => {
|
change: (e) => {
|
||||||
this.controls['filter_index'].set_input(this.controls['filter_index'].get_input_value());
|
this.reload_component(this.controls['component'].get_input_value());
|
||||||
this.set_table_filters();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
controls['add_filter'] = this.qr.page.add_field({
|
controls['component_type'] = this.qr.page.add_field({
|
||||||
label: __('Add Filter'),
|
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',
|
fieldtype: 'Button',
|
||||||
fieldname: 'add_filter',
|
fieldname: 'add_component',
|
||||||
click: () => {
|
click: () => {
|
||||||
|
this.check_datatable();
|
||||||
let section_name = this.controls['section_name'].get_input_value();
|
let section_name = this.controls['section_name'].get_input_value();
|
||||||
if (section_name) {
|
if (section_name) {
|
||||||
let prefix = 'Filter';
|
const component_type = this.controls['component_type'].get_input_value();
|
||||||
let data = {
|
let idx = 0;
|
||||||
filters: this.qr.datatable.columnmanager.getAppliedFilters(),
|
const names = Object.keys(this.sections[section_name]);
|
||||||
fieldname: this.get_value_fieldname()
|
if (names.length > 0) {
|
||||||
|
const idxs = names.map((key) => parseInt(key.match(/\d+$/)) || 0);
|
||||||
|
idx = Math.max(...idxs) + 1;
|
||||||
}
|
}
|
||||||
const fidxs = Object.keys(this.sections[section_name]);
|
const filters = this.qr.datatable.columnmanager.getAppliedFilters();
|
||||||
let new_idx = prefix + '0';
|
if (component_type === 'filter') {
|
||||||
if (fidxs.length > 0) {
|
const name = 'Filter' + idx.toString();
|
||||||
const fiidxs = fidxs.map((key) => parseInt(key.replace(prefix, '')));
|
let data = {
|
||||||
new_idx = prefix + (Math.max(...fiidxs) + 1).toString();
|
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'));
|
||||||
}
|
}
|
||||||
this.sections[section_name][new_idx] = data;
|
|
||||||
this.controls['filter_index'].set_input(new_idx);
|
|
||||||
this.reload_filter();
|
|
||||||
} else {
|
} else {
|
||||||
frappe.throw(__('Please add or select the Section first'));
|
frappe.throw(__('Please select the Section first'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
controls['delete_filter'] = this.qr.page.add_field({
|
controls['delete_component'] = this.qr.page.add_field({
|
||||||
label: __('Delete Filter'),
|
label: __('Delete Component'),
|
||||||
fieldtype: 'Button',
|
fieldtype: 'Button',
|
||||||
fieldname: 'delete_filter',
|
fieldname: 'delete_component',
|
||||||
click: () => {
|
click: () => {
|
||||||
let cur_filter = this.controls['filter_index'].get_input_value();
|
const component = this.controls['component'].get_input_value();
|
||||||
if (cur_filter) {
|
if (component) {
|
||||||
frappe.confirm(__('Are you sure you want to delete filter ') + cur_filter + '?',
|
frappe.confirm(__('Are you sure you want to delete component ') + component + '?',
|
||||||
() => {this.delete(cur_filter, 'filter')});
|
() => {this.delete(component, 'component')});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
controls['value_field'] = this.qr.page.add_field({
|
|
||||||
label: __('Value Column'),
|
|
||||||
fieldtype: 'Select',
|
|
||||||
fieldname: 'value_field',
|
|
||||||
change: (e) => {
|
|
||||||
this.controls['value_field'].set_input(this.controls['value_field'].get_input_value());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
controls['save'] = this.qr.page.add_field({
|
controls['save'] = this.qr.page.add_field({
|
||||||
label: __('Save & Run'),
|
label: __('Save & Run'),
|
||||||
fieldtype: 'Button',
|
fieldtype: 'Button',
|
||||||
@ -380,13 +376,16 @@ erpnext.TaxDetail = class TaxDetail {
|
|||||||
this.controls = controls;
|
this.controls = controls;
|
||||||
}
|
}
|
||||||
show_help() {
|
show_help() {
|
||||||
const help = __(`You can add multiple sections to your custom report using the New Section button above.
|
const help = __(`<strong>Help:</strong> Your custom report is built from General Ledger Entries within the date range.
|
||||||
To specify what data goes in each section, specify column filters in the data table, then save with Add Filter.
|
You can add multiple sections to the report using the New Section button.
|
||||||
Each section can have multiple filters added but be careful with the duplicated data rows.
|
Each component added to a section adds a subset of the data into the specified section.
|
||||||
You can specify which Currency column will be summed for each filter in the final report with the Value Column
|
Beware of duplicated data rows.
|
||||||
select box. Use the Show Detail box to see the data rows included in each section in the final report.
|
The Filtered Row component type saves the datatable column filters to specify the added data.
|
||||||
Once you're done, hit Save & Run.`);
|
The Section component type refers to the data in a previously defined section, but it cannot refer to its parent section.
|
||||||
this.qr.$report_footer.append(`<div class="col-md-12">${help}</div>`);
|
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">${help}<a href="https://www.casesolved.co.uk"> Case Solved</a></div>`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ from __future__ import unicode_literals
|
|||||||
import frappe, json
|
import frappe, json
|
||||||
from frappe import _
|
from frappe import _
|
||||||
|
|
||||||
# NOTE: Payroll is implemented using Journal Entries which translate directly to GL Entries
|
# NOTE: Payroll is implemented using Journal Entries which are included as GL Entries
|
||||||
|
|
||||||
# field lists in multiple doctypes will be coalesced
|
# field lists in multiple doctypes will be coalesced
|
||||||
required_sql_fields = {
|
required_sql_fields = {
|
||||||
@ -60,23 +60,35 @@ def run_report(report_name, data):
|
|||||||
columns = report_config.get('columns')
|
columns = report_config.get('columns')
|
||||||
sections = report_config.get('sections', {})
|
sections = report_config.get('sections', {})
|
||||||
show_detail = report_config.get('show_detail', 1)
|
show_detail = report_config.get('show_detail', 1)
|
||||||
|
report = {}
|
||||||
new_data = []
|
new_data = []
|
||||||
summary = []
|
summary = []
|
||||||
for section_name, section in sections.items():
|
for section_name, section in sections.items():
|
||||||
section_total = 0.0
|
report[section_name] = {'rows': [], 'subtotal': 0.0}
|
||||||
for filt_name, filt in section.items():
|
for component_name, component in section.items():
|
||||||
value_field = filt['fieldname']
|
if component['type'] == 'filter':
|
||||||
rmidxs = []
|
for row in data:
|
||||||
for colno, filter_string in filt['filters'].items():
|
matched = True
|
||||||
filter_field = columns[int(colno) - 1]['fieldname']
|
for colno, filter_string in component['filters'].items():
|
||||||
for i, row in enumerate(data):
|
filter_field = columns[int(colno) - 1]['fieldname']
|
||||||
if not filter_match(row[filter_field], filter_string):
|
if not filter_match(row[filter_field], filter_string):
|
||||||
rmidxs += [i]
|
matched = False
|
||||||
rows = [row for i, row in enumerate(data) if i not in rmidxs]
|
break
|
||||||
section_total += subtotal(rows, value_field)
|
if matched:
|
||||||
if show_detail: new_data += rows
|
report[section_name]['rows'] += [row]
|
||||||
new_data += [ {columns[1]['fieldname']: section_name, columns[2]['fieldname']: section_total} ]
|
report[section_name]['subtotal'] += row['amount']
|
||||||
summary += [ {'label': section_name, 'datatype': 'Currency', 'value': section_total} ]
|
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 += [ {} ]
|
if show_detail: new_data += [ {} ]
|
||||||
return new_data or data, summary or None
|
return new_data or data, summary or None
|
||||||
|
|
||||||
@ -123,11 +135,6 @@ def filter_match(value, string):
|
|||||||
if operator == '<': return True
|
if operator == '<': return True
|
||||||
return False
|
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(' ')) + '.'
|
abbrev = lambda dt: ''.join(l[0].lower() for l in dt.split(' ')) + '.'
|
||||||
doclist = lambda dt, dfs: [abbrev(dt) + f for f in dfs]
|
doclist = lambda dt, dfs: [abbrev(dt) + f for f in dfs]
|
||||||
@ -185,6 +192,9 @@ def modify_report_columns(doctype, field, column):
|
|||||||
if field in ["item_tax_rate", "base_net_amount"]:
|
if field in ["item_tax_rate", "base_net_amount"]:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
if doctype == "GL Entry" and field in ["debit", "credit"]:
|
||||||
|
column.update({"label": _("Amount"), "fieldname": "amount"})
|
||||||
|
|
||||||
if field == "taxes_and_charges":
|
if field == "taxes_and_charges":
|
||||||
column.update({"label": _("Taxes and Charges Template")})
|
column.update({"label": _("Taxes and Charges Template")})
|
||||||
return column
|
return column
|
||||||
@ -193,6 +203,8 @@ def modify_report_data(data):
|
|||||||
import json
|
import json
|
||||||
new_data = []
|
new_data = []
|
||||||
for line in 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
|
# Remove Invoice GL Tax Entries and generate Tax entries from the invoice lines
|
||||||
if "Invoice" in line.voucher_type:
|
if "Invoice" in line.voucher_type:
|
||||||
if line.account_type != "Tax":
|
if line.account_type != "Tax":
|
||||||
@ -204,11 +216,11 @@ def modify_report_data(data):
|
|||||||
tax_line.account_type = "Tax"
|
tax_line.account_type = "Tax"
|
||||||
tax_line.account = account
|
tax_line.account = account
|
||||||
if line.voucher_type == "Sales Invoice":
|
if line.voucher_type == "Sales Invoice":
|
||||||
line.credit = line.base_net_amount
|
line.amount = line.base_net_amount
|
||||||
tax_line.credit = line.base_net_amount * (rate / 100)
|
tax_line.amount = line.base_net_amount * (rate / 100)
|
||||||
if line.voucher_type == "Purchase Invoice":
|
if line.voucher_type == "Purchase Invoice":
|
||||||
line.debit = line.base_net_amount
|
line.amount = -line.base_net_amount
|
||||||
tax_line.debit = line.base_net_amount * (rate / 100)
|
tax_line.amount = -line.base_net_amount * (rate / 100)
|
||||||
new_data += [tax_line]
|
new_data += [tax_line]
|
||||||
else:
|
else:
|
||||||
new_data += [line]
|
new_data += [line]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user