Merge branch 'develop' into product-bundle-fixes
diff --git a/erpnext/__init__.py b/erpnext/__init__.py
index 9d99ebb..a5de50f 100644
--- a/erpnext/__init__.py
+++ b/erpnext/__init__.py
@@ -4,7 +4,7 @@
from erpnext.hooks import regional_overrides
-__version__ = '13.9.0'
+__version__ = '14.0.0-dev'
def get_default_company(user=None):
'''Get default company for user'''
diff --git a/erpnext/accounts/doctype/advance_tax/__init__.py b/erpnext/accounts/doctype/advance_tax/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/accounts/doctype/advance_tax/__init__.py
diff --git a/erpnext/accounts/doctype/advance_tax/advance_tax.json b/erpnext/accounts/doctype/advance_tax/advance_tax.json
new file mode 100644
index 0000000..68706ab
--- /dev/null
+++ b/erpnext/accounts/doctype/advance_tax/advance_tax.json
@@ -0,0 +1,56 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "creation": "2021-11-25 10:24:39.836195",
+ "doctype": "DocType",
+ "engine": "InnoDB",
+ "field_order": [
+ "reference_type",
+ "reference_name",
+ "reference_detail",
+ "account_head",
+ "allocated_amount"
+ ],
+ "fields": [
+ {
+ "fieldname": "reference_type",
+ "fieldtype": "Link",
+ "label": "Reference Type",
+ "options": "DocType"
+ },
+ {
+ "fieldname": "reference_name",
+ "fieldtype": "Dynamic Link",
+ "label": "Reference Name",
+ "options": "reference_type"
+ },
+ {
+ "fieldname": "reference_detail",
+ "fieldtype": "Data",
+ "label": "Reference Detail"
+ },
+ {
+ "fieldname": "account_head",
+ "fieldtype": "Link",
+ "label": "Account Head",
+ "options": "Account"
+ },
+ {
+ "fieldname": "allocated_amount",
+ "fieldtype": "Currency",
+ "label": "Allocated Amount",
+ "options": "party_account_currency"
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-11-25 10:27:51.712286",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Advance Tax",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC"
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/advance_tax/advance_tax.py b/erpnext/accounts/doctype/advance_tax/advance_tax.py
new file mode 100644
index 0000000..2e784ef
--- /dev/null
+++ b/erpnext/accounts/doctype/advance_tax/advance_tax.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+
+class AdvanceTax(Document):
+ pass
diff --git a/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json b/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json
index 4d63499..05b284a 100644
--- a/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json
+++ b/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json
@@ -25,8 +25,7 @@
"allocated_amount",
"column_break_13",
"base_tax_amount",
- "base_total",
- "base_allocated_amount"
+ "base_total"
],
"fields": [
{
@@ -169,12 +168,6 @@
"options": "currency"
},
{
- "fieldname": "base_allocated_amount",
- "fieldtype": "Currency",
- "label": "Allocated Amount (Company Currency)",
- "options": "Company:company:default_currency"
- },
- {
"fetch_from": "account_head.account_currency",
"fieldname": "currency",
"fieldtype": "Link",
@@ -186,7 +179,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2021-06-09 11:46:58.373170",
+ "modified": "2021-11-25 11:10:10.945027",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Advance Taxes and Charges",
diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py
index 5cbf00b..e7371fb 100644
--- a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py
+++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py
@@ -434,7 +434,7 @@
def get_ec_matching_query(bank_account, company, amount_condition):
# get matching Expense Claim query
- mode_of_payments = [x["parent"] for x in frappe.db.get_list("Mode of Payment Account",
+ mode_of_payments = [x["parent"] for x in frappe.db.get_all("Mode of Payment Account",
filters={"default_account": bank_account}, fields=["parent"])]
mode_of_payments = '(\'' + '\', \''.join(mode_of_payments) + '\' )'
company_currency = get_company_currency(company)
diff --git a/erpnext/accounts/doctype/fiscal_year/test_fiscal_year.py b/erpnext/accounts/doctype/fiscal_year/test_fiscal_year.py
index bc8c6ab..69e13a4 100644
--- a/erpnext/accounts/doctype/fiscal_year/test_fiscal_year.py
+++ b/erpnext/accounts/doctype/fiscal_year/test_fiscal_year.py
@@ -5,10 +5,10 @@
import unittest
import frappe
+from frappe.utils import now_datetime
from erpnext.accounts.doctype.fiscal_year.fiscal_year import FiscalYearIncorrectDate
-test_records = frappe.get_test_records('Fiscal Year')
test_ignore = ["Company"]
class TestFiscalYear(unittest.TestCase):
@@ -25,3 +25,29 @@
})
self.assertRaises(FiscalYearIncorrectDate, fy.insert)
+
+
+def test_record_generator():
+ test_records = [
+ {
+ "doctype": "Fiscal Year",
+ "year": "_Test Short Fiscal Year 2011",
+ "is_short_year": 1,
+ "year_end_date": "2011-04-01",
+ "year_start_date": "2011-12-31"
+ }
+ ]
+
+ start = 2012
+ end = now_datetime().year + 5
+ for year in range(start, end):
+ test_records.append({
+ "doctype": "Fiscal Year",
+ "year": f"_Test Fiscal Year {year}",
+ "year_start_date": f"{year}-01-01",
+ "year_end_date": f"{year}-12-31"
+ })
+
+ return test_records
+
+test_records = test_record_generator()
diff --git a/erpnext/accounts/doctype/fiscal_year/test_records.json b/erpnext/accounts/doctype/fiscal_year/test_records.json
deleted file mode 100644
index 4405253..0000000
--- a/erpnext/accounts/doctype/fiscal_year/test_records.json
+++ /dev/null
@@ -1,69 +0,0 @@
-[
- {
- "doctype": "Fiscal Year",
- "year": "_Test Short Fiscal Year 2011",
- "is_short_year": 1,
- "year_end_date": "2011-04-01",
- "year_start_date": "2011-12-31"
- },
- {
- "doctype": "Fiscal Year",
- "year": "_Test Fiscal Year 2012",
- "year_end_date": "2012-12-31",
- "year_start_date": "2012-01-01"
- },
- {
- "doctype": "Fiscal Year",
- "year": "_Test Fiscal Year 2013",
- "year_end_date": "2013-12-31",
- "year_start_date": "2013-01-01"
- },
- {
- "doctype": "Fiscal Year",
- "year": "_Test Fiscal Year 2014",
- "year_end_date": "2014-12-31",
- "year_start_date": "2014-01-01"
- },
- {
- "doctype": "Fiscal Year",
- "year": "_Test Fiscal Year 2015",
- "year_end_date": "2015-12-31",
- "year_start_date": "2015-01-01"
- },
- {
- "doctype": "Fiscal Year",
- "year": "_Test Fiscal Year 2016",
- "year_end_date": "2016-12-31",
- "year_start_date": "2016-01-01"
- },
- {
- "doctype": "Fiscal Year",
- "year": "_Test Fiscal Year 2017",
- "year_end_date": "2017-12-31",
- "year_start_date": "2017-01-01"
- },
- {
- "doctype": "Fiscal Year",
- "year": "_Test Fiscal Year 2018",
- "year_end_date": "2018-12-31",
- "year_start_date": "2018-01-01"
- },
- {
- "doctype": "Fiscal Year",
- "year": "_Test Fiscal Year 2019",
- "year_end_date": "2019-12-31",
- "year_start_date": "2019-01-01"
- },
- {
- "doctype": "Fiscal Year",
- "year": "_Test Fiscal Year 2020",
- "year_end_date": "2020-12-31",
- "year_start_date": "2020-01-01"
- },
- {
- "doctype": "Fiscal Year",
- "year": "_Test Fiscal Year 2021",
- "year_end_date": "2021-12-31",
- "year_start_date": "2021-01-01"
- }
-]
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json
index ee2e319..c8d1db9 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.json
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json
@@ -61,7 +61,6 @@
"taxes_and_charges_section",
"purchase_taxes_and_charges_template",
"sales_taxes_and_charges_template",
- "advance_tax_account",
"column_break_55",
"apply_tax_withholding_amount",
"tax_withholding_category",
@@ -686,15 +685,6 @@
"hide_border": 1
},
{
- "depends_on": "eval:doc.apply_tax_withholding_amount",
- "description": "Provisional tax account for advance tax. Taxes are parked in this account until payments are allocated to invoices",
- "fieldname": "advance_tax_account",
- "fieldtype": "Link",
- "label": "Advance Tax Account",
- "mandatory_depends_on": "eval:doc.apply_tax_withholding_amount",
- "options": "Account"
- },
- {
"depends_on": "eval:doc.received_amount && doc.payment_type != 'Internal Transfer'",
"fieldname": "received_amount_after_tax",
"fieldtype": "Currency",
@@ -730,7 +720,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2021-10-22 17:50:24.632806",
+ "modified": "2021-11-24 18:58:24.919764",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry",
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 26fd16a..c1b056b 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -20,7 +20,7 @@
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import (
get_party_tax_withholding_details,
)
-from erpnext.accounts.general_ledger import make_gl_entries
+from erpnext.accounts.general_ledger import make_gl_entries, process_gl_map
from erpnext.accounts.party import get_party_account
from erpnext.accounts.utils import get_account_currency, get_balance_on, get_outstanding_invoices
from erpnext.controllers.accounts_controller import (
@@ -339,7 +339,7 @@
for k, v in no_oustanding_refs.items():
frappe.msgprint(
_("{} - {} now have {} as they had no outstanding amount left before submitting the Payment Entry.")
- .format(k, frappe.bold(", ".join(d.reference_name for d in v)), frappe.bold("negative outstanding amount"))
+ .format(_(k), frappe.bold(", ".join(d.reference_name for d in v)), frappe.bold(_("negative outstanding amount")))
+ "<br><br>" + _("If this is undesirable please cancel the corresponding Payment Entry."),
title=_("Warning"), indicator="orange")
@@ -433,23 +433,12 @@
if not self.apply_tax_withholding_amount:
return
- if not self.advance_tax_account:
- frappe.throw(_("Advance TDS account is mandatory for advance TDS deduction"))
-
net_total = self.paid_amount
- for reference in self.get("references"):
- net_total_for_tds = 0
- if reference.reference_doctype == 'Purchase Order':
- net_total_for_tds += flt(frappe.db.get_value('Purchase Order', reference.reference_name, 'net_total'))
-
- if net_total_for_tds:
- net_total = net_total_for_tds
-
# Adding args as purchase invoice to get TDS amount
args = frappe._dict({
'company': self.company,
- 'doctype': 'Purchase Invoice',
+ 'doctype': 'Payment Entry',
'supplier': self.party,
'posting_date': self.posting_date,
'net_total': net_total
@@ -461,7 +450,6 @@
return
tax_withholding_details.update({
- 'add_deduct_tax': 'Add',
'cost_center': self.cost_center or erpnext.get_default_cost_center(self.company)
})
@@ -623,7 +611,7 @@
if not total_negative_outstanding:
frappe.throw(_("Cannot {0} {1} {2} without any negative outstanding invoice")
- .format(self.payment_type, ("to" if self.party_type=="Customer" else "from"),
+ .format(_(self.payment_type), (_("to") if self.party_type=="Customer" else _("from")),
self.party_type), InvalidPaymentEntry)
elif paid_amount - additional_charges > total_negative_outstanding:
@@ -689,6 +677,7 @@
self.add_deductions_gl_entries(gl_entries)
self.add_tax_gl_entries(gl_entries)
+ gl_entries = process_gl_map(gl_entries)
make_gl_entries(gl_entries, cancel=cancel, adv_adj=adv_adj)
def add_party_gl_entries(self, gl_entries):
@@ -752,7 +741,8 @@
"against": self.party if self.payment_type=="Pay" else self.paid_to,
"credit_in_account_currency": self.paid_amount,
"credit": self.base_paid_amount,
- "cost_center": self.cost_center
+ "cost_center": self.cost_center,
+ "post_net_value": True
}, item=self)
)
if self.payment_type in ("Receive", "Internal Transfer"):
@@ -782,14 +772,10 @@
rev_dr_or_cr = "credit" if dr_or_cr == "debit" else "debit"
against = self.party or self.paid_to
- payment_or_advance_account = self.get_party_account_for_taxes()
+ payment_account = self.get_party_account_for_taxes()
tax_amount = d.tax_amount
base_tax_amount = d.base_tax_amount
- if self.advance_tax_account:
- tax_amount = -1 * tax_amount
- base_tax_amount = -1 * base_tax_amount
-
gl_entries.append(
self.get_gl_dict({
"account": d.account_head,
@@ -798,19 +784,21 @@
dr_or_cr + "_in_account_currency": base_tax_amount
if account_currency==self.company_currency
else d.tax_amount,
- "cost_center": d.cost_center
+ "cost_center": d.cost_center,
+ "post_net_value": True,
}, account_currency, item=d))
- if not d.included_in_paid_amount or self.advance_tax_account:
+ if not d.included_in_paid_amount:
gl_entries.append(
self.get_gl_dict({
- "account": payment_or_advance_account,
+ "account": payment_account,
"against": against,
rev_dr_or_cr: tax_amount,
rev_dr_or_cr + "_in_account_currency": base_tax_amount
if account_currency==self.company_currency
else d.tax_amount,
"cost_center": self.cost_center,
+ "post_net_value": True,
}, account_currency, item=d))
def add_deductions_gl_entries(self, gl_entries):
@@ -832,9 +820,7 @@
)
def get_party_account_for_taxes(self):
- if self.advance_tax_account:
- return self.advance_tax_account
- elif self.payment_type == 'Receive':
+ if self.payment_type == 'Receive':
return self.paid_to
elif self.payment_type in ('Pay', 'Internal Transfer'):
return self.paid_from
@@ -1106,7 +1092,7 @@
if not data:
frappe.msgprint(_("No outstanding invoices found for the {0} {1} which qualify the filters you have specified.")
- .format(args.get("party_type").lower(), frappe.bold(args.get("party"))))
+ .format(_(args.get("party_type")).lower(), frappe.bold(args.get("party"))))
return data
@@ -1599,13 +1585,6 @@
})
pe.set_difference_amount()
- if doc.doctype == 'Purchase Order' and doc.apply_tds:
- pe.apply_tax_withholding_amount = 1
- pe.tax_withholding_category = doc.tax_withholding_category
-
- if not pe.advance_tax_account:
- pe.advance_tax_account = frappe.db.get_value('Company', pe.company, 'unrealized_profit_loss_account')
-
return pe
def get_bank_cash_account(doc, bank_account):
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json
index bff8587..0c6e7ed 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json
@@ -171,6 +171,7 @@
"sales_team_section_break",
"sales_partner",
"column_break10",
+ "amount_eligible_for_commission",
"commission_rate",
"total_commission",
"section_break2",
@@ -1561,16 +1562,23 @@
"label": "Coupon Code",
"options": "Coupon Code",
"print_hide": 1
+ },
+ {
+ "fieldname": "amount_eligible_for_commission",
+ "fieldtype": "Currency",
+ "label": "Amount Eligible for Commission",
+ "read_only": 1
}
],
"icon": "fa fa-file-text",
"is_submittable": 1,
"links": [],
- "modified": "2021-08-27 20:12:57.306772",
+ "modified": "2021-10-05 12:11:53.871828",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Invoice",
"name_case": "Title Case",
+ "naming_rule": "By \"Naming Series\" field",
"owner": "Administrator",
"permissions": [
{
diff --git a/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json b/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json
index 8b71eb0..3f85668 100644
--- a/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json
+++ b/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json
@@ -46,6 +46,7 @@
"base_amount",
"pricing_rules",
"is_free_item",
+ "grant_commission",
"section_break_21",
"net_rate",
"net_amount",
@@ -800,14 +801,22 @@
"no_copy": 1,
"print_hide": 1,
"read_only": 1
+ },
+ {
+ "default": "0",
+ "fieldname": "grant_commission",
+ "fieldtype": "Check",
+ "label": "Grant Commission",
+ "read_only": 1
}
],
"istable": 1,
"links": [],
- "modified": "2021-01-04 17:34:49.924531",
+ "modified": "2021-10-05 12:23:47.506290",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Invoice Item",
+ "naming_rule": "Random",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.js b/erpnext/accounts/doctype/pos_profile/pos_profile.js
index efdeb1a..813d20d 100755
--- a/erpnext/accounts/doctype/pos_profile/pos_profile.js
+++ b/erpnext/accounts/doctype/pos_profile/pos_profile.js
@@ -3,22 +3,20 @@
{% include "erpnext/public/js/controllers/accounts.js" %}
-frappe.ui.form.on("POS Profile", "onload", function(frm) {
- frm.set_query("selling_price_list", function() {
- return { filters: { selling: 1 } };
- });
-
- frm.set_query("tc_name", function() {
- return { filters: { selling: 1 } };
- });
-
- erpnext.queries.setup_queries(frm, "Warehouse", function() {
- return erpnext.queries.warehouse(frm.doc);
- });
-});
-
frappe.ui.form.on('POS Profile', {
setup: function(frm) {
+ frm.set_query("selling_price_list", function() {
+ return { filters: { selling: 1 } };
+ });
+
+ frm.set_query("tc_name", function() {
+ return { filters: { selling: 1 } };
+ });
+
+ erpnext.queries.setup_queries(frm, "Warehouse", function() {
+ return erpnext.queries.warehouse(frm.doc);
+ });
+
frm.set_query("print_format", function() {
return {
filters: [
@@ -27,10 +25,16 @@
};
});
- frm.set_query("account_for_change_amount", function() {
+ frm.set_query("account_for_change_amount", function(doc) {
+ if (!doc.company) {
+ frappe.throw(__('Please set Company'));
+ }
+
return {
filters: {
- account_type: ['in', ["Cash", "Bank"]]
+ account_type: ['in', ["Cash", "Bank"]],
+ is_group: 0,
+ company: doc.company
}
};
});
@@ -45,7 +49,7 @@
});
frm.set_query('company_address', function(doc) {
- if(!doc.company) {
+ if (!doc.company) {
frappe.throw(__('Please set Company'));
}
@@ -58,11 +62,79 @@
};
});
+ frm.set_query('income_account', function(doc) {
+ if (!doc.company) {
+ frappe.throw(__('Please set Company'));
+ }
+
+ return {
+ filters: {
+ 'is_group': 0,
+ 'company': doc.company,
+ 'account_type': "Income Account"
+ }
+ };
+ });
+
+ frm.set_query('cost_center', function(doc) {
+ if (!doc.company) {
+ frappe.throw(__('Please set Company'));
+ }
+
+ return {
+ filters: {
+ 'company': doc.company,
+ 'is_group': 0
+ }
+ };
+ });
+
+ frm.set_query('expense_account', function(doc) {
+ if (!doc.company) {
+ frappe.throw(__('Please set Company'));
+ }
+
+ return {
+ filters: {
+ "report_type": "Profit and Loss",
+ "company": doc.company,
+ "is_group": 0
+ }
+ };
+ });
+
+ frm.set_query("select_print_heading", function() {
+ return {
+ filters: [
+ ['Print Heading', 'docstatus', '!=', 2]
+ ]
+ };
+ });
+
+ frm.set_query("write_off_account", function(doc) {
+ return {
+ filters: {
+ 'report_type': 'Profit and Loss',
+ 'is_group': 0,
+ 'company': doc.company
+ }
+ };
+ });
+
+ frm.set_query("write_off_cost_center", function(doc) {
+ return {
+ filters: {
+ 'is_group': 0,
+ 'company': doc.company
+ }
+ };
+ });
+
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
},
refresh: function(frm) {
- if(frm.doc.company) {
+ if (frm.doc.company) {
frm.trigger("toggle_display_account_head");
}
},
@@ -76,71 +148,4 @@
frm.toggle_display('expense_account',
erpnext.is_perpetual_inventory_enabled(frm.doc.company));
}
-})
-
-// Income Account
-// --------------------------------
-cur_frm.fields_dict['income_account'].get_query = function(doc,cdt,cdn) {
- return{
- filters:{
- 'is_group': 0,
- 'company': doc.company,
- 'account_type': "Income Account"
- }
- };
-};
-
-
-// Cost Center
-// -----------------------------
-cur_frm.fields_dict['cost_center'].get_query = function(doc,cdt,cdn) {
- return{
- filters:{
- 'company': doc.company,
- 'is_group': 0
- }
- };
-};
-
-
-// Expense Account
-// -----------------------------
-cur_frm.fields_dict["expense_account"].get_query = function(doc) {
- return {
- filters: {
- "report_type": "Profit and Loss",
- "company": doc.company,
- "is_group": 0
- }
- };
-};
-
-// ------------------ Get Print Heading ------------------------------------
-cur_frm.fields_dict['select_print_heading'].get_query = function(doc, cdt, cdn) {
- return{
- filters:[
- ['Print Heading', 'docstatus', '!=', 2]
- ]
- };
-};
-
-cur_frm.fields_dict.write_off_account.get_query = function(doc) {
- return{
- filters:{
- 'report_type': 'Profit and Loss',
- 'is_group': 0,
- 'company': doc.company
- }
- };
-};
-
-// Write off cost center
-// -----------------------
-cur_frm.fields_dict.write_off_cost_center.get_query = function(doc) {
- return{
- filters:{
- 'is_group': 0,
- 'company': doc.company
- }
- };
-};
+});
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
index 03cbc4a..bd01164 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
@@ -130,6 +130,7 @@
"allocate_advances_automatically",
"get_advances",
"advances",
+ "advance_tax",
"payment_schedule_section",
"payment_terms_template",
"ignore_default_payment_terms_template",
@@ -1408,13 +1409,21 @@
{
"fieldname": "column_break_147",
"fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "advance_tax",
+ "fieldtype": "Table",
+ "hidden": 1,
+ "label": "Advance Tax",
+ "options": "Advance Tax",
+ "read_only": 1
}
],
"icon": "fa fa-file-text",
"idx": 204,
"is_submittable": 1,
"links": [],
- "modified": "2021-10-12 20:55:16.145651",
+ "modified": "2021-11-25 13:31:02.716727",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index 62e3dc8..48b5cb9 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -427,6 +427,7 @@
self.update_project()
update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference)
+ self.update_advance_tax_references()
self.process_common_party_accounting()
@@ -472,8 +473,6 @@
self.make_exchange_gain_loss_gl_entries(gl_entries)
self.make_internal_transfer_gl_entries(gl_entries)
- self.allocate_advance_taxes(gl_entries)
-
gl_entries = make_regional_gl_entries(gl_entries, self)
gl_entries = merge_similar_entries(gl_entries)
@@ -729,7 +728,7 @@
"account": self.stock_received_but_not_billed,
"against": self.supplier,
"debit": flt(item.item_tax_amount, item.precision("item_tax_amount")),
- "remarks": self.remarks or "Accounting Entry for Stock",
+ "remarks": self.remarks or _("Accounting Entry for Stock"),
"cost_center": self.cost_center,
"project": item.project or self.project
}, item=item)
@@ -937,7 +936,7 @@
"cost_center": tax.cost_center,
"against": self.supplier,
"credit": valuation_tax[tax.name],
- "remarks": self.remarks or "Accounting Entry for Stock"
+ "remarks": self.remarks or _("Accounting Entry for Stock")
}, item=tax))
@property
@@ -1074,6 +1073,7 @@
unlink_inter_company_doc(self.doctype, self.name, self.inter_company_invoice_reference)
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry', 'Repost Item Valuation')
+ self.update_advance_tax_references(cancel=1)
def update_project(self):
project_list = []
@@ -1150,7 +1150,10 @@
if not self.tax_withholding_category:
return
- tax_withholding_details = get_party_tax_withholding_details(self, self.tax_withholding_category)
+ tax_withholding_details, advance_taxes = get_party_tax_withholding_details(self, self.tax_withholding_category)
+
+ # Adjust TDS paid on advances
+ self.allocate_advance_tds(tax_withholding_details, advance_taxes)
if not tax_withholding_details:
return
@@ -1174,6 +1177,39 @@
# calculate totals again after applying TDS
self.calculate_taxes_and_totals()
+ def allocate_advance_tds(self, tax_withholding_details, advance_taxes):
+ self.set('advance_tax', [])
+ for tax in advance_taxes:
+ allocated_amount = 0
+ pending_amount = flt(tax.tax_amount - tax.allocated_amount)
+ if flt(tax_withholding_details.get('tax_amount')) >= pending_amount:
+ tax_withholding_details['tax_amount'] -= pending_amount
+ allocated_amount = pending_amount
+ elif flt(tax_withholding_details.get('tax_amount')) and flt(tax_withholding_details.get('tax_amount')) < pending_amount:
+ allocated_amount = tax_withholding_details['tax_amount']
+ tax_withholding_details['tax_amount'] = 0
+
+ self.append('advance_tax', {
+ 'reference_type': 'Payment Entry',
+ 'reference_name': tax.parent,
+ 'reference_detail': tax.name,
+ 'account_head': tax.account_head,
+ 'allocated_amount': allocated_amount
+ })
+
+ def update_advance_tax_references(self, cancel=0):
+ for tax in self.get('advance_tax'):
+ at = frappe.qb.DocType("Advance Taxes and Charges").as_("at")
+
+ if cancel:
+ frappe.qb.update(at).set(
+ at.allocated_amount, at.allocated_amount - tax.allocated_amount
+ ).where(at.name == tax.reference_detail).run()
+ else:
+ frappe.qb.update(at).set(
+ at.allocated_amount, at.allocated_amount + tax.allocated_amount
+ ).where(at.name == tax.reference_detail).run()
+
def set_status(self, update=False, status=None, update_modified=True):
if self.is_new():
if self.get('amended_from'):
diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
index 78396a5..aa2408e 100644
--- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
@@ -1160,25 +1160,21 @@
# Create Purchase Order with TDS applied
po = create_purchase_order(do_not_save=1, supplier=supplier.name, rate=3000, item='_Test Non Stock Item',
posting_date='2021-09-15')
- po.apply_tds = 1
- po.tax_withholding_category = 'TDS - 194 - Dividends - Individual'
po.save()
po.submit()
- # Update Unrealized Profit / Loss Account which is used as default advance tax account
- frappe.db.set_value('Company', '_Test Company', 'unrealized_profit_loss_account', '_Test Account Excise Duty - _TC')
-
# Create Payment Entry Against the order
payment_entry = get_payment_entry(dt='Purchase Order', dn=po.name)
payment_entry.paid_from = 'Cash - _TC'
+ payment_entry.apply_tax_withholding_amount = 1
+ payment_entry.tax_withholding_category = 'TDS - 194 - Dividends - Individual'
payment_entry.save()
payment_entry.submit()
# Check GLE for Payment Entry
expected_gle = [
- ['_Test Account Excise Duty - _TC', 3000, 0],
['Cash - _TC', 0, 27000],
- ['Creditors - _TC', 27000, 0],
+ ['Creditors - _TC', 30000, 0],
['TDS Payable - _TC', 0, 3000],
]
@@ -1204,9 +1200,7 @@
# Zero net effect on final TDS Payable on invoice
expected_gle = [
['_Test Account Cost for Goods Sold - _TC', 30000],
- ['_Test Account Excise Duty - _TC', -3000],
- ['Creditors - _TC', -27000],
- ['TDS Payable - _TC', 0]
+ ['Creditors - _TC', -30000]
]
gl_entries = frappe.db.sql("""select account, sum(debit - credit) as amount
@@ -1219,6 +1213,14 @@
self.assertEqual(expected_gle[i][0], gle.account)
self.assertEqual(expected_gle[i][1], gle.amount)
+ payment_entry.load_from_db()
+ self.assertEqual(payment_entry.taxes[0].allocated_amount, 3000)
+
+ purchase_invoice.cancel()
+
+ payment_entry.load_from_db()
+ self.assertEqual(payment_entry.taxes[0].allocated_amount, 0)
+
def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
gl_entries = frappe.db.sql("""select account, debit, credit, posting_date
from `tabGL Entry`
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index a1f3ee4..39dfd8d 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -516,15 +516,6 @@
}
}
-// project name
-//--------------------------
-cur_frm.fields_dict['project'].get_query = function(doc, cdt, cdn) {
- return{
- query: "erpnext.controllers.queries.get_project_name",
- filters: {'customer': doc.customer}
- }
-}
-
// Income Account in Details Table
// --------------------------------
cur_frm.set_query("income_account", "items", function(doc) {
@@ -978,7 +969,7 @@
}
if (frm.doc.is_debit_note) {
- frm.set_df_property('return_against', 'label', 'Adjustment Against');
+ frm.set_df_property('return_against', 'label', __('Adjustment Against'));
}
if (frappe.boot.active_domains.includes("Healthcare")) {
@@ -988,10 +979,10 @@
if (cint(frm.doc.docstatus==0) && cur_frm.page.current_view_name!=="pos" && !frm.doc.is_return) {
frm.add_custom_button(__('Healthcare Services'), function() {
get_healthcare_services_to_invoice(frm);
- },"Get Items From");
+ },__("Get Items From"));
frm.add_custom_button(__('Prescriptions'), function() {
get_drugs_to_invoice(frm);
- },"Get Items From");
+ },__("Get Items From"));
}
}
else {
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
index 93e32f1..545abf7 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
@@ -182,6 +182,7 @@
"sales_team_section_break",
"sales_partner",
"column_break10",
+ "amount_eligible_for_commission",
"commission_rate",
"total_commission",
"section_break2",
@@ -2019,6 +2020,12 @@
"label": "Total Billing Hours",
"print_hide": 1,
"read_only": 1
+ },
+ {
+ "fieldname": "amount_eligible_for_commission",
+ "fieldtype": "Currency",
+ "label": "Amount Eligible for Commission",
+ "read_only": 1
}
],
"icon": "fa fa-file-text",
@@ -2031,7 +2038,7 @@
"link_fieldname": "consolidated_invoice"
}
],
- "modified": "2021-10-11 20:19:38.667508",
+ "modified": "2021-10-21 20:19:38.667508",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",
@@ -2086,4 +2093,4 @@
"title_field": "title",
"track_changes": 1,
"track_seen": 1
-}
\ No newline at end of file
+}
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 59d46fc..c4d59f1 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -842,8 +842,6 @@
self.make_exchange_gain_loss_gl_entries(gl_entries)
self.make_internal_transfer_gl_entries(gl_entries)
- self.allocate_advance_taxes(gl_entries)
-
self.make_item_gl_entries(gl_entries)
self.make_discount_gl_entries(gl_entries)
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index b5453ac..6a488ea 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -2385,6 +2385,29 @@
si.reload()
self.assertEqual(si.status, "Paid")
+ def test_sales_commission(self):
+ si = frappe.copy_doc(test_records[0])
+ item = copy.deepcopy(si.get('items')[0])
+ item.update({
+ "qty": 1,
+ "rate": 500,
+ "grant_commission": 1
+ })
+ si.append("items", item)
+
+ # Test valid values
+ for commission_rate, total_commission in ((0, 0), (10, 50), (100, 500)):
+ si.commission_rate = commission_rate
+ si.save()
+ self.assertEqual(si.amount_eligible_for_commission, 500)
+ self.assertEqual(si.total_commission, total_commission)
+
+ # Test invalid values
+ for commission_rate in (101, -1):
+ si.reload()
+ si.commission_rate = commission_rate
+ self.assertRaises(frappe.ValidationError, si.save)
+
def test_sales_invoice_submission_post_account_freezing_date(self):
frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', add_days(getdate(), 1))
si = create_sales_invoice(do_not_save=True)
@@ -2397,6 +2420,32 @@
frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None)
+ def test_over_billing_case_against_delivery_note(self):
+ '''
+ Test a case where duplicating the item with qty = 1 in the invoice
+ allows overbilling even if it is disabled
+ '''
+ from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
+
+ over_billing_allowance = frappe.db.get_single_value('Accounts Settings', 'over_billing_allowance')
+ frappe.db.set_value('Accounts Settings', None, 'over_billing_allowance', 0)
+
+ dn = create_delivery_note()
+ dn.submit()
+
+ si = make_sales_invoice(dn.name)
+ # make a copy of first item and add it to invoice
+ item_copy = frappe.copy_doc(si.items[0])
+ si.append('items', item_copy)
+ si.save()
+
+ with self.assertRaises(frappe.ValidationError) as err:
+ si.submit()
+
+ self.assertTrue("cannot overbill" in str(err.exception).lower())
+
+ frappe.db.set_value('Accounts Settings', None, 'over_billing_allowance', over_billing_allowance)
+
def get_sales_invoice_for_e_invoice():
si = make_sales_invoice_for_ewaybill()
si.naming_series = 'INV-2020-.#####'
diff --git a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
index b90f3f0..ae9ac35 100644
--- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
+++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
@@ -47,6 +47,7 @@
"pricing_rules",
"stock_uom_rate",
"is_free_item",
+ "grant_commission",
"section_break_21",
"net_rate",
"net_amount",
@@ -828,15 +829,23 @@
"fieldtype": "Link",
"label": "Discount Account",
"options": "Account"
+ },
+ {
+ "default": "0",
+ "fieldname": "grant_commission",
+ "fieldtype": "Check",
+ "label": "Grant Commission",
+ "read_only": 1
}
],
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2021-08-19 13:41:53.435827",
+ "modified": "2021-10-05 12:24:54.968907",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Item",
+ "naming_rule": "Random",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
index dc1818a..5bb9b93 100644
--- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
+++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
@@ -95,7 +95,7 @@
frappe.throw(_('Tax Withholding Category {} against Company {} for Customer {} should have Cumulative Threshold value.')
.format(tax_withholding_category, inv.company, party))
- tax_amount, tax_deducted = get_tax_amount(
+ tax_amount, tax_deducted, tax_deducted_on_advances = get_tax_amount(
party_type, parties,
inv, tax_details,
posting_date, pan_no
@@ -106,7 +106,10 @@
else:
tax_row = get_tax_row_for_tcs(inv, tax_details, tax_amount, tax_deducted)
- return tax_row
+ if inv.doctype == 'Purchase Invoice':
+ return tax_row, tax_deducted_on_advances
+ else:
+ return tax_row
def get_tax_withholding_details(tax_withholding_category, posting_date, company):
tax_withholding = frappe.get_doc("Tax Withholding Category", tax_withholding_category)
@@ -194,6 +197,10 @@
advance_vouchers = get_advance_vouchers(parties, company=inv.company, from_date=tax_details.from_date,
to_date=tax_details.to_date, party_type=party_type)
taxable_vouchers = vouchers + advance_vouchers
+ tax_deducted_on_advances = 0
+
+ if inv.doctype == 'Purchase Invoice':
+ tax_deducted_on_advances = get_taxes_deducted_on_advances_allocated(inv, tax_details)
tax_deducted = 0
if taxable_vouchers:
@@ -223,7 +230,7 @@
if cint(tax_details.round_off_tax_amount):
tax_amount = round(tax_amount)
- return tax_amount, tax_deducted
+ return tax_amount, tax_deducted, tax_deducted_on_advances
def get_invoice_vouchers(parties, tax_details, company, party_type='Supplier'):
dr_or_cr = 'credit' if party_type == 'Supplier' else 'debit'
@@ -281,6 +288,29 @@
return frappe.get_all('GL Entry', filters=filters, distinct=1, pluck='voucher_no') or [""]
+def get_taxes_deducted_on_advances_allocated(inv, tax_details):
+ advances = [d.reference_name for d in inv.get('advances')]
+ tax_info = []
+
+ if advances:
+ pe = frappe.qb.DocType("Payment Entry").as_("pe")
+ at = frappe.qb.DocType("Advance Taxes and Charges").as_("at")
+
+ tax_info = frappe.qb.from_(at).inner_join(pe).on(
+ pe.name == at.parent
+ ).select(
+ at.parent, at.name, at.tax_amount, at.allocated_amount
+ ).where(
+ pe.tax_withholding_category == tax_details.get('tax_withholding_category')
+ ).where(
+ at.parent.isin(advances)
+ ).where(
+ at.account_head == tax_details.account_head
+ ).run(as_dict=True)
+
+ return tax_info
+
+
def get_deducted_tax(taxable_vouchers, tax_details):
# check if TDS / TCS account is already charged on taxable vouchers
filters = {
diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py
index 8ef7d7e..1836db6 100644
--- a/erpnext/accounts/general_ledger.py
+++ b/erpnext/accounts/general_ledger.py
@@ -73,8 +73,28 @@
flt(entry.debit_in_account_currency) - flt(entry.credit_in_account_currency)
entry.credit_in_account_currency = 0.0
+ update_net_values(entry)
+
return gl_map
+def update_net_values(entry):
+ # In some scenarios net value needs to be shown in the ledger
+ # This method updates net values as debit or credit
+ if entry.post_net_value and entry.debit and entry.credit:
+ if entry.debit > entry.credit:
+ entry.debit = entry.debit - entry.credit
+ entry.debit_in_account_currency = entry.debit_in_account_currency \
+ - entry.credit_in_account_currency
+ entry.credit = 0
+ entry.credit_in_account_currency = 0
+ else:
+ entry.credit = entry.credit - entry.debit
+ entry.credit_in_account_currency = entry.credit_in_account_currency \
+ - entry.debit_in_account_currency
+
+ entry.debit = 0
+ entry.debit_in_account_currency = 0
+
def merge_similar_entries(gl_map, precision=None):
merged_gl_map = []
accounting_dimensions = get_accounting_dimensions()
diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js
index e24a5f9..d3e836a 100644
--- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js
+++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js
@@ -92,6 +92,11 @@
"label": __("Include Default Book Entries"),
"fieldtype": "Check",
"default": 1
+ },
+ {
+ "fieldname": "show_zero_values",
+ "label": __("Show zero values"),
+ "fieldtype": "Check"
}
],
"formatter": function(value, row, column, data, default_formatter) {
diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py
index c71bc17..01799d5 100644
--- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py
+++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py
@@ -22,7 +22,11 @@
get_cash_flow_accounts,
)
from erpnext.accounts.report.cash_flow.cash_flow import get_report_summary as get_cash_flow_summary
-from erpnext.accounts.report.financial_statements import get_fiscal_year_data, sort_accounts
+from erpnext.accounts.report.financial_statements import (
+ filter_out_zero_value_rows,
+ get_fiscal_year_data,
+ sort_accounts,
+)
from erpnext.accounts.report.profit_and_loss_statement.profit_and_loss_statement import (
get_chart_data as get_pl_chart_data,
)
@@ -265,7 +269,7 @@
return columns
def get_data(companies, root_type, balance_must_be, fiscal_year, filters=None, ignore_closing_entries=False):
- accounts, accounts_by_name = get_account_heads(root_type,
+ accounts, accounts_by_name, parent_children_map = get_account_heads(root_type,
companies, filters)
if not accounts: return []
@@ -294,6 +298,8 @@
out = prepare_data(accounts, start_date, end_date, balance_must_be, companies, company_currency, filters)
+ out = filter_out_zero_value_rows(out, parent_children_map, show_zero_values=filters.get("show_zero_values"))
+
if out:
add_total_row(out, root_type, balance_must_be, companies, company_currency)
@@ -370,7 +376,7 @@
accounts, accounts_by_name, parent_children_map = filter_accounts(accounts)
- return accounts, accounts_by_name
+ return accounts, accounts_by_name, parent_children_map
def update_parent_account_names(accounts):
"""Update parent_account_name in accounts list.
diff --git a/erpnext/accounts/report/sales_partners_commission/sales_partners_commission.json b/erpnext/accounts/report/sales_partners_commission/sales_partners_commission.json
index a740de3..9dd4e43 100644
--- a/erpnext/accounts/report/sales_partners_commission/sales_partners_commission.json
+++ b/erpnext/accounts/report/sales_partners_commission/sales_partners_commission.json
@@ -1,27 +1,30 @@
{
- "add_total_row": 0,
- "apply_user_permissions": 1,
- "creation": "2013-05-06 12:28:23",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Report",
- "idx": 3,
- "is_standard": "Yes",
- "modified": "2017-03-06 05:52:57.645281",
- "modified_by": "Administrator",
- "module": "Accounts",
- "name": "Sales Partners Commission",
- "owner": "Administrator",
- "query": "SELECT\n sales_partner as \"Sales Partner:Link/Sales Partner:150\",\n\tsum(base_net_total) as \"Invoiced Amount (Exclusive Tax):Currency:210\",\n\tsum(total_commission) as \"Total Commission:Currency:150\",\n\tsum(total_commission)*100/sum(base_net_total) as \"Average Commission Rate:Currency:170\"\nFROM\n\t`tabSales Invoice`\nWHERE\n\tdocstatus = 1 and ifnull(base_net_total, 0) > 0 and ifnull(total_commission, 0) > 0\nGROUP BY\n\tsales_partner\nORDER BY\n\t\"Total Commission:Currency:120\"",
- "ref_doctype": "Sales Invoice",
- "report_name": "Sales Partners Commission",
- "report_type": "Query Report",
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2013-05-06 12:28:23",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 3,
+ "is_standard": "Yes",
+ "modified": "2021-10-06 06:26:07.881340",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Sales Partners Commission",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "query": "SELECT\n sales_partner as \"Sales Partner:Link/Sales Partner:220\",\n\tsum(base_net_total) as \"Invoiced Amount (Excl. Tax):Currency:220\",\n\tsum(amount_eligible_for_commission) as \"Amount Eligible for Commission:Currency:220\",\n\tsum(total_commission) as \"Total Commission:Currency:170\",\n\tsum(total_commission)*100/sum(amount_eligible_for_commission) as \"Average Commission Rate:Percent:220\"\nFROM\n\t`tabSales Invoice`\nWHERE\n\tdocstatus = 1 and ifnull(base_net_total, 0) > 0 and ifnull(total_commission, 0) > 0\nGROUP BY\n\tsales_partner\nORDER BY\n\t\"Total Commission:Currency:120\"",
+ "ref_doctype": "Sales Invoice",
+ "report_name": "Sales Partners Commission",
+ "report_type": "Query Report",
"roles": [
{
"role": "Accounts Manager"
- },
+ },
{
"role": "Accounts User"
}
]
-}
+}
\ No newline at end of file
diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.js b/erpnext/assets/doctype/asset_repair/asset_repair.js
index 18a56d3..d554d52 100644
--- a/erpnext/assets/doctype/asset_repair/asset_repair.js
+++ b/erpnext/assets/doctype/asset_repair/asset_repair.js
@@ -60,6 +60,10 @@
if (frm.doc.repair_status == "Completed") {
frm.set_value('completion_date', frappe.datetime.now_datetime());
}
+ },
+
+ stock_items_on_form_rendered() {
+ erpnext.setup_serial_or_batch_no();
}
});
diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.py b/erpnext/assets/doctype/asset_repair/asset_repair.py
index d780c18..36848e9 100644
--- a/erpnext/assets/doctype/asset_repair/asset_repair.py
+++ b/erpnext/assets/doctype/asset_repair/asset_repair.py
@@ -118,9 +118,10 @@
for stock_item in self.get('stock_items'):
stock_entry.append('items', {
"s_warehouse": self.warehouse,
- "item_code": stock_item.item,
+ "item_code": stock_item.item_code,
"qty": stock_item.consumed_quantity,
- "basic_rate": stock_item.valuation_rate
+ "basic_rate": stock_item.valuation_rate,
+ "serial_no": stock_item.serial_no
})
stock_entry.insert()
diff --git a/erpnext/assets/doctype/asset_repair/test_asset_repair.py b/erpnext/assets/doctype/asset_repair/test_asset_repair.py
index 81b4f6c..7c0d057 100644
--- a/erpnext/assets/doctype/asset_repair/test_asset_repair.py
+++ b/erpnext/assets/doctype/asset_repair/test_asset_repair.py
@@ -11,12 +11,15 @@
create_asset_data,
set_depreciation_settings_in_company,
)
+from erpnext.stock.doctype.item.test_item import create_item
class TestAssetRepair(unittest.TestCase):
- def setUp(self):
+ @classmethod
+ def setUpClass(cls):
set_depreciation_settings_in_company()
create_asset_data()
+ create_item("_Test Stock Item")
frappe.db.sql("delete from `tabTax Rule`")
def test_update_status(self):
@@ -70,9 +73,28 @@
self.assertEqual(stock_entry.stock_entry_type, "Material Issue")
self.assertEqual(stock_entry.items[0].s_warehouse, asset_repair.warehouse)
- self.assertEqual(stock_entry.items[0].item_code, asset_repair.stock_items[0].item)
+ self.assertEqual(stock_entry.items[0].item_code, asset_repair.stock_items[0].item_code)
self.assertEqual(stock_entry.items[0].qty, asset_repair.stock_items[0].consumed_quantity)
+ def test_serialized_item_consumption(self):
+ from erpnext.stock.doctype.serial_no.serial_no import SerialNoRequiredError
+ from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
+
+ stock_entry = make_serialized_item()
+ serial_nos = stock_entry.get("items")[0].serial_no
+ serial_no = serial_nos.split("\n")[0]
+
+ # should not raise any error
+ create_asset_repair(stock_consumption = 1, item_code = stock_entry.get("items")[0].item_code,
+ warehouse = "_Test Warehouse - _TC", serial_no = serial_no, submit = 1)
+
+ # should raise error
+ asset_repair = create_asset_repair(stock_consumption = 1, warehouse = "_Test Warehouse - _TC",
+ item_code = stock_entry.get("items")[0].item_code)
+
+ asset_repair.repair_status = "Completed"
+ self.assertRaises(SerialNoRequiredError, asset_repair.submit)
+
def test_increase_in_asset_value_due_to_stock_consumption(self):
asset = create_asset(calculate_depreciation = 1, submit=1)
initial_asset_value = get_asset_value(asset)
@@ -137,11 +159,12 @@
if args.stock_consumption:
asset_repair.stock_consumption = 1
- asset_repair.warehouse = create_warehouse("Test Warehouse", company = asset.company)
+ asset_repair.warehouse = args.warehouse or create_warehouse("Test Warehouse", company = asset.company)
asset_repair.append("stock_items", {
- "item": args.item or args.item_code or "_Test Item",
+ "item_code": args.item_code or "_Test Stock Item",
"valuation_rate": args.rate if args.get("rate") is not None else 100,
- "consumed_quantity": args.qty or 1
+ "consumed_quantity": args.qty or 1,
+ "serial_no": args.serial_no
})
asset_repair.insert(ignore_if_duplicate=True)
@@ -158,7 +181,7 @@
})
stock_entry.append('items', {
"t_warehouse": asset_repair.warehouse,
- "item_code": asset_repair.stock_items[0].item,
+ "item_code": asset_repair.stock_items[0].item_code,
"qty": asset_repair.stock_items[0].consumed_quantity
})
stock_entry.submit()
diff --git a/erpnext/assets/doctype/asset_repair_consumed_item/asset_repair_consumed_item.json b/erpnext/assets/doctype/asset_repair_consumed_item/asset_repair_consumed_item.json
index 528f0ec..f63add1 100644
--- a/erpnext/assets/doctype/asset_repair_consumed_item/asset_repair_consumed_item.json
+++ b/erpnext/assets/doctype/asset_repair_consumed_item/asset_repair_consumed_item.json
@@ -5,20 +5,14 @@
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
- "item",
+ "item_code",
"valuation_rate",
"consumed_quantity",
- "total_value"
+ "total_value",
+ "serial_no"
],
"fields": [
{
- "fieldname": "item",
- "fieldtype": "Link",
- "in_list_view": 1,
- "label": "Item",
- "options": "Item"
- },
- {
"fetch_from": "item.valuation_rate",
"fieldname": "valuation_rate",
"fieldtype": "Currency",
@@ -38,12 +32,24 @@
"in_list_view": 1,
"label": "Total Value",
"read_only": 1
+ },
+ {
+ "fieldname": "serial_no",
+ "fieldtype": "Small Text",
+ "label": "Serial No"
+ },
+ {
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Item",
+ "options": "Item"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2021-05-12 03:19:55.006300",
+ "modified": "2021-11-11 18:23:00.492483",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Repair Consumed Item",
diff --git a/erpnext/buying/doctype/supplier/supplier.py b/erpnext/buying/doctype/supplier/supplier.py
index 32659d6..14d2ccd 100644
--- a/erpnext/buying/doctype/supplier/supplier.py
+++ b/erpnext/buying/doctype/supplier/supplier.py
@@ -11,7 +11,11 @@
)
from frappe.model.naming import set_name_by_naming_series, set_name_from_naming_options
-from erpnext.accounts.party import get_dashboard_info, validate_party_accounts
+from erpnext.accounts.party import ( # noqa
+ get_dashboard_info,
+ get_timeline_data,
+ validate_party_accounts,
+)
from erpnext.utilities.transaction_base import TransactionBase
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 3190fea..2c92820 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -145,11 +145,6 @@
self.validate_party()
self.validate_currency()
- if self.doctype == 'Purchase Invoice':
- self.calculate_paid_amount()
- # apply tax withholding only if checked and applicable
- self.set_tax_withholding()
-
if self.doctype in ['Purchase Invoice', 'Sales Invoice']:
pos_check_field = "is_pos" if self.doctype=="Sales Invoice" else "is_paid"
if cint(self.allocate_advances_automatically) and not cint(self.get(pos_check_field)):
@@ -164,6 +159,11 @@
self.set_inter_company_account()
+ if self.doctype == 'Purchase Invoice':
+ self.calculate_paid_amount()
+ # apply tax withholding only if checked and applicable
+ self.set_tax_withholding()
+
validate_regional(self)
if self.doctype != 'Material Request':
@@ -250,7 +250,12 @@
from erpnext.controllers.taxes_and_totals import calculate_taxes_and_totals
calculate_taxes_and_totals(self)
- if self.doctype in ["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"]:
+ if self.doctype in (
+ 'Sales Order',
+ 'Delivery Note',
+ 'Sales Invoice',
+ 'POS Invoice',
+ ):
self.calculate_commission()
self.calculate_contribution()
@@ -524,7 +529,8 @@
'is_opening': self.get("is_opening") or "No",
'party_type': None,
'party': None,
- 'project': self.get("project")
+ 'project': self.get("project"),
+ 'post_net_value': args.get('post_net_value')
})
accounting_dimensions = get_accounting_dimensions()
@@ -805,7 +811,6 @@
from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries
if self.doctype in ["Sales Invoice", "Purchase Invoice"]:
- self.update_allocated_advance_taxes_on_cancel()
if frappe.db.get_single_value('Accounts Settings', 'unlink_payment_on_cancellation_of_invoice'):
unlink_ref_doc_from_payment_entries(self)
@@ -853,29 +858,6 @@
return tax_map
- def update_allocated_advance_taxes_on_cancel(self):
- if self.get('advances'):
- tax_accounts = [d.account_head for d in self.get('taxes')]
- allocated_tax_map = frappe._dict(frappe.get_all('GL Entry', fields=['account', 'sum(credit - debit)'],
- filters={'voucher_no': self.name, 'account': ('in', tax_accounts)},
- group_by='account', as_list=1))
-
- tax_map = self.get_tax_map()
-
- for pe in self.get('advances'):
- if pe.reference_type == 'Payment Entry':
- pe = frappe.get_doc('Payment Entry', pe.reference_name)
- for tax in pe.get('taxes'):
- allocated_amount = tax_map.get(tax.account_head) - allocated_tax_map.get(tax.account_head)
- if allocated_amount > tax.tax_amount:
- allocated_amount = tax.tax_amount
-
- if allocated_amount:
- frappe.db.set_value('Advance Taxes and Charges', tax.name, 'allocated_amount',
- tax.allocated_amount - allocated_amount)
- tax_map[tax.account_head] -= allocated_amount
- allocated_tax_map[tax.account_head] -= allocated_amount
-
def get_amount_and_base_amount(self, item, enable_discount_accounting):
amount = item.net_amount
base_amount = item.base_net_amount
@@ -959,58 +941,10 @@
}, item=self)
)
- def allocate_advance_taxes(self, gl_entries):
- tax_map = self.get_tax_map()
- for pe in self.get("advances"):
- if pe.reference_type == "Payment Entry" and \
- frappe.db.get_value('Payment Entry', pe.reference_name, 'advance_tax_account'):
- pe = frappe.get_doc("Payment Entry", pe.reference_name)
- for tax in pe.get("taxes"):
- account_currency = get_account_currency(tax.account_head)
-
- if self.doctype == "Purchase Invoice":
- dr_or_cr = "debit" if tax.add_deduct_tax == "Add" else "credit"
- rev_dr_cr = "credit" if tax.add_deduct_tax == "Add" else "debit"
- else:
- dr_or_cr = "credit" if tax.add_deduct_tax == "Add" else "debit"
- rev_dr_cr = "debit" if tax.add_deduct_tax == "Add" else "credit"
-
- party = self.supplier if self.doctype == "Purchase Invoice" else self.customer
- unallocated_amount = tax.tax_amount - tax.allocated_amount
- if tax_map.get(tax.account_head):
- amount = tax_map.get(tax.account_head)
- if amount < unallocated_amount:
- unallocated_amount = amount
-
- gl_entries.append(
- self.get_gl_dict({
- "account": tax.account_head,
- "against": party,
- dr_or_cr: unallocated_amount,
- dr_or_cr + "_in_account_currency": unallocated_amount
- if account_currency==self.company_currency
- else unallocated_amount,
- "cost_center": tax.cost_center
- }, account_currency, item=tax))
-
- gl_entries.append(
- self.get_gl_dict({
- "account": pe.advance_tax_account,
- "against": party,
- rev_dr_cr: unallocated_amount,
- rev_dr_cr + "_in_account_currency": unallocated_amount
- if account_currency==self.company_currency
- else unallocated_amount,
- "cost_center": tax.cost_center
- }, account_currency, item=tax))
-
- frappe.db.set_value("Advance Taxes and Charges", tax.name, "allocated_amount",
- tax.allocated_amount + unallocated_amount)
-
- tax_map[tax.account_head] -= unallocated_amount
def validate_multiple_billing(self, ref_dt, item_ref_dn, based_on, parentfield):
from erpnext.controllers.status_updater import get_allowance_for
+
item_allowance = {}
global_qty_allowance, global_amount_allowance = None, None
@@ -1031,12 +965,7 @@
.format(item.item_code, ref_dt), title=_("Warning"), indicator="orange")
continue
- 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]
+ already_billed = self.get_billed_amount_for_item(item, item_ref_dn, based_on)
total_billed_amt = flt(flt(already_billed) + flt(item.get(based_on)),
self.precision(based_on, item))
@@ -1064,6 +993,43 @@
frappe.msgprint(_("Overbilling of {} ignored because you have {} role.")
.format(total_overbilled_amt, role_allowed_to_over_bill), indicator="orange", alert=True)
+ def get_billed_amount_for_item(self, item, item_ref_dn, based_on):
+ '''
+ Returns Sum of Amount of
+ Sales/Purchase Invoice Items
+ that are linked to `item_ref_dn` (`dn_detail` / `pr_detail`)
+ that are submitted OR not submitted but are under current invoice
+ '''
+
+ from frappe.query_builder import Criterion
+ from frappe.query_builder.functions import Sum
+
+ item_doctype = frappe.qb.DocType(item.doctype)
+ based_on_field = frappe.qb.Field(based_on)
+ join_field = frappe.qb.Field(item_ref_dn)
+
+ result = (
+ frappe.qb.from_(item_doctype)
+ .select(Sum(based_on_field))
+ .where(
+ join_field == item.get(item_ref_dn)
+ ).where(
+ Criterion.any([ # select all items from other invoices OR current invoices
+ Criterion.all([ # for selecting items from other invoices
+ item_doctype.docstatus == 1,
+ item_doctype.parent != self.name
+ ]),
+ Criterion.all([ # for selecting items from current invoice, that are linked to same reference
+ item_doctype.docstatus == 0,
+ item_doctype.parent == self.name,
+ item_doctype.name != item.name
+ ])
+ ])
+ )
+ ).run()
+
+ return result[0][0] if result else 0
+
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")
.format(item.item_code, item.idx, max_allowed_amt))
diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py
index 76a7d1a..dc04dab 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -539,6 +539,10 @@
dimension_filters = get_dimension_filter_map()
dimension_filters = dimension_filters.get((filters.get('dimension'),filters.get('account')))
query_filters = []
+ or_filters = []
+ fields = ['name']
+
+ searchfields = frappe.get_meta(doctype).get_search_fields()
meta = frappe.get_meta(doctype)
if meta.is_tree:
@@ -550,8 +554,9 @@
if meta.has_field('company'):
query_filters.append(['company', '=', filters.get('company')])
- if txt:
- query_filters.append([searchfield, 'LIKE', "%%%s%%" % txt])
+ for field in searchfields:
+ or_filters.append([field, 'LIKE', "%%%s%%" % txt])
+ fields.append(field)
if dimension_filters:
if dimension_filters['allow_or_restrict'] == 'Allow':
@@ -566,10 +571,9 @@
query_filters.append(['name', query_selector, dimensions])
- output = frappe.get_list(doctype, filters=query_filters)
- result = [d.name for d in output]
+ output = frappe.get_list(doctype, fields=fields, filters=query_filters, or_filters=or_filters, as_list=1)
- return [(d,) for d in set(result)]
+ return [tuple(d) for d in set(output)]
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py
index dad3ed7..cc773b7 100644
--- a/erpnext/controllers/selling_controller.py
+++ b/erpnext/controllers/selling_controller.py
@@ -120,13 +120,27 @@
self.in_words = money_in_words(amount, self.currency)
def calculate_commission(self):
- if self.meta.get_field("commission_rate"):
- self.round_floats_in(self, ["base_net_total", "commission_rate"])
- if self.commission_rate > 100.0:
- throw(_("Commission rate cannot be greater than 100"))
+ if not self.meta.get_field("commission_rate"):
+ return
- self.total_commission = flt(self.base_net_total * self.commission_rate / 100.0,
- self.precision("total_commission"))
+ self.round_floats_in(
+ self, ("amount_eligible_for_commission", "commission_rate")
+ )
+
+ if not (0 <= self.commission_rate <= 100.0):
+ throw("{} {}".format(
+ _(self.meta.get_label("commission_rate")),
+ _("must be between 0 and 100"),
+ ))
+
+ self.amount_eligible_for_commission = sum(
+ item.base_net_amount for item in self.items if item.grant_commission
+ )
+
+ self.total_commission = flt(
+ self.amount_eligible_for_commission * self.commission_rate / 100.0,
+ self.precision("total_commission")
+ )
def calculate_contribution(self):
if not self.meta.get_field("sales_team"):
@@ -138,7 +152,7 @@
self.round_floats_in(sales_person)
sales_person.allocated_amount = flt(
- self.base_net_total * sales_person.allocated_percentage / 100.0,
+ self.amount_eligible_for_commission * sales_person.allocated_percentage / 100.0,
self.precision("allocated_amount", sales_person))
if sales_person.commission_rate:
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index ae17094..7073e32 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -134,7 +134,7 @@
"against": expense_account,
"cost_center": item_row.cost_center,
"project": item_row.project or self.get('project'),
- "remarks": self.get("remarks") or "Accounting Entry for Stock",
+ "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"debit": flt(sle.stock_value_difference, precision),
"is_opening": item_row.get("is_opening") or self.get("is_opening") or "No",
}, warehouse_account[sle.warehouse]["account_currency"], item=item_row))
@@ -143,7 +143,7 @@
"account": expense_account,
"against": warehouse_account[sle.warehouse]["account"],
"cost_center": item_row.cost_center,
- "remarks": self.get("remarks") or "Accounting Entry for Stock",
+ "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"credit": flt(sle.stock_value_difference, precision),
"project": item_row.get("project") or self.get("project"),
"is_opening": item_row.get("is_opening") or self.get("is_opening") or "No"
diff --git a/erpnext/education/api.py b/erpnext/education/api.py
index 53d1482..d9013b0 100644
--- a/erpnext/education/api.py
+++ b/erpnext/education/api.py
@@ -125,7 +125,7 @@
:param student: Student.
"""
- guardians = frappe.get_list("Student Guardian", fields=["guardian"] ,
+ guardians = frappe.get_all("Student Guardian", fields=["guardian"] ,
filters={"parent": student})
return guardians
@@ -137,10 +137,10 @@
:param student_group: Student Group.
"""
if include_inactive:
- students = frappe.get_list("Student Group Student", fields=["student", "student_name"] ,
+ students = frappe.get_all("Student Group Student", fields=["student", "student_name"] ,
filters={"parent": student_group}, order_by= "group_roll_number")
else:
- students = frappe.get_list("Student Group Student", fields=["student", "student_name"] ,
+ students = frappe.get_all("Student Group Student", fields=["student", "student_name"] ,
filters={"parent": student_group, "active": 1}, order_by= "group_roll_number")
return students
@@ -164,7 +164,7 @@
:param fee_structure: Fee Structure.
"""
if fee_structure:
- fs = frappe.get_list("Fee Component", fields=["fees_category", "description", "amount"] , filters={"parent": fee_structure}, order_by= "idx")
+ fs = frappe.get_all("Fee Component", fields=["fees_category", "description", "amount"] , filters={"parent": fee_structure}, order_by= "idx")
return fs
@@ -175,7 +175,7 @@
:param program: Program.
:param student_category: Student Category
"""
- fs = frappe.get_list("Program Fee", fields=["academic_term", "fee_structure", "due_date", "amount"] ,
+ fs = frappe.get_all("Program Fee", fields=["academic_term", "fee_structure", "due_date", "amount"] ,
filters={"parent": program, "student_category": student_category }, order_by= "idx")
return fs
@@ -220,7 +220,7 @@
:param Course: Course
"""
- return frappe.get_list("Course Assessment Criteria", \
+ return frappe.get_all("Course Assessment Criteria",
fields=["assessment_criteria", "weightage"], filters={"parent": course}, order_by= "idx")
@@ -253,7 +253,7 @@
:param Assessment Plan: Assessment Plan
"""
- return frappe.get_list("Assessment Plan Criteria", \
+ return frappe.get_all("Assessment Plan Criteria",
fields=["assessment_criteria", "maximum_score", "docstatus"], filters={"parent": assessment_plan}, order_by= "idx")
diff --git a/erpnext/education/doctype/course_schedule/course_schedule.json b/erpnext/education/doctype/course_schedule/course_schedule.json
index 8c6746b..38d9b50 100644
--- a/erpnext/education/doctype/course_schedule/course_schedule.json
+++ b/erpnext/education/doctype/course_schedule/course_schedule.json
@@ -1,520 +1,143 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
+ "actions": [],
"allow_import": 1,
- "allow_rename": 0,
"autoname": "naming_series:",
- "beta": 0,
"creation": "2015-09-09 16:34:04.960369",
- "custom": 0,
- "docstatus": 0,
"doctype": "DocType",
"document_type": "Document",
- "editable_grid": 0,
"engine": "InnoDB",
+ "field_order": [
+ "student_group",
+ "instructor",
+ "instructor_name",
+ "column_break_2",
+ "naming_series",
+ "course",
+ "color",
+ "section_break_6",
+ "schedule_date",
+ "room",
+ "column_break_9",
+ "from_time",
+ "to_time",
+ "title"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "student_group",
"fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
"in_global_search": 1,
- "in_list_view": 0,
"in_standard_filter": 1,
"label": "Student Group",
- "length": 0,
- "no_copy": 0,
"options": "Student Group",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "reqd": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "instructor",
"fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
"in_standard_filter": 1,
"label": "Instructor",
- "length": 0,
- "no_copy": 0,
"options": "Instructor",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "reqd": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "instructor.Instructor_name",
+ "fetch_from": "instructor.instructor_name",
"fieldname": "instructor_name",
"fieldtype": "Read Only",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
"in_global_search": 1,
- "in_list_view": 0,
- "in_standard_filter": 0,
"label": "Instructor Name",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "read_only": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "column_break_2",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "fieldtype": "Column Break"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "",
"fieldname": "naming_series",
"fieldtype": "Select",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
"label": "Naming Series",
- "length": 0,
- "no_copy": 0,
"options": "EDU-CSH-.YYYY.-",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 1,
- "translatable": 0,
- "unique": 0
+ "set_only_once": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "course",
"fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
"in_global_search": 1,
- "in_list_view": 0,
- "in_standard_filter": 0,
"label": "Course",
- "length": 0,
- "no_copy": 0,
"options": "Course",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "reqd": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "color",
"fieldtype": "Color",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
"label": "Color",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "print_hide": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "section_break_6",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "fieldtype": "Section Break"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"default": "Today",
"fieldname": "schedule_date",
"fieldtype": "Date",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Schedule Date",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "label": "Schedule Date"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "room",
"fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
"label": "Room",
- "length": 0,
- "no_copy": 0,
"options": "Room",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "reqd": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "column_break_9",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "fieldtype": "Column Break"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "from_time",
"fieldtype": "Time",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
"in_list_view": 1,
- "in_standard_filter": 0,
"label": "From Time",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "reqd": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "to_time",
"fieldtype": "Time",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
"in_list_view": 1,
- "in_standard_filter": 0,
"label": "To Time",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "reqd": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "title",
"fieldtype": "Data",
"hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Title",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "label": "Title"
}
],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "menu_index": 0,
- "modified": "2018-08-21 14:44:51.827225",
+ "links": [],
+ "modified": "2021-11-24 11:57:08.164449",
"modified_by": "Administrator",
"module": "Education",
"name": "Course Schedule",
- "name_case": "",
+ "naming_rule": "By \"Naming Series\" field",
"owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Academics User",
- "set_user_permissions": 0,
"share": 1,
- "submit": 0,
"write": 1
}
],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
"restrict_to_domain": "Education",
- "show_name_in_global_search": 0,
"sort_field": "schedule_date",
"sort_order": "DESC",
- "title_field": "title",
- "track_changes": 0,
- "track_seen": 0,
- "track_views": 0
+ "title_field": "title"
}
\ No newline at end of file
diff --git a/erpnext/education/doctype/student_attendance_tool/student_attendance_tool.py b/erpnext/education/doctype/student_attendance_tool/student_attendance_tool.py
index 4e3f98d..7deb6b1 100644
--- a/erpnext/education/doctype/student_attendance_tool/student_attendance_tool.py
+++ b/erpnext/education/doctype/student_attendance_tool/student_attendance_tool.py
@@ -14,24 +14,36 @@
student_list = []
student_attendance_list = []
- if based_on=="Course Schedule":
+ if based_on == "Course Schedule":
student_group = frappe.db.get_value("Course Schedule", course_schedule, "student_group")
if student_group:
- student_list = frappe.get_list("Student Group Student", fields=["student", "student_name", "group_roll_number"] , \
+ student_list = frappe.get_all("Student Group Student", fields=["student", "student_name", "group_roll_number"],
filters={"parent": student_group, "active": 1}, order_by= "group_roll_number")
if not student_list:
- student_list = frappe.get_list("Student Group Student", fields=["student", "student_name", "group_roll_number"] ,
+ student_list = frappe.get_all("Student Group Student", fields=["student", "student_name", "group_roll_number"],
filters={"parent": student_group, "active": 1}, order_by= "group_roll_number")
+ table = frappe.qb.DocType("Student Attendance")
+
if course_schedule:
- student_attendance_list= frappe.db.sql('''select student, status from `tabStudent Attendance` where \
- course_schedule= %s''', (course_schedule), as_dict=1)
+ student_attendance_list = (
+ frappe.qb.from_(table)
+ .select(table.student, table.status)
+ .where(
+ (table.course_schedule == course_schedule)
+ )
+ ).run(as_dict=True)
else:
- student_attendance_list= frappe.db.sql('''select student, status from `tabStudent Attendance` where \
- student_group= %s and date= %s and \
- (course_schedule is Null or course_schedule='')''',
- (student_group, date), as_dict=1)
+ student_attendance_list = (
+ frappe.qb.from_(table)
+ .select(table.student, table.status)
+ .where(
+ (table.student_group == student_group)
+ & (table.date == date)
+ & (table.course_schedule == "") | (table.course_schedule.isnull())
+ )
+ ).run(as_dict=True)
for attendance in student_attendance_list:
for student in student_list:
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index e8aac1d..05c46c5 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -248,20 +248,18 @@
"validate": "erpnext.regional.india.utils.validate_tax_category"
},
"Sales Invoice": {
- "after_insert": "erpnext.regional.saudi_arabia.utils.create_qr_code",
"on_submit": [
"erpnext.regional.create_transaction_log",
"erpnext.regional.italy.utils.sales_invoice_on_submit",
+ "erpnext.regional.saudi_arabia.utils.create_qr_code",
"erpnext.erpnext_integrations.taxjar_integration.create_transaction"
],
"on_cancel": [
"erpnext.regional.italy.utils.sales_invoice_on_cancel",
- "erpnext.erpnext_integrations.taxjar_integration.delete_transaction"
- ],
- "on_trash": [
- "erpnext.regional.check_deletion_permission",
+ "erpnext.erpnext_integrations.taxjar_integration.delete_transaction",
"erpnext.regional.saudi_arabia.utils.delete_qr_code_file"
],
+ "on_trash": "erpnext.regional.check_deletion_permission",
"validate": [
"erpnext.regional.india.utils.validate_document_name",
"erpnext.regional.india.utils.update_taxable_values"
diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py
index 79e8f61..88e5ca9 100755
--- a/erpnext/hr/doctype/employee/employee.py
+++ b/erpnext/hr/doctype/employee/employee.py
@@ -96,15 +96,8 @@
'user': self.user_id
})
- if employee_user_permission_exists: return
-
- employee_user_permission_exists = frappe.db.exists('User Permission', {
- 'allow': 'Employee',
- 'for_value': self.name,
- 'user': self.user_id
- })
-
- if employee_user_permission_exists: return
+ if employee_user_permission_exists:
+ return
add_user_permission("Employee", self.name, self.user_id)
set_user_permission_if_allowed("Company", self.company, self.user_id)
diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.py b/erpnext/hr/doctype/employee_advance/employee_advance.py
index 8a8e8db..7aac2b6 100644
--- a/erpnext/hr/doctype/employee_advance/employee_advance.py
+++ b/erpnext/hr/doctype/employee_advance/employee_advance.py
@@ -5,6 +5,7 @@
import frappe
from frappe import _
from frappe.model.document import Document
+from frappe.query_builder.functions import Sum
from frappe.utils import flt, nowdate
import erpnext
@@ -41,24 +42,34 @@
self.status = "Cancelled"
def set_total_advance_paid(self):
- paid_amount = frappe.db.sql("""
- select ifnull(sum(debit), 0) as paid_amount
- from `tabGL Entry`
- where against_voucher_type = 'Employee Advance'
- and against_voucher = %s
- and party_type = 'Employee'
- and party = %s
- """, (self.name, self.employee), as_dict=1)[0].paid_amount
+ gle = frappe.qb.DocType("GL Entry")
- return_amount = frappe.db.sql("""
- select ifnull(sum(credit), 0) as return_amount
- from `tabGL Entry`
- where against_voucher_type = 'Employee Advance'
- and voucher_type != 'Expense Claim'
- and against_voucher = %s
- and party_type = 'Employee'
- and party = %s
- """, (self.name, self.employee), as_dict=1)[0].return_amount
+ paid_amount = (
+ frappe.qb.from_(gle)
+ .select(Sum(gle.debit).as_("paid_amount"))
+ .where(
+ (gle.against_voucher_type == 'Employee Advance')
+ & (gle.against_voucher == self.name)
+ & (gle.party_type == 'Employee')
+ & (gle.party == self.employee)
+ & (gle.docstatus == 1)
+ & (gle.is_cancelled == 0)
+ )
+ ).run(as_dict=True)[0].paid_amount or 0
+
+ return_amount = (
+ frappe.qb.from_(gle)
+ .select(Sum(gle.credit).as_("return_amount"))
+ .where(
+ (gle.against_voucher_type == 'Employee Advance')
+ & (gle.voucher_type != 'Expense Claim')
+ & (gle.against_voucher == self.name)
+ & (gle.party_type == 'Employee')
+ & (gle.party == self.employee)
+ & (gle.docstatus == 1)
+ & (gle.is_cancelled == 0)
+ )
+ ).run(as_dict=True)[0].return_amount or 0
if paid_amount != 0:
paid_amount = flt(paid_amount) / flt(self.exchange_rate)
diff --git a/erpnext/hr/doctype/employee_advance/test_employee_advance.py b/erpnext/hr/doctype/employee_advance/test_employee_advance.py
index 4ecfa60..5f2e720 100644
--- a/erpnext/hr/doctype/employee_advance/test_employee_advance.py
+++ b/erpnext/hr/doctype/employee_advance/test_employee_advance.py
@@ -34,6 +34,24 @@
journal_entry1 = make_payment_entry(advance)
self.assertRaises(EmployeeAdvanceOverPayment, journal_entry1.submit)
+ def test_paid_amount_on_pe_cancellation(self):
+ employee_name = make_employee("_T@employe.advance")
+ advance = make_employee_advance(employee_name)
+
+ pe = make_payment_entry(advance)
+ pe.submit()
+
+ advance.reload()
+
+ self.assertEqual(advance.paid_amount, 1000)
+ self.assertEqual(advance.status, "Paid")
+
+ pe.cancel()
+ advance.reload()
+
+ self.assertEqual(advance.paid_amount, 0)
+ self.assertEqual(advance.status, "Unpaid")
+
def test_repay_unclaimed_amount_from_salary(self):
employee_name = make_employee("_T@employe.advance")
advance = make_employee_advance(employee_name, {"repay_unclaimed_amount_from_salary": 1})
diff --git a/erpnext/hr/doctype/employee_transfer/test_employee_transfer.py b/erpnext/hr/doctype/employee_transfer/test_employee_transfer.py
index 287dfba..64eee40 100644
--- a/erpnext/hr/doctype/employee_transfer/test_employee_transfer.py
+++ b/erpnext/hr/doctype/employee_transfer/test_employee_transfer.py
@@ -2,7 +2,6 @@
# See license.txt
import unittest
-from datetime import date
import frappe
from frappe.utils import add_days, getdate
@@ -12,16 +11,14 @@
class TestEmployeeTransfer(unittest.TestCase):
def setUp(self):
- make_employee("employee2@transfers.com")
- make_employee("employee3@transfers.com")
create_company()
- create_employee()
- create_employee_transfer()
def tearDown(self):
frappe.db.rollback()
def test_submit_before_transfer_date(self):
+ make_employee("employee2@transfers.com")
+
transfer_obj = frappe.get_doc({
"doctype": "Employee Transfer",
"employee": frappe.get_value("Employee", {"user_id":"employee2@transfers.com"}, "name"),
@@ -43,6 +40,8 @@
self.assertEqual(transfer.docstatus, 1)
def test_new_employee_creation(self):
+ make_employee("employee3@transfers.com")
+
transfer = frappe.get_doc({
"doctype": "Employee Transfer",
"employee": frappe.get_value("Employee", {"user_id":"employee3@transfers.com"}, "name"),
@@ -63,60 +62,51 @@
self.assertEqual(frappe.get_value("Employee", transfer.employee, "status"), "Left")
def test_employee_history(self):
- name = frappe.get_value("Employee", {"first_name": "John", "company": "Test Company"}, "name")
- doc = frappe.get_doc("Employee",name)
+ employee = make_employee("employee4@transfers.com",
+ company="Test Company",
+ date_of_birth=getdate("30-09-1980"),
+ date_of_joining=getdate("01-10-2021"),
+ department="Accounts - TC",
+ designation="Accountant"
+ )
+ transfer = create_employee_transfer(employee)
+
count = 0
department = ["Accounts - TC", "Management - TC"]
designation = ["Accountant", "Manager"]
- dt = [getdate("01-10-2021"), date.today()]
+ dt = [getdate("01-10-2021"), getdate()]
- for data in doc.internal_work_history:
+ employee = frappe.get_doc("Employee", employee)
+ for data in employee.internal_work_history:
self.assertEqual(data.department, department[count])
self.assertEqual(data.designation, designation[count])
self.assertEqual(data.from_date, dt[count])
count = count + 1
- data = frappe.db.get_list("Employee Transfer", filters={"employee":name}, fields=["*"])
- doc = frappe.get_doc("Employee Transfer", data[0]["name"])
- doc.cancel()
- employee_doc = frappe.get_doc("Employee",name)
+ transfer.cancel()
+ employee.reload()
- for data in employee_doc.internal_work_history:
+ for data in employee.internal_work_history:
self.assertEqual(data.designation, designation[0])
self.assertEqual(data.department, department[0])
self.assertEqual(data.from_date, dt[0])
-def create_employee():
- doc = frappe.get_doc({
- "doctype": "Employee",
- "first_name": "John",
- "company": "Test Company",
- "gender": "Male",
- "date_of_birth": getdate("30-09-1980"),
- "date_of_joining": getdate("01-10-2021"),
- "department": "Accounts - TC",
- "designation": "Accountant"
- })
-
- doc.save()
def create_company():
- exists = frappe.db.exists("Company", "Test Company")
- if not exists:
- doc = frappe.get_doc({
- "doctype": "Company",
- "company_name": "Test Company",
- "default_currency": "INR",
- "country": "India"
- })
+ if not frappe.db.exists("Company", "Test Company"):
+ frappe.get_doc({
+ "doctype": "Company",
+ "company_name": "Test Company",
+ "default_currency": "INR",
+ "country": "India"
+ }).insert()
- doc.save()
-def create_employee_transfer():
+def create_employee_transfer(employee):
doc = frappe.get_doc({
"doctype": "Employee Transfer",
- "employee": frappe.get_value("Employee", {"first_name": "John", "company": "Test Company"}, "name"),
- "transfer_date": date.today(),
+ "employee": employee,
+ "transfer_date": getdate(),
"transfer_details": [
{
"property": "Designation",
@@ -134,4 +124,6 @@
})
doc.save()
- doc.submit()
\ No newline at end of file
+ doc.submit()
+
+ return doc
\ No newline at end of file
diff --git a/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.json b/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.json
index 70a48f9..6edbcb5c 100644
--- a/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.json
+++ b/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.json
@@ -94,7 +94,6 @@
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Sanctioned Amount",
- "no_copy": 1,
"oldfieldname": "sanctioned_amount",
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
@@ -120,7 +119,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2020-09-18 17:26:09.703215",
+ "modified": "2021-11-26 14:23:45.539922",
"modified_by": "Administrator",
"module": "HR",
"name": "Expense Claim Detail",
diff --git a/erpnext/hr/doctype/shift_assignment/shift_assignment.py b/erpnext/hr/doctype/shift_assignment/shift_assignment.py
index 4e829a3..5177302 100644
--- a/erpnext/hr/doctype/shift_assignment/shift_assignment.py
+++ b/erpnext/hr/doctype/shift_assignment/shift_assignment.py
@@ -19,8 +19,8 @@
validate_active_employee(self.employee)
self.validate_overlapping_dates()
- if self.end_date and self.end_date <= self.start_date:
- frappe.throw(_("End Date must not be lesser than Start Date"))
+ if self.end_date:
+ self.validate_from_to_dates('start_date', 'end_date')
def validate_overlapping_dates(self):
if not self.name:
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js
index b171086..0babf87 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.js
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js
@@ -238,6 +238,12 @@
method: "get_items",
freeze: true,
doc: frm.doc,
+ callback: function() {
+ frm.refresh_field("po_items");
+ if (frm.doc.sub_assembly_items.length > 0) {
+ frm.trigger("get_sub_assembly_items");
+ }
+ }
});
},
diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py
index f4a88dc..f590d68 100644
--- a/erpnext/manufacturing/doctype/work_order/test_work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py
@@ -21,9 +21,10 @@
from erpnext.stock.doctype.stock_entry import test_stock_entry
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
from erpnext.stock.utils import get_bin
+from erpnext.tests.utils import ERPNextTestCase, timeout
-class TestWorkOrder(unittest.TestCase):
+class TestWorkOrder(ERPNextTestCase):
def setUp(self):
self.warehouse = '_Test Warehouse 2 - _TC'
self.item = '_Test Item'
@@ -376,6 +377,7 @@
self.assertEqual(len(ste.additional_costs), 1)
self.assertEqual(ste.total_additional_costs, 1000)
+ @timeout(seconds=60)
def test_job_card(self):
stock_entries = []
bom = frappe.get_doc('BOM', {
@@ -769,6 +771,7 @@
total_pl_qty
)
+ @timeout(seconds=60)
def test_job_card_scrap_item(self):
items = ['Test FG Item for Scrap Item Test', 'Test RM Item 1 for Scrap Item Test',
'Test RM Item 2 for Scrap Item Test']
diff --git a/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json b/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json
index 647c14b..4e1a464 100644
--- a/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json
+++ b/erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json
@@ -178,8 +178,9 @@
},
{
"fieldname": "batch_size",
- "fieldtype": "Int",
- "label": "Batch Size"
+ "fieldtype": "Float",
+ "label": "Batch Size",
+ "read_only": 1
},
{
"fieldname": "sequence_id",
@@ -200,7 +201,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2021-11-24 04:52:54.295168",
+ "modified": "2021-11-29 16:37:18.824489",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Work Order Operation",
diff --git a/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py b/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py
index cf19cbf..090a3e7 100644
--- a/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py
+++ b/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py
@@ -89,7 +89,7 @@
GROUP BY bom_item.item_code""".format(qty_field=qty_field, table=table, conditions=conditions, bom=bom), as_dict=1)
def get_manufacturer_records():
- details = frappe.get_list('Item Manufacturer', fields = ["manufacturer", "manufacturer_part_no", "parent"])
+ details = frappe.get_all('Item Manufacturer', fields = ["manufacturer", "manufacturer_part_no", "parent"])
manufacture_details = frappe._dict()
for detail in details:
dic = manufacture_details.setdefault(detail.get('parent'), {})
diff --git a/erpnext/manufacturing/report/work_order_consumed_materials/__init__.py b/erpnext/manufacturing/report/work_order_consumed_materials/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/manufacturing/report/work_order_consumed_materials/__init__.py
diff --git a/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.js b/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.js
new file mode 100644
index 0000000..b2428e8
--- /dev/null
+++ b/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.js
@@ -0,0 +1,70 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Work Order Consumed Materials"] = {
+ "filters": [
+ {
+ label: __("Company"),
+ fieldname: "company",
+ fieldtype: "Link",
+ options: "Company",
+ default: frappe.defaults.get_user_default("Company"),
+ reqd: 1
+ },
+ {
+ label: __("From Date"),
+ fieldname:"from_date",
+ fieldtype: "Date",
+ default: frappe.datetime.add_months(frappe.datetime.get_today(), -1),
+ reqd: 1
+ },
+ {
+ fieldname:"to_date",
+ label: __("To Date"),
+ fieldtype: "Date",
+ default: frappe.datetime.get_today(),
+ reqd: 1
+ },
+ {
+ label: __("Work Order"),
+ fieldname: "name",
+ fieldtype: "Link",
+ options: "Work Order",
+ get_query: function() {
+ return {
+ filters: {
+ status: ["in", ["In Process", "Completed", "Stopped"]]
+ }
+ }
+ }
+ },
+ {
+ label: __("Production Item"),
+ fieldname: "production_item",
+ fieldtype: "Link",
+ depends_on: "eval: !doc.name",
+ options: "Item"
+ },
+ {
+ label: __("Status"),
+ fieldname: "status",
+ fieldtype: "Select",
+ options: ["In Process", "Completed", "Stopped"]
+ },
+ {
+ label: __("Excess Materials Consumed"),
+ fieldname: "show_extra_consumed_materials",
+ fieldtype: "Check"
+ }
+ ],
+ "formatter": function(value, row, column, data, default_formatter) {
+ value = default_formatter(value, row, column, data);
+
+ if (column.fieldname == "raw_material_name" && data && data.extra_consumed_qty > 0 ) {
+ value = `<div style="color:red">${value}</div>`;
+ }
+
+ return value;
+ },
+};
diff --git a/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.json b/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.json
new file mode 100644
index 0000000..2fc986a
--- /dev/null
+++ b/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.json
@@ -0,0 +1,30 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2021-11-22 17:36:11.886939",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "letter_head": "Gadgets International",
+ "modified": "2021-11-22 17:36:14.999091",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Work Order Consumed Materials",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Work Order",
+ "report_name": "Work Order Consumed Materials",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "Manufacturing User"
+ },
+ {
+ "role": "Stock User"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.py b/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.py
new file mode 100644
index 0000000..0528348
--- /dev/null
+++ b/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.py
@@ -0,0 +1,131 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+import frappe
+from frappe import _
+
+
+def execute(filters=None):
+ columns, data = [], []
+ columns = get_columns()
+ data = get_data(filters)
+
+ return columns, data
+
+def get_data(report_filters):
+ fields = get_fields()
+ filters = get_filter_condition(report_filters)
+
+ wo_items = {}
+ for d in frappe.get_all("Work Order", filters = filters, fields=fields):
+ d.extra_consumed_qty = 0.0
+ if d.consumed_qty and d.consumed_qty > d.required_qty:
+ d.extra_consumed_qty = d.consumed_qty - d.required_qty
+
+ if d.extra_consumed_qty or not report_filters.show_extra_consumed_materials:
+ wo_items.setdefault((d.name, d.production_item), []).append(d)
+
+ data = []
+ for key, wo_data in wo_items.items():
+ for index, row in enumerate(wo_data):
+ if index != 0:
+ #If one work order has multiple raw materials then show parent data in the first row only
+ for field in ["name", "status", "production_item", "qty", "produced_qty"]:
+ row[field] = ""
+
+ data.append(row)
+
+ return data
+
+def get_fields():
+ return ["`tabWork Order Item`.`parent`", "`tabWork Order Item`.`item_code` as raw_material_item_code",
+ "`tabWork Order Item`.`item_name` as raw_material_name", "`tabWork Order Item`.`required_qty`",
+ "`tabWork Order Item`.`transferred_qty`", "`tabWork Order Item`.`consumed_qty`", "`tabWork Order`.`status`",
+ "`tabWork Order`.`name`", "`tabWork Order`.`production_item`", "`tabWork Order`.`qty`",
+ "`tabWork Order`.`produced_qty`"]
+
+def get_filter_condition(report_filters):
+ filters = {
+ "docstatus": 1, "status": ("in", ["In Process", "Completed", "Stopped"]),
+ "creation": ("between", [report_filters.from_date, report_filters.to_date])
+ }
+
+ for field in ["name", "production_item", "company", "status"]:
+ value = report_filters.get(field)
+ if value:
+ key = f"`{field}`"
+ filters.update({key: value})
+
+ return filters
+
+def get_columns():
+ return [
+ {
+ "label": _("Id"),
+ "fieldname": "name",
+ "fieldtype": "Link",
+ "options": "Work Order",
+ "width": 80
+ },
+ {
+ "label": _("Status"),
+ "fieldname": "status",
+ "fieldtype": "Data",
+ "width": 80
+ },
+ {
+ "label": _("Production Item"),
+ "fieldname": "production_item",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 130
+ },
+ {
+ "label": _("Qty to Produce"),
+ "fieldname": "qty",
+ "fieldtype": "Float",
+ "width": 120
+ },
+ {
+ "label": _("Produced Qty"),
+ "fieldname": "produced_qty",
+ "fieldtype": "Float",
+ "width": 110
+ },
+ {
+ "label": _("Raw Material Item"),
+ "fieldname": "raw_material_item_code",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 150
+ },
+ {
+ "label": _("Item Name"),
+ "fieldname": "raw_material_name",
+ "width": 130
+ },
+ {
+ "label": _("Required Qty"),
+ "fieldname": "required_qty",
+ "fieldtype": "Float",
+ "width": 100
+ },
+ {
+ "label": _("Transferred Qty"),
+ "fieldname": "transferred_qty",
+ "fieldtype": "Float",
+ "width": 100
+ },
+ {
+ "label": _("Consumed Qty"),
+ "fieldname": "consumed_qty",
+ "fieldtype": "Float",
+ "width": 100
+ },
+ {
+ "label": _("Extra Consumed Qty"),
+ "fieldname": "extra_consumed_qty",
+ "fieldtype": "Float",
+ "width": 100
+ }
+ ]
diff --git a/erpnext/manufacturing/workspace/manufacturing/manufacturing.json b/erpnext/manufacturing/workspace/manufacturing/manufacturing.json
index cfa80f8..65b4d02 100644
--- a/erpnext/manufacturing/workspace/manufacturing/manufacturing.json
+++ b/erpnext/manufacturing/workspace/manufacturing/manufacturing.json
@@ -1,10 +1,6 @@
{
- "charts": [
- {
- "chart_name": "Produced Quantity"
- }
- ],
- "content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"Manufacturing\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": null, \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Item\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"BOM\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Work Order\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Production Plan\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Forecasting\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Work Order Summary\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"BOM Stock Report\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Production Planning Report\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Production\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Bill of Materials\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Reports\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Tools\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}]",
+ "charts": [],
+ "content": "[{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\",\"level\":4,\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"BOM\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Work Order\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Production Plan\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Forecasting\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Work Order Summary\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"BOM Stock Report\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Production Planning Report\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":4}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\",\"level\":4,\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Production\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Bill of Materials\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Tools\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}}]",
"creation": "2020-03-02 17:11:37.032604",
"docstatus": 0,
"doctype": "Workspace",
@@ -141,14 +137,6 @@
"type": "Link"
},
{
- "hidden": 0,
- "is_query_report": 0,
- "label": "Reports",
- "link_count": 0,
- "onboard": 0,
- "type": "Card Break"
- },
- {
"dependencies": "Work Order",
"hidden": 0,
"is_query_report": 1,
@@ -295,9 +283,126 @@
"link_type": "DocType",
"onboard": 0,
"type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Reports",
+ "link_count": 10,
+ "onboard": 0,
+ "type": "Card Break"
+ },
+ {
+ "dependencies": "Work Order",
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "Production Planning Report",
+ "link_count": 0,
+ "link_to": "Production Planning Report",
+ "link_type": "Report",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Work Order",
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "Work Order Summary",
+ "link_count": 0,
+ "link_to": "Work Order Summary",
+ "link_type": "Report",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Quality Inspection",
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "Quality Inspection Summary",
+ "link_count": 0,
+ "link_to": "Quality Inspection Summary",
+ "link_type": "Report",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Downtime Entry",
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "Downtime Analysis",
+ "link_count": 0,
+ "link_to": "Downtime Analysis",
+ "link_type": "Report",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Job Card",
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "Job Card Summary",
+ "link_count": 0,
+ "link_to": "Job Card Summary",
+ "link_type": "Report",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "BOM",
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "BOM Search",
+ "link_count": 0,
+ "link_to": "BOM Search",
+ "link_type": "Report",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "BOM",
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "BOM Stock Report",
+ "link_count": 0,
+ "link_to": "BOM Stock Report",
+ "link_type": "Report",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Work Order",
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "Production Analytics",
+ "link_count": 0,
+ "link_to": "Production Analytics",
+ "link_type": "Report",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "BOM",
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "BOM Operations Time",
+ "link_count": 0,
+ "link_to": "BOM Operations Time",
+ "link_type": "Report",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Work Order Consumed Materials",
+ "link_count": 0,
+ "link_to": "Work Order Consumed Materials",
+ "link_type": "Report",
+ "onboard": 0,
+ "type": "Link"
}
],
- "modified": "2021-08-05 12:16:00.825742",
+ "modified": "2021-11-22 17:55:03.524496",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Manufacturing",
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index e475229..897e70c 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -287,7 +287,7 @@
erpnext.patches.v13_0.custom_fields_for_taxjar_integration #08-11-2021
erpnext.patches.v13_0.set_operation_time_based_on_operating_cost
erpnext.patches.v13_0.validate_options_for_data_field
-erpnext.patches.v13_0.create_gst_payment_entry_fields
+erpnext.patches.v13_0.create_gst_payment_entry_fields #27-11-2021
erpnext.patches.v14_0.delete_shopify_doctypes
erpnext.patches.v13_0.fix_invoice_statuses
erpnext.patches.v13_0.replace_supplier_item_group_with_party_specific_item
diff --git a/erpnext/patches/v13_0/create_gst_payment_entry_fields.py b/erpnext/patches/v13_0/create_gst_payment_entry_fields.py
index 7e6d67c..4166945 100644
--- a/erpnext/patches/v13_0/create_gst_payment_entry_fields.py
+++ b/erpnext/patches/v13_0/create_gst_payment_entry_fields.py
@@ -9,24 +9,29 @@
frappe.reload_doc('accounts', 'doctype', 'advance_taxes_and_charges')
frappe.reload_doc('accounts', 'doctype', 'payment_entry')
- custom_fields = {
- 'Payment Entry': [
- dict(fieldname='gst_section', label='GST Details', fieldtype='Section Break', insert_after='deductions',
- print_hide=1, collapsible=1),
- dict(fieldname='company_address', label='Company Address', fieldtype='Link', insert_after='gst_section',
- print_hide=1, options='Address'),
- dict(fieldname='company_gstin', label='Company GSTIN',
- fieldtype='Data', insert_after='company_address',
- fetch_from='company_address.gstin', print_hide=1, read_only=1),
- dict(fieldname='place_of_supply', label='Place of Supply',
- fieldtype='Data', insert_after='company_gstin',
- print_hide=1, read_only=1),
- dict(fieldname='customer_address', label='Customer Address', fieldtype='Link', insert_after='place_of_supply',
- print_hide=1, options='Address', depends_on = 'eval:doc.party_type == "Customer"'),
- dict(fieldname='customer_gstin', label='Customer GSTIN',
- fieldtype='Data', insert_after='customer_address',
- fetch_from='customer_address.gstin', print_hide=1, read_only=1)
- ]
- }
+ if frappe.db.exists('Company', {'country': 'India'}):
+ custom_fields = {
+ 'Payment Entry': [
+ dict(fieldname='gst_section', label='GST Details', fieldtype='Section Break', insert_after='deductions',
+ print_hide=1, collapsible=1),
+ dict(fieldname='company_address', label='Company Address', fieldtype='Link', insert_after='gst_section',
+ print_hide=1, options='Address'),
+ dict(fieldname='company_gstin', label='Company GSTIN',
+ fieldtype='Data', insert_after='company_address',
+ fetch_from='company_address.gstin', print_hide=1, read_only=1),
+ dict(fieldname='place_of_supply', label='Place of Supply',
+ fieldtype='Data', insert_after='company_gstin',
+ print_hide=1, read_only=1),
+ dict(fieldname='customer_address', label='Customer Address', fieldtype='Link', insert_after='place_of_supply',
+ print_hide=1, options='Address', depends_on = 'eval:doc.party_type == "Customer"'),
+ dict(fieldname='customer_gstin', label='Customer GSTIN',
+ fieldtype='Data', insert_after='customer_address',
+ fetch_from='customer_address.gstin', print_hide=1, read_only=1)
+ ]
+ }
- create_custom_fields(custom_fields, update=True)
\ No newline at end of file
+ create_custom_fields(custom_fields, update=True)
+ else:
+ fields = ['gst_section', 'company_address', 'company_gstin', 'place_of_supply', 'customer_address', 'customer_gstin']
+ for field in fields:
+ frappe.delete_doc_if_exists("Custom Field", f"Payment Entry-{field}")
\ No newline at end of file
diff --git a/erpnext/payroll/doctype/gratuity/gratuity.py b/erpnext/payroll/doctype/gratuity/gratuity.py
index f563c08..476990a 100644
--- a/erpnext/payroll/doctype/gratuity/gratuity.py
+++ b/erpnext/payroll/doctype/gratuity/gratuity.py
@@ -193,7 +193,7 @@
sal_slip = get_last_salary_slip(employee)
if not sal_slip:
frappe.throw(_("No Salary Slip is found for Employee: {0}").format(bold(employee)))
- component_and_amounts = frappe.get_list("Salary Detail",
+ component_and_amounts = frappe.get_all("Salary Detail",
filters={
"docstatus": 1,
'parent': sal_slip,
diff --git a/erpnext/payroll/doctype/gratuity/test_gratuity.py b/erpnext/payroll/doctype/gratuity/test_gratuity.py
index 78355ca..93cba06 100644
--- a/erpnext/payroll/doctype/gratuity/test_gratuity.py
+++ b/erpnext/payroll/doctype/gratuity/test_gratuity.py
@@ -48,7 +48,7 @@
self.assertEqual(floor(experience), gratuity.current_work_experience)
#amount Calculation
- component_amount = frappe.get_list("Salary Detail",
+ component_amount = frappe.get_all("Salary Detail",
filters={
"docstatus": 1,
'parent': sal_slip,
@@ -84,7 +84,7 @@
self.assertEqual(floor(experience), gratuity.current_work_experience)
#amount Calculation
- component_amount = frappe.get_list("Salary Detail",
+ component_amount = frappe.get_all("Salary Detail",
filters={
"docstatus": 1,
'parent': sal_slip,
diff --git a/erpnext/projects/report/project_profitability/test_project_profitability.py b/erpnext/projects/report/project_profitability/test_project_profitability.py
index 2cf9d38..0415690 100644
--- a/erpnext/projects/report/project_profitability/test_project_profitability.py
+++ b/erpnext/projects/report/project_profitability/test_project_profitability.py
@@ -1,7 +1,7 @@
import unittest
import frappe
-from frappe.utils import add_days, getdate, nowdate
+from frappe.utils import add_days, getdate
from erpnext.hr.doctype.employee.test_employee import make_employee
from erpnext.projects.doctype.timesheet.test_timesheet import (
@@ -13,21 +13,26 @@
class TestProjectProfitability(unittest.TestCase):
-
def setUp(self):
+ frappe.db.sql('delete from `tabTimesheet`')
emp = make_employee('test_employee_9@salary.com', company='_Test Company')
+
if not frappe.db.exists('Salary Component', 'Timesheet Component'):
frappe.get_doc({'doctype': 'Salary Component', 'salary_component': 'Timesheet Component'}).insert()
+
make_salary_structure_for_timesheet(emp, company='_Test Company')
- self.timesheet = make_timesheet(emp, simulate = True, is_billable=1)
+ date = getdate()
+
+ self.timesheet = make_timesheet(emp, is_billable=1)
self.salary_slip = make_salary_slip(self.timesheet.name)
- holidays = self.salary_slip.get_holidays_for_employee(nowdate(), nowdate())
+
+ holidays = self.salary_slip.get_holidays_for_employee(date, date)
if holidays:
frappe.db.set_value('Payroll Settings', None, 'include_holidays_in_total_working_days', 1)
self.salary_slip.submit()
self.sales_invoice = make_sales_invoice(self.timesheet.name, '_Test Item', '_Test Customer')
- self.sales_invoice.due_date = nowdate()
+ self.sales_invoice.due_date = date
self.sales_invoice.submit()
frappe.db.set_value('HR Settings', None, 'standard_working_hours', 8)
@@ -63,6 +68,4 @@
self.assertEqual(fractional_cost, row.fractional_cost)
def tearDown(self):
- frappe.get_doc("Sales Invoice", self.sales_invoice.name).cancel()
- frappe.get_doc("Salary Slip", self.salary_slip.name).cancel()
- frappe.get_doc("Timesheet", self.timesheet.name).cancel()
+ frappe.db.rollback()
diff --git a/erpnext/public/scss/point-of-sale.scss b/erpnext/public/scss/point-of-sale.scss
index 1677e9b..7a3854c 100644
--- a/erpnext/public/scss/point-of-sale.scss
+++ b/erpnext/public/scss/point-of-sale.scss
@@ -495,6 +495,11 @@
font-size: var(--text-md);
}
+ > .item-qty-total-container {
+ @extend .net-total-container;
+ padding: 5px 0px 0px 0px;
+ }
+
> .taxes-container {
display: none;
flex-direction: column;
diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py
index 4bd9195..9746fde 100644
--- a/erpnext/regional/india/utils.py
+++ b/erpnext/regional/india/utils.py
@@ -569,17 +569,17 @@
}
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=hsn_wise)
- for hsn_code, taxable_amount in hsn_taxable_amount.items():
+ for item_or_hsn, taxable_amount in hsn_taxable_amount.items():
item_data = frappe._dict()
- if not hsn_code:
+ if not item_or_hsn:
frappe.throw(_('GST HSN Code does not exist for one or more items'))
- item_data.hsnCode = int(hsn_code)
+ item_data.hsnCode = int(item_or_hsn) if hsn_wise else item_or_hsn
item_data.taxableAmount = taxable_amount
item_data.qtyUnit = ""
for attr in item_data_attrs:
item_data[attr] = 0
- for account, tax_detail in hsn_wise_charges.get(hsn_code, {}).items():
+ for account, tax_detail in hsn_wise_charges.get(item_or_hsn, {}).items():
account_type = gst_accounts.get(account, '')
for tax_acc, attrs in tax_map.items():
if account_type == tax_acc:
diff --git a/erpnext/regional/print_format/ksa_vat_invoice/ksa_vat_invoice.json b/erpnext/regional/print_format/ksa_vat_invoice/ksa_vat_invoice.json
index 681f72f..8e9a728 100644
--- a/erpnext/regional/print_format/ksa_vat_invoice/ksa_vat_invoice.json
+++ b/erpnext/regional/print_format/ksa_vat_invoice/ksa_vat_invoice.json
@@ -10,14 +10,14 @@
"docstatus": 0,
"doctype": "Print Format",
"font_size": 14,
- "html": "<div class=\"ksa-vat-format\">\n <div class=\"qr-flex\">\n <div style=\"qr-flex: 1\">\n <h2 class=\"invoice-heading\">TAX INVOICE</h2>\n <h2 class=\"invoice-heading\">\u0641\u0627\u062a\u0648\u0631\u0629 \u0636\u0631\u064a\u0628\u064a\u0629</h2>\n </div>\n \n <img class=\"qr-code\" src={{doc.qr_code}}>\n </div>\n {% set company = frappe.get_doc(\"Company\", doc.company)%}\n {% if (doc.company_address) %}\n {% set supplier_address_doc = frappe.get_doc('Address', doc.company_address) %}\n {% endif %}\n \n {% if(doc.customer_address) %}\n {% set customer_address = frappe.get_doc('Address', doc.customer_address ) %}\n {% endif %}\n \n {% if(doc.shipping_address_name) %}\n {% set customer_shipping_address = frappe.get_doc('Address', doc.shipping_address_name ) %}\n {% endif %} \n \n <table class=\"ksa-invoice-table two-columns\">\n <thead>\n <tr>\n <th>{{ company.name }}</th>\n <th style=\"text-align: right;\">{{ company.company_name_in_arabic }}</th>\n </tr>\n </thead>\n\n <tbody>\n <!-- Invoice Info -->\n <tr>\n <td>Invoice#: {{doc.name}}</td>\n <td>\u0631\u0642\u0645 \u0627\u0644\u0641\u0627\u062a\u0648\u0631\u0629: {{doc.name}}</td>\n </tr>\n <tr>\n <td>Invoice Date: {{doc.posting_date}}</td>\n <td>\u062a\u0627\u0631\u064a\u062e \u0627\u0644\u0641\u0627\u062a\u0648\u0631\u0629: {{doc.posting_date}}</td>\n </tr>\n <tr>\n <td>Date of Supply:{{doc.posting_date}}</td>\n <td>\u062a\u0627\u0631\u064a\u062e \u0627\u0644\u062a\u0648\u0631\u064a\u062f: {{doc.posting_date}}</td>\n </tr>\n \n <!--Supplier Info -->\n <tr>\n <td>Supplier:</td>\n <td>\u0627\u0644\u0645\u0648\u0631\u062f:</td>\n </tr>\n\t\t{% if (company.tax_id) %}\n <tr>\n <td>Supplier Tax Identification Number:</td>\n <td>\u0631\u0642\u0645 \u0627\u0644\u062a\u0639\u0631\u064a\u0641 \u0627\u0644\u0636\u0631\u064a\u0628\u064a \u0644\u0644\u0645\u0648\u0631\u062f:</td>\n </tr>\n <tr>\n <td>{{ company.tax_id }}</td>\n <td>{{ company.tax_id }}</td>\n </tr>\n {% endif %}\n <tr>\n <td>{{ company.name }}</td>\n <td>{{ company.company_name_in_arabic }} </td>\n </tr>\n \n \n {% if(supplier_address_doc) %}\n <tr>\n <td>{{ supplier_address_doc.address_line1}} </td>\n <td>{{ supplier_address_doc.address_in_arabic}} </td>\n </tr>\n <tr>\n <td>Phone: {{ supplier_address_doc.phone }}</td>\n <td>\u0647\u0627\u062a\u0641: {{ supplier_address_doc.phone }}</td>\n </tr>\n <tr>\n <td>Email: {{ supplier_address_doc.email_id }}</td>\n <td>\u0628\u0631\u064a\u062f \u0627\u0644\u0643\u062a\u0631\u0648\u0646\u064a: {{ supplier_address_doc.email_id }}</td>\n </tr>\n {% endif %}\n \n <!-- Customer Info -->\n <tr>\n <td>CUSTOMER:</td>\n <td>\u0639\u0645\u064a\u0644:</td>\n </tr>\n\t\t{% set customer_tax_id = frappe.db.get_value('Customer', doc.customer, 'tax_id') %}\n\t\t{% if customer_tax_id %}\n <tr>\n <td>Customer Tax Identification Number:</td>\n <td>\u0631\u0642\u0645 \u0627\u0644\u062a\u0639\u0631\u064a\u0641 \u0627\u0644\u0636\u0631\u064a\u0628\u064a \u0644\u0644\u0639\u0645\u064a\u0644:</td>\n </tr>\n <tr>\n <td>{{ customer_tax_id }}</td>\n <td>{{ customer_tax_id }}</td>\n </tr>\n {% endif %}\n <tr>\n <td> {{ doc.customer }}</td>\n <td> {{ doc.customer_name_in_arabic }} </td>\n </tr>\n \n {% if(customer_address) %}\n <tr>\n <td>{{ customer_address.address_line1}} </td>\n <td>{{ customer_address.address_in_arabic}} </td>\n </tr>\n {% endif %}\n \n {% if(customer_shipping_address) %}\n <tr>\n <td>SHIPPING ADDRESS:</td>\n <td>\u0639\u0646\u0648\u0627\u0646 \u0627\u0644\u0634\u062d\u0646:</td>\n </tr>\n \n <tr>\n <td>{{ customer_shipping_address.address_line1}} </td>\n <td>{{ customer_shipping_address.address_in_arabic}} </td>\n </tr>\n {% endif %}\n \n\t\t{% if(doc.po_no) %}\n <tr>\n <td>OTHER INFORMATION</td>\n <td>\u0645\u0639\u0644\u0648\u0645\u0627\u062a \u0623\u062e\u0631\u0649</td>\n </tr>\n \n <tr>\n <td>Purchase Order Number: {{ doc.po_no }}</td>\n <td>\u0631\u0642\u0645 \u0623\u0645\u0631 \u0627\u0644\u0634\u0631\u0627\u0621: {{ doc.po_no }}</td>\n </tr>\n {% endif %}\n \n <tr>\n <td>Payment Due Date: {{ doc.due_date}} </td>\n <td>\u062a\u0627\u0631\u064a\u062e \u0627\u0633\u062a\u062d\u0642\u0627\u0642 \u0627\u0644\u062f\u0641\u0639: {{ doc.due_date}}</td>\n </tr>\n </tbody>\n </table>\n\n <!--Dynamic Colspan for total row columns-->\n {% set col = namespace(one = 2, two = 1) %}\n {% set length = doc.taxes | length %}\n {% set length = length / 2 | round %}\n {% set col.one = col.one + length %}\n {% set col.two = col.two + length %}\n \n {%- if(doc.taxes | length % 2 > 0 ) -%}\n {% set col.two = col.two + 1 %}\n {% endif %}\n \n <!-- Items -->\n {% set total = namespace(amount = 0) %}\n <table class=\"ksa-invoice-table\">\n <thead>\n <tr>\n <th>Nature of goods or services <br />\u0637\u0628\u064a\u0639\u0629 \u0627\u0644\u0633\u0644\u0639 \u0623\u0648 \u0627\u0644\u062e\u062f\u0645\u0627\u062a</th>\n <th>\n Unit price <br />\n \u0633\u0639\u0631 \u0627\u0644\u0648\u062d\u062f\u0629\n </th>\n <th>\n Quantity <br />\n \u0627\u0644\u0643\u0645\u064a\u0629\n </th>\n <th>\n Taxable Amount <br />\n \u0627\u0644\u0645\u0628\u0644\u063a \u0627\u0644\u062e\u0627\u0636\u0639 \u0644\u0644\u0636\u0631\u064a\u0628\u0629\n </th>\n \n {% for row in doc.taxes %}\n <th style=\"min-width: 130px\">{{row.description}}</th>\n {% endfor %}\n \n <th>\n Total <br />\n \u0627\u0644\u0645\u062c\u0645\u0648\u0639\n </th>\n </tr>\n </thead>\n <tbody>\n {%- for item in doc.items -%}\n {% set total.amount = item.amount %}\n <tr>\n <td>{{ item.item_code }}</td>\n <td>{{ item.get_formatted(\"rate\") }}</td>\n <td>{{ item.qty }}</td>\n <td>{{ item.get_formatted(\"amount\") }}</td>\n {% for row in doc.taxes %}\n {% set data_object = json.loads(row.item_wise_tax_detail) %}\n {% set tax_amount = frappe.utils.flt(data_object[item.item_code][1]/doc.conversion_rate, row.precision('tax_amount')) %}\n <td>\n <div class=\"qr-flex\">\n {%- if(data_object[item.item_code][0])-%}\n <span>{{ frappe.format(data_object[item.item_code][0], {'fieldtype': 'Percent'}) }}</span>\n {%- endif -%}\n <span>\n {%- if(data_object[item.item_code][1])-%}\n {{ frappe.format_value(tax_amount, currency=doc.currency) }}</span>\n {% set total.amount = total.amount + tax_amount %}\n {%- endif -%}\n </div>\n </td>\n {% endfor %}\n <td>{{ frappe.format_value(frappe.utils.flt(total.amount, doc.precision('total_taxes_and_charges')), currency=doc.currency) }}</td>\n </tr>\n {%- endfor -%}\n </tbody>\n <tfoot>\n <tr>\n <td>\n {{ doc.get_formatted(\"total\") }} <br />\n {{ doc.get_formatted(\"total_taxes_and_charges\") }}\n </td>\n \n <td colspan={{ col.one }} class=\"qr-rtl\">\n \u0627\u0644\u0625\u062c\u0645\u0627\u0644\u064a \u0628\u0627\u0633\u062a\u062b\u0646\u0627\u0621 \u0636\u0631\u064a\u0628\u0629 \u0627\u0644\u0642\u064a\u0645\u0629 \u0627\u0644\u0645\u0636\u0627\u0641\u0629\n <br />\n \u0625\u062c\u0645\u0627\u0644\u064a \u0636\u0631\u064a\u0628\u0629 \u0627\u0644\u0642\u064a\u0645\u0629 \u0627\u0644\u0645\u0636\u0627\u0641\u0629\n </td>\n <td colspan={{ col.two }}>\n Total (Excluding VAT)\n <br />\n Total VAT\n </td>\n <td>\n {{ doc.get_formatted(\"total\") }} <br />\n {{ doc.get_formatted(\"total_taxes_and_charges\") }}\n </td>\n </tr>\n <tr>\n <td>{{ doc.get_formatted(\"grand_total\") }}</td>\n <td colspan={{ col.one }} class=\"qr-rtl\">\n \u0625\u062c\u0645\u0627\u0644\u064a \u0627\u0644\u0645\u0628\u0644\u063a \u0627\u0644\u0645\u0633\u062a\u062d\u0642</td>\n <td colspan={{ col.two }}>Total Amount Due</td>\n <td>{{ doc.get_formatted(\"grand_total\") }}</td>\n </tr>\n </tfoot>\n </table>\n\n\t{%- if doc.terms -%}\n <p>\n {{doc.terms}}\n </p>\n\t{%- endif -%}\n</div>\n",
+ "html": "<div class=\"ksa-vat-format\">\n <div class=\"qr-flex\">\n <div style=\"qr-flex: 1\">\n <h2 class=\"invoice-heading\">TAX INVOICE</h2>\n <h2 class=\"invoice-heading\">\u0641\u0627\u062a\u0648\u0631\u0629 \u0636\u0631\u064a\u0628\u064a\u0629</h2>\n </div>\n \n <img class=\"qr-code\" src={{doc.qr_code}}>\n </div>\n {% set company = frappe.get_doc(\"Company\", doc.company)%}\n {% if (doc.company_address) %}\n {% set supplier_address_doc = frappe.get_doc('Address', doc.company_address) %}\n {% endif %}\n \n {% if(doc.customer_address) %}\n {% set customer_address = frappe.get_doc('Address', doc.customer_address ) %}\n {% endif %}\n \n {% if(doc.shipping_address_name) %}\n {% set customer_shipping_address = frappe.get_doc('Address', doc.shipping_address_name ) %}\n {% endif %} \n \n <table class=\"ksa-invoice-table two-columns\">\n <thead>\n <tr>\n <th>{{ company.name }}</th>\n <th style=\"text-align: right;\">{{ company.company_name_in_arabic }}</th>\n </tr>\n </thead>\n\n <tbody>\n <!-- Invoice Info -->\n <tr>\n <td>Invoice#: {{doc.name}}</td>\n <td>\u0631\u0642\u0645 \u0627\u0644\u0641\u0627\u062a\u0648\u0631\u0629: {{doc.name}}</td>\n </tr>\n <tr>\n <td>Invoice Date: {{doc.posting_date}}</td>\n <td>\u062a\u0627\u0631\u064a\u062e \u0627\u0644\u0641\u0627\u062a\u0648\u0631\u0629: {{doc.posting_date}}</td>\n </tr>\n <tr>\n <td>Date of Supply:{{doc.posting_date}}</td>\n <td>\u062a\u0627\u0631\u064a\u062e \u0627\u0644\u062a\u0648\u0631\u064a\u062f: {{doc.posting_date}}</td>\n </tr>\n \n <!--Supplier Info -->\n <tr>\n <td>Supplier:</td>\n <td>\u0627\u0644\u0645\u0648\u0631\u062f:</td>\n </tr>\n\t\t{% if (company.tax_id) %}\n <tr>\n <td>Supplier Tax Identification Number:</td>\n <td>\u0631\u0642\u0645 \u0627\u0644\u062a\u0639\u0631\u064a\u0641 \u0627\u0644\u0636\u0631\u064a\u0628\u064a \u0644\u0644\u0645\u0648\u0631\u062f:</td>\n </tr>\n <tr>\n <td>{{ company.tax_id }}</td>\n <td>{{ company.tax_id }}</td>\n </tr>\n {% endif %}\n <tr>\n <td>{{ company.name }}</td>\n <td>{{ company.company_name_in_arabic }} </td>\n </tr>\n \n \n {% if(supplier_address_doc) %}\n <tr>\n <td>{{ supplier_address_doc.address_line1}} </td>\n <td>{{ supplier_address_doc.address_in_arabic}} </td>\n </tr>\n <tr>\n <td>Phone: {{ supplier_address_doc.phone }}</td>\n <td>\u0647\u0627\u062a\u0641: {{ supplier_address_doc.phone }}</td>\n </tr>\n <tr>\n <td>Email: {{ supplier_address_doc.email_id }}</td>\n <td>\u0628\u0631\u064a\u062f \u0627\u0644\u0643\u062a\u0631\u0648\u0646\u064a: {{ supplier_address_doc.email_id }}</td>\n </tr>\n {% endif %}\n \n <!-- Customer Info -->\n <tr>\n <td>CUSTOMER:</td>\n <td>\u0639\u0645\u064a\u0644:</td>\n </tr>\n\t\t{% set customer_tax_id = frappe.db.get_value('Customer', doc.customer, 'tax_id') %}\n\t\t{% if customer_tax_id %}\n <tr>\n <td>Customer Tax Identification Number:</td>\n <td>\u0631\u0642\u0645 \u0627\u0644\u062a\u0639\u0631\u064a\u0641 \u0627\u0644\u0636\u0631\u064a\u0628\u064a \u0644\u0644\u0639\u0645\u064a\u0644:</td>\n </tr>\n <tr>\n <td>{{ customer_tax_id }}</td>\n <td>{{ customer_tax_id }}</td>\n </tr>\n {% endif %}\n <tr>\n <td> {{ doc.customer }}</td>\n <td> {{ doc.customer_name_in_arabic }} </td>\n </tr>\n \n {% if(customer_address) %}\n <tr>\n <td>{{ customer_address.address_line1}} </td>\n <td>{{ customer_address.address_in_arabic}} </td>\n </tr>\n {% endif %}\n \n {% if(customer_shipping_address) %}\n <tr>\n <td>SHIPPING ADDRESS:</td>\n <td>\u0639\u0646\u0648\u0627\u0646 \u0627\u0644\u0634\u062d\u0646:</td>\n </tr>\n \n <tr>\n <td>{{ customer_shipping_address.address_line1}} </td>\n <td>{{ customer_shipping_address.address_in_arabic}} </td>\n </tr>\n {% endif %}\n \n\t\t{% if(doc.po_no) %}\n <tr>\n <td>OTHER INFORMATION</td>\n <td>\u0645\u0639\u0644\u0648\u0645\u0627\u062a \u0623\u062e\u0631\u0649</td>\n </tr>\n \n <tr>\n <td>Purchase Order Number: {{ doc.po_no }}</td>\n <td>\u0631\u0642\u0645 \u0623\u0645\u0631 \u0627\u0644\u0634\u0631\u0627\u0621: {{ doc.po_no }}</td>\n </tr>\n {% endif %}\n \n <tr>\n <td>Payment Due Date: {{ doc.due_date}} </td>\n <td>\u062a\u0627\u0631\u064a\u062e \u0627\u0633\u062a\u062d\u0642\u0627\u0642 \u0627\u0644\u062f\u0641\u0639: {{ doc.due_date}}</td>\n </tr>\n </tbody>\n </table>\n\n <!--Dynamic Colspan for total row columns-->\n {% set col = namespace(one = 2, two = 1) %}\n {% set length = doc.taxes | length %}\n {% set length = length / 2 | round %}\n {% set col.one = col.one + length %}\n {% set col.two = col.two + length %}\n \n {%- if(doc.taxes | length % 2 > 0 ) -%}\n {% set col.two = col.two + 1 %}\n {% endif %}\n \n <!-- Items -->\n {% set total = namespace(amount = 0) %}\n <table class=\"ksa-invoice-table\">\n <thead>\n <tr>\n <th>Nature of goods or services <br />\u0637\u0628\u064a\u0639\u0629 \u0627\u0644\u0633\u0644\u0639 \u0623\u0648 \u0627\u0644\u062e\u062f\u0645\u0627\u062a</th>\n <th>\n Unit price <br />\n \u0633\u0639\u0631 \u0627\u0644\u0648\u062d\u062f\u0629\n </th>\n <th>\n Quantity <br />\n \u0627\u0644\u0643\u0645\u064a\u0629\n </th>\n <th>\n Taxable Amount <br />\n \u0627\u0644\u0645\u0628\u0644\u063a \u0627\u0644\u062e\u0627\u0636\u0639 \u0644\u0644\u0636\u0631\u064a\u0628\u0629\n </th>\n \n {% for row in doc.taxes %}\n <th style=\"min-width: 130px\">{{row.description}}</th>\n {% endfor %}\n \n <th>\n Total <br />\n \u0627\u0644\u0645\u062c\u0645\u0648\u0639\n </th>\n </tr>\n </thead>\n <tbody>\n {%- for item in doc.items -%}\n {% set total.amount = item.amount %}\n <tr>\n <td>{{ item.item_code or item.item_name }}</td>\n <td>{{ item.get_formatted(\"rate\") }}</td>\n <td>{{ item.qty }}</td>\n <td>{{ item.get_formatted(\"amount\") }}</td>\n {% for row in doc.taxes %}\n {% set data_object = json.loads(row.item_wise_tax_detail) %}\n {% set key = item.item_code or item.item_name %}\n {% set tax_amount = frappe.utils.flt(data_object[key][1]/doc.conversion_rate, row.precision('tax_amount')) %}\n <td>\n <div class=\"qr-flex\">\n {%- if(data_object[key][0])-%}\n <span>{{ frappe.format(data_object[key][0], {'fieldtype': 'Percent'}) }}</span>\n {%- endif -%}\n <span>\n {%- if(data_object[key][1])-%}\n {{ frappe.format_value(tax_amount, currency=doc.currency) }}</span>\n {% set total.amount = total.amount + tax_amount %}\n {%- endif -%}\n </div>\n </td>\n {% endfor %}\n <td>{{ frappe.format_value(frappe.utils.flt(total.amount, doc.precision('total_taxes_and_charges')), currency=doc.currency) }}</td>\n </tr>\n {%- endfor -%}\n </tbody>\n <tfoot>\n <tr>\n <td>\n {{ doc.get_formatted(\"total\") }} <br />\n {{ doc.get_formatted(\"total_taxes_and_charges\") }}\n </td>\n \n <td colspan={{ col.one }} class=\"qr-rtl\">\n \u0627\u0644\u0625\u062c\u0645\u0627\u0644\u064a \u0628\u0627\u0633\u062a\u062b\u0646\u0627\u0621 \u0636\u0631\u064a\u0628\u0629 \u0627\u0644\u0642\u064a\u0645\u0629 \u0627\u0644\u0645\u0636\u0627\u0641\u0629\n <br />\n \u0625\u062c\u0645\u0627\u0644\u064a \u0636\u0631\u064a\u0628\u0629 \u0627\u0644\u0642\u064a\u0645\u0629 \u0627\u0644\u0645\u0636\u0627\u0641\u0629\n </td>\n <td colspan={{ col.two }}>\n Total (Excluding VAT)\n <br />\n Total VAT\n </td>\n <td>\n {{ doc.get_formatted(\"total\") }} <br />\n {{ doc.get_formatted(\"total_taxes_and_charges\") }}\n </td>\n </tr>\n <tr>\n <td>{{ doc.get_formatted(\"grand_total\") }}</td>\n <td colspan={{ col.one }} class=\"qr-rtl\">\n \u0625\u062c\u0645\u0627\u0644\u064a \u0627\u0644\u0645\u0628\u0644\u063a \u0627\u0644\u0645\u0633\u062a\u062d\u0642</td>\n <td colspan={{ col.two }}>Total Amount Due</td>\n <td>{{ doc.get_formatted(\"grand_total\") }}</td>\n </tr>\n </tfoot>\n </table>\n\n\t{%- if doc.terms -%}\n <p>\n {{doc.terms}}\n </p>\n\t{%- endif -%}\n</div>\n",
"idx": 0,
"line_breaks": 0,
"margin_bottom": 15.0,
"margin_left": 15.0,
"margin_right": 15.0,
"margin_top": 15.0,
- "modified": "2021-11-22 10:40:24.716932",
+ "modified": "2021-11-29 13:47:37.870818",
"modified_by": "Administrator",
"module": "Regional",
"name": "KSA VAT Invoice",
diff --git a/erpnext/regional/report/eway_bill/eway_bill.py b/erpnext/regional/report/eway_bill/eway_bill.py
index 91a4767..f3fe5e8 100644
--- a/erpnext/regional/report/eway_bill/eway_bill.py
+++ b/erpnext/regional/report/eway_bill/eway_bill.py
@@ -106,14 +106,14 @@
row.update({'ship_to_state': row.to_state})
def set_taxes(row, filters):
- taxes = frappe.get_list("Sales Taxes and Charges",
+ taxes = frappe.get_all("Sales Taxes and Charges",
filters={
'parent': row.dn_id
},
fields=('item_wise_tax_detail', 'account_head'))
account_list = ["cgst_account", "sgst_account", "igst_account", "cess_account"]
- taxes_list = frappe.get_list("GST Account",
+ taxes_list = frappe.get_all("GST Account",
filters={
"parent": "GST Settings",
"company": filters.company
diff --git a/erpnext/regional/report/vat_audit_report/vat_audit_report.py b/erpnext/regional/report/vat_audit_report/vat_audit_report.py
index 5a281a4..17e5064 100644
--- a/erpnext/regional/report/vat_audit_report/vat_audit_report.py
+++ b/erpnext/regional/report/vat_audit_report/vat_audit_report.py
@@ -41,7 +41,7 @@
return self.columns, self.data
def get_sa_vat_accounts(self):
- self.sa_vat_accounts = frappe.get_list("South Africa VAT Account",
+ self.sa_vat_accounts = frappe.get_all("South Africa VAT Account",
filters = {"parent": self.filters.company}, pluck="account")
if not self.sa_vat_accounts and not frappe.flags.in_test and not frappe.flags.in_migrate:
link_to_settings = get_link_to_form("South Africa VAT Settings", "", label="South Africa VAT Settings")
diff --git a/erpnext/regional/saudi_arabia/utils.py b/erpnext/regional/saudi_arabia/utils.py
index a2f634e..1051315 100644
--- a/erpnext/regional/saudi_arabia/utils.py
+++ b/erpnext/regional/saudi_arabia/utils.py
@@ -1,7 +1,10 @@
import io
import os
+from base64 import b64encode
import frappe
+from frappe import _
+from frappe.utils.data import add_to_date, get_time, getdate
from pyqrcode import create as qr_create
from erpnext import get_region
@@ -28,24 +31,74 @@
for field in meta.get_image_fields():
if field.fieldname == 'qr_code':
- from urllib.parse import urlencode
+ ''' TLV conversion for
+ 1. Seller's Name
+ 2. VAT Number
+ 3. Time Stamp
+ 4. Invoice Amount
+ 5. VAT Amount
+ '''
+ tlv_array = []
+ # Sellers Name
- # Creating public url to print format
- default_print_format = frappe.db.get_value('Property Setter', dict(property='default_print_format', doc_type=doc.doctype), "value")
+ seller_name = frappe.db.get_value(
+ 'Company',
+ doc.company,
+ 'company_name_in_arabic')
- # System Language
- language = frappe.get_system_settings('language')
+ if not seller_name:
+ frappe.throw(_('Arabic name missing for {} in the company document').format(doc.company))
- params = urlencode({
- 'format': default_print_format or 'Standard',
- '_lang': language,
- 'key': doc.get_signature()
- })
+ tag = bytes([1]).hex()
+ length = bytes([len(seller_name.encode('utf-8'))]).hex()
+ value = seller_name.encode('utf-8').hex()
+ tlv_array.append(''.join([tag, length, value]))
- # creating qr code for the url
- url = f"{ frappe.utils.get_url() }/{ doc.doctype }/{ doc.name }?{ params }"
+ # VAT Number
+ tax_id = frappe.db.get_value('Company', doc.company, 'tax_id')
+ if not tax_id:
+ frappe.throw(_('Tax ID missing for {} in the company document').format(doc.company))
+
+ tag = bytes([2]).hex()
+ length = bytes([len(tax_id)]).hex()
+ value = tax_id.encode('utf-8').hex()
+ tlv_array.append(''.join([tag, length, value]))
+
+ # Time Stamp
+ posting_date = getdate(doc.posting_date)
+ time = get_time(doc.posting_time)
+ seconds = time.hour * 60 * 60 + time.minute * 60 + time.second
+ time_stamp = add_to_date(posting_date, seconds=seconds)
+ time_stamp = time_stamp.strftime('%Y-%m-%dT%H:%M:%SZ')
+
+ tag = bytes([3]).hex()
+ length = bytes([len(time_stamp)]).hex()
+ value = time_stamp.encode('utf-8').hex()
+ tlv_array.append(''.join([tag, length, value]))
+
+ # Invoice Amount
+ invoice_amount = str(doc.total)
+ tag = bytes([4]).hex()
+ length = bytes([len(invoice_amount)]).hex()
+ value = invoice_amount.encode('utf-8').hex()
+ tlv_array.append(''.join([tag, length, value]))
+
+ # VAT Amount
+ vat_amount = str(doc.total_taxes_and_charges)
+
+ tag = bytes([5]).hex()
+ length = bytes([len(vat_amount)]).hex()
+ value = vat_amount.encode('utf-8').hex()
+ tlv_array.append(''.join([tag, length, value]))
+
+ # Joining bytes into one
+ tlv_buff = ''.join(tlv_array)
+
+ # base64 conversion for QR Code
+ base64_string = b64encode(bytes.fromhex(tlv_buff)).decode()
+
qr_image = io.BytesIO()
- url = qr_create(url, error='L')
+ url = qr_create(base64_string, error='L')
url.png(qr_image, scale=2, quiet_zone=1)
# making file
diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py
index 2e2b8b7..0c8c53a 100644
--- a/erpnext/selling/doctype/customer/customer.py
+++ b/erpnext/selling/doctype/customer/customer.py
@@ -18,7 +18,11 @@
from frappe.utils import cint, cstr, flt, get_formatted_email, today
from frappe.utils.user import get_users_with_role
-from erpnext.accounts.party import get_dashboard_info, validate_party_accounts
+from erpnext.accounts.party import ( # noqa
+ get_dashboard_info,
+ get_timeline_data,
+ validate_party_accounts,
+)
from erpnext.utilities.transaction_base import TransactionBase
diff --git a/erpnext/selling/doctype/quotation/quotation.json b/erpnext/selling/doctype/quotation/quotation.json
index ad788e5..ee5b0ea 100644
--- a/erpnext/selling/doctype/quotation/quotation.json
+++ b/erpnext/selling/doctype/quotation/quotation.json
@@ -961,9 +961,7 @@
"idx": 82,
"is_submittable": 1,
"links": [],
- "max_attachments": 1,
- "migration_hash": "75a86a19f062c2257bcbc8e6e31c7f1e",
- "modified": "2021-10-21 12:58:55.514512",
+ "modified": "2021-11-30 01:33:21.106073",
"modified_by": "Administrator",
"module": "Selling",
"name": "Quotation",
diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json
index 7c7ed9a..7e99a06 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.json
+++ b/erpnext/selling/doctype/sales_order/sales_order.json
@@ -134,6 +134,7 @@
"sales_team_section_break",
"sales_partner",
"column_break7",
+ "amount_eligible_for_commission",
"commission_rate",
"total_commission",
"section_break1",
@@ -1507,16 +1508,23 @@
"fieldtype": "Small Text",
"label": "Dispatch Address",
"read_only": 1
+ },
+ {
+ "fieldname": "amount_eligible_for_commission",
+ "fieldtype": "Currency",
+ "label": "Amount Eligible for Commission",
+ "read_only": 1
}
],
"icon": "fa fa-file-text",
"idx": 105,
"is_submittable": 1,
"links": [],
- "modified": "2021-09-28 13:09:51.515542",
+ "modified": "2021-10-05 12:16:40.775704",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Order",
+ "naming_rule": "By \"Naming Series\" field",
"owner": "Administrator",
"permissions": [
{
diff --git a/erpnext/selling/doctype/sales_order_item/sales_order_item.json b/erpnext/selling/doctype/sales_order_item/sales_order_item.json
index 1e5590e..95f6c4e 100644
--- a/erpnext/selling/doctype/sales_order_item/sales_order_item.json
+++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.json
@@ -48,6 +48,7 @@
"pricing_rules",
"stock_uom_rate",
"is_free_item",
+ "grant_commission",
"section_break_24",
"net_rate",
"net_amount",
@@ -789,15 +790,23 @@
"no_copy": 1,
"options": "currency",
"read_only": 1
+ },
+ {
+ "default": "0",
+ "fieldname": "grant_commission",
+ "fieldtype": "Check",
+ "label": "Grant Commission",
+ "read_only": 1
}
],
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2021-02-23 01:15:05.803091",
+ "modified": "2021-10-05 12:27:25.014789",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Order Item",
+ "naming_rule": "Random",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js
index a5b2d50..4920584 100644
--- a/erpnext/selling/page/point_of_sale/pos_item_cart.js
+++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js
@@ -100,6 +100,10 @@
`<div class="add-discount-wrapper">
${this.get_discount_icon()} ${__('Add Discount')}
</div>
+ <div class="item-qty-total-container">
+ <div class="item-qty-total-label">${__('Total Items')}</div>
+ <div class="item-qty-total-value">0.00</div>
+ </div>
<div class="net-total-container">
<div class="net-total-label">${__("Net Total")}</div>
<div class="net-total-value">0.00</div>
@@ -142,6 +146,7 @@
this.$numpad_section.prepend(
`<div class="numpad-totals">
+ <span class="numpad-item-qty-total"></span>
<span class="numpad-net-total"></span>
<span class="numpad-grand-total"></span>
</div>`
@@ -470,6 +475,7 @@
if (!frm) frm = this.events.get_frm();
this.render_net_total(frm.doc.net_total);
+ this.render_total_item_qty(frm.doc.items);
const grand_total = cint(frappe.sys_defaults.disable_rounded_total) ? frm.doc.grand_total : frm.doc.rounded_total;
this.render_grand_total(grand_total);
@@ -487,6 +493,21 @@
);
}
+ render_total_item_qty(items) {
+ var total_item_qty = 0;
+ items.map((item) => {
+ total_item_qty = total_item_qty + item.qty;
+ });
+
+ this.$totals_section.find('.item-qty-total-container').html(
+ `<div>${__('Total Quantity')}</div><div>${total_item_qty}</div>`
+ );
+
+ this.$numpad_section.find('.numpad-item-qty-total').html(
+ `<div>${__('Total Quantity')}: <span>${total_item_qty}</span></div>`
+ );
+ }
+
render_grand_total(value) {
const currency = this.events.get_frm().doc.currency;
this.$totals_section.find('.grand-total-container').html(
diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js
index 2050478..e2e0db4 100644
--- a/erpnext/selling/sales_common.js
+++ b/erpnext/selling/sales_common.js
@@ -157,25 +157,19 @@
commission_rate() {
this.calculate_commission();
- refresh_field("total_commission");
}
total_commission() {
- if(this.frm.doc.base_net_total) {
- frappe.model.round_floats_in(this.frm.doc, ["base_net_total", "total_commission"]);
+ frappe.model.round_floats_in(this.frm.doc, ["amount_eligible_for_commission", "total_commission"]);
- if(this.frm.doc.base_net_total < this.frm.doc.total_commission) {
- var msg = (__("[Error]") + " " +
- __(frappe.meta.get_label(this.frm.doc.doctype, "total_commission",
- this.frm.doc.name)) + " > " +
- __(frappe.meta.get_label(this.frm.doc.doctype, "base_net_total", this.frm.doc.name)));
- frappe.msgprint(msg);
- throw msg;
- }
+ const { amount_eligible_for_commission } = this.frm.doc;
+ if(!amount_eligible_for_commission) return;
- this.frm.set_value("commission_rate",
- flt(this.frm.doc.total_commission * 100.0 / this.frm.doc.base_net_total));
- }
+ this.frm.set_value(
+ "commission_rate", flt(
+ this.frm.doc.total_commission * 100.0 / amount_eligible_for_commission
+ )
+ );
}
allocated_percentage(doc, cdt, cdn) {
@@ -185,7 +179,7 @@
sales_person.allocated_percentage = flt(sales_person.allocated_percentage,
precision("allocated_percentage", sales_person));
- sales_person.allocated_amount = flt(this.frm.doc.base_net_total *
+ sales_person.allocated_amount = flt(this.frm.doc.amount_eligible_for_commission *
sales_person.allocated_percentage / 100.0,
precision("allocated_amount", sales_person));
refresh_field(["allocated_amount"], sales_person);
@@ -259,28 +253,39 @@
}
calculate_commission() {
- if(this.frm.fields_dict.commission_rate) {
- if(this.frm.doc.commission_rate > 100) {
- var msg = __(frappe.meta.get_label(this.frm.doc.doctype, "commission_rate", this.frm.doc.name)) +
- " " + __("cannot be greater than 100");
- frappe.msgprint(msg);
- throw msg;
- }
+ if(!this.frm.fields_dict.commission_rate) return;
- this.frm.doc.total_commission = flt(this.frm.doc.base_net_total * this.frm.doc.commission_rate / 100.0,
- precision("total_commission"));
+ if(this.frm.doc.commission_rate > 100) {
+ this.frm.set_value("commission_rate", 100);
+ frappe.throw(`${__(frappe.meta.get_label(
+ this.frm.doc.doctype, "commission_rate", this.frm.doc.name
+ ))} ${__("cannot be greater than 100")}`);
}
+
+ this.frm.doc.amount_eligible_for_commission = this.frm.doc.items.reduce(
+ (sum, item) => item.grant_commission ? sum + item.base_net_amount : sum, 0
+ )
+
+ this.frm.doc.total_commission = flt(
+ this.frm.doc.amount_eligible_for_commission * this.frm.doc.commission_rate / 100.0,
+ precision("total_commission")
+ );
+
+ refresh_field(["amount_eligible_for_commission", "total_commission"]);
}
calculate_contribution() {
var me = this;
$.each(this.frm.doc.doctype.sales_team || [], function(i, sales_person) {
frappe.model.round_floats_in(sales_person);
- if(sales_person.allocated_percentage) {
- sales_person.allocated_amount = flt(
- me.frm.doc.base_net_total * sales_person.allocated_percentage / 100.0,
- precision("allocated_amount", sales_person));
- }
+ if (!sales_person.allocated_percentage) return;
+
+ sales_person.allocated_amount = flt(
+ me.frm.doc.amount_eligible_for_commission
+ * sales_person.allocated_percentage
+ / 100.0,
+ precision("allocated_amount", sales_person)
+ );
});
}
diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js
index 95ca386..91f60fb 100644
--- a/erpnext/setup/doctype/company/company.js
+++ b/erpnext/setup/doctype/company/company.js
@@ -12,6 +12,10 @@
}
});
}
+
+ frm.call('check_if_transactions_exist').then(r => {
+ frm.toggle_enable("default_currency", (!r.message));
+ });
},
setup: function(frm) {
erpnext.company.setup_queries(frm);
@@ -87,9 +91,6 @@
frappe.dynamic_link = {doc: frm.doc, fieldname: 'name', doctype: 'Company'}
- frm.toggle_enable("default_currency", (frm.doc.__onload &&
- !frm.doc.__onload.transactions_exist));
-
if (frappe.perm.has_perm("Cost Center", 0, 'read')) {
frm.add_custom_button(__('Cost Centers'), function() {
frappe.set_route('Tree', 'Cost Center', {'company': frm.doc.name});
diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py
index dedd2d3..e739739 100644
--- a/erpnext/setup/doctype/company/company.py
+++ b/erpnext/setup/doctype/company/company.py
@@ -22,8 +22,8 @@
def onload(self):
load_address_and_contact(self, "company")
- self.get("__onload")["transactions_exist"] = self.check_if_transactions_exist()
+ @frappe.whitelist()
def check_if_transactions_exist(self):
exists = False
for doctype in ["Sales Invoice", "Delivery Note", "Sales Order", "Quotation",
diff --git a/erpnext/stock/doctype/bin/bin.py b/erpnext/stock/doctype/bin/bin.py
index 48b1cc5..da9c66d 100644
--- a/erpnext/stock/doctype/bin/bin.py
+++ b/erpnext/stock/doctype/bin/bin.py
@@ -4,6 +4,8 @@
import frappe
from frappe.model.document import Document
+from frappe.query_builder import Case
+from frappe.query_builder.functions import Coalesce, Sum
from frappe.utils import flt, nowdate
@@ -19,34 +21,42 @@
- flt(self.reserved_qty_for_production) - flt(self.reserved_qty_for_sub_contract))
def get_first_sle(self):
- sle = frappe.db.sql("""
- select * from `tabStock Ledger Entry`
- where item_code = %s
- and warehouse = %s
- order by timestamp(posting_date, posting_time) asc, creation asc
- limit 1
- """, (self.item_code, self.warehouse), as_dict=1)
- return sle and sle[0] or None
+ sle = frappe.qb.DocType("Stock Ledger Entry")
+ first_sle = (
+ frappe.qb.from_(sle)
+ .select("*")
+ .where((sle.item_code == self.item_code) & (sle.warehouse == self.warehouse))
+ .orderby(sle.posting_date, sle.posting_time, sle.creation)
+ .limit(1)
+ ).run(as_dict=True)
+
+ return first_sle and first_sle[0] or None
def update_reserved_qty_for_production(self):
'''Update qty reserved for production from Production Item tables
in open work orders'''
- self.reserved_qty_for_production = frappe.db.sql('''
- SELECT
- CASE WHEN ifnull(skip_transfer, 0) = 0 THEN
- SUM(item.required_qty - item.transferred_qty)
- ELSE
- SUM(item.required_qty - item.consumed_qty)
- END
- FROM `tabWork Order` pro, `tabWork Order Item` item
- WHERE
- item.item_code = %s
- and item.parent = pro.name
- and pro.docstatus = 1
- and item.source_warehouse = %s
- and pro.status not in ("Stopped", "Completed")
- and (item.required_qty > item.transferred_qty or item.required_qty > item.consumed_qty)
- ''', (self.item_code, self.warehouse))[0][0]
+
+ wo = frappe.qb.DocType("Work Order")
+ wo_item = frappe.qb.DocType("Work Order Item")
+
+ self.reserved_qty_for_production = (
+ frappe.qb
+ .from_(wo)
+ .from_(wo_item)
+ .select(Case()
+ .when(wo.skip_transfer == 0, Sum(wo_item.required_qty - wo_item.transferred_qty))
+ .else_(Sum(wo_item.required_qty - wo_item.consumed_qty))
+ )
+ .where(
+ (wo_item.item_code == self.item_code)
+ & (wo_item.parent == wo.name)
+ & (wo.docstatus == 1)
+ & (wo_item.source_warehouse == self.warehouse)
+ & (wo.status.notin(["Stopped", "Completed"]))
+ & ((wo_item.required_qty > wo_item.transferred_qty)
+ | (wo_item.required_qty > wo_item.consumed_qty))
+ )
+ ).run()[0][0] or 0.0
self.set_projected_qty()
@@ -55,36 +65,53 @@
def update_reserved_qty_for_sub_contracting(self):
#reserved qty
- reserved_qty_for_sub_contract = frappe.db.sql('''
- select ifnull(sum(itemsup.required_qty),0)
- from `tabPurchase Order` po, `tabPurchase Order Item Supplied` itemsup
- where
- itemsup.rm_item_code = %s
- and itemsup.parent = po.name
- and po.docstatus = 1
- and po.is_subcontracted = 'Yes'
- and po.status != 'Closed'
- and po.per_received < 100
- and itemsup.reserve_warehouse = %s''', (self.item_code, self.warehouse))[0][0]
- #Get Transferred Entries
- materials_transferred = frappe.db.sql("""
- select
- ifnull(sum(CASE WHEN se.is_return = 1 THEN (transfer_qty * -1) ELSE transfer_qty END),0)
- from
- `tabStock Entry` se, `tabStock Entry Detail` sed, `tabPurchase Order` po
- where
- se.docstatus=1
- and se.purpose='Send to Subcontractor'
- and ifnull(se.purchase_order, '') !=''
- and (sed.item_code = %(item)s or sed.original_item = %(item)s)
- and se.name = sed.parent
- and se.purchase_order = po.name
- and po.docstatus = 1
- and po.is_subcontracted = 'Yes'
- and po.status != 'Closed'
- and po.per_received < 100
- """, {'item': self.item_code})[0][0]
+ po = frappe.qb.DocType("Purchase Order")
+ supplied_item = frappe.qb.DocType("Purchase Order Item Supplied")
+
+ reserved_qty_for_sub_contract = (
+ frappe.qb
+ .from_(po)
+ .from_(supplied_item)
+ .select(Sum(Coalesce(supplied_item.required_qty, 0)))
+ .where(
+ (supplied_item.rm_item_code == self.item_code)
+ & (po.name == supplied_item.parent)
+ & (po.docstatus == 1)
+ & (po.is_subcontracted == "Yes")
+ & (po.status != "Closed")
+ & (po.per_received < 100)
+ & (supplied_item.reserve_warehouse == self.warehouse)
+ )
+ ).run()[0][0] or 0.0
+
+ se = frappe.qb.DocType("Stock Entry")
+ se_item = frappe.qb.DocType("Stock Entry Detail")
+
+ materials_transferred = (
+ frappe.qb
+ .from_(se)
+ .from_(se_item)
+ .from_(po)
+ .select(Sum(
+ Case()
+ .when(se.is_return == 1, se_item.transfer_qty * -1)
+ .else_(se_item.transfer_qty)
+ ))
+ .where(
+ (se.docstatus == 1)
+ & (se.purpose == "Send to Subcontractor")
+ & (Coalesce(se.purchase_order, "") != "")
+ & ((se_item.item_code == self.item_code)
+ | (se_item.original_item == self.item_code))
+ & (se.name == se_item.parent)
+ & (po.name == se.purchase_order)
+ & (po.docstatus == 1)
+ & (po.is_subcontracted == "Yes")
+ & (po.status != "Closed")
+ & (po.per_received < 100)
+ )
+ ).run()[0][0] or 0.0
if reserved_qty_for_sub_contract > materials_transferred:
reserved_qty_for_sub_contract = reserved_qty_for_sub_contract - materials_transferred
@@ -160,4 +187,4 @@
'indented_qty': indented_qty,
'planned_qty': planned_qty,
'projected_qty': projected_qty
- })
\ No newline at end of file
+ })
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json
index ad1b3b4..55a4c95 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.json
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.json
@@ -145,6 +145,7 @@
"sales_team_section_break",
"sales_partner",
"column_break7",
+ "amount_eligible_for_commission",
"commission_rate",
"total_commission",
"section_break1",
@@ -1302,16 +1303,23 @@
"label": "Dispatch Address",
"print_hide": 1,
"read_only": 1
+ },
+ {
+ "fieldname": "amount_eligible_for_commission",
+ "fieldtype": "Currency",
+ "label": "Amount Eligible for Commission",
+ "read_only": 1
}
],
"icon": "fa fa-truck",
"idx": 146,
"is_submittable": 1,
"links": [],
- "modified": "2021-10-08 14:29:13.428984",
+ "modified": "2021-10-09 14:29:13.428984",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Note",
+ "naming_rule": "By \"Naming Series\" field",
"owner": "Administrator",
"permissions": [
{
diff --git a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
index a96c299..51c88be 100644
--- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
+++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
@@ -49,6 +49,7 @@
"pricing_rules",
"stock_uom_rate",
"is_free_item",
+ "grant_commission",
"section_break_25",
"net_rate",
"net_amount",
@@ -753,13 +754,20 @@
"no_copy": 1,
"options": "currency",
"read_only": 1
+ },
+ {
+ "default": "0",
+ "fieldname": "grant_commission",
+ "fieldtype": "Check",
+ "label": "Grant Commission",
+ "read_only": 1
}
],
"idx": 1,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2021-10-05 12:12:44.018872",
+ "modified": "2021-10-06 12:12:44.018872",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Note Item",
diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json
index 4b314a0..5469a9f 100644
--- a/erpnext/stock/doctype/item/item.json
+++ b/erpnext/stock/doctype/item/item.json
@@ -88,6 +88,7 @@
"sales_details",
"sales_uom",
"is_sales_item",
+ "grant_commission",
"column_break3",
"max_discount",
"deferred_revenue",
@@ -1020,6 +1021,12 @@
"fieldname": "website_image_alt",
"fieldtype": "Data",
"label": "Image Description"
+ },
+ {
+ "default": "1",
+ "fieldname": "grant_commission",
+ "fieldtype": "Check",
+ "label": "Grant Commission"
}
],
"has_web_view": 1,
@@ -1028,8 +1035,7 @@
"image_field": "image",
"index_web_pages_for_search": 1,
"links": [],
- "max_attachments": 1,
- "modified": "2021-10-27 21:04:00.324786",
+ "modified": "2021-11-30 02:33:06.572442",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item",
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index 5daabe8..c9b8a37 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -222,10 +222,11 @@
'route')) + '/' + self.scrub((self.item_name or self.item_code) + '-' + random_string(5))
def validate_website_image(self):
+ """Validate if the website image is a public file"""
+
if frappe.flags.in_import:
return
- """Validate if the website image is a public file"""
auto_set_website_image = False
if not self.website_image and self.image:
auto_set_website_image = True
@@ -255,10 +256,11 @@
self.website_image = None
def make_thumbnail(self):
+ """Make a thumbnail of `website_image`"""
+
if frappe.flags.in_import:
return
- """Make a thumbnail of `website_image`"""
import requests.exceptions
if not self.is_new() and self.website_image != frappe.db.get_value(self.doctype, self.name, "website_image"):
diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py
index 7237178..8b1224b 100644
--- a/erpnext/stock/doctype/item/test_item.py
+++ b/erpnext/stock/doctype/item/test_item.py
@@ -488,7 +488,7 @@
item_doc.save()
# Check values saved correctly
- barcodes = frappe.get_list(
+ barcodes = frappe.get_all(
'Item Barcode',
fields=['barcode', 'barcode_type'],
filters={'parent': item_code})
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json
index b7d1497..3402972 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"autoname": "naming_series:",
"creation": "2013-03-28 10:35:31",
"description": "This tool helps you to update or fix the quantity and valuation of stock in the system. It is typically used to synchronise the system values and what actually exists in your warehouses.",
@@ -153,11 +154,12 @@
"icon": "fa fa-upload-alt",
"idx": 1,
"is_submittable": 1,
- "max_attachments": 1,
- "modified": "2020-04-08 17:02:47.196206",
+ "links": [],
+ "modified": "2021-11-30 01:33:51.437194",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Reconciliation",
+ "naming_rule": "By \"Naming Series\" field",
"owner": "Administrator",
"permissions": [
{
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index e00382b..9889a22 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -299,7 +299,7 @@
"warehouse": warehouse,
"income_account": get_default_income_account(args, item_defaults, item_group_defaults, brand_defaults),
"expense_account": expense_account or get_default_expense_account(args, item_defaults, item_group_defaults, brand_defaults) ,
- "discount_account": None or get_default_discount_account(args, item_defaults),
+ "discount_account": get_default_discount_account(args, item_defaults),
"cost_center": get_default_cost_center(args, item_defaults, item_group_defaults, brand_defaults),
'has_serial_no': item.has_serial_no,
'has_batch_no': item.has_batch_no,
@@ -317,6 +317,7 @@
"net_rate": 0.0,
"net_amount": 0.0,
"discount_percentage": 0.0,
+ "discount_amount": 0.0,
"supplier": get_default_supplier(args, item_defaults, item_group_defaults, brand_defaults),
"update_stock": args.get("update_stock") if args.get('doctype') in ['Sales Invoice', 'Purchase Invoice'] else 0,
"delivered_by_supplier": item.delivered_by_supplier if args.get("doctype") in ["Sales Order", "Sales Invoice"] else 0,
@@ -326,7 +327,8 @@
"against_blanket_order": args.get("against_blanket_order"),
"bom_no": item.get("default_bom"),
"weight_per_unit": args.get("weight_per_unit") or item.get("weight_per_unit"),
- "weight_uom": args.get("weight_uom") or item.get("weight_uom")
+ "weight_uom": args.get("weight_uom") or item.get("weight_uom"),
+ "grant_commission": item.get("grant_commission")
})
if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"):
diff --git a/erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.py b/erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.py
index d3af5f6..4d1491b 100644
--- a/erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.py
+++ b/erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.py
@@ -46,8 +46,8 @@
item_balance.setdefault((item, item_map[item]["item_group"]), [])
total_stock_value = 0.00
for wh in warehouse_list:
- row += [qty_dict.bal_qty] if wh.name in warehouse else [0.00]
- total_stock_value += qty_dict.bal_val if wh.name in warehouse else 0.00
+ row += [qty_dict.bal_qty] if wh.name == warehouse else [0.00]
+ total_stock_value += qty_dict.bal_val if wh.name == warehouse else 0.00
item_balance[(item, item_map[item]["item_group"])].append(row)
item_value.setdefault((item, item_map[item]["item_group"]),[])
diff --git a/erpnext/tests/utils.py b/erpnext/tests/utils.py
index 91df548..fbf2594 100644
--- a/erpnext/tests/utils.py
+++ b/erpnext/tests/utils.py
@@ -2,6 +2,7 @@
# License: GNU General Public License v3. See license.txt
import copy
+import signal
import unittest
from contextlib import contextmanager
from typing import Any, Dict, NewType, Optional
@@ -135,3 +136,23 @@
report_execute_fn(filter_with_optional_param)
return report_data
+
+
+def timeout(seconds=30, error_message="Test timed out."):
+ """ Timeout decorator to ensure a test doesn't run for too long.
+
+ adapted from https://stackoverflow.com/a/2282656"""
+ def decorator(func):
+ def _handle_timeout(signum, frame):
+ raise Exception(error_message)
+
+ def wrapper(*args, **kwargs):
+ signal.signal(signal.SIGALRM, _handle_timeout)
+ signal.alarm(seconds)
+ try:
+ result = func(*args, **kwargs)
+ finally:
+ signal.alarm(0)
+ return result
+ return wrapper
+ return decorator