Merge branch 'version-14-hotfix' into show-party-values-when-naming-series-unset

This commit is contained in:
Gursheen Kaur Anand
2023-11-17 14:21:52 +05:30
committed by GitHub
245 changed files with 3113 additions and 2286 deletions

View File

@@ -3,7 +3,7 @@ import inspect
import frappe import frappe
__version__ = "14.43.0" __version__ = "14.45.4"
def get_default_company(user=None): def get_default_company(user=None):

View File

@@ -301,3 +301,30 @@ def get_dimensions(with_cost_center_and_project=False):
default_dimensions_map[dimension.company][dimension.fieldname] = dimension.default_dimension default_dimensions_map[dimension.company][dimension.fieldname] = dimension.default_dimension
return dimension_filters, default_dimensions_map 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)

View File

@@ -17,6 +17,7 @@ from erpnext.accounts.report.bank_reconciliation_statement.bank_reconciliation_s
get_entries, get_entries,
) )
from erpnext.accounts.utils import get_balance_on from erpnext.accounts.utils import get_balance_on
from erpnext.setup.utils import get_exchange_rate
class BankReconciliationTool(Document): class BankReconciliationTool(Document):
@@ -129,7 +130,7 @@ def create_journal_entry_bts(
bank_transaction = frappe.db.get_values( bank_transaction = frappe.db.get_values(
"Bank Transaction", "Bank Transaction",
bank_transaction_name, bank_transaction_name,
fieldname=["name", "deposit", "withdrawal", "bank_account"], fieldname=["name", "deposit", "withdrawal", "bank_account", "currency"],
as_dict=True, as_dict=True,
)[0] )[0]
company_account = frappe.get_value("Bank Account", bank_transaction.bank_account, "account") company_account = frappe.get_value("Bank Account", bank_transaction.bank_account, "account")
@@ -143,29 +144,94 @@ def create_journal_entry_bts(
) )
company = frappe.get_value("Account", company_account, "company") 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 = [] accounts = []
# Multi Currency? second_account_dict = {
accounts.append( "account": second_account,
{ "account_currency": second_account_currency,
"account": second_account, "credit_in_account_currency": bank_transaction.deposit,
"credit_in_account_currency": bank_transaction.deposit, "debit_in_account_currency": bank_transaction.withdrawal,
"debit_in_account_currency": bank_transaction.withdrawal, "party_type": party_type,
"party_type": party_type, "party": party,
"party": party, "cost_center": get_default_cost_center(company),
"cost_center": get_default_cost_center(company), }
}
)
accounts.append( company_account_dict = {
{ "account": company_account,
"account": company_account, "account_currency": company_account_currency,
"bank_account": bank_transaction.bank_account, "bank_account": bank_transaction.bank_account,
"credit_in_account_currency": bank_transaction.withdrawal, "credit_in_account_currency": bank_transaction.withdrawal,
"debit_in_account_currency": bank_transaction.deposit, "debit_in_account_currency": bank_transaction.deposit,
"cost_center": get_default_cost_center(company), "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)
journal_entry_dict = { journal_entry_dict = {
"voucher_type": entry_type, "voucher_type": entry_type,
@@ -175,6 +241,9 @@ def create_journal_entry_bts(
"cheque_no": reference_number, "cheque_no": reference_number,
"mode_of_payment": mode_of_payment, "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 = frappe.new_doc("Journal Entry")
journal_entry.update(journal_entry_dict) journal_entry.update(journal_entry_dict)
journal_entry.set("accounts", accounts) journal_entry.set("accounts", accounts)

View File

@@ -2,6 +2,16 @@
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on("Bank Statement Import", { frappe.ui.form.on("Bank Statement Import", {
onload(frm) {
frm.set_query("bank_account", function (doc) {
return {
filters: {
company: doc.company,
},
};
});
},
setup(frm) { setup(frm) {
frappe.realtime.on("data_import_refresh", ({ data_import }) => { frappe.realtime.on("data_import_refresh", ({ data_import }) => {
frm.import_in_progress = false; frm.import_in_progress = false;

View File

@@ -112,7 +112,8 @@ class AutoMatchbyPartyNameDescription:
for party in parties: for party in parties:
filters = {"status": "Active"} if party == "Employee" else {"disabled": 0} filters = {"status": "Active"} if party == "Employee" else {"disabled": 0}
names = frappe.get_all(party, filters=filters, pluck=party.lower() + "_name") field = party.lower() + "_name"
names = frappe.get_all(party, filters=filters, fields=[f"{field} as party_name", "name"])
for field in ["bank_party_name", "description"]: for field in ["bank_party_name", "description"]:
if not self.get(field): if not self.get(field):
@@ -131,7 +132,11 @@ class AutoMatchbyPartyNameDescription:
def fuzzy_search_and_return_result(self, party, names, field) -> Union[Tuple, None]: def fuzzy_search_and_return_result(self, party, names, field) -> Union[Tuple, None]:
skip = False skip = False
result = process.extract(query=self.get(field), choices=names, scorer=fuzz.token_set_ratio) result = process.extract(
query=self.get(field),
choices={row.get("name"): row.get("party_name") for row in names},
scorer=fuzz.token_set_ratio,
)
party_name, skip = self.process_fuzzy_result(result) party_name, skip = self.process_fuzzy_result(result)
if not party_name: if not party_name:
@@ -149,14 +154,14 @@ class AutoMatchbyPartyNameDescription:
Returns: Result, Skip (whether or not to discontinue matching) Returns: Result, Skip (whether or not to discontinue matching)
""" """
PARTY, SCORE, CUTOFF = 0, 1, 80 SCORE, PARTY_ID, CUTOFF = 1, 2, 80
if not result or not len(result): if not result or not len(result):
return None, False return None, False
first_result = result[0] first_result = result[0]
if len(result) == 1: if len(result) == 1:
return (first_result[PARTY] if first_result[SCORE] > CUTOFF else None), True return (first_result[PARTY_ID] if first_result[SCORE] > CUTOFF else None), True
second_result = result[1] second_result = result[1]
if first_result[SCORE] > CUTOFF: if first_result[SCORE] > CUTOFF:
@@ -165,7 +170,7 @@ class AutoMatchbyPartyNameDescription:
if first_result[SCORE] == second_result[SCORE]: if first_result[SCORE] == second_result[SCORE]:
return None, True return None, True
return first_result[PARTY], True return first_result[PARTY_ID], True
else: else:
return None, False return None, False

View File

@@ -89,7 +89,6 @@ class BankTransaction(StatusUpdater):
- 0 > a: Error: already over-allocated - 0 > a: Error: already over-allocated
- clear means: set the latest transaction date as clearance date - 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 remaining_amount = self.unallocated_amount
for payment_entry in self.payment_entries: for payment_entry in self.payment_entries:
if payment_entry.allocated_amount == 0.0: if payment_entry.allocated_amount == 0.0:

View File

@@ -53,10 +53,18 @@ frappe.ui.form.on('Chart of Accounts Importer', {
of Accounts. Please enter the account names and add more rows as per your requirement.`); 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() { primary_action: function() {
var data = d.get_values(); let data = d.get_values();
if (!data.template_type) { if (!data.template_type) {
frappe.throw(__('Please select <b>Template Type</b> to download template')); frappe.throw(__('Please select <b>Template Type</b> to download template'));
@@ -66,7 +74,8 @@ frappe.ui.form.on('Chart of Accounts Importer', {
'/api/method/erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.download_template', '/api/method/erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.download_template',
{ {
file_type: data.file_type, file_type: data.file_type,
template_type: data.template_type template_type: data.template_type,
company: data.company
} }
); );

View File

@@ -8,6 +8,7 @@ from functools import reduce
import frappe import frappe
from frappe import _ from frappe import _
from frappe.desk.form.linked_with import get_linked_fields
from frappe.model.document import Document from frappe.model.document import Document
from frappe.utils import cint, cstr from frappe.utils import cint, cstr
from frappe.utils.csvutils import UnicodeWriter from frappe.utils.csvutils import UnicodeWriter
@@ -294,10 +295,8 @@ def build_response_as_excel(writer):
@frappe.whitelist() @frappe.whitelist()
def download_template(file_type, template_type): def download_template(file_type, template_type, company):
data = frappe._dict(frappe.local.form_dict) writer = get_template(template_type, company)
writer = get_template(template_type)
if file_type == "CSV": if file_type == "CSV":
# download csv file # download csv file
@@ -308,8 +307,7 @@ def download_template(file_type, template_type):
build_response_as_excel(writer) build_response_as_excel(writer)
def get_template(template_type): def get_template(template_type, company):
fields = [ fields = [
"Account Name", "Account Name",
"Parent Account", "Parent Account",
@@ -335,34 +333,17 @@ def get_template(template_type):
["", "", "", "", 0, account_type.get("account_type"), account_type.get("root_type")] ["", "", "", "", 0, account_type.get("account_type"), account_type.get("root_type")]
) )
else: else:
writer = get_sample_template(writer) writer = get_sample_template(writer, company)
return writer return writer
def get_sample_template(writer): def get_sample_template(writer, company):
template = [ currency = frappe.db.get_value("Company", company, "default_currency")
["Application Of Funds(Assets)", "", "", "", 1, "", "Asset"], with open(os.path.join(os.path.dirname(__file__), "coa_sample_template.csv"), "r") as f:
["Sources Of Funds(Liabilities)", "", "", "", 1, "", "Liability"], for row in f:
["Equity", "", "", "", 1, "", "Equity"], row = row.strip().split(",") + [currency]
["Expenses", "", "", "", 1, "", "Expense"], writer.writerow(row)
["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 return writer
@@ -453,14 +434,11 @@ def get_mandatory_account_types():
def unset_existing_data(company): def unset_existing_data(company):
linked = frappe.db.sql(
'''select fieldname from tabDocField
where fieldtype="Link" and options="Account" and parent="Company"''',
as_dict=True,
)
# remove accounts data from company # remove accounts data from company
update_values = {d.fieldname: "" for d in linked}
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}
frappe.db.set_value("Company", company, update_values, update_values) frappe.db.set_value("Company", company, update_values, update_values)
# remove accounts data from various doctypes # remove accounts data from various doctypes

View File

@@ -0,0 +1,17 @@
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
1 Application Of Funds(Assets) 1 Asset
2 Sources Of Funds(Liabilities) 1 Liability
3 Equity 1 Equity
4 Expenses 1 Expense Account Expense
5 Income 1 Income Account Income
6 Bank Accounts Application Of Funds(Assets) 1 Bank Asset
7 Cash In Hand Application Of Funds(Assets) 1 Cash Asset
8 Stock Assets Application Of Funds(Assets) 1 Stock Asset
9 Cost Of Goods Sold Expenses 0 Cost of Goods Sold Expense
10 Asset Depreciation Expenses 0 Depreciation Expense
11 Fixed Assets Application Of Funds(Assets) 0 Fixed Asset Asset
12 Accounts Payable Sources Of Funds(Liabilities) 0 Payable Liability
13 Accounts Receivable Application Of Funds(Assets) 1 Receivable Asset
14 Stock Expenses Expenses 0 Stock Adjustment Expense
15 Sample Bank Bank Accounts 0 Bank Asset
16 Cash Cash In Hand 0 Cash Asset
17 Stores Stock Assets 0 Stock Asset

View File

@@ -152,6 +152,12 @@ frappe.ui.form.on('Payment Entry', {
frm.events.hide_unhide_fields(frm); frm.events.hide_unhide_fields(frm);
frm.events.set_dynamic_labels(frm); frm.events.set_dynamic_labels(frm);
frm.events.show_general_ledger(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); erpnext.accounts.unreconcile_payments.add_unreconcile_btn(frm);
}, },

View File

@@ -13,6 +13,7 @@
"party_type", "party_type",
"party", "party",
"due_date", "due_date",
"voucher_detail_no",
"cost_center", "cost_center",
"finance_book", "finance_book",
"voucher_type", "voucher_type",
@@ -29,7 +30,8 @@
{ {
"fieldname": "posting_date", "fieldname": "posting_date",
"fieldtype": "Date", "fieldtype": "Date",
"label": "Posting Date" "label": "Posting Date",
"search_index": 1
}, },
{ {
"fieldname": "account_type", "fieldname": "account_type",
@@ -63,7 +65,8 @@
"fieldtype": "Link", "fieldtype": "Link",
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Voucher Type", "label": "Voucher Type",
"options": "DocType" "options": "DocType",
"search_index": 1
}, },
{ {
"fieldname": "voucher_no", "fieldname": "voucher_no",
@@ -71,14 +74,16 @@
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Voucher No", "label": "Voucher No",
"options": "voucher_type" "options": "voucher_type",
"search_index": 1
}, },
{ {
"fieldname": "against_voucher_type", "fieldname": "against_voucher_type",
"fieldtype": "Link", "fieldtype": "Link",
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Against Voucher Type", "label": "Against Voucher Type",
"options": "DocType" "options": "DocType",
"search_index": 1
}, },
{ {
"fieldname": "against_voucher_no", "fieldname": "against_voucher_no",
@@ -86,7 +91,8 @@
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Against Voucher No", "label": "Against Voucher No",
"options": "against_voucher_type" "options": "against_voucher_type",
"search_index": 1
}, },
{ {
"fieldname": "amount", "fieldname": "amount",
@@ -142,12 +148,18 @@
"fieldname": "remarks", "fieldname": "remarks",
"fieldtype": "Text", "fieldtype": "Text",
"label": "Remarks" "label": "Remarks"
},
{
"fieldname": "voucher_detail_no",
"fieldtype": "Data",
"label": "Voucher Detail No",
"search_index": 1
} }
], ],
"in_create": 1, "in_create": 1,
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"modified": "2022-08-22 15:32:56.629430", "modified": "2023-11-08 10:53:10.664896",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Payment Ledger Entry", "name": "Payment Ledger Entry",

View File

@@ -216,6 +216,7 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
this.data = []; this.data = [];
const dialog = new frappe.ui.Dialog({ const dialog = new frappe.ui.Dialog({
title: __("Select Difference Account"), title: __("Select Difference Account"),
size: 'extra-large',
fields: [ fields: [
{ {
fieldname: "allocation", fieldname: "allocation",
@@ -239,6 +240,13 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
in_list_view: 1, in_list_view: 1,
read_only: 1 read_only: 1
}, { }, {
fieldtype:'Date',
fieldname:"gain_loss_posting_date",
label: __("Posting Date"),
in_list_view: 1,
reqd: 1,
}, {
fieldtype:'Link', fieldtype:'Link',
options: 'Account', options: 'Account',
in_list_view: 1, in_list_view: 1,
@@ -272,6 +280,9 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
args.forEach(d => { args.forEach(d => {
frappe.model.set_value("Payment Reconciliation Allocation", d.docname, frappe.model.set_value("Payment Reconciliation Allocation", d.docname,
"difference_account", d.difference_account); "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(); this.reconcile_payment_entries();
@@ -287,6 +298,7 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
'reference_name': d.reference_name, 'reference_name': d.reference_name,
'difference_amount': d.difference_amount, 'difference_amount': d.difference_amount,
'difference_account': d.difference_account, 'difference_account': d.difference_account,
'gain_loss_posting_date': d.gain_loss_posting_date
}); });
} }
}); });

View File

@@ -93,6 +93,8 @@ class PaymentReconciliation(Document):
"t2.against_account like %(bank_cash_account)s" if self.bank_cash_account else "1=1" "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 # nosemgrep
journal_entries = frappe.db.sql( journal_entries = frappe.db.sql(
""" """
@@ -116,11 +118,13 @@ class PaymentReconciliation(Document):
ELSE {bank_account_condition} ELSE {bank_account_condition}
END) END)
order by t1.posting_date order by t1.posting_date
{limit}
""".format( """.format(
**{ **{
"dr_or_cr": dr_or_cr, "dr_or_cr": dr_or_cr,
"bank_account_condition": bank_account_condition, "bank_account_condition": bank_account_condition,
"condition": condition, "condition": condition,
"limit": limit,
} }
), ),
{ {
@@ -146,7 +150,7 @@ class PaymentReconciliation(Document):
if self.payment_name: if self.payment_name:
conditions.append(doc.name.like(f"%{self.payment_name}%")) conditions.append(doc.name.like(f"%{self.payment_name}%"))
self.return_invoices = ( self.return_invoices_query = (
qb.from_(doc) qb.from_(doc)
.select( .select(
ConstantColumn(voucher_type).as_("voucher_type"), ConstantColumn(voucher_type).as_("voucher_type"),
@@ -154,8 +158,11 @@ class PaymentReconciliation(Document):
doc.return_against, doc.return_against,
) )
.where(Criterion.all(conditions)) .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): def get_dr_or_cr_notes(self):
@@ -315,6 +322,7 @@ class PaymentReconciliation(Document):
res.difference_amount = self.get_difference_amount(pay, inv, res["allocated_amount"]) res.difference_amount = self.get_difference_amount(pay, inv, res["allocated_amount"])
res.difference_account = default_exchange_gain_loss_account res.difference_account = default_exchange_gain_loss_account
res.exchange_rate = inv.get("exchange_rate") res.exchange_rate = inv.get("exchange_rate")
res.update({"gain_loss_posting_date": pay.get("posting_date")})
if pay.get("amount") == 0: if pay.get("amount") == 0:
entries.append(res) entries.append(res)
@@ -421,6 +429,7 @@ class PaymentReconciliation(Document):
"allocated_amount": flt(row.get("allocated_amount")), "allocated_amount": flt(row.get("allocated_amount")),
"difference_amount": flt(row.get("difference_amount")), "difference_amount": flt(row.get("difference_amount")),
"difference_account": row.get("difference_account"), "difference_account": row.get("difference_account"),
"difference_posting_date": row.get("gain_loss_posting_date"),
"cost_center": row.get("cost_center"), "cost_center": row.get("cost_center"),
} }
) )

View File

@@ -14,6 +14,7 @@ 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.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.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.accounts.party import get_party_account 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 from erpnext.stock.doctype.item.test_item import create_item
test_dependencies = ["Item"] test_dependencies = ["Item"]
@@ -85,26 +86,44 @@ class TestPaymentReconciliation(FrappeTestCase):
self.customer5 = make_customer("_Test PR Customer 5", "EUR") self.customer5 = make_customer("_Test PR Customer 5", "EUR")
def create_account(self): def create_account(self):
account_name = "Debtors EUR" accounts = [
if not frappe.db.get_value( {
"Account", filters={"account_name": account_name, "company": self.company} "attribute": "debtors_eur",
): "account_name": "Debtors EUR",
acc = frappe.new_doc("Account") "parent_account": "Accounts Receivable - _PR",
acc.account_name = account_name "account_currency": "EUR",
acc.parent_account = "Accounts Receivable - _PR" "account_type": "Receivable",
acc.company = self.company },
acc.account_currency = "EUR" {
acc.account_type = "Receivable" "attribute": "creditors_usd",
acc.insert() "account_name": "Payable USD",
else: "parent_account": "Accounts Payable - _PR",
name = frappe.db.get_value( "account_currency": "USD",
"Account", "account_type": "Payable",
filters={"account_name": account_name, "company": self.company}, },
fieldname="name", ]
pluck=True,
) for x in accounts:
acc = frappe.get_doc("Account", name) x = frappe._dict(x)
self.debtors_eur = acc.name 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)
def create_sales_invoice( def create_sales_invoice(
self, qty=1, rate=100, posting_date=nowdate(), do_not_save=False, do_not_submit=False self, qty=1, rate=100, posting_date=nowdate(), do_not_save=False, do_not_submit=False
@@ -151,6 +170,64 @@ class TestPaymentReconciliation(FrappeTestCase):
payment.posting_date = posting_date payment.posting_date = posting_date
return payment 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): def clear_old_entries(self):
doctype_list = [ doctype_list = [
"GL Entry", "GL Entry",
@@ -163,13 +240,11 @@ class TestPaymentReconciliation(FrappeTestCase):
for doctype in doctype_list: for doctype in doctype_list:
qb.from_(qb.DocType(doctype)).delete().where(qb.DocType(doctype).company == self.company).run() qb.from_(qb.DocType(doctype)).delete().where(qb.DocType(doctype).company == self.company).run()
def create_payment_reconciliation(self): def create_payment_reconciliation(self, party_is_customer=True):
pr = frappe.new_doc("Payment Reconciliation") pr = frappe.new_doc("Payment Reconciliation")
pr.company = self.company pr.company = self.company
pr.party_type = ( pr.party_type = "Customer" if party_is_customer else "Supplier"
self.party_type if hasattr(self, "party_type") and self.party_type else "Customer" pr.party = self.customer if party_is_customer else self.supplier
)
pr.party = self.customer
pr.receivable_payable_account = get_party_account(pr.party_type, pr.party, pr.company) 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() pr.from_invoice_date = pr.to_invoice_date = pr.from_payment_date = pr.to_payment_date = nowdate()
return pr return pr
@@ -906,9 +981,13 @@ class TestPaymentReconciliation(FrappeTestCase):
self.assertEqual(pr.allocation[0].difference_amount, 0) self.assertEqual(pr.allocation[0].difference_amount, 0)
def test_reconciliation_purchase_invoice_against_return(self): def test_reconciliation_purchase_invoice_against_return(self):
pi = make_purchase_invoice( self.supplier = "_Test Supplier USD"
supplier="_Test Supplier USD", currency="USD", conversion_rate=50 pi = self.create_purchase_invoice(qty=5, rate=50, do_not_submit=True)
).submit() pi.supplier = self.supplier
pi.currency = "USD"
pi.conversion_rate = 50
pi.credit_to = self.creditors_usd
pi.save().submit()
pi_return = frappe.get_doc(pi.as_dict()) pi_return = frappe.get_doc(pi.as_dict())
pi_return.name = None pi_return.name = None
@@ -918,11 +997,12 @@ class TestPaymentReconciliation(FrappeTestCase):
pi_return.items[0].qty = -pi_return.items[0].qty pi_return.items[0].qty = -pi_return.items[0].qty
pi_return.submit() pi_return.submit()
self.company = "_Test Company" pr = frappe.get_doc("Payment Reconciliation")
self.party_type = "Supplier" pr.company = self.company
self.customer = "_Test Supplier USD" pr.party_type = "Supplier"
pr.party = self.supplier
pr = self.create_payment_reconciliation() pr.receivable_payable_account = self.creditors_usd
pr.from_invoice_date = pr.to_invoice_date = pr.from_payment_date = pr.to_payment_date = nowdate()
pr.get_unreconciled_entries() pr.get_unreconciled_entries()
invoices = [] invoices = []
@@ -931,6 +1011,7 @@ class TestPaymentReconciliation(FrappeTestCase):
if invoice.invoice_number == pi.name: if invoice.invoice_number == pi.name:
invoices.append(invoice.as_dict()) invoices.append(invoice.as_dict())
break break
for payment in pr.payments: for payment in pr.payments:
if payment.reference_name == pi_return.name: if payment.reference_name == pi_return.name:
payments.append(payment.as_dict()) payments.append(payment.as_dict())
@@ -941,6 +1022,121 @@ class TestPaymentReconciliation(FrappeTestCase):
# Should not raise frappe.exceptions.ValidationError: Total Debit must be equal to Total Credit. # Should not raise frappe.exceptions.ValidationError: Total Debit must be equal to Total Credit.
pr.reconcile() 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): def make_customer(customer_name, currency=None):
if not frappe.db.exists("Customer", customer_name): if not frappe.db.exists("Customer", customer_name):

View File

@@ -19,6 +19,7 @@
"is_advance", "is_advance",
"section_break_5", "section_break_5",
"difference_amount", "difference_amount",
"gain_loss_posting_date",
"column_break_7", "column_break_7",
"difference_account", "difference_account",
"exchange_rate", "exchange_rate",
@@ -151,11 +152,16 @@
"fieldtype": "Link", "fieldtype": "Link",
"label": "Cost Center", "label": "Cost Center",
"options": "Cost Center" "options": "Cost Center"
},
{
"fieldname": "gain_loss_posting_date",
"fieldtype": "Date",
"label": "Difference Posting Date"
} }
], ],
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2023-09-03 07:52:33.684217", "modified": "2023-10-23 10:44:56.066303",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Payment Reconciliation Allocation", "name": "Payment Reconciliation Allocation",

View File

@@ -185,6 +185,7 @@
"label": "Image" "label": "Image"
}, },
{ {
"fetch_from": "item_code.image",
"fieldname": "image", "fieldname": "image",
"fieldtype": "Attach", "fieldtype": "Attach",
"hidden": 1, "hidden": 1,
@@ -821,7 +822,7 @@
], ],
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2022-11-02 12:52:39.125295", "modified": "2023-11-14 18:33:22.585715",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "POS Invoice Item", "name": "POS Invoice Item",
@@ -831,4 +832,4 @@
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"states": [] "states": []
} }

View File

@@ -110,7 +110,7 @@
"in_create": 1, "in_create": 1,
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"modified": "2023-04-21 17:36:26.642617", "modified": "2023-11-02 11:32:12.254018",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Process Payment Reconciliation Log", "name": "Process Payment Reconciliation Log",
@@ -125,7 +125,19 @@
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "System Manager", "role": "Accounts Manager",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts User",
"share": 1, "share": 1,
"write": 1 "write": 1
} }

View File

@@ -383,7 +383,8 @@
"label": "Supplier Invoice No", "label": "Supplier Invoice No",
"oldfieldname": "bill_no", "oldfieldname": "bill_no",
"oldfieldtype": "Data", "oldfieldtype": "Data",
"print_hide": 1 "print_hide": 1,
"search_index": 1
}, },
{ {
"fieldname": "column_break_15", "fieldname": "column_break_15",
@@ -406,7 +407,8 @@
"no_copy": 1, "no_copy": 1,
"options": "Purchase Invoice", "options": "Purchase Invoice",
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1,
"search_index": 1
}, },
{ {
"fieldname": "section_addresses", "fieldname": "section_addresses",
@@ -1592,7 +1594,7 @@
"idx": 204, "idx": 204,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2023-10-16 16:24:51.886231", "modified": "2023-11-03 15:47:30.319200",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Purchase Invoice", "name": "Purchase Invoice",

View File

@@ -33,7 +33,7 @@ from erpnext.accounts.general_ledger import (
) )
from erpnext.accounts.party import get_due_date, get_party_account from erpnext.accounts.party import get_due_date, get_party_account
from erpnext.accounts.utils import get_account_currency, get_fiscal_year from erpnext.accounts.utils import get_account_currency, get_fiscal_year
from erpnext.assets.doctype.asset.asset import get_asset_account, is_cwip_accounting_enabled from erpnext.assets.doctype.asset.asset import is_cwip_accounting_enabled
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account 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.buying.utils import check_on_hold_or_closed_status
from erpnext.controllers.accounts_controller import validate_account_head from erpnext.controllers.accounts_controller import validate_account_head
@@ -284,9 +284,6 @@ class PurchaseInvoice(BuyingController):
# in case of auto inventory accounting, # in case of auto inventory accounting,
# expense account is always "Stock Received But Not Billed" for a stock item # expense account is always "Stock Received But Not Billed" for a stock item
# except opening entry, drop-ship entry and fixed asset items # 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 ( if (
auto_accounting_for_stock auto_accounting_for_stock
and item.item_code in stock_items and item.item_code in stock_items
@@ -353,22 +350,26 @@ class PurchaseInvoice(BuyingController):
frappe.msgprint(msg, title=_("Expense Head Changed")) frappe.msgprint(msg, title=_("Expense Head Changed"))
item.expense_account = stock_not_billed_account item.expense_account = stock_not_billed_account
elif item.is_fixed_asset and item.pr_detail:
elif item.is_fixed_asset and not is_cwip_accounting_enabled(asset_category): 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"
)
asset_category_account = get_asset_category_account( asset_category_account = get_asset_category_account(
"fixed_asset_account", item=item.item_code, company=self.company account_type, item=item.item_code, company=self.company
) )
if not asset_category_account: if not asset_category_account:
form_link = get_link_to_form("Asset Category", asset_category) form_link = get_link_to_form("Asset Category", item.asset_category)
throw( throw(
_("Please set Fixed Asset Account in {} against {}.").format(form_link, self.company), _("Please set Fixed Asset Account in {} against {}.").format(form_link, self.company),
title=_("Missing Account"), title=_("Missing Account"),
) )
item.expense_account = asset_category_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: elif not item.expense_account and for_validate:
throw(_("Expense account is mandatory for item {0}").format(item.item_code or item.item_name)) throw(_("Expense account is mandatory for item {0}").format(item.item_code or item.item_name))
@@ -591,12 +592,11 @@ class PurchaseInvoice(BuyingController):
def get_gl_entries(self, warehouse_account=None): def get_gl_entries(self, warehouse_account=None):
self.auto_accounting_for_stock = erpnext.is_perpetual_inventory_enabled(self.company) self.auto_accounting_for_stock = erpnext.is_perpetual_inventory_enabled(self.company)
if self.auto_accounting_for_stock: if self.auto_accounting_for_stock:
self.stock_received_but_not_billed = self.get_company_default("stock_received_but_not_billed") 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: else:
self.stock_received_but_not_billed = None self.stock_received_but_not_billed = None
self.expenses_included_in_valuation = None
self.negative_expense_to_be_booked = 0.0 self.negative_expense_to_be_booked = 0.0
gl_entries = [] gl_entries = []
@@ -605,9 +605,6 @@ class PurchaseInvoice(BuyingController):
self.make_item_gl_entries(gl_entries) self.make_item_gl_entries(gl_entries)
self.make_precision_loss_gl_entry(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_tax_gl_entries(gl_entries)
self.make_internal_transfer_gl_entries(gl_entries) self.make_internal_transfer_gl_entries(gl_entries)
@@ -711,7 +708,11 @@ class PurchaseInvoice(BuyingController):
if item.item_code: if item.item_code:
asset_category = frappe.get_cached_value("Item", item.item_code, "asset_category") 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: if (
self.update_stock
and self.auto_accounting_for_stock
and (item.item_code in stock_items or item.is_fixed_asset)
):
# warehouse account # warehouse account
warehouse_debit_amount = self.make_stock_adjustment_entry( warehouse_debit_amount = self.make_stock_adjustment_entry(
gl_entries, item, voucher_wise_stock_value, account_currency gl_entries, item, voucher_wise_stock_value, account_currency
@@ -826,9 +827,7 @@ class PurchaseInvoice(BuyingController):
) )
) )
elif not item.is_fixed_asset or ( else:
item.is_fixed_asset and not is_cwip_accounting_enabled(asset_category)
):
expense_account = ( expense_account = (
item.expense_account item.expense_account
if (not item.enable_deferred_expense or self.is_return) if (not item.enable_deferred_expense or self.is_return)
@@ -921,40 +920,6 @@ 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 # update gross amount of asset bought through this document
assets = frappe.db.get_all( assets = frappe.db.get_all(
"Asset", filters={"purchase_invoice": self.name, "item_code": item.item_code} "Asset", filters={"purchase_invoice": self.name, "item_code": item.item_code}
@@ -979,11 +944,17 @@ class PurchaseInvoice(BuyingController):
(item.purchase_receipt, valuation_tax_accounts), (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: if not negative_expense_booked_in_pr:
gl_entries.append( gl_entries.append(
self.get_gl_dict( self.get_gl_dict(
{ {
"account": self.stock_received_but_not_billed, "account": stock_rbnb,
"against": self.supplier, "against": self.supplier,
"debit": flt(item.item_tax_amount, item.precision("item_tax_amount")), "debit": flt(item.item_tax_amount, item.precision("item_tax_amount")),
"remarks": self.remarks or _("Accounting Entry for Stock"), "remarks": self.remarks or _("Accounting Entry for Stock"),
@@ -998,156 +969,12 @@ class PurchaseInvoice(BuyingController):
item.item_tax_amount, item.precision("item_tax_amount") item.item_tax_amount, item.precision("item_tax_amount")
) )
def get_asset_gl_entry(self, gl_entries): assets = frappe.db.get_all(
arbnb_account = None "Asset", filters={"purchase_invoice": self.name, "item_code": item.item_code}
eiiav_account = None )
asset_eiiav_currency = None for asset in assets:
frappe.db.set_value("Asset", asset.name, "gross_purchase_amount", flt(item.valuation_rate))
for item in self.get("items"): frappe.db.set_value("Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate))
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( def make_stock_adjustment_entry(
self, gl_entries, item, voucher_wise_stock_value, account_currency self, gl_entries, item, voucher_wise_stock_value, account_currency
@@ -1849,6 +1676,7 @@ def make_purchase_receipt(source_name, target_doc=None):
"po_detail": "purchase_order_item", "po_detail": "purchase_order_item",
"material_request": "material_request", "material_request": "material_request",
"material_request_item": "material_request_item", "material_request_item": "material_request_item",
"wip_composite_asset": "wip_composite_asset",
}, },
"postprocess": update_item, "postprocess": update_item,
"condition": lambda doc: abs(doc.received_qty) < abs(doc.qty), "condition": lambda doc: abs(doc.received_qty) < abs(doc.qty),

View File

@@ -1718,9 +1718,14 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
self.assertTrue(return_pi.docstatus == 1) self.assertTrue(return_pi.docstatus == 1)
def test_gl_entries_for_standalone_debit_note(self): def test_gl_entries_for_standalone_debit_note(self):
make_purchase_invoice(qty=5, rate=500, update_stock=True) from erpnext.stock.doctype.item.test_item import make_item
returned_inv = make_purchase_invoice(qty=-5, rate=5, update_stock=True, is_return=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
)
# override the rate with valuation rate # override the rate with valuation rate
sle = frappe.get_all( sle = frappe.get_all(
@@ -1730,7 +1735,7 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
)[0] )[0]
rate = flt(sle.stock_value_difference) / flt(sle.actual_qty) rate = flt(sle.stock_value_difference) / flt(sle.actual_qty)
self.assertAlmostEqual(returned_inv.items[0].rate, rate) self.assertAlmostEqual(rate, 500)
def test_payment_allocation_for_payment_terms(self): def test_payment_allocation_for_payment_terms(self):
from erpnext.buying.doctype.purchase_order.test_purchase_order import ( from erpnext.buying.doctype.purchase_order.test_purchase_order import (
@@ -1833,6 +1838,12 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
disable_dimension() disable_dimension()
def test_repost_accounting_entries(self): 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( pi = make_purchase_invoice(
rate=1000, rate=1000,
price_list_rate=1000, price_list_rate=1000,
@@ -1858,6 +1869,30 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
pi.load_from_db() pi.load_from_db()
self.assertFalse(pi.repost_required) 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( def check_gl_entries(
doc, doc,

View File

@@ -156,6 +156,7 @@
"width": "300px" "width": "300px"
}, },
{ {
"fetch_from": "item_code.image",
"fieldname": "image", "fieldname": "image",
"fieldtype": "Attach", "fieldtype": "Attach",
"hidden": 1, "hidden": 1,
@@ -890,7 +891,7 @@
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2023-10-03 21:01:01.824892", "modified": "2023-11-14 18:33:48.547297",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Purchase Invoice Item", "name": "Purchase Invoice Item",

View File

@@ -5,9 +5,7 @@ frappe.ui.form.on("Repost Accounting Ledger", {
setup: function(frm) { setup: function(frm) {
frm.fields_dict['vouchers'].grid.get_field('voucher_type').get_query = function(doc) { frm.fields_dict['vouchers'].grid.get_field('voucher_type').get_query = function(doc) {
return { return {
filters: { query: "erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger.get_repost_allowed_types"
name: ['in', ['Purchase Invoice', 'Sales Invoice', 'Payment Entry', 'Journal Entry']],
}
} }
} }

View File

@@ -10,9 +10,12 @@ from frappe.utils.data import comma_and
class RepostAccountingLedger(Document): class RepostAccountingLedger(Document):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(RepostAccountingLedger, self).__init__(*args, **kwargs) super(RepostAccountingLedger, self).__init__(*args, **kwargs)
self._allowed_types = set( self._allowed_types = [
["Purchase Invoice", "Sales Invoice", "Payment Entry", "Journal Entry"] x.document_type
) for x in frappe.db.get_all(
"Repost Allowed Types", filters={"allowed": True}, fields=["distinct(document_type)"]
)
]
def validate(self): def validate(self):
self.validate_vouchers() self.validate_vouchers()
@@ -157,7 +160,7 @@ def start_repost(account_repost_doc=str) -> None:
doc.docstatus = 1 doc.docstatus = 1
doc.make_gl_entries() doc.make_gl_entries()
elif doc.doctype in ["Payment Entry", "Journal Entry"]: elif doc.doctype in ["Payment Entry", "Journal Entry", "Expense Claim"]:
if not repost_doc.delete_cancelled_entries: if not repost_doc.delete_cancelled_entries:
doc.make_gl_entries(1) doc.make_gl_entries(1)
doc.make_gl_entries() doc.make_gl_entries()
@@ -186,3 +189,18 @@ def validate_docs_for_deferred_accounting(sales_docs, purchase_docs):
frappe.bold(comma_and([x[0] for x in docs_with_deferred_expense + docs_with_deferred_revenue])) 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 []

View File

@@ -20,10 +20,18 @@ class TestRepostAccountingLedger(AccountsTestMixin, FrappeTestCase):
self.create_company() self.create_company()
self.create_customer() self.create_customer()
self.create_item() self.create_item()
self.update_repost_settings()
def teadDown(self): def teadDown(self):
frappe.db.rollback() 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): def test_01_basic_functions(self):
si = create_sales_invoice( si = create_sales_invoice(
item=self.item, item=self.item,

View File

@@ -0,0 +1,8 @@
// 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) {
// },
// });

View File

@@ -0,0 +1,46 @@
{
"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
}

View File

@@ -0,0 +1,9 @@
# 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

View File

@@ -0,0 +1,9 @@
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
# import frappe
from frappe.tests.utils import FrappeTestCase
class TestRepostAccountingLedgerSettings(FrappeTestCase):
pass

View File

@@ -0,0 +1,45 @@
{
"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": []
}

View File

@@ -0,0 +1,9 @@
# 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

View File

@@ -181,7 +181,6 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
erpnext.accounts.unreconcile_payments.add_unreconcile_btn(me.frm); erpnext.accounts.unreconcile_payments.add_unreconcile_btn(me.frm);
} }
make_maintenance_schedule() { make_maintenance_schedule() {
frappe.model.open_mapped_doc({ frappe.model.open_mapped_doc({
method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.make_maintenance_schedule", method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.make_maintenance_schedule",
@@ -557,15 +556,6 @@ 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 // Cost Center in Details Table
// ----------------------------- // -----------------------------
cur_frm.fields_dict["items"].grid.get_field("cost_center").get_query = function(doc) { cur_frm.fields_dict["items"].grid.get_field("cost_center").get_query = function(doc) {
@@ -660,6 +650,16 @@ 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 = { frm.custom_make_buttons = {
'Delivery Note': 'Delivery', 'Delivery Note': 'Delivery',
'Sales Invoice': 'Return / Credit Note', 'Sales Invoice': 'Return / Credit Note',

View File

@@ -26,6 +26,7 @@
"is_return", "is_return",
"return_against", "return_against",
"update_billed_amount_in_sales_order", "update_billed_amount_in_sales_order",
"update_billed_amount_in_delivery_note",
"is_debit_note", "is_debit_note",
"amended_from", "amended_from",
"accounting_dimensions_section", "accounting_dimensions_section",
@@ -2144,6 +2145,13 @@
"fieldname": "use_company_roundoff_cost_center", "fieldname": "use_company_roundoff_cost_center",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Use Company default Cost Center for Round off" "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", "icon": "fa fa-file-text",
@@ -2156,7 +2164,7 @@
"link_fieldname": "consolidated_invoice" "link_fieldname": "consolidated_invoice"
} }
], ],
"modified": "2023-06-19 16:02:05.309332", "modified": "2023-11-03 14:39:38.012346",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Sales Invoice", "name": "Sales Invoice",

View File

@@ -261,6 +261,7 @@ class SalesInvoice(SellingController):
self.update_status_updater_args() self.update_status_updater_args()
self.update_prevdoc_status() self.update_prevdoc_status()
self.update_billing_status_in_dn() self.update_billing_status_in_dn()
self.clear_unallocated_mode_of_payments() self.clear_unallocated_mode_of_payments()
@@ -1036,7 +1037,7 @@ class SalesInvoice(SellingController):
def make_customer_gl_entry(self, gl_entries): def make_customer_gl_entry(self, gl_entries):
# Checked both rounding_adjustment and rounded_total # Checked both rounding_adjustment and rounded_total
# because rounded_total had value even before introcution of posting GLE based on rounded total # because rounded_total had value even before introduction of posting GLE based on rounded total
grand_total = ( grand_total = (
self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total
) )
@@ -1271,7 +1272,7 @@ class SalesInvoice(SellingController):
if skip_change_gl_entries and payment_mode.account == self.account_for_change_amount: if skip_change_gl_entries and payment_mode.account == self.account_for_change_amount:
payment_mode.base_amount -= flt(self.change_amount) payment_mode.base_amount -= flt(self.change_amount)
if payment_mode.amount: if payment_mode.base_amount:
# POS, make payment entries # POS, make payment entries
gl_entries.append( gl_entries.append(
self.get_gl_dict( self.get_gl_dict(
@@ -1433,6 +1434,8 @@ class SalesInvoice(SellingController):
) )
def update_billing_status_in_dn(self, update_modified=True): 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 = [] updated_delivery_notes = []
for d in self.get("items"): for d in self.get("items"):
if d.dn_detail: if d.dn_detail:

View File

@@ -2497,12 +2497,6 @@ class TestSalesInvoice(FrappeTestCase):
"stock_received_but_not_billed", "stock_received_but_not_billed",
"Stock Received But Not Billed - _TC1", "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 # begin test
si = create_sales_invoice( si = create_sales_invoice(
@@ -2540,7 +2534,7 @@ class TestSalesInvoice(FrappeTestCase):
# tear down # tear down
frappe.local.enable_perpetual_inventory["_Test Company 1"] = old_perpetual_inventory frappe.local.enable_perpetual_inventory["_Test Company 1"] = old_perpetual_inventory
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", old_negative_stock) frappe.db.set_single_value("Stock Settings", "allow_negative_stock", old_negative_stock)
def test_sle_for_target_warehouse(self): def test_sle_for_target_warehouse(self):
se = make_stock_entry( se = make_stock_entry(
@@ -2552,6 +2546,7 @@ class TestSalesInvoice(FrappeTestCase):
) )
si = frappe.copy_doc(test_records[0]) si = frappe.copy_doc(test_records[0])
si.customer = "_Test Internal Customer 3"
si.update_stock = 1 si.update_stock = 1
si.set_warehouse = "Finished Goods - _TC" si.set_warehouse = "Finished Goods - _TC"
si.set_target_warehouse = "Stores - _TC" si.set_target_warehouse = "Stores - _TC"
@@ -3579,6 +3574,20 @@ def create_internal_parties():
allowed_to_interact_with="_Test Company with perpetual inventory", 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( create_internal_supplier(
supplier_name="_Test Internal Supplier", supplier_name="_Test Internal Supplier",
represents_company="Wind Power LLC", represents_company="Wind Power LLC",

View File

@@ -167,6 +167,7 @@
"print_hide": 1 "print_hide": 1
}, },
{ {
"fetch_from": "item_code.image",
"fieldname": "image", "fieldname": "image",
"fieldtype": "Attach", "fieldtype": "Attach",
"hidden": 1, "hidden": 1,
@@ -891,7 +892,7 @@
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2023-07-25 11:58:10.723833", "modified": "2023-11-14 18:34:10.479329",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Sales Invoice Item", "name": "Sales Invoice Item",
@@ -901,4 +902,4 @@
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"states": [] "states": []
} }

View File

@@ -18,6 +18,14 @@ frappe.ui.form.on('Subscription', {
} }
}; };
}); });
frm.set_query('sales_tax_template', function () {
return {
filters: {
company: frm.doc.company
}
};
});
}, },
refresh: function(frm) { refresh: function(frm) {

View File

@@ -41,7 +41,7 @@ def make_gl_entries(
from_repost=from_repost, from_repost=from_repost,
) )
save_entries(gl_map, adv_adj, update_outstanding, from_repost) save_entries(gl_map, adv_adj, update_outstanding, from_repost)
# Post GL Map proccess there may no be any GL Entries # Post GL Map process there may no be any GL Entries
elif gl_map: elif gl_map:
frappe.throw( frappe.throw(
_( _(

View File

@@ -5,13 +5,8 @@
from typing import Optional from typing import Optional
import frappe import frappe
from frappe import _, msgprint, scrub from frappe import _, msgprint, qb, scrub
from frappe.contacts.doctype.address.address import ( from frappe.contacts.doctype.address.address import get_company_address, get_default_address
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.core.doctype.user_permission.user_permission import get_permitted_documents
from frappe.model.utils import get_fetch_values from frappe.model.utils import get_fetch_values
from frappe.query_builder.functions import Abs, Date, Sum from frappe.query_builder.functions import Abs, Date, Sum
@@ -133,6 +128,7 @@ def _get_party_details(
party_address, party_address,
company_address, company_address,
shipping_address, shipping_address,
ignore_permissions=ignore_permissions,
) )
set_contact_details(party_details, party, party_type) set_contact_details(party_details, party, party_type)
set_other_values(party_details, party, party_type) set_other_values(party_details, party, party_type)
@@ -193,6 +189,8 @@ def set_address_details(
party_address=None, party_address=None,
company_address=None, company_address=None,
shipping_address=None, shipping_address=None,
*,
ignore_permissions=False
): ):
billing_address_field = ( billing_address_field = (
"customer_address" if party_type == "Lead" else party_type.lower() + "_address" "customer_address" if party_type == "Lead" else party_type.lower() + "_address"
@@ -205,13 +203,17 @@ def set_address_details(
get_fetch_values(doctype, billing_address_field, party_details[billing_address_field]) get_fetch_values(doctype, billing_address_field, party_details[billing_address_field])
) )
# address display # address display
party_details.address_display = get_address_display(party_details[billing_address_field]) party_details.address_display = render_address(
party_details[billing_address_field], check_permissions=not ignore_permissions
)
# shipping address # shipping address
if party_type in ["Customer", "Lead"]: if party_type in ["Customer", "Lead"]:
party_details.shipping_address_name = shipping_address or get_party_shipping_address( party_details.shipping_address_name = shipping_address or get_party_shipping_address(
party_type, party.name party_type, party.name
) )
party_details.shipping_address = get_address_display(party_details["shipping_address_name"]) party_details.shipping_address = render_address(
party_details["shipping_address_name"], check_permissions=not ignore_permissions
)
if doctype: if doctype:
party_details.update( party_details.update(
get_fetch_values(doctype, "shipping_address_name", party_details.shipping_address_name) get_fetch_values(doctype, "shipping_address_name", party_details.shipping_address_name)
@@ -229,7 +231,7 @@ def set_address_details(
if shipping_address: if shipping_address:
party_details.update( party_details.update(
shipping_address=shipping_address, shipping_address=shipping_address,
shipping_address_display=get_address_display(shipping_address), shipping_address_display=render_address(shipping_address),
**get_fetch_values(doctype, "shipping_address", shipping_address) **get_fetch_values(doctype, "shipping_address", shipping_address)
) )
@@ -238,7 +240,8 @@ def set_address_details(
party_details.update( party_details.update(
billing_address=party_details.company_address, billing_address=party_details.company_address,
billing_address_display=( billing_address_display=(
party_details.company_address_display or get_address_display(party_details.company_address) party_details.company_address_display
or render_address(party_details.company_address, check_permissions=False)
), ),
**get_fetch_values(doctype, "billing_address", party_details.company_address) **get_fetch_values(doctype, "billing_address", party_details.company_address)
) )
@@ -290,7 +293,34 @@ def set_contact_details(party_details, party, party_type):
} }
) )
else: else:
party_details.update(get_contact_details(party_details.contact_person)) 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)
def set_other_values(party_details, party, party_type): def set_other_values(party_details, party, party_type):
@@ -429,11 +459,19 @@ def get_party_account_currency(party_type, party, company):
def get_party_gle_currency(party_type, party, company): def get_party_gle_currency(party_type, party, company):
def generator(): def generator():
existing_gle_currency = frappe.db.sql( gl = qb.DocType("GL Entry")
"""select account_currency from `tabGL Entry` existing_gle_currency = (
where docstatus=1 and company=%(company)s and party_type=%(party_type)s and party=%(party)s qb.from_(gl)
limit 1""", .select(gl.account_currency)
{"company": company, "party_type": party_type, "party": party}, .where(
(gl.docstatus == 1)
& (gl.company == company)
& (gl.party_type == party_type)
& (gl.party == party)
& (gl.is_cancelled == 0)
)
.limit(1)
.run()
) )
return existing_gle_currency[0][0] if existing_gle_currency else None return existing_gle_currency[0][0] if existing_gle_currency else None
@@ -957,3 +995,13 @@ def add_party_account(party_type, party, company, account):
doc.append("accounts", accounts) doc.append("accounts", accounts)
doc.save() 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)

View File

@@ -143,7 +143,18 @@ frappe.query_reports["Accounts Payable"] = {
"fieldname": "show_future_payments", "fieldname": "show_future_payments",
"label": __("Show Future Payments"), "label": __("Show Future Payments"),
"fieldtype": "Check", "fieldtype": "Check",
},
{
"fieldname": "for_revaluation_journals",
"label": __("Revaluation Journals"),
"fieldtype": "Check",
},
{
"fieldname": "ignore_accounts",
"label": __("Group by Voucher"),
"fieldtype": "Check",
} }
], ],
"formatter": function(value, row, column, data, default_formatter) { "formatter": function(value, row, column, data, default_formatter) {
@@ -175,4 +186,4 @@ function get_party_type_options() {
}); });
}); });
return options; return options;
} }

View File

@@ -110,6 +110,11 @@ frappe.query_reports["Accounts Payable Summary"] = {
"fieldname":"based_on_payment_terms", "fieldname":"based_on_payment_terms",
"label": __("Based On Payment Terms"), "label": __("Based On Payment Terms"),
"fieldtype": "Check", "fieldtype": "Check",
},
{
"fieldname": "for_revaluation_journals",
"label": __("Revaluation Journals"),
"fieldtype": "Check",
} }
], ],

View File

@@ -114,10 +114,13 @@ frappe.query_reports["Accounts Receivable"] = {
"reqd": 1 "reqd": 1
}, },
{ {
"fieldname": "customer_group", "fieldname":"customer_group",
"label": __("Customer Group"), "label": __("Customer Group"),
"fieldtype": "Link", "fieldtype": "MultiSelectList",
"options": "Customer Group" "options": "Customer Group",
get_data: function(txt) {
return frappe.db.get_link_options('Customer Group', txt);
}
}, },
{ {
"fieldname": "payment_terms_template", "fieldname": "payment_terms_template",
@@ -172,7 +175,19 @@ frappe.query_reports["Accounts Receivable"] = {
"fieldname": "show_remarks", "fieldname": "show_remarks",
"label": __("Show Remarks"), "label": __("Show Remarks"),
"fieldtype": "Check", "fieldtype": "Check",
},
{
"fieldname": "for_revaluation_journals",
"label": __("Revaluation Journals"),
"fieldtype": "Check",
},
{
"fieldname": "ignore_accounts",
"label": __("Group by Voucher"),
"fieldtype": "Check",
} }
], ],
"formatter": function(value, row, column, data, default_formatter) { "formatter": function(value, row, column, data, default_formatter) {
@@ -205,4 +220,4 @@ function get_party_type_options() {
}); });
}); });
return options; return options;
} }

View File

@@ -116,7 +116,12 @@ class ReceivablePayableReport(object):
# build all keys, since we want to exclude vouchers beyond the report date # build all keys, since we want to exclude vouchers beyond the report date
for ple in self.ple_entries: for ple in self.ple_entries:
# get the balance object for voucher_type # get the balance object for voucher_type
key = (ple.account, ple.voucher_type, ple.voucher_no, ple.party)
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)
if not key in self.voucher_balance: if not key in self.voucher_balance:
self.voucher_balance[key] = frappe._dict( self.voucher_balance[key] = frappe._dict(
voucher_type=ple.voucher_type, voucher_type=ple.voucher_type,
@@ -183,7 +188,10 @@ class ReceivablePayableReport(object):
): ):
return return
key = (ple.account, ple.against_voucher_type, ple.against_voucher_no, ple.party) 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)
# If payment is made against credit note # If payment is made against credit note
# and credit note is made against a Sales Invoice # and credit note is made against a Sales Invoice
@@ -192,13 +200,19 @@ class ReceivablePayableReport(object):
if ple.against_voucher_no in self.return_entries: if ple.against_voucher_no in self.return_entries:
return_against = self.return_entries.get(ple.against_voucher_no) return_against = self.return_entries.get(ple.against_voucher_no)
if return_against: if return_against:
key = (ple.account, ple.against_voucher_type, return_against, ple.party) 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)
row = self.voucher_balance.get(key) row = self.voucher_balance.get(key)
if not row: if not row:
# no invoice, this is an invoice / stand-alone payment / credit note # no invoice, this is an invoice / stand-alone payment / credit note
row = self.voucher_balance.get((ple.account, ple.voucher_type, ple.voucher_no, ple.party)) 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.party_type = ple.party_type row.party_type = ple.party_type
return row return row
@@ -267,11 +281,20 @@ class ReceivablePayableReport(object):
row.invoice_grand_total = row.invoiced row.invoice_grand_total = row.invoiced
if (abs(row.outstanding) > 1.0 / 10**self.currency_precision) and ( must_consider = False
(abs(row.outstanding_in_account_currency) > 1.0 / 10**self.currency_precision) if self.filters.get("for_revaluation_journals"):
or (row.voucher_no in self.err_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 must_consider:
# non-zero oustanding, we must consider this row # non-zero oustanding, we must consider this row
if self.is_invoice(row) and self.filters.based_on_payment_terms: if self.is_invoice(row) and self.filters.based_on_payment_terms:
@@ -718,6 +741,7 @@ class ReceivablePayableReport(object):
query = ( query = (
qb.from_(ple) qb.from_(ple)
.select( .select(
ple.name,
ple.account, ple.account,
ple.voucher_type, ple.voucher_type,
ple.voucher_no, ple.voucher_no,
@@ -731,13 +755,15 @@ class ReceivablePayableReport(object):
ple.account_currency, ple.account_currency,
ple.amount, ple.amount,
ple.amount_in_account_currency, ple.amount_in_account_currency,
ple.remarks,
) )
.where(ple.delinked == 0) .where(ple.delinked == 0)
.where(Criterion.all(self.qb_selection_filter)) .where(Criterion.all(self.qb_selection_filter))
.where(Criterion.any(self.or_filters)) .where(Criterion.any(self.or_filters))
) )
if self.filters.get("show_remarks"):
query = query.select(ple.remarks)
if self.filters.get("group_by_party"): if self.filters.get("group_by_party"):
query = query.orderby(self.ple.party, self.ple.posting_date) query = query.orderby(self.ple.party, self.ple.posting_date)
else: else:
@@ -823,7 +849,13 @@ class ReceivablePayableReport(object):
self.customer = qb.DocType("Customer") self.customer = qb.DocType("Customer")
if self.filters.get("customer_group"): if self.filters.get("customer_group"):
self.get_hierarchical_filters("Customer Group", "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))
if self.filters.get("territory"): if self.filters.get("territory"):
self.get_hierarchical_filters("Territory", "territory") self.get_hierarchical_filters("Territory", "territory")
@@ -1115,3 +1147,19 @@ class ReceivablePayableReport(object):
.run() .run()
) )
self.err_journals = [x[0] for x in results] if results else [] 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))

View File

@@ -475,6 +475,30 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
report = execute(filters)[1] report = execute(filters)[1]
self.assertEqual(len(report), 0) 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): def test_party_account_filter(self):
si1 = self.create_sales_invoice() si1 = self.create_sales_invoice()
self.customer2 = ( self.customer2 = (

View File

@@ -139,6 +139,11 @@ frappe.query_reports["Accounts Receivable Summary"] = {
"label": __("Show GL Balance"), "label": __("Show GL Balance"),
"fieldtype": "Check", "fieldtype": "Check",
}, },
{
"fieldname": "for_revaluation_journals",
"label": __("Revaluation Journals"),
"fieldtype": "Check",
}
], ],
onload: function(report) { onload: function(report) {

View File

@@ -31,6 +31,18 @@ frappe.query_reports["Asset Depreciation Ledger"] = {
"fieldtype": "Link", "fieldtype": "Link",
"options": "Asset" "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", "fieldname":"finance_book",
"label": __("Finance Book"), "label": __("Finance Book"),
@@ -38,10 +50,10 @@ frappe.query_reports["Asset Depreciation Ledger"] = {
"options": "Finance Book" "options": "Finance Book"
}, },
{ {
"fieldname":"asset_category", "fieldname": "include_default_book_assets",
"label": __("Asset Category"), "label": __("Include Default FB Assets"),
"fieldtype": "Link", "fieldtype": "Check",
"options": "Asset Category" "default": 1
} },
] ]
} }

View File

@@ -1,15 +1,15 @@
{ {
"add_total_row": 1, "add_total_row": 0,
"columns": [], "columns": [],
"creation": "2016-04-08 14:49:58.133098", "creation": "2016-04-08 14:49:58.133098",
"disabled": 0, "disabled": 0,
"docstatus": 0, "docstatus": 0,
"doctype": "Report", "doctype": "Report",
"filters": [], "filters": [],
"idx": 2, "idx": 6,
"is_standard": "Yes", "is_standard": "Yes",
"letterhead": null, "letterhead": null,
"modified": "2023-07-26 21:05:33.554778", "modified": "2023-11-08 20:17:05.774211",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Asset Depreciation Ledger", "name": "Asset Depreciation Ledger",

View File

@@ -4,7 +4,7 @@
import frappe import frappe
from frappe import _ from frappe import _
from frappe.utils import flt from frappe.utils import cstr, flt
def execute(filters=None): def execute(filters=None):
@@ -32,7 +32,6 @@ def get_data(filters):
filters_data.append(["against_voucher", "=", filters.get("asset")]) filters_data.append(["against_voucher", "=", filters.get("asset")])
if filters.get("asset_category"): if filters.get("asset_category"):
assets = frappe.db.sql_list( assets = frappe.db.sql_list(
"""select name from tabAsset """select name from tabAsset
where asset_category = %s and docstatus=1""", where asset_category = %s and docstatus=1""",
@@ -41,12 +40,27 @@ def get_data(filters):
filters_data.append(["against_voucher", "in", assets]) filters_data.append(["against_voucher", "in", assets])
if filters.get("finance_book"): company_fb = frappe.get_cached_value("Company", filters.get("company"), "default_finance_book")
filters_data.append(["finance_book", "in", ["", filters.get("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"]]
gl_entries = frappe.get_all( gl_entries = frappe.get_all(
"GL Entry", "GL Entry",
filters=filters_data, filters=filters_data,
or_filters=or_filters_data,
fields=["against_voucher", "debit_in_account_currency as debit", "voucher_no", "posting_date"], fields=["against_voucher", "debit_in_account_currency as debit", "voucher_no", "posting_date"],
order_by="against_voucher, posting_date", order_by="against_voucher, posting_date",
) )
@@ -61,7 +75,9 @@ def get_data(filters):
asset_data = assets_details.get(d.against_voucher) asset_data = assets_details.get(d.against_voucher)
if asset_data: if asset_data:
if not asset_data.get("accumulated_depreciation_amount"): if not asset_data.get("accumulated_depreciation_amount"):
asset_data.accumulated_depreciation_amount = d.debit asset_data.accumulated_depreciation_amount = d.debit + asset_data.get(
"opening_accumulated_depreciation"
)
else: else:
asset_data.accumulated_depreciation_amount += d.debit asset_data.accumulated_depreciation_amount += d.debit
@@ -70,7 +86,7 @@ def get_data(filters):
{ {
"depreciation_amount": d.debit, "depreciation_amount": d.debit,
"depreciation_date": d.posting_date, "depreciation_date": d.posting_date,
"amount_after_depreciation": ( "value_after_depreciation": (
flt(row.gross_purchase_amount) - flt(row.accumulated_depreciation_amount) flt(row.gross_purchase_amount) - flt(row.accumulated_depreciation_amount)
), ),
"depreciation_entry": d.voucher_no, "depreciation_entry": d.voucher_no,
@@ -88,10 +104,12 @@ def get_assets_details(assets):
fields = [ fields = [
"name as asset", "name as asset",
"gross_purchase_amount", "gross_purchase_amount",
"opening_accumulated_depreciation",
"asset_category", "asset_category",
"status", "status",
"depreciation_method", "depreciation_method",
"purchase_date", "purchase_date",
"cost_center",
] ]
for d in frappe.get_all("Asset", fields=fields, filters={"name": ("in", assets)}): for d in frappe.get_all("Asset", fields=fields, filters={"name": ("in", assets)}):
@@ -121,6 +139,12 @@ def get_columns():
"fieldtype": "Currency", "fieldtype": "Currency",
"width": 120, "width": 120,
}, },
{
"label": _("Opening Accumulated Depreciation"),
"fieldname": "opening_accumulated_depreciation",
"fieldtype": "Currency",
"width": 140,
},
{ {
"label": _("Depreciation Amount"), "label": _("Depreciation Amount"),
"fieldname": "depreciation_amount", "fieldname": "depreciation_amount",
@@ -134,8 +158,8 @@ def get_columns():
"width": 210, "width": 210,
}, },
{ {
"label": _("Amount After Depreciation"), "label": _("Value After Depreciation"),
"fieldname": "amount_after_depreciation", "fieldname": "value_after_depreciation",
"fieldtype": "Currency", "fieldtype": "Currency",
"width": 180, "width": 180,
}, },
@@ -153,12 +177,13 @@ def get_columns():
"options": "Asset Category", "options": "Asset Category",
"width": 120, "width": 120,
}, },
{"label": _("Current Status"), "fieldname": "status", "fieldtype": "Data", "width": 120},
{ {
"label": _("Depreciation Method"), "label": _("Cost Center"),
"fieldname": "depreciation_method", "fieldtype": "Link",
"fieldtype": "Data", "fieldname": "cost_center",
"width": 130, "options": "Cost Center",
"width": 100,
}, },
{"label": _("Current Status"), "fieldname": "status", "fieldtype": "Data", "width": 120},
{"label": _("Purchase Date"), "fieldname": "purchase_date", "fieldtype": "Date", "width": 120}, {"label": _("Purchase Date"), "fieldname": "purchase_date", "fieldtype": "Date", "width": 120},
] ]

View File

@@ -15,7 +15,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
frappe.query_reports["Balance Sheet"]["filters"].push({ frappe.query_reports["Balance Sheet"]["filters"].push({
"fieldname": "include_default_book_entries", "fieldname": "include_default_book_entries",
"label": __("Include Default Book Entries"), "label": __("Include Default FB Entries"),
"fieldtype": "Check", "fieldtype": "Check",
"default": 1 "default": 1
}); });

View File

@@ -16,7 +16,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
frappe.query_reports["Cash Flow"]["filters"].push( frappe.query_reports["Cash Flow"]["filters"].push(
{ {
"fieldname": "include_default_book_entries", "fieldname": "include_default_book_entries",
"label": __("Include Default Book Entries"), "label": __("Include Default FB Entries"),
"fieldtype": "Check", "fieldtype": "Check",
"default": 1 "default": 1
} }

View File

@@ -67,7 +67,7 @@ def setup_mappers(mappers):
mapping["finance_costs"] = [] mapping["finance_costs"] = []
mapping["finance_costs_adjustments"] = [] mapping["finance_costs_adjustments"] = []
doc = frappe.get_doc("Cash Flow Mapper", mapping["name"]) doc = frappe.get_doc("Cash Flow Mapper", mapping["name"])
mapping_names = [item.name for item in doc.accounts] mapping_names = [item.mapping for item in doc.accounts]
if not mapping_names: if not mapping_names:
continue continue

View File

@@ -105,7 +105,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
}, },
{ {
"fieldname": "include_default_book_entries", "fieldname": "include_default_book_entries",
"label": __("Include Default Book Entries"), "label": __("Include Default FB Entries"),
"fieldtype": "Check", "fieldtype": "Check",
"default": 1 "default": 1
}, },

View File

@@ -561,9 +561,7 @@ def apply_additional_conditions(doctype, query, from_date, ignore_closing_entrie
company_fb = frappe.get_cached_value("Company", filters.company, "default_finance_book") 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): if filters.finance_book and company_fb and cstr(filters.finance_book) != cstr(company_fb):
frappe.throw( frappe.throw(_("To use a different finance book, please uncheck 'Include Default FB Entries'"))
_("To use a different finance book, please uncheck 'Include Default Book Entries'")
)
query = query.where( query = query.where(
(gl_entry.finance_book.isin([cstr(filters.finance_book), cstr(company_fb), ""])) (gl_entry.finance_book.isin([cstr(filters.finance_book), cstr(company_fb), ""]))

View File

@@ -79,7 +79,9 @@ class General_Payment_Ledger_Comparison(object):
.select( .select(
gle.company, gle.company,
gle.account, gle.account,
gle.voucher_type,
gle.voucher_no, gle.voucher_no,
gle.party_type,
gle.party, gle.party,
outstanding, outstanding,
) )
@@ -89,7 +91,9 @@ class General_Payment_Ledger_Comparison(object):
& (gle.account.isin(val.accounts)) & (gle.account.isin(val.accounts))
) )
.where(Criterion.all(filter_criterion)) .where(Criterion.all(filter_criterion))
.groupby(gle.company, gle.account, gle.voucher_no, gle.party) .groupby(
gle.company, gle.account, gle.voucher_type, gle.voucher_no, gle.party_type, gle.party
)
.run() .run()
) )
@@ -112,7 +116,13 @@ class General_Payment_Ledger_Comparison(object):
self.account_types[acc_type].ple = ( self.account_types[acc_type].ple = (
qb.from_(ple) qb.from_(ple)
.select( .select(
ple.company, ple.account, ple.voucher_no, ple.party, Sum(ple.amount).as_("outstanding") ple.company,
ple.account,
ple.voucher_type,
ple.voucher_no,
ple.party_type,
ple.party,
Sum(ple.amount).as_("outstanding"),
) )
.where( .where(
(ple.company == self.filters.company) (ple.company == self.filters.company)
@@ -120,7 +130,9 @@ class General_Payment_Ledger_Comparison(object):
& (ple.account.isin(val.accounts)) & (ple.account.isin(val.accounts))
) )
.where(Criterion.all(filter_criterion)) .where(Criterion.all(filter_criterion))
.groupby(ple.company, ple.account, ple.voucher_no, ple.party) .groupby(
ple.company, ple.account, ple.voucher_type, ple.voucher_no, ple.party_type, ple.party
)
.run() .run()
) )
@@ -138,12 +150,12 @@ class General_Payment_Ledger_Comparison(object):
self.diff = frappe._dict({}) self.diff = frappe._dict({})
for x in self.variation_in_payment_ledger: for x in self.variation_in_payment_ledger:
self.diff[(x[0], x[1], x[2], x[3])] = frappe._dict({"gl_balance": x[4]}) self.diff[(x[0], x[1], x[2], x[3], x[4], x[5])] = frappe._dict({"gl_balance": x[6]})
for x in self.variation_in_general_ledger: for x in self.variation_in_general_ledger:
self.diff.setdefault((x[0], x[1], x[2], x[3]), frappe._dict({"gl_balance": 0.0})).update( self.diff.setdefault(
frappe._dict({"pl_balance": x[4]}) (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]}))
def generate_data(self): def generate_data(self):
self.data = [] self.data = []
@@ -151,8 +163,12 @@ class General_Payment_Ledger_Comparison(object):
self.data.append( self.data.append(
frappe._dict( frappe._dict(
{ {
"voucher_no": key[2], "company": key[0],
"party": key[3], "account": key[1],
"voucher_type": key[2],
"voucher_no": key[3],
"party_type": key[4],
"party": key[5],
"gl_balance": val.gl_balance, "gl_balance": val.gl_balance,
"pl_balance": val.pl_balance, "pl_balance": val.pl_balance,
} }
@@ -162,12 +178,52 @@ class General_Payment_Ledger_Comparison(object):
def get_columns(self): def get_columns(self):
self.columns = [] self.columns = []
options = None 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( self.columns.append(
dict( dict(
label=_("Voucher No"), label=_("Voucher No"),
fieldname="voucher_no", fieldname="voucher_no",
fieldtype="Data", fieldtype="Dynamic Link",
options=options, options="voucher_type",
width="100",
)
)
self.columns.append(
dict(
label=_("Party Type"),
fieldname="party_type",
fieldtype="Link",
options="DocType",
width="100", width="100",
) )
) )
@@ -176,8 +232,8 @@ class General_Payment_Ledger_Comparison(object):
dict( dict(
label=_("Party"), label=_("Party"),
fieldname="party", fieldname="party",
fieldtype="Data", fieldtype="Dynamic Link",
options=options, options="party_type",
width="100", width="100",
) )
) )

View File

@@ -50,7 +50,11 @@ class TestGeneralAndPaymentLedger(FrappeTestCase, AccountsTestMixin):
self.assertEqual(len(data), 1) self.assertEqual(len(data), 1)
expected = { expected = {
"company": sinv.company,
"account": sinv.debit_to,
"voucher_type": sinv.doctype,
"voucher_no": sinv.name, "voucher_no": sinv.name,
"party_type": "Customer",
"party": sinv.customer, "party": sinv.customer,
"gl_balance": sinv.grand_total, "gl_balance": sinv.grand_total,
"pl_balance": sinv.grand_total - 1, "pl_balance": sinv.grand_total - 1,

View File

@@ -175,7 +175,7 @@ frappe.query_reports["General Ledger"] = {
}, },
{ {
"fieldname": "include_default_book_entries", "fieldname": "include_default_book_entries",
"label": __("Include Default Book Entries"), "label": __("Include Default FB Entries"),
"fieldtype": "Check", "fieldtype": "Check",
"default": 1 "default": 1
}, },
@@ -188,7 +188,13 @@ frappe.query_reports["General Ledger"] = {
"fieldname": "show_net_values_in_party_account", "fieldname": "show_net_values_in_party_account",
"label": __("Show Net Values in Party Account"), "label": __("Show Net Values in Party Account"),
"fieldtype": "Check" "fieldtype": "Check"
},
{
"fieldname": "show_remarks",
"label": __("Show Remarks"),
"fieldtype": "Check"
} }
] ]
} }

View File

@@ -163,6 +163,9 @@ def get_gl_entries(filters, accounting_dimensions):
select_fields = """, debit, credit, debit_in_account_currency, select_fields = """, debit, credit, debit_in_account_currency,
credit_in_account_currency """ credit_in_account_currency """
if filters.get("show_remarks"):
select_fields += """,remarks"""
order_by_statement = "order by posting_date, account, creation" order_by_statement = "order by posting_date, account, creation"
if filters.get("include_dimensions"): if filters.get("include_dimensions"):
@@ -189,7 +192,7 @@ def get_gl_entries(filters, accounting_dimensions):
voucher_type, voucher_no, {dimension_fields} voucher_type, voucher_no, {dimension_fields}
cost_center, project, cost_center, project,
against_voucher_type, against_voucher, account_currency, against_voucher_type, against_voucher, account_currency,
remarks, against, is_opening, creation {select_fields} against, is_opening, creation {select_fields}
from `tabGL Entry` from `tabGL Entry`
where company=%(company)s {conditions} where company=%(company)s {conditions}
{order_by_statement} {order_by_statement}
@@ -249,9 +252,7 @@ def get_conditions(filters):
if filters.get("company_fb") and cstr(filters.get("finance_book")) != cstr( if filters.get("company_fb") and cstr(filters.get("finance_book")) != cstr(
filters.get("company_fb") filters.get("company_fb")
): ):
frappe.throw( frappe.throw(_("To use a different finance book, please uncheck 'Include Default FB Entries'"))
_("To use a different finance book, please uncheck 'Include Default Book Entries'")
)
else: else:
conditions.append("(finance_book in (%(finance_book)s, '') OR finance_book IS NULL)") conditions.append("(finance_book in (%(finance_book)s, '') OR finance_book IS NULL)")
else: else:
@@ -593,8 +594,10 @@ def get_columns(filters):
"width": 100, "width": 100,
}, },
{"label": _("Supplier Invoice No"), "fieldname": "bill_no", "fieldtype": "Data", "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 return columns

View File

@@ -33,13 +33,6 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
"label": __("Accounting Dimension"), "label": __("Accounting Dimension"),
"fieldtype": "Link", "fieldtype": "Link",
"options": "Accounting Dimension", "options": "Accounting Dimension",
"get_query": () =>{
return {
filters: {
"disabled": 0
}
}
}
}, },
{ {
"fieldname": "fiscal_year", "fieldname": "fiscal_year",

View File

@@ -47,6 +47,7 @@ def get_result(
out = [] out = []
for name, details in gle_map.items(): for name, details in gle_map.items():
tax_amount, total_amount, grand_total, base_total = 0, 0, 0, 0 tax_amount, total_amount, grand_total, base_total = 0, 0, 0, 0
bill_no, bill_date = "", ""
tax_withholding_category = tax_category_map.get(name) tax_withholding_category = tax_category_map.get(name)
rate = tax_rate_map.get(tax_withholding_category) rate = tax_rate_map.get(tax_withholding_category)
@@ -68,9 +69,12 @@ def get_result(
tax_amount += entry.credit - entry.debit tax_amount += entry.credit - entry.debit
if net_total_map.get(name): if net_total_map.get(name):
if voucher_type == "Journal Entry": if voucher_type == "Journal Entry" and tax_amount and rate:
# back calcalute total amount from rate and tax_amount # back calcalute total amount from rate and tax_amount
total_amount = grand_total = base_total = tax_amount / (rate / 100) 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: else:
total_amount, grand_total, base_total = net_total_map.get(name) total_amount, grand_total, base_total = net_total_map.get(name)
else: else:
@@ -96,7 +100,7 @@ def get_result(
row.update( row.update(
{ {
"section_code": tax_withholding_category, "section_code": tax_withholding_category or "",
"entity_type": party_map.get(party, {}).get(party_type), "entity_type": party_map.get(party, {}).get(party_type),
"rate": rate, "rate": rate,
"total_amount": total_amount, "total_amount": total_amount,
@@ -106,10 +110,14 @@ def get_result(
"transaction_date": posting_date, "transaction_date": posting_date,
"transaction_type": voucher_type, "transaction_type": voucher_type,
"ref_no": name, "ref_no": name,
"supplier_invoice_no": bill_no,
"supplier_invoice_date": bill_date,
} }
) )
out.append(row) out.append(row)
out.sort(key=lambda x: x["section_code"])
return out return out
@@ -157,14 +165,14 @@ def get_gle_map(documents):
def get_columns(filters): def get_columns(filters):
pan = "pan" if frappe.db.has_column(filters.party_type, "pan") else "tax_id" pan = "pan" if frappe.db.has_column(filters.party_type, "pan") else "tax_id"
columns = [ columns = [
{"label": _(frappe.unscrub(pan)), "fieldname": pan, "fieldtype": "Data", "width": 60},
{ {
"label": _(filters.get("party_type")), "label": _("Section Code"),
"fieldname": "party", "options": "Tax Withholding Category",
"fieldtype": "Dynamic Link", "fieldname": "section_code",
"options": "party_type", "fieldtype": "Link",
"width": 180, "width": 90,
}, },
{"label": _(frappe.unscrub(pan)), "fieldname": pan, "fieldtype": "Data", "width": 60},
] ]
if filters.naming_series == "Naming Series": if filters.naming_series == "Naming Series":
@@ -189,51 +197,60 @@ def get_columns(filters):
columns.extend( columns.extend(
[ [
{
"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": _("Entity Type"), "fieldname": "entity_type", "fieldtype": "Data", "width": 100},
{ ]
"label": _("Total Amount"), )
"fieldname": "total_amount", if filters.party_type == "Supplier":
"fieldtype": "Float", columns.extend(
"width": 90, [
}, {
"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 %"), "label": _("TDS Rate %") if filters.get("party_type") == "Supplier" else _("TCS Rate %"),
"fieldname": "rate", "fieldname": "rate",
"fieldtype": "Percent", "fieldtype": "Percent",
"width": 90, "width": 60,
}, },
{ {
"label": _("Tax Amount"), "label": _("Total Amount"),
"fieldname": "tax_amount", "fieldname": "total_amount",
"fieldtype": "Float", "fieldtype": "Float",
"width": 90, "width": 120,
},
{
"label": _("Grand Total"),
"fieldname": "grand_total",
"fieldtype": "Float",
"width": 90,
}, },
{ {
"label": _("Base Total"), "label": _("Base Total"),
"fieldname": "base_total", "fieldname": "base_total",
"fieldtype": "Float", "fieldtype": "Float",
"width": 90, "width": 120,
}, },
{"label": _("Transaction Type"), "fieldname": "transaction_type", "width": 100}, {
"label": _("Tax Amount"),
"fieldname": "tax_amount",
"fieldtype": "Float",
"width": 120,
},
{
"label": _("Grand Total"),
"fieldname": "grand_total",
"fieldtype": "Float",
"width": 120,
},
{"label": _("Transaction Type"), "fieldname": "transaction_type", "width": 130},
{ {
"label": _("Reference No."), "label": _("Reference No."),
"fieldname": "ref_no", "fieldname": "ref_no",
@@ -241,6 +258,12 @@ def get_columns(filters):
"options": "transaction_type", "options": "transaction_type",
"width": 180, "width": 180,
}, },
{
"label": _("Date of Transaction"),
"fieldname": "transaction_date",
"fieldtype": "Date",
"width": 100,
},
] ]
) )
@@ -263,27 +286,7 @@ def get_tds_docs(filters):
"Tax Withholding Account", {"company": filters.get("company")}, pluck="account" "Tax Withholding Account", {"company": filters.get("company")}, pluck="account"
) )
query_filters = { tds_docs = get_tds_docs_query(filters, bank_accounts, tds_accounts).run(as_dict=True)
"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: for d in tds_docs:
if d.voucher_type == "Purchase Invoice": if d.voucher_type == "Purchase Invoice":
@@ -319,6 +322,47 @@ 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): def get_journal_entry_party_map(journal_entries):
journal_entry_party_map = {} journal_entry_party_map = {}
for d in frappe.db.get_all( for d in frappe.db.get_all(
@@ -345,6 +389,8 @@ def get_doc_info(vouchers, doctype, tax_category_map, net_total_map=None):
"base_tax_withholding_net_total", "base_tax_withholding_net_total",
"grand_total", "grand_total",
"base_total", "base_total",
"bill_no",
"bill_date",
], ],
"Sales Invoice": ["base_net_total", "grand_total", "base_total"], "Sales Invoice": ["base_net_total", "grand_total", "base_total"],
"Payment Entry": [ "Payment Entry": [
@@ -363,7 +409,13 @@ def get_doc_info(vouchers, doctype, tax_category_map, net_total_map=None):
for entry in entries: for entry in entries:
tax_category_map.update({entry.name: entry.tax_withholding_category}) tax_category_map.update({entry.name: entry.tax_withholding_category})
if doctype == "Purchase Invoice": if doctype == "Purchase Invoice":
value = [entry.base_tax_withholding_net_total, entry.grand_total, entry.base_total] value = [
entry.base_tax_withholding_net_total,
entry.grand_total,
entry.base_total,
entry.bill_no,
entry.bill_date,
]
elif doctype == "Sales Invoice": elif doctype == "Sales Invoice":
value = [entry.base_net_total, entry.grand_total, entry.base_total] value = [entry.base_net_total, entry.grand_total, entry.base_total]
elif doctype == "Payment Entry": elif doctype == "Payment Entry":

View File

@@ -96,7 +96,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
}, },
{ {
"fieldname": "include_default_book_entries", "fieldname": "include_default_book_entries",
"label": __("Include Default Book Entries"), "label": __("Include Default FB Entries"),
"fieldtype": "Check", "fieldtype": "Check",
"default": 1 "default": 1
}, },

View File

@@ -275,9 +275,7 @@ def get_opening_balance(
company_fb = frappe.get_cached_value("Company", filters.company, "default_finance_book") 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): if filters.finance_book and company_fb and cstr(filters.finance_book) != cstr(company_fb):
frappe.throw( frappe.throw(_("To use a different finance book, please uncheck 'Include Default FB Entries'"))
_("To use a different finance book, please uncheck 'Include Default Book Entries'")
)
opening_balance = opening_balance.where( opening_balance = opening_balance.where(
(closing_balance.finance_book.isin([cstr(filters.finance_book), cstr(company_fb), ""])) (closing_balance.finance_book.isin([cstr(filters.finance_book), cstr(company_fb), ""]))

View File

@@ -511,7 +511,7 @@ def check_if_advance_entry_modified(args):
party_account_field = ( party_account_field = (
"paid_from" if erpnext.get_party_account_type(args.party_type) == "Receivable" else "paid_to" "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: if args.voucher_detail_no:
ret = frappe.db.sql( ret = frappe.db.sql(
"""select t1.name """select t1.name
@@ -533,9 +533,9 @@ def check_if_advance_entry_modified(args):
where where
name = %(voucher_no)s and docstatus = 1 name = %(voucher_no)s and docstatus = 1
and party_type = %(party_type)s and party = %(party)s and {0} = %(account)s and party_type = %(party_type)s and party = %(party)s and {0} = %(account)s
and unallocated_amount = %(unreconciled_amount)s and round(unallocated_amount, {1}) = %(unreconciled_amount)s
""".format( """.format(
party_account_field party_account_field, precision
), ),
args, args,
) )
@@ -624,7 +624,7 @@ def update_reference_in_payment_entry(
"outstanding_amount": d.outstanding_amount, "outstanding_amount": d.outstanding_amount,
"allocated_amount": d.allocated_amount, "allocated_amount": d.allocated_amount,
"exchange_rate": d.exchange_rate if d.exchange_gain_loss else payment_entry.get_exchange_rate(), "exchange_rate": d.exchange_rate if d.exchange_gain_loss else payment_entry.get_exchange_rate(),
"exchange_gain_loss": d.exchange_gain_loss, # only populated from invoice in case of advance allocation "exchange_gain_loss": d.exchange_gain_loss,
} }
if d.voucher_detail_no: if d.voucher_detail_no:
@@ -636,28 +636,29 @@ def update_reference_in_payment_entry(
existing_row.reference_doctype, existing_row.reference_name existing_row.reference_doctype, existing_row.reference_name
).set_total_advance_paid() ).set_total_advance_paid()
original_row = existing_row.as_dict().copy() if d.allocated_amount <= existing_row.allocated_amount:
existing_row.update(reference_details) existing_row.allocated_amount -= d.allocated_amount
if d.allocated_amount < original_row.allocated_amount:
new_row = payment_entry.append("references") new_row = payment_entry.append("references")
new_row.docstatus = 1 new_row.docstatus = 1
for field in list(reference_details): for field in list(reference_details):
new_row.set(field, original_row[field]) new_row.set(field, reference_details[field])
new_row.allocated_amount = original_row.allocated_amount - d.allocated_amount
else: else:
new_row = payment_entry.append("references") new_row = payment_entry.append("references")
new_row.docstatus = 1 new_row.docstatus = 1
new_row.update(reference_details) new_row.update(reference_details)
payment_entry.flags.ignore_validate_update_after_submit = True payment_entry.flags.ignore_validate_update_after_submit = True
payment_entry.clear_unallocated_reference_document_rows()
payment_entry.setup_party_account_field() payment_entry.setup_party_account_field()
payment_entry.set_missing_values() payment_entry.set_missing_values()
if not skip_ref_details_update_for_pe: if not skip_ref_details_update_for_pe:
payment_entry.set_missing_ref_details() payment_entry.set_missing_ref_details()
payment_entry.set_amounts() payment_entry.set_amounts()
payment_entry.make_exchange_gain_loss_journal() payment_entry.make_exchange_gain_loss_journal(
frappe._dict({"difference_posting_date": d.difference_posting_date})
)
if not do_not_save: if not do_not_save:
payment_entry.save(ignore_permissions=True) payment_entry.save(ignore_permissions=True)

View File

@@ -9,7 +9,6 @@ frappe.ui.form.on('Asset', {
frm.set_query("item_code", function() { frm.set_query("item_code", function() {
return { return {
"filters": { "filters": {
"disabled": 0,
"is_fixed_asset": 1, "is_fixed_asset": 1,
"is_stock_item": 0 "is_stock_item": 0
} }

View File

@@ -221,11 +221,11 @@
"read_only": 1 "read_only": 1
}, },
{ {
"depends_on": "eval:!(doc.is_composite_asset && !doc.capitalized_in)",
"fieldname": "gross_purchase_amount", "fieldname": "gross_purchase_amount",
"fieldtype": "Currency", "fieldtype": "Currency",
"label": "Gross Purchase Amount", "label": "Gross Purchase Amount",
"options": "Company:company:default_currency", "options": "Company:company:default_currency",
"read_only": 1,
"read_only_depends_on": "eval:!doc.is_existing_asset", "read_only_depends_on": "eval:!doc.is_existing_asset",
"reqd": 1 "reqd": 1
}, },
@@ -413,6 +413,7 @@
"fieldtype": "Column Break" "fieldtype": "Column Break"
}, },
{ {
"depends_on": "eval:!doc.is_composite_asset && !doc.is_existing_asset",
"fieldname": "purchase_receipt", "fieldname": "purchase_receipt",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Purchase Receipt", "label": "Purchase Receipt",
@@ -430,6 +431,7 @@
"read_only": 1 "read_only": 1
}, },
{ {
"depends_on": "eval:!doc.is_composite_asset && !doc.is_existing_asset",
"fieldname": "purchase_invoice", "fieldname": "purchase_invoice",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Purchase Invoice", "label": "Purchase Invoice",
@@ -555,9 +557,14 @@
"link_doctype": "Journal Entry", "link_doctype": "Journal Entry",
"link_fieldname": "reference_name", "link_fieldname": "reference_name",
"table_fieldname": "accounts" "table_fieldname": "accounts"
},
{
"group": "Asset Capitalization",
"link_doctype": "Asset Capitalization",
"link_fieldname": "target_asset"
} }
], ],
"modified": "2023-10-03 23:28:26.732269", "modified": "2023-11-15 17:40:17.315203",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Assets", "module": "Assets",
"name": "Asset", "name": "Asset",

View File

@@ -1216,7 +1216,7 @@ def get_item_details(item_code, asset_category, gross_purchase_amount):
"depreciation_method": d.depreciation_method, "depreciation_method": d.depreciation_method,
"total_number_of_depreciations": d.total_number_of_depreciations, "total_number_of_depreciations": d.total_number_of_depreciations,
"frequency_of_depreciation": d.frequency_of_depreciation, "frequency_of_depreciation": d.frequency_of_depreciation,
"daily_depreciation": d.daily_depreciation, "daily_prorata_based": d.daily_prorata_based,
"salvage_value_percentage": d.salvage_value_percentage, "salvage_value_percentage": d.salvage_value_percentage,
"expected_value_after_useful_life": flt(gross_purchase_amount) "expected_value_after_useful_life": flt(gross_purchase_amount)
* flt(d.salvage_value_percentage / 100), * flt(d.salvage_value_percentage / 100),
@@ -1396,7 +1396,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 # 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: elif asset.flags.decrease_in_asset_value_due_to_value_adjustment:
if row.daily_depreciation: if row.daily_prorata_based:
daily_depr_amount = ( daily_depr_amount = (
flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life) flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)
) / date_diff( ) / date_diff(
@@ -1441,7 +1441,7 @@ def get_straight_line_or_manual_depr_amount(
) / number_of_pending_depreciations ) / number_of_pending_depreciations
# if the Depreciation Schedule is being prepared for the first time # if the Depreciation Schedule is being prepared for the first time
else: else:
if row.daily_depreciation: if row.daily_prorata_based:
daily_depr_amount = ( daily_depr_amount = (
flt(asset.gross_purchase_amount) flt(asset.gross_purchase_amount)
- flt(asset.opening_accumulated_depreciation) - flt(asset.opening_accumulated_depreciation)

View File

@@ -740,6 +740,15 @@ def get_disposal_account_and_cost_center(company):
def get_value_after_depreciation_on_disposal_date(asset, disposal_date, finance_book=None): def get_value_after_depreciation_on_disposal_date(asset, disposal_date, finance_book=None):
asset_doc = frappe.get_doc("Asset", asset) 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: if asset_doc.calculate_depreciation:
asset_doc.prepare_depreciation_data(getdate(disposal_date)) asset_doc.prepare_depreciation_data(getdate(disposal_date))

View File

@@ -186,6 +186,7 @@ class TestAsset(AssetSetup):
def test_is_fixed_asset_set(self): def test_is_fixed_asset_set(self):
asset = create_asset(is_existing_asset=1) asset = create_asset(is_existing_asset=1)
doc = frappe.new_doc("Purchase Invoice") doc = frappe.new_doc("Purchase Invoice")
doc.company = "_Test Company"
doc.supplier = "_Test Supplier" doc.supplier = "_Test Supplier"
doc.append("items", {"item_code": "Macbook Pro", "qty": 1, "asset": asset.name}) doc.append("items", {"item_code": "Macbook Pro", "qty": 1, "asset": asset.name})
@@ -487,7 +488,7 @@ class TestAsset(AssetSetup):
self.assertEqual("Asset Received But Not Billed - _TC", doc.items[0].expense_account) self.assertEqual("Asset Received But Not Billed - _TC", doc.items[0].expense_account)
# CWIP: Capital Work In Progress # Capital Work In Progress
def test_cwip_accounting(self): def test_cwip_accounting(self):
pr = make_purchase_receipt( pr = make_purchase_receipt(
item_code="Macbook Pro", qty=1, rate=5000, do_not_submit=True, location="Test Location" item_code="Macbook Pro", qty=1, rate=5000, do_not_submit=True, location="Test Location"
@@ -520,7 +521,8 @@ class TestAsset(AssetSetup):
pr.submit() pr.submit()
expected_gle = ( expected_gle = (
("Asset Received But Not Billed - _TC", 0.0, 5250.0), ("_Test Account Shipping Charges - _TC", 0.0, 250.0),
("Asset Received But Not Billed - _TC", 0.0, 5000.0),
("CWIP Account - _TC", 5250.0, 0.0), ("CWIP Account - _TC", 5250.0, 0.0),
) )
@@ -539,9 +541,8 @@ class TestAsset(AssetSetup):
expected_gle = ( expected_gle = (
("_Test Account Service Tax - _TC", 250.0, 0.0), ("_Test Account Service Tax - _TC", 250.0, 0.0),
("_Test Account Shipping Charges - _TC", 250.0, 0.0), ("_Test Account Shipping Charges - _TC", 250.0, 0.0),
("Asset Received But Not Billed - _TC", 5250.0, 0.0), ("Asset Received But Not Billed - _TC", 5000.0, 0.0),
("Creditors - _TC", 0.0, 5500.0), ("Creditors - _TC", 0.0, 5500.0),
("Expenses Included In Asset Valuation - _TC", 0.0, 250.0),
) )
pi_gle = frappe.db.sql( pi_gle = frappe.db.sql(
@@ -730,7 +731,9 @@ class TestDepreciationMethods(AssetSetup):
self.assertEqual(schedules, expected_schedules) self.assertEqual(schedules, expected_schedules)
def test_schedule_for_straight_line_method_with_daily_depreciation(self): def test_schedule_for_straight_line_method_with_daily_prorata_based(
self,
):
asset = create_asset( asset = create_asset(
calculate_depreciation=1, calculate_depreciation=1,
available_for_use_date="2023-01-01", available_for_use_date="2023-01-01",
@@ -739,7 +742,7 @@ class TestDepreciationMethods(AssetSetup):
depreciation_start_date="2023-01-31", depreciation_start_date="2023-01-31",
total_number_of_depreciations=12, total_number_of_depreciations=12,
frequency_of_depreciation=1, frequency_of_depreciation=1,
daily_depreciation=1, daily_prorata_based=1,
) )
expected_schedules = [ expected_schedules = [
@@ -1702,7 +1705,7 @@ def create_asset(**args):
"total_number_of_depreciations": args.total_number_of_depreciations or 5, "total_number_of_depreciations": args.total_number_of_depreciations or 5,
"expected_value_after_useful_life": args.expected_value_after_useful_life or 0, "expected_value_after_useful_life": args.expected_value_after_useful_life or 0,
"depreciation_start_date": args.depreciation_start_date, "depreciation_start_date": args.depreciation_start_date,
"daily_depreciation": args.daily_depreciation or 0, "daily_prorata_based": args.daily_prorata_based or 0,
}, },
) )
@@ -1731,6 +1734,7 @@ def create_asset_category():
"fixed_asset_account": "_Test Fixed Asset - _TC", "fixed_asset_account": "_Test Fixed Asset - _TC",
"accumulated_depreciation_account": "_Test Accumulated Depreciations - _TC", "accumulated_depreciation_account": "_Test Accumulated Depreciations - _TC",
"depreciation_expense_account": "_Test Depreciations - _TC", "depreciation_expense_account": "_Test Depreciations - _TC",
"capital_work_in_progress_account": "CWIP Account - _TC",
}, },
) )
asset_category.append( asset_category.append(

View File

@@ -832,12 +832,8 @@ def get_items_tagged_to_wip_composite_asset(asset):
"amount", "amount",
] ]
pi_items = frappe.get_all(
"Purchase Invoice Item", filters={"wip_composite_asset": asset}, fields=fields
)
pr_items = frappe.get_all( pr_items = frappe.get_all(
"Purchase Receipt Item", filters={"wip_composite_asset": asset}, fields=fields "Purchase Receipt Item", filters={"wip_composite_asset": asset}, fields=fields
) )
return pi_items + pr_items return pr_items

View File

@@ -8,7 +8,7 @@
"finance_book", "finance_book",
"depreciation_method", "depreciation_method",
"total_number_of_depreciations", "total_number_of_depreciations",
"daily_depreciation", "daily_prorata_based",
"column_break_5", "column_break_5",
"frequency_of_depreciation", "frequency_of_depreciation",
"depreciation_start_date", "depreciation_start_date",
@@ -82,23 +82,23 @@
"fieldtype": "Percent", "fieldtype": "Percent",
"label": "Rate of Depreciation" "label": "Rate of Depreciation"
}, },
{
"default": "0",
"depends_on": "eval:doc.depreciation_method == \"Straight Line\" || doc.depreciation_method == \"Manual\"",
"fieldname": "daily_depreciation",
"fieldtype": "Check",
"label": "Daily Depreciation"
},
{ {
"fieldname": "salvage_value_percentage", "fieldname": "salvage_value_percentage",
"fieldtype": "Percent", "fieldtype": "Percent",
"label": "Salvage Value Percentage" "label": "Salvage Value Percentage"
},
{
"default": "0",
"depends_on": "eval:doc.depreciation_method == \"Straight Line\" || doc.depreciation_method == \"Manual\"",
"fieldname": "daily_prorata_based",
"fieldtype": "Check",
"label": "Depreciate based on daily pro-rata"
} }
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2023-09-29 15:39:52.740594", "modified": "2023-11-03 22:21:52.090191",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Assets", "module": "Assets",
"name": "Asset Finance Book", "name": "Asset Finance Book",

View File

@@ -52,7 +52,7 @@ frappe.query_reports["Fixed Asset Register"] = {
}, },
{ {
"fieldname": "include_default_book_assets", "fieldname": "include_default_book_assets",
"label": __("Include Default Book Assets"), "label": __("Include Default FB Assets"),
"fieldtype": "Check", "fieldtype": "Check",
"default": 1 "default": 1
}, },

View File

@@ -223,7 +223,7 @@ def get_assets_linked_to_fb(filters):
company_fb = frappe.get_cached_value("Company", filters.company, "default_finance_book") 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): 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 Book Assets'")) frappe.throw(_("To use a different finance book, please uncheck 'Include Default FB Assets'"))
query = query.where( query = query.where(
(afb.finance_book.isin([cstr(filters.finance_book), cstr(company_fb), ""])) (afb.finance_book.isin([cstr(filters.finance_book), cstr(company_fb), ""]))

View File

@@ -16,7 +16,7 @@
"transaction_settings_section", "transaction_settings_section",
"po_required", "po_required",
"pr_required", "pr_required",
"over_order_allowance", "blanket_order_allowance",
"column_break_12", "column_break_12",
"maintain_same_rate", "maintain_same_rate",
"set_landed_cost_based_on_purchase_invoice_rate", "set_landed_cost_based_on_purchase_invoice_rate",
@@ -159,19 +159,19 @@
"fieldtype": "Check", "fieldtype": "Check",
"label": "Set Landed Cost Based on Purchase Invoice Rate" "label": "Set Landed Cost Based on Purchase Invoice Rate"
}, },
{
"default": "0",
"description": "Percentage you are allowed to order more against the Blanket Order Quantity. For example: If you have a Blanket Order of Quantity 100 units. and your Allowance is 10% then you are allowed to order 110 units.",
"fieldname": "over_order_allowance",
"fieldtype": "Float",
"label": "Over Order Allowance (%)"
},
{ {
"default": "0", "default": "0",
"description": "While making Purchase Invoice from Purchase Order, use Exchange Rate on Invoice's transaction date rather than inheriting it from Purchase Order. Only applies for Purchase Invoice.", "description": "While making Purchase Invoice from Purchase Order, use Exchange Rate on Invoice's transaction date rather than inheriting it from Purchase Order. Only applies for Purchase Invoice.",
"fieldname": "use_transaction_date_exchange_rate", "fieldname": "use_transaction_date_exchange_rate",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Use Transaction Date Exchange Rate" "label": "Use Transaction Date Exchange Rate"
},
{
"default": "0",
"description": "Percentage you are allowed to order beyond the Blanket Order quantity.",
"fieldname": "blanket_order_allowance",
"fieldtype": "Float",
"label": "Blanket Order Allowance (%)"
} }
], ],
"icon": "fa fa-cog", "icon": "fa fa-cog",
@@ -179,7 +179,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"issingle": 1, "issingle": 1,
"links": [], "links": [],
"modified": "2023-10-16 16:22:03.201078", "modified": "2023-10-25 14:03:32.520418",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Buying Settings", "name": "Buying Settings",

View File

@@ -522,6 +522,9 @@ def make_purchase_receipt(source_name, target_doc=None):
"bom": "bom", "bom": "bom",
"material_request": "material_request", "material_request": "material_request",
"material_request_item": "material_request_item", "material_request_item": "material_request_item",
"sales_order": "sales_order",
"sales_order_item": "sales_order_item",
"wip_composite_asset": "wip_composite_asset",
}, },
"postprocess": update_item, "postprocess": update_item,
"condition": lambda doc: abs(doc.received_qty) < abs(doc.qty) "condition": lambda doc: abs(doc.received_qty) < abs(doc.qty)
@@ -598,6 +601,7 @@ def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions
"field_map": { "field_map": {
"name": "po_detail", "name": "po_detail",
"parent": "purchase_order", "parent": "purchase_order",
"wip_composite_asset": "wip_composite_asset",
}, },
"postprocess": update_item, "postprocess": update_item,
"condition": lambda doc: (doc.base_amount == 0 or abs(doc.billed_amt) < abs(doc.amount)), "condition": lambda doc: (doc.base_amount == 0 or abs(doc.billed_amt) < abs(doc.amount)),

View File

@@ -86,6 +86,8 @@
"billed_amt", "billed_amt",
"accounting_details", "accounting_details",
"expense_account", "expense_account",
"column_break_fyqr",
"wip_composite_asset",
"manufacture_details", "manufacture_details",
"manufacturer", "manufacturer",
"manufacturer_part_no", "manufacturer_part_no",
@@ -188,6 +190,7 @@
"fieldtype": "Column Break" "fieldtype": "Column Break"
}, },
{ {
"fetch_from": "item_code.image",
"fieldname": "image", "fieldname": "image",
"fieldtype": "Attach", "fieldtype": "Attach",
"hidden": 1, "hidden": 1,
@@ -469,6 +472,7 @@
"fieldname": "material_request", "fieldname": "material_request",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Material Request", "label": "Material Request",
"mandatory_depends_on": "eval: doc.material_request_item",
"no_copy": 1, "no_copy": 1,
"oldfieldname": "prevdoc_docname", "oldfieldname": "prevdoc_docname",
"oldfieldtype": "Link", "oldfieldtype": "Link",
@@ -484,6 +488,7 @@
"fieldtype": "Data", "fieldtype": "Data",
"hidden": 1, "hidden": 1,
"label": "Material Request Item", "label": "Material Request Item",
"mandatory_depends_on": "eval: doc.material_request",
"no_copy": 1, "no_copy": 1,
"oldfieldname": "prevdoc_detail_docname", "oldfieldname": "prevdoc_detail_docname",
"oldfieldtype": "Data", "oldfieldtype": "Data",
@@ -897,13 +902,23 @@
"fieldname": "apply_tds", "fieldname": "apply_tds",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Apply TDS" "label": "Apply TDS"
},
{
"fieldname": "wip_composite_asset",
"fieldtype": "Link",
"label": "WIP Composite Asset",
"options": "Asset"
},
{
"fieldname": "column_break_fyqr",
"fieldtype": "Column Break"
} }
], ],
"idx": 1, "idx": 1,
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2023-09-13 16:22:40.825092", "modified": "2023-11-14 18:34:27.267382",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Purchase Order Item", "name": "Purchase Order Item",

View File

@@ -88,6 +88,7 @@
"width": "300px" "width": "300px"
}, },
{ {
"fetch_from": "item_code.image",
"fieldname": "image", "fieldname": "image",
"fieldtype": "Attach", "fieldtype": "Attach",
"hidden": 1, "hidden": 1,
@@ -261,13 +262,15 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2020-09-24 17:26:46.276934", "modified": "2023-11-14 18:34:48.327224",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Request for Quotation Item", "name": "Request for Quotation Item",
"naming_rule": "Random",
"owner": "Administrator", "owner": "Administrator",
"permissions": [], "permissions": [],
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"states": [],
"track_changes": 1 "track_changes": 1
} }

View File

@@ -170,7 +170,7 @@
"fieldname": "supplier_type", "fieldname": "supplier_type",
"fieldtype": "Select", "fieldtype": "Select",
"label": "Supplier Type", "label": "Supplier Type",
"options": "Company\nIndividual", "options": "Company\nIndividual\nProprietorship\nPartnership",
"reqd": 1 "reqd": 1
}, },
{ {
@@ -457,7 +457,7 @@
"link_fieldname": "party" "link_fieldname": "party"
} }
], ],
"modified": "2023-05-09 15:34:13.408932", "modified": "2023-10-19 16:55:15.148325",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Supplier", "name": "Supplier",
@@ -525,4 +525,4 @@
"states": [], "states": [],
"title_field": "supplier_name", "title_field": "supplier_name",
"track_changes": 1 "track_changes": 1
} }

View File

@@ -8,7 +8,7 @@ def get_data():
"This is based on transactions against this Supplier. See timeline below for details" "This is based on transactions against this Supplier. See timeline below for details"
), ),
"fieldname": "supplier", "fieldname": "supplier",
"non_standard_fieldnames": {"Payment Entry": "party_name", "Bank Account": "party"}, "non_standard_fieldnames": {"Payment Entry": "party", "Bank Account": "party"},
"transactions": [ "transactions": [
{"label": _("Procurement"), "items": ["Request for Quotation", "Supplier Quotation"]}, {"label": _("Procurement"), "items": ["Request for Quotation", "Supplier Quotation"]},
{"label": _("Orders"), "items": ["Purchase Order", "Purchase Receipt", "Purchase Invoice"]}, {"label": _("Orders"), "items": ["Purchase Order", "Purchase Receipt", "Purchase Invoice"]},

View File

@@ -134,6 +134,7 @@
"fieldtype": "Column Break" "fieldtype": "Column Break"
}, },
{ {
"fetch_from": "item_code.image",
"fieldname": "image", "fieldname": "image",
"fieldtype": "Attach", "fieldtype": "Attach",
"hidden": 1, "hidden": 1,
@@ -560,13 +561,15 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2020-10-19 12:36:26.913211", "modified": "2023-11-14 18:35:03.435817",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Supplier Quotation Item", "name": "Supplier Quotation Item",
"naming_rule": "Random",
"owner": "Administrator", "owner": "Administrator",
"permissions": [], "permissions": [],
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"states": [],
"track_changes": 1 "track_changes": 1
} }

View File

@@ -44,11 +44,6 @@ frappe.query_reports["Supplier Quotation Comparison"] = {
} }
} }
} }
else {
return {
filters: { "disabled": 0 }
}
}
} }
}, },
{ {

View File

@@ -1131,7 +1131,9 @@ class AccountsController(TransactionBase):
self.name, self.name,
arg.get("referenced_row"), arg.get("referenced_row"),
): ):
posting_date = frappe.db.get_value(arg.voucher_type, arg.voucher_no, "posting_date") posting_date = arg.get("difference_posting_date") or frappe.db.get_value(
arg.voucher_type, arg.voucher_no, "posting_date"
)
je = create_gain_loss_journal( je = create_gain_loss_journal(
self.company, self.company,
posting_date, posting_date,
@@ -1214,7 +1216,7 @@ class AccountsController(TransactionBase):
je = create_gain_loss_journal( je = create_gain_loss_journal(
self.company, self.company,
self.posting_date, args.get("difference_posting_date") if args else self.posting_date,
self.party_type, self.party_type,
self.party, self.party,
party_account, party_account,
@@ -2247,6 +2249,7 @@ class AccountsController(TransactionBase):
repost_ledger = frappe.new_doc("Repost Accounting Ledger") repost_ledger = frappe.new_doc("Repost Accounting Ledger")
repost_ledger.company = self.company repost_ledger.company = self.company
repost_ledger.append("vouchers", {"voucher_type": self.doctype, "voucher_no": self.name}) repost_ledger.append("vouchers", {"voucher_type": self.doctype, "voucher_no": self.name})
repost_ledger.flags.ignore_permissions = True
repost_ledger.insert() repost_ledger.insert()
repost_ledger.submit() repost_ledger.submit()
self.db_set("repost_required", 0) self.db_set("repost_required", 0)

View File

@@ -4,7 +4,7 @@
import frappe import frappe
from frappe import ValidationError, _, msgprint from frappe import ValidationError, _, msgprint
from frappe.contacts.doctype.address.address import get_address_display from frappe.contacts.doctype.address.address import render_address
from frappe.utils import cint, cstr, flt, getdate from frappe.utils import cint, cstr, flt, getdate
from frappe.utils.data import nowtime from frappe.utils.data import nowtime
@@ -78,26 +78,26 @@ class BuyingController(SubcontractingController):
def set_rate_for_standalone_debit_note(self): def set_rate_for_standalone_debit_note(self):
if self.get("is_return") and self.get("update_stock") and not self.return_against: if self.get("is_return") and self.get("update_stock") and not self.return_against:
for row in self.items: for row in self.items:
if row.rate <= 0:
# override the rate with valuation rate
row.rate = get_incoming_rate(
{
"item_code": row.item_code,
"warehouse": row.warehouse,
"posting_date": self.get("posting_date"),
"posting_time": self.get("posting_time"),
"qty": row.qty,
"serial_and_batch_bundle": row.get("serial_and_batch_bundle"),
"company": self.company,
"voucher_type": self.doctype,
"voucher_no": self.name,
},
raise_error_if_no_rate=False,
)
# override the rate with valuation rate row.discount_percentage = 0.0
row.rate = get_incoming_rate( row.discount_amount = 0.0
{ row.margin_rate_or_amount = 0.0
"item_code": row.item_code,
"warehouse": row.warehouse,
"posting_date": self.get("posting_date"),
"posting_time": self.get("posting_time"),
"qty": row.qty,
"serial_and_batch_bundle": row.get("serial_and_batch_bundle"),
"company": self.company,
"voucher_type": self.doctype,
"voucher_no": self.name,
},
raise_error_if_no_rate=False,
)
row.discount_percentage = 0.0
row.discount_amount = 0.0
row.margin_rate_or_amount = 0.0
def set_missing_values(self, for_validate=False): def set_missing_values(self, for_validate=False):
super(BuyingController, self).set_missing_values(for_validate) super(BuyingController, self).set_missing_values(for_validate)
@@ -219,7 +219,9 @@ class BuyingController(SubcontractingController):
for address_field, address_display_field in address_dict.items(): for address_field, address_display_field in address_dict.items():
if self.get(address_field): if self.get(address_field):
self.set(address_display_field, get_address_display(self.get(address_field))) self.set(
address_display_field, render_address(self.get(address_field), check_permissions=False)
)
def set_total_in_words(self): def set_total_in_words(self):
from frappe.utils import money_in_words from frappe.utils import money_in_words
@@ -336,7 +338,7 @@ class BuyingController(SubcontractingController):
{ {
"item_code": d.item_code, "item_code": d.item_code,
"warehouse": d.get("from_warehouse"), "warehouse": d.get("from_warehouse"),
"posting_date": self.get("posting_date") or self.get("transation_date"), "posting_date": self.get("posting_date") or self.get("transaction_date"),
"posting_time": posting_time, "posting_time": posting_time,
"qty": -1 * flt(d.get("stock_qty")), "qty": -1 * flt(d.get("stock_qty")),
"serial_no": d.get("serial_no"), "serial_no": d.get("serial_no"),

View File

@@ -560,6 +560,8 @@ def get_income_account(doctype, txt, searchfield, start, page_len, filters):
if filters.get("company"): if filters.get("company"):
condition += "and tabAccount.company = %(company)s" condition += "and tabAccount.company = %(company)s"
condition += f"and tabAccount.disabled = {filters.get('disabled', 0)}"
return frappe.db.sql( return frappe.db.sql(
"""select tabAccount.name from `tabAccount` """select tabAccount.name from `tabAccount`
where (tabAccount.report_type = "Profit and Loss" where (tabAccount.report_type = "Profit and Loss"

View File

@@ -4,9 +4,9 @@
import frappe import frappe
from frappe import _, bold, throw from frappe import _, bold, throw
from frappe.contacts.doctype.address.address import get_address_display
from frappe.utils import cint, cstr, flt, get_link_to_form, nowtime from frappe.utils import cint, cstr, flt, get_link_to_form, nowtime
from erpnext.accounts.party import render_address
from erpnext.controllers.accounts_controller import get_taxes_and_charges from erpnext.controllers.accounts_controller import get_taxes_and_charges
from erpnext.controllers.sales_and_purchase_return import get_rate_for_return from erpnext.controllers.sales_and_purchase_return import get_rate_for_return
from erpnext.controllers.stock_controller import StockController from erpnext.controllers.stock_controller import StockController
@@ -591,7 +591,9 @@ class SellingController(StockController):
for address_field, address_display_field in address_dict.items(): for address_field, address_display_field in address_dict.items():
if self.get(address_field): if self.get(address_field):
self.set(address_display_field, get_address_display(self.get(address_field))) self.set(
address_display_field, render_address(self.get(address_field), check_permissions=False)
)
def validate_for_duplicate_items(self): def validate_for_duplicate_items(self):
check_list, chk_dupl_itm = [], [] check_list, chk_dupl_itm = [], []

View File

@@ -47,15 +47,15 @@ status_map = {
], ],
[ [
"To Bill", "To Bill",
"eval:(self.per_delivered == 100 or self.skip_delivery_note) and self.per_billed < 100 and self.docstatus == 1", "eval:(self.per_delivered >= 100 or self.skip_delivery_note) and self.per_billed < 100 and self.docstatus == 1",
], ],
[ [
"To Deliver", "To Deliver",
"eval:self.per_delivered < 100 and self.per_billed == 100 and self.docstatus == 1 and not self.skip_delivery_note", "eval:self.per_delivered < 100 and self.per_billed >= 100 and self.docstatus == 1 and not self.skip_delivery_note",
], ],
[ [
"Completed", "Completed",
"eval:(self.per_delivered == 100 or self.skip_delivery_note) and self.per_billed == 100 and self.docstatus == 1", "eval:(self.per_delivered >= 100 or self.skip_delivery_note) and self.per_billed >= 100 and self.docstatus == 1",
], ],
["Cancelled", "eval:self.docstatus==2"], ["Cancelled", "eval:self.docstatus==2"],
["Closed", "eval:self.status=='Closed' and self.docstatus != 2"], ["Closed", "eval:self.status=='Closed' and self.docstatus != 2"],

View File

@@ -62,9 +62,12 @@ class StockController(AccountsController):
) )
) )
is_asset_pr = any(d.get("is_fixed_asset") for d in self.get("items"))
if ( if (
cint(erpnext.is_perpetual_inventory_enabled(self.company)) cint(erpnext.is_perpetual_inventory_enabled(self.company))
or provisional_accounting_for_non_stock_items or provisional_accounting_for_non_stock_items
or is_asset_pr
): ):
warehouse_account = get_warehouse_account_map(self.company) warehouse_account = get_warehouse_account_map(self.company)
@@ -73,11 +76,6 @@ class StockController(AccountsController):
gl_entries = self.get_gl_entries(warehouse_account) gl_entries = self.get_gl_entries(warehouse_account)
make_gl_entries(gl_entries, from_repost=from_repost) make_gl_entries(gl_entries, from_repost=from_repost)
elif self.doctype in ["Purchase Receipt", "Purchase Invoice"] and self.docstatus == 1:
gl_entries = []
gl_entries = self.get_asset_gl_entry(gl_entries)
make_gl_entries(gl_entries, from_repost=from_repost)
def validate_serialized_batch(self): def validate_serialized_batch(self):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
@@ -670,13 +668,21 @@ class StockController(AccountsController):
d.stock_uom_rate = d.rate / (d.conversion_factor or 1) d.stock_uom_rate = d.rate / (d.conversion_factor or 1)
def validate_internal_transfer(self): def validate_internal_transfer(self):
if ( if self.doctype in ("Sales Invoice", "Delivery Note", "Purchase Invoice", "Purchase Receipt"):
self.doctype in ("Sales Invoice", "Delivery Note", "Purchase Invoice", "Purchase Receipt") if self.is_internal_transfer():
and self.is_internal_transfer() self.validate_in_transit_warehouses()
): self.validate_multi_currency()
self.validate_in_transit_warehouses() self.validate_packed_items()
self.validate_multi_currency() else:
self.validate_packed_items() self.validate_internal_transfer_warehouse()
def validate_internal_transfer_warehouse(self):
for row in self.items:
if row.get("target_warehouse"):
row.target_warehouse = None
if row.get("from_warehouse"):
row.from_warehouse = None
def validate_in_transit_warehouses(self): def validate_in_transit_warehouses(self):
if ( if (
@@ -1037,8 +1043,6 @@ def create_item_wise_repost_entries(voucher_type, voucher_no, allow_zero_rate=Fa
repost_entry = frappe.new_doc("Repost Item Valuation") repost_entry = frappe.new_doc("Repost Item Valuation")
repost_entry.based_on = "Item and Warehouse" repost_entry.based_on = "Item and Warehouse"
repost_entry.voucher_type = voucher_type
repost_entry.voucher_no = voucher_no
repost_entry.item_code = sle.item_code repost_entry.item_code = sle.item_code
repost_entry.warehouse = sle.warehouse repost_entry.warehouse = sle.warehouse

View File

@@ -7,7 +7,7 @@ import frappe
from frappe import qb from frappe import qb
from frappe.query_builder.functions import Sum from frappe.query_builder.functions import Sum
from frappe.tests.utils import FrappeTestCase, change_settings from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import add_days, flt, nowdate from frappe.utils import add_days, flt, getdate, nowdate
from erpnext import get_default_cost_center from erpnext import get_default_cost_center
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
@@ -614,6 +614,73 @@ class TestAccountsController(FrappeTestCase):
self.assertEqual(exc_je_for_si, []) self.assertEqual(exc_je_for_si, [])
self.assertEqual(exc_je_for_pe, []) self.assertEqual(exc_je_for_pe, [])
def test_15_gain_loss_on_different_posting_date(self):
# Invoice in Foreign Currency
si = self.create_sales_invoice(
posting_date=add_days(nowdate(), -2), qty=2, conversion_rate=80, rate=1
)
# Payment
pe = (
self.create_payment_entry(posting_date=add_days(nowdate(), -1), amount=2, source_exc_rate=75)
.save()
.submit()
)
# There should be outstanding in both currencies
si.reload()
self.assertEqual(si.outstanding_amount, 2)
self.assert_ledger_outstanding(si.doctype, si.name, 160.0, 2.0)
# Reconcile the remaining amount
pr = frappe.get_doc("Payment Reconciliation")
pr.company = self.company
pr.party_type = "Customer"
pr.party = self.customer
pr.receivable_payable_account = self.debit_usd
pr.get_unreconciled_entries()
self.assertEqual(len(pr.invoices), 1)
self.assertEqual(len(pr.payments), 1)
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.allocation[0].gain_loss_posting_date = add_days(nowdate(), 1)
pr.reconcile()
# Exchange Gain/Loss Journal should've been created.
exc_je_for_si = self.get_journals_for(si.doctype, si.name)
exc_je_for_pe = self.get_journals_for(pe.doctype, pe.name)
self.assertNotEqual(exc_je_for_si, [])
self.assertEqual(len(exc_je_for_si), 1)
self.assertEqual(len(exc_je_for_pe), 1)
self.assertEqual(exc_je_for_si[0], exc_je_for_pe[0])
self.assertEqual(
frappe.db.get_value("Journal Entry", exc_je_for_si[0].parent, "posting_date"),
getdate(add_days(nowdate(), 1)),
)
self.assertEqual(len(pr.invoices), 0)
self.assertEqual(len(pr.payments), 0)
# There should be no outstanding
si.reload()
self.assertEqual(si.outstanding_amount, 0)
self.assert_ledger_outstanding(si.doctype, si.name, 0.0, 0.0)
# Cancel Payment
pe.reload()
pe.cancel()
si.reload()
self.assertEqual(si.outstanding_amount, 2)
self.assert_ledger_outstanding(si.doctype, si.name, 160.0, 2.0)
# Exchange Gain/Loss Journal should've been cancelled
exc_je_for_si = self.get_journals_for(si.doctype, si.name)
exc_je_for_pe = self.get_journals_for(pe.doctype, pe.name)
self.assertEqual(exc_je_for_si, [])
self.assertEqual(exc_je_for_pe, [])
def test_20_journal_against_sales_invoice(self): def test_20_journal_against_sales_invoice(self):
# Invoice in Foreign Currency # Invoice in Foreign Currency
si = self.create_sales_invoice(qty=1, conversion_rate=80, rate=1) si = self.create_sales_invoice(qty=1, conversion_rate=80, rate=1)

View File

@@ -103,6 +103,7 @@
"fieldtype": "Column Break" "fieldtype": "Column Break"
}, },
{ {
"fetch_from": "item_code.image",
"fieldname": "image", "fieldname": "image",
"fieldtype": "Attach", "fieldtype": "Attach",
"hidden": 1, "hidden": 1,
@@ -165,7 +166,7 @@
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2021-07-30 16:39:09.775720", "modified": "2023-11-14 18:35:30.887278",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "CRM", "module": "CRM",
"name": "Opportunity Item", "name": "Opportunity Item",
@@ -173,5 +174,6 @@
"permissions": [], "permissions": [],
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"states": [],
"track_changes": 1 "track_changes": 1
} }

View File

@@ -7,7 +7,7 @@ import frappe
from frappe import _ from frappe import _
from frappe.desk.doctype.tag.tag import add_tag from frappe.desk.doctype.tag.tag import add_tag
from frappe.model.document import Document from frappe.model.document import Document
from frappe.utils import add_months, formatdate, getdate, today from frappe.utils import add_months, formatdate, getdate, sbool, today
from plaid.errors import ItemError from plaid.errors import ItemError
from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account
@@ -238,8 +238,6 @@ def new_bank_transaction(transaction):
deposit = abs(amount) deposit = abs(amount)
withdrawal = 0.0 withdrawal = 0.0
status = "Pending" if transaction["pending"] == "True" else "Settled"
tags = [] tags = []
try: try:
tags += transaction["category"] tags += transaction["category"]
@@ -247,13 +245,14 @@ def new_bank_transaction(transaction):
except KeyError: except KeyError:
pass pass
if not frappe.db.exists("Bank Transaction", dict(transaction_id=transaction["transaction_id"])): if not frappe.db.exists(
"Bank Transaction", dict(transaction_id=transaction["transaction_id"])
) and not sbool(transaction["pending"]):
try: try:
new_transaction = frappe.get_doc( new_transaction = frappe.get_doc(
{ {
"doctype": "Bank Transaction", "doctype": "Bank Transaction",
"date": getdate(transaction["date"]), "date": getdate(transaction["date"]),
"status": status,
"bank_account": bank_account, "bank_account": bank_account,
"deposit": deposit, "deposit": deposit,
"withdrawal": withdrawal, "withdrawal": withdrawal,

View File

@@ -517,6 +517,7 @@ accounting_dimension_doctypes = [
"Sales Invoice Item", "Sales Invoice Item",
"Purchase Invoice Item", "Purchase Invoice Item",
"Purchase Order Item", "Purchase Order Item",
"Sales Order Item",
"Journal Entry Account", "Journal Entry Account",
"Material Request Item", "Material Request Item",
"Delivery Note Item", "Delivery Note Item",

View File

@@ -411,11 +411,6 @@ def close_unsecured_term_loan(loan):
frappe.throw(_("Cannot close this loan until full repayment")) frappe.throw(_("Cannot close this loan until full repayment"))
def close_loan(loan, total_amount_paid):
frappe.db.set_value("Loan", loan, "total_amount_paid", total_amount_paid)
frappe.db.set_value("Loan", loan, "status", "Closed")
@frappe.whitelist() @frappe.whitelist()
def make_loan_disbursement(loan, company, applicant_type, applicant, pending_amount=0, as_dict=0): def make_loan_disbursement(loan, company, applicant_type, applicant, pending_amount=0, as_dict=0):
disbursement_entry = frappe.new_doc("Loan Disbursement") disbursement_entry = frappe.new_doc("Loan Disbursement")

View File

@@ -9,6 +9,9 @@ from frappe.utils import cint, flt, getdate
import erpnext import erpnext
from erpnext.accounts.general_ledger import make_gl_entries from erpnext.accounts.general_ledger import make_gl_entries
from erpnext.controllers.accounts_controller import AccountsController from erpnext.controllers.accounts_controller import AccountsController
from erpnext.loan_management.doctype.loan_repayment.loan_repayment import (
get_pending_principal_amount,
)
class LoanWriteOff(AccountsController): class LoanWriteOff(AccountsController):
@@ -39,11 +42,13 @@ class LoanWriteOff(AccountsController):
def on_submit(self): def on_submit(self):
self.update_outstanding_amount() self.update_outstanding_amount()
self.make_gl_entries() self.make_gl_entries()
self.close_employee_loan()
def on_cancel(self): def on_cancel(self):
self.update_outstanding_amount(cancel=1) self.update_outstanding_amount(cancel=1)
self.ignore_linked_doctypes = ["GL Entry", "Payment Ledger Entry"] self.ignore_linked_doctypes = ["GL Entry", "Payment Ledger Entry"]
self.make_gl_entries(cancel=1) self.make_gl_entries(cancel=1)
self.close_employee_loan(cancel=1)
def update_outstanding_amount(self, cancel=0): def update_outstanding_amount(self, cancel=0):
written_off_amount = frappe.db.get_value("Loan", self.loan, "written_off_amount") written_off_amount = frappe.db.get_value("Loan", self.loan, "written_off_amount")
@@ -94,3 +99,39 @@ class LoanWriteOff(AccountsController):
) )
make_gl_entries(gl_entries, cancel=cancel, merge_entries=False) make_gl_entries(gl_entries, cancel=cancel, merge_entries=False)
def close_employee_loan(self, cancel=0):
if not frappe.db.has_column("Loan", "repay_from_salary"):
return
loan = frappe.get_value(
"Loan",
self.loan,
[
"total_payment",
"total_principal_paid",
"loan_amount",
"total_interest_payable",
"written_off_amount",
"disbursed_amount",
"status",
"is_secured_loan",
"repay_from_salary",
"name",
],
as_dict=1,
)
if loan.is_secured_loan or not loan.repay_from_salary:
return
if not cancel:
pending_principal_amount = get_pending_principal_amount(loan)
precision = cint(frappe.db.get_default("currency_precision")) or 2
if flt(pending_principal_amount, precision) <= 0:
frappe.db.set_value("Loan", loan.name, "status", "Closed")
frappe.msgprint(_("Loan {0} closed").format(loan.name))
else:
frappe.db.set_value("Loan", loan.loan, "status", "Disbursed")

View File

@@ -107,7 +107,7 @@ def validate_against_blanket_order(order_doc):
allowance = flt( allowance = flt(
frappe.db.get_single_value( frappe.db.get_single_value(
"Selling Settings" if order_doc.doctype == "Sales Order" else "Buying Settings", "Selling Settings" if order_doc.doctype == "Sales Order" else "Buying Settings",
"over_order_allowance", "blanket_order_allowance",
) )
) )
for bo_name, item_data in order_data.items(): for bo_name, item_data in order_data.items():

View File

@@ -63,7 +63,7 @@ class TestBlanketOrder(FrappeTestCase):
po1.currency = get_company_currency(po1.company) po1.currency = get_company_currency(po1.company)
self.assertEqual(po1.items[0].qty, (bo.items[0].qty - bo.items[0].ordered_qty)) self.assertEqual(po1.items[0].qty, (bo.items[0].qty - bo.items[0].ordered_qty))
def test_over_order_allowance(self): def test_blanket_order_allowance(self):
# Sales Order # Sales Order
bo = make_blanket_order(blanket_order_type="Selling", quantity=100) bo = make_blanket_order(blanket_order_type="Selling", quantity=100)
@@ -74,7 +74,7 @@ class TestBlanketOrder(FrappeTestCase):
so.items[0].qty = 110 so.items[0].qty = 110
self.assertRaises(frappe.ValidationError, so.submit) self.assertRaises(frappe.ValidationError, so.submit)
frappe.db.set_single_value("Selling Settings", "over_order_allowance", 10) frappe.db.set_single_value("Selling Settings", "blanket_order_allowance", 10)
so.submit() so.submit()
# Purchase Order # Purchase Order
@@ -87,7 +87,7 @@ class TestBlanketOrder(FrappeTestCase):
po.items[0].qty = 110 po.items[0].qty = 110
self.assertRaises(frappe.ValidationError, po.submit) self.assertRaises(frappe.ValidationError, po.submit)
frappe.db.set_single_value("Buying Settings", "over_order_allowance", 10) frappe.db.set_single_value("Buying Settings", "blanket_order_allowance", 10)
po.submit() po.submit()

View File

@@ -1172,12 +1172,12 @@ def get_children(parent=None, is_root=False, **filters):
def add_additional_cost(stock_entry, work_order): def add_additional_cost(stock_entry, work_order):
# Add non stock items cost in the additional cost # Add non stock items cost in the additional cost
stock_entry.additional_costs = [] stock_entry.additional_costs = []
expenses_included_in_valuation = frappe.get_cached_value( default_expense_account = frappe.get_cached_value(
"Company", work_order.company, "expenses_included_in_valuation" "Company", work_order.company, "default_expense_account"
) )
add_non_stock_items_cost(stock_entry, work_order, expenses_included_in_valuation) add_non_stock_items_cost(stock_entry, work_order, default_expense_account)
add_operations_cost(stock_entry, work_order, expenses_included_in_valuation) add_operations_cost(stock_entry, work_order, default_expense_account)
def add_non_stock_items_cost(stock_entry, work_order, expense_account): def add_non_stock_items_cost(stock_entry, work_order, expense_account):

View File

@@ -85,6 +85,7 @@
"fieldtype": "Column Break" "fieldtype": "Column Break"
}, },
{ {
"fetch_from": "item_code.image",
"fieldname": "image", "fieldname": "image",
"fieldtype": "Attach", "fieldtype": "Attach",
"hidden": 1, "hidden": 1,
@@ -169,7 +170,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2022-05-27 13:42:23.305455", "modified": "2023-11-14 18:35:40.856895",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Manufacturing", "module": "Manufacturing",
"name": "BOM Explosion Item", "name": "BOM Explosion Item",

View File

@@ -111,6 +111,7 @@
"fieldtype": "Column Break" "fieldtype": "Column Break"
}, },
{ {
"fetch_from": "item_code.image",
"fieldname": "image", "fieldname": "image",
"fieldtype": "Attach", "fieldtype": "Attach",
"hidden": 1, "hidden": 1,
@@ -289,7 +290,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2022-07-28 10:20:51.559010", "modified": "2023-11-14 18:35:51.378513",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Manufacturing", "module": "Manufacturing",
"name": "BOM Item", "name": "BOM Item",

View File

@@ -10,8 +10,8 @@ frappe.views.calendar["Job Card"] = {
}, },
gantt: { gantt: {
field_map: { field_map: {
"start": "started_time", "start": "expected_start_date",
"end": "started_time", "end": "expected_end_date",
"id": "name", "id": "name",
"title": "subject", "title": "subject",
"color": "color", "color": "color",

View File

@@ -1,6 +1,6 @@
frappe.listview_settings['Job Card'] = { frappe.listview_settings['Job Card'] = {
has_indicator_for_draft: true, has_indicator_for_draft: true,
add_fields: ["expected_start_date", "expected_end_date"],
get_indicator: function(doc) { get_indicator: function(doc) {
const status_colors = { const status_colors = {
"Work In Progress": "orange", "Work In Progress": "orange",

View File

@@ -89,10 +89,6 @@ frappe.ui.form.on('Production Plan', {
frm.trigger("show_progress"); frm.trigger("show_progress");
if (frm.doc.status !== "Completed") { if (frm.doc.status !== "Completed") {
frm.add_custom_button(__("Work Order Tree"), ()=> {
frappe.set_route('Tree', 'Work Order', {production_plan: frm.doc.name});
}, __('View'));
frm.add_custom_button(__("Production Plan Summary"), ()=> { frm.add_custom_button(__("Production Plan Summary"), ()=> {
frappe.set_route('query-report', 'Production Plan Summary', {production_plan: frm.doc.name}); frappe.set_route('query-report', 'Production Plan Summary', {production_plan: frm.doc.name});
}, __('View')); }, __('View'));

Some files were not shown because too many files have changed in this diff Show More