diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 35dc14d153b2..0abe05b59151 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -4,7 +4,7 @@ import frappe from frappe.utils.user import is_website_user -__version__ = "15.47.1" +__version__ = "15.48.0" def get_default_company(user=None): """Get default company for user""" diff --git a/erpnext/accounts/doctype/account/account_tree.js b/erpnext/accounts/doctype/account/account_tree.js index 49d5396c0f37..a61fcb4f5301 100644 --- a/erpnext/accounts/doctype/account/account_tree.js +++ b/erpnext/accounts/doctype/account/account_tree.js @@ -237,19 +237,22 @@ frappe.treeview_settings["Account"] = { }, post_render: function (treeview) { frappe.treeview_settings["Account"].treeview["tree"] = treeview.tree; - treeview.page.set_primary_action( - __("New"), - function () { - let root_company = treeview.page.fields_dict.root_company.get_value(); - - if (root_company) { - frappe.throw(__("Please add the account to root level Company - {0}"), [root_company]); - } else { - treeview.new_node(); - } - }, - "add" - ); + if (treeview.can_create) { + treeview.page.set_primary_action( + __("New"), + function () { + let root_company = treeview.page.fields_dict.root_company.get_value(); + if (root_company) { + frappe.throw(__("Please add the account to root level Company - {0}"), [ + root_company, + ]); + } else { + treeview.new_node(); + } + }, + "add" + ); + } }, toolbar: [ { diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js index b15745d834c3..7a653e12c727 100644 --- a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js +++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js @@ -120,6 +120,7 @@ frappe.ui.form.on("Bank Reconciliation Tool", { args: { bank_account: frm.doc.bank_account, till_date: frappe.datetime.add_days(frm.doc.bank_statement_from_date, -1), + company: frm.doc.company, }, callback: (response) => { frm.set_value("account_opening_balance", response.message); @@ -135,6 +136,7 @@ frappe.ui.form.on("Bank Reconciliation Tool", { args: { bank_account: frm.doc.bank_account, till_date: frm.doc.bank_statement_to_date, + company: frm.doc.company, }, callback: (response) => { frm.cleared_balance = response.message; diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py index 1f4487d3527a..f0d99c8f515d 100644 --- a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py +++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py @@ -78,10 +78,17 @@ def get_bank_transactions(bank_account, from_date=None, to_date=None): @frappe.whitelist() -def get_account_balance(bank_account, till_date): +def get_account_balance(bank_account, till_date, company): # returns account balance till the specified date account = frappe.db.get_value("Bank Account", bank_account, "account") - filters = frappe._dict({"account": account, "report_date": till_date, "include_pos_transactions": 1}) + filters = frappe._dict( + { + "account": account, + "report_date": till_date, + "include_pos_transactions": 1, + "company": company, + } + ) data = get_entries(filters) balance_as_per_system = get_balance_on(filters["account"], filters["report_date"]) @@ -93,11 +100,7 @@ def get_account_balance(bank_account, till_date): amounts_not_reflected_in_system = get_amounts_not_reflected_in_system(filters) - bank_bal = ( - flt(balance_as_per_system) - flt(total_debit) + flt(total_credit) + amounts_not_reflected_in_system - ) - - return bank_bal + return flt(balance_as_per_system) - flt(total_debit) + flt(total_credit) + amounts_not_reflected_in_system @frappe.whitelist() diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool_erpnext/__init__.py b/erpnext/accounts/doctype/bank_reconciliation_tool_erpnext/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool_erpnext/bank_reconciliation_tool_erpnext.js b/erpnext/accounts/doctype/bank_reconciliation_tool_erpnext/bank_reconciliation_tool_erpnext.js new file mode 100644 index 000000000000..f2506ae760ae --- /dev/null +++ b/erpnext/accounts/doctype/bank_reconciliation_tool_erpnext/bank_reconciliation_tool_erpnext.js @@ -0,0 +1,388 @@ +// Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.provide("erpnext.accounts.bank_reconciliation"); + +frappe.ui.form.on("Bank Reconciliation Tool ERPNext", { + refresh(frm) { + frm.disable_save(); + // frappe.require("bank-reconciliation-tool.bundle.js", () => frm.trigger("make_reconciliation_tool")); + if (frm.doc.closing_balance_as_per_bank_statement && frm.doc.closing_balance_as_per_erp) { + frm.set_value( + "difference_amount", + (frm.doc.closing_balance_as_per_bank_statement - frm.doc.closing_balance_as_per_erp) + ); + } + + frm.add_custom_button(__("Upload Bank Statement"), () => + frappe.call({ + method: "erpnext.accounts.doctype.bank_statement_import.bank_statement_import.upload_bank_statement", + args: { + dt: frm.doc.doctype, + dn: frm.doc.name, + company: frm.doc.company, + bank_account: frm.doc.bank_account, + }, + callback: function (r) { + if (!r.exc) { + var doc = frappe.model.sync(r.message); + frappe.set_route("Form", doc[0].doctype, doc[0].name); + } + }, + }) + ); + if (frm.doc.closing_balance_as_per_erp && frm.doc.closing_balance_as_per_bank_statement) { + frm.set_value( + "difference_amount", + frm.doc.closing_balance_as_per_bank_statement - frm.doc.closing_balance_as_per_erp + ); + } + // console.log(" NOT ALLLOCSATE",frm.doc.bank_statement.length) + if (frm.doc.bank_statement.length && frm.doc.erp_transaction.length) { + // console.log("ALLLOCSATE") + frm.add_custom_button(__("Allocate"), () => frm.trigger("allocate")); + frm.change_custom_button_type(__("Allocate"), null, "primary"); + frm.change_custom_button_type(__("Get Unreconciled Entries"), null, "default"); + } + if (frm.doc.matching_table.length) { + frm.add_custom_button(__("Reconcile"), () => frm.trigger("reconcile")); + frm.change_custom_button_type(__("Reconcile"), null, "primary"); + frm.change_custom_button_type(__("Get Unreconciled Entries"), null, "default"); + frm.change_custom_button_type(__("Allocate"), null, "default"); + frm.doc.matching_table.map((i) => { + frm.doc.erp_transaction.map((j) => { + if (i.reference_id == j.reference_id) { + frm.doc.bank_statement.map((k) => { + if (k.bank_transaction_id == i.bank_transaction_id) { + if ((j.withdraw || j.deposit) - (k.withdraw || k.deposit) >= 0) { + j.remaining_amount = + (j.withdraw || j.deposit) - (k.withdraw || k.deposit); + } else { + j.remaining_amount = 0; + } + } + }); + } + }); + }); + frm.refresh_field("erp_transaction"); + } + + }, + setup: function (frm) { + frm.set_query("bank_account", function () { + return { + filters: { + company: frm.doc.company, + is_company_account: 1, + }, + }; + }); + // let no_bank_transactions_text = `
${__( + // "No Matching Bank Transactions Found" + // )}
`; + // set_field_options("no_bank_transactions", no_bank_transactions_text); + }, + bank_account: function (frm) { + frappe.db.get_value("Bank Account", frm.doc.bank_account, "account", (r) => { + frappe.db.get_value("Account", r.account, "account_currency", (r) => { + frm.doc.account_currency = r.account_currency; + // console.log('rrrr',r, frm.doc.account_currency) + // frm.trigger("render_chart"); + }); + }); + // frm.trigger("render_chart"); + frm.trigger("get_account_opening_balance"); + frm.doc.difference_amount = (frm.doc.closing_balance_as_per_bank_statement - frm.doc.closing_balance_as_per_erp) + frm.refresh_fields(); + frm.add_custom_button(__("Get Unreconciled Entries"), function () { + frm.set_value( + "difference_amount", + (frm.doc.closing_balance_as_per_bank_statement - frm.doc.closing_balance_as_per_erp) + ); + frappe.call({ + method: "erpnext.accounts.doctype.bank_reconciliation_tool_erpnext.bank_reconciliation_tool_erpnext.get_bank_transaction", + args: { + bank_account: frm.doc.bank_account, + company: frm.doc.company, + from_statement_date: frm.doc.from_statement_date, + to_statement_date: frm.doc.to_statement_date, + }, + callback: (response) => { + let existingTransactionIds = new Set( + frm.doc.bank_statement.map((row) => row.bank_transaction_id) // Collect existing IDs + ); + // console.log('Response:', response.message); + + if (Array.isArray(response.message)) { + response.message.forEach((transaction) => { + if (!existingTransactionIds.has(transaction.name)) { + if (transaction.deposit || transaction.withdrawal) { + // Add a new child row to the bank_statement table + let bankTransaction = frm.add_child("bank_statement"); + bankTransaction.date = transaction.date; + bankTransaction.bank_transaction_id = transaction.name; + bankTransaction.description = transaction.description; + bankTransaction.deposit = transaction.deposit; + bankTransaction.withdraw = transaction.withdrawal; + bankTransaction.reference_no = transaction.reference_number; + bankTransaction.unallocated_amount = transaction.unallocated_amount; + + // Track added transaction IDs + existingTransactionIds.add(transaction.name); + } + } + }); + } else { + console.error("Invalid response.message:", response.message); + } + + frm.refresh_field("bank_statement"); + }, + }); + frappe.call({ + method: "erpnext.accounts.doctype.bank_reconciliation_tool_erpnext.bank_reconciliation_tool_erpnext.get_erp_transaction", + args: { + bank_account: frm.doc.bank_account, + company: frm.doc.company, + from_statement_date: frm.doc.from_erp_date, + to_statement_date: frm.doc.to_erp_date, + }, + callback: async (response) => { + // Create a Set of existing reference IDs in the `erp_transaction` table + let existingReferenceIds = new Set( + frm.doc.erp_transaction.map((row) => row.reference_id) + ); + + for (const i of response.message) { + if (i.paid_amount > 0 && !existingReferenceIds.has(i.name)) { + // Add the transaction if it does not already exist + let bnk_tr = frm.add_child("erp_transaction"); + bnk_tr.date = i.posting_date; + bnk_tr.reference_id = i.name; // Unique reference ID + bnk_tr.reference_number = i.reference_no; + bnk_tr.reference_doc = i.doctype; + + if (i.doctype === "Payment Entry") { + const payment_type = await frappe.db.get_value( + "Payment Entry", + i.name, + "payment_type" + ); + + if (payment_type.message.payment_type === "Pay") { + bnk_tr.withdraw = i.paid_amount; + bnk_tr.deposit = 0; + } else if (payment_type.message.payment_type === "Receive") { + bnk_tr.deposit = i.paid_amount; + bnk_tr.withdraw = 0; + } else { + bnk_tr.withdraw = i.paid_amount; + bnk_tr.deposit = 0; + } + } else if (i.doctype === "Journal Entry") { + if (i.bank == 'Credit'){ + bnk_tr.deposit = 0; + bnk_tr.withdraw = i.paid_amount; // Confirm logic here. + } + else if (i.bank == 'Debit'){ + bnk_tr.deposit = i.paid_amount; + bnk_tr.withdraw = 0; // Confirm logic here. + } + } + + // Add the reference ID to the Set to track it + existingReferenceIds.add(i.name); + } + } + + // Refresh field after processing all transactions + frm.refresh_field("erp_transaction"); + // Add custom button for Allocate + frm.add_custom_button(__("Allocate"), () => frm.trigger("allocate")); + frm.change_custom_button_type(__("Allocate"), null, "primary"); + frm.change_custom_button_type(__("Get Unreconciled Entries"), null, "default"); + }, + }); + }); + }, + get_account_opening_balance(frm) { + if (frm.doc.bank_account && frm.doc.from_date && frm.doc.to_date) { + frappe.call({ + method: "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_account_balance", + args: { + bank_account: frm.doc.bank_account, + till_date: frappe.datetime.add_days(frm.doc.bank_statement_from_date, -1), + company: frm.doc.company + }, + callback: (response) => { + frm.set_value("opening_balance", response.message); + }, + }); + } + }, + + onload: function (frm) { + // Set default filter dates + let today = frappe.datetime.get_today(); + frm.doc.from_date = frappe.datetime.add_months(today, -1); + frm.doc.to_date = today; + frm.doc.from_statement_date = frappe.datetime.add_months(today, -1); + frm.doc.to_statement_date = today; + frm.doc.from_erp_date = frappe.datetime.add_months(today, -1); + frm.doc.to_erp_date = today; + frm.trigger("bank_account"); + }, + + opening_balance(frm) { + frm.set_value("closing_balance_as_per_erp", frm.doc.opening_balance); + // console.log("CLOSING BAL") + frappe.call({ + method: "erpnext.accounts.doctype.bank_reconciliation_tool_erpnext.bank_reconciliation_tool_erpnext.get_closing_bal", + args: { + opening_bal: frm.doc.opening_balance, + from_date: frm.doc.from_date, + to_date: frm.doc.to_date, + bank_account: frm.doc.bank_account, + }, + callback: function (r) { + // console.log('GHJJHJHJHJBBJ', r) + if (!r.exc) { + if (r.message) { + frm.doc.closing_balance_as_per_bank_statement = r.message; + } + } + frm.refresh_field("closing_balance_as_per_bank_statement"); + }, + }); + }, + + closing_balance_as_per_erp(frm) { + if (frm.doc.closing_balance_as_per_bank_statement) { + frm.set_value( + "difference_amount", + (frm.doc.closing_balance_as_per_bank_statement - frm.doc.closing_balance_as_per_erp) + ); + } + }, + closing_balance_as_per_bank_statement(frm) { + if (frm.doc.closing_balance_as_per_bank_statement) { + frm.set_value( + "difference_amount", + (frm.doc.closing_balance_as_per_bank_statement - frm.doc.closing_balance_as_per_erp) + ); + } + }, + // make_reconciliation_tool(frm) { + // frm.get_field("reconciliation_tool_cards").$wrapper.empty(); + // if (frm.doc.bank_account && frm.doc.bank_statement_to_date) { + // frm.trigger("get_cleared_balance").then(() => { + // if ( + // frm.doc.bank_account && + // frm.doc.from_date && + // frm.doc.to_date + // ) { + // frm.trigger("render_chart"); + // frm.trigger("render"); + // frappe.utils.scroll_to(frm.get_field("reconciliation_tool_cards").$wrapper, true, 30); + // } + // }); + // } + // }, + // render_chart(frm) { + // frm.cards_manager = new erpnext.accounts.bank_reconciliation.NumberCardManager({ + // $reconciliation_tool_cards: frm.get_field("reconciliation_tool_cards").$wrapper, + // bank_statement_closing_balance: frm.doc.closing_balance_as_per_bank_statement, + // cleared_balance: frm.doc.difference_amount, + // currency: frm.doc.account_currency, + // }); + // }, + allocate(frm) { + let bank_statement = frm.fields_dict.bank_statement.grid.get_selected_children(); + if (!bank_statement.length) { + bank_statement = frm.doc.bank_statement; + } + let erp_transaction = frm.fields_dict.erp_transaction.grid.get_selected_children(); + if (!erp_transaction.length) { + erp_transaction = frm.doc.erp_transaction; + } + return frm.call({ + doc: frm.doc, + method: "allocate_entries", + args: { + bank_statement: bank_statement, + erp_transaction: erp_transaction, + }, + callback: () => { + frm.refresh(); + }, + }); + }, + + reconcile(frm) { + frm.doc.matching_table.map((i) => { + frm.call({ + method: "erpnext.accounts.doctype.bank_reconciliation_tool_erpnext.bank_reconciliation_tool_erpnext.reconcile_bnk_transaction", + args: { + bank_transaction_id: i.bank_transaction_id, + amount: i.matched_amount, + name: i.reference_id, + payment_document: i.reference_to, + }, + callback: function (r) { + frm.clear_table("matching_table"); + frm.refresh(); + // console.log('GHJJHJHJHJBBJ') + if (!r.exc) { + if (r.message) { + frappe.msgprint("done"); + } + } + }, + }); + }); + }, + + // render_chart(frm) { + // frm.cards_manager = new erpnext.accounts.bank_reconciliation.NumberCardManager({ + // $reconciliation_tool_cards: frm.get_field("reconciliation_tool_cards").$wrapper, + // bank_statement_closing_balance: frm.doc.closing_balance_as_per_bank_statement, + // cleared_balance: frm.cleared_balance, + // currency: frm.doc.account_currency, + // }); + // }, + + // render(frm) { + // if (frm.doc.bank_account) { + // frm.bank_reconciliation_data_table_manager = + // new erpnext.accounts.bank_reconciliation.DataTableManager({ + // company: frm.doc.company, + // bank_account: frm.doc.bank_account, + // $reconciliation_tool_dt: frm.get_field("reconciliation_tool_dt").$wrapper, + // $no_bank_transactions: frm.get_field("no_bank_transactions").$wrapper, + // bank_statement_from_date: frm.doc.from_date, + // bank_statement_to_date: frm.doc.to_date, + // filter_by_reference_date: frm.doc.from_statement_date, + // from_reference_date: frm.doc.from_statement_date, + // to_reference_date: frm.doc.to_statement_date, + // bank_statement_closing_balance: frm.doc.closing_balance_as_per_bank_statement, + // cards_manager: frm.cards_manager, + // }); + // } + // }, + + // get_cleared_balance(frm) { + // if (frm.doc.bank_account && frm.doc.bank_statement_to_date) { + // return frappe.call({ + // method: "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_account_balance", + // args: { + // bank_account: frm.doc.bank_account, + // till_date: frm.doc.bank_statement_to_date, + // }, + // callback: (response) => { + // frm.cleared_balance = response.message; + // }, + // }); + // } + // }, +}); diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool_erpnext/bank_reconciliation_tool_erpnext.json b/erpnext/accounts/doctype/bank_reconciliation_tool_erpnext/bank_reconciliation_tool_erpnext.json new file mode 100644 index 000000000000..73e70cb8b7a5 --- /dev/null +++ b/erpnext/accounts/doctype/bank_reconciliation_tool_erpnext/bank_reconciliation_tool_erpnext.json @@ -0,0 +1,196 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2025-01-08 14:08:28.332086", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "company", + "bank_account", + "from_date", + "opening_balance", + "column_break_modz", + "closing_balance_as_per_bank_statement", + "closing_balance_as_per_erp", + "to_date", + "difference_amount", + "filters_section", + "from_statement_date", + "from_erp_date", + "column_break_mmtl", + "to_statement_date", + "to_erp_date", + "unreconciled__entries_section", + "bank_statement", + "column_break_krek", + "erp_transaction", + "allocated_entries_section", + "matching_table", + "reconcile_section", + "reconciliation_tool_cards", + "reconciliation_tool_dt", + "no_bank_transactions" + ], + "fields": [ + { + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company" + }, + { + "fieldname": "bank_account", + "fieldtype": "Link", + "label": "Bank Account", + "options": "Bank Account" + }, + { + "depends_on": "eval: doc.bank_account", + "fieldname": "from_date", + "fieldtype": "Date", + "label": "From Date" + }, + { + "depends_on": "eval:doc.bank_account", + "fieldname": "opening_balance", + "fieldtype": "Currency", + "label": "Opening Balance", + "read_only": 1 + }, + { + "fieldname": "column_break_modz", + "fieldtype": "Column Break" + }, + { + "depends_on": "eval:doc.bank_account", + "fieldname": "closing_balance_as_per_bank_statement", + "fieldtype": "Currency", + "label": "Closing Balance as per Bank Statement" + }, + { + "depends_on": "eval:doc.bank_account", + "fieldname": "closing_balance_as_per_erp", + "fieldtype": "Currency", + "label": "Closing Balance as per ERP" + }, + { + "depends_on": "eval: doc.bank_account", + "fieldname": "to_date", + "fieldtype": "Date", + "label": "To Date " + }, + { + "depends_on": "eval:doc.bank_account", + "fieldname": "difference_amount", + "fieldtype": "Currency", + "label": "Difference Amount", + "read_only": 1 + }, + { + "collapsible": 1, + "depends_on": "eval:doc.bank_account", + "fieldname": "filters_section", + "fieldtype": "Section Break", + "label": "Filters" + }, + { + "fieldname": "from_statement_date", + "fieldtype": "Date", + "label": "From Statement Date " + }, + { + "fieldname": "from_erp_date", + "fieldtype": "Date", + "label": "From ERP Date" + }, + { + "fieldname": "column_break_mmtl", + "fieldtype": "Column Break" + }, + { + "fieldname": "to_statement_date", + "fieldtype": "Date", + "label": "To Statement Date" + }, + { + "fieldname": "to_erp_date", + "fieldtype": "Date", + "label": "To ERP Date" + }, + { + "depends_on": "eval:(doc.bank_statement).length || (doc.erp_transaction).length", + "fieldname": "unreconciled__entries_section", + "fieldtype": "Section Break", + "label": "Unreconciled entries" + }, + { + "fieldname": "bank_statement", + "fieldtype": "Table", + "label": "Bank Statement", + "options": "Bank Statement" + }, + { + "fieldname": "column_break_krek", + "fieldtype": "Column Break" + }, + { + "fieldname": "erp_transaction", + "fieldtype": "Table", + "label": "ERP Transaction", + "options": "ERP Transaction" + }, + { + "depends_on": "matching_table", + "fieldname": "allocated_entries_section", + "fieldtype": "Section Break", + "label": "Allocated Entries" + }, + { + "fieldname": "matching_table", + "fieldtype": "Table", + "label": "Matching Table", + "options": "Matching Table" + }, + { + "fieldname": "reconcile_section", + "fieldtype": "Section Break", + "label": "Reconcile" + }, + { + "fieldname": "reconciliation_tool_cards", + "fieldtype": "HTML" + }, + { + "fieldname": "reconciliation_tool_dt", + "fieldtype": "HTML" + }, + { + "fieldname": "no_bank_transactions", + "fieldtype": "HTML" + } + ], + "hide_toolbar": 1, + "index_web_pages_for_search": 1, + "issingle": 1, + "links": [], + "modified": "2025-01-13 15:20:07.590550", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Bank Reconciliation Tool ERPNext", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool_erpnext/bank_reconciliation_tool_erpnext.py b/erpnext/accounts/doctype/bank_reconciliation_tool_erpnext/bank_reconciliation_tool_erpnext.py new file mode 100644 index 000000000000..eaa5d9605706 --- /dev/null +++ b/erpnext/accounts/doctype/bank_reconciliation_tool_erpnext/bank_reconciliation_tool_erpnext.py @@ -0,0 +1,229 @@ +# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import frappe +from frappe import _ +from frappe.model.document import Document +from frappe.utils import flt + +from erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool import get_linked_payments + + +class BankReconciliationToolERPNext(Document): + # begin: auto-generated types + # This code is auto-generated. Do not modify anything in this block. + + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from erpnext.accounts.doctype.bank_statement.bank_statement import BankStatement + from erpnext.accounts.doctype.erp_transaction.erp_transaction import ERPTransaction + from erpnext.accounts.doctype.matching_table.matching_table import MatchingTable + from frappe.types import DF + + bank_account: DF.Link | None + bank_statement: DF.Table[BankStatement] + closing_balance_as_per_bank_statement: DF.Currency + closing_balance_as_per_erp: DF.Currency + company: DF.Link | None + difference_amount: DF.Currency + erp_transaction: DF.Table[ERPTransaction] + from_date: DF.Date | None + from_erp_date: DF.Date | None + from_statement_date: DF.Date | None + matching_table: DF.Table[MatchingTable] + opening_balance: DF.Currency + to_date: DF.Date | None + to_erp_date: DF.Date | None + to_statement_date: DF.Date | None + # end: auto-generated types + # pass + def validate_entries(self): + if not self.get("erp_transaction"): + frappe.throw(_("No records found in the ERP Transactions table")) + + if not self.get("bank_statement"): + frappe.throw(_("No records found in the Bank Statement table")) + + def get_allocated_entry(self, pay, bnk_st, allocated_amount): + res = frappe._dict( + { + "bank_transaction_id": bnk_st.get("bank_transaction_id"), + "reference_to": pay.get("reference_doc"), + "matched_amount": allocated_amount, + "reference_id": pay.get("reference_id"), + } + ) + + return res + + @frappe.whitelist() + def allocate_entries(self, args): + self.validate_entries() + + entries = [] + + for pay in args.get("erp_transaction"): + # Initialize unreconciled_amount for deposit/withdraw + if flt(pay.get("deposit")) > 0 and flt(pay.get("withdraw")) == 0: + pay["unreconciled_amount"] = pay["deposit"] + elif flt(pay.get("withdraw")) > 0 and flt(pay.get("deposit")) == 0: + pay["unreconciled_amount"] = pay["withdraw"] + + for bnk_st in args.get("bank_statement"): + allocated_amount = min( + pay.get("deposit", 0) or pay.get("withdraw", 0), bnk_st["unallocated_amount"] + ) + + res = self.get_allocated_entry(pay, bnk_st, allocated_amount) + # print(pay.get("name"), pay.get("doctype")) + + if flt(pay.get("deposit")) > 0: + pay["deposit"] -= allocated_amount + elif flt(pay.get("deposit")) > 0: + pay["withdraw"] -= allocated_amount + + bnk_st["unallocated_amount"] -= allocated_amount + + entries.append(res) + + # Break if pay is fully allocated + if pay.get("deposit") == 0 and pay.get("withdraw") == 0: + break + + # Update the matching table + self.set("matching_table", []) + for entry in entries: + if entry["matched_amount"] != 0: + # print('rowwwww',entry["bank_transaction_id"]) + row = self.append("matching_table", {}) + row.update(entry) + + +@frappe.whitelist() +def get_bank_transaction(bank_account, company, from_statement_date=None, to_statement_date=None): + if from_statement_date and to_statement_date: + bank_transactn_list = frappe.db.get_all( + "Bank Transaction", + filters={ + "date": ["between", [from_statement_date, to_statement_date]], + "bank_account": bank_account, + "company": company, + "status": "Unreconciled", + }, + fields=[ + "date", + "name", + "deposit", + "withdrawal", + "description", + "reference_number", + "unallocated_amount", + ], + ) + else: + bank_transactn_list = frappe.db.get_all( + "Bank Transaction", + filters={"bank_account": bank_account, "company": company, "status": "Unreconciled"}, + fields=[ + "date", + "name", + "deposit", + "withdrawal", + "description", + "reference_number", + "unallocated_amount", + ], + ) + return bank_transactn_list + + +@frappe.whitelist() +def get_erp_transaction(bank_account, company, from_statement_date=None, to_statement_date=None): + transaction_list = frappe.db.get_all( + "Bank Transaction", {"bank_account": bank_account, "status": "Unreconciled"}, pluck="name" + ) + account = frappe.db.get_value("Bank Account", bank_account, 'account') + result = [] + for i in transaction_list: + transaction = frappe.get_doc("Bank Transaction", i) + # print("++++++++++++++++++++++++++++++++") + # # print(transaction_list) + # print(get_linked_payments(transaction.name, document_types=["payment_entry","journal_entry"], from_date=from_statement_date, to_date=to_statement_date, filter_by_reference_date=None, from_reference_date=from_statement_date, to_reference_date=to_statement_date)) + # print("++++++++++++++++++++++++++++++++") + + for i in get_linked_payments(transaction.name, document_types=["payment_entry","journal_entry"], from_date=from_statement_date, to_date=to_statement_date, filter_by_reference_date=None, from_reference_date=from_statement_date, to_reference_date=to_statement_date): + if i['doctype'] == "Journal Entry": + list = frappe.db.get_all("Journal Entry Account", + { + 'account': account, + 'parent': i.name + }, + ['credit_in_account_currency', 'debit_in_account_currency', 'parent'] + ) + for j in list: + if j['credit_in_account_currency'] > 0: + i['bank'] = 'Credit' + elif j['debit_in_account_currency'] > 0: + i['bank'] = 'Debit' + result.append(i) + else: + result.append(i) + + return result + # if from_statement_date and to_statement_date: + # pe_list = frappe.db.get_all("Payment Entry",filters={'posting_date': ['between' ,[from_statement_date, to_statement_date]], 'bank_account':bank_account, 'company': company}, fields = ['posting_date', 'paid_amount', 'reference_no', 'payment_type']) + # je_list = frappe.db.get_all("Journal Entry Account", filters={'bank_account': bank_account}, fields = ["parent", "account", "creation", "debit", "credit"]) + # else: + # pe_list = frappe.db.get_all("Payment Entry",filters={'bank_account':bank_account, 'company': company}, fields = ['posting_date', 'paid_amount', 'reference_no', 'payment_type']) + # je_list = frappe.db.get_all("Journal Entry Account", filters={'bank_account': bank_account}, fields = ["parent", "account", "creation", "debit", "credit"]) + # return pe_list, je_list + + +@frappe.whitelist() +def reconcile_bnk_transaction(bank_transaction_id, amount, name, payment_document): + bnk_trn = frappe.get_doc("Bank Transaction", bank_transaction_id) + # print("***********************************") + # print("docc",bank_transaction_id) + # print("***********************************") + bnk_trn.append( + "payment_entries", + {"payment_document": payment_document, "payment_entry": name, "allocated_amount": flt(amount)}, + ) + try: + bnk_trn.save() + frappe.msgprint(_("Successfully Reconciled")) + except Exception as e: + frappe.msgprint("Please Reconcile again to ") + + +@frappe.whitelist() +def get_closing_bal(opening_bal, from_date, to_date, bank_account): + total_credits = ( + frappe.db.sql( + """ + SELECT SUM(deposit) + FROM `tabBank Transaction` + WHERE bank_account = %s AND date BETWEEN %s AND %s AND docstatus = 1 + """, + (bank_account, from_date, to_date), + )[-1][-1] + or 0 + ) + + # Sum of debits (withdrawals) + total_debits = ( + frappe.db.sql( + """ + SELECT SUM(withdrawal) + FROM `tabBank Transaction` + WHERE bank_account = %s AND date BETWEEN %s AND %s AND docstatus = 1 + """, + (bank_account, from_date, to_date), + )[-1][-1] + or 0 + ) + + # Calculate closing balance + closing_balance = float(opening_bal) + float(total_credits) or 0 - float(total_debits) or 0 + return closing_balance diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool_erpnext/test_bank_reconciliation_tool_erpnext.py b/erpnext/accounts/doctype/bank_reconciliation_tool_erpnext/test_bank_reconciliation_tool_erpnext.py new file mode 100644 index 000000000000..0685beed5615 --- /dev/null +++ b/erpnext/accounts/doctype/bank_reconciliation_tool_erpnext/test_bank_reconciliation_tool_erpnext.py @@ -0,0 +1,9 @@ +# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestBankReconciliationToolERPNext(FrappeTestCase): + pass diff --git a/erpnext/accounts/doctype/bank_statement/__init__.py b/erpnext/accounts/doctype/bank_statement/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/erpnext/accounts/doctype/bank_statement/bank_statement.json b/erpnext/accounts/doctype/bank_statement/bank_statement.json new file mode 100644 index 000000000000..fcaa56106703 --- /dev/null +++ b/erpnext/accounts/doctype/bank_statement/bank_statement.json @@ -0,0 +1,79 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2025-01-08 14:52:55.060923", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "date", + "bank_transaction_id", + "description", + "deposit", + "column_break_ofcr", + "withdraw", + "reference_no", + "unallocated_amount" + ], + "fields": [ + { + "fieldname": "date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Date", + "options": "Date" + }, + { + "fieldname": "bank_transaction_id", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Bank Transaction ID" + }, + { + "fieldname": "description", + "fieldtype": "Small Text", + "in_list_view": 1, + "label": "Description" + }, + { + "fieldname": "deposit", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Deposit" + }, + { + "fieldname": "column_break_ofcr", + "fieldtype": "Column Break" + }, + { + "fieldname": "withdraw", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Withdraw" + }, + { + "fieldname": "reference_no", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Reference No" + }, + { + "fieldname": "unallocated_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Unallocated Amount" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2025-01-10 10:17:54.674125", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Bank Statement", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/bank_statement/bank_statement.py b/erpnext/accounts/doctype/bank_statement/bank_statement.py new file mode 100644 index 000000000000..38c9fd9a525b --- /dev/null +++ b/erpnext/accounts/doctype/bank_statement/bank_statement.py @@ -0,0 +1,28 @@ +# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class BankStatement(Document): + # begin: auto-generated types + # This code is auto-generated. Do not modify anything in this block. + + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from frappe.types import DF + + bank_transaction_id: DF.Data | None + date: DF.Date | None + deposit: DF.Currency + description: DF.SmallText | None + parent: DF.Data + parentfield: DF.Data + parenttype: DF.Data + reference_no: DF.Data | None + unallocated_amount: DF.Currency + withdraw: DF.Currency + # end: auto-generated types + pass diff --git a/erpnext/accounts/doctype/budget/budget.py b/erpnext/accounts/doctype/budget/budget.py index 0e5133e40fb7..96424fee1168 100644 --- a/erpnext/accounts/doctype/budget/budget.py +++ b/erpnext/accounts/doctype/budget/budget.py @@ -496,13 +496,17 @@ def get_actual_expense(args): def get_accumulated_monthly_budget(monthly_distribution, posting_date, fiscal_year, annual_budget): distribution = {} if monthly_distribution: - for d in frappe.db.sql( - """select mdp.month, mdp.percentage_allocation - from `tabMonthly Distribution Percentage` mdp, `tabMonthly Distribution` md - where mdp.parent=md.name and md.fiscal_year=%s""", - fiscal_year, - as_dict=1, - ): + mdp = frappe.qb.DocType("Monthly Distribution Percentage") + md = frappe.qb.DocType("Monthly Distribution") + res = ( + frappe.qb.from_(mdp) + .join(md) + .on(mdp.parent == md.name) + .select(mdp.month, mdp.percentage_allocation) + .where(md.fiscal_year == fiscal_year) + .where(md.name == monthly_distribution) + ) + for d in res: distribution.setdefault(d.month, d.percentage_allocation) dt = frappe.get_cached_value("Fiscal Year", fiscal_year, "year_start_date") diff --git a/erpnext/accounts/doctype/erp_transaction/__init__.py b/erpnext/accounts/doctype/erp_transaction/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/erpnext/accounts/doctype/erp_transaction/erp_transaction.json b/erpnext/accounts/doctype/erp_transaction/erp_transaction.json new file mode 100644 index 000000000000..f069eb7f7d98 --- /dev/null +++ b/erpnext/accounts/doctype/erp_transaction/erp_transaction.json @@ -0,0 +1,79 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2025-01-08 14:59:37.654994", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "date", + "reference_id", + "withdraw", + "column_break_mtjz", + "remaining_amount", + "reference_number", + "deposit", + "reference_doc" + ], + "fields": [ + { + "fieldname": "date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Date" + }, + { + "fieldname": "reference_id", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Reference ID" + }, + { + "fieldname": "withdraw", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Withdraw" + }, + { + "fieldname": "column_break_mtjz", + "fieldtype": "Column Break" + }, + { + "fieldname": "remaining_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Remaining Amount" + }, + { + "fieldname": "reference_number", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Reference Number" + }, + { + "fieldname": "deposit", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Deposit" + }, + { + "fieldname": "reference_doc", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Reference Doc", + "options": "Payment Entry\nJournal Entry" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2025-01-10 15:13:55.310249", + "modified_by": "Administrator", + "module": "Accounts", + "name": "ERP Transaction", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/erp_transaction/erp_transaction.py b/erpnext/accounts/doctype/erp_transaction/erp_transaction.py new file mode 100644 index 000000000000..ca8619953e40 --- /dev/null +++ b/erpnext/accounts/doctype/erp_transaction/erp_transaction.py @@ -0,0 +1,28 @@ +# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class ERPTransaction(Document): + # begin: auto-generated types + # This code is auto-generated. Do not modify anything in this block. + + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from frappe.types import DF + + date: DF.Date | None + deposit: DF.Currency + parent: DF.Data + parentfield: DF.Data + parenttype: DF.Data + reference_doc: DF.Literal["Payment Entry", "Journal Entry"] + reference_id: DF.Data | None + reference_number: DF.Data | None + remaining_amount: DF.Currency + withdraw: DF.Currency + # end: auto-generated types + pass diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/test_exchange_rate_revaluation.py b/erpnext/accounts/doctype/exchange_rate_revaluation/test_exchange_rate_revaluation.py index f99c58bd91c1..716a20699eec 100644 --- a/erpnext/accounts/doctype/exchange_rate_revaluation/test_exchange_rate_revaluation.py +++ b/erpnext/accounts/doctype/exchange_rate_revaluation/test_exchange_rate_revaluation.py @@ -740,35 +740,45 @@ def gain_loss_account(company:str): def create_account(**args): account_name = args.get('account_name') - company = args.get('company', "_Test Company") + if not account_name: + return + company = args.get('company', " ") + existing_account = frappe.db.exists("Account", { "name": f"{account_name} - _TC" }) + if not existing_account: try: doc = frappe.get_doc({ "doctype": "Account", - "company": company, "account_type": args.get('account_type', " "), "account_name": account_name, - "parent_account": args.get('parent_account'), "report_type": args.get('report_type', "Balance Sheet"), "root_type": args.get('root_type', "Liability"), "account_currency": args.get('account_currency', "INR"), - }).insert() - + "is_group": args.get('is_group', 0) + }) + if args.get('parent_account'): + doc.parent_account = args.get('parent_account') + if args.get('company'): + doc.company = args.get('company') + doc.insert(ignore_mandatory=True) frappe.db.commit() - print(f"Account {account_name} created successfully.") except Exception as e: frappe.log_error(f"Account Creation Failed: {account_name}", str(e)) - print(f"Error creating account '{account_name}': {str(e)}") - else: - print(f"Account '{account_name} _TC' already exists.") + + def create_records_for_err(): from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_supplier - from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_customer + + create_warehouse( + warehouse_name="_Test Warehouse", + company="_Test Company" + ) + create_account( account_name="_Test Payable USD", parent_account="Current Assets - _TC", @@ -797,7 +807,7 @@ def create_records_for_err(): report_type="Balance Sheet", ) create_cost_center( - cost_center_name="_Test Cost Center - _TC", + cost_center_name="_Test Cost Center", company="_Test Company", parent_cost_center="_Test Company - _TC" ) @@ -838,4 +848,35 @@ def create_records_for_err(): }, ) supplier.save() - frappe.db.commit() \ No newline at end of file + frappe.db.commit() + +def create_warehouse(**args): + warehouse_name = args.get('warehouse_name') + company = args.get('company', "_Test Company") + full_warehouse_name = f"{warehouse_name} - _TC" + if not frappe.db.exists("Warehouse", full_warehouse_name): + frappe.get_doc( + { + "doctype": "Warehouse", + "warehouse_name": warehouse_name, + "company": company, + } + ).insert(ignore_mandatory=True) + frappe.db.commit() + + + +def create_cost_center(**args): + args = frappe._dict(args) + if args.cost_center_name: + company = args.company or "_Test Company" + company_abbr = frappe.db.get_value("Company", company, "abbr") + cc_name = args.cost_center_name + " - " + company_abbr + if not frappe.db.exists("Cost Center", cc_name): + cc = frappe.new_doc("Cost Center") + cc.company = args.company or "_Test Company" + cc.cost_center_name = args.cost_center_name + cc.is_group = args.is_group or 0 + cc.parent_cost_center = args.parent_cost_center or "_Test Company - _TC" + cc.insert() + frappe.db.commit() \ No newline at end of file diff --git a/erpnext/accounts/doctype/gl_closing/gl_closing.py b/erpnext/accounts/doctype/gl_closing/gl_closing.py index 1316c1c858b1..62ec10da9d08 100644 --- a/erpnext/accounts/doctype/gl_closing/gl_closing.py +++ b/erpnext/accounts/doctype/gl_closing/gl_closing.py @@ -104,10 +104,17 @@ def get_merge(): .run(as_dict=True) ) return data + + @frappe.whitelist(allow_guest=True) def get_account_settings(): - acs = frappe.get_all("Accounts Closing Table", fields=["company", "frozen_accounts_modifier"]) + if frappe.db.table_exists("Accounts Closing Table"): + acs = frappe.get_all("Accounts Closing Table", fields=["company", "frozen_accounts_modifier"]) + else: + acs = [] return acs + + @frappe.whitelist(allow_guest=True) def get_user_roles(): user = frappe.session.user diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py index 8678a49f1ee5..6553deaf4af6 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -294,6 +294,9 @@ def validate_party(self): validate_account_party_type(self) def validate_currency(self): + if self.is_cancelled: + return + company_currency = erpnext.get_company_currency(self.company) account_currency = get_account_currency(self.account) diff --git a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py index e4cd0047b8a6..d5a90af2e64e 100644 --- a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py @@ -593,7 +593,7 @@ def test_select_tds_payable_and_creditors_account_TC_ACC_024(self): create_records('_Test Supplier TDS') supplier = frappe.get_doc("Supplier", "_Test Supplier TDS") - account = frappe.get_doc("Account", "Test TDS Payable - _TC") + account = frappe.get_doc("Account", "_Test TDS Payable - _TC") if supplier and account: jv=frappe.new_doc("Journal Entry") @@ -607,7 +607,7 @@ def test_select_tds_payable_and_creditors_account_TC_ACC_024(self): "credit_in_account_currency": 1000 }, { - "account": 'Test Creditors - _TC', + "account": 'Creditors - _TC', "party_type": "Supplier", "party": supplier.name, "debit_in_account_currency": 1000, @@ -628,7 +628,7 @@ def test_select_tds_payable_and_creditors_account_TC_ACC_024(self): self.expected_gle = [ { - "account": 'Test Creditors - _TC', + "account": 'Creditors - _TC', "debit_in_account_currency": 1000, "credit_in_account_currency": 0, "cost_center": "Main - _TC", @@ -861,6 +861,307 @@ def test_deferred_expense_entry_TC_ACC_055(self): self.assertEqual(entry["debit"], expected["debit"], f"Debit mismatch for {entry['account']}.") self.assertEqual(entry["credit"], expected["credit"], f"Credit mismatch for {entry['account']}.") + def test_deferred_expense_entry_TC_ACC_056(self): + # Set up input parameters + entry_type = "Deferred Expense" + debit_account = "Write Off - _TC" + credit_account = "_Test Accumulated Depreciations - _TC" + amount = 30000.0 + + # Create the Journal Entry using the existing function + jv = make_journal_entry( + account1=debit_account, + account2=credit_account, + amount=amount, + save=False, + submit=False, + ) + # Set the entry type and save the journal entry + jv.entry_type = entry_type + jv.save().submit() + + # Fetch GL Entries to validate the transaction + gl_entries = frappe.db.sql( + """SELECT account, debit, credit FROM `tabGL Entry` + WHERE voucher_type='Journal Entry' AND voucher_no=%s + ORDER BY account""", + jv.name, + as_dict=True, + ) + # Expected GL entries + expected_gl_entries = [ + {"account": credit_account, "debit": 0, "credit": amount}, + {"account": debit_account, "debit": amount, "credit": 0} + ] + + # Assertions + self.assertEqual(len(gl_entries), 2, "Incorrect number of GL entries created.") + for entry, expected in zip(gl_entries, expected_gl_entries): + self.assertEqual(entry["account"], expected["account"], "Account mismatch in GL Entry.") + self.assertEqual(entry["debit"], expected["debit"], f"Debit mismatch for {entry['account']}.") + self.assertEqual(entry["credit"], expected["credit"], f"Credit mismatch for {entry['account']}.") + + def test_deferred_revenue_entry_TC_ACC_057(self): + # Set up input parameters + entry_type = "Deferred Revenue" + debit_account = "Debtors - _TC" + credit_account = "Creditors - _TC" + amount = 30000.0 + + # Create the Journal Entry using the existing function + jv = make_journal_entry( + account1=debit_account, + account2=credit_account, + amount=amount, + save=False, + submit=False, + ) + for account in jv.accounts: + if account.account == "Creditors - _TC": + account.party_type = "Supplier" + account.party = "_Test Supplier" + + elif account.account == "Debtors - _TC": + account.party_type = "Customer" + account.party = "_Test Customer" + # Set the entry type and save the journal entry + jv.entry_type = entry_type + jv.save().submit() + + # Fetch GL Entries to validate the transaction + gl_entries = frappe.db.sql( + """SELECT account, debit, credit FROM `tabGL Entry` + WHERE voucher_type='Journal Entry' AND voucher_no=%s + ORDER BY account""", + jv.name, + as_dict=True, + ) + # Expected GL entries + expected_gl_entries = [ + {"account": credit_account, "debit": 0, "credit": amount}, + {"account": debit_account, "debit": amount, "credit": 0} + ] + + # Assertions + self.assertEqual(len(gl_entries), 2, "Incorrect number of GL entries created.") + for entry, expected in zip(gl_entries, expected_gl_entries): + self.assertEqual(entry["account"], expected["account"], "Account mismatch in GL Entry.") + self.assertEqual(entry["debit"], expected["debit"], f"Debit mismatch for {entry['account']}.") + self.assertEqual(entry["credit"], expected["credit"], f"Credit mismatch for {entry['account']}.") + + def test_deferred_revenue_entry_TC_ACC_058(self): + # Set up input parameters + entry_type = "Deferred Revenue" + debit_account = "Creditors - _TC" + credit_account = "Sales - _TC" + amount = 30000.0 + + # Create the Journal Entry using the existing function + jv = make_journal_entry( + account1=debit_account, + account2=credit_account, + amount=amount, + save=False, + submit=False, + ) + for account in jv.accounts: + if account.account == "Creditors - _TC": + account.party_type = "Supplier" + account.party = "_Test Supplier" + # Set the entry type and save the journal entry + jv.entry_type = entry_type + jv.save().submit() + + # Fetch GL Entries to validate the transaction + gl_entries = frappe.db.sql( + """SELECT account, debit, credit FROM `tabGL Entry` + WHERE voucher_type='Journal Entry' AND voucher_no=%s + ORDER BY account""", + jv.name, + as_dict=True, + ) + # Expected GL entries + expected_gl_entries = [ + {"account": debit_account, "debit": amount, "credit": 0}, + {"account": credit_account, "debit": 0, "credit": amount} + + ] + + # Assertions + self.assertEqual(len(gl_entries), 2, "Incorrect number of GL entries created.") + for entry, expected in zip(gl_entries, expected_gl_entries): + self.assertEqual(entry["account"], expected["account"], "Account mismatch in GL Entry.") + self.assertEqual(entry["debit"], expected["debit"], f"Debit mismatch for {entry['account']}.") + self.assertEqual(entry["credit"], expected["credit"], f"Credit mismatch for {entry['account']}.") + + def test_reversal_of_itc_TC_ACC_059(self): + # Set up input parameters + entry_type = "Reversal of ITC" + debit_account_sgst = "Input Tax SGST - _TC" + debit_account_cgst = "Input Tax CGST - _TC" + credit_account = "Creditors - _TC" + amount_sgst = 5000.0 + amount_cgst = 5000.0 + + # Create the Journal Entry + jv = frappe.new_doc("Journal Entry") + jv.posting_date = nowdate() + jv.company = "_Test Company" + jv.entry_type = entry_type + jv.user_remark = "Reversal of ITC Test Case" + + # Add accounts to the Journal Entry + jv.append("accounts", { + "account": debit_account_sgst, + "debit_in_account_currency": amount_sgst, + "credit_in_account_currency": 0, + "cost_center": "_Test Cost Center - _TC" + }) + + jv.append("accounts", { + "account": debit_account_cgst, + "debit_in_account_currency": amount_cgst, + "credit_in_account_currency": 0, + "cost_center": "_Test Cost Center - _TC" + }) + + jv.append("accounts", { + "account": credit_account, + "debit_in_account_currency": 0, + "credit_in_account_currency": amount_sgst + amount_cgst, + "cost_center": "_Test Cost Center - _TC", + "party_type": "Supplier", + "party": "_Test Supplier" + }) + + # Save and submit the Journal Entry + jv.insert() + jv.submit() + + # Fetch GL Entries to validate the transaction + gl_entries = frappe.db.sql( + """SELECT account, debit, credit FROM `tabGL Entry` + WHERE voucher_type='Journal Entry' AND voucher_no=%s + ORDER BY account""", + jv.name, + as_dict=True, + ) + + # Expected GL entries + expected_gl_entries = [ + {"account": credit_account, "debit": 0, "credit": amount_sgst + amount_cgst}, + {"account": debit_account_cgst, "debit": amount_cgst, "credit": 0}, + {"account": debit_account_sgst, "debit": amount_sgst, "credit": 0}, + ] + + # Assertions + self.assertEqual(len(gl_entries), 3, "Incorrect number of GL entries created.") + for entry, expected in zip(gl_entries, expected_gl_entries): + self.assertEqual(entry["account"], expected["account"], f"Account mismatch in GL Entry: {entry['account']}.") + self.assertEqual(entry["debit"], expected["debit"], f"Debit mismatch for {entry['account']}.") + self.assertEqual(entry["credit"], expected["credit"], f"Credit mismatch for {entry['account']}.") + + def test_exchange_gain_or_loss_TC_ACC_060(self): + # Set up input parameters + entry_type = "Exchange Gain or Loss" + debit_account = "Exchange Gain/Loss - _TC" + credit_account = "Debtors - _TC" + party_type = "Customer" + party = "_Test Customer" + new_exchange_rate = 75.0 + amount = 1000.0 + + # Create the Journal Entry using the make_journal_entry method + jv = make_journal_entry( + account1=debit_account, + account2=credit_account, + amount=amount, + save=False, + submit=False + ) + + for account in jv.accounts: + if account.account == credit_account: + account.party_type = party_type + account.party = party + account.exchange_rate = new_exchange_rate + + # Set the entry type and save the journal entry + jv.entry_type = entry_type + jv.save().submit() + + # Fetch GL Entries to validate the transaction + gl_entries = frappe.db.sql( + """SELECT account, debit, credit FROM `tabGL Entry` + WHERE voucher_type='Journal Entry' AND voucher_no=%s + ORDER BY account""", + jv.name, + as_dict=True, + ) + + # Expected GL entries + expected_gl_entries = [ + {"account": credit_account, "debit": 0, "credit": amount}, + {"account": debit_account, "debit": amount, "credit": 0}, + ] + + # Assertions + self.assertEqual(len(gl_entries), 2, "Incorrect number of GL entries created.") + for entry, expected in zip(gl_entries, expected_gl_entries): + self.assertEqual(entry["account"], expected["account"], f"Account mismatch in GL Entry: {entry['account']}.") + self.assertEqual(entry["debit"], expected["debit"], f"Debit mismatch for {entry['account']}.") + self.assertEqual(entry["credit"], expected["credit"], f"Credit mismatch for {entry['account']}.") + + def test_exchange_rate_revaluation_TC_ACC_061(self): + # Set up input parameters + entry_type = "Exchange Rate Revaluation" + debit_account = "Creditors - _TC" + credit_account = "Exchange Gain/Loss - _TC" + party_type = "Supplier" + party = "_Test Supplier" + new_exchange_rate = 80.0 + amount = 2000.0 + + # Create the Journal Entry + jv = make_journal_entry( + account1=debit_account, + account2=credit_account, + amount=amount, + save=False, + submit=False + ) + + for account in jv.accounts: + if account.account == debit_account: + account.party_type = party_type + account.party = party + account.exchange_rate = new_exchange_rate + + # Set the entry type and save the journal entry + jv.entry_type = entry_type + jv.save().submit() + + # Fetch GL Entries to validate the transaction + gl_entries = frappe.db.sql( + """SELECT account, debit, credit FROM `tabGL Entry` + WHERE voucher_type='Journal Entry' AND voucher_no=%s + ORDER BY account""", + jv.name, + as_dict=True, + ) + + # Expected GL entries + expected_gl_entries = [ + {"account": debit_account, "debit": amount, "credit": 0}, + {"account": credit_account, "debit": 0, "credit": amount}, + ] + + # Assertions + self.assertEqual(len(gl_entries), 2, "Incorrect number of GL entries created.") + for entry, expected in zip(gl_entries, expected_gl_entries): + self.assertEqual(entry["account"], expected["account"], f"Account mismatch in GL Entry: {entry['account']}.") + self.assertEqual(entry["debit"], expected["debit"], f"Debit mismatch for {entry['account']}.") + self.assertEqual(entry["credit"], expected["credit"], f"Credit mismatch for {entry['account']}.") diff --git a/erpnext/accounts/doctype/matching_table/__init__.py b/erpnext/accounts/doctype/matching_table/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/erpnext/accounts/doctype/matching_table/matching_table.json b/erpnext/accounts/doctype/matching_table/matching_table.json new file mode 100644 index 000000000000..2b30b8775dbd --- /dev/null +++ b/erpnext/accounts/doctype/matching_table/matching_table.json @@ -0,0 +1,55 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2025-01-08 15:13:57.714637", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "bank_transaction_id", + "reference_to", + "matched_amount", + "reference_id" + ], + "fields": [ + { + "fieldname": "bank_transaction_id", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Bank Transaction ID", + "options": "Bank Transaction" + }, + { + "default": "Payment Entry", + "fieldname": "reference_to", + "fieldtype": "Select", + "label": "Reference To", + "options": "Payment Entry\nJournal Entry" + }, + { + "fieldname": "matched_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Matched Amount" + }, + { + "fieldname": "reference_id", + "fieldtype": "Dynamic Link", + "in_list_view": 1, + "label": "Reference ID", + "options": "reference_to" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2025-01-10 10:17:47.952899", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Matching Table", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/matching_table/matching_table.py b/erpnext/accounts/doctype/matching_table/matching_table.py new file mode 100644 index 000000000000..477d8a914245 --- /dev/null +++ b/erpnext/accounts/doctype/matching_table/matching_table.py @@ -0,0 +1,25 @@ +# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class MatchingTable(Document): + # begin: auto-generated types + # This code is auto-generated. Do not modify anything in this block. + + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from frappe.types import DF + + bank_transaction_id: DF.Link | None + matched_amount: DF.Currency + parent: DF.Data + parentfield: DF.Data + parenttype: DF.Data + reference_id: DF.DynamicLink | None + reference_to: DF.Literal["Payment Entry", "Journal Entry"] + # end: auto-generated types + pass diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index ac38e729c261..d4be744509ca 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -2422,17 +2422,16 @@ def get_orders_to_be_billed( else: grand_total_field = "grand_total" rounded_total_field = "rounded_total" - orders = frappe.db.sql( f""" SELECT name AS voucher_no, CASE - WHEN {rounded_total_field} IS NOT NULL THEN {rounded_total_field} + WHEN {rounded_total_field} > 0 THEN {rounded_total_field} ELSE {grand_total_field} END AS invoice_amount, (CASE - WHEN {rounded_total_field} IS NOT NULL THEN {rounded_total_field} + WHEN {rounded_total_field} > 0 THEN {rounded_total_field} ELSE {grand_total_field} END - advance_paid) AS outstanding_amount, transaction_date AS posting_date @@ -2444,7 +2443,7 @@ def get_orders_to_be_billed( AND company = %s AND status != 'Closed' AND (CASE - WHEN {rounded_total_field} IS NOT NULL THEN {rounded_total_field} + WHEN {rounded_total_field} > 0 THEN {rounded_total_field} ELSE {grand_total_field} END) > advance_paid AND ABS(100 - per_billed) > 0.01 diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index 9b118fabb1c5..55215982add1 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -24,6 +24,7 @@ ) from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order from erpnext.setup.doctype.employee.test_employee import make_employee +import frappe.utils test_dependencies = ["Item"] @@ -1921,7 +1922,7 @@ def test_apply_tax_withholding_category_TC_ACC_021(self): tax_withholding_category=frappe.get_doc("Tax Withholding Category","Test - TDS - 194C - Company") if len(tax_withholding_category.accounts) >0: - self.assertEqual(tax_withholding_category.accounts[0].account,"Test TDS Payable - _TC") + self.assertEqual(tax_withholding_category.accounts[0].account,"_Test TDS Payable - _TC") payment_entry=create_payment_entry( party_type="Supplier", @@ -1937,7 +1938,7 @@ def test_apply_tax_withholding_category_TC_ACC_021(self): payment_entry.append( "taxes", { - "account_head": "Test TDS Payable - _TC", + "account_head": "_Test TDS Payable - _TC", "charge_type": "On Paid Amount", "rate": 0, "add_deduct_tax": "Deduct", @@ -1960,7 +1961,7 @@ def test_apply_tax_withholding_category_TC_ACC_021(self): 'against_voucher': None }, { - 'account': 'Test TDS Payable - _TC', + 'account': '_Test TDS Payable - _TC', 'debit': 0.0, 'credit': 1600.0, 'against_voucher': None } @@ -1977,7 +1978,7 @@ def test_link_advance_payment_with_purchase_invoice_TC_ACC_022(self): tax_withholding_category=frappe.get_doc("Tax Withholding Category","Test - TDS - 194C - Company") if len(tax_withholding_category.accounts) >0: - self.assertEqual(tax_withholding_category.accounts[0].account,"Test TDS Payable - _TC") + self.assertEqual(tax_withholding_category.accounts[0].account,"_Test TDS Payable - _TC") payment_entry=create_payment_entry( party_type="Supplier", @@ -1993,7 +1994,7 @@ def test_link_advance_payment_with_purchase_invoice_TC_ACC_022(self): payment_entry.append( "taxes", { - "account_head": "Test TDS Payable - _TC", + "account_head": "_Test TDS Payable - _TC", "charge_type": "On Paid Amount", "rate": 0, "add_deduct_tax": "Deduct", @@ -2055,7 +2056,7 @@ def test_link_advance_payment_with_purchase_invoice_TC_ACC_022(self): "credit_in_account_currency": 0.0 }, { - "account": "Test TDS Payable - _TC", + "account": "_Test TDS Payable - _TC", "cost_center": "Main - _TC", "account_currency": "INR", "debit": 0.0, @@ -2199,7 +2200,11 @@ def create_supplier(**args): args = frappe._dict(args) if frappe.db.exists("Supplier", args.supplier_name): - return frappe.get_doc("Supplier", args.supplier_name) + doc = frappe.get_doc("Supplier", args.supplier_name) + if doc.name == "_Test Supplier USD" and not frappe.db.exists("Party Account", {"parent": doc.name, "account": "_Test Payable USD - _TC"}): + doc.append("accounts", {"company": args.company, "account": "_Test Payable USD - _TC"}) + frappe.db.commit() + return doc doc = frappe.get_doc( { "doctype": "Supplier", @@ -2212,104 +2217,131 @@ def create_supplier(**args): ) doc.append('accounts',{ 'company': args.company, - 'account': '_Test Payable USD - _TC' if args.default_currency == 'USD' else 'Test TDS Payable', + 'account': '_Test Payable USD - _TC' if args.default_currency == 'USD' else '_Test TDS Payable - _TC', }) - if not args.without_supplier_group: doc.supplier_group = args.supplier_group or "Services" + - doc.insert() - doc.save() + doc.insert(ignore_mandatory=True) frappe.db.commit() return doc def create_account(): - accounts = [ - {"name": "Current Liabilities", "parent": "Source of Funds (Liabilities) - _TC"}, - {"name": "Duties and Taxes", "parent": "Current Liabilities - _TC"}, - {"name": "Test TDS Payable", "parent": "Duties and Taxes - _TC"}, - {"name": "Test Creditors", "parent": "Accounts Payable - _TC"}, - {"name": "_Test Payable USD", "parent": "Current Liabilities - _TC"}, - {"name": "_Test Cash", "parent": "Cash In Hand - _TC"}, - ] - - for account in accounts: - if not frappe.db.exists("Account", f"{account['name']} - _TC"): # Ensure proper check with "- _TC" - try: - doc = frappe.get_doc({ - "doctype": "Account", - "company": "_Test Company", - "account_name": account["name"], - "parent_account": account["parent"], - "report_type": "Balance Sheet", - "root_type": "Liability", - "account_currency": "USD" if account["name"] == "_Test Payable USD" else "INR", - }).insert() - frappe.db.commit() - except Exception as e: - frappe.log_error(f"Failed to insert {account['name']}", str(e)) + accounts = [ + {"name": "Source of Funds (Liabilities)", "parent": ""}, + {"name": "Current Liabilities", "parent": "Source of Funds (Liabilities) - _TC"}, + {"name": "Duties and Taxes", "parent": "Current Liabilities - _TC"}, + {"name": "_Test TDS Payable", "parent": "Duties and Taxes - _TC","account_type":"Tax"}, + {"name": "_Test Creditors", "parent": "Accounts Payable - _TC","account_type":"Payable"}, + {"name": "_Test Payable USD", "parent": "Accounts Payable - _TC","account_type":"Payable"}, + {"name": "_Test Cash", "parent": "Cash In Hand - _TC"}, + ] + + if not frappe.db.exists("Company", "_Test Company"): + return + + for account in accounts: + if frappe.db.exists("Account", f"{account['name']} - _TC"): + continue + + if account["parent"] and not frappe.db.exists("Account", account["parent"]): + continue + + try: + doc = frappe.new_doc("Account") + doc.company = "_Test Company" + doc.account_name = account["name"] + doc.report_type = "Balance Sheet" + doc.root_type = "Liability" + doc.is_group = 1 if account["name"] == 'Source of Funds' else 0 + doc.account_currency = "USD" if account["name"] == "_Test Payable USD" else "INR" + + # Set count_type based on account name + if account["account_type"]: + doc.account_type = account["account_type"] -def create_records(supplier): - from erpnext.accounts.doctype.tax_withholding_category.test_tax_withholding_category import create_tax_withholding_category + if account["parent"]: + doc.parent_account = account["parent"] + + doc.insert(ignore_mandatory=True) + frappe.db.commit() - create_account() + except Exception as e: + frappe.log_error(f"Failed to insert {account['name']}", str(e)) - create_tax_withholding_category( - category_name="Test - TDS - 194C - Company", - rate=2, - from_date=frappe.utils.get_date_str('01-04-2024'), - to_date=frappe.utils.get_date_str('31-03-2025'), - account="Test TDS Payable - _TC", - single_threshold=30000, - cumulative_threshold=100000, - consider_party_ledger_amount=1, - ) - create_supplier( - supplier_name=supplier, - company="_Test Company", - tax_withholding_category="Test - TDS - 194C - Company", - default_currency="USD" if supplier == "_Test Supplier USD" else "INR", - ) +def create_records(supplier): + from erpnext.accounts.doctype.tax_withholding_category.test_tax_withholding_category import create_tax_withholding_category + create_company() + + create_account() + + create_tax_withholding_category( + category_name="Test - TDS - 194C - Company", + rate=2, + from_date=frappe.utils.get_date_str('01-04-2024'), + to_date=frappe.utils.get_date_str('31-03-2025'), + account="_Test TDS Payable - _TC", + single_threshold=30000, + cumulative_threshold=100000, + consider_party_ledger_amount=1, + ) + create_supplier( + supplier_name=supplier, + company="_Test Company", + tax_withholding_category="Test - TDS - 194C - Company", + default_currency="USD" if supplier == "_Test Supplier USD" else "INR", + ) - frappe.db.commit() + frappe.db.commit() def make_test_item(item_name=None): - from erpnext.stock.doctype.item.test_item import make_item + from erpnext.stock.doctype.item.test_item import make_item + app_name = "india_compliance" + if not frappe.db.exists("Item", item_name or "Test Item with Tax"): + if app_name in frappe.get_installed_apps(): + if not frappe.db.exists("GST HSN Code", '888890'): + frappe.get_doc({ + "doctype": 'GST HSN Code', + "hsn_code": '888890', + "description": 'test' + }).insert() + frappe.db.commit() + + item= make_item( + item_name or "Test Item with Tax", + { + "is_stock_item": 1, + "gst_hsn_code": "888890", + }, + ) + + return item - if not frappe.db.exists("Item", item_name or "Test Item with Tax"): - app_name = "india_compliance" - - if app_name in frappe.get_installed_apps(): - if not frappe.db.exists("GST HSN Code", '888890'): - frappe.get_doc({ - "doctype": 'GST HSN Code', - "hsn_code": '888890', - "description": 'test' - }).insert() - frappe.db.commit() - - item= make_item( - item_name or "Test Item with Tax", - { - "is_stock_item": 1, - "gst_hsn_code": "888890", - }, - ) - - return item - - else: - item= make_item( - "Test TDS Item", - { - "is_stock_item": 1, - }, - ) - return item - else: - return frappe.get_doc("Item", item_name or "Test Item with Tax") + else: + item= make_item( + "Test TDS Item", + { + "is_stock_item": 1, + }, + ) + return item + else: + if app_name in frappe.get_installed_apps(): + if not frappe.db.exists("GST HSN Code", '888890'): + frappe.get_doc({ + "doctype": 'GST HSN Code', + "hsn_code": '888890', + "description": 'test' + }).insert() + item=frappe.get_doc("Item", item_name or "Test Item with Tax") + if not item.gst_hsn_code: + item.gst_hsn_code="888890" + item.save() + frappe.db.commit() + return item def create_purchase_invoice(**args): # return sales invoice doc object @@ -2340,4 +2372,16 @@ def create_purchase_invoice(**args): ) pi.save() - return pi \ No newline at end of file + return pi + +def create_company(): + if not frappe.db.exists("Company", "_Test Company"): + frappe.get_doc({ + "doctype": "Company", + "company_name": "_Test Company", + "company_type": "Company", + "default_currency": "INR", + "company_email": "test@example.com", + "abbr":"_TC" + }).insert() + frappe.db.commit() diff --git a/erpnext/accounts/doctype/payment_request/payment_request.js b/erpnext/accounts/doctype/payment_request/payment_request.js index 50f96a4e2b6b..b5b61a23f09b 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.js +++ b/erpnext/accounts/doctype/payment_request/payment_request.js @@ -93,3 +93,7 @@ frappe.ui.form.on("Payment Request", "is_a_subscription", function (frm) { }); } }); + +frappe.ui.form.on("Payment Request", "is_payment_order_required", function (frm) { + frm.toggle_reqd("bank_account", frm.doc.is_payment_order_required); +}) \ No newline at end of file diff --git a/erpnext/accounts/doctype/payment_request/payment_request.json b/erpnext/accounts/doctype/payment_request/payment_request.json index 273e814815bc..dd8bde0b4a7d 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.json +++ b/erpnext/accounts/doctype/payment_request/payment_request.json @@ -1,468 +1,477 @@ { - "actions": [], - "autoname": "naming_series:", - "creation": "2015-12-15 22:23:24.745065", - "doctype": "DocType", - "engine": "InnoDB", - "field_order": [ - "payment_request_type", - "transaction_date", - "column_break_2", - "naming_series", - "company", - "mode_of_payment", - "party_details", - "party_type", - "party", - "party_name", - "column_break_4", - "reference_doctype", - "reference_name", - "transaction_details", - "grand_total", - "currency", - "is_a_subscription", - "column_break_18", - "outstanding_amount", - "party_account_currency", - "subscription_section", - "subscription_plans", - "bank_account_details", - "bank_account", - "bank", - "column_break_11", - "iban", - "branch_code", - "swift_number", - "accounting_dimensions_section", - "cost_center", - "dimension_col_break", - "recipient_and_message", - "print_format", - "email_to", - "subject", - "column_break_9", - "payment_gateway_account", - "status", - "make_sales_invoice", - "section_break_10", - "message", - "message_examples", - "mute_email", - "payment_url", - "section_break_7", - "payment_gateway", - "payment_account", - "payment_channel", - "payment_order", - "amended_from" - ], - "fields": [ - { - "default": "Inward", - "fieldname": "payment_request_type", - "fieldtype": "Select", - "label": "Payment Request Type", - "options": "Outward\nInward", - "reqd": 1 - }, - { - "fieldname": "transaction_date", - "fieldtype": "Date", - "in_preview": 1, - "label": "Transaction Date" - }, - { - "fieldname": "column_break_2", - "fieldtype": "Column Break" - }, - { - "fieldname": "naming_series", - "fieldtype": "Select", - "in_list_view": 1, - "label": "Series", - "no_copy": 1, - "options": "ACC-PRQ-.YYYY.-", - "print_hide": 1, - "reqd": 1, - "set_only_once": 1 - }, - { - "fieldname": "mode_of_payment", - "fieldtype": "Link", - "label": "Mode of Payment", - "options": "Mode of Payment" - }, - { - "fieldname": "party_details", - "fieldtype": "Section Break", - "label": "Party Details" - }, - { - "fieldname": "party_type", - "fieldtype": "Link", - "label": "Party Type", - "options": "DocType" - }, - { - "fieldname": "party", - "fieldtype": "Dynamic Link", - "label": "Party", - "options": "party_type" - }, - { - "fieldname": "column_break_4", - "fieldtype": "Column Break" - }, - { - "fieldname": "reference_doctype", - "fieldtype": "Link", - "in_standard_filter": 1, - "label": "Reference Doctype", - "no_copy": 1, - "options": "DocType", - "print_hide": 1, - "read_only": 1, - "report_hide": 1 - }, - { - "fieldname": "reference_name", - "fieldtype": "Dynamic Link", - "in_global_search": 1, - "in_standard_filter": 1, - "label": "Reference Name", - "no_copy": 1, - "options": "reference_doctype", - "print_hide": 1, - "read_only": 1, - "search_index": 1 - }, - { - "fieldname": "transaction_details", - "fieldtype": "Section Break", - "label": "Transaction Details" - }, - { - "description": "Amount in transaction currency", - "fieldname": "grand_total", - "fieldtype": "Currency", - "in_preview": 1, - "label": "Amount", - "non_negative": 1, - "options": "currency", - "reqd": 1 - }, - { - "default": "0", - "fieldname": "is_a_subscription", - "fieldtype": "Check", - "label": "Is a Subscription" - }, - { - "fieldname": "column_break_18", - "fieldtype": "Column Break" - }, - { - "fieldname": "currency", - "fieldtype": "Link", - "label": "Transaction Currency", - "options": "Currency", - "read_only": 1 - }, - { - "depends_on": "eval:doc.is_a_subscription", - "fieldname": "subscription_section", - "fieldtype": "Section Break", - "label": "Subscription Section" - }, - { - "fieldname": "subscription_plans", - "fieldtype": "Table", - "label": "Subscription Plans", - "options": "Subscription Plan Detail" - }, - { - "collapsible": 1, - "fieldname": "bank_account_details", - "fieldtype": "Section Break", - "label": "Bank Account Details" - }, - { - "fieldname": "bank_account", - "fieldtype": "Link", - "label": "Bank Account", - "options": "Bank Account" - }, - { - "fetch_from": "bank_account.bank", - "fieldname": "bank", - "fieldtype": "Link", - "label": "Bank", - "options": "Bank", - "read_only": 1 - }, - { - "fetch_from": "bank_account.bank_account_no", - "fieldname": "bank_account_no", - "fieldtype": "Read Only", - "label": "Bank Account No" - }, - { - "fetch_from": "bank_account.account", - "fieldname": "account", - "fieldtype": "Read Only", - "label": "Account" - }, - { - "fieldname": "column_break_11", - "fieldtype": "Column Break" - }, - { - "fetch_from": "bank_account.iban", - "fieldname": "iban", - "fieldtype": "Read Only", - "label": "IBAN" - }, - { - "fetch_from": "bank_account.branch_code", - "fetch_if_empty": 1, - "fieldname": "branch_code", - "fieldtype": "Read Only", - "label": "Branch Code" - }, - { - "fetch_from": "bank.swift_number", - "fieldname": "swift_number", - "fieldtype": "Read Only", - "label": "SWIFT Number" - }, - { - "collapsible": 1, - "fieldname": "accounting_dimensions_section", - "fieldtype": "Section Break", - "label": "Accounting Dimensions" - }, - { - "fieldname": "cost_center", - "fieldtype": "Link", - "label": "Cost Center", - "options": "Cost Center" - }, - { - "fieldname": "dimension_col_break", - "fieldtype": "Column Break" - }, - { - "depends_on": "eval: doc.payment_request_type == 'Inward'", - "fieldname": "recipient_and_message", - "fieldtype": "Section Break", - "label": "Recipient Message And Payment Details" - }, - { - "depends_on": "eval: doc.payment_channel != \"Phone\"", - "fieldname": "print_format", - "fieldtype": "Select", - "label": "Print Format" - }, - { - "fieldname": "email_to", - "fieldtype": "Data", - "in_global_search": 1, - "label": "To" - }, - { - "depends_on": "eval: doc.payment_channel != \"Phone\"", - "fieldname": "subject", - "fieldtype": "Data", - "in_global_search": 1, - "label": "Subject" - }, - { - "fieldname": "column_break_9", - "fieldtype": "Column Break" - }, - { - "depends_on": "eval: doc.payment_request_type == 'Inward'", - "fieldname": "payment_gateway_account", - "fieldtype": "Link", - "label": "Payment Gateway Account", - "options": "Payment Gateway Account" - }, - { - "default": "Draft", - "fieldname": "status", - "fieldtype": "Select", - "hidden": 1, - "in_standard_filter": 1, - "label": "Status", - "options": "\nDraft\nRequested\nInitiated\nPartially Paid\nPayment Ordered\nPaid\nFailed\nCancelled", - "read_only": 1 - }, - { - "default": "0", - "depends_on": "eval:doc.reference_doctype==\"Sales Order\"", - "fieldname": "make_sales_invoice", - "fieldtype": "Check", - "hidden": 1, - "label": "Make Sales Invoice", - "read_only": 1 - }, - { - "depends_on": "eval: doc.payment_request_type == 'Inward' || doc.payment_channel != \"Phone\"", - "fieldname": "section_break_10", - "fieldtype": "Section Break" - }, - { - "depends_on": "eval: doc.payment_channel != \"Phone\"", - "fieldname": "message", - "fieldtype": "Text", - "label": "Message" - }, - { - "depends_on": "eval: doc.payment_channel != \"Phone\"", - "fieldname": "message_examples", - "fieldtype": "HTML", - "label": "Message Examples", - "options": "
Message Example
\n\n<p>Dear {{ doc.contact_person }},</p>\n\n<p>Requesting payment for {{ doc.doctype }}, {{ doc.name }} for {{ doc.grand_total }}.</p>\n\n<a href=\"{{ payment_url }}\"> click here to pay </a>\n\n
\n", - "print_hide": 1 - }, - { - "default": "0", - "fieldname": "mute_email", - "fieldtype": "Check", - "hidden": 1, - "label": "Mute Email", - "no_copy": 1, - "print_hide": 1, - "read_only": 1, - "report_hide": 1 - }, - { - "fieldname": "payment_url", - "fieldtype": "Data", - "hidden": 1, - "length": 500, - "options": "URL", - "read_only": 1 - }, - { - "collapsible": 1, - "collapsible_depends_on": "doc.payment_gateway_account", - "depends_on": "eval: doc.payment_request_type == 'Inward'", - "fieldname": "section_break_7", - "fieldtype": "Section Break", - "label": "Payment Gateway Details" - }, - { - "fetch_from": "payment_gateway_account.payment_gateway", - "fieldname": "payment_gateway", - "fieldtype": "Read Only", - "label": "Payment Gateway" - }, - { - "fetch_from": "payment_gateway_account.payment_account", - "fieldname": "payment_account", - "fieldtype": "Read Only", - "label": "Payment Account", - "read_only": 1 - }, - { - "fetch_from": "payment_gateway_account.payment_channel", - "fieldname": "payment_channel", - "fieldtype": "Select", - "label": "Payment Channel", - "options": "\nEmail\nPhone", - "read_only": 1 - }, - { - "fieldname": "payment_order", - "fieldtype": "Link", - "label": "Payment Order", - "options": "Payment Order", - "read_only": 1 - }, - { - "fieldname": "amended_from", - "fieldtype": "Link", - "label": "Amended From", - "no_copy": 1, - "options": "Payment Request", - "print_hide": 1, - "read_only": 1 - }, - { - "depends_on": "eval: doc.docstatus === 1", - "description": "Amount in party's bank account currency", - "fieldname": "outstanding_amount", - "fieldtype": "Currency", - "in_preview": 1, - "label": "Outstanding Amount", - "non_negative": 1, - "options": "party_account_currency", - "read_only": 1 - }, - { - "fieldname": "company", - "fieldtype": "Link", - "label": "Company", - "options": "Company", - "read_only": 1 - }, - { - "fieldname": "party_account_currency", - "fieldtype": "Link", - "label": "Party Account Currency", - "options": "Currency", - "read_only": 1 - }, - { - "fieldname": "party_name", - "fieldtype": "Data", - "label": "Party Name", - "read_only": 1 - } - ], - "in_create": 1, - "index_web_pages_for_search": 1, - "is_submittable": 1, - "links": [], - "modified": "2024-10-23 12:23:40.117336", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Payment Request", - "naming_rule": "By \"Naming Series\" field", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Accounts User", - "share": 1, - "write": 1 - }, - { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Accounts Manager", - "share": 1, - "submit": 1, - "write": 1 - } - ], - "show_preview_popup": 1, - "sort_field": "modified", - "sort_order": "DESC", - "states": [] -} + "actions": [], + "autoname": "naming_series:", + "creation": "2015-12-15 22:23:24.745065", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "payment_request_type", + "transaction_date", + "is_payment_order_required", + "column_break_2", + "naming_series", + "company", + "mode_of_payment", + "party_details", + "party_type", + "party", + "party_name", + "column_break_4", + "reference_doctype", + "reference_name", + "transaction_details", + "grand_total", + "currency", + "is_a_subscription", + "column_break_18", + "outstanding_amount", + "party_account_currency", + "subscription_section", + "subscription_plans", + "bank_account_details", + "bank_account", + "bank", + "column_break_11", + "iban", + "branch_code", + "swift_number", + "accounting_dimensions_section", + "cost_center", + "dimension_col_break", + "recipient_and_message", + "print_format", + "email_to", + "subject", + "column_break_9", + "payment_gateway_account", + "status", + "make_sales_invoice", + "section_break_10", + "message", + "message_examples", + "mute_email", + "payment_url", + "section_break_7", + "payment_gateway", + "payment_account", + "payment_channel", + "payment_order", + "amended_from", + "bank_account_no", + "account" + ], + "fields": [ + { + "default": "Inward", + "fieldname": "payment_request_type", + "fieldtype": "Select", + "label": "Payment Request Type", + "options": "Outward\nInward", + "reqd": 1 + }, + { + "fieldname": "transaction_date", + "fieldtype": "Date", + "in_preview": 1, + "label": "Transaction Date" + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, + { + "fieldname": "naming_series", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Series", + "no_copy": 1, + "options": "ACC-PRQ-.YYYY.-", + "print_hide": 1, + "reqd": 1, + "set_only_once": 1 + }, + { + "fieldname": "mode_of_payment", + "fieldtype": "Link", + "label": "Mode of Payment", + "options": "Mode of Payment" + }, + { + "fieldname": "party_details", + "fieldtype": "Section Break", + "label": "Party Details" + }, + { + "fieldname": "party_type", + "fieldtype": "Link", + "label": "Party Type", + "options": "DocType" + }, + { + "fieldname": "party", + "fieldtype": "Dynamic Link", + "label": "Party", + "options": "party_type" + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "fieldname": "reference_doctype", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Reference Doctype", + "no_copy": 1, + "options": "DocType", + "print_hide": 1, + "read_only": 1, + "report_hide": 1 + }, + { + "fieldname": "reference_name", + "fieldtype": "Dynamic Link", + "in_global_search": 1, + "in_standard_filter": 1, + "label": "Reference Name", + "no_copy": 1, + "options": "reference_doctype", + "print_hide": 1, + "read_only": 1, + "search_index": 1 + }, + { + "fieldname": "transaction_details", + "fieldtype": "Section Break", + "label": "Transaction Details" + }, + { + "description": "Amount in transaction currency", + "fieldname": "grand_total", + "fieldtype": "Currency", + "in_preview": 1, + "label": "Amount", + "non_negative": 1, + "options": "currency", + "reqd": 1 + }, + { + "default": "0", + "fieldname": "is_a_subscription", + "fieldtype": "Check", + "label": "Is a Subscription" + }, + { + "fieldname": "column_break_18", + "fieldtype": "Column Break" + }, + { + "fieldname": "currency", + "fieldtype": "Link", + "label": "Transaction Currency", + "options": "Currency", + "read_only": 1 + }, + { + "depends_on": "eval:doc.is_a_subscription", + "fieldname": "subscription_section", + "fieldtype": "Section Break", + "label": "Subscription Section" + }, + { + "fieldname": "subscription_plans", + "fieldtype": "Table", + "label": "Subscription Plans", + "options": "Subscription Plan Detail" + }, + { + "collapsible": 1, + "fieldname": "bank_account_details", + "fieldtype": "Section Break", + "label": "Bank Account Details" + }, + { + "fieldname": "bank_account", + "fieldtype": "Link", + "label": "Bank Account", + "options": "Bank Account" + }, + { + "fetch_from": "bank_account.bank", + "fieldname": "bank", + "fieldtype": "Link", + "label": "Bank", + "options": "Bank", + "read_only": 1 + }, + { + "fetch_from": "bank_account.bank_account_no", + "fieldname": "bank_account_no", + "fieldtype": "Read Only", + "label": "Bank Account No" + }, + { + "fetch_from": "bank_account.account", + "fieldname": "account", + "fieldtype": "Read Only", + "label": "Account" + }, + { + "fieldname": "column_break_11", + "fieldtype": "Column Break" + }, + { + "fetch_from": "bank_account.iban", + "fieldname": "iban", + "fieldtype": "Read Only", + "label": "IBAN" + }, + { + "fetch_from": "bank_account.branch_code", + "fetch_if_empty": 1, + "fieldname": "branch_code", + "fieldtype": "Read Only", + "label": "Branch Code" + }, + { + "fetch_from": "bank.swift_number", + "fieldname": "swift_number", + "fieldtype": "Read Only", + "label": "SWIFT Number" + }, + { + "collapsible": 1, + "fieldname": "accounting_dimensions_section", + "fieldtype": "Section Break", + "label": "Accounting Dimensions" + }, + { + "fieldname": "cost_center", + "fieldtype": "Link", + "label": "Cost Center", + "options": "Cost Center" + }, + { + "fieldname": "dimension_col_break", + "fieldtype": "Column Break" + }, + { + "depends_on": "eval: doc.payment_request_type == 'Inward'", + "fieldname": "recipient_and_message", + "fieldtype": "Section Break", + "label": "Recipient Message And Payment Details" + }, + { + "depends_on": "eval: doc.payment_channel != \"Phone\"", + "fieldname": "print_format", + "fieldtype": "Select", + "label": "Print Format" + }, + { + "fieldname": "email_to", + "fieldtype": "Data", + "in_global_search": 1, + "label": "To" + }, + { + "depends_on": "eval: doc.payment_channel != \"Phone\"", + "fieldname": "subject", + "fieldtype": "Data", + "in_global_search": 1, + "label": "Subject" + }, + { + "fieldname": "column_break_9", + "fieldtype": "Column Break" + }, + { + "depends_on": "eval: doc.payment_request_type == 'Inward'", + "fieldname": "payment_gateway_account", + "fieldtype": "Link", + "label": "Payment Gateway Account", + "options": "Payment Gateway Account" + }, + { + "default": "Draft", + "fieldname": "status", + "fieldtype": "Select", + "hidden": 1, + "in_standard_filter": 1, + "label": "Status", + "options": "\nDraft\nRequested\nInitiated\nPartially Paid\nPayment Ordered\nPaid\nFailed\nCancelled", + "read_only": 1 + }, + { + "default": "0", + "depends_on": "eval:doc.reference_doctype==\"Sales Order\"", + "fieldname": "make_sales_invoice", + "fieldtype": "Check", + "hidden": 1, + "label": "Make Sales Invoice", + "read_only": 1 + }, + { + "depends_on": "eval: doc.payment_request_type == 'Inward' || doc.payment_channel != \"Phone\"", + "fieldname": "section_break_10", + "fieldtype": "Section Break" + }, + { + "depends_on": "eval: doc.payment_channel != \"Phone\"", + "fieldname": "message", + "fieldtype": "Text", + "label": "Message" + }, + { + "depends_on": "eval: doc.payment_channel != \"Phone\"", + "fieldname": "message_examples", + "fieldtype": "HTML", + "label": "Message Examples", + "options": "
Message Example
\n\n<p>Dear {{ doc.contact_person }},</p>\n\n<p>Requesting payment for {{ doc.doctype }}, {{ doc.name }} for {{ doc.grand_total }}.</p>\n\n<a href=\"{{ payment_url }}\"> click here to pay </a>\n\n
\n", + "print_hide": 1 + }, + { + "default": "0", + "fieldname": "mute_email", + "fieldtype": "Check", + "hidden": 1, + "label": "Mute Email", + "no_copy": 1, + "print_hide": 1, + "read_only": 1, + "report_hide": 1 + }, + { + "fieldname": "payment_url", + "fieldtype": "Data", + "hidden": 1, + "length": 500, + "options": "URL", + "read_only": 1 + }, + { + "collapsible": 1, + "collapsible_depends_on": "doc.payment_gateway_account", + "depends_on": "eval: doc.payment_request_type == 'Inward'", + "fieldname": "section_break_7", + "fieldtype": "Section Break", + "label": "Payment Gateway Details" + }, + { + "fetch_from": "payment_gateway_account.payment_gateway", + "fieldname": "payment_gateway", + "fieldtype": "Read Only", + "label": "Payment Gateway" + }, + { + "fetch_from": "payment_gateway_account.payment_account", + "fieldname": "payment_account", + "fieldtype": "Read Only", + "label": "Payment Account", + "read_only": 1 + }, + { + "fetch_from": "payment_gateway_account.payment_channel", + "fieldname": "payment_channel", + "fieldtype": "Select", + "label": "Payment Channel", + "options": "\nEmail\nPhone", + "read_only": 1 + }, + { + "fieldname": "payment_order", + "fieldtype": "Link", + "label": "Payment Order", + "options": "Payment Order", + "read_only": 1 + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Payment Request", + "print_hide": 1, + "read_only": 1 + }, + { + "depends_on": "eval: doc.docstatus === 1", + "description": "Amount in party's bank account currency", + "fieldname": "outstanding_amount", + "fieldtype": "Currency", + "in_preview": 1, + "label": "Outstanding Amount", + "non_negative": 1, + "options": "party_account_currency", + "read_only": 1 + }, + { + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", + "read_only": 1 + }, + { + "fieldname": "party_account_currency", + "fieldtype": "Link", + "label": "Party Account Currency", + "options": "Currency", + "read_only": 1 + }, + { + "fieldname": "party_name", + "fieldtype": "Data", + "label": "Party Name", + "read_only": 1 + }, + { + "default": "0", + "fieldname": "is_payment_order_required", + "fieldtype": "Check", + "label": "Is Payment Order Required" + } + ], + "in_create": 1, + "index_web_pages_for_search": 1, + "is_submittable": 1, + "links": [], + "modified": "2025-01-14 17:29:21.046587", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Payment Request", + "naming_rule": "By \"Naming Series\" field", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "share": 1, + "write": 1 + }, + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "share": 1, + "submit": 1, + "write": 1 + } + ], + "show_preview_popup": 1, + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index e77c75ff11be..d094e482db2e 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -35,12 +35,9 @@ class PaymentRequest(Document): from typing import TYPE_CHECKING if TYPE_CHECKING: + from erpnext.accounts.doctype.subscription_plan_detail.subscription_plan_detail import SubscriptionPlanDetail from frappe.types import DF - from erpnext.accounts.doctype.subscription_plan_detail.subscription_plan_detail import ( - SubscriptionPlanDetail, - ) - account: DF.ReadOnly | None amended_from: DF.Link | None bank: DF.Link | None @@ -54,6 +51,7 @@ class PaymentRequest(Document): grand_total: DF.Currency iban: DF.ReadOnly | None is_a_subscription: DF.Check + is_payment_order_required: DF.Check make_sales_invoice: DF.Check message: DF.Text | None mode_of_payment: DF.Link | None @@ -65,7 +63,7 @@ class PaymentRequest(Document): party_name: DF.Data | None party_type: DF.Link | None payment_account: DF.ReadOnly | None - payment_channel: DF.Literal["", "Email", "Phone", "Other"] + payment_channel: DF.Literal["", "Email", "Phone"] payment_gateway: DF.ReadOnly | None payment_gateway_account: DF.Link | None payment_order: DF.Link | None @@ -74,17 +72,7 @@ class PaymentRequest(Document): print_format: DF.Literal[None] reference_doctype: DF.Link | None reference_name: DF.DynamicLink | None - status: DF.Literal[ - "", - "Draft", - "Requested", - "Initiated", - "Partially Paid", - "Payment Ordered", - "Paid", - "Failed", - "Cancelled", - ] + status: DF.Literal["", "Draft", "Requested", "Initiated", "Partially Paid", "Payment Ordered", "Paid", "Failed", "Cancelled"] subject: DF.Data | None subscription_plans: DF.Table[SubscriptionPlanDetail] swift_number: DF.ReadOnly | None @@ -211,6 +199,7 @@ def request_phone_payment(self): sender=self.email_to, currency=self.currency, payment_gateway=self.payment_gateway, + phone_number=self.phone_number, ) controller.validate_transaction_currency(self.currency) @@ -619,6 +608,7 @@ def validate_and_calculate_grand_total(grand_total, existing_payment_request_amo "party": args.get("party") or ref_doc.get("customer"), "bank_account": bank_account, "party_name": args.get("party_name") or ref_doc.get("customer_name"), + "phone_number": args.get("phone_number") if args.get("phone_number") else None, } ) diff --git a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py index 1dbc630e62ec..b34a71b7d75b 100644 --- a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py @@ -6,7 +6,7 @@ import frappe from frappe import _ - +from frappe.utils import cint, flt, getdate, today from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice @@ -18,7 +18,8 @@ make_serial_batch_bundle, ) from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry - +from erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry import make_closing_entry_from_opening +from erpnext.accounts.doctype.pos_opening_entry.test_pos_opening_entry import create_opening_entry class TestPOSInvoice(unittest.TestCase): @classmethod @@ -924,8 +925,146 @@ def test_delivered_serial_no_case(self): finally: frappe.db.rollback(save_point="before_test_delivered_serial_no_case") frappe.set_user("Administrator") + + def test_pos_opening_to_pos_closing_with_possi_and_tax_TC_S_102(self): + from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile + + test_user, pos_profile = init_user_and_profile() + opening_entry = create_opening_entry(pos_profile=pos_profile, user=test_user.name) + self.assertEqual(opening_entry.status, "Open") + + pos_inv = create_pos_invoice(rate=3500, do_not_submit=1) + for i in pos_inv.items: + i.item_tax_template = "GST 5% - _TC" + pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3500}) + pos_inv.taxes_and_charges = "Output GST In-state - _TC" + pos_inv.save() + pos_inv.submit() + closing_enrty= make_closing_entry_from_opening(opening_entry) + closing_enrty.submit() + opening_entry.reload() + self.assertEqual(opening_entry.status, "Closed") + + + def test_pos_invoice_with_loyalty_point_TC_S_103(self): + from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile + from erpnext.accounts.doctype.loyalty_program.loyalty_program import get_loyalty_program_details_with_points + + if not frappe.db.exists("Loyalty Program", "Test Single Loyalty"): + frappe.get_doc( + { + "doctype": "Loyalty Program", + "loyalty_program_name": "Test Single Loyalty", + "auto_opt_in": 1, + "from_date": today(), + "loyalty_program_type": "Single Tier Program", + "conversion_factor": 1, + "expiry_duration": 10, + "company": "_Test Company", + "cost_center": "Main - _TC", + "expense_account": "Loyalty - _TC", + "collection_rules": [{"tier_name": "Silver", "collection_factor": 1000, "min_spent": 1000}], + } + ).insert() + + test_user, pos_profile = init_user_and_profile() + opening_entry = create_opening_entry(pos_profile=pos_profile, user=test_user.name) + self.assertEqual(opening_entry.status, "Open") + + before_lp_details = get_loyalty_program_details_with_points( + "Test Loyalty Customer", company="_Test Company", loyalty_program="Test Single Loyalty" + ) + + inv = create_pos_invoice(customer="Test Loyalty Customer", rate=10000, do_not_save=1) + inv.redeem_loyalty_points = 1 + inv.loyalty_points = before_lp_details.loyalty_points + inv.loyalty_amount = inv.loyalty_points * before_lp_details.conversion_factor + inv.append( + "payments", + {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 10000 - inv.loyalty_amount}, + ) + inv.paid_amount = 10000 + inv.submit() + after_redeem_lp_details = get_loyalty_program_details_with_points( + inv.customer, company=inv.company, loyalty_program=inv.loyalty_program + ) + self.assertEqual(after_redeem_lp_details.loyalty_points, 9) + closing_enrty= make_closing_entry_from_opening(opening_entry) + closing_enrty.submit() + opening_entry.reload() + self.assertEqual(inv.status, "Paid") + self.assertEqual(opening_entry.status, "Closed") + + def test_pos_inoivce_with_discount_grand_total_TC_S_104(self): + from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile + + test_user, pos_profile = init_user_and_profile() + opening_entry = create_opening_entry(pos_profile=pos_profile, user=test_user.name) + self.assertEqual(opening_entry.status, "Open") + + inv = create_pos_invoice(customer="Test Loyalty Customer", rate=3000, do_not_save=1) + inv.taxes_and_charges = "Output GST In-state - _TC" + inv.apply_discount_on = "Grand Total" + inv.discount_amount = 1000 + + inv.save() + inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": inv.grand_total}) + inv.paid_amount = inv.grand_total + inv.submit() + + closing_enrty= make_closing_entry_from_opening(opening_entry) + closing_enrty.submit() + opening_entry.reload() + + self.assertEqual(inv.status, "Paid") + self.assertEqual(opening_entry.status, "Closed") + + def test_pos_inoivce_with_discount_net_total_TC_S_105(self): + from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile + + test_user, pos_profile = init_user_and_profile() + opening_entry = create_opening_entry(pos_profile=pos_profile, user=test_user.name) + self.assertEqual(opening_entry.status, "Open") + + inv = create_pos_invoice(customer="Test Loyalty Customer", rate=3000, do_not_save=1) + inv.taxes_and_charges = "Output GST In-state - _TC" + inv.apply_discount_on = "Net Total" + inv.discount_amount = 1000 + + inv.save() + inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": inv.grand_total}) + + inv.paid_amount = inv.grand_total + inv.submit() + closing_enrty= make_closing_entry_from_opening(opening_entry) + closing_enrty.submit() + opening_entry.reload() + self.assertEqual(inv.status, "Paid") + self.assertEqual(opening_entry.status, "Closed") + + def test_pos_inoivce_with_terms_and_conditions_TC_S_107(self): + from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile + + test_user, pos_profile = init_user_and_profile() + opening_entry = create_opening_entry(pos_profile=pos_profile, user=test_user.name) + self.assertEqual(opening_entry.status, "Open") + + inv = create_pos_invoice(customer="Test Loyalty Customer", rate=3000, do_not_save=1) + inv.taxes_and_charges = "Output GST In-state - _TC" + inv.tc_name ="_Test Terms and Conditions" + inv.save() + inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": inv.grand_total}) + + inv.paid_amount = inv.grand_total + inv.submit() + closing_enrty= make_closing_entry_from_opening(opening_entry) + closing_enrty.submit() + opening_entry.reload() + self.assertEqual(inv.status, "Paid") + self.assertEqual(opening_entry.status, "Closed") + def create_pos_invoice(**args): args = frappe._dict(args) pos_profile = None diff --git a/erpnext/accounts/doctype/pos_opening_entry/test_pos_opening_entry.py b/erpnext/accounts/doctype/pos_opening_entry/test_pos_opening_entry.py index 64c658ab151b..029821cedf65 100644 --- a/erpnext/accounts/doctype/pos_opening_entry/test_pos_opening_entry.py +++ b/erpnext/accounts/doctype/pos_opening_entry/test_pos_opening_entry.py @@ -3,12 +3,94 @@ import unittest +from erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry import make_closing_entry_from_opening import frappe class TestPOSOpeningEntry(unittest.TestCase): - pass + def test_pos_opening_to_pos_closing_TC_S_099(self): + from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_invoice + from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile + test_user, pos_profile = init_user_and_profile() + + opening_entry = create_opening_entry(pos_profile=pos_profile, user=test_user.name) + self.assertEqual(opening_entry.status, "Open") + + + pos_inv1 = create_pos_invoice(rate=3500, do_not_submit=1) + pos_inv1.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3500}) + pos_inv1.submit() + + pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1) + pos_inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3200}) + pos_inv2.submit() + + closing_enrty= make_closing_entry_from_opening(opening_entry) + closing_enrty.submit() + opening_entry.reload() + self.assertEqual(opening_entry.status, "Closed") + + def test_pos_opening_to_closing_enrty_check_cashire_and_posprofile_TC_S_100(self): + from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_invoice + from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile + test_user, pos_profile = init_user_and_profile() + + opening_entry = create_opening_entry(pos_profile=pos_profile, user=test_user.name) + self.assertEqual(opening_entry.status, "Open") + self.assertEqual(opening_entry.pos_profile, pos_profile.name) + self.assertEqual(opening_entry.user,test_user.name) + + + pos_inv1 = create_pos_invoice(rate=3500, do_not_submit=1) + pos_inv1.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3500}) + pos_inv1.submit() + + pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1) + pos_inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3200}) + pos_inv2.submit() + + closing_enrty= make_closing_entry_from_opening(opening_entry) + closing_enrty.submit() + opening_entry.reload() + self.assertEqual(opening_entry.status, "Closed") + + def test_pos_opening_to_closing_enrty_with_opening_balance_TC_S_101(self): + from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_invoice + from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile + test_user, pos_profile = init_user_and_profile() + + opening_entry = frappe.new_doc("POS Opening Entry") + opening_entry.pos_profile = pos_profile.name + opening_entry.user = test_user.name + opening_entry.company = pos_profile.company + opening_entry.period_start_date = frappe.utils.get_datetime() + + # Add opening balance details + balance_details = [] + for d in pos_profile.payments: + balance_details.append({ + "mode_of_payment": d.mode_of_payment, + "opening_amount": 1000 + }) + + opening_entry.set("balance_details", balance_details) + opening_entry.save() + opening_entry.submit() + self.assertEqual(opening_entry.status, "Open") + + pos_inv1 = create_pos_invoice(rate=3500, do_not_submit=1) + pos_inv1.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3500}) + pos_inv1.submit() + + pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1) + pos_inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3200}) + pos_inv2.submit() + + closing_enrty= make_closing_entry_from_opening(opening_entry) + closing_enrty.submit() + opening_entry.reload() + self.assertEqual(opening_entry.status, "Closed") def create_opening_entry(pos_profile, user): entry = frappe.new_doc("POS Opening Entry") @@ -24,4 +106,4 @@ def create_opening_entry(pos_profile, user): entry.set("balance_details", balance_details) entry.submit() - return entry.as_dict() + return entry diff --git a/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.json b/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.json index 6ff9bc3b7ffc..35c9c99eb37c 100644 --- a/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.json +++ b/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.json @@ -1,7 +1,6 @@ { "actions": [], "autoname": "format:ACC-PPR-{#####}", - "beta": 1, "creation": "2023-03-30 21:28:39.793927", "default_view": "List", "doctype": "DocType", diff --git a/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.py b/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.py index 882a2c4ad1cc..35f1e31af349 100644 --- a/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.py +++ b/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.py @@ -212,7 +212,7 @@ def trigger_reconciliation_for_queued_docs(): unique_filters = set() queue_size = 5 - fields = ["company", "party_type", "party", "receivable_payable_account"] + fields = ["company", "party_type", "party", "receivable_payable_account", "default_advance_account"] def get_filters_as_tuple(fields, doc): filters = () diff --git a/erpnext/accounts/doctype/process_payment_reconciliation_log/process_payment_reconciliation_log.json b/erpnext/accounts/doctype/process_payment_reconciliation_log/process_payment_reconciliation_log.json index b4ac9812cbf2..e46e7223531e 100644 --- a/erpnext/accounts/doctype/process_payment_reconciliation_log/process_payment_reconciliation_log.json +++ b/erpnext/accounts/doctype/process_payment_reconciliation_log/process_payment_reconciliation_log.json @@ -1,7 +1,6 @@ { "actions": [], "autoname": "format:PPR-LOG-{##}", - "beta": 1, "creation": "2023-03-13 15:00:09.149681", "default_view": "List", "doctype": "DocType", diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index b3cf02df039f..7793ddbd2e3d 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -2490,7 +2490,7 @@ def test_tax_withholding_with_supplier_TC_ACC_023(self): "against_voucher": None }, { - "account": "Test TDS Payable - _TC", + "account": "_Test TDS Payable - _TC", "debit": 0.0, "credit": 1800.0, "against_voucher": None @@ -2602,7 +2602,7 @@ def test_lower_tax_deduction_TC_ACC_025_and_TC_ACC_026(self): ["Creditors - _TC", 0.0, 50000.0, pi.posting_date], ["Creditors - _TC", 600.0, 0.0, pi.posting_date], ["Stock Received But Not Billed - _TC", 50000.0, 0.0, pi.posting_date], - ["Test TDS Payable - _TC", 0.0, 600.0, pi.posting_date] + ["_Test TDS Payable - _TC", 0.0, 600.0, pi.posting_date] ] check_gl_entries(self, pi.name, expected_gle, pi.posting_date) @@ -2632,6 +2632,8 @@ def test_currency_exchange_with_pi_TC_ACC_027(self): pi.submit() pe = get_payment_entry("Purchase Invoice", pi.name) + pe.payment_type= "Pay" + pe.paid_from = "Cash - _TC" pe.target_exchange_rate = 60 pe.save() pe.submit() @@ -2779,6 +2781,8 @@ def test_advance_payment_TC_ACC_028(self): _pe = get_payment_entry('Purchase Invoice', pi.name) _pe.target_exchange_rate = 62 + _pe.payment_type= "Pay" + _pe.paid_from = "Cash - _TC" _pe.save() _pe.submit() @@ -2835,7 +2839,9 @@ def test_single_payment_request_for_purchase_invoice_TC_ACC_035(self): submit_doc=1, return_doc=1, ) - pe=pr.create_payment_entry() + pe=pr.create_payment_entry(submit=False) + pe.payment_type= "Pay" + pe.paid_from = "Cash - _TC" pe.save() pe.submit() pr.load_from_db() @@ -2887,7 +2893,9 @@ def test_multi_payment_request_for_purchase_invoice_TC_ACC_036(self): pr.grand_total = pr.grand_total / 2 pr.save() pr.submit() - pe=pr.create_payment_entry() + pe=pr.create_payment_entry(submit=False) + pe.payment_type= "Pay" + pe.paid_from = "Cash - _TC" pe.save() pe.submit() pr.load_from_db() @@ -2914,7 +2922,9 @@ def test_multi_payment_request_for_purchase_invoice_TC_ACC_036(self): return_doc=1, submit_doc=1, ) - _pe=_pr.create_payment_entry() + _pe=_pr.create_payment_entry(submit=False) + _pe.payment_type= "Pay" + _pe.paid_from = "Cash - _TC" _pe.save() _pe.submit() _pr.load_from_db() @@ -2977,6 +2987,22 @@ def test_invoice_status_on_payment_entry_submit_TC_B_035_and_TC_B_037(self): pi_status_after_reconcile = frappe.db.get_value("Purchase Invoice", pi.name, "status") self.assertEqual(pi_status_after_reconcile, "Unpaid") + new_pi = make_purchase_invoice( + qty=1, + item_code="_Test Item", + supplier = "_Test Supplier", + company = "_Test Company", + rate = 30, + do_not_save =True, + ) + new_pi.save() + new_pi.set_advances() + new_pi.save() + new_pi.submit() + + pi_status_after_advances = frappe.db.get_value("Purchase Invoice", new_pi.name, "status") + self.assertEqual(pi_status_after_advances, "Paid") + def test_partly_paid_of_pi_to_pr_to_pe_TC_B_081(self): from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry pi = make_purchase_invoice( @@ -3176,7 +3202,85 @@ def test_fully_paid_of_pi_to_pr_to_pe_with_gst_TC_B_084(self): pi_status = frappe.db.get_value("Purchase Invoice", pi.name, "status") self.assertEqual(pi_status, "Paid") + + def test_standalone_pi_is_fully_paid_TC_B_088(self): + purchase_tax = frappe.new_doc("Purchase Taxes and Charges Template") + purchase_tax.title = "TEST" + purchase_tax.company = "_Test Company" + purchase_tax.tax_category = "_Test Tax Category 1" + + purchase_tax.append("taxes",{ + "category":"Total", + "add_deduct_tax":"Add", + "charge_type":"On Net Total", + "account_head":"_Test Account Excise Duty - _TC", + "_Test Account Excise Duty":"_Test Account Excise Duty", + "rate":100, + "description":"GST" + }) + + purchase_tax.save() + + pi = make_purchase_invoice( + qty=1, + item_code="_Test Item", + supplier = "_Test Supplier", + company = "_Test Company", + rate = 500, + do_not_save = True + ) + + + pi.taxes_and_charges = purchase_tax.name + pi.is_paid = 1 + pi.mode_of_payment = "Cash" + pi.cash_bank_account = "Cash - _TC" + pi.save() + pi.paid_amount = pi.grand_total + pi.submit() + pi_status = frappe.db.get_value("Purchase Invoice", pi.name, "status") + self.assertEqual(pi_status, "Paid") + + def test_standalone_pi_is_partly_paid_TC_B_090(self): + purchase_tax = frappe.new_doc("Purchase Taxes and Charges Template") + purchase_tax.title = "TEST" + purchase_tax.company = "_Test Company" + purchase_tax.tax_category = "_Test Tax Category 1" + + purchase_tax.append("taxes",{ + "category":"Total", + "add_deduct_tax":"Add", + "charge_type":"On Net Total", + "account_head":"_Test Account Excise Duty - _TC", + "_Test Account Excise Duty":"_Test Account Excise Duty", + "rate":100, + "description":"GST" + }) + + purchase_tax.save() + + pi = make_purchase_invoice( + qty=1, + item_code="_Test Item", + supplier = "_Test Supplier", + company = "_Test Company", + rate = 500, + do_not_save = True + ) + + + pi.taxes_and_charges = purchase_tax.name + pi.is_paid = 1 + pi.mode_of_payment = "Cash" + pi.cash_bank_account = "Cash - _TC" + pi.save() + pi.paid_amount = pi.grand_total / 2 + pi.submit() + + pi_status = frappe.db.get_value("Purchase Invoice", pi.name, "status") + self.assertEqual(pi_status, "Partly Paid") + def set_advance_flag(company, flag, default_account): frappe.db.set_value( "Company", @@ -3213,7 +3317,6 @@ def check_gl_entries( for col in additional_columns: query = query.select(gl[col]) gl_entries = query.run(as_dict=True) - for i, gle in enumerate(gl_entries): doc.assertEqual(expected_gle[i][0], gle.account) doc.assertEqual(expected_gle[i][1], gle.debit) @@ -3449,6 +3552,7 @@ def update_ldc_details(supplier): setattr(supplier,'custom_lower_tds_deduction_applicable','Yes') if not supplier.pan: setattr(supplier,'pan','DAJPC4150P') + supplier.flags.ignore_mandatory = True supplier.save() frappe.db.commit() diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index c168cac5675e..0982e52a0d64 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -4263,15 +4263,15 @@ def test_jv_records_creation_diff_ex_rate_TC_ACC_029(self): from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center from erpnext.accounts.doctype.payment_entry.test_payment_entry import make_test_item from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import get_jv_entry_account - + create_cost_center( - cost_center_name="_Test Cost Center - _TC", + cost_center_name="_Test Cost Center", company="_Test Company", parent_cost_center="_Test Company - _TC" ) create_account( - account_name="_Test Receivable USD - _TC", + account_name="_Test Receivable USD", parent_account="Current Assets - _TC", company="_Test Company", account_currency="USD", @@ -4315,7 +4315,11 @@ def test_jv_records_creation_diff_ex_rate_TC_ACC_029(self): si.save() si.submit() - pe = get_payment_entry("Sales Invoice", si.name) + pe = get_payment_entry("Sales Invoice", si.name) + pe.payment_type == "Receive" + pe.mode_of_payment = "Cash" + pe.paid_from="_Test Receivable USD - _TC" + pe.paid_to = "Cash - _TC" pe.source_exchange_rate = 60 pe.save() pe.submit() @@ -4352,20 +4356,20 @@ def test_jv_records_creation_diff_ex_rate_TC_ACC_030(self): from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import get_jv_entry_account create_cost_center( - cost_center_name="_Test Cost Center - _TC", + cost_center_name="_Test Cost Center", company="_Test Company", parent_cost_center="_Test Company - _TC" ) create_account( - account_name="_Test Receivable USD - _TC", + account_name="_Test Receivable USD", parent_account="Current Assets - _TC", company="_Test Company", account_currency="USD", account_type="Receivable", ) create_account( - account_name="_Test Cash - _TC", + account_name="_Test Cash", parent_account="Cash In Hand - _TC", company="_Test Company", account_currency="INR", @@ -4461,8 +4465,221 @@ def test_jv_records_creation_diff_ex_rate_TC_ACC_030(self): voucher_type="Journal Entry" ) + def test_single_payment_request_for_purchase_invoice_TC_ACC_037(self): + from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center + from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request + from erpnext.accounts.doctype.payment_entry.test_payment_entry import make_test_item + + setup_bank_accounts() + + create_cost_center( + cost_center_name="_Test Cost Center", + company="_Test Company", + parent_cost_center="_Test Company - _TC" + ) + + create_account( + account_name="_Test Receivable", + parent_account="Current Assets - _TC", + company="_Test Company", + account_currency="INR", + account_type="Receivable", + ) + create_account( + account_name="_Test Cash", + parent_account="Cash In Hand - _TC", + company="_Test Company", + account_currency="INR", + account_type="Cash", + ) + + create_customer( + customer_name="_Test Customer", + currency="INR", + company="_Test Company", + account="_Test Receivable - _TC" + ) + + item = make_test_item(item_name="_Test Item") + + if item.is_new(): + item.append( + "item_defaults", + { + "default_warehouse": '_Test Warehouse - _TC', + "company": "_Test Company", + "selling_cost_center": "_Test Cost Center - _TC", + }, + ) + item.save() + + frappe.db.commit() + + customer =frappe.get_doc("Customer", "_Test Customer") + + if customer: + si=create_sales_invoice( + customer="_Test Customer", + company="_Test Company", + parent_cost_center="_Test Cost Center - _TC", + item_code=item.name, + qty=1, + rate=5000, + ) + + si.submit() + + pr =make_payment_request( + dt="Sales Invoice", + dn=si.name, + recipient_id="test@test.com", + payment_gateway_account="_Test Gateway - INR", + mute_email=1, + submit_doc=1, + return_doc=1, + ) + + pe = pr.set_as_paid() + pe.save() + pe.submit() + pr.load_from_db() + self.assertEqual(pr.status, "Paid") + si.load_from_db() + self.assertEqual(si.status, "Paid") + expected_gle = [ + ["Debtors - _TC", 0.0, si.grand_total, pe.posting_date], + ["_Test Bank - _TC", si.grand_total, 0.0, pe.posting_date] + ] + check_gl_entries( + doc=self, + voucher_no=pe.name, + expected_gle=expected_gle, + voucher_type="Payment Entry", + posting_date=pe.posting_date + ) + + def test_multiple_payment_request_for_purchase_invoice_TC_ACC_038(self): + from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center + from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request + from erpnext.accounts.doctype.payment_entry.test_payment_entry import make_test_item + + setup_bank_accounts() + + create_cost_center( + cost_center_name="_Test Cost Center", + company="_Test Company", + parent_cost_center="_Test Company - _TC" + ) + + create_account( + account_name="_Test Receivable", + parent_account="Current Assets - _TC", + company="_Test Company", + account_currency="INR", + account_type="Receivable", + ) + create_account( + account_name="_Test Cash", + parent_account="Cash In Hand - _TC", + company="_Test Company", + account_currency="INR", + account_type="Cash", + ) + + create_customer( + customer_name="_Test Customer", + currency="INR", + company="_Test Company", + account="_Test Receivable - _TC" + ) + + item = make_test_item(item_name="_Test Item") + + if item.is_new(): + item.append( + "item_defaults", + { + "default_warehouse": '_Test Warehouse - _TC', + "company": "_Test Company", + "selling_cost_center": "_Test Cost Center - _TC", + }, + ) + item.save() + frappe.db.commit() + + customer =frappe.get_doc("Customer", "_Test Customer") + + if customer: + si=create_sales_invoice( + customer="_Test Customer", + company="_Test Company", + parent_cost_center="_Test Cost Center - _TC", + item_code=item.name, + qty=1, + rate=5000, + ) + si.submit() + + pr =make_payment_request( + dt="Sales Invoice", + dn=si.name, + recipient_id="test@test.com", + payment_gateway_account="_Test Gateway - INR", + mute_email=1, + return_doc=1, + ) + pr.grand_total = pr.grand_total / 2 + pr.save() + pr.submit() + pe = pr.set_as_paid() + pe.save() + pe.submit() + + pr.load_from_db() + self.assertEqual(pr.status, "Paid") + si.load_from_db() + self.assertEqual(si.status, "Partly Paid") + expected_gle = [ + ["Debtors - _TC", 0.0, si.grand_total/2, pe.posting_date], + ["_Test Bank - _TC", si.grand_total/2, 0.0, pe.posting_date] + ] + check_gl_entries( + doc=self, + voucher_no=pe.name, + expected_gle=expected_gle, + voucher_type="Payment Entry", + posting_date=pe.posting_date + ) + _pr=make_payment_request( + dt="Sales Invoice", + dn=si.name, + recipient_id="test@test.com", + payment_gateway_account="_Test Gateway - INR", + mute_email=1, + return_doc=1, + submit_doc=1, + ) + _pe=_pr.set_as_paid() + _pe.save() + _pe.submit() + + _pr.load_from_db() + self.assertEqual(_pr.status, "Paid") + si.load_from_db() + self.assertEqual(si.status, "Paid") + expected_gle = [ + ["Debtors - _TC", 0.0, si.grand_total/2, _pe.posting_date], + ["_Test Bank - _TC", si.grand_total/2, 0.0, _pe.posting_date] + ] + check_gl_entries( + doc=self, + voucher_no=_pe.name, + expected_gle=expected_gle, + voucher_type="Payment Entry", + posting_date=pe.posting_date + ) def test_sales_invoice_without_sales_order_with_gst_TC_S_016(self): setting = frappe.get_doc("Selling Settings") setting.so_required = 'No' @@ -5077,7 +5294,7 @@ def get_taxes_and_charges(): def create_internal_parties(): from erpnext.selling.doctype.customer.test_customer import create_internal_customer - + create_internal_customer( customer_name="_Test Internal Customer", represents_company="_Test Company 1", @@ -5196,5 +5413,32 @@ def create_accounts(**args): frappe.log_error(f"Failed to insert {args.get('account_name')}", str(e)) +def setup_bank_accounts(): + payment_gateway = {"doctype": "Payment Gateway", "gateway": "_Test Gateway"} - + payment_method = [ + { + "doctype": "Payment Gateway Account", + "is_default": 1, + "payment_gateway": "_Test Gateway", + "payment_account": "_Test Bank - _TC", + "currency": "INR", + }, + { + "doctype": "Payment Gateway Account", + "payment_gateway": "_Test Gateway", + "payment_account": "_Test Bank USD - _TC", + "currency": "USD", + }, + ] + + if not frappe.db.get_value("Payment Gateway", payment_gateway["gateway"], "name"): + frappe.get_doc(payment_gateway).insert(ignore_permissions=True) + + for method in payment_method: + if not frappe.db.get_value( + "Payment Gateway Account", + {"payment_gateway": method["payment_gateway"], "currency": method["currency"]}, + "name", + ): + frappe.get_doc(method).insert(ignore_permissions=True) \ No newline at end of file diff --git a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py index 58ac76b83abc..f90d92996a74 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py @@ -1091,7 +1091,14 @@ def create_tax_withholding_category( "accounts": [{"company": "_Test Company", "account": account}], } ).insert() - + elif frappe.db.exists("Tax Withholding Category", category_name): + doc = frappe.get_doc("Tax Withholding Category",category_name) + if doc.accounts: + child=frappe.get_doc("Tax Withholding Account",doc.accounts[0].name) + if child.account != account: + child.account=account + child.save() + frappe.db.commit() def create_lower_deduction_certificate(supplier, tax_withholding_category, tax_rate, certificate_no, limit): fiscal_year = get_fiscal_year(today(), company="_Test Company") 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 e0be8e575b81..eec1951c6a9e 100644 --- a/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py +++ b/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py @@ -142,7 +142,8 @@ def get_journal_entries(filters): where jvd.parent = jv.name and jv.docstatus=1 and jvd.account = %(account)s and jv.posting_date <= %(report_date)s and ifnull(jv.clearance_date, '4000-01-01') > %(report_date)s - and ifnull(jv.is_opening, 'No') = 'No'""", + and ifnull(jv.is_opening, 'No') = 'No' + and jv.company = %(company)s """, filters, as_dict=1, ) @@ -168,6 +169,7 @@ def get_payment_entries(filters): AND docstatus = 1 AND posting_date <= %(report_date)s AND COALESCE(clearance_date, '4000-01-01') > %(report_date)s + and company = %(company)s """, filters, as_dict=1, @@ -186,6 +188,7 @@ def get_pos_entries(filters): sip.account=%(account)s and si.docstatus=1 and sip.parent = si.name and account.name = sip.account and si.posting_date <= %(report_date)s and ifnull(sip.clearance_date, '4000-01-01') > %(report_date)s + and si.company = %(company)s order by si.posting_date ASC, si.name DESC """, diff --git a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py index e540aa9993c8..dec189da4185 100644 --- a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py +++ b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py @@ -240,8 +240,8 @@ def get_actual_details(name, filters): if filters.get("budget_against") == "Cost Center": cc_lft, cc_rgt = frappe.db.get_value("Cost Center", name, ["lft", "rgt"]) cond = f""" - and lft >= "{cc_lft}" - and rgt <= "{cc_rgt}" + and lft >= {cc_lft} + and rgt <= {cc_rgt} """ ac_details = frappe.db.sql( @@ -251,8 +251,8 @@ def get_actual_details(name, filters): gl.debit, gl.credit, gl.fiscal_year, - MONTHNAME(gl.posting_date) as month_name, - b.{budget_against} as budget_against + TO_CHAR(gl.posting_date, 'Month') AS month_name, + (ARRAY_AGG(b.{budget_against}))[1] as budget_against from `tabGL Entry` gl, `tabBudget Account` ba, diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py index 0a55ae273a43..1a6bcb3bd633 100644 --- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py +++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py @@ -810,13 +810,25 @@ def get_group_by_and_display_fields(filters): def add_sub_total_row(item, total_row_map, group_by_value, tax_columns): - total_row = total_row_map.get(group_by_value) - total_row["stock_qty"] += item["stock_qty"] - total_row["amount"] += item["amount"] - total_row["total_tax"] += item["total_tax"] - total_row["total"] += item["total"] - total_row["percent_gt"] += item["percent_gt"] - - for tax in tax_columns: - total_row.setdefault(frappe.scrub(tax + " Amount"), 0.0) - total_row[frappe.scrub(tax + " Amount")] += flt(item[frappe.scrub(tax + " Amount")]) + if group_by_value not in total_row_map: + total_row_map[group_by_value] = { + "stock_qty": 0.0, + "amount": 0.0, + "total_tax": 0.0, + "total": 0.0, + "percent_gt": 0.0 + } + + total_row = total_row_map.get(group_by_value) + + total_row["stock_qty"] += item.get("stock_qty", 0) + total_row["amount"] += item.get("amount", 0) + total_row["total_tax"] += item.get("total_tax", 0) + total_row["total"] += item.get("total", 0) + total_row["percent_gt"] += item.get("percent_gt", 0) + + for tax in tax_columns: + tax_amount_field = frappe.scrub(tax + " Amount") + total_row.setdefault(tax_amount_field, 0.0) + total_row[tax_amount_field] += item.get(tax_amount_field, 0.0) + diff --git a/erpnext/accounts/report/trial_balance/trial_balance.py b/erpnext/accounts/report/trial_balance/trial_balance.py index 8e7370e64738..9f3db7435c2f 100644 --- a/erpnext/accounts/report/trial_balance/trial_balance.py +++ b/erpnext/accounts/report/trial_balance/trial_balance.py @@ -4,6 +4,7 @@ import frappe from frappe import _ +from frappe.boot import DocType from frappe.query_builder.functions import Sum from frappe.utils import add_days, cstr, flt, formatdate, getdate @@ -143,13 +144,23 @@ def get_rootwise_opening_balances(filters, report_type): ) if not ignore_closing_balances: - last_period_closing_voucher = frappe.db.get_all( - "Period Closing Voucher", - filters={"docstatus": 1, "company": filters.company, "period_end_date": ("<", filters.from_date)}, - fields=["period_end_date", "name"], - order_by="period_end_date desc", - limit=1, + PeriodClosingVoucher = DocType('Period Closing Voucher') + + query = ( + frappe.qb.from_(PeriodClosingVoucher) + .select( + PeriodClosingVoucher.period_end_date, + PeriodClosingVoucher.name + ) + .where( + (PeriodClosingVoucher.docstatus == 1) & + (PeriodClosingVoucher.company == filters.company) & + (PeriodClosingVoucher.period_end_date < filters.from_date) + ) + .orderby(PeriodClosingVoucher.period_end_date, order=frappe.qb.desc) + .limit(1) ) + last_period_closing_voucher = query.run() accounting_dimensions = get_accounting_dimensions(as_list=False) diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index 6cbe6c5d0efc..4ce25aaffa72 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -1444,10 +1444,10 @@ def test_pi_return_TC_B_043(self): check_gl_entries(self, pi.name, expected_gle, nowdate()) actual_qty_1 = get_qty_after_transaction(warehouse="Finished Goods - _TC") self.assertEqual(actual_qty_0 + 1, actual_qty_1) - + po_status = frappe.db.get_value("Purchase Order", po.name, "status") self.assertEqual(po_status, "Completed") - + pi_return = make_debit_note(pi.name) pi_return.update_outstanding_for_self = 0 pi_return.update_billed_amount_in_purchase_receipt = 0 @@ -1462,27 +1462,27 @@ def test_pi_return_TC_B_043(self): check_gl_entries(self, pi_return.name, expected_gle, nowdate()) actual_qty_2 = get_qty_after_transaction(warehouse="Finished Goods - _TC") self.assertEqual(actual_qty_1 - 1, actual_qty_2) - + pi_status = frappe.db.get_value("Purchase Invoice", pi.name, "status") self.assertEqual(pi_status, "Debit Note Issued") def test_payment_entry_TC_B_037(self): from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import check_gl_entries - + po = create_purchase_order( warehouse="Finished Goods - _TC", rate=30, qty=1, ) - + self.assertEqual(po.status, "To Receive and Bill") pi = make_pi_from_po(po.name) pi.update_stock = 1 pi.save() pi.submit() pi.load_from_db() - + expected_gle = [ ["Creditors - _TC", 0.0, 30, nowdate()], ["_Test Account Cost for Goods Sold - _TC", 30, 0.0, nowdate()], @@ -1499,17 +1499,17 @@ def test_payment_entry_TC_B_037(self): {"account": "Cash - _TC", "debit": 0.0, "credit": 30.0}, ] check_payment_gl_entries(self, pe.name, expected_gle) - + def test_purchase_invoice_cancellation_TC_B_041(self): from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice - + po = create_purchase_order( warehouse="Finished Goods - _TC", rate=130, qty=1, ) self.assertEqual(po.status, "To Receive and Bill") - + pr = make_purchase_receipt(po.name) pr.save() pr.submit() @@ -1532,7 +1532,6 @@ def test_purchase_invoice_cancellation_TC_B_041(self): self.assertEqual(po_status, "To Bill") pr_status = frappe.db.get_value("Purchase Receipt", pr.name, "status") self.assertEqual(pr_status, "To Bill") - def test_purchase_invoice_return_TC_B_042(self): from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import make_debit_note @@ -1558,11 +1557,11 @@ def test_purchase_invoice_return_TC_B_042(self): self.assertEqual(pi_return.status, "Return") pi_status = frappe.db.get_value("Purchase Invoice", pi.name, "status") self.assertEqual(pi_status, "Debit Note Issued") - + def test_50_50_payment_terms_TC_B_045(self): from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry - + po = create_purchase_order( warehouse="Finished Goods - _TC", rate=130, @@ -1572,7 +1571,7 @@ def test_50_50_payment_terms_TC_B_045(self): po.payment_terms_template = "_Test Payment Term Template" po.save() po.submit() - + pe = get_payment_entry("Purchase Order", po.name, party_amount=po.grand_total/2) pe.save() pe.submit() @@ -1595,7 +1594,7 @@ def test_50_50_payment_terms_TC_B_045(self): pe.submit() po_status = frappe.db.get_value("Purchase Order", po.name, "status") self.assertEqual(po_status, "Completed") - + pi_status = frappe.db.get_value("Purchase Invoice", pi.name, "status") self.assertEqual(pi_status, "Paid") @@ -1960,6 +1959,221 @@ def test_pr_to_lcv_add_value_to_stock_TC_B_034(self): ) self.assertGreater(len(gl_entries), 0) + def test_po_to_pr_with_gst_partly_paid_TC_B_085(self): + # Scenario : PO => PR with GST Partly Paid + + purchase_tax = frappe.new_doc("Purchase Taxes and Charges Template") + purchase_tax.title = "TEST" + purchase_tax.company = "_Test Company" + purchase_tax.tax_category = "_Test Tax Category 1" + + purchase_tax.append("taxes",{ + "category":"Total", + "add_deduct_tax":"Add", + "charge_type":"On Net Total", + "account_head":"_Test Account Excise Duty - _TC", + "_Test Account Excise Duty":"_Test Account Excise Duty", + "rate":100, + "description":"GST" + }) + purchase_tax.save() + po = create_purchase_order(do_not_submit=True) + po.taxes_and_charges = purchase_tax.name + po.save() + po.submit() + self.assertEqual(po.docstatus,1) + + args = { + "dt": po.doctype, + "dn": po.name, + "payment_request_type": 'Outward', + "party_type": "Supplier", + "party": po.supplier, + "party_name": po.supplier_name + } + partly_pr = make_payment_request(**args) + doc_pr = frappe.get_doc("Payment Request", partly_pr.name) + # set half amount to be paid + doc_pr.grand_total = po.grand_total / 2 + doc_pr.submit() + po_status = frappe.db.get_value("Purchase Order",po.name,'status') + self.assertEqual(po_status,'To Receive and Bill') + + def test_po_to_pr_with_gst_fully_paid_TC_B_086(self): + # Scenario : PO => PR with GST Fully Paid + + purchase_tax = frappe.new_doc("Purchase Taxes and Charges Template") + purchase_tax.title = "TEST" + purchase_tax.company = "_Test Company" + purchase_tax.tax_category = "_Test Tax Category 1" + + purchase_tax.append("taxes",{ + "category":"Total", + "add_deduct_tax":"Add", + "charge_type":"On Net Total", + "account_head":"_Test Account Excise Duty - _TC", + "_Test Account Excise Duty":"_Test Account Excise Duty", + "rate":100, + "description":"GST" + }) + purchase_tax.save() + po = create_purchase_order(do_not_submit=True) + po.taxes_and_charges = purchase_tax.name + po.save() + po.submit() + self.assertEqual(po.docstatus,1) + + args = { + "dt": po.doctype, + "dn": po.name, + "payment_request_type": 'Outward', + "party_type": "Supplier", + "party": po.supplier, + "party_name": po.supplier_name + } + partly_pr = make_payment_request(**args) + doc_pr = frappe.get_doc("Payment Request", partly_pr.name) + doc_pr.grand_total = po.grand_total + doc_pr.submit() + po_status = frappe.db.get_value("Purchase Order",po.name,'status') + self.assertEqual(po_status,'To Receive and Bill') + + def test_po_to_pr_to_pi_fully_paid_TC_B_087(self): + from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice + + purchase_tax = frappe.new_doc("Purchase Taxes and Charges Template") + purchase_tax.title = "TEST" + purchase_tax.company = "_Test Company" + purchase_tax.tax_category = "_Test Tax Category 1" + + purchase_tax.append("taxes",{ + "category":"Total", + "add_deduct_tax":"Add", + "charge_type":"On Net Total", + "account_head":"_Test Account Excise Duty - _TC", + "_Test Account Excise Duty":"_Test Account Excise Duty", + "rate":100, + "description":"GST" + }) + + purchase_tax.save() + + po = create_purchase_order(do_not_save=True) + po.taxes_and_charges = purchase_tax.name + po.save() + po.submit() + po_status_before = frappe.db.get_value("Purchase Order",po.name,'status') + self.assertEqual(po_status_before,'To Receive and Bill') + + pr = make_purchase_receipt(po.name) + pr.save() + pr.submit() + + po_status_after_pr = frappe.db.get_value("Purchase Order",po.name,'status') + self.assertEqual(po_status_after_pr,'To Bill') + + pi = make_purchase_invoice(pr.name) + pi.is_paid = 1 + pi.mode_of_payment = "Cash" + pi.cash_bank_account = "Cash - _TC" + pi.paid_amount = pr.grand_total + pi.save() + pi.submit() + + pi_status = frappe.db.get_value("Purchase Invoice",pi.name,'status') + self.assertEqual(pi_status,'Paid') + + po_status_after_paid = frappe.db.get_value("Purchase Order",po.name,'status') + self.assertEqual(po_status_after_paid,'Completed') + + def test_po_to_pr_to_pi_partly_paid_TC_B_089(self): + from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice + purchase_tax = frappe.new_doc("Purchase Taxes and Charges Template") + purchase_tax.title = "TEST" + purchase_tax.company = "_Test Company" + purchase_tax.tax_category = "_Test Tax Category 1" + + purchase_tax.append("taxes",{ + "category":"Total", + "add_deduct_tax":"Add", + "charge_type":"On Net Total", + "account_head":"_Test Account Excise Duty - _TC", + "_Test Account Excise Duty":"_Test Account Excise Duty", + "rate":100, + "description":"GST" + }) + + purchase_tax.save() + + po = create_purchase_order(do_not_save=True) + po.taxes_and_charges = purchase_tax.name + po.save() + po.submit() + po_status_before = frappe.db.get_value("Purchase Order",po.name,'status') + self.assertEqual(po_status_before,'To Receive and Bill') + + pr = make_purchase_receipt(po.name) + pr.save() + pr.submit() + + po_status_after_pr = frappe.db.get_value("Purchase Order",po.name,'status') + self.assertEqual(po_status_after_pr,'To Bill') + + pi = make_purchase_invoice(pr.name) + pi.is_paid = 1 + pi.mode_of_payment = "Cash" + pi.cash_bank_account = "Cash - _TC" + pi.paid_amount = pr.grand_total / 2 + pi.save() + pi.submit() + + pi_status = frappe.db.get_value("Purchase Invoice",pi.name,'status') + self.assertEqual(pi_status,'Partly Paid') + + po_status_after_paid = frappe.db.get_value("Purchase Order",po.name,'status') + self.assertEqual(po_status_after_paid,'Completed') + + def test_po_with_additional_discount_TC_B_057(self): + company = "_Test Company" + item_code = "Testing-31" + target_warehouse = "Stores - _TC" + supplier = "_Test Supplier 1" + item_price = 10000 + if not frappe.db.exists("Item", item_code): + frappe.get_doc({ + "doctype": "Item", + "item_code": item_code, + "item_name": item_code, + "is_stock_item": 1, + "is_purchase_item": 1, + "is_sales_item": 0, + "company": company + }).insert() + pi = frappe.get_doc({ + "doctype": "Purchase Order", + "supplier": supplier, + "company": company, + "schedule_date": today(), + "set_warehouse": target_warehouse, + "items": [ + { + "item_code": item_code, + "warehouse": target_warehouse, + "qty": 1, + "rate": item_price + } + ] + }) + pi.insert() + self.assertEqual(len(pi.items), 1) + self.assertEqual(pi.items[0].rate, item_price) + self.assertEqual(pi.net_total, item_price) + pi.apply_discount_on = "Net Total" + pi.additional_discount_percentage = 10 + pi.save() + pi.submit() + self.assertEqual(pi.discount_amount, 1000) + self.assertEqual(pi.net_total, 9000) def create_po_for_sc_testing(): diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 932cee51322f..83ffc9be9f29 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2350,6 +2350,7 @@ def validate_payment_schedule_dates(self): for d in self.get("payment_schedule"): + d.validate_from_to_dates("discount_date", "due_date") if self.doctype == "Sales Order" and getdate(d.due_date) < getdate(self.transaction_date): frappe.throw( _("Row {0}: Due Date in the Payment Terms table cannot be before Posting Date").format( diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py index f33f2987f23d..89c73b65e1dc 100644 --- a/erpnext/controllers/subcontracting_controller.py +++ b/erpnext/controllers/subcontracting_controller.py @@ -1241,6 +1241,7 @@ def add_items_in_ste(ste_doc, row, qty, rm_details, rm_detail_field="sco_rm_deta "item_code": row.item_details["rm_item_code"], "subcontracted_item": row.item_details["main_item_code"], "serial_no": "\n".join(row.serial_no) if row.serial_no else "", + "use_serial_batch_fields": 1, } ) @@ -1274,11 +1275,14 @@ def make_return_stock_entry_for_subcontract( for _key, value in available_materials.items(): if not value.qty: continue + + if item_details := value.get("item_details"): + item_details["serial_and_batch_bundle"] = None if value.batch_no: for batch_no, qty in value.batch_no.items(): if qty > 0: - add_items_in_ste(ste_doc, value, value.qty, rm_details, rm_detail_field, batch_no) + add_items_in_ste(ste_doc, value, qty, rm_details, rm_detail_field, batch_no) else: add_items_in_ste(ste_doc, value, value.qty, rm_details, rm_detail_field) diff --git a/erpnext/controllers/tests/test_subcontracting_controller.py b/erpnext/controllers/tests/test_subcontracting_controller.py index 761720d4ef73..41a2f6fc5d28 100644 --- a/erpnext/controllers/tests/test_subcontracting_controller.py +++ b/erpnext/controllers/tests/test_subcontracting_controller.py @@ -282,6 +282,74 @@ def test_subcontracting_with_same_components_different_fg(self): frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1) + + def test_return_non_consumed_batch_materials(self): + """ + - Set backflush based on Material Transfer. + - Create SCO for item Subcontracted Item SA2. + - Transfer the batched components from Stores to Supplier warehouse with serial nos. + - Transfer extra qty of component for the subcontracted item Subcontracted Item SA2. + - Create SCR for full qty against the SCO and change the qty of raw material. + - After that return the non consumed material back to the store from supplier's warehouse. + """ + frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 0) + set_backflush_based_on("Material Transferred for Subcontract") + service_item = make_item("Subcontracted Service FG Item A", properties={"is_stock_item": 0}).name + fg_item = make_item( + "Subcontracted FG Item SA2", properties={"is_stock_item": 1, "is_sub_contracted_item": 1} + ).name + rm_item = make_item( + "Subcontracted Batch RM Item SA2", + properties={ + "is_stock_item": 1, + "create_new_batch": 1, + "has_batch_no": 1, + "batch_number_series": "BATCH-RM-IRM-.####", + }, + ).name + make_bom(item=fg_item, raw_materials=[rm_item], rate=100, currency="INR") + service_items = [ + { + "warehouse": "_Test Warehouse - _TC", + "item_code": service_item, + "qty": 5, + "rate": 100, + "fg_item": fg_item, + "fg_item_qty": 5, + }, + ] + sco = get_subcontracting_order(service_items=service_items) + rm_items = get_rm_items(sco.supplied_items) + rm_items[0]["qty"] += 1 + itemwise_details = make_stock_in_entry(rm_items=rm_items) + for item in rm_items: + item["sco_rm_detail"] = sco.items[0].name + make_stock_transfer_entry( + sco_no=sco.name, + rm_items=rm_items, + itemwise_details=copy.deepcopy(itemwise_details), + ) + scr1 = make_subcontracting_receipt(sco.name) + scr1.save() + scr1.supplied_items[0].consumed_qty = 5 + scr1.submit() + for key, value in get_supplied_items(scr1).items(): + transferred_detais = itemwise_details.get(key) + self.assertEqual(value.qty, 5) + self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get("serial_no")[0:5])) + sco.load_from_db() + self.assertEqual(sco.supplied_items[0].consumed_qty, 5) + doc = get_materials_from_supplier(sco.name, [d.name for d in sco.supplied_items]) + doc.save() + self.assertEqual(doc.items[0].qty, 1) + self.assertEqual(doc.items[0].s_warehouse, "_Test Warehouse 1 - _TC") + self.assertEqual(doc.items[0].t_warehouse, "_Test Warehouse - _TC") + self.assertTrue(doc.items[0].batch_no) + self.assertTrue(doc.items[0].use_serial_batch_fields) + frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1) + + + def test_return_non_consumed_materials(self): """ - Set backflush based on Material Transfer. diff --git a/erpnext/manufacturing/doctype/bom/bom_item_preview.html b/erpnext/manufacturing/doctype/bom/bom_item_preview.html index eb4135e03ac0..2c0f091da588 100644 --- a/erpnext/manufacturing/doctype/bom/bom_item_preview.html +++ b/erpnext/manufacturing/doctype/bom/bom_item_preview.html @@ -3,7 +3,7 @@
{% if data.image %}
- +
{% endif %}
diff --git a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py index 2e8dba1ccfeb..af3b9d5149ea 100644 --- a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py +++ b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py @@ -9,7 +9,7 @@ import frappe from frappe.model.document import Document - +from frappe.utils import date_diff, get_datetime, now class BOMUpdateTool(Document): # begin: auto-generated types @@ -50,12 +50,18 @@ def auto_update_latest_price_in_all_boms() -> None: if frappe.db.get_single_value("Manufacturing Settings", "update_bom_costs_automatically"): wip_log = frappe.get_all( "BOM Update Log", - {"update_type": "Update Cost", "status": ["in", ["Queued", "In Progress"]]}, + fields=["creation", "status"], + filters={"update_type": "Update Cost", "status": ["in", ["Queued", "In Progress"]]}, limit_page_length=1, + order_by="creation desc", ) - if not wip_log: + if not wip_log or is_older_log(wip_log[0]): create_bom_update_log(update_type="Update Cost") +def is_older_log(log: dict) -> bool: + no_of_days = date_diff(get_datetime(now()), get_datetime(log.creation)) + return no_of_days > 10 + def create_bom_update_log( boms: dict[str, str] | None = None, diff --git a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json index cb766762b342..6332fa4ae73f 100644 --- a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json +++ b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json @@ -242,7 +242,7 @@ "depends_on": "eval:doc.backflush_raw_materials_based_on == \"BOM\"", "fieldname": "validate_components_quantities_per_bom", "fieldtype": "Check", - "label": "Validate Components Quantities Per BOM" + "label": "Validate Components and Quantities Per BOM" } ], "icon": "icon-wrench", diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 5e2c68883752..9c320edd4baa 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -2198,6 +2198,44 @@ def test_components_qty_for_bom_based_manufacture_entry(self): manufacture_entry.submit() frappe.db.set_single_value("Manufacturing Settings", "validate_components_quantities_per_bom", 0) + def test_components_as_per_bom_for_manufacture_entry(self): + frappe.db.set_single_value("Manufacturing Settings", "backflush_raw_materials_based_on", "BOM") + frappe.db.set_single_value("Manufacturing Settings", "validate_components_quantities_per_bom", 1) + fg_item = "Test FG Item For Component Validation 1" + source_warehouse = "Stores - _TC" + raw_materials = ["Test Component Validation RM Item 11", "Test Component Validation RM Item 12"] + make_item(fg_item, {"is_stock_item": 1}) + for item in raw_materials: + make_item(item, {"is_stock_item": 1}) + test_stock_entry.make_stock_entry( + item_code=item, + target=source_warehouse, + qty=10, + basic_rate=100, + ) + make_bom(item=fg_item, source_warehouse=source_warehouse, raw_materials=raw_materials) + wo = make_wo_order_test_record( + item=fg_item, + qty=10, + source_warehouse=source_warehouse, + ) + transfer_entry = frappe.get_doc(make_stock_entry(wo.name, "Material Transfer for Manufacture", 10)) + transfer_entry.save() + transfer_entry.remove(transfer_entry.items[0]) + self.assertRaises(frappe.ValidationError, transfer_entry.save) + transfer_entry = frappe.get_doc(make_stock_entry(wo.name, "Material Transfer for Manufacture", 10)) + transfer_entry.save() + transfer_entry.submit() + manufacture_entry = frappe.get_doc(make_stock_entry(wo.name, "Manufacture", 10)) + manufacture_entry.save() + manufacture_entry.remove(manufacture_entry.items[0]) + self.assertRaises(frappe.ValidationError, manufacture_entry.save) + manufacture_entry.delete() + manufacture_entry = frappe.get_doc(make_stock_entry(wo.name, "Manufacture", 10)) + manufacture_entry.save() + manufacture_entry.submit() + frappe.db.set_single_value("Manufacturing Settings", "validate_components_quantities_per_bom", 0) + def make_operation(**kwargs): diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index ef17ede9c7cd..43e90708f731 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -122,6 +122,31 @@ frappe.ui.form.on("Work Order", { ); }, + allow_alternative_item: function (frm) { + let has_alternative = false; + if (frm.doc.required_items) { + has_alternative = frm.doc.required_items.find((i) => i.allow_alternative_item === 1); + } + if (frm.doc.allow_alternative_item && frm.doc.docstatus === 0 && has_alternative) { + frm.add_custom_button(__("Alternate Item"), () => { + erpnext.utils.select_alternate_items({ + frm: frm, + child_docname: "required_items", + warehouse_field: "source_warehouse", + child_doctype: "Work Order Item", + original_item_field: "original_item", + condition: (d) => { + if (d.allow_alternative_item) { + return true; + } + }, + }); + }); + } else { + frm.remove_custom_button(__("Alternate Item")); + } + }, + refresh: function (frm) { erpnext.toggle_naming_series(); erpnext.work_order.set_custom_buttons(frm); @@ -155,25 +180,6 @@ frappe.ui.form.on("Work Order", { } } - if (frm.doc.required_items && frm.doc.allow_alternative_item) { - const has_alternative = frm.doc.required_items.find((i) => i.allow_alternative_item === 1); - if (frm.doc.docstatus == 0 && has_alternative) { - frm.add_custom_button(__("Alternate Item"), () => { - erpnext.utils.select_alternate_items({ - frm: frm, - child_docname: "required_items", - warehouse_field: "source_warehouse", - child_doctype: "Work Order Item", - original_item_field: "original_item", - condition: (d) => { - if (d.allow_alternative_item) { - return true; - } - }, - }); - }); - } - } if (frm.doc.status == "Completed") { if (frm.doc.__onload.backflush_raw_materials_based_on == "Material Transferred for Manufacture") { @@ -201,7 +207,7 @@ frappe.ui.form.on("Work Order", { ); } - frm.trigger("add_custom_button_to_return_components"); + frm.trigger("allow_alternative_item"); }, add_custom_button_to_return_components: function (frm) { @@ -526,6 +532,9 @@ frappe.ui.form.on("Work Order", { }); frappe.ui.form.on("Work Order Item", { + allow_alternative_item(frm) { + frm.trigger("allow_alternative_item"); + }, source_warehouse: function (frm, cdt, cdn) { var row = locals[cdt][cdn]; if (!row.item_code) { @@ -604,7 +613,7 @@ erpnext.work_order = { set_custom_buttons: function (frm) { var doc = frm.doc; - if (doc.status !== "Closed") { + if (doc.docstatus === 1 && doc.status !== "Closed") { frm.add_custom_button( __("Close"), function () { diff --git a/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.py b/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.py index 64363e20e394..a5690c477e4c 100644 --- a/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.py +++ b/erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.py @@ -50,7 +50,11 @@ def get_returned_materials(work_orders): raw_materials = frappe.get_all( "Stock Entry", - fields=["`tabStock Entry Detail`.`item_code`", "`tabStock Entry Detail`.`qty`"], + fields=[ + "`tabStock Entry`.`work_order`", + "`tabStock Entry Detail`.`item_code`", + "`tabStock Entry Detail`.`qty`", + ], filters=[ ["Stock Entry", "is_return", "=", 1], ["Stock Entry Detail", "docstatus", "=", 1], @@ -59,12 +63,14 @@ def get_returned_materials(work_orders): ) for d in raw_materials: - raw_materials_qty[d.item_code] += d.qty + key = (d.work_order, d.item_code) + raw_materials_qty[key] += d.qty for row in work_orders: row.returned_qty = 0.0 - if raw_materials_qty.get(row.raw_material_item_code): - row.returned_qty = raw_materials_qty.get(row.raw_material_item_code) + key = (row.parent, row.raw_material_item_code) + if raw_materials_qty.get(key): + row.returned_qty = raw_materials_qty.get(key) def get_fields(): diff --git a/erpnext/patches/v15_0/update_asset_status_to_work_in_progress.py b/erpnext/patches/v15_0/update_asset_status_to_work_in_progress.py new file mode 100644 index 000000000000..447492989a83 --- /dev/null +++ b/erpnext/patches/v15_0/update_asset_status_to_work_in_progress.py @@ -0,0 +1,9 @@ +import frappe +def execute(): + Asset = frappe.qb.DocType("Asset") + query = ( + frappe.qb.update(Asset) + .set(Asset.status, "Work In Progress") + .where((Asset.docstatus == 0) & (Asset.is_composite_asset == 1)) + ) + query.run() \ No newline at end of file diff --git a/erpnext/public/js/bank_reconciliation_tool/data_table_manager.js b/erpnext/public/js/bank_reconciliation_tool/data_table_manager.js index 4aa087e99cfe..ad5fd528cda9 100644 --- a/erpnext/public/js/bank_reconciliation_tool/data_table_manager.js +++ b/erpnext/public/js/bank_reconciliation_tool/data_table_manager.js @@ -16,7 +16,7 @@ erpnext.accounts.bank_reconciliation.DataTableManager = class DataTableManager { } make_dt() { - var me = this; + const me = this; frappe.call({ method: "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_bank_transactions", args: { @@ -193,6 +193,7 @@ erpnext.accounts.bank_reconciliation.DataTableManager = class DataTableManager { args: { bank_account: this.bank_account, till_date: this.bank_statement_to_date, + company: this.company, }, callback: (response) => (this.cleared_balance = response.message), }); diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index aa7bf029d974..acd2d3a42c16 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -303,11 +303,10 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } const me = this; - if (!this.frm.is_new() && this.frm.doc.docstatus === 0 && frappe.model.can_create("Quality Inspection")) { + if (!this.frm.is_new() && this.frm.doc.docstatus === 0 && frappe.model.can_create("Quality Inspection") && this.frm.doc.update_stock) { this.frm.add_custom_button(__("Quality Inspection(s)"), () => { me.make_quality_inspection(); }, __("Create")); - this.frm.page.set_inner_btn_group_as_primary(__('Create')); } const inspection_type = ["Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"].includes(this.frm.doc.doctype) diff --git a/erpnext/selling/doctype/quotation/test_quotation.py b/erpnext/selling/doctype/quotation/test_quotation.py index 9e35ba7500c0..97fd960deb91 100644 --- a/erpnext/selling/doctype/quotation/test_quotation.py +++ b/erpnext/selling/doctype/quotation/test_quotation.py @@ -1039,6 +1039,18 @@ def test_quotation_to_sales_invoice_to_with_2_delivery_note_TC_S_083(self): dn_2.submit() self.stock_check(voucher=dn_2.name,qty=-2) self.assertEqual(dn_2.status, "Completed") + + def test_quotation_to_material_request_TC_S_084(self): + from erpnext.selling.doctype.sales_order.sales_order import make_material_request + quotation = self.create_and_submit_quotation("_Test Item Home Desktop 100", 4, 5000, "Stores - _TC") + sales_order = self.create_and_submit_sales_order(quotation.name, add_days(nowdate(), 5)) + quotation.reload() + self.assertEqual(quotation.status, "Ordered") + mr = make_material_request(sales_order.name) + mr.save() + mr.submit() + mr.reload() + self.assertEqual(mr.status, "Pending") def stock_check(self,voucher,qty): stock_entries = frappe.get_all( diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index 9a12cce39173..73caacb85f82 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -2598,640 +2598,496 @@ def test_sales_order_for_service_item_TC_S_010(self): self.assertEqual(si_acc_debit, 5000) def test_sales_order_full_payment_with_gst_TC_S_011(self): - company = frappe.get_all("Company", {"name": "_Test Indian Registered Company"}, ["gstin", "gst_category"]) - customer = frappe.get_all("Customer", {"name": "_Test Registered Customer"}, ["gstin", "gst_category"]) - company_add = frappe.get_all("Address", {"name": "_Test Indian Registered Company-Billing"}, ["name", "gstin", "gst_category"]) - customer_add = frappe.get_all("Address", {"name": "_Test Registered Customer-Billing"}, ["name", "gstin", "gst_category"]) - - if company[0].get("gst_category") == "Registered Regular" and customer[0].get("gst_category") == "Registered Regular" and customer[0].get("gstin") and customer[0].get("gstin"): - if company_add[0].get("gst_category") == "Registered Regular" and customer_add[0].get("gst_category") == "Registered Regular" and company_add[0].get("gstin") and customer_add[0].get("gstin"): - so = make_sales_order(company='_Test Indian Registered Company', customer='_Test Registered Customer', warehouse='Stores - _TIRC' ,cost_center='Main - _TIRC', selling_price_list='Standard Selling', item_code='_Test Item', qty=1, rate=5000, do_not_save=True) - so.tax_category = 'In-State' - so.taxes_and_charges = 'Output GST In-state - _TIRC' - so.customer_address = customer_add[0].get("name") - so.billing_address_gstin = customer_add[0].get("gstin") - so.company_address = company_add[0].get("name") - so.company_gstin = company_add[0].get("gstin") - so.save() - so.submit() + so = self.create_and_submit_sales_order_with_gst("_Test Item", qty=1, rate=5000) - self.assertEqual(so.status, "To Deliver and Bill", "Sales Order not created") - self.assertEqual(so.grand_total, so.total + so.total_taxes_and_charges) + dn = make_delivery_note(so.name) + dn.save() + dn.submit() - dn = make_delivery_note(so.name) - dn.save() - dn.submit() + self.assertEqual(dn.status, "To Bill", "Delivery Note not created") - self.assertEqual(dn.status, "To Bill", "Delivery Note not created") + qty_change = frappe.get_all('Stock Ledger Entry', {'item_code': '_Test Item', 'voucher_no': dn.name, 'warehouse': 'Stores - _TIRC'}, ['actual_qty', 'valuation_rate']) + self.assertEqual(qty_change[0].get("actual_qty"), -1) - qty_change = frappe.get_all('Stock Ledger Entry', {'item_code': '_Test Item', 'voucher_no': dn.name, 'warehouse': 'Stores - _TIRC'}, ['actual_qty', 'valuation_rate']) - self.assertEqual(qty_change[0].get("actual_qty"), -1) - - dn_acc_credit1 = frappe.db.get_value('GL Entry', {'voucher_type': 'Delivery Note', 'voucher_no': dn.name, 'account': 'Stock In Hand - _TIRC'}, 'credit') - self.assertEqual(dn_acc_credit1, qty_change[0].get("valuation_rate") * 1) + dn_acc_credit1 = frappe.db.get_value('GL Entry', {'voucher_type': 'Delivery Note', 'voucher_no': dn.name, 'account': 'Stock In Hand - _TIRC'}, 'credit') + self.assertEqual(dn_acc_credit1, qty_change[0].get("valuation_rate") * 1) - dn_acc_debit1 = frappe.db.get_value('GL Entry', {'voucher_type': 'Delivery Note', 'voucher_no': dn.name, 'account': 'Cost of Goods Sold - _TIRC'}, 'debit') - self.assertEqual(dn_acc_debit1, qty_change[0].get("valuation_rate") * 1) - - from erpnext.stock.doctype.delivery_note.delivery_note import (make_sales_invoice) + dn_acc_debit1 = frappe.db.get_value('GL Entry', {'voucher_type': 'Delivery Note', 'voucher_no': dn.name, 'account': 'Cost of Goods Sold - _TIRC'}, 'debit') + self.assertEqual(dn_acc_debit1, qty_change[0].get("valuation_rate") * 1) - si = make_sales_invoice(dn.name) - si.insert() - si.submit() + from erpnext.stock.doctype.delivery_note.delivery_note import (make_sales_invoice) - self.assertEqual(si.status, "Unpaid", "Sales Invoice not created") - - voucher_params_si = { - 'voucher_type': 'Sales Invoice', - 'voucher_no': si.name - } - gl_accounts = { - 'Sales - _TIRC': 'credit', - 'Debtors - _TIRC': 'debit', - 'Output Tax SGST - _TIRC': 'credit', - 'Output Tax CGST - _TIRC': 'credit' - } - gl_entries = { - account: frappe.db.get_value('GL Entry', {**voucher_params_si, 'account': account}, field) - for account, field in gl_accounts.items() - } + si = make_sales_invoice(dn.name) + si.insert() + si.submit() - self.assertEqual(gl_entries['Sales - _TIRC'], 5000) - self.assertEqual(gl_entries['Debtors - _TIRC'], 5900) - self.assertEqual(gl_entries['Output Tax SGST - _TIRC'], 450) - self.assertEqual(gl_entries['Output Tax CGST - _TIRC'], 450) - - dn.reload() - self.assertEqual(dn.status, "Completed") + self.assertEqual(si.status, "Unpaid", "Sales Invoice not created") + + voucher_params_si = { + 'voucher_type': 'Sales Invoice', + 'voucher_no': si.name + } + gl_accounts = { + 'Sales - _TIRC': 'credit', + 'Debtors - _TIRC': 'debit', + 'Output Tax SGST - _TIRC': 'credit', + 'Output Tax CGST - _TIRC': 'credit' + } + gl_entries = { + account: frappe.db.get_value('GL Entry', {**voucher_params_si, 'account': account}, field) + for account, field in gl_accounts.items() + } + + self.assertEqual(gl_entries['Sales - _TIRC'], 5000) + self.assertEqual(gl_entries['Debtors - _TIRC'], 5900) + self.assertEqual(gl_entries['Output Tax SGST - _TIRC'], 450) + self.assertEqual(gl_entries['Output Tax CGST - _TIRC'], 450) + + dn.reload() + self.assertEqual(dn.status, "Completed") def test_sales_order_partial_payment_with_gst_TC_S_012(self): - company = frappe.get_all("Company", {"name": "_Test Indian Registered Company"}, ["gstin", "gst_category"]) - customer = frappe.get_all("Customer", {"name": "_Test Registered Customer"}, ["gstin", "gst_category"]) - company_add = frappe.get_all("Address", {"name": "_Test Indian Registered Company-Billing"}, ["name", "gstin", "gst_category"]) - customer_add = frappe.get_all("Address", {"name": "_Test Registered Customer-Billing"}, ["name", "gstin", "gst_category"]) - - if company[0].get("gst_category") == "Registered Regular" and customer[0].get("gst_category") == "Registered Regular" and customer[0].get("gstin") and customer[0].get("gstin"): - if company_add[0].get("gst_category") == "Registered Regular" and customer_add[0].get("gst_category") == "Registered Regular" and company_add[0].get("gstin") and customer_add[0].get("gstin"): - so = make_sales_order(company='_Test Indian Registered Company', customer='_Test Registered Customer', warehouse='Stores - _TIRC' ,cost_center='Main - _TIRC', selling_price_list='Standard Selling', item_code='_Test Item', qty=4, rate=5000, do_not_save=True) - so.tax_category = 'In-State' - so.taxes_and_charges = 'Output GST In-state - _TIRC' - so.customer_address = customer_add[0].get("name") - so.billing_address_gstin = customer_add[0].get("gstin") - so.company_address = company_add[0].get("name") - so.company_gstin = company_add[0].get("gstin") - so.save() - so.submit() + so = self.create_and_submit_sales_order_with_gst("_Test Item", qty=4, rate=5000) - self.assertEqual(so.status, "To Deliver and Bill", "Sales Order not created") - self.assertEqual(so.grand_total, so.total + so.total_taxes_and_charges) + dn1 = make_delivery_note(so.name) + dn1.get("items")[0].qty = 2 + dn1.save() + dn1.submit() - dn1 = make_delivery_note(so.name) - dn1.get("items")[0].qty = 2 - dn1.save() - dn1.submit() + self.assertEqual(dn1.status, "To Bill", "Delivery Note not created") - self.assertEqual(dn1.status, "To Bill", "Delivery Note not created") + qty_change1 = frappe.get_all('Stock Ledger Entry', {'item_code': '_Test Item', 'voucher_no': dn1.name, 'warehouse': 'Stores - _TIRC'}, ['actual_qty', 'valuation_rate']) + self.assertEqual(qty_change1[0].get("actual_qty"), -2) - qty_change1 = frappe.get_all('Stock Ledger Entry', {'item_code': '_Test Item', 'voucher_no': dn1.name, 'warehouse': 'Stores - _TIRC'}, ['actual_qty', 'valuation_rate']) - self.assertEqual(qty_change1[0].get("actual_qty"), -2) - - dn_acc_credit1 = frappe.db.get_value('GL Entry', {'voucher_type': 'Delivery Note', 'voucher_no': dn1.name, 'account': 'Stock In Hand - _TIRC'}, 'credit') - self.assertEqual(dn_acc_credit1, qty_change1[0].get("valuation_rate") * 2) + dn_acc_credit1 = frappe.db.get_value('GL Entry', {'voucher_type': 'Delivery Note', 'voucher_no': dn1.name, 'account': 'Stock In Hand - _TIRC'}, 'credit') + self.assertEqual(dn_acc_credit1, qty_change1[0].get("valuation_rate") * 2) - dn_acc_debit1 = frappe.db.get_value('GL Entry', {'voucher_type': 'Delivery Note', 'voucher_no': dn1.name, 'account': 'Cost of Goods Sold - _TIRC'}, 'debit') - self.assertEqual(dn_acc_debit1, qty_change1[0].get("valuation_rate") * 2) - - from erpnext.stock.doctype.delivery_note.delivery_note import (make_sales_invoice) + dn_acc_debit1 = frappe.db.get_value('GL Entry', {'voucher_type': 'Delivery Note', 'voucher_no': dn1.name, 'account': 'Cost of Goods Sold - _TIRC'}, 'debit') + self.assertEqual(dn_acc_debit1, qty_change1[0].get("valuation_rate") * 2) - si1 = make_sales_invoice(dn1.name) - si1.insert() - si1.submit() + from erpnext.stock.doctype.delivery_note.delivery_note import (make_sales_invoice) - self.assertEqual(si1.status, "Unpaid", "Sales Invoice not created") + si1 = make_sales_invoice(dn1.name) + si1.insert() + si1.submit() - voucher_params_si1 = { - 'voucher_type': 'Sales Invoice', - 'voucher_no': si1.name - } - gl_accounts = { - 'Sales - _TIRC': 'credit', - 'Debtors - _TIRC': 'debit', - 'Output Tax SGST - _TIRC': 'credit', - 'Output Tax CGST - _TIRC': 'credit' - } - gl_entries_si1 = { - account: frappe.db.get_value('GL Entry', {**voucher_params_si1, 'account': account}, field) - for account, field in gl_accounts.items() - } - self.assertEqual(gl_entries_si1['Sales - _TIRC'], 10000) - self.assertEqual(gl_entries_si1['Debtors - _TIRC'], 11800) - self.assertEqual(gl_entries_si1['Output Tax SGST - _TIRC'], 900) - self.assertEqual(gl_entries_si1['Output Tax CGST - _TIRC'], 900) - - dn1.reload() - self.assertEqual(dn1.status, "Completed") - - dn2 = make_delivery_note(so.name) - dn2.save() - dn2.submit() + self.assertEqual(si1.status, "Unpaid", "Sales Invoice not created") - qty_change2 = frappe.get_all('Stock Ledger Entry', {'item_code': '_Test Item', 'voucher_no': dn2.name, 'warehouse': 'Stores - _TIRC'}, ['actual_qty', 'valuation_rate']) - self.assertEqual(qty_change2[0].get("actual_qty"), -2) - - dn_acc_credit2 = frappe.db.get_value('GL Entry', {'voucher_type': 'Delivery Note', 'voucher_no': dn2.name, 'account': 'Stock In Hand - _TIRC'}, 'credit') - self.assertEqual(dn_acc_credit2, qty_change2[0].get("valuation_rate") * 2) + voucher_params_si1 = { + 'voucher_type': 'Sales Invoice', + 'voucher_no': si1.name + } + gl_accounts = { + 'Sales - _TIRC': 'credit', + 'Debtors - _TIRC': 'debit', + 'Output Tax SGST - _TIRC': 'credit', + 'Output Tax CGST - _TIRC': 'credit' + } + gl_entries_si1 = { + account: frappe.db.get_value('GL Entry', {**voucher_params_si1, 'account': account}, field) + for account, field in gl_accounts.items() + } + self.assertEqual(gl_entries_si1['Sales - _TIRC'], 10000) + self.assertEqual(gl_entries_si1['Debtors - _TIRC'], 11800) + self.assertEqual(gl_entries_si1['Output Tax SGST - _TIRC'], 900) + self.assertEqual(gl_entries_si1['Output Tax CGST - _TIRC'], 900) + + dn1.reload() + self.assertEqual(dn1.status, "Completed") + + dn2 = make_delivery_note(so.name) + dn2.save() + dn2.submit() - dn_acc_debit2 = frappe.db.get_value('GL Entry', {'voucher_type': 'Delivery Note', 'voucher_no': dn2.name, 'account': 'Cost of Goods Sold - _TIRC'}, 'debit') - self.assertEqual(dn_acc_debit2, qty_change2[0].get("valuation_rate") * 2) + qty_change2 = frappe.get_all('Stock Ledger Entry', {'item_code': '_Test Item', 'voucher_no': dn2.name, 'warehouse': 'Stores - _TIRC'}, ['actual_qty', 'valuation_rate']) + self.assertEqual(qty_change2[0].get("actual_qty"), -2) - si2 = make_sales_invoice(dn2.name) - si2.insert() - si2.submit() + dn_acc_credit2 = frappe.db.get_value('GL Entry', {'voucher_type': 'Delivery Note', 'voucher_no': dn2.name, 'account': 'Stock In Hand - _TIRC'}, 'credit') + self.assertEqual(dn_acc_credit2, qty_change2[0].get("valuation_rate") * 2) - self.assertEqual(si2.status, "Unpaid", "Sales Invoice not created") + dn_acc_debit2 = frappe.db.get_value('GL Entry', {'voucher_type': 'Delivery Note', 'voucher_no': dn2.name, 'account': 'Cost of Goods Sold - _TIRC'}, 'debit') + self.assertEqual(dn_acc_debit2, qty_change2[0].get("valuation_rate") * 2) - voucher_params_si2 = { - 'voucher_type': 'Sales Invoice', - 'voucher_no': si2.name - } - gl_accounts = { - 'Sales - _TIRC': 'credit', - 'Debtors - _TIRC': 'debit', - 'Output Tax SGST - _TIRC': 'credit', - 'Output Tax CGST - _TIRC': 'credit' - } - gl_entries_si2 = { - account: frappe.db.get_value('GL Entry', {**voucher_params_si2, 'account': account}, field) - for account, field in gl_accounts.items() - } - self.assertEqual(gl_entries_si2['Sales - _TIRC'], 10000) - self.assertEqual(gl_entries_si2['Debtors - _TIRC'], 11800) - self.assertEqual(gl_entries_si2['Output Tax SGST - _TIRC'], 900) - self.assertEqual(gl_entries_si2['Output Tax CGST - _TIRC'], 900) - - dn2.reload() - self.assertEqual(dn2.status, "Completed") + si2 = make_sales_invoice(dn2.name) + si2.insert() + si2.submit() + + self.assertEqual(si2.status, "Unpaid", "Sales Invoice not created") + + voucher_params_si2 = { + 'voucher_type': 'Sales Invoice', + 'voucher_no': si2.name + } + gl_accounts = { + 'Sales - _TIRC': 'credit', + 'Debtors - _TIRC': 'debit', + 'Output Tax SGST - _TIRC': 'credit', + 'Output Tax CGST - _TIRC': 'credit' + } + gl_entries_si2 = { + account: frappe.db.get_value('GL Entry', {**voucher_params_si2, 'account': account}, field) + for account, field in gl_accounts.items() + } + self.assertEqual(gl_entries_si2['Sales - _TIRC'], 10000) + self.assertEqual(gl_entries_si2['Debtors - _TIRC'], 11800) + self.assertEqual(gl_entries_si2['Output Tax SGST - _TIRC'], 900) + self.assertEqual(gl_entries_si2['Output Tax CGST - _TIRC'], 900) + + dn2.reload() + self.assertEqual(dn2.status, "Completed") def test_sales_order_partial_sales_invoice_with_gst_TC_S_013(self): - company = frappe.get_all("Company", {"name": "_Test Indian Registered Company"}, ["gstin", "gst_category"]) - customer = frappe.get_all("Customer", {"name": "_Test Registered Customer"}, ["gstin", "gst_category"]) - company_add = frappe.get_all("Address", {"name": "_Test Indian Registered Company-Billing"}, ["name", "gstin", "gst_category"]) - customer_add = frappe.get_all("Address", {"name": "_Test Registered Customer-Billing"}, ["name", "gstin", "gst_category"]) - - if company[0].get("gst_category") == "Registered Regular" and customer[0].get("gst_category") == "Registered Regular" and customer[0].get("gstin") and customer[0].get("gstin"): - if company_add[0].get("gst_category") == "Registered Regular" and customer_add[0].get("gst_category") == "Registered Regular" and company_add[0].get("gstin") and customer_add[0].get("gstin"): - so = make_sales_order(company='_Test Indian Registered Company', customer='_Test Registered Customer', warehouse='Stores - _TIRC' ,cost_center='Main - _TIRC', selling_price_list='Standard Selling', item_code='_Test Item', qty=4, rate=5000, do_not_save=True) - so.tax_category = 'In-State' - so.taxes_and_charges = 'Output GST In-state - _TIRC' - so.customer_address = customer_add[0].get("name") - so.billing_address_gstin = customer_add[0].get("gstin") - so.company_address = company_add[0].get("name") - so.company_gstin = company_add[0].get("gstin") - so.save() - so.submit() + so = self.create_and_submit_sales_order_with_gst("_Test Item", qty=4, rate=5000) + + dn = make_delivery_note(so.name) + dn.save() + dn.submit() - self.assertEqual(so.status, "To Deliver and Bill", "Sales Order not created") - self.assertEqual(so.grand_total, so.total + so.total_taxes_and_charges) + self.assertEqual(dn.status, "To Bill", "Delivery Note not created") - dn = make_delivery_note(so.name) - dn.save() - dn.submit() + qty_change = frappe.get_all('Stock Ledger Entry', {'item_code': '_Test Item', 'voucher_no': dn.name, 'warehouse': 'Stores - _TIRC'}, ['actual_qty', 'valuation_rate']) + self.assertEqual(qty_change[0].get("actual_qty"), -4) - self.assertEqual(dn.status, "To Bill", "Delivery Note not created") + dn_acc_credit1 = frappe.db.get_value('GL Entry', {'voucher_type': 'Delivery Note', 'voucher_no': dn.name, 'account': 'Stock In Hand - _TIRC'}, 'credit') + self.assertEqual(dn_acc_credit1, qty_change[0].get("valuation_rate") * 4) - qty_change = frappe.get_all('Stock Ledger Entry', {'item_code': '_Test Item', 'voucher_no': dn.name, 'warehouse': 'Stores - _TIRC'}, ['actual_qty', 'valuation_rate']) - self.assertEqual(qty_change[0].get("actual_qty"), -4) - - dn_acc_credit1 = frappe.db.get_value('GL Entry', {'voucher_type': 'Delivery Note', 'voucher_no': dn.name, 'account': 'Stock In Hand - _TIRC'}, 'credit') - self.assertEqual(dn_acc_credit1, qty_change[0].get("valuation_rate") * 4) + dn_acc_debit1 = frappe.db.get_value('GL Entry', {'voucher_type': 'Delivery Note', 'voucher_no': dn.name, 'account': 'Cost of Goods Sold - _TIRC'}, 'debit') + self.assertEqual(dn_acc_debit1, qty_change[0].get("valuation_rate") * 4) - dn_acc_debit1 = frappe.db.get_value('GL Entry', {'voucher_type': 'Delivery Note', 'voucher_no': dn.name, 'account': 'Cost of Goods Sold - _TIRC'}, 'debit') - self.assertEqual(dn_acc_debit1, qty_change[0].get("valuation_rate") * 4) - - from erpnext.stock.doctype.delivery_note.delivery_note import (make_sales_invoice) + from erpnext.stock.doctype.delivery_note.delivery_note import (make_sales_invoice) - si1 = make_sales_invoice(dn.name) - si1.get("items")[0].qty = 2 - si1.insert() - si1.submit() + si1 = make_sales_invoice(dn.name) + si1.get("items")[0].qty = 2 + si1.insert() + si1.submit() - self.assertEqual(si1.status, "Unpaid", "Sales Invoice not created") + self.assertEqual(si1.status, "Unpaid", "Sales Invoice not created") - voucher_params_si1 = { - 'voucher_type': 'Sales Invoice', - 'voucher_no': si1.name - } - gl_accounts = { - 'Sales - _TIRC': 'credit', - 'Debtors - _TIRC': 'debit', - 'Output Tax SGST - _TIRC': 'credit', - 'Output Tax CGST - _TIRC': 'credit' - } - gl_entries_si1 = { - account: frappe.db.get_value('GL Entry', {**voucher_params_si1, 'account': account}, field) - for account, field in gl_accounts.items() - } - self.assertEqual(gl_entries_si1['Sales - _TIRC'], 10000) - self.assertEqual(gl_entries_si1['Debtors - _TIRC'], 11800) - self.assertEqual(gl_entries_si1['Output Tax SGST - _TIRC'], 900) - self.assertEqual(gl_entries_si1['Output Tax CGST - _TIRC'], 900) - - si2 = make_sales_invoice(dn.name) - si2.insert() - si2.submit() + voucher_params_si1 = { + 'voucher_type': 'Sales Invoice', + 'voucher_no': si1.name + } + gl_accounts = { + 'Sales - _TIRC': 'credit', + 'Debtors - _TIRC': 'debit', + 'Output Tax SGST - _TIRC': 'credit', + 'Output Tax CGST - _TIRC': 'credit' + } + gl_entries_si1 = { + account: frappe.db.get_value('GL Entry', {**voucher_params_si1, 'account': account}, field) + for account, field in gl_accounts.items() + } + self.assertEqual(gl_entries_si1['Sales - _TIRC'], 10000) + self.assertEqual(gl_entries_si1['Debtors - _TIRC'], 11800) + self.assertEqual(gl_entries_si1['Output Tax SGST - _TIRC'], 900) + self.assertEqual(gl_entries_si1['Output Tax CGST - _TIRC'], 900) - self.assertEqual(si2.status, "Unpaid", "Sales Invoice not created") + si2 = make_sales_invoice(dn.name) + si2.insert() + si2.submit() - voucher_params_si2 = { - 'voucher_type': 'Sales Invoice', - 'voucher_no': si2.name - } - gl_accounts = { - 'Sales - _TIRC': 'credit', - 'Debtors - _TIRC': 'debit', - 'Output Tax SGST - _TIRC': 'credit', - 'Output Tax CGST - _TIRC': 'credit' - } - gl_entries_si2 = { - account: frappe.db.get_value('GL Entry', {**voucher_params_si2, 'account': account}, field) - for account, field in gl_accounts.items() - } - self.assertEqual(gl_entries_si2['Sales - _TIRC'], 10000) - self.assertEqual(gl_entries_si2['Debtors - _TIRC'], 11800) - self.assertEqual(gl_entries_si2['Output Tax SGST - _TIRC'], 900) - self.assertEqual(gl_entries_si2['Output Tax CGST - _TIRC'], 900) - - dn.reload() - self.assertEqual(dn.status, "Completed") + self.assertEqual(si2.status, "Unpaid", "Sales Invoice not created") + + voucher_params_si2 = { + 'voucher_type': 'Sales Invoice', + 'voucher_no': si2.name + } + gl_accounts = { + 'Sales - _TIRC': 'credit', + 'Debtors - _TIRC': 'debit', + 'Output Tax SGST - _TIRC': 'credit', + 'Output Tax CGST - _TIRC': 'credit' + } + gl_entries_si2 = { + account: frappe.db.get_value('GL Entry', {**voucher_params_si2, 'account': account}, field) + for account, field in gl_accounts.items() + } + self.assertEqual(gl_entries_si2['Sales - _TIRC'], 10000) + self.assertEqual(gl_entries_si2['Debtors - _TIRC'], 11800) + self.assertEqual(gl_entries_si2['Output Tax SGST - _TIRC'], 900) + self.assertEqual(gl_entries_si2['Output Tax CGST - _TIRC'], 900) + + dn.reload() + self.assertEqual(dn.status, "Completed") def test_sales_order_create_dn_via_si_with_gst_TC_S_014(self): - company = frappe.get_all("Company", {"name": "_Test Indian Registered Company"}, ["gstin", "gst_category"]) - customer = frappe.get_all("Customer", {"name": "_Test Registered Customer"}, ["gstin", "gst_category"]) - company_add = frappe.get_all("Address", {"name": "_Test Indian Registered Company-Billing"}, ["name", "gstin", "gst_category"]) - customer_add = frappe.get_all("Address", {"name": "_Test Registered Customer-Billing"}, ["name", "gstin", "gst_category"]) - - if company[0].get("gst_category") == "Registered Regular" and customer[0].get("gst_category") == "Registered Regular" and customer[0].get("gstin") and customer[0].get("gstin"): - if company_add[0].get("gst_category") == "Registered Regular" and customer_add[0].get("gst_category") == "Registered Regular" and company_add[0].get("gstin") and customer_add[0].get("gstin"): - so = make_sales_order(company='_Test Indian Registered Company', customer='_Test Registered Customer', warehouse='Stores - _TIRC' ,cost_center='Main - _TIRC', selling_price_list='Standard Selling', item_code='_Test Item', qty=4, rate=5000, do_not_save=True) - so.tax_category = 'In-State' - so.taxes_and_charges = 'Output GST In-state - _TIRC' - so.customer_address = customer_add[0].get("name") - so.billing_address_gstin = customer_add[0].get("gstin") - so.company_address = company_add[0].get("name") - so.company_gstin = company_add[0].get("gstin") - so.save() - so.submit() - - self.assertEqual(so.status, "To Deliver and Bill", "Sales Order not created") - self.assertEqual(so.grand_total, so.total + so.total_taxes_and_charges) + so = self.create_and_submit_sales_order_with_gst("_Test Item", qty=4, rate=5000) - si = make_sales_invoice(so.name) - si.save() - si.submit() + si = make_sales_invoice(so.name) + si.save() + si.submit() - self.assertEqual(si.status, "Unpaid", "Sales Invoice not created") - - si_acc_credit = frappe.db.get_value('GL Entry', {'voucher_type': 'Sales Invoice', 'voucher_no': si.name, 'account': 'Sales - _TIRC'}, 'credit') - self.assertEqual(si_acc_credit, 20000) + self.assertEqual(si.status, "Unpaid", "Sales Invoice not created") - si_acc_debit = frappe.db.get_value('GL Entry', {'voucher_type': 'Sales Invoice', 'voucher_no': si.name, 'account': 'Debtors - _TIRC'}, 'debit') - self.assertEqual(si_acc_debit, 23600) - - si_acc_credit_gst = frappe.db.get_value('GL Entry', {'voucher_type': 'Sales Invoice', 'voucher_no': si.name, 'account': 'Output Tax SGST - _TIRC'}, 'credit') - self.assertEqual(si_acc_credit_gst, 1800) + si_acc_credit = frappe.db.get_value('GL Entry', {'voucher_type': 'Sales Invoice', 'voucher_no': si.name, 'account': 'Sales - _TIRC'}, 'credit') + self.assertEqual(si_acc_credit, 20000) - si_acc_debit_gst = frappe.db.get_value('GL Entry', {'voucher_type': 'Sales Invoice', 'voucher_no': si.name, 'account': 'Output Tax CGST - _TIRC'}, 'credit') - self.assertEqual(si_acc_debit_gst, 1800) - - from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_delivery_note - dn = make_delivery_note(si.name) - dn.save() - dn.submit() + si_acc_debit = frappe.db.get_value('GL Entry', {'voucher_type': 'Sales Invoice', 'voucher_no': si.name, 'account': 'Debtors - _TIRC'}, 'debit') + self.assertEqual(si_acc_debit, 23600) + + si_acc_credit_gst = frappe.db.get_value('GL Entry', {'voucher_type': 'Sales Invoice', 'voucher_no': si.name, 'account': 'Output Tax SGST - _TIRC'}, 'credit') + self.assertEqual(si_acc_credit_gst, 1800) - self.assertEqual(dn.status, "Completed", "Delivery Note not created") + si_acc_debit_gst = frappe.db.get_value('GL Entry', {'voucher_type': 'Sales Invoice', 'voucher_no': si.name, 'account': 'Output Tax CGST - _TIRC'}, 'credit') + self.assertEqual(si_acc_debit_gst, 1800) - qty_change = frappe.get_all('Stock Ledger Entry', {'item_code': '_Test Item', 'voucher_no': dn.name, 'warehouse': 'Stores - _TIRC'}, ['actual_qty', 'valuation_rate']) - self.assertEqual(qty_change[0].get("actual_qty"), -4) + from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_delivery_note + dn = make_delivery_note(si.name) + dn.save() + dn.submit() - dn_acc_credit = frappe.db.get_value('GL Entry', {'voucher_type': 'Delivery Note', 'voucher_no': dn.name, 'account': 'Stock In Hand - _TIRC'}, 'credit') - self.assertEqual(dn_acc_credit, qty_change[0].get("valuation_rate") * 4) + self.assertEqual(dn.status, "Completed", "Delivery Note not created") - dn_acc_debit = frappe.db.get_value('GL Entry', {'voucher_type': 'Delivery Note', 'voucher_no': dn.name, 'account': 'Cost of Goods Sold - _TIRC'}, 'debit') - self.assertEqual(dn_acc_debit, qty_change[0].get("valuation_rate") * 4) + qty_change = frappe.get_all('Stock Ledger Entry', {'item_code': '_Test Item', 'voucher_no': dn.name, 'warehouse': 'Stores - _TIRC'}, ['actual_qty', 'valuation_rate']) + self.assertEqual(qty_change[0].get("actual_qty"), -4) + + dn_acc_credit = frappe.db.get_value('GL Entry', {'voucher_type': 'Delivery Note', 'voucher_no': dn.name, 'account': 'Stock In Hand - _TIRC'}, 'credit') + self.assertEqual(dn_acc_credit, qty_change[0].get("valuation_rate") * 4) + + dn_acc_debit = frappe.db.get_value('GL Entry', {'voucher_type': 'Delivery Note', 'voucher_no': dn.name, 'account': 'Cost of Goods Sold - _TIRC'}, 'debit') + self.assertEqual(dn_acc_debit, qty_change[0].get("valuation_rate") * 4) def test_sales_order_create_partial_dn_via_si_with_gst_TC_S_015(self): - company = frappe.get_all("Company", {"name": "_Test Indian Registered Company"}, ["gstin", "gst_category"]) - customer = frappe.get_all("Customer", {"name": "_Test Registered Customer"}, ["gstin", "gst_category"]) - company_add = frappe.get_all("Address", {"name": "_Test Indian Registered Company-Billing"}, ["name", "gstin", "gst_category"]) - customer_add = frappe.get_all("Address", {"name": "_Test Registered Customer-Billing"}, ["name", "gstin", "gst_category"]) - - if company[0].get("gst_category") == "Registered Regular" and customer[0].get("gst_category") == "Registered Regular" and customer[0].get("gstin") and customer[0].get("gstin"): - if company_add[0].get("gst_category") == "Registered Regular" and customer_add[0].get("gst_category") == "Registered Regular" and company_add[0].get("gstin") and customer_add[0].get("gstin"): - so = make_sales_order(company='_Test Indian Registered Company', customer='_Test Registered Customer', warehouse='Stores - _TIRC' ,cost_center='Main - _TIRC', selling_price_list='Standard Selling', item_code='_Test Item', qty=4, rate=5000, do_not_save=True) - so.tax_category = 'In-State' - so.taxes_and_charges = 'Output GST In-state - _TIRC' - so.customer_address = customer_add[0].get("name") - so.billing_address_gstin = customer_add[0].get("gstin") - so.company_address = company_add[0].get("name") - so.company_gstin = company_add[0].get("gstin") - so.save() - so.submit() + so = self.create_and_submit_sales_order_with_gst("_Test Item", qty=4, rate=5000) + + si = make_sales_invoice(so.name) + si.save() + si.submit() - self.assertEqual(so.status, "To Deliver and Bill", "Sales Order not created") - self.assertEqual(so.grand_total, so.total + so.total_taxes_and_charges) - - si = make_sales_invoice(so.name) - si.save() - si.submit() + self.assertEqual(si.status, "Unpaid", "Sales Invoice not created") - self.assertEqual(si.status, "Unpaid", "Sales Invoice not created") - - voucher_params_si = { - 'voucher_type': 'Sales Invoice', - 'voucher_no': si.name - } - gl_accounts = { - 'Sales - _TIRC': 'credit', - 'Debtors - _TIRC': 'debit', - 'Output Tax SGST - _TIRC': 'credit', - 'Output Tax CGST - _TIRC': 'credit' - } - gl_entries = { - account: frappe.db.get_value('GL Entry', {**voucher_params_si, 'account': account}, field) - for account, field in gl_accounts.items() - } - self.assertEqual(gl_entries['Sales - _TIRC'], 20000) - self.assertEqual(gl_entries['Debtors - _TIRC'], 23600) - self.assertEqual(gl_entries['Output Tax SGST - _TIRC'], 1800) - self.assertEqual(gl_entries['Output Tax CGST - _TIRC'], 1800) - - from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_delivery_note - dn1 = make_delivery_note(si.name) - dn1.get("items")[0].qty = 2 - dn1.save() - dn1.submit() + voucher_params_si = { + 'voucher_type': 'Sales Invoice', + 'voucher_no': si.name + } + gl_accounts = { + 'Sales - _TIRC': 'credit', + 'Debtors - _TIRC': 'debit', + 'Output Tax SGST - _TIRC': 'credit', + 'Output Tax CGST - _TIRC': 'credit' + } + gl_entries = { + account: frappe.db.get_value('GL Entry', {**voucher_params_si, 'account': account}, field) + for account, field in gl_accounts.items() + } + self.assertEqual(gl_entries['Sales - _TIRC'], 20000) + self.assertEqual(gl_entries['Debtors - _TIRC'], 23600) + self.assertEqual(gl_entries['Output Tax SGST - _TIRC'], 1800) + self.assertEqual(gl_entries['Output Tax CGST - _TIRC'], 1800) - self.assertEqual(dn1.status, "Completed", "Delivery Note not created") + from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_delivery_note + dn1 = make_delivery_note(si.name) + dn1.get("items")[0].qty = 2 + dn1.save() + dn1.submit() - qty_change1 = frappe.get_all( - 'Stock Ledger Entry', - {'item_code': '_Test Item', 'voucher_no': dn1.name, 'warehouse': 'Stores - _TIRC'}, - ['actual_qty', 'valuation_rate'] - ) - actual_qty = qty_change1[0].get("actual_qty") - valuation_rate = qty_change1[0].get("valuation_rate") + self.assertEqual(dn1.status, "Completed", "Delivery Note not created") - self.assertEqual(actual_qty, -2) + qty_change1 = frappe.get_all( + 'Stock Ledger Entry', + {'item_code': '_Test Item', 'voucher_no': dn1.name, 'warehouse': 'Stores - _TIRC'}, + ['actual_qty', 'valuation_rate'] + ) + actual_qty = qty_change1[0].get("actual_qty") + valuation_rate = qty_change1[0].get("valuation_rate") - voucher_params_dn1 = { - 'voucher_type': 'Delivery Note','voucher_no': dn1.name - } - gl_accounts_dn1 = { - 'Stock In Hand - _TIRC': 'credit','Cost of Goods Sold - _TIRC': 'debit' - } - gl_entries_dn1 = { - account: frappe.db.get_value('GL Entry', {**voucher_params_dn1, 'account': account}, field) - for account, field in gl_accounts_dn1.items() - } - self.assertEqual(gl_entries_dn1['Stock In Hand - _TIRC'], valuation_rate * 2) - self.assertEqual(gl_entries_dn1['Cost of Goods Sold - _TIRC'], valuation_rate * 2) - - dn2 = make_delivery_note(si.name) - dn2.save() - dn2.submit() - - self.assertEqual(dn1.status, "Completed", "Delivery Note not created") + self.assertEqual(actual_qty, -2) - qty_change2 = frappe.get_all( - 'Stock Ledger Entry', - {'item_code': '_Test Item', 'voucher_no': dn2.name, 'warehouse': 'Stores - _TIRC'}, - ['actual_qty', 'valuation_rate'] - ) - actual_qty = qty_change2[0].get("actual_qty") - valuation_rate = qty_change2[0].get("valuation_rate") + voucher_params_dn1 = { + 'voucher_type': 'Delivery Note','voucher_no': dn1.name + } + gl_accounts_dn1 = { + 'Stock In Hand - _TIRC': 'credit','Cost of Goods Sold - _TIRC': 'debit' + } + gl_entries_dn1 = { + account: frappe.db.get_value('GL Entry', {**voucher_params_dn1, 'account': account}, field) + for account, field in gl_accounts_dn1.items() + } + self.assertEqual(gl_entries_dn1['Stock In Hand - _TIRC'], valuation_rate * 2) + self.assertEqual(gl_entries_dn1['Cost of Goods Sold - _TIRC'], valuation_rate * 2) + + dn2 = make_delivery_note(si.name) + dn2.save() + dn2.submit() - self.assertEqual(actual_qty, -2) + self.assertEqual(dn1.status, "Completed", "Delivery Note not created") - voucher_params_dn2 = { - 'voucher_type': 'Delivery Note', - 'voucher_no': dn2.name - } - gl_accounts_dn2 = { - 'Stock In Hand - _TIRC': 'credit', - 'Cost of Goods Sold - _TIRC': 'debit' - } - gl_entries_dn2 = { - account: frappe.db.get_value('GL Entry', {**voucher_params_dn2, 'account': account}, field) - for account, field in gl_accounts_dn2.items() - } - self.assertEqual(gl_entries_dn2['Stock In Hand - _TIRC'], valuation_rate * 2) - self.assertEqual(gl_entries_dn2['Cost of Goods Sold - _TIRC'], valuation_rate * 2) + qty_change2 = frappe.get_all( + 'Stock Ledger Entry', + {'item_code': '_Test Item', 'voucher_no': dn2.name, 'warehouse': 'Stores - _TIRC'}, + ['actual_qty', 'valuation_rate'] + ) + actual_qty = qty_change2[0].get("actual_qty") + valuation_rate = qty_change2[0].get("valuation_rate") + + self.assertEqual(actual_qty, -2) + + voucher_params_dn2 = { + 'voucher_type': 'Delivery Note', + 'voucher_no': dn2.name + } + gl_accounts_dn2 = { + 'Stock In Hand - _TIRC': 'credit', + 'Cost of Goods Sold - _TIRC': 'debit' + } + gl_entries_dn2 = { + account: frappe.db.get_value('GL Entry', {**voucher_params_dn2, 'account': account}, field) + for account, field in gl_accounts_dn2.items() + } + self.assertEqual(gl_entries_dn2['Stock In Hand - _TIRC'], valuation_rate * 2) + self.assertEqual(gl_entries_dn2['Cost of Goods Sold - _TIRC'], valuation_rate * 2) def test_sales_order_update_stock_in_si_with_gst_TC_S_018(self): - company = frappe.get_all("Company", {"name": "_Test Indian Registered Company"}, ["gstin", "gst_category"]) - customer = frappe.get_all("Customer", {"name": "_Test Registered Customer"}, ["gstin", "gst_category"]) - company_add = frappe.get_all("Address", {"name": "_Test Indian Registered Company-Billing"}, ["name", "gstin", "gst_category"]) - customer_add = frappe.get_all("Address", {"name": "_Test Registered Customer-Billing"}, ["name", "gstin", "gst_category"]) - - if company[0].get("gst_category") == "Registered Regular" and customer[0].get("gst_category") == "Registered Regular" and customer[0].get("gstin") and customer[0].get("gstin"): - if company_add[0].get("gst_category") == "Registered Regular" and customer_add[0].get("gst_category") == "Registered Regular" and company_add[0].get("gstin") and customer_add[0].get("gstin"): - so = make_sales_order(company='_Test Indian Registered Company', customer='_Test Registered Customer', warehouse='Stores - _TIRC' ,cost_center='Main - _TIRC', selling_price_list='Standard Selling', item_code='_Test Item', qty=4, rate=5000, do_not_save=True) - so.tax_category = 'In-State' - so.taxes_and_charges = 'Output GST In-state - _TIRC' - so.customer_address = customer_add[0].get("name") - so.billing_address_gstin = customer_add[0].get("gstin") - so.company_address = company_add[0].get("name") - so.company_gstin = company_add[0].get("gstin") - so.save() - so.submit() + so = self.create_and_submit_sales_order_with_gst("_Test Item", qty=4, rate=5000) - self.assertEqual(so.status, "To Deliver and Bill", "Sales Order not created") - self.assertEqual(so.grand_total, so.total + so.total_taxes_and_charges) + si = make_sales_invoice(so.name) + si.update_stock = 1 + si.save() + si.submit() - si = make_sales_invoice(so.name) - si.update_stock = 1 - si.save() - si.submit() + self.assertEqual(si.status, "Unpaid", "Sales Invoice not created") - self.assertEqual(si.status, "Unpaid", "Sales Invoice not created") + qty_change = frappe.get_all( + 'Stock Ledger Entry', + {'item_code': '_Test Item', 'voucher_no': si.name, 'warehouse': 'Stores - _TIRC'}, + ['actual_qty', 'valuation_rate'] + ) + actual_qty = qty_change[0].get("actual_qty") + valuation_rate = qty_change[0].get("valuation_rate") - qty_change = frappe.get_all( - 'Stock Ledger Entry', - {'item_code': '_Test Item', 'voucher_no': si.name, 'warehouse': 'Stores - _TIRC'}, - ['actual_qty', 'valuation_rate'] - ) - actual_qty = qty_change[0].get("actual_qty") - valuation_rate = qty_change[0].get("valuation_rate") + self.assertEqual(actual_qty, -4) - self.assertEqual(actual_qty, -4) + voucher_params_si = { + 'voucher_type': 'Sales Invoice', + 'voucher_no': si.name + } + gl_accounts_si = { + 'Stock In Hand - _TIRC': 'credit', + 'Cost of Goods Sold - _TIRC': 'debit', + 'Sales - _TIRC': 'credit', + 'Debtors - _TIRC': 'debit', + 'Output Tax SGST - _TIRC': 'credit', + 'Output Tax CGST - _TIRC': 'credit' + } + gl_entries_si = { + account: frappe.db.get_value('GL Entry', {**voucher_params_si, 'account': account}, field) + for account, field in gl_accounts_si.items() + } + self.assertEqual(gl_entries_si['Stock In Hand - _TIRC'], valuation_rate * 4) + self.assertEqual(gl_entries_si['Cost of Goods Sold - _TIRC'], valuation_rate * 4) + self.assertEqual(gl_entries_si['Sales - _TIRC'], 20000) + self.assertEqual(gl_entries_si['Debtors - _TIRC'], 23600) + self.assertEqual(gl_entries_si['Output Tax SGST - _TIRC'], 1800) + self.assertEqual(gl_entries_si['Output Tax CGST - _TIRC'], 1800) - voucher_params_si = { - 'voucher_type': 'Sales Invoice', - 'voucher_no': si.name - } - gl_accounts_si = { - 'Stock In Hand - _TIRC': 'credit', - 'Cost of Goods Sold - _TIRC': 'debit', - 'Sales - _TIRC': 'credit', - 'Debtors - _TIRC': 'debit', - 'Output Tax SGST - _TIRC': 'credit', - 'Output Tax CGST - _TIRC': 'credit' - } - gl_entries_si = { - account: frappe.db.get_value('GL Entry', {**voucher_params_si, 'account': account}, field) - for account, field in gl_accounts_si.items() - } - self.assertEqual(gl_entries_si['Stock In Hand - _TIRC'], valuation_rate * 4) - self.assertEqual(gl_entries_si['Cost of Goods Sold - _TIRC'], valuation_rate * 4) - self.assertEqual(gl_entries_si['Sales - _TIRC'], 20000) - self.assertEqual(gl_entries_si['Debtors - _TIRC'], 23600) - self.assertEqual(gl_entries_si['Output Tax SGST - _TIRC'], 1800) - self.assertEqual(gl_entries_si['Output Tax CGST - _TIRC'], 1800) - - so.reload() - self.assertEqual(so.status, 'Completed') + so.reload() + self.assertEqual(so.status, 'Completed') def test_sales_order_update_stock_in_partial_si_with_gst_TC_S_019(self): - company = frappe.get_all("Company", {"name": "_Test Indian Registered Company"}, ["gstin", "gst_category"]) - customer = frappe.get_all("Customer", {"name": "_Test Registered Customer"}, ["gstin", "gst_category"]) - company_add = frappe.get_all("Address", {"name": "_Test Indian Registered Company-Billing"}, ["name", "gstin", "gst_category"]) - customer_add = frappe.get_all("Address", {"name": "_Test Registered Customer-Billing"}, ["name", "gstin", "gst_category"]) - - if company[0].get("gst_category") == "Registered Regular" and customer[0].get("gst_category") == "Registered Regular" and customer[0].get("gstin") and customer[0].get("gstin"): - if company_add[0].get("gst_category") == "Registered Regular" and customer_add[0].get("gst_category") == "Registered Regular" and company_add[0].get("gstin") and customer_add[0].get("gstin"): - so = make_sales_order(company='_Test Indian Registered Company', customer='_Test Registered Customer', warehouse='Stores - _TIRC' ,cost_center='Main - _TIRC', selling_price_list='Standard Selling', item_code='_Test Item', qty=4, rate=5000, do_not_save=True) - so.tax_category = 'In-State' - so.taxes_and_charges = 'Output GST In-state - _TIRC' - so.customer_address = customer_add[0].get("name") - so.billing_address_gstin = customer_add[0].get("gstin") - so.company_address = company_add[0].get("name") - so.company_gstin = company_add[0].get("gstin") - so.save() - so.submit() + so = self.create_and_submit_sales_order_with_gst("_Test Item", qty=4, rate=5000) - self.assertEqual(so.status, "To Deliver and Bill", "Sales Order not created") - self.assertEqual(so.grand_total, so.total + so.total_taxes_and_charges) + si1 = make_sales_invoice(so.name) + si1.get("items")[0].qty = 2 + si1.update_stock = 1 + si1.save() + si1.submit() - si1 = make_sales_invoice(so.name) - si1.get("items")[0].qty = 2 - si1.update_stock = 1 - si1.save() - si1.submit() + self.assertEqual(si1.status, "Unpaid", "Sales Invoice not created") - self.assertEqual(si1.status, "Unpaid", "Sales Invoice not created") + qty_change1 = frappe.get_all( + 'Stock Ledger Entry', + {'item_code': '_Test Item', 'voucher_no': si1.name, 'warehouse': 'Stores - _TIRC'}, + ['actual_qty', 'valuation_rate'] + ) - qty_change1 = frappe.get_all( - 'Stock Ledger Entry', - {'item_code': '_Test Item', 'voucher_no': si1.name, 'warehouse': 'Stores - _TIRC'}, - ['actual_qty', 'valuation_rate'] - ) + actual_qty1 = qty_change1[0].get("actual_qty") + valuation_rate1 = qty_change1[0].get("valuation_rate") - actual_qty1 = qty_change1[0].get("actual_qty") - valuation_rate1 = qty_change1[0].get("valuation_rate") + self.assertEqual(actual_qty1, -2) - self.assertEqual(actual_qty1, -2) + voucher_params_si1 = { + 'voucher_type': 'Sales Invoice','voucher_no': si1.name + } + gl_accounts_si1 = { + 'Stock In Hand - _TIRC': 'credit', + 'Cost of Goods Sold - _TIRC': 'debit', + 'Sales - _TIRC': 'credit', + 'Debtors - _TIRC': 'debit' + } + gl_entries_si1 = { + account: frappe.db.get_value('GL Entry', {**voucher_params_si1, 'account': account}, field) + for account, field in gl_accounts_si1.items() + } + self.assertEqual(gl_entries_si1['Stock In Hand - _TIRC'], valuation_rate1 * 2) + self.assertEqual(gl_entries_si1['Cost of Goods Sold - _TIRC'], valuation_rate1 * 2) + self.assertEqual(gl_entries_si1['Sales - _TIRC'], 10000) + self.assertEqual(gl_entries_si1['Debtors - _TIRC'], 11800) - voucher_params_si1 = { - 'voucher_type': 'Sales Invoice','voucher_no': si1.name - } - gl_accounts_si1 = { - 'Stock In Hand - _TIRC': 'credit', - 'Cost of Goods Sold - _TIRC': 'debit', - 'Sales - _TIRC': 'credit', - 'Debtors - _TIRC': 'debit' - } - gl_entries_si1 = { - account: frappe.db.get_value('GL Entry', {**voucher_params_si1, 'account': account}, field) - for account, field in gl_accounts_si1.items() - } - self.assertEqual(gl_entries_si1['Stock In Hand - _TIRC'], valuation_rate1 * 2) - self.assertEqual(gl_entries_si1['Cost of Goods Sold - _TIRC'], valuation_rate1 * 2) - self.assertEqual(gl_entries_si1['Sales - _TIRC'], 10000) - self.assertEqual(gl_entries_si1['Debtors - _TIRC'], 11800) - - si2 = make_sales_invoice(so.name) - si2.update_stock = 1 - si2.save() - si2.submit() + si2 = make_sales_invoice(so.name) + si2.update_stock = 1 + si2.save() + si2.submit() - self.assertEqual(si2.status, "Unpaid", "Sales Invoice not created") + self.assertEqual(si2.status, "Unpaid", "Sales Invoice not created") - qty_change2 = frappe.get_all( - 'Stock Ledger Entry', - {'item_code': '_Test Item', 'voucher_no': si2.name, 'warehouse': 'Stores - _TIRC'}, - ['actual_qty', 'valuation_rate'] - ) - actual_qty2 = qty_change2[0].get("actual_qty") - valuation_rate2 = qty_change2[0].get("valuation_rate") + qty_change2 = frappe.get_all( + 'Stock Ledger Entry', + {'item_code': '_Test Item', 'voucher_no': si2.name, 'warehouse': 'Stores - _TIRC'}, + ['actual_qty', 'valuation_rate'] + ) + actual_qty2 = qty_change2[0].get("actual_qty") + valuation_rate2 = qty_change2[0].get("valuation_rate") - self.assertEqual(actual_qty2, -2) + self.assertEqual(actual_qty2, -2) - voucher_params_si2 = { - 'voucher_type': 'Sales Invoice','voucher_no': si2.name - } - gl_accounts_si2 = { - 'Stock In Hand - _TIRC': 'credit', - 'Cost of Goods Sold - _TIRC': 'debit', - 'Sales - _TIRC': 'credit', - 'Debtors - _TIRC': 'debit' - } - gl_entries_si2 = { - account: frappe.db.get_value('GL Entry', {**voucher_params_si2, 'account': account}, field) - for account, field in gl_accounts_si2.items() - } - self.assertEqual(gl_entries_si2['Stock In Hand - _TIRC'], valuation_rate2 * 2) - self.assertEqual(gl_entries_si2['Cost of Goods Sold - _TIRC'], valuation_rate2 * 2) - self.assertEqual(gl_entries_si2['Sales - _TIRC'], 10000) - self.assertEqual(gl_entries_si2['Debtors - _TIRC'], 11800) + voucher_params_si2 = { + 'voucher_type': 'Sales Invoice','voucher_no': si2.name + } + gl_accounts_si2 = { + 'Stock In Hand - _TIRC': 'credit', + 'Cost of Goods Sold - _TIRC': 'debit', + 'Sales - _TIRC': 'credit', + 'Debtors - _TIRC': 'debit' + } + gl_entries_si2 = { + account: frappe.db.get_value('GL Entry', {**voucher_params_si2, 'account': account}, field) + for account, field in gl_accounts_si2.items() + } + self.assertEqual(gl_entries_si2['Stock In Hand - _TIRC'], valuation_rate2 * 2) + self.assertEqual(gl_entries_si2['Cost of Goods Sold - _TIRC'], valuation_rate2 * 2) + self.assertEqual(gl_entries_si2['Sales - _TIRC'], 10000) + self.assertEqual(gl_entries_si2['Debtors - _TIRC'], 11800) - so.reload() - self.assertEqual(so.status, 'Completed') + so.reload() + self.assertEqual(so.status, 'Completed') def test_sales_order_for_service_item_with_gst_TC_S_020(self): make_service_item() - company = frappe.get_all("Company", {"name": "_Test Indian Registered Company"}, ["gstin", "gst_category"]) - customer = frappe.get_all("Customer", {"name": "_Test Registered Customer"}, ["gstin", "gst_category"]) - company_add = frappe.get_all("Address", {"name": "_Test Indian Registered Company-Billing"}, ["name", "gstin", "gst_category"]) - customer_add = frappe.get_all("Address", {"name": "_Test Registered Customer-Billing"}, ["name", "gstin", "gst_category"]) - - if company[0].get("gst_category") == "Registered Regular" and customer[0].get("gst_category") == "Registered Regular" and customer[0].get("gstin") and customer[0].get("gstin"): - if company_add[0].get("gst_category") == "Registered Regular" and customer_add[0].get("gst_category") == "Registered Regular" and company_add[0].get("gstin") and customer_add[0].get("gstin"): - so = make_sales_order(company='_Test Indian Registered Company', customer='_Test Registered Customer', warehouse='Stores - _TIRC' ,cost_center='Main - _TIRC', selling_price_list='Standard Selling', item_code='Consultancy', qty=1, rate=5000, do_not_save=True) - so.tax_category = 'In-State' - so.taxes_and_charges = 'Output GST In-state - _TIRC' - so.customer_address = customer_add[0].get("name") - so.billing_address_gstin = customer_add[0].get("gstin") - so.company_address = company_add[0].get("name") - so.company_gstin = company_add[0].get("gstin") - so.save() - so.submit() - - self.assertEqual(so.status, "To Deliver and Bill", "Sales Order not created") - self.assertEqual(so.grand_total, so.total + so.total_taxes_and_charges) - - si = make_sales_invoice(so.name) - si.save() - si.submit() - - self.assertEqual(si.status, "Unpaid", "Sales Invoice not created") + so = self.create_and_submit_sales_order_with_gst("_Test Item", qty=1, rate=5000) - voucher_params_si = { - 'voucher_type': 'Sales Invoice','voucher_no': si.name - } - gl_accounts_si = { - 'Sales - _TIRC': 'credit', - 'Debtors - _TIRC': 'debit', - 'Output Tax SGST - _TIRC': 'credit', - 'Output Tax CGST - _TIRC': 'credit' - } - gl_entries_si = { - account: frappe.db.get_value('GL Entry', {**voucher_params_si, 'account': account}, field) - for account, field in gl_accounts_si.items() - } - self.assertEqual(gl_entries_si['Sales - _TIRC'], 5000) - self.assertEqual(gl_entries_si['Debtors - _TIRC'], 5900) - self.assertEqual(gl_entries_si['Output Tax SGST - _TIRC'], 450) - self.assertEqual(gl_entries_si['Output Tax CGST - _TIRC'], 450) + si = make_sales_invoice(so.name) + si.save() + si.submit() + + self.assertEqual(si.status, "Unpaid", "Sales Invoice not created") + + voucher_params_si = { + 'voucher_type': 'Sales Invoice','voucher_no': si.name + } + gl_accounts_si = { + 'Sales - _TIRC': 'credit', + 'Debtors - _TIRC': 'debit', + 'Output Tax SGST - _TIRC': 'credit', + 'Output Tax CGST - _TIRC': 'credit' + } + gl_entries_si = { + account: frappe.db.get_value('GL Entry', {**voucher_params_si, 'account': account}, field) + for account, field in gl_accounts_si.items() + } + self.assertEqual(gl_entries_si['Sales - _TIRC'], 5000) + self.assertEqual(gl_entries_si['Debtors - _TIRC'], 5900) + self.assertEqual(gl_entries_si['Output Tax SGST - _TIRC'], 450) + self.assertEqual(gl_entries_si['Output Tax CGST - _TIRC'], 450) def test_sales_order_of_full_payment_with_shipping_rule_TC_S_021(self): so = make_sales_order(cost_center='Main - _TC', selling_price_list='Standard Selling', qty=1, rate=5000, do_not_save=True) @@ -4013,37 +3869,7 @@ def validate_gl_entries(voucher_type, voucher_no, accounts): self.assertEqual(so.status, "To Deliver", "Sales Order not updated") def test_sales_order_create_si_via_pe_dn_with_gst_TC_S_042(self): - company = get_gst_details("Company", {"name": "_Test Indian Registered Company"})[0] - customer = get_gst_details("Customer", {"name": "_Test Registered Customer"})[0] - company_add = get_gst_details("Address", {"name": "_Test Indian Registered Company-Billing"})[0] - customer_add = get_gst_details("Address", {"name": "_Test Registered Customer-Billing"})[0] - - if not (is_registered_regular(company) and is_registered_regular(customer) and - is_registered_regular(company_add) and is_registered_regular(customer_add)): - self.fail("GST details are not properly configured") - - so = make_sales_order( - company="_Test Indian Registered Company", - customer="_Test Registered Customer", - warehouse="Stores - _TIRC", - cost_center="Main - _TIRC", - selling_price_list="Standard Selling", - item_code="_Test Item", - qty=1, - rate=5000, - do_not_save=True - ) - so.tax_category = "In-State" - so.taxes_and_charges = "Output GST In-state - _TIRC" - so.customer_address = customer_add.get("name") - so.billing_address_gstin = customer_add.get("gstin") - so.company_address = company_add.get("name") - so.company_gstin = company_add.get("gstin") - so.save() - so.submit() - - self.assertEqual(so.status, "To Deliver and Bill", "Sales Order not created") - self.assertEqual(so.grand_total, so.total + so.total_taxes_and_charges) + so = self.create_and_submit_sales_order_with_gst("_Test Item", qty=1, rate=5000) from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry @@ -4120,110 +3946,385 @@ def test_sales_order_create_si_via_pe_dn_with_gst_TC_S_042(self): ) def test_sales_order_create_si_via_partial_pe_dn_with_gst_TC_S_043(self): - company = get_gst_details("Company", {"name": "_Test Indian Registered Company"}) - customer = get_gst_details("Customer", {"name": "_Test Registered Customer"}) - company_add = get_gst_details("Address", {"name": "_Test Indian Registered Company-Billing"}) - customer_add = get_gst_details("Address", {"name": "_Test Registered Customer-Billing"}) + so = self.create_and_submit_sales_order_with_gst("_Test Item", qty=1, rate=5000) + + create_registered_bank_account() - if not (is_registered_regular(company[0]) and is_registered_regular(customer[0]) and - is_registered_regular(company_add[0]) and is_registered_regular(customer_add[0])): - self.fail("GST details are not properly configured") + from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry + pe = get_payment_entry(dt="Sales Order",dn=so.name) + pe.reference_no = "1" + pe.reference_date = nowdate() + pe.paid_amount= 2000 + for i in pe.references: + i.allocated_amount = 2000 + pe.save() + pe.submit() + + self.assertEqual(pe.status, 'Submitted') + self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': pe.name, 'account': 'Debtors - _TIRC'}, 'credit'), 2000) + self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': pe.name, 'account': '_Test Registered Bank Account - _TIRC'}, 'debit'), 2000) + + dn = make_delivery_note(so.name) + dn.submit() + + stock_entry = frappe.get_all('Stock Ledger Entry', { + 'voucher_no': dn.name, + 'warehouse': 'Stores - _TIRC', + 'item_code': '_Test Item' + }, ['valuation_rate', 'actual_qty'])[0] + self.assertEqual(stock_entry.get("actual_qty"), -1) + + dn_acc_value = stock_entry.get("valuation_rate") + self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': dn.name, 'account': 'Stock In Hand - _TIRC'}, 'credit'), dn_acc_value) + self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': dn.name, 'account': 'Cost of Goods Sold - _TIRC'}, 'debit'), dn_acc_value) + + from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice + + si = make_sales_invoice(dn.name) + si.allocate_advances_automatically = 1 + si.save() + si.submit() + + si.reload() + self.assertEqual(si.status, 'Partly Paid') + + self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': si.name, 'account': 'Sales - _TIRC'}, 'credit'), 5000) + self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': si.name, 'account': 'Debtors - _TIRC'}, 'debit'), 5900) + self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': si.name, 'account': 'Output Tax SGST - _TIRC'}, 'credit'), 450) + self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': si.name, 'account': 'Output Tax CGST - _TIRC'}, 'credit'), 450) + pe2 = get_payment_entry(dt="Sales Invoice",dn=si.name) + pe2.reference_no = "1" + pe2.reference_date = nowdate() + pe2.save() + pe2.submit() + + self.assertEqual(pe2.status, 'Submitted') + self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': pe2.name, 'account': 'Debtors - _TIRC'}, 'credit'), 3900) + self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': pe2.name, 'account': '_Test Registered Bank Account - _TIRC'}, 'debit'), 3900) + + si.reload() + self.assertEqual(si.outstanding_amount, 0) + self.assertEqual(si.status, "Paid") + + def test_sales_order_with_full_advance_payment_and_shipping_rule_TC_S_044(self): so = make_sales_order( - company='_Test Indian Registered Company', - customer='_Test Registered Customer', - warehouse='Stores - _TIRC', - cost_center='Main - _TIRC', - selling_price_list='Standard Selling', - item_code='_Test Item', - qty=1, - rate=5000, + cost_center='Main - _TC', + selling_price_list='Standard Selling', + qty=1, + rate=5000, do_not_save=True ) - so.tax_category = 'In-State' - so.taxes_and_charges = 'Output GST In-state - _TIRC' - so.customer_address = customer_add[0].get("name") - so.billing_address_gstin = customer_add[0].get("gstin") - so.company_address = company_add[0].get("name") - so.company_gstin = company_add[0].get("gstin") + so.shipping_rule = "_Test Shipping Rule" so.save() so.submit() self.assertEqual(so.status, "To Deliver and Bill", "Sales Order not created") self.assertEqual(so.grand_total, so.total + so.total_taxes_and_charges) + + self.create_and_submit_payment_entry(dt="Sales Order", dn=so.name) + + dn = make_delivery_note(so.name) + dn.submit() + + stock_ledger_entry = frappe.get_all( + 'Stock Ledger Entry', + {'voucher_type': 'Delivery Note', 'voucher_no': dn.name, 'warehouse': '_Test Warehouse - _TC', 'item_code': '_Test Item'}, + ['valuation_rate', 'actual_qty'] + ) + self.assertEqual(stock_ledger_entry[0].get("actual_qty"), -1) + + from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice + + si = make_sales_invoice(dn.name) + si.allocate_advances_automatically = 1 + si.save() + si.submit() + si.reload() + self.assertEqual(si.status, 'Paid') + + self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': si.name,'account': 'Sales - _TC'}, 'credit'), 5000) + self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': si.name,'account': 'Debtors - _TC'}, 'debit'), 5200) + self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': si.name,'account': '_Test Account Shipping Charges - _TC'}, 'credit'), 200) + + def test_sales_order_with_partial_advance_payment_and_shipping_rule_TC_S_045(self): + so = make_sales_order( + cost_center='Main - _TC', + selling_price_list='Standard Selling', + qty=1, + rate=5000, + do_not_save=True + ) + so.shipping_rule = "_Test Shipping Rule" + so.save() + so.submit() + + self.assertEqual(so.status, "To Deliver and Bill", "Sales Order not created") + self.assertEqual(so.grand_total, so.total + so.total_taxes_and_charges) + + create_registered_bank_account() + + from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry + + pe = get_payment_entry(dt="Sales Order",dn=so.name) + pe.reference_no = "1" + pe.reference_date = nowdate() + pe.paid_amount= 2000 + for i in pe.references: + i.allocated_amount = 2000 + pe.save() + pe.submit() - def create_payment(allocated_amount, reference_doctype, reference_name): - from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry - pe = create_payment_entry( - company="_Test Indian Registered Company", - payment_type="Receive", - party_type="Customer", - party="_Test Registered Customer", - paid_from="Debtors - _TIRC", - paid_to="Cash - _TIRC", - paid_amount=allocated_amount, - ) - pe.append('references', { - "reference_doctype": reference_doctype, - "reference_name": reference_name, - "total_amount": so.grand_total, - "allocated_amount": allocated_amount, - "account": "Debtors - _TIRC" - }) - pe.save() - pe.submit() - return pe - - pe = create_payment(2000, "Sales Order", so.name) self.assertEqual(pe.status, 'Submitted') - self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': pe.name, 'account': 'Debtors - _TIRC'}, 'credit'), 2000) - self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': pe.name, 'account': 'Cash - _TIRC'}, 'debit'), 2000) + self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': pe.name, 'account': 'Debtors - _TC'}, 'credit'), 2000) + self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': pe.name, 'account': 'Cash - _TC'}, 'debit'), 2000) + + dn = make_delivery_note(so.name) + dn.submit() + + stock_ledger_entry = frappe.get_all( + 'Stock Ledger Entry', + {'voucher_type': 'Delivery Note', 'voucher_no': dn.name, 'warehouse': '_Test Warehouse - _TC', 'item_code': '_Test Item'}, + ['valuation_rate', 'actual_qty'] + ) + self.assertEqual(stock_ledger_entry[0].get("actual_qty"), -1) + + from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice + + si = make_sales_invoice(dn.name) + si.allocate_advances_automatically = 1 + si.save() + si.submit() + si.reload() + self.assertEqual(si.status, 'Partly Paid') + + self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': si.name,'account': 'Sales - _TC'}, 'credit'), 5000) + self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': si.name,'account': 'Debtors - _TC'}, 'debit'), 5200) + self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': si.name,'account': '_Test Account Shipping Charges - _TC'}, 'credit'), 200) + + pe2 = get_payment_entry(dt="Sales Invoice",dn=si.name) + pe2.reference_no = "1" + pe2.reference_date = nowdate() + pe2.save() + pe2.submit() + + self.assertEqual(pe2.status, 'Submitted') + self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': pe2.name, 'account': 'Debtors - _TC'}, 'credit'), 3200) + self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': pe2.name, 'account': 'Cash - _TC'}, 'debit'), 3200) + + si.reload() + self.assertEqual(si.outstanding_amount, 0) + self.assertEqual(si.status, "Paid") + + def test_sales_order_create_si_via_pe_dn_with_pricing_rule_TC_S_046(self): + make_item_price() + make_pricing_rule() + + so = self.create_and_submit_sales_order(qty=10) + + from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry + + pe = get_payment_entry(dt="Sales Order",dn=so.name) + pe.save() + pe.submit() + + self.assertEqual(pe.status, 'Submitted') + self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': pe.name, 'account': 'Debtors - _TC'}, 'credit'), 900) + self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': pe.name, 'account': 'Cash - _TC'}, 'debit'), 900) + + dn = make_delivery_note(so.name) + dn.submit() + self.assertEqual(dn.status, "To Bill", "Delivery Note not created") + stock_ledger_entry = frappe.get_all( + 'Stock Ledger Entry', + {'voucher_type': 'Delivery Note', 'voucher_no': dn.name, 'warehouse': '_Test Warehouse - _TC', 'item_code': '_Test Item'}, + ['valuation_rate', 'actual_qty'] + ) + self.assertEqual(stock_ledger_entry[0].get("actual_qty"), -10) + + si = self.create_and_submit_sales_invoice(dn.name,advances_automatically= 1,expected_amount=900) + si.reload() + self.assertEqual(si.status, "Paid") + + def test_sales_order_create_si_via_partial_pe_with_pricing_rule_TC_S_047(self): + make_item_price() + make_pricing_rule() + + so = self.create_and_submit_sales_order(qty=10) + + self.create_and_submit_payment_entry(dt="Sales Order", dn=so.name, amt=400) + dn = make_delivery_note(so.name) dn.submit() - stock_entry = frappe.get_all('Stock Ledger Entry', { - 'voucher_no': dn.name, - 'warehouse': 'Stores - _TIRC', - 'item_code': '_Test Item' - }, ['valuation_rate', 'actual_qty'])[0] - self.assertEqual(stock_entry.get("actual_qty"), -1) + self.assertEqual(dn.status, "To Bill", "Delivery Note not created") + stock_ledger_entry = frappe.get_all( + 'Stock Ledger Entry', + {'voucher_type': 'Delivery Note', 'voucher_no': dn.name, 'warehouse': '_Test Warehouse - _TC', 'item_code': '_Test Item'}, + ['valuation_rate', 'actual_qty'] + ) + self.assertEqual(stock_ledger_entry[0].get("actual_qty"), -10) + + si = self.create_and_submit_sales_invoice(dn.name,advances_automatically= 1,expected_amount=900) + si.reload() + self.assertEqual(si.status, "Partly Paid") + + self.create_and_submit_payment_entry(dt="Sales Invoice", dn=si.name) + + si.reload() + self.assertEqual(si.status, "Paid") + + def test_sales_order_creating_credit_note_after_SR_TC_S_048(self): + so = self.create_and_submit_sales_order(qty=5, rate=3000) + + self.create_and_submit_payment_entry(dt="Sales Order", dn=so.name) + + dn = self.create_and_validate_delivery_note(so.name, -5) + + si = self.create_and_submit_sales_invoice(dn.name,advances_automatically= 1,expected_amount=15000) + si.reload() + self.assertEqual(si.status, "Paid") + + from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_return + sr = make_sales_return(dn.name) + sr.save() + sr.submit() + + self.assertEqual(sr.status, "To Bill", "Sales Return not created") + + qty_change_return = frappe.db.get_value('Stock Ledger Entry', { + 'item_code': '_Test Item', + 'voucher_no': sr.name, + 'warehouse': '_Test Warehouse - _TC' + }, 'actual_qty') + self.assertEqual(qty_change_return, 5) + + from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_sales_return as make_credit_note + cn = make_credit_note(si.name) + cn.advances.clear() + cn.save() + cn.submit() + + self.assertEqual(cn.status, "Return", "Credit Note not created") + + voucher_params_cn = {'voucher_type': 'Sales Invoice', 'voucher_no': cn.name} + gl_accounts_cn = {'Debtors - _TC': 'credit', 'Sales - _TC': 'debit'} + gl_entries_cn = { + account: frappe.db.get_value('GL Entry', {**voucher_params_cn, 'account': account}, field) + for account, field in gl_accounts_cn.items() + } + self.assertEqual(gl_entries_cn['Debtors - _TC'], 15000) + self.assertEqual(gl_entries_cn['Sales - _TC'], 15000) + + def test_sales_order_creating_full_si_for_service_item_SI_TC_S_050(self): + make_service_item() + + so = make_sales_order(cost_center='Main - _TC', selling_price_list='Standard Selling', item_code='Consultancy', qty=1, rate=5000) + so.save() + so.submit() + + self.assertEqual(so.status, "To Deliver and Bill", "Sales Order not created") + + self.create_and_submit_payment_entry(dt="Sales Order", dn=so.name) + + si = make_sales_invoice(so.name) + si.allocate_advances_automatically= 1 + si.only_include_allocated_payments = 1 + si.save() + si.submit() + si.reload() + + self.assertEqual(si.status, "Paid", "Sales Invoice not created") + self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': si.name, 'account': 'Sales - _TC'}, 'credit'), 5000) + self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': si.name, 'account': 'Debtors - _TC'}, 'debit'), 5000) + + def test_sales_order_creating_partial_pe_for_service_item_SI_TC_S_051(self): + make_service_item() + + so = make_sales_order(cost_center='Main - _TC', selling_price_list='Standard Selling', item_code='Consultancy', qty=1, rate=5000) + so.save() + so.submit() - dn_acc_value = stock_entry.get("valuation_rate") - self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': dn.name, 'account': 'Stock In Hand - _TIRC'}, 'credit'), dn_acc_value) - self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': dn.name, 'account': 'Cost of Goods Sold - _TIRC'}, 'debit'), dn_acc_value) + self.assertEqual(so.status, "To Deliver and Bill", "Sales Order not created") + + self.create_and_submit_payment_entry(dt="Sales Order", dn=so.name, amt=2000) + + si = make_sales_invoice(so.name) + si.allocate_advances_automatically= 1 + si.only_include_allocated_payments = 1 + si.save() + si.submit() + si.reload() - from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice + self.assertEqual(si.status, "Partly Paid", "Sales Invoice not created") + self.assertEqual(si.outstanding_amount, 3000) + self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': si.name, 'account': 'Sales - _TC'}, 'credit'), 5000) + self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': si.name, 'account': 'Debtors - _TC'}, 'debit'), 5000) + + self.create_and_submit_payment_entry(dt="Sales Invoice", dn=si.name) - si = make_sales_invoice(dn.name) - si.allocate_advances_automatically = 1 + si.reload() + self.assertEqual(si.status, "Paid") + self.assertEqual(si.outstanding_amount, 0) + + def test_sales_order_creating_si_with_update_stock_SI_TC_S_052(self): + so = self.create_and_submit_sales_order(qty=1, rate=5000) + + self.create_and_submit_payment_entry(dt="Sales Order", dn=so.name, amt=so.grand_total) + + si = make_sales_invoice(so.name) + si.allocate_advances_automatically= 1 + si.only_include_allocated_payments = 1 + si.update_stock = 1 si.save() si.submit() + si.reload() - self.assertEqual(si.status, 'Unpaid') - self.assertEqual(si.total_advance, 2000) - self.assertEqual(si.outstanding_amount, 3900) + self.assertEqual(si.status, "Paid", "Sales Invoice not created") + self.assertEqual(frappe.db.get_value('Stock Ledger Entry', {'voucher_no': si.name, 'warehouse': '_Test Warehouse - _TC', 'item_code': '_Test Item'}, + 'actual_qty'), -1) + self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': si.name, 'account': 'Sales - _TC'}, 'credit'), 5000) + self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': si.name, 'account': 'Debtors - _TC'}, 'debit'), 5000) + + def test_sales_order_creating_partial_pe_with_update_stock_SI_TC_S_053(self): + so = self.create_and_submit_sales_order(qty=1, rate=5000) - self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': si.name, 'account': 'Sales - _TIRC'}, 'credit'), 5000) - self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': si.name, 'account': 'Debtors - _TIRC'}, 'debit'), 5900) - self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': si.name, 'account': 'Output Tax SGST - _TIRC'}, 'credit'), 450) - self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': si.name, 'account': 'Output Tax CGST - _TIRC'}, 'credit'), 450) + self.create_and_submit_payment_entry(dt="Sales Order", dn=so.name, amt=2000) + + si = make_sales_invoice(so.name) + si.allocate_advances_automatically= 1 + si.only_include_allocated_payments = 1 + si.update_stock = 1 + si.save() + si.submit() + si.reload() - pe2 = create_payment(si.outstanding_amount, "Sales Invoice", si.name) - self.assertEqual(pe2.status, 'Submitted') - self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': pe2.name, 'account': 'Debtors - _TIRC'}, 'credit'), 3900) - self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': pe2.name, 'account': 'Cash - _TIRC'}, 'debit'), 3900) + self.assertEqual(si.status, "Partly Paid", "Sales Invoice not created") + self.assertEqual(frappe.db.get_value('Stock Ledger Entry', {'voucher_no': si.name, 'warehouse': '_Test Warehouse - _TC', 'item_code': '_Test Item'}, + 'actual_qty'), -1) + self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': si.name, 'account': 'Sales - _TC'}, 'credit'), 5000) + self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': si.name, 'account': 'Debtors - _TC'}, 'debit'), 5000) + + self.create_and_submit_payment_entry(dt="Sales Invoice", dn=si.name) si.reload() + self.assertEqual(si.status, "Paid") self.assertEqual(si.outstanding_amount, 0) + + def test_sales_order_creating_si_with_product_bundle_and_shipping_rule_TC_S_058(self): + product_bundle = make_item("_Test Product Bundle", {"is_stock_item": 0}) + make_item("_Test Bundle Item 1", {"is_stock_item": 1}) + make_item("_Test Bundle Item 2", {"is_stock_item": 1}) - - def test_sales_order_with_full_advance_payment_and_shipping_rule_TC_S_044(self): + make_product_bundle("_Test Product Bundle", ["_Test Bundle Item 1", "_Test Bundle Item 2"]) + so = make_sales_order( cost_center='Main - _TC', selling_price_list='Standard Selling', + item_code=product_bundle.item_code, qty=1, - rate=5000, + rate=20000, do_not_save=True ) so.shipping_rule = "_Test Shipping Rule" @@ -4231,180 +4332,313 @@ def test_sales_order_with_full_advance_payment_and_shipping_rule_TC_S_044(self): so.submit() self.assertEqual(so.status, "To Deliver and Bill", "Sales Order not created") - self.assertEqual(so.grand_total, so.total + so.total_taxes_and_charges) + self.assertEqual(so.grand_total, so.total + so.total_taxes_and_charges) + + dn = make_delivery_note(so.name) + dn.submit() - from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry - pe = create_payment_entry( - payment_type="Receive", - party_type="Customer", - party="_Test Customer", - paid_from="Debtors - _TC", - paid_to="Cash - _TC", - paid_amount=so.grand_total, - ) - pe.append('references', { - "reference_doctype": "Sales Order", - "reference_name": so.name, - "total_amount": so.grand_total, - "account": "Debtors - _TC", - }) - pe.save() - pe.submit() + self.assertEqual(dn.status, "To Bill", "Delivery Note not created") + self.assertEqual(frappe.db.get_value('Stock Ledger Entry', {'voucher_no': dn.name, 'warehouse': '_Test Warehouse - _TC', 'item_code': '_Test Bundle Item 1'}, 'actual_qty'), -1) + self.assertEqual(frappe.db.get_value('Stock Ledger Entry', {'voucher_no': dn.name, 'warehouse': '_Test Warehouse - _TC', 'item_code': '_Test Bundle Item 2'}, 'actual_qty'), -1) + + from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice + si = make_sales_invoice(dn.name) + si.save() + si.submit() + si.reload() + self.assertEqual(si.status, 'Unpaid') + + self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': si.name,'account': 'Sales - _TC'}, 'credit'), 20000) + self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': si.name,'account': 'Debtors - _TC'}, 'debit'), 20200) + self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': si.name,'account': '_Test Account Shipping Charges - _TC'}, 'credit'), 200) + + def test_sales_order_creating_si_with_product_bundle_and_gst_rule_TC_S_059(self): + product_bundle = make_item("_Test Product Bundle", {"is_stock_item": 0}) + item_data = [ + {"name": "_Test Bundle Item 1", "valuation_rate": 100}, + {"name": "_Test Bundle Item 2", "valuation_rate": 200} + ] + for item in item_data: + itm = make_item(item["name"], {"is_stock_item": 1}) + itm.valuation_rate = item["valuation_rate"] + itm.save() + + make_product_bundle("_Test Product Bundle", [item["name"] for item in item_data]) + + so = self.create_and_submit_sales_order_with_gst(product_bundle.item_code, qty=1, rate=20000) + + dn = make_delivery_note(so.name) + dn.submit() + self.assertEqual(dn.status, "To Bill", "Delivery Note not created") + for item in item_data: + self.assertEqual( + frappe.db.get_value( + 'Stock Ledger Entry', + {'voucher_no': dn.name, 'warehouse': 'Stores - _TIRC', 'item_code': item["name"]}, + 'actual_qty' + ), + -1 + ) self.assertEqual( - frappe.db.get_value('GL Entry', { - 'voucher_type': 'Payment Entry', - 'voucher_no': pe.name, - 'account': 'Debtors - _TC' - }, 'credit'), - so.grand_total + frappe.db.get_value('GL Entry', {'voucher_no': dn.name, 'account': 'Cost of Goods Sold - _TIRC'}, 'debit'), + sum(item["valuation_rate"] for item in item_data) ) self.assertEqual( - frappe.db.get_value('GL Entry', { - 'voucher_type': 'Payment Entry', - 'voucher_no': pe.name, - 'account': 'Cash - _TC' - }, 'debit'), - so.grand_total + frappe.db.get_value('GL Entry', {'voucher_no': dn.name, 'account': 'Stock In Hand - _TIRC'}, 'credit'), + sum(item["valuation_rate"] for item in item_data) ) + from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice + si = make_sales_invoice(dn.name) + si.save() + si.submit() + si.reload() + + self.assertEqual(si.status, 'Unpaid') + self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': si.name, 'account': 'Sales - _TIRC'}, 'credit'), 20000) + self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': si.name, 'account': 'Debtors - _TIRC'}, 'debit'), 23600) + self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': si.name, 'account': 'Output Tax SGST - _TIRC'}, 'credit'), 1800) + self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': si.name, 'account': 'Output Tax CGST - _TIRC'}, 'credit'), 1800) + + def test_sales_order_creating_si_with_installation_note_TC_S_060(self): + so = self.create_and_submit_sales_order(qty=5, rate=3000) + dn = make_delivery_note(so.name) dn.submit() + self.assertEqual(dn.status, "To Bill", "Delivery Note not created") stock_ledger_entry = frappe.get_all( 'Stock Ledger Entry', {'voucher_type': 'Delivery Note', 'voucher_no': dn.name, 'warehouse': '_Test Warehouse - _TC', 'item_code': '_Test Item'}, ['valuation_rate', 'actual_qty'] ) - self.assertEqual(stock_ledger_entry[0].get("actual_qty"), -1) + self.assertEqual(stock_ledger_entry[0].get("actual_qty"), -5) + + from erpnext.stock.doctype.delivery_note.delivery_note import (make_installation_note, make_sales_invoice) + install_note = make_installation_note(dn.name) + install_note.inst_date = nowdate() + install_note.inst_time = datetime.now().time() + install_note.submit() + self.assertEqual(install_note.status, "Submitted", "Installation Note not created") + + si = make_sales_invoice(dn.name) + si.save() + si.submit() - from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice + self.assertEqual(si.status, 'Unpaid') + self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': si.name, 'account': 'Sales - _TC'}, 'credit'), 15000) + self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': si.name, 'account': 'Debtors - _TC'}, 'debit'), 15000) + + return dn, si + + def test_sales_order_creating_returns_with_installation_note_TC_S_061(self): + dn, si = self.test_sales_order_creating_si_with_installation_note_TC_S_060() + + from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_return + sr = make_sales_return(dn.name) + sr.save() + sr.submit() + + self.assertEqual(sr.status, "To Bill", "Sales Return not created") + + qty_change_return = frappe.db.get_value('Stock Ledger Entry', {'item_code': '_Test Item', 'voucher_no': sr.name, 'warehouse': '_Test Warehouse - _TC'}, 'actual_qty') + self.assertEqual(qty_change_return, 5) + + from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_sales_return as make_credit_note + cn = make_credit_note(si.name) + cn.save() + cn.submit() + + self.assertEqual(cn.status, "Return", "Credit Note not created") + self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': si.name, 'account': 'Sales - _TC'}, 'credit'), 15000) + self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': si.name, 'account': 'Debtors - _TC'}, 'debit'), 15000) + + def test_sales_order_creating_invoice_with_installation_note_and_gst_TC_S_062(self): + so = self.create_and_submit_sales_order_with_gst("_Test Item", qty=5, rate=20) + + dn = make_delivery_note(so.name) + dn.save() + dn.submit() + + self.assertEqual(dn.status, "To Bill", "Delivery Note not created") + + qty_change = frappe.get_all('Stock Ledger Entry', {'item_code': '_Test Item', 'voucher_no': dn.name, 'warehouse': 'Stores - _TIRC'}, ['actual_qty', 'valuation_rate']) + self.assertEqual(qty_change[0].get("actual_qty"), -5) + self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': dn.name, 'account': 'Stock In Hand - _TIRC'}, 'credit'), qty_change[0].get("valuation_rate") * 5) + self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': dn.name, 'account': 'Cost of Goods Sold - _TIRC'}, 'debit'), qty_change[0].get("valuation_rate") * 5) + + from erpnext.stock.doctype.delivery_note.delivery_note import (make_installation_note, make_sales_invoice) + install_note = make_installation_note(dn.name) + install_note.inst_date = nowdate() + install_note.inst_time = datetime.now().time() + install_note.submit() + self.assertEqual(install_note.status, "Submitted", "Installation Note not created") + si = make_sales_invoice(dn.name) - si.allocate_advances_automatically = 1 si.save() si.submit() - self.assertEqual(si.status, 'Paid') - - self.assertEqual( - frappe.db.get_value('GL Entry', { - 'voucher_type': 'Sales Invoice', - 'voucher_no': si.name, - 'account': 'Sales - _TC' - }, 'credit'), - 5000 - ) - self.assertEqual( - frappe.db.get_value('GL Entry', { - 'voucher_type': 'Sales Invoice', - 'voucher_no': si.name, - 'account': 'Debtors - _TC' - }, 'debit'), - 5200 - ) - self.assertEqual( - frappe.db.get_value('GL Entry', { - 'voucher_type': 'Sales Invoice', - 'voucher_no': si.name, - 'account': '_Test Account Shipping Charges - _TC' - }, 'credit'), - 200 + self.assertEqual(si.status, 'Unpaid') + self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': si.name, 'account': 'Sales - _TIRC'}, 'credit'), 100) + self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': si.name, 'account': 'Debtors - _TIRC'}, 'debit'), 118) + self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': si.name, 'account': 'Output Tax SGST - _TIRC'}, 'credit'), 9) + self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': si.name, 'account': 'Output Tax CGST - _TIRC'}, 'credit'), 9) + + def test_sales_order_for_stock_reservation_TC_S_063(self): + make_stock_entry(item_code="_Test Item", qty=10, rate=5000, target="_Test Warehouse - _TC") + + stock_setting = frappe.get_doc('Stock Settings') + stock_setting.enable_stock_resrvation = 1 + stock_setting.save() + + so = self.create_and_submit_sales_order(qty=1, rate=5000) + + from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import create_stock_reservation_entries_for_so_items + + item_details = [{'__checked': 1, 'sales_order_item': so.items[0].get("name"), 'item_code': '_Test Item', + 'warehouse': '_Test Warehouse - _TC', 'qty_to_reserve': 1, 'idx': 1, 'name': 'row 1'}] + + create_stock_reservation_entries_for_so_items( + sales_order=so, + items_details=item_details, + from_voucher_type=None, + notify=True, ) - def test_sales_order_with_partial_advance_payment_and_shipping_rule_TC_S_045(self): + self.assertEqual(frappe.db.get_value("Stock Reservation Entry", {"voucher_no": so.name}, "status"), "Reserved") + + dn = make_delivery_note(so.name) + dn.save() + dn.submit() + + self.assertEqual(dn.status, "To Bill", "Delivery Note not created") + + qty_change = frappe.db.get_value('Stock Ledger Entry', {'item_code': '_Test Item', 'voucher_no': dn.name, 'warehouse': '_Test Warehouse - _TC'}, 'actual_qty') + self.assertEqual(qty_change, -1) + + self.assertEqual(frappe.db.get_value("Stock Reservation Entry", {"voucher_no": so.name}, "status"), "Delivered") + + from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice + si = make_sales_invoice(dn.name) + si.save() + si.submit() + + self.assertEqual(si.status, 'Unpaid') + self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': si.name, 'account': 'Sales - _TC'}, 'credit'), 5000) + self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': si.name, 'account': 'Debtors - _TC'}, 'debit'), 5000) + + def create_and_submit_sales_order(self, qty=None, rate=None): + sales_order = make_sales_order(cost_center='Main - _TC', selling_price_list='Standard Selling', do_not_save=True) + sales_order.delivery_date = nowdate() + if qty and rate: + for item in sales_order.items: + item.qty = qty + item.rate = rate + sales_order.save() + else: + for item in sales_order.items: + item.qty = qty + sales_order.save() + sales_order.submit() + self.assertEqual(sales_order.status, "To Deliver and Bill") + return sales_order + + def create_and_submit_sales_order_with_gst(self, item_code, qty=None, rate=None): + company = get_gst_details("Company", {"name": "_Test Indian Registered Company"})[0] + customer = get_gst_details("Customer", {"name": "_Test Registered Customer"})[0] + company_add = get_gst_details("Address", {"name": "_Test Indian Registered Company-Billing"})[0] + customer_add = get_gst_details("Address", {"name": "_Test Registered Customer-Billing"})[0] + + if not (is_registered_regular(company) and is_registered_regular(customer) and + is_registered_regular(company_add) and is_registered_regular(customer_add)): + self.fail("GST details are not properly configured") + so = make_sales_order( - cost_center='Main - _TC', - selling_price_list='Standard Selling', - qty=1, - rate=5000, + company="_Test Indian Registered Company", + customer="_Test Registered Customer", + warehouse="Stores - _TIRC", + cost_center="Main - _TIRC", + selling_price_list="Standard Selling", + item_code=item_code, + qty=qty, + rate=rate, do_not_save=True ) - so.shipping_rule = "_Test Shipping Rule" + so.tax_category = "In-State" + so.taxes_and_charges = "Output GST In-state - _TIRC" + so.customer_address = customer_add.get("name") + so.billing_address_gstin = customer_add.get("gstin") + so.company_address = company_add.get("name") + so.company_gstin = company_add.get("gstin") + for i in so.items: + i.gst_hsn_code = "01011020" so.save() so.submit() self.assertEqual(so.status, "To Deliver and Bill", "Sales Order not created") self.assertEqual(so.grand_total, so.total + so.total_taxes_and_charges) + + return so - def create_payment(allocated_amount, reference_doctype, reference_name): - from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry - pe = create_payment_entry( - company="_Test Company", - payment_type="Receive", - party_type="Customer", - party="_Test Customer", - paid_from="Debtors - _TC", - paid_to="Cash - _TC", - paid_amount=allocated_amount, - ) - pe.append('references', { - "reference_doctype": reference_doctype, - "reference_name": reference_name, - "total_amount": so.grand_total, - "allocated_amount": allocated_amount, - "account": "Debtors - _TIRC" - }) - pe.save() - pe.submit() - return pe - - pe = create_payment(2000, "Sales Order", so.name) - self.assertEqual(pe.status, 'Submitted') - self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': pe.name, 'account': 'Debtors - _TC'}, 'credit'), 2000) - self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': pe.name, 'account': 'Cash - _TC'}, 'debit'), 2000) + def create_and_submit_sales_invoice(self, delivery_note_name, qty=None, expected_amount=None,advances_automatically=None): + from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice + sales_invoice = make_sales_invoice(delivery_note_name) + sales_invoice.insert() + if qty: + for item in sales_invoice.items: + item.qty = qty + + if advances_automatically: + sales_invoice.allocate_advances_automatically= 1 + sales_invoice.only_include_allocated_payments = 1 + sales_invoice.save() + sales_invoice.submit() + if expected_amount: + self.validate_gl_entries(sales_invoice.name, expected_amount) + return sales_invoice + + def create_and_submit_payment_entry(self, dt=None, dn=None, amt=None): + from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry + payment_entry = get_payment_entry(dt=dt,dn=dn) + payment_entry.insert() + if amt: + payment_entry.paid_amount= amt + for i in payment_entry.references: + i.allocated_amount = amt + payment_entry.save() + payment_entry.submit() + + self.assertEqual(payment_entry.status, "Submitted", "Payment Entry not created") + self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': payment_entry.name, 'account': 'Debtors - _TC'}, 'credit'), payment_entry.paid_amount) + self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': payment_entry.name, 'account': 'Cash - _TC'}, 'debit'), payment_entry.paid_amount) + return payment_entry - dn = make_delivery_note(so.name) - dn.submit() + def validate_gl_entries(self, voucher_no, amount): + debtor_account = frappe.db.get_value("Company", "_Test Company", "default_receivable_account") + sales_account = frappe.db.get_value("Company", "_Test Company", "default_income_account") + gl_entries = frappe.get_all("GL Entry", filters={"voucher_no": voucher_no}, fields=["account", "debit", "credit"]) + + gl_debits = {entry.account: entry.debit for entry in gl_entries} + gl_credits = {entry.account: entry.credit for entry in gl_entries} + + self.assertAlmostEqual(gl_debits[debtor_account], amount) + self.assertAlmostEqual(gl_credits[sales_account], amount) + + def create_and_validate_delivery_note(self, sales_order_name, expected_amount): + delivery_note = make_delivery_note(sales_order_name) + delivery_note.submit() + self.assertEqual(delivery_note.status, "To Bill", "Delivery Note not created") stock_ledger_entry = frappe.get_all( 'Stock Ledger Entry', - {'voucher_type': 'Delivery Note', 'voucher_no': dn.name, 'warehouse': '_Test Warehouse - _TC', 'item_code': '_Test Item'}, + {'voucher_type': 'Delivery Note', 'voucher_no': delivery_note.name, 'warehouse': '_Test Warehouse - _TC', 'item_code': '_Test Item'}, ['valuation_rate', 'actual_qty'] ) - self.assertEqual(stock_ledger_entry[0].get("actual_qty"), -1) - - from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice - - si = make_sales_invoice(dn.name) - si.allocate_advances_automatically = 1 - si.save() - si.submit() - - self.assertEqual(si.status, 'Partly Paid') - - self.assertEqual( - frappe.db.get_value('GL Entry', { - 'voucher_type': 'Sales Invoice', - 'voucher_no': si.name, - 'account': 'Sales - _TC' - }, 'credit'), - 5000 - ) - self.assertEqual( - frappe.db.get_value('GL Entry', { - 'voucher_type': 'Sales Invoice', - 'voucher_no': si.name, - 'account': 'Debtors - _TC' - }, 'debit'), - 5200 - ) - self.assertEqual( - frappe.db.get_value('GL Entry', { - 'voucher_type': 'Sales Invoice', - 'voucher_no': si.name, - 'account': '_Test Account Shipping Charges - _TC' - }, 'credit'), - 200 - ) + self.assertEqual(stock_ledger_entry[0].get("actual_qty"), expected_amount) - pe2 = create_payment(si.outstanding_amount, "Sales Invoice", si.name) - self.assertEqual(pe2.status, 'Submitted') - self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': pe2.name, 'account': 'Debtors - _TIRC'}, 'credit'), 3900) - self.assertEqual(frappe.db.get_value('GL Entry', {'voucher_no': pe2.name, 'account': 'Cash - _TIRC'}, 'debit'), 3900) - - si.reload() - self.assertEqual(si.outstanding_amount, 0) + return delivery_note def get_gst_details(doctype, filters): return frappe.get_all(doctype, filters, ["gstin", "gst_category", "name"]) @@ -4412,6 +4646,17 @@ def get_gst_details(doctype, filters): def is_registered_regular(details): return details.get("gst_category") == "Registered Regular" and details.get("gstin") +def create_registered_bank_account(): + if not frappe.db.exists('Account', {'name': '_Test Registered Bank Account - _TIRC'}): + acc_doc = frappe.new_doc("Account") + acc_data = { + "company": "_Test Indian Registered Company", + "parent_account":"Bank Accounts - _TIRC" + } + acc_doc.update(acc_data) + acc_doc.save() + return acc_doc + def automatically_fetch_payment_terms(enable=1): accounts_settings = frappe.get_doc("Accounts Settings") accounts_settings.automatically_fetch_payment_terms = enable @@ -4425,7 +4670,6 @@ def compare_payment_schedules(doc, doc1, doc2): doc.assertEqual(schedule.invoice_portion, doc2.payment_schedule[index].invoice_portion) doc.assertEqual(schedule.payment_amount, doc2.payment_schedule[index].payment_amount) - def make_sales_order(**args): so = frappe.new_doc("Sales Order") args = frappe._dict(args) diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.py b/erpnext/selling/page/point_of_sale/point_of_sale.py index d8d97fb4fdb7..d1b6cca4146f 100644 --- a/erpnext/selling/page/point_of_sale/point_of_sale.py +++ b/erpnext/selling/page/point_of_sale/point_of_sale.py @@ -58,13 +58,16 @@ def search_by_term(search_term, warehouse, price_list): item_stock_qty = item_stock_qty // item.get("conversion_factor", 1) item.update({"actual_qty": item_stock_qty}) + price_filters = { + "price_list": price_list, + "item_code": item_code, + } + if batch_no: + price_filters["batch_no"] = batch_no + price = frappe.get_list( doctype="Item Price", - filters={ - "price_list": price_list, - "item_code": item_code, - "batch_no": batch_no, - }, + filters=price_filters, fields=["uom", "currency", "price_list_rate", "batch_no"], ) diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js index 0017b8cfab04..a382b175b313 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_cart.js +++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js @@ -928,10 +928,13 @@ erpnext.PointOfSale.ItemCart = class { const me = this; dfs.forEach((df) => { this[`customer_${df.fieldname}_field`] = frappe.ui.form.make_control({ - df: { ...df, onchange: handle_customer_field_change }, + df: df, parent: $customer_form.find(`.${df.fieldname}-field`), render_input: true, }); + this[`customer_${df.fieldname}_field`].$input?.on("blur", () => { + handle_customer_field_change.apply(this[`customer_${df.fieldname}_field`]); + }); this[`customer_${df.fieldname}_field`].set_value(this.customer_info[df.fieldname]); }); diff --git a/erpnext/selling/page/point_of_sale/pos_item_details.js b/erpnext/selling/page/point_of_sale/pos_item_details.js index af1beab5304c..f2f85122edc5 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_details.js +++ b/erpnext/selling/page/point_of_sale/pos_item_details.js @@ -210,10 +210,21 @@ erpnext.PointOfSale.ItemDetails = class { make_auto_serial_selection_btn(item) { if (item.has_serial_no || item.has_batch_no) { - const label = item.has_serial_no ? __("Select Serial No") : __("Select Batch No"); - this.$form_container.append( - `
${label}
` - ); + if (item.has_serial_no && item.has_batch_no) { + this.$form_container.append( + `
${__( + "Select Serial No / Batch No" + )}
` + ); + } else { + const classname = item.has_serial_no ? ".serial_no-control" : ".batch_no-control"; + const label = item.has_serial_no ? __("Select Serial No") : __("Select Batch No"); + this.$form_container + .find(classname) + .append( + `
${label}
` + ); + } this.$form_container.find(".serial_no-control").find("textarea").css("height", "6rem"); } } diff --git a/erpnext/selling/page/point_of_sale/pos_item_selector.js b/erpnext/selling/page/point_of_sale/pos_item_selector.js index 0b3bf9696cfc..c72e94e1cb65 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_selector.js +++ b/erpnext/selling/page/point_of_sale/pos_item_selector.js @@ -328,13 +328,15 @@ erpnext.PointOfSale.ItemSelector = class { } filter_items({ search_term = "" } = {}) { + const selling_price_list = this.events.get_frm().doc.selling_price_list; if (search_term) { search_term = search_term.toLowerCase(); // memoize this.search_index = this.search_index || {}; - if (this.search_index[search_term]) { - const items = this.search_index[search_term]; + this.search_index[selling_price_list] = this.search_index[selling_price_list] || {}; + if (this.search_index[selling_price_list][search_term]) { + const items = this.search_index[selling_price_list][search_term]; this.items = items; this.render_item_list(items); this.auto_add_item && this.items.length == 1 && this.add_filtered_item_to_cart(); @@ -346,7 +348,7 @@ erpnext.PointOfSale.ItemSelector = class { // eslint-disable-next-line no-unused-vars const { items, serial_no, batch_no, barcode } = message; if (search_term && !barcode) { - this.search_index[search_term] = items; + this.search_index[selling_price_list][search_term] = items; } this.items = items; this.render_item_list(items); diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json index 7233fc97114c..acbea2ce53a8 100644 --- a/erpnext/setup/doctype/company/company.json +++ b/erpnext/setup/doctype/company/company.json @@ -820,10 +820,6 @@ "read": 1, "role": "Stock User" }, - { - "read": 1, - "role": "Projects User" - }, { "create": 1, "delete": 1, diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js index 267173845a12..90bf4829fc15 100644 --- a/erpnext/stock/doctype/material_request/material_request.js +++ b/erpnext/stock/doctype/material_request/material_request.js @@ -43,6 +43,14 @@ frappe.ui.form.on("Material Request", { }; }); + frm.set_query("project", "items", function (doc, cdt, cdn) { + return { + filters: { + company: doc.company, + }, + }; + }); + var list = frm.fields_dict['items'].grid.get_field('work_breakdown_structure').get_query = function (doc, cdt, cdn) { var child = locals[cdt][cdn]; return { diff --git a/erpnext/stock/doctype/material_request/test_material_request.py b/erpnext/stock/doctype/material_request/test_material_request.py index fa8286bf8e95..17b3036a13ed 100644 --- a/erpnext/stock/doctype/material_request/test_material_request.py +++ b/erpnext/stock/doctype/material_request/test_material_request.py @@ -2912,6 +2912,81 @@ def test_create_material_req_to_2po_to_1pr_cancel_TC_SCK_058(self): sle = frappe.get_doc('Stock Ledger Entry',{'voucher_no':pr.name}) self.assertEqual(sle.qty_after_transaction, 0) + def test_create_mr_issue_to_stock_entry_with_batch_and_TC_SCK_062(self): + from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry as _make_stock_entry + + fields = { + "has_batch_no": 1, + "is_stock_item": 1, + "create_new_batch": 1, + "batch_naming_series": "Test-SBBTYT-NNS.#####", + } + + if frappe.db.has_column("Item", "gst_hsn_code"): + fields["gst_hsn_code"] = "01011010" + + company = "_Test Company" + qty = 10 + frappe.db.set_value("Company", "_Test Company", "enable_perpetual_inventory", 1) + frappe.db.set_value("Company", "_Test Company", "stock_adjustment_account", "Stock Adjustment - _TC") + target_warehouse = create_warehouse("_Test Warehouse", properties=None, company=company) + item = make_item("Test Use Serial and Batch Item SN Item", fields).name + + new_stock = _make_stock_entry( + item_code=item, + qty=10, + to_warehouse=target_warehouse, + company="_Test Company", + rate=100, + ) + self.assertTrue(new_stock.items[0].serial_and_batch_bundle) + + mr = make_material_request( + material_request_type="Material Issue", qty=qty, warehouse=target_warehouse, item_code=item + ) + self.assertEqual(mr.status, "Pending") + + bin_qty = ( + frappe.db.get_value("Bin", {"item_code": item, "warehouse": target_warehouse}, "actual_qty") or 0 + ) + stock_in_hand_account = get_inventory_account(company, target_warehouse) + + # Make stock entry against material request issue + se = make_stock_entry(mr.name) + se.items[0].expense_account = "Cost of Goods Sold - _TC" + se.serial_and_batch_bundle = new_stock.items[0].serial_and_batch_bundle + se.insert() + se.submit() + mr.load_from_db() + self.assertEqual(mr.status, "Issued") + + sle = frappe.get_doc("Stock Ledger Entry", {"voucher_no": se.name}) + stock_value_diff = abs( + frappe.db.get_value( + "Stock Ledger Entry", + {"voucher_type": "Stock Entry", "voucher_no": se.name}, + "stock_value_difference", + ) + ) + gle = get_gle(company, se.name, stock_in_hand_account) + gle1 = get_gle(company, se.name, "Cost of Goods Sold - _TC") + self.assertEqual(sle.qty_after_transaction, bin_qty - qty) + self.assertEqual(gle[1], stock_value_diff) + self.assertEqual(gle1[0], stock_value_diff) + se.cancel() + mr.load_from_db() + + # After stock entry cancel + current_bin_qty = ( + frappe.db.get_value("Bin", {"item_code": item, "warehouse": target_warehouse}, "actual_qty") or 0 + ) + sh_gle = get_gle(company, se.name, stock_in_hand_account) + cogs_gle = get_gle(company, se.name, "Cost of Goods Sold - _TC") + + self.assertEqual(sh_gle[0], sh_gle[1]) + self.assertEqual(cogs_gle[0], cogs_gle[1]) + self.assertEqual(current_bin_qty, bin_qty) + def test_mr_transfer_to_se_cancel_TC_SCK_061(self): source_wh = create_warehouse( warehouse_name="_Test Source Warehouse", @@ -2978,6 +3053,1210 @@ def test_mr_po_pi_TC_SCK_082(self): gl_stock_debit = frappe.db.get_value('GL Entry',{'voucher_no':doc_pi.name, 'account': 'Creditors - _TC'},'credit') self.assertEqual(gl_stock_debit, 1000) + def test_mr_po_2pi_TC_SCK_083(self): + # MR => PO => 2PI + mr_dict_list = [{ + "company" : "_Test Company", + "item_code" : "Testing-31", + "warehouse" : "Stores - _TC", + "qty" : 10, + "rate" : 100, + }, + ] + + doc_mr = make_material_request(**mr_dict_list[0]) + self.assertEqual(doc_mr.docstatus, 1) + + doc_po = make_test_po(doc_mr.name) + doc_pi = create_purchase_invoice(doc_po.name) + doc_pi.items[0].qty = 5 + doc_pi.submit() + + self.assertEqual(doc_pi.docstatus, 1) + doc_mr.reload() + self.assertEqual(doc_mr.status, "Ordered") + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Stock Received But Not Billed - _TC'}): + gl_temp_credit = frappe.db.get_value('GL Entry',{'voucher_no':doc_pi.name, 'account': 'Stock Received But Not Billed - _TC'},'debit') + self.assertEqual(gl_temp_credit, 500) + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Creditors - _TC'}): + gl_stock_debit = frappe.db.get_value('GL Entry',{'voucher_no':doc_pi.name, 'account': 'Creditors - _TC'},'credit') + self.assertEqual(gl_stock_debit, 500) + + doc_pi = create_purchase_invoice(doc_po.name) + doc_pi.items[0].qty = 5 + doc_pi.submit() + + self.assertEqual(doc_pi.docstatus, 1) + doc_mr.reload() + self.assertEqual(doc_mr.status, "Ordered") + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Stock Received But Not Billed - _TC'}): + gl_temp_credit = frappe.db.get_value('GL Entry',{'voucher_no':doc_pi.name, 'account': 'Stock Received But Not Billed - _TC'},'debit') + self.assertEqual(gl_temp_credit, 500) + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Creditors - _TC'}): + gl_stock_debit = frappe.db.get_value('GL Entry',{'voucher_no':doc_pi.name, 'account': 'Creditors - _TC'},'credit') + self.assertEqual(gl_stock_debit, 500) + + def test_create_material_req_to_2po_to_2pi_TC_SCK_084(self): + mr = make_material_request() + + #partially qty + po = make_purchase_order(mr.name) + po.supplier = "_Test Supplier" + po.get("items")[0].rate = 100 + po.get("items")[0].qty = 5 + po.insert() + po.submit() + + #remaining qty + po1 = make_purchase_order(mr.name) + po1.supplier = "_Test Supplier" + po1.get("items")[0].rate = 100 + po1.get("items")[0].qty = 5 + po1.insert() + po1.submit() + + pr = create_purchase_invoice(po.name) + pr.submit() + pr1 = create_purchase_invoice(po1.name) + pr1.submit() + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Stock Received But Not Billed - _TC'}): + recive_account = frappe.db.get_value("Company",mr.company,"stock_received_but_not_billed") + gl_temp_credit = frappe.db.get_value('GL Entry',{'voucher_no':pr.name, 'account': recive_account},'debit') + self.assertEqual(gl_temp_credit, 500) + gl_temp_credit = frappe.db.get_value('GL Entry',{'voucher_no':pr1.name, 'account': recive_account},'debit') + self.assertEqual(gl_temp_credit, 500) + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Creditors - _TC'}): + payable_act = frappe.db.get_value("Company",mr.company,"default_payable_account") + gl_stock_debit = frappe.db.get_value('GL Entry',{'voucher_no':pr.name, 'account': payable_act},'credit') + self.assertEqual(gl_stock_debit, 500) + gl_stock_debit = frappe.db.get_value('GL Entry',{'voucher_no':pr1.name, 'account': payable_act},'credit') + self.assertEqual(gl_stock_debit, 500) + + def test_create_material_req_to_2po_to_1pi_TC_SCK_085(self): + mr = make_material_request() + + #partially qty + po = make_purchase_order(mr.name) + po.supplier = "_Test Supplier" + po.get("items")[0].rate = 100 + po.get("items")[0].qty = 5 + po.insert() + po.submit() + + #remaining qty + po1 = make_purchase_order(mr.name) + po1.supplier = "_Test Supplier" + po1.get("items")[0].rate = 100 + po1.get("items")[0].qty = 5 + po1.insert() + po1.submit() + + pr = create_purchase_invoice(po.name) + pr = create_purchase_invoice(po1.name, target_doc=pr) + pr.submit() + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Stock Received But Not Billed - _TC'}): + recive_account = frappe.db.get_value("Company",mr.company,"stock_received_but_not_billed") + gl_temp_credit = frappe.db.get_value('GL Entry',{'voucher_no':pr.name, 'account': recive_account},'debit') + self.assertEqual(gl_temp_credit, 1000) + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Creditors - _TC'}): + payable_act = frappe.db.get_value("Company",mr.company,"default_payable_account") + gl_stock_debit = frappe.db.get_value('GL Entry',{'voucher_no':pr.name, 'account': payable_act},'credit') + self.assertEqual(gl_stock_debit, 1000) + + def test_mr_po_pi_cancel_TC_SCK_086(self): + # MR => PO => PI => PI Cancel + mr_dict_list = [{ + "company" : "_Test Company", + "item_code" : "Testing-31", + "warehouse" : "Stores - _TC", + "qty" : 10, + "rate" : 100, + }, + ] + + doc_mr = make_material_request(**mr_dict_list[0]) + self.assertEqual(doc_mr.docstatus, 1) + + doc_po = make_test_po(doc_mr.name) + doc_pi = create_purchase_invoice(doc_po.name) + doc_pi.submit() + + self.assertEqual(doc_pi.docstatus, 1) + doc_pi.reload() + doc_pi.load_from_db() + self.assertEqual(doc_pi.status, "Unpaid") + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Stock Received But Not Billed - _TC'}): + gl_temp_credit = frappe.db.get_value('GL Entry',{'voucher_no':doc_pi.name, 'account': 'Stock Received But Not Billed - _TC'},'debit') + self.assertEqual(gl_temp_credit, 1000) + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Creditors - _TC'}): + gl_stock_debit = frappe.db.get_value('GL Entry',{'voucher_no':doc_pi.name, 'account': 'Creditors - _TC'},'credit') + self.assertEqual(gl_stock_debit, 1000) + + doc_pi.cancel() + doc_pi.reload() + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Stock Received But Not Billed - _TC'}): + gl_temp_credit = frappe.db.get_value('GL Entry',{'voucher_no':doc_pi.name, 'account': 'Stock Received But Not Billed - _TC'},'credit') + self.assertEqual(gl_temp_credit, 1000) + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Creditors - _TC'}): + gl_stock_debit = frappe.db.get_value('GL Entry',{'voucher_no':doc_pi.name, 'account': 'Creditors - _TC'},'debit') + self.assertEqual(gl_stock_debit, 1000) + + def test_mr_po_2pi_cancel_TC_SCK_087(self): + # MR => PO => 2PI => 2PI cancel + mr_dict_list = [{ + "company" : "_Test Company", + "item_code" : "Testing-31", + "warehouse" : "Stores - _TC", + "qty" : 10, + "rate" : 100, + }, + ] + + doc_mr = make_material_request(**mr_dict_list[0]) + self.assertEqual(doc_mr.docstatus, 1) + + doc_po = make_test_po(doc_mr.name) + doc_pi = create_purchase_invoice(doc_po.name) + doc_pi.items[0].qty = 5 + doc_pi.submit() + + self.assertEqual(doc_pi.docstatus, 1) + doc_mr.reload() + self.assertEqual(doc_mr.status, "Ordered") + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Stock Received But Not Billed - _TC'}): + gl_temp_credit = frappe.db.get_value('GL Entry',{'voucher_no':doc_pi.name, 'account': 'Stock Received But Not Billed - _TC'},'debit') + self.assertEqual(gl_temp_credit, 500) + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Creditors - _TC'}): + gl_stock_debit = frappe.db.get_value('GL Entry',{'voucher_no':doc_pi.name, 'account': 'Creditors - _TC'},'credit') + self.assertEqual(gl_stock_debit, 500) + + doc_pi1 = create_purchase_invoice(doc_po.name) + doc_pi1.items[0].qty = 5 + doc_pi1.submit() + + self.assertEqual(doc_pi1.docstatus, 1) + doc_pi1.reload() + self.assertEqual(doc_pi1.status, "Unpaid") + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Stock Received But Not Billed - _TC'}): + gl_temp_credit = frappe.db.get_value('GL Entry',{'voucher_no':doc_pi1.name, 'account': 'Stock Received But Not Billed - _TC'},'debit') + self.assertEqual(gl_temp_credit, 500) + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Creditors - _TC'}): + gl_stock_debit = frappe.db.get_value('GL Entry',{'voucher_no':doc_pi1.name, 'account': 'Creditors - _TC'},'credit') + self.assertEqual(gl_stock_debit, 500) + + #cancel PI's + doc_pi.reload() + doc_pi.cancel() + doc_pi.reload() + self.assertEqual(doc_pi.status, "Cancelled") + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Stock Received But Not Billed - _TC'}): + gl_temp_credit = frappe.db.get_value('GL Entry',{'voucher_no':doc_pi.name, 'account': 'Stock Received But Not Billed - _TC'},'credit') + self.assertEqual(gl_temp_credit, 500) + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Creditors - _TC'}): + gl_stock_debit = frappe.db.get_value('GL Entry',{'voucher_no':doc_pi.name, 'account': 'Creditors - _TC'},'debit') + self.assertEqual(gl_stock_debit, 500) + + doc_pi1.cancel() + doc_pi1.reload() + self.assertEqual(doc_pi1.status, "Cancelled") + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Stock Received But Not Billed - _TC'}): + gl_temp_credit = frappe.db.get_value('GL Entry',{'voucher_no':doc_pi1.name, 'account': 'Stock Received But Not Billed - _TC'},'credit') + self.assertEqual(gl_temp_credit, 500) + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Creditors - _TC'}): + gl_stock_debit = frappe.db.get_value('GL Entry',{'voucher_no':doc_pi1.name, 'account': 'Creditors - _TC'},'debit') + self.assertEqual(gl_stock_debit, 500) + + def test_create_material_req_to_2po_to_2pi_cancel_TC_SCK_088(self): + mr = make_material_request() + + #partially qty + po = make_purchase_order(mr.name) + po.supplier = "_Test Supplier" + po.get("items")[0].rate = 100 + po.get("items")[0].qty = 5 + po.insert() + po.submit() + + #remaining qty + po1 = make_purchase_order(mr.name) + po1.supplier = "_Test Supplier" + po1.get("items")[0].rate = 100 + po1.get("items")[0].qty = 5 + po1.insert() + po1.submit() + + pr = create_purchase_invoice(po.name) + pr.submit() + pr1 = create_purchase_invoice(po1.name) + pr1.submit() + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Stock Received But Not Billed - _TC'}): + recive_account = frappe.db.get_value("Company",mr.company,"stock_received_but_not_billed") + gl_temp_credit = frappe.db.get_value('GL Entry',{'voucher_no':pr.name, 'account': recive_account},'debit') + self.assertEqual(gl_temp_credit, 500) + gl_temp_credit = frappe.db.get_value('GL Entry',{'voucher_no':pr1.name, 'account': recive_account},'debit') + self.assertEqual(gl_temp_credit, 500) + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Creditors - _TC'}): + payable_act = frappe.db.get_value("Company",mr.company,"default_payable_account") + gl_stock_debit = frappe.db.get_value('GL Entry',{'voucher_no':pr.name, 'account': payable_act},'credit') + self.assertEqual(gl_stock_debit, 500) + gl_stock_debit = frappe.db.get_value('GL Entry',{'voucher_no':pr1.name, 'account': payable_act},'credit') + self.assertEqual(gl_stock_debit, 500) + + #cancel PI's + pr.reload() + pr.cancel() + pr.reload() + self.assertEqual(pr.status, "Cancelled") + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Stock Received But Not Billed - _TC'}): + gl_temp_credit = frappe.db.get_value('GL Entry',{'voucher_no':pr.name, 'account': 'Stock Received But Not Billed - _TC'},'credit') + self.assertEqual(gl_temp_credit, 500) + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Creditors - _TC'}): + gl_stock_debit = frappe.db.get_value('GL Entry',{'voucher_no':pr.name, 'account': 'Creditors - _TC'},'debit') + self.assertEqual(gl_stock_debit, 500) + + pr1.reload() + pr1.cancel() + pr1.reload() + self.assertEqual(pr1.status, "Cancelled") + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Stock Received But Not Billed - _TC'}): + gl_temp_credit = frappe.db.get_value('GL Entry',{'voucher_no':pr1.name, 'account': 'Stock Received But Not Billed - _TC'},'credit') + self.assertEqual(gl_temp_credit, 500) + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Creditors - _TC'}): + gl_stock_debit = frappe.db.get_value('GL Entry',{'voucher_no':pr1.name, 'account': 'Creditors - _TC'},'debit') + self.assertEqual(gl_stock_debit, 500) + + def test_create_mr_material_transfer_to_stock_entry_TC_SCK_064(self): + from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry as _make_stock_entry + from erpnext.stock.doctype.stock_entry.test_stock_entry import TestStockEntry as tse + + item = create_item("_Test Item") + source_warehouse = create_warehouse( + "_Test Source Warehouse", properties=None, company="_Test Company" + ) + t_warehouse = create_warehouse( + warehouse_name="_Test Warehouse 1", properties=None, company="_Test Company" + ) + _make_stock_entry( + item_code=item.name, + qty=10, + to_warehouse=source_warehouse, + company="_Test Company", + rate=120, + ) + s_bin_qty = ( + frappe.db.get_value("Bin", {"item_code": item.name, "warehouse": source_warehouse}, "actual_qty") + or 0 + ) + + # Create Material Request for Material Transfer + mr = make_material_request( + material_request_type="Material Transfer", + qty=10, + warehouse=t_warehouse, + from_warehouse=source_warehouse, + item_code=item.name, + ) + self.assertEqual(mr.status, "Pending") + + # Create Stock Entry based on Material Request + se = make_stock_entry(mr.name) + se.save() + se.submit() + tse.check_stock_ledger_entries( + self, + "Stock Entry", + se.name, + [ + [item.name, t_warehouse, 10], + [item.name, source_warehouse, -10], + ], + ) + mr.load_from_db() + self.assertEqual(mr.status, "Transferred") + + # Cancel Stock Entry and check qty in source warehouse + se.cancel() + mr.load_from_db() + current_qty = ( + frappe.db.get_value("Bin", {"item_code": item.name, "warehouse": source_warehouse}, "actual_qty") + or 0 + ) + self.assertEqual(current_qty, s_bin_qty) + self.assertEqual(mr.status, "Pending") + + def test_create_mr_for_purchase_to_po_TC_SCK_019(self): + from erpnext.stock.doctype.stock_entry.test_stock_entry import TestStockEntry as tse + + # Create Material Request for Purchase + fields = { + "has_batch_no": 1, + "has_serial_no": 1, + "is_stock_item": 1, + "create_new_batch": 1, + "batch_naming_series": "Test-SABBMRP-Bno.#####", + } + + if frappe.db.has_column("Item", "gst_hsn_code"): + fields["gst_hsn_code"] = "01011010" + + item = make_item("Test Use Serial and Batch Item SN Item", fields).name + mr = make_material_request( + material_request_type="Purchase", + qty=2, + item_code=item, + rate=10000 + ) + + po = make_purchase_order(mr.name) + po.supplier = "_Test Supplier" + po.save() + po.submit() + + pr = make_purchase_receipt(po.name) + pr.items[0].use_serial_batch_fields = 1 + pr.items[0].serial_no = "Test-SABBMRP-Sno-001\nTest-SABBMRP-Sno-002" + + if not frappe.db.exists({"doctype": "Batch", "batch_id":"Test-SABBMRP-Bno-001"}): + b_no = frappe.new_doc("Batch") + b_no.batch_id = "Test-SABBMRP-Bno-001" + b_no.item = item + b_no.save() + + pr.items[0].batch_no = "Test-SABBMRP-Bno-001" + pr.save() + pr.submit() + + sl_entry = frappe.db.get_all( + "Stock Ledger Entry", + {"voucher_type": "Purchase Receipt", "voucher_no": pr.name}, + ["actual_qty", "serial_and_batch_bundle"], + order_by="creation", + ) + + sabb = frappe.get_doc("Serial and Batch Bundle", sl_entry[0].serial_and_batch_bundle) + self.assertEqual(sl_entry[0].actual_qty, 2) + self.assertEqual(sabb.entries[0].serial_no, "Test-SABBMRP-Sno-001") + self.assertEqual(sabb.entries[1].serial_no, "Test-SABBMRP-Sno-002") + self.assertEqual(sabb.entries[0].batch_no, "Test-SABBMRP-Bno-001") + + def test_create_mr_for_purchase_to_po_2pr_TC_SCK_020(self): + fields = { + "has_batch_no": 1, + "has_serial_no": 1, + "is_stock_item": 1, + "create_new_batch": 1, + "batch_naming_series": "Test-SABBMRP-Bno.#####", + } + + if frappe.db.has_column("Item", "gst_hsn_code"): + fields["gst_hsn_code"] = "01011010" + + item = make_item("Test Use Serial and Batch Item SN Item", fields).name + + # Create Material Request for Purchase + mr = make_material_request( + material_request_type="Purchase", + qty=5, + item_code=item, + rate=10000, + do_not_submit=True + ) + mr.transaction_date = "01-08-2024" + mr.schedule_date = "15-08-2024" + mr.save() + mr.submit() + + po = make_purchase_order(mr.name) + po.posting_date = "05-08-2024" + po.supplier = "_Test Supplier" + po.save() + po.submit() + + pr1 = make_purchase_receipt(po.name) + pr1.posting_date = "05-08-2024" + pr1.items[0].use_serial_batch_fields = 1 + pr1.items[0].qty = 3 + pr1.items[0].serial_no = "Test-SABBMRP-Sno-001\nTest-SABBMRP-Sno-002\nTest-SABBMRP-Sno-003" + + if not frappe.db.exists({"doctype": "Batch", "batch_id":"Test-SABBMRP-Bno-001"}): + b_no = frappe.new_doc("Batch") + b_no.batch_id = "Test-SABBMRP-Bno-001" + b_no.item = item + b_no.save() + + pr1.items[0].batch_no = "Test-SABBMRP-Bno-001" + pr1.save() + pr1.submit() + + sl_entry = frappe.db.get_all( + "Stock Ledger Entry", + {"voucher_type": "Purchase Receipt", "voucher_no": pr1.name}, + ["actual_qty", "serial_and_batch_bundle"], + order_by="creation", + ) + + sabb = frappe.get_doc("Serial and Batch Bundle", sl_entry[0].serial_and_batch_bundle) + self.assertEqual(sl_entry[0].actual_qty, 3) + self.assertEqual(sabb.entries[0].serial_no, "Test-SABBMRP-Sno-001") + self.assertEqual(sabb.entries[0].batch_no, "Test-SABBMRP-Bno-001") + + pr2 = make_purchase_receipt(po.name) + pr2.posting_date = "10-08-2024" + pr2.items[0].use_serial_batch_fields = 1 + pr2.items[0].qty = 2 + pr2.items[0].serial_no = "Test-SABBMRP-Sno-004\nTest-SABBMRP-Sno-005" + + if not frappe.db.exists({"doctype": "Batch", "batch_id":"Test-SABBMRP-Bno-001"}): + b_no = frappe.new_doc("Batch") + b_no.batch_id = "Test-SABBMRP-Bno-001" + b_no.item = item + b_no.save() + + pr2.items[0].batch_no = "Test-SABBMRP-Bno-001" + pr2.save() + pr2.submit() + + sl_entry = frappe.db.get_all( + "Stock Ledger Entry", + {"voucher_type": "Purchase Receipt", "voucher_no": pr2.name}, + ["actual_qty", "serial_and_batch_bundle"], + order_by="creation", + ) + + sabb = frappe.get_doc("Serial and Batch Bundle", sl_entry[0].serial_and_batch_bundle) + self.assertEqual(sl_entry[0].actual_qty, 2) + self.assertEqual(sabb.entries[1].serial_no, "Test-SABBMRP-Sno-005") + self.assertEqual(sabb.entries[1].batch_no, "Test-SABBMRP-Bno-001") + + + def test_create_material_req_to_2po_to_1pi_cancel_TC_SCK_089(self): + mr = make_material_request() + + #partially qty + po = make_purchase_order(mr.name) + po.supplier = "_Test Supplier" + po.get("items")[0].rate = 100 + po.get("items")[0].qty = 5 + po.insert() + po.submit() + + #remaining qty + po1 = make_purchase_order(mr.name) + po1.supplier = "_Test Supplier" + po1.get("items")[0].rate = 100 + po1.get("items")[0].qty = 5 + po1.insert() + po1.submit() + + pr = create_purchase_invoice(po.name) + pr = create_purchase_invoice(po1.name, target_doc=pr) + pr.submit() + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Stock Received But Not Billed - _TC'}): + recive_account = frappe.db.get_value("Company",mr.company,"stock_received_but_not_billed") + gl_temp_credit = frappe.db.get_value('GL Entry',{'voucher_no':pr.name, 'account': recive_account},'debit') + self.assertEqual(gl_temp_credit, 1000) + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Creditors - _TC'}): + payable_act = frappe.db.get_value("Company",mr.company,"default_payable_account") + gl_stock_debit = frappe.db.get_value('GL Entry',{'voucher_no':pr.name, 'account': payable_act},'credit') + self.assertEqual(gl_stock_debit, 1000) + + pr.reload() + pr.cancel() + pr.reload() + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Stock Received But Not Billed - _TC'}): + gl_temp_credit = frappe.db.get_value('GL Entry',{'voucher_no':pr.name, 'account': 'Stock Received But Not Billed - _TC'},'credit') + self.assertEqual(gl_temp_credit, 1000) + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Creditors - _TC'}): + gl_stock_debit = frappe.db.get_value('GL Entry',{'voucher_no':pr.name, 'account': 'Creditors - _TC'},'debit') + self.assertEqual(gl_stock_debit, 1000) + + def test_mr_po_pi_return_TC_SCK_090(self): + # MR => PO => PI => Return + mr_dict_list = [{ + "company" : "_Test Company", + "item_code" : "Testing-31", + "warehouse" : "Stores - _TC", + "qty" : 10, + "rate" : 100, + }, + ] + + doc_mr = make_material_request(**mr_dict_list[0]) + self.assertEqual(doc_mr.docstatus, 1) + + doc_po = make_test_po(doc_mr.name) + doc_pi = create_purchase_invoice(doc_po.name) + doc_pi.submit() + + self.assertEqual(doc_pi.docstatus, 1) + doc_mr.reload() + self.assertEqual(doc_mr.status, "Ordered") + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Stock Received But Not Billed - _TC'}): + gl_temp_credit = frappe.db.get_value('GL Entry',{'voucher_no':doc_pi.name, 'account': 'Stock Received But Not Billed - _TC'},'debit') + self.assertEqual(gl_temp_credit, 1000) + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Creditors - _TC'}): + gl_stock_debit = frappe.db.get_value('GL Entry',{'voucher_no':doc_pi.name, 'account': 'Creditors - _TC'},'credit') + self.assertEqual(gl_stock_debit, 1000) + + doc_pi.load_from_db() + from erpnext.controllers.sales_and_purchase_return import make_return_doc + return_pi = make_return_doc("Purchase Invoice", doc_pi.name) + return_pi.submit() + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Stock Received But Not Billed - _TC'}): + gl_temp_credit = frappe.db.get_value('GL Entry',{'voucher_no':return_pi.name, 'account': 'Stock Received But Not Billed - _TC'},'credit') + self.assertEqual(gl_temp_credit, 1000) + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Stock In Hand - _TC'}): + gl_stock_debit = frappe.db.get_value('GL Entry',{'voucher_no':return_pi.name, 'account': 'Stock In Hand - _TC'},'debit') + self.assertEqual(gl_stock_debit, 1000) + + def test_mr_po_2pi_return_TC_SCK_101(self): + # MR => PO => 2PI => 2PI return + mr_dict_list = [{ + "company" : "_Test Company", + "item_code" : "Testing-31", + "warehouse" : "Stores - _TC", + "qty" : 10, + "rate" : 100, + }, + ] + + doc_mr = make_material_request(**mr_dict_list[0]) + self.assertEqual(doc_mr.docstatus, 1) + + doc_po = make_test_po(doc_mr.name) + doc_pi = create_purchase_invoice(doc_po.name) + doc_pi.items[0].qty = 5 + doc_pi.submit() + + self.assertEqual(doc_pi.docstatus, 1) + doc_mr.reload() + self.assertEqual(doc_mr.status, "Ordered") + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Stock Received But Not Billed - _TC'}): + gl_temp_credit = frappe.db.get_value('GL Entry',{'voucher_no':doc_pi.name, 'account': 'Stock Received But Not Billed - _TC'},'debit') + self.assertEqual(gl_temp_credit, 500) + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Creditors - _TC'}): + gl_stock_debit = frappe.db.get_value('GL Entry',{'voucher_no':doc_pi.name, 'account': 'Creditors - _TC'},'credit') + self.assertEqual(gl_stock_debit, 500) + + doc_pi1 = create_purchase_invoice(doc_po.name) + doc_pi1.items[0].qty = 5 + doc_pi1.submit() + + self.assertEqual(doc_pi1.docstatus, 1) + doc_pi1.reload() + self.assertEqual(doc_pi1.status, "Unpaid") + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Stock Received But Not Billed - _TC'}): + gl_temp_credit = frappe.db.get_value('GL Entry',{'voucher_no':doc_pi1.name, 'account': 'Stock Received But Not Billed - _TC'},'debit') + self.assertEqual(gl_temp_credit, 500) + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Creditors - _TC'}): + gl_stock_debit = frappe.db.get_value('GL Entry',{'voucher_no':doc_pi1.name, 'account': 'Creditors - _TC'},'credit') + self.assertEqual(gl_stock_debit, 500) + + #Return PI's + doc_pi.load_from_db() + from erpnext.controllers.sales_and_purchase_return import make_return_doc + return_pi = make_return_doc("Purchase Invoice", doc_pi.name) + return_pi.submit() + return_pi = make_return_doc("Purchase Invoice", doc_pi1.name) + return_pi.submit() + + doc_pi.reload() + doc_pi1.reload() + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Stock Received But Not Billed - _TC'}): + gl_temp_credit = frappe.db.get_value('GL Entry',{'voucher_no':doc_pi.name, 'account': 'Stock Received But Not Billed - _TC'},'debit') + self.assertEqual(gl_temp_credit, 500) + gl_temp_credit = frappe.db.get_value('GL Entry',{'voucher_no':doc_pi1.name, 'account': 'Stock Received But Not Billed - _TC'},'debit') + self.assertEqual(gl_temp_credit, 500) + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Creditors - _TC'}): + gl_stock_debit = frappe.db.get_value('GL Entry',{'voucher_no':doc_pi.name, 'account': 'Creditors - _TC'},'credit') + self.assertEqual(gl_stock_debit, 500) + gl_stock_debit = frappe.db.get_value('GL Entry',{'voucher_no':doc_pi1.name, 'account': 'Creditors - _TC'},'credit') + self.assertEqual(gl_stock_debit, 500) + + def test_create_material_req_to_2po_to_2pi_return_TC_SCK_102(self): + mr = make_material_request() + + #partially qty + po = make_purchase_order(mr.name) + po.supplier = "_Test Supplier" + po.get("items")[0].rate = 100 + po.get("items")[0].qty = 5 + po.insert() + po.submit() + + #remaining qty + po1 = make_purchase_order(mr.name) + po1.supplier = "_Test Supplier" + po1.get("items")[0].rate = 100 + po1.get("items")[0].qty = 5 + po1.insert() + po1.submit() + + pr = create_purchase_invoice(po.name) + pr.submit() + pr1 = create_purchase_invoice(po1.name) + pr1.submit() + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Stock Received But Not Billed - _TC'}): + recive_account = frappe.db.get_value("Company",mr.company,"stock_received_but_not_billed") + gl_temp_credit = frappe.db.get_value('GL Entry',{'voucher_no':pr.name, 'account': recive_account},'debit') + self.assertEqual(gl_temp_credit, 500) + gl_temp_credit = frappe.db.get_value('GL Entry',{'voucher_no':pr1.name, 'account': recive_account},'debit') + self.assertEqual(gl_temp_credit, 500) + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Creditors - _TC'}): + payable_act = frappe.db.get_value("Company",mr.company,"default_payable_account") + gl_stock_debit = frappe.db.get_value('GL Entry',{'voucher_no':pr.name, 'account': payable_act},'credit') + self.assertEqual(gl_stock_debit, 500) + gl_stock_debit = frappe.db.get_value('GL Entry',{'voucher_no':pr1.name, 'account': payable_act},'credit') + self.assertEqual(gl_stock_debit, 500) + + #Return PI's + from erpnext.controllers.sales_and_purchase_return import make_return_doc + return_pi = make_return_doc("Purchase Invoice", pr.name) + return_pi.submit() + return_pi1 = make_return_doc("Purchase Invoice", pr1.name) + return_pi1.submit() + + pr.reload() + pr1.reload() + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Stock Received But Not Billed - _TC'}): + gl_temp_credit = frappe.db.get_value('GL Entry',{'voucher_no':pr.name, 'account': 'Stock Received But Not Billed - _TC'},'debit') + self.assertEqual(gl_temp_credit, 500) + gl_temp_credit = frappe.db.get_value('GL Entry',{'voucher_no':pr1.name, 'account': 'Stock Received But Not Billed - _TC'},'debit') + self.assertEqual(gl_temp_credit, 500) + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Creditors - _TC'}): + gl_stock_debit = frappe.db.get_value('GL Entry',{'voucher_no':pr.name, 'account': 'Creditors - _TC'},'credit') + self.assertEqual(gl_stock_debit, 500) + gl_stock_debit = frappe.db.get_value('GL Entry',{'voucher_no':pr1.name, 'account': 'Creditors - _TC'},'credit') + self.assertEqual(gl_stock_debit, 500) + + def test_create_material_req_to_2po_to_1pi_return_TC_SCK_103(self): + mr = make_material_request() + + #partially qty + po = make_purchase_order(mr.name) + po.supplier = "_Test Supplier" + po.get("items")[0].rate = 100 + po.get("items")[0].qty = 5 + po.insert() + po.submit() + + #remaining qty + po1 = make_purchase_order(mr.name) + po1.supplier = "_Test Supplier" + po1.get("items")[0].rate = 100 + po1.get("items")[0].qty = 5 + po1.insert() + po1.submit() + + pr = create_purchase_invoice(po.name) + pr = create_purchase_invoice(po1.name, target_doc=pr) + pr.submit() + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Stock Received But Not Billed - _TC'}): + recive_account = frappe.db.get_value("Company",mr.company,"stock_received_but_not_billed") + gl_temp_credit = frappe.db.get_value('GL Entry',{'voucher_no':pr.name, 'account': recive_account},'debit') + self.assertEqual(gl_temp_credit, 1000) + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Creditors - _TC'}): + payable_act = frappe.db.get_value("Company",mr.company,"default_payable_account") + gl_stock_debit = frappe.db.get_value('GL Entry',{'voucher_no':pr.name, 'account': payable_act},'credit') + self.assertEqual(gl_stock_debit, 1000) + + from erpnext.controllers.sales_and_purchase_return import make_return_doc + return_pi = make_return_doc("Purchase Invoice", pr.name) + return_pi.submit() + pr.reload() + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Stock Received But Not Billed - _TC'}): + gl_temp_credit = frappe.db.get_value('GL Entry',{'voucher_no':pr.name, 'account': 'Stock Received But Not Billed - _TC'},'debit') + self.assertEqual(gl_temp_credit, 1000) + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Stock In Hand - _TC'}): + gl_stock_debit = frappe.db.get_value('GL Entry',{'voucher_no':pr.name, 'account': 'Stock In Hand - _TC'},'credit') + self.assertEqual(gl_stock_debit, 1000) + + def test_mr_po_pi_partial_return_TC_SCK_104(self): + # MR => PO => PI => Return + mr_dict_list = [{ + "company" : "_Test Company", + "item_code" : "Testing-31", + "warehouse" : "Stores - _TC", + "qty" : 10, + "rate" : 100, + }, + ] + + doc_mr = make_material_request(**mr_dict_list[0]) + self.assertEqual(doc_mr.docstatus, 1) + + doc_po = make_test_po(doc_mr.name) + doc_pi = create_purchase_invoice(doc_po.name) + doc_pi.submit() + + self.assertEqual(doc_pi.docstatus, 1) + doc_mr.reload() + self.assertEqual(doc_mr.status, "Ordered") + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Stock Received But Not Billed - _TC'}): + gl_temp_credit = frappe.db.get_value('GL Entry',{'voucher_no':doc_pi.name, 'account': 'Stock Received But Not Billed - _TC'},'debit') + self.assertEqual(gl_temp_credit, 1000) + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Creditors - _TC'}): + gl_stock_debit = frappe.db.get_value('GL Entry',{'voucher_no':doc_pi.name, 'account': 'Creditors - _TC'},'credit') + self.assertEqual(gl_stock_debit, 1000) + + doc_pi.load_from_db() + from erpnext.controllers.sales_and_purchase_return import make_return_doc + return_pi = make_return_doc("Purchase Invoice", doc_pi.name) + return_pi.get("items")[0].qty = -5 + return_pi.submit() + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Stock Received But Not Billed - _TC'}): + gl_temp_credit = frappe.db.get_value('GL Entry',{'voucher_no':return_pi.name, 'account': 'Stock Received But Not Billed - _TC'},'credit') + self.assertEqual(gl_temp_credit, 500) + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Stock In Hand - _TC'}): + gl_stock_debit = frappe.db.get_value('GL Entry',{'voucher_no':return_pi.name, 'account': 'Stock In Hand - _TC'},'debit') + self.assertEqual(gl_stock_debit, 500) + + def test_mr_po_2pi_partial_return_TC_SCK_105(self): + # MR => PO => 2PI => 2PI return + mr_dict_list = [{ + "company" : "_Test Company", + "item_code" : "Testing-31", + "warehouse" : "Stores - _TC", + "qty" : 10, + "rate" : 100, + }, + ] + + doc_mr = make_material_request(**mr_dict_list[0]) + self.assertEqual(doc_mr.docstatus, 1) + + doc_po = make_test_po(doc_mr.name) + doc_pi = create_purchase_invoice(doc_po.name) + doc_pi.items[0].qty = 5 + doc_pi.submit() + + self.assertEqual(doc_pi.docstatus, 1) + doc_mr.reload() + self.assertEqual(doc_mr.status, "Ordered") + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Stock Received But Not Billed - _TC'}): + gl_temp_credit = frappe.db.get_value('GL Entry',{'voucher_no':doc_pi.name, 'account': 'Stock Received But Not Billed - _TC'},'debit') + self.assertEqual(gl_temp_credit, 500) + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Creditors - _TC'}): + gl_stock_debit = frappe.db.get_value('GL Entry',{'voucher_no':doc_pi.name, 'account': 'Creditors - _TC'},'credit') + self.assertEqual(gl_stock_debit, 500) + + doc_pi1 = create_purchase_invoice(doc_po.name) + doc_pi1.items[0].qty = 5 + doc_pi1.submit() + + self.assertEqual(doc_pi1.docstatus, 1) + doc_pi1.reload() + self.assertEqual(doc_pi1.status, "Unpaid") + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Stock Received But Not Billed - _TC'}): + gl_temp_credit = frappe.db.get_value('GL Entry',{'voucher_no':doc_pi1.name, 'account': 'Stock Received But Not Billed - _TC'},'debit') + self.assertEqual(gl_temp_credit, 500) + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Creditors - _TC'}): + gl_stock_debit = frappe.db.get_value('GL Entry',{'voucher_no':doc_pi1.name, 'account': 'Creditors - _TC'},'credit') + self.assertEqual(gl_stock_debit, 500) + + #Return PI's + doc_pi.load_from_db() + from erpnext.controllers.sales_and_purchase_return import make_return_doc + return_pi = make_return_doc("Purchase Invoice", doc_pi.name) + return_pi.submit() + + doc_pi.reload() + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Stock Received But Not Billed - _TC'}): + gl_temp_credit = frappe.db.get_value('GL Entry',{'voucher_no':doc_pi.name, 'account': 'Stock Received But Not Billed - _TC'},'debit') + self.assertEqual(gl_temp_credit, 500) + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Creditors - _TC'}): + gl_stock_debit = frappe.db.get_value('GL Entry',{'voucher_no':doc_pi.name, 'account': 'Creditors - _TC'},'credit') + self.assertEqual(gl_stock_debit, 500) + + def test_create_material_req_to_2po_to_1pr_return_TC_SCK_036(self): + mr = make_material_request() + + #partially qty + po = make_purchase_order(mr.name) + po.supplier = "_Test Supplier" + po.get("items")[0].rate = 100 + po.get("items")[0].qty = 5 + po.insert() + po.submit() + + #remaining qty + po1 = make_purchase_order(mr.name) + po1.supplier = "_Test Supplier" + po1.get("items")[0].rate = 100 + po1.get("items")[0].qty = 5 + po1.insert() + po1.submit() + + pr = make_purchase_receipt(po.name) + pr = make_purchase_receipt(po1.name, target_doc=pr) + pr.submit() + + bin_qty = frappe.db.get_value("Bin", {"item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC"}, "actual_qty") + sle = frappe.get_doc('Stock Ledger Entry',{'voucher_no':pr.name}) + self.assertEqual(sle.qty_after_transaction, bin_qty) + self.assertEqual(sle.warehouse, mr.get("items")[0].warehouse) + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Stock Received But Not Billed - _TC'}): + gl_temp_credit = frappe.db.get_value('GL Entry',{'voucher_no':pr.name, 'account': 'Stock Received But Not Billed - _TC'},'credit') + self.assertEqual(gl_temp_credit, 1000) + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Stock In Hand - _TC'}): + gl_stock_debit = frappe.db.get_value('GL Entry',{'voucher_no':pr.name, 'account': 'Stock In Hand - _TC'},'debit') + self.assertEqual(gl_stock_debit, 1000) + + #Return PI's + pr.load_from_db() + from erpnext.controllers.sales_and_purchase_return import make_return_doc + return_pi = make_return_doc("Purchase Receipt", pr.name) + return_pi.submit() + + pr.reload() + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Stock Received But Not Billed - _TC'}): + gl_temp_credit = frappe.db.get_value('GL Entry',{'voucher_no':return_pi.name, 'account': 'Stock Received But Not Billed - _TC'},'debit') + self.assertEqual(gl_temp_credit, 1000) + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Stock In Hand - _TC'}): + gl_stock_debit = frappe.db.get_value('GL Entry',{'voucher_no':return_pi.name, 'account': 'Stock In Hand - _TC'},'credit') + self.assertEqual(gl_stock_debit, 1000) + + def test_mr_po_pr_partial_return_TC_SCK_038(self): + mr_dict_list = [{ + "company" : "_Test Company", + "item_code" : "_Test Item", + "warehouse" : "Stores - _TC", + "qty" : 10, + "rate" : 100, + }, + ] + + doc_mr = make_material_request(**mr_dict_list[0]) + self.assertEqual(doc_mr.docstatus, 1) + + doc_po = make_test_po(doc_mr.name) + doc_pr = make_test_pr(doc_po.name) + + doc_mr.reload() + self.assertEqual(doc_mr.status, "Received") + doc_pr.load_from_db() + from erpnext.controllers.sales_and_purchase_return import make_return_doc + return_pi = make_return_doc("Purchase Receipt", doc_pr.name) + return_pi.get("items")[0].rejected_qty = -5 + return_pi.submit() + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Stock Received But Not Billed - _TC'}): + gl_temp_credit = frappe.db.get_value('GL Entry',{'voucher_no':return_pi.name, 'account': 'Stock Received But Not Billed - _TC'},'credit') + self.assertEqual(gl_temp_credit, 500) + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Stock In Hand - _TC'}): + gl_stock_debit = frappe.db.get_value('GL Entry',{'voucher_no':return_pi.name, 'account': 'Stock In Hand - _TC'},'debit') + self.assertEqual(gl_stock_debit, 500) + + def test_mr_po_2pr_partial_return_TC_SCK_041(self): + # MR => PO => 2PR => PR return + mr_dict_list = [{ + "company" : "_Test Company", + "item_code" : "Testing-31", + "warehouse" : "Stores - _TC", + "qty" : 10, + "rate" : 100, + }, + ] + + doc_mr = make_material_request(**mr_dict_list[0]) + self.assertEqual(doc_mr.docstatus, 1) + + doc_po = make_test_po(doc_mr.name) + doc_pr = make_test_pr(doc_po.name,received_qty = 5) + doc_pr.submit() + + doc_mr.reload() + self.assertEqual(doc_pr.status, "To Bill") + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Stock Received But Not Billed - _TC'}): + gl_temp_credit = frappe.db.get_value('GL Entry',{'voucher_no':doc_pr.name, 'account': 'Stock Received But Not Billed - _TC'},'credit') + self.assertEqual(gl_temp_credit, 500) + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Stock In Hand - _TC'}): + gl_stock_debit = frappe.db.get_value('GL Entry',{'voucher_no':doc_pr.name, 'account': 'Stock In Hand - _TC'},'debit') + self.assertEqual(gl_stock_debit, 500) + + doc_pr1 = make_test_pr(doc_po.name) + doc_pr1.items[0].accepted_qty = 5 + doc_pr1.submit() + + self.assertEqual(doc_pr1.docstatus, 1) + doc_pr1.reload() + self.assertEqual(doc_pr1.status, "To Bill") + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Stock Received But Not Billed - _TC'}): + gl_temp_credit = frappe.db.get_value('GL Entry',{'voucher_no':doc_pr1.name, 'account': 'Stock Received But Not Billed - _TC'},'credit') + self.assertEqual(gl_temp_credit, 500) + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Stock In Hand - _TC'}): + gl_stock_debit = frappe.db.get_value('GL Entry',{'voucher_no':doc_pr1.name, 'account': 'Stock In Hand - _TC'},'debit') + self.assertEqual(gl_stock_debit, 500) + + #Return PI's + doc_pr.load_from_db() + from erpnext.controllers.sales_and_purchase_return import make_return_doc + return_pr = make_return_doc("Purchase Receipt", doc_pr.name) + return_pr.get("items")[0].received_qty = -5 + return_pr.submit() + + doc_pr.reload() + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Stock Received But Not Billed - _TC'}): + gl_temp_credit = frappe.db.get_value('GL Entry',{'voucher_no':doc_pr.name, 'account': 'Stock Received But Not Billed - _TC'},'credit') + self.assertEqual(gl_temp_credit, 500) + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Stock In Hand - _TC'}): + gl_stock_debit = frappe.db.get_value('GL Entry',{'voucher_no':doc_pr.name, 'account': 'Stock In Hand - _TC'},'debit') + self.assertEqual(gl_stock_debit, 500) + + def test_create_mr_to_2po_to_1pr_part_return_TC_SCK_042(self): + mr = make_material_request() + + #partially qty + po = make_purchase_order(mr.name) + po.supplier = "_Test Supplier" + po.get("items")[0].rate = 100 + po.get("items")[0].qty = 5 + po.insert() + po.submit() + + #remaining qty + po1 = make_purchase_order(mr.name) + po1.supplier = "_Test Supplier" + po1.get("items")[0].rate = 100 + po1.get("items")[0].qty = 5 + po1.insert() + po1.submit() + + pr = make_purchase_receipt(po.name) + pr = make_purchase_receipt(po1.name, target_doc=pr) + pr.submit() + + bin_qty = frappe.db.get_value("Bin", {"item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC"}, "actual_qty") + sle = frappe.get_doc('Stock Ledger Entry',{'voucher_no':pr.name}) + self.assertEqual(sle.qty_after_transaction, bin_qty) + self.assertEqual(sle.warehouse, mr.get("items")[0].warehouse) + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Stock Received But Not Billed - _TC'}): + gl_temp_credit = frappe.db.get_value('GL Entry',{'voucher_no':pr.name, 'account': 'Stock Received But Not Billed - _TC'},'credit') + self.assertEqual(gl_temp_credit, 1000) + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Stock In Hand - _TC'}): + gl_stock_debit = frappe.db.get_value('GL Entry',{'voucher_no':pr.name, 'account': 'Stock In Hand - _TC'},'debit') + self.assertEqual(gl_stock_debit, 1000) + + #Return PI's + pr.load_from_db() + from erpnext.controllers.sales_and_purchase_return import make_return_doc + return_pi = make_return_doc("Purchase Receipt", pr.name) + return_pi.items.pop(0) + return_pi.items[0].received_qty = -5 + return_pi.submit() + + pr.reload() + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Stock Received But Not Billed - _TC'}): + gl_temp_credit = frappe.db.get_value('GL Entry',{'voucher_no':pr.name, 'account': 'Stock Received But Not Billed - _TC'},'credit') + self.assertEqual(gl_temp_credit, 1000) + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Stock In Hand - _TC'}): + gl_stock_debit = frappe.db.get_value('GL Entry',{'voucher_no':pr.name, 'account': 'Stock In Hand - _TC'},'debit') + self.assertEqual(gl_stock_debit, 1000) + + def test_create_mr_to_2po_to_2pi_partial_return_TC_SCK_106(self): + mr = make_material_request() + + #partially qty + po = make_purchase_order(mr.name) + po.supplier = "_Test Supplier" + po.get("items")[0].rate = 100 + po.get("items")[0].qty = 5 + po.insert() + po.submit() + + #remaining qty + po1 = make_purchase_order(mr.name) + po1.supplier = "_Test Supplier" + po1.get("items")[0].rate = 100 + po1.get("items")[0].qty = 5 + po1.insert() + po1.submit() + + pr = create_purchase_invoice(po.name) + pr.submit() + pr1 = create_purchase_invoice(po1.name) + pr1.submit() + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Stock Received But Not Billed - _TC'}): + recive_account = frappe.db.get_value("Company",mr.company,"stock_received_but_not_billed") + gl_temp_credit = frappe.db.get_value('GL Entry',{'voucher_no':pr.name, 'account': recive_account},'debit') + self.assertEqual(gl_temp_credit, 500) + gl_temp_credit = frappe.db.get_value('GL Entry',{'voucher_no':pr1.name, 'account': recive_account},'debit') + self.assertEqual(gl_temp_credit, 500) + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Creditors - _TC'}): + payable_act = frappe.db.get_value("Company",mr.company,"default_payable_account") + gl_stock_debit = frappe.db.get_value('GL Entry',{'voucher_no':pr.name, 'account': payable_act},'credit') + self.assertEqual(gl_stock_debit, 500) + gl_stock_debit = frappe.db.get_value('GL Entry',{'voucher_no':pr1.name, 'account': payable_act},'credit') + self.assertEqual(gl_stock_debit, 500) + + #Return PI's + from erpnext.controllers.sales_and_purchase_return import make_return_doc + return_pi = make_return_doc("Purchase Invoice", pr.name) + return_pi.submit() + + pr.reload() + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Stock Received But Not Billed - _TC'}): + gl_temp_credit = frappe.db.get_value('GL Entry',{'voucher_no':pr.name, 'account': 'Stock Received But Not Billed - _TC'},'debit') + self.assertEqual(gl_temp_credit, 500) + gl_temp_credit = frappe.db.get_value('GL Entry',{'voucher_no':pr1.name, 'account': 'Stock Received But Not Billed - _TC'},'debit') + self.assertEqual(gl_temp_credit, 500) + + #if account setup in company + if frappe.db.exists('GL Entry',{'account': 'Creditors - _TC'}): + gl_stock_debit = frappe.db.get_value('GL Entry',{'voucher_no':pr.name, 'account': 'Creditors - _TC'},'credit') + self.assertEqual(gl_stock_debit, 500) + gl_stock_debit = frappe.db.get_value('GL Entry',{'voucher_no':pr1.name, 'account': 'Creditors - _TC'},'credit') + self.assertEqual(gl_stock_debit, 500) + def get_in_transit_warehouse(company): if not frappe.db.exists("Warehouse Type", "Transit"): frappe.get_doc( diff --git a/erpnext/stock/doctype/pick_list/test_pick_list.py b/erpnext/stock/doctype/pick_list/test_pick_list.py index ef74ec9d6274..816b2260c266 100644 --- a/erpnext/stock/doctype/pick_list/test_pick_list.py +++ b/erpnext/stock/doctype/pick_list/test_pick_list.py @@ -20,6 +20,7 @@ from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import ( EmptyStockReconciliationItemsError, ) +from frappe.utils import add_days, add_months, flt, getdate, nowdate test_dependencies = ["Item", "Sales Invoice", "Stock Entry", "Batch"] @@ -1266,3 +1267,196 @@ def test_ignore_pricing_rule_in_pick_list(self): delivery_note = create_delivery_note(pl.name) self.assertEqual(len(delivery_note.items), 1) + + + def test_quotation_to_sales_invoice_with_pick_list_TC_S_085(self): + from erpnext.selling.doctype.quotation.quotation import make_sales_order + from erpnext.selling.doctype.quotation.test_quotation import make_quotation + from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry + from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice + + make_stock_entry(item="_Test Item Home Desktop 100", target="Stores - _TC", qty=10, rate=4000) + quotation = make_quotation( + item="_Test Item Home Desktop 100", + qty=4, + rate=5000, + warehouse="Stores - _TC", + ) + quotation.save() + quotation.submit() + self.assertEqual(quotation.status, "Open") + + sales_order = make_sales_order(quotation.name) + sales_order.delivery_date = add_days(nowdate(), 5) + sales_order.insert() + sales_order.submit() + + self.assertEqual(sales_order.status, "To Deliver and Bill") + quotation.reload() + self.assertEqual(quotation.status, "Ordered") + # Pick list + pick_list = create_pick_list(sales_order.name) + pick_list.save() + pick_list.submit() + # Delivery note + delivery_note = create_delivery_note(pick_list.name) + delivery_note.save() + delivery_note.submit() + + stock_check(self,delivery_note.name,-4) + + # sales invoice + sales_invoice = make_sales_invoice(delivery_note.name) + sales_invoice.insert() + sales_invoice.submit() + validate_gl_entries(self, sales_invoice.name, 20000) + + def test_sales_order_to_sales_invoice_with_pick_list_TC_S_086(self): + from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry + from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice + + make_stock_entry(item="_Test Item Home Desktop 100", target="Stores - _TC", qty=5, rate=4000) + + sales_order = make_sales_order(item_code="_Test Item Home Desktop 100", qty=4, rate=5000) + self.assertEqual(sales_order.status, "To Deliver and Bill") + + # Pick list + pick_list = create_pick_list(sales_order.name) + pick_list.save() + pick_list.submit() + # Delivery note + delivery_note = create_delivery_note(pick_list.name) + delivery_note.save() + delivery_note.submit() + + stock_check(self,delivery_note.name,-4) + + # sales invoice + sales_invoice = make_sales_invoice(delivery_note.name) + sales_invoice.insert() + sales_invoice.submit() + validate_gl_entries(self, sales_invoice.name, 20000) + + def test_sales_order_to_sales_invoice_with_double_entries_TC_S_087(self): + from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry + from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice + + make_stock_entry(item="_Test Item Home Desktop 100", target="Stores - _TC", qty=5, rate=4000) + + sales_order = make_sales_order(item_code="_Test Item Home Desktop 100", qty=4, rate=5000) + self.assertEqual(sales_order.status, "To Deliver and Bill") + + # Pick list + pick_list_1 = create_pick_list(sales_order.name) + pick_list_1.save() + for i in pick_list_1.locations: + i.qty = 2 + i.stock_qty = 2 + pick_list_1.submit() + # Delivery note + delivery_note_1 = create_delivery_note(pick_list_1.name) + delivery_note_1.save() + delivery_note_1.submit() + + stock_check(self,delivery_note_1.name,-2) + + # sales invoice + sales_invoice_1 = make_sales_invoice(delivery_note_1.name) + sales_invoice_1.insert() + sales_invoice_1.submit() + validate_gl_entries(self, sales_invoice_1.name, 10000) + + delivery_note_1.reload() + self.assertEqual(sales_invoice_1.status, "Unpaid") + self.assertEqual(delivery_note_1.status, "Completed") + + # Pick list + pick_list_2 = create_pick_list(sales_order.name) + pick_list_2.save() + for i in pick_list_2.locations: + i.qty = 2 + i.stock_qty = 2 + pick_list_2.submit() + # Delivery note + delivery_note_2 = create_delivery_note(pick_list_2.name) + delivery_note_2.save() + delivery_note_2.submit() + + stock_check(self,delivery_note_2.name,-2) + + # sales invoice + sales_invoice_2 = make_sales_invoice(delivery_note_2.name) + sales_invoice_2.insert() + sales_invoice_2.submit() + validate_gl_entries(self, sales_invoice_2.name, 10000) + + sales_order.reload() + delivery_note_2.reload() + self.assertEqual(sales_invoice_2.status, "Unpaid") + self.assertEqual(sales_order.status, "Completed") + self.assertEqual(delivery_note_2.status, "Completed") + + + def test_sales_order_to_sales_invoice_with_2_SI_TC_S_088(self): + from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry + from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice + + make_stock_entry(item="_Test Item Home Desktop 100", target="Stores - _TC", qty=5, rate=4000) + + sales_order = make_sales_order(item_code="_Test Item Home Desktop 100", qty=4, rate=5000) + self.assertEqual(sales_order.status, "To Deliver and Bill") + + # Pick list + pick_list = create_pick_list(sales_order.name) + pick_list.save() + pick_list.submit() + # Delivery note + delivery_note = create_delivery_note(pick_list.name) + delivery_note.save() + delivery_note.submit() + + stock_check(self,delivery_note.name,-4) + self.assertEqual(delivery_note.status, "To Bill") + + + # sales invoice + sales_invoice_1 = make_sales_invoice(delivery_note.name) + for i in sales_invoice_1.items: + i.qty=2 + sales_invoice_1.insert() + sales_invoice_1.submit() + validate_gl_entries(self, sales_invoice_1.name, 10000) + self.assertEqual(sales_invoice_1.status, "Unpaid") + + + sales_invoice_2 = make_sales_invoice(delivery_note.name) + for i in sales_invoice_2.items: + i.qty=2 + sales_invoice_2.insert() + sales_invoice_2.submit() + validate_gl_entries(self, sales_invoice_2.name, 10000) + self.assertEqual(sales_invoice_2.status, "Unpaid") + + sales_order.reload() + delivery_note.reload() + self.assertEqual(sales_order.status, "Completed") + self.assertEqual(delivery_note.status, "Completed") + +def stock_check(self,voucher,qty): + stock_entries = frappe.get_all( + "Stock Ledger Entry", + filters={"voucher_no":voucher, "warehouse": "Stores - _TC"}, + fields=["actual_qty"] + ) + self.assertEqual(sum([entry.actual_qty for entry in stock_entries]), qty) + +def validate_gl_entries(self, voucher_no, amount): + debtor_account = frappe.db.get_value("Company", "_Test Company", "default_receivable_account") + sales_account = frappe.db.get_value("Company", "_Test Company", "default_income_account") + gl_entries = frappe.get_all("GL Entry", filters={"voucher_no": voucher_no}, fields=["account", "debit", "credit"]) + + gl_debits = {entry.account: entry.debit for entry in gl_entries} + gl_credits = {entry.account: entry.credit for entry in gl_entries} + + self.assertAlmostEqual(gl_debits[debtor_account], amount) + self.assertAlmostEqual(gl_credits[sales_account], amount) \ No newline at end of file diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 5ba8c280a862..9c1b1528f863 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -4345,6 +4345,48 @@ def check_gl_entry(self, doc, expected_values): self.assertEqual(expected_values[gle.account][0], gle.debit) self.assertEqual(expected_values[gle.account][1], gle.credit) + def test_pr_with_additional_discount_TC_B_056(self): + company = "_Test Company" + item_code = "Testing-31" + target_warehouse = "Stores - _TC" + supplier = "_Test Supplier 1" + item_price = 10000 + if not frappe.db.exists("Item", item_code): + frappe.get_doc({ + "doctype": "Item", + "item_code": item_code, + "item_name": item_code, + "is_stock_item": 1, + "is_purchase_item": 1, + "is_sales_item": 0, + "company": company + }).insert() + pi = frappe.get_doc({ + "doctype": "Purchase Receipt", + "supplier": supplier, + "company": company, + "posting_date": today(), + "set_warehouse": target_warehouse, + "items": [ + { + "item_code": item_code, + "warehouse": target_warehouse, + "qty": 1, + "rate": item_price + } + ] + }) + pi.insert() + self.assertEqual(len(pi.items), 1) + self.assertEqual(pi.items[0].rate, item_price) + self.assertEqual(pi.net_total, item_price) + pi.apply_discount_on = "Net Total" + pi.additional_discount_percentage = 10 + pi.save() + self.assertEqual(pi.discount_amount, 1000) + self.assertEqual(pi.net_total, 9000) + pi.submit() + def prepare_data_for_internal_transfer(): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py index f1a8ace7ba41..3fdd103d2c69 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py @@ -1562,7 +1562,7 @@ def get_type_of_transaction(parent_doc, child_row): elif parent_doc.get("doctype") == "Stock Reconciliation": type_of_transaction = "Inward" - if parent_doc.get("is_return"): + if parent_doc.get("is_return") and parent_doc.get("doctype") != "Stock Entry": type_of_transaction = "Inward" if ( parent_doc.get("doctype") in ["Purchase Receipt", "Purchase Invoice"] diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 78dde44f7446..7a3a16a11b49 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -200,7 +200,6 @@ def validate(self): self.validate_purpose() self.validate_item() self.validate_customer_provided_item() - self.validate_qty() self.set_transfer_qty() self.validate_uom_is_integer("uom", "qty") self.validate_uom_is_integer("stock_uom", "transfer_qty") @@ -231,7 +230,7 @@ def validate(self): self.validate_serialized_batch() self.calculate_rate_and_amount() self.validate_putaway_capacity() - self.validate_component_quantities() + self.validate_component_and_quantities() if not self.get("purpose") == "Manufacture": # ignore scrap item wh difference and empty source/target wh @@ -425,39 +424,6 @@ def validate_item(self): flt(item.qty) * flt(item.conversion_factor), self.precision("transfer_qty", item) ) - def validate_qty(self): - manufacture_purpose = ["Manufacture", "Material Consumption for Manufacture"] - - if self.purpose in manufacture_purpose and self.work_order: - if not frappe.get_value("Work Order", self.work_order, "skip_transfer"): - item_code = [] - for item in self.items: - if cstr(item.t_warehouse) == "": - req_items = frappe.get_all( - "Work Order Item", - filters={"parent": self.work_order, "item_code": item.item_code}, - fields=["item_code"], - ) - - transferred_materials = frappe.db.sql( - """ - select - sum(sed.qty) as qty - from `tabStock Entry` se,`tabStock Entry Detail` sed - where - se.name = sed.parent and se.docstatus=1 and - (se.purpose='Material Transfer for Manufacture' or se.purpose='Manufacture') - and sed.item_code=%s and se.work_order= %s and ifnull(sed.t_warehouse, '') != '' - """, - (item.item_code, self.work_order), - as_dict=1, - ) - - stock_qty = flt(item.qty) - trans_qty = flt(transferred_materials[0].qty) - if req_items: - if stock_qty > trans_qty: - item_code.append(item.item_code) def validate_fg_completed_qty(self): item_wise_qty = {} @@ -710,7 +676,7 @@ def set_actual_qty(self): title=_("Insufficient Stock"), ) - def validate_component_quantities(self): + def validate_component_and_quantities(self): if self.purpose not in ["Manufacture", "Material Transfer for Manufacture"]: return @@ -723,20 +689,29 @@ def validate_component_quantities(self): raw_materials = self.get_bom_raw_materials(self.fg_completed_qty) precision = frappe.get_precision("Stock Entry Detail", "qty") - for row in self.items: - if not row.s_warehouse: - continue - - if details := raw_materials.get(row.item_code): - if flt(details.get("qty"), precision) != flt(row.qty, precision): + for item_code, details in raw_materials.items(): + if matched_item := self.get_matched_items(item_code): + if flt(details.get("qty"), precision) != flt(matched_item.qty, precision): frappe.throw( _("For the item {0}, the quantity should be {1} according to the BOM {2}.").format( - frappe.bold(row.item_code), - flt(details.get("qty"), precision), + frappe.bold(item_code), + flt(details.get("qty")), get_link_to_form("BOM", self.bom_no), ), title=_("Incorrect Component Quantity"), ) + else: + frappe.throw( + _("According to the BOM {0}, the Item '{1}' is missing in the stock entry.").format( + get_link_to_form("BOM", self.bom_no), frappe.bold(item_code) + ), + title=_("Missing Item"), + ) + def get_matched_items(self, item_code): + for row in self.items: + if row.item_code == item_code: + return row + return {} @frappe.whitelist() diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index 5705da9dd984..e04003f7a4f1 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -20,6 +20,7 @@ ) from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse from erpnext.stock.doctype.material_request.test_material_request import get_gle, make_material_request +from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt from erpnext.stock.doctype.serial_no.serial_no import * from erpnext.stock.doctype.stock_entry.stock_entry import FinishedGoodError, make_stock_in_entry from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry @@ -1960,6 +1961,156 @@ def test_stock_entry_ledgers_for_mr_purpose_and_TC_SCK_052(self): self.assertEqual(sh_gle[0], sh_gle[1]) self.assertEqual(sa_gle[0], sa_gle[1]) + def test_create_stock_repack_via_bom_TC_SCK_016(self): + self.create_stock_repack_via_bom_TC_SCK_016() + + def test_create_and_cancel_stock_repack_via_bom_TC_SCK_065(self): + se = self.create_stock_repack_via_bom_TC_SCK_016() + se.cancel() + + sl_entry_cancelled = frappe.db.get_all( + "Stock Ledger Entry", + {"voucher_type": "Stock Entry", "voucher_no": se.name}, + ["actual_qty", "warehouse"], + order_by="creation", + ) + warehouse_qty = { + "_Test Target Warehouse - _TC": 0, + "_Test Warehouse - _TC": 0 + } + + for sle in sl_entry_cancelled: + warehouse_qty[sle.get('warehouse')] += sle.get('actual_qty') + + self.assertEqual(len(sl_entry_cancelled), 4) + self.assertEqual(warehouse_qty["_Test Target Warehouse - _TC"], 0) + self.assertEqual(warehouse_qty["_Test Warehouse - _TC"], 0) + + def create_stock_repack_via_bom_TC_SCK_016(self): + t_warehouse = create_warehouse( + warehouse_name="_Test Target Warehouse", + properties={"parent_warehouse": "All Warehouses - _TC"}, + company="_Test Company", + ) + fields = { + "is_stock_item": 1, + "stock_uom": "Kg", + "uoms": [ + { + 'uom': "Kg", + "conversion_factor": 1 + }, + { + 'uom': "Tonne", + "conversion_factor": 1000 + } + ] + } + if frappe.db.has_column("Item", "gst_hsn_code"): + fields["gst_hsn_code"] = "01011010" + + item_wheet = make_item("_Test Item Wheet", properties=fields).name + fields["stock_uom"]= "Nos" + fields["uoms"]= [ + { + 'uom': "Nos", + "conversion_factor": 1 + }, + { + 'uom': "Kg", + "conversion_factor": 10 + } + ] + item_wheet_bag = make_item("_Test Item Wheet 10Kg Bag", properties=fields).name + + # Create Purchase Receipt + pr = make_purchase_receipt(item_code=item_wheet, qty=1, rate=20000, uom="Tonne", stock_uom="Kg", conversion_factor=1000) + + # Check Stock Ledger Entries + self.check_stock_ledger_entries( + "Purchase Receipt", + pr.name, + [ + ["_Test Item Wheet", "_Test Warehouse - _TC", 1000], + ] + ) + + # Create BOM + rm_items=[{ + "item_code": item_wheet, + "qty": 10, + "uom": "Kg" + }] + bom_doc = create_bom( + item_wheet_bag, rm_items + ) + + # Create Repack + se = make_stock_entry( + item_code=item_wheet, + expense_account="Stock Adjustment - _TC", + company = "_Test Company", + purpose="Repack", + qty=10, + do_not_submit=True, + do_not_save=True + ) + se.from_bom = 1 + se.bom_no = bom_doc.name + se.fg_completed_qty = 10 + se.get_items() + se.items[0].s_warehouse = "_Test Warehouse - _TC" + se.items[0].t_warehouse = None + se.items[1].s_warehouse = None + se.items[1].t_warehouse = t_warehouse + se.save() + se.submit() + + # Check Stock Ledger Entries + self.check_stock_ledger_entries( + "Stock Entry", + se.name, + [ + ['_Test Item Wheet 10Kg Bag', '_Test Target Warehouse - _TC', 10.0], + ['_Test Item Wheet', '_Test Warehouse - _TC', -100.0], + ] + ) + + return se + + +def create_bom(bom_item, rm_items, company=None, qty=None, properties=None): + bom = frappe.new_doc("BOM") + bom.update( + { + "item": bom_item or "_Test Item", + "company": company or "_Test Company", + "quantity": qty or 1, + } + ) + if properties: + bom.update(properties) + + for item in rm_items: + item_args = {} + + item_args.update( + { + "item_code": item.get('item_code'), + "qty": item.get('qty'), + "uom": item.get('uom'), + "rate": item.get('rate') + } + ) + + bom.append("items", item_args) + + bom.save(ignore_permissions=True) + bom.submit() + + return bom + + def make_serialized_item(**args): args = frappe._dict(args) se = frappe.copy_doc(test_records[0]) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index cf885c5f276b..622d2ce25702 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -1053,6 +1053,7 @@ def get_current_qty_for_batch_nos(self, doc): posting_date=doc.posting_date, posting_time=doc.posting_time, ignore_voucher_nos=[doc.voucher_no], + for_stock_levels=True, ) or 0 ) * -1 diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py index 3f04b06a75cf..bcd26cb473af 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.py +++ b/erpnext/stock/report/stock_balance/stock_balance.py @@ -315,7 +315,6 @@ def prepare_stock_ledger_entries(self): .where((sle.docstatus < 2) & (sle.is_cancelled == 0)) .orderby(sle.posting_datetime) .orderby(sle.creation) - .orderby(sle.actual_qty) ) query = self.apply_inventory_dimensions_filters(query, sle) diff --git a/erpnext/support/web_form/issues/issues.json b/erpnext/support/web_form/issues/issues.json index f3834c73a7ce..c8a374ecc5b3 100644 --- a/erpnext/support/web_form/issues/issues.json +++ b/erpnext/support/web_form/issues/issues.json @@ -54,7 +54,7 @@ "label": "Status", "max_length": 0, "max_value": 0, - "options": "Open\nReplied\nHold\nClosed", + "options": "Open\nReplied\nOn Hold\nClosed", "read_only": 1, "reqd": 0, "show_in_filter": 1