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