Compare commits

..

124 Commits

Author SHA1 Message Date
Frappe PR Bot
35fab8c23f chore(release): Bumped to Version 14.82.0
# [14.82.0](https://github.com/frappe/erpnext/compare/v14.81.0...v14.82.0) (2025-02-05)

### Bug Fixes

* allow multiple email ids ([9510758](9510758ce4))
* bind this to function ([83bce78](83bce785ff))
* check billing address ([2f33f6b](2f33f6bdf5))
* closing stock balance report not generating ([3f6beeb](3f6beebeec))
* correct error message in payment entry ([9f5d7e4](9f5d7e41ec))
* default payment terms template selected while duplicating ([82cfafb](82cfafb610))
* Gross Profit Report with Correct Totals and Gross Margin (backport [#45548](https://github.com/frappe/erpnext/issues/45548)) ([#45597](https://github.com/frappe/erpnext/issues/45597)) ([0086656](0086656748))
* handling company in bank reconciliation tool ([#45582](https://github.com/frappe/erpnext/issues/45582)) ([7823f1b](7823f1b06f))
* payment schedule table is empty while duplicating record ([d0ff91b](d0ff91b0e0))
* **pos:** add item in the existing item row when discount is applied ([3c6ed0a](3c6ed0a565))
* posting_date to posting_datetime in stock related queries ([bf3d68e](bf3d68e76d))
* renamed Commments Tab to Notes tab in Lead doctype ([0eddd1e](0eddd1e2d7))
* track employee changes ([#45674](https://github.com/frappe/erpnext/issues/45674)) ([43a5c33](43a5c33dbf))

### Features

* set bank account of company to default company bank account from masters ([dac5307](dac53074f2))
2025-02-05 11:45:31 +00:00
ruthra kumar
21b7833d57 Merge pull request #45704 from frappe/version-14-hotfix
chore: release v14
2025-02-05 17:14:10 +05:30
ruthra kumar
b217a7ee3e Merge pull request #45736 from frappe/mergify/bp/version-14-hotfix/pr-45717
fix(Purchase Invoice): default payment terms template selected while duplicating (backport #45717)
2025-02-05 15:30:42 +05:30
ruthra kumar
49787b6d84 chore: resolve conflicts 2025-02-05 15:22:13 +05:30
Ejaaz Khan
95903c9f96 refactor: remove log
(cherry picked from commit bfc01441a0)
2025-02-05 09:47:58 +00:00
Ejaaz Khan
d0ff91b0e0 fix: payment schedule table is empty while duplicating record
(cherry picked from commit fb3f08a441)

# Conflicts:
#	erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
2025-02-05 09:47:58 +00:00
Ejaaz Khan
82cfafb610 fix: default payment terms template selected while duplicating
(cherry picked from commit 18127603fe)
2025-02-05 09:47:57 +00:00
ruthra kumar
d14acb4f58 Merge pull request #45729 from frappe/mergify/bp/version-14-hotfix/pr-45686
fix: allow multiple email ids (backport #45686)
2025-02-05 14:27:57 +05:30
ruthra kumar
202693d4c3 Merge pull request #45730 from frappe/mergify/bp/version-14-hotfix/pr-45447
fix(pos): add item in the existing item row when discount is applied (backport #45447)
2025-02-05 14:10:21 +05:30
venkat102
3c6ed0a565 fix(pos): add item in the existing item row when discount is applied
(cherry picked from commit bee2c04d0b)
2025-02-05 14:03:54 +05:30
Sudharsanan11
2f33f6bdf5 fix: check billing address
(cherry picked from commit 9950e4aa0c)
2025-02-05 08:19:59 +00:00
Sudharsanan11
9510758ce4 fix: allow multiple email ids
(cherry picked from commit 423decb93c)
2025-02-05 08:19:58 +00:00
ruthra kumar
f01765db6f Merge pull request #45722 from frappe/mergify/bp/version-14-hotfix/pr-45721
fix: closing stock balance report not generating (backport #45721)
2025-02-05 10:38:34 +05:30
Rohit Waghchaure
3f6beebeec fix: closing stock balance report not generating
(cherry picked from commit 47d1c3b5a3)
2025-02-05 03:16:09 +00:00
ruthra kumar
8a9d554c32 Merge pull request #45712 from frappe/mergify/bp/version-14-hotfix/pr-45582
fix: handling company in bank reconciliation tool (backport #45582)
2025-02-04 17:32:26 +05:30
Aayush Dalal
7823f1b06f fix: handling company in bank reconciliation tool (#45582)
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
(cherry picked from commit d1c927530e)
2025-02-04 11:45:02 +00:00
ruthra kumar
4d5becbd7c Merge pull request #45676 from frappe/mergify/bp/version-14-hotfix/pr-45674
fix: track employee changes (backport #45674)
2025-02-03 15:27:16 +05:30
Ankush Menat
43a5c33dbf fix: track employee changes (#45674)
closes https://github.com/frappe/erpnext/issues/45571

(cherry picked from commit 827afbfa2e)
2025-02-03 08:52:37 +00:00
ruthra kumar
50c26ba017 Merge pull request #45668 from frappe/mergify/bp/version-14-hotfix/pr-45619
feat: set bank account of company to default company bank account fro… (backport #45619)
2025-02-03 14:05:38 +05:30
ruthra kumar
7bfe0526a1 Merge pull request #45670 from frappe/mergify/bp/version-14-hotfix/pr-44790
refactor: configurable posting date for Exc Gain / Loss journal (backport #44790)
2025-02-03 14:04:56 +05:30
Shariq Ansari
235b38a3af Merge pull request #45672 from frappe/mergify/bp/version-14-hotfix/pr-45637
fix: renamed Comments Tab to Notes tab in Lead doctype (backport #45637)
2025-02-03 13:27:22 +05:30
Shariq Ansari
c6ed82a304 chore: resolved conflict 2025-02-03 13:25:10 +05:30
ruthra kumar
e9d934d378 chore: resolve conflicts 2025-02-03 13:24:27 +05:30
Shariq Ansari
0eddd1e2d7 fix: renamed Commments Tab to Notes tab in Lead doctype
(cherry picked from commit 018df3135a)

# Conflicts:
#	erpnext/crm/doctype/lead/lead.json
2025-02-03 07:53:25 +00:00
ruthra kumar
cc275318e3 refactor: support JE posting date in semi-auto reconciilation tool
(cherry picked from commit a71718883e)

# Conflicts:
#	erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.json
#	erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.py
2025-02-03 07:29:24 +00:00
ruthra kumar
620cdc2489 test: exc gain/loss posting date based on configuration
(cherry picked from commit 2f3281579a)

# Conflicts:
#	erpnext/controllers/tests/test_accounts_controller.py
2025-02-03 07:29:24 +00:00
ruthra kumar
ef6e264887 refactor: only apply configuration on normal payments
patch to update default value

(cherry picked from commit b2c3da135e)

# Conflicts:
#	erpnext/controllers/accounts_controller.py
#	erpnext/patches.txt
2025-02-03 07:29:24 +00:00
ruthra kumar
5a62bd6e85 refactor: allow reconciliation date for exchange gain / loss
(cherry picked from commit 95af63e305)

# Conflicts:
#	erpnext/accounts/doctype/accounts_settings/accounts_settings.json
2025-02-03 07:29:23 +00:00
ruthra kumar
035139d4c7 refactor: configurable posting date for Exc Gain / Loss journal
(cherry picked from commit 5257413a93)
2025-02-03 07:29:23 +00:00
ruthra kumar
b5637c43fa refactor: configurable posting date for Exc Gain / Loss journal
(cherry picked from commit 3fbd2ca0d9)

# Conflicts:
#	erpnext/accounts/doctype/accounts_settings/accounts_settings.json
#	erpnext/accounts/doctype/accounts_settings/accounts_settings.py
2025-02-03 07:29:23 +00:00
Mihir Kandoi
dac53074f2 feat: set bank account of company to default company bank account from masters
(cherry picked from commit ce7702cc19)
2025-02-03 06:31:55 +00:00
rohitwaghchaure
b6a6bced61 Merge pull request #45666 from frappe/mergify/bp/version-14-hotfix/pr-45621
fix: delivered button of purchase order (backport #45621)
2025-02-03 12:01:11 +05:30
rohitwaghchaure
7b3c35c167 chore: fix conflicts 2025-02-03 11:58:10 +05:30
Mihir Kandoi
83bce785ff fix: bind this to function
(cherry picked from commit 41649cf52d)

# Conflicts:
#	erpnext/buying/doctype/purchase_order/purchase_order.js
2025-02-03 06:22:42 +00:00
mergify[bot]
0086656748 fix: Gross Profit Report with Correct Totals and Gross Margin (backport #45548) (#45597)
* fix: Gross Profit Report with Correct Totals and Gross Margin (#45548)

Co-authored-by: Sanket322 <shahsanket322003.com>
(cherry picked from commit aaf720ab61)

# Conflicts:
#	erpnext/accounts/report/gross_profit/test_gross_profit.py
#	erpnext/patches.txt

* fix: conflicts

---------

Co-authored-by: Sanket Shah <113279972+Sanket322@users.noreply.github.com>
Co-authored-by: ljain112 <ljain112@gmail.com>
2025-01-31 16:10:25 +05:30
rohitwaghchaure
36b1c436ea Merge pull request #45618 from frappe/mergify/bp/version-14-hotfix/pr-45612
fix: posting_date to posting_datetime in stock related queries (backport #45612)
2025-01-31 14:43:40 +05:30
ruthra kumar
9f4b3e86b3 Merge pull request #45635 from frappe/mergify/bp/version-14-hotfix/pr-45615
fix: correct error message in payment entry (backport #45615)
2025-01-31 14:18:49 +05:30
ljain112
9f5d7e41ec fix: correct error message in payment entry
(cherry picked from commit 592704cfd0)
2025-01-31 07:27:37 +00:00
rohitwaghchaure
50aa4ed55a chore: fix conflicts 2025-01-31 12:01:45 +05:30
rohitwaghchaure
dee6e2b697 chore: fix conflicts 2025-01-31 10:38:08 +05:30
rohitwaghchaure
9217e919c3 chore: fix conflicts 2025-01-31 10:36:25 +05:30
Rohit Waghchaure
bf3d68e76d fix: posting_date to posting_datetime in stock related queries
(cherry picked from commit e61ab48145)

# Conflicts:
#	erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py
#	erpnext/stock/report/stock_ledger/stock_ledger.py
2025-01-30 15:50:38 +00:00
Frappe PR Bot
4eaaffe550 chore(release): Bumped to Version 14.81.0
# [14.81.0](https://github.com/frappe/erpnext/compare/v14.80.0...v14.81.0) (2025-01-29)

### Bug Fixes

* add condition to check if item is delivered by supplier in make_purchase_order_for_default_supplier() ([#45370](https://github.com/frappe/erpnext/issues/45370)) ([69464ab](69464ab7ff))
* add multiple item issue in stock entry (backport [#45544](https://github.com/frappe/erpnext/issues/45544)) ([#45579](https://github.com/frappe/erpnext/issues/45579)) ([1160df9](1160df9350))
* currency decimal on POS Past Order List (backport [#45524](https://github.com/frappe/erpnext/issues/45524)) ([#45526](https://github.com/frappe/erpnext/issues/45526)) ([4d2352a](4d2352af00))
* fix creating documents from sales invoice (backport [#45346](https://github.com/frappe/erpnext/issues/45346)) ([#45407](https://github.com/frappe/erpnext/issues/45407)) ([452b205](452b205021))
* get stock balance filtered by company for validating stock value in jv (backport [#45549](https://github.com/frappe/erpnext/issues/45549)) ([#45577](https://github.com/frappe/erpnext/issues/45577)) ([e682d2c](e682d2c9ae))
* heatmap for Customer and Supplier not rendering ([#44717](https://github.com/frappe/erpnext/issues/44717)) ([ecdff8f](ecdff8f320))
* Merge Conflicts ([35f801f](35f801feda))
* remove applied pricing rule ([c484563](c484563bea))
* resolved pos return setting to default mode of payment instead of user selection ([#45377](https://github.com/frappe/erpnext/issues/45377)) ([#45436](https://github.com/frappe/erpnext/issues/45436)) ([24dc1bf](24dc1bf1a3))
* secure bulk transaction (backport [#45386](https://github.com/frappe/erpnext/issues/45386)) ([#45425](https://github.com/frappe/erpnext/issues/45425)) ([c6bc928](c6bc928f50))
* set company related values ([03068ab](03068ab96c))
* set preferred email in Employee via backend controller (backport [#45320](https://github.com/frappe/erpnext/issues/45320)) ([#45378](https://github.com/frappe/erpnext/issues/45378)) ([1622fc8](1622fc8728))
* update fields on change of item code In `Update Items` of `Sales Order` ([#45125](https://github.com/frappe/erpnext/issues/45125)) ([8befe7f](8befe7f244))
* update voucher outstanding from payment ledger ([a015527](a0155279e0))
* use user defined discount amount or default ([914f4bf](914f4bffea))
* Wrong `bank_ac_no` filter + simplify convoluted logic ([b6b453c](b6b453ca5d))

### Features

* add company level validation for accounting dimension ([cca5fbd](cca5fbd81a))
2025-01-29 11:30:28 +00:00
ruthra kumar
2c693c638d Merge pull request #45567 from frappe/version-14-hotfix
chore: release v14
2025-01-29 16:58:57 +05:30
ruthra kumar
39e82dfbc1 Merge pull request #45593 from frappe/mergify/bp/version-14-hotfix/pr-45569
fix: update voucher outstanding from payment ledger (backport #45569)
2025-01-29 16:33:00 +05:30
ljain112
a0155279e0 fix: update voucher outstanding from payment ledger
(cherry picked from commit dd77070351)
2025-01-29 10:42:17 +00:00
mergify[bot]
e682d2c9ae fix: get stock balance filtered by company for validating stock value in jv (backport #45549) (#45577)
* fix: get stock balance filtered by company for validating stock value in jv (#45549)

* fix: get stock balance filtered by company for validating stock value in jv

* test: error is raised  on validate

(cherry picked from commit 9f20854bd9)

# Conflicts:
#	erpnext/accounts/doctype/journal_entry/test_journal_entry.py

* fix: conflict

---------

Co-authored-by: Lakshit Jain <108322669+ljain112@users.noreply.github.com>
Co-authored-by: ljain112 <ljain112@gmail.com>
2025-01-29 12:46:13 +05:30
mergify[bot]
1160df9350 fix: add multiple item issue in stock entry (backport #45544) (#45579)
fix: add multiple item issue in stock entry (#45544)

(cherry picked from commit 5a023dc8d4)

Co-authored-by: Ejaaz Khan <67804911+iamejaaz@users.noreply.github.com>
2025-01-29 12:45:48 +05:30
ruthra kumar
80ed2fb1fb Merge pull request #45538 from frappe/mergify/bp/version-14-hotfix/pr-45345
fix:  maintain existing discounts in get_pricing_rule_for_item (backport #45345)
2025-01-29 12:26:52 +05:30
ruthra kumar
9eda931b97 chore: resolve conflicts 2025-01-29 11:58:21 +05:30
ruthra kumar
30f001edea Merge pull request #45556 from frappe/mergify/bp/version-14-hotfix/pr-45125
fix: update fields on change of item code In `Update Items` of `Sales Order` (backport #45125)
2025-01-28 11:32:08 +05:30
Sanket Shah
8befe7f244 fix: update fields on change of item code In Update Items of Sales Order (#45125)
* fix: update fields on change of item code

* fix: minor update

* fix: set the new values always

* Revert "fix: set the new values always"

This reverts commit 44daa0a641.

---------

Co-authored-by: Sanket322 <shahsanket322003.com>
Co-authored-by: ruthra kumar <ruthra@erpnext.com>
(cherry picked from commit 9933d3c8ff)
2025-01-28 05:58:26 +00:00
mergify[bot]
de531a81b6 chore: bump actions/cache to v4 (backport #45541) (#45545)
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2025-01-27 15:57:05 +01:00
ruthra kumar
ecbeaaf533 Merge pull request #45533 from frappe/mergify/bp/version-14-hotfix/pr-45302
feat: add company level validation for accounting dimension (backport #45302)
2025-01-27 17:30:52 +05:30
ruthra kumar
befc16cc97 refactor(test): update test data 2025-01-27 16:12:18 +05:30
ruthra kumar
b8e4d80b4e chore: resolve conflicts 2025-01-27 16:12:18 +05:30
Sugesh393
9767dc61a6 chore: update variable names for improved readability
(cherry picked from commit 36bae55299)
2025-01-27 16:12:18 +05:30
Sugesh393
03068ab96c fix: set company related values
(cherry picked from commit 454067198e)
2025-01-27 16:12:12 +05:30
Sanket322
c484563bea fix: remove applied pricing rule
(cherry picked from commit 50223c6bec)

# Conflicts:
#	erpnext/public/js/controllers/transaction.js
2025-01-27 10:32:53 +00:00
Sanket322
914f4bffea fix: use user defined discount amount or default
(cherry picked from commit e2a32b7257)
2025-01-27 10:32:52 +00:00
Sugesh393
2fb1aaa5c3 test: add new unit test for company validation in accounting dimension
(cherry picked from commit c94091d68f)

# Conflicts:
#	erpnext/controllers/tests/test_accounts_controller.py
2025-01-27 09:24:59 +00:00
Sugesh393
cca5fbd81a feat: add company level validation for accounting dimension
(cherry picked from commit 60efd3e219)

# Conflicts:
#	erpnext/controllers/accounts_controller.py
2025-01-27 09:24:59 +00:00
mergify[bot]
4d2352af00 fix: currency decimal on POS Past Order List (backport #45524) (#45526)
fix: currency decimal on POS Past Order List (#45524)

* fix: currency decimal on POS

* fix: removed precision

(cherry picked from commit 2ac8c92e7f)

Co-authored-by: Diptanil Saha <diptanil@frappe.io>
2025-01-27 13:21:34 +05:30
Diptanil Saha
24dc1bf1a3 fix: resolved pos return setting to default mode of payment instead of user selection (#45377) (#45436)
* fix: resolved pos return setting to default mode of payment instead of user selection

* refactor: removed console log statement

* refactor: moved get_payment_data to sales_and_purchase_return.py
2025-01-24 22:51:22 +05:30
mergify[bot]
c6bc928f50 fix: secure bulk transaction (backport #45386) (#45425)
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
fix: secure bulk transaction (#45386)
2025-01-24 17:19:17 +01:00
ruthra kumar
9518063a81 Merge pull request #45401 from frappe/mergify/bp/version-14-hotfix/pr-45129
fix: Wrong `bank_ac_no` filter + simplify logic in automatch (backport #45129)
2025-01-24 10:43:32 +05:30
mergify[bot]
452b205021 fix: fix creating documents from sales invoice (backport #45346) (#45407)
* fix: fix creating documents from sales invoice (#45346)

Co-authored-by: Meike Nedwidek <nedwidek@kk-software.de>
(cherry picked from commit 1758e125e0)

# Conflicts:
#	erpnext/accounts/doctype/sales_invoice/sales_invoice.js

* fix: resolved conflict

---------

Co-authored-by: meike289 <63092915+meike289@users.noreply.github.com>
Co-authored-by: Nabin Hait <nabinhait@gmail.com>
2025-01-23 18:19:32 +05:30
marination
35f801feda fix: Merge Conflicts 2025-01-23 13:28:21 +01:00
Shanuka Hewage
69464ab7ff fix: add condition to check if item is delivered by supplier in make_purchase_order_for_default_supplier() (#45370) 2025-01-23 17:57:04 +05:30
marination
b6b453ca5d fix: Wrong bank_ac_no filter + simplify convoluted logic
(cherry picked from commit 8521796811)

# Conflicts:
#	erpnext/accounts/doctype/bank_transaction/auto_match_party.py
2025-01-23 11:13:05 +00:00
mergify[bot]
1622fc8728 fix: set preferred email in Employee via backend controller (backport #45320) (#45378)
fix: set preferred email in Employee via backend controller (#45320)

fix: set preferred email in Employee (backend)

Set "Preferred Email" for Employee via validate. Unset value when
prefered_contact_email is also unset.

(cherry picked from commit 4481ca83ff)

Co-authored-by: gavin <gavin18d@gmail.com>
2025-01-23 11:15:13 +05:30
HENRY Florian
ecdff8f320 fix: heatmap for Customer and Supplier not rendering (#44717) 2025-01-22 12:11:16 +05:30
Frappe PR Bot
a22d3b9895 chore(release): Bumped to Version 14.80.0
# [14.80.0](https://github.com/frappe/erpnext/compare/v14.79.0...v14.80.0) (2025-01-22)

### Bug Fixes

* do not reset picked items ([fe5de30](fe5de30256))
* include pos invoice in modifing key for returned item validation ([431fa22](431fa225e3))
* pos search by term items price (backport [#45006](https://github.com/frappe/erpnext/issues/45006)) ([#45102](https://github.com/frappe/erpnext/issues/45102)) ([524a8d7](524a8d77f7))
* **Project:** re-phrase welcome email ([#45175](https://github.com/frappe/erpnext/issues/45175)) ([77e92b3](77e92b38eb))
* round off tax withholding amount ([#45271](https://github.com/frappe/erpnext/issues/45271)) ([667e659](667e659e3f))
* update query ([49e3865](49e3865265))
* validate linked sales person ([f9420db](f9420db3ca))

### Features

* add difference_posting_date field ([1753509](17535095e2))
* use difference_posting_date for journal entry posting_date ([0fdd681](0fdd6817a6))

### Reverts

* avoid change to translatable string ([20bb151](20bb15167d))
2025-01-22 03:34:40 +00:00
ruthra kumar
ad960c1470 Merge pull request #45358 from frappe/version-14-hotfix
chore: release v14
2025-01-22 09:03:17 +05:30
ruthra kumar
0c7219159a Merge pull request #45359 from frappe/mergify/bp/version-14-hotfix/pr-45242
fix: include pos invoice in modifing key for returned item validation (backport #45242)
2025-01-21 16:58:13 +05:30
venkat102
431fa225e3 fix: include pos invoice in modifing key for returned item validation
(cherry picked from commit 2936139c79)
2025-01-21 11:06:02 +00:00
ruthra kumar
f27e35c8f4 Merge pull request #45351 from frappe/mergify/bp/version-14-hotfix/pr-44808
feat: Added difference_posting_date field in Sales Invoice Advance and Purchase Invoice Advance (backport #44808)
2025-01-21 14:48:27 +05:30
ruthra kumar
5fbffcbd7b Merge pull request #45311 from frappe/mergify/bp/version-14-hotfix/pr-45175
fix(Project): re-phrase welcome email (backport #45175)
2025-01-21 13:56:30 +05:30
ruthra kumar
bb949da334 Merge pull request #45313 from frappe/mergify/bp/version-14-hotfix/pr-45271
fix: round off tax withholding amount (backport #45271)
2025-01-21 13:55:19 +05:30
ruthra kumar
8764a321c7 chore: resolve conflicts 2025-01-21 12:57:10 +05:30
rs-rethik
49e3865265 fix: update query
(cherry picked from commit 854e37c05c)
2025-01-21 06:54:23 +00:00
rs-rethik
33a1da8194 refactor: convert sql query to query builder
(cherry picked from commit 2d58e845e6)
2025-01-21 06:54:23 +00:00
rs-rethik
52309fe0b6 test: add unit test to validate journal entry posting date
(cherry picked from commit c14a2d73bf)

# Conflicts:
#	erpnext/controllers/tests/test_accounts_controller.py
2025-01-21 06:54:23 +00:00
rs-rethik
0fdd6817a6 feat: use difference_posting_date for journal entry posting_date
(cherry picked from commit ff1d040a6e)
2025-01-21 06:54:22 +00:00
rs-rethik
17535095e2 feat: add difference_posting_date field
(cherry picked from commit 225e56cbca)

# Conflicts:
#	erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json
#	erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.py
#	erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.json
#	erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.py
2025-01-21 06:54:22 +00:00
ruthra kumar
4d74597f94 Merge pull request #45336 from frappe/mergify/bp/version-14-hotfix/pr-45167
fix: validate linked sales person (backport #45167)
2025-01-20 12:10:39 +05:30
Sudharsanan11
f9420db3ca fix: validate linked sales person
(cherry picked from commit e614f07795)
2025-01-20 06:20:20 +00:00
rohitwaghchaure
8996685f44 Merge pull request #45326 from frappe/mergify/bp/version-14-hotfix/pr-44783
fix: do not reset picked items (backport #44783)
2025-01-19 15:07:00 +05:30
rohitwaghchaure
7046a01921 chore: fix test case 2025-01-19 14:22:52 +05:30
rohitwaghchaure
0b8cf3a369 chore: fix conflicts 2025-01-19 13:52:49 +05:30
Rohit Waghchaure
fe5de30256 fix: do not reset picked items
(cherry picked from commit 34a80bfcd3)

# Conflicts:
#	erpnext/stock/doctype/pick_list/test_pick_list.py
2025-01-18 10:15:22 +00:00
barredterra
20bb15167d revert: avoid change to translatable string 2025-01-17 14:38:27 +01:00
barredterra
1ccf30d97b chore: resolve confilcts 2025-01-17 14:38:03 +01:00
mergify[bot]
524a8d77f7 fix: pos search by term items price (backport #45006) (#45102)
* fix: load price list rate for pos search term

(cherry picked from commit 4b6cae156e)

# Conflicts:
#	erpnext/selling/page/point_of_sale/point_of_sale.py

* fix: load search term price with customer default price list

(cherry picked from commit 2beb485d77)

* chore: resolve conflict

---------

Co-authored-by: diptanilsaha <diptanil.dev@gmail.com>
Co-authored-by: ruthra kumar <ruthra@erpnext.com>
2025-01-17 17:02:34 +05:30
Lakshit Jain
667e659e3f fix: round off tax withholding amount (#45271)
(cherry picked from commit ada272a29b)
2025-01-17 11:21:09 +00:00
Patrick Eißler
77e92b38eb fix(Project): re-phrase welcome email (#45175)
(cherry picked from commit 8d66142865)

# Conflicts:
#	erpnext/projects/doctype/project/project.py
2025-01-17 11:19:03 +00:00
Frappe PR Bot
ff3425ead1 chore(release): Bumped to Version 14.79.0
# [14.79.0](https://github.com/frappe/erpnext/compare/v14.78.9...v14.79.0) (2025-01-15)

### Bug Fixes

* deduct tds on excess amount if checked ([6a52f79](6a52f79cce))
* do not add ordered items from Quotation to new Sales Order ([d42173b](d42173beb5))
* **Timesheet:** ignore permissions when updating Task and Project (backport [#45168](https://github.com/frappe/erpnext/issues/45168)) ([#45171](https://github.com/frappe/erpnext/issues/45171)) ([49ffecc](49ffeccafa))
* typo ([#45233](https://github.com/frappe/erpnext/issues/45233)) ([6bc210d](6bc210d9f4))

### Features

* validate discount date in payment schedule (backport [#44646](https://github.com/frappe/erpnext/issues/44646)) ([#44726](https://github.com/frappe/erpnext/issues/44726)) ([f4b7fa8](f4b7fa8980))
2025-01-15 12:09:13 +00:00
rohitwaghchaure
d098fd3fc3 Merge pull request #45264 from frappe/version-14-hotfix
chore: release v14
2025-01-15 17:37:57 +05:30
ruthra kumar
a66d475b56 Merge pull request #45253 from frappe/mergify/bp/version-14-hotfix/pr-45001
fix: deduct tds on excess amount if checked (backport #45001)
2025-01-14 10:28:19 +05:30
ljain112
6a52f79cce fix: deduct tds on excess amount if checked
(cherry picked from commit a203e3ffaf)
2025-01-14 04:33:09 +00:00
mergify[bot]
49ffeccafa fix(Timesheet): ignore permissions when updating Task and Project (backport #45168) (#45171)
* fix(Timesheet): ignore permissions when updating Task and Project (#45168)

(cherry picked from commit 9e760e54a5)

# Conflicts:
#	erpnext/projects/doctype/timesheet/timesheet.py

* chore: resolve conflicts

---------

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2025-01-13 12:08:26 +05:30
Nabin Hait
6bc210d9f4 fix: typo (#45233) 2025-01-13 12:04:40 +05:30
mergify[bot]
f4b7fa8980 feat: validate discount date in payment schedule (backport #44646) (#44726)
Co-authored-by: barredterra <14891507+barredterra@users.noreply.github.com>
2025-01-09 18:51:27 +01:00
rohitwaghchaure
c331a4fa84 Merge pull request #45185 from frappe/mergify/bp/version-14-hotfix/pr-45180
fix: do not add ordered items from Quotation to new Sales Order (backport #45180)
2025-01-09 16:27:15 +05:30
Rohit Waghchaure
d42173beb5 fix: do not add ordered items from Quotation to new Sales Order
(cherry picked from commit 2e930eb97b)
2025-01-09 09:09:56 +00:00
Frappe PR Bot
611c1f1ec2 chore(release): Bumped to Version 14.78.9
## [14.78.9](https://github.com/frappe/erpnext/compare/v14.78.8...v14.78.9) (2025-01-08)

### Bug Fixes

* add monthly distributation and write query in qb ([7d1d0c8](7d1d0c8e0c))
* discount resetting on date change ([01d2794](01d2794968)), closes [#44989](https://github.com/frappe/erpnext/issues/44989)
* header_img field schema ([eb55051](eb5505187e))
* Missing company filter breaks `get_account_balance` in Bank Reco ([821cfe2](821cfe2c39))
* Override pre-commit behaviour due to conflicts with CI ([46894a5](46894a5b86))
* **Project:** make status in confirmation dialog translatable ([#45118](https://github.com/frappe/erpnext/issues/45118)) ([07653c5](07653c54f3))
* Returned Qty in Work Order Consumed Materials report ([affa67e](affa67e74d))
* update customer contact details on pos (backport [#45071](https://github.com/frappe/erpnext/issues/45071)) ([#45105](https://github.com/frappe/erpnext/issues/45105)) ([2d63fc9](2d63fc98d0))
2025-01-08 10:27:38 +00:00
ruthra kumar
a05fb916ff Merge pull request #45132 from frappe/version-14-hotfix
chore: release v14
2025-01-08 15:56:16 +05:30
ruthra kumar
b2d35fae10 Merge pull request #45156 from frappe/revert-45152-mergify/bp/version-14-hotfix/pr-45112
Revert "fix: Missing company filter breaks `get_account_balance` in Bank Reco (backport #45112)"
2025-01-08 09:01:37 +05:30
ruthra kumar
8d13ef050e Revert "fix: Missing company filter breaks get_account_balance in Bank Reco (backport #45112)" 2025-01-08 09:00:21 +05:30
ruthra kumar
8ac40f07e3 Merge pull request #45152 from frappe/mergify/bp/version-14-hotfix/pr-45112
fix: Missing company filter breaks `get_account_balance` in Bank Reco (backport #45112)
2025-01-08 08:59:22 +05:30
marination
46894a5b86 fix: Override pre-commit behaviour due to conflicts with CI
(cherry picked from commit d7bf73cffa)
2025-01-08 02:46:51 +00:00
marination
821cfe2c39 fix: Missing company filter breaks get_account_balance in Bank Reco
(cherry picked from commit 8de0fe78ea)
2025-01-08 02:46:51 +00:00
ruthra kumar
b8b76a5b58 Merge pull request #45122 from frappe/mergify/bp/version-14-hotfix/pr-45121
fix: discount resetting on date change (backport #45121)
2025-01-07 11:26:24 +05:30
ruthra kumar
01d2794968 fix: discount resetting on date change
revert #44989

(cherry picked from commit 886281f81a)
2025-01-07 05:51:46 +00:00
ruthra kumar
02cfb589a2 Merge pull request #45108 from frappe/mergify/bp/version-14-hotfix/pr-45107
fix: Returned Qty in Work Order Consumed Materials report (backport #45107)
2025-01-07 10:12:56 +05:30
ruthra kumar
caf5faceda Merge pull request #45119 from frappe/mergify/bp/version-14-hotfix/pr-45118
fix(Project): make status in confirmation dialog translatable (backport #45118)
2025-01-07 10:12:04 +05:30
Raffael Meyer
07653c54f3 fix(Project): make status in confirmation dialog translatable (#45118)
(cherry picked from commit 9eede907f8)
2025-01-06 22:13:47 +00:00
Rohit Waghchaure
affa67e74d fix: Returned Qty in Work Order Consumed Materials report
(cherry picked from commit 30d68a31e0)
2025-01-06 10:37:28 +00:00
mergify[bot]
2d63fc98d0 fix: update customer contact details on pos (backport #45071) (#45105)
fix: update customer contact details on pos (#45071)

* fix: update customer contact details on pos

* refactor: removed console log statement

(cherry picked from commit d79e561248)

Co-authored-by: Diptanil Saha <diptanil@frappe.io>
2025-01-06 14:58:36 +05:30
ruthra kumar
8bb4415f65 Merge pull request #45079 from frappe/mergify/bp/version-14-hotfix/pr-44983
fix: add monthly distributation and write query in qb (backport #44983)
2025-01-06 10:28:45 +05:30
ruthra kumar
90b2ec9aba Merge pull request #42438 from trustedcomputer/version-14-hotfix
fix: header_img field schema (backport payments #83)
2025-01-04 05:20:58 +05:30
ruthra kumar
24ae74ebb3 refactor: store result in variable before enumeration
helps to inspect result while debugging

(cherry picked from commit b60bd17d1d)
2025-01-03 23:48:31 +00:00
Sanket322
7d1d0c8e0c fix: add monthly distributation and write query in qb
(cherry picked from commit 27195c7c96)
2025-01-03 23:48:30 +00:00
trustedcomputer
eb5505187e fix: header_img field schema 2024-07-22 12:35:46 -07:00
74 changed files with 916 additions and 470 deletions

View File

@@ -59,7 +59,7 @@ jobs:
run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts
- name: Cache pip
uses: actions/cache@v2
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml') }}
@@ -68,7 +68,7 @@ jobs:
${{ runner.os }}-
- name: Cache node modules
uses: actions/cache@v2
uses: actions/cache@v4
env:
cache-name: cache-node-modules
with:
@@ -83,7 +83,7 @@ jobs:
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v2
- uses: actions/cache@v4
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}

View File

@@ -79,7 +79,7 @@ jobs:
run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts
- name: Cache pip
uses: actions/cache@v2
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml') }}
@@ -88,7 +88,7 @@ jobs:
${{ runner.os }}-
- name: Cache node modules
uses: actions/cache@v2
uses: actions/cache@v4
env:
cache-name: cache-node-modules
with:
@@ -103,7 +103,7 @@ jobs:
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v2
- uses: actions/cache@v4
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}

View File

@@ -66,7 +66,7 @@ jobs:
run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts
- name: Cache pip
uses: actions/cache@v2
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml') }}
@@ -75,7 +75,7 @@ jobs:
${{ runner.os }}-
- name: Cache node modules
uses: actions/cache@v2
uses: actions/cache@v4
env:
cache-name: cache-node-modules
with:
@@ -90,7 +90,7 @@ jobs:
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v2
- uses: actions/cache@v4
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}

View File

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

View File

@@ -31,7 +31,8 @@
"label": "Reference Document Type",
"options": "DocType",
"read_only_depends_on": "eval:!doc.__islocal",
"reqd": 1
"reqd": 1,
"search_index": 1
},
{
"default": "0",

View File

@@ -44,6 +44,7 @@
"section_break_jpd0",
"auto_reconcile_payments",
"stale_days",
"exchange_gain_loss_posting_date",
"invoicing_settings_tab",
"accounts_transactions_settings_section",
"over_billing_allowance",
@@ -383,7 +384,7 @@
{
"fieldname": "section_break_jpd0",
"fieldtype": "Section Break",
"label": "Payment Reconciliations"
"label": "Payment Reconciliation Settings"
},
{
"default": "0",
@@ -462,6 +463,14 @@
"fieldname": "remarks_section",
"fieldtype": "Section Break",
"label": "Remarks Column Length"
},
{
"default": "Payment",
"description": "Only applies for Normal Payments",
"fieldname": "exchange_gain_loss_posting_date",
"fieldtype": "Select",
"label": "Posting Date Inheritance for Exchange Gain / Loss",
"options": "Invoice\nPayment\nReconciliation Date"
}
],
"icon": "icon-cog",
@@ -469,7 +478,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2024-01-22 12:10:10.151819",
"modified": "2025-01-23 13:15:44.077853",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",
@@ -498,4 +507,4 @@
"sort_order": "ASC",
"states": [],
"track_changes": 1
}
}

View File

@@ -19,10 +19,15 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
},
onload: function (frm) {
if (!frm.doc.company) {
frm.set_value("company", frappe.defaults.get_default("company"));
}
// Set default filter dates
let today = frappe.datetime.get_today();
frm.doc.bank_statement_from_date = frappe.datetime.add_months(today, -1);
frm.doc.bank_statement_to_date = today;
frm.trigger("bank_account");
},
@@ -94,7 +99,7 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
make_reconciliation_tool(frm) {
frm.get_field("reconciliation_tool_cards").$wrapper.empty();
if (frm.doc.bank_account && frm.doc.bank_statement_to_date) {
if (frm.doc.company && frm.doc.bank_account && frm.doc.bank_statement_to_date) {
frm.trigger("get_cleared_balance").then(() => {
if (
frm.doc.bank_account &&
@@ -110,7 +115,7 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
},
get_account_opening_balance(frm) {
if (frm.doc.bank_account && frm.doc.bank_statement_from_date) {
if (frm.doc.company && frm.doc.bank_account && frm.doc.bank_statement_from_date) {
frappe.call({
method: "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_account_balance",
args: {
@@ -125,7 +130,7 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
},
get_cleared_balance(frm) {
if (frm.doc.bank_account && frm.doc.bank_statement_to_date) {
if (frm.doc.company && frm.doc.bank_account && frm.doc.bank_statement_to_date) {
return frappe.call({
method: "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_account_balance",
args: {

View File

@@ -45,42 +45,41 @@ class AutoMatchbyAccountIBAN:
if not (self.bank_party_account_number or self.bank_party_iban):
return None
result = self.match_account_in_party()
return result
return self.match_account_in_party()
def match_account_in_party(self) -> tuple | None:
"""Check if there is a IBAN/Account No. match in Customer/Supplier/Employee"""
result = None
parties = get_parties_in_order(self.deposit)
or_filters = self.get_or_filters()
"""
Returns (Party Type, Party) if a matching account is found in Bank Account or Employee:
1. Get party from a matching (iban/account no) Bank Account
2. If not found, get party from Employee with matching bank account details (iban/account no)
"""
if not (self.bank_party_account_number or self.bank_party_iban):
# Nothing to match
return None
for party in parties:
party_result = frappe.db.get_all(
"Bank Account", or_filters=or_filters, pluck="party", limit_page_length=1
)
# Search for a matching Bank Account that has party set
party_result = frappe.db.get_all(
"Bank Account",
or_filters=self.get_or_filters(),
filters={"party_type": ("is", "set"), "party": ("is", "set")},
fields=["party", "party_type"],
limit_page_length=1,
)
if result := party_result[0] if party_result else None:
return (result["party_type"], result["party"])
if party == "Employee" and not party_result:
# Search in Bank Accounts first for Employee, and then Employee record
if "bank_account_no" in or_filters:
or_filters["bank_ac_no"] = or_filters.pop("bank_account_no")
# If no party is found, search in Employee (since it has bank account details)
if employee_result := frappe.db.get_all(
"Employee", or_filters=self.get_or_filters("Employee"), pluck="name", limit_page_length=1
):
return ("Employee", employee_result[0])
party_result = frappe.db.get_all(
party, or_filters=or_filters, pluck="name", limit_page_length=1
)
if party_result:
result = (
party,
party_result[0],
)
break
return result
def get_or_filters(self) -> dict:
def get_or_filters(self, party: str | None = None) -> dict:
"""Return OR filters for Bank Account and IBAN"""
or_filters = {}
if self.bank_party_account_number:
or_filters["bank_account_no"] = self.bank_party_account_number
bank_ac_field = "bank_ac_no" if party == "Employee" else "bank_account_no"
or_filters[bank_ac_field] = self.bank_party_account_number
if self.bank_party_iban:
or_filters["iban"] = self.bank_party_iban
@@ -100,8 +99,7 @@ class AutoMatchbyPartyNameDescription:
if not (self.bank_party_name or self.description):
return None
result = self.match_party_name_desc_in_party()
return result
return self.match_party_name_desc_in_party()
def match_party_name_desc_in_party(self) -> tuple | None:
"""Fuzzy search party name and/or description against parties in the system"""
@@ -110,7 +108,7 @@ class AutoMatchbyPartyNameDescription:
for party in parties:
filters = {"status": "Active"} if party == "Employee" else {"disabled": 0}
field = party.lower() + "_name"
field = f"{party.lower()}_name"
names = frappe.get_all(party, filters=filters, fields=[f"{field} as party_name", "name"])
for field in ["bank_party_name", "description"]:
@@ -137,13 +135,7 @@ class AutoMatchbyPartyNameDescription:
)
party_name, skip = self.process_fuzzy_result(result)
if not party_name:
return None, skip
return (
party,
party_name,
), skip
return ((party, party_name), skip) if party_name else (None, skip)
def process_fuzzy_result(self, result: list | None):
"""
@@ -161,8 +153,8 @@ class AutoMatchbyPartyNameDescription:
if len(result) == 1:
return (first_result[PARTY_ID] if first_result[SCORE] > CUTOFF else None), True
second_result = result[1]
if first_result[SCORE] > CUTOFF:
second_result = result[1]
# If multiple matches with the same score, return None but discontinue matching
# Matches were found but were too close to distinguish between
if first_result[SCORE] == second_result[SCORE]:
@@ -174,8 +166,8 @@ class AutoMatchbyPartyNameDescription:
def get_parties_in_order(deposit: float) -> list:
parties = ["Supplier", "Employee", "Customer"] # most -> least likely to receive
if flt(deposit) > 0:
parties = ["Customer", "Supplier", "Employee"] # most -> least likely to pay
return parties
return (
["Customer", "Supplier", "Employee"] # most -> least likely to pay us
if flt(deposit) > 0
else ["Supplier", "Employee", "Customer"] # most -> least likely to receive from us
)

View File

@@ -460,13 +460,20 @@ def get_actual_expense(args):
def get_accumulated_monthly_budget(monthly_distribution, posting_date, fiscal_year, annual_budget):
distribution = {}
if monthly_distribution:
for d in frappe.db.sql(
"""select mdp.month, mdp.percentage_allocation
from `tabMonthly Distribution Percentage` mdp, `tabMonthly Distribution` md
where mdp.parent=md.name and md.fiscal_year=%s""",
fiscal_year,
as_dict=1,
):
mdp = frappe.qb.DocType("Monthly Distribution Percentage")
md = frappe.qb.DocType("Monthly Distribution")
res = (
frappe.qb.from_(mdp)
.join(md)
.on(mdp.parent == md.name)
.select(mdp.month, mdp.percentage_allocation)
.where(md.fiscal_year == fiscal_year)
.where(md.name == monthly_distribution)
.run(as_dict=True)
)
for d in res:
distribution.setdefault(d.month, d.percentage_allocation)
dt = frappe.db.get_value("Fiscal Year", fiscal_year, "year_start_date")

View File

@@ -465,7 +465,7 @@ class PaymentEntry(AccountsController):
if d.reference_doctype not in valid_reference_doctypes:
frappe.throw(
_("Reference Doctype must be one of {0}").format(
comma_or(_(d) for d in valid_reference_doctypes)
comma_or([_(d) for d in valid_reference_doctypes])
)
)
@@ -2481,6 +2481,7 @@ def get_payment_entry(
pe.paid_amount = paid_amount
pe.received_amount = received_amount
pe.letter_head = doc.get("letter_head")
pe.bank_account = frappe.db.get_value("Bank Account", {"is_company_account": 1, "is_default": 1}, "name")
if dt in ["Purchase Order", "Sales Order", "Sales Invoice", "Purchase Invoice"]:
pe.project = doc.get("project") or reduce(

View File

@@ -270,6 +270,7 @@ class PaymentReconciliation(Document):
for payment in non_reconciled_payments:
row = self.append("payments", {})
row.update(payment)
row.is_advance = payment.book_advance_payments_in_separate_party_account
def get_invoice_entries(self):
# Fetch JVs, Sales and Purchase Invoices for 'invoices' to reconcile against
@@ -354,6 +355,9 @@ class PaymentReconciliation(Document):
def allocate_entries(self, args):
self.validate_entries()
exc_gain_loss_posting_date = frappe.db.get_single_value(
"Accounts Settings", "exchange_gain_loss_posting_date", cache=True
)
invoice_exchange_map = self.get_invoice_exchange_map(args.get("invoices"), args.get("payments"))
default_exchange_gain_loss_account = frappe.get_cached_value(
"Company", self.company, "exchange_gain_loss_account"
@@ -380,6 +384,11 @@ class PaymentReconciliation(Document):
res.difference_account = default_exchange_gain_loss_account
res.exchange_rate = inv.get("exchange_rate")
res.update({"gain_loss_posting_date": pay.get("posting_date")})
if not pay.get("is_advance"):
if exc_gain_loss_posting_date == "Invoice":
res.update({"gain_loss_posting_date": inv.get("invoice_date")})
elif exc_gain_loss_posting_date == "Reconciliation Date":
res.update({"gain_loss_posting_date": nowdate()})
if pay.get("amount") == 0:
entries.append(res)

View File

@@ -328,8 +328,6 @@ def get_pricing_rule_for_item(args, doc=None, for_validate=False):
"parent": args.parent,
"parenttype": args.parenttype,
"child_docname": args.get("child_docname"),
"discount_percentage": 0.0,
"discount_amount": 0,
}
)

View File

@@ -20,6 +20,7 @@
"is_advance",
"section_break_5",
"difference_amount",
"gain_loss_posting_date",
"column_break_7",
"difference_account",
"exchange_rate",
@@ -153,11 +154,16 @@
"fieldtype": "Check",
"in_list_view": 1,
"label": "Reconciled"
},
{
"fieldname": "gain_loss_posting_date",
"fieldtype": "Date",
"label": "Difference Posting Date"
}
],
"istable": 1,
"links": [],
"modified": "2023-03-20 21:05:43.121945",
"modified": "2025-01-23 16:09:01.058574",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Process Payment Reconciliation Log Allocations",

View File

@@ -262,9 +262,12 @@ def get_recipients_and_cc(customer, doc):
recipients = []
for clist in doc.customers:
if clist.customer == customer:
recipients.append(clist.billing_email)
if clist.billing_email:
for email in clist.billing_email.split(","):
recipients.append(email.strip())
if doc.primary_mandatory and clist.primary_email:
recipients.append(clist.primary_email)
for email in clist.primary_email.split(","):
recipients.append(email.strip())
cc = []
if doc.cc_to != "":
try:

View File

@@ -302,7 +302,11 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
if (this.frm.doc.__onload && this.frm.doc.__onload.load_after_mapping) return;
erpnext.utils.get_party_details(this.frm, "erpnext.accounts.party.get_party_details",
let payment_terms_template = this.frm.doc.payment_terms_template;
erpnext.utils.get_party_details(
this.frm,
"erpnext.accounts.party.get_party_details",
{
posting_date: this.frm.doc.posting_date,
bill_date: this.frm.doc.bill_date,
@@ -320,7 +324,14 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
me.frm.doc.tax_withholding_category = me.frm.supplier_tds;
me.frm.set_df_property("apply_tds", "read_only", me.frm.supplier_tds ? 0 : 1);
me.frm.set_df_property("tax_withholding_category", "hidden", me.frm.supplier_tds ? 0 : 1);
})
// while duplicating, don't change payment terms
if (me.frm.doc.__run_link_triggers === false) {
me.frm.set_value("payment_terms_template", payment_terms_template);
me.frm.refresh_field("payment_terms_template");
}
}
);
}
apply_tds(frm) {

View File

@@ -10,7 +10,6 @@ from frappe.utils import cint, cstr, flt, formatdate, get_link_to_form, getdate,
import erpnext
from erpnext.accounts.deferred_revenue import validate_service_stop_date
from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt
from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import (
validate_docs_for_deferred_accounting,
validate_docs_for_voucher_types,
@@ -33,7 +32,7 @@ from erpnext.accounts.general_ledger import (
merge_similar_entries,
)
from erpnext.accounts.party import get_due_date, get_party_account
from erpnext.accounts.utils import get_account_currency, get_fiscal_year
from erpnext.accounts.utils import get_account_currency, get_fiscal_year, update_voucher_outstanding
from erpnext.assets.doctype.asset.asset import is_cwip_accounting_enabled
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
from erpnext.buying.utils import check_on_hold_or_closed_status
@@ -661,12 +660,12 @@ class PurchaseInvoice(BuyingController):
def update_supplier_outstanding(self, update_outstanding):
if update_outstanding == "No":
update_outstanding_amt(
self.credit_to,
"Supplier",
self.supplier,
self.doctype,
self.return_against if cint(self.is_return) and self.return_against else self.name,
update_voucher_outstanding(
voucher_type=self.doctype,
voucher_no=self.return_against if cint(self.is_return) and self.return_against else self.name,
account=self.credit_to,
party_type="Supplier",
party=self.supplier,
)
def get_gl_entries(self, warehouse_account=None):

View File

@@ -45,12 +45,16 @@ frappe.listview_settings["Purchase Invoice"] = {
},
onload: function (listview) {
listview.page.add_action_item(__("Purchase Receipt"), () => {
erpnext.bulk_transaction_processing.create(listview, "Purchase Invoice", "Purchase Receipt");
});
if (frappe.model.can_create("Purchase Receipt")) {
listview.page.add_action_item(__("Purchase Receipt"), () => {
erpnext.bulk_transaction_processing.create(listview, "Purchase Invoice", "Purchase Receipt");
});
}
listview.page.add_action_item(__("Payment"), () => {
erpnext.bulk_transaction_processing.create(listview, "Purchase Invoice", "Payment Entry");
});
if (frappe.model.can_create("Payment Entry")) {
listview.page.add_action_item(__("Payment"), () => {
erpnext.bulk_transaction_processing.create(listview, "Purchase Invoice", "Payment Entry");
});
}
},
};

View File

@@ -14,7 +14,8 @@
"advance_amount",
"allocated_amount",
"exchange_gain_loss",
"ref_exchange_rate"
"ref_exchange_rate",
"difference_posting_date"
],
"fields": [
{
@@ -30,7 +31,7 @@
"width": "180px"
},
{
"columns": 3,
"columns": 2,
"fieldname": "reference_name",
"fieldtype": "Dynamic Link",
"in_list_view": 1,
@@ -40,7 +41,7 @@
"read_only": 1
},
{
"columns": 3,
"columns": 2,
"fieldname": "remarks",
"fieldtype": "Text",
"in_list_view": 1,
@@ -111,13 +112,20 @@
"label": "Reference Exchange Rate",
"non_negative": 1,
"read_only": 1
},
{
"columns": 2,
"fieldname": "difference_posting_date",
"fieldtype": "Date",
"in_list_view": 1,
"label": "Difference Posting Date"
}
],
"idx": 1,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-09-26 15:47:28.167371",
"modified": "2024-12-20 12:04:46.729972",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Advance",

View File

@@ -9,6 +9,10 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
setup(doc) {
this.setup_posting_date_time_check();
super.setup(doc);
this.frm.make_methods = {
Dunning: this.make_dunning.bind(this),
"Invoice Discounting": this.make_invoice_discounting.bind(this),
};
}
company() {
super.company();
@@ -94,26 +98,35 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
}
}
if (doc.outstanding_amount>0) {
cur_frm.add_custom_button(__('Payment Request'), function() {
me.make_payment_request();
}, __('Create'));
if (doc.outstanding_amount > 0) {
this.frm.add_custom_button(
__("Payment Request"),
function () {
me.make_payment_request();
},
__("Create")
);
this.frm.add_custom_button(
__("Invoice Discounting"),
this.make_invoice_discounting.bind(this),
__("Create")
);
cur_frm.add_custom_button(__('Invoice Discounting'), function() {
cur_frm.events.create_invoice_discounting(cur_frm);
}, __('Create'));
const payment_is_overdue = doc.payment_schedule
.map((row) => Date.parse(row.due_date) < Date.now())
.reduce((prev, current) => prev || current, false);
if (doc.due_date < frappe.datetime.get_today()) {
cur_frm.add_custom_button(__('Dunning'), function() {
cur_frm.events.create_dunning(cur_frm);
}, __('Create'));
if (payment_is_overdue) {
this.frm.add_custom_button(__("Dunning"), this.make_dunning.bind(this), __("Create"));
}
}
if (doc.docstatus === 1) {
cur_frm.add_custom_button(__('Maintenance Schedule'), function () {
cur_frm.cscript.make_maintenance_schedule();
}, __('Create'));
this.frm.add_custom_button(
__("Maintenance Schedule"),
this.make_maintenance_schedule.bind(this),
__("Create")
);
}
if(!doc.auto_repeat) {
@@ -146,6 +159,20 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
erpnext.accounts.unreconcile_payment.add_unreconcile_btn(me.frm);
}
make_invoice_discounting() {
frappe.model.open_mapped_doc({
method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.create_invoice_discounting",
frm: this.frm,
});
}
make_dunning() {
frappe.model.open_mapped_doc({
method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.create_dunning",
frm: this.frm,
});
}
make_maintenance_schedule() {
frappe.model.open_mapped_doc({
method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.make_maintenance_schedule",
@@ -948,20 +975,6 @@ frappe.ui.form.on('Sales Invoice', {
frm.set_df_property('return_against', 'label', __('Adjustment Against'));
}
},
create_invoice_discounting: function(frm) {
frappe.model.open_mapped_doc({
method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.create_invoice_discounting",
frm: frm
});
},
create_dunning: function(frm) {
frappe.model.open_mapped_doc({
method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.create_dunning",
frm: frm
});
}
});
frappe.ui.form.on("Sales Invoice Timesheet", {

View File

@@ -24,7 +24,11 @@ from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category
)
from erpnext.accounts.general_ledger import get_round_off_account_and_cost_center
from erpnext.accounts.party import get_due_date, get_party_account, get_party_details
from erpnext.accounts.utils import cancel_exchange_gain_loss_journal, get_account_currency
from erpnext.accounts.utils import (
cancel_exchange_gain_loss_journal,
get_account_currency,
update_voucher_outstanding,
)
from erpnext.assets.doctype.asset.depreciation import (
depreciate_asset,
get_disposal_account_and_cost_center,
@@ -1019,14 +1023,14 @@ class SalesInvoice(SellingController):
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
if update_outstanding == "No":
from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt
update_outstanding_amt(
self.debit_to,
"Customer",
self.customer,
self.doctype,
self.return_against if cint(self.is_return) and self.return_against else self.name,
update_voucher_outstanding(
voucher_type=self.doctype,
voucher_no=self.return_against
if cint(self.is_return) and self.return_against
else self.name,
account=self.debit_to,
party_type="Customer",
party=self.customer,
)
elif self.docstatus == 2 and cint(self.update_stock) and cint(auto_accounting_for_stock):

View File

@@ -32,12 +32,16 @@ frappe.listview_settings["Sales Invoice"] = {
right_column: "grand_total",
onload: function (listview) {
listview.page.add_action_item(__("Delivery Note"), () => {
erpnext.bulk_transaction_processing.create(listview, "Sales Invoice", "Delivery Note");
});
if (frappe.model.can_create("Delivery Note")) {
listview.page.add_action_item(__("Delivery Note"), () => {
erpnext.bulk_transaction_processing.create(listview, "Sales Invoice", "Delivery Note");
});
}
listview.page.add_action_item(__("Payment"), () => {
erpnext.bulk_transaction_processing.create(listview, "Sales Invoice", "Payment Entry");
});
if (frappe.model.can_create("Payment Entry")) {
listview.page.add_action_item(__("Payment"), () => {
erpnext.bulk_transaction_processing.create(listview, "Sales Invoice", "Payment Entry");
});
}
},
};

View File

@@ -3860,6 +3860,7 @@ class TestSalesInvoice(FrappeTestCase):
si = create_sales_invoice(do_not_submit=True)
project = frappe.new_doc("Project")
project.company = "_Test Company"
project.project_name = "Test Total Billed Amount"
project.save()

View File

@@ -14,7 +14,8 @@
"advance_amount",
"allocated_amount",
"exchange_gain_loss",
"ref_exchange_rate"
"ref_exchange_rate",
"difference_posting_date"
],
"fields": [
{
@@ -30,7 +31,7 @@
"width": "250px"
},
{
"columns": 3,
"columns": 2,
"fieldname": "reference_name",
"fieldtype": "Dynamic Link",
"in_list_view": 1,
@@ -41,7 +42,7 @@
"read_only": 1
},
{
"columns": 3,
"columns": 2,
"fieldname": "remarks",
"fieldtype": "Text",
"in_list_view": 1,
@@ -112,13 +113,20 @@
"label": "Reference Exchange Rate",
"non_negative": 1,
"read_only": 1
},
{
"columns": 2,
"fieldname": "difference_posting_date",
"fieldtype": "Date",
"in_list_view": 1,
"label": "Difference Posting Date"
}
],
"idx": 1,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-09-26 15:47:46.911595",
"modified": "2024-12-20 11:58:28.962370",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Advance",

View File

@@ -124,6 +124,9 @@ def get_party_tax_withholding_details(inv, tax_withholding_category=None):
cost_center = get_cost_center(inv)
tax_row.update({"cost_center": cost_center})
if cint(tax_details.round_off_tax_amount):
inv.round_off_applicable_accounts_for_tax_withholding = tax_details.account_head
if inv.doctype == "Purchase Invoice":
return tax_row, tax_deducted_on_advances, voucher_wise_amount
else:
@@ -523,9 +526,11 @@ def get_tds_amount(ldc, parties, inv, tax_details, vouchers):
else:
tax_withholding_net_total = inv.get("tax_withholding_net_total", 0)
if (threshold and tax_withholding_net_total >= threshold) or (
has_cumulative_threshold_breached = (
cumulative_threshold and (supp_credit_amt + supp_inv_credit_amt) >= cumulative_threshold
):
)
if (threshold and tax_withholding_net_total >= threshold) or (has_cumulative_threshold_breached):
# Get net total again as TDS is calculated on net total
# Grand is used to just check for threshold breach
net_total = (
@@ -533,9 +538,7 @@ def get_tds_amount(ldc, parties, inv, tax_details, vouchers):
)
supp_credit_amt += net_total
if (cumulative_threshold and supp_credit_amt >= cumulative_threshold) and cint(
tax_details.tax_on_excess_amount
):
if has_cumulative_threshold_breached and cint(tax_details.tax_on_excess_amount):
supp_credit_amt = net_total + tax_withholding_net_total - cumulative_threshold
if ldc and is_valid_certificate(ldc, inv.get("posting_date") or inv.get("transaction_date"), 0):

View File

@@ -1,5 +1,5 @@
{
"add_total_row": 1,
"add_total_row": 0,
"columns": [],
"creation": "2013-02-25 17:03:34",
"disable_prepared_report": 0,
@@ -9,7 +9,7 @@
"filters": [],
"idx": 3,
"is_standard": "Yes",
"modified": "2022-02-11 10:18:36.956558",
"modified": "2025-01-27 18:40:24.493829",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Gross Profit",

View File

@@ -166,7 +166,14 @@ def get_data_when_grouped_by_invoice(columns, gross_profit_data, filters, group_
# removing Item Code and Item Name columns
del columns[4:6]
total_base_amount = 0
total_buying_amount = 0
for src in gross_profit_data.si_list:
if src.indent == 1:
total_base_amount += src.base_amount or 0.0
total_buying_amount += src.buying_amount or 0.0
row = frappe._dict()
row.indent = src.indent
row.parent_invoice = src.parent_invoice
@@ -177,6 +184,27 @@ def get_data_when_grouped_by_invoice(columns, gross_profit_data, filters, group_
data.append(row)
total_gross_profit = total_base_amount - total_buying_amount
data.append(
frappe._dict(
{
"sales_invoice": "Total",
"qty": None,
"avg._selling_rate": None,
"valuation_rate": None,
"selling_amount": total_base_amount,
"buying_amount": total_buying_amount,
"gross_profit": total_gross_profit,
"gross_profit_%": flt(
(total_gross_profit / total_base_amount) * 100.0,
cint(frappe.db.get_default("currency_precision")) or 3,
)
if total_base_amount
else 0,
}
)
)
def get_data_when_not_grouped_by_invoice(gross_profit_data, filters, group_wise_columns, data):
for src in gross_profit_data.grouped_data:

View File

@@ -558,3 +558,33 @@ class TestGrossProfit(FrappeTestCase):
}
gp_entry = [x for x in data if x.parent_invoice == sinv.name]
self.assertDictContainsSubset(expected_entry, gp_entry[0])
def test_gross_profit_groupby_invoices(self):
create_sales_invoice(
qty=1,
rate=100,
company=self.company,
customer=self.customer,
item_code=self.item,
item_name=self.item,
cost_center=self.cost_center,
warehouse=self.warehouse,
debit_to=self.debit_to,
parent_cost_center=self.cost_center,
update_stock=0,
currency="INR",
income_account=self.income_account,
expense_account=self.expense_account,
)
filters = frappe._dict(
company=self.company, from_date=nowdate(), to_date=nowdate(), group_by="Invoice"
)
_, data = execute(filters=filters)
total = data[-1]
self.assertEqual(total.selling_amount, 100.0)
self.assertEqual(total.buying_amount, 0.0)
self.assertEqual(total.gross_profit, 100.0)
self.assertEqual(total.get("gross_profit_%"), 100.0)

View File

@@ -1587,7 +1587,7 @@ def get_stock_and_account_balance(account=None, posting_date=None, company=None)
if wh_details.account == account and not wh_details.is_group
]
total_stock_value = get_stock_value_on(related_warehouses, posting_date)
total_stock_value = get_stock_value_on(related_warehouses, posting_date, company=company)
precision = frappe.get_precision("Journal Entry Account", "debit_in_account_currency")
return flt(account_balance, precision), flt(total_stock_value, precision), related_warehouses

View File

@@ -205,9 +205,12 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e
}
}
if(is_drop_ship && doc.status!="Delivered") {
this.frm.add_custom_button(__('Delivered'),
this.delivered_by_supplier, __("Status"));
if (is_drop_ship && doc.status != "Delivered") {
this.frm.add_custom_button(
__("Delivered"),
this.delivered_by_supplier.bind(this),
__("Status")
);
this.frm.page.set_inner_btn_group_as_primary(__("Status"));
}
@@ -582,4 +585,4 @@ frappe.ui.form.on("Purchase Order", "is_subcontracted", function(frm) {
if (frm.doc.is_old_subcontracting_flow) {
erpnext.buying.get_default_bom(frm);
}
});
});

View File

@@ -51,16 +51,22 @@ frappe.listview_settings["Purchase Order"] = {
listview.call_for_selected_items(method, { status: "Submitted" });
});
listview.page.add_action_item(__("Purchase Invoice"), () => {
erpnext.bulk_transaction_processing.create(listview, "Purchase Order", "Purchase Invoice");
});
if (frappe.model.can_create("Purchase Invoice")) {
listview.page.add_action_item(__("Purchase Invoice"), () => {
erpnext.bulk_transaction_processing.create(listview, "Purchase Order", "Purchase Invoice");
});
}
listview.page.add_action_item(__("Purchase Receipt"), () => {
erpnext.bulk_transaction_processing.create(listview, "Purchase Order", "Purchase Receipt");
});
if (frappe.model.can_create("Purchase Receipt")) {
listview.page.add_action_item(__("Purchase Receipt"), () => {
erpnext.bulk_transaction_processing.create(listview, "Purchase Order", "Purchase Receipt");
});
}
listview.page.add_action_item(__("Advance Payment"), () => {
erpnext.bulk_transaction_processing.create(listview, "Purchase Order", "Payment Entry");
});
if (frappe.model.can_create("Payment Entry")) {
listview.page.add_action_item(__("Advance Payment"), () => {
erpnext.bulk_transaction_processing.create(listview, "Purchase Order", "Payment Entry");
});
}
},
};

View File

@@ -13,6 +13,7 @@ from frappe.model.naming import set_name_by_naming_series, set_name_from_naming_
from erpnext.accounts.party import (
get_dashboard_info,
get_timeline_data,
validate_party_accounts,
)
from erpnext.utilities.transaction_base import TransactionBase

View File

@@ -11,12 +11,20 @@ frappe.listview_settings["Supplier Quotation"] = {
},
onload: function (listview) {
listview.page.add_action_item(__("Purchase Order"), () => {
erpnext.bulk_transaction_processing.create(listview, "Supplier Quotation", "Purchase Order");
});
if (frappe.model.can_create("Purchase Order")) {
listview.page.add_action_item(__("Purchase Order"), () => {
erpnext.bulk_transaction_processing.create(listview, "Supplier Quotation", "Purchase Order");
});
}
listview.page.add_action_item(__("Purchase Invoice"), () => {
erpnext.bulk_transaction_processing.create(listview, "Supplier Quotation", "Purchase Invoice");
});
if (frappe.model.can_create("Purchase Invoice")) {
listview.page.add_action_item(__("Purchase Invoice"), () => {
erpnext.bulk_transaction_processing.create(
listview,
"Supplier Quotation",
"Purchase Invoice"
);
});
}
},
};

View File

@@ -8,7 +8,7 @@ from collections import defaultdict
import frappe
from frappe import _, bold, qb, throw
from frappe.model.workflow import get_workflow_name, is_transition_condition_satisfied
from frappe.query_builder import Criterion
from frappe.query_builder import Criterion, DocType
from frappe.query_builder.custom import ConstantColumn
from frappe.query_builder.functions import Abs, Sum
from frappe.utils import (
@@ -250,6 +250,7 @@ class AccountsController(TransactionBase):
apply_pricing_rule_on_transaction(self)
self.set_total_in_words()
self.validate_company_in_accounting_dimension()
def init_internal_values(self):
# init all the internal values as 0 on sa
@@ -346,13 +347,47 @@ class AccountsController(TransactionBase):
== 1
)
).run()
frappe.db.sql(
"delete from `tabGL Entry` where voucher_type=%s and voucher_no=%s", (self.doctype, self.name)
)
frappe.db.sql(
"delete from `tabStock Ledger Entry` where voucher_type=%s and voucher_no=%s",
(self.doctype, self.name),
)
gle = frappe.qb.DocType("GL Entry")
frappe.qb.from_(gle).delete().where(
(gle.voucher_type == self.doctype) & (gle.voucher_no == self.name)
).run()
sle = frappe.qb.DocType("Stock Ledger Entry")
frappe.qb.from_(sle).delete().where(
(sle.voucher_type == self.doctype) & (sle.voucher_no == self.name)
).run()
def validate_company_in_accounting_dimension(self):
doc_field = DocType("DocField")
accounting_dimension = DocType("Accounting Dimension")
dimension_list = (
frappe.qb.from_(accounting_dimension)
.select(accounting_dimension.document_type)
.join(doc_field)
.on(doc_field.parent == accounting_dimension.document_type)
.where(doc_field.fieldname == "company")
).run(as_list=True)
dimension_list = sum(dimension_list, ["Project"])
self.validate_company(dimension_list)
for child in self.get_all_children() or []:
self.validate_company(dimension_list, child)
def validate_company(self, dimension_list, child=None):
for dimension in dimension_list:
if not child:
dimension_value = self.get(frappe.scrub(dimension))
else:
dimension_value = child.get(frappe.scrub(dimension))
if dimension_value:
company = frappe.get_cached_value(dimension, dimension_value, "company")
if company and company != self.company:
frappe.throw(
_("{0}: {1} does not belong to the Company: {2}").format(
dimension, frappe.bold(dimension_value), self.company
)
)
def validate_return_against_account(self):
if self.doctype in ["Sales Invoice", "Purchase Invoice"] and self.is_return and self.return_against:
@@ -1027,11 +1062,12 @@ class AccountsController(TransactionBase):
def clear_unallocated_advances(self, childtype, parentfield):
self.set(parentfield, self.get(parentfield, {"allocated_amount": ["not in", [0, None, ""]]}))
frappe.db.sql(
"""delete from `tab{}` where parentfield={} and parent = {}
and allocated_amount = 0""".format(childtype, "%s", "%s"),
(parentfield, self.name),
)
doctype = frappe.qb.DocType(childtype)
frappe.qb.from_(doctype).delete().where(
(doctype.parentfield == parentfield)
& (doctype.parent == self.name)
& (doctype.allocated_amount == 0)
).run()
@frappe.whitelist()
def apply_shipping_rule(self):
@@ -1082,6 +1118,7 @@ class AccountsController(TransactionBase):
"advance_amount": flt(d.amount),
"allocated_amount": allocated_amount,
"ref_exchange_rate": flt(d.exchange_rate), # exchange_rate of advance entry
"difference_posting_date": self.posting_date,
}
self.append("advances", advance_row)
@@ -1332,7 +1369,6 @@ class AccountsController(TransactionBase):
gain_loss_account = frappe.get_cached_value(
"Company", self.company, "exchange_gain_loss_account"
)
je = create_gain_loss_journal(
self.company,
args.get("difference_posting_date") if args else self.posting_date,
@@ -1445,6 +1481,7 @@ class AccountsController(TransactionBase):
"Company", self.company, "exchange_gain_loss_account"
),
"exchange_gain_loss": flt(d.get("exchange_gain_loss")),
"difference_posting_date": d.get("difference_posting_date"),
}
)
lst.append(args)
@@ -1971,11 +2008,9 @@ class AccountsController(TransactionBase):
for adv in self.advances:
consider_for_total_advance = True
if adv.reference_name == linked_doc_name:
frappe.db.sql(
f"""delete from `tab{self.doctype} Advance`
where name = %s""",
adv.name,
)
doctype = frappe.qb.DocType(self.doctype + " Advance")
frappe.qb.from_(doctype).delete().where(doctype.name == adv.name).run()
consider_for_total_advance = False
if consider_for_total_advance:
@@ -2188,6 +2223,9 @@ class AccountsController(TransactionBase):
return
for d in self.get("payment_schedule"):
if d.due_date and d.discount_date:
d.validate_from_to_dates("discount_date", "due_date")
if self.doctype == "Sales Order" and getdate(d.due_date) < getdate(self.transaction_date):
frappe.throw(
_("Row {0}: Due Date in the Payment Terms table cannot be before Posting Date").format(

View File

@@ -75,7 +75,11 @@ def validate_returned_items(doc):
if doc.doctype != "Purchase Invoice":
select_fields += ",serial_no, batch_no"
if doc.doctype in ["Purchase Invoice", "Purchase Receipt", "Subcontracting Receipt"]:
if doc.doctype in [
"Purchase Invoice",
"Purchase Receipt",
"Subcontracting Receipt",
]:
select_fields += ",rejected_qty, received_qty"
for d in frappe.db.sql(
@@ -105,7 +109,12 @@ def validate_returned_items(doc):
for d in doc.get("items"):
key = d.item_code
raise_exception = False
if doc.doctype in ["Purchase Receipt", "Purchase Invoice", "Sales Invoice"]:
if doc.doctype in [
"Purchase Receipt",
"Purchase Invoice",
"Sales Invoice",
"POS Invoice",
]:
field = frappe.scrub(doc.doctype) + "_item"
if d.get(field):
key = (d.item_code, d.get(field))
@@ -175,7 +184,11 @@ def validate_returned_items(doc):
def validate_quantity(doc, key, args, ref, valid_items, already_returned_items):
fields = ["stock_qty"]
if doc.doctype in ["Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"]:
if doc.doctype in [
"Purchase Receipt",
"Purchase Invoice",
"Subcontracting Receipt",
]:
fields.extend(["received_qty", "rejected_qty"])
already_returned_data = already_returned_items.get(key) or {}
@@ -203,7 +216,8 @@ def validate_quantity(doc, key, args, ref, valid_items, already_returned_items):
frappe.throw(_("{0} must be negative in return document").format(label))
elif returned_qty >= reference_qty and args.get(column):
frappe.throw(
_("Item {0} has already been returned").format(args.item_code), StockOverReturnError
_("Item {0} has already been returned").format(args.item_code),
StockOverReturnError,
)
elif abs(flt(current_stock_qty, stock_qty_precision)) > max_returnable_qty:
frappe.throw(
@@ -242,7 +256,11 @@ def get_ref_item_dict(valid_items, ref_item_row):
if ref_item_row.get("rate", 0) > item_dict["rate"]:
item_dict["rate"] = ref_item_row.get("rate", 0)
if ref_item_row.parenttype in ["Purchase Invoice", "Purchase Receipt", "Subcontracting Receipt"]:
if ref_item_row.parenttype in [
"Purchase Invoice",
"Purchase Receipt",
"Subcontracting Receipt",
]:
item_dict["received_qty"] += ref_item_row.received_qty
item_dict["rejected_qty"] += ref_item_row.rejected_qty
@@ -257,7 +275,11 @@ def get_ref_item_dict(valid_items, ref_item_row):
def get_already_returned_items(doc):
column = "child.item_code, sum(abs(child.qty)) as qty, sum(abs(child.stock_qty)) as stock_qty"
if doc.doctype in ["Purchase Invoice", "Purchase Receipt", "Subcontracting Receipt"]:
if doc.doctype in [
"Purchase Invoice",
"Purchase Receipt",
"Subcontracting Receipt",
]:
column += """, sum(abs(child.rejected_qty) * child.conversion_factor) as rejected_qty,
sum(abs(child.received_qty) * child.conversion_factor) as received_qty"""
@@ -384,7 +406,8 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None):
paid_amount = 0.00
base_paid_amount = 0.00
data.base_amount = flt(
data.amount * source.conversion_rate, source.precision("base_paid_amount")
data.amount * source.conversion_rate,
source.precision("base_paid_amount"),
)
paid_amount += data.amount
base_paid_amount += data.base_amount
@@ -544,10 +567,17 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None):
},
doctype + " Item": {
"doctype": doctype + " Item",
"field_map": {"serial_no": "serial_no", "batch_no": "batch_no", "bom": "bom"},
"field_map": {
"serial_no": "serial_no",
"batch_no": "batch_no",
"bom": "bom",
},
"postprocess": update_item,
},
"Payment Schedule": {"doctype": "Payment Schedule", "postprocess": update_terms},
"Payment Schedule": {
"doctype": "Payment Schedule",
"postprocess": update_terms,
},
},
target_doc,
set_missing_values,
@@ -580,13 +610,20 @@ def get_rate_for_return(
item_row,
)
if voucher_type in ("Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"):
if voucher_type in (
"Purchase Receipt",
"Purchase Invoice",
"Subcontracting Receipt",
):
select_field = "incoming_rate"
else:
select_field = "abs(stock_value_difference / actual_qty)"
rate = flt(frappe.db.get_value("Stock Ledger Entry", filters, select_field))
if not (rate and return_against) and voucher_type in ["Sales Invoice", "Delivery Note"]:
if not (rate and return_against) and voucher_type in [
"Sales Invoice",
"Delivery Note",
]:
rate = frappe.db.get_value(f"{voucher_type} Item", voucher_detail_no, "incoming_rate")
if not rate and sle:
@@ -629,7 +666,11 @@ def get_filters(
return_against_item_field,
item_row,
):
filters = {"voucher_type": voucher_type, "voucher_no": return_against, "item_code": item_code}
filters = {
"voucher_type": voucher_type,
"voucher_no": return_against,
"item_code": item_code,
}
if item_row:
reference_voucher_detail_no = item_row.get(return_against_item_field)
@@ -669,3 +710,9 @@ def get_returned_serial_nos(child_doc, parent_doc, serial_no_field="serial_no"):
serial_nos.extend(get_serial_nos(row.get(serial_no_field)))
return serial_nos
@frappe.whitelist()
def get_payment_data(invoice):
payment = frappe.db.get_all("Sales Invoice Payment", {"parent": invoice}, ["mode_of_payment", "amount"])
return payment

View File

@@ -27,6 +27,11 @@ class calculate_taxes_and_totals:
self.doc = doc
frappe.flags.round_off_applicable_accounts = []
if doc.get("round_off_applicable_accounts_for_tax_withholding"):
frappe.flags.round_off_applicable_accounts.append(
doc.round_off_applicable_accounts_for_tax_withholding
)
self._items = self.filter_rows() if self.doc.doctype == "Quotation" else self.doc.get("items")
get_round_off_applicable_accounts(self.doc.company, frappe.flags.round_off_applicable_accounts)

View File

@@ -2,11 +2,14 @@
# For license information, please see license.txt
from datetime import datetime
import frappe
from frappe import qb
from frappe.query_builder.functions import Sum
from frappe.tests.utils import FrappeTestCase
from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import add_days, getdate, nowdate
from frappe.utils.data import getdate as convert_to_date
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry
@@ -705,6 +708,67 @@ class TestAccountsController(FrappeTestCase):
self.assertEqual(exc_je_for_si, [])
self.assertEqual(exc_je_for_pe, [])
@change_settings("Accounts Settings", {"exchange_gain_loss_posting_date": "Reconciliation Date"})
def test_17_gain_loss_posting_date_for_normal_payment(self):
# Sales Invoice in Foreign Currency
rate = 80
rate_in_account_currency = 1
adv_date = convert_to_date(add_days(nowdate(), -2))
inv_date = convert_to_date(add_days(nowdate(), -1))
si = self.create_sales_invoice(posting_date=inv_date, qty=1, rate=rate_in_account_currency)
# Test payments with different exchange rates
pe = self.create_payment_entry(posting_date=adv_date, amount=1, source_exc_rate=75.1).save().submit()
pr = self.create_payment_reconciliation()
pr.from_invoice_date = add_days(nowdate(), -1)
pr.to_invoice_date = nowdate()
pr.from_payment_date = add_days(nowdate(), -2)
pr.to_payment_date = nowdate()
pr.get_unreconciled_entries()
self.assertEqual(len(pr.invoices), 1)
self.assertEqual(len(pr.payments), 1)
invoices = [x.as_dict() for x in pr.invoices]
payments = [x.as_dict() for x in pr.payments]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
pr.reconcile()
self.assertEqual(len(pr.invoices), 0)
self.assertEqual(len(pr.payments), 0)
# Outstanding in both currencies should be '0'
si.reload()
self.assertEqual(si.outstanding_amount, 0)
self.assert_ledger_outstanding(si.doctype, si.name, 0.0, 0.0)
# Exchange Gain/Loss Journal should've been created.
exc_je_for_si = self.get_journals_for(si.doctype, si.name)
exc_je_for_pe = self.get_journals_for(pe.doctype, pe.name)
self.assertNotEqual(exc_je_for_si, [])
self.assertEqual(len(exc_je_for_si), 1)
self.assertEqual(len(exc_je_for_pe), 1)
self.assertEqual(exc_je_for_si[0], exc_je_for_pe[0])
self.assertEqual(
getdate(nowdate()), frappe.db.get_value("Journal Entry", exc_je_for_pe[0].parent, "posting_date")
)
# Cancel Payment
pe.reload()
pe.cancel()
# outstanding should be same as grand total
si.reload()
self.assertEqual(si.outstanding_amount, rate_in_account_currency)
self.assert_ledger_outstanding(si.doctype, si.name, rate, rate_in_account_currency)
# Exchange Gain/Loss Journal should've been cancelled
exc_je_for_si = self.get_journals_for(si.doctype, si.name)
exc_je_for_pe = self.get_journals_for(pe.doctype, pe.name)
self.assertEqual(exc_je_for_si, [])
self.assertEqual(exc_je_for_pe, [])
def test_20_journal_against_sales_invoice(self):
# Invoice in Foreign Currency
si = self.create_sales_invoice(qty=1, conversion_rate=80, rate=1)
@@ -1342,32 +1406,32 @@ class TestAccountsController(FrappeTestCase):
# Invoices
si1 = self.create_sales_invoice(qty=1, rate=rate_in_account_currency, do_not_submit=True)
si1.department = "Management"
si1.department = "Management - _TC"
si1.save().submit()
si2 = self.create_sales_invoice(qty=1, rate=rate_in_account_currency, do_not_submit=True)
si2.department = "Operations"
si2.department = "Operations - _TC"
si2.save().submit()
# Payments
cr_note1 = self.create_sales_invoice(qty=-1, conversion_rate=75, rate=1, do_not_save=True)
cr_note1.department = "Management"
cr_note1.department = "Management - _TC"
cr_note1.is_return = 1
cr_note1.save().submit()
cr_note2 = self.create_sales_invoice(qty=-1, conversion_rate=75, rate=1, do_not_save=True)
cr_note2.department = "Legal"
cr_note2.department = "Legal - _TC"
cr_note2.is_return = 1
cr_note2.save().submit()
pe1 = get_payment_entry(si1.doctype, si1.name)
pe1.references = []
pe1.department = "Research & Development"
pe1.department = "Research & Development - _TC"
pe1.save().submit()
pe2 = get_payment_entry(si1.doctype, si1.name)
pe2.references = []
pe2.department = "Management"
pe2.department = "Management - _TC"
pe2.save().submit()
je1 = self.create_journal_entry(
@@ -1380,7 +1444,7 @@ class TestAccountsController(FrappeTestCase):
)
je1.accounts[0].party_type = "Customer"
je1.accounts[0].party = self.customer
je1.accounts[0].department = "Management"
je1.accounts[0].department = "Management - _TC"
je1.save().submit()
# assert dimension filter's result
@@ -1389,17 +1453,17 @@ class TestAccountsController(FrappeTestCase):
self.assertEqual(len(pr.invoices), 2)
self.assertEqual(len(pr.payments), 5)
pr.department = "Legal"
pr.department = "Legal - _TC"
pr.get_unreconciled_entries()
self.assertEqual(len(pr.invoices), 0)
self.assertEqual(len(pr.payments), 1)
pr.department = "Management"
pr.department = "Management - _TC"
pr.get_unreconciled_entries()
self.assertEqual(len(pr.invoices), 1)
self.assertEqual(len(pr.payments), 3)
pr.department = "Research & Development"
pr.department = "Research & Development - _TC"
pr.get_unreconciled_entries()
self.assertEqual(len(pr.invoices), 0)
self.assertEqual(len(pr.payments), 1)
@@ -1411,17 +1475,17 @@ class TestAccountsController(FrappeTestCase):
# Invoice
si = self.create_sales_invoice(qty=1, rate=rate_in_account_currency, do_not_submit=True)
si.department = "Management"
si.department = "Management - _TC"
si.save().submit()
# Payment
cr_note = self.create_sales_invoice(qty=-1, conversion_rate=75, rate=1, do_not_save=True)
cr_note.department = "Management"
cr_note.department = "Management - _TC"
cr_note.is_return = 1
cr_note.save().submit()
pr = self.create_payment_reconciliation()
pr.department = "Management"
pr.department = "Management - _TC"
pr.get_unreconciled_entries()
self.assertEqual(len(pr.invoices), 1)
self.assertEqual(len(pr.payments), 1)
@@ -1454,7 +1518,7 @@ class TestAccountsController(FrappeTestCase):
# Sales Invoice in Foreign Currency
self.setup_dimensions()
rate_in_account_currency = 1
dpt = "Research & Development"
dpt = "Research & Development - _TC"
si = self.create_sales_invoice(qty=1, rate=rate_in_account_currency, do_not_save=True)
si.department = dpt
@@ -1490,7 +1554,7 @@ class TestAccountsController(FrappeTestCase):
def test_93_dimension_inheritance_on_advance(self):
self.setup_dimensions()
dpt = "Research & Development"
dpt = "Research & Development - _TC"
adv = self.create_payment_entry(amount=1, source_exc_rate=85)
adv.department = dpt

View File

@@ -375,7 +375,7 @@
"depends_on": "eval:!doc.__islocal",
"fieldname": "notes_tab",
"fieldtype": "Tab Break",
"label": "Comments"
"label": "Notes"
},
{
"collapsible": 1,
@@ -514,7 +514,7 @@
"idx": 5,
"image_field": "image",
"links": [],
"modified": "2023-12-01 18:46:49.468526",
"modified": "2025-01-31 13:40:08.094759",
"modified_by": "Administrator",
"module": "CRM",
"name": "Lead",

View File

@@ -1,211 +1,78 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"actions": [],
"autoname": "field:gateway_name",
"beta": 0,
"creation": "2018-02-06 16:11:10.028249",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"gateway_name",
"section_break_2",
"access_token",
"webhooks_secret",
"use_sandbox",
"header_img"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "gateway_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Payment Gateway Name",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
"unique": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_2",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
"fieldtype": "Section Break"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "access_token",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Access Token",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "webhooks_secret",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Webhooks Secret",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
"label": "Webhooks Secret"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0",
"fieldname": "use_sandbox",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Use Sandbox",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
"label": "Use Sandbox"
},
{
"fieldname": "header_img",
"fieldtype": "Attach Image",
"label": "Header Image"
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2022-02-12 14:18:47.209114",
"links": [],
"modified": "2024-07-22 12:34:26.791274",
"modified_by": "Administrator",
"module": "ERPNext Integrations",
"name": "GoCardless Settings",
"name_case": "",
"naming_rule": "By fieldname",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
}
"states": [],
"track_changes": 1
}

View File

@@ -201,7 +201,7 @@
"description": "In the case of 'Use Multi-Level BOM' in a work order, if the user wishes to add sub-assembly costs to Finished Goods items without using a job card as well the scrap items, then this option needs to be enable.",
"fieldname": "set_op_cost_and_scrape_from_sub_assemblies",
"fieldtype": "Check",
"label": "Set Operating Cost / Scrape Items From Sub-assemblies"
"label": "Set Operating Cost / Scrap Items From Sub-assemblies"
}
],
"icon": "icon-wrench",
@@ -226,4 +226,4 @@
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View File

@@ -50,7 +50,11 @@ def get_returned_materials(work_orders):
raw_materials = frappe.get_all(
"Stock Entry",
fields=["`tabStock Entry Detail`.`item_code`", "`tabStock Entry Detail`.`qty`"],
fields=[
"`tabStock Entry`.`work_order`",
"`tabStock Entry Detail`.`item_code`",
"`tabStock Entry Detail`.`qty`",
],
filters=[
["Stock Entry", "is_return", "=", 1],
["Stock Entry Detail", "docstatus", "=", 1],
@@ -59,12 +63,14 @@ def get_returned_materials(work_orders):
)
for d in raw_materials:
raw_materials_qty[d.item_code] += d.qty
key = (d.work_order, d.item_code)
raw_materials_qty[key] += d.qty
for row in work_orders:
row.returned_qty = 0.0
if raw_materials_qty.get(row.raw_material_item_code):
row.returned_qty = raw_materials_qty.get(row.raw_material_item_code)
key = (row.parent, row.raw_material_item_code)
if raw_materials_qty.get(key):
row.returned_qty = raw_materials_qty.get(key)
def get_fields():

View File

@@ -368,3 +368,5 @@ erpnext.patches.v14_0.remove_cancelled_asset_capitalization_from_asset
erpnext.patches.v14_0.enable_set_priority_for_pricing_rules #1
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")

View File

@@ -0,0 +1,5 @@
import frappe
def execute():
frappe.db.set_value("Report", "Gross Profit", "add_total_row", 0)

View File

@@ -190,7 +190,7 @@ frappe.ui.form.on("Project", {
},
set_status: function (frm, status) {
frappe.confirm(__("Set Project and all Tasks to status {0}?", [status.bold()]), () => {
frappe.confirm(__("Set Project and all Tasks to status {0}?", [__(status).bold()]), () => {
frappe
.xcall("erpnext.projects.doctype.project.project.set_project_status", {
project: frm.doc.name,

View File

@@ -8,7 +8,7 @@ from frappe import _, qb
from frappe.desk.reportview import get_match_cond
from frappe.model.document import Document
from frappe.query_builder.functions import Sum
from frappe.utils import add_days, flt, get_datetime, get_time, get_url, nowtime, today
from frappe.utils import add_days, flt, get_datetime, get_link_to_form, get_time, nowtime, today
from erpnext import get_default_company
from erpnext.controllers.queries import get_filters_cond
@@ -275,24 +275,19 @@ class Project(Document):
frappe.db.set_value("Project", new_name, "copied_from", new_name)
def send_welcome_email(self):
url = get_url(f"/project/?name={self.name}")
messages = (
_("You have been invited to collaborate on the project: {0}").format(self.name),
url,
_("Join"),
)
label = f"{self.project_name} ({self.name})"
url = get_link_to_form(self.doctype, self.name, label)
content = """
<p>{0}.</p>
<p><a href="{1}">{2}</a></p>
"""
content = "<p>{}</p>".format(
_("You have been invited to collaborate on the project: {0}").format(url)
)
for user in self.users:
if user.welcome_email_sent == 0:
frappe.sendmail(
user.user,
subject=_("Project Collaboration Invitation"),
content=content.format(*messages),
content=content,
)
user.welcome_email_sent = 1

View File

@@ -1,6 +1,7 @@
[
{
"project_name": "_Test Project",
"status": "Open"
"status": "Open",
"company": "_Test Company"
}
]

View File

@@ -120,7 +120,7 @@ class Timesheet(Document):
if data.task and data.task not in tasks:
task = frappe.get_doc("Task", data.task)
task.update_time_and_costing()
task.save()
task.save(ignore_permissions=True)
tasks.append(data.task)
elif data.project and data.project not in projects:

View File

@@ -806,7 +806,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
}
}
set_total_amount_to_default_mop() {
async set_total_amount_to_default_mop() {
let grand_total = this.frm.doc.rounded_total || this.frm.doc.grand_total;
let base_grand_total = this.frm.doc.base_rounded_total || this.frm.doc.base_grand_total;
@@ -828,6 +828,45 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
);
}
/*
During returns, if an user select mode of payment other than
default mode of payment, it should retain the user selection
instead resetting it to default mode of payment.
*/
let payment_amount = 0;
this.frm.doc.payments.forEach(payment => {
payment_amount += payment.amount
});
if (payment_amount == total_amount_to_pay) {
return;
}
/*
For partial return, if the payment was made using single mode of payment
it should set the return to that mode of payment only.
*/
let return_against_mop = await frappe.call({
method: 'erpnext.controllers.sales_and_purchase_return.get_payment_data',
args: {
invoice: this.frm.doc.return_against
}
});
if (return_against_mop.message.length === 1) {
this.frm.doc.payments.forEach(payment => {
if (payment.mode_of_payment == return_against_mop.message[0].mode_of_payment) {
payment.amount = total_amount_to_pay;
} else {
payment.amount = 0;
}
});
this.frm.refresh_fields();
return;
}
this.frm.doc.payments.find(payment => {
if (payment.default) {
payment.amount = total_amount_to_pay;

View File

@@ -841,7 +841,6 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
}
transaction_date() {
this.apply_pricing_rule()
if (this.frm.doc.transaction_date) {
this.frm.transaction_date = this.frm.doc.transaction_date;
frappe.ui.form.trigger(this.frm.doc.doctype, "currency");
@@ -850,7 +849,6 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
posting_date() {
var me = this;
me.apply_pricing_rule()
if (this.frm.doc.posting_date) {
this.frm.posting_date = this.frm.doc.posting_date;
@@ -1529,7 +1527,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
"serial_no": d.serial_no,
"batch_no": d.batch_no,
"price_list_rate": d.price_list_rate,
"conversion_factor": d.conversion_factor || 1.0
"conversion_factor": d.conversion_factor || 1.0,
});
// if doctype is Quotation Item / Sales Order Iten then add Margin Type and rate in item_list

View File

@@ -628,6 +628,62 @@ erpnext.utils.update_child_items = function (opts) {
filters: filters,
};
},
onchange: function () {
const me = this;
frm.call({
method: "erpnext.stock.get_item_details.get_item_details",
args: {
doc: frm.doc,
ctx: {
item_code: this.value,
set_warehouse: frm.doc.set_warehouse,
customer: frm.doc.customer || frm.doc.party_name,
quotation_to: frm.doc.quotation_to,
supplier: frm.doc.supplier,
currency: frm.doc.currency,
is_internal_supplier: frm.doc.is_internal_supplier,
is_internal_customer: frm.doc.is_internal_customer,
conversion_rate: frm.doc.conversion_rate,
price_list: frm.doc.selling_price_list || frm.doc.buying_price_list,
price_list_currency: frm.doc.price_list_currency,
plc_conversion_rate: frm.doc.plc_conversion_rate,
company: frm.doc.company,
order_type: frm.doc.order_type,
is_pos: cint(frm.doc.is_pos),
is_return: cint(frm.doc.is_return),
is_subcontracted: frm.doc.is_subcontracted,
ignore_pricing_rule: frm.doc.ignore_pricing_rule,
doctype: frm.doc.doctype,
name: frm.doc.name,
qty: me.doc.qty || 1,
uom: me.doc.uom,
pos_profile: cint(frm.doc.is_pos) ? frm.doc.pos_profile : "",
tax_category: frm.doc.tax_category,
child_doctype: frm.doc.doctype + " Item",
is_old_subcontracting_flow: frm.doc.is_old_subcontracting_flow,
},
},
callback: function (r) {
if (r.message) {
const { qty, price_list_rate: rate, uom, conversion_factor } = r.message;
const row = dialog.fields_dict.trans_items.df.data.find(
(doc) => doc.idx == me.doc.idx
);
if (row) {
Object.assign(row, {
conversion_factor: me.doc.conversion_factor || conversion_factor,
uom: me.doc.uom || uom,
qty: me.doc.qty || qty,
rate: me.doc.rate || rate,
});
dialog.fields_dict.trans_items.grid.refresh();
}
}
},
});
},
},
{
fieldtype: "Link",

View File

@@ -20,6 +20,7 @@ from frappe.utils.user import get_users_with_role
from erpnext.accounts.party import (
get_dashboard_info,
get_timeline_data,
validate_party_accounts,
)
from erpnext.utilities.transaction_base import TransactionBase

View File

@@ -322,7 +322,11 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False):
2. If selections: Is Alternative Item/Has Alternative Item: Map if selected and adequate qty
3. If selections: Simple row: Map if adequate qty
"""
has_qty = item.qty > 0
balance_qty = item.qty - ordered_items.get(item.item_code, 0.0)
if balance_qty <= 0:
return False
has_qty = balance_qty
if not selected_rows:
return not item.is_alternative

View File

@@ -12,13 +12,17 @@ frappe.listview_settings["Quotation"] = {
};
}
listview.page.add_action_item(__("Sales Order"), () => {
erpnext.bulk_transaction_processing.create(listview, "Quotation", "Sales Order");
});
if (frappe.model.can_create("Sales Order")) {
listview.page.add_action_item(__("Sales Order"), () => {
erpnext.bulk_transaction_processing.create(listview, "Quotation", "Sales Order");
});
}
listview.page.add_action_item(__("Sales Invoice"), () => {
erpnext.bulk_transaction_processing.create(listview, "Quotation", "Sales Invoice");
});
if (frappe.model.can_create("Sales Invoice")) {
listview.page.add_action_item(__("Sales Invoice"), () => {
erpnext.bulk_transaction_processing.create(listview, "Quotation", "Sales Invoice");
});
}
},
get_indicator: function (doc) {

View File

@@ -30,6 +30,38 @@ class TestQuotation(FrappeTestCase):
self.assertTrue(sales_order.get("payment_schedule"))
def test_do_not_add_ordered_items_in_new_sales_order(self):
from erpnext.selling.doctype.quotation.quotation import make_sales_order
from erpnext.stock.doctype.item.test_item import make_item
item = make_item("_Test Item for Quotation for SO", {"is_stock_item": 1})
quotation = make_quotation(qty=5, do_not_submit=True)
quotation.append(
"items",
{
"item_code": item.name,
"qty": 5,
"rate": 100,
"conversion_factor": 1,
"uom": item.stock_uom,
"warehouse": "_Test Warehouse - _TC",
"stock_uom": item.stock_uom,
},
)
quotation.submit()
sales_order = make_sales_order(quotation.name)
sales_order.delivery_date = nowdate()
self.assertEqual(len(sales_order.items), 2)
sales_order.remove(sales_order.items[1])
sales_order.submit()
sales_order = make_sales_order(quotation.name)
self.assertEqual(len(sales_order.items), 1)
self.assertEqual(sales_order.items[0].item_code, item.name)
self.assertEqual(sales_order.items[0].qty, 5.0)
def test_gross_profit(self):
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry

View File

@@ -1044,7 +1044,8 @@ def make_purchase_order_for_default_supplier(source_name, selected_items=None, t
"postprocess": update_item,
"condition": lambda doc: doc.ordered_qty < doc.stock_qty
and doc.supplier == supplier
and doc.item_code in items_to_map,
and doc.item_code in items_to_map
and doc.delivered_by_supplier == 1,
},
},
target_doc,

View File

@@ -60,16 +60,22 @@ frappe.listview_settings["Sales Order"] = {
listview.call_for_selected_items(method, { status: "Submitted" });
});
listview.page.add_action_item(__("Sales Invoice"), () => {
erpnext.bulk_transaction_processing.create(listview, "Sales Order", "Sales Invoice");
});
if (frappe.model.can_create("Sales Invoice")) {
listview.page.add_action_item(__("Sales Invoice"), () => {
erpnext.bulk_transaction_processing.create(listview, "Sales Order", "Sales Invoice");
});
}
listview.page.add_action_item(__("Delivery Note"), () => {
erpnext.bulk_transaction_processing.create(listview, "Sales Order", "Delivery Note");
});
if (frappe.model.can_create("Delivery Note")) {
listview.page.add_action_item(__("Delivery Note"), () => {
erpnext.bulk_transaction_processing.create(listview, "Sales Order", "Delivery Note");
});
}
listview.page.add_action_item(__("Advance Payment"), () => {
erpnext.bulk_transaction_processing.create(listview, "Sales Order", "Payment Entry");
});
if (frappe.model.can_create("Payment Entry")) {
listview.page.add_action_item(__("Advance Payment"), () => {
erpnext.bulk_transaction_processing.create(listview, "Sales Order", "Payment Entry");
});
}
},
};

View File

@@ -51,13 +51,18 @@ def search_by_term(search_term, warehouse, price_list):
item_stock_qty = item_stock_qty // item.get("conversion_factor", 1)
item.update({"actual_qty": item_stock_qty})
price_filters = {
"price_list": price_list,
"item_code": item_code,
}
if batch_no:
price_filters["batch_no"] = batch_no
price = frappe.get_list(
doctype="Item Price",
filters={
"price_list": price_list,
"item_code": item_code,
},
fields=["uom", "currency", "price_list_rate"],
filters=price_filters,
fields=["uom", "currency", "price_list_rate", "batch_no"],
)
def __sort(p):

View File

@@ -635,7 +635,7 @@ erpnext.PointOfSale.Controller = class {
i.item_code === item_code &&
(!has_batch_no || (has_batch_no && i.batch_no === batch_no)) &&
i.uom === uom &&
i.rate == rate
i.price_list_rate === flt(rate)
);
}

View File

@@ -913,10 +913,13 @@ erpnext.PointOfSale.ItemCart = class {
const me = this;
dfs.forEach((df) => {
this[`customer_${df.fieldname}_field`] = frappe.ui.form.make_control({
df: { ...df, onchange: handle_customer_field_change },
df: df,
parent: $customer_form.find(`.${df.fieldname}-field`),
render_input: true,
});
this[`customer_${df.fieldname}_field`].$input?.on("blur", () => {
handle_customer_field_change.apply(this[`customer_${df.fieldname}_field`]);
});
this[`customer_${df.fieldname}_field`].set_value(this.customer_info[df.fieldname]);
});

View File

@@ -325,13 +325,16 @@ erpnext.PointOfSale.ItemSelector = class {
}
filter_items({ search_term = "" } = {}) {
const selling_price_list = this.events.get_frm().doc.selling_price_list;
if (search_term) {
search_term = search_term.toLowerCase();
// memoize
this.search_index = this.search_index || {};
if (this.search_index[search_term]) {
const items = this.search_index[search_term];
this.search_index[selling_price_list] = this.search_index[selling_price_list] || {};
if (this.search_index[selling_price_list][search_term]) {
const items = this.search_index[selling_price_list][search_term];
this.items = items;
this.render_item_list(items);
this.auto_add_item && this.items.length == 1 && this.add_filtered_item_to_cart();
@@ -343,7 +346,7 @@ erpnext.PointOfSale.ItemSelector = class {
// eslint-disable-next-line no-unused-vars
const { items, serial_no, batch_no, barcode } = message;
if (search_term && !barcode) {
this.search_index[search_term] = items;
this.search_index[selling_price_list][search_term] = items;
}
this.items = items;
this.render_item_list(items);

View File

@@ -110,7 +110,7 @@ erpnext.PointOfSale.PastOrderList = class {
</div>
</div>
<div class="invoice-total-status">
<div class="invoice-total">${format_currency(invoice.grand_total, invoice.currency, 0) || 0}</div>
<div class="invoice-total">${format_currency(invoice.grand_total, invoice.currency) || 0}</div>
<div class="invoice-date">${posting_datetime}</div>
</div>
</div>

View File

@@ -871,5 +871,6 @@
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"title_field": "employee_name"
}
"title_field": "employee_name",
"track_changes": 1
}

View File

@@ -42,6 +42,7 @@ class Employee(NestedSet):
self.validate_email()
self.validate_status()
self.validate_reports_to()
self.set_preferred_email()
self.validate_preferred_email()
if self.user_id:
@@ -184,9 +185,7 @@ class Employee(NestedSet):
def set_preferred_email(self):
preferred_email_field = frappe.scrub(self.prefered_contact_email)
if preferred_email_field:
preferred_email = self.get(preferred_email_field)
self.prefered_email = preferred_email
self.prefered_email = self.get(preferred_email_field) if preferred_email_field else None
def validate_status(self):
if self.status == "Left":

View File

@@ -5,6 +5,7 @@
import frappe
from frappe import _
from frappe.utils import flt
from frappe.utils.data import get_url_to_list
from frappe.utils.nestedset import NestedSet, get_root_of
from erpnext import get_default_currency
@@ -14,6 +15,9 @@ class SalesPerson(NestedSet):
nsm_parent_field = "parent_sales_person"
def validate(self):
if not self.enabled:
self.validate_sales_person()
if not self.parent_sales_person:
self.parent_sales_person = get_root_of("Sales Person")
@@ -55,6 +59,25 @@ class SalesPerson(NestedSet):
super().on_update()
self.validate_one_root()
def validate_sales_person(self):
sales_team = frappe.qb.DocType("Sales Team")
query = (
frappe.qb.from_(sales_team)
.select(sales_team.sales_person)
.where((sales_team.sales_person == self.name) & (sales_team.parenttype == "Customer"))
.groupby(sales_team.sales_person)
).run(as_dict=True)
if query:
frappe.throw(
_("The Sales Person is linked with {0}").format(
frappe.bold(
f"""<a href="{get_url_to_list("Customer")}?sales_person={self.name}">{"Customers"}</a>"""
)
)
)
def get_email_id(self):
if self.employee:
user = frappe.db.get_value("Employee", self.employee, "user_id")

View File

@@ -95,7 +95,6 @@ class ClosingStockBalance(Document):
"item_group": self.item_group,
"warehouse_type": self.warehouse_type,
"include_uom": self.include_uom,
"ignore_closing_balance": 1,
"show_variant_attributes": 1,
"show_stock_ageing_data": 1,
}

View File

@@ -63,16 +63,20 @@ frappe.listview_settings["Delivery Note"] = {
}
};
// doclist.page.add_actions_menu_item(__('Create Delivery Trip'), action, false);
if (frappe.model.can_create("Delivery Trip")) {
doclist.page.add_action_item(__("Create Delivery Trip"), action);
}
doclist.page.add_action_item(__("Create Delivery Trip"), action);
if (frappe.model.can_create("Sales Invoice")) {
doclist.page.add_action_item(__("Sales Invoice"), () => {
erpnext.bulk_transaction_processing.create(doclist, "Delivery Note", "Sales Invoice");
});
}
doclist.page.add_action_item(__("Sales Invoice"), () => {
erpnext.bulk_transaction_processing.create(doclist, "Delivery Note", "Sales Invoice");
});
doclist.page.add_action_item(__("Packaging Slip From Delivery Note"), () => {
erpnext.bulk_transaction_processing.create(doclist, "Delivery Note", "Packing Slip");
});
if (frappe.model.can_create("Packing Slip")) {
doclist.page.add_action_item(__("Packaging Slip From Delivery Note"), () => {
erpnext.bulk_transaction_processing.create(doclist, "Delivery Note", "Packing Slip");
});
}
},
};

View File

@@ -26,9 +26,8 @@ from erpnext.stock.get_item_details import get_conversion_factor
class PickList(Document):
def validate(self):
self.validate_for_qty()
if self.pick_manually and self.get("locations"):
self.validate_stock_qty()
self.check_serial_no_status()
self.validate_stock_qty()
self.check_serial_no_status()
def before_save(self):
self.update_status()
@@ -42,14 +41,24 @@ class PickList(Document):
from erpnext.stock.doctype.batch.batch import get_batch_qty
for row in self.get("locations"):
if row.batch_no and not row.qty:
if not row.picked_qty:
continue
if row.batch_no and row.picked_qty:
batch_qty = get_batch_qty(row.batch_no, row.warehouse, row.item_code)
if row.qty > batch_qty:
if row.picked_qty > batch_qty:
frappe.throw(
_(
"At Row #{0}: The picked quantity {1} for the item {2} is greater than available stock {3} for the batch {4} in the warehouse {5}."
).format(row.idx, row.item_code, batch_qty, row.batch_no, bold(row.warehouse)),
"At Row #{0}: The picked quantity {1} for the item {2} is greater than available stock {3} for the batch {4} in the warehouse {5}. Please restock the item."
).format(
row.idx,
row.picked_qty,
row.item_code,
batch_qty,
row.batch_no,
bold(row.warehouse),
),
title=_("Insufficient Stock"),
)
@@ -61,11 +70,11 @@ class PickList(Document):
"actual_qty",
)
if row.qty > flt(bin_qty):
if row.picked_qty > flt(bin_qty):
frappe.throw(
_(
"At Row #{0}: The picked quantity {1} for the item {2} is greater than available stock {3} in the warehouse {4}."
).format(row.idx, row.qty, bold(row.item_code), bin_qty, bold(row.warehouse)),
).format(row.idx, row.picked_qty, bold(row.item_code), bin_qty, bold(row.warehouse)),
title=_("Insufficient Stock"),
)
@@ -253,7 +262,14 @@ class PickList(Document):
locations_replica = self.get("locations")
# reset
self.delete_key("locations")
reset_rows = []
for row in self.get("locations"):
if not row.picked_qty:
reset_rows.append(row)
for row in reset_rows:
self.remove(row)
updated_locations = frappe._dict()
for item_doc in items:
item_code = item_doc.item_code
@@ -323,6 +339,9 @@ class PickList(Document):
# aggregate qty for same item
item_map = OrderedDict()
for item in locations:
if item.picked_qty:
continue
if not item.item_code:
frappe.throw(f"Row #{item.idx}: Item Code is Mandatory")
if not cint(

View File

@@ -842,5 +842,47 @@ class TestPickList(FrappeTestCase):
for row in pl.locations:
row.qty = row.qty + 10
row.picked_qty = row.qty
self.assertRaises(frappe.ValidationError, pl.save)
def test_pick_list_not_reset_batch(self):
warehouse = "_Test Warehouse - _TC"
item = make_item(
"Test Do Not Reset Picked Item",
properties={
"is_stock_item": 1,
"has_batch_no": 1,
"create_new_batch": 1,
"batch_number_series": "BTH-PICKLT-.######",
},
).name
se = make_stock_entry(item=item, to_warehouse=warehouse, qty=10)
se.reload()
batch1 = se.items[0].batch_no
se = make_stock_entry(item=item, to_warehouse=warehouse, qty=10)
se.reload()
batch2 = se.items[0].batch_no
so = make_sales_order(item_code=item, qty=10, rate=100)
pl = create_pick_list(so.name)
pl.save()
for loc in pl.locations:
self.assertEqual(loc.batch_no, batch1)
loc.batch_no = batch2
loc.picked_qty = 0.0
pl.save()
for loc in pl.locations:
self.assertEqual(loc.batch_no, batch1)
loc.batch_no = batch2
loc.picked_qty = 10.0
pl.save()
for loc in pl.locations:
self.assertEqual(loc.batch_no, batch2)

View File

@@ -803,7 +803,12 @@ frappe.ui.form.on('Stock Entry Detail', {
var d = locals[cdt][cdn];
$.each(r.message, function(k, v) {
if (v) {
frappe.model.set_value(cdt, cdn, k, v); // qty and it's subsequent fields weren't triggered
// set_value trigger barcode function and barcode set qty to 1 in stock_controller.js, to avoid this set value manually instead of set value.
if (k != "barcode") {
frappe.model.set_value(cdt, cdn, k, v); // qty and it's subsequent fields weren't triggered
} else {
d.barcode = v;
}
}
});
refresh_field("items");

View File

@@ -4,7 +4,7 @@
import frappe
from frappe import _
from frappe.utils import cint, flt, get_table_name, getdate
from frappe.utils import cint, flt, get_datetime, get_table_name, getdate
from pypika import functions as fn
from erpnext.stock.doctype.warehouse.warehouse import apply_warehouse_filter
@@ -99,6 +99,8 @@ def get_stock_ledger_entries(filters):
if not filters.get("to_date"):
frappe.throw(_("'To Date' is required"))
to_date = get_datetime(filters.get("to_date") + " 23:59:59")
sle = frappe.qb.DocType("Stock Ledger Entry")
query = (
frappe.qb.from_(sle)
@@ -113,7 +115,7 @@ def get_stock_ledger_entries(filters):
(sle.docstatus < 2)
& (sle.is_cancelled == 0)
& (fn.IfNull(sle.batch_no, "") != "")
& (sle.posting_date <= filters["to_date"])
& (sle.posting_datetime <= to_date)
)
.groupby(sle.voucher_no, sle.batch_no, sle.item_code, sle.warehouse)
.orderby(sle.item_code, sle.warehouse)

View File

@@ -6,7 +6,7 @@ from operator import itemgetter
import frappe
from frappe import _
from frappe.utils import cint, date_diff, flt
from frappe.utils import cint, date_diff, flt, get_datetime
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
@@ -387,6 +387,7 @@ class FIFOSlots:
def __get_stock_ledger_entries(self) -> list[dict]:
sle = frappe.qb.DocType("Stock Ledger Entry")
item = self.__get_item_query() # used as derived table in sle query
to_date = get_datetime(self.filters.get("to_date") + " 23:59:59")
sle_query = (
frappe.qb.from_(sle)
@@ -411,7 +412,7 @@ class FIFOSlots:
.where(
(sle.item_code == item.name)
& (sle.company == self.filters.get("company"))
& (sle.posting_date <= self.filters.get("to_date"))
& (sle.posting_datetime <= to_date)
& (sle.is_cancelled != 1)
)
)
@@ -428,7 +429,7 @@ class FIFOSlots:
if warehouses:
sle_query = sle_query.where(sle.warehouse.isin(warehouses))
sle_query = sle_query.orderby(sle.posting_date, sle.posting_time, sle.creation, sle.actual_qty)
sle_query = sle_query.orderby(sle.posting_datetime, sle.creation)
return sle_query.run(as_dict=True, as_iterator=True)

View File

@@ -289,7 +289,6 @@ class StockBalanceReport:
.where((sle.docstatus < 2) & (sle.is_cancelled == 0))
.orderby(sle.posting_datetime)
.orderby(sle.creation)
.orderby(sle.actual_qty)
)
query = self.apply_inventory_dimensions_filters(query, sle)

View File

@@ -5,7 +5,7 @@
import frappe
from frappe import _
from frappe.query_builder.functions import CombineDatetime
from frappe.utils import cint, flt
from frappe.utils import cint, flt, get_datetime
from erpnext.stock.doctype.inventory_dimension.inventory_dimension import get_inventory_dimensions
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
@@ -264,6 +264,9 @@ def get_columns(filters):
def get_stock_ledger_entries(filters, items):
from_date = get_datetime(filters.from_date + " 00:00:00")
to_date = get_datetime(filters.to_date + " 23:59:59")
sle = frappe.qb.DocType("Stock Ledger Entry")
query = (
frappe.qb.from_(sle)
@@ -286,12 +289,8 @@ def get_stock_ledger_entries(filters, items):
sle.serial_no,
sle.project,
)
.where(
(sle.docstatus < 2)
& (sle.is_cancelled == 0)
& (sle.posting_date[filters.from_date : filters.to_date])
)
.orderby(CombineDatetime(sle.posting_date, sle.posting_time))
.where((sle.docstatus < 2) & (sle.is_cancelled == 0) & (sle.posting_datetime[from_date:to_date]))
.orderby(sle.posting_datetime)
.orderby(sle.creation)
)

View File

@@ -54,7 +54,10 @@ def get_stock_value_from_bin(warehouse=None, item_code=None):
def get_stock_value_on(
warehouses: list | str | None = None, posting_date: str | None = None, item_code: str | None = None
warehouses: list | str | None = None,
posting_date: str | None = None,
item_code: str | None = None,
company: str | None = None,
) -> float:
if not posting_date:
posting_date = nowdate()
@@ -82,6 +85,9 @@ def get_stock_value_on(
if item_code:
query = query.where(sle.item_code == item_code)
if company:
query = query.where(sle.company == company)
return query.run(as_list=True)[0][0]

View File

@@ -8,6 +8,9 @@ from frappe.utils import get_link_to_form, today
@frappe.whitelist()
def transaction_processing(data, from_doctype, to_doctype):
frappe.has_permission(from_doctype, "read", throw=True)
frappe.has_permission(to_doctype, "create", throw=True)
if isinstance(data, str):
deserialized_data = json.loads(data)
else: