mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-17 00:18:39 +00:00
fix(accounts): disallow reversing a reverse journal entry
check read permission on the source entry before the guards run, so the reversal relationship is not disclosed to a user who cannot read it.
(cherry picked from commit 9dd37d5f32)
# Conflicts:
# erpnext/accounts/doctype/journal_entry/journal_entry.js
# erpnext/accounts/doctype/journal_entry/mapper.py
This commit is contained in:
@@ -498,8 +498,360 @@ $.extend(erpnext.journal_entry, {
|
||||
var grid = frm.get_field("accounts").grid;
|
||||
if (grid) grid.set_column_disp(fields, frm.doc.multi_currency);
|
||||
|
||||
<<<<<<< HEAD
|
||||
// dynamic label
|
||||
var field_label_map = {
|
||||
=======
|
||||
lock_reversal_entry(frm) {
|
||||
frm.fields
|
||||
.filter((field) => field.has_input)
|
||||
.filter((field) => !["posting_date", "custom_remark", "remark"].includes(field.df.fieldname))
|
||||
.forEach((field) => frm.set_df_property(field.df.fieldname, "read_only", 1));
|
||||
frm.set_df_property("accounts", "read_only", 1);
|
||||
},
|
||||
|
||||
add_custom_buttons(frm) {
|
||||
if (frm.doc.docstatus > 0) {
|
||||
frm.add_custom_button(
|
||||
__("Ledger"),
|
||||
() => erpnext.journal_entry.show_general_ledger(frm),
|
||||
__("View")
|
||||
);
|
||||
}
|
||||
|
||||
if (frm.doc.docstatus == 1 && !frm.doc.reversal_of) {
|
||||
frm.add_custom_button(
|
||||
__("Reverse Journal Entry"),
|
||||
() => erpnext.journal_entry.reverse_journal_entry(frm),
|
||||
__("Actions")
|
||||
);
|
||||
}
|
||||
|
||||
if (frm.doc.__islocal) {
|
||||
frm.add_custom_button(__("Quick Entry"), () => erpnext.journal_entry.quick_entry(frm));
|
||||
}
|
||||
|
||||
if (
|
||||
frm.doc.voucher_type == "Inter Company Journal Entry" &&
|
||||
frm.doc.docstatus == 1 &&
|
||||
!frm.doc.inter_company_journal_entry_reference
|
||||
) {
|
||||
frm.add_custom_button(
|
||||
__("Create Inter Company Journal Entry"),
|
||||
() => erpnext.journal_entry.make_inter_company_journal_entry(frm),
|
||||
__("Make")
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
show_general_ledger(frm) {
|
||||
frappe.route_options = {
|
||||
voucher_no: frm.doc.name,
|
||||
from_date: frm.doc.posting_date,
|
||||
to_date: moment(frm.doc.modified).format("YYYY-MM-DD"),
|
||||
company: frm.doc.company,
|
||||
finance_book: frm.doc.finance_book,
|
||||
categorize_by: "",
|
||||
show_cancelled_entries: frm.doc.docstatus === 2,
|
||||
};
|
||||
frappe.set_route("query-report", "General Ledger");
|
||||
},
|
||||
|
||||
make_inter_company_journal_entry(frm) {
|
||||
const dialog = new frappe.ui.Dialog({
|
||||
title: __("Select Company"),
|
||||
fields: [
|
||||
{
|
||||
fieldname: "company",
|
||||
fieldtype: "Link",
|
||||
label: __("Company"),
|
||||
options: "Company",
|
||||
reqd: 1,
|
||||
get_query: () => {
|
||||
return { filters: [["Company", "name", "!=", frm.doc.company]] };
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
dialog.set_primary_action(__("Create"), () => {
|
||||
dialog.hide();
|
||||
frappe.call({
|
||||
method: "erpnext.accounts.doctype.journal_entry.mapper.make_inter_company_journal_entry",
|
||||
args: {
|
||||
name: frm.doc.name,
|
||||
voucher_type: frm.doc.voucher_type,
|
||||
company: dialog.get_value("company"),
|
||||
},
|
||||
callback: ({ message }) => {
|
||||
if (message) {
|
||||
const doc = frappe.model.sync(message)[0];
|
||||
frappe.set_route("Form", doc.doctype, doc.name);
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
dialog.show();
|
||||
},
|
||||
|
||||
reverse_journal_entry(frm) {
|
||||
frappe.model.open_mapped_doc({
|
||||
method: "erpnext.accounts.doctype.journal_entry.mapper.make_reverse_journal_entry",
|
||||
frm: frm,
|
||||
});
|
||||
},
|
||||
|
||||
quick_entry(frm) {
|
||||
const naming_series_options = frm.fields_dict.naming_series.df.options;
|
||||
const naming_series_default =
|
||||
frm.fields_dict.naming_series.df.default || naming_series_options.split("\n")[0];
|
||||
|
||||
const dialog = new frappe.ui.Dialog({
|
||||
title: __("Quick Journal Entry"),
|
||||
fields: [
|
||||
{ fieldtype: "Currency", fieldname: "debit", label: __("Amount"), reqd: 1 },
|
||||
{
|
||||
fieldtype: "Link",
|
||||
fieldname: "debit_account",
|
||||
label: __("Debit Account"),
|
||||
reqd: 1,
|
||||
options: "Account",
|
||||
get_query: () => erpnext.journal_entry.account_query(frm),
|
||||
},
|
||||
{
|
||||
fieldtype: "Link",
|
||||
fieldname: "credit_account",
|
||||
label: __("Credit Account"),
|
||||
reqd: 1,
|
||||
options: "Account",
|
||||
get_query: () => erpnext.journal_entry.account_query(frm),
|
||||
},
|
||||
{
|
||||
fieldtype: "Date",
|
||||
fieldname: "posting_date",
|
||||
label: __("Date"),
|
||||
reqd: 1,
|
||||
default: frm.doc.posting_date,
|
||||
},
|
||||
{ fieldtype: "Small Text", fieldname: "remark", label: __("Remark") },
|
||||
{
|
||||
fieldtype: "Select",
|
||||
fieldname: "naming_series",
|
||||
label: __("Series"),
|
||||
reqd: 1,
|
||||
options: naming_series_options,
|
||||
default: naming_series_default,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
dialog.set_primary_action(__("Save"), () => {
|
||||
erpnext.journal_entry.save_quick_entry(frm, dialog.get_values());
|
||||
dialog.hide();
|
||||
});
|
||||
dialog.show();
|
||||
},
|
||||
|
||||
save_quick_entry(frm, values) {
|
||||
frm.set_value("posting_date", values.posting_date);
|
||||
frm.set_value("naming_series", values.naming_series);
|
||||
frm.set_value("custom_remark", values.remark ? 1 : 0);
|
||||
frm.set_value("remark", values.remark || "");
|
||||
|
||||
// clear table in case a previous add left a partially populated row behind
|
||||
frm.clear_table("accounts");
|
||||
|
||||
// grid.add_new_row() adds the row in the UI as well as locals, which the triggers need
|
||||
erpnext.journal_entry.add_quick_entry_row(
|
||||
frm,
|
||||
values.debit_account,
|
||||
"debit_in_account_currency",
|
||||
values.debit
|
||||
);
|
||||
erpnext.journal_entry.add_quick_entry_row(
|
||||
frm,
|
||||
values.credit_account,
|
||||
"credit_in_account_currency",
|
||||
values.debit
|
||||
);
|
||||
|
||||
frm.save();
|
||||
},
|
||||
|
||||
add_quick_entry_row(frm, account, amount_field, amount) {
|
||||
const row = frm.fields_dict.accounts.grid.add_new_row();
|
||||
frappe.model.set_value(row.doctype, row.name, "account", account);
|
||||
frappe.model.set_value(row.doctype, row.name, amount_field, amount);
|
||||
},
|
||||
|
||||
get_outstanding(frm, reference_type, reference_name, child) {
|
||||
return frappe.call({
|
||||
method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_outstanding",
|
||||
args: {
|
||||
doctype: reference_type,
|
||||
docname: reference_name,
|
||||
company: frm.doc.company,
|
||||
account: child.account,
|
||||
party: child.party,
|
||||
account_currency: child.account_currency,
|
||||
},
|
||||
callback: ({ message }) => {
|
||||
if (!message) return;
|
||||
Object.entries(message).forEach(([field, value]) =>
|
||||
frappe.model.set_value(child.doctype, child.name, field, value)
|
||||
);
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
set_account_details(frm, cdt, cdn) {
|
||||
const row = frappe.get_doc(cdt, cdn);
|
||||
if (!row.account) {
|
||||
erpnext.journal_entry.clear_fields(frm, cdt, cdn);
|
||||
return;
|
||||
}
|
||||
if (!frm.doc.company) frappe.throw(__("Please select Company first"));
|
||||
if (!frm.doc.posting_date) frappe.throw(__("Please select Posting Date first"));
|
||||
|
||||
return frappe.call({
|
||||
method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_account_details_and_party_type",
|
||||
args: {
|
||||
account: row.account,
|
||||
date: frm.doc.posting_date,
|
||||
company: frm.doc.company,
|
||||
debit: flt(row.debit_in_account_currency),
|
||||
credit: flt(row.credit_in_account_currency),
|
||||
exchange_rate: row.exchange_rate,
|
||||
},
|
||||
callback: ({ message }) => {
|
||||
if (!message) return;
|
||||
$.extend(row, message);
|
||||
erpnext.journal_entry.set_amount_on_last_row(frm, cdt, cdn);
|
||||
erpnext.journal_entry.set_debit_credit_in_company_currency(frm, cdt, cdn);
|
||||
frm.refresh_field("accounts");
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
set_amount_on_last_row(frm, cdt, cdn) {
|
||||
const row = frappe.get_doc(cdt, cdn);
|
||||
if (row.idx != frm.doc.accounts.length) return;
|
||||
|
||||
const difference = frm.doc.accounts.reduce((total, account) => {
|
||||
return account.idx == row.idx ? total : total + account.debit - account.credit;
|
||||
}, 0);
|
||||
erpnext.journal_entry.set_balancing_amount(row, difference);
|
||||
},
|
||||
|
||||
set_balancing_amount(row, difference) {
|
||||
if (!difference) return;
|
||||
|
||||
const exchange_rate = row.exchange_rate || 1;
|
||||
if (difference > 0) {
|
||||
row.credit_in_account_currency = difference / exchange_rate;
|
||||
row.credit = difference;
|
||||
} else {
|
||||
row.debit_in_account_currency = -difference / exchange_rate;
|
||||
row.debit = -difference;
|
||||
}
|
||||
},
|
||||
|
||||
clear_fields(frm, cdt, cdn) {
|
||||
const row = frappe.get_doc(cdt, cdn);
|
||||
row.party_type = null;
|
||||
row.party = null;
|
||||
row.bank_account = null;
|
||||
frm.refresh_field("accounts");
|
||||
},
|
||||
|
||||
setup_queries(frm) {
|
||||
frm.set_query("periodic_entry_difference_account", () => {
|
||||
return { filters: { is_group: 0, company: frm.doc.company } };
|
||||
});
|
||||
|
||||
frm.set_query("stock_asset_account", () => {
|
||||
return { filters: { is_group: 0, account_type: "Stock", company: frm.doc.company } };
|
||||
});
|
||||
|
||||
frm.set_query("project", "accounts", (doc, cdt, cdn) => {
|
||||
const row = frappe.get_doc(cdt, cdn);
|
||||
const filters = { company: doc.company };
|
||||
if (row.party_type == "Customer") filters.customer = row.party;
|
||||
return { query: "erpnext.controllers.queries.get_project_name", filters };
|
||||
});
|
||||
|
||||
frm.set_query("account", "accounts", () => erpnext.journal_entry.account_query(frm));
|
||||
|
||||
frm.set_query("party_type", "accounts", (doc, cdt, cdn) => {
|
||||
return {
|
||||
query: "erpnext.setup.doctype.party_type.party_type.get_party_type",
|
||||
filters: { account: frappe.get_doc(cdt, cdn).account },
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("reference_name", "accounts", (doc, cdt, cdn) => {
|
||||
return erpnext.journal_entry.reference_name_query(frappe.get_doc(cdt, cdn));
|
||||
});
|
||||
},
|
||||
|
||||
reference_name_query(row) {
|
||||
if (row.reference_type === "Journal Entry") {
|
||||
frappe.model.validate_missing(row, "account");
|
||||
return {
|
||||
query: "erpnext.accounts.doctype.journal_entry.journal_entry.get_against_jv",
|
||||
filters: { account: row.account, party: row.party },
|
||||
};
|
||||
}
|
||||
|
||||
const out = { filters: [[row.reference_type, "docstatus", "=", 1]] };
|
||||
|
||||
if (["Sales Invoice", "Purchase Invoice"].includes(row.reference_type)) {
|
||||
out.filters.push([row.reference_type, "outstanding_amount", "!=", 0]);
|
||||
if (row.cost_center) {
|
||||
out.filters.push([row.reference_type, "cost_center", "in", ["", row.cost_center]]);
|
||||
}
|
||||
frappe.model.validate_missing(row, "account");
|
||||
const party_account_field = row.reference_type === "Sales Invoice" ? "debit_to" : "credit_to";
|
||||
out.filters.push([row.reference_type, party_account_field, "=", row.account]);
|
||||
}
|
||||
|
||||
if (["Sales Order", "Purchase Order"].includes(row.reference_type)) {
|
||||
frappe.model.validate_missing(row, "party_type");
|
||||
frappe.model.validate_missing(row, "party");
|
||||
out.filters.push([row.reference_type, "per_billed", "<", 100]);
|
||||
}
|
||||
|
||||
if (row.party_type && row.party) {
|
||||
let party_field = "";
|
||||
if (row.reference_type.indexOf("Sales") === 0) {
|
||||
party_field = "customer";
|
||||
} else if (row.reference_type.indexOf("Purchase") === 0) {
|
||||
party_field = "supplier";
|
||||
}
|
||||
if (party_field) out.filters.push([row.reference_type, party_field, "=", row.party]);
|
||||
}
|
||||
|
||||
return out;
|
||||
},
|
||||
|
||||
account_query(frm) {
|
||||
const filters = { company: frm.doc.company, is_group: 0 };
|
||||
if (!frm.doc.multi_currency) {
|
||||
const company_currency = erpnext.get_currency(frm.doc.company);
|
||||
filters.account_currency = ["in", [company_currency, null]];
|
||||
}
|
||||
return { filters };
|
||||
},
|
||||
|
||||
toggle_fields_based_on_currency(frm) {
|
||||
const fields = ["currency_section", "account_currency", "exchange_rate", "debit", "credit"];
|
||||
const grid = frm.get_field("accounts").grid;
|
||||
if (!grid) return;
|
||||
|
||||
grid.set_column_disp(fields, frm.doc.multi_currency);
|
||||
|
||||
const field_label_map = {
|
||||
>>>>>>> 9dd37d5f32 (fix(accounts): disallow reversing a reverse journal entry)
|
||||
debit_in_account_currency: "Debit",
|
||||
credit_in_account_currency: "Credit",
|
||||
};
|
||||
|
||||
275
erpnext/accounts/doctype/journal_entry/mapper.py
Normal file
275
erpnext/accounts/doctype/journal_entry/mapper.py
Normal file
@@ -0,0 +1,275 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
"""Document builders that map a source document to a Journal Entry or to a
|
||||
Payment Entry raised against it."""
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import flt, get_link_to_form, nowdate
|
||||
|
||||
from erpnext.accounts.doctype.invoice_discounting.invoice_discounting import (
|
||||
get_party_account_based_on_invoice_discounting,
|
||||
)
|
||||
from erpnext.accounts.party import get_party_account
|
||||
from erpnext.accounts.utils import get_account_currency
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_payment_entry_against_order(
|
||||
dt: str,
|
||||
dn: str,
|
||||
amount: float | None = None,
|
||||
debit_in_account_currency: str | float | None = None,
|
||||
journal_entry: bool = False,
|
||||
bank_account: str | None = None,
|
||||
) -> dict | Document:
|
||||
"""Build an advance-payment Journal Entry against an unbilled Sales/Purchase Order."""
|
||||
ref_doc = frappe.get_doc(dt, dn)
|
||||
|
||||
if flt(ref_doc.per_billed, 2) > 0:
|
||||
frappe.throw(_("Can only make payment against unbilled {0}").format(dt))
|
||||
|
||||
if dt == "Sales Order":
|
||||
party_type = "Customer"
|
||||
amount_field_party = "credit_in_account_currency"
|
||||
amount_field_bank = "debit_in_account_currency"
|
||||
else:
|
||||
party_type = "Supplier"
|
||||
amount_field_party = "debit_in_account_currency"
|
||||
amount_field_bank = "credit_in_account_currency"
|
||||
|
||||
party_account = get_party_account(party_type, ref_doc.get(party_type.lower()), ref_doc.company)
|
||||
party_account_currency = get_account_currency(party_account)
|
||||
|
||||
if not amount:
|
||||
if party_account_currency == ref_doc.company_currency:
|
||||
amount = flt(ref_doc.base_grand_total) - flt(ref_doc.advance_paid)
|
||||
else:
|
||||
amount = flt(ref_doc.grand_total) - flt(ref_doc.advance_paid)
|
||||
|
||||
return get_payment_entry(
|
||||
ref_doc,
|
||||
{
|
||||
"party_type": party_type,
|
||||
"party_account": party_account,
|
||||
"party_account_currency": party_account_currency,
|
||||
"amount_field_party": amount_field_party,
|
||||
"amount_field_bank": amount_field_bank,
|
||||
"amount": amount,
|
||||
"debit_in_account_currency": debit_in_account_currency,
|
||||
"remarks": f"Advance Payment received against {dt} {dn}",
|
||||
"is_advance": "Yes",
|
||||
"bank_account": bank_account,
|
||||
"journal_entry": journal_entry,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_payment_entry_against_invoice(
|
||||
dt: str,
|
||||
dn: str,
|
||||
amount: float | None = None,
|
||||
debit_in_account_currency: str | None = None,
|
||||
journal_entry: bool = False,
|
||||
bank_account: str | None = None,
|
||||
) -> dict | Document:
|
||||
"""Build a payment Journal Entry against a Sales/Purchase Invoice's outstanding amount."""
|
||||
ref_doc = frappe.get_doc(dt, dn)
|
||||
if dt == "Sales Invoice":
|
||||
party_type = "Customer"
|
||||
party_account = get_party_account_based_on_invoice_discounting(dn) or ref_doc.debit_to
|
||||
else:
|
||||
party_type = "Supplier"
|
||||
party_account = ref_doc.credit_to
|
||||
|
||||
if (dt == "Sales Invoice" and ref_doc.outstanding_amount > 0) or (
|
||||
dt == "Purchase Invoice" and ref_doc.outstanding_amount < 0
|
||||
):
|
||||
amount_field_party = "credit_in_account_currency"
|
||||
amount_field_bank = "debit_in_account_currency"
|
||||
else:
|
||||
amount_field_party = "debit_in_account_currency"
|
||||
amount_field_bank = "credit_in_account_currency"
|
||||
|
||||
return get_payment_entry(
|
||||
ref_doc,
|
||||
{
|
||||
"party_type": party_type,
|
||||
"party_account": party_account,
|
||||
"party_account_currency": ref_doc.party_account_currency,
|
||||
"amount_field_party": amount_field_party,
|
||||
"amount_field_bank": amount_field_bank,
|
||||
"amount": amount if amount else abs(ref_doc.outstanding_amount),
|
||||
"debit_in_account_currency": debit_in_account_currency,
|
||||
"remarks": f"Payment received against {dt} {dn}. {ref_doc.remarks}",
|
||||
"is_advance": "No",
|
||||
"bank_account": bank_account,
|
||||
"journal_entry": journal_entry,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def get_payment_entry(ref_doc, args: dict) -> dict | Document:
|
||||
"""Build a Bank Entry Journal Entry paying `ref_doc`, with a party row and a bank row.
|
||||
|
||||
Returns the Journal Entry document when `args["journal_entry"]` is truthy, otherwise its
|
||||
dict (for client calls).
|
||||
"""
|
||||
je = frappe.new_doc("Journal Entry")
|
||||
je.update({"voucher_type": "Bank Entry", "company": ref_doc.company, "remark": args.get("remarks")})
|
||||
|
||||
cost_center = ref_doc.get("cost_center") or frappe.get_cached_value(
|
||||
"Company", ref_doc.company, "cost_center"
|
||||
)
|
||||
exchange_rate = _reference_exchange_rate(ref_doc, args)
|
||||
|
||||
party_row = _append_party_row(je, ref_doc, args, cost_center, exchange_rate)
|
||||
bank_row = _append_bank_row(je, ref_doc, args, cost_center, exchange_rate)
|
||||
|
||||
if party_row.account_currency != ref_doc.company_currency or (
|
||||
bank_row.account_currency and bank_row.account_currency != ref_doc.company_currency
|
||||
):
|
||||
je.multi_currency = 1
|
||||
|
||||
je.set_amounts_in_company_currency()
|
||||
je.set_total_debit_credit()
|
||||
|
||||
return je if args.get("journal_entry") else je.as_dict()
|
||||
|
||||
|
||||
def _reference_exchange_rate(ref_doc, args: dict) -> float:
|
||||
"""Exchange rate of the party account on the reference document's posting date."""
|
||||
if not args.get("party_account"):
|
||||
return 1
|
||||
|
||||
from erpnext.accounts.doctype.journal_entry.journal_entry import get_exchange_rate
|
||||
|
||||
return get_exchange_rate(
|
||||
ref_doc.get("posting_date") or ref_doc.get("transaction_date"),
|
||||
args.get("party_account"),
|
||||
args.get("party_account_currency"),
|
||||
ref_doc.company,
|
||||
ref_doc.doctype,
|
||||
ref_doc.name,
|
||||
)
|
||||
|
||||
|
||||
def _append_party_row(je, ref_doc, args: dict, cost_center, exchange_rate: float):
|
||||
"""Append the party (debtor/creditor) row that records the advance/payment."""
|
||||
return je.append(
|
||||
"accounts",
|
||||
{
|
||||
"account": args.get("party_account"),
|
||||
"party_type": args.get("party_type"),
|
||||
"party": ref_doc.get(args.get("party_type").lower()),
|
||||
"cost_center": cost_center,
|
||||
"account_type": frappe.get_cached_value("Account", args.get("party_account"), "account_type"),
|
||||
"account_currency": args.get("party_account_currency")
|
||||
or get_account_currency(args.get("party_account")),
|
||||
"exchange_rate": exchange_rate,
|
||||
args.get("amount_field_party"): args.get("amount"),
|
||||
"is_advance": args.get("is_advance"),
|
||||
"reference_type": ref_doc.doctype,
|
||||
"reference_name": ref_doc.name,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def _append_bank_row(je, ref_doc, args: dict, cost_center, exchange_rate: float):
|
||||
"""Append the bank/cash row, defaulting the account and converting the amount to it."""
|
||||
from erpnext.accounts.doctype.journal_entry.journal_entry import (
|
||||
get_default_bank_cash_account,
|
||||
get_exchange_rate,
|
||||
)
|
||||
|
||||
bank_row = je.append("accounts")
|
||||
bank_account = get_default_bank_cash_account(ref_doc.company, "Bank", account=args.get("bank_account"))
|
||||
if bank_account:
|
||||
bank_row.update(bank_account)
|
||||
# posting date assumed to be the reference document's posting/transaction date
|
||||
bank_row.exchange_rate = get_exchange_rate(
|
||||
ref_doc.get("posting_date") or ref_doc.get("transaction_date"),
|
||||
bank_account["account"],
|
||||
bank_account["account_currency"],
|
||||
ref_doc.company,
|
||||
)
|
||||
|
||||
bank_row.cost_center = cost_center
|
||||
|
||||
amount = args.get("debit_in_account_currency") or args.get("amount")
|
||||
if bank_row.account_currency == args.get("party_account_currency"):
|
||||
bank_row.set(args.get("amount_field_bank"), amount)
|
||||
else:
|
||||
bank_row.set(args.get("amount_field_bank"), amount * exchange_rate)
|
||||
|
||||
return bank_row
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_inter_company_journal_entry(name: str, voucher_type: str, company: str) -> dict:
|
||||
"""Build the counterpart Journal Entry in another company, linked back to `name`."""
|
||||
journal_entry = frappe.new_doc("Journal Entry")
|
||||
journal_entry.voucher_type = voucher_type
|
||||
journal_entry.company = company
|
||||
journal_entry.posting_date = nowdate()
|
||||
journal_entry.inter_company_journal_entry_reference = name
|
||||
return journal_entry.as_dict()
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_reverse_journal_entry(source_name: str, target_doc: str | dict | Document | None = None) -> Document:
|
||||
"""Map a submitted Journal Entry to a reversing one (debits and credits swapped)."""
|
||||
# `get_mapped_doc` checks this as well, but the guards below disclose which entry
|
||||
# reverses which, so read access has to be settled before they run
|
||||
if not frappe.has_permission("Journal Entry", doc=source_name):
|
||||
frappe.throw(_("Not permitted"), frappe.PermissionError)
|
||||
|
||||
reversal_of = frappe.db.get_value("Journal Entry", source_name, "reversal_of")
|
||||
if reversal_of:
|
||||
frappe.throw(
|
||||
_("{0} is already a Reverse Journal Entry of {1}. Cancel it instead of reversing it.").format(
|
||||
get_link_to_form("Journal Entry", source_name),
|
||||
get_link_to_form("Journal Entry", reversal_of),
|
||||
)
|
||||
)
|
||||
|
||||
existing_reverse = frappe.db.exists("Journal Entry", {"reversal_of": source_name, "docstatus": 1})
|
||||
if existing_reverse:
|
||||
frappe.throw(
|
||||
_("A Reverse Journal Entry {0} already exists for this Journal Entry.").format(
|
||||
get_link_to_form("Journal Entry", existing_reverse)
|
||||
)
|
||||
)
|
||||
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
|
||||
def post_process(source, target) -> None:
|
||||
target.reversal_of = source.name
|
||||
|
||||
doclist = get_mapped_doc(
|
||||
"Journal Entry",
|
||||
source_name,
|
||||
{
|
||||
"Journal Entry": {"doctype": "Journal Entry", "validation": {"docstatus": ["=", 1]}},
|
||||
"Journal Entry Account": {
|
||||
"doctype": "Journal Entry Account",
|
||||
"field_map": {
|
||||
"account_currency": "account_currency",
|
||||
"exchange_rate": "exchange_rate",
|
||||
"debit_in_account_currency": "credit_in_account_currency",
|
||||
"debit": "credit",
|
||||
"credit_in_account_currency": "debit_in_account_currency",
|
||||
"credit": "debit",
|
||||
"reference_type": "reference_type",
|
||||
"reference_name": "reference_name",
|
||||
},
|
||||
},
|
||||
},
|
||||
target_doc,
|
||||
post_process,
|
||||
)
|
||||
|
||||
return doclist
|
||||
Reference in New Issue
Block a user