Merge branch 'develop' into new-sql-functions-syntax-qb-query
This commit is contained in:
@@ -70,7 +70,7 @@ frappe.ui.form.on("Bank Statement Import", {
|
||||
|
||||
frm.get_field("import_file").df.options = {
|
||||
restrictions: {
|
||||
allowed_file_types: [".csv", ".xls", ".xlsx"],
|
||||
allowed_file_types: [".csv", ".xls", ".xlsx", ".TXT", ".txt"],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -81,6 +81,7 @@ frappe.ui.form.on("Bank Statement Import", {
|
||||
|
||||
refresh(frm) {
|
||||
frm.page.hide_icon_group();
|
||||
frm.trigger("toggle_mt940_note");
|
||||
frm.trigger("update_indicators");
|
||||
frm.trigger("import_file");
|
||||
frm.trigger("show_import_log");
|
||||
@@ -192,6 +193,24 @@ frappe.ui.form.on("Bank Statement Import", {
|
||||
});
|
||||
},
|
||||
|
||||
import_mt940_fromat(frm) {
|
||||
frm.trigger("toggle_mt940_note");
|
||||
frm.save();
|
||||
},
|
||||
|
||||
toggle_mt940_note(frm) {
|
||||
if (!frm.doc.import_mt940_fromat) {
|
||||
frm.set_df_property("custom_delimiters", "hidden", 0);
|
||||
frm.set_df_property("google_sheets_url", "hidden", 0);
|
||||
frm.set_df_property("html_5", "hidden", 0);
|
||||
} else {
|
||||
frm.set_df_property("custom_delimiters", "hidden", 1);
|
||||
frm.set_df_property("google_sheets_url", "hidden", 1);
|
||||
frm.set_df_property("html_5", "hidden", 1);
|
||||
}
|
||||
frm.set_value("import_mt940_fromat", frm.doc.import_mt940_fromat);
|
||||
},
|
||||
|
||||
show_report_error_button(frm) {
|
||||
if (frm.doc.status === "Error") {
|
||||
frappe.db
|
||||
@@ -290,6 +309,26 @@ frappe.ui.form.on("Bank Statement Import", {
|
||||
.html(__("Loading import file..."))
|
||||
.appendTo(frm.get_field("import_preview").$wrapper);
|
||||
|
||||
frappe.run_serially([
|
||||
// Convert MT940 to CSV if .txt file
|
||||
() => {
|
||||
if (frm.doc.import_file && frm.doc.import_file.toLowerCase().endsWith(".txt")) {
|
||||
return frm
|
||||
.call({
|
||||
method: "convert_mt940_to_csv",
|
||||
args: {
|
||||
data_import: frm.doc.name,
|
||||
mt940_file_path: frm.doc.import_file,
|
||||
},
|
||||
})
|
||||
.then((r) => {
|
||||
const file_url = r.message;
|
||||
frm.set_value("import_file", file_url);
|
||||
frm.save();
|
||||
});
|
||||
}
|
||||
},
|
||||
() => {
|
||||
frm.call({
|
||||
method: "get_preview_from_template",
|
||||
args: {
|
||||
@@ -308,6 +347,8 @@ frappe.ui.form.on("Bank Statement Import", {
|
||||
frm.events.show_import_warnings(frm, preview_data);
|
||||
});
|
||||
},
|
||||
]);
|
||||
},
|
||||
// method: 'frappe.core.doctype.data_import.data_import.get_preview_from_template',
|
||||
|
||||
show_import_preview(frm, preview_data) {
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"bank_account",
|
||||
"bank",
|
||||
"column_break_4",
|
||||
"import_mt940_fromat",
|
||||
"custom_delimiters",
|
||||
"delimiter_options",
|
||||
"google_sheets_url",
|
||||
@@ -20,6 +21,7 @@
|
||||
"download_template",
|
||||
"status",
|
||||
"template_options",
|
||||
"use_csv_sniffer",
|
||||
"import_warnings_section",
|
||||
"template_warnings",
|
||||
"import_warnings",
|
||||
@@ -207,14 +209,28 @@
|
||||
"fieldname": "delimiter_options",
|
||||
"fieldtype": "Data",
|
||||
"label": "Delimiter options"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "use_csv_sniffer",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Use CSV Sniffer"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "import_mt940_fromat",
|
||||
"fieldtype": "Check",
|
||||
"label": "Import MT940 Fromat"
|
||||
}
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
"links": [],
|
||||
"modified": "2024-06-25 17:32:07.658250",
|
||||
"modified": "2025-06-11 02:23:22.159961",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Bank Statement Import",
|
||||
"naming_rule": "Expression",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
@@ -230,6 +246,7 @@
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
|
||||
@@ -3,15 +3,19 @@
|
||||
|
||||
|
||||
import csv
|
||||
import io
|
||||
import json
|
||||
import re
|
||||
from datetime import date, datetime
|
||||
|
||||
import frappe
|
||||
import mt940
|
||||
import openpyxl
|
||||
from frappe import _
|
||||
from frappe.core.doctype.data_import.data_import import DataImport
|
||||
from frappe.core.doctype.data_import.importer import Importer, ImportFile
|
||||
from frappe.utils.background_jobs import enqueue
|
||||
from frappe.utils.file_manager import get_file, save_file
|
||||
from frappe.utils.xlsxutils import ILLEGAL_CHARACTERS_RE, handle_html
|
||||
from openpyxl.styles import Font
|
||||
from openpyxl.utils import get_column_letter
|
||||
@@ -35,6 +39,7 @@ class BankStatementImport(DataImport):
|
||||
delimiter_options: DF.Data | None
|
||||
google_sheets_url: DF.Data | None
|
||||
import_file: DF.Attach | None
|
||||
import_mt940_fromat: DF.Check
|
||||
import_type: DF.Literal["", "Insert New Records", "Update Existing Records"]
|
||||
mute_emails: DF.Check
|
||||
reference_doctype: DF.Link
|
||||
@@ -43,6 +48,7 @@ class BankStatementImport(DataImport):
|
||||
submit_after_import: DF.Check
|
||||
template_options: DF.Code | None
|
||||
template_warnings: DF.Code | None
|
||||
use_csv_sniffer: DF.Check
|
||||
# end: auto-generated types
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -65,6 +71,7 @@ class BankStatementImport(DataImport):
|
||||
|
||||
self.template_warnings = ""
|
||||
|
||||
if self.import_file and not self.import_file.lower().endswith(".txt"):
|
||||
self.validate_import_file()
|
||||
self.validate_google_sheets_url()
|
||||
|
||||
@@ -104,6 +111,68 @@ class BankStatementImport(DataImport):
|
||||
return None
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def convert_mt940_to_csv(data_import, mt940_file_path):
|
||||
doc = frappe.get_doc("Bank Statement Import", data_import)
|
||||
|
||||
file_doc, content = get_file(mt940_file_path)
|
||||
|
||||
if not is_mt940_format(content):
|
||||
frappe.throw(_("The uploaded file does not appear to be in valid MT940 format."))
|
||||
|
||||
if is_mt940_format(content) and not doc.import_mt940_fromat:
|
||||
frappe.throw(_("MT940 file detected. Please enable 'Import MT940 Format' to proceed."))
|
||||
|
||||
try:
|
||||
transactions = mt940.parse(content)
|
||||
except Exception as e:
|
||||
frappe.throw(_("Failed to parse MT940 format. Error: {0}").format(str(e)))
|
||||
|
||||
if not transactions:
|
||||
frappe.throw(_("Parsed file is not in valid MT940 format or contains no transactions."))
|
||||
|
||||
# Use in-memory file buffer instead of writing to temp file
|
||||
csv_buffer = io.StringIO()
|
||||
writer = csv.writer(csv_buffer)
|
||||
|
||||
headers = ["Date", "Deposit", "Withdrawal", "Description", "Reference Number", "Bank Account", "Currency"]
|
||||
writer.writerow(headers)
|
||||
|
||||
for txn in transactions:
|
||||
txn_date = getattr(txn, "date", None)
|
||||
raw_date = txn.data.get("date", "")
|
||||
|
||||
if txn_date:
|
||||
date_str = txn_date.strftime("%Y-%m-%d")
|
||||
elif isinstance(raw_date, date | datetime):
|
||||
date_str = raw_date.strftime("%Y-%m-%d")
|
||||
else:
|
||||
date_str = str(raw_date)
|
||||
|
||||
raw_amount = str(txn.data.get("amount", ""))
|
||||
parts = raw_amount.strip().split()
|
||||
amount_value = float(parts[0]) if parts else 0.0
|
||||
|
||||
deposit = amount_value if amount_value > 0 else ""
|
||||
withdrawal = abs(amount_value) if amount_value < 0 else ""
|
||||
description = txn.data.get("extra_details") or ""
|
||||
reference = txn.data.get("transaction_reference") or ""
|
||||
currency = txn.data.get("currency", "")
|
||||
|
||||
writer.writerow([date_str, deposit, withdrawal, description, reference, doc.bank_account, currency])
|
||||
|
||||
# Prepare in-memory CSV for upload
|
||||
csv_content = csv_buffer.getvalue().encode("utf-8")
|
||||
csv_buffer.close()
|
||||
|
||||
filename = f"{frappe.utils.now_datetime().strftime('%Y%m%d%H%M%S')}_converted_mt940.csv"
|
||||
|
||||
# Save to File Manager
|
||||
saved_file = save_file(filename, csv_content, doc.doctype, doc.name, is_private=True, df="import_file")
|
||||
|
||||
return saved_file.file_url
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_preview_from_template(data_import, import_file=None, google_sheets_url=None):
|
||||
return frappe.get_doc("Bank Statement Import", data_import).get_preview_from_template(
|
||||
@@ -128,6 +197,12 @@ def download_import_log(data_import_name):
|
||||
return frappe.get_doc("Bank Statement Import", data_import_name).download_import_log()
|
||||
|
||||
|
||||
def is_mt940_format(content: str) -> bool:
|
||||
"""Check if the content has key MT940 tags"""
|
||||
required_tags = [":20:", ":25:", ":28C:", ":61:"]
|
||||
return all(tag in content for tag in required_tags)
|
||||
|
||||
|
||||
def parse_data_from_template(raw_data):
|
||||
data = []
|
||||
|
||||
|
||||
@@ -20,6 +20,39 @@ frappe.ui.form.on("Journal Entry", {
|
||||
"Unreconcile Payment Entries",
|
||||
"Bank Transaction",
|
||||
];
|
||||
|
||||
frm.trigger("set_queries");
|
||||
},
|
||||
|
||||
set_queries(frm) {
|
||||
frm.set_query("periodic_entry_difference_account", function () {
|
||||
return {
|
||||
filters: {
|
||||
is_group: 0,
|
||||
company: frm.doc.company,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("stock_asset_account", function () {
|
||||
return {
|
||||
filters: {
|
||||
is_group: 0,
|
||||
account_type: "Stock",
|
||||
company: frm.doc.company,
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
get_balance_for_periodic_accounting(frm) {
|
||||
frm.call({
|
||||
method: "get_balance_for_periodic_accounting",
|
||||
doc: frm.doc,
|
||||
callback: function (r) {
|
||||
refresh_field("accounts");
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
refresh: function (frm) {
|
||||
|
||||
@@ -13,15 +13,21 @@
|
||||
"title",
|
||||
"voucher_type",
|
||||
"naming_series",
|
||||
"finance_book",
|
||||
"process_deferred_accounting",
|
||||
"reversal_of",
|
||||
"tax_withholding_category",
|
||||
"column_break1",
|
||||
"from_template",
|
||||
"company",
|
||||
"posting_date",
|
||||
"finance_book",
|
||||
"apply_tds",
|
||||
"tax_withholding_category",
|
||||
"section_break_tcvw",
|
||||
"for_all_stock_asset_accounts",
|
||||
"column_break_wpau",
|
||||
"stock_asset_account",
|
||||
"periodic_entry_difference_account",
|
||||
"get_balance_for_periodic_accounting",
|
||||
"2_add_edit_gl_entries",
|
||||
"accounts",
|
||||
"section_break99",
|
||||
@@ -89,7 +95,7 @@
|
||||
"label": "Entry Type",
|
||||
"oldfieldname": "voucher_type",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "Journal Entry\nInter Company Journal Entry\nBank Entry\nCash Entry\nCredit Card Entry\nDebit Note\nCredit Note\nContra Entry\nExcise Entry\nWrite Off Entry\nOpening Entry\nDepreciation Entry\nAsset Disposal\nExchange Rate Revaluation\nExchange Gain Or Loss\nDeferred Revenue\nDeferred Expense",
|
||||
"options": "Journal Entry\nInter Company Journal Entry\nBank Entry\nCash Entry\nCredit Card Entry\nDebit Note\nCredit Note\nContra Entry\nExcise Entry\nWrite Off Entry\nOpening Entry\nDepreciation Entry\nAsset Disposal\nPeriodic Accounting Entry\nExchange Rate Revaluation\nExchange Gain Or Loss\nDeferred Revenue\nDeferred Expense",
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
@@ -543,6 +549,42 @@
|
||||
"label": "Is System Generated",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.voucher_type === \"Periodic Accounting Entry\"",
|
||||
"fieldname": "periodic_entry_difference_account",
|
||||
"fieldtype": "Link",
|
||||
"label": "Periodic Entry Difference Account",
|
||||
"mandatory_depends_on": "eval:doc.voucher_type === \"Periodic Accounting Entry\"",
|
||||
"options": "Account"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.voucher_type === \"Periodic Accounting Entry\"",
|
||||
"fieldname": "section_break_tcvw",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Periodic Accounting"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "for_all_stock_asset_accounts",
|
||||
"fieldtype": "Check",
|
||||
"label": "For All Stock Asset Accounts"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.for_all_stock_asset_accounts === 0",
|
||||
"fieldname": "stock_asset_account",
|
||||
"fieldtype": "Link",
|
||||
"label": "Stock Asset Account",
|
||||
"options": "Account"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_wpau",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "get_balance_for_periodic_accounting",
|
||||
"fieldtype": "Button",
|
||||
"label": "Get Balance"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
@@ -557,7 +599,7 @@
|
||||
"table_fieldname": "payment_entries"
|
||||
}
|
||||
],
|
||||
"modified": "2024-12-26 15:32:20.730666",
|
||||
"modified": "2025-06-17 15:18:13.322681",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Journal Entry",
|
||||
@@ -602,6 +644,7 @@
|
||||
"role": "Auditor"
|
||||
}
|
||||
],
|
||||
"row_format": "Dynamic",
|
||||
"search_fields": "voucher_type,posting_date, due_date, cheque_no",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
|
||||
@@ -62,6 +62,7 @@ class JournalEntry(AccountsController):
|
||||
difference: DF.Currency
|
||||
due_date: DF.Date | None
|
||||
finance_book: DF.Link | None
|
||||
for_all_stock_asset_accounts: DF.Check
|
||||
from_template: DF.Link | None
|
||||
inter_company_journal_entry_reference: DF.Link | None
|
||||
is_opening: DF.Literal["No", "Yes"]
|
||||
@@ -73,11 +74,13 @@ class JournalEntry(AccountsController):
|
||||
paid_loan: DF.Data | None
|
||||
pay_to_recd_from: DF.Data | None
|
||||
payment_order: DF.Link | None
|
||||
periodic_entry_difference_account: DF.Link | None
|
||||
posting_date: DF.Date
|
||||
process_deferred_accounting: DF.Link | None
|
||||
remark: DF.SmallText | None
|
||||
reversal_of: DF.Link | None
|
||||
select_print_heading: DF.Link | None
|
||||
stock_asset_account: DF.Link | None
|
||||
stock_entry: DF.Link | None
|
||||
tax_withholding_category: DF.Link | None
|
||||
title: DF.Data | None
|
||||
@@ -101,6 +104,7 @@ class JournalEntry(AccountsController):
|
||||
"Opening Entry",
|
||||
"Depreciation Entry",
|
||||
"Asset Disposal",
|
||||
"Periodic Accounting Entry",
|
||||
"Exchange Rate Revaluation",
|
||||
"Exchange Gain Or Loss",
|
||||
"Deferred Revenue",
|
||||
@@ -198,6 +202,76 @@ class JournalEntry(AccountsController):
|
||||
self.update_inter_company_jv()
|
||||
self.update_invoice_discounting()
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_balance_for_periodic_accounting(self):
|
||||
self.validate_company_for_periodic_accounting()
|
||||
|
||||
stock_accounts = self.get_stock_accounts_for_periodic_accounting()
|
||||
self.set("accounts", [])
|
||||
for account in stock_accounts:
|
||||
account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(
|
||||
account, self.posting_date, self.company
|
||||
)
|
||||
|
||||
difference_value = flt(stock_bal - account_bal, self.precision("difference"))
|
||||
|
||||
if difference_value == 0:
|
||||
frappe.msgprint(
|
||||
_("No difference found for stock account {0}").format(frappe.bold(account)),
|
||||
alert=True,
|
||||
)
|
||||
continue
|
||||
|
||||
self.append(
|
||||
"accounts",
|
||||
{
|
||||
"account": account,
|
||||
"debit_in_account_currency": difference_value if difference_value > 0 else 0,
|
||||
"credit_in_account_currency": abs(difference_value) if difference_value < 0 else 0,
|
||||
},
|
||||
)
|
||||
|
||||
self.append(
|
||||
"accounts",
|
||||
{
|
||||
"account": self.periodic_entry_difference_account,
|
||||
"credit_in_account_currency": difference_value if difference_value > 0 else 0,
|
||||
"debit_in_account_currency": abs(difference_value) if difference_value < 0 else 0,
|
||||
},
|
||||
)
|
||||
|
||||
def validate_company_for_periodic_accounting(self):
|
||||
if erpnext.is_perpetual_inventory_enabled(self.company):
|
||||
frappe.throw(
|
||||
_(
|
||||
"Periodic Accounting Entry is not allowed for company {0} with perpetual inventory enabled"
|
||||
).format(self.company)
|
||||
)
|
||||
|
||||
if not self.periodic_entry_difference_account:
|
||||
frappe.throw(_("Please select Periodic Accounting Entry Difference Account"))
|
||||
|
||||
def get_stock_accounts_for_periodic_accounting(self):
|
||||
if self.voucher_type != "Periodic Accounting Entry":
|
||||
return []
|
||||
|
||||
if self.for_all_stock_asset_accounts:
|
||||
return frappe.get_all(
|
||||
"Account",
|
||||
filters={
|
||||
"company": self.company,
|
||||
"account_type": "Stock",
|
||||
"root_type": "Asset",
|
||||
"is_group": 0,
|
||||
},
|
||||
pluck="name",
|
||||
)
|
||||
|
||||
if not self.stock_asset_account:
|
||||
frappe.throw(_("Please select Stock Asset Account"))
|
||||
|
||||
return [self.stock_asset_account]
|
||||
|
||||
def on_update_after_submit(self):
|
||||
# Flag will be set on Reconciliation
|
||||
# Reconciliation tool will anyways repost ledger entries. So, no need to check and do implicit repost.
|
||||
@@ -280,6 +354,10 @@ class JournalEntry(AccountsController):
|
||||
frappe.throw(_("Account {0} should be of type Expense").format(d.account))
|
||||
|
||||
def validate_stock_accounts(self):
|
||||
if self.voucher_type == "Periodic Accounting Entry":
|
||||
# Skip validation for periodic accounting entry
|
||||
return
|
||||
|
||||
stock_accounts = get_stock_accounts(self.company, accounts=self.accounts)
|
||||
for account in stock_accounts:
|
||||
account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(
|
||||
|
||||
@@ -788,7 +788,6 @@ class TestPaymentRequest(IntegrationTestCase):
|
||||
pr = make_payment_request(dt="Sales Invoice", dn=si.name, mute_email=1)
|
||||
self.assertEqual(pr.grand_total, si.outstanding_amount)
|
||||
|
||||
|
||||
def test_partial_paid_invoice_with_submitted_payment_entry(self):
|
||||
pi = make_purchase_invoice(currency="INR", qty=1, rate=5000)
|
||||
pi.save()
|
||||
|
||||
@@ -24,7 +24,7 @@ def get_chart_data(data, conditions, filters):
|
||||
|
||||
datapoints = []
|
||||
|
||||
start = 2 if filters.get("based_on") in ["Item", "Supplier"] else 1
|
||||
start = 3 if filters.get("based_on") in ["Item", "Supplier"] else 1
|
||||
if filters.get("group_by"):
|
||||
start += 1
|
||||
|
||||
|
||||
@@ -98,9 +98,10 @@ def get_data(filters, conditions):
|
||||
sel_col = "t1.supplier"
|
||||
|
||||
if filters.get("based_on") in ["Item", "Customer", "Supplier"]:
|
||||
inc = 2
|
||||
inc = 3
|
||||
else:
|
||||
inc = 1
|
||||
|
||||
data1 = frappe.db.sql(
|
||||
""" select {} from `tab{}` t1, `tab{} Item` t2 {}
|
||||
where t2.parent = t1.name and t1.company = {} and {} between {} and {} and
|
||||
@@ -330,11 +331,20 @@ def based_wise_columns_query(based_on, trans):
|
||||
based_on_details["addl_tables"] = ""
|
||||
|
||||
elif based_on == "Customer":
|
||||
if trans == "Quotation":
|
||||
based_on_details["based_on_cols"] = [
|
||||
"Customer:Link/Customer:120",
|
||||
"Party:Link/Customer:120",
|
||||
"Party Name:Data:120",
|
||||
"Territory:Link/Territory:120",
|
||||
]
|
||||
based_on_details["based_on_select"] = "t1.customer_name, t1.territory, "
|
||||
based_on_details["based_on_select"] = "t1.party_name, t1.customer_name, t1.territory,"
|
||||
else:
|
||||
based_on_details["based_on_cols"] = [
|
||||
"Customer:Link/Customer:120",
|
||||
"Customer Name:Data:120",
|
||||
"Territory:Link/Territory:120",
|
||||
]
|
||||
based_on_details["based_on_select"] = "t1.customer, t1.customer_name, t1.territory,"
|
||||
based_on_details["based_on_group_by"] = "t1.party_name" if trans == "Quotation" else "t1.customer"
|
||||
based_on_details["addl_tables"] = ""
|
||||
|
||||
@@ -347,9 +357,10 @@ def based_wise_columns_query(based_on, trans):
|
||||
elif based_on == "Supplier":
|
||||
based_on_details["based_on_cols"] = [
|
||||
"Supplier:Link/Supplier:120",
|
||||
"Supplier Name:Data:120",
|
||||
"Supplier Group:Link/Supplier Group:140",
|
||||
]
|
||||
based_on_details["based_on_select"] = "t1.supplier, t3.supplier_group,"
|
||||
based_on_details["based_on_select"] = "t1.supplier, t1.supplier_name, t3.supplier_group,"
|
||||
based_on_details["based_on_group_by"] = "t1.supplier"
|
||||
based_on_details["addl_tables"] = ",`tabSupplier` t3"
|
||||
based_on_details["addl_tables_relational_cond"] = " and t1.supplier = t3.name"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -722,7 +722,7 @@ erpnext.utils.update_child_items = function (opts) {
|
||||
} = r.message;
|
||||
|
||||
const row = dialog.fields_dict.trans_items.df.data.find(
|
||||
(doc) => doc.idx == me.doc.idx
|
||||
(row) => row.name == me.doc.name
|
||||
);
|
||||
if (row) {
|
||||
Object.assign(row, {
|
||||
|
||||
@@ -181,14 +181,20 @@ frappe.ui.form.on("Sales Order", {
|
||||
}
|
||||
erpnext.queries.setup_queries(frm, "Warehouse", function () {
|
||||
return {
|
||||
filters: [["Warehouse", "company", "in", ["", cstr(frm.doc.company)]]],
|
||||
filters: [
|
||||
["Warehouse", "company", "in", ["", cstr(frm.doc.company)]],
|
||||
["Warehouse", "is_group", "=", 0],
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("warehouse", "items", function (doc, cdt, cdn) {
|
||||
let row = locals[cdt][cdn];
|
||||
let query = {
|
||||
filters: [["Warehouse", "company", "in", ["", cstr(frm.doc.company)]]],
|
||||
filters: [
|
||||
["Warehouse", "company", "in", ["", cstr(frm.doc.company)]],
|
||||
["Warehouse", "is_group", "=", 0],
|
||||
],
|
||||
};
|
||||
if (row.item_code) {
|
||||
query.query = "erpnext.controllers.queries.warehouse_query";
|
||||
|
||||
@@ -25,7 +25,7 @@ def get_chart_data(data, conditions, filters):
|
||||
|
||||
datapoints = []
|
||||
|
||||
start = 2 if filters.get("based_on") in ["Item", "Customer"] else 1
|
||||
start = 3 if filters.get("based_on") in ["Item", "Customer"] else 1
|
||||
if filters.get("group_by"):
|
||||
start += 1
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ def get_chart_data(data, conditions, filters):
|
||||
|
||||
datapoints = []
|
||||
|
||||
start = 2 if filters.get("based_on") in ["Item", "Customer"] else 1
|
||||
start = 3 if filters.get("based_on") in ["Item", "Customer"] else 1
|
||||
if filters.get("group_by"):
|
||||
start += 1
|
||||
|
||||
|
||||
@@ -2016,6 +2016,75 @@ class TestStockEntry(IntegrationTestCase):
|
||||
|
||||
self.assertEqual(se.items[0].basic_rate, 300)
|
||||
|
||||
def test_periodic_accounting_entries(self):
|
||||
item_code = "_Test Periodic Accounting Item"
|
||||
make_item(item_code, {"is_stock_item": 1})
|
||||
|
||||
company = "_Test Periodic Accounting Company"
|
||||
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Company",
|
||||
"company_name": company,
|
||||
"abbr": "_TPC",
|
||||
"default_currency": "INR",
|
||||
"enable_perpetual_inventory": 0,
|
||||
}
|
||||
).insert(ignore_permissions=True)
|
||||
|
||||
warehouse = frappe.db.get_value("Warehouse", {"company": company, "is_group": 0}, "name")
|
||||
|
||||
make_stock_entry(
|
||||
item_code=item_code,
|
||||
qty=10,
|
||||
to_warehouse=warehouse,
|
||||
basic_rate=100,
|
||||
posting_date=add_days(nowdate(), -2),
|
||||
)
|
||||
|
||||
jv = frappe.new_doc("Journal Entry")
|
||||
jv.voucher_type = "Periodic Accounting Entry"
|
||||
jv.posting_date = add_days(nowdate(), -1)
|
||||
jv.posting_time = nowtime()
|
||||
jv.company = company
|
||||
jv.for_all_stock_asset_accounts = 1
|
||||
jv.periodic_entry_difference_account = "Stock Adjustment - _TPC"
|
||||
jv.get_balance_for_periodic_accounting()
|
||||
jv.save()
|
||||
jv.submit()
|
||||
|
||||
self.assertEqual(len(jv.accounts), 2)
|
||||
self.assertEqual(jv.accounts[0].debit_in_account_currency, 1000)
|
||||
self.assertEqual(jv.accounts[1].credit_in_account_currency, 1000)
|
||||
self.assertEqual(jv.accounts[0].account, "Stock In Hand - _TPC")
|
||||
self.assertEqual(jv.accounts[1].account, "Stock Adjustment - _TPC")
|
||||
|
||||
make_stock_entry(
|
||||
item_code=item_code,
|
||||
qty=5,
|
||||
from_warehouse=warehouse,
|
||||
company=company,
|
||||
posting_date=nowdate(),
|
||||
posting_time=nowtime(),
|
||||
)
|
||||
|
||||
jv = frappe.new_doc("Journal Entry")
|
||||
jv.voucher_type = "Periodic Accounting Entry"
|
||||
jv.posting_date = nowdate()
|
||||
jv.posting_time = nowtime()
|
||||
jv.company = company
|
||||
jv.for_all_stock_asset_accounts = 1
|
||||
jv.periodic_entry_difference_account = "Stock Adjustment - _TPC"
|
||||
jv.get_balance_for_periodic_accounting()
|
||||
jv.save()
|
||||
jv.submit()
|
||||
|
||||
self.assertEqual(len(jv.accounts), 2)
|
||||
self.assertEqual(jv.accounts[0].credit_in_account_currency, 500)
|
||||
self.assertEqual(jv.accounts[1].debit_in_account_currency, 500)
|
||||
self.assertEqual(jv.accounts[0].account, "Stock In Hand - _TPC")
|
||||
self.assertEqual(jv.accounts[1].account, "Stock Adjustment - _TPC")
|
||||
|
||||
|
||||
def make_serialized_item(self, **args):
|
||||
args = frappe._dict(args)
|
||||
|
||||
@@ -22,6 +22,9 @@ dependencies = [
|
||||
|
||||
# Not used directly - required by PyQRCode for PNG generation
|
||||
"pypng~=0.20220715.0",
|
||||
|
||||
# MT940 parser for bank statements
|
||||
"mt-940>=4.26.0"
|
||||
]
|
||||
|
||||
[build-system]
|
||||
|
||||
Reference in New Issue
Block a user