Merge remote-tracking branch 'upstream/develop' into payments-based-dunning

This commit is contained in:
barredterra
2021-10-12 15:51:06 +02:00
88 changed files with 1373 additions and 325 deletions

View File

@@ -1,6 +1,8 @@
[flake8] [flake8]
ignore = ignore =
B007, B007,
B009,
B010,
B950, B950,
E101, E101,
E111, E111,
@@ -65,11 +67,6 @@ ignore =
E713, E713,
E712, E712,
enable-extensions =
M90
select =
M511
max-line-length = 200 max-line-length = 200
exclude=.github/helper/semgrep_rules,test_*.py exclude=.github/helper/semgrep_rules,test_*.py

View File

@@ -21,9 +21,9 @@ repos:
hooks: hooks:
- id: flake8 - id: flake8
additional_dependencies: [ additional_dependencies: [
'flake8-mutable', 'flake8-bugbear',
] ]
args: ['--select=M511', '--config', '.github/helper/.flake8_strict'] args: ['--config', '.github/helper/.flake8_strict']
exclude: ".*setup.py$" exclude: ".*setup.py$"
- repo: https://github.com/timothycrosley/isort - repo: https://github.com/timothycrosley/isort

View File

@@ -8,6 +8,8 @@ from frappe import _, throw
from frappe.utils import cint, cstr from frappe.utils import cint, cstr
from frappe.utils.nestedset import NestedSet, get_ancestors_of, get_descendants_of from frappe.utils.nestedset import NestedSet, get_ancestors_of, get_descendants_of
import erpnext
class RootNotEditable(frappe.ValidationError): pass class RootNotEditable(frappe.ValidationError): pass
class BalanceMismatchError(frappe.ValidationError): pass class BalanceMismatchError(frappe.ValidationError): pass
@@ -196,7 +198,7 @@ class Account(NestedSet):
"company": company, "company": company,
# parent account's currency should be passed down to child account's curreny # 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 # 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] "parent_account": parent_acc_name_map[company]
}) })
@@ -207,8 +209,7 @@ class Account(NestedSet):
# update the parent company's value in child companies # update the parent company's value in child companies
doc = frappe.get_doc("Account", child_account) doc = frappe.get_doc("Account", child_account)
parent_value_changed = False parent_value_changed = False
for field in ['account_type', 'account_currency', for field in ['account_type', 'freeze_account', 'balance_must_be']:
'freeze_account', 'balance_must_be']:
if doc.get(field) != self.get(field): if doc.get(field) != self.get(field):
parent_value_changed = True parent_value_changed = True
doc.set(field, self.get(field)) doc.set(field, self.get(field))

View File

@@ -174,7 +174,7 @@
"default": "0", "default": "0",
"fieldname": "automatically_fetch_payment_terms", "fieldname": "automatically_fetch_payment_terms",
"fieldtype": "Check", "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 ", "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, "index_web_pages_for_search": 1,
"issingle": 1, "issingle": 1,
"links": [], "links": [],
"modified": "2021-08-19 11:17:38.788054", "modified": "2021-10-11 17:42:36.427699",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Accounts Settings", "name": "Accounts Settings",

View File

