Merge pull request #27884 from nextchamp-saqib/invalid-gl-entry-patch-fix
fix: patch fails if accounts are frozen
diff --git a/.github/helper/.flake8_strict b/.github/helper/.flake8_strict
index 4c7f5f8..a79137d 100644
--- a/.github/helper/.flake8_strict
+++ b/.github/helper/.flake8_strict
@@ -1,6 +1,8 @@
[flake8]
ignore =
B007,
+ B009,
+ B010,
B950,
E101,
E111,
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 2b3a471..b74d9a6 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -20,6 +20,9 @@
rev: 3.9.2
hooks:
- id: flake8
+ additional_dependencies: [
+ 'flake8-bugbear',
+ ]
args: ['--config', '.github/helper/.flake8_strict']
exclude: ".*setup.py$"
diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py
index f6198eb..605262f 100644
--- a/erpnext/accounts/doctype/account/account.py
+++ b/erpnext/accounts/doctype/account/account.py
@@ -8,6 +8,8 @@
from frappe.utils import cint, cstr
from frappe.utils.nestedset import NestedSet, get_ancestors_of, get_descendants_of
+import erpnext
+
class RootNotEditable(frappe.ValidationError): pass
class BalanceMismatchError(frappe.ValidationError): pass
@@ -196,7 +198,7 @@
"company": company,
# parent account's currency should be passed down to child account's curreny
# if it is None, it picks it up from default company currency, which might be unintended
- "account_currency": self.account_currency,
+ "account_currency": erpnext.get_company_currency(company),
"parent_account": parent_acc_name_map[company]
})
@@ -207,8 +209,7 @@
# update the parent company's value in child companies
doc = frappe.get_doc("Account", child_account)
parent_value_changed = False
- for field in ['account_type', 'account_currency',
- 'freeze_account', 'balance_must_be']:
+ for field in ['account_type', 'freeze_account', 'balance_must_be']:
if doc.get(field) != self.get(field):
parent_value_changed = True
doc.set(field, self.get(field))
diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
index 7d0ecfb..55ea571 100644
--- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
+++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
@@ -174,7 +174,7 @@
"default": "0",
"fieldname": "automatically_fetch_payment_terms",
"fieldtype": "Check",
- "label": "Automatically Fetch Payment Terms"
+ "label": "Automatically Fetch Payment Terms from Order"
},
{
"description": "The percentage you are allowed to bill more against the amount ordered. For example, if the order value is $100 for an item and tolerance is set as 10%, then you are allowed to bill up to $110 ",
@@ -282,7 +282,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
- "modified": "2021-08-19 11:17:38.788054",
+ "modified": "2021-10-11 17:42:36.427699",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",
diff --git a/erpnext/accounts/doctype/loyalty_point_entry/loyalty_point_entry.py b/erpnext/accounts/doctype/loyalty_point_entry/loyalty_point_entry.py
index 0813926..003389e 100644
--- a/erpnext/accounts/doctype/loyalty_point_entry/loyalty_point_entry.py
+++ b/erpnext/accounts/doctype/loyalty_point_entry/loyalty_point_entry.py
@@ -16,7 +16,7 @@
def get_loyalty_point_entries(customer, loyalty_program, company, expiry_date=None):
if not expiry_date:
- date = today()
+ expiry_date = today()
return frappe.db.sql('''
select name, loyalty_points, expiry_date, loyalty_program_tier, invoice_type, invoice
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 8037ca1..0740ccd 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -712,10 +712,14 @@
dr_or_cr = "credit" if erpnext.get_party_account_type(self.party_type) == 'Receivable' else "debit"
for d in self.get("references"):
+ cost_center = self.cost_center
+ if d.reference_doctype == "Sales Invoice" and not cost_center:
+ cost_center = frappe.db.get_value(d.reference_doctype, d.reference_name, "cost_center")
gle = party_gl_dict.copy()
gle.update({
"against_voucher_type": d.reference_doctype,
- "against_voucher": d.reference_name
+ "against_voucher": d.reference_name,
+ "cost_center": cost_center
})
allocated_amount_in_company_currency = flt(flt(d.allocated_amount) * flt(d.exchange_rate),
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js
index b1f3e6f..412833b 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js
@@ -52,21 +52,35 @@
refresh() {
this.frm.disable_save();
+ this.frm.set_df_property('invoices', 'cannot_delete_rows', true);
+ this.frm.set_df_property('payments', 'cannot_delete_rows', true);
+ this.frm.set_df_property('allocation', 'cannot_delete_rows', true);
+
+ this.frm.set_df_property('invoices', 'cannot_add_rows', true);
+ this.frm.set_df_property('payments', 'cannot_add_rows', true);
+ this.frm.set_df_property('allocation', 'cannot_add_rows', true);
+
if (this.frm.doc.receivable_payable_account) {
this.frm.add_custom_button(__('Get Unreconciled Entries'), () =>
this.frm.trigger("get_unreconciled_entries")
);
+ this.frm.change_custom_button_type('Get Unreconciled Entries', null, 'primary');
}
if (this.frm.doc.invoices.length && this.frm.doc.payments.length) {
this.frm.add_custom_button(__('Allocate'), () =>
this.frm.trigger("allocate")
);
+ this.frm.change_custom_button_type('Allocate', null, 'primary');
+ this.frm.change_custom_button_type('Get Unreconciled Entries', null, 'default');
}
if (this.frm.doc.allocation.length) {
this.frm.add_custom_button(__('Reconcile'), () =>
this.frm.trigger("reconcile")
);
+ this.frm.change_custom_button_type('Reconcile', null, 'primary');
+ this.frm.change_custom_button_type('Get Unreconciled Entries', null, 'default');
+ this.frm.change_custom_button_type('Allocate', null, 'default');
}
}
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json
index 9023b36..eb0c20f 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json
@@ -12,15 +12,16 @@
"receivable_payable_account",
"col_break1",
"from_invoice_date",
- "to_invoice_date",
- "minimum_invoice_amount",
- "maximum_invoice_amount",
- "invoice_limit",
- "column_break_13",
"from_payment_date",
- "to_payment_date",
+ "minimum_invoice_amount",
"minimum_payment_amount",
+ "column_break_11",
+ "to_invoice_date",
+ "to_payment_date",
+ "maximum_invoice_amount",
"maximum_payment_amount",
+ "column_break_13",
+ "invoice_limit",
"payment_limit",
"bank_cash_account",
"sec_break1",
@@ -79,6 +80,7 @@
},
{
"depends_on": "eval:(doc.payments).length || (doc.invoices).length",
+ "description": "If you need to reconcile particular transactions against each other, then please select accordingly. If not, all the transactions will be allocated in FIFO order.",
"fieldname": "sec_break1",
"fieldtype": "Section Break",
"label": "Unreconciled Entries"
@@ -163,6 +165,7 @@
"label": "Maximum Payment Amount"
},
{
+ "description": "System will fetch all the entries if limit value is zero.",
"fieldname": "payment_limit",
"fieldtype": "Int",
"label": "Payment Limit"
@@ -171,13 +174,17 @@
"fieldname": "maximum_invoice_amount",
"fieldtype": "Currency",
"label": "Maximum Invoice Amount"
+ },
+ {
+ "fieldname": "column_break_11",
+ "fieldtype": "Column Break"
}
],
"hide_toolbar": 1,
"icon": "icon-resize-horizontal",
"issingle": 1,
"links": [],
- "modified": "2021-08-30 13:05:51.977861",
+ "modified": "2021-10-04 20:27:11.114194",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Reconciliation",
diff --git a/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json b/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json
index b8c65ee..6a21692 100644
--- a/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json
+++ b/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json
@@ -14,8 +14,8 @@
"section_break_6",
"allocated_amount",
"unreconciled_amount",
- "amount",
"column_break_8",
+ "amount",
"is_advance",
"section_break_5",
"difference_amount",
@@ -127,12 +127,13 @@
"fieldname": "reference_row",
"fieldtype": "Data",
"hidden": 1,
- "label": "Reference Row"
+ "label": "Reference Row",
+ "read_only": 1
}
],
"istable": 1,
"links": [],
- "modified": "2021-09-20 17:23:09.455803",
+ "modified": "2021-10-06 11:48:59.616562",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Reconciliation Allocation",
diff --git a/erpnext/accounts/doctype/pos_profile/test_pos_profile.py b/erpnext/accounts/doctype/pos_profile/test_pos_profile.py
index 83ecfb4..7c53f4a 100644
--- a/erpnext/accounts/doctype/pos_profile/test_pos_profile.py
+++ b/erpnext/accounts/doctype/pos_profile/test_pos_profile.py
@@ -33,7 +33,9 @@
frappe.db.sql("delete from `tabPOS Profile`")
-def get_customers_list(pos_profile={}):
+def get_customers_list(pos_profile=None):
+ if pos_profile is None:
+ pos_profile = {}
cond = "1=1"
customer_groups = []
if pos_profile.get('customer_groups'):
diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py
index 12b486e..0637fda 100644
--- a/erpnext/accounts/doctype/pricing_rule/utils.py
+++ b/erpnext/accounts/doctype/pricing_rule/utils.py
@@ -398,7 +398,9 @@
pricing_rules[0].apply_rule_on_other_items = items
return pricing_rules
-def get_qty_amount_data_for_cumulative(pr_doc, doc, items=[]):
+def get_qty_amount_data_for_cumulative(pr_doc, doc, items=None):
+ if items is None:
+ items = []
sum_qty, sum_amt = [0, 0]
doctype = doc.get('parenttype') or doc.doctype
diff --git a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py
index d09f7dc..f5391ca 100644
--- a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py
+++ b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py
@@ -69,7 +69,9 @@
{'promotional_scheme': self.name}):
frappe.delete_doc('Pricing Rule', rule.name)
-def get_pricing_rules(doc, rules = {}):
+def get_pricing_rules(doc, rules=None):
+ if rules is None:
+ rules = {}
new_doc = []
for child_doc, fields in {'price_discount_slabs': price_discount_fields,
'product_discount_slabs': product_discount_fields}.items():
@@ -78,7 +80,9 @@
return new_doc
-def _get_pricing_rules(doc, child_doc, discount_fields, rules = {}):
+def _get_pricing_rules(doc, child_doc, discount_fields, rules=None):
+ if rules is None:
+ rules = {}
new_doc = []
args = get_args_for_pricing_rule(doc)
applicable_for = frappe.scrub(doc.get('applicable_for'))
diff --git a/erpnext/accounts/report/cash_flow/cash_flow.py b/erpnext/accounts/report/cash_flow/cash_flow.py
index d527188..bb8138b 100644
--- a/erpnext/accounts/report/cash_flow/cash_flow.py
+++ b/erpnext/accounts/report/cash_flow/cash_flow.py
@@ -139,9 +139,9 @@
data["total"] = total
return data
-def get_account_type_based_gl_data(company, start_date, end_date, account_type, filters={}):
+def get_account_type_based_gl_data(company, start_date, end_date, account_type, filters=None):
cond = ""
- filters = frappe._dict(filters)
+ filters = frappe._dict(filters or {})
if filters.include_default_book_entries:
company_fb = frappe.db.get_value("Company", company, 'default_finance_book')
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 6a8301a..e24a5f9 100644
--- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js
+++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js
@@ -103,8 +103,11 @@
column.is_tree = true;
}
- value = default_formatter(value, row, column, data);
+ if (data && data.account && column.apply_currency_formatter) {
+ data.currency = erpnext.get_currency(column.company_name);
+ }
+ value = default_formatter(value, row, column, data);
if (!data.parent_account) {
value = $(`<span>${value}</span>`);
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 b0cfbac..a600ead 100644
--- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py
+++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py
@@ -3,12 +3,14 @@
from __future__ import unicode_literals
+from collections import defaultdict
+
import frappe
from frappe import _
from frappe.utils import cint, flt, getdate
+import erpnext
from erpnext.accounts.report.balance_sheet.balance_sheet import (
- check_opening_balance,
get_chart_data,
get_provisional_profit_loss,
)
@@ -31,7 +33,7 @@
from erpnext.accounts.report.profit_and_loss_statement.profit_and_loss_statement import (
get_report_summary as get_pl_summary,
)
-from erpnext.accounts.report.utils import convert_to_presentation_currency
+from erpnext.accounts.report.utils import convert, convert_to_presentation_currency
def execute(filters=None):
@@ -42,7 +44,7 @@
fiscal_year = get_fiscal_year_data(filters.get('from_fiscal_year'), filters.get('to_fiscal_year'))
companies_column, companies = get_companies(filters)
- columns = get_columns(companies_column)
+ columns = get_columns(companies_column, filters)
if filters.get('report') == "Balance Sheet":
data, message, chart, report_summary = get_balance_sheet_data(fiscal_year, companies, columns, filters)
@@ -73,21 +75,24 @@
provisional_profit_loss, total_credit = get_provisional_profit_loss(asset, liability, equity,
companies, filters.get('company'), company_currency, True)
- message, opening_balance = check_opening_balance(asset, liability, equity)
+ message, opening_balance = prepare_companywise_opening_balance(asset, liability, equity, companies)
- if opening_balance and round(opening_balance,2) !=0:
- unclosed ={
+ if opening_balance:
+ unclosed = {
"account_name": "'" + _("Unclosed Fiscal Years Profit / Loss (Credit)") + "'",
"account": "'" + _("Unclosed Fiscal Years Profit / Loss (Credit)") + "'",
"warn_if_negative": True,
"currency": company_currency
}
- for company in companies:
- unclosed[company] = opening_balance
- if provisional_profit_loss:
- provisional_profit_loss[company] = provisional_profit_loss[company] - opening_balance
- unclosed["total"]=opening_balance
+ for company in companies:
+ unclosed[company] = opening_balance.get(company)
+ if provisional_profit_loss and provisional_profit_loss.get(company):
+ provisional_profit_loss[company] = (
+ flt(provisional_profit_loss[company]) - flt(opening_balance.get(company))
+ )
+
+ unclosed["total"] = opening_balance.get(company)
data.append(unclosed)
if provisional_profit_loss:
@@ -102,6 +107,37 @@
return data, message, chart, report_summary
+def prepare_companywise_opening_balance(asset_data, liability_data, equity_data, companies):
+ opening_balance = {}
+ for company in companies:
+ opening_value = 0
+
+ # opening_value = Aseet - liability - equity
+ for data in [asset_data, liability_data, equity_data]:
+ account_name = get_root_account_name(data[0].root_type, company)
+ opening_value += get_opening_balance(account_name, data, company)
+
+ opening_balance[company] = opening_value
+
+ if opening_balance:
+ return _("Previous Financial Year is not closed"), opening_balance
+
+ return '', {}
+
+def get_opening_balance(account_name, data, company):
+ for row in data:
+ if row.get('account_name') == account_name:
+ return row.get('company_wise_opening_bal', {}).get(company, 0.0)
+
+def get_root_account_name(root_type, company):
+ return frappe.get_all(
+ 'Account',
+ fields=['account_name'],
+ filters = {'root_type': root_type, 'is_group': 1,
+ 'company': company, 'parent_account': ('is', 'not set')},
+ as_list=1
+ )[0][0]
+
def get_profit_loss_data(fiscal_year, companies, columns, filters):
income, expense, net_profit_loss = get_income_expense_data(companies, fiscal_year, filters)
company_currency = get_company_currency(filters)
@@ -193,30 +229,37 @@
data["total"] = total
return data
-def get_columns(companies):
- columns = [{
- "fieldname": "account",
- "label": _("Account"),
- "fieldtype": "Link",
- "options": "Account",
- "width": 300
- }]
-
- columns.append({
- "fieldname": "currency",
- "label": _("Currency"),
- "fieldtype": "Link",
- "options": "Currency",
- "hidden": 1
- })
+def get_columns(companies, filters):
+ columns = [
+ {
+ "fieldname": "account",
+ "label": _("Account"),
+ "fieldtype": "Link",
+ "options": "Account",
+ "width": 300
+ }, {
+ "fieldname": "currency",
+ "label": _("Currency"),
+ "fieldtype": "Link",
+ "options": "Currency",
+ "hidden": 1
+ }
+ ]
for company in companies:
+ apply_currency_formatter = 1 if not filters.presentation_currency else 0
+ currency = filters.presentation_currency
+ if not currency:
+ currency = erpnext.get_company_currency(company)
+
columns.append({
"fieldname": company,
- "label": company,
+ "label": f'{company} ({currency})',
"fieldtype": "Currency",
"options": "currency",
- "width": 150
+ "width": 150,
+ "apply_currency_formatter": apply_currency_formatter,
+ "company_name": company
})
return columns
@@ -236,6 +279,8 @@
start_date = filters.period_start_date if filters.report != 'Balance Sheet' else None
end_date = filters.period_end_date
+ filters.end_date = end_date
+
gl_entries_by_account = {}
for root in frappe.db.sql("""select lft, rgt from tabAccount
where root_type=%s and ifnull(parent_account, '') = ''""", root_type, as_dict=1):
@@ -244,9 +289,10 @@
end_date, root.lft, root.rgt, filters,
gl_entries_by_account, accounts_by_name, accounts, ignore_closing_entries=False)
- calculate_values(accounts_by_name, gl_entries_by_account, companies, start_date, filters)
+ calculate_values(accounts_by_name, gl_entries_by_account, companies, filters, fiscal_year)
accumulate_values_into_parents(accounts, accounts_by_name, companies)
- out = prepare_data(accounts, start_date, end_date, balance_must_be, companies, company_currency)
+
+ out = prepare_data(accounts, start_date, end_date, balance_must_be, companies, company_currency, filters)
if out:
add_total_row(out, root_type, balance_must_be, companies, company_currency)
@@ -257,7 +303,10 @@
return (filters.get('presentation_currency')
or frappe.get_cached_value('Company', filters.company, "default_currency"))
-def calculate_values(accounts_by_name, gl_entries_by_account, companies, start_date, filters):
+def calculate_values(accounts_by_name, gl_entries_by_account, companies, filters, fiscal_year):
+ start_date = (fiscal_year.year_start_date
+ if filters.filter_based_on == 'Fiscal Year' else filters.period_start_date)
+
for entries in gl_entries_by_account.values():
for entry in entries:
if entry.account_number:
@@ -266,15 +315,32 @@
account_name = entry.account_name
d = accounts_by_name.get(account_name)
+
if d:
+ debit, credit = 0, 0
for company in companies:
# check if posting date is within the period
if (entry.company == company or (filters.get('accumulated_in_group_company'))
and entry.company in companies.get(company)):
- d[company] = d.get(company, 0.0) + flt(entry.debit) - flt(entry.credit)
+ parent_company_currency = erpnext.get_company_currency(d.company)
+ child_company_currency = erpnext.get_company_currency(entry.company)
+
+ debit, credit = flt(entry.debit), flt(entry.credit)
+
+ if (not filters.get('presentation_currency')
+ and entry.company != company
+ and parent_company_currency != child_company_currency
+ and filters.get('accumulated_in_group_company')):
+ debit = convert(debit, parent_company_currency, child_company_currency, filters.end_date)
+ credit = convert(credit, parent_company_currency, child_company_currency, filters.end_date)
+
+ d[company] = d.get(company, 0.0) + flt(debit) - flt(credit)
+
+ if entry.posting_date < getdate(start_date):
+ d['company_wise_opening_bal'][company] += (flt(debit) - flt(credit))
if entry.posting_date < getdate(start_date):
- d["opening_balance"] = d.get("opening_balance", 0.0) + flt(entry.debit) - flt(entry.credit)
+ d["opening_balance"] = d.get("opening_balance", 0.0) + flt(debit) - flt(credit)
def accumulate_values_into_parents(accounts, accounts_by_name, companies):
"""accumulate children's values in parent accounts"""
@@ -282,17 +348,18 @@
if d.parent_account:
account = d.parent_account_name
- if not accounts_by_name.get(account):
- continue
+ # if not accounts_by_name.get(account):
+ # continue
for company in companies:
accounts_by_name[account][company] = \
accounts_by_name[account].get(company, 0.0) + d.get(company, 0.0)
+ accounts_by_name[account]['company_wise_opening_bal'][company] += d.get('company_wise_opening_bal', {}).get(company, 0.0)
+
accounts_by_name[account]["opening_balance"] = \
accounts_by_name[account].get("opening_balance", 0.0) + d.get("opening_balance", 0.0)
-
def get_account_heads(root_type, companies, filters):
accounts = get_accounts(root_type, filters)
@@ -353,7 +420,7 @@
`tabAccount` where company = %s and root_type = %s
""" , (filters.get('company'), root_type), as_dict=1)
-def prepare_data(accounts, start_date, end_date, balance_must_be, companies, company_currency):
+def prepare_data(accounts, start_date, end_date, balance_must_be, companies, company_currency, filters):
data = []
for d in accounts:
@@ -367,10 +434,13 @@
"parent_account": _(d.parent_account),
"indent": flt(d.indent),
"year_start_date": start_date,
+ "root_type": d.root_type,
"year_end_date": end_date,
- "currency": company_currency,
+ "currency": filters.presentation_currency,
+ "company_wise_opening_bal": d.company_wise_opening_bal,
"opening_balance": d.get("opening_balance", 0.0) * (1 if balance_must_be == "Debit" else -1)
})
+
for company in companies:
if d.get(company) and balance_must_be == "Credit":
# change sign based on Debit or Credit, since calculation is done using (debit - credit)
@@ -385,6 +455,7 @@
row["has_value"] = has_value
row["total"] = total
+
data.append(row)
return data
@@ -447,6 +518,7 @@
'is_group', 'account_name', 'account_number', 'parent_account', 'lft', 'rgt'], as_dict=1)
def validate_entries(key, entry, accounts_by_name, accounts):
+ # If an account present in the child company and not in the parent company
if key not in accounts_by_name:
args = get_account_details(entry.account)
@@ -456,12 +528,23 @@
args.update({
'lft': parent_args.lft + 1,
'rgt': parent_args.rgt - 1,
+ 'indent': 3,
'root_type': parent_args.root_type,
- 'report_type': parent_args.report_type
+ 'report_type': parent_args.report_type,
+ 'parent_account_name': parent_args.account_name,
+ 'company_wise_opening_bal': defaultdict(float)
})
accounts_by_name.setdefault(key, args)
- accounts.append(args)
+
+ idx = len(accounts)
+ # To identify parent account index
+ for index, row in enumerate(accounts):
+ if row.parent_account_name == args.parent_account_name:
+ idx = index
+ break
+
+ accounts.insert(idx+1, args)
def get_additional_conditions(from_date, ignore_closing_entries, filters):
additional_conditions = []
@@ -491,7 +574,6 @@
for company in companies:
total_row.setdefault(company, 0.0)
total_row[company] += row.get(company, 0.0)
- row[company] = 0.0
total_row.setdefault("total", 0.0)
total_row["total"] += flt(row["total"])
@@ -511,6 +593,7 @@
account_name = d.account_number + ' - ' + d.account_name
else:
account_name = d.account_name
+ d['company_wise_opening_bal'] = defaultdict(float)
accounts_by_name[account_name] = d
parent_children_map.setdefault(d.parent_account or None, []).append(d)
diff --git a/erpnext/accounts/workspace/accounting/accounting.json b/erpnext/accounts/workspace/accounting/accounting.json
index 2b26ac5..30ed58b 100644
--- a/erpnext/accounts/workspace/accounting/accounting.json
+++ b/erpnext/accounts/workspace/accounting/accounting.json
@@ -534,6 +534,17 @@
"type": "Link"
},
{
+ "dependencies": "GL Entry",
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "KSA VAT Report",
+ "link_to": "KSA VAT",
+ "link_type": "Report",
+ "onboard": 0,
+ "only_for": "Saudi Arabia",
+ "type": "Link"
+ },
+ {
"hidden": 0,
"is_query_report": 0,
"label": "Financial Statements",
@@ -1154,6 +1165,16 @@
"type": "Link"
},
{
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "KSA VAT Setting",
+ "link_to": "KSA VAT Setting",
+ "link_type": "DocType",
+ "onboard": 0,
+ "only_for": "Saudi Arabia",
+ "type": "Link"
+ },
+ {
"hidden": 0,
"is_query_report": 0,
"label": "Profitability",
@@ -1206,7 +1227,7 @@
"type": "Link"
}
],
- "modified": "2021-08-27 12:15:52.872470",
+ "modified": "2021-08-26 13:15:52.872470",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounting",
diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.js b/erpnext/buying/doctype/buying_settings/buying_settings.js
index 944bb61..32431fc 100644
--- a/erpnext/buying/doctype/buying_settings/buying_settings.js
+++ b/erpnext/buying/doctype/buying_settings/buying_settings.js
@@ -11,7 +11,7 @@
{
fieldname: "supp_master_name",
title: "Supplier Naming By",
- description: __("By default, the Supplier Name is set as per the Supplier Name entered. If you want Suppliers to be named by a ") + "<a href='https://docs.erpnext.com/docs/user/manual/en/setting-up/settings/naming-series' target='_blank'>Naming Series</a>" + __(" choose the 'Naming Series' option."),
+ description: __("By default, the Supplier Name is set as per the Supplier Name entered. If you want Suppliers to be named by a <a href='https://docs.erpnext.com/docs/user/manual/en/setting-up/settings/naming-series' target='_blank'>Naming Series</a> choose the 'Naming Series' option."),
},
{
fieldname: "buying_price_list",
diff --git a/erpnext/buying/form_tour/buying_settings/buying_settings.json b/erpnext/buying/form_tour/buying_settings/buying_settings.json
new file mode 100644
index 0000000..fa8c80d
--- /dev/null
+++ b/erpnext/buying/form_tour/buying_settings/buying_settings.json
@@ -0,0 +1,77 @@
+{
+ "creation": "2021-07-28 11:51:42.319984",
+ "docstatus": 0,
+ "doctype": "Form Tour",
+ "idx": 0,
+ "is_standard": 1,
+ "modified": "2021-10-05 13:06:56.414584",
+ "modified_by": "Administrator",
+ "module": "Buying",
+ "name": "Buying Settings",
+ "owner": "Administrator",
+ "reference_doctype": "Buying Settings",
+ "save_on_complete": 0,
+ "steps": [
+ {
+ "description": "When a Supplier is saved, system generates a unique identity or name for that Supplier which can be used to refer the Supplier in various Buying transactions.",
+ "field": "",
+ "fieldname": "supp_master_name",
+ "fieldtype": "Select",
+ "has_next_condition": 0,
+ "is_table_field": 0,
+ "label": "Supplier Naming By",
+ "parent_field": "",
+ "position": "Bottom",
+ "title": "Supplier Naming By"
+ },
+ {
+ "description": "Configure what should be the default value of Supplier Group when creating a new Supplier.",
+ "field": "",
+ "fieldname": "supplier_group",
+ "fieldtype": "Link",
+ "has_next_condition": 0,
+ "is_table_field": 0,
+ "label": "Default Supplier Group",
+ "parent_field": "",
+ "position": "Right",
+ "title": "Default Supplier Group"
+ },
+ {
+ "description": "Item prices will be fetched from this Price List.",
+ "field": "",
+ "fieldname": "buying_price_list",
+ "fieldtype": "Link",
+ "has_next_condition": 0,
+ "is_table_field": 0,
+ "label": "Default Buying Price List",
+ "parent_field": "",
+ "position": "Bottom",
+ "title": "Default Buying Price List"
+ },
+ {
+ "description": "If this option is configured \"Yes\", ERPNext will prevent you from creating a Purchase Invoice or a Purchase Receipt directly without creating a Purchase Order first.",
+ "field": "",
+ "fieldname": "po_required",
+ "fieldtype": "Select",
+ "has_next_condition": 0,
+ "is_table_field": 0,
+ "label": "Is Purchase Order Required for Purchase Invoice & Receipt Creation?",
+ "parent_field": "",
+ "position": "Bottom",
+ "title": "Purchase Order Required"
+ },
+ {
+ "description": "If this option is configured \"Yes\", ERPNext will prevent you from creating a Purchase Invoice without creating a Purchase Receipt first.",
+ "field": "",
+ "fieldname": "pr_required",
+ "fieldtype": "Select",
+ "has_next_condition": 0,
+ "is_table_field": 0,
+ "label": "Is Purchase Receipt Required for Purchase Invoice Creation?",
+ "parent_field": "",
+ "position": "Bottom",
+ "title": "Purchase Receipt Required"
+ }
+ ],
+ "title": "Buying Settings"
+}
\ No newline at end of file
diff --git a/erpnext/buying/form_tour/purchase_order/purchase_order.json b/erpnext/buying/form_tour/purchase_order/purchase_order.json
new file mode 100644
index 0000000..3cc88fb
--- /dev/null
+++ b/erpnext/buying/form_tour/purchase_order/purchase_order.json
@@ -0,0 +1,82 @@
+{
+ "creation": "2021-07-29 14:11:58.271113",
+ "docstatus": 0,
+ "doctype": "Form Tour",
+ "idx": 0,
+ "is_standard": 1,
+ "modified": "2021-10-05 13:11:31.436135",
+ "modified_by": "Administrator",
+ "module": "Buying",
+ "name": "Purchase Order",
+ "owner": "Administrator",
+ "reference_doctype": "Purchase Order",
+ "save_on_complete": 1,
+ "steps": [
+ {
+ "description": "Select a Supplier",
+ "field": "",
+ "fieldname": "supplier",
+ "fieldtype": "Link",
+ "has_next_condition": 0,
+ "is_table_field": 0,
+ "label": "Supplier",
+ "parent_field": "",
+ "position": "Right",
+ "title": "Supplier"
+ },
+ {
+ "description": "Set the \"Required By\" date for the materials. This sets the \"Required By\" date for all the items.",
+ "field": "",
+ "fieldname": "schedule_date",
+ "fieldtype": "Date",
+ "has_next_condition": 0,
+ "is_table_field": 0,
+ "label": "Required By",
+ "parent_field": "",
+ "position": "Left",
+ "title": "Required By"
+ },
+ {
+ "description": "Items to be purchased can be added here.",
+ "field": "",
+ "fieldname": "items",
+ "fieldtype": "Table",
+ "has_next_condition": 0,
+ "is_table_field": 0,
+ "label": "Items",
+ "parent_field": "",
+ "position": "Bottom",
+ "title": "Items Table"
+ },
+ {
+ "child_doctype": "Purchase Order Item",
+ "description": "Enter the Item Code.",
+ "field": "",
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "has_next_condition": 1,
+ "is_table_field": 1,
+ "label": "Item Code",
+ "next_step_condition": "eval: doc.item_code",
+ "parent_field": "",
+ "parent_fieldname": "items",
+ "position": "Right",
+ "title": "Item Code"
+ },
+ {
+ "child_doctype": "Purchase Order Item",
+ "description": "Enter the required quantity for the material.",
+ "field": "",
+ "fieldname": "qty",
+ "fieldtype": "Float",
+ "has_next_condition": 0,
+ "is_table_field": 1,
+ "label": "Quantity",
+ "parent_field": "",
+ "parent_fieldname": "items",
+ "position": "Bottom",
+ "title": "Quantity"
+ }
+ ],
+ "title": "Purchase Order"
+}
\ No newline at end of file
diff --git a/erpnext/buying/module_onboarding/buying/buying.json b/erpnext/buying/module_onboarding/buying/buying.json
index 887f85b..84e97a2 100644
--- a/erpnext/buying/module_onboarding/buying/buying.json
+++ b/erpnext/buying/module_onboarding/buying/buying.json
@@ -19,7 +19,7 @@
"documentation_url": "https://docs.erpnext.com/docs/user/manual/en/buying",
"idx": 0,
"is_complete": 0,
- "modified": "2020-07-08 14:05:28.273641",
+ "modified": "2021-08-24 18:13:42.463776",
"modified_by": "Administrator",
"module": "Buying",
"name": "Buying",
@@ -29,22 +29,10 @@
"step": "Introduction to Buying"
},
{
- "step": "Create a Supplier"
- },
- {
- "step": "Setup your Warehouse"
- },
- {
- "step": "Create a Product"
- },
- {
"step": "Create a Material Request"
},
{
"step": "Create your first Purchase Order"
- },
- {
- "step": "Buying Settings"
}
],
"subtitle": "Products, Purchases, Analysis, and more.",
diff --git a/erpnext/buying/onboarding_step/create_a_material_request/create_a_material_request.json b/erpnext/buying/onboarding_step/create_a_material_request/create_a_material_request.json
index 9dc493d..28e86ab 100644
--- a/erpnext/buying/onboarding_step/create_a_material_request/create_a_material_request.json
+++ b/erpnext/buying/onboarding_step/create_a_material_request/create_a_material_request.json
@@ -1,19 +1,21 @@
{
- "action": "Create Entry",
+ "action": "Show Form Tour",
+ "action_label": "Let\u2019s create your first Material Request",
"creation": "2020-05-15 14:39:09.818764",
+ "description": "# Track Material Request\n\n\nAlso known as Purchase Request or an Indent, is a document identifying a requirement of a set of items (products or services) for various purposes like procurement, transfer, issue, or manufacturing. Once the Material Request is validated, a purchase manager can take the next actions for purchasing items like requesting RFQ from a supplier or directly placing an order with an identified Supplier.\n\n",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
"is_complete": 0,
- "is_mandatory": 1,
"is_single": 0,
"is_skipped": 0,
- "modified": "2020-05-15 14:39:09.818764",
+ "modified": "2021-08-24 18:08:08.347501",
"modified_by": "Administrator",
"name": "Create a Material Request",
"owner": "Administrator",
"reference_document": "Material Request",
+ "show_form_tour": 1,
"show_full_form": 1,
- "title": "Create a Material Request",
+ "title": "Track Material Request",
"validate_action": 1
}
\ No newline at end of file
diff --git a/erpnext/buying/onboarding_step/create_your_first_purchase_order/create_your_first_purchase_order.json b/erpnext/buying/onboarding_step/create_your_first_purchase_order/create_your_first_purchase_order.json
index 9dbed23..18a3931 100644
--- a/erpnext/buying/onboarding_step/create_your_first_purchase_order/create_your_first_purchase_order.json
+++ b/erpnext/buying/onboarding_step/create_your_first_purchase_order/create_your_first_purchase_order.json
@@ -1,19 +1,21 @@
{
- "action": "Create Entry",
+ "action": "Show Form Tour",
+ "action_label": "Let\u2019s create your first Purchase Order",
"creation": "2020-05-12 18:17:49.976035",
+ "description": "# Create first Purchase Order\n\nPurchase Order is at the heart of your buying transactions. In ERPNext, Purchase Order can can be created against a Purchase Material Request (indent) and Supplier Quotation as well. Purchase Orders is also linked to Purchase Receipt and Purchase Invoices, allowing you to keep a birds-eye view on your purchase deals.\n\n",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
"is_complete": 0,
- "is_mandatory": 0,
"is_single": 0,
"is_skipped": 0,
- "modified": "2020-05-12 18:31:56.856112",
+ "modified": "2021-08-24 18:08:08.936484",
"modified_by": "Administrator",
"name": "Create your first Purchase Order",
"owner": "Administrator",
"reference_document": "Purchase Order",
+ "show_form_tour": 0,
"show_full_form": 0,
- "title": "Create your first Purchase Order",
+ "title": "Create first Purchase Order",
"validate_action": 1
}
\ No newline at end of file
diff --git a/erpnext/buying/onboarding_step/introduction_to_buying/introduction_to_buying.json b/erpnext/buying/onboarding_step/introduction_to_buying/introduction_to_buying.json
index fd98fdd..01ac8b8 100644
--- a/erpnext/buying/onboarding_step/introduction_to_buying/introduction_to_buying.json
+++ b/erpnext/buying/onboarding_step/introduction_to_buying/introduction_to_buying.json
@@ -1,19 +1,22 @@
{
- "action": "Watch Video",
+ "action": "Show Form Tour",
+ "action_label": "Let\u2019s walk-through few Buying Settings",
"creation": "2020-05-06 15:37:09.477765",
+ "description": "# Buying Settings\n\n\nBuying module\u2019s features are highly configurable as per your business needs. Buying Settings is the place where you can set your preferences for:\n\n- Supplier naming and default values\n- Billing and shipping preference in buying transactions\n\n\n",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
"is_complete": 0,
- "is_mandatory": 0,
- "is_single": 0,
+ "is_single": 1,
"is_skipped": 0,
- "modified": "2020-05-12 18:25:08.509900",
+ "modified": "2021-08-24 18:08:08.345735",
"modified_by": "Administrator",
"name": "Introduction to Buying",
"owner": "Administrator",
- "show_full_form": 0,
- "title": "Introduction to Buying",
+ "reference_document": "Buying Settings",
+ "show_form_tour": 1,
+ "show_full_form": 1,
+ "title": "Buying Settings",
"validate_action": 1,
"video_url": "https://youtu.be/efFajTTQBa8"
}
\ No newline at end of file
diff --git a/erpnext/education/doctype/student/student.py b/erpnext/education/doctype/student/student.py
index ae498ba..be4ee56 100644
--- a/erpnext/education/doctype/student/student.py
+++ b/erpnext/education/doctype/student/student.py
@@ -138,7 +138,9 @@
enrollment.submit()
return enrollment
- def enroll_in_course(self, course_name, program_enrollment, enrollment_date=frappe.utils.datetime.datetime.now()):
+ def enroll_in_course(self, course_name, program_enrollment, enrollment_date=None):
+ if enrollment_date is None:
+ enrollment_date = frappe.utils.datetime.datetime.now()
try:
enrollment = frappe.get_doc({
"doctype": "Course Enrollment",
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index d708f92..05f07f5 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -250,6 +250,7 @@
"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",
@@ -259,7 +260,10 @@
"erpnext.regional.italy.utils.sales_invoice_on_cancel",
"erpnext.erpnext_integrations.taxjar_integration.delete_transaction"
],
- "on_trash": "erpnext.regional.check_deletion_permission",
+ "on_trash": [
+ "erpnext.regional.check_deletion_permission",
+ "erpnext.regional.saudi_arabia.utils.delete_qr_code_file"
+ ],
"validate": [
"erpnext.regional.india.utils.validate_document_name",
"erpnext.regional.india.utils.update_taxable_values"
diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.js b/erpnext/hr/doctype/expense_claim/expense_claim.js
index 3c4c672..218e97d 100644
--- a/erpnext/hr/doctype/expense_claim/expense_claim.js
+++ b/erpnext/hr/doctype/expense_claim/expense_claim.js
@@ -10,6 +10,26 @@
},
company: function(frm) {
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
+ var expenses = frm.doc.expenses;
+ for (var i = 0; i < expenses.length; i++) {
+ var expense = expenses[i];
+ if (!expense.expense_type) {
+ continue;
+ }
+ frappe.call({
+ method: "erpnext.hr.doctype.expense_claim.expense_claim.get_expense_claim_account_and_cost_center",
+ args: {
+ "expense_claim_type": expense.expense_type,
+ "company": frm.doc.company
+ },
+ callback: function(r) {
+ if (r.message) {
+ expense.default_account = r.message.account;
+ expense.cost_center = r.message.cost_center;
+ }
+ }
+ });
+ }
},
});
diff --git a/erpnext/hr/doctype/holiday_list/holiday_list.py b/erpnext/hr/doctype/holiday_list/holiday_list.py
index f46f14d..7d1b991 100644
--- a/erpnext/hr/doctype/holiday_list/holiday_list.py
+++ b/erpnext/hr/doctype/holiday_list/holiday_list.py
@@ -1,4 +1,3 @@
-
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
@@ -94,9 +93,11 @@
update={"allDay": 1})
-def is_holiday(holiday_list, date=today()):
+def is_holiday(holiday_list, date=None):
"""Returns true if the given date is a holiday in the given holiday list
"""
+ if date is None:
+ date = today()
if holiday_list:
return bool(frappe.get_all('Holiday List',
dict(name=holiday_list, holiday_date=date)))
diff --git a/erpnext/hr/doctype/shift_assignment/shift_assignment.py b/erpnext/hr/doctype/shift_assignment/shift_assignment.py
index 69af5c5..05b74a0 100644
--- a/erpnext/hr/doctype/shift_assignment/shift_assignment.py
+++ b/erpnext/hr/doctype/shift_assignment/shift_assignment.py
@@ -139,7 +139,7 @@
return shift_timing_map
-def get_employee_shift(employee, for_date=nowdate(), consider_default_shift=False, next_shift_direction=None):
+def get_employee_shift(employee, for_date=None, consider_default_shift=False, next_shift_direction=None):
"""Returns a Shift Type for the given employee on the given date. (excluding the holidays)
:param employee: Employee for which shift is required.
@@ -147,6 +147,8 @@
:param consider_default_shift: If set to true, default shift is taken when no shift assignment is found.
:param next_shift_direction: One of: None, 'forward', 'reverse'. Direction to look for next shift if shift not found on given date.
"""
+ if for_date is None:
+ for_date = nowdate()
default_shift = frappe.db.get_value('Employee', employee, 'default_shift')
shift_type_name = None
shift_assignment_details = frappe.db.get_value('Shift Assignment', {'employee':employee, 'start_date':('<=', for_date), 'docstatus': '1', 'status': "Active"}, ['shift_type', 'end_date'])
@@ -200,9 +202,11 @@
return get_shift_details(shift_type_name, for_date)
-def get_employee_shift_timings(employee, for_timestamp=now_datetime(), consider_default_shift=False):
+def get_employee_shift_timings(employee, for_timestamp=None, consider_default_shift=False):
"""Returns previous shift, current/upcoming shift, next_shift for the given timestamp and employee
"""
+ if for_timestamp is None:
+ for_timestamp = now_datetime()
# write and verify a test case for midnight shift.
prev_shift = curr_shift = next_shift = None
curr_shift = get_employee_shift(employee, for_timestamp.date(), consider_default_shift, 'forward')
@@ -220,7 +224,7 @@
return prev_shift, curr_shift, next_shift
-def get_shift_details(shift_type_name, for_date=nowdate()):
+def get_shift_details(shift_type_name, for_date=None):
"""Returns Shift Details which contain some additional information as described below.
'shift_details' contains the following keys:
'shift_type' - Object of DocType Shift Type,
@@ -234,6 +238,8 @@
"""
if not shift_type_name:
return None
+ if not for_date:
+ for_date = nowdate()
shift_type = frappe.get_doc('Shift Type', shift_type_name)
start_datetime = datetime.combine(for_date, datetime.min.time()) + shift_type.start_time
for_date = for_date + timedelta(days=1) if shift_type.start_time > shift_type.end_time else for_date
diff --git a/erpnext/hr/doctype/staffing_plan/staffing_plan.py b/erpnext/hr/doctype/staffing_plan/staffing_plan.py
index 57a92b0..93cd4e1 100644
--- a/erpnext/hr/doctype/staffing_plan/staffing_plan.py
+++ b/erpnext/hr/doctype/staffing_plan/staffing_plan.py
@@ -155,7 +155,11 @@
return employee_counts
@frappe.whitelist()
-def get_active_staffing_plan_details(company, designation, from_date=getdate(nowdate()), to_date=getdate(nowdate())):
+def get_active_staffing_plan_details(company, designation, from_date=None, to_date=None):
+ if from_date is None:
+ from_date = getdate(nowdate())
+ if to_date is None:
+ to_date = getdate(nowdate())
if not company or not designation:
frappe.throw(_("Please select Company and Designation"))
diff --git a/erpnext/loan_management/doctype/loan/loan.json b/erpnext/loan_management/doctype/loan/loan.json
index c9f23ca..5979992 100644
--- a/erpnext/loan_management/doctype/loan/loan.json
+++ b/erpnext/loan_management/doctype/loan/loan.json
@@ -334,7 +334,6 @@
},
{
"depends_on": "eval:doc.is_secured_loan",
- "fetch_from": "loan_application.maximum_loan_amount",
"fieldname": "maximum_loan_amount",
"fieldtype": "Currency",
"label": "Maximum Loan Amount",
@@ -360,7 +359,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2021-04-19 18:10:32.360818",
+ "modified": "2021-10-12 18:10:32.360818",
"modified_by": "Administrator",
"module": "Loan Management",
"name": "Loan",
diff --git a/erpnext/loan_management/doctype/loan/loan.py b/erpnext/loan_management/doctype/loan/loan.py
index 7dbd422..0f2c3cf 100644
--- a/erpnext/loan_management/doctype/loan/loan.py
+++ b/erpnext/loan_management/doctype/loan/loan.py
@@ -137,16 +137,23 @@
frappe.throw(_("Loan amount is mandatory"))
def link_loan_security_pledge(self):
- if self.is_secured_loan:
- loan_security_pledge = frappe.db.get_value('Loan Security Pledge', {'loan_application': self.loan_application},
- 'name')
+ if self.is_secured_loan and self.loan_application:
+ maximum_loan_value = frappe.db.get_value('Loan Security Pledge',
+ {
+ 'loan_application': self.loan_application,
+ 'status': 'Requested'
+ },
+ 'sum(maximum_loan_value)'
+ )
- if loan_security_pledge:
- frappe.db.set_value('Loan Security Pledge', loan_security_pledge, {
- 'loan': self.name,
- 'status': 'Pledged',
- 'pledge_time': now_datetime()
- })
+ if maximum_loan_value:
+ frappe.db.sql("""
+ UPDATE `tabLoan Security Pledge`
+ SET loan = %s, pledge_time = %s, status = 'Pledged'
+ WHERE status = 'Requested' and loan_application = %s
+ """, (self.name, now_datetime(), self.loan_application))
+
+ self.db_set('maximum_loan_amount', maximum_loan_value)
def unlink_loan_security_pledge(self):
pledges = frappe.get_all('Loan Security Pledge', fields=['name'], filters={'loan': self.name})
diff --git a/erpnext/loan_management/doctype/loan_application/loan_application.py b/erpnext/loan_management/doctype/loan_application/loan_application.py
index e492920..ede0467 100644
--- a/erpnext/loan_management/doctype/loan_application/loan_application.py
+++ b/erpnext/loan_management/doctype/loan_application/loan_application.py
@@ -130,10 +130,11 @@
def create_loan(source_name, target_doc=None, submit=0):
def update_accounts(source_doc, target_doc, source_parent):
account_details = frappe.get_all("Loan Type",
- fields=["mode_of_payment", "payment_account","loan_account", "interest_income_account", "penalty_income_account"],
- filters = {'name': source_doc.loan_type}
- )[0]
+ fields=["mode_of_payment", "payment_account","loan_account", "interest_income_account", "penalty_income_account"],
+ filters = {'name': source_doc.loan_type})[0]
+ if source_doc.is_secured_loan:
+ target_doc.maximum_loan_amount = 0
target_doc.mode_of_payment = account_details.mode_of_payment
target_doc.payment_account = account_details.payment_account
diff --git a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py
index 6d9d4f4..99f0d25 100644
--- a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py
+++ b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py
@@ -198,7 +198,7 @@
security_value = get_total_pledged_security_value(loan)
if loan_details.is_secured_loan and not on_current_security_price:
- security_value = flt(loan_details.maximum_loan_amount)
+ security_value = get_maximum_amount_as_per_pledged_security(loan)
if not security_value and not loan_details.is_secured_loan:
security_value = flt(loan_details.loan_amount)
@@ -209,3 +209,6 @@
disbursal_amount = loan_details.loan_amount - loan_details.disbursed_amount
return disbursal_amount
+
+def get_maximum_amount_as_per_pledged_security(loan):
+ return flt(frappe.db.get_value('Loan Security Pledge', {'loan': loan}, 'sum(maximum_loan_value)'))
diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
index 13b7357..40bb581 100644
--- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
+++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
@@ -411,7 +411,7 @@
if due_date and not final_due_date:
final_due_date = add_days(due_date, loan_type_details.grace_period_in_days)
- if against_loan_doc.status in ('Disbursed', 'Loan Closure Requested', 'Closed'):
+ if against_loan_doc.status in ('Disbursed', 'Closed') or against_loan_doc.disbursed_amount >= against_loan_doc.loan_amount:
pending_principal_amount = against_loan_doc.total_payment - against_loan_doc.total_principal_paid \
- against_loan_doc.total_interest_payable - against_loan_doc.written_off_amount
else:
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index 7cfec97..232e3a0 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -1133,8 +1133,7 @@
query_filters["has_variants"] = 0
if filters and filters.get("is_stock_item"):
- or_cond_filters["is_stock_item"] = 1
- or_cond_filters["has_variants"] = 1
+ query_filters["is_stock_item"] = 1
return frappe.get_list("Item",
fields = fields, filters=query_filters,
diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py
index 706ea26..4c03230 100644
--- a/erpnext/manufacturing/doctype/bom/test_bom.py
+++ b/erpnext/manufacturing/doctype/bom/test_bom.py
@@ -4,13 +4,14 @@
import unittest
from collections import deque
+from functools import partial
import frappe
from frappe.test_runner import make_test_records
from frappe.utils import cstr, flt
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
-from erpnext.manufacturing.doctype.bom.bom import make_variant_bom
+from erpnext.manufacturing.doctype.bom.bom import item_query, make_variant_bom
from erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool import update_cost
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
@@ -375,6 +376,16 @@
# FG Items in Scrap/Loss Table should have Is Process Loss set
self.assertRaises(frappe.ValidationError, bom_doc.submit)
+ def test_bom_item_query(self):
+ query = partial(item_query, doctype="Item", txt="", searchfield="name", start=0, page_len=20, filters={"is_stock_item": 1})
+
+ test_items = query(txt="_Test")
+ filtered = query(txt="_Test Item 2")
+
+ self.assertNotEqual(len(test_items), len(filtered), msg="Item filtering showing excessive results")
+ self.assertTrue(0 < len(filtered) <= 3, msg="Item filtering showing excessive results")
+
+
def get_default_bom(item_code="_Test FG Item 2"):
return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1})
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 5ea666f..f15c65e 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -246,7 +246,7 @@
erpnext.patches.v12_0.add_state_code_for_ladakh
erpnext.patches.v13_0.item_reposting_for_incorrect_sl_and_gl
erpnext.patches.v13_0.delete_old_bank_reconciliation_doctypes
-erpnext.patches.v12_0.update_vehicle_no_reqd_condition
+erpnext.patches.v13_0.update_vehicle_no_reqd_condition
erpnext.patches.v13_0.setup_fields_for_80g_certificate_and_donation
erpnext.patches.v13_0.rename_membership_settings_to_non_profit_settings
erpnext.patches.v13_0.setup_gratuity_rule_for_india_and_uae
@@ -302,4 +302,4 @@
erpnext.patches.v13_0.modify_invalid_gain_loss_gl_entries #2
erpnext.patches.v13_0.fix_additional_cost_in_mfg_stock_entry
erpnext.patches.v13_0.set_status_in_maintenance_schedule_table
-erpnext.patches.v13_0.add_default_interview_notification_templates
\ No newline at end of file
+erpnext.patches.v13_0.add_default_interview_notification_templates
diff --git a/erpnext/patches/v12_0/update_vehicle_no_reqd_condition.py b/erpnext/patches/v13_0/update_vehicle_no_reqd_condition.py
similarity index 81%
rename from erpnext/patches/v12_0/update_vehicle_no_reqd_condition.py
rename to erpnext/patches/v13_0/update_vehicle_no_reqd_condition.py
index 69bfaaa..902707b 100644
--- a/erpnext/patches/v12_0/update_vehicle_no_reqd_condition.py
+++ b/erpnext/patches/v13_0/update_vehicle_no_reqd_condition.py
@@ -2,7 +2,7 @@
def execute():
- frappe.reload_doc('custom', 'doctype', 'custom_field')
+ frappe.reload_doc('custom', 'doctype', 'custom_field', force=True)
company = frappe.get_all('Company', filters = {'country': 'India'})
if not company:
return
diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.json b/erpnext/payroll/doctype/salary_slip/salary_slip.json
index 1974403..7a80e69 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.json
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.json
@@ -329,7 +329,7 @@
{
"fieldname": "earning_deduction",
"fieldtype": "Section Break",
- "label": "Earning & Deduction",
+ "label": "Earnings & Deductions",
"oldfieldtype": "Section Break"
},
{
@@ -380,7 +380,7 @@
"depends_on": "total_loan_repayment",
"fieldname": "loan_repayment",
"fieldtype": "Section Break",
- "label": "Loan repayment"
+ "label": "Loan Repayment"
},
{
"fieldname": "loans",
@@ -425,7 +425,7 @@
{
"fieldname": "net_pay_info",
"fieldtype": "Section Break",
- "label": "net pay info"
+ "label": "Net Pay Info"
},
{
"fieldname": "net_pay",
@@ -647,7 +647,7 @@
"idx": 9,
"is_submittable": 1,
"links": [],
- "modified": "2021-09-01 10:35:52.374549",
+ "modified": "2021-10-08 11:47:47.098248",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Salary Slip",
diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
index 9ed6686..178cd5c 100644
--- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
@@ -141,7 +141,6 @@
create_salary_structure_assignment,
)
- no_of_days = self.get_no_of_days()
# Payroll based on attendance
frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Attendance")
@@ -168,9 +167,6 @@
ss = make_salary_slip_for_payment_days_dependency_test("test_payment_days_based_component@salary.com", salary_structure.name)
self.assertEqual(ss.absent_days, 1)
- days_in_month = no_of_days[0]
- no_of_holidays = no_of_days[1]
-
ss.reload()
payment_days_based_comp_amount = 0
for component in ss.earnings:
@@ -992,13 +988,14 @@
return salary_structure_doc
def make_salary_slip_for_payment_days_dependency_test(employee, salary_structure):
- employee = frappe.db.get_value("Employee", {
- "user_id": employee
- },
+ employee = frappe.db.get_value(
+ "Employee",
+ {"user_id": employee},
["name", "company", "employee_name"],
- as_dict=True)
+ as_dict=True
+ )
- salary_slip_name = frappe.db.get_value("Salary Slip", {"employee": frappe.db.get_value("Employee", {"user_id": employee})})
+ salary_slip_name = frappe.db.get_value("Salary Slip", {"employee": employee.name})
if not salary_slip_name:
salary_slip = make_salary_slip(salary_structure, employee=employee.name)
@@ -1009,4 +1006,4 @@
else:
salary_slip = frappe.get_doc("Salary Slip", salary_slip_name)
- return salary_slip
\ No newline at end of file
+ return salary_slip
diff --git a/erpnext/public/js/help_links.js b/erpnext/public/js/help_links.js
index d0c935f..b643cca 100644
--- a/erpnext/public/js/help_links.js
+++ b/erpnext/public/js/help_links.js
@@ -5,7 +5,7 @@
frappe.help.help_links["Form/Rename Tool"] = [
{
label: "Bulk Rename",
- url: docsUrl + "user/manual/en/setting-up/data/bulk-rename",
+ url: docsUrl + "user/manual/en/using-erpnext/articles/bulk-rename",
},
];
@@ -59,10 +59,23 @@
},
];
-frappe.help.help_links["data-import-tool"] = [
+frappe.help.help_links["Form/Data Import"] = [
{
label: "Importing and Exporting Data",
- url: docsUrl + "user/manual/en/setting-up/data/data-import-tool",
+ url: docsUrl + "user/manual/en/setting-up/data/data-import",
+ },
+ {
+ label: "Overwriting Data from Data Import Tool",
+ url:
+ docsUrl +
+ "user/manual/en/setting-up/articles/overwriting-data-from-data-import-tool",
+ },
+];
+
+frappe.help.help_links["List/Data Import"] = [
+ {
+ label: "Importing and Exporting Data",
+ url: docsUrl + "user/manual/en/setting-up/data/data-import",
},
{
label: "Overwriting Data from Data Import Tool",
@@ -101,14 +114,14 @@
},
];
-frappe.help.help_links["Form/Email Digest"] = [
+frappe.help.help_links["List/Print Heading"] = [
{
- label: "Email Digest",
- url: docsUrl + "user/manual/en/setting-up/email/email-digest",
+ label: "Print Heading",
+ url: docsUrl + "user/manual/en/setting-up/print/print-headings",
},
];
-frappe.help.help_links["List/Print Heading"] = [
+frappe.help.help_links["Form/Print Heading"] = [
{
label: "Print Heading",
url: docsUrl + "user/manual/en/setting-up/print/print-headings",
@@ -153,18 +166,25 @@
frappe.help.help_links["List/Notification"] = [
{
label: "Notification",
- url: docsUrl + "user/manual/en/setting-up/email/notifications",
+ url: docsUrl + "user/manual/en/setting-up/notifications",
},
];
frappe.help.help_links["Form/Notification"] = [
{
label: "Notification",
- url: docsUrl + "user/manual/en/setting-up/email/notifications",
+ url: docsUrl + "user/manual/en/setting-up/notifications",
},
];
-frappe.help.help_links["List/Email Digest"] = [
+frappe.help.help_links["Form/Email Digest"] = [
+ {
+ label: "Email Digest",
+ url: docsUrl + "user/manual/en/setting-up/email/email-digest",
+ },
+];
+
+frappe.help.help_links["Form/Email Digest"] = [
{
label: "Email Digest",
url: docsUrl + "user/manual/en/setting-up/email/email-digest",
@@ -174,7 +194,7 @@
frappe.help.help_links["List/Auto Email Report"] = [
{
label: "Auto Email Reports",
- url: docsUrl + "user/manual/en/setting-up/email/email-reports",
+ url: docsUrl + "user/manual/en/setting-up/email/auto-email-reports",
},
];
@@ -188,14 +208,7 @@
frappe.help.help_links["print-format-builder"] = [
{
label: "Print Format Builder",
- url: docsUrl + "user/manual/en/setting-up/print/print-settings",
- },
-];
-
-frappe.help.help_links["List/Print Heading"] = [
- {
- label: "Print Heading",
- url: docsUrl + "user/manual/en/setting-up/print/print-headings",
+ url: docsUrl + "user/manual/en/setting-up/print/print-format-builder",
},
];
@@ -300,7 +313,7 @@
},
{
label: "Recurring Sales Order",
- url: docsUrl + "user/manual/en/accounts/recurring-orders-and-invoices",
+ url: docsUrl + "user/manual/en/accounts/articles/recurring-orders-and-invoices",
},
{
label: "Applying Discount",
@@ -315,7 +328,7 @@
},
{
label: "Recurring Sales Order",
- url: docsUrl + "user/manual/en/accounts/recurring-orders-and-invoices",
+ url: docsUrl + "user/manual/en/accounts/articles/recurring-orders-and-invoices",
},
{
label: "Applying Discount",
@@ -344,14 +357,14 @@
frappe.help.help_links["Form/Product Bundle"] = [
{
label: "Product Bundle",
- url: docsUrl + "user/manual/en/selling/setup/product-bundle",
+ url: docsUrl + "user/manual/en/selling/product-bundle",
},
];
frappe.help.help_links["Form/Selling Settings"] = [
{
label: "Selling Settings",
- url: docsUrl + "user/manual/en/selling/setup/selling-settings",
+ url: docsUrl + "user/manual/en/selling/selling-settings",
},
];
@@ -397,7 +410,7 @@
},
{
label: "Recurring Purchase Order",
- url: docsUrl + "user/manual/en/accounts/recurring-orders-and-invoices",
+ url: docsUrl + "user/manual/en/accounts/articles/recurring-orders-and-invoices",
},
];
@@ -420,7 +433,7 @@
},
{
label: "Recurring Purchase Order",
- url: docsUrl + "user/manual/en/accounts/recurring-orders-and-invoices",
+ url: docsUrl + "user/manual/en/accounts/articles/recurring-orders-and-invoices",
},
{
label: "Subcontracting",
@@ -435,24 +448,17 @@
},
];
-frappe.help.help_links["List/POS Profile"] = [
- {
- label: "POS Profile",
- url: docsUrl + "user/manual/en/setting-up/pos-setting",
- },
-];
-
frappe.help.help_links["List/Price List"] = [
{
label: "Price List",
- url: docsUrl + "user/manual/en/setting-up/price-lists",
+ url: docsUrl + "user/manual/en/stock/price-lists",
},
];
frappe.help.help_links["List/Authorization Rule"] = [
{
label: "Authorization Rule",
- url: docsUrl + "user/manual/en/setting-up/authorization-rule",
+ url: docsUrl + "user/manual/en/customize-erpnext/authorization-rule",
},
];
@@ -468,27 +474,14 @@
label: "Stock Reconciliation",
url:
docsUrl +
- "user/manual/en/setting-up/stock-reconciliation-for-non-serialized-item",
+ "user/manual/en/stock/stock-reconciliation",
},
];
frappe.help.help_links["Tree/Territory"] = [
{
label: "Territory",
- url: docsUrl + "user/manual/en/setting-up/territory",
- },
-];
-
-frappe.help.help_links["Form/Dropbox Backup"] = [
- {
- label: "Dropbox Backup",
- url: docsUrl + "user/manual/en/setting-up/third-party-backups",
- },
- {
- label: "Setting Up Dropbox Backup",
- url:
- docsUrl +
- "user/manual/en/setting-up/articles/setting-up-dropbox-backups",
+ url: docsUrl + "user/manual/en/selling/territory",
},
];
@@ -502,12 +495,6 @@
url: docsUrl + "user/manual/en/setting-up/company-setup",
},
{
- label: "Managing Multiple Companies",
- url:
- docsUrl +
- "user/manual/en/setting-up/articles/managing-multiple-companies",
- },
- {
label: "Delete All Related Transactions for a Company",
url:
docsUrl +
@@ -517,21 +504,6 @@
//Accounts
-frappe.help.help_links["modules/Accounts"] = [
- {
- label: "Introduction to Accounts",
- url: docsUrl + "user/manual/en/accounts/",
- },
- {
- label: "Chart of Accounts",
- url: docsUrl + "user/manual/en/accounts/chart-of-accounts.html",
- },
- {
- label: "Multi Currency Accounting",
- url: docsUrl + "user/manual/en/accounts/multi-currency-accounting",
- },
-];
-
frappe.help.help_links["Tree/Account"] = [
{
label: "Chart of Accounts",
@@ -552,7 +524,7 @@
},
{
label: "Accounts Opening Balance",
- url: docsUrl + "user/manual/en/accounts/opening-accounts",
+ url: docsUrl + "user/manual/en/accounts/opening-balance",
},
{
label: "Sales Return",
@@ -560,7 +532,7 @@
},
{
label: "Recurring Sales Invoice",
- url: docsUrl + "user/manual/en/accounts/recurring-orders-and-invoices",
+ url: docsUrl + "user/manual/en/accounts/articles/recurring-orders-and-invoices",
},
];
@@ -571,7 +543,7 @@
},
{
label: "Accounts Opening Balance",
- url: docsUrl + "user/manual/en/accounts/opening-accounts",
+ url: docsUrl + "user/manual/en/accounts/opening-balances",
},
{
label: "Sales Return",
@@ -579,21 +551,28 @@
},
{
label: "Recurring Sales Invoice",
- url: docsUrl + "user/manual/en/accounts/recurring-orders-and-invoices",
+ url: docsUrl + "user/manual/en/accounts/articles/recurring-orders-and-invoices",
},
];
-frappe.help.help_links["pos"] = [
+frappe.help.help_links["point-of-sale"] = [
{
label: "Point of Sale Invoice",
- url: docsUrl + "user/manual/en/accounts/point-of-sale-pos-invoice",
+ url: docsUrl + "user/manual/en/accounts/point-of-sales",
},
];
frappe.help.help_links["List/POS Profile"] = [
{
label: "Point of Sale Profile",
- url: docsUrl + "user/manual/en/setting-up/pos-setting",
+ url: docsUrl + "user/manual/en/accounts/pos-profile",
+ },
+];
+
+frappe.help.help_links["Form/POS Profile"] = [
+ {
+ label: "POS Profile",
+ url: docsUrl + "user/manual/en/accounts/pos-profile",
},
];
@@ -604,11 +583,11 @@
},
{
label: "Accounts Opening Balance",
- url: docsUrl + "user/manual/en/accounts/opening-accounts",
+ url: docsUrl + "user/manual/en/accounts/opening-balance",
},
{
label: "Recurring Purchase Invoice",
- url: docsUrl + "user/manual/en/accounts/recurring-orders-and-invoices",
+ url: docsUrl + "user/manual/en/accounts/articles/recurring-orders-and-invoices",
},
];
@@ -623,7 +602,7 @@
},
{
label: "Accounts Opening Balance",
- url: docsUrl + "user/manual/en/accounts/opening-accounts",
+ url: docsUrl + "user/manual/en/accounts/opening-balance",
},
];
@@ -644,7 +623,7 @@
frappe.help.help_links["List/Asset"] = [
{
label: "Managing Fixed Assets",
- url: docsUrl + "user/manual/en/accounts/opening-balance/fixed_assets",
+ url: docsUrl + "user/manual/en/asset",
},
];
@@ -659,6 +638,8 @@
{ label: "Budgeting", url: docsUrl + "user/manual/en/accounts/budgeting" },
];
+//Stock
+
frappe.help.help_links["List/Item"] = [
{ label: "Item", url: docsUrl + "user/manual/en/stock/item" },
{
@@ -676,7 +657,7 @@
},
{
label: "Managing Fixed Assets",
- url: docsUrl + "user/manual/en/accounts/opening-balance/fixed_assets",
+ url: docsUrl + "user/manual/en/asset",
},
{
label: "Item Codification",
@@ -711,7 +692,7 @@
},
{
label: "Managing Fixed Assets",
- url: docsUrl + "user/manual/en/accounts/opening-balance/fixed_assets",
+ url: docsUrl + "user/manual/en/asset",
},
{
label: "Item Codification",
@@ -771,10 +752,6 @@
url:
docsUrl + "user/manual/en/stock/articles/track-items-using-barcode",
},
- {
- label: "Subcontracting",
- url: docsUrl + "user/manual/en/manufacturing/subcontracting",
- },
];
frappe.help.help_links["List/Installation Note"] = [
@@ -784,21 +761,10 @@
},
];
-frappe.help.help_links["Tree"] = [
- {
- label: "Managing Tree Structure Masters",
- url:
- docsUrl +
- "user/manual/en/setting-up/articles/managing-tree-structure-masters",
- },
-];
-
frappe.help.help_links["List/Budget"] = [
{ label: "Budgeting", url: docsUrl + "user/manual/en/accounts/budgeting" },
];
-//Stock
-
frappe.help.help_links["List/Material Request"] = [
{
label: "Material Request",
@@ -861,6 +827,10 @@
{ label: "Serial No", url: docsUrl + "user/manual/en/stock/serial-no" },
];
+frappe.help.help_links["List/Batch"] = [
+ { label: "Batch", url: docsUrl + "user/manual/en/stock/batch" },
+];
+
frappe.help.help_links["Form/Batch"] = [
{ label: "Batch", url: docsUrl + "user/manual/en/stock/batch" },
];
@@ -868,35 +838,35 @@
frappe.help.help_links["Form/Packing Slip"] = [
{
label: "Packing Slip",
- url: docsUrl + "user/manual/en/stock/tools/packing-slip",
+ url: docsUrl + "user/manual/en/stock/packing-slip",
},
];
frappe.help.help_links["Form/Quality Inspection"] = [
{
label: "Quality Inspection",
- url: docsUrl + "user/manual/en/stock/tools/quality-inspection",
+ url: docsUrl + "user/manual/en/stock/quality-inspection",
},
];
frappe.help.help_links["Form/Landed Cost Voucher"] = [
{
label: "Landed Cost Voucher",
- url: docsUrl + "user/manual/en/stock/tools/landed-cost-voucher",
+ url: docsUrl + "user/manual/en/stock/landed-cost-voucher",
},
];
frappe.help.help_links["Tree/Item Group"] = [
{
label: "Item Group",
- url: docsUrl + "user/manual/en/stock/setup/item-group",
+ url: docsUrl + "user/manual/en/stock/item-group",
},
];
frappe.help.help_links["Form/Item Attribute"] = [
{
label: "Item Attribute",
- url: docsUrl + "user/manual/en/stock/setup/item-attribute",
+ url: docsUrl + "user/manual/en/stock/item-attribute",
},
];
@@ -911,7 +881,7 @@
frappe.help.help_links["Form/Stock Reconciliation"] = [
{
label: "Opening Stock Entry",
- url: docsUrl + "user/manual/en/stock/opening-stock",
+ url: docsUrl + "user/manual/en/stock/stock-reconciliation",
},
];
@@ -938,13 +908,13 @@
];
frappe.help.help_links["Form/Campaign"] = [
- { label: "Campaign", url: docsUrl + "user/manual/en/CRM/setup/campaign" },
+ { label: "Campaign", url: docsUrl + "user/manual/en/CRM/campaign" },
];
frappe.help.help_links["Tree/Sales Person"] = [
{
label: "Sales Person",
- url: docsUrl + "user/manual/en/CRM/setup/sales-person",
+ url: docsUrl + "user/manual/en/CRM/sales-person",
},
];
@@ -953,30 +923,13 @@
label: "Sales Person Target",
url:
docsUrl +
- "user/manual/en/selling/setup/sales-person-target-allocation",
+ "user/manual/en/selling/sales-person-target-allocation",
},
-];
-
-//Support
-
-frappe.help.help_links["List/Feedback Trigger"] = [
{
- label: "Feedback Trigger",
- url: docsUrl + "user/manual/en/setting-up/feedback/setting-up-feedback",
- },
-];
-
-frappe.help.help_links["List/Feedback Request"] = [
- {
- label: "Feedback Request",
- url: docsUrl + "user/manual/en/setting-up/feedback/submit-feedback",
- },
-];
-
-frappe.help.help_links["List/Feedback Request"] = [
- {
- label: "Feedback Request",
- url: docsUrl + "user/manual/en/setting-up/feedback/submit-feedback",
+ label: "Sales Person in Transactions",
+ url:
+ docsUrl +
+ "user/manual/en/selling/articles/sales-persons-in-the-sales-transactions",
},
];
@@ -1019,7 +972,7 @@
frappe.help.help_links["Form/BOM Update Tool"] = [
{
label: "BOM Update Tool",
- url: docsUrl + "user/manual/en/manufacturing/tools/bom-update-tool",
+ url: docsUrl + "user/manual/en/manufacturing/bom-update-tool",
},
];
@@ -1036,7 +989,7 @@
},
];
-frappe.help.help_links["Form/Custom Field"] = [
+frappe.help.help_links["List/Custom Field"] = [
{
label: "Custom Field",
url: docsUrl + "user/manual/en/customize-erpnext/custom-field",
diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js
index 7f39b99..0323a42 100755
--- a/erpnext/public/js/utils.js
+++ b/erpnext/public/js/utils.js
@@ -712,6 +712,7 @@
allow_child_item_selection: opts.allow_child_item_selection,
child_fieldname: opts.child_fielname,
child_columns: opts.child_columns,
+ size: opts.size,
action: function(selections, args) {
let values = selections;
if (values.length === 0) {
diff --git a/erpnext/regional/__init__.py b/erpnext/regional/__init__.py
index 45a689e..d7dcbf4 100644
--- a/erpnext/regional/__init__.py
+++ b/erpnext/regional/__init__.py
@@ -31,3 +31,4 @@
"document_name": doc.name,
"data": data
}).insert(ignore_permissions=True)
+
diff --git a/erpnext/regional/doctype/ksa_vat_purchase_account/__init__.py b/erpnext/regional/doctype/ksa_vat_purchase_account/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/regional/doctype/ksa_vat_purchase_account/__init__.py
diff --git a/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.json b/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.json
new file mode 100644
index 0000000..89ba3e9
--- /dev/null
+++ b/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.json
@@ -0,0 +1,49 @@
+{
+ "actions": [],
+ "creation": "2021-07-13 09:17:09.862163",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "title",
+ "item_tax_template",
+ "account"
+ ],
+ "fields": [
+ {
+ "fieldname": "account",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Account",
+ "options": "Account",
+ "reqd": 1
+ },
+ {
+ "fieldname": "title",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Title",
+ "reqd": 1
+ },
+ {
+ "fieldname": "item_tax_template",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Item Tax Template",
+ "options": "Item Tax Template",
+ "reqd": 1
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-08-04 06:42:38.205597",
+ "modified_by": "Administrator",
+ "module": "Regional",
+ "name": "KSA VAT Purchase Account",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.py b/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.py
new file mode 100644
index 0000000..3920bc5
--- /dev/null
+++ b/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2021, Havenir Solutions and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+
+class KSAVATPurchaseAccount(Document):
+ pass
diff --git a/erpnext/regional/doctype/ksa_vat_sales_account/__init__.py b/erpnext/regional/doctype/ksa_vat_sales_account/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/regional/doctype/ksa_vat_sales_account/__init__.py
diff --git a/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.js b/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.js
new file mode 100644
index 0000000..72613f4
--- /dev/null
+++ b/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2021, Havenir Solutions and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('KSA VAT Sales Account', {
+ // refresh: function(frm) {
+
+ // }
+});
diff --git a/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.json b/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.json
new file mode 100644
index 0000000..df27478
--- /dev/null
+++ b/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.json
@@ -0,0 +1,49 @@
+{
+ "actions": [],
+ "creation": "2021-07-13 08:46:33.820968",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "title",
+ "item_tax_template",
+ "account"
+ ],
+ "fields": [
+ {
+ "fieldname": "account",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Account",
+ "options": "Account",
+ "reqd": 1
+ },
+ {
+ "fieldname": "title",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Title",
+ "reqd": 1
+ },
+ {
+ "fieldname": "item_tax_template",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Item Tax Template",
+ "options": "Item Tax Template",
+ "reqd": 1
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-08-04 06:42:00.081407",
+ "modified_by": "Administrator",
+ "module": "Regional",
+ "name": "KSA VAT Sales Account",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.py b/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.py
new file mode 100644
index 0000000..7c2689f
--- /dev/null
+++ b/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2021, Havenir Solutions and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+
+class KSAVATSalesAccount(Document):
+ pass
diff --git a/erpnext/regional/doctype/ksa_vat_sales_account/test_ksa_vat_sales_account.py b/erpnext/regional/doctype/ksa_vat_sales_account/test_ksa_vat_sales_account.py
new file mode 100644
index 0000000..1d6a6a7
--- /dev/null
+++ b/erpnext/regional/doctype/ksa_vat_sales_account/test_ksa_vat_sales_account.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2021, Havenir Solutions and Contributors
+# See license.txt
+
+# import frappe
+import unittest
+
+
+class TestKSAVATSalesAccount(unittest.TestCase):
+ pass
diff --git a/erpnext/regional/doctype/ksa_vat_setting/__init__.py b/erpnext/regional/doctype/ksa_vat_setting/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/regional/doctype/ksa_vat_setting/__init__.py
diff --git a/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.js b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.js
new file mode 100644
index 0000000..00b62b9
--- /dev/null
+++ b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2021, Havenir Solutions and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('KSA VAT Setting', {
+ onload: function () {
+ frappe.breadcrumbs.add('Accounts', 'KSA VAT Setting');
+ }
+});
diff --git a/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.json b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.json
new file mode 100644
index 0000000..3361946
--- /dev/null
+++ b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.json
@@ -0,0 +1,49 @@
+{
+ "actions": [],
+ "autoname": "field:company",
+ "creation": "2021-07-13 08:49:01.100356",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "company",
+ "ksa_vat_sales_accounts",
+ "ksa_vat_purchase_accounts"
+ ],
+ "fields": [
+ {
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Company",
+ "options": "Company",
+ "reqd": 1,
+ "unique": 1
+ },
+ {
+ "fieldname": "ksa_vat_sales_accounts",
+ "fieldtype": "Table",
+ "label": "KSA VAT Sales Accounts",
+ "options": "KSA VAT Sales Account",
+ "reqd": 1
+ },
+ {
+ "fieldname": "ksa_vat_purchase_accounts",
+ "fieldtype": "Table",
+ "label": "KSA VAT Purchase Accounts",
+ "options": "KSA VAT Purchase Account",
+ "reqd": 1
+ }
+ ],
+ "links": [],
+ "modified": "2021-08-26 04:29:06.499378",
+ "modified_by": "Administrator",
+ "module": "Regional",
+ "name": "KSA VAT Setting",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "title_field": "company",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.py b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.py
new file mode 100644
index 0000000..bdae116
--- /dev/null
+++ b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2021, Havenir Solutions and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+
+class KSAVATSetting(Document):
+ pass
diff --git a/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting_list.js b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting_list.js
new file mode 100644
index 0000000..269cbec
--- /dev/null
+++ b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting_list.js
@@ -0,0 +1,5 @@
+frappe.listview_settings['KSA VAT Setting'] = {
+ onload () {
+ frappe.breadcrumbs.add('Accounts');
+ }
+}
\ No newline at end of file
diff --git a/erpnext/regional/doctype/ksa_vat_setting/test_ksa_vat_setting.py b/erpnext/regional/doctype/ksa_vat_setting/test_ksa_vat_setting.py
new file mode 100644
index 0000000..7207901
--- /dev/null
+++ b/erpnext/regional/doctype/ksa_vat_setting/test_ksa_vat_setting.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2021, Havenir Solutions and Contributors
+# See license.txt
+
+# import frappe
+import unittest
+
+
+class TestKSAVATSetting(unittest.TestCase):
+ pass
diff --git a/erpnext/regional/report/ksa_vat/__init__.py b/erpnext/regional/report/ksa_vat/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/regional/report/ksa_vat/__init__.py
diff --git a/erpnext/regional/report/ksa_vat/ksa_vat.js b/erpnext/regional/report/ksa_vat/ksa_vat.js
new file mode 100644
index 0000000..d46d260
--- /dev/null
+++ b/erpnext/regional/report/ksa_vat/ksa_vat.js
@@ -0,0 +1,60 @@
+// Copyright (c) 2016, Havenir Solutions and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["KSA VAT"] = {
+ onload() {
+ frappe.breadcrumbs.add('Accounts');
+ },
+ "filters": [
+ {
+ "fieldname": "company",
+ "label": __("Company"),
+ "fieldtype": "Link",
+ "options": "Company",
+ "reqd": 1,
+ "default": frappe.defaults.get_user_default("Company")
+ },
+ {
+ "fieldname": "from_date",
+ "label": __("From Date"),
+ "fieldtype": "Date",
+ "reqd": 1,
+ "default": frappe.datetime.add_months(frappe.datetime.get_today(), -1),
+ },
+ {
+ "fieldname": "to_date",
+ "label": __("To Date"),
+ "fieldtype": "Date",
+ "reqd": 1,
+ "default": frappe.datetime.get_today()
+ }
+ ],
+ "formatter": function(value, row, column, data, default_formatter) {
+ if (data
+ && (data.title=='VAT on Sales' || data.title=='VAT on Purchases')
+ && data.title==value) {
+ value = $(`<span>${value}</span>`);
+ var $value = $(value).css("font-weight", "bold");
+ value = $value.wrap("<p></p>").parent().html();
+ return value
+ }else if (data.title=='Grand Total'){
+ if (data.title==value) {
+ value = $(`<span>${value}</span>`);
+ var $value = $(value).css("font-weight", "bold");
+ value = $value.wrap("<p></p>").parent().html();
+ return value
+ }else{
+ value = default_formatter(value, row, column, data);
+ value = $(`<span>${value}</span>`);
+ var $value = $(value).css("font-weight", "bold");
+ value = $value.wrap("<p></p>").parent().html();
+ console.log($value)
+ return value
+ }
+ }else{
+ value = default_formatter(value, row, column, data);
+ return value;
+ }
+ },
+};
diff --git a/erpnext/regional/report/ksa_vat/ksa_vat.json b/erpnext/regional/report/ksa_vat/ksa_vat.json
new file mode 100644
index 0000000..036e260
--- /dev/null
+++ b/erpnext/regional/report/ksa_vat/ksa_vat.json
@@ -0,0 +1,32 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2021-07-13 08:54:38.000949",
+ "disable_prepared_report": 1,
+ "disabled": 1,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2021-08-26 04:14:37.202594",
+ "modified_by": "Administrator",
+ "module": "Regional",
+ "name": "KSA VAT",
+ "owner": "Administrator",
+ "prepared_report": 1,
+ "ref_doctype": "GL Entry",
+ "report_name": "KSA VAT",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "System Manager"
+ },
+ {
+ "role": "Accounts Manager"
+ },
+ {
+ "role": "Accounts User"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/regional/report/ksa_vat/ksa_vat.py b/erpnext/regional/report/ksa_vat/ksa_vat.py
new file mode 100644
index 0000000..a42ebc9
--- /dev/null
+++ b/erpnext/regional/report/ksa_vat/ksa_vat.py
@@ -0,0 +1,176 @@
+# Copyright (c) 2013, Havenir Solutions and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+
+import json
+
+import frappe
+from frappe import _
+from frappe.utils import get_url_to_list
+
+
+def execute(filters=None):
+ columns = columns = get_columns()
+ data = get_data(filters)
+ return columns, data
+
+def get_columns():
+ return [
+ {
+ "fieldname": "title",
+ "label": _("Title"),
+ "fieldtype": "Data",
+ "width": 300
+ },
+ {
+ "fieldname": "amount",
+ "label": _("Amount (SAR)"),
+ "fieldtype": "Currency",
+ "width": 150,
+ },
+ {
+ "fieldname": "adjustment_amount",
+ "label": _("Adjustment (SAR)"),
+ "fieldtype": "Currency",
+ "width": 150,
+ },
+ {
+ "fieldname": "vat_amount",
+ "label": _("VAT Amount (SAR)"),
+ "fieldtype": "Currency",
+ "width": 150,
+ }
+ ]
+
+def get_data(filters):
+ data = []
+
+ # Validate if vat settings exist
+ company = filters.get('company')
+ if frappe.db.exists('KSA VAT Setting', company) is None:
+ url = get_url_to_list('KSA VAT Setting')
+ frappe.msgprint(_('Create <a href="{}">KSA VAT Setting</a> for this company').format(url))
+ return data
+
+ ksa_vat_setting = frappe.get_doc('KSA VAT Setting', company)
+
+ # Sales Heading
+ append_data(data, 'VAT on Sales', '', '', '')
+
+ grand_total_taxable_amount = 0
+ grand_total_taxable_adjustment_amount = 0
+ grand_total_tax = 0
+
+ for vat_setting in ksa_vat_setting.ksa_vat_sales_accounts:
+ total_taxable_amount, total_taxable_adjustment_amount, \
+ total_tax = get_tax_data_for_each_vat_setting(vat_setting, filters, 'Sales Invoice')
+
+ # Adding results to data
+ append_data(data, vat_setting.title, total_taxable_amount,
+ total_taxable_adjustment_amount, total_tax)
+
+ grand_total_taxable_amount += total_taxable_amount
+ grand_total_taxable_adjustment_amount += total_taxable_adjustment_amount
+ grand_total_tax += total_tax
+
+ # Sales Grand Total
+ append_data(data, 'Grand Total', grand_total_taxable_amount,
+ grand_total_taxable_adjustment_amount, grand_total_tax)
+
+ # Blank Line
+ append_data(data, '', '', '', '')
+
+ # Purchase Heading
+ append_data(data, 'VAT on Purchases', '', '', '')
+
+ grand_total_taxable_amount = 0
+ grand_total_taxable_adjustment_amount = 0
+ grand_total_tax = 0
+
+ for vat_setting in ksa_vat_setting.ksa_vat_purchase_accounts:
+ total_taxable_amount, total_taxable_adjustment_amount, \
+ total_tax = get_tax_data_for_each_vat_setting(vat_setting, filters, 'Purchase Invoice')
+
+ # Adding results to data
+ append_data(data, vat_setting.title, total_taxable_amount,
+ total_taxable_adjustment_amount, total_tax)
+
+ grand_total_taxable_amount += total_taxable_amount
+ grand_total_taxable_adjustment_amount += total_taxable_adjustment_amount
+ grand_total_tax += total_tax
+
+ # Purchase Grand Total
+ append_data(data, 'Grand Total', grand_total_taxable_amount,
+ grand_total_taxable_adjustment_amount, grand_total_tax)
+
+ return data
+
+def get_tax_data_for_each_vat_setting(vat_setting, filters, doctype):
+ '''
+ (KSA, {filters}, 'Sales Invoice') => 500, 153, 10 \n
+ calculates and returns \n
+ total_taxable_amount, total_taxable_adjustment_amount, total_tax'''
+ from_date = filters.get('from_date')
+ to_date = filters.get('to_date')
+
+ # Initiate variables
+ total_taxable_amount = 0
+ total_taxable_adjustment_amount = 0
+ total_tax = 0
+ # Fetch All Invoices
+ invoices = frappe.get_list(doctype,
+ filters ={
+ 'docstatus': 1,
+ 'posting_date': ['between', [from_date, to_date]]
+ }, fields =['name', 'is_return'])
+
+ for invoice in invoices:
+ invoice_items = frappe.get_list(f'{doctype} Item',
+ filters ={
+ 'docstatus': 1,
+ 'parent': invoice.name,
+ 'item_tax_template': vat_setting.item_tax_template
+ }, fields =['item_code', 'net_amount'])
+
+ for item in invoice_items:
+ # Summing up total taxable amount
+ if invoice.is_return == 0:
+ total_taxable_amount += item.net_amount
+
+ if invoice.is_return == 1:
+ total_taxable_adjustment_amount += item.net_amount
+
+ # Summing up total tax
+ total_tax += get_tax_amount(item.item_code, vat_setting.account, doctype, invoice.name)
+
+ return total_taxable_amount, total_taxable_adjustment_amount, total_tax
+
+
+
+def append_data(data, title, amount, adjustment_amount, vat_amount):
+ """Returns data with appended value."""
+ data.append({"title": _(title), "amount": amount, "adjustment_amount": adjustment_amount, "vat_amount": vat_amount})
+
+def get_tax_amount(item_code, account_head, doctype, parent):
+ if doctype == 'Sales Invoice':
+ tax_doctype = 'Sales Taxes and Charges'
+
+ elif doctype == 'Purchase Invoice':
+ tax_doctype = 'Purchase Taxes and Charges'
+
+ item_wise_tax_detail = frappe.get_value(tax_doctype, {
+ 'docstatus': 1,
+ 'parent': parent,
+ 'account_head': account_head
+ }, 'item_wise_tax_detail')
+
+ tax_amount = 0
+ if item_wise_tax_detail and len(item_wise_tax_detail) > 0:
+ item_wise_tax_detail = json.loads(item_wise_tax_detail)
+ for key, value in item_wise_tax_detail.items():
+ if key == item_code:
+ tax_amount = value[1]
+ break
+
+ return tax_amount
diff --git a/erpnext/regional/saudi_arabia/setup.py b/erpnext/regional/saudi_arabia/setup.py
index 9b3677d..6113f48 100644
--- a/erpnext/regional/saudi_arabia/setup.py
+++ b/erpnext/regional/saudi_arabia/setup.py
@@ -2,10 +2,36 @@
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
-
-from erpnext.regional.united_arab_emirates.setup import make_custom_fields, add_print_formats
-
+import frappe
+from frappe.permissions import add_permission, update_permission_property
+from erpnext.regional.united_arab_emirates.setup import make_custom_fields as uae_custom_fields, add_print_formats
+from erpnext.regional.saudi_arabia.wizard.operations.setup_ksa_vat_setting import create_ksa_vat_setting
+from frappe.custom.doctype.custom_field.custom_field import create_custom_field
def setup(company=None, patch=True):
- make_custom_fields()
+ uae_custom_fields()
add_print_formats()
+ add_permissions()
+ create_ksa_vat_setting(company)
+ make_qrcode_field()
+
+def add_permissions():
+ """Add Permissions for KSA VAT Setting."""
+ add_permission('KSA VAT Setting', 'All', 0)
+ for role in ('Accounts Manager', 'Accounts User', 'System Manager'):
+ add_permission('KSA VAT Setting', role, 0)
+ update_permission_property('KSA VAT Setting', role, 0, 'write', 1)
+ update_permission_property('KSA VAT Setting', role, 0, 'create', 1)
+
+ """Enable KSA VAT Report"""
+ frappe.db.set_value('Report', 'KSA VAT', 'disabled', 0)
+
+def make_qrcode_field():
+ """Created QR code Image file"""
+ qr_code_field = dict(
+ fieldname='qr_code',
+ label='QR Code',
+ fieldtype='Attach Image',
+ read_only=1, no_copy=1, hidden=1)
+
+ create_custom_field('Sales Invoice', qr_code_field)
diff --git a/erpnext/regional/saudi_arabia/utils.py b/erpnext/regional/saudi_arabia/utils.py
new file mode 100644
index 0000000..cc6c0af
--- /dev/null
+++ b/erpnext/regional/saudi_arabia/utils.py
@@ -0,0 +1,77 @@
+import io
+import os
+
+import frappe
+from pyqrcode import create as qr_create
+
+from erpnext import get_region
+
+
+def create_qr_code(doc, method):
+ """Create QR Code after inserting Sales Inv
+ """
+
+ region = get_region(doc.company)
+ if region not in ['Saudi Arabia']:
+ return
+
+ # if QR Code field not present, do nothing
+ if not hasattr(doc, 'qr_code'):
+ return
+
+ # Don't create QR Code if it already exists
+ qr_code = doc.get("qr_code")
+ if qr_code and frappe.db.exists({"doctype": "File", "file_url": qr_code}):
+ return
+
+ meta = frappe.get_meta('Sales Invoice')
+
+ for field in meta.get_image_fields():
+ if field.fieldname == 'qr_code':
+ # 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")
+
+ # System Language
+ language = frappe.get_system_settings('language')
+
+ # creating qr code for the url
+ url = f"{ frappe.utils.get_url() }/{ doc.doctype }/{ doc.name }?format={ default_print_format or 'Standard' }&_lang={ language }&key={ doc.get_signature() }"
+ qr_image = io.BytesIO()
+ url = qr_create(url, error='L')
+ url.png(qr_image, scale=2, quiet_zone=1)
+
+ # making file
+ filename = f"QR-CODE-{doc.name}.png".replace(os.path.sep, "__")
+ _file = frappe.get_doc({
+ "doctype": "File",
+ "file_name": filename,
+ "is_private": 0,
+ "content": qr_image.getvalue(),
+ "attached_to_doctype": doc.get("doctype"),
+ "attached_to_name": doc.get("name"),
+ "attached_to_field": "qr_code"
+ })
+
+ _file.save()
+
+ # assigning to document
+ doc.db_set('qr_code', _file.file_url)
+ doc.notify_update()
+
+ break
+
+
+def delete_qr_code_file(doc, method):
+ """Delete QR Code on deleted sales invoice"""
+
+ region = get_region(doc.company)
+ if region not in ['Saudi Arabia']:
+ return
+
+ if hasattr(doc, 'qr_code'):
+ if doc.get('qr_code'):
+ file_doc = frappe.get_list('File', {
+ 'file_url': doc.get('qr_code')
+ })
+ if len(file_doc):
+ frappe.delete_doc('File', file_doc[0].name)
\ No newline at end of file
diff --git a/erpnext/regional/saudi_arabia/wizard/__init__.py b/erpnext/regional/saudi_arabia/wizard/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/regional/saudi_arabia/wizard/__init__.py
diff --git a/erpnext/regional/saudi_arabia/wizard/data/__init__.py b/erpnext/regional/saudi_arabia/wizard/data/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/regional/saudi_arabia/wizard/data/__init__.py
diff --git a/erpnext/regional/saudi_arabia/wizard/data/ksa_vat_settings.json b/erpnext/regional/saudi_arabia/wizard/data/ksa_vat_settings.json
new file mode 100644
index 0000000..709d65b
--- /dev/null
+++ b/erpnext/regional/saudi_arabia/wizard/data/ksa_vat_settings.json
@@ -0,0 +1,47 @@
+[
+ {
+ "type": "Sales Account",
+ "accounts": [
+ {
+ "title": "Standard rated Sales",
+ "item_tax_template": "KSA VAT 5%",
+ "account": "VAT 5%"
+ },
+ {
+ "title": "Zero rated domestic sales",
+ "item_tax_template": "KSA VAT Zero",
+ "account": "VAT Zero"
+ },
+ {
+ "title": "Exempted sales",
+ "item_tax_template": "KSA VAT Exempted",
+ "account": "VAT Zero"
+ }
+ ]
+ },
+ {
+ "type": "Purchase Account",
+ "accounts": [
+ {
+ "title": "Standard rated domestic purchases",
+ "item_tax_template": "KSA VAT 5%",
+ "account": "VAT 5%"
+ },
+ {
+ "title": "Imports subject to VAT paid at customs",
+ "item_tax_template": "KSA Excise 50%",
+ "account": "Excise 50%"
+ },
+ {
+ "title": "Zero rated purchases",
+ "item_tax_template": "KSA VAT Zero",
+ "account": "VAT Zero"
+ },
+ {
+ "title": "Exempted purchases",
+ "item_tax_template": "KSA VAT Exempted",
+ "account": "VAT Zero"
+ }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/erpnext/regional/saudi_arabia/wizard/operations/__init__.py b/erpnext/regional/saudi_arabia/wizard/operations/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/regional/saudi_arabia/wizard/operations/__init__.py
diff --git a/erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py b/erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py
new file mode 100644
index 0000000..3c89edd
--- /dev/null
+++ b/erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py
@@ -0,0 +1,46 @@
+import json
+import os
+
+import frappe
+
+from erpnext.setup.setup_wizard.operations.taxes_setup import setup_taxes_and_charges
+
+
+def create_ksa_vat_setting(company):
+ """On creation of first company. Creates KSA VAT Setting"""
+
+ company = frappe.get_doc('Company', company)
+ setup_taxes_and_charges(company.name, company.country)
+
+ file_path = os.path.join(os.path.dirname(__file__), '..', 'data', 'ksa_vat_settings.json')
+ with open(file_path, 'r') as json_file:
+ account_data = json.load(json_file)
+
+ # Creating KSA VAT Setting
+ ksa_vat_setting = frappe.get_doc({
+ 'doctype': 'KSA VAT Setting',
+ 'company': company.name
+ })
+
+ for data in account_data:
+ if data['type'] == 'Sales Account':
+ for row in data['accounts']:
+ item_tax_template = row['item_tax_template']
+ account = row['account']
+ ksa_vat_setting.append('ksa_vat_sales_accounts', {
+ 'title': row['title'],
+ 'item_tax_template': f'{item_tax_template} - {company.abbr}',
+ 'account': f'{account} - {company.abbr}'
+ })
+
+ elif data['type'] == 'Purchase Account':
+ for row in data['accounts']:
+ item_tax_template = row['item_tax_template']
+ account = row['account']
+ ksa_vat_setting.append('ksa_vat_purchase_accounts', {
+ 'title': row['title'],
+ 'item_tax_template': f'{item_tax_template} - {company.abbr}',
+ 'account': f'{account} - {company.abbr}'
+ })
+
+ ksa_vat_setting.save()
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index 9367609..dcf478b 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -110,7 +110,7 @@
if self.order_type == 'Sales' and not self.skip_delivery_note:
delivery_date_list = [d.delivery_date for d in self.get("items") if d.delivery_date]
max_delivery_date = max(delivery_date_list) if delivery_date_list else None
- if not self.delivery_date:
+ if (max_delivery_date and not self.delivery_date) or (max_delivery_date and getdate(self.delivery_date) != getdate(max_delivery_date)):
self.delivery_date = max_delivery_date
if self.delivery_date:
for d in self.get("items"):
@@ -119,8 +119,6 @@
if getdate(self.transaction_date) > getdate(d.delivery_date):
frappe.msgprint(_("Expected Delivery Date should be after Sales Order Date"),
indicator='orange', title=_('Warning'))
- if getdate(self.delivery_date) != getdate(max_delivery_date):
- self.delivery_date = max_delivery_date
else:
frappe.throw(_("Please enter Delivery Date"))
diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js
index 8403193..95ca386 100644
--- a/erpnext/setup/doctype/company/company.js
+++ b/erpnext/setup/doctype/company/company.js
@@ -46,43 +46,6 @@
});
},
- change_abbreviation(frm) {
- var dialog = new frappe.ui.Dialog({
- title: "Replace Abbr",
- fields: [
- {"fieldtype": "Data", "label": "New Abbreviation", "fieldname": "new_abbr",
- "reqd": 1 },
- {"fieldtype": "Button", "label": "Update", "fieldname": "update"},
- ]
- });
-
- dialog.fields_dict.update.$input.click(function() {
- var args = dialog.get_values();
- if (!args) return;
- frappe.show_alert(__("Update in progress. It might take a while."));
- return frappe.call({
- method: "erpnext.setup.doctype.company.company.enqueue_replace_abbr",
- args: {
- "company": frm.doc.name,
- "old": frm.doc.abbr,
- "new": args.new_abbr
- },
- callback: function(r) {
- if (r.exc) {
- frappe.msgprint(__("There were errors."));
- return;
- } else {
- frm.set_value("abbr", args.new_abbr);
- }
- dialog.hide();
- frm.refresh();
- },
- btn: this
- });
- });
- dialog.show();
- },
-
company_name: function(frm) {
if(frm.doc.__islocal) {
// add missing " " arg in split method
@@ -164,10 +127,6 @@
}, __('Manage'));
}
}
-
- frm.add_custom_button(__('Change Abbreviation'), () => {
- frm.trigger('change_abbreviation');
- }, __('Manage'));
}
erpnext.company.set_chart_of_accounts_options(frm.doc);
diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json
index 58cb52c..63d96bf 100644
--- a/erpnext/setup/doctype/company/company.json
+++ b/erpnext/setup/doctype/company/company.json
@@ -125,7 +125,8 @@
"label": "Abbr",
"oldfieldname": "abbr",
"oldfieldtype": "Data",
- "reqd": 1
+ "reqd": 1,
+ "set_only_once": 1
},
{
"bold": 1,
@@ -747,10 +748,11 @@
"image_field": "company_logo",
"is_tree": 1,
"links": [],
- "modified": "2021-07-12 11:27:06.353860",
+ "modified": "2021-10-04 12:09:25.833133",
"modified_by": "Administrator",
"module": "Setup",
"name": "Company",
+ "naming_rule": "By fieldname",
"nsm_parent_field": "parent_company",
"owner": "Administrator",
"permissions": [
@@ -808,4 +810,4 @@
"sort_field": "modified",
"sort_order": "ASC",
"track_changes": 1
-}
+}
\ No newline at end of file
diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py
index 87d67a5..0b1b4a1 100644
--- a/erpnext/setup/doctype/company/company.py
+++ b/erpnext/setup/doctype/company/company.py
@@ -399,44 +399,6 @@
if not frappe.db.get_value('GL Entry', {'company': self.name}):
frappe.db.sql("delete from `tabProcess Deferred Accounting` where company=%s", self.name)
-@frappe.whitelist()
-def enqueue_replace_abbr(company, old, new):
- kwargs = dict(queue="long", company=company, old=old, new=new)
- frappe.enqueue('erpnext.setup.doctype.company.company.replace_abbr', **kwargs)
-
-
-@frappe.whitelist()
-def replace_abbr(company, old, new):
- new = new.strip()
- if not new:
- frappe.throw(_("Abbr can not be blank or space"))
-
- frappe.only_for("System Manager")
-
- def _rename_record(doc):
- parts = doc[0].rsplit(" - ", 1)
- if len(parts) == 1 or parts[1].lower() == old.lower():
- frappe.rename_doc(dt, doc[0], parts[0] + " - " + new, force=True)
-
- def _rename_records(dt):
- # rename is expensive so let's be economical with memory usage
- doc = (d for d in frappe.db.sql("select name from `tab%s` where company=%s" % (dt, '%s'), company))
- for d in doc:
- _rename_record(d)
- try:
- frappe.db.auto_commit_on_many_writes = 1
- for dt in ["Warehouse", "Account", "Cost Center", "Department",
- "Sales Taxes and Charges Template", "Purchase Taxes and Charges Template"]:
- _rename_records(dt)
- frappe.db.commit()
- frappe.db.set_value("Company", company, "abbr", new)
-
- except Exception:
- frappe.log_error(title=_('Abbreviation Rename Error'))
- finally:
- frappe.db.auto_commit_on_many_writes = 0
-
-
def get_name_with_abbr(name, company):
company_abbr = frappe.get_cached_value('Company', company, "abbr")
parts = name.split(" - ")
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json
index 9bf142c..ad1b3b4 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.json
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.json
@@ -395,8 +395,7 @@
"fieldtype": "Link",
"label": "Billing Address Name",
"options": "Address",
- "print_hide": 1,
- "read_only": 1
+ "print_hide": 1
},
{
"fieldname": "tax_id",
@@ -1309,7 +1308,7 @@
"idx": 146,
"is_submittable": 1,
"links": [],
- "modified": "2021-09-28 13:10:09.761714",
+ "modified": "2021-10-08 14:29:13.428984",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Note",
diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py
index cf98b19..17df977 100644
--- a/erpnext/stock/doctype/material_request/material_request.py
+++ b/erpnext/stock/doctype/material_request/material_request.py
@@ -296,7 +296,7 @@
return d.ordered_qty < d.stock_qty and child_filter
- doclist = get_mapped_doc("Material Request", source_name, {
+ doclist = get_mapped_doc("Material Request", source_name, {
"Material Request": {
"doctype": "Purchase Order",
"validation": {
@@ -323,7 +323,7 @@
@frappe.whitelist()
def make_request_for_quotation(source_name, target_doc=None):
- doclist = get_mapped_doc("Material Request", source_name, {
+ doclist = get_mapped_doc("Material Request", source_name, {
"Material Request": {
"doctype": "Request for Quotation",
"validation": {
diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py
index 82d8aae..a9254fb 100644
--- a/erpnext/stock/doctype/serial_no/serial_no.py
+++ b/erpnext/stock/doctype/serial_no/serial_no.py
@@ -611,7 +611,9 @@
return reserved_sr_nos
-def fetch_serial_numbers(filters, qty, do_not_include=[]):
+def fetch_serial_numbers(filters, qty, do_not_include=None):
+ if do_not_include is None:
+ do_not_include = []
batch_join_selection = ""
batch_no_condition = ""
batch_nos = filters.get("batch_no")
diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json
index 2463a21..40ae340 100644
--- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json
+++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json
@@ -142,6 +142,7 @@
"oldfieldtype": "Data",
"print_width": "150px",
"read_only": 1,
+ "search_index": 1,
"width": "150px"
},
{
@@ -316,7 +317,7 @@
"in_create": 1,
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2020-09-07 11:10:35.318872",
+ "modified": "2021-10-08 12:42:51.857631",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Ledger Entry",
@@ -338,4 +339,4 @@
],
"sort_field": "modified",
"sort_order": "DESC"
-}
\ No newline at end of file
+}
diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
index caa1d42..382fdfa 100644
--- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
+++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
@@ -181,4 +181,3 @@
frappe.db.add_index("Stock Ledger Entry", ["voucher_no", "voucher_type"])
frappe.db.add_index("Stock Ledger Entry", ["batch_no", "item_code", "warehouse"])
- frappe.db.add_index("Stock Ledger Entry", ["voucher_detail_no"])
diff --git a/erpnext/stock/form_tour/material_request/material_request.json b/erpnext/stock/form_tour/material_request/material_request.json
new file mode 100644
index 0000000..145b4a0
--- /dev/null
+++ b/erpnext/stock/form_tour/material_request/material_request.json
@@ -0,0 +1,97 @@
+{
+ "creation": "2021-07-29 12:32:08.929900",
+ "docstatus": 0,
+ "doctype": "Form Tour",
+ "idx": 0,
+ "is_standard": 1,
+ "modified": "2021-10-05 13:11:13.119453",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Material Request",
+ "owner": "Administrator",
+ "reference_doctype": "Material Request",
+ "save_on_complete": 1,
+ "steps": [
+ {
+ "description": "The purpose of the material request can be selected here. For now select \"Purchase\" as the purpose.",
+ "field": "",
+ "fieldname": "material_request_type",
+ "fieldtype": "Select",
+ "has_next_condition": 1,
+ "is_table_field": 0,
+ "label": "Purpose",
+ "next_step_condition": "eval: doc.material_request_type == \"Purchase\"",
+ "parent_field": "",
+ "position": "Bottom",
+ "title": "Purpose"
+ },
+ {
+ "description": "Set the \"Required By\" date for the materials. This sets the \"Required By\" date for all the items.",
+ "field": "",
+ "fieldname": "schedule_date",
+ "fieldtype": "Date",
+ "has_next_condition": 0,
+ "is_table_field": 0,
+ "label": "Required By",
+ "next_step_condition": "",
+ "parent_field": "",
+ "position": "Left",
+ "title": "Required By"
+ },
+ {
+ "description": "Setting the target warehouse sets it for all the items.",
+ "field": "",
+ "fieldname": "set_warehouse",
+ "fieldtype": "Link",
+ "has_next_condition": 0,
+ "is_table_field": 0,
+ "label": "Set Target Warehouse",
+ "next_step_condition": "",
+ "parent_field": "",
+ "position": "Left",
+ "title": "Target Warehouse"
+ },
+ {
+ "description": "Items table",
+ "field": "",
+ "fieldname": "items",
+ "fieldtype": "Table",
+ "has_next_condition": 0,
+ "is_table_field": 0,
+ "label": "Items",
+ "parent_field": "",
+ "position": "Bottom",
+ "title": "Items"
+ },
+ {
+ "child_doctype": "Material Request Item",
+ "description": "Select an Item code. Item details will be fetched automatically.",
+ "field": "",
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "has_next_condition": 1,
+ "is_table_field": 1,
+ "label": "Item Code",
+ "next_step_condition": "eval: doc.item_code",
+ "parent_field": "",
+ "parent_fieldname": "items",
+ "position": "Right",
+ "title": "Item Code"
+ },
+ {
+ "child_doctype": "Material Request Item",
+ "description": "Enter the required quantity for the material.",
+ "field": "",
+ "fieldname": "qty",
+ "fieldtype": "Float",
+ "has_next_condition": 0,
+ "is_table_field": 1,
+ "label": "Quantity",
+ "parent_field": "",
+ "parent_fieldname": "items",
+ "position": "Bottom",
+ "title": "Quantity"
+ }
+ ],
+ "title": "Material Request"
+}
\ No newline at end of file
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index 19597c3..cbff214 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -382,7 +382,7 @@
return out
-def get_item_warehouse(item, args, overwrite_warehouse, defaults={}):
+def get_item_warehouse(item, args, overwrite_warehouse, defaults=None):
if not defaults:
defaults = frappe._dict({
'item_defaults' : get_item_defaults(item.name, args.company),
diff --git a/erpnext/stock/reorder_item.py b/erpnext/stock/reorder_item.py
index 3cd4cd2..7c6fbfd 100644
--- a/erpnext/stock/reorder_item.py
+++ b/erpnext/stock/reorder_item.py
@@ -4,6 +4,7 @@
from __future__ import unicode_literals
import json
+from math import ceil
import frappe
from frappe import _
@@ -149,11 +150,16 @@
conversion_factor = frappe.db.get_value("UOM Conversion Detail",
{'parent': item.name, 'uom': uom}, 'conversion_factor') or 1.0
+ must_be_whole_number = frappe.db.get_value("UOM", uom, "must_be_whole_number", cache=True)
+ qty = d.reorder_qty / conversion_factor
+ if must_be_whole_number:
+ qty = ceil(qty)
+
mr.append("items", {
"doctype": "Material Request Item",
"item_code": d.item_code,
"schedule_date": add_days(nowdate(),cint(item.lead_time_days)),
- "qty": d.reorder_qty / conversion_factor,
+ "qty": qty,
"uom": uom,
"stock_uom": item.stock_uom,
"warehouse": d.warehouse,