mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-18 04:29:18 +00:00
Compare commits
55 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
19604e91c6 | ||
|
|
4f40939c15 | ||
|
|
6748780591 | ||
|
|
11da4372e2 | ||
|
|
4c74ac8738 | ||
|
|
3e21e343d3 | ||
|
|
c734373c9f | ||
|
|
703fd816d1 | ||
|
|
101c71c508 | ||
|
|
a0cd08e9ea | ||
|
|
57a0717778 | ||
|
|
0cf9c94a37 | ||
|
|
e48a03f130 | ||
|
|
e7d97865e5 | ||
|
|
4b16272a01 | ||
|
|
1ff0edd492 | ||
|
|
ef37388993 | ||
|
|
116798df96 | ||
|
|
e515b91988 | ||
|
|
2edf083c35 | ||
|
|
86ddabeae6 | ||
|
|
fc6f568a6c | ||
|
|
cd3a411401 | ||
|
|
6f6133f2e2 | ||
|
|
5a8b81409e | ||
|
|
44a16bb544 | ||
|
|
206d0f1856 | ||
|
|
84432fc035 | ||
|
|
a344b8b9ae | ||
|
|
c65f421da9 | ||
|
|
5c6028340f | ||
|
|
aa0ada9670 | ||
|
|
534b25c448 | ||
|
|
f109303f85 | ||
|
|
ab8fceb68d | ||
|
|
e53a78c2bd | ||
|
|
2ed3bdcc2e | ||
|
|
c61e4e2ddf | ||
|
|
08ba77538b | ||
|
|
de14bf1010 | ||
|
|
46eba50c8c | ||
|
|
c4358c049a | ||
|
|
a43f1badd5 | ||
|
|
9f79da0015 | ||
|
|
ca9df9db07 | ||
|
|
49074aa2fa | ||
|
|
6b9dad7768 | ||
|
|
9e36cac0d1 | ||
|
|
256318bb1c | ||
|
|
91caca05bb | ||
|
|
ceb5997256 | ||
|
|
573ce645b2 | ||
|
|
09cefd9d63 | ||
|
|
222bd9351d | ||
|
|
9985a03f39 |
@@ -3,7 +3,7 @@ import inspect
|
||||
|
||||
import frappe
|
||||
|
||||
__version__ = "14.82.1"
|
||||
__version__ = "14.83.1"
|
||||
|
||||
|
||||
def get_default_company(user=None):
|
||||
|
||||
@@ -25,6 +25,7 @@ class AccountingDimension(Document):
|
||||
"Accounting Dimension Detail",
|
||||
"Company",
|
||||
"Account",
|
||||
"Finance Book",
|
||||
):
|
||||
msg = _("Not allowed to create accounting dimension for {0}").format(self.document_type)
|
||||
frappe.throw(msg)
|
||||
|
||||
@@ -73,6 +73,7 @@
|
||||
"reports_tab",
|
||||
"remarks_section",
|
||||
"general_ledger_remarks_length",
|
||||
"ignore_is_opening_check_for_reporting",
|
||||
"column_break_lvjk",
|
||||
"receivable_payable_remarks_length"
|
||||
],
|
||||
@@ -471,6 +472,13 @@
|
||||
"fieldtype": "Select",
|
||||
"label": "Posting Date Inheritance for Exchange Gain / Loss",
|
||||
"options": "Invoice\nPayment\nReconciliation Date"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Ignores legacy Is Opening field in GL Entry that allows adding opening balance post the system is in use while generating reports",
|
||||
"fieldname": "ignore_is_opening_check_for_reporting",
|
||||
"fieldtype": "Check",
|
||||
"label": "Ignore Is Opening check for reporting"
|
||||
}
|
||||
],
|
||||
"icon": "icon-cog",
|
||||
|
||||
@@ -83,7 +83,7 @@ class GLEntry(Document):
|
||||
if not self.get(k):
|
||||
frappe.throw(_("{0} is required").format(_(self.meta.get_label(k))))
|
||||
|
||||
if not (self.party_type and self.party):
|
||||
if not self.is_cancelled and not (self.party_type and self.party):
|
||||
account_type = frappe.get_cached_value("Account", self.account, "account_type")
|
||||
if account_type == "Receivable":
|
||||
frappe.throw(
|
||||
|
||||
@@ -246,6 +246,7 @@ class PaymentRequest(Document):
|
||||
"payer_name": data.customer_name,
|
||||
"order_id": self.name,
|
||||
"currency": self.currency,
|
||||
"payment_gateway": self.payment_gateway,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
"column_break_42",
|
||||
"free_item_uom",
|
||||
"round_free_qty",
|
||||
"dont_enforce_free_item_qty",
|
||||
"is_recursive",
|
||||
"recurse_for",
|
||||
"apply_recursion_over",
|
||||
@@ -643,12 +644,19 @@
|
||||
"fieldname": "has_priority",
|
||||
"fieldtype": "Check",
|
||||
"label": "Has Priority"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.price_or_product_discount == 'Product'",
|
||||
"fieldname": "dont_enforce_free_item_qty",
|
||||
"fieldtype": "Check",
|
||||
"label": "Don't Enforce Free Item Qty"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-gift",
|
||||
"idx": 1,
|
||||
"links": [],
|
||||
"modified": "2024-09-16 18:14:51.314765",
|
||||
"modified": "2025-02-17 18:15:39.824639",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Pricing Rule",
|
||||
|
||||
@@ -553,7 +553,7 @@ def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None, ra
|
||||
if pricing_rule.margin_type in ["Percentage", "Amount"]:
|
||||
item_details.margin_rate_or_amount = 0.0
|
||||
item_details.margin_type = None
|
||||
elif pricing_rule.get("free_item"):
|
||||
elif pricing_rule.get("free_item") and not pricing_rule.get("dont_enforce_free_item_qty"):
|
||||
item_details.remove_free_item = (
|
||||
item_code if pricing_rule.get("same_item") else pricing_rule.get("free_item")
|
||||
)
|
||||
|
||||
@@ -428,6 +428,54 @@ class TestPricingRule(FrappeTestCase):
|
||||
self.assertEqual(so.items[1].is_free_item, 1)
|
||||
self.assertEqual(so.items[1].item_code, "_Test Item 2")
|
||||
|
||||
def test_dont_enforce_free_item_qty(self):
|
||||
# this test is only for testing non-enforcement as all other tests in this file already test with enforcement
|
||||
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule")
|
||||
test_record = {
|
||||
"doctype": "Pricing Rule",
|
||||
"title": "_Test Pricing Rule",
|
||||
"apply_on": "Item Code",
|
||||
"currency": "USD",
|
||||
"items": [
|
||||
{
|
||||
"item_code": "_Test Item",
|
||||
}
|
||||
],
|
||||
"selling": 1,
|
||||
"rate_or_discount": "Discount Percentage",
|
||||
"rate": 0,
|
||||
"min_qty": 0,
|
||||
"max_qty": 7,
|
||||
"discount_percentage": 17.5,
|
||||
"price_or_product_discount": "Product",
|
||||
"same_item": 0,
|
||||
"free_item": "_Test Item 2",
|
||||
"free_qty": 1,
|
||||
"company": "_Test Company",
|
||||
}
|
||||
pricing_rule = frappe.get_doc(test_record.copy()).insert()
|
||||
|
||||
# With enforcement
|
||||
so = make_sales_order(item_code="_Test Item", qty=1, do_not_submit=True)
|
||||
self.assertEqual(so.items[1].is_free_item, 1)
|
||||
self.assertEqual(so.items[1].item_code, "_Test Item 2")
|
||||
|
||||
# Test 1 : Saving a document with an item with pricing list without it's corresponding free item will cause it the free item to be refetched on save
|
||||
so.items.pop(1)
|
||||
so.save()
|
||||
so.reload()
|
||||
self.assertEqual(len(so.items), 2)
|
||||
|
||||
# Without enforcement
|
||||
pricing_rule.dont_enforce_free_item_qty = 1
|
||||
pricing_rule.save()
|
||||
|
||||
# Test 2 : Deleted free item will not be fetched again on save without enforcement
|
||||
so.items.pop(1)
|
||||
so.save()
|
||||
so.reload()
|
||||
self.assertEqual(len(so.items), 1)
|
||||
|
||||
def test_cumulative_pricing_rule(self):
|
||||
frappe.delete_doc_if_exists("Pricing Rule", "_Test Cumulative Pricing Rule")
|
||||
test_record = {
|
||||
@@ -1239,6 +1287,7 @@ def make_pricing_rule(**args):
|
||||
"discount_amount": args.discount_amount or 0.0,
|
||||
"apply_multiple_pricing_rules": args.apply_multiple_pricing_rules or 0,
|
||||
"has_priority": args.has_priority or 0,
|
||||
"enforce_free_item_qty": args.dont_enforce_free_item_qty or 0,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -691,7 +691,10 @@ def apply_pricing_rule_for_free_items(doc, pricing_rule_args):
|
||||
args.pop((item.item_code, item.pricing_rules))
|
||||
|
||||
for free_item in args.values():
|
||||
doc.append("items", free_item)
|
||||
if doc.is_new() or not frappe.get_value(
|
||||
"Pricing Rule", free_item["pricing_rules"], "dont_enforce_free_item_qty"
|
||||
):
|
||||
doc.append("items", free_item)
|
||||
|
||||
|
||||
def get_pricing_rule_items(pr_doc, other_items=False) -> list:
|
||||
|
||||
@@ -177,17 +177,21 @@ def get_ar_filters(doc, entry):
|
||||
|
||||
def get_html(doc, filters, entry, col, res, ageing):
|
||||
base_template_path = "frappe/www/printview.html"
|
||||
template_path = (
|
||||
"erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html"
|
||||
if doc.report == "General Ledger"
|
||||
else "erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts_accounts_receivable.html"
|
||||
)
|
||||
template_path = "erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts_accounts_receivable.html"
|
||||
if doc.report == "General Ledger":
|
||||
template_path = (
|
||||
"erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html"
|
||||
)
|
||||
|
||||
process_soa_html = frappe.get_hooks("process_soa_html")
|
||||
# fetching custom print format for Process Statement of Accounts
|
||||
if process_soa_html and process_soa_html.get(doc.report):
|
||||
template_path = process_soa_html[doc.report][-1]
|
||||
|
||||
if doc.letter_head:
|
||||
from frappe.www.printview import get_letter_head
|
||||
|
||||
letter_head = get_letter_head(doc, 0)
|
||||
|
||||
html = frappe.render_template(
|
||||
template_path,
|
||||
{
|
||||
@@ -203,7 +207,6 @@ def get_html(doc, filters, entry, col, res, ageing):
|
||||
else None,
|
||||
},
|
||||
)
|
||||
|
||||
html = frappe.render_template(
|
||||
base_template_path,
|
||||
{"body": html, "css": get_print_style(), "title": "Statement For " + entry.customer},
|
||||
|
||||
@@ -236,7 +236,7 @@ def get_lower_deduction_certificate(company, posting_date, tax_details, pan_no):
|
||||
|
||||
|
||||
def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=None):
|
||||
vouchers, voucher_wise_amount = get_invoice_vouchers(
|
||||
vouchers, voucher_wise_amount, zero_rate_ldc_invoices = get_invoice_vouchers(
|
||||
parties, tax_details, inv.company, party_type=party_type
|
||||
)
|
||||
|
||||
@@ -290,7 +290,8 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N
|
||||
# once tds is deducted, not need to add vouchers in the invoice
|
||||
voucher_wise_amount = {}
|
||||
else:
|
||||
tax_amount = get_tds_amount(ldc, parties, inv, tax_details, vouchers)
|
||||
taxable_vouchers = list(set(vouchers) - set(zero_rate_ldc_invoices))
|
||||
tax_amount = get_tds_amount(ldc, parties, inv, tax_details, taxable_vouchers)
|
||||
|
||||
elif party_type == "Customer":
|
||||
if tax_deducted:
|
||||
@@ -309,12 +310,33 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N
|
||||
|
||||
def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"):
|
||||
doctype = "Purchase Invoice" if party_type == "Supplier" else "Sales Invoice"
|
||||
field = (
|
||||
"base_tax_withholding_net_total as base_net_total" if party_type == "Supplier" else "base_net_total"
|
||||
)
|
||||
field = [
|
||||
"name",
|
||||
"base_tax_withholding_net_total as base_net_total" if party_type == "Supplier" else "base_net_total",
|
||||
"posting_date",
|
||||
]
|
||||
voucher_wise_amount = {}
|
||||
vouchers = []
|
||||
|
||||
ldcs = frappe.db.get_all(
|
||||
"Lower Deduction Certificate",
|
||||
filters={
|
||||
"valid_from": [">=", tax_details.from_date],
|
||||
"valid_upto": ["<=", tax_details.to_date],
|
||||
"company": company,
|
||||
"supplier": ["in", parties],
|
||||
},
|
||||
fields=["supplier", "valid_from", "valid_upto", "rate"],
|
||||
)
|
||||
|
||||
doctype = "Purchase Invoice" if party_type == "Supplier" else "Sales Invoice"
|
||||
field = [
|
||||
"base_tax_withholding_net_total as base_net_total" if party_type == "Supplier" else "base_net_total",
|
||||
"name",
|
||||
"grand_total",
|
||||
"posting_date",
|
||||
]
|
||||
|
||||
filters = {
|
||||
"company": company,
|
||||
frappe.scrub(party_type): ["in", parties],
|
||||
@@ -328,11 +350,31 @@ def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"):
|
||||
{"apply_tds": 1, "tax_withholding_category": tax_details.get("tax_withholding_category")}
|
||||
)
|
||||
|
||||
invoices_details = frappe.get_all(doctype, filters=filters, fields=["name", field])
|
||||
invoices_details = frappe.get_all(doctype, filters=filters, fields=field)
|
||||
|
||||
ldcs = frappe.db.get_all(
|
||||
"Lower Deduction Certificate",
|
||||
filters={
|
||||
"valid_from": [">=", tax_details.from_date],
|
||||
"valid_upto": ["<=", tax_details.to_date],
|
||||
"company": company,
|
||||
"supplier": ["in", parties],
|
||||
"rate": 0,
|
||||
},
|
||||
fields=["name", "supplier", "valid_from", "valid_upto"],
|
||||
)
|
||||
|
||||
zero_rate_ldc_invoices = []
|
||||
for d in invoices_details:
|
||||
vouchers.append(d.name)
|
||||
voucher_wise_amount.update({d.name: {"amount": d.base_net_total, "voucher_type": doctype}})
|
||||
_voucher_detail = {"amount": d.base_net_total, "voucher_type": doctype}
|
||||
|
||||
if ldc := [x for x in ldcs if d.posting_date >= x.valid_from and d.posting_date <= x.valid_upto]:
|
||||
if ldc[0].supplier in parties:
|
||||
_voucher_detail.update({"amount": 0})
|
||||
zero_rate_ldc_invoices.append(d.name)
|
||||
|
||||
voucher_wise_amount.update({d.name: _voucher_detail})
|
||||
|
||||
journal_entries_details = frappe.db.sql(
|
||||
"""
|
||||
@@ -363,7 +405,7 @@ def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"):
|
||||
vouchers.append(d.name)
|
||||
voucher_wise_amount.update({d.name: {"amount": d.amount, "voucher_type": "Journal Entry"}})
|
||||
|
||||
return vouchers, voucher_wise_amount
|
||||
return vouchers, voucher_wise_amount, zero_rate_ldc_invoices
|
||||
|
||||
|
||||
def get_payment_entry_vouchers(parties, tax_details, company, party_type="Supplier"):
|
||||
|
||||
@@ -7,7 +7,7 @@ import unittest
|
||||
import frappe
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||
from frappe.utils import add_days, today
|
||||
from frappe.utils import add_days, add_months, today
|
||||
|
||||
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
@@ -614,6 +614,49 @@ class TestTaxWithholdingCategory(FrappeTestCase):
|
||||
pi2.cancel()
|
||||
pi3.cancel()
|
||||
|
||||
def test_ldc_at_0_rate(self):
|
||||
frappe.db.set_value(
|
||||
"Supplier",
|
||||
"Test LDC Supplier",
|
||||
{
|
||||
"tax_withholding_category": "Test Service Category",
|
||||
"pan": "ABCTY1234D",
|
||||
},
|
||||
)
|
||||
|
||||
fiscal_year = get_fiscal_year(today(), company="_Test Company")
|
||||
valid_from = fiscal_year[1]
|
||||
valid_upto = add_months(valid_from, 1)
|
||||
create_lower_deduction_certificate(
|
||||
supplier="Test LDC Supplier",
|
||||
certificate_no="1AE0423AAJ",
|
||||
tax_withholding_category="Test Service Category",
|
||||
tax_rate=0,
|
||||
limit=50000,
|
||||
valid_from=valid_from,
|
||||
valid_upto=valid_upto,
|
||||
)
|
||||
|
||||
pi1 = create_purchase_invoice(
|
||||
supplier="Test LDC Supplier", rate=35000, posting_date=valid_from, set_posting_time=True
|
||||
)
|
||||
pi1.submit()
|
||||
self.assertEqual(pi1.taxes, [])
|
||||
|
||||
pi2 = create_purchase_invoice(
|
||||
supplier="Test LDC Supplier",
|
||||
rate=35000,
|
||||
posting_date=add_days(valid_upto, 1),
|
||||
set_posting_time=True,
|
||||
)
|
||||
pi2.submit()
|
||||
self.assertEqual(len(pi2.taxes), 1)
|
||||
# pi1 net total shouldn't be included as it lies within LDC at rate of '0'
|
||||
self.assertEqual(pi2.taxes[0].tax_amount, 3500)
|
||||
|
||||
pi1.cancel()
|
||||
pi2.cancel()
|
||||
|
||||
def set_previous_fy_and_tax_category(self):
|
||||
test_company = "_Test Company"
|
||||
category = "Cumulative Threshold TDS"
|
||||
@@ -771,7 +814,8 @@ def create_purchase_invoice(**args):
|
||||
pi = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Purchase Invoice",
|
||||
"posting_date": today(),
|
||||
"set_posting_time": args.set_posting_time or False,
|
||||
"posting_date": args.posting_date or today(),
|
||||
"apply_tds": 0 if args.do_not_apply_tds else 1,
|
||||
"supplier": args.supplier,
|
||||
"company": "_Test Company",
|
||||
@@ -1099,7 +1143,9 @@ def create_tax_withholding_category(
|
||||
).insert()
|
||||
|
||||
|
||||
def create_lower_deduction_certificate(supplier, tax_withholding_category, tax_rate, certificate_no, limit):
|
||||
def create_lower_deduction_certificate(
|
||||
supplier, tax_withholding_category, tax_rate, certificate_no, limit, valid_from=None, valid_upto=None
|
||||
):
|
||||
fiscal_year = get_fiscal_year(today(), company="_Test Company")
|
||||
if not frappe.db.exists("Lower Deduction Certificate", certificate_no):
|
||||
frappe.get_doc(
|
||||
@@ -1110,8 +1156,8 @@ def create_lower_deduction_certificate(supplier, tax_withholding_category, tax_r
|
||||
"certificate_no": certificate_no,
|
||||
"tax_withholding_category": tax_withholding_category,
|
||||
"fiscal_year": fiscal_year[0],
|
||||
"valid_from": fiscal_year[1],
|
||||
"valid_upto": fiscal_year[2],
|
||||
"valid_from": valid_from or fiscal_year[1],
|
||||
"valid_upto": valid_upto or fiscal_year[2],
|
||||
"rate": tax_rate,
|
||||
"certificate_limit": limit,
|
||||
}
|
||||
|
||||
@@ -111,6 +111,7 @@ frappe.query_reports["Accounts Payable"] = {
|
||||
fieldname: "party",
|
||||
label: __("Party"),
|
||||
fieldtype: "MultiSelectList",
|
||||
options: "party_type",
|
||||
get_data: function (txt) {
|
||||
if (!frappe.query_report.filters) return;
|
||||
|
||||
|
||||
@@ -88,6 +88,7 @@ frappe.query_reports["Accounts Payable Summary"] = {
|
||||
fieldname: "party",
|
||||
label: __("Party"),
|
||||
fieldtype: "MultiSelectList",
|
||||
options: "party_type",
|
||||
get_data: function (txt) {
|
||||
if (!frappe.query_report.filters) return;
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ frappe.query_reports["Accounts Receivable"] = {
|
||||
fieldname: "party",
|
||||
label: __("Party"),
|
||||
fieldtype: "MultiSelectList",
|
||||
options: "party_type",
|
||||
get_data: function (txt) {
|
||||
if (!frappe.query_report.filters) return;
|
||||
|
||||
|
||||
@@ -88,6 +88,7 @@ frappe.query_reports["Accounts Receivable Summary"] = {
|
||||
fieldname: "party",
|
||||
label: __("Party"),
|
||||
fieldtype: "MultiSelectList",
|
||||
options: "party_type",
|
||||
get_data: function (txt) {
|
||||
if (!frappe.query_report.filters) return;
|
||||
|
||||
|
||||
@@ -91,6 +91,7 @@ function get_filters() {
|
||||
fieldname: "budget_against_filter",
|
||||
label: __("Dimension Filter"),
|
||||
fieldtype: "MultiSelectList",
|
||||
options: "budget_against",
|
||||
get_data: function (txt) {
|
||||
if (!frappe.query_report.filters) return;
|
||||
|
||||
|
||||
@@ -512,12 +512,16 @@ def get_accounting_entries(
|
||||
.where(gl_entry.company == filters.company)
|
||||
)
|
||||
|
||||
ignore_is_opening = frappe.db.get_single_value(
|
||||
"Accounts Settings", "ignore_is_opening_check_for_reporting"
|
||||
)
|
||||
|
||||
if doctype == "GL Entry":
|
||||
query = query.select(gl_entry.posting_date, gl_entry.is_opening, gl_entry.fiscal_year)
|
||||
query = query.where(gl_entry.is_cancelled == 0)
|
||||
query = query.where(gl_entry.posting_date <= to_date)
|
||||
|
||||
if ignore_opening_entries:
|
||||
if ignore_opening_entries and not ignore_is_opening:
|
||||
query = query.where(gl_entry.is_opening == "No")
|
||||
else:
|
||||
query = query.select(gl_entry.closing_date.as_("posting_date"))
|
||||
|
||||
@@ -68,6 +68,7 @@ frappe.query_reports["General Ledger"] = {
|
||||
fieldname: "party",
|
||||
label: __("Party"),
|
||||
fieldtype: "MultiSelectList",
|
||||
options: "party_type",
|
||||
get_data: function (txt) {
|
||||
if (!frappe.query_report.filters) return;
|
||||
|
||||
@@ -146,6 +147,7 @@ frappe.query_reports["General Ledger"] = {
|
||||
fieldname: "cost_center",
|
||||
label: __("Cost Center"),
|
||||
fieldtype: "MultiSelectList",
|
||||
options: "Cost Center",
|
||||
get_data: function (txt) {
|
||||
return frappe.db.get_link_options("Cost Center", txt, {
|
||||
company: frappe.query_report.get_filter_value("company"),
|
||||
@@ -156,6 +158,7 @@ frappe.query_reports["General Ledger"] = {
|
||||
fieldname: "project",
|
||||
label: __("Project"),
|
||||
fieldtype: "MultiSelectList",
|
||||
options: "Project",
|
||||
get_data: function (txt) {
|
||||
return frappe.db.get_link_options("Project", txt, {
|
||||
company: frappe.query_report.get_filter_value("company"),
|
||||
|
||||
@@ -209,6 +209,10 @@ def get_gl_entries(filters, accounting_dimensions):
|
||||
def get_conditions(filters):
|
||||
conditions = []
|
||||
|
||||
ignore_is_opening = frappe.db.get_single_value(
|
||||
"Accounts Settings", "ignore_is_opening_check_for_reporting"
|
||||
)
|
||||
|
||||
if filters.get("account"):
|
||||
filters.account = get_accounts_with_children(filters.account)
|
||||
if filters.account:
|
||||
@@ -268,9 +272,15 @@ def get_conditions(filters):
|
||||
or filters.get("party")
|
||||
or filters.get("group_by") in ["Group by Account", "Group by Party"]
|
||||
):
|
||||
conditions.append("(posting_date >=%(from_date)s or is_opening = 'Yes')")
|
||||
if not ignore_is_opening:
|
||||
conditions.append("(posting_date >=%(from_date)s or is_opening = 'Yes')")
|
||||
else:
|
||||
conditions.append("posting_date >=%(from_date)s")
|
||||
|
||||
conditions.append("(posting_date <=%(to_date)s or is_opening = 'Yes')")
|
||||
if not ignore_is_opening:
|
||||
conditions.append("(posting_date <=%(to_date)s or is_opening = 'Yes')")
|
||||
else:
|
||||
conditions.append("posting_date <=%(to_date)s")
|
||||
|
||||
if filters.get("project"):
|
||||
conditions.append("project in %(project)s")
|
||||
|
||||
@@ -207,15 +207,34 @@ def get_data_when_grouped_by_invoice(columns, gross_profit_data, filters, group_
|
||||
|
||||
|
||||
def get_data_when_not_grouped_by_invoice(gross_profit_data, filters, group_wise_columns, data):
|
||||
for src in gross_profit_data.grouped_data:
|
||||
row = []
|
||||
for col in group_wise_columns.get(scrub(filters.group_by)):
|
||||
row.append(src.get(col))
|
||||
total_base_amount = 0
|
||||
total_buying_amount = 0
|
||||
|
||||
row.append(filters.currency)
|
||||
group_columns = group_wise_columns.get(scrub(filters.group_by))
|
||||
|
||||
for src in gross_profit_data.grouped_data:
|
||||
total_base_amount += src.base_amount or 0.00
|
||||
total_buying_amount += src.buying_amount or 0.00
|
||||
|
||||
row = [src.get(col) for col in group_columns] + [filters.currency]
|
||||
|
||||
data.append(row)
|
||||
|
||||
total_gross_profit = total_base_amount - total_buying_amount
|
||||
currency_precision = cint(frappe.db.get_default("currency_precision")) or 3
|
||||
gross_profit_percent = (total_gross_profit / total_base_amount * 100.0) if total_base_amount else 0
|
||||
|
||||
total_row = {
|
||||
group_columns[0]: "Total",
|
||||
"base_amount": total_base_amount,
|
||||
"buying_amount": total_buying_amount,
|
||||
"gross_profit": total_gross_profit,
|
||||
"gross_profit_percent": flt(gross_profit_percent, currency_precision),
|
||||
}
|
||||
|
||||
total_row = [total_row.get(col, None) for col in [*group_columns, "currency"]]
|
||||
data.append(total_row)
|
||||
|
||||
|
||||
def get_columns(group_wise_columns, filters):
|
||||
columns = []
|
||||
|
||||
@@ -51,6 +51,7 @@ function get_filters() {
|
||||
fieldname: "party",
|
||||
label: __("Party"),
|
||||
fieldtype: "MultiSelectList",
|
||||
options: "party_type",
|
||||
get_data: function (txt) {
|
||||
if (!frappe.query_report.filters) return;
|
||||
|
||||
|
||||
@@ -14,14 +14,14 @@
|
||||
"owner": "Administrator",
|
||||
"ref_doctype": "GL Entry",
|
||||
"report_name": "Trial Balance",
|
||||
"report_type": "Script Report",
|
||||
"report_type": "Script Report",
|
||||
"roles": [
|
||||
{
|
||||
"role": "Accounts User"
|
||||
},
|
||||
},
|
||||
{
|
||||
"role": "Accounts Manager"
|
||||
},
|
||||
},
|
||||
{
|
||||
"role": "Auditor"
|
||||
}
|
||||
|
||||
@@ -89,6 +89,10 @@ def get_data(filters):
|
||||
)
|
||||
company_currency = filters.presentation_currency or erpnext.get_company_currency(filters.company)
|
||||
|
||||
ignore_is_opening = frappe.db.get_single_value(
|
||||
"Accounts Settings", "ignore_is_opening_check_for_reporting"
|
||||
)
|
||||
|
||||
if not accounts:
|
||||
return None
|
||||
|
||||
@@ -102,7 +106,7 @@ def get_data(filters):
|
||||
|
||||
gl_entries_by_account = {}
|
||||
|
||||
opening_balances = get_opening_balances(filters)
|
||||
opening_balances = get_opening_balances(filters, ignore_is_opening)
|
||||
|
||||
# add filter inside list so that the query in financial_statements.py doesn't break
|
||||
if filters.project:
|
||||
@@ -120,7 +124,13 @@ def get_data(filters):
|
||||
ignore_opening_entries=True,
|
||||
)
|
||||
|
||||
calculate_values(accounts, gl_entries_by_account, opening_balances, filters.get("show_net_values"))
|
||||
calculate_values(
|
||||
accounts,
|
||||
gl_entries_by_account,
|
||||
opening_balances,
|
||||
filters.get("show_net_values"),
|
||||
ignore_is_opening=ignore_is_opening,
|
||||
)
|
||||
accumulate_values_into_parents(accounts, accounts_by_name)
|
||||
|
||||
data = prepare_data(accounts, filters, parent_children_map, company_currency)
|
||||
@@ -131,15 +141,15 @@ def get_data(filters):
|
||||
return data
|
||||
|
||||
|
||||
def get_opening_balances(filters):
|
||||
balance_sheet_opening = get_rootwise_opening_balances(filters, "Balance Sheet")
|
||||
pl_opening = get_rootwise_opening_balances(filters, "Profit and Loss")
|
||||
def get_opening_balances(filters, ignore_is_opening):
|
||||
balance_sheet_opening = get_rootwise_opening_balances(filters, "Balance Sheet", ignore_is_opening)
|
||||
pl_opening = get_rootwise_opening_balances(filters, "Profit and Loss", ignore_is_opening)
|
||||
|
||||
balance_sheet_opening.update(pl_opening)
|
||||
return balance_sheet_opening
|
||||
|
||||
|
||||
def get_rootwise_opening_balances(filters, report_type):
|
||||
def get_rootwise_opening_balances(filters, report_type, ignore_is_opening):
|
||||
gle = []
|
||||
|
||||
last_period_closing_voucher = ""
|
||||
@@ -165,16 +175,24 @@ def get_rootwise_opening_balances(filters, report_type):
|
||||
report_type,
|
||||
accounting_dimensions,
|
||||
period_closing_voucher=last_period_closing_voucher[0].name,
|
||||
ignore_is_opening=ignore_is_opening,
|
||||
)
|
||||
|
||||
# Report getting generate from the mid of a fiscal year
|
||||
if getdate(last_period_closing_voucher[0].posting_date) < getdate(add_days(filters.from_date, -1)):
|
||||
start_date = add_days(last_period_closing_voucher[0].posting_date, 1)
|
||||
gle += get_opening_balance(
|
||||
"GL Entry", filters, report_type, accounting_dimensions, start_date=start_date
|
||||
"GL Entry",
|
||||
filters,
|
||||
report_type,
|
||||
accounting_dimensions,
|
||||
start_date=start_date,
|
||||
ignore_is_opening=ignore_is_opening,
|
||||
)
|
||||
else:
|
||||
gle = get_opening_balance("GL Entry", filters, report_type, accounting_dimensions)
|
||||
gle = get_opening_balance(
|
||||
"GL Entry", filters, report_type, accounting_dimensions, ignore_is_opening=ignore_is_opening
|
||||
)
|
||||
|
||||
opening = frappe._dict()
|
||||
for d in gle:
|
||||
@@ -193,7 +211,13 @@ def get_rootwise_opening_balances(filters, report_type):
|
||||
|
||||
|
||||
def get_opening_balance(
|
||||
doctype, filters, report_type, accounting_dimensions, period_closing_voucher=None, start_date=None
|
||||
doctype,
|
||||
filters,
|
||||
report_type,
|
||||
accounting_dimensions,
|
||||
period_closing_voucher=None,
|
||||
start_date=None,
|
||||
ignore_is_opening=0,
|
||||
):
|
||||
closing_balance = frappe.qb.DocType(doctype)
|
||||
account = frappe.qb.DocType("Account")
|
||||
@@ -229,11 +253,16 @@ def get_opening_balance(
|
||||
(closing_balance.posting_date >= start_date)
|
||||
& (closing_balance.posting_date < filters.from_date)
|
||||
)
|
||||
opening_balance = opening_balance.where(closing_balance.is_opening == "No")
|
||||
|
||||
if not ignore_is_opening:
|
||||
opening_balance = opening_balance.where(closing_balance.is_opening == "No")
|
||||
else:
|
||||
opening_balance = opening_balance.where(
|
||||
(closing_balance.posting_date < filters.from_date) | (closing_balance.is_opening == "Yes")
|
||||
)
|
||||
if not ignore_is_opening:
|
||||
opening_balance = opening_balance.where(
|
||||
(closing_balance.posting_date < filters.from_date) | (closing_balance.is_opening == "Yes")
|
||||
)
|
||||
else:
|
||||
opening_balance = opening_balance.where(closing_balance.posting_date < filters.from_date)
|
||||
|
||||
if doctype == "GL Entry":
|
||||
opening_balance = opening_balance.where(closing_balance.is_cancelled == 0)
|
||||
@@ -304,7 +333,7 @@ def get_opening_balance(
|
||||
return gle
|
||||
|
||||
|
||||
def calculate_values(accounts, gl_entries_by_account, opening_balances, show_net_values):
|
||||
def calculate_values(accounts, gl_entries_by_account, opening_balances, show_net_values, ignore_is_opening=0):
|
||||
init = {
|
||||
"opening_debit": 0.0,
|
||||
"opening_credit": 0.0,
|
||||
@@ -322,7 +351,7 @@ def calculate_values(accounts, gl_entries_by_account, opening_balances, show_net
|
||||
d["opening_credit"] = opening_balances.get(d.name, {}).get("opening_credit", 0)
|
||||
|
||||
for entry in gl_entries_by_account.get(d.name, []):
|
||||
if cstr(entry.is_opening) != "Yes":
|
||||
if cstr(entry.is_opening) != "Yes" or ignore_is_opening:
|
||||
d["debit"] += flt(entry.debit)
|
||||
d["credit"] += flt(entry.credit)
|
||||
|
||||
|
||||
@@ -68,16 +68,12 @@ frappe.query_reports["Trial Balance for Party"] = {
|
||||
{
|
||||
fieldname: "account",
|
||||
label: __("Account"),
|
||||
fieldtype: "Link",
|
||||
fieldtype: "MultiSelectList",
|
||||
options: "Account",
|
||||
get_query: function () {
|
||||
var company = frappe.query_report.get_filter_value("company");
|
||||
return {
|
||||
doctype: "Account",
|
||||
filters: {
|
||||
company: company,
|
||||
},
|
||||
};
|
||||
get_data: function (txt) {
|
||||
return frappe.db.get_link_options("Account", txt, {
|
||||
company: frappe.query_report.get_filter_value("company"),
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.query_builder.functions import Sum
|
||||
from frappe.utils import cint, flt
|
||||
|
||||
from erpnext.accounts.report.general_ledger.general_ledger import get_accounts_with_children
|
||||
from erpnext.accounts.report.trial_balance.trial_balance import validate_filters
|
||||
|
||||
|
||||
@@ -35,9 +37,14 @@ def get_data(filters, show_party_name):
|
||||
filters=party_filters,
|
||||
order_by="name",
|
||||
)
|
||||
|
||||
account_filter = []
|
||||
if filters.get("account"):
|
||||
account_filter = get_accounts_with_children(filters.get("account"))
|
||||
|
||||
company_currency = frappe.get_cached_value("Company", filters.company, "default_currency")
|
||||
opening_balances = get_opening_balances(filters)
|
||||
balances_within_period = get_balances_within_period(filters)
|
||||
opening_balances = get_opening_balances(filters, account_filter)
|
||||
balances_within_period = get_balances_within_period(filters, account_filter)
|
||||
|
||||
data = []
|
||||
# total_debit, total_credit = 0, 0
|
||||
@@ -89,30 +96,34 @@ def get_data(filters, show_party_name):
|
||||
return data
|
||||
|
||||
|
||||
def get_opening_balances(filters):
|
||||
account_filter = ""
|
||||
if filters.get("account"):
|
||||
account_filter = "and account = %s" % (frappe.db.escape(filters.get("account")))
|
||||
def get_opening_balances(filters, account_filter=None):
|
||||
GL_Entry = frappe.qb.DocType("GL Entry")
|
||||
|
||||
gle = frappe.db.sql(
|
||||
f"""
|
||||
select party, sum(debit) as opening_debit, sum(credit) as opening_credit
|
||||
from `tabGL Entry`
|
||||
where company=%(company)s
|
||||
and is_cancelled=0
|
||||
and ifnull(party_type, '') = %(party_type)s and ifnull(party, '') != ''
|
||||
and (posting_date < %(from_date)s or (ifnull(is_opening, 'No') = 'Yes' and posting_date <= %(to_date)s))
|
||||
{account_filter}
|
||||
group by party""",
|
||||
{
|
||||
"company": filters.company,
|
||||
"from_date": filters.from_date,
|
||||
"to_date": filters.to_date,
|
||||
"party_type": filters.party_type,
|
||||
},
|
||||
as_dict=True,
|
||||
query = (
|
||||
frappe.qb.from_(GL_Entry)
|
||||
.select(
|
||||
GL_Entry.party,
|
||||
Sum(GL_Entry.debit).as_("opening_debit"),
|
||||
Sum(GL_Entry.credit).as_("opening_credit"),
|
||||
)
|
||||
.where(
|
||||
(GL_Entry.company == filters.company)
|
||||
& (GL_Entry.is_cancelled == 0)
|
||||
& (GL_Entry.party_type == filters.party_type)
|
||||
& (GL_Entry.party != "")
|
||||
& (
|
||||
(GL_Entry.posting_date < filters.from_date)
|
||||
| ((GL_Entry.is_opening == "Yes") & (GL_Entry.posting_date <= filters.to_date))
|
||||
)
|
||||
)
|
||||
.groupby(GL_Entry.party)
|
||||
)
|
||||
|
||||
if account_filter:
|
||||
query = query.where(GL_Entry.account.isin(account_filter))
|
||||
|
||||
gle = query.run(as_dict=True)
|
||||
|
||||
opening = frappe._dict()
|
||||
for d in gle:
|
||||
opening_debit, opening_credit = toggle_debit_credit(d.opening_debit, d.opening_credit)
|
||||
@@ -121,31 +132,33 @@ def get_opening_balances(filters):
|
||||
return opening
|
||||
|
||||
|
||||
def get_balances_within_period(filters):
|
||||
account_filter = ""
|
||||
if filters.get("account"):
|
||||
account_filter = "and account = %s" % (frappe.db.escape(filters.get("account")))
|
||||
def get_balances_within_period(filters, account_filter=None):
|
||||
GL_Entry = frappe.qb.DocType("GL Entry")
|
||||
|
||||
gle = frappe.db.sql(
|
||||
f"""
|
||||
select party, sum(debit) as debit, sum(credit) as credit
|
||||
from `tabGL Entry`
|
||||
where company=%(company)s
|
||||
and is_cancelled = 0
|
||||
and ifnull(party_type, '') = %(party_type)s and ifnull(party, '') != ''
|
||||
and posting_date >= %(from_date)s and posting_date <= %(to_date)s
|
||||
and ifnull(is_opening, 'No') = 'No'
|
||||
{account_filter}
|
||||
group by party""",
|
||||
{
|
||||
"company": filters.company,
|
||||
"from_date": filters.from_date,
|
||||
"to_date": filters.to_date,
|
||||
"party_type": filters.party_type,
|
||||
},
|
||||
as_dict=True,
|
||||
query = (
|
||||
frappe.qb.from_(GL_Entry)
|
||||
.select(
|
||||
GL_Entry.party,
|
||||
Sum(GL_Entry.debit).as_("debit"),
|
||||
Sum(GL_Entry.credit).as_("credit"),
|
||||
)
|
||||
.where(
|
||||
(GL_Entry.company == filters.company)
|
||||
& (GL_Entry.is_cancelled == 0)
|
||||
& (GL_Entry.party_type == filters.party_type)
|
||||
& (GL_Entry.party != "")
|
||||
& (GL_Entry.posting_date >= filters.from_date)
|
||||
& (GL_Entry.posting_date <= filters.to_date)
|
||||
& (GL_Entry.is_opening == "No")
|
||||
)
|
||||
.groupby(GL_Entry.party)
|
||||
)
|
||||
|
||||
if account_filter:
|
||||
query = query.where(GL_Entry.account.isin(account_filter))
|
||||
|
||||
gle = query.run(as_dict=True)
|
||||
|
||||
balances_within_period = frappe._dict()
|
||||
for d in gle:
|
||||
balances_within_period.setdefault(d.party, [d.debit, d.credit])
|
||||
|
||||
@@ -330,7 +330,11 @@ def make_supplier_quotation_from_rfq(source_name, target_doc=None, for_supplier=
|
||||
},
|
||||
"Request for Quotation Item": {
|
||||
"doctype": "Supplier Quotation Item",
|
||||
"field_map": {"name": "request_for_quotation_item", "parent": "request_for_quotation"},
|
||||
"field_map": {
|
||||
"name": "request_for_quotation_item",
|
||||
"parent": "request_for_quotation",
|
||||
"project_name": "project",
|
||||
},
|
||||
},
|
||||
},
|
||||
target_doc,
|
||||
|
||||
@@ -53,6 +53,7 @@ frappe.query_reports["Purchase Order Analysis"] = {
|
||||
label: __("Status"),
|
||||
fieldtype: "MultiSelectList",
|
||||
width: "80",
|
||||
options: ["To Pay", "To Bill", "To Receive", "To Receive and Bill", "Completed"],
|
||||
get_data: function (txt) {
|
||||
let status = ["To Bill", "To Receive", "To Receive and Bill", "Completed"];
|
||||
let options = [];
|
||||
|
||||
@@ -50,6 +50,7 @@ frappe.query_reports["Supplier Quotation Comparison"] = {
|
||||
fieldname: "supplier",
|
||||
label: __("Supplier"),
|
||||
fieldtype: "MultiSelectList",
|
||||
options: "Supplier",
|
||||
get_data: function (txt) {
|
||||
return frappe.db.get_link_options("Supplier", txt);
|
||||
},
|
||||
@@ -58,6 +59,7 @@ frappe.query_reports["Supplier Quotation Comparison"] = {
|
||||
fieldtype: "MultiSelectList",
|
||||
label: __("Supplier Quotation"),
|
||||
fieldname: "supplier_quotation",
|
||||
options: "Supplier Quotation",
|
||||
default: "",
|
||||
get_data: function (txt) {
|
||||
return frappe.db.get_link_options("Supplier Quotation", txt, { docstatus: ["<", 2] });
|
||||
|
||||
@@ -158,7 +158,7 @@ class AccountsController(TransactionBase):
|
||||
self.validate_qty_is_not_zero()
|
||||
|
||||
if (
|
||||
self.doctype in ["Sales Invoice", "Purchase Invoice"]
|
||||
self.doctype in ["Sales Invoice", "Purchase Invoice", "POS Invoice"]
|
||||
and self.get("is_return")
|
||||
and self.get("update_stock")
|
||||
):
|
||||
@@ -1728,22 +1728,22 @@ class AccountsController(TransactionBase):
|
||||
continue
|
||||
|
||||
ref_amt = flt(reference_details.get(item.get(item_ref_dn)), self.precision(based_on, item))
|
||||
based_on_amt = flt(item.get(based_on))
|
||||
|
||||
if not ref_amt:
|
||||
frappe.msgprint(
|
||||
_("System will not check over billing since amount for Item {0} in {1} is zero").format(
|
||||
item.item_code, ref_dt
|
||||
),
|
||||
title=_("Warning"),
|
||||
indicator="orange",
|
||||
)
|
||||
if based_on_amt: # Skip warning for free items
|
||||
frappe.msgprint(
|
||||
_(
|
||||
"System will not check over billing since amount for Item {0} in {1} is zero"
|
||||
).format(item.item_code, ref_dt),
|
||||
title=_("Warning"),
|
||||
indicator="orange",
|
||||
)
|
||||
continue
|
||||
|
||||
already_billed = self.get_billed_amount_for_item(item, item_ref_dn, based_on)
|
||||
|
||||
total_billed_amt = flt(
|
||||
flt(already_billed) + flt(item.get(based_on)), self.precision(based_on, item)
|
||||
)
|
||||
total_billed_amt = flt(flt(already_billed) + based_on_amt, self.precision(based_on, item))
|
||||
|
||||
allowance, item_allowance, global_qty_allowance, global_amount_allowance = get_allowance_for(
|
||||
item.item_code, item_allowance, global_qty_allowance, global_amount_allowance, "amount"
|
||||
|
||||
@@ -285,7 +285,7 @@ def get_already_returned_items(doc):
|
||||
|
||||
field = (
|
||||
frappe.scrub(doc.doctype) + "_item"
|
||||
if doc.doctype in ["Purchase Invoice", "Purchase Receipt", "Sales Invoice"]
|
||||
if doc.doctype in ["Purchase Invoice", "Purchase Receipt", "Sales Invoice", "POS Invoice"]
|
||||
else "dn_detail"
|
||||
)
|
||||
data = frappe.db.sql(
|
||||
@@ -653,6 +653,7 @@ def get_return_against_item_fields(voucher_type):
|
||||
"Delivery Note": "dn_detail",
|
||||
"Sales Invoice": "sales_invoice_item",
|
||||
"Subcontracting Receipt": "subcontracting_receipt_item",
|
||||
"POS Invoice": "sales_invoice_item",
|
||||
}
|
||||
return return_against_item_fields[voucher_type]
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ frappe.query_reports["Opportunity Summary by Sales Stage"] = {
|
||||
fieldname: "status",
|
||||
label: __("Status"),
|
||||
fieldtype: "MultiSelectList",
|
||||
options: ["Open", "Converted", "Quotation", "Replied"],
|
||||
get_data: function () {
|
||||
return [
|
||||
{ value: "Open", description: "Status" },
|
||||
|
||||
@@ -57,6 +57,7 @@ frappe.query_reports["Job Card Summary"] = {
|
||||
label: __("Work Orders"),
|
||||
fieldname: "work_order",
|
||||
fieldtype: "MultiSelectList",
|
||||
options: "Work Order",
|
||||
get_data: function (txt) {
|
||||
return frappe.db.get_link_options("Work Order", txt);
|
||||
},
|
||||
@@ -65,6 +66,7 @@ frappe.query_reports["Job Card Summary"] = {
|
||||
label: __("Production Item"),
|
||||
fieldname: "production_item",
|
||||
fieldtype: "MultiSelectList",
|
||||
options: "Item",
|
||||
get_data: function (txt) {
|
||||
return frappe.db.get_link_options("Item", txt);
|
||||
},
|
||||
|
||||
@@ -42,7 +42,7 @@ frappe.query_reports["Production Planning Report"] = {
|
||||
fieldname: "docnames",
|
||||
label: __("Document Name"),
|
||||
fieldtype: "MultiSelectList",
|
||||
options: "Sales Order",
|
||||
options: "based_on",
|
||||
get_data: function (txt) {
|
||||
if (!frappe.query_report.filters) return;
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ frappe.query_reports["Work Order Summary"] = {
|
||||
label: __("Sales Orders"),
|
||||
fieldname: "sales_order",
|
||||
fieldtype: "MultiSelectList",
|
||||
options: "Sales Order",
|
||||
get_data: function (txt) {
|
||||
return frappe.db.get_link_options("Sales Order", txt);
|
||||
},
|
||||
@@ -51,6 +52,7 @@ frappe.query_reports["Work Order Summary"] = {
|
||||
label: __("Production Item"),
|
||||
fieldname: "production_item",
|
||||
fieldtype: "MultiSelectList",
|
||||
options: "Item",
|
||||
get_data: function (txt) {
|
||||
return frappe.db.get_link_options("Item", txt);
|
||||
},
|
||||
|
||||
@@ -370,3 +370,5 @@ erpnext.patches.v14_0.update_currency_exchange_settings_for_frankfurter
|
||||
erpnext.patches.v14_0.update_stock_uom_in_work_order_item
|
||||
erpnext.patches.v14_0.disable_add_row_in_gross_profit
|
||||
execute:frappe.db.set_single_value("Accounts Settings", "exchange_gain_loss_posting_date", "Payment")
|
||||
erpnext.patches.v14_0.update_posting_datetime
|
||||
|
||||
|
||||
10
erpnext/patches/v14_0/update_posting_datetime.py
Normal file
10
erpnext/patches/v14_0/update_posting_datetime.py
Normal file
@@ -0,0 +1,10 @@
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
frappe.db.sql(
|
||||
"""
|
||||
UPDATE `tabStock Ledger Entry`
|
||||
SET posting_datetime = timestamp(posting_date, posting_time)
|
||||
"""
|
||||
)
|
||||
@@ -381,7 +381,7 @@ erpnext.SerialNoBatchSelector = class SerialNoBatchSelector {
|
||||
query: "erpnext.controllers.queries.get_batch_no",
|
||||
};
|
||||
},
|
||||
change: function () {
|
||||
onchange: function () {
|
||||
const batch_no = this.get_value();
|
||||
if (!batch_no) {
|
||||
this.grid_row.on_grid_fields_dict.available_qty.set_value(0);
|
||||
|
||||
@@ -331,22 +331,19 @@ def sales_invoice_on_submit(doc, method):
|
||||
]:
|
||||
return
|
||||
|
||||
if not len(doc.payment_schedule):
|
||||
frappe.throw(_("Please set the Payment Schedule"), title=_("E-Invoicing Information Missing"))
|
||||
else:
|
||||
for schedule in doc.payment_schedule:
|
||||
if not schedule.mode_of_payment:
|
||||
frappe.throw(
|
||||
_("Row {0}: Please set the Mode of Payment in Payment Schedule").format(schedule.idx),
|
||||
title=_("E-Invoicing Information Missing"),
|
||||
)
|
||||
elif not frappe.db.get_value("Mode of Payment", schedule.mode_of_payment, "mode_of_payment_code"):
|
||||
frappe.throw(
|
||||
_("Row {0}: Please set the correct code on Mode of Payment {1}").format(
|
||||
schedule.idx, schedule.mode_of_payment
|
||||
),
|
||||
title=_("E-Invoicing Information Missing"),
|
||||
)
|
||||
for schedule in doc.payment_schedule:
|
||||
if not schedule.mode_of_payment:
|
||||
frappe.throw(
|
||||
_("Row {0}: Please set the Mode of Payment in Payment Schedule").format(schedule.idx),
|
||||
title=_("E-Invoicing Information Missing"),
|
||||
)
|
||||
elif not frappe.db.get_value("Mode of Payment", schedule.mode_of_payment, "mode_of_payment_code"):
|
||||
frappe.throw(
|
||||
_("Row {0}: Please set the correct code on Mode of Payment {1}").format(
|
||||
schedule.idx, schedule.mode_of_payment
|
||||
),
|
||||
title=_("E-Invoicing Information Missing"),
|
||||
)
|
||||
|
||||
prepare_and_attach_invoice(doc)
|
||||
|
||||
|
||||
@@ -733,6 +733,7 @@ erpnext.PointOfSale.ItemCart = class {
|
||||
frappe.utils.play_sound("error");
|
||||
return;
|
||||
}
|
||||
this.highlight_numpad_btn($btn, current_action);
|
||||
|
||||
if (first_click_event || field_to_edit_changed) {
|
||||
this.prev_action = current_action;
|
||||
@@ -778,7 +779,6 @@ erpnext.PointOfSale.ItemCart = class {
|
||||
this.numpad_value = current_action;
|
||||
}
|
||||
|
||||
this.highlight_numpad_btn($btn, current_action);
|
||||
this.events.numpad_event(this.numpad_value, this.prev_action);
|
||||
}
|
||||
|
||||
|
||||
@@ -88,6 +88,7 @@ function get_filters() {
|
||||
fieldname: "status",
|
||||
label: __("Status"),
|
||||
fieldtype: "MultiSelectList",
|
||||
options: ["Overdue", "Unpaid", "Completed", "Partly Paid"],
|
||||
width: 100,
|
||||
get_data: function (txt) {
|
||||
let status = ["Overdue", "Unpaid", "Completed", "Partly Paid"];
|
||||
|
||||
@@ -48,6 +48,7 @@ frappe.query_reports["Sales Order Analysis"] = {
|
||||
fieldname: "status",
|
||||
label: __("Status"),
|
||||
fieldtype: "MultiSelectList",
|
||||
options: ["To Pay", "To Bill", "To Deliver", "To Deliver and Bill", "Completed"],
|
||||
width: "80",
|
||||
get_data: function (txt) {
|
||||
let status = ["To Bill", "To Deliver", "To Deliver and Bill", "Completed"];
|
||||
|
||||
@@ -182,8 +182,6 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "user_id.user_image",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "image",
|
||||
"fieldtype": "Attach Image",
|
||||
"hidden": 1,
|
||||
@@ -824,7 +822,7 @@
|
||||
"image_field": "image",
|
||||
"is_tree": 1,
|
||||
"links": [],
|
||||
"modified": "2024-01-03 17:36:20.984421",
|
||||
"modified": "2025-02-07 13:54:40.122345",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Setup",
|
||||
"name": "Employee",
|
||||
@@ -873,4 +871,4 @@
|
||||
"states": [],
|
||||
"title_field": "employee_name",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
@@ -65,14 +65,12 @@ class Employee(NestedSet):
|
||||
|
||||
def validate_user_details(self):
|
||||
if self.user_id:
|
||||
data = frappe.db.get_value("User", self.user_id, ["enabled", "user_image"], as_dict=1)
|
||||
data = frappe.db.get_value("User", self.user_id, ["enabled"], as_dict=1)
|
||||
|
||||
if not data:
|
||||
self.user_id = None
|
||||
return
|
||||
|
||||
if data.get("user_image") and self.image == "":
|
||||
self.image = data.get("user_image")
|
||||
self.validate_for_enabled_user_id(data.get("enabled", 0))
|
||||
self.validate_duplicate_user_id()
|
||||
|
||||
|
||||
@@ -430,7 +430,7 @@ def make_request_for_quotation(source_name, target_doc=None):
|
||||
"field_map": [
|
||||
["name", "material_request_item"],
|
||||
["parent", "material_request"],
|
||||
["uom", "uom"],
|
||||
["project", "project_name"],
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1165,26 +1165,25 @@ def get_item_account_wise_additional_cost(purchase_document):
|
||||
for item in landed_cost_voucher_doc.items:
|
||||
if item.receipt_document == purchase_document:
|
||||
for account in landed_cost_voucher_doc.taxes:
|
||||
exchange_rate = account.exchange_rate or 1
|
||||
item_account_wise_cost.setdefault((item.item_code, item.purchase_receipt_item), {})
|
||||
item_account_wise_cost[(item.item_code, item.purchase_receipt_item)].setdefault(
|
||||
account.expense_account, {"amount": 0.0, "base_amount": 0.0}
|
||||
)
|
||||
|
||||
if total_item_cost > 0:
|
||||
item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][
|
||||
account.expense_account
|
||||
]["amount"] += account.amount * item.get(based_on_field) / total_item_cost
|
||||
item_row = item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][
|
||||
account.expense_account
|
||||
]
|
||||
|
||||
item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][
|
||||
account.expense_account
|
||||
]["base_amount"] += account.base_amount * item.get(based_on_field) / total_item_cost
|
||||
if total_item_cost > 0:
|
||||
item_row["amount"] += account.amount * item.get(based_on_field) / total_item_cost
|
||||
|
||||
item_row["base_amount"] += (
|
||||
account.base_amount * item.get(based_on_field) / total_item_cost
|
||||
)
|
||||
else:
|
||||
item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][
|
||||
account.expense_account
|
||||
]["amount"] += item.applicable_charges
|
||||
item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][
|
||||
account.expense_account
|
||||
]["base_amount"] += item.applicable_charges
|
||||
item_row["amount"] += item.applicable_charges / exchange_rate
|
||||
item_row["base_amount"] += item.applicable_charges
|
||||
|
||||
return item_account_wise_cost
|
||||
|
||||
|
||||
@@ -262,6 +262,11 @@ frappe.ui.form.on("Stock Reconciliation Item", {
|
||||
|
||||
qty: function (frm, cdt, cdn) {
|
||||
frm.events.set_amount_quantity(frm, cdt, cdn);
|
||||
|
||||
let row = locals[cdt][cdn];
|
||||
if (!row.qty) {
|
||||
frappe.model.set_value(cdt, cdn, "serial_no", "");
|
||||
}
|
||||
},
|
||||
|
||||
valuation_rate: function (frm, cdt, cdn) {
|
||||
|
||||
@@ -250,7 +250,7 @@ class StockReconciliation(StockController):
|
||||
validate_is_stock_item(item_code, item.is_stock_item)
|
||||
|
||||
# item should not be serialized
|
||||
if item.has_serial_no and not row.serial_no and not item.serial_no_series:
|
||||
if item.has_serial_no and row.qty and not row.serial_no and not item.serial_no_series:
|
||||
raise frappe.ValidationError(
|
||||
_("Serial no(s) required for serialized item {0}").format(item_code)
|
||||
)
|
||||
|
||||
@@ -17,6 +17,7 @@ frappe.query_reports["Item Shortage Report"] = {
|
||||
fieldname: "warehouse",
|
||||
label: __("Warehouse"),
|
||||
fieldtype: "MultiSelectList",
|
||||
options: "Warehouse",
|
||||
width: "100",
|
||||
get_data: function (txt) {
|
||||
return frappe.db.get_link_options("Warehouse", txt);
|
||||
|
||||
@@ -654,6 +654,10 @@ class update_entries_after:
|
||||
|
||||
if not sle.is_adjustment_entry:
|
||||
sle.stock_value_difference = stock_value_difference
|
||||
elif sle.is_adjustment_entry and not self.args.get("sle_id"):
|
||||
sle.stock_value_difference = get_stock_value_difference(
|
||||
sle.item_code, sle.warehouse, sle.posting_date, sle.posting_time, sle.voucher_no
|
||||
)
|
||||
|
||||
sle.doctype = "Stock Ledger Entry"
|
||||
frappe.get_doc(sle).db_update()
|
||||
|
||||
@@ -631,4 +631,4 @@ def get_combine_datetime(posting_date, posting_time):
|
||||
if isinstance(posting_time, datetime.timedelta):
|
||||
posting_time = (datetime.datetime.min + posting_time).time()
|
||||
|
||||
return datetime.datetime.combine(posting_date, posting_time).replace(microsecond=0)
|
||||
return datetime.datetime.combine(posting_date, posting_time)
|
||||
|
||||
@@ -72,8 +72,7 @@
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-right col-2">
|
||||
{%- set party_name = doc.supplier_name if doc.doctype in ['Supplier Quotation', 'Purchase Invoice', 'Purchase
|
||||
Order'] else doc.customer_name %}
|
||||
{%- set party_name = doc.supplier_name if doc.doctype in ['Supplier Quotation', 'Purchase Invoice', 'Purchase Order'] else doc.customer_name %}
|
||||
<b>{{ party_name }}</b>
|
||||
|
||||
{% if doc.contact_display and doc.contact_display != party_name %}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
|
||||
import frappe
|
||||
from frappe.utils import escape_html
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
@@ -11,6 +12,8 @@ def send_message(sender, message, subject="Website Query"):
|
||||
|
||||
website_send_message(sender, message, subject)
|
||||
|
||||
message = escape_html(message)
|
||||
|
||||
lead = customer = None
|
||||
customer = frappe.db.sql(
|
||||
"""select distinct dl.link_name from `tabDynamic Link` dl
|
||||
|
||||
Reference in New Issue
Block a user