Compare commits

..

55 Commits

Author SHA1 Message Date
Frappe PR Bot
19604e91c6 chore(release): Bumped to Version 14.83.1
## [14.83.1](https://github.com/frappe/erpnext/compare/v14.83.0...v14.83.1) (2025-02-26)

### Bug Fixes

* incorrect stock value difference for adjustment entry ([c734373](c734373c9f))
2025-02-26 12:43:06 +00:00
ruthra kumar
4f40939c15 Merge pull request #46133 from frappe/version-14-hotfix
chore: release v14
2025-02-26 18:11:42 +05:30
rohitwaghchaure
6748780591 Merge pull request #46027 from frappe/mergify/bp/version-14-hotfix/pr-46021
fix: incorrect stock value difference for adjustment entry (backport #46021)
2025-02-20 13:06:07 +05:30
Frappe PR Bot
11da4372e2 chore(release): Bumped to Version 14.83.0
# [14.83.0](https://github.com/frappe/erpnext/compare/v14.82.2...v14.83.0) (2025-02-19)

### Bug Fixes

* '0' rate LDC's Invoice net totals should be ignored (backport [#45639](https://github.com/frappe/erpnext/issues/45639)) ([#45783](https://github.com/frappe/erpnext/issues/45783)) ([534b25c](534b25c448))
* add is_new in if condition ([ef37388](ef37388993))
* fetch child account data for selected parent ([#45904](https://github.com/frappe/erpnext/issues/45904)) ([e7d9786](e7d97865e5))
* include missing payment_gateway parameter in Payment Request URL ([a344b8b](a344b8b9ae))
* millisecond issue for posting datetime ([fc6f568](fc6f568a6c))
* on selection of batch qty is not fetching ([44a16bb](44a16bb544))
* pos return validation on v14 ([#45859](https://github.com/frappe/erpnext/issues/45859)) ([84432fc](84432fc035))
* **report:** add options to multiselectlist fields ([aa0ada9](aa0ada9670))
* **send_message:** escape HTML in the text ([703fd81](703fd816d1))
* serial no is mandatory for zero qty validation ([6f6133f](6f6133f2e2))
* set default value to 0 as per new logic ([4b16272](4b16272a01))
* tests ([e515b91](e515b91988))

### Features

* added ability to use custom html format for process statement of accounts ([#45746](https://github.com/frappe/erpnext/issues/45746)) ([a0cd08e](a0cd08e9ea))
* added option to enforce free item qty in pricing rule ([2edf083](2edf083c35))
2025-02-19 11:48:59 +00:00
ruthra kumar
4c74ac8738 Merge pull request #45982 from frappe/version-14-hotfix
chore: release v14
2025-02-19 17:17:34 +05:30
Akhil Narang
3e21e343d3 Merge pull request #46024 from frappe/mergify/bp/version-14-hotfix/pr-46003
fix(send_message): escape HTML in the text (backport #46003)
2025-02-19 16:43:09 +05:30
Rohit Waghchaure
c734373c9f fix: incorrect stock value difference for adjustment entry
(cherry picked from commit df83e427a3)
2025-02-19 11:11:09 +00:00
Akhil Narang
703fd816d1 fix(send_message): escape HTML in the text
Signed-off-by: Akhil Narang <me@akhilnarang.dev>
(cherry picked from commit 448a5db20f)
2025-02-19 10:45:28 +00:00
rohitwaghchaure
101c71c508 Merge pull request #46004 from frappe/mergify/bp/version-14-hotfix/pr-45750
feat: added option to enforce free item qty in pricing rule (backport #45750)
2025-02-19 15:45:14 +05:30
Steve Wilson
a0cd08e9ea feat: added ability to use custom html format for process statement of accounts (#45746)
* feat: added ability to use custom print format for process statement of accounts documents.

* fix: handles missing hook issues

* chore: linter changes

---------

Co-authored-by: Boy4099 <mashtawayne4099@gmail.com>
Co-authored-by: ruthra kumar <ruthra@erpnext.com>
2025-02-19 15:19:36 +05:30
rohitwaghchaure
57a0717778 chore: fix conflicts 2025-02-19 15:12:08 +05:30
rohitwaghchaure
0cf9c94a37 chore: fix conflicts 2025-02-19 15:10:34 +05:30
ruthra kumar
e48a03f130 Merge pull request #46009 from frappe/mergify/bp/version-14-hotfix/pr-45904
fix: fetch child account data for selected parent (backport #45904)
2025-02-19 14:21:34 +05:30
Bhavansathru
e7d97865e5 fix: fetch child account data for selected parent (#45904)
* fix: fetch child account data for selected parent

* fix: change reference name

---------

Co-authored-by: venkat102 <venkatesharunachalam659@gmail.com>
(cherry picked from commit 73e82b7afa)
2025-02-19 08:29:00 +00:00
Mihir Kandoi
4b16272a01 fix: set default value to 0 as per new logic
(cherry picked from commit 844f1636c0)
2025-02-19 07:21:39 +00:00
Mihir Kandoi
1ff0edd492 refactor: rename field
(cherry picked from commit f3d598881c)
2025-02-19 07:21:39 +00:00
Mihir Kandoi
ef37388993 fix: add is_new in if condition
(cherry picked from commit 4dcac56486)
2025-02-19 07:21:39 +00:00
Mihir Kandoi
116798df96 test: added test
(cherry picked from commit ac3259b8f1)
2025-02-19 07:21:38 +00:00
Mihir Kandoi
e515b91988 fix: tests
(cherry picked from commit 366ae85d85)

# Conflicts:
#	erpnext/stock/doctype/pick_list/test_pick_list.py
2025-02-19 07:21:38 +00:00
Mihir Kandoi
2edf083c35 feat: added option to enforce free item qty in pricing rule
(cherry picked from commit 19c01b1457)

# Conflicts:
#	erpnext/accounts/doctype/pricing_rule/pricing_rule.py
2025-02-19 07:21:38 +00:00
rohitwaghchaure
86ddabeae6 Merge pull request #45985 from rohitwaghchaure/fixed-support-31935-1
fix: millisecond issue for posting datetime
2025-02-18 20:11:28 +05:30
Rohit Waghchaure
fc6f568a6c fix: millisecond issue for posting datetime 2025-02-18 18:54:17 +05:30
rohitwaghchaure
cd3a411401 Merge pull request #45931 from rohitwaghchaure/fixed-support-31345-1
fix: serial no is mandatory for zero qty validation
2025-02-15 22:01:40 +05:30
Rohit Waghchaure
6f6133f2e2 fix: serial no is mandatory for zero qty validation 2025-02-15 15:34:37 +05:30
rohitwaghchaure
5a8b81409e Merge pull request #45920 from rohitwaghchaure/fixed-support-31326
fix: on selection of batch qty is not fetching
2025-02-14 17:34:21 +05:30
Rohit Waghchaure
44a16bb544 fix: on selection of batch qty is not fetching 2025-02-14 17:26:50 +05:30
ruthra kumar
206d0f1856 Merge pull request #45918 from frappe/mergify/bp/version-14-hotfix/pr-45912
fix: include missing payment_gateway parameter in Payment Request URL (backport #45912)
2025-02-14 17:13:20 +05:30
Diptanil Saha
84432fc035 fix: pos return validation on v14 (#45859)
fix: pos return validation v-14
2025-02-14 17:11:03 +05:30
Navin-S-R
a344b8b9ae fix: include missing payment_gateway parameter in Payment Request URL
(cherry picked from commit dbac8cfc94)
2025-02-14 11:20:17 +00:00
ruthra kumar
c65f421da9 Merge pull request #45893 from frappe/mergify/bp/version-14-hotfix/pr-45804
fix(report): add options to multiselectlist fields (backport #45804)
2025-02-13 14:27:22 +05:30
ruthra kumar
5c6028340f chore: resolve conflicts 2025-02-13 14:25:41 +05:30
venkat102
aa0ada9670 fix(report): add options to multiselectlist fields
(cherry picked from commit 8785342fce)

# Conflicts:
#	erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.js
#	erpnext/accounts/report/gross_profit/gross_profit.js
#	erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.js
#	erpnext/stock/report/serial_and_batch_summary/serial_and_batch_summary.js
2025-02-13 08:46:55 +00:00
mergify[bot]
534b25c448 fix: '0' rate LDC's Invoice net totals should be ignored (backport #45639) (#45783)
* fix: '0' rate LDC's Invoice net totals should be ignored

(cherry picked from commit 325c4e3536)

# Conflicts:
#	erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py

* test: ldc @ 0 rate

(cherry picked from commit 0cdd346f8f)

# Conflicts:
#	erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py

* chore: resolve conflicts

* fix: incorrect parameters

* fix: ignore 0 rate ldc invoices

---------

Co-authored-by: ruthra kumar <ruthra@erpnext.com>
2025-02-13 13:19:34 +05:30
Frappe PR Bot
f109303f85 chore(release): Bumped to Version 14.82.2
## [14.82.2](https://github.com/frappe/erpnext/compare/v14.82.1...v14.82.2) (2025-02-12)

### Bug Fixes

* add total row in non_grouped_invoices ([49074aa](49074aa2fa))
* correct amt in account currency for lcv with manually distributed charges. (backport [#45532](https://github.com/frappe/erpnext/issues/45532)) ([#45863](https://github.com/frappe/erpnext/issues/45863)) ([c61e4e2](c61e4e2ddf))
* do not allow "Finance Book" in Accounting Dimensions (backport [#45696](https://github.com/frappe/erpnext/issues/45696)) ([#45855](https://github.com/frappe/erpnext/issues/45855)) ([08ba775](08ba77538b))
* do not validate party against  Receivable and Payable account for cancelled gl entries ([9f79da0](9f79da0015))
* map project from rfq to supplier quotation (backport [#45745](https://github.com/frappe/erpnext/issues/45745)) ([#45827](https://github.com/frappe/erpnext/issues/45827)) ([c4358c0](c4358c049a))
* Party name in Supplier Portal for Purchase Order (backport [#45772](https://github.com/frappe/erpnext/issues/45772)) ([#45857](https://github.com/frappe/erpnext/issues/45857)) ([46eba50](46eba50c8c))
* pos numpad editable action buttons (backport [#45823](https://github.com/frappe/erpnext/issues/45823)) ([#45825](https://github.com/frappe/erpnext/issues/45825)) ([6b9dad7](6b9dad7768))
* **regional:** removed payment schedule validation in sales invoice for italy (backport [#45852](https://github.com/frappe/erpnext/issues/45852)) ([#45853](https://github.com/frappe/erpnext/issues/45853)) ([de14bf1](de14bf1010))
* skip warning for free items ([2ed3bdc](2ed3bdcc2e))
* unable to remove image from employee ([91caca0](91caca05bb))
* update ctx to args ([573ce64](573ce645b2))

### Performance Improvements

* Ignore is_opening column in GL Queries ([#45327](https://github.com/frappe/erpnext/issues/45327)) ([9985a03](9985a03f39))
2025-02-12 12:08:02 +00:00
ruthra kumar
ab8fceb68d Merge pull request #45861 from frappe/version-14-hotfix
chore: release v14
2025-02-12 17:36:33 +05:30
rohitwaghchaure
e53a78c2bd Merge pull request #45883 from frappe/mergify/bp/version-14-hotfix/pr-45786
fix: skip warning for free items (backport #45786)
2025-02-12 15:16:15 +05:30
barredterra
2ed3bdcc2e fix: skip warning for free items
(cherry picked from commit 772776ad8a)
2025-02-12 09:20:04 +00:00
mergify[bot]
c61e4e2ddf fix: correct amt in account currency for lcv with manually distributed charges. (backport #45532) (#45863)
fix: correct amt in account currency for lcv with manually distributed charges.

(cherry picked from commit db38e7bf5a)

Co-authored-by: ljain112 <ljain112@gmail.com>
2025-02-11 16:05:35 +05:30
mergify[bot]
08ba77538b fix: do not allow "Finance Book" in Accounting Dimensions (backport #45696) (#45855)
fix: do not allow "Finance Book" in Accounting Dimensions

(cherry picked from commit a44be73a98)

Co-authored-by: ljain112 <ljain112@gmail.com>
2025-02-11 15:42:27 +05:30
mergify[bot]
de14bf1010 fix(regional): removed payment schedule validation in sales invoice for italy (backport #45852) (#45853)
fix(regional): removed payment schedule validation in sales invoice for italy (#45852)

(cherry picked from commit 494310293c)

Co-authored-by: Lakshit Jain <108322669+ljain112@users.noreply.github.com>
2025-02-11 15:41:39 +05:30
mergify[bot]
46eba50c8c fix: Party name in Supplier Portal for Purchase Order (backport #45772) (#45857)
fix: Party name in Supplier Portal for Purchase Order

(cherry picked from commit fc8663421b)

Co-authored-by: ljain112 <ljain112@gmail.com>
2025-02-11 15:41:04 +05:30
mergify[bot]
c4358c049a fix: map project from rfq to supplier quotation (backport #45745) (#45827)
* fix: map project from rfq to supplier quotation

(cherry picked from commit d0479036bb)

* fix: add project field map from mr to rfq

(cherry picked from commit 8fa39bec61)

---------

Co-authored-by: HenningWendtland <156231187+HenningWendtland@users.noreply.github.com>
2025-02-11 14:52:21 +05:30
ruthra kumar
a43f1badd5 Merge pull request #45846 from frappe/mergify/bp/version-14-hotfix/pr-45792
fix: do not validate party against Receivable and Payable account for cancelled gl entries (backport #45792)
2025-02-11 12:53:20 +05:30
ljain112
9f79da0015 fix: do not validate party against Receivable and Payable account for cancelled gl entries
(cherry picked from commit 0809e00455)
2025-02-11 06:44:37 +00:00
ruthra kumar
ca9df9db07 Merge pull request #45843 from frappe/mergify/bp/version-14-hotfix/pr-45781
fix: Added Total Row for `Gross Profit` Report in Non-Grouped Invoices (backport #45781)
2025-02-11 10:55:37 +05:30
Sanket322
49074aa2fa fix: add total row in non_grouped_invoices
(cherry picked from commit 2d32ddacc3)
2025-02-11 04:51:32 +00:00
mergify[bot]
6b9dad7768 fix: pos numpad editable action buttons (backport #45823) (#45825)
fix: pos numpad editable action buttons (#45823)

(cherry picked from commit 0b9c28620f)

Co-authored-by: Diptanil Saha <diptanil@frappe.io>
2025-02-10 16:33:21 +05:30
ruthra kumar
9e36cac0d1 Merge pull request #45812 from frappe/mergify/bp/version-14-hotfix/pr-45793
fix: unable to remove image from employee (backport #45793)
2025-02-10 11:01:25 +05:30
ruthra kumar
256318bb1c chore: resolve conflict 2025-02-10 10:37:50 +05:30
Asmita Hase
91caca05bb fix: unable to remove image from employee
fix: employee image disappears when newly created user_id is linked to employee

(cherry picked from commit 0207d2d7b6)

# Conflicts:
#	erpnext/setup/doctype/employee/employee.json
2025-02-10 05:02:53 +00:00
ruthra kumar
ceb5997256 Merge pull request #45796 from frappe/mergify/copy/version-14-hotfix/pr-45770
Fix: Update `ctx` to `args` for compatibility. (copy #45770)
2025-02-07 19:50:15 +05:30
Sanket322
573ce645b2 fix: update ctx to args
(cherry picked from commit d4bc3d182f)
2025-02-07 14:15:35 +00:00
ruthra kumar
09cefd9d63 Merge pull request #45785 from frappe/mergify/bp/version-14-hotfix/pr-45327
perf: Ignore is_opening column in GL Queries (backport #45327)
2025-02-07 17:15:58 +05:30
ruthra kumar
222bd9351d chore: resolve conflicts 2025-02-07 15:39:21 +05:30
Deepesh Garg
9985a03f39 perf: Ignore is_opening column in GL Queries (#45327)
* perf: Ignore is_opening column in GL Queries

* chore: Remove unwanted changes

* chore: Remove unwanted changes

* chore: Remove unwanted changes

* chore: Remove unwanted changes

* chore: Remove unwanted changes

* chore: Remove unwanted changes

(cherry picked from commit 993f40fa43)

# Conflicts:
#	erpnext/accounts/doctype/accounts_settings/accounts_settings.json
#	erpnext/accounts/doctype/accounts_settings/accounts_settings.py
2025-02-07 10:06:07 +00:00
53 changed files with 434 additions and 162 deletions

View File

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

View File

@@ -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)

View File

@@ -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",

View File

@@ -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(

View File

@@ -246,6 +246,7 @@ class PaymentRequest(Document):
"payer_name": data.customer_name,
"order_id": self.name,
"currency": self.currency,
"payment_gateway": self.payment_gateway,
}
)

View File

@@ -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",

View File

@@ -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")
)

View File

@@ -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,
}
)

View File

@@ -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:

View File

@@ -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},

View File

@@ -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"):

View File

@@ -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,
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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"))

View File

@@ -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"),

View File

@@ -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")

View File

@@ -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 = []

View File

@@ -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;

View File

@@ -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"
}

View File

@@ -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)

View File

@@ -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"),
});
},
},
{

View File

@@ -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])

View File

@@ -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,

View File

@@ -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 = [];

View File

@@ -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] });

View File

@@ -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"

View File

@@ -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]

View File

@@ -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" },

View File

@@ -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);
},

View File

@@ -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;

View File

@@ -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);
},

View File

@@ -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

View File

@@ -0,0 +1,10 @@
import frappe
def execute():
frappe.db.sql(
"""
UPDATE `tabStock Ledger Entry`
SET posting_datetime = timestamp(posting_date, posting_time)
"""
)

View File

@@ -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);

View File

@@ -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)

View File

@@ -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);
}

View File

@@ -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"];

View File

@@ -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"];

View File

@@ -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
}
}

View File

@@ -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()

View File

@@ -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"],
],
},
},

View File

@@ -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

View File

@@ -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) {

View File

@@ -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)
)

View File

@@ -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);

View File

@@ -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()

View File

@@ -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)

View File

@@ -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 %}

View File

@@ -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