mirror of
https://github.com/frappe/erpnext.git
synced 2026-07-08 16:02:28 +00:00
Compare commits
1 Commits
v14.48.0
...
develop-ri
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3714b795d6 |
@@ -3,7 +3,7 @@ import inspect
|
||||
|
||||
import frappe
|
||||
|
||||
__version__ = "14.48.0"
|
||||
__version__ = "14.34.3"
|
||||
|
||||
|
||||
def get_default_company(user=None):
|
||||
|
||||
@@ -37,7 +37,6 @@ def make_closing_entries(closing_entries, voucher_name, company, closing_date):
|
||||
}
|
||||
)
|
||||
cle.flags.ignore_permissions = True
|
||||
cle.flags.ignore_links = True
|
||||
cle.submit()
|
||||
|
||||
|
||||
|
||||
@@ -301,30 +301,3 @@ def get_dimensions(with_cost_center_and_project=False):
|
||||
default_dimensions_map[dimension.company][dimension.fieldname] = dimension.default_dimension
|
||||
|
||||
return dimension_filters, default_dimensions_map
|
||||
|
||||
|
||||
def create_accounting_dimensions_for_doctype(doctype):
|
||||
accounting_dimensions = frappe.db.get_all(
|
||||
"Accounting Dimension", fields=["fieldname", "label", "document_type", "disabled"]
|
||||
)
|
||||
|
||||
if not accounting_dimensions:
|
||||
return
|
||||
|
||||
for d in accounting_dimensions:
|
||||
field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": d.fieldname})
|
||||
|
||||
if field:
|
||||
continue
|
||||
|
||||
df = {
|
||||
"fieldname": d.fieldname,
|
||||
"label": d.label,
|
||||
"fieldtype": "Link",
|
||||
"options": d.document_type,
|
||||
"insert_after": "accounting_dimensions_section",
|
||||
}
|
||||
|
||||
create_custom_field(doctype, df, ignore_validate=True)
|
||||
|
||||
frappe.clear_cache(doctype=doctype)
|
||||
|
||||
@@ -17,7 +17,6 @@ from erpnext.accounts.report.bank_reconciliation_statement.bank_reconciliation_s
|
||||
get_entries,
|
||||
)
|
||||
from erpnext.accounts.utils import get_balance_on
|
||||
from erpnext.setup.utils import get_exchange_rate
|
||||
|
||||
|
||||
class BankReconciliationTool(Document):
|
||||
@@ -130,7 +129,7 @@ def create_journal_entry_bts(
|
||||
bank_transaction = frappe.db.get_values(
|
||||
"Bank Transaction",
|
||||
bank_transaction_name,
|
||||
fieldname=["name", "deposit", "withdrawal", "bank_account", "currency"],
|
||||
fieldname=["name", "deposit", "withdrawal", "bank_account"],
|
||||
as_dict=True,
|
||||
)[0]
|
||||
company_account = frappe.get_value("Bank Account", bank_transaction.bank_account, "account")
|
||||
@@ -144,94 +143,29 @@ def create_journal_entry_bts(
|
||||
)
|
||||
|
||||
company = frappe.get_value("Account", company_account, "company")
|
||||
company_default_currency = frappe.get_cached_value("Company", company, "default_currency")
|
||||
company_account_currency = frappe.get_cached_value("Account", company_account, "account_currency")
|
||||
second_account_currency = frappe.get_cached_value("Account", second_account, "account_currency")
|
||||
|
||||
# determine if multi-currency Journal or not
|
||||
is_multi_currency = (
|
||||
True
|
||||
if company_default_currency != company_account_currency
|
||||
or company_default_currency != second_account_currency
|
||||
or company_default_currency != bank_transaction.currency
|
||||
else False
|
||||
)
|
||||
|
||||
accounts = []
|
||||
second_account_dict = {
|
||||
"account": second_account,
|
||||
"account_currency": second_account_currency,
|
||||
"credit_in_account_currency": bank_transaction.deposit,
|
||||
"debit_in_account_currency": bank_transaction.withdrawal,
|
||||
"party_type": party_type,
|
||||
"party": party,
|
||||
"cost_center": get_default_cost_center(company),
|
||||
}
|
||||
# Multi Currency?
|
||||
accounts.append(
|
||||
{
|
||||
"account": second_account,
|
||||
"credit_in_account_currency": bank_transaction.deposit,
|
||||
"debit_in_account_currency": bank_transaction.withdrawal,
|
||||
"party_type": party_type,
|
||||
"party": party,
|
||||
"cost_center": get_default_cost_center(company),
|
||||
}
|
||||
)
|
||||
|
||||
company_account_dict = {
|
||||
"account": company_account,
|
||||
"account_currency": company_account_currency,
|
||||
"bank_account": bank_transaction.bank_account,
|
||||
"credit_in_account_currency": bank_transaction.withdrawal,
|
||||
"debit_in_account_currency": bank_transaction.deposit,
|
||||
"cost_center": get_default_cost_center(company),
|
||||
}
|
||||
|
||||
# convert transaction amount to company currency
|
||||
if is_multi_currency:
|
||||
exc_rate = get_exchange_rate(bank_transaction.currency, company_default_currency, posting_date)
|
||||
withdrawal_in_company_currency = flt(exc_rate * abs(bank_transaction.withdrawal))
|
||||
deposit_in_company_currency = flt(exc_rate * abs(bank_transaction.deposit))
|
||||
else:
|
||||
withdrawal_in_company_currency = bank_transaction.withdrawal
|
||||
deposit_in_company_currency = bank_transaction.deposit
|
||||
|
||||
# if second account is of foreign currency, convert and set debit and credit fields.
|
||||
if second_account_currency != company_default_currency:
|
||||
exc_rate = get_exchange_rate(second_account_currency, company_default_currency, posting_date)
|
||||
second_account_dict.update(
|
||||
{
|
||||
"exchange_rate": exc_rate,
|
||||
"credit": deposit_in_company_currency,
|
||||
"debit": withdrawal_in_company_currency,
|
||||
"credit_in_account_currency": flt(deposit_in_company_currency / exc_rate) or 0,
|
||||
"debit_in_account_currency": flt(withdrawal_in_company_currency / exc_rate) or 0,
|
||||
}
|
||||
)
|
||||
else:
|
||||
second_account_dict.update(
|
||||
{
|
||||
"exchange_rate": 1,
|
||||
"credit": deposit_in_company_currency,
|
||||
"debit": withdrawal_in_company_currency,
|
||||
"credit_in_account_currency": deposit_in_company_currency,
|
||||
"debit_in_account_currency": withdrawal_in_company_currency,
|
||||
}
|
||||
)
|
||||
|
||||
# if company account is of foreign currency, convert and set debit and credit fields.
|
||||
if company_account_currency != company_default_currency:
|
||||
exc_rate = get_exchange_rate(company_account_currency, company_default_currency, posting_date)
|
||||
company_account_dict.update(
|
||||
{
|
||||
"exchange_rate": exc_rate,
|
||||
"credit": withdrawal_in_company_currency,
|
||||
"debit": deposit_in_company_currency,
|
||||
}
|
||||
)
|
||||
else:
|
||||
company_account_dict.update(
|
||||
{
|
||||
"exchange_rate": 1,
|
||||
"credit": withdrawal_in_company_currency,
|
||||
"debit": deposit_in_company_currency,
|
||||
"credit_in_account_currency": withdrawal_in_company_currency,
|
||||
"debit_in_account_currency": deposit_in_company_currency,
|
||||
}
|
||||
)
|
||||
|
||||
accounts.append(second_account_dict)
|
||||
accounts.append(company_account_dict)
|
||||
accounts.append(
|
||||
{
|
||||
"account": company_account,
|
||||
"bank_account": bank_transaction.bank_account,
|
||||
"credit_in_account_currency": bank_transaction.withdrawal,
|
||||
"debit_in_account_currency": bank_transaction.deposit,
|
||||
"cost_center": get_default_cost_center(company),
|
||||
}
|
||||
)
|
||||
|
||||
journal_entry_dict = {
|
||||
"voucher_type": entry_type,
|
||||
@@ -241,9 +175,6 @@ def create_journal_entry_bts(
|
||||
"cheque_no": reference_number,
|
||||
"mode_of_payment": mode_of_payment,
|
||||
}
|
||||
if is_multi_currency:
|
||||
journal_entry_dict.update({"multi_currency": True})
|
||||
|
||||
journal_entry = frappe.new_doc("Journal Entry")
|
||||
journal_entry.update(journal_entry_dict)
|
||||
journal_entry.set("accounts", accounts)
|
||||
|
||||
@@ -2,16 +2,6 @@
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on("Bank Statement Import", {
|
||||
onload(frm) {
|
||||
frm.set_query("bank_account", function (doc) {
|
||||
return {
|
||||
filters: {
|
||||
company: doc.company,
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
setup(frm) {
|
||||
frappe.realtime.on("data_import_refresh", ({ data_import }) => {
|
||||
frm.import_in_progress = false;
|
||||
@@ -362,11 +352,10 @@ frappe.ui.form.on("Bank Statement Import", {
|
||||
|
||||
export_errored_rows(frm) {
|
||||
open_url_post(
|
||||
"/api/method/erpnext.accounts.doctype.bank_statement_import.bank_statement_import.download_errored_template",
|
||||
"/api/method/frappe.core.doctype.data_import.data_import.download_errored_template",
|
||||
{
|
||||
data_import_name: frm.doc.name,
|
||||
},
|
||||
true
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
|
||||
@@ -112,8 +112,7 @@ class AutoMatchbyPartyNameDescription:
|
||||
|
||||
for party in parties:
|
||||
filters = {"status": "Active"} if party == "Employee" else {"disabled": 0}
|
||||
field = party.lower() + "_name"
|
||||
names = frappe.get_all(party, filters=filters, fields=[f"{field} as party_name", "name"])
|
||||
names = frappe.get_all(party, filters=filters, pluck=party.lower() + "_name")
|
||||
|
||||
for field in ["bank_party_name", "description"]:
|
||||
if not self.get(field):
|
||||
@@ -132,11 +131,7 @@ class AutoMatchbyPartyNameDescription:
|
||||
|
||||
def fuzzy_search_and_return_result(self, party, names, field) -> Union[Tuple, None]:
|
||||
skip = False
|
||||
result = process.extract(
|
||||
query=self.get(field),
|
||||
choices={row.get("name"): row.get("party_name") for row in names},
|
||||
scorer=fuzz.token_set_ratio,
|
||||
)
|
||||
result = process.extract(query=self.get(field), choices=names, scorer=fuzz.token_set_ratio)
|
||||
party_name, skip = self.process_fuzzy_result(result)
|
||||
|
||||
if not party_name:
|
||||
@@ -154,14 +149,14 @@ class AutoMatchbyPartyNameDescription:
|
||||
|
||||
Returns: Result, Skip (whether or not to discontinue matching)
|
||||
"""
|
||||
SCORE, PARTY_ID, CUTOFF = 1, 2, 80
|
||||
PARTY, SCORE, CUTOFF = 0, 1, 80
|
||||
|
||||
if not result or not len(result):
|
||||
return None, False
|
||||
|
||||
first_result = result[0]
|
||||
if len(result) == 1:
|
||||
return (first_result[PARTY_ID] if first_result[SCORE] > CUTOFF else None), True
|
||||
return (first_result[PARTY] if first_result[SCORE] > CUTOFF else None), True
|
||||
|
||||
second_result = result[1]
|
||||
if first_result[SCORE] > CUTOFF:
|
||||
@@ -170,7 +165,7 @@ class AutoMatchbyPartyNameDescription:
|
||||
if first_result[SCORE] == second_result[SCORE]:
|
||||
return None, True
|
||||
|
||||
return first_result[PARTY_ID], True
|
||||
return first_result[PARTY], True
|
||||
else:
|
||||
return None, False
|
||||
|
||||
|
||||
@@ -89,6 +89,7 @@ class BankTransaction(StatusUpdater):
|
||||
- 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:
|
||||
|
||||
@@ -53,18 +53,10 @@ frappe.ui.form.on('Chart of Accounts Importer', {
|
||||
of Accounts. Please enter the account names and add more rows as per your requirement.`);
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label : "Company",
|
||||
fieldname: "company",
|
||||
fieldtype: "Link",
|
||||
reqd: 1,
|
||||
hidden: 1,
|
||||
default: frm.doc.company,
|
||||
},
|
||||
}
|
||||
],
|
||||
primary_action: function() {
|
||||
let data = d.get_values();
|
||||
var data = d.get_values();
|
||||
|
||||
if (!data.template_type) {
|
||||
frappe.throw(__('Please select <b>Template Type</b> to download template'));
|
||||
@@ -74,8 +66,7 @@ frappe.ui.form.on('Chart of Accounts Importer', {
|
||||
'/api/method/erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.download_template',
|
||||
{
|
||||
file_type: data.file_type,
|
||||
template_type: data.template_type,
|
||||
company: data.company
|
||||
template_type: data.template_type
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ from functools import reduce
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.desk.form.linked_with import get_linked_fields
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import cint, cstr
|
||||
from frappe.utils.csvutils import UnicodeWriter
|
||||
@@ -295,8 +294,10 @@ def build_response_as_excel(writer):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def download_template(file_type, template_type, company):
|
||||
writer = get_template(template_type, company)
|
||||
def download_template(file_type, template_type):
|
||||
data = frappe._dict(frappe.local.form_dict)
|
||||
|
||||
writer = get_template(template_type)
|
||||
|
||||
if file_type == "CSV":
|
||||
# download csv file
|
||||
@@ -307,7 +308,8 @@ def download_template(file_type, template_type, company):
|
||||
build_response_as_excel(writer)
|
||||
|
||||
|
||||
def get_template(template_type, company):
|
||||
def get_template(template_type):
|
||||
|
||||
fields = [
|
||||
"Account Name",
|
||||
"Parent Account",
|
||||
@@ -333,17 +335,34 @@ def get_template(template_type, company):
|
||||
["", "", "", "", 0, account_type.get("account_type"), account_type.get("root_type")]
|
||||
)
|
||||
else:
|
||||
writer = get_sample_template(writer, company)
|
||||
writer = get_sample_template(writer)
|
||||
|
||||
return writer
|
||||
|
||||
|
||||
def get_sample_template(writer, company):
|
||||
currency = frappe.db.get_value("Company", company, "default_currency")
|
||||
with open(os.path.join(os.path.dirname(__file__), "coa_sample_template.csv"), "r") as f:
|
||||
for row in f:
|
||||
row = row.strip().split(",") + [currency]
|
||||
writer.writerow(row)
|
||||
def get_sample_template(writer):
|
||||
template = [
|
||||
["Application Of Funds(Assets)", "", "", "", 1, "", "Asset"],
|
||||
["Sources Of Funds(Liabilities)", "", "", "", 1, "", "Liability"],
|
||||
["Equity", "", "", "", 1, "", "Equity"],
|
||||
["Expenses", "", "", "", 1, "", "Expense"],
|
||||
["Income", "", "", "", 1, "", "Income"],
|
||||
["Bank Accounts", "Application Of Funds(Assets)", "", "", 1, "Bank", "Asset"],
|
||||
["Cash In Hand", "Application Of Funds(Assets)", "", "", 1, "Cash", "Asset"],
|
||||
["Stock Assets", "Application Of Funds(Assets)", "", "", 1, "Stock", "Asset"],
|
||||
["Cost Of Goods Sold", "Expenses", "", "", 0, "Cost of Goods Sold", "Expense"],
|
||||
["Asset Depreciation", "Expenses", "", "", 0, "Depreciation", "Expense"],
|
||||
["Fixed Assets", "Application Of Funds(Assets)", "", "", 0, "Fixed Asset", "Asset"],
|
||||
["Accounts Payable", "Sources Of Funds(Liabilities)", "", "", 0, "Payable", "Liability"],
|
||||
["Accounts Receivable", "Application Of Funds(Assets)", "", "", 1, "Receivable", "Asset"],
|
||||
["Stock Expenses", "Expenses", "", "", 0, "Stock Adjustment", "Expense"],
|
||||
["Sample Bank", "Bank Accounts", "", "", 0, "Bank", "Asset"],
|
||||
["Cash", "Cash In Hand", "", "", 0, "Cash", "Asset"],
|
||||
["Stores", "Stock Assets", "", "", 0, "Stock", "Asset"],
|
||||
]
|
||||
|
||||
for row in template:
|
||||
writer.writerow(row)
|
||||
|
||||
return writer
|
||||
|
||||
@@ -434,11 +453,14 @@ def get_mandatory_account_types():
|
||||
|
||||
|
||||
def unset_existing_data(company):
|
||||
# remove accounts data from company
|
||||
linked = frappe.db.sql(
|
||||
'''select fieldname from tabDocField
|
||||
where fieldtype="Link" and options="Account" and parent="Company"''',
|
||||
as_dict=True,
|
||||
)
|
||||
|
||||
fieldnames = get_linked_fields("Account").get("Company", {}).get("fieldname", [])
|
||||
linked = [{"fieldname": name} for name in fieldnames]
|
||||
update_values = {d.get("fieldname"): "" for d in linked}
|
||||
# remove accounts data from company
|
||||
update_values = {d.fieldname: "" for d in linked}
|
||||
frappe.db.set_value("Company", company, update_values, update_values)
|
||||
|
||||
# remove accounts data from various doctypes
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
Application Of Funds(Assets),,,,1,,Asset
|
||||
Sources Of Funds(Liabilities),,,,1,,Liability
|
||||
Equity,,,,1,,Equity
|
||||
Expenses,,,,1,Expense Account,Expense
|
||||
Income,,,,1,Income Account,Income
|
||||
Bank Accounts,Application Of Funds(Assets),,,1,Bank,Asset
|
||||
Cash In Hand,Application Of Funds(Assets),,,1,Cash,Asset
|
||||
Stock Assets,Application Of Funds(Assets),,,1,Stock,Asset
|
||||
Cost Of Goods Sold,Expenses,,,0,Cost of Goods Sold,Expense
|
||||
Asset Depreciation,Expenses,,,0,Depreciation,Expense
|
||||
Fixed Assets,Application Of Funds(Assets),,,0,Fixed Asset,Asset
|
||||
Accounts Payable,Sources Of Funds(Liabilities),,,0,Payable,Liability
|
||||
Accounts Receivable,Application Of Funds(Assets),,,1,Receivable,Asset
|
||||
Stock Expenses,Expenses,,,0,Stock Adjustment,Expense
|
||||
Sample Bank,Bank Accounts,,,0,Bank,Asset
|
||||
Cash,Cash In Hand,,,0,Cash,Asset
|
||||
Stores,Stock Assets,,,0,Stock,Asset
|
||||
|
@@ -6,10 +6,8 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"api_details_section",
|
||||
"disabled",
|
||||
"service_provider",
|
||||
"api_endpoint",
|
||||
"access_key",
|
||||
"url",
|
||||
"column_break_3",
|
||||
"help",
|
||||
@@ -79,24 +77,12 @@
|
||||
"label": "Service Provider",
|
||||
"options": "frankfurter.app\nexchangerate.host\nCustom",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "disabled",
|
||||
"fieldtype": "Check",
|
||||
"label": "Disabled"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.service_provider == 'exchangerate.host';",
|
||||
"fieldname": "access_key",
|
||||
"fieldtype": "Data",
|
||||
"label": "Access Key"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2023-10-04 15:30:25.333860",
|
||||
"modified": "2022-01-10 15:51:14.521174",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Currency Exchange Settings",
|
||||
|
||||
@@ -18,21 +18,11 @@ class CurrencyExchangeSettings(Document):
|
||||
|
||||
def set_parameters_and_result(self):
|
||||
if self.service_provider == "exchangerate.host":
|
||||
|
||||
if not self.access_key:
|
||||
frappe.throw(
|
||||
_("Access Key is required for Service Provider: {0}").format(
|
||||
frappe.bold(self.service_provider)
|
||||
)
|
||||
)
|
||||
|
||||
self.set("result_key", [])
|
||||
self.set("req_params", [])
|
||||
|
||||
self.api_endpoint = "https://api.exchangerate.host/convert"
|
||||
self.append("result_key", {"key": "result"})
|
||||
self.append("req_params", {"key": "access_key", "value": self.access_key})
|
||||
self.append("req_params", {"key": "amount", "value": "1"})
|
||||
self.append("req_params", {"key": "date", "value": "{transaction_date}"})
|
||||
self.append("req_params", {"key": "from", "value": "{from_currency}"})
|
||||
self.append("req_params", {"key": "to", "value": "{to_currency}"})
|
||||
|
||||
@@ -50,18 +50,8 @@ frappe.ui.form.on("Journal Entry", {
|
||||
frm.trigger("make_inter_company_journal_entry");
|
||||
}, __('Make'));
|
||||
}
|
||||
},
|
||||
|
||||
erpnext.accounts.unreconcile_payments.add_unreconcile_btn(frm);
|
||||
},
|
||||
before_save: function(frm) {
|
||||
if ((frm.doc.docstatus == 0) && (!frm.doc.is_system_generated)) {
|
||||
let payment_entry_references = frm.doc.accounts.filter(elem => (elem.reference_type == "Payment Entry"));
|
||||
if (payment_entry_references.length > 0) {
|
||||
let rows = payment_entry_references.map(x => "#"+x.idx);
|
||||
frappe.throw(__("Rows: {0} have 'Payment Entry' as reference_type. This should not be set manually.", [frappe.utils.comma_and(rows)]));
|
||||
}
|
||||
}
|
||||
},
|
||||
make_inter_company_journal_entry: function(frm) {
|
||||
var d = new frappe.ui.Dialog({
|
||||
title: __("Select Company"),
|
||||
|
||||
@@ -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', 'Journal Entry', 'Repost Payment Ledger','Repost Accounting Ledger', 'Unreconcile Payments', 'Unreconcile Payment Entries'];
|
||||
frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', 'Repost Payment Ledger','Repost Accounting Ledger'];
|
||||
|
||||
if(frm.doc.__islocal) {
|
||||
if (!frm.doc.paid_from) frm.set_value("paid_from_account_currency", null);
|
||||
@@ -152,13 +152,6 @@ frappe.ui.form.on('Payment Entry', {
|
||||
frm.events.hide_unhide_fields(frm);
|
||||
frm.events.set_dynamic_labels(frm);
|
||||
frm.events.show_general_ledger(frm);
|
||||
if((frm.doc.references) && (frm.doc.references.find((elem) => {return elem.exchange_gain_loss != 0}))) {
|
||||
frm.add_custom_button(__("View Exchange Gain/Loss Journals"), function() {
|
||||
frappe.set_route("List", "Journal Entry", {"voucher_type": "Exchange Gain Or Loss", "reference_name": frm.doc.name});
|
||||
}, __('Actions'));
|
||||
|
||||
}
|
||||
erpnext.accounts.unreconcile_payments.add_unreconcile_btn(frm);
|
||||
},
|
||||
|
||||
validate_company: (frm) => {
|
||||
|
||||
@@ -107,8 +107,6 @@ class PaymentEntry(AccountsController):
|
||||
"Repost Payment Ledger Items",
|
||||
"Repost Accounting Ledger",
|
||||
"Repost Accounting Ledger Items",
|
||||
"Unreconcile Payments",
|
||||
"Unreconcile Payment Entries",
|
||||
)
|
||||
super(PaymentEntry, self).on_cancel()
|
||||
self.make_gl_entries(cancel=1)
|
||||
@@ -229,18 +227,16 @@ class PaymentEntry(AccountsController):
|
||||
|
||||
# if no payment template is used by invoice and has a custom term(no `payment_term`), then invoice outstanding will be in 'None' key
|
||||
latest = latest.get(d.payment_term) or latest.get(None)
|
||||
|
||||
# The reference has already been fully paid
|
||||
if not latest:
|
||||
frappe.throw(
|
||||
_("{0} {1} has already been fully paid.").format(_(d.reference_doctype), d.reference_name)
|
||||
)
|
||||
# The reference has already been partly paid
|
||||
elif (
|
||||
latest.outstanding_amount < latest.invoice_amount
|
||||
and flt(d.outstanding_amount, d.precision("outstanding_amount"))
|
||||
!= flt(latest.outstanding_amount, d.precision("outstanding_amount"))
|
||||
and d.payment_term == ""
|
||||
):
|
||||
elif latest.outstanding_amount < latest.invoice_amount and flt(
|
||||
d.outstanding_amount, d.precision("outstanding_amount")
|
||||
) != flt(latest.outstanding_amount, d.precision("outstanding_amount")):
|
||||
frappe.throw(
|
||||
_(
|
||||
"{0} {1} has already been partly paid. Please use the 'Get Outstanding Invoice' or the 'Get Outstanding Orders' button to get the latest outstanding amounts."
|
||||
@@ -1604,10 +1600,11 @@ def split_invoices_based_on_payment_terms(outstanding_invoices, company):
|
||||
"voucher_type": d.voucher_type,
|
||||
"posting_date": d.posting_date,
|
||||
"invoice_amount": flt(d.invoice_amount),
|
||||
"outstanding_amount": payment_term_outstanding
|
||||
"outstanding_amount": flt(d.outstanding_amount),
|
||||
"payment_term_outstanding": payment_term_outstanding,
|
||||
"allocated_amount": payment_term_outstanding
|
||||
if payment_term_outstanding
|
||||
else d.outstanding_amount,
|
||||
"payment_term_outstanding": payment_term_outstanding,
|
||||
"payment_amount": payment_term.payment_amount,
|
||||
"payment_term": payment_term.payment_term,
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
"party_type",
|
||||
"party",
|
||||
"due_date",
|
||||
"voucher_detail_no",
|
||||
"cost_center",
|
||||
"finance_book",
|
||||
"voucher_type",
|
||||
@@ -30,8 +29,7 @@
|
||||
{
|
||||
"fieldname": "posting_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Posting Date",
|
||||
"search_index": 1
|
||||
"label": "Posting Date"
|
||||
},
|
||||
{
|
||||
"fieldname": "account_type",
|
||||
@@ -65,8 +63,7 @@
|
||||
"fieldtype": "Link",
|
||||
"in_standard_filter": 1,
|
||||
"label": "Voucher Type",
|
||||
"options": "DocType",
|
||||
"search_index": 1
|
||||
"options": "DocType"
|
||||
},
|
||||
{
|
||||
"fieldname": "voucher_no",
|
||||
@@ -74,16 +71,14 @@
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Voucher No",
|
||||
"options": "voucher_type",
|
||||
"search_index": 1
|
||||
"options": "voucher_type"
|
||||
},
|
||||
{
|
||||
"fieldname": "against_voucher_type",
|
||||
"fieldtype": "Link",
|
||||
"in_standard_filter": 1,
|
||||
"label": "Against Voucher Type",
|
||||
"options": "DocType",
|
||||
"search_index": 1
|
||||
"options": "DocType"
|
||||
},
|
||||
{
|
||||
"fieldname": "against_voucher_no",
|
||||
@@ -91,8 +86,7 @@
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Against Voucher No",
|
||||
"options": "against_voucher_type",
|
||||
"search_index": 1
|
||||
"options": "against_voucher_type"
|
||||
},
|
||||
{
|
||||
"fieldname": "amount",
|
||||
@@ -148,18 +142,12 @@
|
||||
"fieldname": "remarks",
|
||||
"fieldtype": "Text",
|
||||
"label": "Remarks"
|
||||
},
|
||||
{
|
||||
"fieldname": "voucher_detail_no",
|
||||
"fieldtype": "Data",
|
||||
"label": "Voucher Detail No",
|
||||
"search_index": 1
|
||||
}
|
||||
],
|
||||
"in_create": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2023-11-08 10:53:10.664896",
|
||||
"modified": "2022-08-22 15:32:56.629430",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Ledger Entry",
|
||||
|
||||
@@ -216,7 +216,6 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
|
||||
this.data = [];
|
||||
const dialog = new frappe.ui.Dialog({
|
||||
title: __("Select Difference Account"),
|
||||
size: 'extra-large',
|
||||
fields: [
|
||||
{
|
||||
fieldname: "allocation",
|
||||
@@ -240,13 +239,6 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
|
||||
in_list_view: 1,
|
||||
read_only: 1
|
||||
}, {
|
||||
fieldtype:'Date',
|
||||
fieldname:"gain_loss_posting_date",
|
||||
label: __("Posting Date"),
|
||||
in_list_view: 1,
|
||||
reqd: 1,
|
||||
}, {
|
||||
|
||||
fieldtype:'Link',
|
||||
options: 'Account',
|
||||
in_list_view: 1,
|
||||
@@ -280,9 +272,6 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
|
||||
args.forEach(d => {
|
||||
frappe.model.set_value("Payment Reconciliation Allocation", d.docname,
|
||||
"difference_account", d.difference_account);
|
||||
frappe.model.set_value("Payment Reconciliation Allocation", d.docname,
|
||||
"gain_loss_posting_date", d.gain_loss_posting_date);
|
||||
|
||||
});
|
||||
|
||||
this.reconcile_payment_entries();
|
||||
@@ -298,7 +287,6 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
|
||||
'reference_name': d.reference_name,
|
||||
'difference_amount': d.difference_amount,
|
||||
'difference_account': d.difference_account,
|
||||
'gain_loss_posting_date': d.gain_loss_posting_date
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -19,7 +19,7 @@ from erpnext.accounts.utils import (
|
||||
get_outstanding_invoices,
|
||||
reconcile_against_document,
|
||||
)
|
||||
from erpnext.controllers.accounts_controller import get_advance_payment_entries_for_regional
|
||||
from erpnext.controllers.accounts_controller import get_advance_payment_entries
|
||||
|
||||
|
||||
class PaymentReconciliation(Document):
|
||||
@@ -62,7 +62,7 @@ class PaymentReconciliation(Document):
|
||||
if self.payment_name:
|
||||
condition += "name like '%%{0}%%'".format(self.payment_name)
|
||||
|
||||
payment_entries = get_advance_payment_entries_for_regional(
|
||||
payment_entries = get_advance_payment_entries(
|
||||
self.party_type,
|
||||
self.party,
|
||||
self.receivable_payable_account,
|
||||
@@ -93,8 +93,6 @@ class PaymentReconciliation(Document):
|
||||
"t2.against_account like %(bank_cash_account)s" if self.bank_cash_account else "1=1"
|
||||
)
|
||||
|
||||
limit = f"limit {self.payment_limit}" if self.payment_limit else " "
|
||||
|
||||
# nosemgrep
|
||||
journal_entries = frappe.db.sql(
|
||||
"""
|
||||
@@ -118,13 +116,11 @@ class PaymentReconciliation(Document):
|
||||
ELSE {bank_account_condition}
|
||||
END)
|
||||
order by t1.posting_date
|
||||
{limit}
|
||||
""".format(
|
||||
**{
|
||||
"dr_or_cr": dr_or_cr,
|
||||
"bank_account_condition": bank_account_condition,
|
||||
"condition": condition,
|
||||
"limit": limit,
|
||||
}
|
||||
),
|
||||
{
|
||||
@@ -150,7 +146,7 @@ class PaymentReconciliation(Document):
|
||||
if self.payment_name:
|
||||
conditions.append(doc.name.like(f"%{self.payment_name}%"))
|
||||
|
||||
self.return_invoices_query = (
|
||||
self.return_invoices = (
|
||||
qb.from_(doc)
|
||||
.select(
|
||||
ConstantColumn(voucher_type).as_("voucher_type"),
|
||||
@@ -158,11 +154,8 @@ class PaymentReconciliation(Document):
|
||||
doc.return_against,
|
||||
)
|
||||
.where(Criterion.all(conditions))
|
||||
.run(as_dict=True)
|
||||
)
|
||||
if self.payment_limit:
|
||||
self.return_invoices_query = self.return_invoices_query.limit(self.payment_limit)
|
||||
|
||||
self.return_invoices = self.return_invoices_query.run(as_dict=True)
|
||||
|
||||
def get_dr_or_cr_notes(self):
|
||||
|
||||
@@ -322,7 +315,6 @@ class PaymentReconciliation(Document):
|
||||
res.difference_amount = self.get_difference_amount(pay, inv, res["allocated_amount"])
|
||||
res.difference_account = default_exchange_gain_loss_account
|
||||
res.exchange_rate = inv.get("exchange_rate")
|
||||
res.update({"gain_loss_posting_date": pay.get("posting_date")})
|
||||
|
||||
if pay.get("amount") == 0:
|
||||
entries.append(res)
|
||||
@@ -358,7 +350,6 @@ class PaymentReconciliation(Document):
|
||||
)
|
||||
|
||||
def reconcile_allocations(self, skip_ref_details_update_for_pe=False):
|
||||
adjust_allocations_for_taxes(self)
|
||||
dr_or_cr = (
|
||||
"credit_in_account_currency"
|
||||
if erpnext.get_party_account_type(self.party_type) == "Receivable"
|
||||
@@ -429,7 +420,6 @@ class PaymentReconciliation(Document):
|
||||
"allocated_amount": flt(row.get("allocated_amount")),
|
||||
"difference_amount": flt(row.get("difference_amount")),
|
||||
"difference_account": row.get("difference_account"),
|
||||
"difference_posting_date": row.get("gain_loss_posting_date"),
|
||||
"cost_center": row.get("cost_center"),
|
||||
}
|
||||
)
|
||||
@@ -660,8 +650,3 @@ def reconcile_dr_cr_note(dr_cr_notes, company):
|
||||
None,
|
||||
inv.cost_center,
|
||||
)
|
||||
|
||||
|
||||
@erpnext.allow_regional
|
||||
def adjust_allocations_for_taxes(doc):
|
||||
pass
|
||||
|
||||
@@ -14,7 +14,6 @@ from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_pay
|
||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
from erpnext.accounts.party import get_party_account
|
||||
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
|
||||
from erpnext.stock.doctype.item.test_item import create_item
|
||||
|
||||
test_dependencies = ["Item"]
|
||||
@@ -86,44 +85,26 @@ class TestPaymentReconciliation(FrappeTestCase):
|
||||
self.customer5 = make_customer("_Test PR Customer 5", "EUR")
|
||||
|
||||
def create_account(self):
|
||||
accounts = [
|
||||
{
|
||||
"attribute": "debtors_eur",
|
||||
"account_name": "Debtors EUR",
|
||||
"parent_account": "Accounts Receivable - _PR",
|
||||
"account_currency": "EUR",
|
||||
"account_type": "Receivable",
|
||||
},
|
||||
{
|
||||
"attribute": "creditors_usd",
|
||||
"account_name": "Payable USD",
|
||||
"parent_account": "Accounts Payable - _PR",
|
||||
"account_currency": "USD",
|
||||
"account_type": "Payable",
|
||||
},
|
||||
]
|
||||
|
||||
for x in accounts:
|
||||
x = frappe._dict(x)
|
||||
if not frappe.db.get_value(
|
||||
"Account", filters={"account_name": x.account_name, "company": self.company}
|
||||
):
|
||||
acc = frappe.new_doc("Account")
|
||||
acc.account_name = x.account_name
|
||||
acc.parent_account = x.parent_account
|
||||
acc.company = self.company
|
||||
acc.account_currency = x.account_currency
|
||||
acc.account_type = x.account_type
|
||||
acc.insert()
|
||||
else:
|
||||
name = frappe.db.get_value(
|
||||
"Account",
|
||||
filters={"account_name": x.account_name, "company": self.company},
|
||||
fieldname="name",
|
||||
pluck=True,
|
||||
)
|
||||
acc = frappe.get_doc("Account", name)
|
||||
setattr(self, x.attribute, acc.name)
|
||||
account_name = "Debtors EUR"
|
||||
if not frappe.db.get_value(
|
||||
"Account", filters={"account_name": account_name, "company": self.company}
|
||||
):
|
||||
acc = frappe.new_doc("Account")
|
||||
acc.account_name = account_name
|
||||
acc.parent_account = "Accounts Receivable - _PR"
|
||||
acc.company = self.company
|
||||
acc.account_currency = "EUR"
|
||||
acc.account_type = "Receivable"
|
||||
acc.insert()
|
||||
else:
|
||||
name = frappe.db.get_value(
|
||||
"Account",
|
||||
filters={"account_name": account_name, "company": self.company},
|
||||
fieldname="name",
|
||||
pluck=True,
|
||||
)
|
||||
acc = frappe.get_doc("Account", name)
|
||||
self.debtors_eur = acc.name
|
||||
|
||||
def create_sales_invoice(
|
||||
self, qty=1, rate=100, posting_date=nowdate(), do_not_save=False, do_not_submit=False
|
||||
@@ -170,64 +151,6 @@ class TestPaymentReconciliation(FrappeTestCase):
|
||||
payment.posting_date = posting_date
|
||||
return payment
|
||||
|
||||
def create_purchase_invoice(
|
||||
self, qty=1, rate=100, posting_date=nowdate(), do_not_save=False, do_not_submit=False
|
||||
):
|
||||
"""
|
||||
Helper function to populate default values in sales invoice
|
||||
"""
|
||||
pinv = make_purchase_invoice(
|
||||
qty=qty,
|
||||
rate=rate,
|
||||
company=self.company,
|
||||
customer=self.supplier,
|
||||
item_code=self.item,
|
||||
item_name=self.item,
|
||||
cost_center=self.cost_center,
|
||||
warehouse=self.warehouse,
|
||||
debit_to=self.debit_to,
|
||||
parent_cost_center=self.cost_center,
|
||||
update_stock=0,
|
||||
currency="INR",
|
||||
is_pos=0,
|
||||
is_return=0,
|
||||
return_against=None,
|
||||
income_account=self.income_account,
|
||||
expense_account=self.expense_account,
|
||||
do_not_save=do_not_save,
|
||||
do_not_submit=do_not_submit,
|
||||
)
|
||||
return pinv
|
||||
|
||||
def create_purchase_order(
|
||||
self, qty=1, rate=100, posting_date=nowdate(), do_not_save=False, do_not_submit=False
|
||||
):
|
||||
"""
|
||||
Helper function to populate default values in sales invoice
|
||||
"""
|
||||
pord = create_purchase_order(
|
||||
qty=qty,
|
||||
rate=rate,
|
||||
company=self.company,
|
||||
customer=self.supplier,
|
||||
item_code=self.item,
|
||||
item_name=self.item,
|
||||
cost_center=self.cost_center,
|
||||
warehouse=self.warehouse,
|
||||
debit_to=self.debit_to,
|
||||
parent_cost_center=self.cost_center,
|
||||
update_stock=0,
|
||||
currency="INR",
|
||||
is_pos=0,
|
||||
is_return=0,
|
||||
return_against=None,
|
||||
income_account=self.income_account,
|
||||
expense_account=self.expense_account,
|
||||
do_not_save=do_not_save,
|
||||
do_not_submit=do_not_submit,
|
||||
)
|
||||
return pord
|
||||
|
||||
def clear_old_entries(self):
|
||||
doctype_list = [
|
||||
"GL Entry",
|
||||
@@ -240,11 +163,13 @@ class TestPaymentReconciliation(FrappeTestCase):
|
||||
for doctype in doctype_list:
|
||||
qb.from_(qb.DocType(doctype)).delete().where(qb.DocType(doctype).company == self.company).run()
|
||||
|
||||
def create_payment_reconciliation(self, party_is_customer=True):
|
||||
def create_payment_reconciliation(self):
|
||||
pr = frappe.new_doc("Payment Reconciliation")
|
||||
pr.company = self.company
|
||||
pr.party_type = "Customer" if party_is_customer else "Supplier"
|
||||
pr.party = self.customer if party_is_customer else self.supplier
|
||||
pr.party_type = (
|
||||
self.party_type if hasattr(self, "party_type") and self.party_type else "Customer"
|
||||
)
|
||||
pr.party = self.customer
|
||||
pr.receivable_payable_account = get_party_account(pr.party_type, pr.party, pr.company)
|
||||
pr.from_invoice_date = pr.to_invoice_date = pr.from_payment_date = pr.to_payment_date = nowdate()
|
||||
return pr
|
||||
@@ -981,13 +906,9 @@ class TestPaymentReconciliation(FrappeTestCase):
|
||||
self.assertEqual(pr.allocation[0].difference_amount, 0)
|
||||
|
||||
def test_reconciliation_purchase_invoice_against_return(self):
|
||||
self.supplier = "_Test Supplier USD"
|
||||
pi = self.create_purchase_invoice(qty=5, rate=50, do_not_submit=True)
|
||||
pi.supplier = self.supplier
|
||||
pi.currency = "USD"
|
||||
pi.conversion_rate = 50
|
||||
pi.credit_to = self.creditors_usd
|
||||
pi.save().submit()
|
||||
pi = make_purchase_invoice(
|
||||
supplier="_Test Supplier USD", currency="USD", conversion_rate=50
|
||||
).submit()
|
||||
|
||||
pi_return = frappe.get_doc(pi.as_dict())
|
||||
pi_return.name = None
|
||||
@@ -997,12 +918,11 @@ class TestPaymentReconciliation(FrappeTestCase):
|
||||
pi_return.items[0].qty = -pi_return.items[0].qty
|
||||
pi_return.submit()
|
||||
|
||||
pr = frappe.get_doc("Payment Reconciliation")
|
||||
pr.company = self.company
|
||||
pr.party_type = "Supplier"
|
||||
pr.party = self.supplier
|
||||
pr.receivable_payable_account = self.creditors_usd
|
||||
pr.from_invoice_date = pr.to_invoice_date = pr.from_payment_date = pr.to_payment_date = nowdate()
|
||||
self.company = "_Test Company"
|
||||
self.party_type = "Supplier"
|
||||
self.customer = "_Test Supplier USD"
|
||||
|
||||
pr = self.create_payment_reconciliation()
|
||||
pr.get_unreconciled_entries()
|
||||
|
||||
invoices = []
|
||||
@@ -1011,7 +931,6 @@ class TestPaymentReconciliation(FrappeTestCase):
|
||||
if invoice.invoice_number == pi.name:
|
||||
invoices.append(invoice.as_dict())
|
||||
break
|
||||
|
||||
for payment in pr.payments:
|
||||
if payment.reference_name == pi_return.name:
|
||||
payments.append(payment.as_dict())
|
||||
@@ -1022,121 +941,6 @@ class TestPaymentReconciliation(FrappeTestCase):
|
||||
# Should not raise frappe.exceptions.ValidationError: Total Debit must be equal to Total Credit.
|
||||
pr.reconcile()
|
||||
|
||||
def test_reconciliation_from_purchase_order_to_multiple_invoices(self):
|
||||
"""
|
||||
Reconciling advance payment from PO/SO to multiple invoices should not cause overallocation
|
||||
"""
|
||||
|
||||
self.supplier = "_Test Supplier"
|
||||
|
||||
pi1 = self.create_purchase_invoice(qty=10, rate=100)
|
||||
pi2 = self.create_purchase_invoice(qty=10, rate=100)
|
||||
po = self.create_purchase_order(qty=20, rate=100)
|
||||
pay = get_payment_entry(po.doctype, po.name)
|
||||
# Overpay Puchase Order
|
||||
pay.paid_amount = 3000
|
||||
pay.save().submit()
|
||||
# assert total allocated and unallocated before reconciliation
|
||||
self.assertEqual(
|
||||
(
|
||||
pay.references[0].reference_doctype,
|
||||
pay.references[0].reference_name,
|
||||
pay.references[0].allocated_amount,
|
||||
),
|
||||
(po.doctype, po.name, 2000),
|
||||
)
|
||||
self.assertEqual(pay.total_allocated_amount, 2000)
|
||||
self.assertEqual(pay.unallocated_amount, 1000)
|
||||
self.assertEqual(pay.difference_amount, 0)
|
||||
|
||||
pr = self.create_payment_reconciliation(party_is_customer=False)
|
||||
pr.get_unreconciled_entries()
|
||||
|
||||
self.assertEqual(len(pr.invoices), 2)
|
||||
self.assertEqual(len(pr.payments), 2)
|
||||
|
||||
for x in pr.payments:
|
||||
self.assertEqual((x.reference_type, x.reference_name), (pay.doctype, pay.name))
|
||||
|
||||
invoices = [x.as_dict() for x in pr.invoices]
|
||||
payments = [x.as_dict() for x in pr.payments]
|
||||
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
||||
# partial allocation on pi1 and full allocate on pi2
|
||||
pr.allocation[0].allocated_amount = 100
|
||||
pr.reconcile()
|
||||
|
||||
# assert references and total allocated and unallocated amount
|
||||
pay.reload()
|
||||
self.assertEqual(len(pay.references), 3)
|
||||
self.assertEqual(
|
||||
(
|
||||
pay.references[0].reference_doctype,
|
||||
pay.references[0].reference_name,
|
||||
pay.references[0].allocated_amount,
|
||||
),
|
||||
(po.doctype, po.name, 900),
|
||||
)
|
||||
self.assertEqual(
|
||||
(
|
||||
pay.references[1].reference_doctype,
|
||||
pay.references[1].reference_name,
|
||||
pay.references[1].allocated_amount,
|
||||
),
|
||||
(pi1.doctype, pi1.name, 100),
|
||||
)
|
||||
self.assertEqual(
|
||||
(
|
||||
pay.references[2].reference_doctype,
|
||||
pay.references[2].reference_name,
|
||||
pay.references[2].allocated_amount,
|
||||
),
|
||||
(pi2.doctype, pi2.name, 1000),
|
||||
)
|
||||
self.assertEqual(pay.total_allocated_amount, 2000)
|
||||
self.assertEqual(pay.unallocated_amount, 1000)
|
||||
self.assertEqual(pay.difference_amount, 0)
|
||||
|
||||
pr.get_unreconciled_entries()
|
||||
self.assertEqual(len(pr.invoices), 1)
|
||||
self.assertEqual(len(pr.payments), 2)
|
||||
|
||||
invoices = [x.as_dict() for x in pr.invoices]
|
||||
payments = [x.as_dict() for x in pr.payments]
|
||||
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
||||
pr.reconcile()
|
||||
|
||||
# assert references and total allocated and unallocated amount
|
||||
pay.reload()
|
||||
self.assertEqual(len(pay.references), 3)
|
||||
# PO references should be removed now
|
||||
self.assertEqual(
|
||||
(
|
||||
pay.references[0].reference_doctype,
|
||||
pay.references[0].reference_name,
|
||||
pay.references[0].allocated_amount,
|
||||
),
|
||||
(pi1.doctype, pi1.name, 100),
|
||||
)
|
||||
self.assertEqual(
|
||||
(
|
||||
pay.references[1].reference_doctype,
|
||||
pay.references[1].reference_name,
|
||||
pay.references[1].allocated_amount,
|
||||
),
|
||||
(pi2.doctype, pi2.name, 1000),
|
||||
)
|
||||
self.assertEqual(
|
||||
(
|
||||
pay.references[2].reference_doctype,
|
||||
pay.references[2].reference_name,
|
||||
pay.references[2].allocated_amount,
|
||||
),
|
||||
(pi1.doctype, pi1.name, 900),
|
||||
)
|
||||
self.assertEqual(pay.total_allocated_amount, 2000)
|
||||
self.assertEqual(pay.unallocated_amount, 1000)
|
||||
self.assertEqual(pay.difference_amount, 0)
|
||||
|
||||
|
||||
def make_customer(customer_name, currency=None):
|
||||
if not frappe.db.exists("Customer", customer_name):
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
"is_advance",
|
||||
"section_break_5",
|
||||
"difference_amount",
|
||||
"gain_loss_posting_date",
|
||||
"column_break_7",
|
||||
"difference_account",
|
||||
"exchange_rate",
|
||||
@@ -152,16 +151,11 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Cost Center",
|
||||
"options": "Cost Center"
|
||||
},
|
||||
{
|
||||
"fieldname": "gain_loss_posting_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Difference Posting Date"
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-10-23 10:44:56.066303",
|
||||
"modified": "2023-09-03 07:52:33.684217",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Reconciliation Allocation",
|
||||
|
||||
@@ -249,7 +249,7 @@ class PaymentRequest(Document):
|
||||
if (
|
||||
party_account_currency == ref_doc.company_currency and party_account_currency != self.currency
|
||||
):
|
||||
party_amount = ref_doc.get("base_rounded_total") or ref_doc.get("base_grand_total")
|
||||
party_amount = ref_doc.base_grand_total
|
||||
else:
|
||||
party_amount = self.grand_total
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
"transaction_date",
|
||||
"posting_date",
|
||||
"fiscal_year",
|
||||
"year_start_date",
|
||||
"amended_from",
|
||||
"company",
|
||||
"column_break1",
|
||||
@@ -101,22 +100,16 @@
|
||||
"fieldtype": "Text",
|
||||
"label": "Error Message",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "year_start_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Year Start Date"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
"idx": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-09-11 20:19:11.810533",
|
||||
"modified": "2022-07-20 14:51:04.714154",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Period Closing Voucher",
|
||||
"naming_rule": "Expression (old style)",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
@@ -151,6 +144,5 @@
|
||||
"search_fields": "posting_date, fiscal_year",
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"title_field": "closing_account_head"
|
||||
}
|
||||
@@ -33,7 +33,7 @@ class PeriodClosingVoucher(AccountsController):
|
||||
def on_cancel(self):
|
||||
self.validate_future_closing_vouchers()
|
||||
self.db_set("gle_processing_status", "In Progress")
|
||||
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Payment Ledger Entry")
|
||||
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry")
|
||||
gle_count = frappe.db.count(
|
||||
"GL Entry",
|
||||
{"voucher_type": "Period Closing Voucher", "voucher_no": self.name, "is_cancelled": 0},
|
||||
@@ -95,23 +95,15 @@ class PeriodClosingVoucher(AccountsController):
|
||||
|
||||
self.check_if_previous_year_closed()
|
||||
|
||||
pcv = frappe.qb.DocType("Period Closing Voucher")
|
||||
existing_entry = (
|
||||
frappe.qb.from_(pcv)
|
||||
.select(pcv.name)
|
||||
.where(
|
||||
(pcv.posting_date >= self.posting_date)
|
||||
& (pcv.fiscal_year == self.fiscal_year)
|
||||
& (pcv.docstatus == 1)
|
||||
& (pcv.company == self.company)
|
||||
)
|
||||
.run()
|
||||
pce = frappe.db.sql(
|
||||
"""select name from `tabPeriod Closing Voucher`
|
||||
where posting_date > %s and fiscal_year = %s and docstatus = 1 and company = %s""",
|
||||
(self.posting_date, self.fiscal_year, self.company),
|
||||
)
|
||||
|
||||
if existing_entry and existing_entry[0][0]:
|
||||
if pce and pce[0][0]:
|
||||
frappe.throw(
|
||||
_("Another Period Closing Entry {0} has been made after {1}").format(
|
||||
existing_entry[0][0], self.posting_date
|
||||
pce[0][0], self.posting_date
|
||||
)
|
||||
)
|
||||
|
||||
@@ -138,27 +130,18 @@ class PeriodClosingVoucher(AccountsController):
|
||||
frappe.enqueue(
|
||||
process_gl_entries,
|
||||
gl_entries=gl_entries,
|
||||
voucher_name=self.name,
|
||||
timeout=3000,
|
||||
)
|
||||
|
||||
frappe.enqueue(
|
||||
process_closing_entries,
|
||||
gl_entries=gl_entries,
|
||||
closing_entries=closing_entries,
|
||||
voucher_name=self.name,
|
||||
company=self.company,
|
||||
closing_date=self.posting_date,
|
||||
timeout=3000,
|
||||
queue="long",
|
||||
)
|
||||
|
||||
frappe.msgprint(
|
||||
_("The GL Entries will be processed in the background, it can take a few minutes."),
|
||||
alert=True,
|
||||
)
|
||||
else:
|
||||
process_gl_entries(gl_entries, self.name)
|
||||
process_closing_entries(gl_entries, closing_entries, self.name, self.company, self.posting_date)
|
||||
process_gl_entries(gl_entries, closing_entries, self.name, self.company, self.posting_date)
|
||||
|
||||
def get_grouped_gl_entries(self, get_opening_entries=False):
|
||||
closing_entries = []
|
||||
@@ -339,12 +322,17 @@ class PeriodClosingVoucher(AccountsController):
|
||||
return query.run(as_dict=1)
|
||||
|
||||
|
||||
def process_gl_entries(gl_entries, voucher_name):
|
||||
def process_gl_entries(gl_entries, closing_entries, voucher_name, company, closing_date):
|
||||
from erpnext.accounts.doctype.account_closing_balance.account_closing_balance import (
|
||||
make_closing_entries,
|
||||
)
|
||||
from erpnext.accounts.general_ledger import make_gl_entries
|
||||
|
||||
try:
|
||||
if gl_entries:
|
||||
make_gl_entries(gl_entries, merge_entries=False)
|
||||
|
||||
make_closing_entries(gl_entries + closing_entries, voucher_name, company, closing_date)
|
||||
frappe.db.set_value("Period Closing Voucher", voucher_name, "gle_processing_status", "Completed")
|
||||
except Exception as e:
|
||||
frappe.db.rollback()
|
||||
@@ -352,19 +340,6 @@ def process_gl_entries(gl_entries, voucher_name):
|
||||
frappe.db.set_value("Period Closing Voucher", voucher_name, "gle_processing_status", "Failed")
|
||||
|
||||
|
||||
def process_closing_entries(gl_entries, closing_entries, voucher_name, company, closing_date):
|
||||
from erpnext.accounts.doctype.account_closing_balance.account_closing_balance import (
|
||||
make_closing_entries,
|
||||
)
|
||||
|
||||
try:
|
||||
if gl_entries + closing_entries:
|
||||
make_closing_entries(gl_entries + closing_entries, voucher_name, company, closing_date)
|
||||
except Exception as e:
|
||||
frappe.db.rollback()
|
||||
frappe.log_error(e)
|
||||
|
||||
|
||||
def make_reverse_gl_entries(voucher_type, voucher_no):
|
||||
from erpnext.accounts.general_ledger import make_reverse_gl_entries
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ from frappe.utils import add_months, today
|
||||
from erpnext.accounts.doctype.finance_book.test_finance_book import create_finance_book
|
||||
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
from erpnext.accounts.utils import get_fiscal_year, now
|
||||
|
||||
|
||||
class TestPeriodClosingVoucher(unittest.TestCase):
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
|
||||
import collections
|
||||
|
||||
import frappe
|
||||
@@ -45,6 +43,7 @@ class POSInvoice(SalesInvoice):
|
||||
self.validate_debit_to_acc()
|
||||
self.validate_write_off_account()
|
||||
self.validate_change_amount()
|
||||
self.validate_duplicate_serial_and_batch_no()
|
||||
self.validate_change_account()
|
||||
self.validate_item_cost_centers()
|
||||
self.validate_warehouse()
|
||||
@@ -57,7 +56,6 @@ class POSInvoice(SalesInvoice):
|
||||
self.validate_payment_amount()
|
||||
self.validate_loyalty_transaction()
|
||||
self.validate_company_with_pos_company()
|
||||
self.validate_duplicate_serial_no()
|
||||
if self.coupon_code:
|
||||
from erpnext.accounts.doctype.pricing_rule.utils import validate_coupon_code
|
||||
|
||||
@@ -158,18 +156,27 @@ class POSInvoice(SalesInvoice):
|
||||
title=_("Item Unavailable"),
|
||||
)
|
||||
|
||||
def validate_duplicate_serial_no(self):
|
||||
def validate_duplicate_serial_and_batch_no(self):
|
||||
serial_nos = []
|
||||
batch_nos = []
|
||||
|
||||
for row in self.get("items"):
|
||||
if row.serial_no:
|
||||
serial_nos = row.serial_no.split("\n")
|
||||
|
||||
if row.batch_no and not row.serial_no:
|
||||
batch_nos.append(row.batch_no)
|
||||
|
||||
if serial_nos:
|
||||
for key, value in collections.Counter(serial_nos).items():
|
||||
if value > 1:
|
||||
frappe.throw(_("Duplicate Serial No {0} found").format("key"))
|
||||
|
||||
if batch_nos:
|
||||
for key, value in collections.Counter(batch_nos).items():
|
||||
if value > 1:
|
||||
frappe.throw(_("Duplicate Batch No {0} found").format("key"))
|
||||
|
||||
def validate_pos_reserved_batch_qty(self, item):
|
||||
filters = {"item_code": item.item_code, "warehouse": item.warehouse, "batch_no": item.batch_no}
|
||||
|
||||
@@ -518,7 +525,7 @@ class POSInvoice(SalesInvoice):
|
||||
selling_price_list = (
|
||||
customer_price_list or customer_group_price_list or profile.get("selling_price_list")
|
||||
)
|
||||
if customer_currency and customer_currency != profile.get("currency"):
|
||||
if customer_currency != profile.get("currency"):
|
||||
self.set("currency", customer_currency)
|
||||
|
||||
else:
|
||||
|
||||
@@ -464,37 +464,6 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
pos2.insert()
|
||||
self.assertRaises(frappe.ValidationError, pos2.submit)
|
||||
|
||||
def test_pos_invoice_with_duplicate_serial_no(self):
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
|
||||
|
||||
se = make_serialized_item(
|
||||
company="_Test Company",
|
||||
target_warehouse="Stores - _TC",
|
||||
cost_center="Main - _TC",
|
||||
expense_account="Cost of Goods Sold - _TC",
|
||||
)
|
||||
|
||||
serial_nos = get_serial_nos(se.get("items")[0].serial_no)
|
||||
|
||||
pos = create_pos_invoice(
|
||||
company="_Test Company",
|
||||
debit_to="Debtors - _TC",
|
||||
account_for_change_amount="Cash - _TC",
|
||||
warehouse="Stores - _TC",
|
||||
income_account="Sales - _TC",
|
||||
expense_account="Cost of Goods Sold - _TC",
|
||||
cost_center="Main - _TC",
|
||||
item=se.get("items")[0].item_code,
|
||||
rate=1000,
|
||||
qty=2,
|
||||
do_not_save=1,
|
||||
)
|
||||
|
||||
pos.get("items")[0].has_serial_no = 1
|
||||
pos.get("items")[0].serial_no = serial_nos[0] + "\n" + serial_nos[0]
|
||||
self.assertRaises(frappe.ValidationError, pos.submit)
|
||||
|
||||
def test_invalid_serial_no_validation(self):
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@
|
||||
"in_create": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2023-11-02 11:32:12.254018",
|
||||
"modified": "2023-04-21 17:36:26.642617",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Process Payment Reconciliation Log",
|
||||
@@ -125,19 +125,7 @@
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts User",
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
|
||||
@@ -47,20 +47,6 @@ class ProcessStatementOfAccounts(Document):
|
||||
|
||||
|
||||
def get_report_pdf(doc, consolidated=True):
|
||||
statement_dict = get_statement_dict(doc)
|
||||
if not bool(statement_dict):
|
||||
return False
|
||||
elif consolidated:
|
||||
delimiter = '<div style="page-break-before: always;"></div>' if doc.include_break else ""
|
||||
result = delimiter.join(list(statement_dict.values()))
|
||||
return get_pdf(result, {"orientation": doc.orientation})
|
||||
else:
|
||||
for customer, statement_html in statement_dict.items():
|
||||
statement_dict[customer] = get_pdf(statement_html, {"orientation": doc.orientation})
|
||||
return statement_dict
|
||||
|
||||
|
||||
def get_statement_dict(doc, get_statement_dict=False):
|
||||
statement_dict = {}
|
||||
ageing = ""
|
||||
|
||||
@@ -78,7 +64,6 @@ def get_statement_dict(doc, get_statement_dict=False):
|
||||
filters = get_common_filters(doc)
|
||||
|
||||
if doc.report == "General Ledger":
|
||||
filters.update(get_gl_filters(doc, entry, tax_id, presentation_currency))
|
||||
col, res = get_soa(filters)
|
||||
for x in [0, -2, -1]:
|
||||
res[x]["account"] = res[x]["account"].replace("'", "")
|
||||
@@ -91,11 +76,17 @@ def get_statement_dict(doc, get_statement_dict=False):
|
||||
if not res:
|
||||
continue
|
||||
|
||||
statement_dict[entry.customer] = (
|
||||
[res, ageing] if get_statement_dict else get_html(doc, filters, entry, col, res, ageing)
|
||||
)
|
||||
statement_dict[entry.customer] = get_html(doc, filters, entry, col, res, ageing)
|
||||
|
||||
return statement_dict
|
||||
if not bool(statement_dict):
|
||||
return False
|
||||
elif consolidated:
|
||||
result = "".join(list(statement_dict.values()))
|
||||
return get_pdf(result, {"orientation": doc.orientation})
|
||||
else:
|
||||
for customer, statement_html in statement_dict.items():
|
||||
statement_dict[customer] = get_pdf(statement_html, {"orientation": doc.orientation})
|
||||
return statement_dict
|
||||
|
||||
|
||||
def set_ageing(doc, entry):
|
||||
@@ -108,8 +99,7 @@ def set_ageing(doc, entry):
|
||||
"range2": 60,
|
||||
"range3": 90,
|
||||
"range4": 120,
|
||||
"party_type": "Customer",
|
||||
"party": [entry.customer],
|
||||
"customer": entry.customer,
|
||||
}
|
||||
)
|
||||
col1, ageing = get_ageing(ageing_filters)
|
||||
@@ -152,8 +142,7 @@ def get_gl_filters(doc, entry, tax_id, presentation_currency):
|
||||
def get_ar_filters(doc, entry):
|
||||
return {
|
||||
"report_date": doc.posting_date if doc.posting_date else None,
|
||||
"party_type": "Customer",
|
||||
"party": [entry.customer],
|
||||
"customer": entry.customer,
|
||||
"customer_name": entry.customer_name if entry.customer_name else None,
|
||||
"payment_terms_template": doc.payment_terms_template if doc.payment_terms_template else None,
|
||||
"sales_partner": doc.sales_partner if doc.sales_partner else None,
|
||||
|
||||
@@ -4,107 +4,39 @@
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
from frappe.utils import add_days, getdate, today
|
||||
|
||||
from erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts import (
|
||||
get_statement_dict,
|
||||
send_emails,
|
||||
)
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
|
||||
|
||||
|
||||
class TestProcessStatementOfAccounts(AccountsTestMixin, FrappeTestCase):
|
||||
class TestProcessStatementOfAccounts(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.create_company()
|
||||
self.create_customer()
|
||||
self.create_customer(customer_name="Other Customer")
|
||||
self.clear_old_entries()
|
||||
self.si = create_sales_invoice()
|
||||
create_sales_invoice(customer="Other Customer")
|
||||
|
||||
def test_process_soa_for_gl(self):
|
||||
"""Tests the utils for Statement of Accounts(General Ledger)"""
|
||||
process_soa = create_process_soa(
|
||||
name="_Test Process SOA for GL",
|
||||
customers=[{"customer": "_Test Customer"}, {"customer": "Other Customer"}],
|
||||
)
|
||||
statement_dict = get_statement_dict(process_soa, get_statement_dict=True)
|
||||
|
||||
# Checks if the statements are filtered based on the Customer
|
||||
self.assertIn("Other Customer", statement_dict)
|
||||
self.assertIn("_Test Customer", statement_dict)
|
||||
|
||||
# Checks if the correct number of receivable entries exist
|
||||
# 3 rows for opening and closing and 1 row for SI
|
||||
receivable_entries = statement_dict["_Test Customer"][0]
|
||||
self.assertEqual(len(receivable_entries), 4)
|
||||
|
||||
# Checks the amount for the receivable entry
|
||||
self.assertEqual(receivable_entries[1].voucher_no, self.si.name)
|
||||
self.assertEqual(receivable_entries[1].balance, 100)
|
||||
|
||||
def test_process_soa_for_ar(self):
|
||||
"""Tests the utils for Statement of Accounts(Accounts Receivable)"""
|
||||
process_soa = create_process_soa(name="_Test Process SOA for AR", report="Accounts Receivable")
|
||||
statement_dict = get_statement_dict(process_soa, get_statement_dict=True)
|
||||
|
||||
# Checks if the statements are filtered based on the Customer
|
||||
self.assertNotIn("Other Customer", statement_dict)
|
||||
self.assertIn("_Test Customer", statement_dict)
|
||||
|
||||
# Checks if the correct number of receivable entries exist
|
||||
receivable_entries = statement_dict["_Test Customer"][0]
|
||||
self.assertEqual(len(receivable_entries), 1)
|
||||
|
||||
# Checks the amount for the receivable entry
|
||||
self.assertEqual(receivable_entries[0].voucher_no, self.si.name)
|
||||
self.assertEqual(receivable_entries[0].total_due, 100)
|
||||
|
||||
# Checks the ageing summary for AR
|
||||
ageing_summary = statement_dict["_Test Customer"][1][0]
|
||||
expected_summary = frappe._dict(
|
||||
range1=100,
|
||||
range2=0,
|
||||
range3=0,
|
||||
range4=0,
|
||||
range5=0,
|
||||
)
|
||||
self.check_ageing_summary(ageing_summary, expected_summary)
|
||||
self.process_soa = create_process_soa()
|
||||
|
||||
def test_auto_email_for_process_soa_ar(self):
|
||||
process_soa = create_process_soa(
|
||||
name="_Test Process SOA", enable_auto_email=1, report="Accounts Receivable"
|
||||
)
|
||||
send_emails(process_soa.name, from_scheduler=True)
|
||||
process_soa.load_from_db()
|
||||
self.assertEqual(process_soa.posting_date, getdate(add_days(today(), 7)))
|
||||
|
||||
def check_ageing_summary(self, ageing, expected_ageing):
|
||||
for age_range in expected_ageing:
|
||||
self.assertEqual(expected_ageing[age_range], ageing.get(age_range))
|
||||
send_emails(self.process_soa.name, from_scheduler=True)
|
||||
self.process_soa.load_from_db()
|
||||
self.assertEqual(self.process_soa.posting_date, getdate(add_days(today(), 7)))
|
||||
|
||||
def tearDown(self):
|
||||
frappe.db.rollback()
|
||||
frappe.delete_doc_if_exists("Process Statement Of Accounts", "Test Process SOA")
|
||||
|
||||
|
||||
def create_process_soa(**args):
|
||||
args = frappe._dict(args)
|
||||
frappe.delete_doc_if_exists("Process Statement Of Accounts", args.name)
|
||||
def create_process_soa():
|
||||
frappe.delete_doc_if_exists("Process Statement Of Accounts", "Test Process SOA")
|
||||
process_soa = frappe.new_doc("Process Statement Of Accounts")
|
||||
soa_dict = frappe._dict(
|
||||
name=args.name,
|
||||
company=args.company or "_Test Company",
|
||||
customers=args.customers or [{"customer": "_Test Customer"}],
|
||||
enable_auto_email=1 if args.enable_auto_email else 0,
|
||||
frequency=args.frequency or "Weekly",
|
||||
report=args.report or "General Ledger",
|
||||
from_date=args.from_date or getdate(today()),
|
||||
to_date=args.to_date or getdate(today()),
|
||||
posting_date=args.posting_date or getdate(today()),
|
||||
include_ageing=1,
|
||||
)
|
||||
soa_dict = {
|
||||
"name": "Test Process SOA",
|
||||
"company": "_Test Company",
|
||||
}
|
||||
process_soa.update(soa_dict)
|
||||
process_soa.set("customers", [{"customer": "_Test Customer"}])
|
||||
process_soa.enable_auto_email = 1
|
||||
process_soa.frequency = "Weekly"
|
||||
process_soa.report = "Accounts Receivable"
|
||||
process_soa.save()
|
||||
return process_soa
|
||||
|
||||
@@ -59,25 +59,6 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
|
||||
this.show_stock_ledger();
|
||||
}
|
||||
|
||||
if (this.frm.doc.repost_required && this.frm.doc.docstatus===1) {
|
||||
this.frm.set_intro(__("Accounting entries for this invoice need to be reposted. Please click on 'Repost' button to update."));
|
||||
this.frm.add_custom_button(__('Repost Accounting Entries'),
|
||||
() => {
|
||||
this.frm.call({
|
||||
doc: this.frm.doc,
|
||||
method: 'repost_accounting_entries',
|
||||
freeze: true,
|
||||
freeze_message: __('Reposting...'),
|
||||
callback: (r) => {
|
||||
if (!r.exc) {
|
||||
frappe.msgprint(__('Accounting Entries are reposted.'));
|
||||
me.frm.refresh();
|
||||
}
|
||||
}
|
||||
});
|
||||
}).removeClass('btn-default').addClass('btn-warning');
|
||||
}
|
||||
|
||||
if(!doc.is_return && doc.docstatus == 1 && doc.outstanding_amount != 0){
|
||||
if(doc.on_hold) {
|
||||
this.frm.add_custom_button(
|
||||
@@ -181,7 +162,6 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
|
||||
}
|
||||
|
||||
this.frm.set_df_property("tax_withholding_category", "hidden", doc.apply_tds ? 0 : 1);
|
||||
erpnext.accounts.unreconcile_payments.add_unreconcile_btn(me.frm);
|
||||
}
|
||||
|
||||
unblock_invoice() {
|
||||
@@ -480,12 +460,6 @@ cur_frm.set_query("expense_account", "items", function(doc) {
|
||||
}
|
||||
});
|
||||
|
||||
cur_frm.set_query("wip_composite_asset", "items", function() {
|
||||
return {
|
||||
filters: {'is_composite_asset': 1, 'docstatus': 0 }
|
||||
}
|
||||
});
|
||||
|
||||
cur_frm.cscript.expense_account = function(doc, cdt, cdn){
|
||||
var d = locals[cdt][cdn];
|
||||
if(d.idx == 1 && d.expense_account){
|
||||
|
||||
@@ -36,7 +36,6 @@
|
||||
"currency_and_price_list",
|
||||
"currency",
|
||||
"conversion_rate",
|
||||
"use_transaction_date_exchange_rate",
|
||||
"column_break2",
|
||||
"buying_price_list",
|
||||
"price_list_currency",
|
||||
@@ -167,7 +166,6 @@
|
||||
"against_expense_account",
|
||||
"column_break_63",
|
||||
"unrealized_profit_loss_account",
|
||||
"repost_required",
|
||||
"subscription_section",
|
||||
"auto_repeat",
|
||||
"update_auto_repeat_reference",
|
||||
@@ -192,7 +190,8 @@
|
||||
"inter_company_invoice_reference",
|
||||
"is_old_subcontracting_flow",
|
||||
"remarks",
|
||||
"connections_tab"
|
||||
"connections_tab",
|
||||
"column_break_38"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -383,8 +382,7 @@
|
||||
"label": "Supplier Invoice No",
|
||||
"oldfieldname": "bill_no",
|
||||
"oldfieldtype": "Data",
|
||||
"print_hide": 1,
|
||||
"search_index": 1
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_15",
|
||||
@@ -407,8 +405,7 @@
|
||||
"no_copy": 1,
|
||||
"options": "Purchase Invoice",
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_addresses",
|
||||
@@ -990,7 +987,6 @@
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "cash_bank_account",
|
||||
"fieldtype": "Link",
|
||||
"label": "Cash/Bank Account",
|
||||
@@ -1054,7 +1050,6 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"depends_on": "eval:flt(doc.write_off_amount)!=0",
|
||||
"fieldname": "write_off_account",
|
||||
"fieldtype": "Link",
|
||||
@@ -1218,7 +1213,6 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"default": "No",
|
||||
"fieldname": "is_opening",
|
||||
"fieldtype": "Select",
|
||||
@@ -1351,7 +1345,6 @@
|
||||
"options": "Project"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"depends_on": "eval:doc.is_internal_supplier",
|
||||
"description": "Unrealized Profit/Loss account for intra-company transfers",
|
||||
"fieldname": "unrealized_profit_loss_account",
|
||||
@@ -1384,7 +1377,6 @@
|
||||
"depends_on": "eval:doc.is_subcontracted",
|
||||
"fieldname": "supplier_warehouse",
|
||||
"fieldtype": "Link",
|
||||
"ignore_user_permissions": 1,
|
||||
"label": "Supplier Warehouse",
|
||||
"no_copy": 1,
|
||||
"options": "Warehouse",
|
||||
@@ -1502,6 +1494,10 @@
|
||||
"fieldname": "column_break_6",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_38",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_50",
|
||||
"fieldtype": "Column Break"
|
||||
@@ -1572,29 +1568,13 @@
|
||||
"fieldname": "use_company_roundoff_cost_center",
|
||||
"fieldtype": "Check",
|
||||
"label": "Use Company Default Round Off Cost Center"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "repost_required",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Repost Required",
|
||||
"options": "Account",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "use_transaction_date_exchange_rate",
|
||||
"fieldtype": "Check",
|
||||
"label": "Use Transaction Date Exchange Rate",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
"idx": 204,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-11-03 15:47:30.319200",
|
||||
"modified": "2023-07-04 17:23:59.145031",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice",
|
||||
|
||||
@@ -11,9 +11,6 @@ from frappe.utils import cint, cstr, flt, formatdate, get_link_to_form, getdate,
|
||||
import erpnext
|
||||
from erpnext.accounts.deferred_revenue import validate_service_stop_date
|
||||
from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt
|
||||
from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import (
|
||||
validate_docs_for_deferred_accounting,
|
||||
)
|
||||
from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
|
||||
check_if_return_invoice_linked_with_payment_entry,
|
||||
get_total_in_party_account_currency,
|
||||
@@ -33,7 +30,7 @@ from erpnext.accounts.general_ledger import (
|
||||
)
|
||||
from erpnext.accounts.party import get_due_date, get_party_account
|
||||
from erpnext.accounts.utils import get_account_currency, get_fiscal_year
|
||||
from erpnext.assets.doctype.asset.asset import is_cwip_accounting_enabled
|
||||
from erpnext.assets.doctype.asset.asset import get_asset_account, is_cwip_accounting_enabled
|
||||
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
|
||||
from erpnext.buying.utils import check_on_hold_or_closed_status
|
||||
from erpnext.controllers.accounts_controller import validate_account_head
|
||||
@@ -284,6 +281,9 @@ class PurchaseInvoice(BuyingController):
|
||||
# in case of auto inventory accounting,
|
||||
# expense account is always "Stock Received But Not Billed" for a stock item
|
||||
# except opening entry, drop-ship entry and fixed asset items
|
||||
if item.item_code:
|
||||
asset_category = frappe.get_cached_value("Item", item.item_code, "asset_category")
|
||||
|
||||
if (
|
||||
auto_accounting_for_stock
|
||||
and item.item_code in stock_items
|
||||
@@ -350,26 +350,22 @@ class PurchaseInvoice(BuyingController):
|
||||
frappe.msgprint(msg, title=_("Expense Head Changed"))
|
||||
|
||||
item.expense_account = stock_not_billed_account
|
||||
elif item.is_fixed_asset and item.pr_detail:
|
||||
if not asset_received_but_not_billed:
|
||||
asset_received_but_not_billed = self.get_company_default("asset_received_but_not_billed")
|
||||
item.expense_account = asset_received_but_not_billed
|
||||
elif item.is_fixed_asset:
|
||||
account_type = (
|
||||
"capital_work_in_progress_account"
|
||||
if is_cwip_accounting_enabled(item.asset_category)
|
||||
else "fixed_asset_account"
|
||||
)
|
||||
|
||||
elif item.is_fixed_asset and not is_cwip_accounting_enabled(asset_category):
|
||||
asset_category_account = get_asset_category_account(
|
||||
account_type, item=item.item_code, company=self.company
|
||||
"fixed_asset_account", item=item.item_code, company=self.company
|
||||
)
|
||||
if not asset_category_account:
|
||||
form_link = get_link_to_form("Asset Category", item.asset_category)
|
||||
form_link = get_link_to_form("Asset Category", asset_category)
|
||||
throw(
|
||||
_("Please set Fixed Asset Account in {} against {}.").format(form_link, self.company),
|
||||
title=_("Missing Account"),
|
||||
)
|
||||
item.expense_account = asset_category_account
|
||||
elif item.is_fixed_asset and item.pr_detail:
|
||||
if not asset_received_but_not_billed:
|
||||
asset_received_but_not_billed = self.get_company_default("asset_received_but_not_billed")
|
||||
item.expense_account = asset_received_but_not_billed
|
||||
elif not item.expense_account and for_validate:
|
||||
throw(_("Expense account is mandatory for item {0}").format(item.item_code or item.item_name))
|
||||
|
||||
@@ -491,11 +487,6 @@ class PurchaseInvoice(BuyingController):
|
||||
_("Stock cannot be updated against Purchase Receipt {0}").format(item.purchase_receipt)
|
||||
)
|
||||
|
||||
def validate_for_repost(self):
|
||||
self.validate_write_off_account()
|
||||
self.validate_expense_account()
|
||||
validate_docs_for_deferred_accounting([], [self.name])
|
||||
|
||||
def on_submit(self):
|
||||
super(PurchaseInvoice, self).on_submit()
|
||||
|
||||
@@ -538,19 +529,6 @@ class PurchaseInvoice(BuyingController):
|
||||
|
||||
self.process_common_party_accounting()
|
||||
|
||||
def on_update_after_submit(self):
|
||||
if hasattr(self, "repost_required"):
|
||||
fields_to_check = [
|
||||
"cash_bank_account",
|
||||
"write_off_account",
|
||||
"unrealized_profit_loss_account",
|
||||
]
|
||||
child_tables = {"items": ("expense_account",), "taxes": ("account_head",)}
|
||||
self.needs_repost = self.check_if_fields_updated(fields_to_check, child_tables)
|
||||
if self.needs_repost:
|
||||
self.validate_for_repost()
|
||||
self.db_set("repost_required", self.needs_repost)
|
||||
|
||||
def make_gl_entries(self, gl_entries=None, from_repost=False):
|
||||
if not gl_entries:
|
||||
gl_entries = self.get_gl_entries()
|
||||
@@ -592,11 +570,12 @@ class PurchaseInvoice(BuyingController):
|
||||
|
||||
def get_gl_entries(self, warehouse_account=None):
|
||||
self.auto_accounting_for_stock = erpnext.is_perpetual_inventory_enabled(self.company)
|
||||
|
||||
if self.auto_accounting_for_stock:
|
||||
self.stock_received_but_not_billed = self.get_company_default("stock_received_but_not_billed")
|
||||
self.expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
|
||||
else:
|
||||
self.stock_received_but_not_billed = None
|
||||
self.expenses_included_in_valuation = None
|
||||
|
||||
self.negative_expense_to_be_booked = 0.0
|
||||
gl_entries = []
|
||||
@@ -605,6 +584,9 @@ class PurchaseInvoice(BuyingController):
|
||||
self.make_item_gl_entries(gl_entries)
|
||||
self.make_precision_loss_gl_entry(gl_entries)
|
||||
|
||||
if self.check_asset_cwip_enabled():
|
||||
self.get_asset_gl_entry(gl_entries)
|
||||
|
||||
self.make_tax_gl_entries(gl_entries)
|
||||
self.make_internal_transfer_gl_entries(gl_entries)
|
||||
|
||||
@@ -708,11 +690,7 @@ class PurchaseInvoice(BuyingController):
|
||||
if item.item_code:
|
||||
asset_category = frappe.get_cached_value("Item", item.item_code, "asset_category")
|
||||
|
||||
if (
|
||||
self.update_stock
|
||||
and self.auto_accounting_for_stock
|
||||
and (item.item_code in stock_items or item.is_fixed_asset)
|
||||
):
|
||||
if self.update_stock and self.auto_accounting_for_stock and item.item_code in stock_items:
|
||||
# warehouse account
|
||||
warehouse_debit_amount = self.make_stock_adjustment_entry(
|
||||
gl_entries, item, voucher_wise_stock_value, account_currency
|
||||
@@ -827,7 +805,9 @@ class PurchaseInvoice(BuyingController):
|
||||
)
|
||||
)
|
||||
|
||||
else:
|
||||
elif not item.is_fixed_asset or (
|
||||
item.is_fixed_asset and not is_cwip_accounting_enabled(asset_category)
|
||||
):
|
||||
expense_account = (
|
||||
item.expense_account
|
||||
if (not item.enable_deferred_expense or self.is_return)
|
||||
@@ -920,6 +900,40 @@ class PurchaseInvoice(BuyingController):
|
||||
)
|
||||
)
|
||||
|
||||
# If asset is bought through this document and not linked to PR
|
||||
if self.update_stock and item.landed_cost_voucher_amount:
|
||||
expenses_included_in_asset_valuation = self.get_company_default(
|
||||
"expenses_included_in_asset_valuation"
|
||||
)
|
||||
# Amount added through landed-cost-voucher
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": expenses_included_in_asset_valuation,
|
||||
"against": expense_account,
|
||||
"cost_center": item.cost_center,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||
"credit": flt(item.landed_cost_voucher_amount),
|
||||
"project": item.project or self.project,
|
||||
},
|
||||
item=item,
|
||||
)
|
||||
)
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": expense_account,
|
||||
"against": expenses_included_in_asset_valuation,
|
||||
"cost_center": item.cost_center,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||
"debit": flt(item.landed_cost_voucher_amount),
|
||||
"project": item.project or self.project,
|
||||
},
|
||||
item=item,
|
||||
)
|
||||
)
|
||||
|
||||
# update gross amount of asset bought through this document
|
||||
assets = frappe.db.get_all(
|
||||
"Asset", filters={"purchase_invoice": self.name, "item_code": item.item_code}
|
||||
@@ -944,17 +958,11 @@ class PurchaseInvoice(BuyingController):
|
||||
(item.purchase_receipt, valuation_tax_accounts),
|
||||
)
|
||||
|
||||
stock_rbnb = (
|
||||
self.get_company_default("asset_received_but_not_billed")
|
||||
if item.is_fixed_asset
|
||||
else self.stock_received_but_not_billed
|
||||
)
|
||||
|
||||
if not negative_expense_booked_in_pr:
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": stock_rbnb,
|
||||
"account": self.stock_received_but_not_billed,
|
||||
"against": self.supplier,
|
||||
"debit": flt(item.item_tax_amount, item.precision("item_tax_amount")),
|
||||
"remarks": self.remarks or _("Accounting Entry for Stock"),
|
||||
@@ -969,12 +977,156 @@ class PurchaseInvoice(BuyingController):
|
||||
item.item_tax_amount, item.precision("item_tax_amount")
|
||||
)
|
||||
|
||||
assets = frappe.db.get_all(
|
||||
"Asset", filters={"purchase_invoice": self.name, "item_code": item.item_code}
|
||||
)
|
||||
for asset in assets:
|
||||
frappe.db.set_value("Asset", asset.name, "gross_purchase_amount", flt(item.valuation_rate))
|
||||
frappe.db.set_value("Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate))
|
||||
def get_asset_gl_entry(self, gl_entries):
|
||||
arbnb_account = None
|
||||
eiiav_account = None
|
||||
asset_eiiav_currency = None
|
||||
|
||||
for item in self.get("items"):
|
||||
if item.is_fixed_asset:
|
||||
asset_amount = flt(item.net_amount) + flt(item.item_tax_amount / self.conversion_rate)
|
||||
base_asset_amount = flt(item.base_net_amount + item.item_tax_amount)
|
||||
|
||||
item_exp_acc_type = frappe.db.get_value("Account", item.expense_account, "account_type")
|
||||
if not item.expense_account or item_exp_acc_type not in [
|
||||
"Asset Received But Not Billed",
|
||||
"Fixed Asset",
|
||||
]:
|
||||
if not arbnb_account:
|
||||
arbnb_account = self.get_company_default("asset_received_but_not_billed")
|
||||
item.expense_account = arbnb_account
|
||||
|
||||
if not self.update_stock:
|
||||
arbnb_currency = get_account_currency(item.expense_account)
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": item.expense_account,
|
||||
"against": self.supplier,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
|
||||
"debit": base_asset_amount,
|
||||
"debit_in_account_currency": (
|
||||
base_asset_amount if arbnb_currency == self.company_currency else asset_amount
|
||||
),
|
||||
"cost_center": item.cost_center,
|
||||
"project": item.project or self.project,
|
||||
},
|
||||
item=item,
|
||||
)
|
||||
)
|
||||
|
||||
if item.item_tax_amount:
|
||||
if not eiiav_account or not asset_eiiav_currency:
|
||||
eiiav_account = self.get_company_default("expenses_included_in_asset_valuation")
|
||||
asset_eiiav_currency = get_account_currency(eiiav_account)
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": eiiav_account,
|
||||
"against": self.supplier,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
|
||||
"cost_center": item.cost_center,
|
||||
"project": item.project or self.project,
|
||||
"credit": item.item_tax_amount,
|
||||
"credit_in_account_currency": (
|
||||
item.item_tax_amount
|
||||
if asset_eiiav_currency == self.company_currency
|
||||
else item.item_tax_amount / self.conversion_rate
|
||||
),
|
||||
},
|
||||
item=item,
|
||||
)
|
||||
)
|
||||
else:
|
||||
cwip_account = get_asset_account(
|
||||
"capital_work_in_progress_account", asset_category=item.asset_category, company=self.company
|
||||
)
|
||||
|
||||
cwip_account_currency = get_account_currency(cwip_account)
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": cwip_account,
|
||||
"against": self.supplier,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
|
||||
"debit": base_asset_amount,
|
||||
"debit_in_account_currency": (
|
||||
base_asset_amount if cwip_account_currency == self.company_currency else asset_amount
|
||||
),
|
||||
"cost_center": self.cost_center,
|
||||
"project": item.project or self.project,
|
||||
},
|
||||
item=item,
|
||||
)
|
||||
)
|
||||
|
||||
if item.item_tax_amount and not cint(erpnext.is_perpetual_inventory_enabled(self.company)):
|
||||
if not eiiav_account or not asset_eiiav_currency:
|
||||
eiiav_account = self.get_company_default("expenses_included_in_asset_valuation")
|
||||
asset_eiiav_currency = get_account_currency(eiiav_account)
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": eiiav_account,
|
||||
"against": self.supplier,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
|
||||
"cost_center": item.cost_center,
|
||||
"credit": item.item_tax_amount,
|
||||
"project": item.project or self.project,
|
||||
"credit_in_account_currency": (
|
||||
item.item_tax_amount
|
||||
if asset_eiiav_currency == self.company_currency
|
||||
else item.item_tax_amount / self.conversion_rate
|
||||
),
|
||||
},
|
||||
item=item,
|
||||
)
|
||||
)
|
||||
|
||||
# Assets are bought through this document then it will be linked to this document
|
||||
if flt(item.landed_cost_voucher_amount):
|
||||
if not eiiav_account:
|
||||
eiiav_account = self.get_company_default("expenses_included_in_asset_valuation")
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": eiiav_account,
|
||||
"against": cwip_account,
|
||||
"cost_center": item.cost_center,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||
"credit": flt(item.landed_cost_voucher_amount),
|
||||
"project": item.project or self.project,
|
||||
},
|
||||
item=item,
|
||||
)
|
||||
)
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": cwip_account,
|
||||
"against": eiiav_account,
|
||||
"cost_center": item.cost_center,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||
"debit": flt(item.landed_cost_voucher_amount),
|
||||
"project": item.project or self.project,
|
||||
},
|
||||
item=item,
|
||||
)
|
||||
)
|
||||
|
||||
# update gross amount of assets bought through this document
|
||||
assets = frappe.db.get_all(
|
||||
"Asset", filters={"purchase_invoice": self.name, "item_code": item.item_code}
|
||||
)
|
||||
for asset in assets:
|
||||
frappe.db.set_value("Asset", asset.name, "gross_purchase_amount", flt(item.valuation_rate))
|
||||
frappe.db.set_value("Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate))
|
||||
|
||||
return gl_entries
|
||||
|
||||
def make_stock_adjustment_entry(
|
||||
self, gl_entries, item, voucher_wise_stock_value, account_currency
|
||||
@@ -1676,7 +1828,6 @@ def make_purchase_receipt(source_name, target_doc=None):
|
||||
"po_detail": "purchase_order_item",
|
||||
"material_request": "material_request",
|
||||
"material_request_item": "material_request_item",
|
||||
"wip_composite_asset": "wip_composite_asset",
|
||||
},
|
||||
"postprocess": update_item,
|
||||
"condition": lambda doc: abs(doc.received_qty) < abs(doc.qty),
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||
from frappe.tests.utils import change_settings
|
||||
from frappe.utils import add_days, cint, flt, getdate, nowdate, today
|
||||
|
||||
import erpnext
|
||||
@@ -33,7 +33,7 @@ test_dependencies = ["Item", "Cost Center", "Payment Term", "Payment Terms Templ
|
||||
test_ignore = ["Serial No"]
|
||||
|
||||
|
||||
class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
|
||||
class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
|
||||
@classmethod
|
||||
def setUpClass(self):
|
||||
unlink_payment_on_cancel_of_invoice()
|
||||
@@ -43,9 +43,6 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
|
||||
def tearDownClass(self):
|
||||
unlink_payment_on_cancel_of_invoice(0)
|
||||
|
||||
def tearDown(self):
|
||||
frappe.db.rollback()
|
||||
|
||||
def test_purchase_invoice_received_qty(self):
|
||||
"""
|
||||
1. Test if received qty is validated against accepted + rejected
|
||||
@@ -420,7 +417,6 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
|
||||
self.assertEqual(tax.tax_amount, expected_values[i][1])
|
||||
self.assertEqual(tax.total, expected_values[i][2])
|
||||
|
||||
@change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1})
|
||||
def test_purchase_invoice_with_advance(self):
|
||||
from erpnext.accounts.doctype.journal_entry.test_journal_entry import (
|
||||
test_records as jv_test_records,
|
||||
@@ -475,7 +471,6 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
|
||||
)
|
||||
)
|
||||
|
||||
@change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1})
|
||||
def test_invoice_with_advance_and_multi_payment_terms(self):
|
||||
from erpnext.accounts.doctype.journal_entry.test_journal_entry import (
|
||||
test_records as jv_test_records,
|
||||
@@ -1214,7 +1209,6 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
|
||||
acc_settings.submit_journal_entriessubmit_journal_entries = 0
|
||||
acc_settings.save()
|
||||
|
||||
@change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1})
|
||||
def test_gain_loss_with_advance_entry(self):
|
||||
unlink_enabled = frappe.db.get_value(
|
||||
"Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice"
|
||||
@@ -1417,7 +1411,6 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
|
||||
)
|
||||
frappe.db.set_value("Company", "_Test Company", "exchange_gain_loss_account", original_account)
|
||||
|
||||
@change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1})
|
||||
def test_purchase_invoice_advance_taxes(self):
|
||||
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
||||
|
||||
@@ -1718,14 +1711,9 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
|
||||
self.assertTrue(return_pi.docstatus == 1)
|
||||
|
||||
def test_gl_entries_for_standalone_debit_note(self):
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
make_purchase_invoice(qty=5, rate=500, update_stock=True)
|
||||
|
||||
item_code = make_item(properties={"is_stock_item": 1})
|
||||
make_purchase_invoice(item_code=item_code, qty=5, rate=500, update_stock=True)
|
||||
|
||||
returned_inv = make_purchase_invoice(
|
||||
item_code=item_code, qty=-5, rate=5, update_stock=True, is_return=True
|
||||
)
|
||||
returned_inv = make_purchase_invoice(qty=-5, rate=5, update_stock=True, is_return=True)
|
||||
|
||||
# override the rate with valuation rate
|
||||
sle = frappe.get_all(
|
||||
@@ -1735,7 +1723,7 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
|
||||
)[0]
|
||||
|
||||
rate = flt(sle.stock_value_difference) / flt(sle.actual_qty)
|
||||
self.assertAlmostEqual(rate, 500)
|
||||
self.assertAlmostEqual(returned_inv.items[0].rate, rate)
|
||||
|
||||
def test_payment_allocation_for_payment_terms(self):
|
||||
from erpnext.buying.doctype.purchase_order.test_purchase_order import (
|
||||
@@ -1808,6 +1796,7 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
|
||||
|
||||
pi = make_purchase_invoice(
|
||||
company="_Test Company",
|
||||
customer="_Test Supplier",
|
||||
do_not_save=True,
|
||||
do_not_submit=True,
|
||||
rate=1000,
|
||||
@@ -1837,62 +1826,6 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
|
||||
clear_dimension_defaults("Branch")
|
||||
disable_dimension()
|
||||
|
||||
def test_repost_accounting_entries(self):
|
||||
# update repost settings
|
||||
settings = frappe.get_doc("Repost Accounting Ledger Settings")
|
||||
if not [x for x in settings.allowed_types if x.document_type == "Purchase Invoice"]:
|
||||
settings.append("allowed_types", {"document_type": "Purchase Invoice", "allowed": True})
|
||||
settings.save()
|
||||
|
||||
pi = make_purchase_invoice(
|
||||
rate=1000,
|
||||
price_list_rate=1000,
|
||||
qty=1,
|
||||
)
|
||||
expected_gle = [
|
||||
["_Test Account Cost for Goods Sold - _TC", 1000, 0.0, nowdate()],
|
||||
["Creditors - _TC", 0.0, 1000, nowdate()],
|
||||
]
|
||||
check_gl_entries(self, pi.name, expected_gle, nowdate())
|
||||
|
||||
pi.items[0].expense_account = "Service - _TC"
|
||||
pi.save()
|
||||
pi.load_from_db()
|
||||
self.assertTrue(pi.repost_required)
|
||||
pi.repost_accounting_entries()
|
||||
|
||||
expected_gle = [
|
||||
["Creditors - _TC", 0.0, 1000, nowdate()],
|
||||
["Service - _TC", 1000, 0.0, nowdate()],
|
||||
]
|
||||
check_gl_entries(self, pi.name, expected_gle, nowdate())
|
||||
pi.load_from_db()
|
||||
self.assertFalse(pi.repost_required)
|
||||
|
||||
def test_default_cost_center_for_purchase(self):
|
||||
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
|
||||
|
||||
for c_center in ["_Test Cost Center Selling", "_Test Cost Center Buying"]:
|
||||
create_cost_center(cost_center_name=c_center)
|
||||
|
||||
item = create_item(
|
||||
"_Test Cost Center Item For Purchase",
|
||||
is_stock_item=1,
|
||||
buying_cost_center="_Test Cost Center Buying - _TC",
|
||||
selling_cost_center="_Test Cost Center Selling - _TC",
|
||||
)
|
||||
|
||||
pi = make_purchase_invoice(
|
||||
item=item.name, qty=1, rate=1000, update_stock=True, do_not_submit=True, cost_center=""
|
||||
)
|
||||
|
||||
pi.items[0].cost_center = ""
|
||||
pi.set_missing_values()
|
||||
pi.calculate_taxes_and_totals()
|
||||
pi.save()
|
||||
|
||||
self.assertEqual(pi.items[0].cost_center, "_Test Cost Center Buying - _TC")
|
||||
|
||||
|
||||
def check_gl_entries(
|
||||
doc,
|
||||
|
||||
@@ -75,7 +75,6 @@
|
||||
"manufacturer_part_no",
|
||||
"accounting",
|
||||
"expense_account",
|
||||
"wip_composite_asset",
|
||||
"col_break5",
|
||||
"is_fixed_asset",
|
||||
"asset_location",
|
||||
@@ -468,7 +467,6 @@
|
||||
"label": "Accounting"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "expense_account",
|
||||
"fieldtype": "Link",
|
||||
"label": "Expense Head",
|
||||
@@ -879,18 +877,12 @@
|
||||
"fieldname": "apply_tds",
|
||||
"fieldtype": "Check",
|
||||
"label": "Apply TDS"
|
||||
},
|
||||
{
|
||||
"fieldname": "wip_composite_asset",
|
||||
"fieldtype": "Link",
|
||||
"label": "WIP Composite Asset",
|
||||
"options": "Asset"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-10-03 21:01:01.824892",
|
||||
"modified": "2023-07-04 17:22:21.501152",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice Item",
|
||||
@@ -900,4 +892,4 @@
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +86,6 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"columns": 2,
|
||||
"fieldname": "account_head",
|
||||
"fieldtype": "Link",
|
||||
@@ -98,7 +97,6 @@
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"default": ":Company",
|
||||
"fieldname": "cost_center",
|
||||
"fieldtype": "Link",
|
||||
|
||||
@@ -5,7 +5,9 @@ frappe.ui.form.on("Repost Accounting Ledger", {
|
||||
setup: function(frm) {
|
||||
frm.fields_dict['vouchers'].grid.get_field('voucher_type').get_query = function(doc) {
|
||||
return {
|
||||
query: "erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger.get_repost_allowed_types"
|
||||
filters: {
|
||||
name: ['in', ['Purchase Invoice', 'Sales Invoice', 'Payment Entry', 'Journal Entry']],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-09-26 14:21:27.362567",
|
||||
"modified": "2023-07-27 15:47:58.975034",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Repost Accounting Ledger",
|
||||
@@ -77,6 +77,5 @@
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
"states": []
|
||||
}
|
||||
@@ -10,12 +10,9 @@ from frappe.utils.data import comma_and
|
||||
class RepostAccountingLedger(Document):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(RepostAccountingLedger, self).__init__(*args, **kwargs)
|
||||
self._allowed_types = [
|
||||
x.document_type
|
||||
for x in frappe.db.get_all(
|
||||
"Repost Allowed Types", filters={"allowed": True}, fields=["distinct(document_type)"]
|
||||
)
|
||||
]
|
||||
self._allowed_types = set(
|
||||
["Purchase Invoice", "Sales Invoice", "Payment Entry", "Journal Entry"]
|
||||
)
|
||||
|
||||
def validate(self):
|
||||
self.validate_vouchers()
|
||||
@@ -24,8 +21,29 @@ class RepostAccountingLedger(Document):
|
||||
|
||||
def validate_for_deferred_accounting(self):
|
||||
sales_docs = [x.voucher_no for x in self.vouchers if x.voucher_type == "Sales Invoice"]
|
||||
docs_with_deferred_revenue = frappe.db.get_all(
|
||||
"Sales Invoice Item",
|
||||
filters={"parent": ["in", sales_docs], "docstatus": 1, "enable_deferred_revenue": True},
|
||||
fields=["parent"],
|
||||
as_list=1,
|
||||
)
|
||||
|
||||
purchase_docs = [x.voucher_no for x in self.vouchers if x.voucher_type == "Purchase Invoice"]
|
||||
validate_docs_for_deferred_accounting(sales_docs, purchase_docs)
|
||||
docs_with_deferred_expense = frappe.db.get_all(
|
||||
"Purchase Invoice Item",
|
||||
filters={"parent": ["in", purchase_docs], "docstatus": 1, "enable_deferred_expense": 1},
|
||||
fields=["parent"],
|
||||
as_list=1,
|
||||
)
|
||||
|
||||
if docs_with_deferred_revenue or docs_with_deferred_expense:
|
||||
frappe.throw(
|
||||
_("Documents: {0} have deferred revenue/expense enabled for them. Cannot repost.").format(
|
||||
frappe.bold(
|
||||
comma_and([x[0] for x in docs_with_deferred_expense + docs_with_deferred_revenue])
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
def validate_for_closed_fiscal_year(self):
|
||||
if self.vouchers:
|
||||
@@ -121,17 +139,14 @@ class RepostAccountingLedger(Document):
|
||||
return rendered_page
|
||||
|
||||
def on_submit(self):
|
||||
if len(self.vouchers) > 1:
|
||||
job_name = "repost_accounting_ledger_" + self.name
|
||||
frappe.enqueue(
|
||||
method="erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger.start_repost",
|
||||
account_repost_doc=self.name,
|
||||
is_async=True,
|
||||
job_name=job_name,
|
||||
)
|
||||
frappe.msgprint(_("Repost has started in the background"))
|
||||
else:
|
||||
start_repost(self.name)
|
||||
job_name = "repost_accounting_ledger_" + self.name
|
||||
frappe.enqueue(
|
||||
method="erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger.start_repost",
|
||||
account_repost_doc=self.name,
|
||||
is_async=True,
|
||||
job_name=job_name,
|
||||
)
|
||||
frappe.msgprint(_("Repost has started in the background"))
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@@ -160,47 +175,9 @@ def start_repost(account_repost_doc=str) -> None:
|
||||
doc.docstatus = 1
|
||||
doc.make_gl_entries()
|
||||
|
||||
elif doc.doctype in ["Payment Entry", "Journal Entry", "Expense Claim"]:
|
||||
elif doc.doctype in ["Payment Entry", "Journal Entry"]:
|
||||
if not repost_doc.delete_cancelled_entries:
|
||||
doc.make_gl_entries(1)
|
||||
doc.make_gl_entries()
|
||||
|
||||
frappe.db.commit()
|
||||
|
||||
|
||||
def validate_docs_for_deferred_accounting(sales_docs, purchase_docs):
|
||||
docs_with_deferred_revenue = frappe.db.get_all(
|
||||
"Sales Invoice Item",
|
||||
filters={"parent": ["in", sales_docs], "docstatus": 1, "enable_deferred_revenue": True},
|
||||
fields=["parent"],
|
||||
as_list=1,
|
||||
)
|
||||
|
||||
docs_with_deferred_expense = frappe.db.get_all(
|
||||
"Purchase Invoice Item",
|
||||
filters={"parent": ["in", purchase_docs], "docstatus": 1, "enable_deferred_expense": 1},
|
||||
fields=["parent"],
|
||||
as_list=1,
|
||||
)
|
||||
|
||||
if docs_with_deferred_revenue or docs_with_deferred_expense:
|
||||
frappe.throw(
|
||||
_("Documents: {0} have deferred revenue/expense enabled for them. Cannot repost.").format(
|
||||
frappe.bold(comma_and([x[0] for x in docs_with_deferred_expense + docs_with_deferred_revenue]))
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_repost_allowed_types(doctype, txt, searchfield, start, page_len, filters):
|
||||
filters = {"allowed": True}
|
||||
|
||||
if txt:
|
||||
filters.update({"document_type": ("like", f"%{txt}%")})
|
||||
|
||||
if allowed_types := frappe.db.get_all(
|
||||
"Repost Allowed Types", filters=filters, fields=["distinct(document_type)"], as_list=1
|
||||
):
|
||||
return allowed_types
|
||||
return []
|
||||
|
||||
@@ -20,18 +20,10 @@ class TestRepostAccountingLedger(AccountsTestMixin, FrappeTestCase):
|
||||
self.create_company()
|
||||
self.create_customer()
|
||||
self.create_item()
|
||||
self.update_repost_settings()
|
||||
|
||||
def teadDown(self):
|
||||
frappe.db.rollback()
|
||||
|
||||
def update_repost_settings(self):
|
||||
allowed_types = ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal Entry"]
|
||||
repost_settings = frappe.get_doc("Repost Accounting Ledger Settings")
|
||||
for x in allowed_types:
|
||||
repost_settings.append("allowed_types", {"document_type": x, "allowed": True})
|
||||
repost_settings.save()
|
||||
|
||||
def test_01_basic_functions(self):
|
||||
si = create_sales_invoice(
|
||||
item=self.item,
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("Repost Accounting Ledger Settings", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@@ -1,46 +0,0 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2023-11-07 09:57:20.619939",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"allowed_types"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "allowed_types",
|
||||
"fieldtype": "Table",
|
||||
"label": "Allowed Doctypes",
|
||||
"options": "Repost Allowed Types"
|
||||
}
|
||||
],
|
||||
"in_create": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2023-11-07 14:24:13.321522",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Repost Accounting Ledger Settings",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"role": "Administrator",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"read": 1,
|
||||
"role": "System Manager",
|
||||
"select": 1
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class RepostAccountingLedgerSettings(Document):
|
||||
pass
|
||||
@@ -1,9 +0,0 @@
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
|
||||
|
||||
class TestRepostAccountingLedgerSettings(FrappeTestCase):
|
||||
pass
|
||||
@@ -1,45 +0,0 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2023-11-07 09:58:03.595382",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"document_type",
|
||||
"column_break_sfzb",
|
||||
"allowed"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "document_type",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Doctype",
|
||||
"options": "DocType"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "allowed",
|
||||
"fieldtype": "Check",
|
||||
"in_list_view": 1,
|
||||
"label": "Allowed"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_sfzb",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-11-07 10:01:39.217861",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Repost Allowed Types",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class RepostAllowedTypes(Document):
|
||||
pass
|
||||
@@ -99,7 +99,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-09-26 14:21:35.719727",
|
||||
"modified": "2022-11-08 07:38:40.079038",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Repost Payment Ledger",
|
||||
@@ -155,6 +155,5 @@
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
"states": []
|
||||
}
|
||||
@@ -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', "Repost Payment Ledger", "Repost Accounting Ledger", "Unreconcile Payments", "Unreconcile Payment Entries"];
|
||||
'POS Closing Entry', 'Journal Entry', 'Payment Entry', "Repost Payment Ledger", "Repost Accounting Ledger"];
|
||||
|
||||
if(!this.frm.doc.__islocal && !this.frm.doc.customer && this.frm.doc.debit_to) {
|
||||
// show debit_to in print format
|
||||
@@ -177,8 +177,6 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
|
||||
}, __('Create'));
|
||||
}
|
||||
}
|
||||
|
||||
erpnext.accounts.unreconcile_payments.add_unreconcile_btn(me.frm);
|
||||
}
|
||||
|
||||
make_maintenance_schedule() {
|
||||
@@ -556,6 +554,15 @@ cur_frm.fields_dict.write_off_cost_center.get_query = function(doc) {
|
||||
}
|
||||
}
|
||||
|
||||
// Income Account in Details Table
|
||||
// --------------------------------
|
||||
cur_frm.set_query("income_account", "items", function(doc) {
|
||||
return{
|
||||
query: "erpnext.controllers.queries.get_income_account",
|
||||
filters: {'company': doc.company}
|
||||
}
|
||||
});
|
||||
|
||||
// Cost Center in Details Table
|
||||
// -----------------------------
|
||||
cur_frm.fields_dict["items"].grid.get_field("cost_center").get_query = function(doc) {
|
||||
@@ -650,16 +657,6 @@ frappe.ui.form.on('Sales Invoice', {
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("income_account", "items", function() {
|
||||
return{
|
||||
query: "erpnext.controllers.queries.get_income_account",
|
||||
filters: {
|
||||
'company': frm.doc.company,
|
||||
"disabled": 0
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
frm.custom_make_buttons = {
|
||||
'Delivery Note': 'Delivery',
|
||||
'Sales Invoice': 'Return / Credit Note',
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
"is_return",
|
||||
"return_against",
|
||||
"update_billed_amount_in_sales_order",
|
||||
"update_billed_amount_in_delivery_note",
|
||||
"is_debit_note",
|
||||
"amended_from",
|
||||
"accounting_dimensions_section",
|
||||
@@ -2145,13 +2144,6 @@
|
||||
"fieldname": "use_company_roundoff_cost_center",
|
||||
"fieldtype": "Check",
|
||||
"label": "Use Company default Cost Center for Round off"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval: doc.is_return",
|
||||
"fieldname": "update_billed_amount_in_delivery_note",
|
||||
"fieldtype": "Check",
|
||||
"label": "Update Billed Amount in Delivery Note"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
@@ -2164,7 +2156,7 @@
|
||||
"link_fieldname": "consolidated_invoice"
|
||||
}
|
||||
],
|
||||
"modified": "2023-11-03 14:39:38.012346",
|
||||
"modified": "2023-06-19 16:02:05.309332",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice",
|
||||
|
||||
@@ -11,13 +11,13 @@ from frappe.utils import add_days, cint, cstr, flt, formatdate, get_link_to_form
|
||||
|
||||
import erpnext
|
||||
from erpnext.accounts.deferred_revenue import validate_service_stop_date
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||
get_accounting_dimensions,
|
||||
)
|
||||
from erpnext.accounts.doctype.loyalty_program.loyalty_program import (
|
||||
get_loyalty_program_details_with_points,
|
||||
validate_loyalty_points,
|
||||
)
|
||||
from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import (
|
||||
validate_docs_for_deferred_accounting,
|
||||
)
|
||||
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import (
|
||||
get_party_tax_withholding_details,
|
||||
)
|
||||
@@ -176,12 +176,6 @@ class SalesInvoice(SellingController):
|
||||
self.validate_account_for_change_amount()
|
||||
self.validate_income_account()
|
||||
|
||||
def validate_for_repost(self):
|
||||
self.validate_write_off_account()
|
||||
self.validate_account_for_change_amount()
|
||||
self.validate_income_account()
|
||||
validate_docs_for_deferred_accounting([self.name], [])
|
||||
|
||||
def validate_fixed_asset(self):
|
||||
for d in self.get("items"):
|
||||
if d.is_fixed_asset and d.meta.get_field("asset") and d.asset:
|
||||
@@ -261,7 +255,6 @@ class SalesInvoice(SellingController):
|
||||
|
||||
self.update_status_updater_args()
|
||||
self.update_prevdoc_status()
|
||||
|
||||
self.update_billing_status_in_dn()
|
||||
self.clear_unallocated_mode_of_payments()
|
||||
|
||||
@@ -408,8 +401,6 @@ class SalesInvoice(SellingController):
|
||||
"Repost Payment Ledger Items",
|
||||
"Repost Accounting Ledger",
|
||||
"Repost Accounting Ledger Items",
|
||||
"Unreconcile Payments",
|
||||
"Unreconcile Payment Entries",
|
||||
"Payment Ledger Entry",
|
||||
)
|
||||
|
||||
@@ -536,22 +527,89 @@ class SalesInvoice(SellingController):
|
||||
|
||||
def on_update_after_submit(self):
|
||||
if hasattr(self, "repost_required"):
|
||||
fields_to_check = [
|
||||
"additional_discount_account",
|
||||
"cash_bank_account",
|
||||
"account_for_change_amount",
|
||||
"write_off_account",
|
||||
"loyalty_redemption_account",
|
||||
"unrealized_profit_loss_account",
|
||||
]
|
||||
child_tables = {
|
||||
"items": ("income_account", "expense_account", "discount_account"),
|
||||
"taxes": ("account_head",),
|
||||
}
|
||||
self.needs_repost = self.check_if_fields_updated(fields_to_check, child_tables)
|
||||
if self.needs_repost:
|
||||
self.validate_for_repost()
|
||||
self.db_set("repost_required", self.needs_repost)
|
||||
needs_repost = 0
|
||||
|
||||
# Check if any field affecting accounting entry is altered
|
||||
doc_before_update = self.get_doc_before_save()
|
||||
accounting_dimensions = get_accounting_dimensions() + ["cost_center", "project"]
|
||||
|
||||
# Check if opening entry check updated
|
||||
if doc_before_update.get("is_opening") != self.is_opening:
|
||||
needs_repost = 1
|
||||
|
||||
if not needs_repost:
|
||||
# Parent Level Accounts excluding party account
|
||||
for field in (
|
||||
"additional_discount_account",
|
||||
"cash_bank_account",
|
||||
"account_for_change_amount",
|
||||
"write_off_account",
|
||||
"loyalty_redemption_account",
|
||||
"unrealized_profit_loss_account",
|
||||
):
|
||||
if doc_before_update.get(field) != self.get(field):
|
||||
needs_repost = 1
|
||||
break
|
||||
|
||||
# Check for parent accounting dimensions
|
||||
for dimension in accounting_dimensions:
|
||||
if doc_before_update.get(dimension) != self.get(dimension):
|
||||
needs_repost = 1
|
||||
break
|
||||
|
||||
# Check for child tables
|
||||
if self.check_if_child_table_updated(
|
||||
"items",
|
||||
doc_before_update,
|
||||
("income_account", "expense_account", "discount_account"),
|
||||
accounting_dimensions,
|
||||
):
|
||||
needs_repost = 1
|
||||
|
||||
if self.check_if_child_table_updated(
|
||||
"taxes", doc_before_update, ("account_head",), accounting_dimensions
|
||||
):
|
||||
needs_repost = 1
|
||||
|
||||
self.validate_accounts()
|
||||
|
||||
# validate if deferred revenue is enabled for any item
|
||||
# Don't allow to update the invoice if deferred revenue is enabled
|
||||
for item in self.get("items"):
|
||||
if item.enable_deferred_revenue:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Deferred Revenue is enabled for item {0}. You cannot update the invoice after submission."
|
||||
).format(item.item_code)
|
||||
)
|
||||
|
||||
self.db_set("repost_required", needs_repost)
|
||||
|
||||
def check_if_child_table_updated(
|
||||
self, child_table, doc_before_update, fields_to_check, accounting_dimensions
|
||||
):
|
||||
# Check if any field affecting accounting entry is altered
|
||||
for index, item in enumerate(self.get(child_table)):
|
||||
for field in fields_to_check:
|
||||
if doc_before_update.get(child_table)[index].get(field) != item.get(field):
|
||||
return True
|
||||
|
||||
for dimension in accounting_dimensions:
|
||||
if doc_before_update.get(child_table)[index].get(dimension) != item.get(dimension):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@frappe.whitelist()
|
||||
def repost_accounting_entries(self):
|
||||
if self.repost_required:
|
||||
self.docstatus = 2
|
||||
self.make_gl_entries_on_cancel()
|
||||
self.docstatus = 1
|
||||
self.make_gl_entries()
|
||||
self.db_set("repost_required", 0)
|
||||
else:
|
||||
frappe.throw(_("No updates pending for reposting"))
|
||||
|
||||
def set_paid_amount(self):
|
||||
paid_amount = 0.0
|
||||
@@ -1037,7 +1095,7 @@ class SalesInvoice(SellingController):
|
||||
|
||||
def make_customer_gl_entry(self, gl_entries):
|
||||
# Checked both rounding_adjustment and rounded_total
|
||||
# because rounded_total had value even before introduction of posting GLE based on rounded total
|
||||
# because rounded_total had value even before introcution of posting GLE based on rounded total
|
||||
grand_total = (
|
||||
self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total
|
||||
)
|
||||
@@ -1272,7 +1330,7 @@ class SalesInvoice(SellingController):
|
||||
if skip_change_gl_entries and payment_mode.account == self.account_for_change_amount:
|
||||
payment_mode.base_amount -= flt(self.change_amount)
|
||||
|
||||
if payment_mode.base_amount:
|
||||
if payment_mode.amount:
|
||||
# POS, make payment entries
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
@@ -1434,8 +1492,6 @@ class SalesInvoice(SellingController):
|
||||
)
|
||||
|
||||
def update_billing_status_in_dn(self, update_modified=True):
|
||||
if self.is_return and not self.update_billed_amount_in_delivery_note:
|
||||
return
|
||||
updated_delivery_notes = []
|
||||
for d in self.get("items"):
|
||||
if d.dn_detail:
|
||||
|
||||
@@ -7,7 +7,7 @@ import unittest
|
||||
import frappe
|
||||
from frappe.model.dynamic_links import get_dynamic_link_map
|
||||
from frappe.model.naming import make_autoname
|
||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||
from frappe.tests.utils import change_settings
|
||||
from frappe.utils import add_days, flt, getdate, nowdate, today
|
||||
|
||||
import erpnext
|
||||
@@ -38,17 +38,13 @@ from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import
|
||||
from erpnext.stock.utils import get_incoming_rate, get_stock_balance
|
||||
|
||||
|
||||
class TestSalesInvoice(FrappeTestCase):
|
||||
class TestSalesInvoice(unittest.TestCase):
|
||||
def setUp(self):
|
||||
from erpnext.stock.doctype.stock_ledger_entry.test_stock_ledger_entry import create_items
|
||||
|
||||
create_items(["_Test Internal Transfer Item"], uoms=[{"uom": "Box", "conversion_factor": 10}])
|
||||
create_internal_parties()
|
||||
setup_accounts()
|
||||
frappe.db.set_single_value("Accounts Settings", "acc_frozen_upto", None)
|
||||
|
||||
def tearDown(self):
|
||||
frappe.db.rollback()
|
||||
|
||||
def make(self):
|
||||
w = frappe.copy_doc(test_records[0])
|
||||
@@ -176,7 +172,6 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
self.assertRaises(frappe.LinkExistsError, si.cancel)
|
||||
unlink_payment_on_cancel_of_invoice()
|
||||
|
||||
@change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1})
|
||||
def test_payment_entry_unlink_against_standalone_credit_note(self):
|
||||
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
|
||||
|
||||
@@ -1298,7 +1293,6 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
dn.submit()
|
||||
return dn
|
||||
|
||||
@change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1})
|
||||
def test_sales_invoice_with_advance(self):
|
||||
from erpnext.accounts.doctype.journal_entry.test_journal_entry import (
|
||||
test_records as jv_test_records,
|
||||
@@ -1807,10 +1801,6 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
)
|
||||
|
||||
def test_outstanding_amount_after_advance_payment_entry_cancellation(self):
|
||||
"""Test impact of advance PE submission/cancellation on SI and SO."""
|
||||
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||
|
||||
sales_order = make_sales_order(item_code="138-CMS Shoe", qty=1, price_list_rate=500)
|
||||
pe = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Payment Entry",
|
||||
@@ -1830,25 +1820,10 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
"paid_to": "_Test Cash - _TC",
|
||||
}
|
||||
)
|
||||
pe.append(
|
||||
"references",
|
||||
{
|
||||
"reference_doctype": "Sales Order",
|
||||
"reference_name": sales_order.name,
|
||||
"total_amount": sales_order.grand_total,
|
||||
"outstanding_amount": sales_order.grand_total,
|
||||
"allocated_amount": 300,
|
||||
},
|
||||
)
|
||||
pe.insert()
|
||||
pe.submit()
|
||||
|
||||
sales_order.reload()
|
||||
self.assertEqual(sales_order.advance_paid, 300)
|
||||
|
||||
si = frappe.copy_doc(test_records[0])
|
||||
si.items[0].sales_order = sales_order.name
|
||||
si.items[0].so_detail = sales_order.get("items")[0].name
|
||||
si.is_pos = 0
|
||||
si.append(
|
||||
"advances",
|
||||
@@ -1856,7 +1831,6 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
"doctype": "Sales Invoice Advance",
|
||||
"reference_type": "Payment Entry",
|
||||
"reference_name": pe.name,
|
||||
"reference_row": pe.references[0].name,
|
||||
"advance_amount": 300,
|
||||
"allocated_amount": 300,
|
||||
"remarks": pe.remarks,
|
||||
@@ -1865,13 +1839,7 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
si.insert()
|
||||
si.submit()
|
||||
|
||||
si.reload()
|
||||
pe.reload()
|
||||
sales_order.reload()
|
||||
|
||||
# Check if SO is unlinked/replaced by SI in PE & if SO advance paid is 0
|
||||
self.assertEqual(pe.references[0].reference_name, si.name)
|
||||
self.assertEqual(sales_order.advance_paid, 0.0)
|
||||
si.load_from_db()
|
||||
|
||||
# check outstanding after advance allocation
|
||||
self.assertEqual(
|
||||
@@ -1879,9 +1847,11 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
flt(si.rounded_total - si.total_advance, si.precision("outstanding_amount")),
|
||||
)
|
||||
|
||||
# added to avoid Document has been modified exception
|
||||
pe = frappe.get_doc("Payment Entry", pe.name)
|
||||
pe.cancel()
|
||||
si.reload()
|
||||
|
||||
si.load_from_db()
|
||||
# check outstanding after advance cancellation
|
||||
self.assertEqual(
|
||||
flt(si.outstanding_amount),
|
||||
@@ -2497,6 +2467,12 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
"stock_received_but_not_billed",
|
||||
"Stock Received But Not Billed - _TC1",
|
||||
)
|
||||
frappe.db.set_value(
|
||||
"Company",
|
||||
"_Test Company 1",
|
||||
"expenses_included_in_valuation",
|
||||
"Expenses Included In Valuation - _TC1",
|
||||
)
|
||||
|
||||
# begin test
|
||||
si = create_sales_invoice(
|
||||
@@ -2534,7 +2510,7 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
|
||||
# tear down
|
||||
frappe.local.enable_perpetual_inventory["_Test Company 1"] = old_perpetual_inventory
|
||||
frappe.db.set_single_value("Stock Settings", "allow_negative_stock", old_negative_stock)
|
||||
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", old_negative_stock)
|
||||
|
||||
def test_sle_for_target_warehouse(self):
|
||||
se = make_stock_entry(
|
||||
@@ -2546,7 +2522,6 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
)
|
||||
|
||||
si = frappe.copy_doc(test_records[0])
|
||||
si.customer = "_Test Internal Customer 3"
|
||||
si.update_stock = 1
|
||||
si.set_warehouse = "Finished Goods - _TC"
|
||||
si.set_target_warehouse = "Stores - _TC"
|
||||
@@ -2775,13 +2750,6 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
company="_Test Company",
|
||||
)
|
||||
|
||||
tds_payable_account = create_account(
|
||||
account_name="TDS Payable",
|
||||
account_type="Tax",
|
||||
parent_account="Duties and Taxes - _TC",
|
||||
company="_Test Company",
|
||||
)
|
||||
|
||||
si = create_sales_invoice(parent_cost_center="Main - _TC", do_not_save=1)
|
||||
si.apply_discount_on = "Grand Total"
|
||||
si.additional_discount_account = additional_discount_account
|
||||
@@ -3080,8 +3048,8 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
si.commission_rate = commission_rate
|
||||
self.assertRaises(frappe.ValidationError, si.save)
|
||||
|
||||
@change_settings("Accounts Settings", {"acc_frozen_upto": add_days(getdate(), 1)})
|
||||
def test_sales_invoice_submission_post_account_freezing_date(self):
|
||||
frappe.db.set_value("Accounts Settings", None, "acc_frozen_upto", add_days(getdate(), 1))
|
||||
si = create_sales_invoice(do_not_save=True)
|
||||
si.posting_date = add_days(getdate(), 1)
|
||||
si.save()
|
||||
@@ -3090,6 +3058,8 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
si.posting_date = getdate()
|
||||
si.submit()
|
||||
|
||||
frappe.db.set_value("Accounts Settings", None, "acc_frozen_upto", None)
|
||||
|
||||
def test_over_billing_case_against_delivery_note(self):
|
||||
"""
|
||||
Test a case where duplicating the item with qty = 1 in the invoice
|
||||
@@ -3118,13 +3088,6 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
|
||||
frappe.db.set_value("Accounts Settings", None, "over_billing_allowance", over_billing_allowance)
|
||||
|
||||
@change_settings(
|
||||
"Accounts Settings",
|
||||
{
|
||||
"book_deferred_entries_via_journal_entry": 1,
|
||||
"submit_journal_entries": 1,
|
||||
},
|
||||
)
|
||||
def test_multi_currency_deferred_revenue_via_journal_entry(self):
|
||||
deferred_account = create_account(
|
||||
account_name="Deferred Revenue",
|
||||
@@ -3132,6 +3095,11 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
company="_Test Company",
|
||||
)
|
||||
|
||||
acc_settings = frappe.get_single("Accounts Settings")
|
||||
acc_settings.book_deferred_entries_via_journal_entry = 1
|
||||
acc_settings.submit_journal_entries = 1
|
||||
acc_settings.save()
|
||||
|
||||
item = create_item("_Test Item for Deferred Accounting")
|
||||
item.enable_deferred_expense = 1
|
||||
item.item_defaults[0].deferred_revenue_account = deferred_account
|
||||
@@ -3197,6 +3165,13 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
self.assertEqual(expected_gle[i][2], gle.debit)
|
||||
self.assertEqual(getdate(expected_gle[i][3]), gle.posting_date)
|
||||
|
||||
acc_settings = frappe.get_single("Accounts Settings")
|
||||
acc_settings.book_deferred_entries_via_journal_entry = 0
|
||||
acc_settings.submit_journal_entries = 0
|
||||
acc_settings.save()
|
||||
|
||||
frappe.db.set_value("Accounts Settings", None, "acc_frozen_upto", None)
|
||||
|
||||
def test_standalone_serial_no_return(self):
|
||||
si = create_sales_invoice(
|
||||
item_code="_Test Serialized Item With Series", update_stock=True, is_return=True, qty=-1
|
||||
@@ -3574,20 +3549,6 @@ def create_internal_parties():
|
||||
allowed_to_interact_with="_Test Company with perpetual inventory",
|
||||
)
|
||||
|
||||
create_internal_customer(
|
||||
customer_name="_Test Internal Customer 3",
|
||||
represents_company="_Test Company",
|
||||
allowed_to_interact_with="_Test Company",
|
||||
)
|
||||
|
||||
account = create_account(
|
||||
account_name="Unrealized Profit",
|
||||
parent_account="Current Liabilities - _TC",
|
||||
company="_Test Company",
|
||||
)
|
||||
|
||||
frappe.db.set_value("Company", "_Test Company", "unrealized_profit_loss_account", account)
|
||||
|
||||
create_internal_supplier(
|
||||
supplier_name="_Test Internal Supplier",
|
||||
represents_company="Wind Power LLC",
|
||||
|
||||
@@ -18,14 +18,6 @@ frappe.ui.form.on('Subscription', {
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query('sales_tax_template', function () {
|
||||
return {
|
||||
filters: {
|
||||
company: frm.doc.company
|
||||
}
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
from frappe.utils.data import (
|
||||
add_days,
|
||||
add_months,
|
||||
@@ -91,14 +90,10 @@ def create_parties():
|
||||
customer.insert()
|
||||
|
||||
|
||||
class TestSubscription(FrappeTestCase):
|
||||
class TestSubscription(unittest.TestCase):
|
||||
def setUp(self):
|
||||
create_plan()
|
||||
create_parties()
|
||||
frappe.db.set_single_value("Accounts Settings", "acc_frozen_upto", None)
|
||||
|
||||
def tearDown(self):
|
||||
frappe.db.rollback()
|
||||
|
||||
def test_create_subscription_with_trial_with_correct_period(self):
|
||||
subscription = frappe.new_doc("Subscription")
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2023-08-22 10:28:10.196712",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"account",
|
||||
"party_type",
|
||||
"party",
|
||||
"reference_doctype",
|
||||
"reference_name",
|
||||
"allocated_amount",
|
||||
"account_currency",
|
||||
"unlinked"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "reference_name",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Reference Name",
|
||||
"options": "reference_doctype"
|
||||
},
|
||||
{
|
||||
"fieldname": "allocated_amount",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Allocated Amount",
|
||||
"options": "account_currency"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "unlinked",
|
||||
"fieldtype": "Check",
|
||||
"in_list_view": 1,
|
||||
"label": "Unlinked",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "reference_doctype",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Reference Type",
|
||||
"options": "DocType"
|
||||
},
|
||||
{
|
||||
"fieldname": "account",
|
||||
"fieldtype": "Data",
|
||||
"label": "Account"
|
||||
},
|
||||
{
|
||||
"fieldname": "party_type",
|
||||
"fieldtype": "Data",
|
||||
"label": "Party Type"
|
||||
},
|
||||
{
|
||||
"fieldname": "party",
|
||||
"fieldtype": "Data",
|
||||
"label": "Party"
|
||||
},
|
||||
{
|
||||
"fieldname": "account_currency",
|
||||
"fieldtype": "Link",
|
||||
"label": "Account Currency",
|
||||
"options": "Currency",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-09-05 09:33:28.620149",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Unreconcile Payment Entries",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class UnreconcilePaymentEntries(Document):
|
||||
pass
|
||||
@@ -1,316 +0,0 @@
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
from frappe.utils import today
|
||||
|
||||
from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
|
||||
|
||||
|
||||
class TestUnreconcilePayments(AccountsTestMixin, FrappeTestCase):
|
||||
def setUp(self):
|
||||
self.create_company()
|
||||
self.create_customer()
|
||||
self.create_usd_receivable_account()
|
||||
self.create_item()
|
||||
self.clear_old_entries()
|
||||
|
||||
def tearDown(self):
|
||||
frappe.db.rollback()
|
||||
|
||||
def create_sales_invoice(self, do_not_submit=False):
|
||||
si = create_sales_invoice(
|
||||
item=self.item,
|
||||
company=self.company,
|
||||
customer=self.customer,
|
||||
debit_to=self.debit_to,
|
||||
posting_date=today(),
|
||||
parent_cost_center=self.cost_center,
|
||||
cost_center=self.cost_center,
|
||||
rate=100,
|
||||
price_list_rate=100,
|
||||
do_not_submit=do_not_submit,
|
||||
)
|
||||
return si
|
||||
|
||||
def create_payment_entry(self):
|
||||
pe = create_payment_entry(
|
||||
company=self.company,
|
||||
payment_type="Receive",
|
||||
party_type="Customer",
|
||||
party=self.customer,
|
||||
paid_from=self.debit_to,
|
||||
paid_to=self.cash,
|
||||
paid_amount=200,
|
||||
save=True,
|
||||
)
|
||||
return pe
|
||||
|
||||
def test_01_unreconcile_invoice(self):
|
||||
si1 = self.create_sales_invoice()
|
||||
si2 = self.create_sales_invoice()
|
||||
|
||||
pe = self.create_payment_entry()
|
||||
pe.append(
|
||||
"references",
|
||||
{"reference_doctype": si1.doctype, "reference_name": si1.name, "allocated_amount": 100},
|
||||
)
|
||||
pe.append(
|
||||
"references",
|
||||
{"reference_doctype": si2.doctype, "reference_name": si2.name, "allocated_amount": 100},
|
||||
)
|
||||
# Allocation payment against both invoices
|
||||
pe.save().submit()
|
||||
|
||||
# Assert outstanding
|
||||
[doc.reload() for doc in [si1, si2, pe]]
|
||||
self.assertEqual(si1.outstanding_amount, 0)
|
||||
self.assertEqual(si2.outstanding_amount, 0)
|
||||
self.assertEqual(pe.unallocated_amount, 0)
|
||||
|
||||
unreconcile = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Unreconcile Payments",
|
||||
"company": self.company,
|
||||
"voucher_type": pe.doctype,
|
||||
"voucher_no": pe.name,
|
||||
}
|
||||
)
|
||||
unreconcile.add_references()
|
||||
self.assertEqual(len(unreconcile.allocations), 2)
|
||||
allocations = [x.reference_name for x in unreconcile.allocations]
|
||||
self.assertEquals([si1.name, si2.name], allocations)
|
||||
# unreconcile si1
|
||||
for x in unreconcile.allocations:
|
||||
if x.reference_name != si1.name:
|
||||
unreconcile.remove(x)
|
||||
unreconcile.save().submit()
|
||||
|
||||
# Assert outstanding
|
||||
[doc.reload() for doc in [si1, si2, pe]]
|
||||
self.assertEqual(si1.outstanding_amount, 100)
|
||||
self.assertEqual(si2.outstanding_amount, 0)
|
||||
self.assertEqual(len(pe.references), 1)
|
||||
self.assertEqual(pe.unallocated_amount, 100)
|
||||
|
||||
def test_02_unreconcile_one_payment_from_multi_payments(self):
|
||||
"""
|
||||
Scenario: 2 payments, both split against 2 different invoices
|
||||
Unreconcile only one payment from one invoice
|
||||
"""
|
||||
si1 = self.create_sales_invoice()
|
||||
si2 = self.create_sales_invoice()
|
||||
pe1 = self.create_payment_entry()
|
||||
pe1.paid_amount = 100
|
||||
# Allocate payment against both invoices
|
||||
pe1.append(
|
||||
"references",
|
||||
{"reference_doctype": si1.doctype, "reference_name": si1.name, "allocated_amount": 50},
|
||||
)
|
||||
pe1.append(
|
||||
"references",
|
||||
{"reference_doctype": si2.doctype, "reference_name": si2.name, "allocated_amount": 50},
|
||||
)
|
||||
pe1.save().submit()
|
||||
|
||||
pe2 = self.create_payment_entry()
|
||||
pe2.paid_amount = 100
|
||||
# Allocate payment against both invoices
|
||||
pe2.append(
|
||||
"references",
|
||||
{"reference_doctype": si1.doctype, "reference_name": si1.name, "allocated_amount": 50},
|
||||
)
|
||||
pe2.append(
|
||||
"references",
|
||||
{"reference_doctype": si2.doctype, "reference_name": si2.name, "allocated_amount": 50},
|
||||
)
|
||||
pe2.save().submit()
|
||||
|
||||
# Assert outstanding and unallocated
|
||||
[doc.reload() for doc in [si1, si2, pe1, pe2]]
|
||||
self.assertEqual(si1.outstanding_amount, 0.0)
|
||||
self.assertEqual(si2.outstanding_amount, 0.0)
|
||||
self.assertEqual(pe1.unallocated_amount, 0.0)
|
||||
self.assertEqual(pe2.unallocated_amount, 0.0)
|
||||
|
||||
unreconcile = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Unreconcile Payments",
|
||||
"company": self.company,
|
||||
"voucher_type": pe2.doctype,
|
||||
"voucher_no": pe2.name,
|
||||
}
|
||||
)
|
||||
unreconcile.add_references()
|
||||
self.assertEqual(len(unreconcile.allocations), 2)
|
||||
allocations = [x.reference_name for x in unreconcile.allocations]
|
||||
self.assertEquals([si1.name, si2.name], allocations)
|
||||
# unreconcile si1 from pe2
|
||||
for x in unreconcile.allocations:
|
||||
if x.reference_name != si1.name:
|
||||
unreconcile.remove(x)
|
||||
unreconcile.save().submit()
|
||||
|
||||
# Assert outstanding and unallocated
|
||||
[doc.reload() for doc in [si1, si2, pe1, pe2]]
|
||||
self.assertEqual(si1.outstanding_amount, 50)
|
||||
self.assertEqual(si2.outstanding_amount, 0)
|
||||
self.assertEqual(len(pe1.references), 2)
|
||||
self.assertEqual(len(pe2.references), 1)
|
||||
self.assertEqual(pe1.unallocated_amount, 0)
|
||||
self.assertEqual(pe2.unallocated_amount, 50)
|
||||
|
||||
def test_03_unreconciliation_on_multi_currency_invoice(self):
|
||||
self.create_customer("_Test MC Customer USD", "USD")
|
||||
si1 = self.create_sales_invoice(do_not_submit=True)
|
||||
si1.currency = "USD"
|
||||
si1.debit_to = self.debtors_usd
|
||||
si1.conversion_rate = 80
|
||||
si1.save().submit()
|
||||
|
||||
si2 = self.create_sales_invoice(do_not_submit=True)
|
||||
si2.currency = "USD"
|
||||
si2.debit_to = self.debtors_usd
|
||||
si2.conversion_rate = 80
|
||||
si2.save().submit()
|
||||
|
||||
pe = self.create_payment_entry()
|
||||
pe.paid_from = self.debtors_usd
|
||||
pe.paid_from_account_currency = "USD"
|
||||
pe.source_exchange_rate = 75
|
||||
pe.received_amount = 75 * 200
|
||||
pe.save()
|
||||
# Allocate payment against both invoices
|
||||
pe.append(
|
||||
"references",
|
||||
{"reference_doctype": si1.doctype, "reference_name": si1.name, "allocated_amount": 100},
|
||||
)
|
||||
pe.append(
|
||||
"references",
|
||||
{"reference_doctype": si2.doctype, "reference_name": si2.name, "allocated_amount": 100},
|
||||
)
|
||||
pe.save().submit()
|
||||
|
||||
unreconcile = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Unreconcile Payments",
|
||||
"company": self.company,
|
||||
"voucher_type": pe.doctype,
|
||||
"voucher_no": pe.name,
|
||||
}
|
||||
)
|
||||
unreconcile.add_references()
|
||||
self.assertEqual(len(unreconcile.allocations), 2)
|
||||
allocations = [x.reference_name for x in unreconcile.allocations]
|
||||
self.assertEquals([si1.name, si2.name], allocations)
|
||||
# unreconcile si1 from pe
|
||||
for x in unreconcile.allocations:
|
||||
if x.reference_name != si1.name:
|
||||
unreconcile.remove(x)
|
||||
unreconcile.save().submit()
|
||||
|
||||
# Assert outstanding and unallocated
|
||||
[doc.reload() for doc in [si1, si2, pe]]
|
||||
self.assertEqual(si1.outstanding_amount, 100)
|
||||
self.assertEqual(si2.outstanding_amount, 0)
|
||||
self.assertEqual(len(pe.references), 1)
|
||||
self.assertEqual(pe.unallocated_amount, 100)
|
||||
|
||||
# Exc gain/loss JE should've been cancelled as well
|
||||
self.assertEqual(
|
||||
frappe.db.count(
|
||||
"Journal Entry Account",
|
||||
filters={"reference_type": si1.doctype, "reference_name": si1.name, "docstatus": 1},
|
||||
),
|
||||
0,
|
||||
)
|
||||
|
||||
def test_04_unreconciliation_on_multi_currency_invoice(self):
|
||||
"""
|
||||
2 payments split against 2 foreign currency invoices
|
||||
"""
|
||||
self.create_customer("_Test MC Customer USD", "USD")
|
||||
si1 = self.create_sales_invoice(do_not_submit=True)
|
||||
si1.currency = "USD"
|
||||
si1.debit_to = self.debtors_usd
|
||||
si1.conversion_rate = 80
|
||||
si1.save().submit()
|
||||
|
||||
si2 = self.create_sales_invoice(do_not_submit=True)
|
||||
si2.currency = "USD"
|
||||
si2.debit_to = self.debtors_usd
|
||||
si2.conversion_rate = 80
|
||||
si2.save().submit()
|
||||
|
||||
pe1 = self.create_payment_entry()
|
||||
pe1.paid_from = self.debtors_usd
|
||||
pe1.paid_from_account_currency = "USD"
|
||||
pe1.source_exchange_rate = 75
|
||||
pe1.received_amount = 75 * 100
|
||||
pe1.save()
|
||||
# Allocate payment against both invoices
|
||||
pe1.append(
|
||||
"references",
|
||||
{"reference_doctype": si1.doctype, "reference_name": si1.name, "allocated_amount": 50},
|
||||
)
|
||||
pe1.append(
|
||||
"references",
|
||||
{"reference_doctype": si2.doctype, "reference_name": si2.name, "allocated_amount": 50},
|
||||
)
|
||||
pe1.save().submit()
|
||||
|
||||
pe2 = self.create_payment_entry()
|
||||
pe2.paid_from = self.debtors_usd
|
||||
pe2.paid_from_account_currency = "USD"
|
||||
pe2.source_exchange_rate = 75
|
||||
pe2.received_amount = 75 * 100
|
||||
pe2.save()
|
||||
# Allocate payment against both invoices
|
||||
pe2.append(
|
||||
"references",
|
||||
{"reference_doctype": si1.doctype, "reference_name": si1.name, "allocated_amount": 50},
|
||||
)
|
||||
pe2.append(
|
||||
"references",
|
||||
{"reference_doctype": si2.doctype, "reference_name": si2.name, "allocated_amount": 50},
|
||||
)
|
||||
pe2.save().submit()
|
||||
|
||||
unreconcile = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Unreconcile Payments",
|
||||
"company": self.company,
|
||||
"voucher_type": pe2.doctype,
|
||||
"voucher_no": pe2.name,
|
||||
}
|
||||
)
|
||||
unreconcile.add_references()
|
||||
self.assertEqual(len(unreconcile.allocations), 2)
|
||||
allocations = [x.reference_name for x in unreconcile.allocations]
|
||||
self.assertEquals([si1.name, si2.name], allocations)
|
||||
# unreconcile si1 from pe2
|
||||
for x in unreconcile.allocations:
|
||||
if x.reference_name != si1.name:
|
||||
unreconcile.remove(x)
|
||||
unreconcile.save().submit()
|
||||
|
||||
# Assert outstanding and unallocated
|
||||
[doc.reload() for doc in [si1, si2, pe1, pe2]]
|
||||
self.assertEqual(si1.outstanding_amount, 50)
|
||||
self.assertEqual(si2.outstanding_amount, 0)
|
||||
self.assertEqual(len(pe1.references), 2)
|
||||
self.assertEqual(len(pe2.references), 1)
|
||||
self.assertEqual(pe1.unallocated_amount, 0)
|
||||
self.assertEqual(pe2.unallocated_amount, 50)
|
||||
|
||||
# Exc gain/loss JE from PE1 should be available
|
||||
self.assertEqual(
|
||||
frappe.db.count(
|
||||
"Journal Entry Account",
|
||||
filters={"reference_type": si1.doctype, "reference_name": si1.name, "docstatus": 1},
|
||||
),
|
||||
1,
|
||||
)
|
||||
@@ -1,41 +0,0 @@
|
||||
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on("Unreconcile Payments", {
|
||||
refresh(frm) {
|
||||
frm.set_query("voucher_type", function() {
|
||||
return {
|
||||
filters: {
|
||||
name: ["in", ["Payment Entry", "Journal Entry"]]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
frm.set_query("voucher_no", function(doc) {
|
||||
return {
|
||||
filters: {
|
||||
company: doc.company,
|
||||
docstatus: 1
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
},
|
||||
get_allocations: function(frm) {
|
||||
frm.clear_table("allocations");
|
||||
frappe.call({
|
||||
method: "get_allocations_from_payment",
|
||||
doc: frm.doc,
|
||||
callback: function(r) {
|
||||
if (r.message) {
|
||||
r.message.forEach(x => {
|
||||
frm.add_child("allocations", x)
|
||||
})
|
||||
frm.refresh_fields();
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
});
|
||||
@@ -1,93 +0,0 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"autoname": "format:UNREC-{#####}",
|
||||
"creation": "2023-08-22 10:26:34.421423",
|
||||
"default_view": "List",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"company",
|
||||
"voucher_type",
|
||||
"voucher_no",
|
||||
"get_allocations",
|
||||
"allocations",
|
||||
"amended_from"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "amended_from",
|
||||
"fieldtype": "Link",
|
||||
"label": "Amended From",
|
||||
"no_copy": 1,
|
||||
"options": "Unreconcile Payments",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"label": "Company",
|
||||
"options": "Company"
|
||||
},
|
||||
{
|
||||
"fieldname": "voucher_type",
|
||||
"fieldtype": "Link",
|
||||
"label": "Voucher Type",
|
||||
"options": "DocType"
|
||||
},
|
||||
{
|
||||
"fieldname": "voucher_no",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"label": "Voucher No",
|
||||
"options": "voucher_type"
|
||||
},
|
||||
{
|
||||
"fieldname": "get_allocations",
|
||||
"fieldtype": "Button",
|
||||
"label": "Get Allocations"
|
||||
},
|
||||
{
|
||||
"fieldname": "allocations",
|
||||
"fieldtype": "Table",
|
||||
"label": "Allocations",
|
||||
"options": "Unreconcile Payment Entries"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-08-28 17:42:50.261377",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Unreconcile Payments",
|
||||
"naming_rule": "Expression",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"read": 1,
|
||||
"role": "Accounts Manager",
|
||||
"select": 1,
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"read": 1,
|
||||
"role": "Accounts User",
|
||||
"select": 1,
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
from frappe import _, qb
|
||||
from frappe.model.document import Document
|
||||
from frappe.query_builder import Criterion
|
||||
from frappe.query_builder.functions import Abs, Sum
|
||||
from frappe.utils.data import comma_and
|
||||
|
||||
from erpnext.accounts.utils import (
|
||||
cancel_exchange_gain_loss_journal,
|
||||
unlink_ref_doc_from_payment_entries,
|
||||
update_voucher_outstanding,
|
||||
)
|
||||
|
||||
|
||||
class UnreconcilePayments(Document):
|
||||
def validate(self):
|
||||
self.supported_types = ["Payment Entry", "Journal Entry"]
|
||||
if not self.voucher_type in self.supported_types:
|
||||
frappe.throw(_("Only {0} are supported").format(comma_and(self.supported_types)))
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_allocations_from_payment(self):
|
||||
allocated_references = []
|
||||
ple = qb.DocType("Payment Ledger Entry")
|
||||
allocated_references = (
|
||||
qb.from_(ple)
|
||||
.select(
|
||||
ple.account,
|
||||
ple.party_type,
|
||||
ple.party,
|
||||
ple.against_voucher_type.as_("reference_doctype"),
|
||||
ple.against_voucher_no.as_("reference_name"),
|
||||
Abs(Sum(ple.amount_in_account_currency)).as_("allocated_amount"),
|
||||
ple.account_currency,
|
||||
)
|
||||
.where(
|
||||
(ple.docstatus == 1)
|
||||
& (ple.voucher_type == self.voucher_type)
|
||||
& (ple.voucher_no == self.voucher_no)
|
||||
& (ple.voucher_no != ple.against_voucher_no)
|
||||
)
|
||||
.groupby(ple.against_voucher_type, ple.against_voucher_no)
|
||||
.run(as_dict=True)
|
||||
)
|
||||
|
||||
return allocated_references
|
||||
|
||||
def add_references(self):
|
||||
allocations = self.get_allocations_from_payment()
|
||||
|
||||
for alloc in allocations:
|
||||
self.append("allocations", alloc)
|
||||
|
||||
def on_submit(self):
|
||||
# todo: more granular unreconciliation
|
||||
for alloc in self.allocations:
|
||||
doc = frappe.get_doc(alloc.reference_doctype, alloc.reference_name)
|
||||
unlink_ref_doc_from_payment_entries(doc, self.voucher_no)
|
||||
cancel_exchange_gain_loss_journal(doc, self.voucher_type, self.voucher_no)
|
||||
update_voucher_outstanding(
|
||||
alloc.reference_doctype, alloc.reference_name, alloc.account, alloc.party_type, alloc.party
|
||||
)
|
||||
frappe.db.set_value("Unreconcile Payment Entries", alloc.name, "unlinked", True)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def doc_has_references(doctype: str = None, docname: str = None):
|
||||
if doctype in ["Sales Invoice", "Purchase Invoice"]:
|
||||
return frappe.db.count(
|
||||
"Payment Ledger Entry",
|
||||
filters={"delinked": 0, "against_voucher_no": docname, "amount": ["<", 0]},
|
||||
)
|
||||
else:
|
||||
return frappe.db.count(
|
||||
"Payment Ledger Entry",
|
||||
filters={"delinked": 0, "voucher_no": docname, "against_voucher_no": ["!=", docname]},
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_linked_payments_for_doc(
|
||||
company: str = None, doctype: str = None, docname: str = None
|
||||
) -> list:
|
||||
if company and doctype and docname:
|
||||
_dt = doctype
|
||||
_dn = docname
|
||||
ple = qb.DocType("Payment Ledger Entry")
|
||||
if _dt in ["Sales Invoice", "Purchase Invoice"]:
|
||||
criteria = [
|
||||
(ple.company == company),
|
||||
(ple.delinked == 0),
|
||||
(ple.against_voucher_no == _dn),
|
||||
(ple.amount < 0),
|
||||
]
|
||||
|
||||
res = (
|
||||
qb.from_(ple)
|
||||
.select(
|
||||
ple.company,
|
||||
ple.voucher_type,
|
||||
ple.voucher_no,
|
||||
Abs(Sum(ple.amount_in_account_currency)).as_("allocated_amount"),
|
||||
ple.account_currency,
|
||||
)
|
||||
.where(Criterion.all(criteria))
|
||||
.groupby(ple.voucher_no, ple.against_voucher_no)
|
||||
.having(qb.Field("allocated_amount") > 0)
|
||||
.run(as_dict=True)
|
||||
)
|
||||
return res
|
||||
else:
|
||||
criteria = [
|
||||
(ple.company == company),
|
||||
(ple.delinked == 0),
|
||||
(ple.voucher_no == _dn),
|
||||
(ple.against_voucher_no != _dn),
|
||||
]
|
||||
|
||||
query = (
|
||||
qb.from_(ple)
|
||||
.select(
|
||||
ple.company,
|
||||
ple.against_voucher_type.as_("voucher_type"),
|
||||
ple.against_voucher_no.as_("voucher_no"),
|
||||
Abs(Sum(ple.amount_in_account_currency)).as_("allocated_amount"),
|
||||
ple.account_currency,
|
||||
)
|
||||
.where(Criterion.all(criteria))
|
||||
.groupby(ple.against_voucher_no)
|
||||
)
|
||||
res = query.run(as_dict=True)
|
||||
return res
|
||||
return []
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_unreconcile_doc_for_selection(selections=None):
|
||||
if selections:
|
||||
selections = frappe.json.loads(selections)
|
||||
# assuming each row is a unique voucher
|
||||
for row in selections:
|
||||
unrecon = frappe.new_doc("Unreconcile Payments")
|
||||
unrecon.company = row.get("company")
|
||||
unrecon.voucher_type = row.get("voucher_type")
|
||||
unrecon.voucher_no = row.get("voucher_no")
|
||||
unrecon.add_references()
|
||||
|
||||
# remove unselected references
|
||||
unrecon.allocations = [
|
||||
x
|
||||
for x in unrecon.allocations
|
||||
if x.reference_doctype == row.get("against_voucher_type")
|
||||
and x.reference_name == row.get("against_voucher_no")
|
||||
]
|
||||
unrecon.save().submit()
|
||||
@@ -41,7 +41,7 @@ def make_gl_entries(
|
||||
from_repost=from_repost,
|
||||
)
|
||||
save_entries(gl_map, adv_adj, update_outstanding, from_repost)
|
||||
# Post GL Map process there may no be any GL Entries
|
||||
# Post GL Map proccess there may no be any GL Entries
|
||||
elif gl_map:
|
||||
frappe.throw(
|
||||
_(
|
||||
|
||||
@@ -5,8 +5,13 @@
|
||||
from typing import Optional
|
||||
|
||||
import frappe
|
||||
from frappe import _, msgprint, qb, scrub
|
||||
from frappe.contacts.doctype.address.address import get_company_address, get_default_address
|
||||
from frappe import _, msgprint, scrub
|
||||
from frappe.contacts.doctype.address.address import (
|
||||
get_address_display,
|
||||
get_company_address,
|
||||
get_default_address,
|
||||
)
|
||||
from frappe.contacts.doctype.contact.contact import get_contact_details
|
||||
from frappe.core.doctype.user_permission.user_permission import get_permitted_documents
|
||||
from frappe.model.utils import get_fetch_values
|
||||
from frappe.query_builder.functions import Abs, Date, Sum
|
||||
@@ -128,7 +133,6 @@ def _get_party_details(
|
||||
party_address,
|
||||
company_address,
|
||||
shipping_address,
|
||||
ignore_permissions=ignore_permissions,
|
||||
)
|
||||
set_contact_details(party_details, party, party_type)
|
||||
set_other_values(party_details, party, party_type)
|
||||
@@ -189,8 +193,6 @@ def set_address_details(
|
||||
party_address=None,
|
||||
company_address=None,
|
||||
shipping_address=None,
|
||||
*,
|
||||
ignore_permissions=False
|
||||
):
|
||||
billing_address_field = (
|
||||
"customer_address" if party_type == "Lead" else party_type.lower() + "_address"
|
||||
@@ -203,17 +205,13 @@ def set_address_details(
|
||||
get_fetch_values(doctype, billing_address_field, party_details[billing_address_field])
|
||||
)
|
||||
# address display
|
||||
party_details.address_display = render_address(
|
||||
party_details[billing_address_field], check_permissions=not ignore_permissions
|
||||
)
|
||||
party_details.address_display = get_address_display(party_details[billing_address_field])
|
||||
# shipping address
|
||||
if party_type in ["Customer", "Lead"]:
|
||||
party_details.shipping_address_name = shipping_address or get_party_shipping_address(
|
||||
party_type, party.name
|
||||
)
|
||||
party_details.shipping_address = render_address(
|
||||
party_details["shipping_address_name"], check_permissions=not ignore_permissions
|
||||
)
|
||||
party_details.shipping_address = get_address_display(party_details["shipping_address_name"])
|
||||
if doctype:
|
||||
party_details.update(
|
||||
get_fetch_values(doctype, "shipping_address_name", party_details.shipping_address_name)
|
||||
@@ -231,7 +229,7 @@ def set_address_details(
|
||||
if shipping_address:
|
||||
party_details.update(
|
||||
shipping_address=shipping_address,
|
||||
shipping_address_display=render_address(shipping_address),
|
||||
shipping_address_display=get_address_display(shipping_address),
|
||||
**get_fetch_values(doctype, "shipping_address", shipping_address)
|
||||
)
|
||||
|
||||
@@ -240,8 +238,7 @@ def set_address_details(
|
||||
party_details.update(
|
||||
billing_address=party_details.company_address,
|
||||
billing_address_display=(
|
||||
party_details.company_address_display
|
||||
or render_address(party_details.company_address, check_permissions=False)
|
||||
party_details.company_address_display or get_address_display(party_details.company_address)
|
||||
),
|
||||
**get_fetch_values(doctype, "billing_address", party_details.company_address)
|
||||
)
|
||||
@@ -293,34 +290,7 @@ def set_contact_details(party_details, party, party_type):
|
||||
}
|
||||
)
|
||||
else:
|
||||
fields = [
|
||||
"name as contact_person",
|
||||
"salutation",
|
||||
"first_name",
|
||||
"last_name",
|
||||
"email_id as contact_email",
|
||||
"mobile_no as contact_mobile",
|
||||
"phone as contact_phone",
|
||||
"designation as contact_designation",
|
||||
"department as contact_department",
|
||||
]
|
||||
|
||||
contact_details = frappe.db.get_value(
|
||||
"Contact", party_details.contact_person, fields, as_dict=True
|
||||
)
|
||||
|
||||
contact_details.contact_display = " ".join(
|
||||
filter(
|
||||
None,
|
||||
[
|
||||
contact_details.get("salutation"),
|
||||
contact_details.get("first_name"),
|
||||
contact_details.get("last_name"),
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
party_details.update(contact_details)
|
||||
party_details.update(get_contact_details(party_details.contact_person))
|
||||
|
||||
|
||||
def set_other_values(party_details, party, party_type):
|
||||
@@ -459,19 +429,11 @@ def get_party_account_currency(party_type, party, company):
|
||||
|
||||
def get_party_gle_currency(party_type, party, company):
|
||||
def generator():
|
||||
gl = qb.DocType("GL Entry")
|
||||
existing_gle_currency = (
|
||||
qb.from_(gl)
|
||||
.select(gl.account_currency)
|
||||
.where(
|
||||
(gl.docstatus == 1)
|
||||
& (gl.company == company)
|
||||
& (gl.party_type == party_type)
|
||||
& (gl.party == party)
|
||||
& (gl.is_cancelled == 0)
|
||||
)
|
||||
.limit(1)
|
||||
.run()
|
||||
existing_gle_currency = frappe.db.sql(
|
||||
"""select account_currency from `tabGL Entry`
|
||||
where docstatus=1 and company=%(company)s and party_type=%(party_type)s and party=%(party)s
|
||||
limit 1""",
|
||||
{"company": company, "party_type": party_type, "party": party},
|
||||
)
|
||||
|
||||
return existing_gle_currency[0][0] if existing_gle_currency else None
|
||||
@@ -995,13 +957,3 @@ def add_party_account(party_type, party, company, account):
|
||||
doc.append("accounts", accounts)
|
||||
|
||||
doc.save()
|
||||
|
||||
|
||||
def render_address(address, check_permissions=True):
|
||||
try:
|
||||
from frappe.contacts.doctype.address.address import render_address as _render
|
||||
except ImportError:
|
||||
# Older frappe versions where this function is not available
|
||||
from frappe.contacts.doctype.address.address import get_address_display as _render
|
||||
|
||||
return frappe.call(_render, address, check_permissions=check_permissions)
|
||||
|
||||
@@ -95,27 +95,30 @@ frappe.query_reports["Accounts Payable"] = {
|
||||
"options": "Payment Terms Template"
|
||||
},
|
||||
{
|
||||
"fieldname":"party_type",
|
||||
"fieldname": "party_type",
|
||||
"label": __("Party Type"),
|
||||
"fieldtype": "Autocomplete",
|
||||
options: get_party_type_options(),
|
||||
on_change: function() {
|
||||
"fieldtype": "Link",
|
||||
"options": "Party Type",
|
||||
get_query: () => {
|
||||
return {
|
||||
filters: {
|
||||
'account_type': 'Payable'
|
||||
}
|
||||
};
|
||||
},
|
||||
on_change: () => {
|
||||
frappe.query_report.set_filter_value('party', "");
|
||||
let party_type = frappe.query_report.get_filter_value('party_type');
|
||||
frappe.query_report.toggle_filter_display('supplier_group', frappe.query_report.get_filter_value('party_type') !== "Supplier");
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
{
|
||||
"fieldname":"party",
|
||||
"label": __("Party"),
|
||||
"fieldtype": "MultiSelectList",
|
||||
get_data: function(txt) {
|
||||
if (!frappe.query_report.filters) return;
|
||||
|
||||
let party_type = frappe.query_report.get_filter_value('party_type');
|
||||
if (!party_type) return;
|
||||
|
||||
return frappe.db.get_link_options(party_type, txt);
|
||||
},
|
||||
"fieldtype": "Dynamic Link",
|
||||
"options": "party_type",
|
||||
},
|
||||
{
|
||||
"fieldname": "supplier_group",
|
||||
@@ -143,13 +146,7 @@ frappe.query_reports["Accounts Payable"] = {
|
||||
"fieldname": "show_future_payments",
|
||||
"label": __("Show Future Payments"),
|
||||
"fieldtype": "Check",
|
||||
},
|
||||
{
|
||||
"fieldname": "ignore_accounts",
|
||||
"label": __("Group by Voucher"),
|
||||
"fieldtype": "Check",
|
||||
}
|
||||
|
||||
],
|
||||
|
||||
"formatter": function(value, row, column, data, default_formatter) {
|
||||
@@ -170,15 +167,3 @@ frappe.query_reports["Accounts Payable"] = {
|
||||
}
|
||||
|
||||
erpnext.utils.add_dimensions('Accounts Payable', 9);
|
||||
|
||||
function get_party_type_options() {
|
||||
let options = [];
|
||||
frappe.db.get_list(
|
||||
"Party Type", {filters:{"account_type": "Payable"}, fields:['name']}
|
||||
).then((res) => {
|
||||
res.forEach((party_type) => {
|
||||
options.push(party_type.name);
|
||||
});
|
||||
});
|
||||
return options;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
||||
def tearDown(self):
|
||||
frappe.db.rollback()
|
||||
|
||||
def test_accounts_payable_for_foreign_currency_supplier(self):
|
||||
def test_accounts_receivable_with_supplier(self):
|
||||
pi = self.create_purchase_invoice(do_not_submit=True)
|
||||
pi.currency = "USD"
|
||||
pi.conversion_rate = 80
|
||||
@@ -34,7 +34,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
||||
filters = {
|
||||
"company": self.company,
|
||||
"party_type": "Supplier",
|
||||
"party": [self.supplier],
|
||||
"party": self.supplier,
|
||||
"report_date": today(),
|
||||
"range1": 30,
|
||||
"range2": 60,
|
||||
|
||||
@@ -72,27 +72,10 @@ frappe.query_reports["Accounts Payable Summary"] = {
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname":"party_type",
|
||||
"label": __("Party Type"),
|
||||
"fieldtype": "Autocomplete",
|
||||
options: get_party_type_options(),
|
||||
on_change: function() {
|
||||
frappe.query_report.set_filter_value('party', "");
|
||||
frappe.query_report.toggle_filter_display('supplier_group', frappe.query_report.get_filter_value('party_type') !== "Supplier");
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname":"party",
|
||||
"label": __("Party"),
|
||||
"fieldtype": "MultiSelectList",
|
||||
get_data: function(txt) {
|
||||
if (!frappe.query_report.filters) return;
|
||||
|
||||
let party_type = frappe.query_report.get_filter_value('party_type');
|
||||
if (!party_type) return;
|
||||
|
||||
return frappe.db.get_link_options(party_type, txt);
|
||||
},
|
||||
"fieldname":"supplier",
|
||||
"label": __("Supplier"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Supplier"
|
||||
},
|
||||
{
|
||||
"fieldname":"payment_terms_template",
|
||||
@@ -110,11 +93,6 @@ frappe.query_reports["Accounts Payable Summary"] = {
|
||||
"fieldname":"based_on_payment_terms",
|
||||
"label": __("Based On Payment Terms"),
|
||||
"fieldtype": "Check",
|
||||
},
|
||||
{
|
||||
"fieldname": "for_revaluation_journals",
|
||||
"label": __("Revaluation Journals"),
|
||||
"fieldtype": "Check",
|
||||
}
|
||||
],
|
||||
|
||||
@@ -127,15 +105,3 @@ frappe.query_reports["Accounts Payable Summary"] = {
|
||||
}
|
||||
|
||||
erpnext.utils.add_dimensions('Accounts Payable Summary', 9);
|
||||
|
||||
function get_party_type_options() {
|
||||
let options = [];
|
||||
frappe.db.get_list(
|
||||
"Party Type", {filters:{"account_type": "Payable"}, fields:['name']}
|
||||
).then((res) => {
|
||||
res.forEach((party_type) => {
|
||||
options.push(party_type.name);
|
||||
});
|
||||
});
|
||||
return options;
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
frappe.provide("erpnext.utils");
|
||||
|
||||
frappe.query_reports["Accounts Receivable"] = {
|
||||
"filters": [
|
||||
{
|
||||
@@ -40,28 +38,32 @@ frappe.query_reports["Accounts Receivable"] = {
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname":"party_type",
|
||||
"label": __("Party Type"),
|
||||
"fieldtype": "Autocomplete",
|
||||
options: get_party_type_options(),
|
||||
on_change: function() {
|
||||
frappe.query_report.set_filter_value('party', "");
|
||||
frappe.query_report.toggle_filter_display('customer_group', frappe.query_report.get_filter_value('party_type') !== "Customer");
|
||||
"fieldname": "customer",
|
||||
"label": __("Customer"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Customer",
|
||||
on_change: () => {
|
||||
var customer = frappe.query_report.get_filter_value('customer');
|
||||
var company = frappe.query_report.get_filter_value('company');
|
||||
if (customer) {
|
||||
frappe.db.get_value('Customer', customer, ["customer_name", "payment_terms"], function(value) {
|
||||
frappe.query_report.set_filter_value('customer_name', value["customer_name"]);
|
||||
frappe.query_report.set_filter_value('payment_terms', value["payment_terms"]);
|
||||
});
|
||||
|
||||
frappe.db.get_value('Customer Credit Limit', {'parent': customer, 'company': company},
|
||||
["credit_limit"], function(value) {
|
||||
if (value) {
|
||||
frappe.query_report.set_filter_value('credit_limit', value["credit_limit"]);
|
||||
}
|
||||
}, "Customer");
|
||||
} else {
|
||||
frappe.query_report.set_filter_value('customer_name', "");
|
||||
frappe.query_report.set_filter_value('credit_limit', "");
|
||||
frappe.query_report.set_filter_value('payment_terms', "");
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname":"party",
|
||||
"label": __("Party"),
|
||||
"fieldtype": "MultiSelectList",
|
||||
get_data: function(txt) {
|
||||
if (!frappe.query_report.filters) return;
|
||||
|
||||
let party_type = frappe.query_report.get_filter_value('party_type');
|
||||
if (!party_type) return;
|
||||
|
||||
return frappe.db.get_link_options(party_type, txt);
|
||||
},
|
||||
},
|
||||
{
|
||||
"fieldname": "party_account",
|
||||
"label": __("Receivable Account"),
|
||||
@@ -114,13 +116,10 @@ frappe.query_reports["Accounts Receivable"] = {
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"customer_group",
|
||||
"fieldname": "customer_group",
|
||||
"label": __("Customer Group"),
|
||||
"fieldtype": "MultiSelectList",
|
||||
"options": "Customer Group",
|
||||
get_data: function(txt) {
|
||||
return frappe.db.get_link_options('Customer Group', txt);
|
||||
}
|
||||
"fieldtype": "Link",
|
||||
"options": "Customer Group"
|
||||
},
|
||||
{
|
||||
"fieldname": "payment_terms_template",
|
||||
@@ -177,17 +176,23 @@ frappe.query_reports["Accounts Receivable"] = {
|
||||
"fieldtype": "Check",
|
||||
},
|
||||
{
|
||||
"fieldname": "for_revaluation_journals",
|
||||
"label": __("Revaluation Journals"),
|
||||
"fieldtype": "Check",
|
||||
"fieldname": "customer_name",
|
||||
"label": __("Customer Name"),
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "ignore_accounts",
|
||||
"label": __("Group by Voucher"),
|
||||
"fieldtype": "Check",
|
||||
"fieldname": "payment_terms",
|
||||
"label": __("Payment Tems"),
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "credit_limit",
|
||||
"label": __("Credit Limit"),
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 1
|
||||
}
|
||||
|
||||
|
||||
],
|
||||
|
||||
"formatter": function(value, row, column, data, default_formatter) {
|
||||
@@ -208,16 +213,3 @@ frappe.query_reports["Accounts Receivable"] = {
|
||||
}
|
||||
|
||||
erpnext.utils.add_dimensions('Accounts Receivable', 9);
|
||||
|
||||
|
||||
function get_party_type_options() {
|
||||
let options = [];
|
||||
frappe.db.get_list(
|
||||
"Party Type", {filters:{"account_type": "Receivable"}, fields:['name']}
|
||||
).then((res) => {
|
||||
res.forEach((party_type) => {
|
||||
options.push(party_type.name);
|
||||
});
|
||||
});
|
||||
return options;
|
||||
}
|
||||
|
||||
@@ -116,12 +116,7 @@ class ReceivablePayableReport(object):
|
||||
# build all keys, since we want to exclude vouchers beyond the report date
|
||||
for ple in self.ple_entries:
|
||||
# get the balance object for voucher_type
|
||||
|
||||
if self.filters.get("ignore_accounts"):
|
||||
key = (ple.voucher_type, ple.voucher_no, ple.party)
|
||||
else:
|
||||
key = (ple.account, ple.voucher_type, ple.voucher_no, ple.party)
|
||||
|
||||
key = (ple.voucher_type, ple.voucher_no, ple.party)
|
||||
if not key in self.voucher_balance:
|
||||
self.voucher_balance[key] = frappe._dict(
|
||||
voucher_type=ple.voucher_type,
|
||||
@@ -188,10 +183,7 @@ class ReceivablePayableReport(object):
|
||||
):
|
||||
return
|
||||
|
||||
if self.filters.get("ignore_accounts"):
|
||||
key = (ple.against_voucher_type, ple.against_voucher_no, ple.party)
|
||||
else:
|
||||
key = (ple.account, ple.against_voucher_type, ple.against_voucher_no, ple.party)
|
||||
key = (ple.against_voucher_type, ple.against_voucher_no, ple.party)
|
||||
|
||||
# If payment is made against credit note
|
||||
# and credit note is made against a Sales Invoice
|
||||
@@ -200,19 +192,13 @@ class ReceivablePayableReport(object):
|
||||
if ple.against_voucher_no in self.return_entries:
|
||||
return_against = self.return_entries.get(ple.against_voucher_no)
|
||||
if return_against:
|
||||
if self.filters.get("ignore_accounts"):
|
||||
key = (ple.against_voucher_type, return_against, ple.party)
|
||||
else:
|
||||
key = (ple.account, ple.against_voucher_type, return_against, ple.party)
|
||||
key = (ple.against_voucher_type, return_against, ple.party)
|
||||
|
||||
row = self.voucher_balance.get(key)
|
||||
|
||||
if not row:
|
||||
# no invoice, this is an invoice / stand-alone payment / credit note
|
||||
if self.filters.get("ignore_accounts"):
|
||||
row = self.voucher_balance.get((ple.voucher_type, ple.voucher_no, ple.party))
|
||||
else:
|
||||
row = self.voucher_balance.get((ple.account, ple.voucher_type, ple.voucher_no, ple.party))
|
||||
row = self.voucher_balance.get((ple.voucher_type, ple.voucher_no, ple.party))
|
||||
|
||||
row.party_type = ple.party_type
|
||||
return row
|
||||
@@ -281,20 +267,11 @@ class ReceivablePayableReport(object):
|
||||
|
||||
row.invoice_grand_total = row.invoiced
|
||||
|
||||
must_consider = False
|
||||
if self.filters.get("for_revaluation_journals"):
|
||||
if (abs(row.outstanding) > 1.0 / 10**self.currency_precision) or (
|
||||
(abs(row.outstanding_in_account_currency) > 1.0 / 10**self.currency_precision)
|
||||
):
|
||||
must_consider = True
|
||||
else:
|
||||
if (abs(row.outstanding) > 1.0 / 10**self.currency_precision) and (
|
||||
(abs(row.outstanding_in_account_currency) > 1.0 / 10**self.currency_precision)
|
||||
or (row.voucher_no in self.err_journals)
|
||||
):
|
||||
must_consider = True
|
||||
if (abs(row.outstanding) > 1.0 / 10**self.currency_precision) and (
|
||||
(abs(row.outstanding_in_account_currency) > 1.0 / 10**self.currency_precision)
|
||||
or (row.voucher_no in self.err_journals)
|
||||
):
|
||||
|
||||
if must_consider:
|
||||
# non-zero oustanding, we must consider this row
|
||||
|
||||
if self.is_invoice(row) and self.filters.based_on_payment_terms:
|
||||
@@ -741,7 +718,6 @@ class ReceivablePayableReport(object):
|
||||
query = (
|
||||
qb.from_(ple)
|
||||
.select(
|
||||
ple.name,
|
||||
ple.account,
|
||||
ple.voucher_type,
|
||||
ple.voucher_no,
|
||||
@@ -755,15 +731,13 @@ class ReceivablePayableReport(object):
|
||||
ple.account_currency,
|
||||
ple.amount,
|
||||
ple.amount_in_account_currency,
|
||||
ple.remarks,
|
||||
)
|
||||
.where(ple.delinked == 0)
|
||||
.where(Criterion.all(self.qb_selection_filter))
|
||||
.where(Criterion.any(self.or_filters))
|
||||
)
|
||||
|
||||
if self.filters.get("show_remarks"):
|
||||
query = query.select(ple.remarks)
|
||||
|
||||
if self.filters.get("group_by_party"):
|
||||
query = query.orderby(self.ple.party, self.ple.posting_date)
|
||||
else:
|
||||
@@ -795,12 +769,15 @@ class ReceivablePayableReport(object):
|
||||
self.or_filters = []
|
||||
|
||||
for party_type in self.party_type:
|
||||
self.add_common_filters()
|
||||
party_type_field = scrub(party_type)
|
||||
self.or_filters.append(self.ple.party_type == party_type)
|
||||
|
||||
if self.account_type == "Receivable":
|
||||
self.add_common_filters(party_type_field=party_type_field)
|
||||
|
||||
if party_type_field == "customer":
|
||||
self.add_customer_filters()
|
||||
|
||||
elif self.account_type == "Payable":
|
||||
elif party_type_field == "supplier":
|
||||
self.add_supplier_filters()
|
||||
|
||||
if self.filters.cost_center:
|
||||
@@ -816,18 +793,21 @@ class ReceivablePayableReport(object):
|
||||
]
|
||||
self.qb_selection_filter.append(self.ple.cost_center.isin(cost_center_list))
|
||||
|
||||
def add_common_filters(self):
|
||||
def add_common_filters(self, party_type_field):
|
||||
if self.filters.company:
|
||||
self.qb_selection_filter.append(self.ple.company == self.filters.company)
|
||||
|
||||
if self.filters.finance_book:
|
||||
self.qb_selection_filter.append(self.ple.finance_book == self.filters.finance_book)
|
||||
|
||||
if self.filters.get(party_type_field):
|
||||
self.qb_selection_filter.append(self.ple.party == self.filters.get(party_type_field))
|
||||
|
||||
if self.filters.get("party_type"):
|
||||
self.qb_selection_filter.append(self.filters.party_type == self.ple.party_type)
|
||||
|
||||
if self.filters.get("party"):
|
||||
self.qb_selection_filter.append(self.ple.party.isin(self.filters.party))
|
||||
self.qb_selection_filter.append(self.filters.party == self.ple.party)
|
||||
|
||||
if self.filters.party_account:
|
||||
self.qb_selection_filter.append(self.ple.account == self.filters.party_account)
|
||||
@@ -849,13 +829,7 @@ class ReceivablePayableReport(object):
|
||||
self.customer = qb.DocType("Customer")
|
||||
|
||||
if self.filters.get("customer_group"):
|
||||
groups = get_customer_group_with_children(self.filters.customer_group)
|
||||
customers = (
|
||||
qb.from_(self.customer)
|
||||
.select(self.customer.name)
|
||||
.where(self.customer["customer_group"].isin(groups))
|
||||
)
|
||||
self.qb_selection_filter.append(self.ple.party.isin(customers))
|
||||
self.get_hierarchical_filters("Customer Group", "customer_group")
|
||||
|
||||
if self.filters.get("territory"):
|
||||
self.get_hierarchical_filters("Territory", "territory")
|
||||
@@ -995,20 +969,6 @@ class ReceivablePayableReport(object):
|
||||
fieldtype="Link",
|
||||
options="Contact",
|
||||
)
|
||||
if self.filters.party_type == "Customer":
|
||||
self.add_column(
|
||||
_("Customer Name"),
|
||||
fieldname="customer_name",
|
||||
fieldtype="Link",
|
||||
options="Customer",
|
||||
)
|
||||
elif self.filters.party_type == "Supplier":
|
||||
self.add_column(
|
||||
_("Supplier Name"),
|
||||
fieldname="supplier_name",
|
||||
fieldtype="Link",
|
||||
options="Supplier",
|
||||
)
|
||||
|
||||
self.add_column(label=_("Cost Center"), fieldname="cost_center", fieldtype="Data")
|
||||
self.add_column(label=_("Voucher Type"), fieldname="voucher_type", fieldtype="Data")
|
||||
@@ -1147,19 +1107,3 @@ class ReceivablePayableReport(object):
|
||||
.run()
|
||||
)
|
||||
self.err_journals = [x[0] for x in results] if results else []
|
||||
|
||||
|
||||
def get_customer_group_with_children(customer_groups):
|
||||
if not isinstance(customer_groups, list):
|
||||
customer_groups = [d.strip() for d in customer_groups.strip().split(",") if d]
|
||||
|
||||
all_customer_groups = []
|
||||
for d in customer_groups:
|
||||
if frappe.db.exists("Customer Group", d):
|
||||
lft, rgt = frappe.db.get_value("Customer Group", d, ["lft", "rgt"])
|
||||
children = frappe.get_all("Customer Group", filters={"lft": [">=", lft], "rgt": ["<=", rgt]})
|
||||
all_customer_groups += [c.name for c in children]
|
||||
else:
|
||||
frappe.throw(_("Customer Group: {0} does not exist").format(d))
|
||||
|
||||
return list(set(all_customer_groups))
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe import qb
|
||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||
from frappe.utils import add_days, flt, getdate, today
|
||||
|
||||
@@ -24,6 +23,29 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
||||
def tearDown(self):
|
||||
frappe.db.rollback()
|
||||
|
||||
def create_usd_account(self):
|
||||
name = "Debtors USD"
|
||||
exists = frappe.db.get_list(
|
||||
"Account", filters={"company": "_Test Company 2", "account_name": "Debtors USD"}
|
||||
)
|
||||
if exists:
|
||||
self.debtors_usd = exists[0].name
|
||||
else:
|
||||
debtors = frappe.get_doc(
|
||||
"Account",
|
||||
frappe.db.get_list(
|
||||
"Account", filters={"company": "_Test Company 2", "account_name": "Debtors"}
|
||||
)[0].name,
|
||||
)
|
||||
|
||||
debtors_usd = frappe.new_doc("Account")
|
||||
debtors_usd.company = debtors.company
|
||||
debtors_usd.account_name = "Debtors USD"
|
||||
debtors_usd.account_currency = "USD"
|
||||
debtors_usd.parent_account = debtors.parent_account
|
||||
debtors_usd.account_type = debtors.account_type
|
||||
self.debtors_usd = debtors_usd.save().name
|
||||
|
||||
def create_sales_invoice(self, no_payment_schedule=False, do_not_submit=False):
|
||||
frappe.set_user("Administrator")
|
||||
si = create_sales_invoice(
|
||||
@@ -475,30 +497,6 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
||||
report = execute(filters)[1]
|
||||
self.assertEqual(len(report), 0)
|
||||
|
||||
def test_multi_customer_group_filter(self):
|
||||
si = self.create_sales_invoice()
|
||||
cus_group = frappe.db.get_value("Customer", self.customer, "customer_group")
|
||||
# Create a list of customer groups, e.g., ["Group1", "Group2"]
|
||||
cus_groups_list = [cus_group, "_Test Customer Group 1"]
|
||||
|
||||
filters = {
|
||||
"company": self.company,
|
||||
"report_date": today(),
|
||||
"range1": 30,
|
||||
"range2": 60,
|
||||
"range3": 90,
|
||||
"range4": 120,
|
||||
"customer_group": cus_groups_list, # Use the list of customer groups
|
||||
}
|
||||
report = execute(filters)[1]
|
||||
|
||||
# Assert that the report contains data for the specified customer groups
|
||||
self.assertTrue(len(report) > 0)
|
||||
|
||||
for row in report:
|
||||
# Assert that the customer group of each row is in the list of customer groups
|
||||
self.assertIn(row.customer_group, cus_groups_list)
|
||||
|
||||
def test_party_account_filter(self):
|
||||
si1 = self.create_sales_invoice()
|
||||
self.customer2 = (
|
||||
@@ -570,169 +568,3 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
||||
row.account_currency,
|
||||
],
|
||||
)
|
||||
|
||||
def test_usd_customer_filter(self):
|
||||
filters = {
|
||||
"company": self.company,
|
||||
"party_type": "Customer",
|
||||
"party": [self.customer],
|
||||
"report_date": today(),
|
||||
"range1": 30,
|
||||
"range2": 60,
|
||||
"range3": 90,
|
||||
"range4": 120,
|
||||
}
|
||||
|
||||
si = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True)
|
||||
si.currency = "USD"
|
||||
si.conversion_rate = 80
|
||||
si.debit_to = self.debtors_usd
|
||||
si.save().submit()
|
||||
name = si.name
|
||||
|
||||
# check invoice grand total and invoiced column's value for 3 payment terms
|
||||
report = execute(filters)
|
||||
|
||||
expected = {
|
||||
"voucher_type": si.doctype,
|
||||
"voucher_no": si.name,
|
||||
"party_account": self.debtors_usd,
|
||||
"customer_name": self.customer,
|
||||
"invoiced": 100.0,
|
||||
"outstanding": 100.0,
|
||||
"account_currency": "USD",
|
||||
}
|
||||
self.assertEqual(len(report[1]), 1)
|
||||
report_output = report[1][0]
|
||||
for field in expected:
|
||||
with self.subTest(field=field):
|
||||
self.assertEqual(report_output.get(field), expected.get(field))
|
||||
|
||||
def test_multi_select_party_filter(self):
|
||||
self.customer1 = self.customer
|
||||
self.create_customer("_Test Customer 2")
|
||||
self.customer2 = self.customer
|
||||
self.create_customer("_Test Customer 3")
|
||||
self.customer3 = self.customer
|
||||
|
||||
filters = {
|
||||
"company": self.company,
|
||||
"party_type": "Customer",
|
||||
"party": [self.customer1, self.customer3],
|
||||
"report_date": today(),
|
||||
"range1": 30,
|
||||
"range2": 60,
|
||||
"range3": 90,
|
||||
"range4": 120,
|
||||
}
|
||||
|
||||
si1 = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True)
|
||||
si1.customer = self.customer1
|
||||
si1.save().submit()
|
||||
|
||||
si2 = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True)
|
||||
si2.customer = self.customer2
|
||||
si2.save().submit()
|
||||
|
||||
si3 = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True)
|
||||
si3.customer = self.customer3
|
||||
si3.save().submit()
|
||||
|
||||
# check invoice grand total and invoiced column's value for 3 payment terms
|
||||
report = execute(filters)
|
||||
|
||||
expected_output = {self.customer1, self.customer3}
|
||||
self.assertEqual(len(report[1]), 2)
|
||||
output_for = set([x.party for x in report[1]])
|
||||
self.assertEqual(output_for, expected_output)
|
||||
|
||||
def test_report_output_if_party_is_missing(self):
|
||||
acc_name = "Additional Debtors"
|
||||
if not frappe.db.get_value(
|
||||
"Account", filters={"account_name": acc_name, "company": self.company}
|
||||
):
|
||||
additional_receivable_acc = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Account",
|
||||
"account_name": acc_name,
|
||||
"parent_account": "Accounts Receivable - " + self.company_abbr,
|
||||
"company": self.company,
|
||||
"account_type": "Receivable",
|
||||
}
|
||||
).save()
|
||||
self.debtors2 = additional_receivable_acc.name
|
||||
|
||||
je = frappe.new_doc("Journal Entry")
|
||||
je.company = self.company
|
||||
je.posting_date = today()
|
||||
je.append(
|
||||
"accounts",
|
||||
{
|
||||
"account": self.debit_to,
|
||||
"party_type": "Customer",
|
||||
"party": self.customer,
|
||||
"debit_in_account_currency": 150,
|
||||
"credit_in_account_currency": 0,
|
||||
"cost_center": self.cost_center,
|
||||
},
|
||||
)
|
||||
je.append(
|
||||
"accounts",
|
||||
{
|
||||
"account": self.debtors2,
|
||||
"party_type": "Customer",
|
||||
"party": self.customer,
|
||||
"debit_in_account_currency": 200,
|
||||
"credit_in_account_currency": 0,
|
||||
"cost_center": self.cost_center,
|
||||
},
|
||||
)
|
||||
je.append(
|
||||
"accounts",
|
||||
{
|
||||
"account": self.cash,
|
||||
"debit_in_account_currency": 0,
|
||||
"credit_in_account_currency": 350,
|
||||
"cost_center": self.cost_center,
|
||||
},
|
||||
)
|
||||
je.save().submit()
|
||||
|
||||
# manually remove party from Payment Ledger
|
||||
ple = qb.DocType("Payment Ledger Entry")
|
||||
qb.update(ple).set(ple.party, None).where(ple.voucher_no == je.name).run()
|
||||
|
||||
filters = {
|
||||
"company": self.company,
|
||||
"report_date": today(),
|
||||
"range1": 30,
|
||||
"range2": 60,
|
||||
"range3": 90,
|
||||
"range4": 120,
|
||||
}
|
||||
|
||||
report_ouput = execute(filters)[1]
|
||||
expected_data = [
|
||||
[self.debtors2, je.doctype, je.name, "Customer", self.customer, 200.0, 0.0, 0.0, 200.0],
|
||||
[self.debit_to, je.doctype, je.name, "Customer", self.customer, 150.0, 0.0, 0.0, 150.0],
|
||||
]
|
||||
self.assertEqual(len(report_ouput), 2)
|
||||
# fetch only required fields
|
||||
report_output = [
|
||||
[
|
||||
x.party_account,
|
||||
x.voucher_type,
|
||||
x.voucher_no,
|
||||
"Customer",
|
||||
self.customer,
|
||||
x.invoiced,
|
||||
x.paid,
|
||||
x.credit_note,
|
||||
x.outstanding,
|
||||
]
|
||||
for x in report_ouput
|
||||
]
|
||||
# use account name to sort
|
||||
# post sorting output should be [[Additional Debtors, ...], [Debtors, ...]]
|
||||
report_output = sorted(report_output, key=lambda x: x[0])
|
||||
self.assertEqual(expected_data, report_output)
|
||||
|
||||
@@ -72,27 +72,10 @@ frappe.query_reports["Accounts Receivable Summary"] = {
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname":"party_type",
|
||||
"label": __("Party Type"),
|
||||
"fieldtype": "Autocomplete",
|
||||
options: get_party_type_options(),
|
||||
on_change: function() {
|
||||
frappe.query_report.set_filter_value('party', "");
|
||||
frappe.query_report.toggle_filter_display('customer_group', frappe.query_report.get_filter_value('party_type') !== "Customer");
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname":"party",
|
||||
"label": __("Party"),
|
||||
"fieldtype": "MultiSelectList",
|
||||
get_data: function(txt) {
|
||||
if (!frappe.query_report.filters) return;
|
||||
|
||||
let party_type = frappe.query_report.get_filter_value('party_type');
|
||||
if (!party_type) return;
|
||||
|
||||
return frappe.db.get_link_options(party_type, txt);
|
||||
},
|
||||
"fieldname":"customer",
|
||||
"label": __("Customer"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Customer"
|
||||
},
|
||||
{
|
||||
"fieldname":"customer_group",
|
||||
@@ -139,11 +122,6 @@ frappe.query_reports["Accounts Receivable Summary"] = {
|
||||
"label": __("Show GL Balance"),
|
||||
"fieldtype": "Check",
|
||||
},
|
||||
{
|
||||
"fieldname": "for_revaluation_journals",
|
||||
"label": __("Revaluation Journals"),
|
||||
"fieldtype": "Check",
|
||||
}
|
||||
],
|
||||
|
||||
onload: function(report) {
|
||||
@@ -155,15 +133,3 @@ frappe.query_reports["Accounts Receivable Summary"] = {
|
||||
}
|
||||
|
||||
erpnext.utils.add_dimensions('Accounts Receivable Summary', 9);
|
||||
|
||||
function get_party_type_options() {
|
||||
let options = [];
|
||||
frappe.db.get_list(
|
||||
"Party Type", {filters:{"account_type": "Receivable"}, fields:['name']}
|
||||
).then((res) => {
|
||||
res.forEach((party_type) => {
|
||||
options.push(party_type.name);
|
||||
});
|
||||
});
|
||||
return options;
|
||||
}
|
||||
@@ -99,11 +99,13 @@ class AccountsReceivableSummary(ReceivablePayableReport):
|
||||
|
||||
# Add all amount columns
|
||||
for k in list(self.party_total[d.party]):
|
||||
if isinstance(self.party_total[d.party][k], float):
|
||||
self.party_total[d.party][k] += d.get(k) or 0.0
|
||||
if k not in ["currency", "sales_person"]:
|
||||
|
||||
self.party_total[d.party][k] += d.get(k, 0.0)
|
||||
|
||||
# set territory, customer_group, sales person etc
|
||||
self.set_party_details(d)
|
||||
self.party_total[d.party].update({"party_type": d.party_type})
|
||||
|
||||
def init_party_total(self, row):
|
||||
self.party_total.setdefault(
|
||||
@@ -122,7 +124,6 @@ class AccountsReceivableSummary(ReceivablePayableReport):
|
||||
"total_due": 0.0,
|
||||
"future_amount": 0.0,
|
||||
"sales_person": [],
|
||||
"party_type": row.party_type,
|
||||
}
|
||||
),
|
||||
)
|
||||
@@ -132,12 +133,13 @@ class AccountsReceivableSummary(ReceivablePayableReport):
|
||||
|
||||
for key in ("territory", "customer_group", "supplier_group"):
|
||||
if row.get(key):
|
||||
self.party_total[row.party][key] = row.get(key, "")
|
||||
self.party_total[row.party][key] = row.get(key)
|
||||
|
||||
if row.sales_person:
|
||||
self.party_total[row.party].sales_person.append(row.get("sales_person", ""))
|
||||
self.party_total[row.party].sales_person.append(row.sales_person)
|
||||
|
||||
if self.filters.sales_partner:
|
||||
self.party_total[row.party]["default_sales_partner"] = row.get("default_sales_partner", "")
|
||||
self.party_total[row.party]["default_sales_partner"] = row.get("default_sales_partner")
|
||||
|
||||
def get_columns(self):
|
||||
self.columns = []
|
||||
|
||||
@@ -31,18 +31,6 @@ frappe.query_reports["Asset Depreciation Ledger"] = {
|
||||
"fieldtype": "Link",
|
||||
"options": "Asset"
|
||||
},
|
||||
{
|
||||
"fieldname":"asset_category",
|
||||
"label": __("Asset Category"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Asset Category"
|
||||
},
|
||||
{
|
||||
"fieldname":"cost_center",
|
||||
"label": __("Cost Center"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Cost Center"
|
||||
},
|
||||
{
|
||||
"fieldname":"finance_book",
|
||||
"label": __("Finance Book"),
|
||||
@@ -50,10 +38,10 @@ frappe.query_reports["Asset Depreciation Ledger"] = {
|
||||
"options": "Finance Book"
|
||||
},
|
||||
{
|
||||
"fieldname": "include_default_book_assets",
|
||||
"label": __("Include Default FB Assets"),
|
||||
"fieldtype": "Check",
|
||||
"default": 1
|
||||
},
|
||||
"fieldname":"asset_category",
|
||||
"label": __("Asset Category"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Asset Category"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"add_total_row": 0,
|
||||
"add_total_row": 1,
|
||||
"columns": [],
|
||||
"creation": "2016-04-08 14:49:58.133098",
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"filters": [],
|
||||
"idx": 6,
|
||||
"idx": 2,
|
||||
"is_standard": "Yes",
|
||||
"letterhead": null,
|
||||
"modified": "2023-11-08 20:17:05.774211",
|
||||
"modified": "2023-07-26 21:05:33.554778",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Asset Depreciation Ledger",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import cstr, flt
|
||||
from frappe.utils import flt
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
@@ -32,6 +32,7 @@ def get_data(filters):
|
||||
filters_data.append(["against_voucher", "=", filters.get("asset")])
|
||||
|
||||
if filters.get("asset_category"):
|
||||
|
||||
assets = frappe.db.sql_list(
|
||||
"""select name from tabAsset
|
||||
where asset_category = %s and docstatus=1""",
|
||||
@@ -40,27 +41,12 @@ def get_data(filters):
|
||||
|
||||
filters_data.append(["against_voucher", "in", assets])
|
||||
|
||||
company_fb = frappe.get_cached_value("Company", filters.get("company"), "default_finance_book")
|
||||
|
||||
if filters.get("include_default_book_assets") and company_fb:
|
||||
if filters.get("finance_book") and cstr(filters.get("finance_book")) != cstr(company_fb):
|
||||
frappe.throw(_("To use a different finance book, please uncheck 'Include Default FB Assets'"))
|
||||
else:
|
||||
finance_book = company_fb
|
||||
elif filters.get("finance_book"):
|
||||
finance_book = filters.get("finance_book")
|
||||
else:
|
||||
finance_book = None
|
||||
|
||||
if finance_book:
|
||||
or_filters_data = [["finance_book", "in", ["", finance_book]], ["finance_book", "is", "not set"]]
|
||||
else:
|
||||
or_filters_data = [["finance_book", "in", [""]], ["finance_book", "is", "not set"]]
|
||||
if filters.get("finance_book"):
|
||||
filters_data.append(["finance_book", "in", ["", filters.get("finance_book")]])
|
||||
|
||||
gl_entries = frappe.get_all(
|
||||
"GL Entry",
|
||||
filters=filters_data,
|
||||
or_filters=or_filters_data,
|
||||
fields=["against_voucher", "debit_in_account_currency as debit", "voucher_no", "posting_date"],
|
||||
order_by="against_voucher, posting_date",
|
||||
)
|
||||
@@ -75,9 +61,7 @@ def get_data(filters):
|
||||
asset_data = assets_details.get(d.against_voucher)
|
||||
if asset_data:
|
||||
if not asset_data.get("accumulated_depreciation_amount"):
|
||||
asset_data.accumulated_depreciation_amount = d.debit + asset_data.get(
|
||||
"opening_accumulated_depreciation"
|
||||
)
|
||||
asset_data.accumulated_depreciation_amount = d.debit
|
||||
else:
|
||||
asset_data.accumulated_depreciation_amount += d.debit
|
||||
|
||||
@@ -86,7 +70,7 @@ def get_data(filters):
|
||||
{
|
||||
"depreciation_amount": d.debit,
|
||||
"depreciation_date": d.posting_date,
|
||||
"value_after_depreciation": (
|
||||
"amount_after_depreciation": (
|
||||
flt(row.gross_purchase_amount) - flt(row.accumulated_depreciation_amount)
|
||||
),
|
||||
"depreciation_entry": d.voucher_no,
|
||||
@@ -104,12 +88,10 @@ def get_assets_details(assets):
|
||||
fields = [
|
||||
"name as asset",
|
||||
"gross_purchase_amount",
|
||||
"opening_accumulated_depreciation",
|
||||
"asset_category",
|
||||
"status",
|
||||
"depreciation_method",
|
||||
"purchase_date",
|
||||
"cost_center",
|
||||
]
|
||||
|
||||
for d in frappe.get_all("Asset", fields=fields, filters={"name": ("in", assets)}):
|
||||
@@ -139,12 +121,6 @@ def get_columns():
|
||||
"fieldtype": "Currency",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Opening Accumulated Depreciation"),
|
||||
"fieldname": "opening_accumulated_depreciation",
|
||||
"fieldtype": "Currency",
|
||||
"width": 140,
|
||||
},
|
||||
{
|
||||
"label": _("Depreciation Amount"),
|
||||
"fieldname": "depreciation_amount",
|
||||
@@ -158,8 +134,8 @@ def get_columns():
|
||||
"width": 210,
|
||||
},
|
||||
{
|
||||
"label": _("Value After Depreciation"),
|
||||
"fieldname": "value_after_depreciation",
|
||||
"label": _("Amount After Depreciation"),
|
||||
"fieldname": "amount_after_depreciation",
|
||||
"fieldtype": "Currency",
|
||||
"width": 180,
|
||||
},
|
||||
@@ -177,13 +153,12 @@ def get_columns():
|
||||
"options": "Asset Category",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Cost Center"),
|
||||
"fieldtype": "Link",
|
||||
"fieldname": "cost_center",
|
||||
"options": "Cost Center",
|
||||
"width": 100,
|
||||
},
|
||||
{"label": _("Current Status"), "fieldname": "status", "fieldtype": "Data", "width": 120},
|
||||
{
|
||||
"label": _("Depreciation Method"),
|
||||
"fieldname": "depreciation_method",
|
||||
"fieldtype": "Data",
|
||||
"width": 130,
|
||||
},
|
||||
{"label": _("Purchase Date"), "fieldname": "purchase_date", "fieldtype": "Date", "width": 120},
|
||||
]
|
||||
|
||||
@@ -15,7 +15,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
||||
|
||||
frappe.query_reports["Balance Sheet"]["filters"].push({
|
||||
"fieldname": "include_default_book_entries",
|
||||
"label": __("Include Default FB Entries"),
|
||||
"label": __("Include Default Book Entries"),
|
||||
"fieldtype": "Check",
|
||||
"default": 1
|
||||
});
|
||||
|
||||
@@ -23,7 +23,6 @@ class TestBankReconciliationStatement(FrappeTestCase):
|
||||
"Payment Entry",
|
||||
]:
|
||||
frappe.db.delete(dt)
|
||||
frappe.db.set_single_value("Accounts Settings", "acc_frozen_upto", None)
|
||||
|
||||
def test_loan_entries_in_bank_reco_statement(self):
|
||||
create_loan_accounts()
|
||||
|
||||
@@ -16,7 +16,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
||||
frappe.query_reports["Cash Flow"]["filters"].push(
|
||||
{
|
||||
"fieldname": "include_default_book_entries",
|
||||
"label": __("Include Default FB Entries"),
|
||||
"label": __("Include Default Book Entries"),
|
||||
"fieldtype": "Check",
|
||||
"default": 1
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ def setup_mappers(mappers):
|
||||
mapping["finance_costs"] = []
|
||||
mapping["finance_costs_adjustments"] = []
|
||||
doc = frappe.get_doc("Cash Flow Mapper", mapping["name"])
|
||||
mapping_names = [item.mapping for item in doc.accounts]
|
||||
mapping_names = [item.name for item in doc.accounts]
|
||||
|
||||
if not mapping_names:
|
||||
continue
|
||||
|
||||
@@ -105,7 +105,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
||||
},
|
||||
{
|
||||
"fieldname": "include_default_book_entries",
|
||||
"label": __("Include Default FB Entries"),
|
||||
"label": __("Include Default Book Entries"),
|
||||
"fieldtype": "Check",
|
||||
"default": 1
|
||||
},
|
||||
|
||||
@@ -561,7 +561,9 @@ def apply_additional_conditions(doctype, query, from_date, ignore_closing_entrie
|
||||
company_fb = frappe.get_cached_value("Company", filters.company, "default_finance_book")
|
||||
|
||||
if filters.finance_book and company_fb and cstr(filters.finance_book) != cstr(company_fb):
|
||||
frappe.throw(_("To use a different finance book, please uncheck 'Include Default FB Entries'"))
|
||||
frappe.throw(
|
||||
_("To use a different finance book, please uncheck 'Include Default Book Entries'")
|
||||
)
|
||||
|
||||
query = query.where(
|
||||
(gl_entry.finance_book.isin([cstr(filters.finance_book), cstr(company_fb), ""]))
|
||||
|
||||
@@ -79,9 +79,7 @@ class General_Payment_Ledger_Comparison(object):
|
||||
.select(
|
||||
gle.company,
|
||||
gle.account,
|
||||
gle.voucher_type,
|
||||
gle.voucher_no,
|
||||
gle.party_type,
|
||||
gle.party,
|
||||
outstanding,
|
||||
)
|
||||
@@ -91,9 +89,7 @@ class General_Payment_Ledger_Comparison(object):
|
||||
& (gle.account.isin(val.accounts))
|
||||
)
|
||||
.where(Criterion.all(filter_criterion))
|
||||
.groupby(
|
||||
gle.company, gle.account, gle.voucher_type, gle.voucher_no, gle.party_type, gle.party
|
||||
)
|
||||
.groupby(gle.company, gle.account, gle.voucher_no, gle.party)
|
||||
.run()
|
||||
)
|
||||
|
||||
@@ -116,13 +112,7 @@ class General_Payment_Ledger_Comparison(object):
|
||||
self.account_types[acc_type].ple = (
|
||||
qb.from_(ple)
|
||||
.select(
|
||||
ple.company,
|
||||
ple.account,
|
||||
ple.voucher_type,
|
||||
ple.voucher_no,
|
||||
ple.party_type,
|
||||
ple.party,
|
||||
Sum(ple.amount).as_("outstanding"),
|
||||
ple.company, ple.account, ple.voucher_no, ple.party, Sum(ple.amount).as_("outstanding")
|
||||
)
|
||||
.where(
|
||||
(ple.company == self.filters.company)
|
||||
@@ -130,9 +120,7 @@ class General_Payment_Ledger_Comparison(object):
|
||||
& (ple.account.isin(val.accounts))
|
||||
)
|
||||
.where(Criterion.all(filter_criterion))
|
||||
.groupby(
|
||||
ple.company, ple.account, ple.voucher_type, ple.voucher_no, ple.party_type, ple.party
|
||||
)
|
||||
.groupby(ple.company, ple.account, ple.voucher_no, ple.party)
|
||||
.run()
|
||||
)
|
||||
|
||||
@@ -145,17 +133,15 @@ class General_Payment_Ledger_Comparison(object):
|
||||
self.gle_balances = set(val.gle) | self.gle_balances
|
||||
self.ple_balances = set(val.ple) | self.ple_balances
|
||||
|
||||
self.variation_in_payment_ledger = self.gle_balances.difference(self.ple_balances)
|
||||
self.variation_in_general_ledger = self.ple_balances.difference(self.gle_balances)
|
||||
self.diff1 = self.gle_balances.difference(self.ple_balances)
|
||||
self.diff2 = self.ple_balances.difference(self.gle_balances)
|
||||
self.diff = frappe._dict({})
|
||||
|
||||
for x in self.variation_in_payment_ledger:
|
||||
self.diff[(x[0], x[1], x[2], x[3], x[4], x[5])] = frappe._dict({"gl_balance": x[6]})
|
||||
for x in self.diff1:
|
||||
self.diff[(x[0], x[1], x[2], x[3])] = frappe._dict({"gl_balance": x[4]})
|
||||
|
||||
for x in self.variation_in_general_ledger:
|
||||
self.diff.setdefault(
|
||||
(x[0], x[1], x[2], x[3], x[4], x[5]), frappe._dict({"gl_balance": 0.0})
|
||||
).update(frappe._dict({"pl_balance": x[6]}))
|
||||
for x in self.diff2:
|
||||
self.diff[(x[0], x[1], x[2], x[3])].update(frappe._dict({"pl_balance": x[4]}))
|
||||
|
||||
def generate_data(self):
|
||||
self.data = []
|
||||
@@ -163,12 +149,8 @@ class General_Payment_Ledger_Comparison(object):
|
||||
self.data.append(
|
||||
frappe._dict(
|
||||
{
|
||||
"company": key[0],
|
||||
"account": key[1],
|
||||
"voucher_type": key[2],
|
||||
"voucher_no": key[3],
|
||||
"party_type": key[4],
|
||||
"party": key[5],
|
||||
"voucher_no": key[2],
|
||||
"party": key[3],
|
||||
"gl_balance": val.gl_balance,
|
||||
"pl_balance": val.pl_balance,
|
||||
}
|
||||
@@ -178,52 +160,12 @@ class General_Payment_Ledger_Comparison(object):
|
||||
def get_columns(self):
|
||||
self.columns = []
|
||||
options = None
|
||||
self.columns.append(
|
||||
dict(
|
||||
label=_("Company"),
|
||||
fieldname="company",
|
||||
fieldtype="Link",
|
||||
options="Company",
|
||||
width="100",
|
||||
)
|
||||
)
|
||||
|
||||
self.columns.append(
|
||||
dict(
|
||||
label=_("Account"),
|
||||
fieldname="account",
|
||||
fieldtype="Link",
|
||||
options="Account",
|
||||
width="100",
|
||||
)
|
||||
)
|
||||
|
||||
self.columns.append(
|
||||
dict(
|
||||
label=_("Voucher Type"),
|
||||
fieldname="voucher_type",
|
||||
fieldtype="Link",
|
||||
options="DocType",
|
||||
width="100",
|
||||
)
|
||||
)
|
||||
|
||||
self.columns.append(
|
||||
dict(
|
||||
label=_("Voucher No"),
|
||||
fieldname="voucher_no",
|
||||
fieldtype="Dynamic Link",
|
||||
options="voucher_type",
|
||||
width="100",
|
||||
)
|
||||
)
|
||||
|
||||
self.columns.append(
|
||||
dict(
|
||||
label=_("Party Type"),
|
||||
fieldname="party_type",
|
||||
fieldtype="Link",
|
||||
options="DocType",
|
||||
fieldtype="Data",
|
||||
options=options,
|
||||
width="100",
|
||||
)
|
||||
)
|
||||
@@ -232,8 +174,8 @@ class General_Payment_Ledger_Comparison(object):
|
||||
dict(
|
||||
label=_("Party"),
|
||||
fieldname="party",
|
||||
fieldtype="Dynamic Link",
|
||||
options="party_type",
|
||||
fieldtype="Data",
|
||||
options=options,
|
||||
width="100",
|
||||
)
|
||||
)
|
||||
|
||||
@@ -50,11 +50,7 @@ class TestGeneralAndPaymentLedger(FrappeTestCase, AccountsTestMixin):
|
||||
self.assertEqual(len(data), 1)
|
||||
|
||||
expected = {
|
||||
"company": sinv.company,
|
||||
"account": sinv.debit_to,
|
||||
"voucher_type": sinv.doctype,
|
||||
"voucher_no": sinv.name,
|
||||
"party_type": "Customer",
|
||||
"party": sinv.customer,
|
||||
"gl_balance": sinv.grand_total,
|
||||
"pl_balance": sinv.grand_total - 1,
|
||||
|
||||
@@ -175,7 +175,7 @@ frappe.query_reports["General Ledger"] = {
|
||||
},
|
||||
{
|
||||
"fieldname": "include_default_book_entries",
|
||||
"label": __("Include Default FB Entries"),
|
||||
"label": __("Include Default Book Entries"),
|
||||
"fieldtype": "Check",
|
||||
"default": 1
|
||||
},
|
||||
@@ -188,13 +188,7 @@ frappe.query_reports["General Ledger"] = {
|
||||
"fieldname": "show_net_values_in_party_account",
|
||||
"label": __("Show Net Values in Party Account"),
|
||||
"fieldtype": "Check"
|
||||
},
|
||||
{
|
||||
"fieldname": "show_remarks",
|
||||
"label": __("Show Remarks"),
|
||||
"fieldtype": "Check"
|
||||
}
|
||||
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -163,9 +163,6 @@ def get_gl_entries(filters, accounting_dimensions):
|
||||
select_fields = """, debit, credit, debit_in_account_currency,
|
||||
credit_in_account_currency """
|
||||
|
||||
if filters.get("show_remarks"):
|
||||
select_fields += """,remarks"""
|
||||
|
||||
order_by_statement = "order by posting_date, account, creation"
|
||||
|
||||
if filters.get("include_dimensions"):
|
||||
@@ -192,7 +189,7 @@ def get_gl_entries(filters, accounting_dimensions):
|
||||
voucher_type, voucher_no, {dimension_fields}
|
||||
cost_center, project,
|
||||
against_voucher_type, against_voucher, account_currency,
|
||||
against, is_opening, creation {select_fields}
|
||||
remarks, against, is_opening, creation {select_fields}
|
||||
from `tabGL Entry`
|
||||
where company=%(company)s {conditions}
|
||||
{order_by_statement}
|
||||
@@ -252,7 +249,9 @@ def get_conditions(filters):
|
||||
if filters.get("company_fb") and cstr(filters.get("finance_book")) != cstr(
|
||||
filters.get("company_fb")
|
||||
):
|
||||
frappe.throw(_("To use a different finance book, please uncheck 'Include Default FB Entries'"))
|
||||
frappe.throw(
|
||||
_("To use a different finance book, please uncheck 'Include Default Book Entries'")
|
||||
)
|
||||
else:
|
||||
conditions.append("(finance_book in (%(finance_book)s, '') OR finance_book IS NULL)")
|
||||
else:
|
||||
@@ -594,10 +593,8 @@ def get_columns(filters):
|
||||
"width": 100,
|
||||
},
|
||||
{"label": _("Supplier Invoice No"), "fieldname": "bill_no", "fieldtype": "Data", "width": 100},
|
||||
{"label": _("Remarks"), "fieldname": "remarks", "width": 400},
|
||||
]
|
||||
)
|
||||
|
||||
if filters.get("show_remarks"):
|
||||
columns.extend([{"label": _("Remarks"), "fieldname": "remarks", "width": 400}])
|
||||
|
||||
return columns
|
||||
|
||||
@@ -544,8 +544,6 @@ class GrossProfitGenerator(object):
|
||||
new_row.qty += flt(row.qty)
|
||||
new_row.buying_amount += flt(row.buying_amount, self.currency_precision)
|
||||
new_row.base_amount += flt(row.base_amount, self.currency_precision)
|
||||
if self.filters.get("group_by") == "Sales Person":
|
||||
new_row.allocated_amount += flt(row.allocated_amount, self.currency_precision)
|
||||
new_row = self.set_average_rate(new_row)
|
||||
self.grouped_data.append(new_row)
|
||||
|
||||
|
||||
@@ -33,6 +33,13 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
||||
"label": __("Accounting Dimension"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Accounting Dimension",
|
||||
"get_query": () =>{
|
||||
return {
|
||||
filters: {
|
||||
"disabled": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname": "fiscal_year",
|
||||
|
||||
@@ -47,7 +47,6 @@ def get_result(
|
||||
out = []
|
||||
for name, details in gle_map.items():
|
||||
tax_amount, total_amount, grand_total, base_total = 0, 0, 0, 0
|
||||
bill_no, bill_date = "", ""
|
||||
tax_withholding_category = tax_category_map.get(name)
|
||||
rate = tax_rate_map.get(tax_withholding_category)
|
||||
|
||||
@@ -69,14 +68,7 @@ def get_result(
|
||||
tax_amount += entry.credit - entry.debit
|
||||
|
||||
if net_total_map.get(name):
|
||||
if voucher_type == "Journal Entry" and tax_amount and rate:
|
||||
# back calcalute total amount from rate and tax_amount
|
||||
if rate:
|
||||
total_amount = grand_total = base_total = tax_amount / (rate / 100)
|
||||
elif voucher_type == "Purchase Invoice":
|
||||
total_amount, grand_total, base_total, bill_no, bill_date = net_total_map.get(name)
|
||||
else:
|
||||
total_amount, grand_total, base_total = net_total_map.get(name)
|
||||
total_amount, grand_total, base_total = net_total_map.get(name)
|
||||
else:
|
||||
total_amount += entry.credit
|
||||
|
||||
@@ -100,7 +92,7 @@ def get_result(
|
||||
|
||||
row.update(
|
||||
{
|
||||
"section_code": tax_withholding_category or "",
|
||||
"section_code": tax_withholding_category,
|
||||
"entity_type": party_map.get(party, {}).get(party_type),
|
||||
"rate": rate,
|
||||
"total_amount": total_amount,
|
||||
@@ -110,14 +102,10 @@ def get_result(
|
||||
"transaction_date": posting_date,
|
||||
"transaction_type": voucher_type,
|
||||
"ref_no": name,
|
||||
"supplier_invoice_no": bill_no,
|
||||
"supplier_invoice_date": bill_date,
|
||||
}
|
||||
)
|
||||
out.append(row)
|
||||
|
||||
out.sort(key=lambda x: x["section_code"])
|
||||
|
||||
return out
|
||||
|
||||
|
||||
@@ -165,14 +153,14 @@ def get_gle_map(documents):
|
||||
def get_columns(filters):
|
||||
pan = "pan" if frappe.db.has_column(filters.party_type, "pan") else "tax_id"
|
||||
columns = [
|
||||
{
|
||||
"label": _("Section Code"),
|
||||
"options": "Tax Withholding Category",
|
||||
"fieldname": "section_code",
|
||||
"fieldtype": "Link",
|
||||
"width": 90,
|
||||
},
|
||||
{"label": _(frappe.unscrub(pan)), "fieldname": pan, "fieldtype": "Data", "width": 60},
|
||||
{
|
||||
"label": _(filters.get("party_type")),
|
||||
"fieldname": "party",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"options": "party_type",
|
||||
"width": 180,
|
||||
},
|
||||
]
|
||||
|
||||
if filters.naming_series == "Naming Series":
|
||||
@@ -185,62 +173,53 @@ def get_columns(filters):
|
||||
}
|
||||
)
|
||||
|
||||
columns.extend(
|
||||
[
|
||||
{"label": _("Entity Type"), "fieldname": "entity_type", "fieldtype": "Data", "width": 100},
|
||||
]
|
||||
)
|
||||
if filters.party_type == "Supplier":
|
||||
columns.extend(
|
||||
[
|
||||
{
|
||||
"label": _("Supplier Invoice No"),
|
||||
"fieldname": "supplier_invoice_no",
|
||||
"fieldtype": "Data",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Supplier Invoice Date"),
|
||||
"fieldname": "supplier_invoice_date",
|
||||
"fieldtype": "Date",
|
||||
"width": 120,
|
||||
},
|
||||
]
|
||||
)
|
||||
|
||||
columns.extend(
|
||||
[
|
||||
{
|
||||
"label": _("TDS Rate %") if filters.get("party_type") == "Supplier" else _("TCS Rate %"),
|
||||
"fieldname": "rate",
|
||||
"fieldtype": "Percent",
|
||||
"width": 60,
|
||||
"label": _("Date of Transaction"),
|
||||
"fieldname": "transaction_date",
|
||||
"fieldtype": "Date",
|
||||
"width": 100,
|
||||
},
|
||||
{
|
||||
"label": _("Section Code"),
|
||||
"options": "Tax Withholding Category",
|
||||
"fieldname": "section_code",
|
||||
"fieldtype": "Link",
|
||||
"width": 90,
|
||||
},
|
||||
{"label": _("Entity Type"), "fieldname": "entity_type", "fieldtype": "Data", "width": 100},
|
||||
{
|
||||
"label": _("Total Amount"),
|
||||
"fieldname": "total_amount",
|
||||
"fieldtype": "Float",
|
||||
"width": 120,
|
||||
"width": 90,
|
||||
},
|
||||
{
|
||||
"label": _("Base Total"),
|
||||
"fieldname": "base_total",
|
||||
"fieldtype": "Float",
|
||||
"width": 120,
|
||||
"label": _("TDS Rate %") if filters.get("party_type") == "Supplier" else _("TCS Rate %"),
|
||||
"fieldname": "rate",
|
||||
"fieldtype": "Percent",
|
||||
"width": 90,
|
||||
},
|
||||
{
|
||||
"label": _("Tax Amount"),
|
||||
"fieldname": "tax_amount",
|
||||
"fieldtype": "Float",
|
||||
"width": 120,
|
||||
"width": 90,
|
||||
},
|
||||
{
|
||||
"label": _("Grand Total"),
|
||||
"fieldname": "grand_total",
|
||||
"fieldtype": "Float",
|
||||
"width": 120,
|
||||
"width": 90,
|
||||
},
|
||||
{"label": _("Transaction Type"), "fieldname": "transaction_type", "width": 130},
|
||||
{
|
||||
"label": _("Base Total"),
|
||||
"fieldname": "base_total",
|
||||
"fieldtype": "Float",
|
||||
"width": 90,
|
||||
},
|
||||
{"label": _("Transaction Type"), "fieldname": "transaction_type", "width": 100},
|
||||
{
|
||||
"label": _("Reference No."),
|
||||
"fieldname": "ref_no",
|
||||
@@ -248,12 +227,6 @@ def get_columns(filters):
|
||||
"options": "transaction_type",
|
||||
"width": 180,
|
||||
},
|
||||
{
|
||||
"label": _("Date of Transaction"),
|
||||
"fieldname": "transaction_date",
|
||||
"fieldtype": "Date",
|
||||
"width": 100,
|
||||
},
|
||||
]
|
||||
)
|
||||
|
||||
@@ -276,7 +249,27 @@ def get_tds_docs(filters):
|
||||
"Tax Withholding Account", {"company": filters.get("company")}, pluck="account"
|
||||
)
|
||||
|
||||
tds_docs = get_tds_docs_query(filters, bank_accounts, tds_accounts).run(as_dict=True)
|
||||
query_filters = {
|
||||
"account": ("in", tds_accounts),
|
||||
"posting_date": ("between", [filters.get("from_date"), filters.get("to_date")]),
|
||||
"is_cancelled": 0,
|
||||
"against": ("not in", bank_accounts),
|
||||
}
|
||||
|
||||
party = frappe.get_all(filters.get("party_type"), pluck="name")
|
||||
or_filters.update({"against": ("in", party), "voucher_type": "Journal Entry"})
|
||||
|
||||
if filters.get("party"):
|
||||
del query_filters["account"]
|
||||
del query_filters["against"]
|
||||
or_filters = {"against": filters.get("party"), "party": filters.get("party")}
|
||||
|
||||
tds_docs = frappe.get_all(
|
||||
"GL Entry",
|
||||
filters=query_filters,
|
||||
or_filters=or_filters,
|
||||
fields=["voucher_no", "voucher_type", "against", "party"],
|
||||
)
|
||||
|
||||
for d in tds_docs:
|
||||
if d.voucher_type == "Purchase Invoice":
|
||||
@@ -312,47 +305,6 @@ def get_tds_docs(filters):
|
||||
)
|
||||
|
||||
|
||||
def get_tds_docs_query(filters, bank_accounts, tds_accounts):
|
||||
if not tds_accounts:
|
||||
frappe.throw(
|
||||
_("No {0} Accounts found for this company.").format(frappe.bold("Tax Withholding")),
|
||||
title=_("Accounts Missing Error"),
|
||||
)
|
||||
gle = frappe.qb.DocType("GL Entry")
|
||||
query = (
|
||||
frappe.qb.from_(gle)
|
||||
.select("voucher_no", "voucher_type", "against", "party")
|
||||
.where((gle.is_cancelled == 0))
|
||||
)
|
||||
|
||||
if filters.get("from_date"):
|
||||
query = query.where(gle.posting_date >= filters.get("from_date"))
|
||||
if filters.get("to_date"):
|
||||
query = query.where(gle.posting_date <= filters.get("to_date"))
|
||||
|
||||
if bank_accounts:
|
||||
query = query.where(gle.against.notin(bank_accounts))
|
||||
|
||||
if filters.get("party"):
|
||||
party = [filters.get("party")]
|
||||
query = query.where(
|
||||
((gle.account.isin(tds_accounts) & gle.against.isin(party)))
|
||||
| ((gle.voucher_type == "Journal Entry") & (gle.party == filters.get("party")))
|
||||
| gle.party.isin(party)
|
||||
)
|
||||
else:
|
||||
party = frappe.get_all(filters.get("party_type"), pluck="name")
|
||||
query = query.where(
|
||||
((gle.account.isin(tds_accounts) & gle.against.isin(party)))
|
||||
| (
|
||||
(gle.voucher_type == "Journal Entry")
|
||||
& ((gle.party_type == filters.get("party_type")) | (gle.party_type == ""))
|
||||
)
|
||||
| gle.party.isin(party)
|
||||
)
|
||||
return query
|
||||
|
||||
|
||||
def get_journal_entry_party_map(journal_entries):
|
||||
journal_entry_party_map = {}
|
||||
for d in frappe.db.get_all(
|
||||
@@ -379,8 +331,6 @@ def get_doc_info(vouchers, doctype, tax_category_map, net_total_map=None):
|
||||
"base_tax_withholding_net_total",
|
||||
"grand_total",
|
||||
"base_total",
|
||||
"bill_no",
|
||||
"bill_date",
|
||||
],
|
||||
"Sales Invoice": ["base_net_total", "grand_total", "base_total"],
|
||||
"Payment Entry": [
|
||||
@@ -399,13 +349,7 @@ def get_doc_info(vouchers, doctype, tax_category_map, net_total_map=None):
|
||||
for entry in entries:
|
||||
tax_category_map.update({entry.name: entry.tax_withholding_category})
|
||||
if doctype == "Purchase Invoice":
|
||||
value = [
|
||||
entry.base_tax_withholding_net_total,
|
||||
entry.grand_total,
|
||||
entry.base_total,
|
||||
entry.bill_no,
|
||||
entry.bill_date,
|
||||
]
|
||||
value = [entry.base_tax_withholding_net_total, entry.grand_total, entry.base_total]
|
||||
elif doctype == "Sales Invoice":
|
||||
value = [entry.base_net_total, entry.grand_total, entry.base_total]
|
||||
elif doctype == "Payment Entry":
|
||||
|
||||
@@ -96,13 +96,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
||||
},
|
||||
{
|
||||
"fieldname": "include_default_book_entries",
|
||||
"label": __("Include Default FB Entries"),
|
||||
"fieldtype": "Check",
|
||||
"default": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "show_net_values",
|
||||
"label": __("Show net values in opening and closing columns"),
|
||||
"label": __("Include Default Book Entries"),
|
||||
"fieldtype": "Check",
|
||||
"default": 1
|
||||
}
|
||||
|
||||
@@ -120,9 +120,7 @@ def get_data(filters):
|
||||
ignore_opening_entries=True,
|
||||
)
|
||||
|
||||
calculate_values(
|
||||
accounts, gl_entries_by_account, opening_balances, filters.get("show_net_values")
|
||||
)
|
||||
calculate_values(accounts, gl_entries_by_account, opening_balances)
|
||||
accumulate_values_into_parents(accounts, accounts_by_name)
|
||||
|
||||
data = prepare_data(accounts, filters, parent_children_map, company_currency)
|
||||
@@ -275,7 +273,9 @@ def get_opening_balance(
|
||||
company_fb = frappe.get_cached_value("Company", filters.company, "default_finance_book")
|
||||
|
||||
if filters.finance_book and company_fb and cstr(filters.finance_book) != cstr(company_fb):
|
||||
frappe.throw(_("To use a different finance book, please uncheck 'Include Default FB Entries'"))
|
||||
frappe.throw(
|
||||
_("To use a different finance book, please uncheck 'Include Default Book Entries'")
|
||||
)
|
||||
|
||||
opening_balance = opening_balance.where(
|
||||
(closing_balance.finance_book.isin([cstr(filters.finance_book), cstr(company_fb), ""]))
|
||||
@@ -310,7 +310,7 @@ def get_opening_balance(
|
||||
return gle
|
||||
|
||||
|
||||
def calculate_values(accounts, gl_entries_by_account, opening_balances, show_net_values):
|
||||
def calculate_values(accounts, gl_entries_by_account, opening_balances):
|
||||
init = {
|
||||
"opening_debit": 0.0,
|
||||
"opening_credit": 0.0,
|
||||
@@ -335,8 +335,7 @@ def calculate_values(accounts, gl_entries_by_account, opening_balances, show_net
|
||||
d["closing_debit"] = d["opening_debit"] + d["debit"]
|
||||
d["closing_credit"] = d["opening_credit"] + d["credit"]
|
||||
|
||||
if show_net_values:
|
||||
prepare_opening_closing(d)
|
||||
prepare_opening_closing(d)
|
||||
|
||||
|
||||
def calculate_total_row(accounts, company_currency):
|
||||
@@ -376,7 +375,7 @@ def prepare_data(accounts, filters, parent_children_map, company_currency):
|
||||
|
||||
for d in accounts:
|
||||
# Prepare opening closing for group account
|
||||
if parent_children_map.get(d.account) and filters.get("show_net_values"):
|
||||
if parent_children_map.get(d.account):
|
||||
prepare_opening_closing(d)
|
||||
|
||||
has_value = False
|
||||
|
||||
@@ -46,7 +46,6 @@ def get_data(filters):
|
||||
.select(
|
||||
gle.voucher_type, gle.voucher_no, Sum(gle.debit).as_("debit"), Sum(gle.credit).as_("credit")
|
||||
)
|
||||
.where(gle.is_cancelled == 0)
|
||||
.groupby(gle.voucher_no)
|
||||
)
|
||||
query = apply_filters(query, filters, gle)
|
||||
|
||||
@@ -511,7 +511,7 @@ def check_if_advance_entry_modified(args):
|
||||
party_account_field = (
|
||||
"paid_from" if erpnext.get_party_account_type(args.party_type) == "Receivable" else "paid_to"
|
||||
)
|
||||
precision = frappe.get_precision("Payment Entry", "unallocated_amount")
|
||||
|
||||
if args.voucher_detail_no:
|
||||
ret = frappe.db.sql(
|
||||
"""select t1.name
|
||||
@@ -533,9 +533,9 @@ def check_if_advance_entry_modified(args):
|
||||
where
|
||||
name = %(voucher_no)s and docstatus = 1
|
||||
and party_type = %(party_type)s and party = %(party)s and {0} = %(account)s
|
||||
and round(unallocated_amount, {1}) = %(unreconciled_amount)s
|
||||
and unallocated_amount = %(unreconciled_amount)s
|
||||
""".format(
|
||||
party_account_field, precision
|
||||
party_account_field
|
||||
),
|
||||
args,
|
||||
)
|
||||
@@ -560,10 +560,6 @@ def update_reference_in_journal_entry(d, journal_entry, do_not_save=False):
|
||||
"""
|
||||
jv_detail = journal_entry.get("accounts", {"name": d["voucher_detail_no"]})[0]
|
||||
|
||||
# Update Advance Paid in SO/PO since they might be getting unlinked
|
||||
if jv_detail.get("reference_type") in ("Sales Order", "Purchase Order"):
|
||||
frappe.get_doc(jv_detail.reference_type, jv_detail.reference_name).set_total_advance_paid()
|
||||
|
||||
if flt(d["unadjusted_amount"]) - flt(d["allocated_amount"]) != 0:
|
||||
# adjust the unreconciled balance
|
||||
amount_in_account_currency = flt(d["unadjusted_amount"]) - flt(d["allocated_amount"])
|
||||
@@ -624,49 +620,39 @@ def update_reference_in_payment_entry(
|
||||
"outstanding_amount": d.outstanding_amount,
|
||||
"allocated_amount": d.allocated_amount,
|
||||
"exchange_rate": d.exchange_rate if d.exchange_gain_loss else payment_entry.get_exchange_rate(),
|
||||
"exchange_gain_loss": d.exchange_gain_loss,
|
||||
"exchange_gain_loss": d.exchange_gain_loss, # only populated from invoice in case of advance allocation
|
||||
}
|
||||
|
||||
if d.voucher_detail_no:
|
||||
existing_row = payment_entry.get("references", {"name": d["voucher_detail_no"]})[0]
|
||||
original_row = existing_row.as_dict().copy()
|
||||
existing_row.update(reference_details)
|
||||
|
||||
# Update Advance Paid in SO/PO since they are getting unlinked
|
||||
if existing_row.get("reference_doctype") in ("Sales Order", "Purchase Order"):
|
||||
frappe.get_doc(
|
||||
existing_row.reference_doctype, existing_row.reference_name
|
||||
).set_total_advance_paid()
|
||||
|
||||
if d.allocated_amount <= existing_row.allocated_amount:
|
||||
existing_row.allocated_amount -= d.allocated_amount
|
||||
|
||||
if d.allocated_amount < original_row.allocated_amount:
|
||||
new_row = payment_entry.append("references")
|
||||
new_row.docstatus = 1
|
||||
for field in list(reference_details):
|
||||
new_row.set(field, reference_details[field])
|
||||
new_row.set(field, original_row[field])
|
||||
|
||||
new_row.allocated_amount = original_row.allocated_amount - d.allocated_amount
|
||||
else:
|
||||
new_row = payment_entry.append("references")
|
||||
new_row.docstatus = 1
|
||||
new_row.update(reference_details)
|
||||
|
||||
payment_entry.flags.ignore_validate_update_after_submit = True
|
||||
payment_entry.clear_unallocated_reference_document_rows()
|
||||
payment_entry.setup_party_account_field()
|
||||
payment_entry.set_missing_values()
|
||||
if not skip_ref_details_update_for_pe:
|
||||
payment_entry.set_missing_ref_details()
|
||||
payment_entry.set_amounts()
|
||||
payment_entry.make_exchange_gain_loss_journal(
|
||||
frappe._dict({"difference_posting_date": d.difference_posting_date})
|
||||
)
|
||||
payment_entry.make_exchange_gain_loss_journal()
|
||||
|
||||
if not do_not_save:
|
||||
payment_entry.save(ignore_permissions=True)
|
||||
|
||||
|
||||
def cancel_exchange_gain_loss_journal(
|
||||
parent_doc: dict | object, referenced_dt: str = None, referenced_dn: str = None
|
||||
) -> None:
|
||||
def cancel_exchange_gain_loss_journal(parent_doc: dict | object) -> None:
|
||||
"""
|
||||
Cancel Exchange Gain/Loss for Sales/Purchase Invoice, if they have any.
|
||||
"""
|
||||
@@ -693,147 +679,76 @@ def cancel_exchange_gain_loss_journal(
|
||||
as_list=1,
|
||||
)
|
||||
for doc in gain_loss_journals:
|
||||
gain_loss_je = frappe.get_doc("Journal Entry", doc[0])
|
||||
if referenced_dt and referenced_dn:
|
||||
references = [(x.reference_type, x.reference_name) for x in gain_loss_je.accounts]
|
||||
if (
|
||||
len(references) == 2
|
||||
and (referenced_dt, referenced_dn) in references
|
||||
and (parent_doc.doctype, parent_doc.name) in references
|
||||
):
|
||||
# only cancel JE generated against parent_doc and referenced_dn
|
||||
gain_loss_je.cancel()
|
||||
else:
|
||||
gain_loss_je.cancel()
|
||||
frappe.get_doc("Journal Entry", doc[0]).cancel()
|
||||
|
||||
|
||||
def update_accounting_ledgers_after_reference_removal(
|
||||
ref_type: str = None, ref_no: str = None, payment_name: str = None
|
||||
):
|
||||
# General Ledger
|
||||
gle = qb.DocType("GL Entry")
|
||||
gle_update_query = (
|
||||
qb.update(gle)
|
||||
.set(gle.against_voucher_type, None)
|
||||
.set(gle.against_voucher, None)
|
||||
.set(gle.modified, now())
|
||||
.set(gle.modified_by, frappe.session.user)
|
||||
.where((gle.against_voucher_type == ref_type) & (gle.against_voucher == ref_no))
|
||||
def unlink_ref_doc_from_payment_entries(ref_doc):
|
||||
remove_ref_doc_link_from_jv(ref_doc.doctype, ref_doc.name)
|
||||
remove_ref_doc_link_from_pe(ref_doc.doctype, ref_doc.name)
|
||||
|
||||
frappe.db.sql(
|
||||
"""update `tabGL Entry`
|
||||
set against_voucher_type=null, against_voucher=null,
|
||||
modified=%s, modified_by=%s
|
||||
where against_voucher_type=%s and against_voucher=%s
|
||||
and voucher_no != ifnull(against_voucher, '')""",
|
||||
(now(), frappe.session.user, ref_doc.doctype, ref_doc.name),
|
||||
)
|
||||
|
||||
if payment_name:
|
||||
gle_update_query = gle_update_query.where(gle.voucher_no == payment_name)
|
||||
gle_update_query.run()
|
||||
|
||||
# Payment Ledger
|
||||
ple = qb.DocType("Payment Ledger Entry")
|
||||
ple_update_query = (
|
||||
qb.update(ple)
|
||||
.set(ple.against_voucher_type, ple.voucher_type)
|
||||
.set(ple.against_voucher_no, ple.voucher_no)
|
||||
.set(ple.modified, now())
|
||||
.set(ple.modified_by, frappe.session.user)
|
||||
.where(
|
||||
(ple.against_voucher_type == ref_type)
|
||||
& (ple.against_voucher_no == ref_no)
|
||||
& (ple.delinked == 0)
|
||||
)
|
||||
)
|
||||
|
||||
if payment_name:
|
||||
ple_update_query = ple_update_query.where(ple.voucher_no == payment_name)
|
||||
ple_update_query.run()
|
||||
qb.update(ple).set(ple.against_voucher_type, ple.voucher_type).set(
|
||||
ple.against_voucher_no, ple.voucher_no
|
||||
).set(ple.modified, now()).set(ple.modified_by, frappe.session.user).where(
|
||||
(ple.against_voucher_type == ref_doc.doctype)
|
||||
& (ple.against_voucher_no == ref_doc.name)
|
||||
& (ple.delinked == 0)
|
||||
).run()
|
||||
|
||||
|
||||
def remove_ref_from_advance_section(ref_doc: object = None):
|
||||
# TODO: this might need some testing
|
||||
if ref_doc.doctype in ("Sales Invoice", "Purchase Invoice"):
|
||||
ref_doc.set("advances", [])
|
||||
adv_type = qb.DocType(f"{ref_doc.doctype} Advance")
|
||||
qb.from_(adv_type).delete().where(adv_type.parent == ref_doc.name).run()
|
||||
|
||||
|
||||
def unlink_ref_doc_from_payment_entries(ref_doc: object = None, payment_name: str = None):
|
||||
remove_ref_doc_link_from_jv(ref_doc.doctype, ref_doc.name, payment_name)
|
||||
remove_ref_doc_link_from_pe(ref_doc.doctype, ref_doc.name, payment_name)
|
||||
update_accounting_ledgers_after_reference_removal(ref_doc.doctype, ref_doc.name, payment_name)
|
||||
remove_ref_from_advance_section(ref_doc)
|
||||
|
||||
|
||||
def remove_ref_doc_link_from_jv(
|
||||
ref_type: str = None, ref_no: str = None, payment_name: str = None
|
||||
):
|
||||
jea = qb.DocType("Journal Entry Account")
|
||||
|
||||
linked_jv = (
|
||||
qb.from_(jea)
|
||||
.select(jea.parent)
|
||||
.where((jea.reference_type == ref_type) & (jea.reference_name == ref_no) & (jea.docstatus.lt(2)))
|
||||
.run(as_list=1)
|
||||
)
|
||||
linked_jv = convert_to_list(linked_jv)
|
||||
# remove reference only from specified payment
|
||||
linked_jv = [x for x in linked_jv if x == payment_name] if payment_name else linked_jv
|
||||
|
||||
if linked_jv:
|
||||
update_query = (
|
||||
qb.update(jea)
|
||||
.set(jea.reference_type, None)
|
||||
.set(jea.reference_name, None)
|
||||
.set(jea.modified, now())
|
||||
.set(jea.modified_by, frappe.session.user)
|
||||
.where((jea.reference_type == ref_type) & (jea.reference_name == ref_no))
|
||||
frappe.db.sql(
|
||||
"""delete from `tab{0} Advance` where parent = %s""".format(ref_doc.doctype), ref_doc.name
|
||||
)
|
||||
|
||||
if payment_name:
|
||||
update_query = update_query.where(jea.parent == payment_name)
|
||||
|
||||
update_query.run()
|
||||
def remove_ref_doc_link_from_jv(ref_type, ref_no):
|
||||
linked_jv = frappe.db.sql_list(
|
||||
"""select parent from `tabJournal Entry Account`
|
||||
where reference_type=%s and reference_name=%s and docstatus < 2""",
|
||||
(ref_type, ref_no),
|
||||
)
|
||||
|
||||
if linked_jv:
|
||||
frappe.db.sql(
|
||||
"""update `tabJournal Entry Account`
|
||||
set reference_type=null, reference_name = null,
|
||||
modified=%s, modified_by=%s
|
||||
where reference_type=%s and reference_name=%s
|
||||
and docstatus < 2""",
|
||||
(now(), frappe.session.user, ref_type, ref_no),
|
||||
)
|
||||
|
||||
frappe.msgprint(_("Journal Entries {0} are un-linked").format("\n".join(linked_jv)))
|
||||
|
||||
|
||||
def convert_to_list(result):
|
||||
"""
|
||||
Convert tuple to list
|
||||
"""
|
||||
return [x[0] for x in result]
|
||||
|
||||
|
||||
def remove_ref_doc_link_from_pe(
|
||||
ref_type: str = None, ref_no: str = None, payment_name: str = None
|
||||
):
|
||||
per = qb.DocType("Payment Entry Reference")
|
||||
pay = qb.DocType("Payment Entry")
|
||||
|
||||
linked_pe = (
|
||||
qb.from_(per)
|
||||
.select(per.parent)
|
||||
.where(
|
||||
(per.reference_doctype == ref_type) & (per.reference_name == ref_no) & (per.docstatus.lt(2))
|
||||
)
|
||||
.run(as_list=1)
|
||||
def remove_ref_doc_link_from_pe(ref_type, ref_no):
|
||||
linked_pe = frappe.db.sql_list(
|
||||
"""select parent from `tabPayment Entry Reference`
|
||||
where reference_doctype=%s and reference_name=%s and docstatus < 2""",
|
||||
(ref_type, ref_no),
|
||||
)
|
||||
linked_pe = convert_to_list(linked_pe)
|
||||
# remove reference only from specified payment
|
||||
linked_pe = [x for x in linked_pe if x == payment_name] if payment_name else linked_pe
|
||||
|
||||
if linked_pe:
|
||||
update_query = (
|
||||
qb.update(per)
|
||||
.set(per.allocated_amount, 0)
|
||||
.set(per.modified, now())
|
||||
.set(per.modified_by, frappe.session.user)
|
||||
.where(
|
||||
(per.docstatus.lt(2) & (per.reference_doctype == ref_type) & (per.reference_name == ref_no))
|
||||
)
|
||||
frappe.db.sql(
|
||||
"""update `tabPayment Entry Reference`
|
||||
set allocated_amount=0, modified=%s, modified_by=%s
|
||||
where reference_doctype=%s and reference_name=%s
|
||||
and docstatus < 2""",
|
||||
(now(), frappe.session.user, ref_type, ref_no),
|
||||
)
|
||||
|
||||
if payment_name:
|
||||
update_query = update_query.where(per.parent == payment_name)
|
||||
|
||||
update_query.run()
|
||||
|
||||
for pe in linked_pe:
|
||||
try:
|
||||
pe_doc = frappe.get_doc("Payment Entry", pe)
|
||||
@@ -846,13 +761,19 @@ def remove_ref_doc_link_from_pe(
|
||||
msg += _("Please cancel payment entry manually first")
|
||||
frappe.throw(msg, exc=PaymentEntryUnlinkError, title=_("Payment Unlink Error"))
|
||||
|
||||
qb.update(pay).set(pay.total_allocated_amount, pe_doc.total_allocated_amount).set(
|
||||
pay.base_total_allocated_amount, pe_doc.base_total_allocated_amount
|
||||
).set(pay.unallocated_amount, pe_doc.unallocated_amount).set(pay.modified, now()).set(
|
||||
pay.modified_by, frappe.session.user
|
||||
).where(
|
||||
pay.name == pe
|
||||
).run()
|
||||
frappe.db.sql(
|
||||
"""update `tabPayment Entry` set total_allocated_amount=%s,
|
||||
base_total_allocated_amount=%s, unallocated_amount=%s, modified=%s, modified_by=%s
|
||||
where name=%s""",
|
||||
(
|
||||
pe_doc.total_allocated_amount,
|
||||
pe_doc.base_total_allocated_amount,
|
||||
pe_doc.unallocated_amount,
|
||||
now(),
|
||||
frappe.session.user,
|
||||
pe,
|
||||
),
|
||||
)
|
||||
|
||||
frappe.msgprint(_("Payment Entries {0} are un-linked").format("\n".join(linked_pe)))
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ frappe.ui.form.on('Asset', {
|
||||
frm.set_query("item_code", function() {
|
||||
return {
|
||||
"filters": {
|
||||
"disabled": 0,
|
||||
"is_fixed_asset": 1,
|
||||
"is_stock_item": 0
|
||||
}
|
||||
@@ -146,15 +147,6 @@ frappe.ui.form.on('Asset', {
|
||||
|
||||
if (frm.doc.docstatus == 0) {
|
||||
frm.toggle_reqd("finance_books", frm.doc.calculate_depreciation);
|
||||
|
||||
if (frm.doc.is_composite_asset && !frm.doc.capitalized_in) {
|
||||
$('.primary-action').prop('hidden', true);
|
||||
$('.form-message').text('Capitalize this asset to confirm');
|
||||
|
||||
frm.add_custom_button(__("Capitalize Asset"), function() {
|
||||
frm.trigger("create_asset_capitalization");
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -176,7 +168,7 @@ frappe.ui.form.on('Asset', {
|
||||
frm.set_df_property('purchase_invoice', 'read_only', 1);
|
||||
frm.set_df_property('purchase_receipt', 'read_only', 1);
|
||||
}
|
||||
else if (frm.doc.is_existing_asset || frm.doc.is_composite_asset) {
|
||||
else if (frm.doc.is_existing_asset) {
|
||||
frm.toggle_reqd('purchase_receipt', 0);
|
||||
frm.toggle_reqd('purchase_invoice', 0);
|
||||
}
|
||||
@@ -283,7 +275,7 @@ frappe.ui.form.on('Asset', {
|
||||
|
||||
|
||||
item_code: function(frm) {
|
||||
if(frm.doc.item_code && frm.doc.calculate_depreciation && frm.doc.gross_purchase_amount) {
|
||||
if(frm.doc.item_code && frm.doc.calculate_depreciation) {
|
||||
frm.trigger('set_finance_book');
|
||||
} else {
|
||||
frm.set_value('finance_books', []);
|
||||
@@ -295,8 +287,7 @@ frappe.ui.form.on('Asset', {
|
||||
method: "erpnext.assets.doctype.asset.asset.get_item_details",
|
||||
args: {
|
||||
item_code: frm.doc.item_code,
|
||||
asset_category: frm.doc.asset_category,
|
||||
gross_purchase_amount: frm.doc.gross_purchase_amount
|
||||
asset_category: frm.doc.asset_category
|
||||
},
|
||||
callback: function(r, rt) {
|
||||
if(r.message) {
|
||||
@@ -308,17 +299,7 @@ frappe.ui.form.on('Asset', {
|
||||
|
||||
is_existing_asset: function(frm) {
|
||||
frm.trigger("toggle_reference_doc");
|
||||
},
|
||||
|
||||
is_composite_asset: function(frm) {
|
||||
if(frm.doc.is_composite_asset) {
|
||||
frm.set_value('gross_purchase_amount', 0);
|
||||
frm.set_df_property('gross_purchase_amount', 'read_only', 1);
|
||||
} else {
|
||||
frm.set_df_property('gross_purchase_amount', 'read_only', 0);
|
||||
}
|
||||
|
||||
frm.trigger("toggle_reference_doc");
|
||||
// frm.toggle_reqd("next_depreciation_date", (!frm.doc.is_existing_asset && frm.doc.calculate_depreciation));
|
||||
},
|
||||
|
||||
make_schedules_editable: function(frm) {
|
||||
@@ -379,19 +360,6 @@ frappe.ui.form.on('Asset', {
|
||||
});
|
||||
},
|
||||
|
||||
create_asset_capitalization: function(frm) {
|
||||
frappe.call({
|
||||
args: {
|
||||
"asset": frm.doc.name,
|
||||
},
|
||||
method: "erpnext.assets.doctype.asset.asset.create_asset_capitalization",
|
||||
callback: function(r) {
|
||||
var doclist = frappe.model.sync(r.message);
|
||||
frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
split_asset: function(frm) {
|
||||
const title = __('Split Asset');
|
||||
|
||||
@@ -447,7 +415,7 @@ frappe.ui.form.on('Asset', {
|
||||
|
||||
calculate_depreciation: function(frm) {
|
||||
frm.toggle_reqd("finance_books", frm.doc.calculate_depreciation);
|
||||
if (frm.doc.item_code && frm.doc.calculate_depreciation && frm.doc.gross_purchase_amount) {
|
||||
if (frm.doc.item_code && frm.doc.calculate_depreciation ) {
|
||||
frm.trigger("set_finance_book");
|
||||
} else {
|
||||
frm.set_value("finance_books", []);
|
||||
@@ -455,11 +423,9 @@ frappe.ui.form.on('Asset', {
|
||||
},
|
||||
|
||||
gross_purchase_amount: function(frm) {
|
||||
if (frm.doc.finance_books) {
|
||||
frm.doc.finance_books.forEach(d => {
|
||||
frm.events.set_depreciation_rate(frm, d);
|
||||
})
|
||||
}
|
||||
frm.doc.finance_books.forEach(d => {
|
||||
frm.events.set_depreciation_rate(frm, d);
|
||||
})
|
||||
},
|
||||
|
||||
purchase_receipt: (frm) => {
|
||||
@@ -538,21 +504,7 @@ frappe.ui.form.on('Asset', {
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
set_salvage_value_percentage_or_expected_value_after_useful_life: function(frm, row, salvage_value_percentage_changed, expected_value_after_useful_life_changed) {
|
||||
if (expected_value_after_useful_life_changed) {
|
||||
frappe.flags.from_set_salvage_value_percentage_or_expected_value_after_useful_life = true;
|
||||
const new_salvage_value_percentage = flt((row.expected_value_after_useful_life * 100) / frm.doc.gross_purchase_amount, precision("salvage_value_percentage", row));
|
||||
frappe.model.set_value(row.doctype, row.name, "salvage_value_percentage", new_salvage_value_percentage);
|
||||
frappe.flags.from_set_salvage_value_percentage_or_expected_value_after_useful_life = false;
|
||||
} else if (salvage_value_percentage_changed) {
|
||||
frappe.flags.from_set_salvage_value_percentage_or_expected_value_after_useful_life = true;
|
||||
const new_expected_value_after_useful_life = flt(frm.doc.gross_purchase_amount * (row.salvage_value_percentage / 100), precision('gross_purchase_amount'));
|
||||
frappe.model.set_value(row.doctype, row.name, "expected_value_after_useful_life", new_expected_value_after_useful_life);
|
||||
frappe.flags.from_set_salvage_value_percentage_or_expected_value_after_useful_life = false;
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
frappe.ui.form.on('Asset Finance Book', {
|
||||
@@ -564,19 +516,9 @@ frappe.ui.form.on('Asset Finance Book', {
|
||||
|
||||
expected_value_after_useful_life: function(frm, cdt, cdn) {
|
||||
const row = locals[cdt][cdn];
|
||||
if (!frappe.flags.from_set_salvage_value_percentage_or_expected_value_after_useful_life) {
|
||||
frm.events.set_salvage_value_percentage_or_expected_value_after_useful_life(frm, row, false, true);
|
||||
}
|
||||
frm.events.set_depreciation_rate(frm, row);
|
||||
},
|
||||
|
||||
salvage_value_percentage: function(frm, cdt, cdn) {
|
||||
const row = locals[cdt][cdn];
|
||||
if (!frappe.flags.from_set_salvage_value_percentage_or_expected_value_after_useful_life) {
|
||||
frm.events.set_salvage_value_percentage_or_expected_value_after_useful_life(frm, row, true, false);
|
||||
}
|
||||
},
|
||||
|
||||
frequency_of_depreciation: function(frm, cdt, cdn) {
|
||||
const row = locals[cdt][cdn];
|
||||
frm.events.set_depreciation_rate(frm, row);
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
"asset_owner",
|
||||
"asset_owner_company",
|
||||
"is_existing_asset",
|
||||
"is_composite_asset",
|
||||
"supplier",
|
||||
"customer",
|
||||
"image",
|
||||
@@ -73,8 +72,7 @@
|
||||
"purchase_receipt_amount",
|
||||
"default_finance_book",
|
||||
"depr_entry_posting_status",
|
||||
"amended_from",
|
||||
"capitalized_in"
|
||||
"amended_from"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -201,7 +199,7 @@
|
||||
"fieldtype": "Date",
|
||||
"label": "Purchase Date",
|
||||
"read_only": 1,
|
||||
"read_only_depends_on": "eval:!doc.is_existing_asset && !doc.is_composite_asset",
|
||||
"read_only_depends_on": "eval:!doc.is_existing_asset",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
@@ -221,11 +219,11 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!(doc.is_composite_asset && !doc.capitalized_in)",
|
||||
"fieldname": "gross_purchase_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Gross Purchase Amount",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1,
|
||||
"read_only_depends_on": "eval:!doc.is_existing_asset",
|
||||
"reqd": 1
|
||||
},
|
||||
@@ -239,12 +237,10 @@
|
||||
"default": "0",
|
||||
"fieldname": "calculate_depreciation",
|
||||
"fieldtype": "Check",
|
||||
"label": "Calculate Depreciation",
|
||||
"read_only_depends_on": "eval:doc.is_composite_asset && !doc.gross_purchase_amount"
|
||||
"label": "Calculate Depreciation"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:!doc.is_composite_asset",
|
||||
"fieldname": "is_existing_asset",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Existing Asset"
|
||||
@@ -413,7 +409,6 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.is_composite_asset && !doc.is_existing_asset",
|
||||
"fieldname": "purchase_receipt",
|
||||
"fieldtype": "Link",
|
||||
"label": "Purchase Receipt",
|
||||
@@ -431,7 +426,6 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.is_composite_asset && !doc.is_existing_asset",
|
||||
"fieldname": "purchase_invoice",
|
||||
"fieldtype": "Link",
|
||||
"label": "Purchase Invoice",
|
||||
@@ -495,11 +489,10 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval.doc.asset_quantity",
|
||||
"fieldname": "asset_quantity",
|
||||
"fieldtype": "Int",
|
||||
"label": "Asset Quantity",
|
||||
"read_only": 1
|
||||
"read_only_depends_on": "eval:!doc.is_existing_asset"
|
||||
},
|
||||
{
|
||||
"fieldname": "depr_entry_posting_status",
|
||||
@@ -517,21 +510,6 @@
|
||||
"fieldname": "is_fully_depreciated",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Fully Depreciated"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:!doc.is_existing_asset",
|
||||
"fieldname": "is_composite_asset",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Composite Asset"
|
||||
},
|
||||
{
|
||||
"fieldname": "capitalized_in",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 1,
|
||||
"label": "Capitalized In",
|
||||
"options": "Asset Capitalization",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"idx": 72,
|
||||
@@ -558,14 +536,9 @@
|
||||
"link_doctype": "Journal Entry",
|
||||
"link_fieldname": "reference_name",
|
||||
"table_fieldname": "accounts"
|
||||
},
|
||||
{
|
||||
"group": "Asset Capitalization",
|
||||
"link_doctype": "Asset Capitalization",
|
||||
"link_fieldname": "target_asset"
|
||||
}
|
||||
],
|
||||
"modified": "2023-10-27 17:03:46.629617",
|
||||
"modified": "2023-08-10 20:25:09.913073",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset",
|
||||
|
||||
@@ -204,9 +204,7 @@ class Asset(AccountsController):
|
||||
self.asset_category = frappe.get_cached_value("Item", self.item_code, "asset_category")
|
||||
|
||||
if self.item_code and not self.get("finance_books"):
|
||||
finance_books = get_item_details(
|
||||
self.item_code, self.asset_category, self.gross_purchase_amount
|
||||
)
|
||||
finance_books = get_item_details(self.item_code, self.asset_category)
|
||||
self.set("finance_books", finance_books)
|
||||
|
||||
def validate_finance_books(self):
|
||||
@@ -234,7 +232,7 @@ class Asset(AccountsController):
|
||||
if not self.asset_category:
|
||||
self.asset_category = frappe.get_cached_value("Item", self.item_code, "asset_category")
|
||||
|
||||
if not flt(self.gross_purchase_amount) and not self.is_composite_asset:
|
||||
if not flt(self.gross_purchase_amount):
|
||||
frappe.throw(_("Gross Purchase Amount is mandatory"), frappe.MandatoryError)
|
||||
|
||||
if is_cwip_accounting_enabled(self.asset_category):
|
||||
@@ -1166,15 +1164,6 @@ def create_asset_repair(asset, asset_name):
|
||||
return asset_repair
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_asset_capitalization(asset):
|
||||
asset_capitalization = frappe.new_doc("Asset Capitalization")
|
||||
asset_capitalization.update(
|
||||
{"target_asset": asset, "capitalization_method": "Choose a WIP composite asset"}
|
||||
)
|
||||
return asset_capitalization
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_asset_value_adjustment(asset, asset_category, company):
|
||||
asset_value_adjustment = frappe.new_doc("Asset Value Adjustment")
|
||||
@@ -1206,7 +1195,7 @@ def transfer_asset(args):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_item_details(item_code, asset_category, gross_purchase_amount):
|
||||
def get_item_details(item_code, asset_category):
|
||||
asset_category_doc = frappe.get_doc("Asset Category", asset_category)
|
||||
books = []
|
||||
for d in asset_category_doc.finance_books:
|
||||
@@ -1216,11 +1205,7 @@ def get_item_details(item_code, asset_category, gross_purchase_amount):
|
||||
"depreciation_method": d.depreciation_method,
|
||||
"total_number_of_depreciations": d.total_number_of_depreciations,
|
||||
"frequency_of_depreciation": d.frequency_of_depreciation,
|
||||
"daily_prorata_based": d.daily_prorata_based,
|
||||
"salvage_value_percentage": d.salvage_value_percentage,
|
||||
"expected_value_after_useful_life": flt(gross_purchase_amount)
|
||||
* flt(d.salvage_value_percentage / 100),
|
||||
"depreciation_start_date": d.depreciation_start_date or nowdate(),
|
||||
"start_date": nowdate(),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1396,7 +1381,7 @@ def get_straight_line_or_manual_depr_amount(
|
||||
)
|
||||
# if the Depreciation Schedule is being modified after Asset Value Adjustment due to decrease in asset value
|
||||
elif asset.flags.decrease_in_asset_value_due_to_value_adjustment:
|
||||
if row.daily_prorata_based:
|
||||
if row.daily_depreciation:
|
||||
daily_depr_amount = (
|
||||
flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)
|
||||
) / date_diff(
|
||||
@@ -1441,7 +1426,7 @@ def get_straight_line_or_manual_depr_amount(
|
||||
) / number_of_pending_depreciations
|
||||
# if the Depreciation Schedule is being prepared for the first time
|
||||
else:
|
||||
if row.daily_prorata_based:
|
||||
if row.daily_depreciation:
|
||||
daily_depr_amount = (
|
||||
flt(asset.gross_purchase_amount)
|
||||
- flt(asset.opening_accumulated_depreciation)
|
||||
|
||||
@@ -740,15 +740,6 @@ def get_disposal_account_and_cost_center(company):
|
||||
def get_value_after_depreciation_on_disposal_date(asset, disposal_date, finance_book=None):
|
||||
asset_doc = frappe.get_doc("Asset", asset)
|
||||
|
||||
if asset_doc.available_for_use_date > getdate(disposal_date):
|
||||
frappe.throw(
|
||||
"Disposal date {0} cannot be before available for use date {1} of the asset.".format(
|
||||
disposal_date, asset_doc.available_for_use_date
|
||||
)
|
||||
)
|
||||
elif asset_doc.available_for_use_date == getdate(disposal_date):
|
||||
return flt(asset_doc.gross_purchase_amount - asset_doc.opening_accumulated_depreciation)
|
||||
|
||||
if asset_doc.calculate_depreciation:
|
||||
asset_doc.prepare_depreciation_data(getdate(disposal_date))
|
||||
|
||||
|
||||
@@ -186,7 +186,6 @@ class TestAsset(AssetSetup):
|
||||
def test_is_fixed_asset_set(self):
|
||||
asset = create_asset(is_existing_asset=1)
|
||||
doc = frappe.new_doc("Purchase Invoice")
|
||||
doc.company = "_Test Company"
|
||||
doc.supplier = "_Test Supplier"
|
||||
doc.append("items", {"item_code": "Macbook Pro", "qty": 1, "asset": asset.name})
|
||||
|
||||
@@ -488,7 +487,7 @@ class TestAsset(AssetSetup):
|
||||
|
||||
self.assertEqual("Asset Received But Not Billed - _TC", doc.items[0].expense_account)
|
||||
|
||||
# Capital Work In Progress
|
||||
# CWIP: Capital Work In Progress
|
||||
def test_cwip_accounting(self):
|
||||
pr = make_purchase_receipt(
|
||||
item_code="Macbook Pro", qty=1, rate=5000, do_not_submit=True, location="Test Location"
|
||||
@@ -521,8 +520,7 @@ class TestAsset(AssetSetup):
|
||||
pr.submit()
|
||||
|
||||
expected_gle = (
|
||||
("_Test Account Shipping Charges - _TC", 0.0, 250.0),
|
||||
("Asset Received But Not Billed - _TC", 0.0, 5000.0),
|
||||
("Asset Received But Not Billed - _TC", 0.0, 5250.0),
|
||||
("CWIP Account - _TC", 5250.0, 0.0),
|
||||
)
|
||||
|
||||
@@ -541,8 +539,9 @@ class TestAsset(AssetSetup):
|
||||
expected_gle = (
|
||||
("_Test Account Service Tax - _TC", 250.0, 0.0),
|
||||
("_Test Account Shipping Charges - _TC", 250.0, 0.0),
|
||||
("Asset Received But Not Billed - _TC", 5000.0, 0.0),
|
||||
("Asset Received But Not Billed - _TC", 5250.0, 0.0),
|
||||
("Creditors - _TC", 0.0, 5500.0),
|
||||
("Expenses Included In Asset Valuation - _TC", 0.0, 250.0),
|
||||
)
|
||||
|
||||
pi_gle = frappe.db.sql(
|
||||
@@ -731,9 +730,7 @@ class TestDepreciationMethods(AssetSetup):
|
||||
|
||||
self.assertEqual(schedules, expected_schedules)
|
||||
|
||||
def test_schedule_for_straight_line_method_with_daily_prorata_based(
|
||||
self,
|
||||
):
|
||||
def test_schedule_for_straight_line_method_with_daily_depreciation(self):
|
||||
asset = create_asset(
|
||||
calculate_depreciation=1,
|
||||
available_for_use_date="2023-01-01",
|
||||
@@ -742,7 +739,7 @@ class TestDepreciationMethods(AssetSetup):
|
||||
depreciation_start_date="2023-01-31",
|
||||
total_number_of_depreciations=12,
|
||||
frequency_of_depreciation=1,
|
||||
daily_prorata_based=1,
|
||||
daily_depreciation=1,
|
||||
)
|
||||
|
||||
expected_schedules = [
|
||||
@@ -1689,7 +1686,6 @@ def create_asset(**args):
|
||||
"location": args.location or "Test Location",
|
||||
"asset_owner": args.asset_owner or "Company",
|
||||
"is_existing_asset": args.is_existing_asset or 1,
|
||||
"is_composite_asset": args.is_composite_asset or 0,
|
||||
"asset_quantity": args.get("asset_quantity") or 1,
|
||||
"depr_entry_posting_status": args.depr_entry_posting_status or "",
|
||||
}
|
||||
@@ -1705,7 +1701,7 @@ def create_asset(**args):
|
||||
"total_number_of_depreciations": args.total_number_of_depreciations or 5,
|
||||
"expected_value_after_useful_life": args.expected_value_after_useful_life or 0,
|
||||
"depreciation_start_date": args.depreciation_start_date,
|
||||
"daily_prorata_based": args.daily_prorata_based or 0,
|
||||
"daily_depreciation": args.daily_depreciation or 0,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1734,7 +1730,6 @@ def create_asset_category():
|
||||
"fixed_asset_account": "_Test Fixed Asset - _TC",
|
||||
"accumulated_depreciation_account": "_Test Accumulated Depreciations - _TC",
|
||||
"depreciation_expense_account": "_Test Depreciations - _TC",
|
||||
"capital_work_in_progress_account": "CWIP Account - _TC",
|
||||
},
|
||||
)
|
||||
asset_category.append(
|
||||
|
||||
@@ -15,15 +15,9 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s
|
||||
|
||||
refresh() {
|
||||
this.show_general_ledger();
|
||||
|
||||
if ((this.frm.doc.stock_items && this.frm.doc.stock_items.length) || !this.frm.doc.target_is_fixed_asset) {
|
||||
this.show_stock_ledger();
|
||||
}
|
||||
|
||||
if (this.frm.doc.stock_items && !this.frm.doc.stock_items.length && this.frm.doc.target_asset && this.frm.doc.capitalization_method === "Choose a WIP composite asset") {
|
||||
this.set_consumed_stock_items_tagged_to_wip_composite_asset(this.frm.doc.target_asset);
|
||||
this.get_target_asset_details();
|
||||
}
|
||||
}
|
||||
|
||||
setup_queries() {
|
||||
@@ -40,9 +34,18 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s
|
||||
});
|
||||
|
||||
me.frm.set_query("target_asset", function() {
|
||||
return {
|
||||
filters: {'is_composite_asset': 1, 'docstatus': 0 }
|
||||
var filters = {};
|
||||
|
||||
if (me.frm.doc.target_item_code) {
|
||||
filters['item_code'] = me.frm.doc.target_item_code;
|
||||
}
|
||||
|
||||
filters['status'] = ["not in", ["Draft", "Scrapped", "Sold", "Capitalized", "Decapitalized"]];
|
||||
filters['docstatus'] = 1;
|
||||
|
||||
return {
|
||||
filters: filters
|
||||
};
|
||||
});
|
||||
|
||||
me.frm.set_query("asset", "asset_items", function() {
|
||||
@@ -101,39 +104,6 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s
|
||||
return this.get_target_item_details();
|
||||
}
|
||||
|
||||
target_asset() {
|
||||
if (this.frm.doc.target_asset && this.frm.doc.capitalization_method === "Choose a WIP composite asset") {
|
||||
this.set_consumed_stock_items_tagged_to_wip_composite_asset(this.frm.doc.target_asset);
|
||||
this.get_target_asset_details();
|
||||
}
|
||||
}
|
||||
|
||||
set_consumed_stock_items_tagged_to_wip_composite_asset(asset) {
|
||||
var me = this;
|
||||
|
||||
if (asset) {
|
||||
return me.frm.call({
|
||||
method: "erpnext.assets.doctype.asset_capitalization.asset_capitalization.get_items_tagged_to_wip_composite_asset",
|
||||
args: {
|
||||
asset: asset,
|
||||
},
|
||||
callback: function (r) {
|
||||
if (!r.exc && r.message) {
|
||||
me.frm.clear_table("stock_items");
|
||||
|
||||
for (let item of r.message) {
|
||||
me.frm.add_child("stock_items", item);
|
||||
}
|
||||
|
||||
refresh_field("stock_items");
|
||||
|
||||
me.calculate_totals();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
item_code(doc, cdt, cdn) {
|
||||
var row = frappe.get_doc(cdt, cdn);
|
||||
if (cdt === "Asset Capitalization Stock Item") {
|
||||
@@ -248,26 +218,6 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s
|
||||
}
|
||||
}
|
||||
|
||||
get_target_asset_details() {
|
||||
var me = this;
|
||||
|
||||
if (me.frm.doc.target_asset) {
|
||||
return me.frm.call({
|
||||
method: "erpnext.assets.doctype.asset_capitalization.asset_capitalization.get_target_asset_details",
|
||||
child: me.frm.doc,
|
||||
args: {
|
||||
asset: me.frm.doc.target_asset,
|
||||
company: me.frm.doc.company,
|
||||
},
|
||||
callback: function (r) {
|
||||
if (!r.exc) {
|
||||
me.frm.refresh_fields();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
get_consumed_stock_item_details(row) {
|
||||
var me = this;
|
||||
|
||||
|
||||
@@ -8,25 +8,24 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"title",
|
||||
"company",
|
||||
"naming_series",
|
||||
"entry_type",
|
||||
"target_item_code",
|
||||
"target_asset",
|
||||
"target_item_name",
|
||||
"target_is_fixed_asset",
|
||||
"target_has_batch_no",
|
||||
"target_has_serial_no",
|
||||
"column_break_9",
|
||||
"capitalization_method",
|
||||
"target_item_code",
|
||||
"target_asset_location",
|
||||
"target_asset",
|
||||
"target_asset_name",
|
||||
"target_asset_location",
|
||||
"target_warehouse",
|
||||
"target_qty",
|
||||
"target_stock_uom",
|
||||
"target_batch_no",
|
||||
"target_serial_no",
|
||||
"column_break_5",
|
||||
"company",
|
||||
"finance_book",
|
||||
"posting_date",
|
||||
"posting_time",
|
||||
@@ -58,13 +57,12 @@
|
||||
"label": "Title"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:(doc.target_item_code && !doc.__islocal && doc.capitalization_method !== 'Choose a WIP composite asset') || ((doc.entry_type=='Capitalization' && doc.capitalization_method=='Create a new composite asset') || doc.entry_type=='Decapitalization')",
|
||||
"fieldname": "target_item_code",
|
||||
"fieldtype": "Link",
|
||||
"in_standard_filter": 1,
|
||||
"label": "Target Item Code",
|
||||
"mandatory_depends_on": "eval:(doc.entry_type=='Capitalization' && doc.capitalization_method=='Create a new composite asset') || doc.entry_type=='Decapitalization'",
|
||||
"options": "Item"
|
||||
"options": "Item",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.target_item_code && doc.target_item_name != doc.target_item_code",
|
||||
@@ -88,18 +86,16 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:(doc.target_asset && !doc.__islocal) || (doc.entry_type=='Capitalization' && doc.capitalization_method=='Choose a WIP composite asset')",
|
||||
"fieldname": "target_asset",
|
||||
"fieldtype": "Link",
|
||||
"in_standard_filter": 1,
|
||||
"label": "Target Asset",
|
||||
"mandatory_depends_on": "eval:doc.entry_type=='Capitalization' && doc.capitalization_method=='Choose a WIP composite asset'",
|
||||
"no_copy": 1,
|
||||
"options": "Asset",
|
||||
"read_only_depends_on": "eval:(doc.entry_type=='Decapitalization') || (doc.entry_type=='Capitalization' && doc.capitalization_method=='Create a new composite asset')"
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:(doc.target_asset_name && !doc.__islocal) || (doc.target_asset && doc.entry_type=='Capitalization' && doc.capitalization_method=='Choose a WIP composite asset')",
|
||||
"depends_on": "eval:doc.entry_type=='Capitalization'",
|
||||
"fetch_from": "target_asset.asset_name",
|
||||
"fieldname": "target_asset_name",
|
||||
"fieldtype": "Data",
|
||||
@@ -190,14 +186,12 @@
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"depends_on": "eval:doc.entry_type=='Decapitalization'",
|
||||
"fieldname": "target_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Target Qty",
|
||||
"read_only_depends_on": "eval:doc.entry_type=='Capitalization'"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.entry_type=='Decapitalization'",
|
||||
"fetch_from": "target_item_code.stock_uom",
|
||||
"fieldname": "target_stock_uom",
|
||||
"fieldtype": "Link",
|
||||
@@ -337,26 +331,18 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.entry_type=='Capitalization' && doc.capitalization_method=='Create a new composite asset'",
|
||||
"depends_on": "eval:doc.entry_type=='Capitalization'",
|
||||
"fieldname": "target_asset_location",
|
||||
"fieldtype": "Link",
|
||||
"label": "Target Asset Location",
|
||||
"mandatory_depends_on": "eval:doc.entry_type=='Capitalization' && doc.capitalization_method=='Create a new composite asset'",
|
||||
"options": "Location"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.entry_type=='Capitalization'",
|
||||
"fieldname": "capitalization_method",
|
||||
"fieldtype": "Select",
|
||||
"label": "Capitalization Method",
|
||||
"mandatory_depends_on": "eval:doc.entry_type=='Capitalization'",
|
||||
"options": "\nCreate a new composite asset\nChoose a WIP composite asset"
|
||||
"options": "Location"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-10-03 22:55:59.461456",
|
||||
"modified": "2023-06-22 14:17:07.995120",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Capitalization",
|
||||
|
||||
@@ -53,7 +53,6 @@ class AssetCapitalization(StockController):
|
||||
self.validate_posting_time()
|
||||
self.set_missing_values(for_validate=True)
|
||||
self.validate_target_item()
|
||||
self.validate_target_asset()
|
||||
self.validate_consumed_stock_item()
|
||||
self.validate_consumed_asset_item()
|
||||
self.validate_service_item()
|
||||
@@ -64,12 +63,12 @@ class AssetCapitalization(StockController):
|
||||
|
||||
def before_submit(self):
|
||||
self.validate_source_mandatory()
|
||||
self.create_target_asset()
|
||||
if self.entry_type == "Capitalization":
|
||||
self.create_target_asset()
|
||||
|
||||
def on_submit(self):
|
||||
self.update_stock_ledger()
|
||||
self.make_gl_entries()
|
||||
self.update_target_asset()
|
||||
|
||||
def on_cancel(self):
|
||||
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Repost Item Valuation")
|
||||
@@ -86,11 +85,6 @@ class AssetCapitalization(StockController):
|
||||
if self.meta.has_field(k) and (not self.get(k) or k in force_fields):
|
||||
self.set(k, v)
|
||||
|
||||
target_asset_details = get_target_asset_details(self.target_asset, self.company)
|
||||
for k, v in target_asset_details.items():
|
||||
if self.meta.has_field(k) and (not self.get(k) or k in force_fields):
|
||||
self.set(k, v)
|
||||
|
||||
for d in self.stock_items:
|
||||
args = self.as_dict()
|
||||
args.update(d.as_dict())
|
||||
@@ -152,33 +146,6 @@ class AssetCapitalization(StockController):
|
||||
|
||||
self.validate_item(target_item)
|
||||
|
||||
def validate_target_asset(self):
|
||||
if self.target_asset:
|
||||
target_asset = self.get_asset_for_validation(self.target_asset)
|
||||
|
||||
if not target_asset.is_composite_asset:
|
||||
frappe.throw(_("Target Asset {0} needs to be composite asset").format(target_asset.name))
|
||||
|
||||
if target_asset.item_code != self.target_item_code:
|
||||
frappe.throw(
|
||||
_("Asset {0} does not belong to Item {1}").format(self.target_asset, self.target_item_code)
|
||||
)
|
||||
|
||||
if target_asset.status in ("Scrapped", "Sold", "Capitalized", "Decapitalized"):
|
||||
frappe.throw(
|
||||
_("Target Asset {0} cannot be {1}").format(target_asset.name, target_asset.status)
|
||||
)
|
||||
|
||||
if target_asset.docstatus == 1:
|
||||
frappe.throw(_("Target Asset {0} cannot be submitted").format(target_asset.name))
|
||||
elif target_asset.docstatus == 2:
|
||||
frappe.throw(_("Target Asset {0} cannot be cancelled").format(target_asset.name))
|
||||
|
||||
if target_asset.company != self.company:
|
||||
frappe.throw(
|
||||
_("Target Asset {0} does not belong to company {1}").format(target_asset.name, self.company)
|
||||
)
|
||||
|
||||
def validate_consumed_stock_item(self):
|
||||
for d in self.stock_items:
|
||||
if d.item_code:
|
||||
@@ -203,23 +170,7 @@ class AssetCapitalization(StockController):
|
||||
)
|
||||
|
||||
asset = self.get_asset_for_validation(d.asset)
|
||||
|
||||
if asset.status in ("Draft", "Scrapped", "Sold", "Capitalized", "Decapitalized"):
|
||||
frappe.throw(
|
||||
_("Row #{0}: Consumed Asset {1} cannot be {2}").format(d.idx, asset.name, asset.status)
|
||||
)
|
||||
|
||||
if asset.docstatus == 0:
|
||||
frappe.throw(_("Row #{0}: Consumed Asset {1} cannot be Draft").format(d.idx, asset.name))
|
||||
elif asset.docstatus == 2:
|
||||
frappe.throw(_("Row #{0}: Consumed Asset {1} cannot be cancelled").format(d.idx, asset.name))
|
||||
|
||||
if asset.company != self.company:
|
||||
frappe.throw(
|
||||
_("Row #{0}: Consumed Asset {1} does not belong to company {2}").format(
|
||||
d.idx, asset.name, self.company
|
||||
)
|
||||
)
|
||||
self.validate_asset(asset)
|
||||
|
||||
def validate_service_item(self):
|
||||
for d in self.service_items:
|
||||
@@ -254,12 +205,21 @@ class AssetCapitalization(StockController):
|
||||
|
||||
def get_asset_for_validation(self, asset):
|
||||
return frappe.db.get_value(
|
||||
"Asset",
|
||||
asset,
|
||||
["name", "item_code", "company", "status", "docstatus", "is_composite_asset"],
|
||||
as_dict=1,
|
||||
"Asset", asset, ["name", "item_code", "company", "status", "docstatus"], as_dict=1
|
||||
)
|
||||
|
||||
def validate_asset(self, asset):
|
||||
if asset.status in ("Draft", "Scrapped", "Sold", "Capitalized", "Decapitalized"):
|
||||
frappe.throw(_("Asset {0} is {1}").format(asset.name, asset.status))
|
||||
|
||||
if asset.docstatus == 0:
|
||||
frappe.throw(_("Asset {0} is Draft").format(asset.name))
|
||||
if asset.docstatus == 2:
|
||||
frappe.throw(_("Asset {0} is cancelled").format(asset.name))
|
||||
|
||||
if asset.company != self.company:
|
||||
frappe.throw(_("Asset {0} does not belong to company {1}").format(asset.name, self.company))
|
||||
|
||||
@frappe.whitelist()
|
||||
def set_warehouse_details(self):
|
||||
for d in self.get("stock_items"):
|
||||
@@ -525,25 +485,16 @@ class AssetCapitalization(StockController):
|
||||
)
|
||||
|
||||
def create_target_asset(self):
|
||||
if (
|
||||
self.entry_type != "Capitalization"
|
||||
or self.capitalization_method != "Create a new composite asset"
|
||||
):
|
||||
return
|
||||
|
||||
total_target_asset_value = flt(self.total_value, self.precision("total_value"))
|
||||
|
||||
asset_doc = frappe.new_doc("Asset")
|
||||
asset_doc.company = self.company
|
||||
asset_doc.item_code = self.target_item_code
|
||||
asset_doc.is_composite_asset = 1
|
||||
asset_doc.is_existing_asset = 1
|
||||
asset_doc.location = self.target_asset_location
|
||||
asset_doc.available_for_use_date = self.posting_date
|
||||
asset_doc.purchase_date = self.posting_date
|
||||
asset_doc.gross_purchase_amount = total_target_asset_value
|
||||
asset_doc.purchase_receipt_amount = total_target_asset_value
|
||||
asset_doc.purchase_receipt_amount = total_target_asset_value
|
||||
asset_doc.capitalized_in = self.name
|
||||
asset_doc.flags.ignore_validate = True
|
||||
asset_doc.insert()
|
||||
|
||||
@@ -559,28 +510,6 @@ class AssetCapitalization(StockController):
|
||||
).format(get_link_to_form("Asset", asset_doc.name))
|
||||
)
|
||||
|
||||
def update_target_asset(self):
|
||||
if (
|
||||
self.entry_type != "Capitalization"
|
||||
or self.capitalization_method != "Choose a WIP composite asset"
|
||||
):
|
||||
return
|
||||
|
||||
total_target_asset_value = flt(self.total_value, self.precision("total_value"))
|
||||
|
||||
asset_doc = frappe.get_doc("Asset", self.target_asset)
|
||||
asset_doc.gross_purchase_amount = total_target_asset_value
|
||||
asset_doc.purchase_receipt_amount = total_target_asset_value
|
||||
asset_doc.capitalized_in = self.name
|
||||
asset_doc.flags.ignore_validate = True
|
||||
asset_doc.save()
|
||||
|
||||
frappe.msgprint(
|
||||
_(
|
||||
"Asset {0} has been updated. Please set the depreciation details if any and submit it."
|
||||
).format(get_link_to_form("Asset", asset_doc.name))
|
||||
)
|
||||
|
||||
def restore_consumed_asset_items(self):
|
||||
for item in self.asset_items:
|
||||
asset = frappe.get_doc("Asset", item.asset)
|
||||
@@ -639,33 +568,6 @@ def get_target_item_details(item_code=None, company=None):
|
||||
return out
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_target_asset_details(asset=None, company=None):
|
||||
out = frappe._dict()
|
||||
|
||||
# Get Asset Details
|
||||
asset_details = frappe._dict()
|
||||
if asset:
|
||||
asset_details = frappe.db.get_value("Asset", asset, ["asset_name", "item_code"], as_dict=1)
|
||||
if not asset_details:
|
||||
frappe.throw(_("Asset {0} does not exist").format(asset))
|
||||
|
||||
# Re-set item code from Asset
|
||||
out.target_item_code = asset_details.item_code
|
||||
|
||||
# Set Asset Details
|
||||
out.asset_name = asset_details.asset_name
|
||||
|
||||
if asset_details.item_code:
|
||||
out.target_fixed_asset_account = get_asset_category_account(
|
||||
"fixed_asset_account", item=asset_details.item_code, company=company
|
||||
)
|
||||
else:
|
||||
out.target_fixed_asset_account = None
|
||||
|
||||
return out
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_consumed_stock_item_details(args):
|
||||
if isinstance(args, string_types):
|
||||
@@ -814,26 +716,3 @@ def get_service_item_details(args):
|
||||
)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_items_tagged_to_wip_composite_asset(asset):
|
||||
fields = [
|
||||
"item_code",
|
||||
"item_name",
|
||||
"batch_no",
|
||||
"serial_no",
|
||||
"stock_qty",
|
||||
"stock_uom",
|
||||
"warehouse",
|
||||
"cost_center",
|
||||
"qty",
|
||||
"valuation_rate",
|
||||
"amount",
|
||||
]
|
||||
|
||||
pr_items = frappe.get_all(
|
||||
"Purchase Receipt Item", filters={"wip_composite_asset": asset}, fields=fields
|
||||
)
|
||||
|
||||
return pr_items
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user