diff --git a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py index 8e0e62d5f8c..3b938ea1ca4 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py @@ -78,6 +78,8 @@ class TestPeriodClosingVoucher(unittest.TestCase): expense_account="Cost of Goods Sold - TPC", rate=400, debit_to="Debtors - TPC", + currency="USD", + customer="_Test Customer USD", ) create_sales_invoice( company=company, @@ -86,6 +88,8 @@ class TestPeriodClosingVoucher(unittest.TestCase): expense_account="Cost of Goods Sold - TPC", rate=200, debit_to="Debtors - TPC", + currency="USD", + customer="_Test Customer USD", ) pcv = self.make_period_closing_voucher(submit=False) @@ -119,14 +123,17 @@ class TestPeriodClosingVoucher(unittest.TestCase): surplus_account = create_account() cost_center = create_cost_center("Test Cost Center 1") - create_sales_invoice( + si = create_sales_invoice( company=company, income_account="Sales - TPC", expense_account="Cost of Goods Sold - TPC", cost_center=cost_center, rate=400, debit_to="Debtors - TPC", + currency="USD", + customer="_Test Customer USD", ) + jv = make_journal_entry( account1="Cash - TPC", account2="Sales - TPC", diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py index 4cf19b4454e..3bd0cd2e837 100644 --- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py @@ -752,7 +752,7 @@ class TestPricingRule(unittest.TestCase): title="_Test Pricing Rule with Min Qty - 2", ) - si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", qty=1, currency="USD") + si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", qty=1) item = si.items[0] item.stock_qty = 1 si.save() diff --git a/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.json b/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.json index b46d2e32f28..c36efb89a30 100644 --- a/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.json +++ b/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.json @@ -1,10 +1,12 @@ { + "actions": [], "allow_import": 1, "allow_rename": 1, "creation": "2013-01-10 16:34:08", "description": "Standard tax template that can be applied to all Purchase Transactions. This template can contain list of tax heads and also other expense heads like \"Shipping\", \"Insurance\", \"Handling\" etc.\n\n#### Note\n\nThe tax rate you define here will be the standard tax rate for all **Items**. If there are **Items** that have different rates, they must be added in the **Item Tax** table in the **Item** master.\n\n#### Description of Columns\n\n1. Calculation Type: \n - This can be on **Net Total** (that is the sum of basic amount).\n - **On Previous Row Total / Amount** (for cumulative taxes or charges). If you select this option, the tax will be applied as a percentage of the previous row (in the tax table) amount or total.\n - **Actual** (as mentioned).\n2. Account Head: The Account ledger under which this tax will be booked\n3. Cost Center: If the tax / charge is an income (like shipping) or expense it needs to be booked against a Cost Center.\n4. Description: Description of the tax (that will be printed in invoices / quotes).\n5. Rate: Tax rate.\n6. Amount: Tax amount.\n7. Total: Cumulative total to this point.\n8. Enter Row: If based on \"Previous Row Total\" you can select the row number which will be taken as a base for this calculation (default is the previous row).\n9. Consider Tax or Charge for: In this section you can specify if the tax / charge is only for valuation (not a part of total) or only for total (does not add value to the item) or for both.\n10. Add or Deduct: Whether you want to add or deduct the tax.", "doctype": "DocType", "document_type": "Setup", + "engine": "InnoDB", "field_order": [ "title", "is_default", @@ -74,7 +76,8 @@ ], "icon": "fa fa-money", "idx": 1, - "modified": "2019-11-25 13:05:26.220275", + "links": [], + "modified": "2022-05-16 16:15:29.059370", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Taxes and Charges Template", @@ -103,6 +106,10 @@ "role": "Purchase User" } ], + "show_title_field_in_link": 1, + "sort_field": "modified", "sort_order": "DESC", + "states": [], + "title_field": "title", "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index e30289a62c9..9dde85fe12e 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -861,27 +861,44 @@ frappe.ui.form.on('Sales Invoice', { set_timesheet_data: function(frm, timesheets) { frm.clear_table("timesheets") - timesheets.forEach(timesheet => { + timesheets.forEach(async (timesheet) => { if (frm.doc.currency != timesheet.currency) { - frappe.call({ - method: "erpnext.setup.utils.get_exchange_rate", - args: { - from_currency: timesheet.currency, - to_currency: frm.doc.currency - }, - callback: function(r) { - if (r.message) { - exchange_rate = r.message; - frm.events.append_time_log(frm, timesheet, exchange_rate); - } - } - }); + const exchange_rate = await frm.events.get_exchange_rate( + frm, timesheet.currency, frm.doc.currency + ) + frm.events.append_time_log(frm, timesheet, exchange_rate) } else { frm.events.append_time_log(frm, timesheet, 1.0); } }); }, + async get_exchange_rate(frm, from_currency, to_currency) { + if ( + frm.exchange_rates + && frm.exchange_rates[from_currency] + && frm.exchange_rates[from_currency][to_currency] + ) { + return frm.exchange_rates[from_currency][to_currency]; + } + + return frappe.call({ + method: "erpnext.setup.utils.get_exchange_rate", + args: { + from_currency, + to_currency + }, + callback: function(r) { + if (r.message) { + // cache exchange rates + frm.exchange_rates = frm.exchange_rates || {}; + frm.exchange_rates[from_currency] = frm.exchange_rates[from_currency] || {}; + frm.exchange_rates[from_currency][to_currency] = r.message; + } + } + }); + }, + append_time_log: function(frm, time_log, exchange_rate) { const row = frm.add_child("timesheets"); row.activity_type = time_log.activity_type; @@ -892,7 +909,7 @@ frappe.ui.form.on('Sales Invoice', { row.billing_hours = time_log.billing_hours; row.billing_amount = flt(time_log.billing_amount) * flt(exchange_rate); row.timesheet_detail = time_log.name; - row.project_name = time_log.project_name; + row.project_name = time_log.project_name; frm.refresh_field("timesheets"); frm.trigger("calculate_timesheet_totals"); diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.json b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.json index 19781bdffaa..408ecbf36dc 100644 --- a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.json +++ b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.json @@ -1,4 +1,5 @@ { + "actions": [], "allow_import": 1, "allow_rename": 1, "creation": "2013-01-10 16:34:09", @@ -77,7 +78,8 @@ ], "icon": "fa fa-money", "idx": 1, - "modified": "2019-11-25 13:06:03.279099", + "links": [], + "modified": "2022-05-16 16:14:52.061672", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Taxes and Charges Template", @@ -113,7 +115,10 @@ "write": 1 } ], + "show_title_field_in_link": 1, "sort_field": "modified", "sort_order": "ASC", + "states": [], + "title_field": "title", "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index db741d97e10..f4a44bd3629 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -897,3 +897,18 @@ def get_default_contact(doctype, name): return None else: return None + + +def add_party_account(party_type, party, company, account): + doc = frappe.get_doc(party_type, party) + account_exists = False + for d in doc.get("accounts"): + if d.account == account: + account_exists = True + + if not account_exists: + accounts = {"company": company, "account": account} + + doc.append("accounts", accounts) + + doc.save() diff --git a/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py b/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py index f3ccc868c4c..c41d0d10ffe 100644 --- a/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py +++ b/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py @@ -198,10 +198,12 @@ def get_loan_entries(filters): amount_field = (loan_doc.disbursed_amount).as_("credit") posting_date = (loan_doc.disbursement_date).as_("posting_date") account = loan_doc.disbursement_account + salary_condition = loan_doc.docstatus == 1 else: amount_field = (loan_doc.amount_paid).as_("debit") posting_date = (loan_doc.posting_date).as_("posting_date") account = loan_doc.payment_account + salary_condition = loan_doc.repay_from_salary == 0 query = ( frappe.qb.from_(loan_doc) @@ -214,14 +216,12 @@ def get_loan_entries(filters): posting_date, ) .where(loan_doc.docstatus == 1) + .where(salary_condition) .where(account == filters.get("account")) .where(posting_date <= getdate(filters.get("report_date"))) .where(ifnull(loan_doc.clearance_date, "4000-01-01") > getdate(filters.get("report_date"))) ) - if doctype == "Loan Repayment": - query.where(loan_doc.repay_from_salary == 0) - entries = query.run(as_dict=1) loan_docs.extend(entries) @@ -267,15 +267,17 @@ def get_loan_amount(filters): amount_field = Sum(loan_doc.disbursed_amount) posting_date = (loan_doc.disbursement_date).as_("posting_date") account = loan_doc.disbursement_account + salary_condition = loan_doc.docstatus == 1 else: amount_field = Sum(loan_doc.amount_paid) posting_date = (loan_doc.posting_date).as_("posting_date") account = loan_doc.payment_account - + salary_condition = loan_doc.repay_from_salary == 0 amount = ( frappe.qb.from_(loan_doc) .select(amount_field) .where(loan_doc.docstatus == 1) + .where(salary_condition) .where(account == filters.get("account")) .where(posting_date > getdate(filters.get("report_date"))) .where(ifnull(loan_doc.clearance_date, "4000-01-01") <= getdate(filters.get("report_date"))) diff --git a/erpnext/accounts/report/cash_flow/cash_flow.py b/erpnext/accounts/report/cash_flow/cash_flow.py index 74926b90ffa..75e983afc0e 100644 --- a/erpnext/accounts/report/cash_flow/cash_flow.py +++ b/erpnext/accounts/report/cash_flow/cash_flow.py @@ -262,7 +262,10 @@ def get_report_summary(summary_data, currency): def get_chart_data(columns, data): labels = [d.get("label") for d in columns[2:]] datasets = [ - {"name": account.get("account").replace("'", ""), "values": [account.get("total")]} + { + "name": account.get("account").replace("'", ""), + "values": [account.get(d.get("fieldname")) for d in columns[2:]], + } for account in data if account.get("parent_account") == None and account.get("currency") ] diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index c9c2ab17a9e..056084b7e81 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -34,6 +34,7 @@ from erpnext.accounts.doctype.pricing_rule.utils import ( from erpnext.accounts.party import ( get_party_account, get_party_account_currency, + get_party_gle_currency, validate_party_frozen_disabled, ) from erpnext.accounts.utils import get_account_currency, get_fiscal_years, validate_fiscal_year @@ -168,6 +169,7 @@ class AccountsController(TransactionBase): self.validate_party() self.validate_currency() + self.validate_party_account_currency() if self.doctype in ["Purchase Invoice", "Sales Invoice"]: pos_check_field = "is_pos" if self.doctype == "Sales Invoice" else "is_paid" @@ -1447,6 +1449,27 @@ class AccountsController(TransactionBase): # at quotation / sales order level and we shouldn't stop someone # from creating a sales invoice if sales order is already created + def validate_party_account_currency(self): + if self.doctype not in ("Sales Invoice", "Purchase Invoice"): + return + + if self.is_opening == "Yes": + return + + party_type, party = self.get_party() + party_gle_currency = get_party_gle_currency(party_type, party, self.company) + party_account = ( + self.get("debit_to") if self.doctype == "Sales Invoice" else self.get("credit_to") + ) + party_account_currency = get_account_currency(party_account) + + if not party_gle_currency and (party_account_currency != self.currency): + frappe.throw( + _("Party Account {0} currency and document currency should be same").format( + frappe.bold(party_account) + ) + ) + def delink_advance_entries(self, linked_doc_name): total_allocated_amount = 0 for adv in self.advances: diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index 8614fcb9cdc..dcbdf8a81e6 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -448,8 +448,6 @@ class LoanRepayment(AccountsController): "remarks": remarks, "cost_center": self.cost_center, "posting_date": getdate(self.posting_date), - "party_type": self.applicant_type if self.repay_from_salary else "", - "party": self.applicant if self.repay_from_salary else "", } ) ) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 4d9a7e06bfd..8c0ebe7a904 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -359,7 +359,7 @@ erpnext.patches.v13_0.set_work_order_qty_in_so_from_mr erpnext.patches.v13_0.update_accounts_in_loan_docs erpnext.patches.v14_0.update_batch_valuation_flag erpnext.patches.v14_0.delete_non_profit_doctypes -erpnext.patches.v14_0.update_employee_advance_status +erpnext.patches.v13_0.update_employee_advance_status erpnext.patches.v13_0.add_cost_center_in_loans erpnext.patches.v13_0.set_return_against_in_pos_invoice_references erpnext.patches.v13_0.remove_unknown_links_to_prod_plan_items # 24-03-2022 diff --git a/erpnext/patches/v14_0/update_employee_advance_status.py b/erpnext/patches/v13_0/update_employee_advance_status.py similarity index 100% rename from erpnext/patches/v14_0/update_employee_advance_status.py rename to erpnext/patches/v13_0/update_employee_advance_status.py diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py index 54d56f9612f..473fb0d7c76 100644 --- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py +++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py @@ -16,6 +16,7 @@ from frappe.utils import ( comma_and, date_diff, flt, + get_link_to_form, getdate, ) @@ -45,6 +46,7 @@ class PayrollEntry(Document): def before_submit(self): self.validate_employee_details() + self.validate_payroll_payable_account() if self.validate_attendance: if self.validate_employee_attendance(): frappe.throw(_("Cannot Submit, Employees left to mark attendance")) @@ -66,6 +68,14 @@ class PayrollEntry(Document): if len(emp_with_sal_slip): frappe.throw(_("Salary Slip already exists for {0}").format(comma_and(emp_with_sal_slip))) + def validate_payroll_payable_account(self): + if frappe.db.get_value("Account", self.payroll_payable_account, "account_type"): + frappe.throw( + _( + "Account type cannot be set for payroll payable account {0}, please remove and try again" + ).format(frappe.bold(get_link_to_form("Account", self.payroll_payable_account))) + ) + def on_cancel(self): frappe.delete_doc( "Salary Slip", diff --git a/erpnext/projects/doctype/project/project.json b/erpnext/projects/doctype/project/project.json index 1cda0a08c47..1790da44d6d 100644 --- a/erpnext/projects/doctype/project/project.json +++ b/erpnext/projects/doctype/project/project.json @@ -234,7 +234,7 @@ }, { "fieldname": "actual_start_date", - "fieldtype": "Data", + "fieldtype": "Date", "label": "Actual Start Date (via Time Sheet)", "read_only": 1 }, @@ -458,7 +458,7 @@ "index_web_pages_for_search": 1, "links": [], "max_attachments": 4, - "modified": "2022-01-29 13:58:27.712714", + "modified": "2022-05-25 22:45:06.108499", "modified_by": "Administrator", "module": "Projects", "name": "Project", @@ -504,4 +504,4 @@ "timeline_field": "customer", "title_field": "project_name", "track_seen": 1 -} \ No newline at end of file +} diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index 3dd11f69a76..16b0b4a866f 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -789,11 +789,23 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { if(this.frm.doc.is_pos && (update_paid_amount===undefined || update_paid_amount)) { $.each(this.frm.doc['payments'] || [], function(index, data) { if(data.default && payment_status && total_amount_to_pay > 0) { - let base_amount = flt(total_amount_to_pay, precision("base_amount", data)); + let base_amount, amount; + + if (me.frm.doc.party_account_currency == me.frm.doc.currency) { + // if customer/supplier currency is same as company currency + // total_amount_to_pay is already in customer/supplier currency + // so base_amount has to be calculated using total_amount_to_pay + base_amount = flt(total_amount_to_pay * me.frm.doc.conversion_rate, precision("base_amount", data)); + amount = flt(total_amount_to_pay, precision("amount", data)); + } else { + base_amount = flt(total_amount_to_pay, precision("base_amount", data)); + amount = flt(total_amount_to_pay / me.frm.doc.conversion_rate, precision("amount", data)); + } + frappe.model.set_value(data.doctype, data.name, "base_amount", base_amount); - let amount = flt(total_amount_to_pay / me.frm.doc.conversion_rate, precision("amount", data)); frappe.model.set_value(data.doctype, data.name, "amount", amount); payment_status = false; + } else if(me.frm.doc.paid_amount) { frappe.model.set_value(data.doctype, data.name, "amount", 0.0); } diff --git a/erpnext/regional/india/taxes.js b/erpnext/regional/india/taxes.js index 5f6dcdeb922..88973e36b6a 100644 --- a/erpnext/regional/india/taxes.js +++ b/erpnext/regional/india/taxes.js @@ -22,6 +22,7 @@ erpnext.setup_auto_gst_taxation = (doctype) => { 'shipping_address': frm.doc.shipping_address || '', 'shipping_address_name': frm.doc.shipping_address_name || '', 'customer_address': frm.doc.customer_address || '', + 'company_address': frm.doc.company_address, 'supplier_address': frm.doc.supplier_address, 'customer': frm.doc.customer, 'supplier': frm.doc.supplier, diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index fd0fe26e880..0bdbe56de60 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -448,7 +448,7 @@ class Gstr1Report(object): hsn_code = self.item_hsn_map.get(item_code) tax_rate = 0 taxable_value = items.get(item_code) - for rates in hsn_wise_tax_rate.get(hsn_code): + for rates in hsn_wise_tax_rate.get(hsn_code, []): if taxable_value > rates.get("minimum_taxable_value"): tax_rate = rates.get("tax_rate") diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index ce3bd56d55a..7fbfa629392 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -1285,6 +1285,14 @@ class TestPurchaseReceipt(FrappeTestCase): from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import ( make_purchase_invoice as create_purchase_invoice, ) + from erpnext.accounts.party import add_party_account + + add_party_account( + "Supplier", + "_Test Supplier USD", + "_Test Company with perpetual inventory", + "_Test Payable USD - TCP1", + ) pi = create_purchase_invoice( company="_Test Company with perpetual inventory", @@ -1293,6 +1301,7 @@ class TestPurchaseReceipt(FrappeTestCase): expense_account="_Test Account Cost for Goods Sold - TCP1", currency="USD", conversion_rate=70, + supplier="_Test Supplier USD", ) pr = create_purchase_receipt(pi.name)