Merge branch 'develop' into fix-reserve-qty

This commit is contained in:
Sagar Sharma
2023-02-19 12:45:39 +05:30
committed by GitHub
47 changed files with 828 additions and 372 deletions

View File

@@ -16,6 +16,7 @@ erpnext/maintenance/ @rohitwaghchaure @s-aga-r
erpnext/manufacturing/ @rohitwaghchaure @s-aga-r erpnext/manufacturing/ @rohitwaghchaure @s-aga-r
erpnext/quality_management/ @rohitwaghchaure @s-aga-r erpnext/quality_management/ @rohitwaghchaure @s-aga-r
erpnext/stock/ @rohitwaghchaure @s-aga-r erpnext/stock/ @rohitwaghchaure @s-aga-r
erpnext/subcontracting @rohitwaghchaure @s-aga-r
erpnext/crm/ @NagariaHussain erpnext/crm/ @NagariaHussain
erpnext/education/ @rutwikhdev erpnext/education/ @rutwikhdev

View File

@@ -118,6 +118,10 @@ erpnext.integrations.refreshPlaidLink = class refreshPlaidLink {
} }
plaid_success(token, response) { 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' });
});
} }
}; };

View File

@@ -155,7 +155,7 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
} }
}, },
render_chart: frappe.utils.debounce((frm) => { render_chart(frm) {
frm.cards_manager = new erpnext.accounts.bank_reconciliation.NumberCardManager( frm.cards_manager = new erpnext.accounts.bank_reconciliation.NumberCardManager(
{ {
$reconciliation_tool_cards: frm.get_field( $reconciliation_tool_cards: frm.get_field(
@@ -167,7 +167,7 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
currency: frm.currency, currency: frm.currency,
} }
); );
}, 500), },
render(frm) { render(frm) {
if (frm.doc.bank_account) { if (frm.doc.bank_account) {

View File

@@ -10,7 +10,7 @@ from frappe.model.document import Document
from frappe.query_builder.custom import ConstantColumn from frappe.query_builder.custom import ConstantColumn
from frappe.utils import cint, flt 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 ( from erpnext.accounts.report.bank_reconciliation_statement.bank_reconciliation_statement import (
get_amounts_not_reflected_in_system, get_amounts_not_reflected_in_system,
get_entries, get_entries,
@@ -28,7 +28,7 @@ def get_bank_transactions(bank_account, from_date=None, to_date=None):
filters = [] filters = []
filters.append(["bank_account", "=", bank_account]) filters.append(["bank_account", "=", bank_account])
filters.append(["docstatus", "=", 1]) filters.append(["docstatus", "=", 1])
filters.append(["unallocated_amount", ">", 0]) filters.append(["unallocated_amount", ">", 0.0])
if to_date: if to_date:
filters.append(["date", "<=", to_date]) filters.append(["date", "<=", to_date])
if from_date: if from_date:
@@ -58,7 +58,7 @@ def get_bank_transactions(bank_account, from_date=None, to_date=None):
@frappe.whitelist() @frappe.whitelist()
def get_account_balance(bank_account, till_date): def get_account_balance(bank_account, till_date):
# returns account balance till the specified 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( filters = frappe._dict(
{"account": account, "report_date": till_date, "include_pos_transactions": 1} {"account": account, "report_date": till_date, "include_pos_transactions": 1}
) )
@@ -66,7 +66,7 @@ def get_account_balance(bank_account, till_date):
balance_as_per_system = get_balance_on(filters["account"], filters["report_date"]) 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: for d in data:
total_debit += flt(d.debit) total_debit += flt(d.debit)
total_credit += flt(d.credit) total_credit += flt(d.credit)
@@ -131,10 +131,8 @@ def create_journal_entry_bts(
fieldname=["name", "deposit", "withdrawal", "bank_account"], fieldname=["name", "deposit", "withdrawal", "bank_account"],
as_dict=True, as_dict=True,
)[0] )[0]
company_account = frappe.get_cached_value( company_account = frappe.get_value("Bank Account", bank_transaction.bank_account, "account")
"Bank Account", bank_transaction.bank_account, "account" account_type = frappe.db.get_value("Account", second_account, "account_type")
)
account_type = frappe.get_cached_value("Account", second_account, "account_type")
if account_type in ["Receivable", "Payable"]: if account_type in ["Receivable", "Payable"]:
if not (party_type and party): if not (party_type and party):
frappe.throw( frappe.throw(
@@ -147,10 +145,8 @@ def create_journal_entry_bts(
accounts.append( accounts.append(
{ {
"account": second_account, "account": second_account,
"credit_in_account_currency": bank_transaction.deposit if bank_transaction.deposit > 0 else 0, "credit_in_account_currency": bank_transaction.deposit,
"debit_in_account_currency": bank_transaction.withdrawal "debit_in_account_currency": bank_transaction.withdrawal,
if bank_transaction.withdrawal > 0
else 0,
"party_type": party_type, "party_type": party_type,
"party": party, "party": party,
} }
@@ -160,14 +156,12 @@ def create_journal_entry_bts(
{ {
"account": company_account, "account": company_account,
"bank_account": bank_transaction.bank_account, "bank_account": bank_transaction.bank_account,
"credit_in_account_currency": bank_transaction.withdrawal "credit_in_account_currency": bank_transaction.withdrawal,
if bank_transaction.withdrawal > 0 "debit_in_account_currency": bank_transaction.deposit,
else 0,
"debit_in_account_currency": bank_transaction.deposit if bank_transaction.deposit > 0 else 0,
} }
) )
company = frappe.get_cached_value("Account", company_account, "company") company = frappe.get_value("Account", company_account, "company")
journal_entry_dict = { journal_entry_dict = {
"voucher_type": entry_type, "voucher_type": entry_type,
@@ -187,16 +181,22 @@ def create_journal_entry_bts(
journal_entry.insert() journal_entry.insert()
journal_entry.submit() journal_entry.submit()
if bank_transaction.deposit > 0: if bank_transaction.deposit > 0.0:
paid_amount = bank_transaction.deposit paid_amount = bank_transaction.deposit
else: else:
paid_amount = bank_transaction.withdrawal paid_amount = bank_transaction.withdrawal
vouchers = json.dumps( 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() @frappe.whitelist()
@@ -220,12 +220,10 @@ def create_payment_entry_bts(
as_dict=True, as_dict=True,
)[0] )[0]
paid_amount = bank_transaction.unallocated_amount 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( company_account = frappe.get_value("Bank Account", bank_transaction.bank_account, "account")
"Bank Account", bank_transaction.bank_account, "account" company = frappe.get_value("Account", company_account, "company")
)
company = frappe.get_cached_value("Account", company_account, "company")
payment_entry_dict = { payment_entry_dict = {
"company": company, "company": company,
"payment_type": payment_type, "payment_type": payment_type,
@@ -261,9 +259,15 @@ def create_payment_entry_bts(
payment_entry.submit() payment_entry.submit()
vouchers = json.dumps( 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() @frappe.whitelist()
@@ -345,59 +349,7 @@ def reconcile_vouchers(bank_transaction_name, vouchers):
# updated clear date of all the vouchers based on the bank transaction # updated clear date of all the vouchers based on the bank transaction
vouchers = json.loads(vouchers) vouchers = json.loads(vouchers)
transaction = frappe.get_doc("Bank Transaction", bank_transaction_name) transaction = frappe.get_doc("Bank Transaction", bank_transaction_name)
company_account = frappe.get_cached_value("Bank Account", transaction.bank_account, "account") transaction.add_payment_entries(vouchers)
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()
return frappe.get_doc("Bank Transaction", bank_transaction_name) return frappe.get_doc("Bank Transaction", bank_transaction_name)
@@ -416,9 +368,9 @@ def get_linked_payments(
bank_account = frappe.db.get_values( bank_account = frappe.db.get_values(
"Bank Account", transaction.bank_account, ["account", "company"], as_dict=True "Bank Account", transaction.bank_account, ["account", "company"], as_dict=True
)[0] )[0]
(account, company) = (bank_account.account, bank_account.company) (gl_account, company) = (bank_account.account, bank_account.company)
matching = check_matching( matching = check_matching(
account, gl_account,
company, company,
transaction, transaction,
document_types, document_types,
@@ -428,7 +380,27 @@ def get_linked_payments(
from_reference_date, from_reference_date,
to_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( def check_matching(
@@ -442,6 +414,7 @@ def check_matching(
from_reference_date, from_reference_date,
to_reference_date, to_reference_date,
): ):
exact_match = True if "exact_match" in document_types else False
# combine all types of vouchers # combine all types of vouchers
subquery = get_queries( subquery = get_queries(
bank_account, bank_account,
@@ -453,10 +426,11 @@ def check_matching(
filter_by_reference_date, filter_by_reference_date,
from_reference_date, from_reference_date,
to_reference_date, to_reference_date,
exact_match,
) )
filters = { filters = {
"amount": transaction.unallocated_amount, "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, "reference_no": transaction.reference_number,
"party_type": transaction.party_type, "party_type": transaction.party_type,
"party": transaction.party, "party": transaction.party,
@@ -465,7 +439,9 @@ def check_matching(
matching_vouchers = [] 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: for query in subquery:
matching_vouchers.extend( matching_vouchers.extend(
@@ -487,10 +463,10 @@ def get_queries(
filter_by_reference_date, filter_by_reference_date,
from_reference_date, from_reference_date,
to_reference_date, to_reference_date,
exact_match,
): ):
# get queries to get matching vouchers # get queries to get matching vouchers
amount_condition = "=" if "exact_match" in document_types else "<=" account_from_to = "paid_to" if transaction.deposit > 0.0 else "paid_from"
account_from_to = "paid_to" if transaction.deposit > 0 else "paid_from"
queries = [] queries = []
# get matching queries from all the apps # get matching queries from all the apps
@@ -501,7 +477,7 @@ def get_queries(
company, company,
transaction, transaction,
document_types, document_types,
amount_condition, exact_match,
account_from_to, account_from_to,
from_date, from_date,
to_date, to_date,
@@ -520,7 +496,7 @@ def get_matching_queries(
company, company,
transaction, transaction,
document_types, document_types,
amount_condition, exact_match,
account_from_to, account_from_to,
from_date, from_date,
to_date, to_date,
@@ -530,8 +506,8 @@ def get_matching_queries(
): ):
queries = [] queries = []
if "payment_entry" in document_types: if "payment_entry" in document_types:
pe_amount_matching = get_pe_matching_query( query = get_pe_matching_query(
amount_condition, exact_match,
account_from_to, account_from_to,
transaction, transaction,
from_date, from_date,
@@ -540,11 +516,11 @@ def get_matching_queries(
from_reference_date, from_reference_date,
to_reference_date, to_reference_date,
) )
queries.extend([pe_amount_matching]) queries.append(query)
if "journal_entry" in document_types: if "journal_entry" in document_types:
je_amount_matching = get_je_matching_query( query = get_je_matching_query(
amount_condition, exact_match,
transaction, transaction,
from_date, from_date,
to_date, to_date,
@@ -552,34 +528,70 @@ def get_matching_queries(
from_reference_date, from_reference_date,
to_reference_date, to_reference_date,
) )
queries.extend([je_amount_matching]) queries.append(query)
if transaction.deposit > 0 and "sales_invoice" in document_types: if transaction.deposit > 0.0 and "sales_invoice" in document_types:
si_amount_matching = get_si_matching_query(amount_condition) query = get_si_matching_query(exact_match)
queries.extend([si_amount_matching]) queries.append(query)
if transaction.withdrawal > 0: if transaction.withdrawal > 0.0:
if "purchase_invoice" in document_types: if "purchase_invoice" in document_types:
pi_amount_matching = get_pi_matching_query(amount_condition) query = get_pi_matching_query(exact_match)
queries.extend([pi_amount_matching]) queries.append(query)
if "bank_transaction" in document_types:
query = get_bt_matching_query(exact_match, transaction)
queries.append(query)
return queries 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 = [] vouchers = []
amount_condition = True if "exact_match" in document_types else False
if transaction.withdrawal > 0 and "loan_disbursement" in document_types: if transaction.withdrawal > 0.0 and "loan_disbursement" in document_types:
vouchers.extend(get_ld_matching_query(bank_account, amount_condition, filters)) vouchers.extend(get_ld_matching_query(bank_account, exact_match, filters))
if transaction.deposit > 0 and "loan_repayment" in document_types: if transaction.deposit > 0.0 and "loan_repayment" in document_types:
vouchers.extend(get_lr_matching_query(bank_account, amount_condition, filters)) vouchers.extend(get_lr_matching_query(bank_account, exact_match, filters))
return vouchers 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") loan_disbursement = frappe.qb.DocType("Loan Disbursement")
matching_reference = loan_disbursement.reference_number == filters.get("reference_number") matching_reference = loan_disbursement.reference_number == filters.get("reference_number")
matching_party = loan_disbursement.applicant_type == filters.get( matching_party = loan_disbursement.applicant_type == filters.get(
@@ -607,17 +619,17 @@ def get_ld_matching_query(bank_account, amount_condition, filters):
.where(loan_disbursement.disbursement_account == bank_account) .where(loan_disbursement.disbursement_account == bank_account)
) )
if amount_condition: if exact_match:
query.where(loan_disbursement.disbursed_amount == filters.get("amount")) query.where(loan_disbursement.disbursed_amount == filters.get("amount"))
else: else:
query.where(loan_disbursement.disbursed_amount <= filters.get("amount")) query.where(loan_disbursement.disbursed_amount > 0.0)
vouchers = query.run(as_list=True) vouchers = query.run(as_list=True)
return vouchers 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") loan_repayment = frappe.qb.DocType("Loan Repayment")
matching_reference = loan_repayment.reference_number == filters.get("reference_number") matching_reference = loan_repayment.reference_number == filters.get("reference_number")
matching_party = loan_repayment.applicant_type == filters.get( matching_party = loan_repayment.applicant_type == filters.get(
@@ -648,10 +660,10 @@ def get_lr_matching_query(bank_account, amount_condition, filters):
if frappe.db.has_column("Loan Repayment", "repay_from_salary"): if frappe.db.has_column("Loan Repayment", "repay_from_salary"):
query = query.where((loan_repayment.repay_from_salary == 0)) query = query.where((loan_repayment.repay_from_salary == 0))
if amount_condition: if exact_match:
query.where(loan_repayment.amount_paid == filters.get("amount")) query.where(loan_repayment.amount_paid == filters.get("amount"))
else: else:
query.where(loan_repayment.amount_paid <= filters.get("amount")) query.where(loan_repayment.amount_paid > 0.0)
vouchers = query.run() vouchers = query.run()
@@ -659,7 +671,7 @@ def get_lr_matching_query(bank_account, amount_condition, filters):
def get_pe_matching_query( def get_pe_matching_query(
amount_condition, exact_match,
account_from_to, account_from_to,
transaction, transaction,
from_date, from_date,
@@ -669,7 +681,7 @@ def get_pe_matching_query(
to_reference_date, to_reference_date,
): ):
# get matching payment entries query # get matching payment entries query
if transaction.deposit > 0: if transaction.deposit > 0.0:
currency_field = "paid_to_account_currency as currency" currency_field = "paid_to_account_currency as currency"
else: else:
currency_field = "paid_from_account_currency as currency" currency_field = "paid_from_account_currency as currency"
@@ -684,7 +696,8 @@ def get_pe_matching_query(
return f""" return f"""
SELECT SELECT
(CASE WHEN reference_no=%(reference_no)s THEN 1 ELSE 0 END (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, + 1 ) AS rank,
'Payment Entry' as doctype, 'Payment Entry' as doctype,
name, name,
@@ -698,20 +711,19 @@ def get_pe_matching_query(
FROM FROM
`tabPayment Entry` `tabPayment Entry`
WHERE WHERE
paid_amount {amount_condition} %(amount)s docstatus = 1
AND docstatus = 1
AND payment_type IN (%(payment_type)s, 'Internal Transfer') AND payment_type IN (%(payment_type)s, 'Internal Transfer')
AND ifnull(clearance_date, '') = "" AND ifnull(clearance_date, '') = ""
AND {account_from_to} = %(bank_account)s AND {account_from_to} = %(bank_account)s
AND paid_amount {'= %(amount)s' if exact_match else '> 0.0'}
{filter_by_date} {filter_by_date}
{filter_by_reference_no} {filter_by_reference_no}
order by{order_by} order by{order_by}
""" """
def get_je_matching_query( def get_je_matching_query(
amount_condition, exact_match,
transaction, transaction,
from_date, from_date,
to_date, to_date,
@@ -723,7 +735,7 @@ def get_je_matching_query(
# We have mapping at the bank level # We have mapping at the bank level
# So one bank could have both types of bank accounts like asset and liability # 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 # 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}'" filter_by_date = f"AND je.posting_date between '{from_date}' and '{to_date}'"
order_by = " je.posting_date" order_by = " je.posting_date"
filter_by_reference_no = "" filter_by_reference_no = ""
@@ -735,26 +747,29 @@ def get_je_matching_query(
return f""" return f"""
SELECT SELECT
(CASE WHEN je.cheque_no=%(reference_no)s THEN 1 ELSE 0 END (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 , + 1) AS rank ,
'Journal Entry' as doctype, 'Journal Entry' AS doctype,
je.name, je.name,
jea.{cr_or_dr}_in_account_currency as paid_amount, jea.{cr_or_dr}_in_account_currency AS paid_amount,
je.cheque_no as reference_no, je.cheque_no AS reference_no,
je.cheque_date as reference_date, je.cheque_date AS reference_date,
je.pay_to_recd_from as party, je.pay_to_recd_from AS party,
jea.party_type, jea.party_type,
je.posting_date, je.posting_date,
jea.account_currency as currency jea.account_currency AS currency
FROM FROM
`tabJournal Entry Account` as jea `tabJournal Entry Account` AS jea
JOIN JOIN
`tabJournal Entry` as je `tabJournal Entry` AS je
ON ON
jea.parent = je.name jea.parent = je.name
WHERE 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.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 AND je.docstatus = 1
{filter_by_date} {filter_by_date}
{filter_by_reference_no} {filter_by_reference_no}
@@ -762,11 +777,12 @@ def get_je_matching_query(
""" """
def get_si_matching_query(amount_condition): def get_si_matching_query(exact_match):
# get matchin sales invoice query # get matching sales invoice query
return f""" return f"""
SELECT 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, + 1 ) AS rank,
'Sales Invoice' as doctype, 'Sales Invoice' as doctype,
si.name, si.name,
@@ -784,18 +800,20 @@ def get_si_matching_query(amount_condition):
`tabSales Invoice` as si `tabSales Invoice` as si
ON ON
sip.parent = si.name 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.account = %(bank_account)s
AND sip.amount {amount_condition} %(amount)s AND sip.amount {'= %(amount)s' if exact_match else '> 0.0'}
AND si.docstatus = 1
""" """
def get_pi_matching_query(amount_condition): def get_pi_matching_query(exact_match):
# get matching purchase invoice query # get matching purchase invoice query when they are also used as payment entries (is_paid)
return f""" return f"""
SELECT SELECT
( CASE WHEN supplier = %(party)s THEN 1 ELSE 0 END ( CASE WHEN supplier = %(party)s THEN 1 ELSE 0 END
+ CASE WHEN paid_amount = %(amount)s THEN 1 ELSE 0 END
+ 1 ) AS rank, + 1 ) AS rank,
'Purchase Invoice' as doctype, 'Purchase Invoice' as doctype,
name, name,
@@ -809,9 +827,9 @@ def get_pi_matching_query(amount_condition):
FROM FROM
`tabPurchase Invoice` `tabPurchase Invoice`
WHERE WHERE
paid_amount {amount_condition} %(amount)s docstatus = 1
AND docstatus = 1
AND is_paid = 1 AND is_paid = 1
AND ifnull(clearance_date, '') = "" 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'}
""" """

View File

@@ -12,8 +12,13 @@ frappe.ui.form.on("Bank Transaction", {
}; };
}); });
}, },
refresh(frm) {
bank_account: function(frm) { frm.add_custom_button(__('Unreconcile Transaction'), () => {
frm.call('remove_payment_entries')
.then( () => frm.refresh() );
});
},
bank_account: function (frm) {
set_bank_statement_filter(frm); set_bank_statement_filter(frm);
}, },
@@ -34,6 +39,7 @@ frappe.ui.form.on("Bank Transaction", {
"Journal Entry", "Journal Entry",
"Sales Invoice", "Sales Invoice",
"Purchase Invoice", "Purchase Invoice",
"Bank Transaction",
]; ];
} }
}); });
@@ -49,7 +55,7 @@ const update_clearance_date = (frm, cdt, cdn) => {
frappe frappe
.xcall( .xcall(
"erpnext.accounts.doctype.bank_transaction.bank_transaction.unclear_reference_payment", "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) => { .then((e) => {
if (e == "success") { if (e == "success") {

View File

@@ -20,9 +20,11 @@
"currency", "currency",
"section_break_10", "section_break_10",
"description", "description",
"section_break_14",
"reference_number", "reference_number",
"column_break_10",
"transaction_id", "transaction_id",
"transaction_type",
"section_break_14",
"payment_entries", "payment_entries",
"section_break_18", "section_break_18",
"allocated_amount", "allocated_amount",
@@ -190,11 +192,21 @@
"label": "Withdrawal", "label": "Withdrawal",
"oldfieldname": "credit", "oldfieldname": "credit",
"options": "currency" "options": "currency"
},
{
"fieldname": "column_break_10",
"fieldtype": "Column Break"
},
{
"fieldname": "transaction_type",
"fieldtype": "Data",
"label": "Transaction Type",
"length": 50
} }
], ],
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2022-03-21 19:05:04.208222", "modified": "2022-05-29 18:36:50.475964",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Bank Transaction", "name": "Bank Transaction",

View File

@@ -1,9 +1,6 @@
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt # For license information, please see license.txt
from functools import reduce
import frappe import frappe
from frappe.utils import flt from frappe.utils import flt
@@ -18,72 +15,137 @@ class BankTransaction(StatusUpdater):
self.clear_linked_payment_entries() self.clear_linked_payment_entries()
self.set_status() self.set_status()
_saving_flag = False
# nosemgrep: frappe-semgrep-rules.rules.frappe-modifying-but-not-comitting
def on_update_after_submit(self): def on_update_after_submit(self):
self.update_allocations() "Run on save(). Avoid recursion caused by multiple saves"
self.clear_linked_payment_entries() if not self._saving_flag:
self.set_status(update=True) self._saving_flag = True
self.clear_linked_payment_entries()
self.update_allocations()
self._saving_flag = False
def on_cancel(self): def on_cancel(self):
self.clear_linked_payment_entries(for_cancel=True) self.clear_linked_payment_entries(for_cancel=True)
self.set_status(update=True) self.set_status(update=True)
def update_allocations(self): def update_allocations(self):
"The doctype does not allow modifications after submission, so write to the db direct"
if self.payment_entries: if self.payment_entries:
allocated_amount = reduce( allocated_amount = sum(p.allocated_amount for p in self.payment_entries)
lambda x, y: flt(x) + flt(y), [x.allocated_amount for x in self.payment_entries]
)
else: else:
allocated_amount = 0 allocated_amount = 0.0
if allocated_amount: amount = abs(flt(self.withdrawal) - flt(self.deposit))
frappe.db.set_value(self.doctype, self.name, "allocated_amount", flt(allocated_amount)) self.db_set("allocated_amount", flt(allocated_amount))
frappe.db.set_value( self.db_set("unallocated_amount", amount - flt(allocated_amount))
self.doctype, self.reload()
self.name, self.set_status(update=True)
"unallocated_amount",
abs(flt(self.withdrawal) - flt(self.deposit)) - flt(allocated_amount),
)
else: def add_payment_entries(self, vouchers):
frappe.db.set_value(self.doctype, self.name, "allocated_amount", 0) "Add the vouchers with zero allocation. Save() will perform the allocations and clearance"
frappe.db.set_value( if 0.0 >= self.unallocated_amount:
self.doctype, self.name, "unallocated_amount", abs(flt(self.withdrawal) - flt(self.deposit)) frappe.throw(frappe._(f"Bank Transaction {self.name} is already fully reconciled"))
)
amount = self.deposit or self.withdrawal added = False
if amount == self.allocated_amount: for voucher in vouchers:
frappe.db.set_value(self.doctype, self.name, "status", "Reconciled") # 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() 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: for payment_entry in self.payment_entries:
if payment_entry.payment_document == "Sales Invoice": self.remove_payment_entry(payment_entry)
self.clear_sales_invoice(payment_entry, for_cancel=for_cancel) # runs on_update_after_submit
elif payment_entry.payment_document in get_doctypes_for_bank_reconciliation(): self.save()
self.clear_simple_entry(payment_entry, for_cancel=for_cancel)
def clear_simple_entry(self, payment_entry, for_cancel=False): def remove_payment_entry(self, payment_entry):
if payment_entry.payment_document == "Payment Entry": "Clear payment entry and clearance"
if ( self.clear_linked_payment_entry(payment_entry, for_cancel=True)
frappe.db.get_value("Payment Entry", payment_entry.payment_entry, "payment_type") self.remove(payment_entry)
== "Internal Transfer"
):
if len(get_reconciled_bank_transactions(payment_entry)) < 2:
return
clearance_date = self.date if not for_cancel else None def clear_linked_payment_entries(self, for_cancel=False):
frappe.db.set_value( if for_cancel:
payment_entry.payment_document, payment_entry.payment_entry, "clearance_date", clearance_date 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): def clear_linked_payment_entry(self, payment_entry, for_cancel=False):
clearance_date = self.date if not for_cancel else None clearance_date = None if for_cancel else self.date
frappe.db.set_value( set_voucher_clearance(
"Sales Invoice Payment", payment_entry.payment_document, payment_entry.payment_entry, clearance_date, self
dict(parenttype=payment_entry.payment_document, parent=payment_entry.payment_entry),
"clearance_date",
clearance_date,
) )
@@ -93,38 +155,112 @@ def get_doctypes_for_bank_reconciliation():
return frappe.get_hooks("bank_reconciliation_doctypes") return frappe.get_hooks("bank_reconciliation_doctypes")
def get_reconciled_bank_transactions(payment_entry): def get_clearance_details(transaction, payment_entry):
reconciled_bank_transactions = frappe.get_all( """
"Bank Transaction Payments", There should only be one bank gle for a voucher.
filters={"payment_entry": payment_entry.payment_entry}, Could be none for a Bank Transaction.
fields=["parent"], 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): def get_related_bank_gl_entries(doctype, docname):
return frappe.db.sql( # nosemgrep: frappe-semgrep-rules.rules.frappe-using-db-sql
result = frappe.db.sql(
""" """
SELECT SELECT
SUM(btp.allocated_amount) as allocated_amount, ABS(gle.credit_in_account_currency - gle.debit_in_account_currency) AS amount,
bt.name gle.account AS gl_account
FROM FROM
`tabBank Transaction Payments` as btp `tabGL Entry` gle
LEFT JOIN LEFT JOIN
`tabBank Transaction` bt ON bt.name=btp.parent `tabAccount` ac ON ac.name=gle.account
WHERE WHERE
btp.payment_document = %s ac.account_type = 'Bank'
AND AND gle.voucher_type = %(doctype)s
btp.payment_entry = %s AND gle.voucher_no = %(docname)s
AND AND is_cancelled = 0
bt.docstatus = 1""", """,
(payment_entry.payment_document, payment_entry.payment_entry), dict(doctype=doctype, docname=docname),
as_dict=True, 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"]: if payment_entry.payment_document in ["Payment Entry", "Sales Invoice", "Purchase Invoice"]:
paid_amount_field = "paid_amount" paid_amount_field = "paid_amount"
@@ -147,7 +283,7 @@ def get_paid_amount(payment_entry, currency, bank_account):
elif payment_entry.payment_document == "Journal Entry": elif payment_entry.payment_document == "Journal Entry":
return frappe.db.get_value( return frappe.db.get_value(
"Journal Entry Account", "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)", "sum(credit_in_account_currency)",
) )
@@ -166,6 +302,12 @@ def get_paid_amount(payment_entry, currency, bank_account):
payment_entry.payment_document, payment_entry.payment_entry, "amount_paid" 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: else:
frappe.throw( frappe.throw(
"Please reconcile {0}: {1} manually".format( "Please reconcile {0}: {1} manually".format(
@@ -174,18 +316,55 @@ def get_paid_amount(payment_entry, currency, bank_account):
) )
@frappe.whitelist() def set_voucher_clearance(doctype, docname, clearance_date, self):
def unclear_reference_payment(doctype, docname): if doctype in [
if frappe.db.exists(doctype, docname): "Payment Entry",
doc = frappe.get_doc(doctype, docname) "Journal Entry",
if doctype == "Sales Invoice": "Purchase Invoice",
frappe.db.set_value( "Expense Claim",
"Sales Invoice Payment", "Loan Repayment",
dict(parenttype=doc.payment_document, parent=doc.payment_entry), "Loan Disbursement",
"clearance_date", ]:
None, if (
) doctype == "Payment Entry"
else: and frappe.db.get_value("Payment Entry", docname, "payment_type") == "Internal Transfer"
frappe.db.set_value(doc.payment_document, doc.payment_entry, "clearance_date", None) 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

View File

@@ -8,7 +8,7 @@ frappe.provide("erpnext.journal_entry");
frappe.ui.form.on("Journal Entry", { frappe.ui.form.on("Journal Entry", {
setup: function(frm) { setup: function(frm) {
frm.add_fetch("bank_account", "account", "account"); 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) { refresh: function(frm) {

View File

@@ -89,7 +89,13 @@ class JournalEntry(AccountsController):
from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries
unlink_ref_doc_from_payment_entries(self) 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.make_gl_entries(1)
self.update_advance_paid() self.update_advance_paid()
self.unlink_advance_entry_reference() self.unlink_advance_entry_reference()
@@ -238,21 +244,16 @@ class JournalEntry(AccountsController):
): ):
processed_assets.append(d.reference_name) processed_assets.append(d.reference_name)
asset = frappe.db.get_value( asset = frappe.get_doc("Asset", d.reference_name)
"Asset", d.reference_name, ["calculate_depreciation", "value_after_depreciation"], as_dict=1
)
if asset.calculate_depreciation: if asset.calculate_depreciation:
continue continue
depr_value = d.debit or d.credit depr_value = d.debit or d.credit
frappe.db.set_value( asset.db_set("value_after_depreciation", asset.value_after_depreciation - depr_value)
"Asset",
d.reference_name, asset.set_status()
"value_after_depreciation",
asset.value_after_depreciation - depr_value,
)
def update_inter_company_jv(self): def update_inter_company_jv(self):
if ( if (
@@ -348,12 +349,9 @@ class JournalEntry(AccountsController):
else: else:
depr_value = d.debit or d.credit depr_value = d.debit or d.credit
frappe.db.set_value( asset.db_set("value_after_depreciation", asset.value_after_depreciation + depr_value)
"Asset",
d.reference_name, asset.set_status()
"value_after_depreciation",
asset.value_after_depreciation + depr_value,
)
def unlink_inter_company_jv(self): def unlink_inter_company_jv(self):
if ( if (

View File

@@ -7,7 +7,7 @@ cur_frm.cscript.tax_table = "Advance Taxes and Charges";
frappe.ui.form.on('Payment Entry', { frappe.ui.form.on('Payment Entry', {
onload: function(frm) { 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.__islocal) {
if (!frm.doc.paid_from) frm.set_value("paid_from_account_currency", null); if (!frm.doc.paid_from) frm.set_value("paid_from_account_currency", null);

View File

@@ -239,7 +239,7 @@
"depends_on": "paid_from", "depends_on": "paid_from",
"fieldname": "paid_from_account_currency", "fieldname": "paid_from_account_currency",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Account Currency", "label": "Account Currency (From)",
"options": "Currency", "options": "Currency",
"print_hide": 1, "print_hide": 1,
"read_only": 1, "read_only": 1,
@@ -249,7 +249,7 @@
"depends_on": "paid_from", "depends_on": "paid_from",
"fieldname": "paid_from_account_balance", "fieldname": "paid_from_account_balance",
"fieldtype": "Currency", "fieldtype": "Currency",
"label": "Account Balance", "label": "Account Balance (From)",
"options": "paid_from_account_currency", "options": "paid_from_account_currency",
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1
@@ -272,7 +272,7 @@
"depends_on": "paid_to", "depends_on": "paid_to",
"fieldname": "paid_to_account_currency", "fieldname": "paid_to_account_currency",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Account Currency", "label": "Account Currency (To)",
"options": "Currency", "options": "Currency",
"print_hide": 1, "print_hide": 1,
"read_only": 1, "read_only": 1,
@@ -282,7 +282,7 @@
"depends_on": "paid_to", "depends_on": "paid_to",
"fieldname": "paid_to_account_balance", "fieldname": "paid_to_account_balance",
"fieldtype": "Currency", "fieldtype": "Currency",
"label": "Account Balance", "label": "Account Balance (To)",
"options": "paid_to_account_currency", "options": "paid_to_account_currency",
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1
@@ -304,7 +304,7 @@
{ {
"fieldname": "source_exchange_rate", "fieldname": "source_exchange_rate",
"fieldtype": "Float", "fieldtype": "Float",
"label": "Exchange Rate", "label": "Source Exchange Rate",
"precision": "9", "precision": "9",
"print_hide": 1, "print_hide": 1,
"reqd": 1 "reqd": 1
@@ -334,7 +334,7 @@
{ {
"fieldname": "target_exchange_rate", "fieldname": "target_exchange_rate",
"fieldtype": "Float", "fieldtype": "Float",
"label": "Exchange Rate", "label": "Target Exchange Rate",
"precision": "9", "precision": "9",
"print_hide": 1, "print_hide": 1,
"reqd": 1 "reqd": 1
@@ -633,14 +633,14 @@
"depends_on": "eval:doc.party_type == 'Supplier'", "depends_on": "eval:doc.party_type == 'Supplier'",
"fieldname": "purchase_taxes_and_charges_template", "fieldname": "purchase_taxes_and_charges_template",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Taxes and Charges Template", "label": "Purchase Taxes and Charges Template",
"options": "Purchase Taxes and Charges Template" "options": "Purchase Taxes and Charges Template"
}, },
{ {
"depends_on": "eval: doc.party_type == 'Customer'", "depends_on": "eval: doc.party_type == 'Customer'",
"fieldname": "sales_taxes_and_charges_template", "fieldname": "sales_taxes_and_charges_template",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Taxes and Charges Template", "label": "Sales Taxes and Charges Template",
"options": "Sales Taxes and Charges Template" "options": "Sales Taxes and Charges Template"
}, },
{ {
@@ -733,7 +733,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2022-12-08 16:25:43.824051", "modified": "2023-02-14 04:52:30.478523",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Payment Entry", "name": "Payment Entry",

View File

@@ -92,7 +92,13 @@ class PaymentEntry(AccountsController):
self.set_status() self.set_status()
def on_cancel(self): 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.make_gl_entries(cancel=1)
self.update_outstanding_amounts() self.update_outstanding_amounts()
self.update_advance_paid() self.update_advance_paid()

View File

@@ -495,7 +495,7 @@ def get_amount(ref_doc, payment_account=None):
"""get amount based on doctype""" """get amount based on doctype"""
dt = ref_doc.doctype dt = ref_doc.doctype
if dt in ["Sales Order", "Purchase Order"]: 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"]: elif dt in ["Sales Invoice", "Purchase Invoice"]:
if ref_doc.party_account_currency == ref_doc.currency: if ref_doc.party_account_currency == ref_doc.currency:

View File

@@ -472,7 +472,7 @@
"description": "If rate is zero them item will be treated as \"Free Item\"", "description": "If rate is zero them item will be treated as \"Free Item\"",
"fieldname": "free_item_rate", "fieldname": "free_item_rate",
"fieldtype": "Currency", "fieldtype": "Currency",
"label": "Rate" "label": "Free Item Rate"
}, },
{ {
"collapsible": 1, "collapsible": 1,
@@ -608,7 +608,7 @@
"icon": "fa fa-gift", "icon": "fa fa-gift",
"idx": 1, "idx": 1,
"links": [], "links": [],
"modified": "2022-10-13 19:05:35.056304", "modified": "2023-02-14 04:53:34.887358",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Pricing Rule", "name": "Pricing Rule",

View File

@@ -31,7 +31,7 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
super.onload(); super.onload();
// Ignore linked advances // 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) { if(!this.frm.doc.__islocal) {
// show credit_to in print format // show credit_to in print format

View File

@@ -1416,6 +1416,8 @@ class PurchaseInvoice(BuyingController):
"GL Entry", "GL Entry",
"Stock Ledger Entry", "Stock Ledger Entry",
"Repost Item Valuation", "Repost Item Valuation",
"Repost Payment Ledger",
"Repost Payment Ledger Items",
"Payment Ledger Entry", "Payment Ledger Entry",
"Tax Withheld Vouchers", "Tax Withheld Vouchers",
) )

View File

@@ -34,7 +34,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
super.onload(); super.onload();
this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice', 'Timesheet', 'POS Invoice Merge Log', 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) { if(!this.frm.doc.__islocal && !this.frm.doc.customer && this.frm.doc.debit_to) {
// show debit_to in print format // show debit_to in print format

View File

@@ -397,6 +397,8 @@ class SalesInvoice(SellingController):
"GL Entry", "GL Entry",
"Stock Ledger Entry", "Stock Ledger Entry",
"Repost Item Valuation", "Repost Item Valuation",
"Repost Payment Ledger",
"Repost Payment Ledger Items",
"Payment Ledger Entry", "Payment Ledger Entry",
) )

View File

@@ -258,7 +258,7 @@ frappe.ui.form.on('Asset', {
$.each(depr_entries || [], function(i, v) { $.each(depr_entries || [], function(i, v) {
x_intervals.push(frappe.format(v.posting_date, { fieldtype: 'Date' })); x_intervals.push(frappe.format(v.posting_date, { fieldtype: 'Date' }));
let last_asset_value = asset_values[asset_values.length - 1] 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')));
}); });
} }

View File

@@ -413,11 +413,14 @@ class Asset(AccountsController):
if self.journal_entry_for_scrap: if self.journal_entry_for_scrap:
status = "Scrapped" status = "Scrapped"
elif self.finance_books: else:
idx = self.get_default_finance_book_idx() or 0 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 if self.calculate_depreciation:
value_after_depreciation = self.finance_books[idx].value_after_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: if flt(value_after_depreciation) <= expected_value_after_useful_life:
status = "Fully Depreciated" status = "Fully Depreciated"
@@ -463,6 +466,7 @@ class Asset(AccountsController):
.where(gle.debit != 0) .where(gle.debit != 0)
.where(gle.is_cancelled == 0) .where(gle.is_cancelled == 0)
.orderby(gle.posting_date) .orderby(gle.posting_date)
.orderby(gle.creation)
).run(as_dict=True) ).run(as_dict=True)
return records return records

View File

@@ -168,7 +168,7 @@ def make_depreciation_entry(asset_depr_schedule_name, date=None):
row.value_after_depreciation -= d.depreciation_amount row.value_after_depreciation -= d.depreciation_amount
row.db_update() 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() asset.set_status()

View File

@@ -91,6 +91,9 @@ class AssetRepair(AccountsController):
make_new_active_asset_depr_schedules_and_cancel_current_ones(self.asset_doc, notes) make_new_active_asset_depr_schedules_and_cancel_current_ones(self.asset_doc, notes)
self.asset_doc.save() self.asset_doc.save()
def after_delete(self):
frappe.get_doc("Asset", self.asset).set_status()
def check_repair_status(self): def check_repair_status(self):
if self.repair_status == "Pending": if self.repair_status == "Pending":
frappe.throw(_("Please update Repair Status.")) frappe.throw(_("Please update Repair Status."))

View File

@@ -21,6 +21,7 @@
"allow_multiple_items", "allow_multiple_items",
"bill_for_rejected_quantity_in_purchase_invoice", "bill_for_rejected_quantity_in_purchase_invoice",
"disable_last_purchase_rate", "disable_last_purchase_rate",
"show_pay_button",
"subcontract", "subcontract",
"backflush_raw_materials_of_subcontract_based_on", "backflush_raw_materials_of_subcontract_based_on",
"column_break_11", "column_break_11",
@@ -140,6 +141,12 @@
"fieldname": "disable_last_purchase_rate", "fieldname": "disable_last_purchase_rate",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Disable Last Purchase Rate" "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", "icon": "fa fa-cog",
@@ -147,7 +154,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"issingle": 1, "issingle": 1,
"links": [], "links": [],
"modified": "2023-01-09 17:08:28.828173", "modified": "2023-02-15 14:42:10.200679",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Buying Settings", "name": "Buying Settings",

View File

@@ -23,7 +23,6 @@
"default_bank_account", "default_bank_account",
"column_break_10", "column_break_10",
"default_price_list", "default_price_list",
"payment_terms",
"internal_supplier_section", "internal_supplier_section",
"is_internal_supplier", "is_internal_supplier",
"represents_company", "represents_company",
@@ -53,6 +52,7 @@
"supplier_primary_address", "supplier_primary_address",
"primary_address", "primary_address",
"accounting_tab", "accounting_tab",
"payment_terms",
"accounts", "accounts",
"settings_tab", "settings_tab",
"allow_purchase_invoice_creation_without_purchase_order", "allow_purchase_invoice_creation_without_purchase_order",
@@ -457,11 +457,10 @@
"link_fieldname": "party" "link_fieldname": "party"
} }
], ],
"modified": "2022-11-09 18:02:59.075203", "modified": "2023-02-18 11:05:50.592270",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Supplier", "name": "Supplier",
"name_case": "Title Case",
"naming_rule": "By \"Naming Series\" field", "naming_rule": "By \"Naming Series\" field",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [

View File

@@ -204,6 +204,12 @@ class AccountsController(TransactionBase):
validate_einvoice_fields(self) validate_einvoice_fields(self)
def on_trash(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 # delete sl and gl entries on deletion of transaction
if frappe.db.get_single_value("Accounts Settings", "delete_linked_ledger_entries"): if frappe.db.get_single_value("Accounts Settings", "delete_linked_ledger_entries"):
ple = frappe.qb.DocType("Payment Ledger Entry") ple = frappe.qb.DocType("Payment Ledger Entry")

View File

@@ -409,7 +409,14 @@ class SubcontractingController(StockController):
if self.available_materials.get(key) and self.available_materials[key]["batch_no"]: if self.available_materials.get(key) and self.available_materials[key]["batch_no"]:
new_rm_obj = None new_rm_obj = None
for batch_no, batch_qty in self.available_materials[key]["batch_no"].items(): 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.__set_batch_no_as_per_qty(item_row, rm_obj, batch_no, qty)
self.available_materials[key]["batch_no"][batch_no] -= qty self.available_materials[key]["batch_no"][batch_no] -= qty
return return

View File

@@ -12,7 +12,7 @@ class PlaidConnector:
def __init__(self, access_token=None): def __init__(self, access_token=None):
self.access_token = access_token self.access_token = access_token
self.settings = frappe.get_single("Plaid Settings") self.settings = frappe.get_single("Plaid Settings")
self.products = ["auth", "transactions"] self.products = ["transactions"]
self.client_name = frappe.local.site self.client_name = frappe.local.site
self.client = plaid.Client( self.client = plaid.Client(
client_id=self.settings.plaid_client_id, client_id=self.settings.plaid_client_id,

View File

@@ -47,7 +47,7 @@ erpnext.integrations.plaidLink = class plaidLink {
} }
async init_config() { async init_config() {
this.product = ["auth", "transactions"]; this.product = ["transactions"];
this.plaid_env = this.frm.doc.plaid_env; this.plaid_env = this.frm.doc.plaid_env;
this.client_name = frappe.boot.sitename; this.client_name = frappe.boot.sitename;
this.token = await this.get_link_token(); this.token = await this.get_link_token();

View File

@@ -70,7 +70,8 @@ def add_bank_accounts(response, bank, company):
except TypeError: except TypeError:
pass pass
bank = json.loads(bank) if isinstance(bank, str):
bank = json.loads(bank)
result = [] result = []
default_gl_account = get_default_bank_cash_account(company, "Bank") default_gl_account = get_default_bank_cash_account(company, "Bank")
@@ -177,16 +178,15 @@ def sync_transactions(bank, bank_account):
) )
result = [] result = []
for transaction in reversed(transactions): if transactions:
result += new_bank_transaction(transaction) for transaction in reversed(transactions):
result += new_bank_transaction(transaction)
if result: if result:
last_transaction_date = frappe.db.get_value("Bank Transaction", result.pop(), "date") last_transaction_date = frappe.db.get_value("Bank Transaction", result.pop(), "date")
frappe.logger().info( frappe.logger().info(
"Plaid added {} new Bank Transactions from '{}' between {} and {}".format( f"Plaid added {len(result)} new Bank Transactions from '{bank_account}' between {start_date} and {end_date}"
len(result), bank_account, start_date, end_date
)
) )
frappe.db.set_value( frappe.db.set_value(
@@ -230,19 +230,20 @@ def new_bank_transaction(transaction):
bank_account = frappe.db.get_value("Bank Account", dict(integration_id=transaction["account_id"])) bank_account = frappe.db.get_value("Bank Account", dict(integration_id=transaction["account_id"]))
if float(transaction["amount"]) >= 0: amount = float(transaction["amount"])
debit = 0 if amount >= 0.0:
credit = float(transaction["amount"]) deposit = 0.0
withdrawal = amount
else: else:
debit = abs(float(transaction["amount"])) deposit = abs(amount)
credit = 0 withdrawal = 0.0
status = "Pending" if transaction["pending"] == "True" else "Settled" status = "Pending" if transaction["pending"] == "True" else "Settled"
tags = [] tags = []
try: try:
tags += transaction["category"] tags += transaction["category"]
tags += ["Plaid Cat. {}".format(transaction["category_id"])] tags += [f'Plaid Cat. {transaction["category_id"]}']
except KeyError: except KeyError:
pass pass
@@ -254,11 +255,18 @@ def new_bank_transaction(transaction):
"date": getdate(transaction["date"]), "date": getdate(transaction["date"]),
"status": status, "status": status,
"bank_account": bank_account, "bank_account": bank_account,
"deposit": debit, "deposit": deposit,
"withdrawal": credit, "withdrawal": withdrawal,
"currency": transaction["iso_currency_code"], "currency": transaction["iso_currency_code"],
"transaction_id": transaction["transaction_id"], "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"], "description": transaction["name"],
} }
) )
@@ -271,7 +279,7 @@ def new_bank_transaction(transaction):
result.append(new_transaction.name) result.append(new_transaction.name)
except Exception: except Exception:
frappe.throw(title=_("Bank transaction creation error")) frappe.throw(_("Bank transaction creation error"))
return result return result
@@ -300,3 +308,26 @@ def enqueue_synchronization():
def get_link_token_for_update(access_token): def get_link_token_for_update(access_token):
plaid = PlaidConnector(access_token) plaid = PlaidConnector(access_token)
return plaid.get_link_token(update_mode=True) 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))

View File

@@ -125,6 +125,8 @@ class TestPlaidSettings(unittest.TestCase):
"unofficial_currency_code": None, "unofficial_currency_code": None,
"name": "INTRST PYMNT", "name": "INTRST PYMNT",
"transaction_type": "place", "transaction_type": "place",
"transaction_code": "direct debit",
"check_number": "3456789",
"amount": -4.22, "amount": -4.22,
"location": { "location": {
"city": None, "city": None,

View File

@@ -306,7 +306,6 @@ erpnext.patches.v13_0.set_per_billed_in_return_delivery_note
execute:frappe.delete_doc("DocType", "Naming Series") execute:frappe.delete_doc("DocType", "Naming Series")
erpnext.patches.v13_0.job_card_status_on_hold 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.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.crm_ux_cleanup
erpnext.patches.v14_0.migrate_existing_lead_notes_as_per_the_new_format erpnext.patches.v14_0.migrate_existing_lead_notes_as_per_the_new_format
erpnext.patches.v14_0.remove_india_localisation # 14-07-2022 erpnext.patches.v14_0.remove_india_localisation # 14-07-2022
@@ -315,7 +314,6 @@ erpnext.patches.v14_0.remove_hr_and_payroll_modules # 20-07-2022
erpnext.patches.v14_0.fix_crm_no_of_employees erpnext.patches.v14_0.fix_crm_no_of_employees
erpnext.patches.v14_0.create_accounting_dimensions_in_subcontracting_doctypes erpnext.patches.v14_0.create_accounting_dimensions_in_subcontracting_doctypes
erpnext.patches.v14_0.fix_subcontracting_receipt_gl_entries 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.update_schedule_type_in_loans
erpnext.patches.v13_0.drop_unused_sle_index_parts erpnext.patches.v13_0.drop_unused_sle_index_parts
erpnext.patches.v14_0.create_accounting_dimensions_for_asset_capitalization erpnext.patches.v14_0.create_accounting_dimensions_for_asset_capitalization
@@ -327,3 +325,6 @@ erpnext.patches.v14_0.update_entry_type_for_journal_entry
erpnext.patches.v14_0.change_autoname_for_tax_withheld_vouchers erpnext.patches.v14_0.change_autoname_for_tax_withheld_vouchers
erpnext.patches.v14_0.set_pick_list_status erpnext.patches.v14_0.set_pick_list_status
erpnext.patches.v15_0.update_asset_value_for_manual_depr_entries 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

View File

@@ -17,10 +17,11 @@ def execute():
for report in reports_to_delete: for report in reports_to_delete:
if frappe.db.exists("Report", report): if frappe.db.exists("Report", report):
delete_links_from_desktop_icons(report)
delete_auto_email_reports(report) delete_auto_email_reports(report)
check_and_delete_linked_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): def delete_auto_email_reports(report):
@@ -28,3 +29,10 @@ def delete_auto_email_reports(report):
auto_email_reports = frappe.db.get_values("Auto Email Report", {"report": report}, ["name"]) auto_email_reports = frappe.db.get_values("Auto Email Report", {"report": report}, ["name"])
for auto_email_report in auto_email_reports: for auto_email_report in auto_email_reports:
frappe.delete_doc("Auto Email Report", auto_email_report[0]) 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)

View File

@@ -408,7 +408,7 @@
"depends_on": "eval:(doc.frequency == \"Daily\" && doc.collect_progress == true)", "depends_on": "eval:(doc.frequency == \"Daily\" && doc.collect_progress == true)",
"fieldname": "daily_time_to_send", "fieldname": "daily_time_to_send",
"fieldtype": "Time", "fieldtype": "Time",
"label": "Time to send" "label": "Daily Time to send"
}, },
{ {
"depends_on": "eval:(doc.frequency == \"Weekly\" && doc.collect_progress == true)", "depends_on": "eval:(doc.frequency == \"Weekly\" && doc.collect_progress == true)",
@@ -421,7 +421,7 @@
"depends_on": "eval:(doc.frequency == \"Weekly\" && doc.collect_progress == true)", "depends_on": "eval:(doc.frequency == \"Weekly\" && doc.collect_progress == true)",
"fieldname": "weekly_time_to_send", "fieldname": "weekly_time_to_send",
"fieldtype": "Time", "fieldtype": "Time",
"label": "Time to send" "label": "Weekly Time to send"
}, },
{ {
"fieldname": "column_break_45", "fieldname": "column_break_45",
@@ -451,7 +451,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"max_attachments": 4, "max_attachments": 4,
"modified": "2022-06-23 16:45:06.108499", "modified": "2023-02-14 04:54:25.819620",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Projects", "module": "Projects",
"name": "Project", "name": "Project",

View File

@@ -282,21 +282,21 @@
{ {
"fieldname": "base_total_costing_amount", "fieldname": "base_total_costing_amount",
"fieldtype": "Currency", "fieldtype": "Currency",
"label": "Total Costing Amount", "label": "Base Total Costing Amount",
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1
}, },
{ {
"fieldname": "base_total_billable_amount", "fieldname": "base_total_billable_amount",
"fieldtype": "Currency", "fieldtype": "Currency",
"label": "Total Billable Amount", "label": "Base Total Billable Amount",
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1
}, },
{ {
"fieldname": "base_total_billed_amount", "fieldname": "base_total_billed_amount",
"fieldtype": "Currency", "fieldtype": "Currency",
"label": "Total Billed Amount", "label": "Base Total Billed Amount",
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1
}, },
@@ -311,10 +311,11 @@
"idx": 1, "idx": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2022-06-15 22:08:53.930200", "modified": "2023-02-14 04:55:41.735991",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Projects", "module": "Projects",
"name": "Timesheet", "name": "Timesheet",
"naming_rule": "By \"Naming Series\" field",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
@@ -388,5 +389,6 @@
], ],
"sort_field": "modified", "sort_field": "modified",
"sort_order": "ASC", "sort_order": "ASC",
"states": [],
"title_field": "title" "title_field": "title"
} }

View File

@@ -182,6 +182,9 @@ erpnext.accounts.bank_reconciliation.DataTableManager = class DataTableManager {
); );
} else { } else {
this.transactions.splice(transaction_index, 1); 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); this.datatable.refresh(this.transactions, this.columns);

View File

@@ -20,7 +20,7 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager {
doctype: "Bank Transaction", doctype: "Bank Transaction",
filters: { name: this.bank_transaction_name }, filters: { name: this.bank_transaction_name },
fieldname: [ fieldname: [
"date as reference_date", "date",
"deposit", "deposit",
"withdrawal", "withdrawal",
"currency", "currency",
@@ -33,6 +33,7 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager {
"party", "party",
"unallocated_amount", "unallocated_amount",
"allocated_amount", "allocated_amount",
"transaction_type",
], ],
}, },
callback: (r) => { callback: (r) => {
@@ -41,11 +42,23 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager {
r.message.payment_entry = 1; r.message.payment_entry = 1;
r.message.journal_entry = 1; r.message.journal_entry = 1;
this.dialog.set_values(r.message); this.dialog.set_values(r.message);
this.copy_data_to_voucher();
this.dialog.show(); 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) { get_linked_vouchers(document_types) {
frappe.call({ frappe.call({
method: method:
@@ -75,10 +88,9 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager {
row[1], row[1],
row[2], row[2],
reference_date, reference_date,
row[8],
format_currency(row[3], row[9]), format_currency(row[3], row[9]),
row[6],
row[4], row[4],
row[6],
]); ]);
}); });
this.get_dt_columns(); this.get_dt_columns();
@@ -104,7 +116,7 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager {
{ {
name: __("Document Name"), name: __("Document Name"),
editable: false, editable: false,
width: 150, width: 1,
}, },
{ {
name: __("Reference Date"), name: __("Reference Date"),
@@ -112,25 +124,19 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager {
width: 120, width: 120,
}, },
{ {
name: "Posting Date", name: __("Remaining"),
editable: false,
width: 120,
},
{
name: __("Amount"),
editable: false, editable: false,
width: 100, width: 100,
}, },
{
name: __("Party"),
editable: false,
width: 120,
},
{ {
name: __("Reference Number"), name: __("Reference Number"),
editable: false, editable: false,
width: 140, width: 200,
},
{
name: __("Party"),
editable: false,
width: 100,
}, },
]; ];
} }
@@ -224,6 +230,16 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager {
fieldname: "exact_match", fieldname: "exact_match",
onchange: () => this.update_options(), 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", fieldtype: "Section Break",
fieldname: "section_break_1", fieldname: "section_break_1",
@@ -289,7 +305,7 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager {
fieldtype: "Column Break", fieldtype: "Column Break",
}, },
{ {
default: "Journal Entry Type", default: "Bank Entry",
fieldname: "journal_entry_type", fieldname: "journal_entry_type",
fieldtype: "Select", fieldtype: "Select",
label: "Journal Entry Type", label: "Journal Entry Type",
@@ -364,7 +380,12 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager {
fieldtype: "Section Break", fieldtype: "Section Break",
fieldname: "details_section", fieldname: "details_section",
label: "Transaction Details", label: "Transaction Details",
collapsible: 1, },
{
fieldname: "date",
fieldtype: "Date",
label: "Date",
read_only: 1,
}, },
{ {
fieldname: "deposit", fieldname: "deposit",
@@ -381,14 +402,14 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager {
read_only: 1, read_only: 1,
}, },
{ {
fieldname: "description", fieldname: "column_break_17",
fieldtype: "Small Text", fieldtype: "Column Break",
label: "Description",
read_only: 1, read_only: 1,
}, },
{ {
fieldname: "column_break_17", fieldname: "description",
fieldtype: "Column Break", fieldtype: "Small Text",
label: "Description",
read_only: 1, read_only: 1,
}, },
{ {
@@ -398,7 +419,6 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager {
options: "Currency", options: "Currency",
read_only: 1, read_only: 1,
}, },
{ {
fieldname: "unallocated_amount", fieldname: "unallocated_amount",
fieldtype: "Currency", fieldtype: "Currency",

View File

@@ -24,10 +24,10 @@
"account_manager", "account_manager",
"image", "image",
"defaults_tab", "defaults_tab",
"default_price_list", "default_currency",
"default_bank_account", "default_bank_account",
"column_break_14", "column_break_14",
"default_currency", "default_price_list",
"internal_customer_section", "internal_customer_section",
"is_internal_customer", "is_internal_customer",
"represents_company", "represents_company",
@@ -568,11 +568,10 @@
"link_fieldname": "party" "link_fieldname": "party"
} }
], ],
"modified": "2022-11-08 15:52:34.462657", "modified": "2023-02-18 11:04:46.343527",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Selling", "module": "Selling",
"name": "Customer", "name": "Customer",
"name_case": "Title Case",
"naming_rule": "By \"Naming Series\" field", "naming_rule": "By \"Naming Series\" field",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [

View File

@@ -1,5 +1,6 @@
{ {
"actions": [], "actions": [],
"allow_import": 1,
"creation": "2021-08-27 19:28:07.559978", "creation": "2021-08-27 19:28:07.559978",
"doctype": "DocType", "doctype": "DocType",
"editable_grid": 1, "editable_grid": 1,
@@ -51,7 +52,7 @@
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"modified": "2021-09-14 13:27:58.612334", "modified": "2023-02-15 13:00:50.379713",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Selling", "module": "Selling",
"name": "Party Specific Item", "name": "Party Specific Item",
@@ -72,6 +73,7 @@
], ],
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"states": [],
"title_field": "party", "title_field": "party",
"track_changes": 1 "track_changes": 1
} }

View File

@@ -521,6 +521,7 @@
"allow_bulk_edit": 1, "allow_bulk_edit": 1,
"fieldname": "items", "fieldname": "items",
"fieldtype": "Table", "fieldtype": "Table",
"label": "Delivery Note Item",
"oldfieldname": "delivery_note_details", "oldfieldname": "delivery_note_details",
"oldfieldtype": "Table", "oldfieldtype": "Table",
"options": "Delivery Note Item", "options": "Delivery Note Item",
@@ -666,6 +667,7 @@
{ {
"fieldname": "taxes", "fieldname": "taxes",
"fieldtype": "Table", "fieldtype": "Table",
"label": "Sales Taxes and Charges",
"oldfieldname": "other_charges", "oldfieldname": "other_charges",
"oldfieldtype": "Table", "oldfieldtype": "Table",
"options": "Sales Taxes and Charges" "options": "Sales Taxes and Charges"
@@ -1401,7 +1403,7 @@
"idx": 146, "idx": 146,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2022-12-12 18:38:53.067799", "modified": "2023-02-14 04:45:44.179670",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Delivery Note", "name": "Delivery Note",

View File

@@ -14,7 +14,7 @@ frappe.listview_settings['Delivery Note'] = {
return [__("Completed"), "green", "per_billed,=,100"]; return [__("Completed"), "green", "per_billed,=,100"];
} }
}, },
onload: function (listview) { onload: function (doclist) {
const action = () => { const action = () => {
const selected_docs = doclist.get_checked_items(); const selected_docs = doclist.get_checked_items();
const docnames = doclist.get_checked_items(true); const docnames = doclist.get_checked_items(true);
@@ -56,14 +56,14 @@ frappe.listview_settings['Delivery Note'] = {
// doclist.page.add_actions_menu_item(__('Create Delivery Trip'), action, false); // 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"), ()=>{ doclist.page.add_action_item(__("Sales Invoice"), ()=>{
erpnext.bulk_transaction_processing.create(listview, "Delivery Note", "Sales Invoice"); erpnext.bulk_transaction_processing.create(doclist, "Delivery Note", "Sales Invoice");
}); });
listview.page.add_action_item(__("Packaging Slip From Delivery Note"), ()=>{ doclist.page.add_action_item(__("Packaging Slip From Delivery Note"), ()=>{
erpnext.bulk_transaction_processing.create(listview, "Delivery Note", "Packing Slip"); erpnext.bulk_transaction_processing.create(doclist, "Delivery Note", "Packing Slip");
}); });
} }
}; };

View File

@@ -706,7 +706,7 @@
"depends_on": "enable_deferred_expense", "depends_on": "enable_deferred_expense",
"fieldname": "no_of_months_exp", "fieldname": "no_of_months_exp",
"fieldtype": "Int", "fieldtype": "Int",
"label": "No of Months" "label": "No of Months (Expense)"
}, },
{ {
"collapsible": 1, "collapsible": 1,
@@ -911,7 +911,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"make_attachments_public": 1, "make_attachments_public": 1,
"modified": "2023-01-07 22:45:00.341745", "modified": "2023-02-14 04:48:26.343620",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Item", "name": "Item",

View File

@@ -2,7 +2,22 @@
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on('Stock Reposting Settings', { 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();
}
}
})
})
}
}); });

View File

@@ -1,6 +1,8 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt # For license information, please see license.txt
import frappe
from frappe import _
from frappe.model.document import Document from frappe.model.document import Document
from frappe.utils import add_to_date, get_datetime, get_time_str, time_diff_in_hours from frappe.utils import add_to_date, get_datetime, get_time_str, time_diff_in_hours
@@ -24,3 +26,62 @@ class StockRepostingSettings(Document):
if diff < 10: if diff < 10:
self.end_time = get_time_str(add_to_date(self.start_time, hours=10, as_datetime=True)) 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()

View File

@@ -2,6 +2,7 @@
# See license.txt # See license.txt
import copy import copy
from collections import defaultdict
import frappe import frappe
from frappe.tests.utils import FrappeTestCase from frappe.tests.utils import FrappeTestCase
@@ -186,6 +187,40 @@ class TestSubcontractingOrder(FrappeTestCase):
) )
self.assertEqual(len(ste.items), len(rm_items)) 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): def test_update_reserved_qty_for_subcontracting(self):
# Create RM Material Receipt # Create RM Material Receipt
make_stock_entry(target="_Test Warehouse - _TC", item_code="_Test Item", qty=10, basic_rate=100) make_stock_entry(target="_Test Warehouse - _TC", item_code="_Test Item", qty=10, basic_rate=100)

View File

@@ -51,13 +51,31 @@ frappe.ui.form.on('Subcontracting Receipt', {
} }
})); }));
frm.set_query("expense_account", "items", function () { frm.set_query('expense_account', 'items', function () {
return { return {
query: "erpnext.controllers.queries.get_expense_account", query: 'erpnext.controllers.queries.get_expense_account',
filters: { 'company': frm.doc.company } 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 => { frappe.db.get_single_value('Buying Settings', 'backflush_raw_materials_of_subcontract_based_on').then(val => {
if (val == 'Material Transferred for Subcontract') { if (val == 'Material Transferred for Subcontract') {
frm.fields_dict['supplied_items'].grid.grid_rows.forEach((grid_row) => { frm.fields_dict['supplied_items'].grid.grid_rows.forEach((grid_row) => {
@@ -73,7 +91,7 @@ frappe.ui.form.on('Subcontracting Receipt', {
refresh: (frm) => { refresh: (frm) => {
if (frm.doc.docstatus > 0) { if (frm.doc.docstatus > 0) {
frm.add_custom_button(__("Stock Ledger"), function () { frm.add_custom_button(__('Stock Ledger'), function () {
frappe.route_options = { frappe.route_options = {
voucher_no: frm.doc.name, voucher_no: frm.doc.name,
from_date: frm.doc.posting_date, from_date: frm.doc.posting_date,
@@ -81,8 +99,8 @@ frappe.ui.form.on('Subcontracting Receipt', {
company: frm.doc.company, company: frm.doc.company,
show_cancelled_entries: frm.doc.docstatus === 2 show_cancelled_entries: frm.doc.docstatus === 2
}; };
frappe.set_route("query-report", "Stock Ledger"); frappe.set_route('query-report', 'Stock Ledger');
}, __("View")); }, __('View'));
frm.add_custom_button(__('Accounting Ledger'), function () { frm.add_custom_button(__('Accounting Ledger'), function () {
frappe.route_options = { frappe.route_options = {
@@ -90,11 +108,11 @@ frappe.ui.form.on('Subcontracting Receipt', {
from_date: frm.doc.posting_date, from_date: frm.doc.posting_date,
to_date: moment(frm.doc.modified).format('YYYY-MM-DD'), to_date: moment(frm.doc.modified).format('YYYY-MM-DD'),
company: frm.doc.company, company: frm.doc.company,
group_by: "Group by Voucher (Consolidated)", group_by: 'Group by Voucher (Consolidated)',
show_cancelled_entries: frm.doc.docstatus === 2 show_cancelled_entries: frm.doc.docstatus === 2
}; };
frappe.set_route("query-report", "General Ledger"); frappe.set_route('query-report', 'General Ledger');
}, __("View")); }, __('View'));
} }
if (!frm.doc.is_return && frm.doc.docstatus == 1 && frm.doc.per_returned < 100) { if (!frm.doc.is_return && frm.doc.docstatus == 1 && frm.doc.per_returned < 100) {
@@ -111,25 +129,25 @@ frappe.ui.form.on('Subcontracting Receipt', {
frm.add_custom_button(__('Subcontracting Order'), function () { frm.add_custom_button(__('Subcontracting Order'), function () {
if (!frm.doc.supplier) { if (!frm.doc.supplier) {
frappe.throw({ frappe.throw({
title: __("Mandatory"), title: __('Mandatory'),
message: __("Please Select a Supplier") message: __('Please Select a Supplier')
}); });
} }
erpnext.utils.map_current_doc({ erpnext.utils.map_current_doc({
method: 'erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order.make_subcontracting_receipt', method: 'erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order.make_subcontracting_receipt',
source_doctype: "Subcontracting Order", source_doctype: 'Subcontracting Order',
target: frm, target: frm,
setters: { setters: {
supplier: frm.doc.supplier, supplier: frm.doc.supplier,
}, },
get_query_filters: { get_query_filters: {
docstatus: 1, docstatus: 1,
per_received: ["<", 100], per_received: ['<', 100],
company: frm.doc.company company: frm.doc.company
} }
}); });
}, __("Get Items From")); }, __('Get Items From'));
} }
}, },

View File

@@ -34,16 +34,18 @@
</a> </a>
</ul> </ul>
</div> </div>
<div class="form-column col-sm-6"> {% if show_pay_button %}
<div class="page-header-actions-block" data-html-block="header-actions"> <div class="form-column col-sm-6">
<p> <div class="page-header-actions-block" data-html-block="header-actions">
<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" <p>
class="btn btn-primary btn-sm" id="pay-for-order"> <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"
{{ _("Pay") }} {{doc.get_formatted("grand_total") }} class="btn btn-primary btn-sm" id="pay-for-order">
</a> {{ _("Pay") }} {{doc.get_formatted("grand_total") }}
</p> </a>
</p>
</div>
</div> </div>
</div> {% endif %}
</div> </div>
{% endblock %} {% endblock %}

View File

@@ -55,6 +55,7 @@ def get_context(context):
) )
context.available_loyalty_points = int(loyalty_program_details.get("loyalty_points")) 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 context.show_make_pi_button = False
if context.doc.get("supplier"): if context.doc.get("supplier"):
# show Make Purchase Invoice button based on permission # show Make Purchase Invoice button based on permission