Merge branch 'develop' into fix-reserve-qty
diff --git a/CODEOWNERS b/CODEOWNERS
index b450301..c4ea163 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -16,6 +16,7 @@
erpnext/manufacturing/ @rohitwaghchaure @s-aga-r
erpnext/quality_management/ @rohitwaghchaure @s-aga-r
erpnext/stock/ @rohitwaghchaure @s-aga-r
+erpnext/subcontracting @rohitwaghchaure @s-aga-r
erpnext/crm/ @NagariaHussain
erpnext/education/ @rutwikhdev
diff --git a/erpnext/accounts/doctype/bank/bank.js b/erpnext/accounts/doctype/bank/bank.js
index 059e1d3..35d606b 100644
--- a/erpnext/accounts/doctype/bank/bank.js
+++ b/erpnext/accounts/doctype/bank/bank.js
@@ -118,6 +118,10 @@
}
plaid_success(token, response) {
- frappe.show_alert({ message: __('Plaid Link Updated'), indicator: 'green' });
+ frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.update_bank_account_ids', {
+ response: response,
+ }).then(() => {
+ frappe.show_alert({ message: __('Plaid Link Updated'), indicator: 'green' });
+ });
}
};
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 c083189..ae84154 100644
--- a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js
+++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js
@@ -155,7 +155,7 @@
}
},
- render_chart: frappe.utils.debounce((frm) => {
+ render_chart(frm) {
frm.cards_manager = new erpnext.accounts.bank_reconciliation.NumberCardManager(
{
$reconciliation_tool_cards: frm.get_field(
@@ -167,7 +167,7 @@
currency: frm.currency,
}
);
- }, 500),
+ },
render(frm) {
if (frm.doc.bank_account) {
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 4ba6146..c4a23a6 100644
--- a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py
+++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py
@@ -10,7 +10,7 @@
from frappe.query_builder.custom import ConstantColumn
from frappe.utils import cint, flt
-from erpnext.accounts.doctype.bank_transaction.bank_transaction import get_paid_amount
+from erpnext.accounts.doctype.bank_transaction.bank_transaction import get_total_allocated_amount
from erpnext.accounts.report.bank_reconciliation_statement.bank_reconciliation_statement import (
get_amounts_not_reflected_in_system,
get_entries,
@@ -28,7 +28,7 @@
filters = []
filters.append(["bank_account", "=", bank_account])
filters.append(["docstatus", "=", 1])
- filters.append(["unallocated_amount", ">", 0])
+ filters.append(["unallocated_amount", ">", 0.0])
if to_date:
filters.append(["date", "<=", to_date])
if from_date:
@@ -58,7 +58,7 @@
@frappe.whitelist()
def get_account_balance(bank_account, till_date):
# returns account balance till the specified date
- account = frappe.get_cached_value("Bank Account", bank_account, "account")
+ account = frappe.db.get_value("Bank Account", bank_account, "account")
filters = frappe._dict(
{"account": account, "report_date": till_date, "include_pos_transactions": 1}
)
@@ -66,7 +66,7 @@
balance_as_per_system = get_balance_on(filters["account"], filters["report_date"])
- total_debit, total_credit = 0, 0
+ total_debit, total_credit = 0.0, 0.0
for d in data:
total_debit += flt(d.debit)
total_credit += flt(d.credit)
@@ -131,10 +131,8 @@
fieldname=["name", "deposit", "withdrawal", "bank_account"],
as_dict=True,
)[0]
- company_account = frappe.get_cached_value(
- "Bank Account", bank_transaction.bank_account, "account"
- )
- account_type = frappe.get_cached_value("Account", second_account, "account_type")
+ company_account = frappe.get_value("Bank Account", bank_transaction.bank_account, "account")
+ account_type = frappe.db.get_value("Account", second_account, "account_type")
if account_type in ["Receivable", "Payable"]:
if not (party_type and party):
frappe.throw(
@@ -147,10 +145,8 @@
accounts.append(
{
"account": second_account,
- "credit_in_account_currency": bank_transaction.deposit if bank_transaction.deposit > 0 else 0,
- "debit_in_account_currency": bank_transaction.withdrawal
- if bank_transaction.withdrawal > 0
- else 0,
+ "credit_in_account_currency": bank_transaction.deposit,
+ "debit_in_account_currency": bank_transaction.withdrawal,
"party_type": party_type,
"party": party,
}
@@ -160,14 +156,12 @@
{
"account": company_account,
"bank_account": bank_transaction.bank_account,
- "credit_in_account_currency": bank_transaction.withdrawal
- if bank_transaction.withdrawal > 0
- else 0,
- "debit_in_account_currency": bank_transaction.deposit if bank_transaction.deposit > 0 else 0,
+ "credit_in_account_currency": bank_transaction.withdrawal,
+ "debit_in_account_currency": bank_transaction.deposit,
}
)
- company = frappe.get_cached_value("Account", company_account, "company")
+ company = frappe.get_value("Account", company_account, "company")
journal_entry_dict = {
"voucher_type": entry_type,
@@ -187,16 +181,22 @@
journal_entry.insert()
journal_entry.submit()
- if bank_transaction.deposit > 0:
+ if bank_transaction.deposit > 0.0:
paid_amount = bank_transaction.deposit
else:
paid_amount = bank_transaction.withdrawal
vouchers = json.dumps(
- [{"payment_doctype": "Journal Entry", "payment_name": journal_entry.name, "amount": paid_amount}]
+ [
+ {
+ "payment_doctype": "Journal Entry",
+ "payment_name": journal_entry.name,
+ "amount": paid_amount,
+ }
+ ]
)
- return reconcile_vouchers(bank_transaction.name, vouchers)
+ return reconcile_vouchers(bank_transaction_name, vouchers)
@frappe.whitelist()
@@ -220,12 +220,10 @@
as_dict=True,
)[0]
paid_amount = bank_transaction.unallocated_amount
- payment_type = "Receive" if bank_transaction.deposit > 0 else "Pay"
+ payment_type = "Receive" if bank_transaction.deposit > 0.0 else "Pay"
- company_account = frappe.get_cached_value(
- "Bank Account", bank_transaction.bank_account, "account"
- )
- company = frappe.get_cached_value("Account", company_account, "company")
+ company_account = frappe.get_value("Bank Account", bank_transaction.bank_account, "account")
+ company = frappe.get_value("Account", company_account, "company")
payment_entry_dict = {
"company": company,
"payment_type": payment_type,
@@ -261,9 +259,15 @@
payment_entry.submit()
vouchers = json.dumps(
- [{"payment_doctype": "Payment Entry", "payment_name": payment_entry.name, "amount": paid_amount}]
+ [
+ {
+ "payment_doctype": "Payment Entry",
+ "payment_name": payment_entry.name,
+ "amount": paid_amount,
+ }
+ ]
)
- return reconcile_vouchers(bank_transaction.name, vouchers)
+ return reconcile_vouchers(bank_transaction_name, vouchers)
@frappe.whitelist()
@@ -345,59 +349,7 @@
# updated clear date of all the vouchers based on the bank transaction
vouchers = json.loads(vouchers)
transaction = frappe.get_doc("Bank Transaction", bank_transaction_name)
- company_account = frappe.get_cached_value("Bank Account", transaction.bank_account, "account")
-
- if transaction.unallocated_amount == 0:
- frappe.throw(_("This bank transaction is already fully reconciled"))
- total_amount = 0
- for voucher in vouchers:
- voucher["payment_entry"] = frappe.get_doc(voucher["payment_doctype"], voucher["payment_name"])
- total_amount += get_paid_amount(
- frappe._dict(
- {
- "payment_document": voucher["payment_doctype"],
- "payment_entry": voucher["payment_name"],
- }
- ),
- transaction.currency,
- company_account,
- )
-
- if total_amount > transaction.unallocated_amount:
- frappe.throw(
- _(
- "The sum total of amounts of all selected vouchers should be less than the unallocated amount of the bank transaction"
- )
- )
- account = frappe.get_cached_value("Bank Account", transaction.bank_account, "account")
-
- for voucher in vouchers:
- gl_entry = frappe.db.get_value(
- "GL Entry",
- dict(
- account=account, voucher_type=voucher["payment_doctype"], voucher_no=voucher["payment_name"]
- ),
- ["credit_in_account_currency as credit", "debit_in_account_currency as debit"],
- as_dict=1,
- )
- gl_amount, transaction_amount = (
- (gl_entry.credit, transaction.deposit)
- if gl_entry.credit > 0
- else (gl_entry.debit, transaction.withdrawal)
- )
- allocated_amount = gl_amount if gl_amount >= transaction_amount else transaction_amount
-
- transaction.append(
- "payment_entries",
- {
- "payment_document": voucher["payment_entry"].doctype,
- "payment_entry": voucher["payment_entry"].name,
- "allocated_amount": allocated_amount,
- },
- )
-
- transaction.save()
- transaction.update_allocations()
+ transaction.add_payment_entries(vouchers)
return frappe.get_doc("Bank Transaction", bank_transaction_name)
@@ -416,9 +368,9 @@
bank_account = frappe.db.get_values(
"Bank Account", transaction.bank_account, ["account", "company"], as_dict=True
)[0]
- (account, company) = (bank_account.account, bank_account.company)
+ (gl_account, company) = (bank_account.account, bank_account.company)
matching = check_matching(
- account,
+ gl_account,
company,
transaction,
document_types,
@@ -428,7 +380,27 @@
from_reference_date,
to_reference_date,
)
- return matching
+ return subtract_allocations(gl_account, matching)
+
+
+def subtract_allocations(gl_account, vouchers):
+ "Look up & subtract any existing Bank Transaction allocations"
+ copied = []
+ for voucher in vouchers:
+ rows = get_total_allocated_amount(voucher[1], voucher[2])
+ amount = None
+ for row in rows:
+ if row["gl_account"] == gl_account:
+ amount = row["total"]
+ break
+
+ if amount:
+ l = list(voucher)
+ l[3] -= amount
+ copied.append(tuple(l))
+ else:
+ copied.append(voucher)
+ return copied
def check_matching(
@@ -442,6 +414,7 @@
from_reference_date,
to_reference_date,
):
+ exact_match = True if "exact_match" in document_types else False
# combine all types of vouchers
subquery = get_queries(
bank_account,
@@ -453,10 +426,11 @@
filter_by_reference_date,
from_reference_date,
to_reference_date,
+ exact_match,
)
filters = {
"amount": transaction.unallocated_amount,
- "payment_type": "Receive" if transaction.deposit > 0 else "Pay",
+ "payment_type": "Receive" if transaction.deposit > 0.0 else "Pay",
"reference_no": transaction.reference_number,
"party_type": transaction.party_type,
"party": transaction.party,
@@ -465,7 +439,9 @@
matching_vouchers = []
- matching_vouchers.extend(get_loan_vouchers(bank_account, transaction, document_types, filters))
+ matching_vouchers.extend(
+ get_loan_vouchers(bank_account, transaction, document_types, filters, exact_match)
+ )
for query in subquery:
matching_vouchers.extend(
@@ -487,10 +463,10 @@
filter_by_reference_date,
from_reference_date,
to_reference_date,
+ exact_match,
):
# get queries to get matching vouchers
- amount_condition = "=" if "exact_match" in document_types else "<="
- account_from_to = "paid_to" if transaction.deposit > 0 else "paid_from"
+ account_from_to = "paid_to" if transaction.deposit > 0.0 else "paid_from"
queries = []
# get matching queries from all the apps
@@ -501,7 +477,7 @@
company,
transaction,
document_types,
- amount_condition,
+ exact_match,
account_from_to,
from_date,
to_date,
@@ -520,7 +496,7 @@
company,
transaction,
document_types,
- amount_condition,
+ exact_match,
account_from_to,
from_date,
to_date,
@@ -530,8 +506,8 @@
):
queries = []
if "payment_entry" in document_types:
- pe_amount_matching = get_pe_matching_query(
- amount_condition,
+ query = get_pe_matching_query(
+ exact_match,
account_from_to,
transaction,
from_date,
@@ -540,11 +516,11 @@
from_reference_date,
to_reference_date,
)
- queries.extend([pe_amount_matching])
+ queries.append(query)
if "journal_entry" in document_types:
- je_amount_matching = get_je_matching_query(
- amount_condition,
+ query = get_je_matching_query(
+ exact_match,
transaction,
from_date,
to_date,
@@ -552,34 +528,70 @@
from_reference_date,
to_reference_date,
)
- queries.extend([je_amount_matching])
+ queries.append(query)
- if transaction.deposit > 0 and "sales_invoice" in document_types:
- si_amount_matching = get_si_matching_query(amount_condition)
- queries.extend([si_amount_matching])
+ if transaction.deposit > 0.0 and "sales_invoice" in document_types:
+ query = get_si_matching_query(exact_match)
+ queries.append(query)
- if transaction.withdrawal > 0:
+ if transaction.withdrawal > 0.0:
if "purchase_invoice" in document_types:
- pi_amount_matching = get_pi_matching_query(amount_condition)
- queries.extend([pi_amount_matching])
+ query = get_pi_matching_query(exact_match)
+ queries.append(query)
+
+ if "bank_transaction" in document_types:
+ query = get_bt_matching_query(exact_match, transaction)
+ queries.append(query)
return queries
-def get_loan_vouchers(bank_account, transaction, document_types, filters):
+def get_loan_vouchers(bank_account, transaction, document_types, filters, exact_match):
vouchers = []
- amount_condition = True if "exact_match" in document_types else False
- if transaction.withdrawal > 0 and "loan_disbursement" in document_types:
- vouchers.extend(get_ld_matching_query(bank_account, amount_condition, filters))
+ if transaction.withdrawal > 0.0 and "loan_disbursement" in document_types:
+ vouchers.extend(get_ld_matching_query(bank_account, exact_match, filters))
- if transaction.deposit > 0 and "loan_repayment" in document_types:
- vouchers.extend(get_lr_matching_query(bank_account, amount_condition, filters))
+ if transaction.deposit > 0.0 and "loan_repayment" in document_types:
+ vouchers.extend(get_lr_matching_query(bank_account, exact_match, filters))
return vouchers
-def get_ld_matching_query(bank_account, amount_condition, filters):
+def get_bt_matching_query(exact_match, transaction):
+ # get matching bank transaction query
+ # find bank transactions in the same bank account with opposite sign
+ # same bank account must have same company and currency
+ field = "deposit" if transaction.withdrawal > 0.0 else "withdrawal"
+
+ return f"""
+
+ SELECT
+ (CASE WHEN reference_number = %(reference_no)s THEN 1 ELSE 0 END
+ + CASE WHEN {field} = %(amount)s THEN 1 ELSE 0 END
+ + CASE WHEN ( party_type = %(party_type)s AND party = %(party)s ) THEN 1 ELSE 0 END
+ + CASE WHEN unallocated_amount = %(amount)s THEN 1 ELSE 0 END
+ + 1) AS rank,
+ 'Bank Transaction' AS doctype,
+ name,
+ unallocated_amount AS paid_amount,
+ reference_number AS reference_no,
+ date AS reference_date,
+ party,
+ party_type,
+ date AS posting_date,
+ currency
+ FROM
+ `tabBank Transaction`
+ WHERE
+ status != 'Reconciled'
+ AND name != '{transaction.name}'
+ AND bank_account = '{transaction.bank_account}'
+ AND {field} {'= %(amount)s' if exact_match else '> 0.0'}
+ """
+
+
+def get_ld_matching_query(bank_account, exact_match, filters):
loan_disbursement = frappe.qb.DocType("Loan Disbursement")
matching_reference = loan_disbursement.reference_number == filters.get("reference_number")
matching_party = loan_disbursement.applicant_type == filters.get(
@@ -607,17 +619,17 @@
.where(loan_disbursement.disbursement_account == bank_account)
)
- if amount_condition:
+ if exact_match:
query.where(loan_disbursement.disbursed_amount == filters.get("amount"))
else:
- query.where(loan_disbursement.disbursed_amount <= filters.get("amount"))
+ query.where(loan_disbursement.disbursed_amount > 0.0)
vouchers = query.run(as_list=True)
return vouchers
-def get_lr_matching_query(bank_account, amount_condition, filters):
+def get_lr_matching_query(bank_account, exact_match, filters):
loan_repayment = frappe.qb.DocType("Loan Repayment")
matching_reference = loan_repayment.reference_number == filters.get("reference_number")
matching_party = loan_repayment.applicant_type == filters.get(
@@ -648,10 +660,10 @@
if frappe.db.has_column("Loan Repayment", "repay_from_salary"):
query = query.where((loan_repayment.repay_from_salary == 0))
- if amount_condition:
+ if exact_match:
query.where(loan_repayment.amount_paid == filters.get("amount"))
else:
- query.where(loan_repayment.amount_paid <= filters.get("amount"))
+ query.where(loan_repayment.amount_paid > 0.0)
vouchers = query.run()
@@ -659,7 +671,7 @@
def get_pe_matching_query(
- amount_condition,
+ exact_match,
account_from_to,
transaction,
from_date,
@@ -669,7 +681,7 @@
to_reference_date,
):
# get matching payment entries query
- if transaction.deposit > 0:
+ if transaction.deposit > 0.0:
currency_field = "paid_to_account_currency as currency"
else:
currency_field = "paid_from_account_currency as currency"
@@ -684,7 +696,8 @@
return f"""
SELECT
(CASE WHEN reference_no=%(reference_no)s THEN 1 ELSE 0 END
- + CASE WHEN (party_type = %(party_type)s AND party = %(party)s ) THEN 1 ELSE 0 END
+ + CASE WHEN (party_type = %(party_type)s AND party = %(party)s ) THEN 1 ELSE 0 END
+ + CASE WHEN paid_amount = %(amount)s THEN 1 ELSE 0 END
+ 1 ) AS rank,
'Payment Entry' as doctype,
name,
@@ -698,20 +711,19 @@
FROM
`tabPayment Entry`
WHERE
- paid_amount {amount_condition} %(amount)s
- AND docstatus = 1
+ docstatus = 1
AND payment_type IN (%(payment_type)s, 'Internal Transfer')
AND ifnull(clearance_date, '') = ""
AND {account_from_to} = %(bank_account)s
+ AND paid_amount {'= %(amount)s' if exact_match else '> 0.0'}
{filter_by_date}
{filter_by_reference_no}
order by{order_by}
-
"""
def get_je_matching_query(
- amount_condition,
+ exact_match,
transaction,
from_date,
to_date,
@@ -723,7 +735,7 @@
# We have mapping at the bank level
# So one bank could have both types of bank accounts like asset and liability
# So cr_or_dr should be judged only on basis of withdrawal and deposit and not account type
- cr_or_dr = "credit" if transaction.withdrawal > 0 else "debit"
+ cr_or_dr = "credit" if transaction.withdrawal > 0.0 else "debit"
filter_by_date = f"AND je.posting_date between '{from_date}' and '{to_date}'"
order_by = " je.posting_date"
filter_by_reference_no = ""
@@ -735,26 +747,29 @@
return f"""
SELECT
(CASE WHEN je.cheque_no=%(reference_no)s THEN 1 ELSE 0 END
+ + CASE WHEN jea.{cr_or_dr}_in_account_currency = %(amount)s THEN 1 ELSE 0 END
+ 1) AS rank ,
- 'Journal Entry' as doctype,
+ 'Journal Entry' AS doctype,
je.name,
- jea.{cr_or_dr}_in_account_currency as paid_amount,
- je.cheque_no as reference_no,
- je.cheque_date as reference_date,
- je.pay_to_recd_from as party,
+ jea.{cr_or_dr}_in_account_currency AS paid_amount,
+ je.cheque_no AS reference_no,
+ je.cheque_date AS reference_date,
+ je.pay_to_recd_from AS party,
jea.party_type,
je.posting_date,
- jea.account_currency as currency
+ jea.account_currency AS currency
FROM
- `tabJournal Entry Account` as jea
+ `tabJournal Entry Account` AS jea
JOIN
- `tabJournal Entry` as je
+ `tabJournal Entry` AS je
ON
jea.parent = je.name
WHERE
- (je.clearance_date is null or je.clearance_date='0000-00-00')
+ je.docstatus = 1
+ AND je.voucher_type NOT IN ('Opening Entry')
+ AND (je.clearance_date IS NULL OR je.clearance_date='0000-00-00')
AND jea.account = %(bank_account)s
- AND jea.{cr_or_dr}_in_account_currency {amount_condition} %(amount)s
+ AND jea.{cr_or_dr}_in_account_currency {'= %(amount)s' if exact_match else '> 0.0'}
AND je.docstatus = 1
{filter_by_date}
{filter_by_reference_no}
@@ -762,11 +777,12 @@
"""
-def get_si_matching_query(amount_condition):
- # get matchin sales invoice query
+def get_si_matching_query(exact_match):
+ # get matching sales invoice query
return f"""
SELECT
- ( CASE WHEN si.customer = %(party)s THEN 1 ELSE 0 END
+ ( CASE WHEN si.customer = %(party)s THEN 1 ELSE 0 END
+ + CASE WHEN sip.amount = %(amount)s THEN 1 ELSE 0 END
+ 1 ) AS rank,
'Sales Invoice' as doctype,
si.name,
@@ -784,18 +800,20 @@
`tabSales Invoice` as si
ON
sip.parent = si.name
- WHERE (sip.clearance_date is null or sip.clearance_date='0000-00-00')
+ WHERE
+ si.docstatus = 1
+ AND (sip.clearance_date is null or sip.clearance_date='0000-00-00')
AND sip.account = %(bank_account)s
- AND sip.amount {amount_condition} %(amount)s
- AND si.docstatus = 1
+ AND sip.amount {'= %(amount)s' if exact_match else '> 0.0'}
"""
-def get_pi_matching_query(amount_condition):
- # get matching purchase invoice query
+def get_pi_matching_query(exact_match):
+ # get matching purchase invoice query when they are also used as payment entries (is_paid)
return f"""
SELECT
( CASE WHEN supplier = %(party)s THEN 1 ELSE 0 END
+ + CASE WHEN paid_amount = %(amount)s THEN 1 ELSE 0 END
+ 1 ) AS rank,
'Purchase Invoice' as doctype,
name,
@@ -809,9 +827,9 @@
FROM
`tabPurchase Invoice`
WHERE
- paid_amount {amount_condition} %(amount)s
- AND docstatus = 1
+ docstatus = 1
AND is_paid = 1
AND ifnull(clearance_date, '') = ""
- AND cash_bank_account = %(bank_account)s
+ AND cash_bank_account = %(bank_account)s
+ AND paid_amount {'= %(amount)s' if exact_match else '> 0.0'}
"""
diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.js b/erpnext/accounts/doctype/bank_transaction/bank_transaction.js
index 6f2900a..e548b4c 100644
--- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.js
+++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.js
@@ -12,8 +12,13 @@
};
});
},
-
- bank_account: function(frm) {
+ refresh(frm) {
+ frm.add_custom_button(__('Unreconcile Transaction'), () => {
+ frm.call('remove_payment_entries')
+ .then( () => frm.refresh() );
+ });
+ },
+ bank_account: function (frm) {
set_bank_statement_filter(frm);
},
@@ -34,6 +39,7 @@
"Journal Entry",
"Sales Invoice",
"Purchase Invoice",
+ "Bank Transaction",
];
}
});
@@ -49,7 +55,7 @@
frappe
.xcall(
"erpnext.accounts.doctype.bank_transaction.bank_transaction.unclear_reference_payment",
- { doctype: cdt, docname: cdn }
+ { doctype: cdt, docname: cdn, bt_name: frm.doc.name }
)
.then((e) => {
if (e == "success") {
diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.json b/erpnext/accounts/doctype/bank_transaction/bank_transaction.json
index 2bdaa10..768d2f0 100644
--- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.json
+++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.json
@@ -20,9 +20,11 @@
"currency",
"section_break_10",
"description",
- "section_break_14",
"reference_number",
+ "column_break_10",
"transaction_id",
+ "transaction_type",
+ "section_break_14",
"payment_entries",
"section_break_18",
"allocated_amount",
@@ -190,11 +192,21 @@
"label": "Withdrawal",
"oldfieldname": "credit",
"options": "currency"
+ },
+ {
+ "fieldname": "column_break_10",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "transaction_type",
+ "fieldtype": "Data",
+ "label": "Transaction Type",
+ "length": 50
}
],
"is_submittable": 1,
"links": [],
- "modified": "2022-03-21 19:05:04.208222",
+ "modified": "2022-05-29 18:36:50.475964",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Transaction",
@@ -248,4 +260,4 @@
"states": [],
"title_field": "bank_account",
"track_changes": 1
-}
\ No newline at end of file
+}
diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
index 9b36c93..1516237 100644
--- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
+++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
@@ -1,9 +1,6 @@
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
-
-from functools import reduce
-
import frappe
from frappe.utils import flt
@@ -18,72 +15,137 @@
self.clear_linked_payment_entries()
self.set_status()
+ _saving_flag = False
+
+ # nosemgrep: frappe-semgrep-rules.rules.frappe-modifying-but-not-comitting
def on_update_after_submit(self):
- self.update_allocations()
- self.clear_linked_payment_entries()
- self.set_status(update=True)
+ "Run on save(). Avoid recursion caused by multiple saves"
+ if not self._saving_flag:
+ self._saving_flag = True
+ self.clear_linked_payment_entries()
+ self.update_allocations()
+ self._saving_flag = False
def on_cancel(self):
self.clear_linked_payment_entries(for_cancel=True)
self.set_status(update=True)
def update_allocations(self):
+ "The doctype does not allow modifications after submission, so write to the db direct"
if self.payment_entries:
- allocated_amount = reduce(
- lambda x, y: flt(x) + flt(y), [x.allocated_amount for x in self.payment_entries]
- )
+ allocated_amount = sum(p.allocated_amount for p in self.payment_entries)
else:
- allocated_amount = 0
+ allocated_amount = 0.0
- if allocated_amount:
- frappe.db.set_value(self.doctype, self.name, "allocated_amount", flt(allocated_amount))
- frappe.db.set_value(
- self.doctype,
- self.name,
- "unallocated_amount",
- abs(flt(self.withdrawal) - flt(self.deposit)) - flt(allocated_amount),
- )
+ amount = abs(flt(self.withdrawal) - flt(self.deposit))
+ self.db_set("allocated_amount", flt(allocated_amount))
+ self.db_set("unallocated_amount", amount - flt(allocated_amount))
+ self.reload()
+ self.set_status(update=True)
- else:
- frappe.db.set_value(self.doctype, self.name, "allocated_amount", 0)
- frappe.db.set_value(
- self.doctype, self.name, "unallocated_amount", abs(flt(self.withdrawal) - flt(self.deposit))
- )
+ def add_payment_entries(self, vouchers):
+ "Add the vouchers with zero allocation. Save() will perform the allocations and clearance"
+ if 0.0 >= self.unallocated_amount:
+ frappe.throw(frappe._(f"Bank Transaction {self.name} is already fully reconciled"))
- amount = self.deposit or self.withdrawal
- if amount == self.allocated_amount:
- frappe.db.set_value(self.doctype, self.name, "status", "Reconciled")
+ added = False
+ for voucher in vouchers:
+ # Can't add same voucher twice
+ found = False
+ for pe in self.payment_entries:
+ if (
+ pe.payment_document == voucher["payment_doctype"]
+ and pe.payment_entry == voucher["payment_name"]
+ ):
+ found = True
+
+ if not found:
+ pe = {
+ "payment_document": voucher["payment_doctype"],
+ "payment_entry": voucher["payment_name"],
+ "allocated_amount": 0.0, # Temporary
+ }
+ child = self.append("payment_entries", pe)
+ added = True
+
+ # runs on_update_after_submit
+ if added:
+ self.save()
+
+ def allocate_payment_entries(self):
+ """Refactored from bank reconciliation tool.
+ Non-zero allocations must be amended/cleared manually
+ Get the bank transaction amount (b) and remove as we allocate
+ For each payment_entry if allocated_amount == 0:
+ - get the amount already allocated against all transactions (t), need latest date
+ - get the voucher amount (from gl) (v)
+ - allocate (a = v - t)
+ - a = 0: should already be cleared, so clear & remove payment_entry
+ - 0 < a <= u: allocate a & clear
+ - 0 < a, a > u: allocate u
+ - 0 > a: Error: already over-allocated
+ - clear means: set the latest transaction date as clearance date
+ """
+ gl_bank_account = frappe.db.get_value("Bank Account", self.bank_account, "account")
+ remaining_amount = self.unallocated_amount
+ for payment_entry in self.payment_entries:
+ if payment_entry.allocated_amount == 0.0:
+ unallocated_amount, should_clear, latest_transaction = get_clearance_details(
+ self, payment_entry
+ )
+
+ if 0.0 == unallocated_amount:
+ if should_clear:
+ latest_transaction.clear_linked_payment_entry(payment_entry)
+ self.db_delete_payment_entry(payment_entry)
+
+ elif remaining_amount <= 0.0:
+ self.db_delete_payment_entry(payment_entry)
+
+ elif 0.0 < unallocated_amount and unallocated_amount <= remaining_amount:
+ payment_entry.db_set("allocated_amount", unallocated_amount)
+ remaining_amount -= unallocated_amount
+ if should_clear:
+ latest_transaction.clear_linked_payment_entry(payment_entry)
+
+ elif 0.0 < unallocated_amount and unallocated_amount > remaining_amount:
+ payment_entry.db_set("allocated_amount", remaining_amount)
+ remaining_amount = 0.0
+
+ elif 0.0 > unallocated_amount:
+ self.db_delete_payment_entry(payment_entry)
+ frappe.throw(
+ frappe._(f"Voucher {payment_entry.payment_entry} is over-allocated by {unallocated_amount}")
+ )
self.reload()
- def clear_linked_payment_entries(self, for_cancel=False):
+ def db_delete_payment_entry(self, payment_entry):
+ frappe.db.delete("Bank Transaction Payments", {"name": payment_entry.name})
+
+ @frappe.whitelist()
+ def remove_payment_entries(self):
for payment_entry in self.payment_entries:
- if payment_entry.payment_document == "Sales Invoice":
- self.clear_sales_invoice(payment_entry, for_cancel=for_cancel)
- elif payment_entry.payment_document in get_doctypes_for_bank_reconciliation():
- self.clear_simple_entry(payment_entry, for_cancel=for_cancel)
+ self.remove_payment_entry(payment_entry)
+ # runs on_update_after_submit
+ self.save()
- def clear_simple_entry(self, payment_entry, for_cancel=False):
- if payment_entry.payment_document == "Payment Entry":
- if (
- frappe.db.get_value("Payment Entry", payment_entry.payment_entry, "payment_type")
- == "Internal Transfer"
- ):
- if len(get_reconciled_bank_transactions(payment_entry)) < 2:
- return
+ def remove_payment_entry(self, payment_entry):
+ "Clear payment entry and clearance"
+ self.clear_linked_payment_entry(payment_entry, for_cancel=True)
+ self.remove(payment_entry)
- clearance_date = self.date if not for_cancel else None
- frappe.db.set_value(
- payment_entry.payment_document, payment_entry.payment_entry, "clearance_date", clearance_date
- )
+ def clear_linked_payment_entries(self, for_cancel=False):
+ if for_cancel:
+ for payment_entry in self.payment_entries:
+ self.clear_linked_payment_entry(payment_entry, for_cancel)
+ else:
+ self.allocate_payment_entries()
- def clear_sales_invoice(self, payment_entry, for_cancel=False):
- clearance_date = self.date if not for_cancel else None
- frappe.db.set_value(
- "Sales Invoice Payment",
- dict(parenttype=payment_entry.payment_document, parent=payment_entry.payment_entry),
- "clearance_date",
- clearance_date,
+ def clear_linked_payment_entry(self, payment_entry, for_cancel=False):
+ clearance_date = None if for_cancel else self.date
+ set_voucher_clearance(
+ payment_entry.payment_document, payment_entry.payment_entry, clearance_date, self
)
@@ -93,38 +155,112 @@
return frappe.get_hooks("bank_reconciliation_doctypes")
-def get_reconciled_bank_transactions(payment_entry):
- reconciled_bank_transactions = frappe.get_all(
- "Bank Transaction Payments",
- filters={"payment_entry": payment_entry.payment_entry},
- fields=["parent"],
+def get_clearance_details(transaction, payment_entry):
+ """
+ There should only be one bank gle for a voucher.
+ Could be none for a Bank Transaction.
+ But if a JE, could affect two banks.
+ Should only clear the voucher if all bank gles are allocated.
+ """
+ gl_bank_account = frappe.db.get_value("Bank Account", transaction.bank_account, "account")
+ gles = get_related_bank_gl_entries(payment_entry.payment_document, payment_entry.payment_entry)
+ bt_allocations = get_total_allocated_amount(
+ payment_entry.payment_document, payment_entry.payment_entry
)
- return reconciled_bank_transactions
+ unallocated_amount = min(
+ transaction.unallocated_amount,
+ get_paid_amount(payment_entry, transaction.currency, gl_bank_account),
+ )
+ unmatched_gles = len(gles)
+ latest_transaction = transaction
+ for gle in gles:
+ if gle["gl_account"] == gl_bank_account:
+ if gle["amount"] <= 0.0:
+ frappe.throw(
+ frappe._(f"Voucher {payment_entry.payment_entry} value is broken: {gle['amount']}")
+ )
+
+ unmatched_gles -= 1
+ unallocated_amount = gle["amount"]
+ for a in bt_allocations:
+ if a["gl_account"] == gle["gl_account"]:
+ unallocated_amount = gle["amount"] - a["total"]
+ if frappe.utils.getdate(transaction.date) < a["latest_date"]:
+ latest_transaction = frappe.get_doc("Bank Transaction", a["latest_name"])
+ else:
+ # Must be a Journal Entry affecting more than one bank
+ for a in bt_allocations:
+ if a["gl_account"] == gle["gl_account"] and a["total"] == gle["amount"]:
+ unmatched_gles -= 1
+
+ return unallocated_amount, unmatched_gles == 0, latest_transaction
-def get_total_allocated_amount(payment_entry):
- return frappe.db.sql(
+def get_related_bank_gl_entries(doctype, docname):
+ # nosemgrep: frappe-semgrep-rules.rules.frappe-using-db-sql
+ result = frappe.db.sql(
"""
SELECT
- SUM(btp.allocated_amount) as allocated_amount,
- bt.name
+ ABS(gle.credit_in_account_currency - gle.debit_in_account_currency) AS amount,
+ gle.account AS gl_account
FROM
- `tabBank Transaction Payments` as btp
+ `tabGL Entry` gle
LEFT JOIN
- `tabBank Transaction` bt ON bt.name=btp.parent
+ `tabAccount` ac ON ac.name=gle.account
WHERE
- btp.payment_document = %s
- AND
- btp.payment_entry = %s
- AND
- bt.docstatus = 1""",
- (payment_entry.payment_document, payment_entry.payment_entry),
+ ac.account_type = 'Bank'
+ AND gle.voucher_type = %(doctype)s
+ AND gle.voucher_no = %(docname)s
+ AND is_cancelled = 0
+ """,
+ dict(doctype=doctype, docname=docname),
as_dict=True,
)
+ return result
-def get_paid_amount(payment_entry, currency, bank_account):
+def get_total_allocated_amount(doctype, docname):
+ """
+ Gets the sum of allocations for a voucher on each bank GL account
+ along with the latest bank transaction name & date
+ NOTE: query may also include just saved vouchers/payments but with zero allocated_amount
+ """
+ # nosemgrep: frappe-semgrep-rules.rules.frappe-using-db-sql
+ result = frappe.db.sql(
+ """
+ SELECT total, latest_name, latest_date, gl_account FROM (
+ SELECT
+ ROW_NUMBER() OVER w AS rownum,
+ SUM(btp.allocated_amount) OVER(PARTITION BY ba.account) AS total,
+ FIRST_VALUE(bt.name) OVER w AS latest_name,
+ FIRST_VALUE(bt.date) OVER w AS latest_date,
+ ba.account AS gl_account
+ FROM
+ `tabBank Transaction Payments` btp
+ LEFT JOIN `tabBank Transaction` bt ON bt.name=btp.parent
+ LEFT JOIN `tabBank Account` ba ON ba.name=bt.bank_account
+ WHERE
+ btp.payment_document = %(doctype)s
+ AND btp.payment_entry = %(docname)s
+ AND bt.docstatus = 1
+ WINDOW w AS (PARTITION BY ba.account ORDER BY bt.date desc)
+ ) temp
+ WHERE
+ rownum = 1
+ """,
+ dict(doctype=doctype, docname=docname),
+ as_dict=True,
+ )
+ for row in result:
+ # Why is this *sometimes* a byte string?
+ if isinstance(row["latest_name"], bytes):
+ row["latest_name"] = row["latest_name"].decode()
+ row["latest_date"] = frappe.utils.getdate(row["latest_date"])
+ return result
+
+
+def get_paid_amount(payment_entry, currency, gl_bank_account):
if payment_entry.payment_document in ["Payment Entry", "Sales Invoice", "Purchase Invoice"]:
paid_amount_field = "paid_amount"
@@ -147,7 +283,7 @@
elif payment_entry.payment_document == "Journal Entry":
return frappe.db.get_value(
"Journal Entry Account",
- {"parent": payment_entry.payment_entry, "account": bank_account},
+ {"parent": payment_entry.payment_entry, "account": gl_bank_account},
"sum(credit_in_account_currency)",
)
@@ -166,6 +302,12 @@
payment_entry.payment_document, payment_entry.payment_entry, "amount_paid"
)
+ elif payment_entry.payment_document == "Bank Transaction":
+ dep, wth = frappe.db.get_value(
+ "Bank Transaction", payment_entry.payment_entry, ("deposit", "withdrawal")
+ )
+ return abs(flt(wth) - flt(dep))
+
else:
frappe.throw(
"Please reconcile {0}: {1} manually".format(
@@ -174,18 +316,55 @@
)
-@frappe.whitelist()
-def unclear_reference_payment(doctype, docname):
- if frappe.db.exists(doctype, docname):
- doc = frappe.get_doc(doctype, docname)
- if doctype == "Sales Invoice":
- frappe.db.set_value(
- "Sales Invoice Payment",
- dict(parenttype=doc.payment_document, parent=doc.payment_entry),
- "clearance_date",
- None,
- )
- else:
- frappe.db.set_value(doc.payment_document, doc.payment_entry, "clearance_date", None)
+def set_voucher_clearance(doctype, docname, clearance_date, self):
+ if doctype in [
+ "Payment Entry",
+ "Journal Entry",
+ "Purchase Invoice",
+ "Expense Claim",
+ "Loan Repayment",
+ "Loan Disbursement",
+ ]:
+ if (
+ doctype == "Payment Entry"
+ and frappe.db.get_value("Payment Entry", docname, "payment_type") == "Internal Transfer"
+ and len(get_reconciled_bank_transactions(doctype, docname)) < 2
+ ):
+ return
+ frappe.db.set_value(doctype, docname, "clearance_date", clearance_date)
- return doc.payment_entry
+ elif doctype == "Sales Invoice":
+ frappe.db.set_value(
+ "Sales Invoice Payment",
+ dict(parenttype=doctype, parent=docname),
+ "clearance_date",
+ clearance_date,
+ )
+
+ elif doctype == "Bank Transaction":
+ # For when a second bank transaction has fixed another, e.g. refund
+ bt = frappe.get_doc(doctype, docname)
+ if clearance_date:
+ vouchers = [{"payment_doctype": "Bank Transaction", "payment_name": self.name}]
+ bt.add_payment_entries(vouchers)
+ else:
+ for pe in bt.payment_entries:
+ if pe.payment_document == self.doctype and pe.payment_entry == self.name:
+ bt.remove(pe)
+ bt.save()
+ break
+
+
+def get_reconciled_bank_transactions(doctype, docname):
+ return frappe.get_all(
+ "Bank Transaction Payments",
+ filters={"payment_document": doctype, "payment_entry": docname},
+ pluck="parent",
+ )
+
+
+@frappe.whitelist()
+def unclear_reference_payment(doctype, docname, bt_name):
+ bt = frappe.get_doc("Bank Transaction", bt_name)
+ set_voucher_clearance(doctype, docname, None, bt)
+ return docname
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js
index 21f27ae..089f20b 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.js
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js
@@ -8,7 +8,7 @@
frappe.ui.form.on("Journal Entry", {
setup: function(frm) {
frm.add_fetch("bank_account", "account", "account");
- frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry'];
+ frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', "Repost Payment Ledger"];
},
refresh: function(frm) {
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py
index 5b0322a..db399b7 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py
@@ -89,7 +89,13 @@
from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries
unlink_ref_doc_from_payment_entries(self)
- self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Payment Ledger Entry")
+ self.ignore_linked_doctypes = (
+ "GL Entry",
+ "Stock Ledger Entry",
+ "Payment Ledger Entry",
+ "Repost Payment Ledger",
+ "Repost Payment Ledger Items",
+ )
self.make_gl_entries(1)
self.update_advance_paid()
self.unlink_advance_entry_reference()
@@ -238,21 +244,16 @@
):
processed_assets.append(d.reference_name)
- asset = frappe.db.get_value(
- "Asset", d.reference_name, ["calculate_depreciation", "value_after_depreciation"], as_dict=1
- )
+ asset = frappe.get_doc("Asset", d.reference_name)
if asset.calculate_depreciation:
continue
depr_value = d.debit or d.credit
- frappe.db.set_value(
- "Asset",
- d.reference_name,
- "value_after_depreciation",
- asset.value_after_depreciation - depr_value,
- )
+ asset.db_set("value_after_depreciation", asset.value_after_depreciation - depr_value)
+
+ asset.set_status()
def update_inter_company_jv(self):
if (
@@ -348,12 +349,9 @@
else:
depr_value = d.debit or d.credit
- frappe.db.set_value(
- "Asset",
- d.reference_name,
- "value_after_depreciation",
- asset.value_after_depreciation + depr_value,
- )
+ asset.db_set("value_after_depreciation", asset.value_after_depreciation + depr_value)
+
+ asset.set_status()
def unlink_inter_company_jv(self):
if (
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index 6039bdf..91374ae 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -7,7 +7,7 @@
frappe.ui.form.on('Payment Entry', {
onload: function(frm) {
- frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice'];
+ frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', "Repost Payment Ledger"];
if(frm.doc.__islocal) {
if (!frm.doc.paid_from) frm.set_value("paid_from_account_currency", null);
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json
index 4a7a57b..3927eca 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.json
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json
@@ -239,7 +239,7 @@
"depends_on": "paid_from",
"fieldname": "paid_from_account_currency",
"fieldtype": "Link",
- "label": "Account Currency",
+ "label": "Account Currency (From)",
"options": "Currency",
"print_hide": 1,
"read_only": 1,
@@ -249,7 +249,7 @@
"depends_on": "paid_from",
"fieldname": "paid_from_account_balance",
"fieldtype": "Currency",
- "label": "Account Balance",
+ "label": "Account Balance (From)",
"options": "paid_from_account_currency",
"print_hide": 1,
"read_only": 1
@@ -272,7 +272,7 @@
"depends_on": "paid_to",
"fieldname": "paid_to_account_currency",
"fieldtype": "Link",
- "label": "Account Currency",
+ "label": "Account Currency (To)",
"options": "Currency",
"print_hide": 1,
"read_only": 1,
@@ -282,7 +282,7 @@
"depends_on": "paid_to",
"fieldname": "paid_to_account_balance",
"fieldtype": "Currency",
- "label": "Account Balance",
+ "label": "Account Balance (To)",
"options": "paid_to_account_currency",
"print_hide": 1,
"read_only": 1
@@ -304,7 +304,7 @@
{
"fieldname": "source_exchange_rate",
"fieldtype": "Float",
- "label": "Exchange Rate",
+ "label": "Source Exchange Rate",
"precision": "9",
"print_hide": 1,
"reqd": 1
@@ -334,7 +334,7 @@
{
"fieldname": "target_exchange_rate",
"fieldtype": "Float",
- "label": "Exchange Rate",
+ "label": "Target Exchange Rate",
"precision": "9",
"print_hide": 1,
"reqd": 1
@@ -633,14 +633,14 @@
"depends_on": "eval:doc.party_type == 'Supplier'",
"fieldname": "purchase_taxes_and_charges_template",
"fieldtype": "Link",
- "label": "Taxes and Charges Template",
+ "label": "Purchase Taxes and Charges Template",
"options": "Purchase Taxes and Charges Template"
},
{
"depends_on": "eval: doc.party_type == 'Customer'",
"fieldname": "sales_taxes_and_charges_template",
"fieldtype": "Link",
- "label": "Taxes and Charges Template",
+ "label": "Sales Taxes and Charges Template",
"options": "Sales Taxes and Charges Template"
},
{
@@ -733,7 +733,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2022-12-08 16:25:43.824051",
+ "modified": "2023-02-14 04:52:30.478523",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry",
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 1cccbd9..cd5b6d5 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -92,7 +92,13 @@
self.set_status()
def on_cancel(self):
- self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Payment Ledger Entry")
+ self.ignore_linked_doctypes = (
+ "GL Entry",
+ "Stock Ledger Entry",
+ "Payment Ledger Entry",
+ "Repost Payment Ledger",
+ "Repost Payment Ledger Items",
+ )
self.make_gl_entries(cancel=1)
self.update_outstanding_amounts()
self.update_advance_paid()
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index 52eb29b..2f43914 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -495,7 +495,7 @@
"""get amount based on doctype"""
dt = ref_doc.doctype
if dt in ["Sales Order", "Purchase Order"]:
- grand_total = flt(ref_doc.grand_total) - flt(ref_doc.advance_paid)
+ grand_total = flt(ref_doc.rounded_total) - flt(ref_doc.advance_paid)
elif dt in ["Sales Invoice", "Purchase Invoice"]:
if ref_doc.party_account_currency == ref_doc.currency:
diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json
index ce9ce64..a63039e 100644
--- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json
+++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json
@@ -472,7 +472,7 @@
"description": "If rate is zero them item will be treated as \"Free Item\"",
"fieldname": "free_item_rate",
"fieldtype": "Currency",
- "label": "Rate"
+ "label": "Free Item Rate"
},
{
"collapsible": 1,
@@ -608,7 +608,7 @@
"icon": "fa fa-gift",
"idx": 1,
"links": [],
- "modified": "2022-10-13 19:05:35.056304",
+ "modified": "2023-02-14 04:53:34.887358",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Pricing Rule",
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
index a098e8d..e2b4a1a 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
@@ -31,7 +31,7 @@
super.onload();
// Ignore linked advances
- this.frm.ignore_doctypes_on_cancel_all = ['Journal Entry', 'Payment Entry', 'Purchase Invoice'];
+ this.frm.ignore_doctypes_on_cancel_all = ['Journal Entry', 'Payment Entry', 'Purchase Invoice', "Repost Payment Ledger"];
if(!this.frm.doc.__islocal) {
// show credit_to in print format
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index 0e9f976..f588d5c 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -1416,6 +1416,8 @@
"GL Entry",
"Stock Ledger Entry",
"Repost Item Valuation",
+ "Repost Payment Ledger",
+ "Repost Payment Ledger Items",
"Payment Ledger Entry",
"Tax Withheld Vouchers",
)
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index 7abf3f3..47e3f9b 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -34,7 +34,7 @@
super.onload();
this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice', 'Timesheet', 'POS Invoice Merge Log',
- 'POS Closing Entry', 'Journal Entry', 'Payment Entry'];
+ 'POS Closing Entry', 'Journal Entry', 'Payment Entry', "Repost Payment Ledger"];
if(!this.frm.doc.__islocal && !this.frm.doc.customer && this.frm.doc.debit_to) {
// show debit_to in print format
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 31cf120..5cda276 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -397,6 +397,8 @@
"GL Entry",
"Stock Ledger Entry",
"Repost Item Valuation",
+ "Repost Payment Ledger",
+ "Repost Payment Ledger Items",
"Payment Ledger Entry",
)
diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js
index a61e8de..4951385 100644
--- a/erpnext/assets/doctype/asset/asset.js
+++ b/erpnext/assets/doctype/asset/asset.js
@@ -258,7 +258,7 @@
$.each(depr_entries || [], function(i, v) {
x_intervals.push(frappe.format(v.posting_date, { fieldtype: 'Date' }));
let last_asset_value = asset_values[asset_values.length - 1]
- asset_values.push(last_asset_value - v.value);
+ asset_values.push(flt(last_asset_value - v.value, precision('gross_purchase_amount')));
});
}
diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py
index e00f3a5..e1d58a0 100644
--- a/erpnext/assets/doctype/asset/asset.py
+++ b/erpnext/assets/doctype/asset/asset.py
@@ -413,11 +413,14 @@
if self.journal_entry_for_scrap:
status = "Scrapped"
- elif self.finance_books:
- idx = self.get_default_finance_book_idx() or 0
+ else:
+ expected_value_after_useful_life = 0
+ value_after_depreciation = self.value_after_depreciation
- expected_value_after_useful_life = self.finance_books[idx].expected_value_after_useful_life
- value_after_depreciation = self.finance_books[idx].value_after_depreciation
+ if self.calculate_depreciation:
+ idx = self.get_default_finance_book_idx() or 0
+ expected_value_after_useful_life = self.finance_books[idx].expected_value_after_useful_life
+ value_after_depreciation = self.finance_books[idx].value_after_depreciation
if flt(value_after_depreciation) <= expected_value_after_useful_life:
status = "Fully Depreciated"
@@ -463,6 +466,7 @@
.where(gle.debit != 0)
.where(gle.is_cancelled == 0)
.orderby(gle.posting_date)
+ .orderby(gle.creation)
).run(as_dict=True)
return records
diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py
index e7a2532..fb6e174 100644
--- a/erpnext/assets/doctype/asset/depreciation.py
+++ b/erpnext/assets/doctype/asset/depreciation.py
@@ -168,7 +168,7 @@
row.value_after_depreciation -= d.depreciation_amount
row.db_update()
- frappe.db.set_value("Asset", asset_name, "depr_entry_posting_status", "Successful")
+ asset.db_set("depr_entry_posting_status", "Successful")
asset.set_status()
diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.py b/erpnext/assets/doctype/asset_repair/asset_repair.py
index 9a05a74..a7172a7 100644
--- a/erpnext/assets/doctype/asset_repair/asset_repair.py
+++ b/erpnext/assets/doctype/asset_repair/asset_repair.py
@@ -91,6 +91,9 @@
make_new_active_asset_depr_schedules_and_cancel_current_ones(self.asset_doc, notes)
self.asset_doc.save()
+ def after_delete(self):
+ frappe.get_doc("Asset", self.asset).set_status()
+
def check_repair_status(self):
if self.repair_status == "Pending":
frappe.throw(_("Please update Repair Status."))
diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.json b/erpnext/buying/doctype/buying_settings/buying_settings.json
index 34417f7..652dcf0 100644
--- a/erpnext/buying/doctype/buying_settings/buying_settings.json
+++ b/erpnext/buying/doctype/buying_settings/buying_settings.json
@@ -21,6 +21,7 @@
"allow_multiple_items",
"bill_for_rejected_quantity_in_purchase_invoice",
"disable_last_purchase_rate",
+ "show_pay_button",
"subcontract",
"backflush_raw_materials_of_subcontract_based_on",
"column_break_11",
@@ -140,6 +141,12 @@
"fieldname": "disable_last_purchase_rate",
"fieldtype": "Check",
"label": "Disable Last Purchase Rate"
+ },
+ {
+ "default": "1",
+ "fieldname": "show_pay_button",
+ "fieldtype": "Check",
+ "label": "Show Pay Button in Purchase Order Portal"
}
],
"icon": "fa fa-cog",
@@ -147,7 +154,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
- "modified": "2023-01-09 17:08:28.828173",
+ "modified": "2023-02-15 14:42:10.200679",
"modified_by": "Administrator",
"module": "Buying",
"name": "Buying Settings",
diff --git a/erpnext/buying/doctype/supplier/supplier.json b/erpnext/buying/doctype/supplier/supplier.json
index 66eafe9..1bf7f58 100644
--- a/erpnext/buying/doctype/supplier/supplier.json
+++ b/erpnext/buying/doctype/supplier/supplier.json
@@ -23,7 +23,6 @@
"default_bank_account",
"column_break_10",
"default_price_list",
- "payment_terms",
"internal_supplier_section",
"is_internal_supplier",
"represents_company",
@@ -53,6 +52,7 @@
"supplier_primary_address",
"primary_address",
"accounting_tab",
+ "payment_terms",
"accounts",
"settings_tab",
"allow_purchase_invoice_creation_without_purchase_order",
@@ -457,11 +457,10 @@
"link_fieldname": "party"
}
],
- "modified": "2022-11-09 18:02:59.075203",
+ "modified": "2023-02-18 11:05:50.592270",
"modified_by": "Administrator",
"module": "Buying",
"name": "Supplier",
- "name_case": "Title Case",
"naming_rule": "By \"Naming Series\" field",
"owner": "Administrator",
"permissions": [
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 6fa44c9..3705fcf 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -204,6 +204,12 @@
validate_einvoice_fields(self)
def on_trash(self):
+ # delete references in 'Repost Payment Ledger'
+ rpi = frappe.qb.DocType("Repost Payment Ledger Items")
+ frappe.qb.from_(rpi).delete().where(
+ (rpi.voucher_type == self.doctype) & (rpi.voucher_no == self.name)
+ ).run()
+
# delete sl and gl entries on deletion of transaction
if frappe.db.get_single_value("Accounts Settings", "delete_linked_ledger_entries"):
ple = frappe.qb.DocType("Payment Ledger Entry")
diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py
index a9561fe..cc80f6c 100644
--- a/erpnext/controllers/subcontracting_controller.py
+++ b/erpnext/controllers/subcontracting_controller.py
@@ -409,7 +409,14 @@
if self.available_materials.get(key) and self.available_materials[key]["batch_no"]:
new_rm_obj = None
for batch_no, batch_qty in self.available_materials[key]["batch_no"].items():
- if batch_qty >= qty:
+ if batch_qty >= qty or (
+ rm_obj.consumed_qty == 0
+ and self.backflush_based_on == "BOM"
+ and len(self.available_materials[key]["batch_no"]) == 1
+ ):
+ if rm_obj.consumed_qty == 0:
+ self.__set_consumed_qty(rm_obj, qty)
+
self.__set_batch_no_as_per_qty(item_row, rm_obj, batch_no, qty)
self.available_materials[key]["batch_no"][batch_no] -= qty
return
diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py
index 38d6993..f44fad3 100644
--- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py
+++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py
@@ -12,7 +12,7 @@
def __init__(self, access_token=None):
self.access_token = access_token
self.settings = frappe.get_single("Plaid Settings")
- self.products = ["auth", "transactions"]
+ self.products = ["transactions"]
self.client_name = frappe.local.site
self.client = plaid.Client(
client_id=self.settings.plaid_client_id,
diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js
index 3740d04..3ba6bb9 100644
--- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js
+++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js
@@ -47,7 +47,7 @@
}
async init_config() {
- this.product = ["auth", "transactions"];
+ this.product = ["transactions"];
this.plaid_env = this.frm.doc.plaid_env;
this.client_name = frappe.boot.sitename;
this.token = await this.get_link_token();
diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py
index 62ea85f..f3aa6a3 100644
--- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py
+++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py
@@ -70,7 +70,8 @@
except TypeError:
pass
- bank = json.loads(bank)
+ if isinstance(bank, str):
+ bank = json.loads(bank)
result = []
default_gl_account = get_default_bank_cash_account(company, "Bank")
@@ -177,16 +178,15 @@
)
result = []
- for transaction in reversed(transactions):
- result += new_bank_transaction(transaction)
+ if transactions:
+ for transaction in reversed(transactions):
+ result += new_bank_transaction(transaction)
if result:
last_transaction_date = frappe.db.get_value("Bank Transaction", result.pop(), "date")
frappe.logger().info(
- "Plaid added {} new Bank Transactions from '{}' between {} and {}".format(
- len(result), bank_account, start_date, end_date
- )
+ f"Plaid added {len(result)} new Bank Transactions from '{bank_account}' between {start_date} and {end_date}"
)
frappe.db.set_value(
@@ -230,19 +230,20 @@
bank_account = frappe.db.get_value("Bank Account", dict(integration_id=transaction["account_id"]))
- if float(transaction["amount"]) >= 0:
- debit = 0
- credit = float(transaction["amount"])
+ amount = float(transaction["amount"])
+ if amount >= 0.0:
+ deposit = 0.0
+ withdrawal = amount
else:
- debit = abs(float(transaction["amount"]))
- credit = 0
+ deposit = abs(amount)
+ withdrawal = 0.0
status = "Pending" if transaction["pending"] == "True" else "Settled"
tags = []
try:
tags += transaction["category"]
- tags += ["Plaid Cat. {}".format(transaction["category_id"])]
+ tags += [f'Plaid Cat. {transaction["category_id"]}']
except KeyError:
pass
@@ -254,11 +255,18 @@
"date": getdate(transaction["date"]),
"status": status,
"bank_account": bank_account,
- "deposit": debit,
- "withdrawal": credit,
+ "deposit": deposit,
+ "withdrawal": withdrawal,
"currency": transaction["iso_currency_code"],
"transaction_id": transaction["transaction_id"],
- "reference_number": transaction["payment_meta"]["reference_number"],
+ "transaction_type": (
+ transaction["transaction_code"] or transaction["payment_meta"]["payment_method"]
+ ),
+ "reference_number": (
+ transaction["check_number"]
+ or transaction["payment_meta"]["reference_number"]
+ or transaction["name"]
+ ),
"description": transaction["name"],
}
)
@@ -271,7 +279,7 @@
result.append(new_transaction.name)
except Exception:
- frappe.throw(title=_("Bank transaction creation error"))
+ frappe.throw(_("Bank transaction creation error"))
return result
@@ -300,3 +308,26 @@
def get_link_token_for_update(access_token):
plaid = PlaidConnector(access_token)
return plaid.get_link_token(update_mode=True)
+
+
+def get_company(bank_account_name):
+ from frappe.defaults import get_user_default
+
+ company_names = frappe.db.get_all("Company", pluck="name")
+ if len(company_names) == 1:
+ return company_names[0]
+ if frappe.db.exists("Bank Account", bank_account_name):
+ return frappe.db.get_value("Bank Account", bank_account_name, "company")
+ company_default = get_user_default("Company")
+ if company_default:
+ return company_default
+ frappe.throw(_("Could not detect the Company for updating Bank Accounts"))
+
+
+@frappe.whitelist()
+def update_bank_account_ids(response):
+ data = json.loads(response)
+ institution_name = data["institution"]["name"]
+ bank = frappe.get_doc("Bank", institution_name).as_dict()
+ bank_account_name = f"{data['account']['name']} - {institution_name}"
+ return add_bank_accounts(response, bank, get_company(bank_account_name))
diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py
index e8dc3e2..6d34a20 100644
--- a/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py
+++ b/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py
@@ -125,6 +125,8 @@
"unofficial_currency_code": None,
"name": "INTRST PYMNT",
"transaction_type": "place",
+ "transaction_code": "direct debit",
+ "check_number": "3456789",
"amount": -4.22,
"location": {
"city": None,
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 211f074..e6be933 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -306,7 +306,6 @@
execute:frappe.delete_doc("DocType", "Naming Series")
erpnext.patches.v13_0.job_card_status_on_hold
erpnext.patches.v14_0.copy_is_subcontracted_value_to_is_old_subcontracting_flow
-erpnext.patches.v14_0.migrate_gl_to_payment_ledger
erpnext.patches.v14_0.crm_ux_cleanup
erpnext.patches.v14_0.migrate_existing_lead_notes_as_per_the_new_format
erpnext.patches.v14_0.remove_india_localisation # 14-07-2022
@@ -315,7 +314,6 @@
erpnext.patches.v14_0.fix_crm_no_of_employees
erpnext.patches.v14_0.create_accounting_dimensions_in_subcontracting_doctypes
erpnext.patches.v14_0.fix_subcontracting_receipt_gl_entries
-erpnext.patches.v14_0.migrate_remarks_from_gl_to_payment_ledger
erpnext.patches.v13_0.update_schedule_type_in_loans
erpnext.patches.v13_0.drop_unused_sle_index_parts
erpnext.patches.v14_0.create_accounting_dimensions_for_asset_capitalization
@@ -327,3 +325,6 @@
erpnext.patches.v14_0.change_autoname_for_tax_withheld_vouchers
erpnext.patches.v14_0.set_pick_list_status
erpnext.patches.v15_0.update_asset_value_for_manual_depr_entries
+# below 2 migration patches should always run last
+erpnext.patches.v14_0.migrate_gl_to_payment_ledger
+erpnext.patches.v14_0.migrate_remarks_from_gl_to_payment_ledger
diff --git a/erpnext/patches/v13_0/delete_old_purchase_reports.py b/erpnext/patches/v13_0/delete_old_purchase_reports.py
index 987f53f..60621fb 100644
--- a/erpnext/patches/v13_0/delete_old_purchase_reports.py
+++ b/erpnext/patches/v13_0/delete_old_purchase_reports.py
@@ -17,10 +17,11 @@
for report in reports_to_delete:
if frappe.db.exists("Report", report):
+ delete_links_from_desktop_icons(report)
delete_auto_email_reports(report)
check_and_delete_linked_reports(report)
- frappe.delete_doc("Report", report)
+ frappe.delete_doc("Report", report, force=True)
def delete_auto_email_reports(report):
@@ -28,3 +29,10 @@
auto_email_reports = frappe.db.get_values("Auto Email Report", {"report": report}, ["name"])
for auto_email_report in auto_email_reports:
frappe.delete_doc("Auto Email Report", auto_email_report[0])
+
+
+def delete_links_from_desktop_icons(report):
+ """Check for one or multiple Desktop Icons and delete"""
+ desktop_icons = frappe.db.get_values("Desktop Icon", {"_report": report}, ["name"])
+ for desktop_icon in desktop_icons:
+ frappe.delete_doc("Desktop Icon", desktop_icon[0], force=True)
diff --git a/erpnext/projects/doctype/project/project.json b/erpnext/projects/doctype/project/project.json
index 37d98ad..ba7aa85 100644
--- a/erpnext/projects/doctype/project/project.json
+++ b/erpnext/projects/doctype/project/project.json
@@ -408,7 +408,7 @@
"depends_on": "eval:(doc.frequency == \"Daily\" && doc.collect_progress == true)",
"fieldname": "daily_time_to_send",
"fieldtype": "Time",
- "label": "Time to send"
+ "label": "Daily Time to send"
},
{
"depends_on": "eval:(doc.frequency == \"Weekly\" && doc.collect_progress == true)",
@@ -421,7 +421,7 @@
"depends_on": "eval:(doc.frequency == \"Weekly\" && doc.collect_progress == true)",
"fieldname": "weekly_time_to_send",
"fieldtype": "Time",
- "label": "Time to send"
+ "label": "Weekly Time to send"
},
{
"fieldname": "column_break_45",
@@ -451,7 +451,7 @@
"index_web_pages_for_search": 1,
"links": [],
"max_attachments": 4,
- "modified": "2022-06-23 16:45:06.108499",
+ "modified": "2023-02-14 04:54:25.819620",
"modified_by": "Administrator",
"module": "Projects",
"name": "Project",
@@ -497,4 +497,4 @@
"timeline_field": "customer",
"title_field": "project_name",
"track_seen": 1
-}
+}
\ No newline at end of file
diff --git a/erpnext/projects/doctype/timesheet/timesheet.json b/erpnext/projects/doctype/timesheet/timesheet.json
index 0cce129..4683006 100644
--- a/erpnext/projects/doctype/timesheet/timesheet.json
+++ b/erpnext/projects/doctype/timesheet/timesheet.json
@@ -282,21 +282,21 @@
{
"fieldname": "base_total_costing_amount",
"fieldtype": "Currency",
- "label": "Total Costing Amount",
+ "label": "Base Total Costing Amount",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "base_total_billable_amount",
"fieldtype": "Currency",
- "label": "Total Billable Amount",
+ "label": "Base Total Billable Amount",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "base_total_billed_amount",
"fieldtype": "Currency",
- "label": "Total Billed Amount",
+ "label": "Base Total Billed Amount",
"print_hide": 1,
"read_only": 1
},
@@ -311,10 +311,11 @@
"idx": 1,
"is_submittable": 1,
"links": [],
- "modified": "2022-06-15 22:08:53.930200",
+ "modified": "2023-02-14 04:55:41.735991",
"modified_by": "Administrator",
"module": "Projects",
"name": "Timesheet",
+ "naming_rule": "By \"Naming Series\" field",
"owner": "Administrator",
"permissions": [
{
@@ -388,5 +389,6 @@
],
"sort_field": "modified",
"sort_order": "ASC",
+ "states": [],
"title_field": "title"
}
\ 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 f7c19a1..0cda938 100644
--- a/erpnext/public/js/bank_reconciliation_tool/data_table_manager.js
+++ b/erpnext/public/js/bank_reconciliation_tool/data_table_manager.js
@@ -182,6 +182,9 @@
);
} else {
this.transactions.splice(transaction_index, 1);
+ for (const [k, v] of Object.entries(this.transaction_dt_map)) {
+ if (v > transaction_index) this.transaction_dt_map[k] = v - 1;
+ }
}
this.datatable.refresh(this.transactions, this.columns);
diff --git a/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js b/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js
index 911343d..321b812 100644
--- a/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js
+++ b/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js
@@ -20,7 +20,7 @@
doctype: "Bank Transaction",
filters: { name: this.bank_transaction_name },
fieldname: [
- "date as reference_date",
+ "date",
"deposit",
"withdrawal",
"currency",
@@ -33,6 +33,7 @@
"party",
"unallocated_amount",
"allocated_amount",
+ "transaction_type",
],
},
callback: (r) => {
@@ -41,11 +42,23 @@
r.message.payment_entry = 1;
r.message.journal_entry = 1;
this.dialog.set_values(r.message);
+ this.copy_data_to_voucher();
this.dialog.show();
}
},
});
}
+
+ copy_data_to_voucher() {
+ let copied = {
+ reference_number: this.bank_transaction.reference_number || this.bank_transaction.description,
+ posting_date: this.bank_transaction.date,
+ reference_date: this.bank_transaction.date,
+ mode_of_payment: this.bank_transaction.transaction_type,
+ };
+ this.dialog.set_values(copied);
+ }
+
get_linked_vouchers(document_types) {
frappe.call({
method:
@@ -75,10 +88,9 @@
row[1],
row[2],
reference_date,
- row[8],
format_currency(row[3], row[9]),
- row[6],
row[4],
+ row[6],
]);
});
this.get_dt_columns();
@@ -104,7 +116,7 @@
{
name: __("Document Name"),
editable: false,
- width: 150,
+ width: 1,
},
{
name: __("Reference Date"),
@@ -112,25 +124,19 @@
width: 120,
},
{
- name: "Posting Date",
- editable: false,
- width: 120,
- },
- {
- name: __("Amount"),
+ name: __("Remaining"),
editable: false,
width: 100,
},
{
- name: __("Party"),
- editable: false,
- width: 120,
- },
-
- {
name: __("Reference Number"),
editable: false,
- width: 140,
+ width: 200,
+ },
+ {
+ name: __("Party"),
+ editable: false,
+ width: 100,
},
];
}
@@ -225,6 +231,16 @@
onchange: () => this.update_options(),
},
{
+ fieldname: "column_break_5",
+ fieldtype: "Column Break",
+ },
+ {
+ fieldtype: "Check",
+ label: "Bank Transaction",
+ fieldname: "bank_transaction",
+ onchange: () => this.update_options(),
+ },
+ {
fieldtype: "Section Break",
fieldname: "section_break_1",
label: __("Select Vouchers to Match"),
@@ -289,7 +305,7 @@
fieldtype: "Column Break",
},
{
- default: "Journal Entry Type",
+ default: "Bank Entry",
fieldname: "journal_entry_type",
fieldtype: "Select",
label: "Journal Entry Type",
@@ -364,7 +380,12 @@
fieldtype: "Section Break",
fieldname: "details_section",
label: "Transaction Details",
- collapsible: 1,
+ },
+ {
+ fieldname: "date",
+ fieldtype: "Date",
+ label: "Date",
+ read_only: 1,
},
{
fieldname: "deposit",
@@ -381,14 +402,14 @@
read_only: 1,
},
{
- fieldname: "description",
- fieldtype: "Small Text",
- label: "Description",
+ fieldname: "column_break_17",
+ fieldtype: "Column Break",
read_only: 1,
},
{
- fieldname: "column_break_17",
- fieldtype: "Column Break",
+ fieldname: "description",
+ fieldtype: "Small Text",
+ label: "Description",
read_only: 1,
},
{
@@ -398,7 +419,6 @@
options: "Currency",
read_only: 1,
},
-
{
fieldname: "unallocated_amount",
fieldtype: "Currency",
@@ -593,4 +613,4 @@
}
}
-};
\ No newline at end of file
+};
diff --git a/erpnext/selling/doctype/customer/customer.json b/erpnext/selling/doctype/customer/customer.json
index 7482a33..c133cd3 100644
--- a/erpnext/selling/doctype/customer/customer.json
+++ b/erpnext/selling/doctype/customer/customer.json
@@ -24,10 +24,10 @@
"account_manager",
"image",
"defaults_tab",
- "default_price_list",
+ "default_currency",
"default_bank_account",
"column_break_14",
- "default_currency",
+ "default_price_list",
"internal_customer_section",
"is_internal_customer",
"represents_company",
@@ -568,11 +568,10 @@
"link_fieldname": "party"
}
],
- "modified": "2022-11-08 15:52:34.462657",
+ "modified": "2023-02-18 11:04:46.343527",
"modified_by": "Administrator",
"module": "Selling",
"name": "Customer",
- "name_case": "Title Case",
"naming_rule": "By \"Naming Series\" field",
"owner": "Administrator",
"permissions": [
diff --git a/erpnext/selling/doctype/party_specific_item/party_specific_item.json b/erpnext/selling/doctype/party_specific_item/party_specific_item.json
index 32b5d47..a1f9902 100644
--- a/erpnext/selling/doctype/party_specific_item/party_specific_item.json
+++ b/erpnext/selling/doctype/party_specific_item/party_specific_item.json
@@ -1,5 +1,6 @@
{
"actions": [],
+ "allow_import": 1,
"creation": "2021-08-27 19:28:07.559978",
"doctype": "DocType",
"editable_grid": 1,
@@ -51,7 +52,7 @@
],
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2021-09-14 13:27:58.612334",
+ "modified": "2023-02-15 13:00:50.379713",
"modified_by": "Administrator",
"module": "Selling",
"name": "Party Specific Item",
@@ -72,6 +73,7 @@
],
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
"title_field": "party",
"track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json
index 165a56b..0c1f820 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.json
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.json
@@ -521,6 +521,7 @@
"allow_bulk_edit": 1,
"fieldname": "items",
"fieldtype": "Table",
+ "label": "Delivery Note Item",
"oldfieldname": "delivery_note_details",
"oldfieldtype": "Table",
"options": "Delivery Note Item",
@@ -666,6 +667,7 @@
{
"fieldname": "taxes",
"fieldtype": "Table",
+ "label": "Sales Taxes and Charges",
"oldfieldname": "other_charges",
"oldfieldtype": "Table",
"options": "Sales Taxes and Charges"
@@ -1401,7 +1403,7 @@
"idx": 146,
"is_submittable": 1,
"links": [],
- "modified": "2022-12-12 18:38:53.067799",
+ "modified": "2023-02-14 04:45:44.179670",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Note",
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note_list.js b/erpnext/stock/doctype/delivery_note/delivery_note_list.js
index 9e6f3bc..6ff3ed3 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note_list.js
+++ b/erpnext/stock/doctype/delivery_note/delivery_note_list.js
@@ -14,7 +14,7 @@
return [__("Completed"), "green", "per_billed,=,100"];
}
},
- onload: function (listview) {
+ onload: function (doclist) {
const action = () => {
const selected_docs = doclist.get_checked_items();
const docnames = doclist.get_checked_items(true);
@@ -56,14 +56,14 @@
// doclist.page.add_actions_menu_item(__('Create Delivery Trip'), action, false);
- listview.page.add_action_item(__('Create Delivery Trip'), action);
+ doclist.page.add_action_item(__('Create Delivery Trip'), action);
- listview.page.add_action_item(__("Sales Invoice"), ()=>{
- erpnext.bulk_transaction_processing.create(listview, "Delivery Note", "Sales Invoice");
+ doclist.page.add_action_item(__("Sales Invoice"), ()=>{
+ erpnext.bulk_transaction_processing.create(doclist, "Delivery Note", "Sales Invoice");
});
- listview.page.add_action_item(__("Packaging Slip From Delivery Note"), ()=>{
- erpnext.bulk_transaction_processing.create(listview, "Delivery Note", "Packing Slip");
+ doclist.page.add_action_item(__("Packaging Slip From Delivery Note"), ()=>{
+ erpnext.bulk_transaction_processing.create(doclist, "Delivery Note", "Packing Slip");
});
}
};
diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json
index 629e50e..34adbeb 100644
--- a/erpnext/stock/doctype/item/item.json
+++ b/erpnext/stock/doctype/item/item.json
@@ -706,7 +706,7 @@
"depends_on": "enable_deferred_expense",
"fieldname": "no_of_months_exp",
"fieldtype": "Int",
- "label": "No of Months"
+ "label": "No of Months (Expense)"
},
{
"collapsible": 1,
@@ -911,7 +911,7 @@
"index_web_pages_for_search": 1,
"links": [],
"make_attachments_public": 1,
- "modified": "2023-01-07 22:45:00.341745",
+ "modified": "2023-02-14 04:48:26.343620",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item",
diff --git a/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.js b/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.js
index 42d0723..5f81679 100644
--- a/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.js
+++ b/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.js
@@ -2,7 +2,22 @@
// For license information, please see license.txt
frappe.ui.form.on('Stock Reposting Settings', {
- // refresh: function(frm) {
+ refresh: function(frm) {
+ frm.trigger('convert_to_item_based_reposting');
+ },
- // }
+ convert_to_item_based_reposting: function(frm) {
+ frm.add_custom_button(__('Convert to Item Based Reposting'), function() {
+ frm.call({
+ method: 'convert_to_item_wh_reposting',
+ frezz: true,
+ doc: frm.doc,
+ callback: function(r) {
+ if (!r.exc) {
+ frm.reload_doc();
+ }
+ }
+ })
+ })
+ }
});
diff --git a/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.py b/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.py
index e0c8ed1..51fb5ac 100644
--- a/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.py
+++ b/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.py
@@ -1,6 +1,8 @@
# Copyright (c) 2021, 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 add_to_date, get_datetime, get_time_str, time_diff_in_hours
@@ -24,3 +26,62 @@
if diff < 10:
self.end_time = get_time_str(add_to_date(self.start_time, hours=10, as_datetime=True))
+
+ @frappe.whitelist()
+ def convert_to_item_wh_reposting(self):
+ """Convert Transaction reposting to Item Warehouse based reposting if Item Based Reposting has enabled."""
+
+ reposting_data = get_reposting_entries()
+
+ vouchers = [d.voucher_no for d in reposting_data]
+
+ item_warehouses = {}
+
+ for ledger in get_stock_ledgers(vouchers):
+ key = (ledger.item_code, ledger.warehouse)
+ if key not in item_warehouses:
+ item_warehouses[key] = ledger.posting_date
+ elif frappe.utils.getdate(item_warehouses.get(key)) > frappe.utils.getdate(ledger.posting_date):
+ item_warehouses[key] = ledger.posting_date
+
+ for key, posting_date in item_warehouses.items():
+ item_code, warehouse = key
+ create_repost_item_valuation(item_code, warehouse, posting_date)
+
+ for row in reposting_data:
+ frappe.db.set_value("Repost Item Valuation", row.name, "status", "Skipped")
+
+ self.db_set("item_based_reposting", 1)
+ frappe.msgprint(_("Item Warehouse based reposting has been enabled."))
+
+
+def get_reposting_entries():
+ return frappe.get_all(
+ "Repost Item Valuation",
+ fields=["voucher_no", "name"],
+ filters={"status": ("in", ["Queued", "In Progress"]), "docstatus": 1, "based_on": "Transaction"},
+ )
+
+
+def get_stock_ledgers(vouchers):
+ return frappe.get_all(
+ "Stock Ledger Entry",
+ fields=["item_code", "warehouse", "posting_date"],
+ filters={"voucher_no": ("in", vouchers)},
+ )
+
+
+def create_repost_item_valuation(item_code, warehouse, posting_date):
+ frappe.get_doc(
+ {
+ "doctype": "Repost Item Valuation",
+ "company": frappe.get_cached_value("Warehouse", warehouse, "company"),
+ "posting_date": posting_date,
+ "based_on": "Item and Warehouse",
+ "posting_time": "00:00:01",
+ "item_code": item_code,
+ "warehouse": warehouse,
+ "allow_negative_stock": True,
+ "status": "Queued",
+ }
+ ).submit()
diff --git a/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py
index d054ce0..6a2983f 100644
--- a/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py
+++ b/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py
@@ -2,6 +2,7 @@
# See license.txt
import copy
+from collections import defaultdict
import frappe
from frappe.tests.utils import FrappeTestCase
@@ -186,6 +187,40 @@
)
self.assertEqual(len(ste.items), len(rm_items))
+ def test_make_rm_stock_entry_for_batch_items_with_less_transfer(self):
+ set_backflush_based_on("BOM")
+
+ service_items = [
+ {
+ "warehouse": "_Test Warehouse - _TC",
+ "item_code": "Subcontracted Service Item 4",
+ "qty": 5,
+ "rate": 100,
+ "fg_item": "Subcontracted Item SA4",
+ "fg_item_qty": 5,
+ }
+ ]
+
+ sco = get_subcontracting_order(service_items=service_items)
+ rm_items = get_rm_items(sco.supplied_items)
+ itemwise_details = make_stock_in_entry(rm_items=rm_items)
+
+ itemwise_transfer_qty = defaultdict(int)
+ for item in rm_items:
+ item["qty"] -= 1
+ itemwise_transfer_qty[item["item_code"]] += item["qty"]
+
+ ste = make_stock_transfer_entry(
+ sco_no=sco.name,
+ rm_items=rm_items,
+ itemwise_details=copy.deepcopy(itemwise_details),
+ )
+
+ scr = make_subcontracting_receipt(sco.name)
+
+ for row in scr.supplied_items:
+ self.assertEqual(row.consumed_qty, itemwise_transfer_qty.get(row.rm_item_code) + 1)
+
def test_update_reserved_qty_for_subcontracting(self):
# Create RM Material Receipt
make_stock_entry(target="_Test Warehouse - _TC", item_code="_Test Item", qty=10, basic_rate=100)
diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js
index b6bef8c..3a2c53f 100644
--- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js
+++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js
@@ -51,13 +51,31 @@
}
}));
- frm.set_query("expense_account", "items", function () {
+ frm.set_query('expense_account', 'items', function () {
return {
- query: "erpnext.controllers.queries.get_expense_account",
+ query: 'erpnext.controllers.queries.get_expense_account',
filters: { 'company': frm.doc.company }
};
});
+ frm.set_query('batch_no', 'items', function(doc, cdt, cdn) {
+ var row = locals[cdt][cdn];
+ return {
+ filters: {
+ item: row.item_code
+ }
+ }
+ });
+
+ let batch_no_field = frm.get_docfield("items", "batch_no");
+ if (batch_no_field) {
+ batch_no_field.get_route_options_for_new_doc = function(row) {
+ return {
+ "item": row.doc.item_code
+ }
+ };
+ }
+
frappe.db.get_single_value('Buying Settings', 'backflush_raw_materials_of_subcontract_based_on').then(val => {
if (val == 'Material Transferred for Subcontract') {
frm.fields_dict['supplied_items'].grid.grid_rows.forEach((grid_row) => {
@@ -73,7 +91,7 @@
refresh: (frm) => {
if (frm.doc.docstatus > 0) {
- frm.add_custom_button(__("Stock Ledger"), function () {
+ frm.add_custom_button(__('Stock Ledger'), function () {
frappe.route_options = {
voucher_no: frm.doc.name,
from_date: frm.doc.posting_date,
@@ -81,8 +99,8 @@
company: frm.doc.company,
show_cancelled_entries: frm.doc.docstatus === 2
};
- frappe.set_route("query-report", "Stock Ledger");
- }, __("View"));
+ frappe.set_route('query-report', 'Stock Ledger');
+ }, __('View'));
frm.add_custom_button(__('Accounting Ledger'), function () {
frappe.route_options = {
@@ -90,11 +108,11 @@
from_date: frm.doc.posting_date,
to_date: moment(frm.doc.modified).format('YYYY-MM-DD'),
company: frm.doc.company,
- group_by: "Group by Voucher (Consolidated)",
+ group_by: 'Group by Voucher (Consolidated)',
show_cancelled_entries: frm.doc.docstatus === 2
};
- frappe.set_route("query-report", "General Ledger");
- }, __("View"));
+ frappe.set_route('query-report', 'General Ledger');
+ }, __('View'));
}
if (!frm.doc.is_return && frm.doc.docstatus == 1 && frm.doc.per_returned < 100) {
@@ -111,25 +129,25 @@
frm.add_custom_button(__('Subcontracting Order'), function () {
if (!frm.doc.supplier) {
frappe.throw({
- title: __("Mandatory"),
- message: __("Please Select a Supplier")
+ title: __('Mandatory'),
+ message: __('Please Select a Supplier')
});
}
erpnext.utils.map_current_doc({
method: 'erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order.make_subcontracting_receipt',
- source_doctype: "Subcontracting Order",
+ source_doctype: 'Subcontracting Order',
target: frm,
setters: {
supplier: frm.doc.supplier,
},
get_query_filters: {
docstatus: 1,
- per_received: ["<", 100],
+ per_received: ['<', 100],
company: frm.doc.company
}
});
- }, __("Get Items From"));
+ }, __('Get Items From'));
}
},
diff --git a/erpnext/templates/pages/order.html b/erpnext/templates/pages/order.html
index 6b354b2..bc34ad5 100644
--- a/erpnext/templates/pages/order.html
+++ b/erpnext/templates/pages/order.html
@@ -34,16 +34,18 @@
</a>
</ul>
</div>
- <div class="form-column col-sm-6">
- <div class="page-header-actions-block" data-html-block="header-actions">
- <p>
- <a href="/api/method/erpnext.accounts.doctype.payment_request.payment_request.make_payment_request?dn={{ doc.name }}&dt={{ doc.doctype }}&submit_doc=1&order_type=Shopping Cart"
- class="btn btn-primary btn-sm" id="pay-for-order">
- {{ _("Pay") }} {{doc.get_formatted("grand_total") }}
- </a>
- </p>
+ {% if show_pay_button %}
+ <div class="form-column col-sm-6">
+ <div class="page-header-actions-block" data-html-block="header-actions">
+ <p>
+ <a href="/api/method/erpnext.accounts.doctype.payment_request.payment_request.make_payment_request?dn={{ doc.name }}&dt={{ doc.doctype }}&submit_doc=1&order_type=Shopping Cart"
+ class="btn btn-primary btn-sm" id="pay-for-order">
+ {{ _("Pay") }} {{doc.get_formatted("grand_total") }}
+ </a>
+ </p>
+ </div>
</div>
- </div>
+ {% endif %}
</div>
{% endblock %}
diff --git a/erpnext/templates/pages/order.py b/erpnext/templates/pages/order.py
index 185ec66..13772d3 100644
--- a/erpnext/templates/pages/order.py
+++ b/erpnext/templates/pages/order.py
@@ -55,6 +55,7 @@
)
context.available_loyalty_points = int(loyalty_program_details.get("loyalty_points"))
+ context.show_pay_button = frappe.db.get_single_value("Buying Settings", "show_pay_button")
context.show_make_pi_button = False
if context.doc.get("supplier"):
# show Make Purchase Invoice button based on permission