mirror of
https://github.com/frappe/erpnext.git
synced 2026-04-17 22:05:10 +00:00
Merge remote-tracking branch 'upstream/develop' into payments-based-dunning
This commit is contained in:
@@ -6,7 +6,7 @@ context('Organizational Chart', () => {
|
||||
|
||||
it('navigates to org chart', () => {
|
||||
cy.visit('/app');
|
||||
cy.awesomebar('Organizational Chart');
|
||||
cy.visit('/app/organizational-chart');
|
||||
cy.url().should('include', '/organizational-chart');
|
||||
|
||||
cy.window().its('frappe.csrf_token').then(csrf_token => {
|
||||
|
||||
@@ -7,7 +7,7 @@ context('Organizational Chart Mobile', () => {
|
||||
it('navigates to org chart', () => {
|
||||
cy.viewport(375, 667);
|
||||
cy.visit('/app');
|
||||
cy.awesomebar('Organizational Chart');
|
||||
cy.visit('/app/organizational-chart');
|
||||
cy.url().should('include', '/organizational-chart');
|
||||
|
||||
cy.window().its('frappe.csrf_token').then(csrf_token => {
|
||||
|
||||
@@ -374,12 +374,15 @@ def make_gl_entries(doc, credit_account, debit_account, against,
|
||||
try:
|
||||
make_gl_entries(gl_entries, cancel=(doc.docstatus == 2), merge_entries=True)
|
||||
frappe.db.commit()
|
||||
except Exception:
|
||||
frappe.db.rollback()
|
||||
traceback = frappe.get_traceback()
|
||||
frappe.log_error(message=traceback)
|
||||
except Exception as e:
|
||||
if frappe.flags.in_test:
|
||||
raise e
|
||||
else:
|
||||
frappe.db.rollback()
|
||||
traceback = frappe.get_traceback()
|
||||
frappe.log_error(message=traceback)
|
||||
|
||||
frappe.flags.deferred_accounting_error = True
|
||||
frappe.flags.deferred_accounting_error = True
|
||||
|
||||
def send_mail(deferred_process):
|
||||
title = _("Error while processing deferred accounting for {0}").format(deferred_process)
|
||||
|
||||
@@ -79,7 +79,6 @@ frappe.ui.form.on('Chart of Accounts Importer', {
|
||||
$(frm.fields_dict['chart_tree'].wrapper).empty(); // empty wrapper on removing file
|
||||
} else {
|
||||
generate_tree_preview(frm);
|
||||
validate_csv_data(frm);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -104,23 +103,6 @@ frappe.ui.form.on('Chart of Accounts Importer', {
|
||||
}
|
||||
});
|
||||
|
||||
var validate_csv_data = function(frm) {
|
||||
frappe.call({
|
||||
method: "erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.validate_accounts",
|
||||
args: {file_name: frm.doc.import_file},
|
||||
callback: function(r) {
|
||||
if(r.message && r.message[0]===true) {
|
||||
frm.page["show_import_button"] = true;
|
||||
frm.page["total_accounts"] = r.message[1];
|
||||
frm.trigger("refresh");
|
||||
} else {
|
||||
frm.page.set_indicator(__('Resolve error and upload again.'), 'orange');
|
||||
frappe.throw(__(r.message));
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var create_import_button = function(frm) {
|
||||
frm.page.set_primary_action(__("Import"), function () {
|
||||
frappe.call({
|
||||
@@ -151,23 +133,25 @@ var create_reset_button = function(frm) {
|
||||
};
|
||||
|
||||
var generate_tree_preview = function(frm) {
|
||||
let parent = __('All Accounts');
|
||||
$(frm.fields_dict['chart_tree'].wrapper).empty(); // empty wrapper to load new data
|
||||
if (frm.doc.import_file) {
|
||||
let parent = __('All Accounts');
|
||||
$(frm.fields_dict['chart_tree'].wrapper).empty(); // empty wrapper to load new data
|
||||
|
||||
// generate tree structure based on the csv data
|
||||
new frappe.ui.Tree({
|
||||
parent: $(frm.fields_dict['chart_tree'].wrapper),
|
||||
label: parent,
|
||||
expandable: true,
|
||||
method: 'erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.get_coa',
|
||||
args: {
|
||||
file_name: frm.doc.import_file,
|
||||
parent: parent,
|
||||
doctype: 'Chart of Accounts Importer',
|
||||
file_type: frm.doc.file_type
|
||||
},
|
||||
onclick: function(node) {
|
||||
parent = node.value;
|
||||
}
|
||||
});
|
||||
// generate tree structure based on the csv data
|
||||
new frappe.ui.Tree({
|
||||
parent: $(frm.fields_dict['chart_tree'].wrapper),
|
||||
label: parent,
|
||||
expandable: true,
|
||||
method: 'erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.get_coa',
|
||||
args: {
|
||||
file_name: frm.doc.import_file,
|
||||
parent: parent,
|
||||
doctype: 'Chart of Accounts Importer',
|
||||
file_type: frm.doc.file_type
|
||||
},
|
||||
onclick: function(node) {
|
||||
parent = node.value;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -25,8 +25,16 @@ from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import
|
||||
|
||||
|
||||
class ChartofAccountsImporter(Document):
|
||||
def validate(self):
|
||||
validate_accounts(self.import_file)
|
||||
pass
|
||||
|
||||
def validate_columns(data):
|
||||
if not data:
|
||||
frappe.throw(_('No data found. Seems like you uploaded a blank file'))
|
||||
|
||||
no_of_columns = max([len(d) for d in data])
|
||||
|
||||
if no_of_columns > 7:
|
||||
frappe.throw(_('More columns found than expected. Please compare the uploaded file with standard template'))
|
||||
|
||||
@frappe.whitelist()
|
||||
def validate_company(company):
|
||||
@@ -131,6 +139,8 @@ def get_coa(doctype, parent, is_root=False, file_name=None):
|
||||
else:
|
||||
data = generate_data_from_excel(file_doc, extension)
|
||||
|
||||
validate_columns(data)
|
||||
validate_accounts(data)
|
||||
forest = build_forest(data)
|
||||
accounts = build_tree_from_json("", chart_data=forest) # returns alist of dict in a tree render-able form
|
||||
|
||||
@@ -322,9 +332,6 @@ def validate_accounts(file_name):
|
||||
|
||||
def validate_root(accounts):
|
||||
roots = [accounts[d] for d in accounts if not accounts[d].get('parent_account')]
|
||||
if len(roots) < 4:
|
||||
frappe.throw(_("Number of root accounts cannot be less than 4"))
|
||||
|
||||
error_messages = []
|
||||
|
||||
for account in roots:
|
||||
@@ -364,20 +371,12 @@ def get_mandatory_account_types():
|
||||
|
||||
def validate_account_types(accounts):
|
||||
account_types_for_ledger = ["Cost of Goods Sold", "Depreciation", "Fixed Asset", "Payable", "Receivable", "Stock Adjustment"]
|
||||
account_types = [accounts[d]["account_type"] for d in accounts if not accounts[d]['is_group'] == 1]
|
||||
account_types = [accounts[d]["account_type"] for d in accounts if not cint(accounts[d]['is_group']) == 1]
|
||||
|
||||
missing = list(set(account_types_for_ledger) - set(account_types))
|
||||
if missing:
|
||||
frappe.throw(_("Please identify/create Account (Ledger) for type - {0}").format(' , '.join(missing)))
|
||||
|
||||
account_types_for_group = ["Bank", "Cash", "Stock"]
|
||||
# fix logic bug
|
||||
account_groups = [accounts[d]["account_type"] for d in accounts if accounts[d]['is_group'] == 1]
|
||||
|
||||
missing = list(set(account_types_for_group) - set(account_groups))
|
||||
if missing:
|
||||
frappe.throw(_("Please identify/create Account (Group) for type - {0}").format(' , '.join(missing)))
|
||||
|
||||
def unset_existing_data(company):
|
||||
linked = frappe.db.sql('''select fieldname from tabDocField
|
||||
where fieldtype="Link" and options="Account" and parent="Company"''', as_dict=True)
|
||||
|
||||
@@ -1,63 +1,33 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2014-10-02 13:35:44.155278",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"editable_grid": 1,
|
||||
"actions": [],
|
||||
"creation": "2014-10-02 13:35:44.155278",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"company"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Company",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Company",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"ignore_user_permissions": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Company",
|
||||
"options": "Company"
|
||||
}
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2016-07-11 03:28:00.505946",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Fiscal Year Company",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_seen": 0
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-09-28 18:01:53.495929",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Fiscal Year Company",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -13,10 +13,12 @@
|
||||
"voucher_type",
|
||||
"naming_series",
|
||||
"finance_book",
|
||||
"tax_withholding_category",
|
||||
"column_break1",
|
||||
"from_template",
|
||||
"company",
|
||||
"posting_date",
|
||||
"apply_tds",
|
||||
"2_add_edit_gl_entries",
|
||||
"accounts",
|
||||
"section_break99",
|
||||
@@ -498,16 +500,32 @@
|
||||
"options": "Journal Entry Template",
|
||||
"print_hide": 1,
|
||||
"report_hide": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.apply_tds",
|
||||
"fieldname": "tax_withholding_category",
|
||||
"fieldtype": "Link",
|
||||
"label": "Tax Withholding Category",
|
||||
"mandatory_depends_on": "eval:doc.apply_tds",
|
||||
"options": "Tax Withholding Category"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:['Credit Note', 'Debit Note'].includes(doc.voucher_type)",
|
||||
"fieldname": "apply_tds",
|
||||
"fieldtype": "Check",
|
||||
"label": "Apply Tax Withholding Amount "
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
"idx": 176,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-10-30 13:56:01.121995",
|
||||
"modified": "2021-09-09 15:31:14.484029",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Journal Entry",
|
||||
"naming_rule": "By \"Naming Series\" field",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
|
||||
@@ -15,6 +15,9 @@ from erpnext.accounts.deferred_revenue import get_deferred_booking_accounts
|
||||
from erpnext.accounts.doctype.invoice_discounting.invoice_discounting import (
|
||||
get_party_account_based_on_invoice_discounting,
|
||||
)
|
||||
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import (
|
||||
get_party_tax_withholding_details,
|
||||
)
|
||||
from erpnext.accounts.party import get_party_account
|
||||
from erpnext.accounts.utils import (
|
||||
check_if_stock_and_account_balance_synced,
|
||||
@@ -57,7 +60,8 @@ class JournalEntry(AccountsController):
|
||||
|
||||
self.validate_against_jv()
|
||||
self.validate_reference_doc()
|
||||
self.set_against_account()
|
||||
if self.docstatus == 0:
|
||||
self.set_against_account()
|
||||
self.create_remarks()
|
||||
self.set_print_format_fields()
|
||||
self.validate_expense_claim()
|
||||
@@ -66,6 +70,10 @@ class JournalEntry(AccountsController):
|
||||
self.set_account_and_party_balance()
|
||||
self.validate_inter_company_accounts()
|
||||
self.validate_stock_accounts()
|
||||
|
||||
if self.docstatus == 0:
|
||||
self.apply_tax_withholding()
|
||||
|
||||
if not self.title:
|
||||
self.title = self.get_title()
|
||||
|
||||
@@ -139,6 +147,72 @@ class JournalEntry(AccountsController):
|
||||
frappe.throw(_("Account: {0} can only be updated via Stock Transactions")
|
||||
.format(account), StockAccountInvalidTransaction)
|
||||
|
||||
def apply_tax_withholding(self):
|
||||
from erpnext.accounts.report.general_ledger.general_ledger import get_account_type_map
|
||||
|
||||
if not self.apply_tds or self.voucher_type not in ('Debit Note', 'Credit Note'):
|
||||
return
|
||||
|
||||
parties = [d.party for d in self.get('accounts') if d.party]
|
||||
parties = list(set(parties))
|
||||
|
||||
if len(parties) > 1:
|
||||
frappe.throw(_("Cannot apply TDS against multiple parties in one entry"))
|
||||
|
||||
account_type_map = get_account_type_map(self.company)
|
||||
party_type = 'supplier' if self.voucher_type == 'Credit Note' else 'customer'
|
||||
doctype = 'Purchase Invoice' if self.voucher_type == 'Credit Note' else 'Sales Invoice'
|
||||
debit_or_credit = 'debit_in_account_currency' if self.voucher_type == 'Credit Note' else 'credit_in_account_currency'
|
||||
rev_debit_or_credit = 'credit_in_account_currency' if debit_or_credit == 'debit_in_account_currency' else 'debit_in_account_currency'
|
||||
|
||||
party_account = get_party_account(party_type.title(), parties[0], self.company)
|
||||
|
||||
net_total = sum(d.get(debit_or_credit) for d in self.get('accounts') if account_type_map.get(d.account)
|
||||
not in ('Tax', 'Chargeable'))
|
||||
|
||||
party_amount = sum(d.get(rev_debit_or_credit) for d in self.get('accounts') if d.account == party_account)
|
||||
|
||||
inv = frappe._dict({
|
||||
party_type: parties[0],
|
||||
'doctype': doctype,
|
||||
'company': self.company,
|
||||
'posting_date': self.posting_date,
|
||||
'net_total': net_total
|
||||
})
|
||||
|
||||
tax_withholding_details = get_party_tax_withholding_details(inv, self.tax_withholding_category)
|
||||
|
||||
if not tax_withholding_details:
|
||||
return
|
||||
|
||||
accounts = []
|
||||
for d in self.get('accounts'):
|
||||
if d.get('account') == tax_withholding_details.get("account_head"):
|
||||
d.update({
|
||||
'account': tax_withholding_details.get("account_head"),
|
||||
debit_or_credit: tax_withholding_details.get('tax_amount')
|
||||
})
|
||||
|
||||
accounts.append(d.get('account'))
|
||||
|
||||
if d.get('account') == party_account:
|
||||
d.update({
|
||||
rev_debit_or_credit: party_amount - tax_withholding_details.get('tax_amount')
|
||||
})
|
||||
|
||||
if not accounts or tax_withholding_details.get("account_head") not in accounts:
|
||||
self.append("accounts", {
|
||||
'account': tax_withholding_details.get("account_head"),
|
||||
rev_debit_or_credit: tax_withholding_details.get('tax_amount'),
|
||||
'against_account': parties[0]
|
||||
})
|
||||
|
||||
to_remove = [d for d in self.get('accounts')
|
||||
if not d.get(rev_debit_or_credit) and d.account == tax_withholding_details.get("account_head")]
|
||||
|
||||
for d in to_remove:
|
||||
self.remove(d)
|
||||
|
||||
def update_inter_company_jv(self):
|
||||
if self.voucher_type == "Inter Company Journal Entry" and self.inter_company_journal_entry_reference:
|
||||
frappe.db.set_value("Journal Entry", self.inter_company_journal_entry_reference,\
|
||||
|
||||
@@ -93,6 +93,7 @@
|
||||
"options": "Payment Term"
|
||||
},
|
||||
{
|
||||
"depends_on": "exchange_gain_loss",
|
||||
"fieldname": "exchange_gain_loss",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Exchange Gain/Loss",
|
||||
@@ -103,7 +104,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-04-21 13:30:11.605388",
|
||||
"modified": "2021-09-26 17:06:55.597389",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Entry Reference",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"field_order": [
|
||||
"reference_type",
|
||||
"reference_name",
|
||||
"reference_row",
|
||||
"column_break_3",
|
||||
"invoice_type",
|
||||
"invoice_number",
|
||||
@@ -121,11 +122,17 @@
|
||||
"label": "Amount",
|
||||
"options": "Currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "reference_row",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "Reference Row"
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-08-30 10:58:42.665107",
|
||||
"modified": "2021-09-20 17:23:09.455803",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Reconciliation Allocation",
|
||||
|
||||
@@ -40,6 +40,7 @@ class POSInvoice(SalesInvoice):
|
||||
self.validate_change_amount()
|
||||
self.validate_change_account()
|
||||
self.validate_item_cost_centers()
|
||||
self.validate_warehouse()
|
||||
self.validate_serialised_or_batched_item()
|
||||
self.validate_stock_availablility()
|
||||
self.validate_return_items_qty()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -15,6 +15,7 @@ from erpnext.accounts.deferred_revenue import validate_service_stop_date
|
||||
from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt
|
||||
from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
|
||||
check_if_return_invoice_linked_with_payment_entry,
|
||||
is_overdue,
|
||||
unlink_inter_company_doc,
|
||||
update_linked_doc,
|
||||
validate_inter_company_party,
|
||||
@@ -1145,6 +1146,12 @@ class PurchaseInvoice(BuyingController):
|
||||
if not self.apply_tds:
|
||||
return
|
||||
|
||||
if self.apply_tds and not self.get('tax_withholding_category'):
|
||||
self.tax_withholding_category = frappe.db.get_value('Supplier', self.supplier, 'tax_withholding_category')
|
||||
|
||||
if not self.tax_withholding_category:
|
||||
return
|
||||
|
||||
tax_withholding_details = get_party_tax_withholding_details(self, self.tax_withholding_category)
|
||||
|
||||
if not tax_withholding_details:
|
||||
@@ -1175,10 +1182,7 @@ class PurchaseInvoice(BuyingController):
|
||||
self.status = 'Draft'
|
||||
return
|
||||
|
||||
precision = self.precision("outstanding_amount")
|
||||
outstanding_amount = flt(self.outstanding_amount, precision)
|
||||
due_date = getdate(self.due_date)
|
||||
nowdate = getdate()
|
||||
outstanding_amount = flt(self.outstanding_amount, self.precision("outstanding_amount"))
|
||||
|
||||
if not status:
|
||||
if self.docstatus == 2:
|
||||
@@ -1186,9 +1190,11 @@ class PurchaseInvoice(BuyingController):
|
||||
elif self.docstatus == 1:
|
||||
if self.is_internal_transfer():
|
||||
self.status = 'Internal Transfer'
|
||||
elif outstanding_amount > 0 and due_date < nowdate:
|
||||
elif is_overdue(self):
|
||||
self.status = "Overdue"
|
||||
elif outstanding_amount > 0 and due_date >= nowdate:
|
||||
elif 0 < outstanding_amount < flt(self.grand_total, self.precision("grand_total")):
|
||||
self.status = "Partly Paid"
|
||||
elif outstanding_amount > 0 and getdate(self.due_date) >= getdate():
|
||||
self.status = "Unpaid"
|
||||
#Check if outstanding amount is 0 due to debit note issued against invoice
|
||||
elif outstanding_amount <= 0 and self.is_return == 0 and frappe.db.get_value('Purchase Invoice', {'is_return': 1, 'return_against': self.name, 'docstatus': 1}):
|
||||
|
||||
@@ -2,28 +2,58 @@
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
// render
|
||||
frappe.listview_settings['Purchase Invoice'] = {
|
||||
add_fields: ["supplier", "supplier_name", "base_grand_total", "outstanding_amount", "due_date", "company",
|
||||
"currency", "is_return", "release_date", "on_hold", "represents_company", "is_internal_supplier"],
|
||||
get_indicator: function(doc) {
|
||||
if ((flt(doc.outstanding_amount) <= 0) && doc.docstatus == 1 && doc.status == 'Debit Note Issued') {
|
||||
return [__("Debit Note Issued"), "darkgrey", "outstanding_amount,<=,0"];
|
||||
} else if (flt(doc.outstanding_amount) > 0 && doc.docstatus==1) {
|
||||
if(cint(doc.on_hold) && !doc.release_date) {
|
||||
return [__("On Hold"), "darkgrey"];
|
||||
} else if (cint(doc.on_hold) && doc.release_date && frappe.datetime.get_diff(doc.release_date, frappe.datetime.nowdate()) > 0) {
|
||||
return [__("Temporarily on Hold"), "darkgrey"];
|
||||
} else if (frappe.datetime.get_diff(doc.due_date) < 0) {
|
||||
return [__("Overdue"), "red", "outstanding_amount,>,0|due_date,<,Today"];
|
||||
} else {
|
||||
return [__("Unpaid"), "orange", "outstanding_amount,>,0|due_date,>=,Today"];
|
||||
}
|
||||
} else if (cint(doc.is_return)) {
|
||||
return [__("Return"), "gray", "is_return,=,Yes"];
|
||||
} else if (doc.company == doc.represents_company && doc.is_internal_supplier) {
|
||||
return [__("Internal Transfer"), "darkgrey", "outstanding_amount,=,0"];
|
||||
} else if (flt(doc.outstanding_amount)==0 && doc.docstatus==1) {
|
||||
return [__("Paid"), "green", "outstanding_amount,=,0"];
|
||||
frappe.listview_settings["Purchase Invoice"] = {
|
||||
add_fields: [
|
||||
"supplier",
|
||||
"supplier_name",
|
||||
"base_grand_total",
|
||||
"outstanding_amount",
|
||||
"due_date",
|
||||
"company",
|
||||
"currency",
|
||||
"is_return",
|
||||
"release_date",
|
||||
"on_hold",
|
||||
"represents_company",
|
||||
"is_internal_supplier",
|
||||
],
|
||||
get_indicator(doc) {
|
||||
if (doc.status == "Debit Note Issued") {
|
||||
return [__(doc.status), "darkgrey", "status,=," + doc.status];
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
flt(doc.outstanding_amount) > 0 &&
|
||||
doc.docstatus == 1 &&
|
||||
cint(doc.on_hold)
|
||||
) {
|
||||
if (!doc.release_date) {
|
||||
return [__("On Hold"), "darkgrey"];
|
||||
} else if (
|
||||
frappe.datetime.get_diff(
|
||||
doc.release_date,
|
||||
frappe.datetime.nowdate()
|
||||
) > 0
|
||||
) {
|
||||
return [__("Temporarily on Hold"), "darkgrey"];
|
||||
}
|
||||
}
|
||||
|
||||
const status_colors = {
|
||||
"Unpaid": "orange",
|
||||
"Paid": "green",
|
||||
"Return": "gray",
|
||||
"Overdue": "red",
|
||||
"Partly Paid": "yellow",
|
||||
"Internal Transfer": "darkgrey",
|
||||
};
|
||||
|
||||
if (status_colors[doc.status]) {
|
||||
return [
|
||||
__(doc.status),
|
||||
status_colors[doc.status],
|
||||
"status,=," + doc.status,
|
||||
];
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -97,6 +97,7 @@
|
||||
"width": "100px"
|
||||
},
|
||||
{
|
||||
"depends_on": "exchange_gain_loss",
|
||||
"fieldname": "exchange_gain_loss",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Exchange Gain/Loss",
|
||||
@@ -104,6 +105,7 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "exchange_gain_loss",
|
||||
"fieldname": "ref_exchange_rate",
|
||||
"fieldtype": "Float",
|
||||
"label": "Reference Exchange Rate",
|
||||
@@ -115,7 +117,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-04-20 16:26:53.820530",
|
||||
"modified": "2021-09-26 15:47:28.167371",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice Advance",
|
||||
|
||||
@@ -1651,7 +1651,7 @@
|
||||
"label": "Status",
|
||||
"length": 30,
|
||||
"no_copy": 1,
|
||||
"options": "\nDraft\nReturn\nCredit Note Issued\nSubmitted\nPaid\nUnpaid\nUnpaid and Discounted\nOverdue and Discounted\nOverdue\nCancelled\nInternal Transfer",
|
||||
"options": "\nDraft\nReturn\nCredit Note Issued\nSubmitted\nPaid\nPartly Paid\nUnpaid\nUnpaid and Discounted\nPartly Paid and Discounted\nOverdue and Discounted\nOverdue\nCancelled\nInternal Transfer",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
@@ -1953,6 +1953,7 @@
|
||||
"fetch_from": "customer.represents_company",
|
||||
"fieldname": "represents_company",
|
||||
"fieldtype": "Link",
|
||||
"ignore_user_permissions": 1,
|
||||
"label": "Represents Company",
|
||||
"options": "Company",
|
||||
"read_only": 1
|
||||
@@ -2022,11 +2023,12 @@
|
||||
"link_fieldname": "consolidated_invoice"
|
||||
}
|
||||
],
|
||||
"modified": "2021-09-08 15:24:25.486499",
|
||||
"modified": "2021-09-28 13:09:34.391799",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice",
|
||||
"name_case": "Title Case",
|
||||
"naming_rule": "By \"Naming Series\" field",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
|
||||
@@ -1475,14 +1475,7 @@ class SalesInvoice(SellingController):
|
||||
self.status = 'Draft'
|
||||
return
|
||||
|
||||
precision = self.precision("outstanding_amount")
|
||||
outstanding_amount = flt(self.outstanding_amount, precision)
|
||||
due_date = getdate(self.due_date)
|
||||
nowdate = getdate()
|
||||
|
||||
discounting_status = None
|
||||
if self.is_discounted:
|
||||
discounting_status = get_discounting_status(self.name)
|
||||
outstanding_amount = flt(self.outstanding_amount, self.precision("outstanding_amount"))
|
||||
|
||||
if not status:
|
||||
if self.docstatus == 2:
|
||||
@@ -1490,15 +1483,13 @@ class SalesInvoice(SellingController):
|
||||
elif self.docstatus == 1:
|
||||
if self.is_internal_transfer():
|
||||
self.status = 'Internal Transfer'
|
||||
elif outstanding_amount > 0 and due_date < nowdate and self.is_discounted and discounting_status=='Disbursed':
|
||||
self.status = "Overdue and Discounted"
|
||||
elif outstanding_amount > 0 and due_date < nowdate:
|
||||
elif is_overdue(self):
|
||||
self.status = "Overdue"
|
||||
elif outstanding_amount > 0 and due_date >= nowdate and self.is_discounted and discounting_status=='Disbursed':
|
||||
self.status = "Unpaid and Discounted"
|
||||
elif outstanding_amount > 0 and due_date >= nowdate:
|
||||
elif 0 < outstanding_amount < flt(self.grand_total, self.precision("grand_total")):
|
||||
self.status = "Partly Paid"
|
||||
elif outstanding_amount > 0 and getdate(self.due_date) >= getdate():
|
||||
self.status = "Unpaid"
|
||||
#Check if outstanding amount is 0 due to credit note issued against invoice
|
||||
# Check if outstanding amount is 0 due to credit note issued against invoice
|
||||
elif outstanding_amount <= 0 and self.is_return == 0 and frappe.db.get_value('Sales Invoice', {'is_return': 1, 'return_against': self.name, 'docstatus': 1}):
|
||||
self.status = "Credit Note Issued"
|
||||
elif self.is_return == 1:
|
||||
@@ -1507,12 +1498,42 @@ class SalesInvoice(SellingController):
|
||||
self.status = "Paid"
|
||||
else:
|
||||
self.status = "Submitted"
|
||||
|
||||
if (
|
||||
self.status in ("Unpaid", "Partly Paid", "Overdue")
|
||||
and self.is_discounted
|
||||
and get_discounting_status(self.name) == "Disbursed"
|
||||
):
|
||||
self.status += " and Discounted"
|
||||
|
||||
else:
|
||||
self.status = "Draft"
|
||||
|
||||
if update:
|
||||
self.db_set('status', self.status, update_modified = update_modified)
|
||||
|
||||
def is_overdue(doc):
|
||||
outstanding_amount = flt(doc.outstanding_amount, doc.precision("outstanding_amount"))
|
||||
|
||||
if outstanding_amount <= 0:
|
||||
return
|
||||
|
||||
grand_total = flt(doc.grand_total, doc.precision("grand_total"))
|
||||
nowdate = getdate()
|
||||
if doc.payment_schedule:
|
||||
# calculate payable amount till date
|
||||
payable_amount = sum(
|
||||
payment.payment_amount
|
||||
for payment in doc.payment_schedule
|
||||
if getdate(payment.due_date) < nowdate
|
||||
)
|
||||
|
||||
if (grand_total - outstanding_amount) < payable_amount:
|
||||
return True
|
||||
|
||||
elif getdate(doc.due_date) < nowdate:
|
||||
return True
|
||||
|
||||
def get_discounting_status(sales_invoice):
|
||||
status = None
|
||||
|
||||
|
||||
@@ -6,18 +6,20 @@ frappe.listview_settings['Sales Invoice'] = {
|
||||
add_fields: ["customer", "customer_name", "base_grand_total", "outstanding_amount", "due_date", "company",
|
||||
"currency", "is_return"],
|
||||
get_indicator: function(doc) {
|
||||
var status_color = {
|
||||
const status_colors = {
|
||||
"Draft": "grey",
|
||||
"Unpaid": "orange",
|
||||
"Paid": "green",
|
||||
"Return": "gray",
|
||||
"Credit Note Issued": "gray",
|
||||
"Unpaid and Discounted": "orange",
|
||||
"Partly Paid and Discounted": "yellow",
|
||||
"Overdue and Discounted": "red",
|
||||
"Overdue": "red",
|
||||
"Partly Paid": "yellow",
|
||||
"Internal Transfer": "darkgrey"
|
||||
};
|
||||
return [__(doc.status), status_color[doc.status], "status,=,"+doc.status];
|
||||
return [__(doc.status), status_colors[doc.status], "status,=,"+doc.status];
|
||||
},
|
||||
right_column: "grand_total"
|
||||
};
|
||||
|
||||
@@ -133,6 +133,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
|
||||
def test_payment_entry_unlink_against_invoice(self):
|
||||
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
|
||||
|
||||
si = frappe.copy_doc(test_records[0])
|
||||
si.is_pos = 0
|
||||
si.insert()
|
||||
@@ -156,6 +157,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
|
||||
def test_payment_entry_unlink_against_standalone_credit_note(self):
|
||||
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
|
||||
|
||||
si1 = create_sales_invoice(rate=1000)
|
||||
si2 = create_sales_invoice(rate=300)
|
||||
si3 = create_sales_invoice(qty=-1, rate=300, is_return=1)
|
||||
@@ -1420,15 +1422,22 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
itemised_tax, itemised_taxable_amount = get_itemised_tax_breakup_data(si)
|
||||
|
||||
expected_itemised_tax = {
|
||||
"999800": {
|
||||
"_Test Item": {
|
||||
"Service Tax": {
|
||||
"tax_rate": 10.0,
|
||||
"tax_amount": 1500.0
|
||||
"tax_amount": 1000.0
|
||||
}
|
||||
},
|
||||
"_Test Item 2": {
|
||||
"Service Tax": {
|
||||
"tax_rate": 10.0,
|
||||
"tax_amount": 500.0
|
||||
}
|
||||
}
|
||||
}
|
||||
expected_itemised_taxable_amount = {
|
||||
"999800": 15000.0
|
||||
"_Test Item": 10000.0,
|
||||
"_Test Item 2": 5000.0
|
||||
}
|
||||
|
||||
self.assertEqual(itemised_tax, expected_itemised_tax)
|
||||
@@ -1639,6 +1648,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
|
||||
def test_credit_note(self):
|
||||
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
|
||||
|
||||
si = create_sales_invoice(item_code = "_Test Item", qty = (5 * -1), rate=500, is_return = 1)
|
||||
|
||||
outstanding_amount = get_outstanding_amount(si.doctype,
|
||||
@@ -1790,6 +1800,47 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
|
||||
check_gl_entries(self, si.name, expected_gle, "2019-01-30")
|
||||
|
||||
def test_deferred_revenue_post_account_freeze_upto_by_admin(self):
|
||||
frappe.set_user("Administrator")
|
||||
|
||||
frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None)
|
||||
frappe.db.set_value('Accounts Settings', None, 'frozen_accounts_modifier', None)
|
||||
|
||||
deferred_account = create_account(account_name="Deferred Revenue",
|
||||
parent_account="Current Liabilities - _TC", company="_Test Company")
|
||||
|
||||
item = create_item("_Test Item for Deferred Accounting")
|
||||
item.enable_deferred_revenue = 1
|
||||
item.deferred_revenue_account = deferred_account
|
||||
item.no_of_months = 12
|
||||
item.save()
|
||||
|
||||
si = create_sales_invoice(item=item.name, posting_date="2019-01-10", do_not_save=True)
|
||||
si.items[0].enable_deferred_revenue = 1
|
||||
si.items[0].service_start_date = "2019-01-10"
|
||||
si.items[0].service_end_date = "2019-03-15"
|
||||
si.items[0].deferred_revenue_account = deferred_account
|
||||
si.save()
|
||||
si.submit()
|
||||
|
||||
frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', getdate('2019-01-31'))
|
||||
frappe.db.set_value('Accounts Settings', None, 'frozen_accounts_modifier', 'System Manager')
|
||||
|
||||
pda1 = frappe.get_doc(dict(
|
||||
doctype='Process Deferred Accounting',
|
||||
posting_date=nowdate(),
|
||||
start_date="2019-01-01",
|
||||
end_date="2019-03-31",
|
||||
type="Income",
|
||||
company="_Test Company"
|
||||
))
|
||||
|
||||
pda1.insert()
|
||||
self.assertRaises(frappe.ValidationError, pda1.submit)
|
||||
|
||||
frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None)
|
||||
frappe.db.set_value('Accounts Settings', None, 'frozen_accounts_modifier', None)
|
||||
|
||||
def test_fixed_deferred_revenue(self):
|
||||
deferred_account = create_account(account_name="Deferred Revenue",
|
||||
parent_account="Current Liabilities - _TC", company="_Test Company")
|
||||
@@ -2262,6 +2313,54 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
party_link.delete()
|
||||
frappe.db.set_value('Accounts Settings', None, 'enable_common_party_accounting', 0)
|
||||
|
||||
def test_payment_statuses(self):
|
||||
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
|
||||
|
||||
today = nowdate()
|
||||
|
||||
# Test Overdue
|
||||
si = create_sales_invoice(do_not_submit=True)
|
||||
si.payment_schedule = []
|
||||
si.append("payment_schedule", {
|
||||
"due_date": add_days(today, -5),
|
||||
"invoice_portion": 50,
|
||||
"payment_amount": si.grand_total / 2
|
||||
})
|
||||
si.append("payment_schedule", {
|
||||
"due_date": add_days(today, 5),
|
||||
"invoice_portion": 50,
|
||||
"payment_amount": si.grand_total / 2
|
||||
})
|
||||
si.submit()
|
||||
self.assertEqual(si.status, "Overdue")
|
||||
|
||||
# Test payment less than due amount
|
||||
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank - _TC")
|
||||
pe.reference_no = "1"
|
||||
pe.reference_date = nowdate()
|
||||
pe.paid_amount = 1
|
||||
pe.references[0].allocated_amount = pe.paid_amount
|
||||
pe.submit()
|
||||
si.reload()
|
||||
self.assertEqual(si.status, "Overdue")
|
||||
|
||||
# Test Partly Paid
|
||||
pe = frappe.copy_doc(pe)
|
||||
pe.paid_amount = si.grand_total / 2
|
||||
pe.references[0].allocated_amount = pe.paid_amount
|
||||
pe.submit()
|
||||
si.reload()
|
||||
self.assertEqual(si.status, "Partly Paid")
|
||||
|
||||
# Test Paid
|
||||
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank - _TC")
|
||||
pe.reference_no = "1"
|
||||
pe.reference_date = nowdate()
|
||||
pe.paid_amount = si.outstanding_amount
|
||||
pe.submit()
|
||||
si.reload()
|
||||
self.assertEqual(si.status, "Paid")
|
||||
|
||||
def get_sales_invoice_for_e_invoice():
|
||||
si = make_sales_invoice_for_ewaybill()
|
||||
si.naming_series = 'INV-2020-.#####'
|
||||
@@ -2294,6 +2393,7 @@ def make_test_address_for_ewaybill():
|
||||
if not frappe.db.exists('Address', '_Test Address for Eway bill-Billing'):
|
||||
address = frappe.get_doc({
|
||||
"address_line1": "_Test Address Line 1",
|
||||
"address_line2": "_Test Address Line 2",
|
||||
"address_title": "_Test Address for Eway bill",
|
||||
"address_type": "Billing",
|
||||
"city": "_Test City",
|
||||
@@ -2315,11 +2415,12 @@ def make_test_address_for_ewaybill():
|
||||
|
||||
address.save()
|
||||
|
||||
if not frappe.db.exists('Address', '_Test Customer-Address for Eway bill-Shipping'):
|
||||
if not frappe.db.exists('Address', '_Test Customer-Address for Eway bill-Billing'):
|
||||
address = frappe.get_doc({
|
||||
"address_line1": "_Test Address Line 1",
|
||||
"address_line2": "_Test Address Line 2",
|
||||
"address_title": "_Test Customer-Address for Eway bill",
|
||||
"address_type": "Shipping",
|
||||
"address_type": "Billing",
|
||||
"city": "_Test City",
|
||||
"state": "Test State",
|
||||
"country": "India",
|
||||
@@ -2339,9 +2440,34 @@ def make_test_address_for_ewaybill():
|
||||
|
||||
address.save()
|
||||
|
||||
if not frappe.db.exists('Address', '_Test Customer-Address for Eway bill-Shipping'):
|
||||
address = frappe.get_doc({
|
||||
"address_line1": "_Test Address Line 1",
|
||||
"address_line2": "_Test Address Line 2",
|
||||
"address_title": "_Test Customer-Address for Eway bill",
|
||||
"address_type": "Shipping",
|
||||
"city": "_Test City",
|
||||
"state": "Test State",
|
||||
"country": "India",
|
||||
"doctype": "Address",
|
||||
"is_primary_address": 1,
|
||||
"phone": "+910000000000",
|
||||
"gst_state": "Maharashtra",
|
||||
"gst_state_number": "27",
|
||||
"pincode": "410098"
|
||||
}).insert()
|
||||
|
||||
address.append("links", {
|
||||
"link_doctype": "Customer",
|
||||
"link_name": "_Test Customer"
|
||||
})
|
||||
|
||||
address.save()
|
||||
|
||||
if not frappe.db.exists('Address', '_Test Dispatch-Address for Eway bill-Shipping'):
|
||||
address = frappe.get_doc({
|
||||
"address_line1": "_Test Dispatch Address Line 1",
|
||||
"address_line2": "_Test Dispatch Address Line 2",
|
||||
"address_title": "_Test Dispatch-Address for Eway bill",
|
||||
"address_type": "Shipping",
|
||||
"city": "_Test City",
|
||||
@@ -2356,11 +2482,6 @@ def make_test_address_for_ewaybill():
|
||||
"pincode": "1100101"
|
||||
}).insert()
|
||||
|
||||
address.append("links", {
|
||||
"link_doctype": "Company",
|
||||
"link_name": "_Test Company"
|
||||
})
|
||||
|
||||
address.save()
|
||||
|
||||
def make_test_transporter_for_ewaybill():
|
||||
@@ -2400,7 +2521,8 @@ def make_sales_invoice_for_ewaybill():
|
||||
|
||||
si.distance = 2000
|
||||
si.company_address = "_Test Address for Eway bill-Billing"
|
||||
si.customer_address = "_Test Customer-Address for Eway bill-Shipping"
|
||||
si.customer_address = "_Test Customer-Address for Eway bill-Billing"
|
||||
si.shipping_address_name = "_Test Customer-Address for Eway bill-Shipping"
|
||||
si.dispatch_address_name = "_Test Dispatch-Address for Eway bill-Shipping"
|
||||
si.vehicle_no = "KA12KA1234"
|
||||
si.gst_category = "Registered Regular"
|
||||
|
||||
@@ -98,6 +98,7 @@
|
||||
"width": "120px"
|
||||
},
|
||||
{
|
||||
"depends_on": "exchange_gain_loss",
|
||||
"fieldname": "exchange_gain_loss",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Exchange Gain/Loss",
|
||||
@@ -105,6 +106,7 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "exchange_gain_loss",
|
||||
"fieldname": "ref_exchange_rate",
|
||||
"fieldtype": "Float",
|
||||
"label": "Reference Exchange Rate",
|
||||
@@ -116,7 +118,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-06-04 20:25:49.832052",
|
||||
"modified": "2021-09-26 15:47:46.911595",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice Advance",
|
||||
|
||||
@@ -100,6 +100,7 @@ def get_tax_withholding_details(tax_withholding_category, posting_date, company)
|
||||
for account_detail in tax_withholding.accounts:
|
||||
if company == account_detail.company:
|
||||
return frappe._dict({
|
||||
"tax_withholding_category": tax_withholding_category,
|
||||
"account_head": account_detail.account,
|
||||
"rate": tax_rate_detail.tax_withholding_rate,
|
||||
"from_date": tax_rate_detail.from_date,
|
||||
@@ -206,18 +207,39 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N
|
||||
|
||||
def get_invoice_vouchers(parties, tax_details, company, party_type='Supplier'):
|
||||
dr_or_cr = 'credit' if party_type == 'Supplier' else 'debit'
|
||||
doctype = 'Purchase Invoice' if party_type == 'Supplier' else 'Sales Invoice'
|
||||
|
||||
filters = {
|
||||
dr_or_cr: ['>', 0],
|
||||
'company': company,
|
||||
'party_type': party_type,
|
||||
'party': ['in', parties],
|
||||
frappe.scrub(party_type): ['in', parties],
|
||||
'posting_date': ['between', (tax_details.from_date, tax_details.to_date)],
|
||||
'is_opening': 'No',
|
||||
'is_cancelled': 0
|
||||
'docstatus': 1
|
||||
}
|
||||
|
||||
return frappe.get_all('GL Entry', filters=filters, distinct=1, pluck="voucher_no") or [""]
|
||||
if not tax_details.get('consider_party_ledger_amount') and doctype != "Sales Invoice":
|
||||
filters.update({
|
||||
'apply_tds': 1,
|
||||
'tax_withholding_category': tax_details.get('tax_withholding_category')
|
||||
})
|
||||
|
||||
invoices = frappe.get_all(doctype, filters=filters, pluck="name") or [""]
|
||||
|
||||
journal_entries = frappe.db.sql("""
|
||||
SELECT j.name
|
||||
FROM `tabJournal Entry` j, `tabJournal Entry Account` ja
|
||||
WHERE
|
||||
j.docstatus = 1
|
||||
AND j.is_opening = 'No'
|
||||
AND j.posting_date between %s and %s
|
||||
AND ja.{dr_or_cr} > 0
|
||||
AND ja.party in %s
|
||||
""".format(dr_or_cr=dr_or_cr), (tax_details.from_date, tax_details.to_date, tuple(parties)), as_list=1)
|
||||
|
||||
if journal_entries:
|
||||
journal_entries = journal_entries[0]
|
||||
|
||||
return invoices + journal_entries
|
||||
|
||||
def get_advance_vouchers(parties, company=None, from_date=None, to_date=None, party_type='Supplier'):
|
||||
# for advance vouchers, debit and credit is reversed
|
||||
|
||||
@@ -176,6 +176,29 @@ class TestTaxWithholdingCategory(unittest.TestCase):
|
||||
for d in invoices:
|
||||
d.cancel()
|
||||
|
||||
def test_multi_category_single_supplier(self):
|
||||
frappe.db.set_value("Supplier", "Test TDS Supplier5", "tax_withholding_category", "Test Service Category")
|
||||
invoices = []
|
||||
|
||||
pi = create_purchase_invoice(supplier = "Test TDS Supplier5", rate = 500, do_not_save=True)
|
||||
pi.tax_withholding_category = "Test Service Category"
|
||||
pi.save()
|
||||
pi.submit()
|
||||
invoices.append(pi)
|
||||
|
||||
# Second Invoice will apply TDS checked
|
||||
pi1 = create_purchase_invoice(supplier = "Test TDS Supplier5", rate = 2500, do_not_save=True)
|
||||
pi1.tax_withholding_category = "Test Goods Category"
|
||||
pi1.save()
|
||||
pi1.submit()
|
||||
invoices.append(pi1)
|
||||
|
||||
self.assertEqual(pi1.taxes[0].tax_amount, 250)
|
||||
|
||||
#delete invoices to avoid clashing
|
||||
for d in invoices:
|
||||
d.cancel()
|
||||
|
||||
def cancel_invoices():
|
||||
purchase_invoices = frappe.get_all("Purchase Invoice", {
|
||||
'supplier': ['in', ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2']],
|
||||
@@ -251,7 +274,8 @@ def create_sales_invoice(**args):
|
||||
|
||||
def create_records():
|
||||
# create a new suppliers
|
||||
for name in ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2', 'Test TDS Supplier3', 'Test TDS Supplier4']:
|
||||
for name in ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2', 'Test TDS Supplier3',
|
||||
'Test TDS Supplier4', 'Test TDS Supplier5']:
|
||||
if frappe.db.exists('Supplier', name):
|
||||
continue
|
||||
|
||||
@@ -390,3 +414,39 @@ def create_tax_with_holding_category():
|
||||
'account': 'TDS - _TC'
|
||||
}]
|
||||
}).insert()
|
||||
|
||||
if not frappe.db.exists("Tax Withholding Category", "Test Service Category"):
|
||||
frappe.get_doc({
|
||||
"doctype": "Tax Withholding Category",
|
||||
"name": "Test Service Category",
|
||||
"category_name": "Test Service Category",
|
||||
"rates": [{
|
||||
'from_date': fiscal_year[1],
|
||||
'to_date': fiscal_year[2],
|
||||
'tax_withholding_rate': 10,
|
||||
'single_threshold': 2000,
|
||||
'cumulative_threshold': 2000
|
||||
}],
|
||||
"accounts": [{
|
||||
'company': '_Test Company',
|
||||
'account': 'TDS - _TC'
|
||||
}]
|
||||
}).insert()
|
||||
|
||||
if not frappe.db.exists("Tax Withholding Category", "Test Goods Category"):
|
||||
frappe.get_doc({
|
||||
"doctype": "Tax Withholding Category",
|
||||
"name": "Test Goods Category",
|
||||
"category_name": "Test Goods Category",
|
||||
"rates": [{
|
||||
'from_date': fiscal_year[1],
|
||||
'to_date': fiscal_year[2],
|
||||
'tax_withholding_rate': 10,
|
||||
'single_threshold': 2000,
|
||||
'cumulative_threshold': 2000
|
||||
}],
|
||||
"accounts": [{
|
||||
'company': '_Test Company',
|
||||
'account': 'TDS - _TC'
|
||||
}]
|
||||
}).insert()
|
||||
|
||||
@@ -284,13 +284,16 @@ def check_freezing_date(posting_date, adv_adj=False):
|
||||
"""
|
||||
Nobody can do GL Entries where posting date is before freezing date
|
||||
except authorized person
|
||||
|
||||
Administrator has all the roles so this check will be bypassed if any role is allowed to post
|
||||
Hence stop admin to bypass if accounts are freezed
|
||||
"""
|
||||
if not adv_adj:
|
||||
acc_frozen_upto = frappe.db.get_value('Accounts Settings', None, 'acc_frozen_upto')
|
||||
if acc_frozen_upto:
|
||||
frozen_accounts_modifier = frappe.db.get_value( 'Accounts Settings', None,'frozen_accounts_modifier')
|
||||
if getdate(posting_date) <= getdate(acc_frozen_upto) \
|
||||
and not frozen_accounts_modifier in frappe.get_roles():
|
||||
and not frozen_accounts_modifier in frappe.get_roles() or frappe.session.user == 'Administrator':
|
||||
frappe.throw(_("You are not authorized to add or update entries before {0}").format(formatdate(acc_frozen_upto)))
|
||||
|
||||
def set_as_cancel(voucher_type, voucher_no):
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
{
|
||||
"add_total_row": 0,
|
||||
"add_total_row": 1,
|
||||
"columns": [],
|
||||
"creation": "2018-08-21 11:25:00.551823",
|
||||
"disable_prepared_report": 0,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"filters": [],
|
||||
"idx": 0,
|
||||
"is_standard": "Yes",
|
||||
"modified": "2018-09-21 11:25:00.551823",
|
||||
"modified": "2021-09-20 17:43:39.518851",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "TDS Computation Summary",
|
||||
|
||||
@@ -2,11 +2,10 @@ from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import flt
|
||||
|
||||
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import (
|
||||
get_advance_vouchers,
|
||||
get_debit_note_amount,
|
||||
from erpnext.accounts.report.tds_payable_monthly.tds_payable_monthly import (
|
||||
get_result,
|
||||
get_tds_docs,
|
||||
)
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
|
||||
@@ -17,9 +16,12 @@ def execute(filters=None):
|
||||
filters.naming_series = frappe.db.get_single_value('Buying Settings', 'supp_master_name')
|
||||
|
||||
columns = get_columns(filters)
|
||||
res = get_result(filters)
|
||||
tds_docs, tds_accounts, tax_category_map = get_tds_docs(filters)
|
||||
|
||||
return columns, res
|
||||
res = get_result(filters, tds_docs, tds_accounts, tax_category_map)
|
||||
final_result = group_by_supplier_and_category(res)
|
||||
|
||||
return columns, final_result
|
||||
|
||||
def validate_filters(filters):
|
||||
''' Validate if dates are properly set and lie in the same fiscal year'''
|
||||
@@ -33,81 +35,39 @@ def validate_filters(filters):
|
||||
|
||||
filters["fiscal_year"] = from_year
|
||||
|
||||
def get_result(filters):
|
||||
# if no supplier selected, fetch data for all tds applicable supplier
|
||||
# else fetch relevant data for selected supplier
|
||||
pan = "pan" if frappe.db.has_column("Supplier", "pan") else "tax_id"
|
||||
fields = ["name", pan+" as pan", "tax_withholding_category", "supplier_type", "supplier_name"]
|
||||
def group_by_supplier_and_category(data):
|
||||
supplier_category_wise_map = {}
|
||||
|
||||
if filters.supplier:
|
||||
filters.supplier = frappe.db.get_list('Supplier',
|
||||
{"name": filters.supplier}, fields)
|
||||
else:
|
||||
filters.supplier = frappe.db.get_list('Supplier',
|
||||
{"tax_withholding_category": ["!=", ""]}, fields)
|
||||
for row in data:
|
||||
supplier_category_wise_map.setdefault((row.get('supplier'), row.get('section_code')), {
|
||||
'pan': row.get('pan'),
|
||||
'supplier': row.get('supplier'),
|
||||
'supplier_name': row.get('supplier_name'),
|
||||
'section_code': row.get('section_code'),
|
||||
'entity_type': row.get('entity_type'),
|
||||
'tds_rate': row.get('tds_rate'),
|
||||
'total_amount_credited': 0.0,
|
||||
'tds_deducted': 0.0
|
||||
})
|
||||
|
||||
supplier_category_wise_map.get((row.get('supplier'), row.get('section_code')))['total_amount_credited'] += \
|
||||
row.get('total_amount_credited', 0.0)
|
||||
|
||||
supplier_category_wise_map.get((row.get('supplier'), row.get('section_code')))['tds_deducted'] += \
|
||||
row.get('tds_deducted', 0.0)
|
||||
|
||||
final_result = get_final_result(supplier_category_wise_map)
|
||||
|
||||
return final_result
|
||||
|
||||
|
||||
def get_final_result(supplier_category_wise_map):
|
||||
out = []
|
||||
for supplier in filters.supplier:
|
||||
tds = frappe.get_doc("Tax Withholding Category", supplier.tax_withholding_category)
|
||||
rate = [d.tax_withholding_rate for d in tds.rates if d.fiscal_year == filters.fiscal_year]
|
||||
|
||||
if rate:
|
||||
rate = rate[0]
|
||||
|
||||
try:
|
||||
account = [d.account for d in tds.accounts if d.company == filters.company][0]
|
||||
|
||||
except IndexError:
|
||||
account = []
|
||||
total_invoiced_amount, tds_deducted = get_invoice_and_tds_amount(supplier.name, account,
|
||||
filters.company, filters.from_date, filters.to_date, filters.fiscal_year)
|
||||
|
||||
if total_invoiced_amount or tds_deducted:
|
||||
row = [supplier.pan, supplier.name]
|
||||
|
||||
if filters.naming_series == 'Naming Series':
|
||||
row.append(supplier.supplier_name)
|
||||
|
||||
row.extend([tds.name, supplier.supplier_type, rate, total_invoiced_amount, tds_deducted])
|
||||
out.append(row)
|
||||
for key, value in supplier_category_wise_map.items():
|
||||
out.append(value)
|
||||
|
||||
return out
|
||||
|
||||
def get_invoice_and_tds_amount(supplier, account, company, from_date, to_date, fiscal_year):
|
||||
''' calculate total invoice amount and total tds deducted for given supplier '''
|
||||
|
||||
entries = frappe.db.sql("""
|
||||
select voucher_no, credit
|
||||
from `tabGL Entry`
|
||||
where party in (%s) and credit > 0
|
||||
and company=%s and is_cancelled = 0
|
||||
and posting_date between %s and %s
|
||||
""", (supplier, company, from_date, to_date), as_dict=1)
|
||||
|
||||
supplier_credit_amount = flt(sum(d.credit for d in entries))
|
||||
|
||||
vouchers = [d.voucher_no for d in entries]
|
||||
vouchers += get_advance_vouchers([supplier], company=company,
|
||||
from_date=from_date, to_date=to_date)
|
||||
|
||||
tds_deducted = 0
|
||||
if vouchers:
|
||||
tds_deducted = flt(frappe.db.sql("""
|
||||
select sum(credit)
|
||||
from `tabGL Entry`
|
||||
where account=%s and posting_date between %s and %s
|
||||
and company=%s and credit > 0 and voucher_no in ({0})
|
||||
""".format(', '.join("'%s'" % d for d in vouchers)),
|
||||
(account, from_date, to_date, company))[0][0])
|
||||
|
||||
date_range_filter = [fiscal_year, from_date, to_date]
|
||||
|
||||
debit_note_amount = get_debit_note_amount([supplier], date_range_filter, company=company)
|
||||
|
||||
total_invoiced_amount = supplier_credit_amount + tds_deducted - debit_note_amount
|
||||
|
||||
return total_invoiced_amount, tds_deducted
|
||||
|
||||
def get_columns(filters):
|
||||
columns = [
|
||||
{
|
||||
@@ -149,7 +109,7 @@ def get_columns(filters):
|
||||
{
|
||||
"label": _("TDS Rate %"),
|
||||
"fieldname": "tds_rate",
|
||||
"fieldtype": "Float",
|
||||
"fieldtype": "Percent",
|
||||
"width": 90
|
||||
},
|
||||
{
|
||||
|
||||
@@ -16,69 +16,6 @@ frappe.query_reports["TDS Payable Monthly"] = {
|
||||
"label": __("Supplier"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Supplier",
|
||||
"get_query": function() {
|
||||
return {
|
||||
"filters": {
|
||||
"tax_withholding_category": ["!=", ""],
|
||||
}
|
||||
}
|
||||
},
|
||||
on_change: function() {
|
||||
frappe.query_report.set_filter_value("purchase_invoice", "");
|
||||
frappe.query_report.refresh();
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname":"purchase_invoice",
|
||||
"label": __("Purchase Invoice"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Purchase Invoice",
|
||||
"get_query": function() {
|
||||
return {
|
||||
"filters": {
|
||||
"name": ["in", frappe.query_report.invoices]
|
||||
}
|
||||
}
|
||||
},
|
||||
on_change: function() {
|
||||
let supplier = frappe.query_report.get_filter_value('supplier');
|
||||
if(!supplier) return; // return if no supplier selected
|
||||
|
||||
// filter invoices based on selected supplier
|
||||
let invoices = [];
|
||||
frappe.query_report.invoice_data.map(d => {
|
||||
if(d.supplier==supplier)
|
||||
invoices.push(d.name)
|
||||
});
|
||||
frappe.query_report.invoices = invoices;
|
||||
frappe.query_report.refresh();
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname":"purchase_order",
|
||||
"label": __("Purchase Order"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Purchase Order",
|
||||
"get_query": function() {
|
||||
return {
|
||||
"filters": {
|
||||
"name": ["in", frappe.query_report.invoices]
|
||||
}
|
||||
}
|
||||
},
|
||||
on_change: function() {
|
||||
let supplier = frappe.query_report.get_filter_value('supplier');
|
||||
if(!supplier) return; // return if no supplier selected
|
||||
|
||||
// filter invoices based on selected supplier
|
||||
let invoices = [];
|
||||
frappe.query_report.invoice_data.map(d => {
|
||||
if(d.supplier==supplier)
|
||||
invoices.push(d.name)
|
||||
});
|
||||
frappe.query_report.invoices = invoices;
|
||||
frappe.query_report.refresh();
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname":"from_date",
|
||||
@@ -96,23 +33,5 @@ frappe.query_reports["TDS Payable Monthly"] = {
|
||||
"reqd": 1,
|
||||
"width": "60px"
|
||||
}
|
||||
],
|
||||
|
||||
onload: function(report) {
|
||||
// fetch all tds applied invoices
|
||||
frappe.call({
|
||||
"method": "erpnext.accounts.report.tds_payable_monthly.tds_payable_monthly.get_tds_invoices_and_orders",
|
||||
callback: function(r) {
|
||||
let invoices = [];
|
||||
|
||||
r.message.map(d => {
|
||||
invoices.push(d.name);
|
||||
});
|
||||
|
||||
report["invoice_data"] = r.message.invoices;
|
||||
report["invoices"] = invoices;
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
{
|
||||
"add_total_row": 1,
|
||||
"columns": [],
|
||||
"creation": "2018-08-21 11:32:30.874923",
|
||||
"disable_prepared_report": 0,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"filters": [],
|
||||
"idx": 0,
|
||||
"is_standard": "Yes",
|
||||
"modified": "2019-09-24 13:46:16.473711",
|
||||
"modified": "2021-09-20 12:05:50.387572",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "TDS Payable Monthly",
|
||||
|
||||
@@ -8,19 +8,12 @@ from frappe import _
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
filters["invoices"] = frappe.cache().hget("invoices", frappe.session.user)
|
||||
validate_filters(filters)
|
||||
set_filters(filters)
|
||||
|
||||
# TDS payment entries
|
||||
payment_entries = get_payment_entires(filters)
|
||||
tds_docs, tds_accounts, tax_category_map = get_tds_docs(filters)
|
||||
|
||||
columns = get_columns(filters)
|
||||
if not filters.get("invoices"):
|
||||
return columns, []
|
||||
|
||||
res = get_result(filters, payment_entries)
|
||||
|
||||
res = get_result(filters, tds_docs, tds_accounts, tax_category_map)
|
||||
return columns, res
|
||||
|
||||
def validate_filters(filters):
|
||||
@@ -28,109 +21,59 @@ def validate_filters(filters):
|
||||
if filters.from_date > filters.to_date:
|
||||
frappe.throw(_("From Date must be before To Date"))
|
||||
|
||||
def set_filters(filters):
|
||||
invoices = []
|
||||
|
||||
if not filters.get("invoices"):
|
||||
filters["invoices"] = get_tds_invoices_and_orders()
|
||||
|
||||
if filters.supplier and filters.purchase_invoice:
|
||||
for d in filters["invoices"]:
|
||||
if d.name == filters.purchase_invoice and d.supplier == filters.supplier:
|
||||
invoices.append(d)
|
||||
elif filters.supplier and not filters.purchase_invoice:
|
||||
for d in filters["invoices"]:
|
||||
if d.supplier == filters.supplier:
|
||||
invoices.append(d)
|
||||
elif filters.purchase_invoice and not filters.supplier:
|
||||
for d in filters["invoices"]:
|
||||
if d.name == filters.purchase_invoice:
|
||||
invoices.append(d)
|
||||
elif filters.supplier and filters.purchase_order:
|
||||
for d in filters.get("invoices"):
|
||||
if d.name == filters.purchase_order and d.supplier == filters.supplier:
|
||||
invoices.append(d)
|
||||
elif filters.supplier and not filters.purchase_order:
|
||||
for d in filters.get("invoices"):
|
||||
if d.supplier == filters.supplier:
|
||||
invoices.append(d)
|
||||
elif filters.purchase_order and not filters.supplier:
|
||||
for d in filters.get("invoices"):
|
||||
if d.name == filters.purchase_order:
|
||||
invoices.append(d)
|
||||
|
||||
filters["invoices"] = invoices if invoices else filters["invoices"]
|
||||
filters.naming_series = frappe.db.get_single_value('Buying Settings', 'supp_master_name')
|
||||
|
||||
#print(filters.get('invoices'))
|
||||
|
||||
def get_result(filters, payment_entries):
|
||||
supplier_map, tds_docs = get_supplier_map(filters, payment_entries)
|
||||
documents = [d.get('name') for d in filters.get('invoices')] + [d.get('name') for d in payment_entries]
|
||||
|
||||
gle_map = get_gle_map(filters, documents)
|
||||
def get_result(filters, tds_docs, tds_accounts, tax_category_map):
|
||||
supplier_map = get_supplier_pan_map()
|
||||
tax_rate_map = get_tax_rate_map(filters)
|
||||
gle_map = get_gle_map(filters, tds_docs)
|
||||
|
||||
out = []
|
||||
for d in gle_map:
|
||||
for name, details in gle_map.items():
|
||||
tds_deducted, total_amount_credited = 0, 0
|
||||
supplier = supplier_map[d]
|
||||
tax_withholding_category = tax_category_map.get(name)
|
||||
rate = tax_rate_map.get(tax_withholding_category)
|
||||
|
||||
tds_doc = tds_docs[supplier.tax_withholding_category]
|
||||
account_list = [i.account for i in tds_doc.accounts if i.company == filters.company]
|
||||
for entry in details:
|
||||
supplier = entry.party or entry.against
|
||||
posting_date = entry.posting_date
|
||||
voucher_type = entry.voucher_type
|
||||
|
||||
if account_list:
|
||||
account = account_list[0]
|
||||
if entry.account in tds_accounts:
|
||||
tds_deducted += (entry.credit - entry.debit)
|
||||
|
||||
for k in gle_map[d]:
|
||||
if k.party == supplier_map[d] and k.credit > 0:
|
||||
total_amount_credited += (k.credit - k.debit)
|
||||
elif account_list and k.account == account and (k.credit - k.debit) > 0:
|
||||
tds_deducted = (k.credit - k.debit)
|
||||
total_amount_credited += (k.credit - k.debit)
|
||||
voucher_type = k.voucher_type
|
||||
total_amount_credited += (entry.credit - entry.debit)
|
||||
|
||||
rate = [i.tax_withholding_rate for i in tds_doc.rates
|
||||
if i.fiscal_year == gle_map[d][0].fiscal_year]
|
||||
|
||||
if rate and len(rate) > 0 and tds_deducted:
|
||||
rate = rate[0]
|
||||
|
||||
row = [supplier.pan, supplier.name]
|
||||
if rate and tds_deducted:
|
||||
row = {
|
||||
'pan' if frappe.db.has_column('Supplier', 'pan') else 'tax_id': supplier_map.get(supplier).pan,
|
||||
'supplier': supplier_map.get(supplier).name
|
||||
}
|
||||
|
||||
if filters.naming_series == 'Naming Series':
|
||||
row.append(supplier.supplier_name)
|
||||
row.update({'supplier_name': supplier_map.get(supplier).supplier_name})
|
||||
|
||||
row.update({
|
||||
'section_code': tax_withholding_category,
|
||||
'entity_type': supplier_map.get(supplier).supplier_type,
|
||||
'tds_rate': rate,
|
||||
'total_amount_credited': total_amount_credited,
|
||||
'tds_deducted': tds_deducted,
|
||||
'transaction_date': posting_date,
|
||||
'transaction_type': voucher_type,
|
||||
'ref_no': name
|
||||
})
|
||||
|
||||
row.extend([tds_doc.name, supplier.supplier_type, rate, total_amount_credited,
|
||||
tds_deducted, gle_map[d][0].posting_date, voucher_type, d])
|
||||
out.append(row)
|
||||
|
||||
return out
|
||||
|
||||
def get_supplier_map(filters, payment_entries):
|
||||
# create a supplier_map of the form {"purchase_invoice": {supplier_name, pan, tds_name}}
|
||||
# pre-fetch all distinct applicable tds docs
|
||||
supplier_map, tds_docs = {}, {}
|
||||
pan = "pan" if frappe.db.has_column("Supplier", "pan") else "tax_id"
|
||||
supplier_list = [d.supplier for d in filters["invoices"]]
|
||||
def get_supplier_pan_map():
|
||||
supplier_map = frappe._dict()
|
||||
suppliers = frappe.db.get_all('Supplier', fields=['name', 'pan', 'supplier_type', 'supplier_name'])
|
||||
|
||||
supplier_detail = frappe.db.get_all('Supplier',
|
||||
{"name": ["in", supplier_list]},
|
||||
["tax_withholding_category", "name", pan+" as pan", "supplier_type", "supplier_name"])
|
||||
for d in suppliers:
|
||||
supplier_map[d.name] = d
|
||||
|
||||
for d in filters["invoices"]:
|
||||
supplier_map[d.get("name")] = [k for k in supplier_detail
|
||||
if k.name == d.get("supplier")][0]
|
||||
|
||||
for d in payment_entries:
|
||||
supplier_map[d.get("name")] = [k for k in supplier_detail
|
||||
if k.name == d.get("supplier")][0]
|
||||
|
||||
for d in supplier_detail:
|
||||
if d.get("tax_withholding_category") not in tds_docs:
|
||||
tds_docs[d.get("tax_withholding_category")] = \
|
||||
frappe.get_doc("Tax Withholding Category", d.get("tax_withholding_category"))
|
||||
|
||||
return supplier_map, tds_docs
|
||||
return supplier_map
|
||||
|
||||
def get_gle_map(filters, documents):
|
||||
# create gle_map of the form
|
||||
@@ -140,10 +83,9 @@ def get_gle_map(filters, documents):
|
||||
gle = frappe.db.get_all('GL Entry',
|
||||
{
|
||||
"voucher_no": ["in", documents],
|
||||
'is_cancelled': 0,
|
||||
'posting_date': ("between", [filters.get('from_date'), filters.get('to_date')]),
|
||||
"credit": (">", 0)
|
||||
},
|
||||
["fiscal_year", "credit", "debit", "account", "voucher_no", "posting_date", "voucher_type"],
|
||||
["credit", "debit", "account", "voucher_no", "posting_date", "voucher_type", "against", "party"],
|
||||
)
|
||||
|
||||
for d in gle:
|
||||
@@ -233,39 +175,57 @@ def get_columns(filters):
|
||||
|
||||
return columns
|
||||
|
||||
def get_payment_entires(filters):
|
||||
filter_dict = {
|
||||
'posting_date': ("between", [filters.get('from_date'), filters.get('to_date')]),
|
||||
'party_type': 'Supplier',
|
||||
'apply_tax_withholding_amount': 1
|
||||
def get_tds_docs(filters):
|
||||
tds_documents = []
|
||||
purchase_invoices = []
|
||||
payment_entries = []
|
||||
journal_entries = []
|
||||
tax_category_map = {}
|
||||
|
||||
tds_accounts = frappe.get_all("Tax Withholding Account", {'company': filters.get('company')},
|
||||
pluck="account")
|
||||
|
||||
query_filters = {
|
||||
"credit": ('>', 0),
|
||||
"account": ("in", tds_accounts),
|
||||
"posting_date": ("between", [filters.get("from_date"), filters.get("to_date")]),
|
||||
"is_cancelled": 0
|
||||
}
|
||||
|
||||
if filters.get('purchase_invoice') or filters.get('purchase_order'):
|
||||
parent = frappe.db.get_all('Payment Entry Reference',
|
||||
{'reference_name': ('in', [d.get('name') for d in filters.get('invoices')])}, ['parent'])
|
||||
if filters.get('supplier'):
|
||||
query_filters.update({'against': filters.get('supplier')})
|
||||
|
||||
filter_dict.update({'name': ('in', [d.get('parent') for d in parent])})
|
||||
tds_docs = frappe.get_all("GL Entry", query_filters, ["voucher_no", "voucher_type", "against", "party"])
|
||||
|
||||
payment_entries = frappe.get_all('Payment Entry', fields=['name', 'party_name as supplier'],
|
||||
filters=filter_dict)
|
||||
for d in tds_docs:
|
||||
if d.voucher_type == "Purchase Invoice":
|
||||
purchase_invoices.append(d.voucher_no)
|
||||
elif d.voucher_type == "Payment Entry":
|
||||
payment_entries.append(d.voucher_no)
|
||||
elif d.voucher_type == "Journal Entry":
|
||||
journal_entries.append(d.voucher_no)
|
||||
|
||||
return payment_entries
|
||||
tds_documents.append(d.voucher_no)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_tds_invoices_and_orders():
|
||||
# fetch tds applicable supplier and fetch invoices for these suppliers
|
||||
suppliers = [d.name for d in frappe.db.get_list("Supplier",
|
||||
{"tax_withholding_category": ["!=", ""]}, ["name"])]
|
||||
if purchase_invoices:
|
||||
get_tax_category_map(purchase_invoices, 'Purchase Invoice', tax_category_map)
|
||||
|
||||
invoices = frappe.db.get_list("Purchase Invoice",
|
||||
{"supplier": ["in", suppliers]}, ["name", "supplier"])
|
||||
if payment_entries:
|
||||
get_tax_category_map(payment_entries, 'Payment Entry', tax_category_map)
|
||||
|
||||
orders = frappe.db.get_list("Purchase Order",
|
||||
{"supplier": ["in", suppliers]}, ["name", "supplier"])
|
||||
if journal_entries:
|
||||
get_tax_category_map(journal_entries, 'Journal Entry', tax_category_map)
|
||||
|
||||
invoices = invoices + orders
|
||||
invoices = [d for d in invoices if d.supplier]
|
||||
return tds_documents, tds_accounts, tax_category_map
|
||||
|
||||
frappe.cache().hset("invoices", frappe.session.user, invoices)
|
||||
def get_tax_category_map(vouchers, doctype, tax_category_map):
|
||||
tax_category_map.update(frappe._dict(frappe.get_all(doctype,
|
||||
filters = {'name': ('in', vouchers)}, fields=['name', 'tax_withholding_category'], as_list=1)))
|
||||
|
||||
return invoices
|
||||
def get_tax_rate_map(filters):
|
||||
rate_map = frappe.get_all('Tax Withholding Rate', filters={
|
||||
'from_date': ('<=', filters.get('from_date')),
|
||||
'to_date': ('>=', filters.get('to_date'))
|
||||
}, fields=['parent', 'tax_withholding_rate'], as_list=1)
|
||||
|
||||
return frappe._dict(rate_map)
|
||||
@@ -394,10 +394,6 @@ class Asset(AccountsController):
|
||||
if cint(self.number_of_depreciations_booked) > cint(row.total_number_of_depreciations):
|
||||
frappe.throw(_("Number of Depreciations Booked cannot be greater than Total Number of Depreciations"))
|
||||
|
||||
if row.depreciation_start_date and getdate(row.depreciation_start_date) < getdate(nowdate()):
|
||||
frappe.msgprint(_("Depreciation Row {0}: Depreciation Start Date is entered as past date")
|
||||
.format(row.idx), title=_('Warning'), indicator='red')
|
||||
|
||||
if row.depreciation_start_date and getdate(row.depreciation_start_date) < getdate(self.purchase_date):
|
||||
frappe.throw(_("Depreciation Row {0}: Next Depreciation Date cannot be before Purchase Date")
|
||||
.format(row.idx))
|
||||
|
||||
@@ -645,12 +645,18 @@ class TestAsset(unittest.TestCase):
|
||||
pr = make_purchase_receipt(item_code="Macbook Pro",
|
||||
qty=1, rate=8000.0, location="Test Location")
|
||||
|
||||
finance_book = frappe.new_doc('Finance Book')
|
||||
finance_book.finance_book_name = 'Income Tax'
|
||||
finance_book.for_income_tax = 1
|
||||
finance_book.insert(ignore_if_duplicate=1)
|
||||
|
||||
asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
|
||||
asset = frappe.get_doc('Asset', asset_name)
|
||||
asset.calculate_depreciation = 1
|
||||
asset.available_for_use_date = '2030-07-12'
|
||||
asset.purchase_date = '2030-01-01'
|
||||
asset.append("finance_books", {
|
||||
"finance_book": finance_book.name,
|
||||
"expected_value_after_useful_life": 1000,
|
||||
"depreciation_method": "Written Down Value",
|
||||
"total_number_of_depreciations": 3,
|
||||
|
||||
@@ -1121,6 +1121,7 @@
|
||||
"fetch_from": "supplier.represents_company",
|
||||
"fieldname": "represents_company",
|
||||
"fieldtype": "Link",
|
||||
"ignore_user_permissions": 1,
|
||||
"label": "Represents Company",
|
||||
"options": "Company",
|
||||
"read_only": 1
|
||||
@@ -1143,7 +1144,7 @@
|
||||
"idx": 105,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-08-30 20:03:14.008804",
|
||||
"modified": "2021-09-28 13:10:47.955401",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Purchase Order",
|
||||
|
||||
@@ -30,7 +30,14 @@ frappe.query_reports["Purchase Order Analysis"] = {
|
||||
"default": frappe.datetime.get_today()
|
||||
},
|
||||
{
|
||||
"fieldname": "purchase_order",
|
||||
"fieldname":"project",
|
||||
"label": __("Project"),
|
||||
"fieldtype": "Link",
|
||||
"width": "80",
|
||||
"options": "Project"
|
||||
},
|
||||
{
|
||||
"fieldname": "name",
|
||||
"label": __("Purchase Order"),
|
||||
"fieldtype": "Link",
|
||||
"width": "80",
|
||||
|
||||
@@ -41,14 +41,12 @@ def get_conditions(filters):
|
||||
if filters.get("from_date") and filters.get("to_date"):
|
||||
conditions += " and po.transaction_date between %(from_date)s and %(to_date)s"
|
||||
|
||||
if filters.get("company"):
|
||||
conditions += " and po.company = %(company)s"
|
||||
for field in ['company', 'name', 'status']:
|
||||
if filters.get(field):
|
||||
conditions += f" and po.{field} = %({field})s"
|
||||
|
||||
if filters.get("purchase_order"):
|
||||
conditions += " and po.name = %(purchase_order)s"
|
||||
|
||||
if filters.get("status"):
|
||||
conditions += " and po.status in %(status)s"
|
||||
if filters.get('project'):
|
||||
conditions += " and poi.project = %(project)s"
|
||||
|
||||
return conditions
|
||||
|
||||
@@ -57,6 +55,7 @@ def get_data(conditions, filters):
|
||||
SELECT
|
||||
po.transaction_date as date,
|
||||
poi.schedule_date as required_date,
|
||||
poi.project,
|
||||
po.name as purchase_order,
|
||||
po.status, po.supplier, poi.item_code,
|
||||
poi.qty, poi.received_qty,
|
||||
@@ -175,6 +174,12 @@ def get_columns(filters):
|
||||
"fieldtype": "Link",
|
||||
"options": "Supplier",
|
||||
"width": 130
|
||||
},{
|
||||
"label": _("Project"),
|
||||
"fieldname": "project",
|
||||
"fieldtype": "Link",
|
||||
"options": "Project",
|
||||
"width": 130
|
||||
}]
|
||||
|
||||
if not filters.get("group_by_po"):
|
||||
|
||||
@@ -685,13 +685,17 @@ class AccountsController(TransactionBase):
|
||||
.format(d.reference_name, d.against_order))
|
||||
|
||||
def set_advance_gain_or_loss(self):
|
||||
if not self.get("advances"):
|
||||
if self.get('conversion_rate') == 1 or not self.get("advances"):
|
||||
return
|
||||
|
||||
is_purchase_invoice = self.doctype == 'Purchase Invoice'
|
||||
party_account = self.credit_to if is_purchase_invoice else self.debit_to
|
||||
if get_account_currency(party_account) != self.currency:
|
||||
return
|
||||
|
||||
for d in self.get("advances"):
|
||||
advance_exchange_rate = d.ref_exchange_rate
|
||||
if (d.allocated_amount and self.conversion_rate != 1
|
||||
and self.conversion_rate != advance_exchange_rate):
|
||||
if (d.allocated_amount and self.conversion_rate != advance_exchange_rate):
|
||||
|
||||
base_allocated_amount_in_ref_rate = advance_exchange_rate * d.allocated_amount
|
||||
base_allocated_amount_in_inv_rate = self.conversion_rate * d.allocated_amount
|
||||
@@ -710,7 +714,7 @@ class AccountsController(TransactionBase):
|
||||
|
||||
gain_loss_account = frappe.db.get_value('Company', self.company, 'exchange_gain_loss_account')
|
||||
if not gain_loss_account:
|
||||
frappe.throw(_("Please set Default Exchange Gain/Loss Account in Company {}")
|
||||
frappe.throw(_("Please set default Exchange Gain/Loss Account in Company {}")
|
||||
.format(self.get('company')))
|
||||
account_currency = get_account_currency(gain_loss_account)
|
||||
if account_currency != self.company_currency:
|
||||
@@ -729,7 +733,7 @@ class AccountsController(TransactionBase):
|
||||
"against": party,
|
||||
dr_or_cr + "_in_account_currency": abs(d.exchange_gain_loss),
|
||||
dr_or_cr: abs(d.exchange_gain_loss),
|
||||
"cost_center": self.cost_center,
|
||||
"cost_center": self.cost_center or erpnext.get_default_cost_center(self.company),
|
||||
"project": self.project
|
||||
}, item=d)
|
||||
)
|
||||
@@ -980,42 +984,55 @@ class AccountsController(TransactionBase):
|
||||
item_allowance = {}
|
||||
global_qty_allowance, global_amount_allowance = None, None
|
||||
|
||||
role_allowed_to_over_bill = frappe.db.get_single_value('Accounts Settings', 'role_allowed_to_over_bill')
|
||||
user_roles = frappe.get_roles()
|
||||
|
||||
total_overbilled_amt = 0.0
|
||||
|
||||
for item in self.get("items"):
|
||||
if item.get(item_ref_dn):
|
||||
ref_amt = flt(frappe.db.get_value(ref_dt + " Item",
|
||||
item.get(item_ref_dn), based_on), self.precision(based_on, item))
|
||||
if not ref_amt:
|
||||
frappe.msgprint(
|
||||
_("Warning: System will not check overbilling since amount for Item {0} in {1} is zero")
|
||||
.format(item.item_code, ref_dt))
|
||||
else:
|
||||
already_billed = frappe.db.sql("""
|
||||
select sum(%s)
|
||||
from `tab%s`
|
||||
where %s=%s and docstatus=1 and parent != %s
|
||||
""" % (based_on, self.doctype + " Item", item_ref_dn, '%s', '%s'),
|
||||
(item.get(item_ref_dn), self.name))[0][0]
|
||||
if not item.get(item_ref_dn):
|
||||
continue
|
||||
|
||||
total_billed_amt = flt(flt(already_billed) + flt(item.get(based_on)),
|
||||
self.precision(based_on, item))
|
||||
ref_amt = flt(frappe.db.get_value(ref_dt + " Item",
|
||||
item.get(item_ref_dn), based_on), self.precision(based_on, item))
|
||||
if not ref_amt:
|
||||
frappe.msgprint(
|
||||
_("System will not check overbilling since amount for Item {0} in {1} is zero")
|
||||
.format(item.item_code, ref_dt), title=_("Warning"), indicator="orange")
|
||||
continue
|
||||
|
||||
allowance, item_allowance, global_qty_allowance, global_amount_allowance = \
|
||||
get_allowance_for(item.item_code, item_allowance, global_qty_allowance, global_amount_allowance, "amount")
|
||||
already_billed = frappe.db.sql("""
|
||||
select sum(%s)
|
||||
from `tab%s`
|
||||
where %s=%s and docstatus=1 and parent != %s
|
||||
""" % (based_on, self.doctype + " Item", item_ref_dn, '%s', '%s'),
|
||||
(item.get(item_ref_dn), self.name))[0][0]
|
||||
|
||||
max_allowed_amt = flt(ref_amt * (100 + allowance) / 100)
|
||||
total_billed_amt = flt(flt(already_billed) + flt(item.get(based_on)),
|
||||
self.precision(based_on, item))
|
||||
|
||||
if total_billed_amt < 0 and max_allowed_amt < 0:
|
||||
# while making debit note against purchase return entry(purchase receipt) getting overbill error
|
||||
total_billed_amt = abs(total_billed_amt)
|
||||
max_allowed_amt = abs(max_allowed_amt)
|
||||
allowance, item_allowance, global_qty_allowance, global_amount_allowance = \
|
||||
get_allowance_for(item.item_code, item_allowance, global_qty_allowance, global_amount_allowance, "amount")
|
||||
|
||||
role_allowed_to_over_bill = frappe.db.get_single_value('Accounts Settings', 'role_allowed_to_over_bill')
|
||||
max_allowed_amt = flt(ref_amt * (100 + allowance) / 100)
|
||||
|
||||
if total_billed_amt - max_allowed_amt > 0.01 and role_allowed_to_over_bill not in frappe.get_roles():
|
||||
if self.doctype != "Purchase Invoice":
|
||||
self.throw_overbill_exception(item, max_allowed_amt)
|
||||
elif not cint(frappe.db.get_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice")):
|
||||
self.throw_overbill_exception(item, max_allowed_amt)
|
||||
if total_billed_amt < 0 and max_allowed_amt < 0:
|
||||
# while making debit note against purchase return entry(purchase receipt) getting overbill error
|
||||
total_billed_amt = abs(total_billed_amt)
|
||||
max_allowed_amt = abs(max_allowed_amt)
|
||||
|
||||
overbill_amt = total_billed_amt - max_allowed_amt
|
||||
total_overbilled_amt += overbill_amt
|
||||
|
||||
if overbill_amt > 0.01 and role_allowed_to_over_bill not in user_roles:
|
||||
if self.doctype != "Purchase Invoice":
|
||||
self.throw_overbill_exception(item, max_allowed_amt)
|
||||
elif not cint(frappe.db.get_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice")):
|
||||
self.throw_overbill_exception(item, max_allowed_amt)
|
||||
|
||||
if role_allowed_to_over_bill in user_roles and total_overbilled_amt > 0.1:
|
||||
frappe.msgprint(_("Overbilling of {} ignored because you have {} role.")
|
||||
.format(total_overbilled_amt, role_allowed_to_over_bill), title=_("Warning"), indicator="orange")
|
||||
|
||||
def throw_overbill_exception(self, item, max_allowed_amt):
|
||||
frappe.throw(_("Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings")
|
||||
@@ -1668,14 +1685,18 @@ def get_advance_payment_entries(party_type, party, party_account, order_doctype,
|
||||
return list(payment_entries_against_order) + list(unallocated_payment_entries)
|
||||
|
||||
def update_invoice_status():
|
||||
# Daily update the status of the invoices
|
||||
|
||||
frappe.db.sql(""" update `tabSales Invoice` set status = 'Overdue'
|
||||
where due_date < CURDATE() and docstatus = 1 and outstanding_amount > 0""")
|
||||
|
||||
frappe.db.sql(""" update `tabPurchase Invoice` set status = 'Overdue'
|
||||
where due_date < CURDATE() and docstatus = 1 and outstanding_amount > 0""")
|
||||
"""Updates status as Overdue for applicable invoices. Runs daily."""
|
||||
|
||||
for doctype in ("Sales Invoice", "Purchase Invoice"):
|
||||
frappe.db.sql("""
|
||||
update `tab{}` as dt set dt.status = 'Overdue'
|
||||
where dt.docstatus = 1
|
||||
and dt.status != 'Overdue'
|
||||
and dt.outstanding_amount > 0
|
||||
and (dt.grand_total - dt.outstanding_amount) <
|
||||
(select sum(payment_amount) from `tabPayment Schedule` as ps
|
||||
where ps.parent = dt.name and ps.due_date < %s)
|
||||
""".format(doctype), getdate())
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_payment_terms(terms_template, posting_date=None, grand_total=None, base_grand_total=None, bill_date=None):
|
||||
|
||||
@@ -85,10 +85,8 @@ def add_bank_accounts(response, bank, company):
|
||||
if not acc_subtype:
|
||||
add_account_subtype(account["subtype"])
|
||||
|
||||
existing_bank_account = frappe.db.exists("Bank Account", {
|
||||
'account_name': account["name"],
|
||||
'bank': bank["bank_name"]
|
||||
})
|
||||
bank_account_name = "{} - {}".format(account["name"], bank["bank_name"])
|
||||
existing_bank_account = frappe.db.exists("Bank Account", bank_account_name)
|
||||
|
||||
if not existing_bank_account:
|
||||
try:
|
||||
@@ -197,6 +195,7 @@ def get_transactions(bank, bank_account=None, start_date=None, end_date=None):
|
||||
|
||||
plaid = PlaidConnector(access_token)
|
||||
|
||||
transactions = []
|
||||
try:
|
||||
transactions = plaid.get_transactions(start_date=start_date, end_date=end_date, account_id=account_id)
|
||||
except ItemError as e:
|
||||
@@ -205,7 +204,7 @@ def get_transactions(bank, bank_account=None, start_date=None, end_date=None):
|
||||
msg += _("Please refresh or reset the Plaid linking of the Bank {}.").format(bank) + " "
|
||||
frappe.log_error(msg, title=_("Plaid Link Refresh Required"))
|
||||
|
||||
return transactions or []
|
||||
return transactions
|
||||
|
||||
|
||||
def new_bank_transaction(transaction):
|
||||
|
||||
@@ -184,7 +184,7 @@ def get_employees_having_an_event_today(event_type):
|
||||
# --------------------------
|
||||
def send_work_anniversary_reminders():
|
||||
"""Send Employee Work Anniversary Reminders if 'Send Work Anniversary Reminders' is checked"""
|
||||
to_send = int(frappe.db.get_single_value("HR Settings", "send_work_anniversary_reminders") or 1)
|
||||
to_send = int(frappe.db.get_single_value("HR Settings", "send_work_anniversary_reminders"))
|
||||
if not to_send:
|
||||
return
|
||||
|
||||
|
||||
@@ -55,8 +55,7 @@ def mark_employee_attendance(employee_list, status, date, leave_type=None, compa
|
||||
else:
|
||||
leave_type = None
|
||||
|
||||
if not company:
|
||||
company = frappe.db.get_value("Employee", employee['employee'], "Company")
|
||||
company = frappe.db.get_value("Employee", employee['employee'], "Company", cache=True)
|
||||
|
||||
attendance=frappe.get_doc(dict(
|
||||
doctype='Attendance',
|
||||
@@ -68,4 +67,4 @@ def mark_employee_attendance(employee_list, status, date, leave_type=None, compa
|
||||
company=company
|
||||
))
|
||||
attendance.insert()
|
||||
attendance.submit()
|
||||
attendance.submit()
|
||||
@@ -21,7 +21,7 @@ frappe.ui.form.on('Training Result', {
|
||||
frm.set_value("employees" ,"");
|
||||
if (r.message) {
|
||||
$.each(r.message, function(i, d) {
|
||||
var row = frappe.model.add_child(cur_frm.doc, "Training Result Employee", "employees");
|
||||
var row = frappe.model.add_child(frm.doc, "Training Result Employee", "employees");
|
||||
row.employee = d.employee;
|
||||
row.employee_name = d.employee_name;
|
||||
});
|
||||
|
||||
@@ -89,13 +89,14 @@
|
||||
"width": "160px"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"columns": 2,
|
||||
"default": "Pending",
|
||||
"fieldname": "completion_status",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Completion Status",
|
||||
"options": "Pending\nPartially Completed\nFully Completed",
|
||||
"read_only": 1
|
||||
"options": "Pending\nPartially Completed\nFully Completed"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_3",
|
||||
@@ -125,10 +126,11 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-05-27 16:07:25.905015",
|
||||
"modified": "2021-09-16 21:25:22.506485",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Maintenance",
|
||||
"name": "Maintenance Schedule Detail",
|
||||
"naming_rule": "Random",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
|
||||
@@ -1133,7 +1133,8 @@ def item_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
query_filters["has_variants"] = 0
|
||||
|
||||
if filters and filters.get("is_stock_item"):
|
||||
query_filters["is_stock_item"] = 1
|
||||
or_cond_filters["is_stock_item"] = 1
|
||||
or_cond_filters["has_variants"] = 1
|
||||
|
||||
return frappe.get_list("Item",
|
||||
fields = fields, filters=query_filters,
|
||||
|
||||
@@ -677,7 +677,7 @@ def get_job_details(start, end, filters=None):
|
||||
conditions = get_filters_cond("Job Card", filters, [])
|
||||
|
||||
job_cards = frappe.db.sql(""" SELECT `tabJob Card`.name, `tabJob Card`.work_order,
|
||||
`tabJob Card`.employee_name, `tabJob Card`.status, ifnull(`tabJob Card`.remarks, ''),
|
||||
`tabJob Card`.status, ifnull(`tabJob Card`.remarks, ''),
|
||||
min(`tabJob Card Time Log`.from_time) as from_time,
|
||||
max(`tabJob Card Time Log`.to_time) as to_time
|
||||
FROM `tabJob Card` , `tabJob Card Time Log`
|
||||
@@ -687,7 +687,7 @@ def get_job_details(start, end, filters=None):
|
||||
|
||||
for d in job_cards:
|
||||
subject_data = []
|
||||
for field in ["name", "work_order", "remarks", "employee_name"]:
|
||||
for field in ["name", "work_order", "remarks"]:
|
||||
if not d.get(field): continue
|
||||
|
||||
subject_data.append(d.get(field))
|
||||
|
||||
@@ -457,7 +457,8 @@ class ProductionPlan(Document):
|
||||
|
||||
def prepare_args_for_sub_assembly_items(self, row, args):
|
||||
for field in ["production_item", "item_name", "qty", "fg_warehouse",
|
||||
"description", "bom_no", "stock_uom", "bom_level", "production_plan_item"]:
|
||||
"description", "bom_no", "stock_uom", "bom_level",
|
||||
"production_plan_item", "schedule_date"]:
|
||||
args[field] = row.get(field)
|
||||
|
||||
args.update({
|
||||
|
||||
@@ -307,4 +307,9 @@ erpnext.patches.v14_0.delete_shopify_doctypes
|
||||
erpnext.patches.v13_0.replace_supplier_item_group_with_party_specific_item
|
||||
erpnext.patches.v13_0.update_dates_in_tax_withholding_category
|
||||
erpnext.patches.v14_0.update_opportunity_currency_fields
|
||||
erpnext.patches.v13_0.gst_fields_for_pos_invoice
|
||||
erpnext.patches.v13_0.create_accounting_dimensions_in_pos_doctypes
|
||||
erpnext.patches.v13_0.create_custom_field_for_finance_book
|
||||
erpnext.patches.v13_0.modify_invalid_gain_loss_gl_entries
|
||||
erpnext.patches.v13_0.fix_additional_cost_in_mfg_stock_entry
|
||||
erpnext.patches.v13_0.set_status_in_maintenance_schedule_table
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import frappe
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
||||
|
||||
|
||||
def execute():
|
||||
company = frappe.get_all('Company', filters = {'country': 'India'})
|
||||
if not company:
|
||||
return
|
||||
|
||||
custom_field = {
|
||||
'Finance Book': [
|
||||
{
|
||||
'fieldname': 'for_income_tax',
|
||||
'label': 'For Income Tax',
|
||||
'fieldtype': 'Check',
|
||||
'insert_after': 'finance_book_name',
|
||||
'description': 'If the asset is put to use for less than 180 days, the first Depreciation Rate will be reduced by 50%.'
|
||||
}
|
||||
]
|
||||
}
|
||||
create_custom_fields(custom_field, update=1)
|
||||
@@ -0,0 +1,76 @@
|
||||
from typing import List, NewType
|
||||
|
||||
import frappe
|
||||
|
||||
StockEntryCode = NewType("StockEntryCode", str)
|
||||
|
||||
|
||||
def execute():
|
||||
stock_entry_codes = find_broken_stock_entries()
|
||||
|
||||
for stock_entry_code in stock_entry_codes:
|
||||
patched_stock_entry = patch_additional_cost(stock_entry_code)
|
||||
create_repost_item_valuation(patched_stock_entry)
|
||||
|
||||
|
||||
def find_broken_stock_entries() -> List[StockEntryCode]:
|
||||
period_closing_date = frappe.db.get_value(
|
||||
"Period Closing Voucher", {"docstatus": 1}, "posting_date", order_by="posting_date desc"
|
||||
)
|
||||
|
||||
stock_entries_to_patch = frappe.db.sql(
|
||||
"""
|
||||
select se.name, sum(sed.additional_cost) as item_additional_cost, se.total_additional_costs
|
||||
from `tabStock Entry` se
|
||||
join `tabStock Entry Detail` sed
|
||||
on sed.parent = se.name
|
||||
where
|
||||
se.docstatus = 1 and
|
||||
se.posting_date > %s
|
||||
group by
|
||||
sed.parent
|
||||
having
|
||||
item_additional_cost != se.total_additional_costs
|
||||
""",
|
||||
period_closing_date,
|
||||
as_dict=True,
|
||||
)
|
||||
|
||||
return [d.name for d in stock_entries_to_patch]
|
||||
|
||||
|
||||
def patch_additional_cost(code: StockEntryCode):
|
||||
stock_entry = frappe.get_doc("Stock Entry", code)
|
||||
stock_entry.distribute_additional_costs()
|
||||
stock_entry.update_valuation_rate()
|
||||
stock_entry.set_total_incoming_outgoing_value()
|
||||
stock_entry.set_total_amount()
|
||||
stock_entry.db_update()
|
||||
for item in stock_entry.items:
|
||||
item.db_update()
|
||||
return stock_entry
|
||||
|
||||
|
||||
def create_repost_item_valuation(stock_entry):
|
||||
from erpnext.controllers.stock_controller import create_repost_item_valuation_entry
|
||||
|
||||
# turn on recalculate flag so reposting corrects the incoming/outgoing rates.
|
||||
frappe.db.set_value(
|
||||
"Stock Ledger Entry",
|
||||
{"voucher_no": stock_entry.name, "actual_qty": (">", 0)},
|
||||
"recalculate_rate",
|
||||
1,
|
||||
update_modified=False,
|
||||
)
|
||||
|
||||
create_repost_item_valuation_entry(
|
||||
args=frappe._dict(
|
||||
{
|
||||
"posting_date": stock_entry.posting_date,
|
||||
"posting_time": stock_entry.posting_time,
|
||||
"voucher_type": stock_entry.doctype,
|
||||
"voucher_no": stock_entry.name,
|
||||
"company": stock_entry.company,
|
||||
}
|
||||
)
|
||||
)
|
||||
44
erpnext/patches/v13_0/gst_fields_for_pos_invoice.py
Normal file
44
erpnext/patches/v13_0/gst_fields_for_pos_invoice.py
Normal file
@@ -0,0 +1,44 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
||||
|
||||
|
||||
def execute():
|
||||
company = frappe.get_all('Company', filters = {'country': 'India'}, fields=['name'])
|
||||
if not company:
|
||||
return
|
||||
|
||||
hsn_sac_field = dict(fieldname='gst_hsn_code', label='HSN/SAC',
|
||||
fieldtype='Data', fetch_from='item_code.gst_hsn_code', insert_after='description',
|
||||
allow_on_submit=1, print_hide=1, fetch_if_empty=1)
|
||||
nil_rated_exempt = dict(fieldname='is_nil_exempt', label='Is Nil Rated or Exempted',
|
||||
fieldtype='Check', fetch_from='item_code.is_nil_exempt', insert_after='gst_hsn_code',
|
||||
print_hide=1)
|
||||
is_non_gst = dict(fieldname='is_non_gst', label='Is Non GST',
|
||||
fieldtype='Check', fetch_from='item_code.is_non_gst', insert_after='is_nil_exempt',
|
||||
print_hide=1)
|
||||
taxable_value = dict(fieldname='taxable_value', label='Taxable Value',
|
||||
fieldtype='Currency', insert_after='base_net_amount', hidden=1, options="Company:company:default_currency",
|
||||
print_hide=1)
|
||||
sales_invoice_gst_fields = [
|
||||
dict(fieldname='billing_address_gstin', label='Billing Address GSTIN',
|
||||
fieldtype='Data', insert_after='customer_address', read_only=1,
|
||||
fetch_from='customer_address.gstin', print_hide=1),
|
||||
dict(fieldname='customer_gstin', label='Customer GSTIN',
|
||||
fieldtype='Data', insert_after='shipping_address_name',
|
||||
fetch_from='shipping_address_name.gstin', print_hide=1),
|
||||
dict(fieldname='place_of_supply', label='Place of Supply',
|
||||
fieldtype='Data', insert_after='customer_gstin',
|
||||
print_hide=1, read_only=1),
|
||||
dict(fieldname='company_gstin', label='Company GSTIN',
|
||||
fieldtype='Data', insert_after='company_address',
|
||||
fetch_from='company_address.gstin', print_hide=1, read_only=1),
|
||||
]
|
||||
|
||||
custom_fields = {
|
||||
'POS Invoice': sales_invoice_gst_fields,
|
||||
'POS Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst, taxable_value],
|
||||
}
|
||||
|
||||
create_custom_fields(custom_fields, update=True)
|
||||
49
erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py
Normal file
49
erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py
Normal file
@@ -0,0 +1,49 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import json
|
||||
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
frappe.reload_doc('accounts', 'doctype', 'purchase_invoice_advance')
|
||||
frappe.reload_doc('accounts', 'doctype', 'sales_invoice_advance')
|
||||
|
||||
purchase_invoices = frappe.db.sql("""
|
||||
select
|
||||
parenttype as type, parent as name
|
||||
from
|
||||
`tabPurchase Invoice Advance`
|
||||
where
|
||||
ref_exchange_rate = 1
|
||||
and docstatus = 1
|
||||
and ifnull(exchange_gain_loss, '') != ''
|
||||
group by
|
||||
parent
|
||||
""", as_dict=1)
|
||||
|
||||
sales_invoices = frappe.db.sql("""
|
||||
select
|
||||
parenttype as type, parent as name
|
||||
from
|
||||
`tabSales Invoice Advance`
|
||||
where
|
||||
ref_exchange_rate = 1
|
||||
and docstatus = 1
|
||||
and ifnull(exchange_gain_loss, '') != ''
|
||||
group by
|
||||
parent
|
||||
""", as_dict=1)
|
||||
|
||||
if purchase_invoices + sales_invoices:
|
||||
frappe.log_error(json.dumps(purchase_invoices + sales_invoices, indent=2), title="Patch Log")
|
||||
|
||||
for invoice in purchase_invoices + sales_invoices:
|
||||
doc = frappe.get_doc(invoice.type, invoice.name)
|
||||
doc.docstatus = 2
|
||||
doc.make_gl_entries()
|
||||
for advance in doc.advances:
|
||||
if advance.ref_exchange_rate == 1:
|
||||
advance.db_set('exchange_gain_loss', 0, False)
|
||||
doc.docstatus = 1
|
||||
doc.make_gl_entries()
|
||||
@@ -0,0 +1,10 @@
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
frappe.reload_doc("maintenance", "doctype", "Maintenance Schedule Detail")
|
||||
frappe.db.sql("""
|
||||
UPDATE `tabMaintenance Schedule Detail`
|
||||
SET completion_status = 'Pending'
|
||||
WHERE docstatus < 2
|
||||
""")
|
||||
@@ -144,6 +144,9 @@ class Project(Document):
|
||||
if self.sales_order:
|
||||
frappe.db.set_value("Sales Order", self.sales_order, "project", self.name)
|
||||
|
||||
def on_trash(self):
|
||||
frappe.db.set_value("Sales Order", {"project": self.name}, "project", "")
|
||||
|
||||
def update_percent_complete(self):
|
||||
if self.percent_complete_method == "Manual":
|
||||
if self.status == "Completed":
|
||||
|
||||
@@ -9,6 +9,8 @@ from frappe.utils import add_days, getdate, nowdate
|
||||
|
||||
from erpnext.projects.doctype.project_template.test_project_template import make_project_template
|
||||
from erpnext.projects.doctype.task.test_task import create_task
|
||||
from erpnext.selling.doctype.sales_order.sales_order import make_project as make_project_from_so
|
||||
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||
|
||||
test_records = frappe.get_test_records('Project')
|
||||
test_ignore = ["Sales Order"]
|
||||
@@ -96,6 +98,21 @@ class TestProject(unittest.TestCase):
|
||||
|
||||
self.assertEqual(len(tasks), 2)
|
||||
|
||||
def test_project_linking_with_sales_order(self):
|
||||
so = make_sales_order()
|
||||
project = make_project_from_so(so.name)
|
||||
|
||||
project.save()
|
||||
self.assertEqual(project.sales_order, so.name)
|
||||
|
||||
so.reload()
|
||||
self.assertEqual(so.project, project.name)
|
||||
|
||||
project.delete()
|
||||
|
||||
so.reload()
|
||||
self.assertFalse(so.project)
|
||||
|
||||
def get_project(name, template):
|
||||
|
||||
project = frappe.get_doc(dict(
|
||||
|
||||
@@ -345,26 +345,14 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
}
|
||||
|
||||
scan_barcode() {
|
||||
let scan_barcode_field = this.frm.fields_dict["scan_barcode"];
|
||||
|
||||
let show_description = function(idx, exist = null) {
|
||||
if (exist) {
|
||||
frappe.show_alert({
|
||||
message: __('Row #{0}: Qty increased by 1', [idx]),
|
||||
indicator: 'green'
|
||||
});
|
||||
} else {
|
||||
frappe.show_alert({
|
||||
message: __('Row #{0}: Item added', [idx]),
|
||||
indicator: 'green'
|
||||
});
|
||||
}
|
||||
}
|
||||
let me = this;
|
||||
|
||||
if(this.frm.doc.scan_barcode) {
|
||||
frappe.call({
|
||||
method: "erpnext.selling.page.point_of_sale.point_of_sale.search_for_serial_or_batch_or_barcode_number",
|
||||
args: { search_value: this.frm.doc.scan_barcode }
|
||||
args: {
|
||||
search_value: this.frm.doc.scan_barcode
|
||||
}
|
||||
}).then(r => {
|
||||
const data = r && r.message;
|
||||
if (!data || Object.keys(data).length === 0) {
|
||||
@@ -375,49 +363,96 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
return;
|
||||
}
|
||||
|
||||
let cur_grid = this.frm.fields_dict.items.grid;
|
||||
|
||||
let row_to_modify = null;
|
||||
const existing_item_row = this.frm.doc.items.find(d => d.item_code === data.item_code);
|
||||
const blank_item_row = this.frm.doc.items.find(d => !d.item_code);
|
||||
|
||||
if (existing_item_row) {
|
||||
row_to_modify = existing_item_row;
|
||||
} else if (blank_item_row) {
|
||||
row_to_modify = blank_item_row;
|
||||
}
|
||||
|
||||
if (!row_to_modify) {
|
||||
// add new row
|
||||
row_to_modify = frappe.model.add_child(this.frm.doc, cur_grid.doctype, 'items');
|
||||
}
|
||||
|
||||
show_description(row_to_modify.idx, row_to_modify.item_code);
|
||||
|
||||
this.frm.from_barcode = this.frm.from_barcode ? this.frm.from_barcode + 1 : 1;
|
||||
frappe.model.set_value(row_to_modify.doctype, row_to_modify.name, {
|
||||
item_code: data.item_code,
|
||||
qty: (row_to_modify.qty || 0) + 1
|
||||
});
|
||||
|
||||
['serial_no', 'batch_no', 'barcode'].forEach(field => {
|
||||
if (data[field] && frappe.meta.has_field(row_to_modify.doctype, field)) {
|
||||
|
||||
let value = (row_to_modify[field] && field === "serial_no")
|
||||
? row_to_modify[field] + '\n' + data[field] : data[field];
|
||||
|
||||
frappe.model.set_value(row_to_modify.doctype,
|
||||
row_to_modify.name, field, value);
|
||||
}
|
||||
});
|
||||
|
||||
scan_barcode_field.set_value('');
|
||||
refresh_field("items");
|
||||
me.modify_table_after_scan(data);
|
||||
});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
modify_table_after_scan(data) {
|
||||
let scan_barcode_field = this.frm.fields_dict["scan_barcode"];
|
||||
let cur_grid = this.frm.fields_dict.items.grid;
|
||||
let row_to_modify = null;
|
||||
|
||||
// Check if batch is scanned and table has batch no field
|
||||
let batch_no_scan = Boolean(data.batch_no) && frappe.meta.has_field(cur_grid.doctype, "batch_no");
|
||||
|
||||
if (batch_no_scan) {
|
||||
row_to_modify = this.get_batch_row_to_modify(data.batch_no);
|
||||
} else {
|
||||
// serial or barcode scan
|
||||
row_to_modify = this.get_row_to_modify_on_scan(row_to_modify, data);
|
||||
}
|
||||
|
||||
if (!row_to_modify) {
|
||||
// add new row if new item/batch is scanned
|
||||
row_to_modify = frappe.model.add_child(this.frm.doc, cur_grid.doctype, 'items');
|
||||
}
|
||||
|
||||
this.show_scan_message(row_to_modify.idx, row_to_modify.item_code);
|
||||
this.set_scanned_values(row_to_modify, data, scan_barcode_field);
|
||||
}
|
||||
|
||||
set_scanned_values(row_to_modify, data, scan_barcode_field) {
|
||||
// increase qty and set scanned value and item in row
|
||||
this.frm.from_barcode = this.frm.from_barcode ? this.frm.from_barcode + 1 : 1;
|
||||
frappe.model.set_value(row_to_modify.doctype, row_to_modify.name, {
|
||||
item_code: data.item_code,
|
||||
qty: (row_to_modify.qty || 0) + 1
|
||||
});
|
||||
|
||||
['serial_no', 'batch_no', 'barcode'].forEach(field => {
|
||||
if (data[field] && frappe.meta.has_field(row_to_modify.doctype, field)) {
|
||||
let is_serial_no = row_to_modify[field] && field === "serial_no";
|
||||
let value = data[field];
|
||||
|
||||
if (is_serial_no) {
|
||||
value = row_to_modify[field] + '\n' + data[field];
|
||||
}
|
||||
|
||||
frappe.model.set_value(row_to_modify.doctype, row_to_modify.name, field, value);
|
||||
}
|
||||
});
|
||||
|
||||
scan_barcode_field.set_value('');
|
||||
refresh_field("items");
|
||||
}
|
||||
|
||||
get_row_to_modify_on_scan(row_to_modify, data) {
|
||||
// get an existing item row to increment or blank row to modify
|
||||
const existing_item_row = this.frm.doc.items.find(d => d.item_code === data.item_code);
|
||||
const blank_item_row = this.frm.doc.items.find(d => !d.item_code);
|
||||
|
||||
if (existing_item_row) {
|
||||
row_to_modify = existing_item_row;
|
||||
} else if (blank_item_row) {
|
||||
row_to_modify = blank_item_row;
|
||||
}
|
||||
|
||||
return row_to_modify;
|
||||
}
|
||||
|
||||
get_batch_row_to_modify(batch_no) {
|
||||
// get row if batch already exists in table
|
||||
const existing_batch_row = this.frm.doc.items.find(d => d.batch_no === batch_no);
|
||||
return existing_batch_row || null;
|
||||
}
|
||||
|
||||
show_scan_message (idx, exist = null) {
|
||||
// show new row or qty increase toast
|
||||
if (exist) {
|
||||
frappe.show_alert({
|
||||
message: __('Row #{0}: Qty increased by 1', [idx]),
|
||||
indicator: 'green'
|
||||
});
|
||||
} else {
|
||||
frappe.show_alert({
|
||||
message: __('Row #{0}: Item added', [idx]),
|
||||
indicator: 'green'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
apply_default_taxes() {
|
||||
var me = this;
|
||||
var taxes_and_charges_field = frappe.meta.get_docfield(me.frm.doc.doctype, "taxes_and_charges",
|
||||
@@ -617,6 +652,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
me.frm.script_manager.trigger('qty', item.doctype, item.name);
|
||||
if (!me.frm.doc.set_warehouse)
|
||||
me.frm.script_manager.trigger('warehouse', item.doctype, item.name);
|
||||
me.apply_price_list(item, true);
|
||||
}, undefined, !frappe.flags.hide_serial_batch_dialog);
|
||||
}
|
||||
},
|
||||
@@ -884,7 +920,9 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
if (r.message) {
|
||||
me.frm.set_value("billing_address", r.message);
|
||||
} else {
|
||||
me.frm.set_value("company_address", "");
|
||||
if (frappe.meta.get_docfield(me.frm.doctype, 'company_address')) {
|
||||
me.frm.set_value("company_address", "");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -63,7 +63,7 @@ erpnext.HierarchyChart = class {
|
||||
});
|
||||
|
||||
node.parent.append(node_card);
|
||||
node.$link = $(`#${node.id}`);
|
||||
node.$link = $(`[id="${node.id}"]`);
|
||||
}
|
||||
|
||||
show() {
|
||||
@@ -223,7 +223,7 @@ erpnext.HierarchyChart = class {
|
||||
let node = undefined;
|
||||
|
||||
$.each(r.message, (_i, data) => {
|
||||
if ($(`#${data.id}`).length)
|
||||
if ($(`[id="${data.id}"]`).length)
|
||||
return;
|
||||
|
||||
node = new me.Node({
|
||||
@@ -263,7 +263,7 @@ erpnext.HierarchyChart = class {
|
||||
this.refresh_connectors(node.parent_id);
|
||||
|
||||
// rebuild incoming connections
|
||||
let grandparent = $(`#${node.parent_id}`).attr('data-parent');
|
||||
let grandparent = $(`[id="${node.parent_id}"]`).attr('data-parent');
|
||||
this.refresh_connectors(grandparent);
|
||||
}
|
||||
|
||||
@@ -282,7 +282,7 @@ erpnext.HierarchyChart = class {
|
||||
|
||||
show_active_path(node) {
|
||||
// mark node parent on active path
|
||||
$(`#${node.parent_id}`).addClass('active-path');
|
||||
$(`[id="${node.parent_id}"]`).addClass('active-path');
|
||||
}
|
||||
|
||||
load_children(node, deep=false) {
|
||||
@@ -317,7 +317,7 @@ erpnext.HierarchyChart = class {
|
||||
|
||||
render_child_nodes(node, child_nodes) {
|
||||
const last_level = this.$hierarchy.find('.level:last').index();
|
||||
const current_level = $(`#${node.id}`).parent().parent().parent().index();
|
||||
const current_level = $(`[id="${node.id}"]`).parent().parent().parent().index();
|
||||
|
||||
if (last_level === current_level) {
|
||||
this.$hierarchy.append(`
|
||||
@@ -382,7 +382,7 @@ erpnext.HierarchyChart = class {
|
||||
node.$children = $('<ul class="node-children"></ul>');
|
||||
|
||||
const last_level = this.$hierarchy.find('.level:last').index();
|
||||
const node_level = $(`#${node.id}`).parent().parent().parent().index();
|
||||
const node_level = $(`[id="${node.id}"]`).parent().parent().parent().index();
|
||||
|
||||
if (last_level === node_level) {
|
||||
this.$hierarchy.append(`
|
||||
@@ -489,7 +489,7 @@ erpnext.HierarchyChart = class {
|
||||
set_path_attributes(path, parent_id, child_id) {
|
||||
path.setAttribute("data-parent", parent_id);
|
||||
path.setAttribute("data-child", child_id);
|
||||
const parent = $(`#${parent_id}`);
|
||||
const parent = $(`[id="${parent_id}"]`);
|
||||
|
||||
if (parent.hasClass('active')) {
|
||||
path.setAttribute("class", "active-connector");
|
||||
@@ -513,7 +513,7 @@ erpnext.HierarchyChart = class {
|
||||
}
|
||||
|
||||
collapse_previous_level_nodes(node) {
|
||||
let node_parent = $(`#${node.parent_id}`);
|
||||
let node_parent = $(`[id="${node.parent_id}"]`);
|
||||
let previous_level_nodes = node_parent.parent().parent().children('li');
|
||||
let node_card = undefined;
|
||||
|
||||
@@ -545,7 +545,7 @@ erpnext.HierarchyChart = class {
|
||||
|
||||
setup_node_click_action(node) {
|
||||
let me = this;
|
||||
let node_element = $(`#${node.id}`);
|
||||
let node_element = $(`[id="${node.id}"]`);
|
||||
|
||||
node_element.click(function() {
|
||||
const is_sibling = me.selected_node.parent_id === node.parent_id;
|
||||
@@ -563,7 +563,7 @@ erpnext.HierarchyChart = class {
|
||||
}
|
||||
|
||||
setup_edit_node_action(node) {
|
||||
let node_element = $(`#${node.id}`);
|
||||
let node_element = $(`[id="${node.id}"]`);
|
||||
let me = this;
|
||||
|
||||
node_element.find('.btn-edit-node').click(function() {
|
||||
@@ -572,7 +572,7 @@ erpnext.HierarchyChart = class {
|
||||
}
|
||||
|
||||
remove_levels_after_node(node) {
|
||||
let level = $(`#${node.id}`).parent().parent().parent().index();
|
||||
let level = $(`[id="${node.id}"]`).parent().parent().parent().index();
|
||||
|
||||
level = $('.hierarchy > li:eq('+ level + ')');
|
||||
level.nextAll('li').remove();
|
||||
@@ -595,7 +595,7 @@ erpnext.HierarchyChart = class {
|
||||
const parent = $(path).data('parent');
|
||||
const child = $(path).data('child');
|
||||
|
||||
if ($(`#${parent}`).length && $(`#${child}`).length)
|
||||
if ($(`[id="${parent}"]`).length && $(`[id="${child}"]`).length)
|
||||
return;
|
||||
|
||||
$(path).remove();
|
||||
|
||||
@@ -54,7 +54,7 @@ erpnext.HierarchyChartMobile = class {
|
||||
});
|
||||
|
||||
node.parent.append(node_card);
|
||||
node.$link = $(`#${node.id}`);
|
||||
node.$link = $(`[id="${node.id}"]`);
|
||||
node.$link.addClass('mobile-node');
|
||||
}
|
||||
|
||||
@@ -184,7 +184,7 @@ erpnext.HierarchyChartMobile = class {
|
||||
this.refresh_connectors(node.parent_id, node.id);
|
||||
|
||||
// rebuild incoming connections of parent
|
||||
let grandparent = $(`#${node.parent_id}`).attr('data-parent');
|
||||
let grandparent = $(`[id="${node.parent_id}"]`).attr('data-parent');
|
||||
this.refresh_connectors(grandparent, node.parent_id);
|
||||
}
|
||||
|
||||
@@ -221,7 +221,7 @@ erpnext.HierarchyChartMobile = class {
|
||||
|
||||
show_active_path(node) {
|
||||
// mark node parent on active path
|
||||
$(`#${node.parent_id}`).addClass('active-path');
|
||||
$(`[id="${node.parent_id}"]`).addClass('active-path');
|
||||
}
|
||||
|
||||
load_children(node) {
|
||||
@@ -256,7 +256,7 @@ erpnext.HierarchyChartMobile = class {
|
||||
if (child_nodes) {
|
||||
$.each(child_nodes, (_i, data) => {
|
||||
this.add_node(node, data);
|
||||
$(`#${data.id}`).addClass('active-child');
|
||||
$(`[id="${data.id}"]`).addClass('active-child');
|
||||
|
||||
setTimeout(() => {
|
||||
this.add_connector(node.id, data.id);
|
||||
@@ -293,9 +293,9 @@ erpnext.HierarchyChartMobile = class {
|
||||
|
||||
let connector = undefined;
|
||||
|
||||
if ($(`#${parent_id}`).hasClass('active')) {
|
||||
if ($(`[id="${parent_id}"]`).hasClass('active')) {
|
||||
connector = this.get_connector_for_active_node(parent_node, child_node);
|
||||
} else if ($(`#${parent_id}`).hasClass('active-path')) {
|
||||
} else if ($(`[id="${parent_id}"]`).hasClass('active-path')) {
|
||||
connector = this.get_connector_for_collapsed_node(parent_node, child_node);
|
||||
}
|
||||
|
||||
@@ -351,7 +351,7 @@ erpnext.HierarchyChartMobile = class {
|
||||
set_path_attributes(path, parent_id, child_id) {
|
||||
path.setAttribute("data-parent", parent_id);
|
||||
path.setAttribute("data-child", child_id);
|
||||
const parent = $(`#${parent_id}`);
|
||||
const parent = $(`[id="${parent_id}"]`);
|
||||
|
||||
if (parent.hasClass('active')) {
|
||||
path.setAttribute("class", "active-connector");
|
||||
@@ -374,7 +374,7 @@ erpnext.HierarchyChartMobile = class {
|
||||
|
||||
setup_node_click_action(node) {
|
||||
let me = this;
|
||||
let node_element = $(`#${node.id}`);
|
||||
let node_element = $(`[id="${node.id}"]`);
|
||||
|
||||
node_element.click(function() {
|
||||
let el = undefined;
|
||||
@@ -398,7 +398,7 @@ erpnext.HierarchyChartMobile = class {
|
||||
}
|
||||
|
||||
setup_edit_node_action(node) {
|
||||
let node_element = $(`#${node.id}`);
|
||||
let node_element = $(`[id="${node.id}"]`);
|
||||
let me = this;
|
||||
|
||||
node_element.find('.btn-edit-node').click(function() {
|
||||
@@ -512,7 +512,7 @@ erpnext.HierarchyChartMobile = class {
|
||||
}
|
||||
|
||||
remove_levels_after_node(node) {
|
||||
let level = $(`#${node.id}`).parent().parent().index();
|
||||
let level = $(`[id="${node.id}"]`).parent().parent().index();
|
||||
|
||||
level = $('.hierarchy-mobile > li:eq('+ level + ')');
|
||||
level.nextAll('li').remove();
|
||||
@@ -533,7 +533,7 @@ erpnext.HierarchyChartMobile = class {
|
||||
const parent = $(path).data('parent');
|
||||
const child = $(path).data('child');
|
||||
|
||||
if ($(`#${parent}`).length && $(`#${child}`).length)
|
||||
if ($(`[id="${parent}"]`).length && $(`[id="${child}"]`).length)
|
||||
return;
|
||||
|
||||
$(path).remove();
|
||||
|
||||
@@ -714,12 +714,15 @@ erpnext.utils.map_current_doc = function(opts) {
|
||||
child_columns: opts.child_columns,
|
||||
action: function(selections, args) {
|
||||
let values = selections;
|
||||
if(values.length === 0){
|
||||
if (values.length === 0) {
|
||||
frappe.msgprint(__("Please select {0}", [opts.source_doctype]))
|
||||
return;
|
||||
}
|
||||
opts.source_name = values;
|
||||
opts.args = args;
|
||||
if (opts.allow_child_item_selection) {
|
||||
// args contains filtered child docnames
|
||||
opts.args = args;
|
||||
}
|
||||
d.dialog.hide();
|
||||
_map();
|
||||
},
|
||||
|
||||
@@ -483,6 +483,7 @@ def make_custom_fields(update=True):
|
||||
'Purchase Order': purchase_invoice_gst_fields,
|
||||
'Purchase Receipt': purchase_invoice_gst_fields,
|
||||
'Sales Invoice': sales_invoice_gst_category + invoice_gst_fields + sales_invoice_shipping_fields + sales_invoice_gst_fields + si_ewaybill_fields,
|
||||
'POS Invoice': sales_invoice_gst_fields,
|
||||
'Delivery Note': sales_invoice_gst_fields + ewaybill_fields + sales_invoice_shipping_fields + delivery_note_gst_category,
|
||||
'Payment Entry': payment_entry_fields,
|
||||
'Journal Entry': journal_entry_fields,
|
||||
@@ -501,6 +502,7 @@ def make_custom_fields(update=True):
|
||||
'Sales Order Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
|
||||
'Delivery Note Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
|
||||
'Sales Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst, taxable_value],
|
||||
'POS Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst, taxable_value],
|
||||
'Purchase Order Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
|
||||
'Purchase Receipt Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
|
||||
'Purchase Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst, taxable_value],
|
||||
@@ -661,6 +663,15 @@ def make_custom_fields(update=True):
|
||||
'fieldtype': 'Data',
|
||||
'insert_after': 'email'
|
||||
}
|
||||
],
|
||||
'Finance Book': [
|
||||
{
|
||||
'fieldname': 'for_income_tax',
|
||||
'label': 'For Income Tax',
|
||||
'fieldtype': 'Check',
|
||||
'insert_after': 'finance_book_name',
|
||||
'description': 'If the asset is put to use for less than 180 days, the first Depreciation Rate will be reduced by 50%.'
|
||||
}
|
||||
]
|
||||
}
|
||||
create_custom_fields(custom_fields, update=update)
|
||||
@@ -750,7 +761,7 @@ def set_salary_components(docs):
|
||||
|
||||
def set_tax_withholding_category(company):
|
||||
accounts = []
|
||||
fiscal_year = None
|
||||
fiscal_year_details = None
|
||||
abbr = frappe.get_value("Company", company, "abbr")
|
||||
tds_account = frappe.get_value("Account", 'TDS Payable - {0}'.format(abbr), 'name')
|
||||
|
||||
|
||||
@@ -112,12 +112,9 @@ def validate_gstin_check_digit(gstin, label='GSTIN'):
|
||||
frappe.throw(_("""Invalid {0}! The check digit validation has failed. Please ensure you've typed the {0} correctly.""").format(label))
|
||||
|
||||
def get_itemised_tax_breakup_header(item_doctype, tax_accounts):
|
||||
if frappe.get_meta(item_doctype).has_field('gst_hsn_code'):
|
||||
return [_("HSN/SAC"), _("Taxable Amount")] + tax_accounts
|
||||
else:
|
||||
return [_("Item"), _("Taxable Amount")] + tax_accounts
|
||||
return [_("Item"), _("Taxable Amount")] + tax_accounts
|
||||
|
||||
def get_itemised_tax_breakup_data(doc, account_wise=False):
|
||||
def get_itemised_tax_breakup_data(doc, account_wise=False, hsn_wise=False):
|
||||
itemised_tax = get_itemised_tax(doc.taxes, with_tax_account=account_wise)
|
||||
|
||||
itemised_taxable_amount = get_itemised_taxable_amount(doc.items)
|
||||
@@ -125,28 +122,29 @@ def get_itemised_tax_breakup_data(doc, account_wise=False):
|
||||
if not frappe.get_meta(doc.doctype + " Item").has_field('gst_hsn_code'):
|
||||
return itemised_tax, itemised_taxable_amount
|
||||
|
||||
item_hsn_map = frappe._dict()
|
||||
for d in doc.items:
|
||||
item_hsn_map.setdefault(d.item_code or d.item_name, d.get("gst_hsn_code"))
|
||||
if hsn_wise:
|
||||
item_hsn_map = frappe._dict()
|
||||
for d in doc.items:
|
||||
item_hsn_map.setdefault(d.item_code or d.item_name, d.get("gst_hsn_code"))
|
||||
|
||||
hsn_tax = {}
|
||||
for item, taxes in itemised_tax.items():
|
||||
hsn_code = item_hsn_map.get(item)
|
||||
hsn_tax.setdefault(hsn_code, frappe._dict())
|
||||
item_or_hsn = item if not hsn_wise else item_hsn_map.get(item)
|
||||
hsn_tax.setdefault(item_or_hsn, frappe._dict())
|
||||
for tax_desc, tax_detail in taxes.items():
|
||||
key = tax_desc
|
||||
if account_wise:
|
||||
key = tax_detail.get('tax_account')
|
||||
hsn_tax[hsn_code].setdefault(key, {"tax_rate": 0, "tax_amount": 0})
|
||||
hsn_tax[hsn_code][key]["tax_rate"] = tax_detail.get("tax_rate")
|
||||
hsn_tax[hsn_code][key]["tax_amount"] += tax_detail.get("tax_amount")
|
||||
hsn_tax[item_or_hsn].setdefault(key, {"tax_rate": 0, "tax_amount": 0})
|
||||
hsn_tax[item_or_hsn][key]["tax_rate"] = tax_detail.get("tax_rate")
|
||||
hsn_tax[item_or_hsn][key]["tax_amount"] += tax_detail.get("tax_amount")
|
||||
|
||||
# set taxable amount
|
||||
hsn_taxable_amount = frappe._dict()
|
||||
for item in itemised_taxable_amount:
|
||||
hsn_code = item_hsn_map.get(item)
|
||||
hsn_taxable_amount.setdefault(hsn_code, 0)
|
||||
hsn_taxable_amount[hsn_code] += itemised_taxable_amount.get(item)
|
||||
item_or_hsn = item if not hsn_wise else item_hsn_map.get(item)
|
||||
hsn_taxable_amount.setdefault(item_or_hsn, 0)
|
||||
hsn_taxable_amount[item_or_hsn] += itemised_taxable_amount.get(item)
|
||||
|
||||
return hsn_tax, hsn_taxable_amount
|
||||
|
||||
@@ -440,7 +438,7 @@ def get_ewb_data(dt, dn):
|
||||
data.itemList = []
|
||||
data.totalValue = doc.total
|
||||
|
||||
data = get_item_list(data, doc)
|
||||
data = get_item_list(data, doc, hsn_wise=True)
|
||||
|
||||
disable_rounded = frappe.db.get_single_value('Global Defaults', 'disable_rounded_total')
|
||||
data.totInvValue = doc.grand_total if disable_rounded else doc.rounded_total
|
||||
@@ -551,7 +549,7 @@ def get_address_details(data, doc, company_address, billing_address, dispatch_ad
|
||||
|
||||
return data
|
||||
|
||||
def get_item_list(data, doc):
|
||||
def get_item_list(data, doc, hsn_wise=False):
|
||||
for attr in ['cgstValue', 'sgstValue', 'igstValue', 'cessValue', 'OthValue']:
|
||||
data[attr] = 0
|
||||
|
||||
@@ -563,7 +561,7 @@ def get_item_list(data, doc):
|
||||
'cess_account': ['cessRate', 'cessValue']
|
||||
}
|
||||
item_data_attrs = ['sgstRate', 'cgstRate', 'igstRate', 'cessRate', 'cessNonAdvol']
|
||||
hsn_wise_charges, hsn_taxable_amount = get_itemised_tax_breakup_data(doc, account_wise=True)
|
||||
hsn_wise_charges, hsn_taxable_amount = get_itemised_tax_breakup_data(doc, account_wise=True, hsn_wise=hsn_wise)
|
||||
for hsn_code, taxable_amount in hsn_taxable_amount.items():
|
||||
item_data = frappe._dict()
|
||||
if not hsn_code:
|
||||
@@ -859,12 +857,13 @@ def get_depreciation_amount(asset, depreciable_value, row):
|
||||
rate_of_depreciation = row.rate_of_depreciation
|
||||
# if its the first depreciation
|
||||
if depreciable_value == asset.gross_purchase_amount:
|
||||
# as per IT act, if the asset is purchased in the 2nd half of fiscal year, then rate is divided by 2
|
||||
diff = date_diff(row.depreciation_start_date, asset.available_for_use_date)
|
||||
if diff <= 180:
|
||||
rate_of_depreciation = rate_of_depreciation / 2
|
||||
frappe.msgprint(
|
||||
_('As per IT Act, the rate of depreciation for the first depreciation entry is reduced by 50%.'))
|
||||
if row.finance_book and frappe.db.get_value('Finance Book', row.finance_book, 'for_income_tax'):
|
||||
# as per IT act, if the asset is purchased in the 2nd half of fiscal year, then rate is divided by 2
|
||||
diff = date_diff(row.depreciation_start_date, asset.available_for_use_date)
|
||||
if diff <= 180:
|
||||
rate_of_depreciation = rate_of_depreciation / 2
|
||||
frappe.msgprint(
|
||||
_('As per IT Act, the rate of depreciation for the first depreciation entry is reduced by 50%.'))
|
||||
|
||||
depreciation_amount = flt(depreciable_value * (flt(rate_of_depreciation) / 100))
|
||||
|
||||
|
||||
@@ -1480,6 +1480,7 @@
|
||||
"fetch_from": "customer.represents_company",
|
||||
"fieldname": "represents_company",
|
||||
"fieldtype": "Link",
|
||||
"ignore_user_permissions": 1,
|
||||
"label": "Represents Company",
|
||||
"options": "Company",
|
||||
"read_only": 1
|
||||
@@ -1512,7 +1513,7 @@
|
||||
"idx": 105,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-09-01 15:12:24.115483",
|
||||
"modified": "2021-09-28 13:09:51.515542",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Sales Order",
|
||||
|
||||
@@ -236,7 +236,7 @@ erpnext.PointOfSale.ItemDetails = class {
|
||||
if (this.value) {
|
||||
me.events.form_updated(me.current_item, 'warehouse', this.value).then(() => {
|
||||
me.item_stock_map = me.events.get_item_stock_map();
|
||||
const available_qty = me.item_stock_map[me.item_row.item_code][this.value];
|
||||
const available_qty = me.item_stock_map[me.item_row.item_code] && me.item_stock_map[me.item_row.item_code][this.value];
|
||||
if (available_qty === undefined) {
|
||||
me.events.get_available_stock(me.item_row.item_code, this.value).then(() => {
|
||||
// item stock map is updated now reset warehouse
|
||||
|
||||
@@ -2116,9 +2116,9 @@
|
||||
},
|
||||
|
||||
"Saudi Arabia": {
|
||||
"KSA VAT 5%": {
|
||||
"account_name": "VAT 5%",
|
||||
"tax_rate": 5.00
|
||||
"KSA VAT 15%": {
|
||||
"account_name": "VAT 15%",
|
||||
"tax_rate": 15.00
|
||||
},
|
||||
"KSA VAT Zero": {
|
||||
"account_name": "VAT Zero",
|
||||
|
||||
@@ -1277,6 +1277,7 @@
|
||||
"fetch_from": "customer.represents_company",
|
||||
"fieldname": "represents_company",
|
||||
"fieldtype": "Link",
|
||||
"ignore_user_permissions": 1,
|
||||
"label": "Represents Company",
|
||||
"options": "Company",
|
||||
"read_only": 1
|
||||
@@ -1308,7 +1309,7 @@
|
||||
"idx": 146,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-08-27 20:14:40.215231",
|
||||
"modified": "2021-09-28 13:10:09.761714",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Delivery Note",
|
||||
|
||||
@@ -563,8 +563,12 @@ class Item(WebsiteGenerator):
|
||||
_("Default BOM ({0}) must be active for this item or its template").format(bom_item))
|
||||
|
||||
def fill_customer_code(self):
|
||||
""" Append all the customer codes and insert into "customer_code" field of item table """
|
||||
self.customer_code = ','.join(d.ref_code for d in self.get("customer_items", []))
|
||||
"""
|
||||
Append all the customer codes and insert into "customer_code" field of item table.
|
||||
Used to search Item by customer code.
|
||||
"""
|
||||
customer_codes = set(d.ref_code for d in self.get("customer_items", []))
|
||||
self.customer_code = ','.join(customer_codes)
|
||||
|
||||
def check_item_tax(self):
|
||||
"""Check whether Tax Rate is not entered twice for same Tax Type"""
|
||||
|
||||
@@ -272,8 +272,9 @@ def update_status(name, status):
|
||||
material_request.update_status(status)
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_purchase_order(source_name, target_doc=None, args={}):
|
||||
|
||||
def make_purchase_order(source_name, target_doc=None, args=None):
|
||||
if args is None:
|
||||
args = {}
|
||||
if isinstance(args, string_types):
|
||||
args = json.loads(args)
|
||||
|
||||
|
||||
@@ -36,7 +36,8 @@
|
||||
"fieldname": "qty",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Qty"
|
||||
"label": "Qty",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "picked_qty",
|
||||
@@ -180,7 +181,7 @@
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-06-24 17:18:57.357120",
|
||||
"modified": "2021-09-28 12:02:16.923056",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Pick List Item",
|
||||
|
||||
@@ -1140,6 +1140,7 @@
|
||||
"fetch_from": "supplier.represents_company",
|
||||
"fieldname": "represents_company",
|
||||
"fieldtype": "Link",
|
||||
"ignore_user_permissions": 1,
|
||||
"label": "Represents Company",
|
||||
"options": "Company",
|
||||
"read_only": 1
|
||||
@@ -1149,7 +1150,7 @@
|
||||
"idx": 261,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-08-17 20:16:40.849885",
|
||||
"modified": "2021-09-28 13:11:10.181328",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Purchase Receipt",
|
||||
|
||||
@@ -548,44 +548,7 @@ frappe.ui.form.on('Stock Entry', {
|
||||
calculate_basic_amount: function(frm, item) {
|
||||
item.basic_amount = flt(flt(item.transfer_qty) * flt(item.basic_rate),
|
||||
precision("basic_amount", item));
|
||||
|
||||
frm.events.calculate_amount(frm);
|
||||
},
|
||||
|
||||
calculate_amount: function(frm) {
|
||||
frm.events.calculate_total_additional_costs(frm);
|
||||
let total_basic_amount = 0;
|
||||
if (in_list(["Repack", "Manufacture"], frm.doc.purpose)) {
|
||||
total_basic_amount = frappe.utils.sum(
|
||||
(frm.doc.items || []).map(function(i) {
|
||||
return i.is_finished_item ? flt(i.basic_amount) : 0;
|
||||
})
|
||||
);
|
||||
} else {
|
||||
total_basic_amount = frappe.utils.sum(
|
||||
(frm.doc.items || []).map(function(i) {
|
||||
return i.t_warehouse ? flt(i.basic_amount) : 0;
|
||||
})
|
||||
);
|
||||
}
|
||||
for (let i in frm.doc.items) {
|
||||
let item = frm.doc.items[i];
|
||||
|
||||
if (((in_list(["Repack", "Manufacture"], frm.doc.purpose) && item.is_finished_item) || item.t_warehouse) && total_basic_amount) {
|
||||
item.additional_cost = (flt(item.basic_amount) / total_basic_amount) * frm.doc.total_additional_costs;
|
||||
} else {
|
||||
item.additional_cost = 0;
|
||||
}
|
||||
|
||||
item.amount = flt(item.basic_amount + flt(item.additional_cost), precision("amount", item));
|
||||
|
||||
if (flt(item.transfer_qty)) {
|
||||
item.valuation_rate = flt(flt(item.basic_rate) + (flt(item.additional_cost) / flt(item.transfer_qty)),
|
||||
precision("valuation_rate", item));
|
||||
}
|
||||
}
|
||||
|
||||
refresh_field('items');
|
||||
},
|
||||
|
||||
calculate_total_additional_costs: function(frm) {
|
||||
@@ -781,11 +744,6 @@ frappe.ui.form.on('Landed Cost Taxes and Charges', {
|
||||
amount: function(frm, cdt, cdn) {
|
||||
frm.events.set_base_amount(frm, cdt, cdn);
|
||||
|
||||
// Adding this check because same table in used in LCV
|
||||
// This causes an error if you try to post an LCV immediately after a Stock Entry
|
||||
if (frm.doc.doctype == 'Stock Entry') {
|
||||
frm.events.calculate_amount(frm);
|
||||
}
|
||||
},
|
||||
|
||||
expense_account: function(frm, cdt, cdn) {
|
||||
@@ -1100,4 +1058,4 @@ function check_should_not_attach_bom_items(bom_no) {
|
||||
);
|
||||
}
|
||||
|
||||
$.extend(cur_frm.cscript, new erpnext.stock.StockEntry({frm: cur_frm}));
|
||||
extend_cscript(cur_frm.cscript, new erpnext.stock.StockEntry({frm: cur_frm}));
|
||||
|
||||
@@ -555,22 +555,27 @@ class StockEntry(StockController):
|
||||
|
||||
def distribute_additional_costs(self):
|
||||
# If no incoming items, set additional costs blank
|
||||
if not any([d.item_code for d in self.items if d.t_warehouse]):
|
||||
if not any(d.item_code for d in self.items if d.t_warehouse):
|
||||
self.additional_costs = []
|
||||
|
||||
self.total_additional_costs = sum([flt(t.base_amount) for t in self.get("additional_costs")])
|
||||
self.total_additional_costs = sum(flt(t.base_amount) for t in self.get("additional_costs"))
|
||||
|
||||
if self.purpose in ("Repack", "Manufacture"):
|
||||
incoming_items_cost = sum([flt(t.basic_amount) for t in self.get("items") if t.is_finished_item])
|
||||
incoming_items_cost = sum(flt(t.basic_amount) for t in self.get("items") if t.is_finished_item)
|
||||
else:
|
||||
incoming_items_cost = sum([flt(t.basic_amount) for t in self.get("items") if t.t_warehouse])
|
||||
incoming_items_cost = sum(flt(t.basic_amount) for t in self.get("items") if t.t_warehouse)
|
||||
|
||||
if incoming_items_cost:
|
||||
for d in self.get("items"):
|
||||
if (self.purpose in ("Repack", "Manufacture") and d.is_finished_item) or d.t_warehouse:
|
||||
d.additional_cost = (flt(d.basic_amount) / incoming_items_cost) * self.total_additional_costs
|
||||
else:
|
||||
d.additional_cost = 0
|
||||
if not incoming_items_cost:
|
||||
return
|
||||
|
||||
for d in self.get("items"):
|
||||
if self.purpose in ("Repack", "Manufacture") and not d.is_finished_item:
|
||||
d.additional_cost = 0
|
||||
continue
|
||||
elif not d.t_warehouse:
|
||||
d.additional_cost = 0
|
||||
continue
|
||||
d.additional_cost = (flt(d.basic_amount) / incoming_items_cost) * self.total_additional_costs
|
||||
|
||||
def update_valuation_rate(self):
|
||||
for d in self.get("items"):
|
||||
@@ -805,7 +810,11 @@ class StockEntry(StockController):
|
||||
def get_gl_entries(self, warehouse_account):
|
||||
gl_entries = super(StockEntry, self).get_gl_entries(warehouse_account)
|
||||
|
||||
total_basic_amount = sum([flt(t.basic_amount) for t in self.get("items") if t.t_warehouse])
|
||||
if self.purpose in ("Repack", "Manufacture"):
|
||||
total_basic_amount = sum(flt(t.basic_amount) for t in self.get("items") if t.is_finished_item)
|
||||
else:
|
||||
total_basic_amount = sum(flt(t.basic_amount) for t in self.get("items") if t.t_warehouse)
|
||||
|
||||
divide_based_on = total_basic_amount
|
||||
|
||||
if self.get("additional_costs") and not total_basic_amount:
|
||||
@@ -816,20 +825,24 @@ class StockEntry(StockController):
|
||||
|
||||
for t in self.get("additional_costs"):
|
||||
for d in self.get("items"):
|
||||
if d.t_warehouse:
|
||||
item_account_wise_additional_cost.setdefault((d.item_code, d.name), {})
|
||||
item_account_wise_additional_cost[(d.item_code, d.name)].setdefault(t.expense_account, {
|
||||
"amount": 0.0,
|
||||
"base_amount": 0.0
|
||||
})
|
||||
if self.purpose in ("Repack", "Manufacture") and not d.is_finished_item:
|
||||
continue
|
||||
elif not d.t_warehouse:
|
||||
continue
|
||||
|
||||
multiply_based_on = d.basic_amount if total_basic_amount else d.qty
|
||||
item_account_wise_additional_cost.setdefault((d.item_code, d.name), {})
|
||||
item_account_wise_additional_cost[(d.item_code, d.name)].setdefault(t.expense_account, {
|
||||
"amount": 0.0,
|
||||
"base_amount": 0.0
|
||||
})
|
||||
|
||||
item_account_wise_additional_cost[(d.item_code, d.name)][t.expense_account]["amount"] += \
|
||||
flt(t.amount * multiply_based_on) / divide_based_on
|
||||
multiply_based_on = d.basic_amount if total_basic_amount else d.qty
|
||||
|
||||
item_account_wise_additional_cost[(d.item_code, d.name)][t.expense_account]["base_amount"] += \
|
||||
flt(t.base_amount * multiply_based_on) / divide_based_on
|
||||
item_account_wise_additional_cost[(d.item_code, d.name)][t.expense_account]["amount"] += \
|
||||
flt(t.amount * multiply_based_on) / divide_based_on
|
||||
|
||||
item_account_wise_additional_cost[(d.item_code, d.name)][t.expense_account]["base_amount"] += \
|
||||
flt(t.base_amount * multiply_based_on) / divide_based_on
|
||||
|
||||
if item_account_wise_additional_cost:
|
||||
for d in self.get("items"):
|
||||
|
||||
@@ -837,6 +837,39 @@ class TestStockEntry(unittest.TestCase):
|
||||
|
||||
frappe.db.set_default("allow_negative_stock", 0)
|
||||
|
||||
def test_additional_cost_distribution_manufacture(self):
|
||||
se = frappe.get_doc(
|
||||
doctype="Stock Entry",
|
||||
purpose="Manufacture",
|
||||
additional_costs=[frappe._dict(base_amount=100)],
|
||||
items=[
|
||||
frappe._dict(item_code="RM", basic_amount=10),
|
||||
frappe._dict(item_code="FG", basic_amount=20, t_warehouse="X", is_finished_item=1),
|
||||
frappe._dict(item_code="scrap", basic_amount=30, t_warehouse="X")
|
||||
],
|
||||
)
|
||||
|
||||
se.distribute_additional_costs()
|
||||
|
||||
distributed_costs = [d.additional_cost for d in se.items]
|
||||
self.assertEqual([0.0, 100.0, 0.0], distributed_costs)
|
||||
|
||||
def test_additional_cost_distribution_non_manufacture(self):
|
||||
se = frappe.get_doc(
|
||||
doctype="Stock Entry",
|
||||
purpose="Material Receipt",
|
||||
additional_costs=[frappe._dict(base_amount=100)],
|
||||
items=[
|
||||
frappe._dict(item_code="RECEIVED_1", basic_amount=20, t_warehouse="X"),
|
||||
frappe._dict(item_code="RECEIVED_2", basic_amount=30, t_warehouse="X")
|
||||
],
|
||||
)
|
||||
|
||||
se.distribute_additional_costs()
|
||||
|
||||
distributed_costs = [d.additional_cost for d in se.items]
|
||||
self.assertEqual([40.0, 60.0], distributed_costs)
|
||||
|
||||
def make_serialized_item(**args):
|
||||
args = frappe._dict(args)
|
||||
se = frappe.copy_doc(test_records[0])
|
||||
|
||||
@@ -11,6 +11,6 @@
|
||||
{% endfor %}
|
||||
</ol>
|
||||
{% else %}
|
||||
<p>You don't have no upcoming holidays this {{ frequency }}.</p>
|
||||
<p>You have no upcoming holidays this {{ frequency }}.</p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="col-xs-5 {%- if doc.align_labels_right %} text-right{%- endif -%}">
|
||||
<label>{{ _(doc.meta.get_label('total')) }}</label></div>
|
||||
<label>{{ _(df.label) }}</label></div>
|
||||
<div class="col-xs-7 text-right">
|
||||
{{ doc.get_formatted("total", doc) }}
|
||||
</div>
|
||||
|
||||
@@ -7,6 +7,8 @@ def create_employee_records():
|
||||
create_company()
|
||||
create_missing_designation()
|
||||
|
||||
frappe.db.sql("DELETE FROM tabEmployee WHERE company='Test Org Chart'")
|
||||
|
||||
emp1 = create_employee('Test Employee 1', 'CEO')
|
||||
emp2 = create_employee('Test Employee 2', 'CTO')
|
||||
emp3 = create_employee('Test Employee 3', 'Head of Marketing and Sales', emp1)
|
||||
|
||||
Reference in New Issue
Block a user