@@ -16,7 +16,7 @@ class LoyaltyPointEntry(Document):
def get_loyalty_point_entries(customer, loyalty_program, company, expiry_date=None): def get_loyalty_point_entries(customer, loyalty_program, company, expiry_date=None):
if not expiry_date: if not expiry_date:
date = today() expiry_date = today()
return frappe.db.sql(''' return frappe.db.sql('''
select name, loyalty_points, expiry_date, loyalty_program_tier, invoice_type, invoice select name, loyalty_points, expiry_date, loyalty_program_tier, invoice_type, invoice

View File

@@ -712,10 +712,14 @@ class PaymentEntry(AccountsController):
dr_or_cr = "credit" if erpnext.get_party_account_type(self.party_type) == 'Receivable' else "debit" dr_or_cr = "credit" if erpnext.get_party_account_type(self.party_type) == 'Receivable' else "debit"
for d in self.get("references"): 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 = party_gl_dict.copy()
gle.update({ gle.update({
"against_voucher_type": d.reference_doctype, "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), allocated_amount_in_company_currency = flt(flt(d.allocated_amount) * flt(d.exchange_rate),

View File

@@ -1087,8 +1087,6 @@ class TestSalesInvoice(unittest.TestCase):
actual_qty_1 = get_qty_after_transaction(item_code = "_Test Item", warehouse = "Stores - TCP1") actual_qty_1 = get_qty_after_transaction(item_code = "_Test Item", warehouse = "Stores - TCP1")
frappe.db.commit()
self.assertEqual(actual_qty_0 - 5, actual_qty_1) self.assertEqual(actual_qty_0 - 5, actual_qty_1)
# outgoing_rate # outgoing_rate

View File

@@ -103,8 +103,11 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
column.is_tree = true; 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) { if (!data.parent_account) {
value = $(`<span>${value}</span>`); value = $(`<span>${value}</span>`);

View File

@@ -3,12 +3,14 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from collections import defaultdict
import frappe import frappe
from frappe import _ from frappe import _
from frappe.utils import cint, flt, getdate from frappe.utils import cint, flt, getdate
import erpnext
from erpnext.accounts.report.balance_sheet.balance_sheet import ( from erpnext.accounts.report.balance_sheet.balance_sheet import (
check_opening_balance,
get_chart_data, get_chart_data,
get_provisional_profit_loss, get_provisional_profit_loss,
) )
@@ -31,7 +33,7 @@ from erpnext.accounts.report.profit_and_loss_statement.profit_and_loss_statement
from erpnext.accounts.report.profit_and_loss_statement.profit_and_loss_statement import ( from erpnext.accounts.report.profit_and_loss_statement.profit_and_loss_statement import (
get_report_summary as get_pl_summary, 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): def execute(filters=None):
@@ -42,7 +44,7 @@ def execute(filters=None):
fiscal_year = get_fiscal_year_data(filters.get('from_fiscal_year'), filters.get('to_fiscal_year')) fiscal_year = get_fiscal_year_data(filters.get('from_fiscal_year'), filters.get('to_fiscal_year'))
companies_column, companies = get_companies(filters) companies_column, companies = get_companies(filters)
columns = get_columns(companies_column) columns = get_columns(companies_column, filters)
if filters.get('report') == "Balance Sheet": if filters.get('report') == "Balance Sheet":
data, message, chart, report_summary = get_balance_sheet_data(fiscal_year, companies, columns, filters) data, message, chart, report_summary = get_balance_sheet_data(fiscal_year, companies, columns, filters)
@@ -73,21 +75,24 @@ def get_balance_sheet_data(fiscal_year, companies, columns, filters):
provisional_profit_loss, total_credit = get_provisional_profit_loss(asset, liability, equity, provisional_profit_loss, total_credit = get_provisional_profit_loss(asset, liability, equity,
companies, filters.get('company'), company_currency, True) 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: if opening_balance:
unclosed ={ unclosed = {
"account_name": "'" + _("Unclosed Fiscal Years Profit / Loss (Credit)") + "'", "account_name": "'" + _("Unclosed Fiscal Years Profit / Loss (Credit)") + "'",
"account": "'" + _("Unclosed Fiscal Years Profit / Loss (Credit)") + "'", "account": "'" + _("Unclosed Fiscal Years Profit / Loss (Credit)") + "'",
"warn_if_negative": True, "warn_if_negative": True,
"currency": company_currency "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) data.append(unclosed)
if provisional_profit_loss: if provisional_profit_loss:
@@ -102,6 +107,37 @@ def get_balance_sheet_data(fiscal_year, companies, columns, filters):
return data, message, chart, report_summary 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): def get_profit_loss_data(fiscal_year, companies, columns, filters):
income, expense, net_profit_loss = get_income_expense_data(companies, fiscal_year, filters) income, expense, net_profit_loss = get_income_expense_data(companies, fiscal_year, filters)
company_currency = get_company_currency(filters) company_currency = get_company_currency(filters)
@@ -193,30 +229,37 @@ def get_account_type_based_data(account_type, companies, fiscal_year, filters):
data["total"] = total data["total"] = total
return data return data
def get_columns(companies): def get_columns(companies, filters):
columns = [{ columns = [
"fieldname": "account", {
"label": _("Account"), "fieldname": "account",
"fieldtype": "Link", "label": _("Account"),
"options": "Account", "fieldtype": "Link",
"width": 300 "options": "Account",
}] "width": 300
}, {
columns.append({ "fieldname": "currency",
"fieldname": "currency", "label": _("Currency"),
"label": _("Currency"), "fieldtype": "Link",
"fieldtype": "Link", "options": "Currency",
"options": "Currency", "hidden": 1
"hidden": 1 }
}) ]
for company in companies: 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({ columns.append({
"fieldname": company, "fieldname": company,
"label": company, "label": f'{company} ({currency})',
"fieldtype": "Currency", "fieldtype": "Currency",
"options": "currency", "options": "currency",
"width": 150 "width": 150,
"apply_currency_formatter": apply_currency_formatter,
"company_name": company
}) })
return columns return columns
@@ -236,6 +279,8 @@ def get_data(companies, root_type, balance_must_be, fiscal_year, filters=None, i
start_date = filters.period_start_date if filters.report != 'Balance Sheet' else None start_date = filters.period_start_date if filters.report != 'Balance Sheet' else None
end_date = filters.period_end_date end_date = filters.period_end_date
filters.end_date = end_date
gl_entries_by_account = {} gl_entries_by_account = {}
for root in frappe.db.sql("""select lft, rgt from tabAccount for root in frappe.db.sql("""select lft, rgt from tabAccount
where root_type=%s and ifnull(parent_account, '') = ''""", root_type, as_dict=1): where root_type=%s and ifnull(parent_account, '') = ''""", root_type, as_dict=1):
@@ -244,9 +289,10 @@ def get_data(companies, root_type, balance_must_be, fiscal_year, filters=None, i
end_date, root.lft, root.rgt, filters, end_date, root.lft, root.rgt, filters,
gl_entries_by_account, accounts_by_name, accounts, ignore_closing_entries=False) 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) 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: if out:
add_total_row(out, root_type, balance_must_be, companies, company_currency) add_total_row(out, root_type, balance_must_be, companies, company_currency)
@@ -257,7 +303,10 @@ def get_company_currency(filters=None):
return (filters.get('presentation_currency') return (filters.get('presentation_currency')
or frappe.get_cached_value('Company', filters.company, "default_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 entries in gl_entries_by_account.values():
for entry in entries: for entry in entries:
if entry.account_number: if entry.account_number:
@@ -266,15 +315,32 @@ def calculate_values(accounts_by_name, gl_entries_by_account, companies, start_d
account_name = entry.account_name account_name = entry.account_name
d = accounts_by_name.get(account_name) d = accounts_by_name.get(account_name)
if d: if d:
debit, credit = 0, 0
for company in companies: for company in companies:
# check if posting date is within the period # check if posting date is within the period
if (entry.company == company or (filters.get('accumulated_in_group_company')) if (entry.company == company or (filters.get('accumulated_in_group_company'))
and entry.company in companies.get(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): 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): def accumulate_values_into_parents(accounts, accounts_by_name, companies):
"""accumulate children's values in parent accounts""" """accumulate children's values in parent accounts"""
@@ -282,17 +348,18 @@ def accumulate_values_into_parents(accounts, accounts_by_name, companies):
if d.parent_account: if d.parent_account:
account = d.parent_account_name account = d.parent_account_name
if not accounts_by_name.get(account): # if not accounts_by_name.get(account):
continue # continue
for company in companies: for company in companies:
accounts_by_name[account][company] = \ accounts_by_name[account][company] = \
accounts_by_name[account].get(company, 0.0) + d.get(company, 0.0) 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]["opening_balance"] = \
accounts_by_name[account].get("opening_balance", 0.0) + d.get("opening_balance", 0.0) accounts_by_name[account].get("opening_balance", 0.0) + d.get("opening_balance", 0.0)
def get_account_heads(root_type, companies, filters): def get_account_heads(root_type, companies, filters):
accounts = get_accounts(root_type, filters) accounts = get_accounts(root_type, filters)
@@ -353,7 +420,7 @@ def get_accounts(root_type, filters):
`tabAccount` where company = %s and root_type = %s `tabAccount` where company = %s and root_type = %s
""" , (filters.get('company'), root_type), as_dict=1) """ , (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 = [] data = []
for d in accounts: for d in accounts:
@@ -367,10 +434,13 @@ def prepare_data(accounts, start_date, end_date, balance_must_be, companies, com
"parent_account": _(d.parent_account), "parent_account": _(d.parent_account),
"indent": flt(d.indent), "indent": flt(d.indent),
"year_start_date": start_date, "year_start_date": start_date,
"root_type": d.root_type,
"year_end_date": end_date, "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) "opening_balance": d.get("opening_balance", 0.0) * (1 if balance_must_be == "Debit" else -1)
}) })
for company in companies: for company in companies:
if d.get(company) and balance_must_be == "Credit": if d.get(company) and balance_must_be == "Credit":
# change sign based on Debit or Credit, since calculation is done using (debit - credit) # change sign based on Debit or Credit, since calculation is done using (debit - credit)
@@ -385,6 +455,7 @@ def prepare_data(accounts, start_date, end_date, balance_must_be, companies, com
row["has_value"] = has_value row["has_value"] = has_value
row["total"] = total row["total"] = total
data.append(row) data.append(row)
return data return data
@@ -447,6 +518,7 @@ def get_account_details(account):
'is_group', 'account_name', 'account_number', 'parent_account', 'lft', 'rgt'], as_dict=1) 'is_group', 'account_name', 'account_number', 'parent_account', 'lft', 'rgt'], as_dict=1)
def validate_entries(key, entry, accounts_by_name, accounts): 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: if key not in accounts_by_name:
args = get_account_details(entry.account) args = get_account_details(entry.account)
@@ -456,12 +528,23 @@ def validate_entries(key, entry, accounts_by_name, accounts):
args.update({ args.update({
'lft': parent_args.lft + 1, 'lft': parent_args.lft + 1,
'rgt': parent_args.rgt - 1, 'rgt': parent_args.rgt - 1,
'indent': 3,
'root_type': parent_args.root_type, '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_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): def get_additional_conditions(from_date, ignore_closing_entries, filters):
additional_conditions = [] additional_conditions = []
@@ -491,7 +574,6 @@ def add_total_row(out, root_type, balance_must_be, companies, company_currency):
for company in companies: for company in companies:
total_row.setdefault(company, 0.0) total_row.setdefault(company, 0.0)
total_row[company] += row.get(company, 0.0) total_row[company] += row.get(company, 0.0)
row[company] = 0.0
total_row.setdefault("total", 0.0) total_row.setdefault("total", 0.0)
total_row["total"] += flt(row["total"]) total_row["total"] += flt(row["total"])
@@ -511,6 +593,7 @@ def filter_accounts(accounts, depth=10):
account_name = d.account_number + ' - ' + d.account_name account_name = d.account_number + ' - ' + d.account_name
else: else:
account_name = d.account_name account_name = d.account_name
d['company_wise_opening_bal'] = defaultdict(float)
accounts_by_name[account_name] = d accounts_by_name[account_name] = d
parent_children_map.setdefault(d.parent_account or None, []).append(d) parent_children_map.setdefault(d.parent_account or None, []).append(d)

View File

@@ -533,6 +533,17 @@
"only_for": "United Arab Emirates", "only_for": "United Arab Emirates",
"type": "Link" "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, "hidden": 0,
"is_query_report": 0, "is_query_report": 0,
@@ -1153,6 +1164,16 @@
"onboard": 0, "onboard": 0,
"type": "Link" "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, "hidden": 0,
"is_query_report": 0, "is_query_report": 0,
@@ -1206,7 +1227,7 @@
"type": "Link" "type": "Link"
} }
], ],
"modified": "2021-08-27 12:15:52.872470", "modified": "2021-08-26 13:15:52.872470",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Accounting", "name": "Accounting",

View File

@@ -11,7 +11,7 @@ frappe.tour['Buying Settings'] = [
{ {
fieldname: "supp_master_name", fieldname: "supp_master_name",
title: "Supplier Naming By", 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", fieldname: "buying_price_list",

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -19,7 +19,7 @@
"documentation_url": "https://docs.erpnext.com/docs/user/manual/en/buying", "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/buying",
"idx": 0, "idx": 0,
"is_complete": 0, "is_complete": 0,
"modified": "2020-07-08 14:05:28.273641", "modified": "2021-08-24 18:13:42.463776",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Buying", "name": "Buying",
@@ -28,23 +28,11 @@
{ {
"step": "Introduction to Buying" "step": "Introduction to Buying"
}, },
{
"step": "Create a Supplier"
},
{
"step": "Setup your Warehouse"
},
{
"step": "Create a Product"
},
{ {
"step": "Create a Material Request" "step": "Create a Material Request"
}, },
{ {
"step": "Create your first Purchase Order" "step": "Create your first Purchase Order"
},
{
"step": "Buying Settings"
} }
], ],
"subtitle": "Products, Purchases, Analysis, and more.", "subtitle": "Products, Purchases, Analysis, and more.",

View File

@@ -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", "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, "docstatus": 0,
"doctype": "Onboarding Step", "doctype": "Onboarding Step",
"idx": 0, "idx": 0,
"is_complete": 0, "is_complete": 0,
"is_mandatory": 1,
"is_single": 0, "is_single": 0,
"is_skipped": 0, "is_skipped": 0,
"modified": "2020-05-15 14:39:09.818764", "modified": "2021-08-24 18:08:08.347501",
"modified_by": "Administrator", "modified_by": "Administrator",
"name": "Create a Material Request", "name": "Create a Material Request",
"owner": "Administrator", "owner": "Administrator",
"reference_document": "Material Request", "reference_document": "Material Request",
"show_form_tour": 1,
"show_full_form": 1, "show_full_form": 1,
"title": "Create a Material Request", "title": "Track Material Request",
"validate_action": 1 "validate_action": 1
} }

View File

@@ -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", "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, "docstatus": 0,
"doctype": "Onboarding Step", "doctype": "Onboarding Step",
"idx": 0, "idx": 0,
"is_complete": 0, "is_complete": 0,
"is_mandatory": 0,
"is_single": 0, "is_single": 0,
"is_skipped": 0, "is_skipped": 0,
"modified": "2020-05-12 18:31:56.856112", "modified": "2021-08-24 18:08:08.936484",
"modified_by": "Administrator", "modified_by": "Administrator",
"name": "Create your first Purchase Order", "name": "Create your first Purchase Order",
"owner": "Administrator", "owner": "Administrator",
"reference_document": "Purchase Order", "reference_document": "Purchase Order",
"show_form_tour": 0,
"show_full_form": 0, "show_full_form": 0,
"title": "Create your first Purchase Order", "title": "Create first Purchase Order",
"validate_action": 1 "validate_action": 1
} }

View File

@@ -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", "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, "docstatus": 0,
"doctype": "Onboarding Step", "doctype": "Onboarding Step",
"idx": 0, "idx": 0,
"is_complete": 0, "is_complete": 0,
"is_mandatory": 0, "is_single": 1,
"is_single": 0,
"is_skipped": 0, "is_skipped": 0,
"modified": "2020-05-12 18:25:08.509900", "modified": "2021-08-24 18:08:08.345735",
"modified_by": "Administrator", "modified_by": "Administrator",
"name": "Introduction to Buying", "name": "Introduction to Buying",
"owner": "Administrator", "owner": "Administrator",
"show_full_form": 0, "reference_document": "Buying Settings",
"title": "Introduction to Buying", "show_form_tour": 1,
"show_full_form": 1,
"title": "Buying Settings",
"validate_action": 1, "validate_action": 1,
"video_url": "https://youtu.be/efFajTTQBa8" "video_url": "https://youtu.be/efFajTTQBa8"
} }

View File

@@ -45,7 +45,6 @@ class TestProcurementTracker(unittest.TestCase):
pr = make_purchase_receipt(po.name) pr = make_purchase_receipt(po.name)
pr.get("items")[0].cost_center = "Main - _TPC" pr.get("items")[0].cost_center = "Main - _TPC"
pr.submit() pr.submit()
frappe.db.commit()
date_obj = datetime.date(datetime.now()) date_obj = datetime.date(datetime.now())
po.load_from_db() po.load_from_db()

View File

@@ -250,6 +250,7 @@ doc_events = {
"validate": "erpnext.regional.india.utils.validate_tax_category" "validate": "erpnext.regional.india.utils.validate_tax_category"
}, },
"Sales Invoice": { "Sales Invoice": {
"after_insert": "erpnext.regional.saudi_arabia.utils.create_qr_code",
"on_submit": [ "on_submit": [
"erpnext.regional.create_transaction_log", "erpnext.regional.create_transaction_log",
"erpnext.regional.italy.utils.sales_invoice_on_submit", "erpnext.regional.italy.utils.sales_invoice_on_submit",
@@ -259,7 +260,10 @@ doc_events = {
"erpnext.regional.italy.utils.sales_invoice_on_cancel", "erpnext.regional.italy.utils.sales_invoice_on_cancel",
"erpnext.erpnext_integrations.taxjar_integration.delete_transaction" "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": [ "validate": [
"erpnext.regional.india.utils.validate_document_name", "erpnext.regional.india.utils.validate_document_name",
"erpnext.regional.india.utils.update_taxable_values" "erpnext.regional.india.utils.update_taxable_values"

View File

@@ -74,7 +74,6 @@ class TestDailyWorkSummary(unittest.TestCase):
from `tabEmail Queue` as q, `tabEmail Queue Recipient` as r \ from `tabEmail Queue` as q, `tabEmail Queue Recipient` as r \
where q.name = r.parent""", as_dict=1) where q.name = r.parent""", as_dict=1)
frappe.db.commit()
def setup_groups(self, hour=None): def setup_groups(self, hour=None):
# setup email to trigger at this hour # setup email to trigger at this hour

View File

@@ -10,6 +10,26 @@ frappe.ui.form.on('Expense Claim', {
}, },
company: function(frm) { company: function(frm) {
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype); 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;
}
}
});
}
}, },
}); });

