Merge branch 'develop' into make-against-field-dynamic
This commit is contained in:
commit
01f133f8c8
@ -73,8 +73,6 @@ New passwords will be created for the ERPNext "Administrator" user, the MariaDB
|
|||||||
1. [Issue Guidelines](https://github.com/frappe/erpnext/wiki/Issue-Guidelines)
|
1. [Issue Guidelines](https://github.com/frappe/erpnext/wiki/Issue-Guidelines)
|
||||||
1. [Report Security Vulnerabilities](https://erpnext.com/security)
|
1. [Report Security Vulnerabilities](https://erpnext.com/security)
|
||||||
1. [Pull Request Requirements](https://github.com/frappe/erpnext/wiki/Contribution-Guidelines)
|
1. [Pull Request Requirements](https://github.com/frappe/erpnext/wiki/Contribution-Guidelines)
|
||||||
1. [Translations](https://translate.erpnext.com)
|
|
||||||
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
@ -66,7 +66,12 @@
|
|||||||
"show_balance_in_coa",
|
"show_balance_in_coa",
|
||||||
"banking_tab",
|
"banking_tab",
|
||||||
"enable_party_matching",
|
"enable_party_matching",
|
||||||
"enable_fuzzy_matching"
|
"enable_fuzzy_matching",
|
||||||
|
"reports_tab",
|
||||||
|
"remarks_section",
|
||||||
|
"general_ledger_remarks_length",
|
||||||
|
"column_break_lvjk",
|
||||||
|
"receivable_payable_remarks_length"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@ -422,6 +427,34 @@
|
|||||||
"fieldname": "round_row_wise_tax",
|
"fieldname": "round_row_wise_tax",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Round Tax Amount Row-wise"
|
"label": "Round Tax Amount Row-wise"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "reports_tab",
|
||||||
|
"fieldtype": "Tab Break",
|
||||||
|
"label": "Reports"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"description": "Truncates 'Remarks' column to set character length",
|
||||||
|
"fieldname": "general_ledger_remarks_length",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"label": "General Ledger"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"description": "Truncates 'Remarks' column to set character length",
|
||||||
|
"fieldname": "receivable_payable_remarks_length",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"label": "Accounts Receivable/Payable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_lvjk",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "remarks_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Remarks Column Length"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "icon-cog",
|
"icon": "icon-cog",
|
||||||
@ -429,7 +462,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-08-28 00:12:02.740633",
|
"modified": "2023-11-20 09:37:47.650347",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Accounts Settings",
|
"name": "Accounts Settings",
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -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,33 +333,16 @@ 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"],
|
|
||||||
["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)
|
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
|
||||||
|
@ -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
|
|
@ -51,7 +51,7 @@ frappe.ui.form.on("Journal Entry", {
|
|||||||
}, __('Make'));
|
}, __('Make'));
|
||||||
}
|
}
|
||||||
|
|
||||||
erpnext.accounts.unreconcile_payments.add_unreconcile_btn(frm);
|
erpnext.accounts.unreconcile_payment.add_unreconcile_btn(frm);
|
||||||
},
|
},
|
||||||
before_save: function(frm) {
|
before_save: function(frm) {
|
||||||
if ((frm.doc.docstatus == 0) && (!frm.doc.is_system_generated)) {
|
if ((frm.doc.docstatus == 0) && (!frm.doc.is_system_generated)) {
|
||||||
|
@ -548,8 +548,16 @@
|
|||||||
"icon": "fa fa-file-text",
|
"icon": "fa fa-file-text",
|
||||||
"idx": 176,
|
"idx": 176,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [
|
||||||
"modified": "2023-08-10 14:32:22.366895",
|
{
|
||||||
|
"is_child_table": 1,
|
||||||
|
"link_doctype": "Bank Transaction Payments",
|
||||||
|
"link_fieldname": "payment_entry",
|
||||||
|
"parent_doctype": "Bank Transaction",
|
||||||
|
"table_fieldname": "payment_entries"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"modified": "2023-11-23 12:11:04.128015",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Journal Entry",
|
"name": "Journal Entry",
|
||||||
|
@ -910,7 +910,7 @@ class JournalEntry(AccountsController):
|
|||||||
party_account_currency = d.account_currency
|
party_account_currency = d.account_currency
|
||||||
|
|
||||||
elif frappe.get_cached_value("Account", d.account, "account_type") in ["Bank", "Cash"]:
|
elif frappe.get_cached_value("Account", d.account, "account_type") in ["Bank", "Cash"]:
|
||||||
bank_amount += d.debit_in_account_currency or d.credit_in_account_currency
|
bank_amount += flt(d.debit_in_account_currency) or flt(d.credit_in_account_currency)
|
||||||
bank_account_currency = d.account_currency
|
bank_account_currency = d.account_currency
|
||||||
|
|
||||||
if party_type and pay_to_recd_from:
|
if party_type and pay_to_recd_from:
|
||||||
|
@ -205,7 +205,8 @@
|
|||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"label": "Reference Type",
|
"label": "Reference Type",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "\nSales Invoice\nPurchase Invoice\nJournal Entry\nSales Order\nPurchase Order\nExpense Claim\nAsset\nLoan\nPayroll Entry\nEmployee Advance\nExchange Rate Revaluation\nInvoice Discounting\nFees\nFull and Final Statement\nPayment Entry"
|
"options": "\nSales Invoice\nPurchase Invoice\nJournal Entry\nSales Order\nPurchase Order\nExpense Claim\nAsset\nLoan\nPayroll Entry\nEmployee Advance\nExchange Rate Revaluation\nInvoice Discounting\nFees\nFull and Final Statement\nPayment Entry",
|
||||||
|
"search_index": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "reference_name",
|
"fieldname": "reference_name",
|
||||||
@ -213,7 +214,8 @@
|
|||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Reference Name",
|
"label": "Reference Name",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "reference_type"
|
"options": "reference_type",
|
||||||
|
"search_index": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:doc.reference_type&&!in_list(doc.reference_type, ['Expense Claim', 'Asset', 'Employee Loan', 'Employee Advance'])",
|
"depends_on": "eval:doc.reference_type&&!in_list(doc.reference_type, ['Expense Claim', 'Asset', 'Employee Loan', 'Employee Advance'])",
|
||||||
@ -301,7 +303,7 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-11-08 12:20:21.489496",
|
"modified": "2023-11-23 11:44:25.841187",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Journal Entry Account",
|
"name": "Journal Entry Account",
|
||||||
|
@ -9,7 +9,7 @@ erpnext.accounts.taxes.setup_tax_filters("Advance Taxes and Charges");
|
|||||||
|
|
||||||
frappe.ui.form.on('Payment Entry', {
|
frappe.ui.form.on('Payment Entry', {
|
||||||
onload: function(frm) {
|
onload: function(frm) {
|
||||||
frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', 'Repost Payment Ledger','Repost Accounting Ledger', 'Unreconcile Payments', 'Unreconcile Payment Entries'];
|
frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', 'Repost Payment Ledger','Repost Accounting Ledger', 'Unreconcile Payment', 'Unreconcile Payment Entries'];
|
||||||
|
|
||||||
if(frm.doc.__islocal) {
|
if(frm.doc.__islocal) {
|
||||||
if (!frm.doc.paid_from) frm.set_value("paid_from_account_currency", null);
|
if (!frm.doc.paid_from) frm.set_value("paid_from_account_currency", null);
|
||||||
@ -154,13 +154,13 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
frm.events.set_dynamic_labels(frm);
|
frm.events.set_dynamic_labels(frm);
|
||||||
frm.events.show_general_ledger(frm);
|
frm.events.show_general_ledger(frm);
|
||||||
erpnext.accounts.ledger_preview.show_accounting_ledger_preview(frm);
|
erpnext.accounts.ledger_preview.show_accounting_ledger_preview(frm);
|
||||||
if(frm.doc.references.find((elem) => {return elem.exchange_gain_loss != 0})) {
|
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() {
|
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});
|
frappe.set_route("List", "Journal Entry", {"voucher_type": "Exchange Gain Or Loss", "reference_name": frm.doc.name});
|
||||||
}, __('Actions'));
|
}, __('Actions'));
|
||||||
|
|
||||||
}
|
}
|
||||||
erpnext.accounts.unreconcile_payments.add_unreconcile_btn(frm);
|
erpnext.accounts.unreconcile_payment.add_unreconcile_btn(frm);
|
||||||
},
|
},
|
||||||
|
|
||||||
validate_company: (frm) => {
|
validate_company: (frm) => {
|
||||||
@ -853,6 +853,7 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
|
|
||||||
var allocated_positive_outstanding = paid_amount + allocated_negative_outstanding;
|
var allocated_positive_outstanding = paid_amount + allocated_negative_outstanding;
|
||||||
} else if (in_list(["Customer", "Supplier"], frm.doc.party_type)) {
|
} else if (in_list(["Customer", "Supplier"], frm.doc.party_type)) {
|
||||||
|
total_negative_outstanding = flt(total_negative_outstanding, precision("outstanding_amount"))
|
||||||
if(paid_amount > total_negative_outstanding) {
|
if(paid_amount > total_negative_outstanding) {
|
||||||
if(total_negative_outstanding == 0) {
|
if(total_negative_outstanding == 0) {
|
||||||
frappe.msgprint(
|
frappe.msgprint(
|
||||||
|
@ -595,6 +595,7 @@
|
|||||||
"fieldname": "status",
|
"fieldname": "status",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"label": "Status",
|
"label": "Status",
|
||||||
|
"no_copy": 1,
|
||||||
"options": "\nDraft\nSubmitted\nCancelled",
|
"options": "\nDraft\nSubmitted\nCancelled",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
@ -749,8 +750,16 @@
|
|||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [
|
||||||
"modified": "2023-06-23 18:07:38.023010",
|
{
|
||||||
|
"is_child_table": 1,
|
||||||
|
"link_doctype": "Bank Transaction Payments",
|
||||||
|
"link_fieldname": "payment_entry",
|
||||||
|
"parent_doctype": "Bank Transaction",
|
||||||
|
"table_fieldname": "payment_entries"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"modified": "2023-11-23 12:07:20.887885",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Payment Entry",
|
"name": "Payment Entry",
|
||||||
|
@ -33,6 +33,7 @@ from erpnext.accounts.utils import (
|
|||||||
get_account_currency,
|
get_account_currency,
|
||||||
get_balance_on,
|
get_balance_on,
|
||||||
get_outstanding_invoices,
|
get_outstanding_invoices,
|
||||||
|
get_party_types_from_account_type,
|
||||||
)
|
)
|
||||||
from erpnext.controllers.accounts_controller import (
|
from erpnext.controllers.accounts_controller import (
|
||||||
AccountsController,
|
AccountsController,
|
||||||
@ -83,7 +84,6 @@ class PaymentEntry(AccountsController):
|
|||||||
self.apply_taxes()
|
self.apply_taxes()
|
||||||
self.set_amounts_after_tax()
|
self.set_amounts_after_tax()
|
||||||
self.clear_unallocated_reference_document_rows()
|
self.clear_unallocated_reference_document_rows()
|
||||||
self.validate_payment_against_negative_invoice()
|
|
||||||
self.validate_transaction_reference()
|
self.validate_transaction_reference()
|
||||||
self.set_title()
|
self.set_title()
|
||||||
self.set_remarks()
|
self.set_remarks()
|
||||||
@ -148,7 +148,7 @@ class PaymentEntry(AccountsController):
|
|||||||
"Repost Payment Ledger Items",
|
"Repost Payment Ledger Items",
|
||||||
"Repost Accounting Ledger",
|
"Repost Accounting Ledger",
|
||||||
"Repost Accounting Ledger Items",
|
"Repost Accounting Ledger Items",
|
||||||
"Unreconcile Payments",
|
"Unreconcile Payment",
|
||||||
"Unreconcile Payment Entries",
|
"Unreconcile Payment Entries",
|
||||||
)
|
)
|
||||||
super(PaymentEntry, self).on_cancel()
|
super(PaymentEntry, self).on_cancel()
|
||||||
@ -952,35 +952,6 @@ class PaymentEntry(AccountsController):
|
|||||||
self.name,
|
self.name,
|
||||||
)
|
)
|
||||||
|
|
||||||
def validate_payment_against_negative_invoice(self):
|
|
||||||
if (self.payment_type != "Pay" or self.party_type != "Customer") and (
|
|
||||||
self.payment_type != "Receive" or self.party_type != "Supplier"
|
|
||||||
):
|
|
||||||
return
|
|
||||||
|
|
||||||
total_negative_outstanding = sum(
|
|
||||||
abs(flt(d.outstanding_amount)) for d in self.get("references") if flt(d.outstanding_amount) < 0
|
|
||||||
)
|
|
||||||
|
|
||||||
paid_amount = self.paid_amount if self.payment_type == "Receive" else self.received_amount
|
|
||||||
additional_charges = sum(flt(d.amount) for d in self.deductions)
|
|
||||||
|
|
||||||
if not total_negative_outstanding:
|
|
||||||
if self.party_type == "Customer":
|
|
||||||
msg = _("Cannot pay to Customer without any negative outstanding invoice")
|
|
||||||
else:
|
|
||||||
msg = _("Cannot receive from Supplier without any negative outstanding invoice")
|
|
||||||
|
|
||||||
frappe.throw(msg, InvalidPaymentEntry)
|
|
||||||
|
|
||||||
elif paid_amount - additional_charges > total_negative_outstanding:
|
|
||||||
frappe.throw(
|
|
||||||
_("Paid Amount cannot be greater than total negative outstanding amount {0}").format(
|
|
||||||
fmt_money(total_negative_outstanding)
|
|
||||||
),
|
|
||||||
InvalidPaymentEntry,
|
|
||||||
)
|
|
||||||
|
|
||||||
def set_title(self):
|
def set_title(self):
|
||||||
if frappe.flags.in_import and self.title:
|
if frappe.flags.in_import and self.title:
|
||||||
# do not set title dynamically if title exists during data import.
|
# do not set title dynamically if title exists during data import.
|
||||||
@ -1051,6 +1022,7 @@ class PaymentEntry(AccountsController):
|
|||||||
self.add_bank_gl_entries(gl_entries)
|
self.add_bank_gl_entries(gl_entries)
|
||||||
self.add_deductions_gl_entries(gl_entries)
|
self.add_deductions_gl_entries(gl_entries)
|
||||||
self.add_tax_gl_entries(gl_entries)
|
self.add_tax_gl_entries(gl_entries)
|
||||||
|
add_regional_gl_entries(gl_entries, self)
|
||||||
return gl_entries
|
return gl_entries
|
||||||
|
|
||||||
def make_gl_entries(self, cancel=0, adv_adj=0):
|
def make_gl_entries(self, cancel=0, adv_adj=0):
|
||||||
@ -1085,11 +1057,9 @@ class PaymentEntry(AccountsController):
|
|||||||
item=self,
|
item=self,
|
||||||
)
|
)
|
||||||
|
|
||||||
dr_or_cr = (
|
|
||||||
"credit" if erpnext.get_party_account_type(self.party_type) == "Receivable" else "debit"
|
|
||||||
)
|
|
||||||
|
|
||||||
for d in self.get("references"):
|
for d in self.get("references"):
|
||||||
|
# re-defining dr_or_cr for every reference in order to avoid the last value affecting calculation of reverse
|
||||||
|
dr_or_cr = "credit" if self.payment_type == "Receive" else "debit"
|
||||||
cost_center = self.cost_center
|
cost_center = self.cost_center
|
||||||
if d.reference_doctype == "Sales Invoice" and not cost_center:
|
if d.reference_doctype == "Sales Invoice" and not cost_center:
|
||||||
cost_center = frappe.db.get_value(d.reference_doctype, d.reference_name, "cost_center")
|
cost_center = frappe.db.get_value(d.reference_doctype, d.reference_name, "cost_center")
|
||||||
@ -1105,10 +1075,25 @@ class PaymentEntry(AccountsController):
|
|||||||
against_voucher_type = d.reference_doctype
|
against_voucher_type = d.reference_doctype
|
||||||
against_voucher = d.reference_name
|
against_voucher = d.reference_name
|
||||||
|
|
||||||
|
reverse_dr_or_cr = 0
|
||||||
|
if d.reference_doctype in ["Sales Invoice", "Purchase Invoice"]:
|
||||||
|
is_return = frappe.db.get_value(d.reference_doctype, d.reference_name, "is_return")
|
||||||
|
payable_party_types = get_party_types_from_account_type("Payable")
|
||||||
|
receivable_party_types = get_party_types_from_account_type("Receivable")
|
||||||
|
if is_return and self.party_type in receivable_party_types and (self.payment_type == "Pay"):
|
||||||
|
reverse_dr_or_cr = 1
|
||||||
|
elif (
|
||||||
|
is_return and self.party_type in payable_party_types and (self.payment_type == "Receive")
|
||||||
|
):
|
||||||
|
reverse_dr_or_cr = 1
|
||||||
|
|
||||||
|
if is_return and not reverse_dr_or_cr:
|
||||||
|
dr_or_cr = "debit" if dr_or_cr == "credit" else "credit"
|
||||||
|
|
||||||
gle.update(
|
gle.update(
|
||||||
{
|
{
|
||||||
dr_or_cr: allocated_amount_in_company_currency,
|
dr_or_cr: abs(allocated_amount_in_company_currency),
|
||||||
dr_or_cr + "_in_account_currency": d.allocated_amount,
|
dr_or_cr + "_in_account_currency": abs(d.allocated_amount),
|
||||||
"against_voucher_type": against_voucher_type,
|
"against_voucher_type": against_voucher_type,
|
||||||
"against_voucher": against_voucher,
|
"against_voucher": against_voucher,
|
||||||
"cost_center": cost_center,
|
"cost_center": cost_center,
|
||||||
@ -1116,6 +1101,7 @@ class PaymentEntry(AccountsController):
|
|||||||
)
|
)
|
||||||
gl_entries.append(gle)
|
gl_entries.append(gle)
|
||||||
|
|
||||||
|
dr_or_cr = "credit" if self.payment_type == "Receive" else "debit"
|
||||||
if self.unallocated_amount:
|
if self.unallocated_amount:
|
||||||
exchange_rate = self.get_exchange_rate()
|
exchange_rate = self.get_exchange_rate()
|
||||||
base_unallocated_amount = self.unallocated_amount * exchange_rate
|
base_unallocated_amount = self.unallocated_amount * exchange_rate
|
||||||
@ -1711,13 +1697,42 @@ def get_outstanding_reference_documents(args, validate=False):
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
def split_invoices_based_on_payment_terms(outstanding_invoices, company):
|
def split_invoices_based_on_payment_terms(outstanding_invoices, company) -> list:
|
||||||
invoice_ref_based_on_payment_terms = {}
|
"""Split a list of invoices based on their payment terms."""
|
||||||
|
exc_rates = get_currency_data(outstanding_invoices, company)
|
||||||
|
|
||||||
|
outstanding_invoices_after_split = []
|
||||||
|
for entry in outstanding_invoices:
|
||||||
|
if entry.voucher_type in ["Sales Invoice", "Purchase Invoice"]:
|
||||||
|
if payment_term_template := frappe.db.get_value(
|
||||||
|
entry.voucher_type, entry.voucher_no, "payment_terms_template"
|
||||||
|
):
|
||||||
|
split_rows = get_split_invoice_rows(entry, payment_term_template, exc_rates)
|
||||||
|
if not split_rows:
|
||||||
|
continue
|
||||||
|
|
||||||
|
frappe.msgprint(
|
||||||
|
_("Splitting {0} {1} into {2} rows as per Payment Terms").format(
|
||||||
|
_(entry.voucher_type), frappe.bold(entry.voucher_no), len(split_rows)
|
||||||
|
),
|
||||||
|
alert=True,
|
||||||
|
)
|
||||||
|
outstanding_invoices_after_split += split_rows
|
||||||
|
continue
|
||||||
|
|
||||||
|
# If not an invoice or no payment terms template, add as it is
|
||||||
|
outstanding_invoices_after_split.append(entry)
|
||||||
|
|
||||||
|
return outstanding_invoices_after_split
|
||||||
|
|
||||||
|
|
||||||
|
def get_currency_data(outstanding_invoices: list, company: str = None) -> dict:
|
||||||
|
"""Get currency and conversion data for a list of invoices."""
|
||||||
|
exc_rates = frappe._dict()
|
||||||
company_currency = (
|
company_currency = (
|
||||||
frappe.db.get_value("Company", company, "default_currency") if company else None
|
frappe.db.get_value("Company", company, "default_currency") if company else None
|
||||||
)
|
)
|
||||||
exc_rates = frappe._dict()
|
|
||||||
for doctype in ["Sales Invoice", "Purchase Invoice"]:
|
for doctype in ["Sales Invoice", "Purchase Invoice"]:
|
||||||
invoices = [x.voucher_no for x in outstanding_invoices if x.voucher_type == doctype]
|
invoices = [x.voucher_no for x in outstanding_invoices if x.voucher_type == doctype]
|
||||||
for x in frappe.db.get_all(
|
for x in frappe.db.get_all(
|
||||||
@ -1732,22 +1747,26 @@ def split_invoices_based_on_payment_terms(outstanding_invoices, company):
|
|||||||
company_currency=company_currency,
|
company_currency=company_currency,
|
||||||
)
|
)
|
||||||
|
|
||||||
for idx, d in enumerate(outstanding_invoices):
|
return exc_rates
|
||||||
if d.voucher_type in ["Sales Invoice", "Purchase Invoice"]:
|
|
||||||
payment_term_template = frappe.db.get_value(
|
|
||||||
d.voucher_type, d.voucher_no, "payment_terms_template"
|
def get_split_invoice_rows(invoice: dict, payment_term_template: str, exc_rates: dict) -> list:
|
||||||
)
|
"""Split invoice based on its payment schedule table."""
|
||||||
if payment_term_template:
|
split_rows = []
|
||||||
allocate_payment_based_on_payment_terms = frappe.get_cached_value(
|
allocate_payment_based_on_payment_terms = frappe.db.get_value(
|
||||||
"Payment Terms Template", payment_term_template, "allocate_payment_based_on_payment_terms"
|
"Payment Terms Template", payment_term_template, "allocate_payment_based_on_payment_terms"
|
||||||
)
|
)
|
||||||
if allocate_payment_based_on_payment_terms:
|
|
||||||
payment_schedule = frappe.get_all(
|
|
||||||
"Payment Schedule", filters={"parent": d.voucher_no}, fields=["*"]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
if not allocate_payment_based_on_payment_terms:
|
||||||
|
return [invoice]
|
||||||
|
|
||||||
|
payment_schedule = frappe.get_all(
|
||||||
|
"Payment Schedule", filters={"parent": invoice.voucher_no}, fields=["*"], order_by="due_date"
|
||||||
|
)
|
||||||
for payment_term in payment_schedule:
|
for payment_term in payment_schedule:
|
||||||
if payment_term.outstanding > 0.1:
|
if not payment_term.outstanding > 0.1:
|
||||||
|
continue
|
||||||
|
|
||||||
doc_details = exc_rates.get(payment_term.parent, None)
|
doc_details = exc_rates.get(payment_term.parent, None)
|
||||||
is_multi_currency_acc = (doc_details.currency != doc_details.company_currency) and (
|
is_multi_currency_acc = (doc_details.currency != doc_details.company_currency) and (
|
||||||
doc_details.party_account_currency != doc_details.company_currency
|
doc_details.party_account_currency != doc_details.company_currency
|
||||||
@ -1756,48 +1775,26 @@ def split_invoices_based_on_payment_terms(outstanding_invoices, company):
|
|||||||
if not is_multi_currency_acc:
|
if not is_multi_currency_acc:
|
||||||
payment_term_outstanding = doc_details.conversion_rate * flt(payment_term.outstanding)
|
payment_term_outstanding = doc_details.conversion_rate * flt(payment_term.outstanding)
|
||||||
|
|
||||||
invoice_ref_based_on_payment_terms.setdefault(idx, [])
|
split_rows.append(
|
||||||
invoice_ref_based_on_payment_terms[idx].append(
|
|
||||||
frappe._dict(
|
frappe._dict(
|
||||||
{
|
{
|
||||||
"due_date": d.due_date,
|
"due_date": invoice.due_date,
|
||||||
"currency": d.currency,
|
"currency": invoice.currency,
|
||||||
"voucher_no": d.voucher_no,
|
"voucher_no": invoice.voucher_no,
|
||||||
"voucher_type": d.voucher_type,
|
"voucher_type": invoice.voucher_type,
|
||||||
"posting_date": d.posting_date,
|
"posting_date": invoice.posting_date,
|
||||||
"invoice_amount": flt(d.invoice_amount),
|
"invoice_amount": flt(invoice.invoice_amount),
|
||||||
"outstanding_amount": payment_term_outstanding
|
"outstanding_amount": payment_term_outstanding
|
||||||
if payment_term_outstanding
|
if payment_term_outstanding
|
||||||
else d.outstanding_amount,
|
else invoice.outstanding_amount,
|
||||||
"payment_term_outstanding": payment_term_outstanding,
|
"payment_term_outstanding": payment_term_outstanding,
|
||||||
"payment_amount": payment_term.payment_amount,
|
"payment_amount": payment_term.payment_amount,
|
||||||
"payment_term": payment_term.payment_term,
|
"payment_term": payment_term.payment_term,
|
||||||
"account": d.account,
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
outstanding_invoices_after_split = []
|
return split_rows
|
||||||
if invoice_ref_based_on_payment_terms:
|
|
||||||
for idx, ref in invoice_ref_based_on_payment_terms.items():
|
|
||||||
voucher_no = ref[0]["voucher_no"]
|
|
||||||
voucher_type = ref[0]["voucher_type"]
|
|
||||||
|
|
||||||
frappe.msgprint(
|
|
||||||
_("Spliting {} {} into {} row(s) as per Payment Terms").format(
|
|
||||||
voucher_type, voucher_no, len(ref)
|
|
||||||
),
|
|
||||||
alert=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
outstanding_invoices_after_split += invoice_ref_based_on_payment_terms[idx]
|
|
||||||
|
|
||||||
existing_row = list(filter(lambda x: x.get("voucher_no") == voucher_no, outstanding_invoices))
|
|
||||||
index = outstanding_invoices.index(existing_row[0])
|
|
||||||
outstanding_invoices.pop(index)
|
|
||||||
|
|
||||||
outstanding_invoices_after_split += outstanding_invoices
|
|
||||||
return outstanding_invoices_after_split
|
|
||||||
|
|
||||||
|
|
||||||
def get_orders_to_be_billed(
|
def get_orders_to_be_billed(
|
||||||
@ -2638,3 +2635,8 @@ def make_payment_order(source_name, target_doc=None):
|
|||||||
)
|
)
|
||||||
|
|
||||||
return doclist
|
return doclist
|
||||||
|
|
||||||
|
|
||||||
|
@erpnext.allow_regional
|
||||||
|
def add_regional_gl_entries(gl_entries, doc):
|
||||||
|
return
|
||||||
|
@ -6,10 +6,11 @@ import unittest
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import qb
|
from frappe import qb
|
||||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||||
from frappe.utils import flt, nowdate
|
from frappe.utils import add_days, flt, nowdate
|
||||||
|
|
||||||
from erpnext.accounts.doctype.payment_entry.payment_entry import (
|
from erpnext.accounts.doctype.payment_entry.payment_entry import (
|
||||||
InvalidPaymentEntry,
|
InvalidPaymentEntry,
|
||||||
|
get_outstanding_reference_documents,
|
||||||
get_payment_entry,
|
get_payment_entry,
|
||||||
get_reference_details,
|
get_reference_details,
|
||||||
)
|
)
|
||||||
@ -683,17 +684,6 @@ class TestPaymentEntry(FrappeTestCase):
|
|||||||
self.validate_gl_entries(pe.name, expected_gle)
|
self.validate_gl_entries(pe.name, expected_gle)
|
||||||
|
|
||||||
def test_payment_against_negative_sales_invoice(self):
|
def test_payment_against_negative_sales_invoice(self):
|
||||||
pe1 = frappe.new_doc("Payment Entry")
|
|
||||||
pe1.payment_type = "Pay"
|
|
||||||
pe1.company = "_Test Company"
|
|
||||||
pe1.party_type = "Customer"
|
|
||||||
pe1.party = "_Test Customer"
|
|
||||||
pe1.paid_from = "_Test Cash - _TC"
|
|
||||||
pe1.paid_amount = 100
|
|
||||||
pe1.received_amount = 100
|
|
||||||
|
|
||||||
self.assertRaises(InvalidPaymentEntry, pe1.validate)
|
|
||||||
|
|
||||||
si1 = create_sales_invoice()
|
si1 = create_sales_invoice()
|
||||||
|
|
||||||
# create full payment entry against si1
|
# create full payment entry against si1
|
||||||
@ -751,8 +741,6 @@ class TestPaymentEntry(FrappeTestCase):
|
|||||||
|
|
||||||
# pay more than outstanding against si1
|
# pay more than outstanding against si1
|
||||||
pe3 = get_payment_entry("Sales Invoice", si1.name, bank_account="_Test Cash - _TC")
|
pe3 = get_payment_entry("Sales Invoice", si1.name, bank_account="_Test Cash - _TC")
|
||||||
pe3.paid_amount = pe3.received_amount = 300
|
|
||||||
self.assertRaises(InvalidPaymentEntry, pe3.validate)
|
|
||||||
|
|
||||||
# pay negative outstanding against si1
|
# pay negative outstanding against si1
|
||||||
pe3.paid_to = "Debtors - _TC"
|
pe3.paid_to = "Debtors - _TC"
|
||||||
@ -1262,6 +1250,130 @@ class TestPaymentEntry(FrappeTestCase):
|
|||||||
so.reload()
|
so.reload()
|
||||||
self.assertEqual(so.advance_paid, so.rounded_total)
|
self.assertEqual(so.advance_paid, so.rounded_total)
|
||||||
|
|
||||||
|
def test_outstanding_invoices_api(self):
|
||||||
|
"""
|
||||||
|
Test if `get_outstanding_reference_documents` fetches invoices in the right order.
|
||||||
|
"""
|
||||||
|
customer = create_customer("Max Mustermann", "INR")
|
||||||
|
create_payment_terms_template()
|
||||||
|
|
||||||
|
# SI has an earlier due date and SI2 has a later due date
|
||||||
|
si = create_sales_invoice(
|
||||||
|
qty=1, rate=100, customer=customer, posting_date=add_days(nowdate(), -4)
|
||||||
|
)
|
||||||
|
si2 = create_sales_invoice(do_not_save=1, qty=1, rate=100, customer=customer)
|
||||||
|
si2.payment_terms_template = "Test Receivable Template"
|
||||||
|
si2.submit()
|
||||||
|
|
||||||
|
args = {
|
||||||
|
"posting_date": nowdate(),
|
||||||
|
"company": "_Test Company",
|
||||||
|
"party_type": "Customer",
|
||||||
|
"payment_type": "Pay",
|
||||||
|
"party": customer,
|
||||||
|
"party_account": "Debtors - _TC",
|
||||||
|
}
|
||||||
|
args.update(
|
||||||
|
{
|
||||||
|
"get_outstanding_invoices": True,
|
||||||
|
"from_posting_date": add_days(nowdate(), -4),
|
||||||
|
"to_posting_date": add_days(nowdate(), 2),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
references = get_outstanding_reference_documents(args)
|
||||||
|
|
||||||
|
self.assertEqual(len(references), 3)
|
||||||
|
self.assertEqual(references[0].voucher_no, si.name)
|
||||||
|
self.assertEqual(references[1].voucher_no, si2.name)
|
||||||
|
self.assertEqual(references[2].voucher_no, si2.name)
|
||||||
|
self.assertEqual(references[1].payment_term, "Basic Amount Receivable")
|
||||||
|
self.assertEqual(references[2].payment_term, "Tax Receivable")
|
||||||
|
|
||||||
|
def test_receive_payment_from_payable_party_type(self):
|
||||||
|
"""
|
||||||
|
Checks GL entries generated while receiving payments from a Payable Party Type.
|
||||||
|
"""
|
||||||
|
pe = create_payment_entry(
|
||||||
|
party_type="Supplier",
|
||||||
|
party="_Test Supplier",
|
||||||
|
payment_type="Receive",
|
||||||
|
paid_from="Creditors - _TC",
|
||||||
|
paid_to="_Test Cash - _TC",
|
||||||
|
save=True,
|
||||||
|
submit=True,
|
||||||
|
)
|
||||||
|
self.voucher_no = pe.name
|
||||||
|
self.expected_gle = [
|
||||||
|
{"account": "Creditors - _TC", "debit": 0.0, "credit": 1000.0},
|
||||||
|
{"account": "_Test Cash - _TC", "debit": 1000.0, "credit": 0.0},
|
||||||
|
]
|
||||||
|
self.check_gl_entries()
|
||||||
|
|
||||||
|
def test_payment_against_partial_return_invoice(self):
|
||||||
|
"""
|
||||||
|
Checks GL entries generated for partial return invoice payments.
|
||||||
|
"""
|
||||||
|
si = create_sales_invoice(qty=10, rate=10, customer="_Test Customer")
|
||||||
|
credit_note = create_sales_invoice(
|
||||||
|
qty=-4, rate=10, customer="_Test Customer", is_return=1, return_against=si.name
|
||||||
|
)
|
||||||
|
pe = create_payment_entry(
|
||||||
|
party_type="Customer",
|
||||||
|
party="_Test Customer",
|
||||||
|
payment_type="Receive",
|
||||||
|
paid_from="Debtors - _TC",
|
||||||
|
paid_to="_Test Cash - _TC",
|
||||||
|
)
|
||||||
|
pe.set(
|
||||||
|
"references",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"reference_doctype": "Sales Invoice",
|
||||||
|
"reference_name": si.name,
|
||||||
|
"due_date": si.get("due_date"),
|
||||||
|
"total_amount": si.grand_total,
|
||||||
|
"outstanding_amount": si.outstanding_amount,
|
||||||
|
"allocated_amount": si.outstanding_amount,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"reference_doctype": "Sales Invoice",
|
||||||
|
"reference_name": credit_note.name,
|
||||||
|
"due_date": credit_note.get("due_date"),
|
||||||
|
"total_amount": credit_note.grand_total,
|
||||||
|
"outstanding_amount": credit_note.outstanding_amount,
|
||||||
|
"allocated_amount": credit_note.outstanding_amount,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
pe.save()
|
||||||
|
pe.submit()
|
||||||
|
self.assertEqual(pe.total_allocated_amount, 60)
|
||||||
|
self.assertEqual(pe.unallocated_amount, 940)
|
||||||
|
self.voucher_no = pe.name
|
||||||
|
self.expected_gle = [
|
||||||
|
{"account": "Debtors - _TC", "debit": 40.0, "credit": 0.0},
|
||||||
|
{"account": "Debtors - _TC", "debit": 0.0, "credit": 940.0},
|
||||||
|
{"account": "Debtors - _TC", "debit": 0.0, "credit": 100.0},
|
||||||
|
{"account": "_Test Cash - _TC", "debit": 1000.0, "credit": 0.0},
|
||||||
|
]
|
||||||
|
self.check_gl_entries()
|
||||||
|
|
||||||
|
def check_gl_entries(self):
|
||||||
|
gle = frappe.qb.DocType("GL Entry")
|
||||||
|
gl_entries = (
|
||||||
|
frappe.qb.from_(gle)
|
||||||
|
.select(
|
||||||
|
gle.account,
|
||||||
|
gle.debit,
|
||||||
|
gle.credit,
|
||||||
|
)
|
||||||
|
.where((gle.voucher_no == self.voucher_no) & (gle.is_cancelled == 0))
|
||||||
|
.orderby(gle.account, gle.debit, gle.credit, order=frappe.qb.desc)
|
||||||
|
).run(as_dict=True)
|
||||||
|
for row in range(len(self.expected_gle)):
|
||||||
|
for field in ["account", "debit", "credit"]:
|
||||||
|
self.assertEqual(self.expected_gle[row][field], gl_entries[row][field])
|
||||||
|
|
||||||
|
|
||||||
def create_payment_entry(**args):
|
def create_payment_entry(**args):
|
||||||
payment_entry = frappe.new_doc("Payment Entry")
|
payment_entry = frappe.new_doc("Payment Entry")
|
||||||
@ -1322,6 +1434,9 @@ def create_payment_terms_template():
|
|||||||
def create_payment_terms_template_with_discount(
|
def create_payment_terms_template_with_discount(
|
||||||
name=None, discount_type=None, discount=None, template_name=None
|
name=None, discount_type=None, discount=None, template_name=None
|
||||||
):
|
):
|
||||||
|
"""
|
||||||
|
Create a Payment Terms Template with % or amount discount.
|
||||||
|
"""
|
||||||
create_payment_term(name or "30 Credit Days with 10% Discount")
|
create_payment_term(name or "30 Credit Days with 10% Discount")
|
||||||
template_name = template_name or "Test Discount Template"
|
template_name = template_name or "Test Discount Template"
|
||||||
|
|
||||||
|
@ -212,9 +212,10 @@
|
|||||||
],
|
],
|
||||||
"hide_toolbar": 1,
|
"hide_toolbar": 1,
|
||||||
"icon": "icon-resize-horizontal",
|
"icon": "icon-resize-horizontal",
|
||||||
|
"is_virtual": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-08-15 05:35:50.109290",
|
"modified": "2023-11-17 17:33:55.701726",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Payment Reconciliation",
|
"name": "Payment Reconciliation",
|
||||||
@ -239,6 +240,5 @@
|
|||||||
],
|
],
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"states": [],
|
"states": []
|
||||||
"track_changes": 1
|
|
||||||
}
|
}
|
@ -29,6 +29,58 @@ class PaymentReconciliation(Document):
|
|||||||
self.accounting_dimension_filter_conditions = []
|
self.accounting_dimension_filter_conditions = []
|
||||||
self.ple_posting_date_filter = []
|
self.ple_posting_date_filter = []
|
||||||
|
|
||||||
|
def load_from_db(self):
|
||||||
|
# 'modified' attribute is required for `run_doc_method` to work properly.
|
||||||
|
doc_dict = frappe._dict(
|
||||||
|
{
|
||||||
|
"modified": None,
|
||||||
|
"company": None,
|
||||||
|
"party": None,
|
||||||
|
"party_type": None,
|
||||||
|
"receivable_payable_account": None,
|
||||||
|
"default_advance_account": None,
|
||||||
|
"from_invoice_date": None,
|
||||||
|
"to_invoice_date": None,
|
||||||
|
"invoice_limit": 50,
|
||||||
|
"from_payment_date": None,
|
||||||
|
"to_payment_date": None,
|
||||||
|
"payment_limit": 50,
|
||||||
|
"minimum_invoice_amount": None,
|
||||||
|
"minimum_payment_amount": None,
|
||||||
|
"maximum_invoice_amount": None,
|
||||||
|
"maximum_payment_amount": None,
|
||||||
|
"bank_cash_account": None,
|
||||||
|
"cost_center": None,
|
||||||
|
"payment_name": None,
|
||||||
|
"invoice_name": None,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
super(Document, self).__init__(doc_dict)
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
return
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_list(args):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_count(args):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_stats(args):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def db_insert(self, *args, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def db_update(self, *args, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def delete(self):
|
||||||
|
pass
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_unreconciled_entries(self):
|
def get_unreconciled_entries(self):
|
||||||
self.get_nonreconciled_payment_entries()
|
self.get_nonreconciled_payment_entries()
|
||||||
|
@ -1137,6 +1137,40 @@ class TestPaymentReconciliation(FrappeTestCase):
|
|||||||
self.assertEqual(pay.unallocated_amount, 1000)
|
self.assertEqual(pay.unallocated_amount, 1000)
|
||||||
self.assertEqual(pay.difference_amount, 0)
|
self.assertEqual(pay.difference_amount, 0)
|
||||||
|
|
||||||
|
def test_rounding_of_unallocated_amount(self):
|
||||||
|
self.supplier = "_Test Supplier USD"
|
||||||
|
pi = self.create_purchase_invoice(qty=1, rate=10, do_not_submit=True)
|
||||||
|
pi.supplier = self.supplier
|
||||||
|
pi.currency = "USD"
|
||||||
|
pi.conversion_rate = 80
|
||||||
|
pi.credit_to = self.creditors_usd
|
||||||
|
pi.save().submit()
|
||||||
|
|
||||||
|
pe = get_payment_entry(pi.doctype, pi.name)
|
||||||
|
pe.target_exchange_rate = 78.726500000
|
||||||
|
pe.received_amount = 26.75
|
||||||
|
pe.paid_amount = 2105.93
|
||||||
|
pe.references = []
|
||||||
|
pe.save().submit()
|
||||||
|
|
||||||
|
# unallocated_amount will have some rounding loss - 26.749950
|
||||||
|
self.assertNotEqual(pe.unallocated_amount, 26.75)
|
||||||
|
|
||||||
|
pr = frappe.get_doc("Payment Reconciliation")
|
||||||
|
pr.company = self.company
|
||||||
|
pr.party_type = "Supplier"
|
||||||
|
pr.party = self.supplier
|
||||||
|
pr.receivable_payable_account = self.creditors_usd
|
||||||
|
pr.from_invoice_date = pr.to_invoice_date = pr.from_payment_date = pr.to_payment_date = nowdate()
|
||||||
|
pr.get_unreconciled_entries()
|
||||||
|
|
||||||
|
invoices = [invoice.as_dict() for invoice in pr.invoices]
|
||||||
|
payments = [payment.as_dict() for payment in pr.payments]
|
||||||
|
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
||||||
|
|
||||||
|
# Should not raise frappe.exceptions.ValidationError: Payment Entry has been modified after you pulled it. Please pull it again.
|
||||||
|
pr.reconcile()
|
||||||
|
|
||||||
|
|
||||||
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):
|
||||||
|
@ -159,9 +159,10 @@
|
|||||||
"label": "Difference Posting Date"
|
"label": "Difference Posting Date"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"is_virtual": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-10-23 10:44:56.066303",
|
"modified": "2023-11-17 17:33:38.612615",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Payment Reconciliation Allocation",
|
"name": "Payment Reconciliation Allocation",
|
||||||
|
@ -71,9 +71,10 @@
|
|||||||
"label": "Exchange Rate"
|
"label": "Exchange Rate"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"is_virtual": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-11-08 18:18:02.502149",
|
"modified": "2023-11-17 17:33:45.455166",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Payment Reconciliation Invoice",
|
"name": "Payment Reconciliation Invoice",
|
||||||
|
@ -107,9 +107,10 @@
|
|||||||
"options": "Cost Center"
|
"options": "Cost Center"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"is_virtual": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-09-03 07:43:29.965353",
|
"modified": "2023-11-17 17:33:34.818530",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Payment Reconciliation Payment",
|
"name": "Payment Reconciliation Payment",
|
||||||
|
@ -175,13 +175,6 @@ class PaymentRequest(Document):
|
|||||||
if self.payment_url:
|
if self.payment_url:
|
||||||
self.db_set("payment_url", self.payment_url)
|
self.db_set("payment_url", self.payment_url)
|
||||||
|
|
||||||
if (
|
|
||||||
self.payment_url
|
|
||||||
or not self.payment_gateway_account
|
|
||||||
or (self.payment_gateway_account and self.payment_channel == "Phone")
|
|
||||||
):
|
|
||||||
self.db_set("status", "Initiated")
|
|
||||||
|
|
||||||
def get_payment_url(self):
|
def get_payment_url(self):
|
||||||
if self.reference_doctype != "Fees":
|
if self.reference_doctype != "Fees":
|
||||||
data = frappe.db.get_value(
|
data = frappe.db.get_value(
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
"is_pos",
|
"is_pos",
|
||||||
"is_return",
|
"is_return",
|
||||||
"update_billed_amount_in_sales_order",
|
"update_billed_amount_in_sales_order",
|
||||||
|
"update_billed_amount_in_delivery_note",
|
||||||
"column_break1",
|
"column_break1",
|
||||||
"company",
|
"company",
|
||||||
"posting_date",
|
"posting_date",
|
||||||
@ -1550,12 +1551,19 @@
|
|||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Amount Eligible for Commission",
|
"label": "Amount Eligible for Commission",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "1",
|
||||||
|
"depends_on": "eval: doc.is_return && doc.return_against",
|
||||||
|
"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",
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-06-03 16:23:41.083409",
|
"modified": "2023-11-20 12:27:12.848149",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "POS Invoice",
|
"name": "POS Invoice",
|
||||||
|
@ -556,7 +556,7 @@ def get_stock_availability(item_code, warehouse):
|
|||||||
return bin_qty - pos_sales_qty, is_stock_item
|
return bin_qty - pos_sales_qty, is_stock_item
|
||||||
else:
|
else:
|
||||||
is_stock_item = True
|
is_stock_item = True
|
||||||
if frappe.db.exists("Product Bundle", item_code):
|
if frappe.db.exists("Product Bundle", {"name": item_code, "disabled": 0}):
|
||||||
return get_bundle_availability(item_code, warehouse), is_stock_item
|
return get_bundle_availability(item_code, warehouse), is_stock_item
|
||||||
else:
|
else:
|
||||||
is_stock_item = False
|
is_stock_item = False
|
||||||
|
@ -6,7 +6,6 @@ import unittest
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import add_days, nowdate
|
|
||||||
|
|
||||||
from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
|
from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
|
||||||
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
|
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
|
||||||
@ -126,64 +125,70 @@ class TestPOSInvoice(unittest.TestCase):
|
|||||||
self.assertEqual(inv.grand_total, 5474.0)
|
self.assertEqual(inv.grand_total, 5474.0)
|
||||||
|
|
||||||
def test_tax_calculation_with_item_tax_template(self):
|
def test_tax_calculation_with_item_tax_template(self):
|
||||||
import json
|
inv = create_pos_invoice(qty=84, rate=4.6, do_not_save=1)
|
||||||
|
item_row = inv.get("items")[0]
|
||||||
|
|
||||||
from erpnext.stock.get_item_details import get_item_details
|
add_items = [
|
||||||
|
(54, "_Test Account Excise Duty @ 12 - _TC"),
|
||||||
# set tax template in item
|
(288, "_Test Account Excise Duty @ 15 - _TC"),
|
||||||
item = frappe.get_cached_doc("Item", "_Test Item")
|
(144, "_Test Account Excise Duty @ 20 - _TC"),
|
||||||
item.set(
|
(430, "_Test Item Tax Template 1 - _TC"),
|
||||||
"taxes",
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"item_tax_template": "_Test Account Excise Duty @ 15 - _TC",
|
|
||||||
"valid_from": add_days(nowdate(), -5),
|
|
||||||
}
|
|
||||||
],
|
|
||||||
)
|
|
||||||
item.save()
|
|
||||||
|
|
||||||
# create POS invoice with item
|
|
||||||
pos_inv = create_pos_invoice(qty=84, rate=4.6, do_not_save=True)
|
|
||||||
item_details = get_item_details(
|
|
||||||
doc=pos_inv,
|
|
||||||
args={
|
|
||||||
"item_code": item.item_code,
|
|
||||||
"company": pos_inv.company,
|
|
||||||
"doctype": "POS Invoice",
|
|
||||||
"conversion_rate": 1.0,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
tax_map = json.loads(item_details.item_tax_rate)
|
|
||||||
for tax in tax_map:
|
|
||||||
pos_inv.append(
|
|
||||||
"taxes",
|
|
||||||
{
|
|
||||||
"charge_type": "On Net Total",
|
|
||||||
"account_head": tax,
|
|
||||||
"rate": tax_map[tax],
|
|
||||||
"description": "Test",
|
|
||||||
"cost_center": "_Test Cost Center - _TC",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
pos_inv.submit()
|
|
||||||
pos_inv.load_from_db()
|
|
||||||
|
|
||||||
# check if correct tax values are applied from tax template
|
|
||||||
self.assertEqual(pos_inv.net_total, 386.4)
|
|
||||||
|
|
||||||
expected_taxes = [
|
|
||||||
{
|
|
||||||
"tax_amount": 57.96,
|
|
||||||
"total": 444.36,
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
|
for qty, item_tax_template in add_items:
|
||||||
|
item_row_copy = copy.deepcopy(item_row)
|
||||||
|
item_row_copy.qty = qty
|
||||||
|
item_row_copy.item_tax_template = item_tax_template
|
||||||
|
inv.append("items", item_row_copy)
|
||||||
|
|
||||||
for i in range(len(expected_taxes)):
|
inv.append(
|
||||||
for key in expected_taxes[i]:
|
"taxes",
|
||||||
self.assertEqual(expected_taxes[i][key], pos_inv.get("taxes")[i].get(key))
|
{
|
||||||
|
"account_head": "_Test Account Excise Duty - _TC",
|
||||||
|
"charge_type": "On Net Total",
|
||||||
|
"cost_center": "_Test Cost Center - _TC",
|
||||||
|
"description": "Excise Duty",
|
||||||
|
"doctype": "Sales Taxes and Charges",
|
||||||
|
"rate": 11,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
inv.append(
|
||||||
|
"taxes",
|
||||||
|
{
|
||||||
|
"account_head": "_Test Account Education Cess - _TC",
|
||||||
|
"charge_type": "On Net Total",
|
||||||
|
"cost_center": "_Test Cost Center - _TC",
|
||||||
|
"description": "Education Cess",
|
||||||
|
"doctype": "Sales Taxes and Charges",
|
||||||
|
"rate": 0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
inv.append(
|
||||||
|
"taxes",
|
||||||
|
{
|
||||||
|
"account_head": "_Test Account S&H Education Cess - _TC",
|
||||||
|
"charge_type": "On Net Total",
|
||||||
|
"cost_center": "_Test Cost Center - _TC",
|
||||||
|
"description": "S&H Education Cess",
|
||||||
|
"doctype": "Sales Taxes and Charges",
|
||||||
|
"rate": 3,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
inv.insert()
|
||||||
|
|
||||||
self.assertEqual(pos_inv.get("base_total_taxes_and_charges"), 57.96)
|
self.assertEqual(inv.net_total, 4600)
|
||||||
|
|
||||||
|
self.assertEqual(inv.get("taxes")[0].tax_amount, 502.41)
|
||||||
|
self.assertEqual(inv.get("taxes")[0].total, 5102.41)
|
||||||
|
|
||||||
|
self.assertEqual(inv.get("taxes")[1].tax_amount, 197.80)
|
||||||
|
self.assertEqual(inv.get("taxes")[1].total, 5300.21)
|
||||||
|
|
||||||
|
self.assertEqual(inv.get("taxes")[2].tax_amount, 375.36)
|
||||||
|
self.assertEqual(inv.get("taxes")[2].total, 5675.57)
|
||||||
|
|
||||||
|
self.assertEqual(inv.grand_total, 5675.57)
|
||||||
|
self.assertEqual(inv.rounding_adjustment, 0.43)
|
||||||
|
self.assertEqual(inv.rounded_total, 5676.0)
|
||||||
|
|
||||||
def test_tax_calculation_with_multiple_items_and_discount(self):
|
def test_tax_calculation_with_multiple_items_and_discount(self):
|
||||||
inv = create_pos_invoice(qty=1, rate=75, do_not_save=True)
|
inv = create_pos_invoice(qty=1, rate=75, do_not_save=True)
|
||||||
|
@ -186,6 +186,7 @@
|
|||||||
"label": "Image"
|
"label": "Image"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"fetch_from": "item_code.image",
|
||||||
"fieldname": "image",
|
"fieldname": "image",
|
||||||
"fieldtype": "Attach",
|
"fieldtype": "Attach",
|
||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
@ -833,7 +834,7 @@
|
|||||||
],
|
],
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-03-12 13:36:40.160468",
|
"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",
|
||||||
|
@ -180,7 +180,7 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.frm.set_df_property("tax_withholding_category", "hidden", doc.apply_tds ? 0 : 1);
|
this.frm.set_df_property("tax_withholding_category", "hidden", doc.apply_tds ? 0 : 1);
|
||||||
erpnext.accounts.unreconcile_payments.add_unreconcile_btn(me.frm);
|
erpnext.accounts.unreconcile_payment.add_unreconcile_btn(me.frm);
|
||||||
}
|
}
|
||||||
|
|
||||||
unblock_invoice() {
|
unblock_invoice() {
|
||||||
|
@ -13,6 +13,7 @@ from erpnext.accounts.deferred_revenue import validate_service_stop_date
|
|||||||
from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt
|
from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt
|
||||||
from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import (
|
from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import (
|
||||||
validate_docs_for_deferred_accounting,
|
validate_docs_for_deferred_accounting,
|
||||||
|
validate_docs_for_voucher_types,
|
||||||
)
|
)
|
||||||
from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
|
from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
|
||||||
check_if_return_invoice_linked_with_payment_entry,
|
check_if_return_invoice_linked_with_payment_entry,
|
||||||
@ -491,6 +492,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
def validate_for_repost(self):
|
def validate_for_repost(self):
|
||||||
self.validate_write_off_account()
|
self.validate_write_off_account()
|
||||||
self.validate_expense_account()
|
self.validate_expense_account()
|
||||||
|
validate_docs_for_voucher_types(["Purchase Invoice"])
|
||||||
validate_docs_for_deferred_accounting([], [self.name])
|
validate_docs_for_deferred_accounting([], [self.name])
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
@ -525,7 +527,11 @@ class PurchaseInvoice(BuyingController):
|
|||||||
if self.update_stock == 1:
|
if self.update_stock == 1:
|
||||||
self.repost_future_sle_and_gle()
|
self.repost_future_sle_and_gle()
|
||||||
|
|
||||||
|
if (
|
||||||
|
frappe.db.get_single_value("Buying Settings", "project_update_frequency") == "Each Transaction"
|
||||||
|
):
|
||||||
self.update_project()
|
self.update_project()
|
||||||
|
|
||||||
update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference)
|
update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference)
|
||||||
self.update_advance_tax_references()
|
self.update_advance_tax_references()
|
||||||
|
|
||||||
@ -1302,6 +1308,9 @@ class PurchaseInvoice(BuyingController):
|
|||||||
if self.update_stock == 1:
|
if self.update_stock == 1:
|
||||||
self.repost_future_sle_and_gle()
|
self.repost_future_sle_and_gle()
|
||||||
|
|
||||||
|
if (
|
||||||
|
frappe.db.get_single_value("Buying Settings", "project_update_frequency") == "Each Transaction"
|
||||||
|
):
|
||||||
self.update_project()
|
self.update_project()
|
||||||
self.db_set("status", "Cancelled")
|
self.db_set("status", "Cancelled")
|
||||||
|
|
||||||
@ -1321,13 +1330,21 @@ class PurchaseInvoice(BuyingController):
|
|||||||
self.update_advance_tax_references(cancel=1)
|
self.update_advance_tax_references(cancel=1)
|
||||||
|
|
||||||
def update_project(self):
|
def update_project(self):
|
||||||
project_list = []
|
projects = frappe._dict()
|
||||||
for d in self.items:
|
for d in self.items:
|
||||||
if d.project and d.project not in project_list:
|
if d.project:
|
||||||
project = frappe.get_doc("Project", d.project)
|
if self.docstatus == 1:
|
||||||
project.update_purchase_costing()
|
projects[d.project] = projects.get(d.project, 0) + d.base_net_amount
|
||||||
project.db_update()
|
elif self.docstatus == 2:
|
||||||
project_list.append(d.project)
|
projects[d.project] = projects.get(d.project, 0) - d.base_net_amount
|
||||||
|
|
||||||
|
pj = frappe.qb.DocType("Project")
|
||||||
|
for proj, value in projects.items():
|
||||||
|
res = (
|
||||||
|
frappe.qb.from_(pj).select(pj.total_purchase_cost).where(pj.name == proj).for_update().run()
|
||||||
|
)
|
||||||
|
current_purchase_cost = res and res[0][0] or 0
|
||||||
|
frappe.db.set_value("Project", proj, "total_purchase_cost", current_purchase_cost + value)
|
||||||
|
|
||||||
def validate_supplier_invoice(self):
|
def validate_supplier_invoice(self):
|
||||||
if self.bill_date:
|
if self.bill_date:
|
||||||
|
@ -1783,9 +1783,14 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
|
|||||||
set_advance_flag(company="_Test Company", flag=0, default_account="")
|
set_advance_flag(company="_Test Company", flag=0, default_account="")
|
||||||
|
|
||||||
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(
|
||||||
@ -1795,7 +1800,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 (
|
||||||
@ -1898,6 +1903,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,
|
||||||
|
@ -158,6 +158,7 @@
|
|||||||
"width": "300px"
|
"width": "300px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"fetch_from": "item_code.image",
|
||||||
"fieldname": "image",
|
"fieldname": "image",
|
||||||
"fieldtype": "Attach",
|
"fieldtype": "Attach",
|
||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
@ -497,6 +498,7 @@
|
|||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_on_submit": 1,
|
||||||
"fieldname": "project",
|
"fieldname": "project",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Project",
|
"label": "Project",
|
||||||
@ -504,6 +506,7 @@
|
|||||||
"print_hide": 1
|
"print_hide": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_on_submit": 1,
|
||||||
"default": ":Company",
|
"default": ":Company",
|
||||||
"depends_on": "eval:!doc.is_fixed_asset",
|
"depends_on": "eval:!doc.is_fixed_asset",
|
||||||
"fieldname": "cost_center",
|
"fieldname": "cost_center",
|
||||||
@ -915,7 +918,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",
|
||||||
|
@ -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']],
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,9 +10,7 @@ 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 = get_allowed_types_from_settings()
|
||||||
["Purchase Invoice", "Sales Invoice", "Payment Entry", "Journal Entry"]
|
|
||||||
)
|
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
self.validate_vouchers()
|
self.validate_vouchers()
|
||||||
@ -53,15 +51,7 @@ class RepostAccountingLedger(Document):
|
|||||||
|
|
||||||
def validate_vouchers(self):
|
def validate_vouchers(self):
|
||||||
if self.vouchers:
|
if self.vouchers:
|
||||||
# Validate voucher types
|
validate_docs_for_voucher_types([x.voucher_type for x in self.vouchers])
|
||||||
voucher_types = set([x.voucher_type for x in self.vouchers])
|
|
||||||
if disallowed_types := voucher_types.difference(self._allowed_types):
|
|
||||||
frappe.throw(
|
|
||||||
_("{0} types are not allowed. Only {1} are.").format(
|
|
||||||
frappe.bold(comma_and(list(disallowed_types))),
|
|
||||||
frappe.bold(comma_and(list(self._allowed_types))),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_existing_ledger_entries(self):
|
def get_existing_ledger_entries(self):
|
||||||
vouchers = [x.voucher_no for x in self.vouchers]
|
vouchers = [x.voucher_no for x in self.vouchers]
|
||||||
@ -157,7 +147,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()
|
||||||
@ -165,6 +155,15 @@ def start_repost(account_repost_doc=str) -> None:
|
|||||||
frappe.db.commit()
|
frappe.db.commit()
|
||||||
|
|
||||||
|
|
||||||
|
def get_allowed_types_from_settings():
|
||||||
|
return [
|
||||||
|
x.document_type
|
||||||
|
for x in frappe.db.get_all(
|
||||||
|
"Repost Allowed Types", filters={"allowed": True}, fields=["distinct(document_type)"]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def validate_docs_for_deferred_accounting(sales_docs, purchase_docs):
|
def validate_docs_for_deferred_accounting(sales_docs, purchase_docs):
|
||||||
docs_with_deferred_revenue = frappe.db.get_all(
|
docs_with_deferred_revenue = frappe.db.get_all(
|
||||||
"Sales Invoice Item",
|
"Sales Invoice Item",
|
||||||
@ -186,3 +185,37 @@ 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]))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_docs_for_voucher_types(doc_voucher_types):
|
||||||
|
allowed_types = get_allowed_types_from_settings()
|
||||||
|
# Validate voucher types
|
||||||
|
voucher_types = set(doc_voucher_types)
|
||||||
|
if disallowed_types := voucher_types.difference(allowed_types):
|
||||||
|
message = "are" if len(disallowed_types) > 1 else "is"
|
||||||
|
frappe.throw(
|
||||||
|
_("{0} {1} not allowed to be reposted. Modify {2} to enable reposting.").format(
|
||||||
|
frappe.bold(comma_and(list(disallowed_types))),
|
||||||
|
message,
|
||||||
|
frappe.bold(
|
||||||
|
frappe.utils.get_link_to_form(
|
||||||
|
"Repost Accounting Ledger Settings", "Repost Accounting Ledger Settings"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
@frappe.validate_and_sanitize_search_inputs
|
||||||
|
def get_repost_allowed_types(doctype, txt, searchfield, start, page_len, filters):
|
||||||
|
filters = {"allowed": True}
|
||||||
|
|
||||||
|
if txt:
|
||||||
|
filters.update({"document_type": ("like", f"%{txt}%")})
|
||||||
|
|
||||||
|
if allowed_types := frappe.db.get_all(
|
||||||
|
"Repost Allowed Types", filters=filters, fields=["distinct(document_type)"], as_list=1
|
||||||
|
):
|
||||||
|
return allowed_types
|
||||||
|
return []
|
||||||
|
@ -20,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,
|
||||||
|
@ -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) {
|
||||||
|
|
||||||
|
// },
|
||||||
|
// });
|
@ -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
|
||||||
|
}
|
@ -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
|
@ -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
|
@ -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": []
|
||||||
|
}
|
@ -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
|
@ -37,7 +37,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
|
|||||||
super.onload();
|
super.onload();
|
||||||
|
|
||||||
this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice', 'Timesheet', 'POS Invoice Merge Log',
|
this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice', 'Timesheet', 'POS Invoice Merge Log',
|
||||||
'POS Closing Entry', 'Journal Entry', 'Payment Entry', "Repost Payment Ledger", "Repost Accounting Ledger", "Unreconcile Payments", "Unreconcile Payment Entries"];
|
'POS Closing Entry', 'Journal Entry', 'Payment Entry', "Repost Payment Ledger", "Repost Accounting Ledger", "Unreconcile Payment", "Unreconcile Payment Entries"];
|
||||||
|
|
||||||
if(!this.frm.doc.__islocal && !this.frm.doc.customer && this.frm.doc.debit_to) {
|
if(!this.frm.doc.__islocal && !this.frm.doc.customer && this.frm.doc.debit_to) {
|
||||||
// show debit_to in print format
|
// show debit_to in print format
|
||||||
@ -184,10 +184,9 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
erpnext.accounts.unreconcile_payments.add_unreconcile_btn(me.frm);
|
erpnext.accounts.unreconcile_payment.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",
|
||||||
@ -563,15 +562,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) {
|
||||||
@ -666,6 +656,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',
|
||||||
|
@ -1615,7 +1615,8 @@
|
|||||||
"hide_seconds": 1,
|
"hide_seconds": 1,
|
||||||
"label": "Inter Company Invoice Reference",
|
"label": "Inter Company Invoice Reference",
|
||||||
"options": "Purchase Invoice",
|
"options": "Purchase Invoice",
|
||||||
"read_only": 1
|
"read_only": 1,
|
||||||
|
"search_index": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "customer_group",
|
"fieldname": "customer_group",
|
||||||
@ -2156,7 +2157,7 @@
|
|||||||
"label": "Use Company default Cost Center for Round off"
|
"label": "Use Company default Cost Center for Round off"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "1",
|
||||||
"depends_on": "eval: doc.is_return",
|
"depends_on": "eval: doc.is_return",
|
||||||
"fieldname": "update_billed_amount_in_delivery_note",
|
"fieldname": "update_billed_amount_in_delivery_note",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
@ -2173,7 +2174,7 @@
|
|||||||
"link_fieldname": "consolidated_invoice"
|
"link_fieldname": "consolidated_invoice"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2023-11-03 14:39:38.012346",
|
"modified": "2023-11-23 16:56:29.679499",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Sales Invoice",
|
"name": "Sales Invoice",
|
||||||
|
@ -17,6 +17,7 @@ from erpnext.accounts.doctype.loyalty_program.loyalty_program import (
|
|||||||
)
|
)
|
||||||
from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import (
|
from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import (
|
||||||
validate_docs_for_deferred_accounting,
|
validate_docs_for_deferred_accounting,
|
||||||
|
validate_docs_for_voucher_types,
|
||||||
)
|
)
|
||||||
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import (
|
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import (
|
||||||
get_party_tax_withholding_details,
|
get_party_tax_withholding_details,
|
||||||
@ -172,6 +173,7 @@ class SalesInvoice(SellingController):
|
|||||||
self.validate_write_off_account()
|
self.validate_write_off_account()
|
||||||
self.validate_account_for_change_amount()
|
self.validate_account_for_change_amount()
|
||||||
self.validate_income_account()
|
self.validate_income_account()
|
||||||
|
validate_docs_for_voucher_types(["Sales Invoice"])
|
||||||
validate_docs_for_deferred_accounting([self.name], [])
|
validate_docs_for_deferred_accounting([self.name], [])
|
||||||
|
|
||||||
def validate_fixed_asset(self):
|
def validate_fixed_asset(self):
|
||||||
@ -395,7 +397,7 @@ class SalesInvoice(SellingController):
|
|||||||
"Repost Payment Ledger Items",
|
"Repost Payment Ledger Items",
|
||||||
"Repost Accounting Ledger",
|
"Repost Accounting Ledger",
|
||||||
"Repost Accounting Ledger Items",
|
"Repost Accounting Ledger Items",
|
||||||
"Unreconcile Payments",
|
"Unreconcile Payment",
|
||||||
"Unreconcile Payment Entries",
|
"Unreconcile Payment Entries",
|
||||||
"Payment Ledger Entry",
|
"Payment Ledger Entry",
|
||||||
"Serial and Batch Bundle",
|
"Serial and Batch Bundle",
|
||||||
|
@ -516,72 +516,70 @@ class TestSalesInvoice(FrappeTestCase):
|
|||||||
self.assertEqual(si.grand_total, 5474.0)
|
self.assertEqual(si.grand_total, 5474.0)
|
||||||
|
|
||||||
def test_tax_calculation_with_item_tax_template(self):
|
def test_tax_calculation_with_item_tax_template(self):
|
||||||
import json
|
|
||||||
|
|
||||||
from erpnext.stock.get_item_details import get_item_details
|
|
||||||
|
|
||||||
# set tax template in item
|
|
||||||
item = frappe.get_cached_doc("Item", "_Test Item")
|
|
||||||
item.set(
|
|
||||||
"taxes",
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"item_tax_template": "_Test Item Tax Template 1 - _TC",
|
|
||||||
"valid_from": add_days(nowdate(), -5),
|
|
||||||
}
|
|
||||||
],
|
|
||||||
)
|
|
||||||
item.save()
|
|
||||||
|
|
||||||
# create sales invoice with item
|
|
||||||
si = create_sales_invoice(qty=84, rate=4.6, do_not_save=True)
|
si = create_sales_invoice(qty=84, rate=4.6, do_not_save=True)
|
||||||
item_details = get_item_details(
|
item_row = si.get("items")[0]
|
||||||
doc=si,
|
|
||||||
args={
|
add_items = [
|
||||||
"item_code": item.item_code,
|
(54, "_Test Account Excise Duty @ 12 - _TC"),
|
||||||
"company": si.company,
|
(288, "_Test Account Excise Duty @ 15 - _TC"),
|
||||||
"doctype": "Sales Invoice",
|
(144, "_Test Account Excise Duty @ 20 - _TC"),
|
||||||
"conversion_rate": 1.0,
|
(430, "_Test Item Tax Template 1 - _TC"),
|
||||||
},
|
]
|
||||||
)
|
for qty, item_tax_template in add_items:
|
||||||
tax_map = json.loads(item_details.item_tax_rate)
|
item_row_copy = copy.deepcopy(item_row)
|
||||||
for tax in tax_map:
|
item_row_copy.qty = qty
|
||||||
|
item_row_copy.item_tax_template = item_tax_template
|
||||||
|
si.append("items", item_row_copy)
|
||||||
|
|
||||||
si.append(
|
si.append(
|
||||||
"taxes",
|
"taxes",
|
||||||
{
|
{
|
||||||
|
"account_head": "_Test Account Excise Duty - _TC",
|
||||||
"charge_type": "On Net Total",
|
"charge_type": "On Net Total",
|
||||||
"account_head": tax,
|
|
||||||
"rate": tax_map[tax],
|
|
||||||
"description": "Test",
|
|
||||||
"cost_center": "_Test Cost Center - _TC",
|
"cost_center": "_Test Cost Center - _TC",
|
||||||
|
"description": "Excise Duty",
|
||||||
|
"doctype": "Sales Taxes and Charges",
|
||||||
|
"rate": 11,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
si.submit()
|
si.append(
|
||||||
si.load_from_db()
|
"taxes",
|
||||||
|
|
||||||
# check if correct tax values are applied from tax template
|
|
||||||
self.assertEqual(si.net_total, 386.4)
|
|
||||||
|
|
||||||
expected_taxes = [
|
|
||||||
{
|
{
|
||||||
"tax_amount": 19.32,
|
"account_head": "_Test Account Education Cess - _TC",
|
||||||
"total": 405.72,
|
"charge_type": "On Net Total",
|
||||||
|
"cost_center": "_Test Cost Center - _TC",
|
||||||
|
"description": "Education Cess",
|
||||||
|
"doctype": "Sales Taxes and Charges",
|
||||||
|
"rate": 0,
|
||||||
},
|
},
|
||||||
|
)
|
||||||
|
si.append(
|
||||||
|
"taxes",
|
||||||
{
|
{
|
||||||
"tax_amount": 38.64,
|
"account_head": "_Test Account S&H Education Cess - _TC",
|
||||||
"total": 444.36,
|
"charge_type": "On Net Total",
|
||||||
|
"cost_center": "_Test Cost Center - _TC",
|
||||||
|
"description": "S&H Education Cess",
|
||||||
|
"doctype": "Sales Taxes and Charges",
|
||||||
|
"rate": 3,
|
||||||
},
|
},
|
||||||
{
|
)
|
||||||
"tax_amount": 57.96,
|
si.insert()
|
||||||
"total": 502.32,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
for i in range(len(expected_taxes)):
|
self.assertEqual(si.net_total, 4600)
|
||||||
for key in expected_taxes[i]:
|
|
||||||
self.assertEqual(expected_taxes[i][key], si.get("taxes")[i].get(key))
|
|
||||||
|
|
||||||
self.assertEqual(si.get("base_total_taxes_and_charges"), 115.92)
|
self.assertEqual(si.get("taxes")[0].tax_amount, 502.41)
|
||||||
|
self.assertEqual(si.get("taxes")[0].total, 5102.41)
|
||||||
|
|
||||||
|
self.assertEqual(si.get("taxes")[1].tax_amount, 197.80)
|
||||||
|
self.assertEqual(si.get("taxes")[1].total, 5300.21)
|
||||||
|
|
||||||
|
self.assertEqual(si.get("taxes")[2].tax_amount, 375.36)
|
||||||
|
self.assertEqual(si.get("taxes")[2].total, 5675.57)
|
||||||
|
|
||||||
|
self.assertEqual(si.grand_total, 5675.57)
|
||||||
|
self.assertEqual(si.rounding_adjustment, 0.43)
|
||||||
|
self.assertEqual(si.rounded_total, 5676.0)
|
||||||
|
|
||||||
def test_tax_calculation_with_multiple_items_and_discount(self):
|
def test_tax_calculation_with_multiple_items_and_discount(self):
|
||||||
si = create_sales_invoice(qty=1, rate=75, do_not_save=True)
|
si = create_sales_invoice(qty=1, rate=75, do_not_save=True)
|
||||||
@ -791,6 +789,28 @@ class TestSalesInvoice(FrappeTestCase):
|
|||||||
w = self.make()
|
w = self.make()
|
||||||
self.assertEqual(w.outstanding_amount, w.base_rounded_total)
|
self.assertEqual(w.outstanding_amount, w.base_rounded_total)
|
||||||
|
|
||||||
|
def test_rounded_total_with_cash_discount(self):
|
||||||
|
si = frappe.copy_doc(test_records[2])
|
||||||
|
|
||||||
|
item = copy.deepcopy(si.get("items")[0])
|
||||||
|
item.update(
|
||||||
|
{
|
||||||
|
"qty": 1,
|
||||||
|
"rate": 14960.66,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
si.set("items", [item])
|
||||||
|
si.set("taxes", [])
|
||||||
|
si.apply_discount_on = "Grand Total"
|
||||||
|
si.is_cash_or_non_trade_discount = 1
|
||||||
|
si.discount_amount = 1
|
||||||
|
si.insert()
|
||||||
|
|
||||||
|
self.assertEqual(si.grand_total, 14959.66)
|
||||||
|
self.assertEqual(si.rounded_total, 14960)
|
||||||
|
self.assertEqual(si.rounding_adjustment, 0.34)
|
||||||
|
|
||||||
def test_payment(self):
|
def test_payment(self):
|
||||||
w = self.make()
|
w = self.make()
|
||||||
|
|
||||||
|
@ -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,
|
||||||
@ -901,7 +902,7 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-07-26 12:53:22.404057",
|
"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",
|
||||||
|
@ -10,7 +10,7 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sal
|
|||||||
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
|
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
|
||||||
|
|
||||||
|
|
||||||
class TestUnreconcilePayments(AccountsTestMixin, FrappeTestCase):
|
class TestUnreconcilePayment(AccountsTestMixin, FrappeTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.create_company()
|
self.create_company()
|
||||||
self.create_customer()
|
self.create_customer()
|
||||||
@ -73,7 +73,7 @@ class TestUnreconcilePayments(AccountsTestMixin, FrappeTestCase):
|
|||||||
|
|
||||||
unreconcile = frappe.get_doc(
|
unreconcile = frappe.get_doc(
|
||||||
{
|
{
|
||||||
"doctype": "Unreconcile Payments",
|
"doctype": "Unreconcile Payment",
|
||||||
"company": self.company,
|
"company": self.company,
|
||||||
"voucher_type": pe.doctype,
|
"voucher_type": pe.doctype,
|
||||||
"voucher_no": pe.name,
|
"voucher_no": pe.name,
|
||||||
@ -138,7 +138,7 @@ class TestUnreconcilePayments(AccountsTestMixin, FrappeTestCase):
|
|||||||
|
|
||||||
unreconcile = frappe.get_doc(
|
unreconcile = frappe.get_doc(
|
||||||
{
|
{
|
||||||
"doctype": "Unreconcile Payments",
|
"doctype": "Unreconcile Payment",
|
||||||
"company": self.company,
|
"company": self.company,
|
||||||
"voucher_type": pe2.doctype,
|
"voucher_type": pe2.doctype,
|
||||||
"voucher_no": pe2.name,
|
"voucher_no": pe2.name,
|
||||||
@ -196,7 +196,7 @@ class TestUnreconcilePayments(AccountsTestMixin, FrappeTestCase):
|
|||||||
|
|
||||||
unreconcile = frappe.get_doc(
|
unreconcile = frappe.get_doc(
|
||||||
{
|
{
|
||||||
"doctype": "Unreconcile Payments",
|
"doctype": "Unreconcile Payment",
|
||||||
"company": self.company,
|
"company": self.company,
|
||||||
"voucher_type": pe.doctype,
|
"voucher_type": pe.doctype,
|
||||||
"voucher_no": pe.name,
|
"voucher_no": pe.name,
|
||||||
@ -281,7 +281,7 @@ class TestUnreconcilePayments(AccountsTestMixin, FrappeTestCase):
|
|||||||
|
|
||||||
unreconcile = frappe.get_doc(
|
unreconcile = frappe.get_doc(
|
||||||
{
|
{
|
||||||
"doctype": "Unreconcile Payments",
|
"doctype": "Unreconcile Payment",
|
||||||
"company": self.company,
|
"company": self.company,
|
||||||
"voucher_type": pe2.doctype,
|
"voucher_type": pe2.doctype,
|
||||||
"voucher_no": pe2.name,
|
"voucher_no": pe2.name,
|
@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
|
|
||||||
frappe.ui.form.on("Unreconcile Payments", {
|
frappe.ui.form.on("Unreconcile Payment", {
|
||||||
refresh(frm) {
|
refresh(frm) {
|
||||||
frm.set_query("voucher_type", function() {
|
frm.set_query("voucher_type", function() {
|
||||||
return {
|
return {
|
@ -21,7 +21,7 @@
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Amended From",
|
"label": "Amended From",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "Unreconcile Payments",
|
"options": "Unreconcile Payment",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
@ -61,7 +61,7 @@
|
|||||||
"modified": "2023-08-28 17:42:50.261377",
|
"modified": "2023-08-28 17:42:50.261377",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Unreconcile Payments",
|
"name": "Unreconcile Payment",
|
||||||
"naming_rule": "Expression",
|
"naming_rule": "Expression",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
@ -15,7 +15,7 @@ from erpnext.accounts.utils import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class UnreconcilePayments(Document):
|
class UnreconcilePayment(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
self.supported_types = ["Payment Entry", "Journal Entry"]
|
self.supported_types = ["Payment Entry", "Journal Entry"]
|
||||||
if not self.voucher_type in self.supported_types:
|
if not self.voucher_type in self.supported_types:
|
||||||
@ -142,7 +142,7 @@ def create_unreconcile_doc_for_selection(selections=None):
|
|||||||
selections = frappe.json.loads(selections)
|
selections = frappe.json.loads(selections)
|
||||||
# assuming each row is a unique voucher
|
# assuming each row is a unique voucher
|
||||||
for row in selections:
|
for row in selections:
|
||||||
unrecon = frappe.new_doc("Unreconcile Payments")
|
unrecon = frappe.new_doc("Unreconcile Payment")
|
||||||
unrecon.company = row.get("company")
|
unrecon.company = row.get("company")
|
||||||
unrecon.voucher_type = row.get("voucher_type")
|
unrecon.voucher_type = row.get("voucher_type")
|
||||||
unrecon.voucher_no = row.get("voucher_no")
|
unrecon.voucher_no = row.get("voucher_no")
|
@ -31,7 +31,12 @@ from erpnext.accounts.utils import get_fiscal_year
|
|||||||
from erpnext.exceptions import InvalidAccountCurrency, PartyDisabled, PartyFrozen
|
from erpnext.exceptions import InvalidAccountCurrency, PartyDisabled, PartyFrozen
|
||||||
from erpnext.utilities.regional import temporary_flag
|
from erpnext.utilities.regional import temporary_flag
|
||||||
|
|
||||||
PURCHASE_TRANSACTION_TYPES = {"Purchase Order", "Purchase Receipt", "Purchase Invoice"}
|
PURCHASE_TRANSACTION_TYPES = {
|
||||||
|
"Supplier Quotation",
|
||||||
|
"Purchase Order",
|
||||||
|
"Purchase Receipt",
|
||||||
|
"Purchase Invoice",
|
||||||
|
}
|
||||||
SALES_TRANSACTION_TYPES = {
|
SALES_TRANSACTION_TYPES = {
|
||||||
"Quotation",
|
"Quotation",
|
||||||
"Sales Order",
|
"Sales Order",
|
||||||
@ -231,7 +236,9 @@ 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=render_address(shipping_address),
|
shipping_address_display=render_address(
|
||||||
|
shipping_address, check_permissions=not ignore_permissions
|
||||||
|
),
|
||||||
**get_fetch_values(doctype, "shipping_address", shipping_address)
|
**get_fetch_values(doctype, "shipping_address", shipping_address)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -144,6 +144,16 @@ frappe.query_reports["Accounts Payable"] = {
|
|||||||
"label": __("Show Future Payments"),
|
"label": __("Show Future Payments"),
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "in_party_currency",
|
||||||
|
"label": __("In Party Currency"),
|
||||||
|
"fieldtype": "Check",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "for_revaluation_journals",
|
||||||
|
"label": __("Revaluation Journals"),
|
||||||
|
"fieldtype": "Check",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "ignore_accounts",
|
"fieldname": "ignore_accounts",
|
||||||
"label": __("Group by Voucher"),
|
"label": __("Group by Voucher"),
|
||||||
|
@ -40,6 +40,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
|||||||
"range2": 60,
|
"range2": 60,
|
||||||
"range3": 90,
|
"range3": 90,
|
||||||
"range4": 120,
|
"range4": 120,
|
||||||
|
"in_party_currency": 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
data = execute(filters)
|
data = execute(filters)
|
||||||
|
@ -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",
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
|
@ -116,8 +116,11 @@ frappe.query_reports["Accounts Receivable"] = {
|
|||||||
{
|
{
|
||||||
"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",
|
||||||
@ -173,12 +176,23 @@ frappe.query_reports["Accounts Receivable"] = {
|
|||||||
"label": __("Show Remarks"),
|
"label": __("Show Remarks"),
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "in_party_currency",
|
||||||
|
"label": __("In Party Currency"),
|
||||||
|
"fieldtype": "Check",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "for_revaluation_journals",
|
||||||
|
"label": __("Revaluation Journals"),
|
||||||
|
"fieldtype": "Check",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "ignore_accounts",
|
"fieldname": "ignore_accounts",
|
||||||
"label": __("Group by Voucher"),
|
"label": __("Group by Voucher"),
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
],
|
],
|
||||||
|
|
||||||
"formatter": function(value, row, column, data, default_formatter) {
|
"formatter": function(value, row, column, data, default_formatter) {
|
||||||
|
72
erpnext/accounts/report/accounts_receivable/accounts_receivable.py
Executable file → Normal file
72
erpnext/accounts/report/accounts_receivable/accounts_receivable.py
Executable file → Normal file
@ -7,14 +7,14 @@ from collections import OrderedDict
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import _, qb, scrub
|
from frappe import _, qb, scrub
|
||||||
from frappe.query_builder import Criterion
|
from frappe.query_builder import Criterion
|
||||||
from frappe.query_builder.functions import Date, Sum
|
from frappe.query_builder.functions import Date, Substring, Sum
|
||||||
from frappe.utils import cint, cstr, flt, getdate, nowdate
|
from frappe.utils import cint, cstr, flt, getdate, nowdate
|
||||||
|
|
||||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||||
get_accounting_dimensions,
|
get_accounting_dimensions,
|
||||||
get_dimension_with_children,
|
get_dimension_with_children,
|
||||||
)
|
)
|
||||||
from erpnext.accounts.utils import get_currency_precision
|
from erpnext.accounts.utils import get_currency_precision, get_party_types_from_account_type
|
||||||
|
|
||||||
# This report gives a summary of all Outstanding Invoices considering the following
|
# This report gives a summary of all Outstanding Invoices considering the following
|
||||||
|
|
||||||
@ -28,8 +28,8 @@ from erpnext.accounts.utils import get_currency_precision
|
|||||||
# 6. Configurable Ageing Groups (0-30, 30-60 etc) can be set via filters
|
# 6. Configurable Ageing Groups (0-30, 30-60 etc) can be set via filters
|
||||||
# 7. For overpayment against an invoice with payment terms, there will be an additional row
|
# 7. For overpayment against an invoice with payment terms, there will be an additional row
|
||||||
# 8. Invoice details like Sales Persons, Delivery Notes are also fetched comma separated
|
# 8. Invoice details like Sales Persons, Delivery Notes are also fetched comma separated
|
||||||
# 9. Report amounts are in "Party Currency" if party is selected, or company currency for multi-party
|
# 9. Report amounts are in party currency if in_party_currency is selected, otherwise company currency
|
||||||
# 10. This reports is based on all GL Entries that are made against account_type "Receivable" or "Payable"
|
# 10. This report is based on Payment Ledger Entries
|
||||||
|
|
||||||
|
|
||||||
def execute(filters=None):
|
def execute(filters=None):
|
||||||
@ -72,9 +72,7 @@ class ReceivablePayableReport(object):
|
|||||||
self.currency_precision = get_currency_precision() or 2
|
self.currency_precision = get_currency_precision() or 2
|
||||||
self.dr_or_cr = "debit" if self.filters.account_type == "Receivable" else "credit"
|
self.dr_or_cr = "debit" if self.filters.account_type == "Receivable" else "credit"
|
||||||
self.account_type = self.filters.account_type
|
self.account_type = self.filters.account_type
|
||||||
self.party_type = frappe.db.get_all(
|
self.party_type = get_party_types_from_account_type(self.account_type)
|
||||||
"Party Type", {"account_type": self.account_type}, pluck="name"
|
|
||||||
)
|
|
||||||
self.party_details = {}
|
self.party_details = {}
|
||||||
self.invoices = set()
|
self.invoices = set()
|
||||||
self.skip_total_row = 0
|
self.skip_total_row = 0
|
||||||
@ -84,6 +82,9 @@ class ReceivablePayableReport(object):
|
|||||||
self.total_row_map = {}
|
self.total_row_map = {}
|
||||||
self.skip_total_row = 1
|
self.skip_total_row = 1
|
||||||
|
|
||||||
|
if self.filters.get("in_party_currency"):
|
||||||
|
self.skip_total_row = 1
|
||||||
|
|
||||||
def get_data(self):
|
def get_data(self):
|
||||||
self.get_ple_entries()
|
self.get_ple_entries()
|
||||||
self.get_sales_invoices_or_customers_based_on_sales_person()
|
self.get_sales_invoices_or_customers_based_on_sales_person()
|
||||||
@ -117,7 +118,7 @@ class ReceivablePayableReport(object):
|
|||||||
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
|
||||||
|
|
||||||
if self.filters.get("ingore_accounts"):
|
if self.filters.get("ignore_accounts"):
|
||||||
key = (ple.voucher_type, ple.voucher_no, ple.party)
|
key = (ple.voucher_type, ple.voucher_no, ple.party)
|
||||||
else:
|
else:
|
||||||
key = (ple.account, ple.voucher_type, ple.voucher_no, ple.party)
|
key = (ple.account, ple.voucher_type, ple.voucher_no, ple.party)
|
||||||
@ -145,7 +146,7 @@ class ReceivablePayableReport(object):
|
|||||||
if self.filters.get("group_by_party"):
|
if self.filters.get("group_by_party"):
|
||||||
self.init_subtotal_row(ple.party)
|
self.init_subtotal_row(ple.party)
|
||||||
|
|
||||||
if self.filters.get("group_by_party"):
|
if self.filters.get("group_by_party") and not self.filters.get("in_party_currency"):
|
||||||
self.init_subtotal_row("Total")
|
self.init_subtotal_row("Total")
|
||||||
|
|
||||||
def get_invoices(self, ple):
|
def get_invoices(self, ple):
|
||||||
@ -188,7 +189,7 @@ class ReceivablePayableReport(object):
|
|||||||
):
|
):
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.filters.get("ingore_accounts"):
|
if self.filters.get("ignore_accounts"):
|
||||||
key = (ple.against_voucher_type, ple.against_voucher_no, ple.party)
|
key = (ple.against_voucher_type, ple.against_voucher_no, ple.party)
|
||||||
else:
|
else:
|
||||||
key = (ple.account, ple.against_voucher_type, ple.against_voucher_no, ple.party)
|
key = (ple.account, ple.against_voucher_type, ple.against_voucher_no, ple.party)
|
||||||
@ -200,7 +201,7 @@ 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:
|
||||||
if self.filters.get("ingore_accounts"):
|
if self.filters.get("ignore_accounts"):
|
||||||
key = (ple.against_voucher_type, return_against, ple.party)
|
key = (ple.against_voucher_type, return_against, ple.party)
|
||||||
else:
|
else:
|
||||||
key = (ple.account, ple.against_voucher_type, return_against, ple.party)
|
key = (ple.account, ple.against_voucher_type, return_against, ple.party)
|
||||||
@ -209,7 +210,7 @@ class ReceivablePayableReport(object):
|
|||||||
|
|
||||||
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
|
||||||
if self.filters.get("ingore_accounts"):
|
if self.filters.get("ignore_accounts"):
|
||||||
row = self.voucher_balance.get((ple.voucher_type, ple.voucher_no, ple.party))
|
row = self.voucher_balance.get((ple.voucher_type, ple.voucher_no, ple.party))
|
||||||
else:
|
else:
|
||||||
row = self.voucher_balance.get((ple.account, ple.voucher_type, ple.voucher_no, ple.party))
|
row = self.voucher_balance.get((ple.account, ple.voucher_type, ple.voucher_no, ple.party))
|
||||||
@ -224,8 +225,7 @@ class ReceivablePayableReport(object):
|
|||||||
if not row:
|
if not row:
|
||||||
return
|
return
|
||||||
|
|
||||||
# amount in "Party Currency", if its supplied. If not, amount in company currency
|
if self.filters.get("in_party_currency"):
|
||||||
if self.filters.get("party_type") and self.filters.get("party"):
|
|
||||||
amount = ple.amount_in_account_currency
|
amount = ple.amount_in_account_currency
|
||||||
else:
|
else:
|
||||||
amount = ple.amount
|
amount = ple.amount
|
||||||
@ -256,8 +256,10 @@ class ReceivablePayableReport(object):
|
|||||||
def update_sub_total_row(self, row, party):
|
def update_sub_total_row(self, row, party):
|
||||||
total_row = self.total_row_map.get(party)
|
total_row = self.total_row_map.get(party)
|
||||||
|
|
||||||
|
if total_row:
|
||||||
for field in self.get_currency_fields():
|
for field in self.get_currency_fields():
|
||||||
total_row[field] += row.get(field, 0.0)
|
total_row[field] += row.get(field, 0.0)
|
||||||
|
total_row["currency"] = row.get("currency", "")
|
||||||
|
|
||||||
def append_subtotal_row(self, party):
|
def append_subtotal_row(self, party):
|
||||||
sub_total_row = self.total_row_map.get(party)
|
sub_total_row = self.total_row_map.get(party)
|
||||||
@ -281,11 +283,20 @@ class ReceivablePayableReport(object):
|
|||||||
|
|
||||||
row.invoice_grand_total = row.invoiced
|
row.invoice_grand_total = row.invoiced
|
||||||
|
|
||||||
|
must_consider = False
|
||||||
|
if self.filters.get("for_revaluation_journals"):
|
||||||
|
if (abs(row.outstanding) > 1.0 / 10**self.currency_precision) or (
|
||||||
|
(abs(row.outstanding_in_account_currency) > 1.0 / 10**self.currency_precision)
|
||||||
|
):
|
||||||
|
must_consider = True
|
||||||
|
else:
|
||||||
if (abs(row.outstanding) > 1.0 / 10**self.currency_precision) and (
|
if (abs(row.outstanding) > 1.0 / 10**self.currency_precision) and (
|
||||||
(abs(row.outstanding_in_account_currency) > 1.0 / 10**self.currency_precision)
|
(abs(row.outstanding_in_account_currency) > 1.0 / 10**self.currency_precision)
|
||||||
or (row.voucher_no in self.err_journals)
|
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:
|
||||||
@ -309,7 +320,7 @@ class ReceivablePayableReport(object):
|
|||||||
if self.filters.get("group_by_party"):
|
if self.filters.get("group_by_party"):
|
||||||
self.append_subtotal_row(self.previous_party)
|
self.append_subtotal_row(self.previous_party)
|
||||||
if self.data:
|
if self.data:
|
||||||
self.data.append(self.total_row_map.get("Total"))
|
self.data.append(self.total_row_map.get("Total", {}))
|
||||||
|
|
||||||
def append_row(self, row):
|
def append_row(self, row):
|
||||||
self.allocate_future_payments(row)
|
self.allocate_future_payments(row)
|
||||||
@ -440,7 +451,7 @@ class ReceivablePayableReport(object):
|
|||||||
party_details = self.get_party_details(row.party) or {}
|
party_details = self.get_party_details(row.party) or {}
|
||||||
row.update(party_details)
|
row.update(party_details)
|
||||||
|
|
||||||
if self.filters.get("party_type") and self.filters.get("party"):
|
if self.filters.get("in_party_currency"):
|
||||||
row.currency = row.account_currency
|
row.currency = row.account_currency
|
||||||
else:
|
else:
|
||||||
row.currency = self.company_currency
|
row.currency = self.company_currency
|
||||||
@ -753,6 +764,11 @@ class ReceivablePayableReport(object):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if self.filters.get("show_remarks"):
|
if self.filters.get("show_remarks"):
|
||||||
|
if remarks_length := frappe.db.get_single_value(
|
||||||
|
"Accounts Settings", "receivable_payable_remarks_length"
|
||||||
|
):
|
||||||
|
query = query.select(Substring(ple.remarks, 1, remarks_length).as_("remarks"))
|
||||||
|
else:
|
||||||
query = query.select(ple.remarks)
|
query = query.select(ple.remarks)
|
||||||
|
|
||||||
if self.filters.get("group_by_party"):
|
if self.filters.get("group_by_party"):
|
||||||
@ -840,7 +856,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")
|
||||||
@ -1132,3 +1154,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))
|
||||||
|
@ -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 = (
|
||||||
@ -557,6 +581,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
|||||||
"range2": 60,
|
"range2": 60,
|
||||||
"range3": 90,
|
"range3": 90,
|
||||||
"range4": 120,
|
"range4": 120,
|
||||||
|
"in_party_currency": 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
si = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True)
|
si = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True)
|
||||||
|
@ -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) {
|
||||||
|
@ -8,6 +8,7 @@ from frappe.utils import cint, flt
|
|||||||
|
|
||||||
from erpnext.accounts.party import get_partywise_advanced_payment_amount
|
from erpnext.accounts.party import get_partywise_advanced_payment_amount
|
||||||
from erpnext.accounts.report.accounts_receivable.accounts_receivable import ReceivablePayableReport
|
from erpnext.accounts.report.accounts_receivable.accounts_receivable import ReceivablePayableReport
|
||||||
|
from erpnext.accounts.utils import get_party_types_from_account_type
|
||||||
|
|
||||||
|
|
||||||
def execute(filters=None):
|
def execute(filters=None):
|
||||||
@ -22,9 +23,7 @@ def execute(filters=None):
|
|||||||
class AccountsReceivableSummary(ReceivablePayableReport):
|
class AccountsReceivableSummary(ReceivablePayableReport):
|
||||||
def run(self, args):
|
def run(self, args):
|
||||||
self.account_type = args.get("account_type")
|
self.account_type = args.get("account_type")
|
||||||
self.party_type = frappe.db.get_all(
|
self.party_type = get_party_types_from_account_type(self.account_type)
|
||||||
"Party Type", {"account_type": self.account_type}, pluck="name"
|
|
||||||
)
|
|
||||||
self.party_naming_by = frappe.db.get_value(
|
self.party_naming_by = frappe.db.get_value(
|
||||||
args.get("naming_by")[0], None, args.get("naming_by")[1]
|
args.get("naming_by")[0], None, args.get("naming_by")[1]
|
||||||
)
|
)
|
||||||
|
@ -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
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -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",
|
||||||
|
@ -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},
|
||||||
]
|
]
|
||||||
|
@ -17,7 +17,7 @@ frappe.query_reports["Balance Sheet"]["filters"].push({
|
|||||||
|
|
||||||
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,
|
||||||
});
|
});
|
||||||
|
@ -17,7 +17,7 @@ frappe.query_reports["Cash Flow"]["filters"].splice(8, 1);
|
|||||||
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
|
||||||
}
|
}
|
||||||
|
@ -104,7 +104,7 @@ frappe.query_reports["Consolidated Financial Statement"] = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"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
|
||||||
},
|
},
|
||||||
|
@ -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), ""]))
|
||||||
|
@ -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
|
||||||
},
|
},
|
||||||
|
@ -164,6 +164,11 @@ def get_gl_entries(filters, accounting_dimensions):
|
|||||||
credit_in_account_currency """
|
credit_in_account_currency """
|
||||||
|
|
||||||
if filters.get("show_remarks"):
|
if filters.get("show_remarks"):
|
||||||
|
if remarks_length := frappe.db.get_single_value(
|
||||||
|
"Accounts Settings", "general_ledger_remarks_length"
|
||||||
|
):
|
||||||
|
select_fields += f",substr(remarks, 1, {remarks_length}) as 'remarks'"
|
||||||
|
else:
|
||||||
select_fields += """,remarks"""
|
select_fields += """,remarks"""
|
||||||
|
|
||||||
order_by_statement = "order by posting_date, account, creation"
|
order_by_statement = "order by posting_date, account, creation"
|
||||||
@ -259,9 +264,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:
|
||||||
|
@ -184,6 +184,16 @@ def get_columns(filters):
|
|||||||
"width": 180,
|
"width": 180,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
columns.append(
|
||||||
|
{
|
||||||
|
"label": _(filters.get("party_type")),
|
||||||
|
"fieldname": "party",
|
||||||
|
"fieldtype": "Dynamic Link",
|
||||||
|
"options": "party_type",
|
||||||
|
"width": 180,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
columns.extend(
|
columns.extend(
|
||||||
[
|
[
|
||||||
@ -316,7 +326,7 @@ def get_tds_docs_query(filters, bank_accounts, tds_accounts):
|
|||||||
if not tds_accounts:
|
if not tds_accounts:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_("No {0} Accounts found for this company.").format(frappe.bold("Tax Withholding")),
|
_("No {0} Accounts found for this company.").format(frappe.bold("Tax Withholding")),
|
||||||
title="Accounts Missing Error",
|
title=_("Accounts Missing Error"),
|
||||||
)
|
)
|
||||||
gle = frappe.qb.DocType("GL Entry")
|
gle = frappe.qb.DocType("GL Entry")
|
||||||
query = (
|
query = (
|
||||||
|
@ -95,7 +95,7 @@ frappe.query_reports["Trial Balance"] = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"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
|
||||||
},
|
},
|
||||||
|
@ -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), ""]))
|
||||||
|
@ -53,6 +53,9 @@ GL_REPOSTING_CHUNK = 100
|
|||||||
def get_fiscal_year(
|
def get_fiscal_year(
|
||||||
date=None, fiscal_year=None, label="Date", verbose=1, company=None, as_dict=False, boolean=False
|
date=None, fiscal_year=None, label="Date", verbose=1, company=None, as_dict=False, boolean=False
|
||||||
):
|
):
|
||||||
|
if isinstance(boolean, str):
|
||||||
|
boolean = frappe.json.loads(boolean)
|
||||||
|
|
||||||
fiscal_years = get_fiscal_years(
|
fiscal_years = get_fiscal_years(
|
||||||
date, fiscal_year, label, verbose, company, as_dict=as_dict, boolean=boolean
|
date, fiscal_year, label, verbose, company, as_dict=as_dict, boolean=boolean
|
||||||
)
|
)
|
||||||
@ -180,6 +183,7 @@ def get_balance_on(
|
|||||||
cost_center=None,
|
cost_center=None,
|
||||||
ignore_account_permission=False,
|
ignore_account_permission=False,
|
||||||
account_type=None,
|
account_type=None,
|
||||||
|
start_date=None,
|
||||||
):
|
):
|
||||||
if not account and frappe.form_dict.get("account"):
|
if not account and frappe.form_dict.get("account"):
|
||||||
account = frappe.form_dict.get("account")
|
account = frappe.form_dict.get("account")
|
||||||
@ -193,6 +197,8 @@ def get_balance_on(
|
|||||||
cost_center = frappe.form_dict.get("cost_center")
|
cost_center = frappe.form_dict.get("cost_center")
|
||||||
|
|
||||||
cond = ["is_cancelled=0"]
|
cond = ["is_cancelled=0"]
|
||||||
|
if start_date:
|
||||||
|
cond.append("posting_date >= %s" % frappe.db.escape(cstr(start_date)))
|
||||||
if date:
|
if date:
|
||||||
cond.append("posting_date <= %s" % frappe.db.escape(cstr(date)))
|
cond.append("posting_date <= %s" % frappe.db.escape(cstr(date)))
|
||||||
else:
|
else:
|
||||||
@ -1831,6 +1837,28 @@ class QueryPaymentLedger(object):
|
|||||||
Table("outstanding").amount_in_account_currency >= self.max_outstanding
|
Table("outstanding").amount_in_account_currency >= self.max_outstanding
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if self.limit and self.get_invoices:
|
||||||
|
outstanding_vouchers = (
|
||||||
|
qb.from_(ple)
|
||||||
|
.select(
|
||||||
|
ple.against_voucher_no.as_("voucher_no"),
|
||||||
|
Sum(ple.amount_in_account_currency).as_("amount_in_account_currency"),
|
||||||
|
)
|
||||||
|
.where(ple.delinked == 0)
|
||||||
|
.where(Criterion.all(filter_on_against_voucher_no))
|
||||||
|
.where(Criterion.all(self.common_filter))
|
||||||
|
.groupby(ple.against_voucher_type, ple.against_voucher_no, ple.party_type, ple.party)
|
||||||
|
.orderby(ple.posting_date, ple.voucher_no)
|
||||||
|
.having(qb.Field("amount_in_account_currency") > 0)
|
||||||
|
.limit(self.limit)
|
||||||
|
.run()
|
||||||
|
)
|
||||||
|
if outstanding_vouchers:
|
||||||
|
filter_on_voucher_no.append(ple.voucher_no.isin([x[0] for x in outstanding_vouchers]))
|
||||||
|
filter_on_against_voucher_no.append(
|
||||||
|
ple.against_voucher_no.isin([x[0] for x in outstanding_vouchers])
|
||||||
|
)
|
||||||
|
|
||||||
# build query for voucher amount
|
# build query for voucher amount
|
||||||
query_voucher_amount = (
|
query_voucher_amount = (
|
||||||
qb.from_(ple)
|
qb.from_(ple)
|
||||||
@ -2047,3 +2075,7 @@ def create_gain_loss_journal(
|
|||||||
journal_entry.save()
|
journal_entry.save()
|
||||||
journal_entry.submit()
|
journal_entry.submit()
|
||||||
return journal_entry.name
|
return journal_entry.name
|
||||||
|
|
||||||
|
|
||||||
|
def get_party_types_from_account_type(account_type):
|
||||||
|
return frappe.db.get_all("Party Type", {"account_type": account_type}, pluck="name")
|
||||||
|
@ -481,11 +481,11 @@
|
|||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval.doc.asset_quantity",
|
"default": "1",
|
||||||
"fieldname": "asset_quantity",
|
"fieldname": "asset_quantity",
|
||||||
"fieldtype": "Int",
|
"fieldtype": "Int",
|
||||||
"label": "Asset Quantity",
|
"label": "Asset Quantity",
|
||||||
"read_only": 1
|
"read_only_depends_on": "eval:!doc.is_existing_asset && !doc.is_composite_asset"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "depr_entry_posting_status",
|
"fieldname": "depr_entry_posting_status",
|
||||||
@ -572,7 +572,7 @@
|
|||||||
"link_fieldname": "target_asset"
|
"link_fieldname": "target_asset"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2023-10-27 17:03:46.629617",
|
"modified": "2023-11-20 20:57:37.010467",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Assets",
|
"module": "Assets",
|
||||||
"name": "Asset",
|
"name": "Asset",
|
||||||
|
@ -46,23 +46,19 @@ class Asset(AccountsController):
|
|||||||
self.validate_item()
|
self.validate_item()
|
||||||
self.validate_cost_center()
|
self.validate_cost_center()
|
||||||
self.set_missing_values()
|
self.set_missing_values()
|
||||||
self.validate_finance_books()
|
|
||||||
if not self.split_from:
|
|
||||||
self.prepare_depreciation_data()
|
|
||||||
update_draft_asset_depr_schedules(self)
|
|
||||||
self.validate_gross_and_purchase_amount()
|
self.validate_gross_and_purchase_amount()
|
||||||
self.validate_expected_value_after_useful_life()
|
self.validate_expected_value_after_useful_life()
|
||||||
|
self.validate_finance_books()
|
||||||
|
|
||||||
self.status = self.get_status()
|
if not self.split_from:
|
||||||
|
self.prepare_depreciation_data()
|
||||||
|
|
||||||
def on_submit(self):
|
if self.calculate_depreciation:
|
||||||
self.validate_in_use_date()
|
update_draft_asset_depr_schedules(self)
|
||||||
self.make_asset_movement()
|
|
||||||
if not self.booked_fixed_asset and self.validate_make_gl_entry():
|
if frappe.db.exists("Asset", self.name):
|
||||||
self.make_gl_entries()
|
|
||||||
if self.calculate_depreciation and not self.split_from:
|
|
||||||
asset_depr_schedules_names = make_draft_asset_depr_schedules_if_not_present(self)
|
asset_depr_schedules_names = make_draft_asset_depr_schedules_if_not_present(self)
|
||||||
convert_draft_asset_depr_schedules_into_active(self)
|
|
||||||
if asset_depr_schedules_names:
|
if asset_depr_schedules_names:
|
||||||
asset_depr_schedules_links = get_comma_separated_links(
|
asset_depr_schedules_links = get_comma_separated_links(
|
||||||
asset_depr_schedules_names, "Asset Depreciation Schedule"
|
asset_depr_schedules_names, "Asset Depreciation Schedule"
|
||||||
@ -72,6 +68,16 @@ class Asset(AccountsController):
|
|||||||
"Asset Depreciation Schedules created:<br>{0}<br><br>Please check, edit if needed, and submit the Asset."
|
"Asset Depreciation Schedules created:<br>{0}<br><br>Please check, edit if needed, and submit the Asset."
|
||||||
).format(asset_depr_schedules_links)
|
).format(asset_depr_schedules_links)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.status = self.get_status()
|
||||||
|
|
||||||
|
def on_submit(self):
|
||||||
|
self.validate_in_use_date()
|
||||||
|
self.make_asset_movement()
|
||||||
|
if not self.booked_fixed_asset and self.validate_make_gl_entry():
|
||||||
|
self.make_gl_entries()
|
||||||
|
if self.calculate_depreciation and not self.split_from:
|
||||||
|
convert_draft_asset_depr_schedules_into_active(self)
|
||||||
self.set_status()
|
self.set_status()
|
||||||
add_asset_activity(self.name, _("Asset submitted"))
|
add_asset_activity(self.name, _("Asset submitted"))
|
||||||
|
|
||||||
@ -827,6 +833,7 @@ def get_item_details(item_code, asset_category, gross_purchase_amount):
|
|||||||
"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),
|
||||||
"depreciation_start_date": d.depreciation_start_date or nowdate(),
|
"depreciation_start_date": d.depreciation_start_date or nowdate(),
|
||||||
|
"rate_of_depreciation": d.rate_of_depreciation,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -509,6 +509,9 @@ def restore_asset(asset_name):
|
|||||||
|
|
||||||
|
|
||||||
def depreciate_asset(asset_doc, date, notes):
|
def depreciate_asset(asset_doc, date, notes):
|
||||||
|
if not asset_doc.calculate_depreciation:
|
||||||
|
return
|
||||||
|
|
||||||
asset_doc.flags.ignore_validate_update_after_submit = True
|
asset_doc.flags.ignore_validate_update_after_submit = True
|
||||||
|
|
||||||
make_new_active_asset_depr_schedules_and_cancel_current_ones(
|
make_new_active_asset_depr_schedules_and_cancel_current_ones(
|
||||||
@ -521,6 +524,9 @@ def depreciate_asset(asset_doc, date, notes):
|
|||||||
|
|
||||||
|
|
||||||
def reset_depreciation_schedule(asset_doc, date, notes):
|
def reset_depreciation_schedule(asset_doc, date, notes):
|
||||||
|
if not asset_doc.calculate_depreciation:
|
||||||
|
return
|
||||||
|
|
||||||
asset_doc.flags.ignore_validate_update_after_submit = True
|
asset_doc.flags.ignore_validate_update_after_submit = True
|
||||||
|
|
||||||
make_new_active_asset_depr_schedules_and_cancel_current_ones(
|
make_new_active_asset_depr_schedules_and_cancel_current_ones(
|
||||||
|
@ -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
|
||||||
},
|
},
|
||||||
|
@ -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), ""]))
|
||||||
|
@ -1,30 +1,21 @@
|
|||||||
// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
|
|
||||||
frappe.ui.form.on('Bulk Transaction Log', {
|
frappe.ui.form.on("Bulk Transaction Log", {
|
||||||
|
refresh(frm) {
|
||||||
refresh: function(frm) {
|
frm.add_custom_button(__('Succeeded Entries'), function() {
|
||||||
frm.disable_save();
|
frappe.set_route('List', 'Bulk Transaction Log Detail', {'date': frm.doc.date, 'transaction_status': "Success"});
|
||||||
frm.add_custom_button(__('Retry Failed Transactions'), ()=>{
|
}, __("View"));
|
||||||
frappe.confirm(__("Retry Failing Transactions ?"), ()=>{
|
frm.add_custom_button(__('Failed Entries'), function() {
|
||||||
query(frm, 1);
|
frappe.set_route('List', 'Bulk Transaction Log Detail', {'date': frm.doc.date, 'transaction_status': "Failed"});
|
||||||
}
|
}, __("View"));
|
||||||
);
|
if (frm.doc.failed) {
|
||||||
});
|
frm.add_custom_button(__('Retry Failed Transactions'), function() {
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function query(frm) {
|
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method: "erpnext.bulk_transaction.doctype.bulk_transaction_log.bulk_transaction_log.retry_failing_transaction",
|
method: "erpnext.utilities.bulk_transaction.retry",
|
||||||
args: {
|
args: {date: frm.doc.date}
|
||||||
log_date: frm.doc.log_date
|
});
|
||||||
}
|
|
||||||
}).then((r) => {
|
|
||||||
if (r.message === "No Failed Records") {
|
|
||||||
frappe.show_alert(__(r.message), 5);
|
|
||||||
} else {
|
|
||||||
frappe.show_alert(__("Retrying Failed Transactions"), 5);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
@ -1,31 +1,64 @@
|
|||||||
{
|
{
|
||||||
"actions": [],
|
"actions": [],
|
||||||
"allow_rename": 1,
|
"allow_copy": 1,
|
||||||
"creation": "2021-11-30 13:41:16.343827",
|
"creation": "2023-11-09 20:14:45.139593",
|
||||||
|
"default_view": "List",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"editable_grid": 1,
|
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
"log_date",
|
"date",
|
||||||
"logger_data"
|
"column_break_bsan",
|
||||||
|
"log_entries",
|
||||||
|
"section_break_mdmv",
|
||||||
|
"succeeded",
|
||||||
|
"column_break_qryp",
|
||||||
|
"failed"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"fieldname": "log_date",
|
"fieldname": "date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"label": "Log Date",
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 1,
|
||||||
|
"label": "Date",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "logger_data",
|
"fieldname": "log_entries",
|
||||||
"fieldtype": "Table",
|
"fieldtype": "Int",
|
||||||
"label": "Logger Data",
|
"in_list_view": 1,
|
||||||
"options": "Bulk Transaction Log Detail"
|
"label": "Log Entries",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_bsan",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_mdmv",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "succeeded",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"label": "Succeeded",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_qryp",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "failed",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"label": "Failed",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"in_create": 1,
|
||||||
|
"is_virtual": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-02-03 17:23:02.935325",
|
"modified": "2023-11-11 04:52:49.347376",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Bulk Transaction",
|
"module": "Bulk Transaction",
|
||||||
"name": "Bulk Transaction Log",
|
"name": "Bulk Transaction Log",
|
||||||
@ -47,5 +80,5 @@
|
|||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"states": [],
|
"states": [],
|
||||||
"track_changes": 1
|
"title_field": "date"
|
||||||
}
|
}
|
@ -1,67 +1,112 @@
|
|||||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
from datetime import date
|
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe import qb
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
from frappe.query_builder.functions import Count
|
||||||
from erpnext.utilities.bulk_transaction import task, update_logger
|
from frappe.utils import cint
|
||||||
|
from pypika import Order
|
||||||
|
|
||||||
|
|
||||||
class BulkTransactionLog(Document):
|
class BulkTransactionLog(Document):
|
||||||
|
def db_insert(self, *args, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def load_from_db(self):
|
||||||
|
log_detail = qb.DocType("Bulk Transaction Log Detail")
|
||||||
|
|
||||||
|
has_records = frappe.db.sql(
|
||||||
|
f"select exists (select * from `tabBulk Transaction Log Detail` where date = '{self.name}');"
|
||||||
|
)[0][0]
|
||||||
|
if not has_records:
|
||||||
|
raise frappe.DoesNotExistError
|
||||||
|
|
||||||
|
succeeded_logs = (
|
||||||
|
qb.from_(log_detail)
|
||||||
|
.select(Count(log_detail.date).as_("count"))
|
||||||
|
.where((log_detail.date == self.name) & (log_detail.transaction_status == "Success"))
|
||||||
|
.run()
|
||||||
|
)[0][0] or 0
|
||||||
|
failed_logs = (
|
||||||
|
qb.from_(log_detail)
|
||||||
|
.select(Count(log_detail.date).as_("count"))
|
||||||
|
.where((log_detail.date == self.name) & (log_detail.transaction_status == "Failed"))
|
||||||
|
.run()
|
||||||
|
)[0][0] or 0
|
||||||
|
total_logs = succeeded_logs + failed_logs
|
||||||
|
transaction_log = frappe._dict(
|
||||||
|
{
|
||||||
|
"date": self.name,
|
||||||
|
"count": total_logs,
|
||||||
|
"succeeded": succeeded_logs,
|
||||||
|
"failed": failed_logs,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
super(Document, self).__init__(serialize_transaction_log(transaction_log))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_list(args):
|
||||||
|
filter_date = parse_list_filters(args)
|
||||||
|
limit = cint(args.get("page_length")) or 20
|
||||||
|
log_detail = qb.DocType("Bulk Transaction Log Detail")
|
||||||
|
|
||||||
|
dates_query = (
|
||||||
|
qb.from_(log_detail)
|
||||||
|
.select(log_detail.date)
|
||||||
|
.distinct()
|
||||||
|
.orderby(log_detail.date, order=Order.desc)
|
||||||
|
.limit(limit)
|
||||||
|
)
|
||||||
|
if filter_date:
|
||||||
|
dates_query = dates_query.where(log_detail.date == filter_date)
|
||||||
|
dates = dates_query.run()
|
||||||
|
|
||||||
|
transaction_logs = []
|
||||||
|
if dates:
|
||||||
|
transaction_logs_query = (
|
||||||
|
qb.from_(log_detail)
|
||||||
|
.select(log_detail.date.as_("date"), Count(log_detail.date).as_("count"))
|
||||||
|
.where(log_detail.date.isin(dates))
|
||||||
|
.orderby(log_detail.date, order=Order.desc)
|
||||||
|
.groupby(log_detail.date)
|
||||||
|
.limit(limit)
|
||||||
|
)
|
||||||
|
transaction_logs = transaction_logs_query.run(as_dict=True)
|
||||||
|
|
||||||
|
return [serialize_transaction_log(x) for x in transaction_logs]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_count(args):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_stats(args):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def db_update(self, *args, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def delete(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
def serialize_transaction_log(data):
|
||||||
def retry_failing_transaction(log_date=None):
|
return frappe._dict(
|
||||||
if not log_date:
|
name=data.date,
|
||||||
log_date = str(date.today())
|
date=data.date,
|
||||||
btp = frappe.qb.DocType("Bulk Transaction Log Detail")
|
log_entries=data.count,
|
||||||
data = (
|
succeeded=data.succeeded,
|
||||||
frappe.qb.from_(btp)
|
failed=data.failed,
|
||||||
.select(btp.transaction_name, btp.from_doctype, btp.to_doctype)
|
|
||||||
.distinct()
|
|
||||||
.where(btp.retried != 1)
|
|
||||||
.where(btp.transaction_status == "Failed")
|
|
||||||
.where(btp.date == log_date)
|
|
||||||
).run(as_dict=True)
|
|
||||||
|
|
||||||
if data:
|
|
||||||
if len(data) > 10:
|
|
||||||
frappe.enqueue(job, queue="long", job_name="bulk_retry", data=data, log_date=log_date)
|
|
||||||
else:
|
|
||||||
job(data, log_date)
|
|
||||||
else:
|
|
||||||
return "No Failed Records"
|
|
||||||
|
|
||||||
|
|
||||||
def job(data, log_date):
|
|
||||||
for d in data:
|
|
||||||
failed = []
|
|
||||||
try:
|
|
||||||
frappe.db.savepoint("before_creation_of_record")
|
|
||||||
task(d.transaction_name, d.from_doctype, d.to_doctype)
|
|
||||||
except Exception as e:
|
|
||||||
frappe.db.rollback(save_point="before_creation_of_record")
|
|
||||||
failed.append(e)
|
|
||||||
update_logger(
|
|
||||||
d.transaction_name,
|
|
||||||
e,
|
|
||||||
d.from_doctype,
|
|
||||||
d.to_doctype,
|
|
||||||
status="Failed",
|
|
||||||
log_date=log_date,
|
|
||||||
restarted=1,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if not failed:
|
|
||||||
update_logger(
|
def parse_list_filters(args):
|
||||||
d.transaction_name,
|
# parse date filter
|
||||||
None,
|
filter_date = None
|
||||||
d.from_doctype,
|
for fil in args.get("filters"):
|
||||||
d.to_doctype,
|
if isinstance(fil, list):
|
||||||
status="Success",
|
for elem in fil:
|
||||||
log_date=log_date,
|
if elem == "date":
|
||||||
restarted=1,
|
filter_date = fil[3]
|
||||||
)
|
return filter_date
|
||||||
|
@ -1,79 +1,9 @@
|
|||||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
|
|
||||||
import unittest
|
# import frappe
|
||||||
from datetime import date
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
import frappe
|
|
||||||
|
|
||||||
from erpnext.utilities.bulk_transaction import transaction_processing
|
|
||||||
|
|
||||||
|
|
||||||
class TestBulkTransactionLog(unittest.TestCase):
|
class TestBulkTransactionLog(FrappeTestCase):
|
||||||
def setUp(self):
|
pass
|
||||||
create_company()
|
|
||||||
create_customer()
|
|
||||||
create_item()
|
|
||||||
|
|
||||||
def test_entry_in_log(self):
|
|
||||||
so_name = create_so()
|
|
||||||
transaction_processing([{"name": so_name}], "Sales Order", "Sales Invoice")
|
|
||||||
doc = frappe.get_doc("Bulk Transaction Log", str(date.today()))
|
|
||||||
for d in doc.get("logger_data"):
|
|
||||||
if d.transaction_name == so_name:
|
|
||||||
self.assertEqual(d.transaction_name, so_name)
|
|
||||||
self.assertEqual(d.transaction_status, "Success")
|
|
||||||
self.assertEqual(d.from_doctype, "Sales Order")
|
|
||||||
self.assertEqual(d.to_doctype, "Sales Invoice")
|
|
||||||
self.assertEqual(d.retried, 0)
|
|
||||||
|
|
||||||
|
|
||||||
def create_company():
|
|
||||||
if not frappe.db.exists("Company", "_Test Company"):
|
|
||||||
frappe.get_doc(
|
|
||||||
{
|
|
||||||
"doctype": "Company",
|
|
||||||
"company_name": "_Test Company",
|
|
||||||
"country": "India",
|
|
||||||
"default_currency": "INR",
|
|
||||||
}
|
|
||||||
).insert()
|
|
||||||
|
|
||||||
|
|
||||||
def create_customer():
|
|
||||||
if not frappe.db.exists("Customer", "Bulk Customer"):
|
|
||||||
frappe.get_doc({"doctype": "Customer", "customer_name": "Bulk Customer"}).insert()
|
|
||||||
|
|
||||||
|
|
||||||
def create_item():
|
|
||||||
if not frappe.db.exists("Item", "MK"):
|
|
||||||
frappe.get_doc(
|
|
||||||
{
|
|
||||||
"doctype": "Item",
|
|
||||||
"item_code": "MK",
|
|
||||||
"item_name": "Milk",
|
|
||||||
"description": "Milk",
|
|
||||||
"item_group": "Products",
|
|
||||||
}
|
|
||||||
).insert()
|
|
||||||
|
|
||||||
|
|
||||||
def create_so(intent=None):
|
|
||||||
so = frappe.new_doc("Sales Order")
|
|
||||||
so.customer = "Bulk Customer"
|
|
||||||
so.company = "_Test Company"
|
|
||||||
so.transaction_date = date.today()
|
|
||||||
|
|
||||||
so.set_warehouse = "Finished Goods - _TC"
|
|
||||||
so.append(
|
|
||||||
"items",
|
|
||||||
{
|
|
||||||
"item_code": "MK",
|
|
||||||
"delivery_date": date.today(),
|
|
||||||
"qty": 10,
|
|
||||||
"rate": 80,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
so.insert()
|
|
||||||
so.submit()
|
|
||||||
return so.name
|
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
// frappe.ui.form.on("Bulk Transaction Log Detail", {
|
||||||
|
// refresh(frm) {
|
||||||
|
|
||||||
|
// },
|
||||||
|
// });
|
@ -6,12 +6,12 @@
|
|||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
|
"from_doctype",
|
||||||
"transaction_name",
|
"transaction_name",
|
||||||
"date",
|
"date",
|
||||||
"time",
|
"time",
|
||||||
"transaction_status",
|
"transaction_status",
|
||||||
"error_description",
|
"error_description",
|
||||||
"from_doctype",
|
|
||||||
"to_doctype",
|
"to_doctype",
|
||||||
"retried"
|
"retried"
|
||||||
],
|
],
|
||||||
@ -20,8 +20,11 @@
|
|||||||
"fieldname": "transaction_name",
|
"fieldname": "transaction_name",
|
||||||
"fieldtype": "Dynamic Link",
|
"fieldtype": "Dynamic Link",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 1,
|
||||||
"label": "Name",
|
"label": "Name",
|
||||||
"options": "from_doctype"
|
"options": "from_doctype",
|
||||||
|
"read_only": 1,
|
||||||
|
"search_index": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "transaction_status",
|
"fieldname": "transaction_status",
|
||||||
@ -39,9 +42,11 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "from_doctype",
|
"fieldname": "from_doctype",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
|
"in_standard_filter": 1,
|
||||||
"label": "From Doctype",
|
"label": "From Doctype",
|
||||||
"options": "DocType",
|
"options": "DocType",
|
||||||
"read_only": 1
|
"read_only": 1,
|
||||||
|
"search_index": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "to_doctype",
|
"fieldname": "to_doctype",
|
||||||
@ -54,8 +59,10 @@
|
|||||||
"fieldname": "date",
|
"fieldname": "date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 1,
|
||||||
"label": "Date ",
|
"label": "Date ",
|
||||||
"read_only": 1
|
"read_only": 1,
|
||||||
|
"search_index": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "time",
|
"fieldname": "time",
|
||||||
@ -66,19 +73,33 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "retried",
|
"fieldname": "retried",
|
||||||
"fieldtype": "Int",
|
"fieldtype": "Int",
|
||||||
|
"in_list_view": 1,
|
||||||
"label": "Retried",
|
"label": "Retried",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"in_create": 1,
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-02-03 19:57:31.650359",
|
"modified": "2023-11-10 11:44:10.758342",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Bulk Transaction",
|
"module": "Bulk Transaction",
|
||||||
"name": "Bulk Transaction Log Detail",
|
"name": "Bulk Transaction Log Detail",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [],
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "System Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"states": [],
|
"states": [],
|
||||||
|
@ -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 TestBulkTransactionLogDetail(FrappeTestCase):
|
||||||
|
pass
|
@ -17,6 +17,7 @@
|
|||||||
"po_required",
|
"po_required",
|
||||||
"pr_required",
|
"pr_required",
|
||||||
"blanket_order_allowance",
|
"blanket_order_allowance",
|
||||||
|
"project_update_frequency",
|
||||||
"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",
|
||||||
@ -172,6 +173,14 @@
|
|||||||
"fieldname": "blanket_order_allowance",
|
"fieldname": "blanket_order_allowance",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"label": "Blanket Order Allowance (%)"
|
"label": "Blanket Order Allowance (%)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "Each Transaction",
|
||||||
|
"description": "How often should Project be updated of Total Purchase Cost ?",
|
||||||
|
"fieldname": "project_update_frequency",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Update frequency of Project",
|
||||||
|
"options": "Each Transaction\nManual"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-cog",
|
"icon": "fa fa-cog",
|
||||||
@ -179,7 +188,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-10-25 14:03:32.520418",
|
"modified": "2023-11-24 10:55:51.287327",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Buying Settings",
|
"name": "Buying Settings",
|
||||||
|
@ -189,6 +189,7 @@
|
|||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"fetch_from": "item_code.image",
|
||||||
"fieldname": "image",
|
"fieldname": "image",
|
||||||
"fieldtype": "Attach",
|
"fieldtype": "Attach",
|
||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
@ -470,6 +471,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",
|
||||||
@ -485,6 +487,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",
|
||||||
@ -914,7 +917,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-10-27 15:50:42.655573",
|
"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",
|
||||||
|
@ -9,6 +9,8 @@
|
|||||||
"field_order": [
|
"field_order": [
|
||||||
"naming_series",
|
"naming_series",
|
||||||
"company",
|
"company",
|
||||||
|
"billing_address",
|
||||||
|
"billing_address_display",
|
||||||
"vendor",
|
"vendor",
|
||||||
"column_break1",
|
"column_break1",
|
||||||
"transaction_date",
|
"transaction_date",
|
||||||
@ -292,13 +294,25 @@
|
|||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Send Document Print",
|
"label": "Send Document Print",
|
||||||
"print_hide": 1
|
"print_hide": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "billing_address",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Company Billing Address",
|
||||||
|
"options": "Address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "billing_address_display",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "Billing Address Details",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-shopping-cart",
|
"icon": "fa fa-shopping-cart",
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-08-09 12:20:26.850623",
|
"modified": "2023-11-06 12:45:28.898706",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Request for Quotation",
|
"name": "Request for Quotation",
|
||||||
|
@ -87,6 +87,7 @@
|
|||||||
"width": "300px"
|
"width": "300px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"fetch_from": "item_code.image",
|
||||||
"fieldname": "image",
|
"fieldname": "image",
|
||||||
"fieldtype": "Attach",
|
"fieldtype": "Attach",
|
||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
@ -260,13 +261,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
|
||||||
}
|
}
|
@ -165,16 +165,17 @@ class Supplier(TransactionBase):
|
|||||||
@frappe.validate_and_sanitize_search_inputs
|
@frappe.validate_and_sanitize_search_inputs
|
||||||
def get_supplier_primary_contact(doctype, txt, searchfield, start, page_len, filters):
|
def get_supplier_primary_contact(doctype, txt, searchfield, start, page_len, filters):
|
||||||
supplier = filters.get("supplier")
|
supplier = filters.get("supplier")
|
||||||
return frappe.db.sql(
|
contact = frappe.qb.DocType("Contact")
|
||||||
"""
|
dynamic_link = frappe.qb.DocType("Dynamic Link")
|
||||||
SELECT
|
|
||||||
`tabContact`.name from `tabContact`,
|
return (
|
||||||
`tabDynamic Link`
|
frappe.qb.from_(contact)
|
||||||
WHERE
|
.join(dynamic_link)
|
||||||
`tabContact`.name = `tabDynamic Link`.parent
|
.on(contact.name == dynamic_link.parent)
|
||||||
and `tabDynamic Link`.link_name = %(supplier)s
|
.select(contact.name, contact.email_id)
|
||||||
and `tabDynamic Link`.link_doctype = 'Supplier'
|
.where(
|
||||||
and `tabContact`.name like %(txt)s
|
(dynamic_link.link_name == supplier)
|
||||||
""",
|
& (dynamic_link.link_doctype == "Supplier")
|
||||||
{"supplier": supplier, "txt": "%%%s%%" % txt},
|
& (contact.name.like("%{0}%".format(txt)))
|
||||||
)
|
)
|
||||||
|
).run(as_dict=False)
|
||||||
|
@ -20,6 +20,10 @@
|
|||||||
"valid_till",
|
"valid_till",
|
||||||
"quotation_number",
|
"quotation_number",
|
||||||
"amended_from",
|
"amended_from",
|
||||||
|
"accounting_dimensions_section",
|
||||||
|
"cost_center",
|
||||||
|
"dimension_col_break",
|
||||||
|
"project",
|
||||||
"currency_and_price_list",
|
"currency_and_price_list",
|
||||||
"currency",
|
"currency",
|
||||||
"conversion_rate",
|
"conversion_rate",
|
||||||
@ -79,6 +83,7 @@
|
|||||||
"pricing_rule_details",
|
"pricing_rule_details",
|
||||||
"pricing_rules",
|
"pricing_rules",
|
||||||
"address_and_contact_tab",
|
"address_and_contact_tab",
|
||||||
|
"supplier_address_section",
|
||||||
"supplier_address",
|
"supplier_address",
|
||||||
"address_display",
|
"address_display",
|
||||||
"column_break_72",
|
"column_break_72",
|
||||||
@ -86,6 +91,14 @@
|
|||||||
"contact_display",
|
"contact_display",
|
||||||
"contact_mobile",
|
"contact_mobile",
|
||||||
"contact_email",
|
"contact_email",
|
||||||
|
"shipping_address_section",
|
||||||
|
"shipping_address",
|
||||||
|
"column_break_zjaq",
|
||||||
|
"shipping_address_display",
|
||||||
|
"company_billing_address_section",
|
||||||
|
"billing_address",
|
||||||
|
"column_break_gcth",
|
||||||
|
"billing_address_display",
|
||||||
"terms_tab",
|
"terms_tab",
|
||||||
"tc_name",
|
"tc_name",
|
||||||
"terms",
|
"terms",
|
||||||
@ -838,6 +851,76 @@
|
|||||||
"fieldname": "named_place",
|
"fieldname": "named_place",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"label": "Named Place"
|
"label": "Named Place"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "shipping_address",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Shipping Address",
|
||||||
|
"options": "Address",
|
||||||
|
"print_hide": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_zjaq",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "shipping_address_display",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "Shipping Address Details",
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "shipping_address_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Shipping Address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "supplier_address_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Supplier Address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "company_billing_address_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Company Billing Address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "billing_address",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Company Billing Address",
|
||||||
|
"options": "Address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_gcth",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "billing_address_display",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "Billing Address Details",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "cost_center",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Cost Center",
|
||||||
|
"options": "Cost Center"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "project",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Project",
|
||||||
|
"options": "Project"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "dimension_col_break",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "accounting_dimensions_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Accounting Dimensions"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-shopping-cart",
|
"icon": "fa fa-shopping-cart",
|
||||||
@ -845,7 +928,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-06-03 16:20:15.880114",
|
"modified": "2023-11-17 12:34:30.083077",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Supplier Quotation",
|
"name": "Supplier Quotation",
|
||||||
|
@ -68,6 +68,8 @@
|
|||||||
"column_break_15",
|
"column_break_15",
|
||||||
"manufacturer_part_no",
|
"manufacturer_part_no",
|
||||||
"ad_sec_break",
|
"ad_sec_break",
|
||||||
|
"cost_center",
|
||||||
|
"dimension_col_break",
|
||||||
"project",
|
"project",
|
||||||
"section_break_44",
|
"section_break_44",
|
||||||
"page_break"
|
"page_break"
|
||||||
@ -553,19 +555,31 @@
|
|||||||
"fieldname": "expected_delivery_date",
|
"fieldname": "expected_delivery_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"label": "Expected Delivery Date"
|
"label": "Expected Delivery Date"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "cost_center",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Cost Center",
|
||||||
|
"options": "Cost Center"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "dimension_col_break",
|
||||||
|
"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": "2020-10-19 12:36:26.913211",
|
"modified": "2023-11-17 12:25:26.235367",
|
||||||
"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
|
||||||
}
|
}
|
@ -239,7 +239,7 @@ class AccountsController(TransactionBase):
|
|||||||
references_map.setdefault(x.parent, []).append(x.name)
|
references_map.setdefault(x.parent, []).append(x.name)
|
||||||
|
|
||||||
for doc, rows in references_map.items():
|
for doc, rows in references_map.items():
|
||||||
unreconcile_doc = frappe.get_doc("Unreconcile Payments", doc)
|
unreconcile_doc = frappe.get_doc("Unreconcile Payment", doc)
|
||||||
for row in rows:
|
for row in rows:
|
||||||
unreconcile_doc.remove(unreconcile_doc.get("allocations", {"name": row})[0])
|
unreconcile_doc.remove(unreconcile_doc.get("allocations", {"name": row})[0])
|
||||||
|
|
||||||
@ -248,9 +248,9 @@ class AccountsController(TransactionBase):
|
|||||||
unreconcile_doc.save(ignore_permissions=True)
|
unreconcile_doc.save(ignore_permissions=True)
|
||||||
|
|
||||||
# delete docs upon parent doc deletion
|
# delete docs upon parent doc deletion
|
||||||
unreconcile_docs = frappe.db.get_all("Unreconcile Payments", filters={"voucher_no": self.name})
|
unreconcile_docs = frappe.db.get_all("Unreconcile Payment", filters={"voucher_no": self.name})
|
||||||
for x in unreconcile_docs:
|
for x in unreconcile_docs:
|
||||||
_doc = frappe.get_doc("Unreconcile Payments", x.name)
|
_doc = frappe.get_doc("Unreconcile Payment", x.name)
|
||||||
if _doc.docstatus == 1:
|
if _doc.docstatus == 1:
|
||||||
_doc.cancel()
|
_doc.cancel()
|
||||||
_doc.delete()
|
_doc.delete()
|
||||||
|
@ -105,7 +105,7 @@ 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
|
# override the rate with valuation rate
|
||||||
row.rate = get_incoming_rate(
|
row.rate = get_incoming_rate(
|
||||||
{
|
{
|
||||||
@ -365,7 +365,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_and_batch_bundle": d.get("serial_and_batch_bundle"),
|
"serial_and_batch_bundle": d.get("serial_and_batch_bundle"),
|
||||||
@ -758,7 +758,7 @@ class BuyingController(SubcontractingController):
|
|||||||
"calculate_depreciation": 0,
|
"calculate_depreciation": 0,
|
||||||
"purchase_receipt_amount": purchase_amount,
|
"purchase_receipt_amount": purchase_amount,
|
||||||
"gross_purchase_amount": purchase_amount,
|
"gross_purchase_amount": purchase_amount,
|
||||||
"asset_quantity": row.qty if is_grouped_asset else 0,
|
"asset_quantity": row.qty if is_grouped_asset else 1,
|
||||||
"purchase_receipt": self.name if self.doctype == "Purchase Receipt" else None,
|
"purchase_receipt": self.name if self.doctype == "Purchase Receipt" else None,
|
||||||
"purchase_invoice": self.name if self.doctype == "Purchase Invoice" else None,
|
"purchase_invoice": self.name if self.doctype == "Purchase Invoice" else None,
|
||||||
}
|
}
|
||||||
|
@ -611,6 +611,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"
|
||||||
|
@ -356,6 +356,7 @@ def make_return_doc(
|
|||||||
if doc.doctype == "Sales Invoice" or doc.doctype == "POS Invoice":
|
if doc.doctype == "Sales Invoice" or doc.doctype == "POS Invoice":
|
||||||
doc.consolidated_invoice = ""
|
doc.consolidated_invoice = ""
|
||||||
doc.set("payments", [])
|
doc.set("payments", [])
|
||||||
|
doc.update_billed_amount_in_delivery_note = True
|
||||||
for data in source.payments:
|
for data in source.payments:
|
||||||
paid_amount = 0.00
|
paid_amount = 0.00
|
||||||
base_paid_amount = 0.00
|
base_paid_amount = 0.00
|
||||||
|
@ -350,11 +350,12 @@ class SellingController(StockController):
|
|||||||
return il
|
return il
|
||||||
|
|
||||||
def has_product_bundle(self, item_code):
|
def has_product_bundle(self, item_code):
|
||||||
return frappe.db.sql(
|
product_bundle = frappe.qb.DocType("Product Bundle")
|
||||||
"""select name from `tabProduct Bundle`
|
return (
|
||||||
where new_item_code=%s and docstatus != 2""",
|
frappe.qb.from_(product_bundle)
|
||||||
item_code,
|
.select(product_bundle.name)
|
||||||
)
|
.where((product_bundle.new_item_code == item_code) & (product_bundle.disabled == 0))
|
||||||
|
).run()
|
||||||
|
|
||||||
def get_already_delivered_qty(self, current_docname, so, so_detail):
|
def get_already_delivered_qty(self, current_docname, so, so_detail):
|
||||||
delivered_via_dn = frappe.db.sql(
|
delivered_via_dn = frappe.db.sql(
|
||||||
|
@ -626,6 +626,18 @@ class SubcontractingController(StockController):
|
|||||||
(row.item_code, row.get(self.subcontract_data.order_field))
|
(row.item_code, row.get(self.subcontract_data.order_field))
|
||||||
] -= row.qty
|
] -= row.qty
|
||||||
|
|
||||||
|
def __set_rate_for_serial_and_batch_bundle(self):
|
||||||
|
if self.doctype != "Subcontracting Receipt":
|
||||||
|
return
|
||||||
|
|
||||||
|
for row in self.get(self.raw_material_table):
|
||||||
|
if not row.get("serial_and_batch_bundle"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
row.rate = frappe.get_cached_value(
|
||||||
|
"Serial and Batch Bundle", row.serial_and_batch_bundle, "avg_rate"
|
||||||
|
)
|
||||||
|
|
||||||
def __modify_serial_and_batch_bundle(self):
|
def __modify_serial_and_batch_bundle(self):
|
||||||
if self.is_new():
|
if self.is_new():
|
||||||
return
|
return
|
||||||
@ -681,6 +693,7 @@ class SubcontractingController(StockController):
|
|||||||
self.__remove_changed_rows()
|
self.__remove_changed_rows()
|
||||||
self.__set_supplied_items()
|
self.__set_supplied_items()
|
||||||
self.__modify_serial_and_batch_bundle()
|
self.__modify_serial_and_batch_bundle()
|
||||||
|
self.__set_rate_for_serial_and_batch_bundle()
|
||||||
|
|
||||||
def __validate_batch_no(self, row, key):
|
def __validate_batch_no(self, row, key):
|
||||||
if row.get("batch_no") and row.get("batch_no") not in self.__transferred_items.get(key).get(
|
if row.get("batch_no") and row.get("batch_no") not in self.__transferred_items.get(key).get(
|
||||||
|
@ -54,6 +54,7 @@ class calculate_taxes_and_totals(object):
|
|||||||
if self.doc.apply_discount_on == "Grand Total" and self.doc.get("is_cash_or_non_trade_discount"):
|
if self.doc.apply_discount_on == "Grand Total" and self.doc.get("is_cash_or_non_trade_discount"):
|
||||||
self.doc.grand_total -= self.doc.discount_amount
|
self.doc.grand_total -= self.doc.discount_amount
|
||||||
self.doc.base_grand_total -= self.doc.base_discount_amount
|
self.doc.base_grand_total -= self.doc.base_discount_amount
|
||||||
|
self.doc.rounding_adjustment = self.doc.base_rounding_adjustment = 0.0
|
||||||
self.set_rounded_total()
|
self.set_rounded_total()
|
||||||
|
|
||||||
self.calculate_shipping_charges()
|
self.calculate_shipping_charges()
|
||||||
|
@ -7,6 +7,8 @@ from frappe.contacts.address_and_contact import (
|
|||||||
delete_contact_and_address,
|
delete_contact_and_address,
|
||||||
load_address_and_contact,
|
load_address_and_contact,
|
||||||
)
|
)
|
||||||
|
from frappe.contacts.doctype.address.address import get_default_address
|
||||||
|
from frappe.contacts.doctype.contact.contact import get_default_contact
|
||||||
from frappe.email.inbox import link_communication_to_document
|
from frappe.email.inbox import link_communication_to_document
|
||||||
from frappe.model.mapper import get_mapped_doc
|
from frappe.model.mapper import get_mapped_doc
|
||||||
from frappe.utils import comma_and, get_link_to_form, has_gravatar, validate_email_address
|
from frappe.utils import comma_and, get_link_to_form, has_gravatar, validate_email_address
|
||||||
@ -251,6 +253,13 @@ def _make_customer(source_name, target_doc=None, ignore_permissions=False):
|
|||||||
|
|
||||||
target.customer_group = frappe.db.get_default("Customer Group")
|
target.customer_group = frappe.db.get_default("Customer Group")
|
||||||
|
|
||||||
|
address = get_default_address("Lead", source.name)
|
||||||
|
contact = get_default_contact("Lead", source.name)
|
||||||
|
if address:
|
||||||
|
target.customer_primary_address = address
|
||||||
|
if contact:
|
||||||
|
target.customer_primary_contact = contact
|
||||||
|
|
||||||
doclist = get_mapped_doc(
|
doclist = get_mapped_doc(
|
||||||
"Lead",
|
"Lead",
|
||||||
source_name,
|
source_name,
|
||||||
|
@ -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
|
||||||
}
|
}
|
@ -421,7 +421,7 @@ scheduler_events = {
|
|||||||
"hourly_long": [
|
"hourly_long": [
|
||||||
"erpnext.accounts.doctype.process_subscription.process_subscription.create_subscription_process",
|
"erpnext.accounts.doctype.process_subscription.process_subscription.create_subscription_process",
|
||||||
"erpnext.stock.doctype.repost_item_valuation.repost_item_valuation.repost_entries",
|
"erpnext.stock.doctype.repost_item_valuation.repost_item_valuation.repost_entries",
|
||||||
"erpnext.bulk_transaction.doctype.bulk_transaction_log.bulk_transaction_log.retry_failing_transaction",
|
"erpnext.utilities.bulk_transaction.retry",
|
||||||
],
|
],
|
||||||
"daily": [
|
"daily": [
|
||||||
"erpnext.support.doctype.issue.issue.auto_close_tickets",
|
"erpnext.support.doctype.issue.issue.auto_close_tickets",
|
||||||
@ -539,6 +539,8 @@ accounting_dimension_doctypes = [
|
|||||||
"Subcontracting Receipt",
|
"Subcontracting Receipt",
|
||||||
"Subcontracting Receipt Item",
|
"Subcontracting Receipt Item",
|
||||||
"Account Closing Balance",
|
"Account Closing Balance",
|
||||||
|
"Supplier Quotation",
|
||||||
|
"Supplier Quotation Item",
|
||||||
]
|
]
|
||||||
|
|
||||||
get_matching_queries = (
|
get_matching_queries = (
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user