View File

@@ -334,7 +334,6 @@
}, },
{ {
"depends_on": "eval:doc.is_secured_loan", "depends_on": "eval:doc.is_secured_loan",
"fetch_from": "loan_application.maximum_loan_amount",
"fieldname": "maximum_loan_amount", "fieldname": "maximum_loan_amount",
"fieldtype": "Currency", "fieldtype": "Currency",
"label": "Maximum Loan Amount", "label": "Maximum Loan Amount",
@@ -360,7 +359,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2021-04-19 18:10:32.360818", "modified": "2021-10-12 18:10:32.360818",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Loan Management", "module": "Loan Management",
"name": "Loan", "name": "Loan",

View File

@@ -137,16 +137,23 @@ class Loan(AccountsController):
frappe.throw(_("Loan amount is mandatory")) frappe.throw(_("Loan amount is mandatory"))
def link_loan_security_pledge(self): def link_loan_security_pledge(self):
if self.is_secured_loan: if self.is_secured_loan and self.loan_application:
loan_security_pledge = frappe.db.get_value('Loan Security Pledge', {'loan_application': self.loan_application}, maximum_loan_value = frappe.db.get_value('Loan Security Pledge',
'name') {
'loan_application': self.loan_application,
'status': 'Requested'
},
'sum(maximum_loan_value)'
)
if loan_security_pledge: if maximum_loan_value:
frappe.db.set_value('Loan Security Pledge', loan_security_pledge, { frappe.db.sql("""
'loan': self.name, UPDATE `tabLoan Security Pledge`
'status': 'Pledged', SET loan = %s, pledge_time = %s, status = 'Pledged'
'pledge_time': now_datetime() 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): def unlink_loan_security_pledge(self):
pledges = frappe.get_all('Loan Security Pledge', fields=['name'], filters={'loan': self.name}) pledges = frappe.get_all('Loan Security Pledge', fields=['name'], filters={'loan': self.name})

View File

@@ -130,10 +130,11 @@ class LoanApplication(Document):
def create_loan(source_name, target_doc=None, submit=0): def create_loan(source_name, target_doc=None, submit=0):
def update_accounts(source_doc, target_doc, source_parent): def update_accounts(source_doc, target_doc, source_parent):
account_details = frappe.get_all("Loan Type", account_details = frappe.get_all("Loan Type",
fields=["mode_of_payment", "payment_account","loan_account", "interest_income_account", "penalty_income_account"], fields=["mode_of_payment", "payment_account","loan_account", "interest_income_account", "penalty_income_account"],
filters = {'name': source_doc.loan_type} filters = {'name': source_doc.loan_type})[0]
)[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.mode_of_payment = account_details.mode_of_payment
target_doc.payment_account = account_details.payment_account target_doc.payment_account = account_details.payment_account

View File

@@ -198,7 +198,7 @@ def get_disbursal_amount(loan, on_current_security_price=0):
security_value = get_total_pledged_security_value(loan) security_value = get_total_pledged_security_value(loan)
if loan_details.is_secured_loan and not on_current_security_price: 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: if not security_value and not loan_details.is_secured_loan:
security_value = flt(loan_details.loan_amount) security_value = flt(loan_details.loan_amount)
@@ -209,3 +209,6 @@ def get_disbursal_amount(loan, on_current_security_price=0):
disbursal_amount = loan_details.loan_amount - loan_details.disbursed_amount disbursal_amount = loan_details.loan_amount - loan_details.disbursed_amount
return disbursal_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)'))

View File

@@ -411,7 +411,7 @@ def get_amounts(amounts, against_loan, posting_date):
if due_date and not final_due_date: if due_date and not final_due_date:
final_due_date = add_days(due_date, loan_type_details.grace_period_in_days) 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 \ 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 - against_loan_doc.total_interest_payable - against_loan_doc.written_off_amount
else: else:

View File

@@ -1133,8 +1133,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters):
query_filters["has_variants"] = 0 query_filters["has_variants"] = 0
if filters and filters.get("is_stock_item"): if filters and filters.get("is_stock_item"):
or_cond_filters["is_stock_item"] = 1 query_filters["is_stock_item"] = 1
or_cond_filters["has_variants"] = 1
return frappe.get_list("Item", return frappe.get_list("Item",
fields = fields, filters=query_filters, fields = fields, filters=query_filters,

View File

@@ -4,13 +4,14 @@
import unittest import unittest
from collections import deque from collections import deque
from functools import partial
import frappe import frappe
from frappe.test_runner import make_test_records from frappe.test_runner import make_test_records
from frappe.utils import cstr, flt from frappe.utils import cstr, flt
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order 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.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.item.test_item import make_item
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import ( from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
@@ -375,6 +376,16 @@ class TestBOM(unittest.TestCase):
# FG Items in Scrap/Loss Table should have Is Process Loss set # FG Items in Scrap/Loss Table should have Is Process Loss set
self.assertRaises(frappe.ValidationError, bom_doc.submit) 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"): 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}) return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1})

View File

@@ -246,7 +246,7 @@ erpnext.patches.v13_0.update_payment_terms_outstanding
erpnext.patches.v12_0.add_state_code_for_ladakh erpnext.patches.v12_0.add_state_code_for_ladakh
erpnext.patches.v13_0.item_reposting_for_incorrect_sl_and_gl erpnext.patches.v13_0.item_reposting_for_incorrect_sl_and_gl
erpnext.patches.v13_0.delete_old_bank_reconciliation_doctypes 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.setup_fields_for_80g_certificate_and_donation
erpnext.patches.v13_0.rename_membership_settings_to_non_profit_settings erpnext.patches.v13_0.rename_membership_settings_to_non_profit_settings
erpnext.patches.v13_0.setup_gratuity_rule_for_india_and_uae erpnext.patches.v13_0.setup_gratuity_rule_for_india_and_uae
@@ -299,8 +299,8 @@ erpnext.patches.v13_0.gst_fields_for_pos_invoice
erpnext.patches.v13_0.create_accounting_dimensions_in_pos_doctypes erpnext.patches.v13_0.create_accounting_dimensions_in_pos_doctypes
erpnext.patches.v13_0.trim_sales_invoice_custom_field_length erpnext.patches.v13_0.trim_sales_invoice_custom_field_length
erpnext.patches.v13_0.create_custom_field_for_finance_book erpnext.patches.v13_0.create_custom_field_for_finance_book
erpnext.patches.v13_0.modify_invalid_gain_loss_gl_entries 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.fix_additional_cost_in_mfg_stock_entry
erpnext.patches.v13_0.set_status_in_maintenance_schedule_table erpnext.patches.v13_0.set_status_in_maintenance_schedule_table
erpnext.patches.v13_0.add_default_interview_notification_templates erpnext.patches.v13_0.add_default_interview_notification_templates
erpnext.patches.v13_0.single_to_multi_dunning erpnext.patches.v13_0.single_to_multi_dunning

View File

@@ -17,7 +17,7 @@ def execute():
where where
ref_exchange_rate = 1 ref_exchange_rate = 1
and docstatus = 1 and docstatus = 1
and ifnull(exchange_gain_loss, '') != '' and ifnull(exchange_gain_loss, 0) != 0
group by group by
parent parent
""", as_dict=1) """, as_dict=1)
@@ -30,7 +30,7 @@ def execute():
where where
ref_exchange_rate = 1 ref_exchange_rate = 1
and docstatus = 1 and docstatus = 1
and ifnull(exchange_gain_loss, '') != '' and ifnull(exchange_gain_loss, 0) != 0
group by group by
parent parent
""", as_dict=1) """, as_dict=1)
@@ -38,12 +38,24 @@ def execute():
if purchase_invoices + sales_invoices: if purchase_invoices + sales_invoices:
frappe.log_error(json.dumps(purchase_invoices + sales_invoices, indent=2), title="Patch Log") frappe.log_error(json.dumps(purchase_invoices + sales_invoices, indent=2), title="Patch Log")
acc_frozen_upto = frappe.db.get_value('Accounts Settings', None, 'acc_frozen_upto')
if acc_frozen_upto:
frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None)
for invoice in purchase_invoices + sales_invoices: for invoice in purchase_invoices + sales_invoices:
doc = frappe.get_doc(invoice.type, invoice.name) try:
doc.docstatus = 2 doc = frappe.get_doc(invoice.type, invoice.name)
doc.make_gl_entries() doc.docstatus = 2
for advance in doc.advances: doc.make_gl_entries()
if advance.ref_exchange_rate == 1: for advance in doc.advances:
advance.db_set('exchange_gain_loss', 0, False) if advance.ref_exchange_rate == 1:
doc.docstatus = 1 advance.db_set('exchange_gain_loss', 0, False)
doc.make_gl_entries() doc.docstatus = 1
doc.make_gl_entries()
frappe.db.commit()
except Exception:
frappe.db.rollback()
print(f'Failed to correct gl entries of {invoice.name}')
if acc_frozen_upto:
frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', acc_frozen_upto)

View File

@@ -2,7 +2,7 @@ import frappe
def execute(): 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'}) company = frappe.get_all('Company', filters = {'country': 'India'})
if not company: if not company:
return return

View File

@@ -329,7 +329,7 @@
{ {
"fieldname": "earning_deduction", "fieldname": "earning_deduction",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Earning & Deduction", "label": "Earnings & Deductions",
"oldfieldtype": "Section Break" "oldfieldtype": "Section Break"
}, },
{ {
@@ -380,7 +380,7 @@
"depends_on": "total_loan_repayment", "depends_on": "total_loan_repayment",
"fieldname": "loan_repayment", "fieldname": "loan_repayment",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Loan repayment" "label": "Loan Repayment"
}, },
{ {
"fieldname": "loans", "fieldname": "loans",
@@ -425,7 +425,7 @@
{ {
"fieldname": "net_pay_info", "fieldname": "net_pay_info",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "net pay info" "label": "Net Pay Info"
}, },
{ {
"fieldname": "net_pay", "fieldname": "net_pay",
@@ -647,7 +647,7 @@
"idx": 9, "idx": 9,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2021-09-01 10:35:52.374549", "modified": "2021-10-08 11:47:47.098248",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Payroll", "module": "Payroll",
"name": "Salary Slip", "name": "Salary Slip",

View File

@@ -5,7 +5,7 @@ const docsUrl = "https://erpnext.com/docs/";
frappe.help.help_links["Form/Rename Tool"] = [ frappe.help.help_links["Form/Rename Tool"] = [
{ {
label: "Bulk Rename", 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["Form/System Settings"] = [
}, },
]; ];
frappe.help.help_links["data-import-tool"] = [ frappe.help.help_links["Form/Data Import"] = [
{ {
label: "Importing and Exporting Data", 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", label: "Overwriting Data from Data Import Tool",
@@ -101,14 +114,14 @@ frappe.help.help_links["Form/Global Defaults"] = [
}, },
]; ];
frappe.help.help_links["Form/Email Digest"] = [ frappe.help.help_links["List/Print Heading"] = [
{ {
label: "Email Digest", label: "Print Heading",
url: docsUrl + "user/manual/en/setting-up/email/email-digest", 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", label: "Print Heading",
url: docsUrl + "user/manual/en/setting-up/print/print-headings", url: docsUrl + "user/manual/en/setting-up/print/print-headings",
@@ -153,18 +166,25 @@ frappe.help.help_links["List/Email Account"] = [
frappe.help.help_links["List/Notification"] = [ frappe.help.help_links["List/Notification"] = [
{ {
label: "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"] = [ frappe.help.help_links["Form/Notification"] = [
{ {
label: "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", label: "Email Digest",
url: docsUrl + "user/manual/en/setting-up/email/email-digest", url: docsUrl + "user/manual/en/setting-up/email/email-digest",
@@ -174,7 +194,7 @@ frappe.help.help_links["List/Email Digest"] = [
frappe.help.help_links["List/Auto Email Report"] = [ frappe.help.help_links["List/Auto Email Report"] = [
{ {
label: "Auto Email Reports", 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["Form/Print Settings"] = [
frappe.help.help_links["print-format-builder"] = [ frappe.help.help_links["print-format-builder"] = [
{ {
label: "Print Format Builder", label: "Print Format Builder",
url: docsUrl + "user/manual/en/setting-up/print/print-settings", url: docsUrl + "user/manual/en/setting-up/print/print-format-builder",
},
];
frappe.help.help_links["List/Print Heading"] = [
{
label: "Print Heading",
url: docsUrl + "user/manual/en/setting-up/print/print-headings",
}, },
]; ];
@@ -315,7 +328,7 @@ frappe.help.help_links["Form/Sales Order"] = [
}, },
{ {
label: "Recurring Sales Order", 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", label: "Applying Discount",
@@ -344,14 +357,14 @@ frappe.help.help_links["Form/Sales Order"] = [
frappe.help.help_links["Form/Product Bundle"] = [ frappe.help.help_links["Form/Product Bundle"] = [
{ {
label: "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"] = [ frappe.help.help_links["Form/Selling Settings"] = [
{ {
label: "Selling Settings", label: "Selling Settings",
url: docsUrl + "user/manual/en/selling/setup/selling-settings", url: docsUrl + "user/manual/en/selling/selling-settings",
}, },
]; ];
@@ -435,24 +448,17 @@ frappe.help.help_links["List/Purchase Taxes and Charges Template"] = [
}, },
]; ];
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"] = [ frappe.help.help_links["List/Price List"] = [
{ {
label: "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"] = [ frappe.help.help_links["List/Authorization Rule"] = [
{ {
label: "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 @@ frappe.help.help_links["List/Stock Reconciliation"] = [
label: "Stock Reconciliation", label: "Stock Reconciliation",
url: url:
docsUrl + docsUrl +
"user/manual/en/setting-up/stock-reconciliation-for-non-serialized-item", "user/manual/en/stock/stock-reconciliation",
}, },
]; ];
frappe.help.help_links["Tree/Territory"] = [ frappe.help.help_links["Tree/Territory"] = [
{ {
label: "Territory", label: "Territory",
url: docsUrl + "user/manual/en/setting-up/territory", url: docsUrl + "user/manual/en/selling/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",
}, },
]; ];
@@ -501,12 +494,6 @@ frappe.help.help_links["List/Company"] = [
label: "Company", label: "Company",
url: docsUrl + "user/manual/en/setting-up/company-setup", 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", label: "Delete All Related Transactions for a Company",
url: url:
@@ -517,21 +504,6 @@ frappe.help.help_links["List/Company"] = [
//Accounts //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"] = [ frappe.help.help_links["Tree/Account"] = [
{ {
label: "Chart of Accounts", label: "Chart of Accounts",
@@ -560,7 +532,7 @@ frappe.help.help_links["Form/Sales Invoice"] = [
}, },
{ {
label: "Recurring Sales Invoice", 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 @@ frappe.help.help_links["List/Sales Invoice"] = [
}, },
{ {
label: "Accounts Opening Balance", label: "Accounts Opening Balance",
url: docsUrl + "user/manual/en/accounts/opening-accounts", url: docsUrl + "user/manual/en/accounts/opening-balances",
}, },
{ {
label: "Sales Return", label: "Sales Return",
@@ -579,21 +551,28 @@ frappe.help.help_links["List/Sales Invoice"] = [
}, },
{ {
label: "Recurring Sales Invoice", 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", 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"] = [ frappe.help.help_links["List/POS Profile"] = [
{ {
label: "Point of Sale 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",
}, },
]; ];
@@ -644,7 +623,7 @@ frappe.help.help_links["List/Payment Request"] = [
frappe.help.help_links["List/Asset"] = [ frappe.help.help_links["List/Asset"] = [
{ {
label: "Managing Fixed Assets", label: "Managing Fixed Assets",
url: docsUrl + "user/manual/en/accounts/opening-balance/fixed_assets", url: docsUrl + "user/manual/en/asset",
}, },
]; ];
@@ -659,6 +638,8 @@ frappe.help.help_links["Tree/Cost Center"] = [
{ label: "Budgeting", url: docsUrl + "user/manual/en/accounts/budgeting" }, { label: "Budgeting", url: docsUrl + "user/manual/en/accounts/budgeting" },
]; ];
//Stock
frappe.help.help_links["List/Item"] = [ frappe.help.help_links["List/Item"] = [
{ label: "Item", url: docsUrl + "user/manual/en/stock/item" }, { label: "Item", url: docsUrl + "user/manual/en/stock/item" },
{ {
@@ -676,7 +657,7 @@ frappe.help.help_links["List/Item"] = [
}, },
{ {
label: "Managing Fixed Assets", label: "Managing Fixed Assets",
url: docsUrl + "user/manual/en/accounts/opening-balance/fixed_assets", url: docsUrl + "user/manual/en/asset",
}, },
{ {
label: "Item Codification", label: "Item Codification",
@@ -711,7 +692,7 @@ frappe.help.help_links["Form/Item"] = [
}, },
{ {
label: "Managing Fixed Assets", label: "Managing Fixed Assets",
url: docsUrl + "user/manual/en/accounts/opening-balance/fixed_assets", url: docsUrl + "user/manual/en/asset",
}, },
{ {
label: "Item Codification", label: "Item Codification",
@@ -771,10 +752,6 @@ frappe.help.help_links["Form/Delivery Note"] = [
url: url:
docsUrl + "user/manual/en/stock/articles/track-items-using-barcode", 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"] = [ frappe.help.help_links["List/Installation Note"] = [
@@ -784,21 +761,10 @@ frappe.help.help_links["List/Installation Note"] = [
}, },
]; ];
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"] = [ frappe.help.help_links["List/Budget"] = [
{ label: "Budgeting", url: docsUrl + "user/manual/en/accounts/budgeting" }, { label: "Budgeting", url: docsUrl + "user/manual/en/accounts/budgeting" },
]; ];
//Stock
frappe.help.help_links["List/Material Request"] = [ frappe.help.help_links["List/Material Request"] = [
{ {
label: "Material Request", label: "Material Request",
@@ -861,6 +827,10 @@ frappe.help.help_links["Form/Serial No"] = [
{ label: "Serial No", url: docsUrl + "user/manual/en/stock/serial-no" }, { 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"] = [ frappe.help.help_links["Form/Batch"] = [
{ label: "Batch", url: docsUrl + "user/manual/en/stock/batch" }, { label: "Batch", url: docsUrl + "user/manual/en/stock/batch" },
]; ];
@@ -868,35 +838,35 @@ frappe.help.help_links["Form/Batch"] = [
frappe.help.help_links["Form/Packing Slip"] = [ frappe.help.help_links["Form/Packing Slip"] = [
{ {
label: "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"] = [ frappe.help.help_links["Form/Quality Inspection"] = [
{ {
label: "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"] = [ frappe.help.help_links["Form/Landed Cost Voucher"] = [
{ {
label: "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"] = [ frappe.help.help_links["Tree/Item Group"] = [
{ {
label: "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"] = [ frappe.help.help_links["Form/Item Attribute"] = [
{ {
label: "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/UOM"] = [
frappe.help.help_links["Form/Stock Reconciliation"] = [ frappe.help.help_links["Form/Stock Reconciliation"] = [
{ {
label: "Opening Stock Entry", 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/Newsletter"] = [
]; ];
frappe.help.help_links["Form/Campaign"] = [ 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"] = [ frappe.help.help_links["Tree/Sales Person"] = [
{ {
label: "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 @@ frappe.help.help_links["Form/Sales Person"] = [
label: "Sales Person Target", label: "Sales Person Target",
url: url:
docsUrl + 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", label: "Sales Person in Transactions",
url: docsUrl + "user/manual/en/setting-up/feedback/setting-up-feedback", url:
}, docsUrl +
]; "user/manual/en/selling/articles/sales-persons-in-the-sales-transactions",
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",
}, },
]; ];
@@ -1019,7 +972,7 @@ frappe.help.help_links["Form/Operation"] = [
frappe.help.help_links["Form/BOM Update Tool"] = [ frappe.help.help_links["Form/BOM Update Tool"] = [
{ {
label: "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/Customize Form"] = [
}, },
]; ];
frappe.help.help_links["Form/Custom Field"] = [ frappe.help.help_links["List/Custom Field"] = [
{ {
label: "Custom Field", label: "Custom Field",
url: docsUrl + "user/manual/en/customize-erpnext/custom-field", url: docsUrl + "user/manual/en/customize-erpnext/custom-field",

View File

@@ -31,3 +31,4 @@ def create_transaction_log(doc, method):
"document_name": doc.name, "document_name": doc.name,
"data": data "data": data
}).insert(ignore_permissions=True) }).insert(ignore_permissions=True)

View File

@@ -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
}

View File

@@ -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

View File

@@ -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) {
// }
});

View File

@@ -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
}

View File

@@ -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

View File

@@ -0,0 +1,9 @@
# Copyright (c) 2021, Havenir Solutions and Contributors
# See license.txt
# import frappe
import unittest
class TestKSAVATSalesAccount(unittest.TestCase):
pass

View File

@@ -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');
}
});

View File

@@ -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
}

View File

@@ -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

View File

@@ -0,0 +1,5 @@
frappe.listview_settings['KSA VAT Setting'] = {
onload () {
frappe.breadcrumbs.add('Accounts');
}
}

View File

@@ -0,0 +1,9 @@
# Copyright (c) 2021, Havenir Solutions and Contributors
# See license.txt
# import frappe
import unittest
class TestKSAVATSetting(unittest.TestCase):
pass

View File

@@ -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;
}
},
};

View File

@@ -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"
}
]
}

View File

@@ -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

View File

@@ -2,10 +2,36 @@
# License: GNU General Public License v3. See license.txt # License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe
from erpnext.regional.united_arab_emirates.setup import make_custom_fields, add_print_formats 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): def setup(company=None, patch=True):
make_custom_fields() uae_custom_fields()
add_print_formats() 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)

View File

@@ -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)

View File

@@ -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"
}
]
}
]

View File

@@ -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()

View File

@@ -110,7 +110,7 @@ class SalesOrder(SellingController):
if self.order_type == 'Sales' and not self.skip_delivery_note: 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] 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 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 self.delivery_date = max_delivery_date
if self.delivery_date: if self.delivery_date:
for d in self.get("items"): for d in self.get("items"):
@@ -119,8 +119,6 @@ class SalesOrder(SellingController):
if getdate(self.transaction_date) > getdate(d.delivery_date): if getdate(self.transaction_date) > getdate(d.delivery_date):
frappe.msgprint(_("Expected Delivery Date should be after Sales Order Date"), frappe.msgprint(_("Expected Delivery Date should be after Sales Order Date"),
indicator='orange', title=_('Warning')) indicator='orange', title=_('Warning'))
if getdate(self.delivery_date) != getdate(max_delivery_date):
self.delivery_date = max_delivery_date
else: else:
frappe.throw(_("Please enter Delivery Date")) frappe.throw(_("Please enter Delivery Date"))

View File

@@ -1382,7 +1382,6 @@ def make_sales_order_workflow():
frappe.get_doc(dict(doctype='Role', role_name='Test Junior Approver')).insert(ignore_if_duplicate=True) frappe.get_doc(dict(doctype='Role', role_name='Test Junior Approver')).insert(ignore_if_duplicate=True)
frappe.get_doc(dict(doctype='Role', role_name='Test Approver')).insert(ignore_if_duplicate=True) frappe.get_doc(dict(doctype='Role', role_name='Test Approver')).insert(ignore_if_duplicate=True)
frappe.db.commit()
frappe.cache().hdel('roles', frappe.session.user) frappe.cache().hdel('roles', frappe.session.user)
workflow = frappe.get_doc({ workflow = frappe.get_doc({

View File

@@ -44,7 +44,6 @@ class TestShoppingCartSettings(unittest.TestCase):
def test_tax_rule_validation(self): def test_tax_rule_validation(self):
frappe.db.sql("update `tabTax Rule` set use_for_shopping_cart = 0") frappe.db.sql("update `tabTax Rule` set use_for_shopping_cart = 0")
frappe.db.commit()
cart_settings = self.get_cart_settings() cart_settings = self.get_cart_settings()
cart_settings.enabled = 1 cart_settings.enabled = 1

View File

@@ -1,8 +1,5 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt # License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import unittest
import frappe import frappe
from frappe.exceptions import ValidationError from frappe.exceptions import ValidationError
@@ -11,9 +8,10 @@ from frappe.utils import cint, flt
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.stock.doctype.batch.batch import UnableToSelectBatchError, get_batch_no, get_batch_qty from erpnext.stock.doctype.batch.batch import UnableToSelectBatchError, get_batch_no, get_batch_qty
from erpnext.stock.get_item_details import get_item_details from erpnext.stock.get_item_details import get_item_details
from erpnext.tests.utils import ERPNextTestCase
class TestBatch(unittest.TestCase): class TestBatch(ERPNextTestCase):
def test_item_has_batch_enabled(self): def test_item_has_batch_enabled(self):
self.assertRaises(ValidationError, frappe.get_doc({ self.assertRaises(ValidationError, frappe.get_doc({
"doctype": "Batch", "doctype": "Batch",

View File

@@ -395,8 +395,7 @@
"fieldtype": "Link", "fieldtype": "Link",
"label": "Billing Address Name", "label": "Billing Address Name",
"options": "Address", "options": "Address",
"print_hide": 1, "print_hide": 1
"read_only": 1
}, },
{ {
"fieldname": "tax_id", "fieldname": "tax_id",
@@ -1309,7 +1308,7 @@
"idx": 146, "idx": 146,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2021-09-28 13:10:09.761714", "modified": "2021-10-08 14:29:13.428984",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Delivery Note", "name": "Delivery Note",

View File

@@ -5,7 +5,6 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import json import json
import unittest
import frappe import frappe
from frappe.utils import cstr, flt, nowdate, nowtime from frappe.utils import cstr, flt, nowdate, nowtime
@@ -37,9 +36,10 @@ from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import
) )
from erpnext.stock.doctype.warehouse.test_warehouse import get_warehouse from erpnext.stock.doctype.warehouse.test_warehouse import get_warehouse
from erpnext.stock.stock_ledger import get_previous_sle from erpnext.stock.stock_ledger import get_previous_sle
from erpnext.tests.utils import ERPNextTestCase
class TestDeliveryNote(unittest.TestCase): class TestDeliveryNote(ERPNextTestCase):
def test_over_billing_against_dn(self): def test_over_billing_against_dn(self):
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1) frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1)

View File

@@ -14,11 +14,12 @@ from erpnext.stock.doctype.delivery_trip.delivery_trip import (
make_expense_claim, make_expense_claim,
notify_customers, notify_customers,
) )
from erpnext.tests.utils import create_test_contact_and_address from erpnext.tests.utils import ERPNextTestCase, create_test_contact_and_address
class TestDeliveryTrip(unittest.TestCase): class TestDeliveryTrip(ERPNextTestCase):
def setUp(self): def setUp(self):
super().setUp()
driver = create_driver() driver = create_driver()
create_vehicle() create_vehicle()
create_delivery_notification() create_delivery_notification()
@@ -32,6 +33,7 @@ class TestDeliveryTrip(unittest.TestCase):
frappe.db.sql("delete from `tabVehicle`") frappe.db.sql("delete from `tabVehicle`")
frappe.db.sql("delete from `tabEmail Template`") frappe.db.sql("delete from `tabEmail Template`")
frappe.db.sql("delete from `tabDelivery Trip`") frappe.db.sql("delete from `tabDelivery Trip`")
return super().tearDown()
def test_expense_claim_fields_are_fetched_properly(self): def test_expense_claim_fields_are_fetched_properly(self):
expense_claim = make_expense_claim(self.delivery_trip.name) expense_claim = make_expense_claim(self.delivery_trip.name)

View File

@@ -4,7 +4,6 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import json import json
import unittest
import frappe import frappe
from frappe.test_runner import make_test_objects from frappe.test_runner import make_test_objects
@@ -25,7 +24,7 @@ from erpnext.stock.doctype.item.item import (
) )
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
from erpnext.stock.get_item_details import get_item_details from erpnext.stock.get_item_details import get_item_details
from erpnext.tests.utils import change_settings from erpnext.tests.utils import ERPNextTestCase, change_settings
test_ignore = ["BOM"] test_ignore = ["BOM"]
test_dependencies = ["Warehouse", "Item Group", "Item Tax Template", "Brand", "Item Attribute"] test_dependencies = ["Warehouse", "Item Group", "Item Tax Template", "Brand", "Item Attribute"]
@@ -53,8 +52,9 @@ def make_item(item_code, properties=None):
return item return item
class TestItem(unittest.TestCase): class TestItem(ERPNextTestCase):
def setUp(self): def setUp(self):
super().setUp()
frappe.flags.attribute_values = None frappe.flags.attribute_values = None
def get_item(self, idx): def get_item(self, idx):

View File

@@ -4,7 +4,6 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import json import json
import unittest
import frappe import frappe
from frappe.utils import flt from frappe.utils import flt
@@ -21,10 +20,12 @@ from erpnext.stock.doctype.item.test_item import create_item
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import ( from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
create_stock_reconciliation, create_stock_reconciliation,
) )
from erpnext.tests.utils import ERPNextTestCase
class TestItemAlternative(unittest.TestCase): class TestItemAlternative(ERPNextTestCase):
def setUp(self): def setUp(self):
super().setUp()
make_items() make_items()
def test_alternative_item_for_subcontract_rm(self): def test_alternative_item_for_subcontract_rm(self):

View File

@@ -3,17 +3,17 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import unittest
import frappe import frappe
test_records = frappe.get_test_records('Item Attribute') test_records = frappe.get_test_records('Item Attribute')
from erpnext.stock.doctype.item_attribute.item_attribute import ItemAttributeIncrementError from erpnext.stock.doctype.item_attribute.item_attribute import ItemAttributeIncrementError
from erpnext.tests.utils import ERPNextTestCase
class TestItemAttribute(unittest.TestCase): class TestItemAttribute(ERPNextTestCase):
def setUp(self): def setUp(self):
super().setUp()
if frappe.db.exists("Item Attribute", "_Test_Length"): if frappe.db.exists("Item Attribute", "_Test_Length"):
frappe.delete_doc("Item Attribute", "_Test_Length") frappe.delete_doc("Item Attribute", "_Test_Length")

View File

@@ -3,17 +3,17 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import unittest
import frappe import frappe
from frappe.test_runner import make_test_records_for_doctype from frappe.test_runner import make_test_records_for_doctype
from erpnext.stock.doctype.item_price.item_price import ItemPriceDuplicateItem from erpnext.stock.doctype.item_price.item_price import ItemPriceDuplicateItem
from erpnext.stock.get_item_details import get_price_list_rate_for, process_args from erpnext.stock.get_item_details import get_price_list_rate_for, process_args
from erpnext.tests.utils import ERPNextTestCase
class TestItemPrice(unittest.TestCase): class TestItemPrice(ERPNextTestCase):
def setUp(self): def setUp(self):
super().setUp()
frappe.db.sql("delete from `tabItem Price`") frappe.db.sql("delete from `tabItem Price`")
make_test_records_for_doctype("Item Price", force=True) make_test_records_for_doctype("Item Price", force=True)

View File

@@ -4,8 +4,6 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import unittest
import frappe import frappe
from frappe.utils import flt from frappe.utils import flt
@@ -16,9 +14,10 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import (
get_gl_entries, get_gl_entries,
make_purchase_receipt, make_purchase_receipt,
) )
from erpnext.tests.utils import ERPNextTestCase
class TestLandedCostVoucher(unittest.TestCase): class TestLandedCostVoucher(ERPNextTestCase):
def test_landed_cost_voucher(self): def test_landed_cost_voucher(self):
frappe.db.set_value("Buying Settings", None, "allow_multiple_items", 1) frappe.db.set_value("Buying Settings", None, "allow_multiple_items", 1)

View File

@@ -6,8 +6,6 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import unittest
import frappe import frappe
from frappe.utils import flt, today from frappe.utils import flt, today
@@ -18,9 +16,10 @@ from erpnext.stock.doctype.material_request.material_request import (
make_supplier_quotation, make_supplier_quotation,
raise_work_orders, raise_work_orders,
) )
from erpnext.tests.utils import ERPNextTestCase
class TestMaterialRequest(unittest.TestCase): class TestMaterialRequest(ERPNextTestCase):
def test_make_purchase_order(self): def test_make_purchase_order(self):
mr = frappe.copy_doc(test_records[0]).insert() mr = frappe.copy_doc(test_records[0]).insert()

View File

@@ -6,6 +6,8 @@ from __future__ import unicode_literals
import unittest import unittest
# test_records = frappe.get_test_records('Packing Slip') # test_records = frappe.get_test_records('Packing Slip')
from erpnext.tests.utils import ERPNextTestCase
class TestPackingSlip(unittest.TestCase): class TestPackingSlip(unittest.TestCase):
pass pass

View File

@@ -3,8 +3,6 @@
# See license.txt # See license.txt
from __future__ import unicode_literals from __future__ import unicode_literals
import unittest
import frappe import frappe
test_dependencies = ['Item', 'Sales Invoice', 'Stock Entry', 'Batch'] test_dependencies = ['Item', 'Sales Invoice', 'Stock Entry', 'Batch']
@@ -15,9 +13,10 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_pu
from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import ( from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import (
EmptyStockReconciliationItemsError, EmptyStockReconciliationItemsError,
) )
from erpnext.tests.utils import ERPNextTestCase
class TestPickList(unittest.TestCase): class TestPickList(ERPNextTestCase):
def test_pick_list_picks_warehouse_for_each_item(self): def test_pick_list_picks_warehouse_for_each_item(self):
try: try:

View File

@@ -17,9 +17,10 @@ from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchas
from erpnext.stock.doctype.serial_no.serial_no import SerialNoDuplicateError, get_serial_nos from erpnext.stock.doctype.serial_no.serial_no import SerialNoDuplicateError, get_serial_nos
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
from erpnext.stock.stock_ledger import SerialNoExistsInFutureTransaction from erpnext.stock.stock_ledger import SerialNoExistsInFutureTransaction
from erpnext.tests.utils import ERPNextTestCase
class TestPurchaseReceipt(unittest.TestCase): class TestPurchaseReceipt(ERPNextTestCase):
def setUp(self): def setUp(self):
frappe.db.set_value("Buying Settings", None, "allow_multiple_items", 1) frappe.db.set_value("Buying Settings", None, "allow_multiple_items", 1)

View File

@@ -3,8 +3,6 @@
# See license.txt # See license.txt
from __future__ import unicode_literals from __future__ import unicode_literals
import unittest
import frappe import frappe
from erpnext.stock.doctype.batch.test_batch import make_new_batch from erpnext.stock.doctype.batch.test_batch import make_new_batch
@@ -13,9 +11,10 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_pu
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
from erpnext.stock.get_item_details import get_conversion_factor from erpnext.stock.get_item_details import get_conversion_factor
from erpnext.tests.utils import ERPNextTestCase
class TestPutawayRule(unittest.TestCase): class TestPutawayRule(ERPNextTestCase):
def setUp(self): def setUp(self):
if not frappe.db.exists("Item", "_Rice"): if not frappe.db.exists("Item", "_Rice"):
make_item("_Rice", { make_item("_Rice", {

View File

@@ -1,8 +1,6 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors
# See license.txt # See license.txt
import unittest
import frappe import frappe
from frappe.utils import nowdate from frappe.utils import nowdate
@@ -15,12 +13,14 @@ from erpnext.controllers.stock_controller import (
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
from erpnext.stock.doctype.item.test_item import create_item from erpnext.stock.doctype.item.test_item import create_item
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
from erpnext.tests.utils import ERPNextTestCase
# test_records = frappe.get_test_records('Quality Inspection') # test_records = frappe.get_test_records('Quality Inspection')
class TestQualityInspection(unittest.TestCase): class TestQualityInspection(ERPNextTestCase):
def setUp(self): def setUp(self):
super().setUp()
create_item("_Test Item with QA") create_item("_Test Item with QA")
frappe.db.set_value( frappe.db.set_value(
"Item", "_Test Item with QA", "inspection_required_before_delivery", 1 "Item", "_Test Item with QA", "inspection_required_before_delivery", 1

View File

@@ -6,8 +6,6 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import unittest
import frappe import frappe
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
@@ -20,9 +18,10 @@ test_dependencies = ["Item"]
test_records = frappe.get_test_records('Serial No') test_records = frappe.get_test_records('Serial No')
from erpnext.stock.doctype.serial_no.serial_no import * from erpnext.stock.doctype.serial_no.serial_no import *
from erpnext.tests.utils import ERPNextTestCase
class TestSerialNo(unittest.TestCase): class TestSerialNo(ERPNextTestCase):
def test_cannot_create_direct(self): def test_cannot_create_direct(self):
frappe.delete_doc_if_exists("Serial No", "_TCSER0001") frappe.delete_doc_if_exists("Serial No", "_TCSER0001")

View File

@@ -3,15 +3,15 @@
# See license.txt # See license.txt
from __future__ import unicode_literals from __future__ import unicode_literals
import unittest
from datetime import date, timedelta from datetime import date, timedelta
import frappe import frappe
from erpnext.stock.doctype.delivery_note.delivery_note import make_shipment from erpnext.stock.doctype.delivery_note.delivery_note import make_shipment
from erpnext.tests.utils import ERPNextTestCase
class TestShipment(unittest.TestCase): class TestShipment(ERPNextTestCase):
def test_shipment_from_delivery_note(self): def test_shipment_from_delivery_note(self):
delivery_note = create_test_delivery_note() delivery_note = create_test_delivery_note()
delivery_note.submit() delivery_note.submit()
@@ -47,7 +47,6 @@ def create_test_delivery_note():
} }
) )
delivery_note.insert() delivery_note.insert()
frappe.db.commit()
return delivery_note return delivery_note
@@ -91,7 +90,6 @@ def create_test_shipment(delivery_notes = None):
} }
) )
shipment.insert() shipment.insert()
frappe.db.commit()
return shipment return shipment

View File

@@ -142,6 +142,7 @@
"oldfieldtype": "Data", "oldfieldtype": "Data",
"print_width": "150px", "print_width": "150px",
"read_only": 1, "read_only": 1,
"search_index": 1,
"width": "150px" "width": "150px"
}, },
{ {
@@ -316,7 +317,7 @@
"in_create": 1, "in_create": 1,
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"modified": "2020-09-07 11:10:35.318872", "modified": "2021-10-08 12:42:51.857631",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Stock Ledger Entry", "name": "Stock Ledger Entry",
@@ -338,4 +339,4 @@
], ],
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC" "sort_order": "DESC"
} }

View File

@@ -181,4 +181,3 @@ def on_doctype_update():
frappe.db.add_index("Stock Ledger Entry", ["voucher_no", "voucher_type"]) 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", ["batch_no", "item_code", "warehouse"])
frappe.db.add_index("Stock Ledger Entry", ["voucher_detail_no"])

View File

@@ -3,8 +3,6 @@
# See license.txt # See license.txt
from __future__ import unicode_literals from __future__ import unicode_literals
import unittest
import frappe import frappe
from frappe.core.page.permission_manager.permission_manager import reset from frappe.core.page.permission_manager.permission_manager import reset
from frappe.utils import add_days, today from frappe.utils import add_days, today
@@ -21,9 +19,10 @@ from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import
create_stock_reconciliation, create_stock_reconciliation,
) )
from erpnext.stock.stock_ledger import get_previous_sle from erpnext.stock.stock_ledger import get_previous_sle
from erpnext.tests.utils import ERPNextTestCase
class TestStockLedgerEntry(unittest.TestCase): class TestStockLedgerEntry(ERPNextTestCase):
def setUp(self): def setUp(self):
items = create_items() items = create_items()
reset('Stock Entry') reset('Stock Entry')

View File

@@ -6,8 +6,6 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import unittest
import frappe import frappe
from frappe.utils import add_days, flt, nowdate, nowtime, random_string from frappe.utils import add_days, flt, nowdate, nowtime, random_string
@@ -22,12 +20,13 @@ from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import (
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
from erpnext.stock.stock_ledger import get_previous_sle, update_entries_after from erpnext.stock.stock_ledger import get_previous_sle, update_entries_after
from erpnext.stock.utils import get_incoming_rate, get_stock_value_on, get_valuation_method from erpnext.stock.utils import get_incoming_rate, get_stock_value_on, get_valuation_method
from erpnext.tests.utils import change_settings from erpnext.tests.utils import ERPNextTestCase, change_settings
class TestStockReconciliation(unittest.TestCase): class TestStockReconciliation(ERPNextTestCase):
@classmethod @classmethod
def setUpClass(self): def setUpClass(self):
super().setUpClass()
create_batch_or_serial_no_items() create_batch_or_serial_no_items()
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1) frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1)
@@ -372,7 +371,6 @@ class TestStockReconciliation(unittest.TestCase):
""" """
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
from erpnext.stock.stock_ledger import NegativeStockError from erpnext.stock.stock_ledger import NegativeStockError
frappe.db.commit()
item_code = "Backdated-Reco-Cancellation-Item" item_code = "Backdated-Reco-Cancellation-Item"
warehouse = "_Test Warehouse - _TC" warehouse = "_Test Warehouse - _TC"
@@ -395,10 +393,6 @@ class TestStockReconciliation(unittest.TestCase):
repost_exists = bool(frappe.db.exists("Repost Item Valuation", {"voucher_no": sr.name})) repost_exists = bool(frappe.db.exists("Repost Item Valuation", {"voucher_no": sr.name}))
self.assertFalse(repost_exists, msg="Negative stock validation not working on reco cancellation") self.assertFalse(repost_exists, msg="Negative stock validation not working on reco cancellation")
# teardown
frappe.db.rollback()
def test_valid_batch(self): def test_valid_batch(self):
create_batch_item_with_batch("Testing Batch Item 1", "001") create_batch_item_with_batch("Testing Batch Item 1", "001")
create_batch_item_with_batch("Testing Batch Item 2", "002") create_batch_item_with_batch("Testing Batch Item 2", "002")

View File

@@ -7,9 +7,12 @@ import unittest
import frappe import frappe
from erpnext.tests.utils import ERPNextTestCase
class TestStockSettings(unittest.TestCase):
class TestStockSettings(ERPNextTestCase):
def setUp(self): def setUp(self):
super().setUp()
frappe.db.set_value("Stock Settings", None, "clean_description_html", 0) frappe.db.set_value("Stock Settings", None, "clean_description_html", 0)
def test_settings(self): def test_settings(self):

View File

@@ -2,8 +2,6 @@
# License: GNU General Public License v3. See license.txt # License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals from __future__ import unicode_literals
import unittest
import frappe import frappe
from frappe.test_runner import make_test_records from frappe.test_runner import make_test_records
from frappe.utils import cint from frappe.utils import cint
@@ -12,11 +10,13 @@ import erpnext
from erpnext.accounts.doctype.account.test_account import create_account, get_inventory_account from erpnext.accounts.doctype.account.test_account import create_account, get_inventory_account
from erpnext.stock.doctype.item.test_item import create_item from erpnext.stock.doctype.item.test_item import create_item
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
from erpnext.tests.utils import ERPNextTestCase
test_records = frappe.get_test_records('Warehouse') test_records = frappe.get_test_records('Warehouse')
class TestWarehouse(unittest.TestCase): class TestWarehouse(ERPNextTestCase):
def setUp(self): def setUp(self):
super().setUp()
if not frappe.get_value('Item', '_Test Item'): if not frappe.get_value('Item', '_Test Item'):
make_test_records('Item') make_test_records('Item')

View File

@@ -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"
}

View File

@@ -5,9 +5,10 @@ from frappe import _dict
from erpnext.accounts.utils import get_fiscal_year from erpnext.accounts.utils import get_fiscal_year
from erpnext.stock.report.stock_analytics.stock_analytics import get_period_date_ranges from erpnext.stock.report.stock_analytics.stock_analytics import get_period_date_ranges
from erpnext.tests.utils import ERPNextTestCase
class TestStockAnalyticsReport(unittest.TestCase): class TestStockAnalyticsReport(ERPNextTestCase):
def test_get_period_date_ranges(self): def test_get_period_date_ranges(self):
filters = _dict(range="Monthly", from_date="2020-12-28", to_date="2021-02-06") filters = _dict(range="Monthly", from_date="2020-12-28", to_date="2021-02-06")

View File

@@ -2,6 +2,7 @@
# License: GNU General Public License v3. See license.txt # License: GNU General Public License v3. See license.txt
import copy import copy
import unittest
from contextlib import contextmanager from contextlib import contextmanager
from typing import Any, Dict, NewType, Optional from typing import Any, Dict, NewType, Optional
@@ -12,6 +13,21 @@ ReportFilters = Dict[str, Any]
ReportName = NewType("ReportName", str) ReportName = NewType("ReportName", str)
class ERPNextTestCase(unittest.TestCase):
"""A sane default test class for ERPNext tests."""
@classmethod
def setUpClass(cls) -> None:
frappe.db.commit()
return super().setUpClass()
@classmethod
def tearDownClass(cls) -> None:
frappe.db.rollback()
return super().tearDownClass()
def create_test_contact_and_address(): def create_test_contact_and_address():
frappe.db.sql('delete from tabContact') frappe.db.sql('delete from tabContact')
frappe.db.sql('delete from `tabContact Email`') frappe.db.sql('delete from `tabContact Email`')