Compare commits

..

177 Commits

Author SHA1 Message Date
Frappe PR Bot
964abce057 chore(release): Bumped to Version 14.7.0
# [14.7.0](https://github.com/frappe/erpnext/compare/v14.6.0...v14.7.0) (2022-11-15)

### Bug Fixes

* add translate function to valitate company msg in chart of accounts importer ([8de4430](8de4430662))
* check type for reference name ([a305793](a30579393e))
* don't set WIP Warehouse if  is checked in WO ([f923183](f923183b64))
* GP incorrect buying amount if no upd on SI and Delivery Note ([2d8f00a](2d8f00afad))
* incorrect fix of conversion factor in PP ([c48d00a](c48d00ad77))
* Label for applicable dimension table ([eb4f8e4](eb4f8e4bd8))
* Maintain same rate between Quotation and Sales Order ([6c155d2](6c155d2825))
* Project filter in timesheet ([37bed12](37bed12df4))
* Purchase Receipt timeout error ([0d5b726](0d5b7269d4))
* repayment schedule regeneration ([2f5033b](2f5033b70f))
* set `WIP Warehouse` in Job Card ([c294652](c294652dab))
* set stock UOM in args to ensure item price is fetched ([a4187b9](a4187b9d8f))
* test cases ([071ee5d](071ee5d81c))
* **ux:** Tab break in Customer and Supplier form ([eeaa932](eeaa9329f6))
* Write Off section visibility for non POS Invoices ([07badbc](07badbc0f2))

### Features

* Repost Payment Ledger entries for vouchers ([de59b50](de59b50407))

### Reverts

* Revert "fix: get `consumed_qty` based on `received_qty` in SCR" ([7fd6c43](7fd6c43752))
* Revert "fix: set `received_qty` before_validate SCR" ([0ecb44d](0ecb44d40c))
2022-11-15 12:59:04 +00:00
Deepesh Garg
c310c8a4b0 Merge pull request #32972 from frappe/version-14-hotfix
chore: release v14
2022-11-15 18:17:10 +05:30
Deepesh Garg
d7340f54cf Merge pull request #32969 from frappe/mergify/bp/version-14-hotfix/pr-32953
chore(payment_entry): Remove dead validations (backport #32953)
2022-11-15 16:32:54 +05:30
Deepesh Garg
19f3d86a12 Merge pull request #32966 from frappe/mergify/bp/version-14-hotfix/pr-32962
fix: Write Off section visibility for non POS Invoices (backport #32962)
2022-11-15 16:31:49 +05:30
Sagar Sharma
48dbf47e76 Merge pull request #32974 from frappe/mergify/bp/version-14-hotfix/pr-32971
Revert "fix: get `consumed_qty` based on `received_qty` in SCR" (backport #32971)
2022-11-15 15:59:45 +05:30
s-aga-r
f10cceb261 test: fix test cases for supplied-items consumed_qty
(cherry picked from commit 369db4eacc)
2022-11-15 09:48:02 +00:00
s-aga-r
7fd6c43752 Revert "fix: get consumed_qty based on received_qty in SCR"
This reverts commit 70c9b8dc50.

(cherry picked from commit 01f56c621c)
2022-11-15 09:48:02 +00:00
s-aga-r
0ecb44d40c Revert "fix: set received_qty before_validate SCR"
This reverts commit c447dfaa9c.

(cherry picked from commit 3706a9b4dc)
2022-11-15 09:48:02 +00:00
Gavin D'souza
30d1491257 chore(payment_entry): Remove dead validations
(cherry picked from commit e1ecc9a819)
2022-11-15 08:34:35 +00:00
Deepesh Garg
ca96c24c8d chore: Resolve conflicts 2022-11-15 13:57:44 +05:30
Deepesh Garg
07badbc0f2 fix: Write Off section visibility for non POS Invoices
(cherry picked from commit 9f5d613c78)

# Conflicts:
#	erpnext/accounts/doctype/sales_invoice/sales_invoice.json
2022-11-15 07:31:58 +00:00
Deepesh Garg
51b3fda2b6 Merge pull request #32960 from frappe/mergify/bp/version-14-hotfix/pr-32956
fix: Label for applicable dimension table (backport #32956)
2022-11-15 13:01:23 +05:30
rohitwaghchaure
d9820af4f0 Merge pull request #32963 from frappe/mergify/bp/version-14-hotfix/pr-32947
fix: incorrect fix of conversion factor in PP (backport #32947)
2022-11-15 11:38:07 +05:30
Rohit Waghchaure
c48d00ad77 fix: incorrect fix of conversion factor in PP
(cherry picked from commit 490b0e3cdf)
2022-11-15 05:01:36 +00:00
Deepesh Garg
eb4f8e4bd8 fix: Label for applicable dimension table
(cherry picked from commit 8c13f70fc5)
2022-11-15 03:55:20 +00:00
Sagar Sharma
5c5cd7bff5 Merge pull request #32948 from frappe/mergify/bp/version-14-hotfix/pr-32937
refactor: rewrite `job_card.py` queries in QB (backport #32937)
2022-11-14 12:22:56 +05:30
s-aga-r
f886577abb refactor: rewrite job_card.py queries in QB
(cherry picked from commit 7df2921d38)
2022-11-14 05:56:38 +00:00
Deepesh Garg
bad4dccf88 Merge pull request #32944 from frappe/mergify/bp/version-14-hotfix/pr-32938
chore: Remove raw SQL query (backport #32938)
2022-11-14 10:43:50 +05:30
Sagar Vora
a30579393e fix: check type for reference name
(cherry picked from commit b06345af46)
2022-11-13 15:12:10 +00:00
Deepesh Garg
3614584a2f chore: Remove qb doc reference
(cherry picked from commit 4b9921782b)
2022-11-13 15:12:10 +00:00
Deepesh Garg
8a01da3b9e chore: Remove raw SQL query
(cherry picked from commit 42a59d5c17)
2022-11-13 15:12:10 +00:00
Deepesh Garg
94087e4e3a Merge pull request #32941 from frappe/mergify/bp/version-14-hotfix/pr-32866
fix: incorrect buying amount on Gross Profit (backport #32866)
2022-11-13 19:47:57 +05:30
ruthra kumar
a24f6a5ac7 test: buying amount of invoices
1. Invoice with unset `update_stock`, with and without Delivery Notes

(cherry picked from commit 2c8b0b17a7)
2022-11-13 13:46:08 +00:00
ruthra kumar
2d8f00afad fix: GP incorrect buying amount if no upd on SI and Delivery Note
(cherry picked from commit e4d16c31da)
2022-11-13 13:46:08 +00:00
Sagar Sharma
9e47371801 Merge pull request #32935 from frappe/mergify/bp/version-14-hotfix/pr-32913
fix: set stock UOM in args to ensure item price is fetched (backport #32913)
2022-11-12 12:04:26 +05:30
Sagar Vora
a4187b9d8f fix: set stock UOM in args to ensure item price is fetched
(cherry picked from commit 57038c3969)
2022-11-12 04:39:25 +00:00
Deepesh Garg
3dde050c15 Merge pull request #32932 from frappe/mergify/bp/version-14-hotfix/pr-32878
fix: repayment schedule regeneration (backport #32878)
2022-11-11 15:15:52 +05:30
Abhinav Raut
2f5033b70f fix: repayment schedule regeneration
(cherry picked from commit d6ab2b3b87)
2022-11-11 08:53:59 +00:00
Deepesh Garg
de68674933 Merge pull request #32927 from frappe/mergify/bp/version-14-hotfix/pr-32880
fix: add translate function to valitate company msg in chart of accounts importer (backport #32880)
2022-11-11 14:21:53 +05:30
Deepesh Garg
04cf3d1c5d Merge pull request #32926 from frappe/mergify/bp/version-14-hotfix/pr-32923
fix: Maintain same rate between Quotation and Sales Order (backport #32923)
2022-11-11 14:21:38 +05:30
rohitwaghchaure
eac1f47b6f Merge pull request #32928 from frappe/mergify/bp/version-14-hotfix/pr-32895
fix: Purchase Receipt timeout error (backport #32895)
2022-11-11 10:52:52 +05:30
Rohit Waghchaure
071ee5d81c fix: test cases
(cherry picked from commit 7278387879)
2022-11-10 17:13:36 +00:00
Rohit Waghchaure
0d5b7269d4 fix: Purchase Receipt timeout error
(cherry picked from commit 4082149f0e)
2022-11-10 17:13:35 +00:00
Ernesto Ruiz
8de4430662 fix: add translate function to valitate company msg in chart of accounts importer
(cherry picked from commit 637c08d189)
2022-11-10 14:38:22 +00:00
Deepesh Garg
6c155d2825 fix: Maintain same rate between Quotation and Sales Order
(cherry picked from commit 362ec7b673)
2022-11-10 14:23:54 +00:00
Deepesh Garg
6f61685510 Merge pull request #32924 from frappe/mergify/bp/version-14-hotfix/pr-32912
fix(ux): Tab break in Customer and Supplier form (backport #32912)
2022-11-10 19:51:27 +05:30
Sagar Sharma
883355adc4 Merge pull request #32919 from frappe/mergify/bp/version-14-hotfix/pr-32918
fix: WO Skip Material Transfer to WIP Warehouse (backport #32918)
2022-11-10 18:42:36 +05:30
Nabin Hait
eeaa9329f6 fix(ux): Tab break in Customer and Supplier form
(cherry picked from commit fb7ee301b5)
2022-11-10 13:01:14 +00:00
Sagar Sharma
c294652dab fix: set WIP Warehouse in Job Card
(cherry picked from commit e7fa2e08ad)
2022-11-10 11:42:53 +00:00
Sagar Sharma
f923183b64 fix: don't set WIP Warehouse if is checked in WO
(cherry picked from commit 9730cd0aec)
2022-11-10 11:42:53 +00:00
Deepesh Garg
f2a1596369 Merge pull request #32909 from frappe/mergify/bp/version-14-hotfix/pr-32742
feat: Tool to repost PLE manually (backport #32742)
2022-11-10 15:41:22 +05:30
ruthra kumar
7a3e3af0b5 chore: CI fix 2022-11-10 14:17:00 +05:30
mergify[bot]
f8b7cfa6dd refactor: Remove usage of deprecated methods (backport #32914) (#32915)
* refactor: Remove usage of deprecated methods (#32914)

Warn: Just used regex to replace all usage.
```regex
s/frappe.db.set(\(.*\),\(.*\),\(.*\))/\1.db_set(\2, \3)/g
```

Required after: https://github.com/frappe/frappe/pull/18815

(cherry picked from commit 7e1742956c)

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

* chore: conflicts

* style: black


Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-11-10 11:09:13 +05:30
ruthra kumar
de59b50407 feat: Repost Payment Ledger entries for vouchers
primarily intended to manually correct PLE entries for vouchers
affected by Item Value repost-https://github.com/frappe/erpnext/pull/32567

(cherry picked from commit 0448c0fa36)
2022-11-09 08:57:23 +00:00
ruthra kumar
a399e2d765 refactor: split delete gl utility function into two
(cherry picked from commit 9209ec59c2)
2022-11-09 08:57:23 +00:00
Deepesh Garg
17ccc0e56c Merge pull request #32891 from frappe/mergify/bp/version-14-hotfix/pr-32883
fix: Project filter in timesheet (backport #32883)
2022-11-08 21:44:59 +05:30
Sagar Sharma
5e6a9833c5 Merge pull request #32898 from frappe/mergify/bp/version-14-hotfix/pr-32888
chore: link SCR Return in SCR Dashboard (backport #32888)
2022-11-08 21:38:27 +05:30
Sagar Sharma
02bb523210 chore: link SCR Return in SCR Dashboard
(cherry picked from commit 47248251e2)
2022-11-08 16:07:34 +00:00
Frappe PR Bot
e917212849 chore(release): Bumped to Version 14.6.0
# [14.6.0](https://github.com/frappe/erpnext/compare/v14.5.1...v14.6.0) (2022-11-08)

### Bug Fixes

* `Material Consumption` option in case of `Skip Transfer to WIP` in WO ([8c856cd](8c856cd5fc))
* add translate function to name of chart labels in budget_variance_report.py ([16f364d](16f364da37))
* add translate function to name of chart labels in deferred_revenue_and_expense.py ([b8caa58](b8caa587d2))
* add translate function to period in  stock_analytics.py ([b0c06d5](b0c06d5a04))
* add translate function to period in sales_analytics.py ([e681f06](e681f06883))
* add translate function to string on budget_variance_report.js to match the variance  word translated ([595aaad](595aaad99d))
* Auto advance allocation against partial invoices ([b7763d9](b7763d953a))
* auto increment qty if item table has no items ([d8e403b](d8e403bf5d))
* conflicts ([ab87a95](ab87a950e5))
* correct linters ([8f6f9a4](8f6f9a429a))
* correct linters ([440e208](440e20859f))
* correct linters ([5acc9be](5acc9be5c9))
* Create POS Opening Entry POS Profile filter. ([60af9c0](60af9c0516))
* Disable tax included prices for internal transfers ([#32794](https://github.com/frappe/erpnext/issues/32794)) ([6838e5e](6838e5ea3b))
* for asset's purchase_date, if bill_date is set, use that instead of posting_date ([01a1c96](01a1c96314))
* get `consumed_qty` based on `received_qty` in SCR ([ea9a502](ea9a50278d))
* Increase columns width in Warehouse wise Item Balance Age and Value ([0b09c31](0b09c31cb0))
* linter ([af60c8f](af60c8f759))
* make `BOM` required in SCR Item ([3f79a05](3f79a057e4))
* make `consumed_qty` editable when backflush based on Material Transfer ([2c5a8c4](2c5a8c43f6))
* make `consumed_qty` read-only in SCR Supplied Items ([68229f0](68229f06d1))
* map `BOM` while mapping the return SCR ([e629cba](e629cba2b7))
* mysql syntax issue ([4d9bbd4](4d9bbd4c9c))
* not able to select customer / supplier ([6989cdf](6989cdf4f2))
* refactor code for better translatable string ([2dc24f2](2dc24f22ea))
* refactor code for better translatable string in stock_ageing.py ([0ead516](0ead51642f))
* rename test method ([97445d9](97445d9516))
* Scan Barcode UX ([1944f4d](1944f4df4d))
* set `received_qty` before_validate SCR ([e316558](e316558286))
* test cases ([0feec4c](0feec4ca8a))
* trailing whitespace ([31bada9](31bada9205))
* update advance paid in SO/PO from Payment Ledger ([a561432](a561432908))
* use `flt` instead of `cint` in `get_batch_no` ([6510464](6510464482))

### Features

* Item Wise TDS Calculation ([b9fb104](b9fb1045d7))

### Performance Improvements

* use `get_cached_value` instead of `db.get_value` in controllers ([#32776](https://github.com/frappe/erpnext/issues/32776)) ([34ca17a](34ca17ab11))
2022-11-08 12:15:30 +00:00
Deepesh Garg
4657f8e9ed Merge pull request #32893 from frappe/version-14-hotfix
chore: release v14
2022-11-08 17:43:43 +05:30
Deepesh Garg
19b3152e32 chore: Resolve conflicts 2022-11-08 17:42:29 +05:30
Deepesh Garg
6163a052c8 chore: Linting Issues
(cherry picked from commit 7b5cf6978e)

# Conflicts:
#	erpnext/manufacturing/doctype/workstation/workstation.py
2022-11-08 09:26:03 +00:00
Deepesh Garg
37bed12df4 fix: Project filter in timesheet
(cherry picked from commit 2b65b22aa2)
2022-11-08 09:26:02 +00:00
Sagar Sharma
1426de0530 Merge pull request #32887 from frappe/mergify/bp/version-14-hotfix/pr-32886
chore: remove `debugger` from `stock_entry_list.js` (backport #32886)
2022-11-08 12:14:22 +05:30
Sagar Sharma
beef0510ee chore: remove debugger from stock_entry_list.js
(cherry picked from commit 84ab100d86)
2022-11-08 06:40:55 +00:00
Sagar Sharma
bacd7ecb1d Merge pull request #32884 from frappe/mergify/bp/version-14-hotfix/pr-32877
fix: make `consumed_qty` read-only in SCR Supplied Items (backport #32877)
2022-11-08 09:51:16 +05:30
Sagar Sharma
af60c8f759 fix: linter
(cherry picked from commit 5e8a22be24)
2022-11-07 23:05:58 +05:30
Sagar Sharma
2c5a8c43f6 fix: make consumed_qty editable when backflush based on Material Transfer
(cherry picked from commit bf4b012cec)
2022-11-07 17:29:45 +00:00
Sagar Sharma
68229f06d1 fix: make consumed_qty read-only in SCR Supplied Items
(cherry picked from commit f8d2e276a5)
2022-11-07 17:29:45 +00:00
Deepesh Garg
cc92e6965a Merge pull request #32864 from frappe/mergify/bp/version-14-hotfix/pr-32846
fix: add german translations (backport #32846)
2022-11-07 18:43:06 +05:30
Raffael Meyer
85f98bb51a Merge branch 'version-14-hotfix' into mergify/bp/version-14-hotfix/pr-32846 2022-11-07 13:34:28 +01:00
barredterra
8e00e62616 chore: resolve merge conflicts 2022-11-07 13:31:54 +01:00
Deepesh Garg
401979bbe5 Merge pull request #32875 from frappe/mergify/bp/version-14-hotfix/pr-32874
fix: Increase columns width in Warehouse wise Item Balance Age and Value (backport #32874)
2022-11-07 15:41:05 +05:30
Nihantra C. Patel
0b09c31cb0 fix: Increase columns width in Warehouse wise Item Balance Age and Value
(cherry picked from commit 8355c1092c)
2022-11-07 08:05:38 +00:00
Deepesh Garg
c2da8cf7cd Merge pull request #32870 from frappe/mergify/bp/version-14-hotfix/pr-32776
perf: use `get_cached_value` instead of `db.get_value` in controllers (backport #32776)
2022-11-07 13:30:15 +05:30
Deepesh Garg
d36c135103 Merge pull request #32872 from frappe/mergify/bp/version-14-hotfix/pr-32802
fix: `Material Consumption` option in case of `Skip Transfer to WIP` in WO (backport #32802)
2022-11-07 10:16:10 +05:30
Sagar Sharma
8c856cd5fc fix: Material Consumption option in case of Skip Transfer to WIP in WO
(cherry picked from commit 8ea6983734)
2022-11-07 04:06:34 +00:00
Daizy Modi
34ca17ab11 perf: use get_cached_value instead of db.get_value in controllers (#32776)
(cherry picked from commit 4efc947f14)
2022-11-07 03:52:39 +00:00
Sagar Sharma
3d2e8c7fb3 Merge pull request #32868 from frappe/mergify/bp/version-14-hotfix/pr-32867
fix: get `consumed_qty` based on `received_qty` in SCR (backport #32867)
2022-11-06 17:04:34 +05:30
Sagar Sharma
8f78be8525 test: add test case for consumed-qty
(cherry picked from commit 4d8da4420e)
2022-11-06 10:48:35 +00:00
Sagar Sharma
ea9a50278d fix: get consumed_qty based on received_qty in SCR
(cherry picked from commit 70c9b8dc50)
2022-11-06 10:48:34 +00:00
Sagar Sharma
e316558286 fix: set received_qty before_validate SCR
(cherry picked from commit c447dfaa9c)
2022-11-06 10:48:34 +00:00
Deepesh Garg
b9fb1045d7 feat: Item Wise TDS Calculation 2022-11-06 11:50:20 +05:30
Raffael Meyer
e276a5ba83 chore: add german translations (#32846)
Mostly for balance sheet

(cherry picked from commit d2b6490bca)

# Conflicts:
#	erpnext/translations/de.csv
2022-11-06 04:53:21 +00:00
Deepesh Garg
1a0f123c8c Merge pull request #32859 from frappe/mergify/bp/version-14-hotfix/pr-32794
fix: Disable tax included prices for internal transfers (backport #32794)
2022-11-05 21:16:44 +05:30
Deepesh Garg
6838e5ea3b fix: Disable tax included prices for internal transfers (#32794)
* fix: Disable tax-included prices for internal transfers

(cherry picked from commit 8d30ebb12b)
2022-11-05 15:22:07 +00:00
Deepesh Garg
41a5905a75 Merge pull request #32856 from frappe/mergify/bp/version-14-hotfix/pr-32847
fix: Create POS Opening Entry POS Profile filter. (backport #32847)
2022-11-05 20:51:49 +05:30
Sagar Sharma
eb6b267001 Merge pull request #32858 from frappe/mergify/bp/version-14-hotfix/pr-32850
fix: wrong consumed items in SCR return (backport #32850)
2022-11-05 20:12:54 +05:30
Sagar Sharma
0856e65700 test: fix test case
(cherry picked from commit 324bfa9fde)
2022-11-05 11:53:50 +00:00
Sagar Sharma
3f79a057e4 fix: make BOM required in SCR Item
(cherry picked from commit 760c26e9c0)
2022-11-05 11:53:50 +00:00
Sagar Sharma
bd7435ce1e test: add test case
(cherry picked from commit 761e9df1bf)
2022-11-05 11:53:50 +00:00
Sagar Sharma
97445d9516 fix: rename test method
(cherry picked from commit 611d827e0b)
2022-11-05 11:53:49 +00:00
Sagar Sharma
e629cba2b7 fix: map BOM while mapping the return SCR
(cherry picked from commit 54072ec9cd)
2022-11-05 11:53:49 +00:00
Deepesh Garg
cc8a184666 Merge pull request #32855 from frappe/mergify/bp/version-14-hotfix/pr-32844
fix: Auto advance allocation against partial invoices (backport #32844)
2022-11-05 16:59:03 +05:30
Maharshi Patel
60af9c0516 fix: Create POS Opening Entry POS Profile filter.
pos_profile_query was variable instead of function.

(cherry picked from commit 1328a45f2a)
2022-11-05 11:15:32 +00:00
Deepesh Garg
c3b2629412 test: Check parital payment allocation
(cherry picked from commit 428971f127)
2022-11-05 10:58:42 +00:00
Deepesh Garg
b7763d953a fix: Auto advance allocation against partial invoices
(cherry picked from commit 181df2fe63)
2022-11-05 10:58:42 +00:00
Sagar Sharma
3bd024ca8f Merge pull request #32831 from bhavesh95863/patch-2
fix: mysql syntax issue
2022-11-05 11:30:14 +05:30
Sagar Sharma
d443925b1e Merge branch 'version-14-hotfix' into patch-2 2022-11-05 10:29:11 +05:30
Sagar Sharma
2f145f9912 refactor: rewrite query in QB 2022-11-05 10:28:06 +05:30
rohitwaghchaure
d9147c1975 Merge pull request #32841 from frappe/mergify/bp/version-14-hotfix/pr-32799
fix: Scan Barcode UX (backport #32799)
2022-11-04 12:09:36 +05:30
rohitwaghchaure
31bada9205 fix: trailing whitespace 2022-11-04 11:28:41 +05:30
rohitwaghchaure
ab87a950e5 fix: conflicts 2022-11-04 11:21:39 +05:30
Rohit Waghchaure
d8e403bf5d fix: auto increment qty if item table has no items
(cherry picked from commit e5b19e3f70)
2022-11-04 05:36:09 +00:00
Rohit Waghchaure
1944f4df4d fix: Scan Barcode UX
(cherry picked from commit e1f9ba78e5)

# Conflicts:
#	erpnext/public/js/utils/barcode_scanner.js
2022-11-04 05:36:09 +00:00
Deepesh Garg
7e15c4789d Merge pull request #32840 from frappe/mergify/bp/version-14-hotfix/pr-32712
chore: add translation  function to Bank Reconciliation Tool-related files (backport #32712)
2022-11-04 08:33:44 +05:30
Ernesto Ruiz
e334b7dfee chore: add translation function to Bank Reconciliation Tool related files
chore: add translation  function to Bank Reconciliation Tool related files
(cherry picked from commit ad0dd693ac)
2022-11-03 19:36:56 +00:00
Frappe PR Bot
3967773fbe chore(release): Bumped to Version 14.5.1
## [14.5.1](https://github.com/frappe/erpnext/compare/v14.5.0...v14.5.1) (2022-11-03)

### Bug Fixes

* not able to select customer / supplier ([dd4dbd4](dd4dbd4b00))
2022-11-03 08:07:36 +00:00
Deepesh Garg
074c75252a Merge pull request #32835 from frappe/mergify/bp/version-14/pr-32833
fix: not able to select customer / supplier (backport #32832) (backport #32833)
2022-11-03 13:35:52 +05:30
Rohit Waghchaure
dd4dbd4b00 fix: not able to select customer / supplier
(cherry picked from commit b0fc568c80)
(cherry picked from commit 6989cdf4f2)
2022-11-03 06:53:41 +00:00
rohitwaghchaure
2d2a126510 Merge pull request #32833 from frappe/mergify/bp/version-14-hotfix/pr-32832
fix: not able to select customer / supplier (backport #32832)
2022-11-03 12:22:41 +05:30
Deepesh Garg
a10ea5efc9 Merge pull request #32830 from frappe/mergify/bp/version-14-hotfix/pr-32773
fix: for asset's purchase_date, if bill_date is set, use that instead of posting_date (backport #32773)
2022-11-03 12:07:35 +05:30
Rohit Waghchaure
6989cdf4f2 fix: not able to select customer / supplier
(cherry picked from commit b0fc568c80)
2022-11-03 06:25:53 +00:00
Anand Baburajan
f5534d7db3 Merge branch 'version-14-hotfix' into mergify/bp/version-14-hotfix/pr-32773 2022-11-03 11:53:55 +05:30
Bhavesh Maheshwari
4d9bbd4c9c fix: mysql syntax issue 2022-11-03 10:59:20 +05:30
Deepesh Garg
7a7b1d33c3 Merge pull request #32829 from frappe/mergify/bp/version-14-hotfix/pr-32713
fix: add missing translation function on report related documents (backport #32713)
2022-11-03 10:29:00 +05:30
anandbaburajan
01a1c96314 fix: for asset's purchase_date, if bill_date is set, use that instead of posting_date
(cherry picked from commit f322c608cf)
2022-11-03 04:22:14 +00:00
Ernesto Ruiz
8f6f9a429a fix: correct linters
(cherry picked from commit 4c7fa9482d)
2022-11-03 04:17:23 +00:00
Ernesto Ruiz
440e20859f fix: correct linters
(cherry picked from commit 9c529c61bb)
2022-11-03 04:17:22 +00:00
Ernesto Ruiz
5acc9be5c9 fix: correct linters
(cherry picked from commit b7b53b5857)
2022-11-03 04:17:22 +00:00
Ernesto Ruiz
595aaad99d fix: add translate function to string on budget_variance_report.js to match the variance word translated
(cherry picked from commit 2012bdf4bd)
2022-11-03 04:17:22 +00:00
Ernesto Ruiz
16f364da37 fix: add translate function to name of chart labels in budget_variance_report.py
(cherry picked from commit 48ed6381b3)
2022-11-03 04:17:21 +00:00
Ernesto Ruiz
e681f06883 fix: add translate function to period in sales_analytics.py
(cherry picked from commit 083a78135c)
2022-11-03 04:17:21 +00:00
Ernesto Ruiz
b0c06d5a04 fix: add translate function to period in stock_analytics.py
(cherry picked from commit c1e608d9ef)
2022-11-03 04:17:20 +00:00
Ernesto Ruiz
0ead51642f fix: refactor code for better translatable string in stock_ageing.py
(cherry picked from commit 71a0ae2e59)
2022-11-03 04:17:20 +00:00
Ernesto Ruiz
2dc24f22ea fix: refactor code for better translatable string
(cherry picked from commit a671652ab2)
2022-11-03 04:17:20 +00:00
Ernesto Ruiz
b8caa587d2 fix: add translate function to name of chart labels in deferred_revenue_and_expense.py
(cherry picked from commit a963618b08)
2022-11-03 04:17:19 +00:00
ruthra kumar
b17761c276 Merge pull request #32822 from frappe/mergify/bp/version-14-hotfix/pr-32816
fix: update advance paid in SO/PO from Payment Ledger (backport #32816)
2022-11-02 19:31:33 +05:30
Sagar Sharma
d8c0a147db Merge pull request #32820 from frappe/mergify/bp/version-14-hotfix/pr-32788
fix: use `flt` instead of `cint` in `get_batch_no` (backport #32788)
2022-11-02 19:11:08 +05:30
Sagar Sharma
611dcc11d2 Merge branch 'version-14-hotfix' into mergify/bp/version-14-hotfix/pr-32788 2022-11-02 17:49:26 +05:30
Sagar Sharma
75d347d757 Merge pull request #32823 from frappe/mergify/bp/version-14-hotfix/pr-32800
fix: test cases (backport #32800)
2022-11-02 17:49:01 +05:30
Rohit Waghchaure
d3fbe3074d test: run tmate
(cherry picked from commit 3f2728e3f7)
2022-11-02 11:44:05 +00:00
Rohit Waghchaure
0feec4ca8a fix: test cases
(cherry picked from commit ddd1b4be3f)
2022-11-02 11:44:05 +00:00
ruthra kumar
cf32e1905c test: SO advance paid on Payment submission and cancellation
(cherry picked from commit 721ac6b847)
2022-11-02 10:59:47 +00:00
ruthra kumar
87dc812a4b test: PO advance paid on payment submission and cancellation
(cherry picked from commit 1a0a8ac7e2)
2022-11-02 10:59:46 +00:00
ruthra kumar
388cf5113b test: refactor use @change_settings decorator when possible
(cherry picked from commit 81d791eea0)
2022-11-02 10:59:46 +00:00
ruthra kumar
a561432908 fix: update advance paid in SO/PO from Payment Ledger
(cherry picked from commit 4487065b67)
2022-11-02 10:59:45 +00:00
Sagar Sharma
6510464482 fix: use flt instead of cint in get_batch_no
(cherry picked from commit 9fb3fb4c83)
2022-11-02 10:25:26 +00:00
Frappe PR Bot
ce5dbf891a chore(release): Bumped to Version 14.5.0
# [14.5.0](https://github.com/frappe/erpnext/compare/v14.4.0...v14.5.0) (2022-11-01)

### Bug Fixes

* add `Sales Order` reference in Material Request Dashboard ([fc63892](fc6389280c))
* Add condition for discount section collapse ([953f78d](953f78d6a9))
* Budget validation for main cost center ([89a1c83](89a1c83431))
* Clear invoice table post importing invoices ([6eafff8](6eafff8694))
* Company bank account filter in Bank Clearance ([797512c](797512ca13))
* Curreny in SOA print for multi-currency party ([195500c](195500cb32))
* duplicate custom fields for inventory dimension ([1152ac3](1152ac3ff1))
* Filter fixes in Accounts Payable report ([29197dc](29197dcd7f))
* Issues while cancel/amending Purchase Invoice with TDS enabled ([74a6479](74a6479f70))
* key error in filter access ([6114241](6114241ff2))
* Mode of payment for returns in POS Sales Invoice ([a260426](a260426dd4))
* Pass project to stock entry items ([4035873](4035873295))
* pro_rata_amount calculation in assets tests ([d1b2786](d1b2786f24))
* Reference due date field type in Journal Entry Accounts table ([faf25c0](faf25c0b95))
* Reset advance paid amount on Oreder cancel and amend ([34bd783](34bd7837e2))
* Total Sales amount update in project via Sales Order ([d742e6d](d742e6d56b))

### Features

* additional filters on Payment terms report ([a03ec0a](a03ec0afb3))
* **pricing rule:** free qty rounding and recursion qty ([#32577](https://github.com/frappe/erpnext/issues/32577)) ([9b66020](9b66020fc7))
2022-11-01 17:19:41 +00:00
Deepesh Garg
fd36ee0fda Merge pull request #32793 from frappe/version-14-hotfix
chore: release v14
2022-11-01 22:48:17 +05:30
Deepesh Garg
d68fa0eff2 Merge pull request #32807 from frappe/mergify/bp/version-14-hotfix/pr-32779
fix: Mode of payment for returns in POS Sales Invoice (backport #32779)
2022-11-01 22:12:11 +05:30
Deepesh Garg
3f72156ea9 chore: Update tests
(cherry picked from commit 5b74161195)
2022-11-01 16:00:18 +00:00
Deepesh Garg
a260426dd4 fix: Mode of payment for returns in POS Sales Invoice
(cherry picked from commit 06e8e28531)
2022-11-01 16:00:18 +00:00
Deepesh Garg
148dc37d92 Merge pull request #32804 from frappe/mergify/bp/version-14-hotfix/pr-32801
fix: Issues while cancel/amending Purchase Invoice with TDS enabled (backport #32801)
2022-11-01 21:29:17 +05:30
Deepesh Garg
74a6479f70 fix: Issues while cancel/amending Purchase Invoice with TDS enabled
(cherry picked from commit f7c9258770)
2022-11-01 15:19:10 +00:00
ruthra kumar
f2f1c160f8 Merge pull request #32797 from frappe/mergify/bp/version-14-hotfix/pr-32577
feat(pricing rule): free qty rounding and recursion qty (backport #32577)
2022-11-01 17:09:46 +05:30
Dany Robert
9b66020fc7 feat(pricing rule): free qty rounding and recursion qty (#32577)
Option to specify recursion start qty and repeating qty

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
(cherry picked from commit 1d83fb20d6)
2022-11-01 11:12:44 +00:00
Deepesh Garg
6af43ba40d Merge pull request #32795 from frappe/mergify/bp/version-14-hotfix/pr-32790
fix: pro_rata_amount calculation in assets tests (backport #32790)
2022-11-01 16:19:49 +05:30
Anand Baburajan
bd531eb898 Merge branch 'version-14-hotfix' into mergify/bp/version-14-hotfix/pr-32790 2022-11-01 15:21:29 +05:30
Deepesh Garg
15f4812108 Merge pull request #32782 from frappe/mergify/bp/version-14-hotfix/pr-32768
fix: Budget validation for main cost center (backport #32768)
2022-11-01 15:21:16 +05:30
anandbaburajan
4c5b688e0d chore: empty commit to try fixing stuck test
(cherry picked from commit 672fbd3849)
2022-11-01 09:48:04 +00:00
anandbaburajan
d1b2786f24 fix: pro_rata_amount calculation in assets tests
(cherry picked from commit 65e855bfff)
2022-11-01 09:48:04 +00:00
rohitwaghchaure
2eb01aae27 Merge pull request #32783 from frappe/mergify/bp/version-14-hotfix/pr-32730
fix: duplicate custom fields for inventory dimension (backport #32730)
2022-10-31 23:06:29 +05:30
Rohit Waghchaure
1152ac3ff1 fix: duplicate custom fields for inventory dimension
(cherry picked from commit 45ededbed5)
2022-10-31 16:56:17 +00:00
Deepesh Garg
89a1c83431 fix: Budget validation for main cost center
(cherry picked from commit 4e26d42d17)
2022-10-31 16:49:31 +00:00
Deepesh Garg
2a1a61238a Merge pull request #32780 from frappe/mergify/bp/version-14-hotfix/pr-32777
fix: Reset advance paid amount on Order cancel and amend (backport #32777)
2022-10-31 22:07:54 +05:30
Deepesh Garg
34bd7837e2 fix: Reset advance paid amount on Oreder cancel and amend
(cherry picked from commit 92f37ca111)
2022-10-31 16:09:27 +00:00
Deepesh Garg
f66a6693f4 Merge pull request #32761 from frappe/mergify/bp/version-14-hotfix/pr-32759
Pass project to stock entry items (backport #32759)
2022-10-30 09:29:03 +05:30
Hossein Yousefian
4035873295 fix: Pass project to stock entry items
fix: Pass project to stock entry items
(cherry picked from commit 54c2ffc36b)
2022-10-29 16:55:25 +00:00
Deepesh Garg
4a86efedfc Merge pull request #32752 from frappe/mergify/bp/version-14-hotfix/pr-32744
refactor: additional filters on Payment Terms status report (backport #32744)
2022-10-29 17:12:17 +05:30
Sagar Sharma
b7cbc804b2 Merge pull request #32756 from frappe/mergify/bp/version-14-hotfix/pr-32754
fix: add `Sales Order` reference in Material Request Dashboard (backport #32754)
2022-10-29 13:49:16 +05:30
Sagar Sharma
fc6389280c fix: add Sales Order reference in Material Request Dashboard
(cherry picked from commit 15ebf4a0cf)
2022-10-29 07:07:25 +00:00
ruthra kumar
6dbb4a3b0d test: due date filter on Payment Terms report
(cherry picked from commit fed39a53cb)
2022-10-29 05:40:16 +00:00
ruthra kumar
6114241ff2 fix: key error in filter access
(cherry picked from commit 4765f937ea)
2022-10-29 05:40:16 +00:00
ruthra kumar
a03ec0afb3 feat: additional filters on Payment terms report
Filter on Status and Due dates

(cherry picked from commit aadb6b1772)
2022-10-29 05:40:15 +00:00
Deepesh Garg
db5d74b7dc Merge pull request #32748 from frappe/mergify/bp/version-14-hotfix/pr-32722
fix: Reference due date field type in Journal Entry Accounts table (backport #32722)
2022-10-29 11:00:01 +05:30
Deepesh Garg
ad6e0f3af7 Merge pull request #32746 from frappe/mergify/bp/version-14-hotfix/pr-32699
fix: Curreny in SOA print for multi-currency party (backport #32699)
2022-10-29 10:59:49 +05:30
Deepesh Garg
abcf30aaa3 Merge pull request #32745 from frappe/mergify/bp/version-14-hotfix/pr-32692
fix: Clear invoice table post importing invoices (backport #32692)
2022-10-29 10:59:35 +05:30
Deepesh Garg
cc9c876007 Merge pull request #32747 from frappe/mergify/bp/version-14-hotfix/pr-32718
fix: Total Sales amount update in project via Sales Order (backport #32718)
2022-10-29 10:59:10 +05:30
Sagar Sharma
199e77faef Merge pull request #32750 from frappe/mergify/bp/version-14-hotfix/pr-32662
refactor: rewrite stock reports queries in qb (backport #32662)
2022-10-28 22:48:03 +05:30
Sagar Sharma
98428f0bce refactor: rewrite Itemwise Recommended Reorder Level Report queries in QB
(cherry picked from commit 40bd121593)
2022-10-28 15:36:01 +00:00
Sagar Sharma
a4b99a34cb refactor: rewrite Product Bundle Balance Report queries in QB
(cherry picked from commit cde785f1bb)
2022-10-28 15:36:01 +00:00
Sagar Sharma
c4587d8caa refactor: rewrite Stock Ledger Report queries in QB
(cherry picked from commit feaa2dbba8)
2022-10-28 15:36:01 +00:00
Deepesh Garg
faf25c0b95 fix: Reference due date field type in Journal Entry Accounts table
(cherry picked from commit e7caa48e2f)
2022-10-28 15:26:29 +00:00
Deepesh Garg
d742e6d56b fix: Total Sales amount update in project via Sales Order
(cherry picked from commit 6063c4e3c0)
2022-10-28 15:16:42 +00:00
Deepesh Garg
3f0b03c0a4 chore: Use account currency as fallback
(cherry picked from commit a18a715bb4)
2022-10-28 15:12:34 +00:00
Deepesh Garg
195500cb32 fix: Curreny in SOA print for multi-currency party
(cherry picked from commit 49ee873655)
2022-10-28 15:12:34 +00:00
Deepesh Garg
6eafff8694 fix: Clear invoice table post importing invoices
(cherry picked from commit 267e7c3a90)
2022-10-28 15:11:41 +00:00
Deepesh Garg
36f2b65401 Merge pull request #32739 from frappe/mergify/bp/version-14-hotfix/pr-32716
fix: Company bank account filter in Bank Clearance (backport #32716)
2022-10-28 17:19:49 +05:30
Sagar Sharma
f082c9e797 Merge pull request #32740 from frappe/mergify/bp/version-14-hotfix/pr-32738
fix: Added Material Request Reference in Purchase Recipt Dashboard for Tracking (backport #32738)
2022-10-28 16:06:44 +05:30
Vishal
0ab69c0e32 chore: minor linting issue fixed
(cherry picked from commit e8c0157017)
2022-10-28 08:54:44 +00:00
Vishal
de3996e411 chore: Added Material Request Reference in Purchase Recipt Dashboard for Tracking
(cherry picked from commit a04c44fe34)
2022-10-28 08:54:44 +00:00
Deepesh Garg
797512ca13 fix: Company bank account filter in Bank Clearance
(cherry picked from commit f9f78c1086)
2022-10-28 07:24:35 +00:00
Deepesh Garg
d39ec5ef5f Merge pull request #32737 from frappe/mergify/bp/version-14-hotfix/pr-32717
fix: Add condition for discount section collapse (backport #32717)
2022-10-28 11:55:37 +05:30
Deepesh Garg
2dae93c0a1 Merge pull request #32736 from frappe/mergify/bp/version-14-hotfix/pr-32724
fix: Filter fixes in Accounts Payable report (backport #32724)
2022-10-28 11:55:24 +05:30
Deepesh Garg
953f78d6a9 fix: Add condition for discount section collapse
(cherry picked from commit 4cd65027c4)
2022-10-28 05:55:06 +00:00
Deepesh Garg
29197dcd7f fix: Filter fixes in Accounts Payable report
(cherry picked from commit a5a73ba857)
2022-10-28 05:54:25 +00:00
136 changed files with 2883 additions and 1091 deletions

View File

@@ -2,7 +2,7 @@ import inspect
import frappe
__version__ = "14.4.0"
__version__ = "14.7.0"
def get_default_company(user=None):

View File

@@ -3,10 +3,6 @@
frappe.ui.form.on('Accounting Dimension Filter', {
refresh: function(frm, cdt, cdn) {
if (frm.doc.accounting_dimension) {
frm.set_df_property('dimensions', 'label', frm.doc.accounting_dimension, cdn, 'dimension_value');
}
let help_content =
`<table class="table table-bordered" style="background-color: var(--scrollbar-track-color);">
<tr><td>
@@ -68,6 +64,7 @@ frappe.ui.form.on('Accounting Dimension Filter', {
frm.clear_table("dimensions");
let row = frm.add_child("dimensions");
row.accounting_dimension = frm.doc.accounting_dimension;
frm.fields_dict["dimensions"].grid.update_docfield_property("dimension_value", "label", frm.doc.accounting_dimension);
frm.refresh_field("dimensions");
frm.trigger('setup_filters');
},

View File

@@ -4,6 +4,23 @@
frappe.ui.form.on("Bank Clearance", {
setup: function(frm) {
frm.add_fetch("account", "account_currency", "account_currency");
frm.set_query("account", function() {
return {
"filters": {
"account_type": ["in",["Bank","Cash"]],
"is_group": 0,
}
};
});
frm.set_query("bank_account", function () {
return {
filters: {
'is_company_account': 1
},
};
});
},
onload: function(frm) {
@@ -12,14 +29,7 @@ frappe.ui.form.on("Bank Clearance", {
locals[":Company"][frappe.defaults.get_user_default("Company")]["default_bank_account"]: "";
frm.set_value("account", default_bank_account);
frm.set_query("account", function() {
return {
"filters": {
"account_type": ["in",["Bank","Cash"]],
"is_group": 0
}
};
});
frm.set_value("from_date", frappe.datetime.month_start());
frm.set_value("to_date", frappe.datetime.month_end());

View File

@@ -43,20 +43,13 @@ frappe.ui.form.on('Bank Guarantee', {
reference_docname: function(frm) {
if (frm.doc.reference_docname && frm.doc.reference_doctype) {
let fields_to_fetch = ["grand_total"];
let party_field = frm.doc.reference_doctype == "Sales Order" ? "customer" : "supplier";
if (frm.doc.reference_doctype == "Sales Order") {
fields_to_fetch.push("project");
}
fields_to_fetch.push(party_field);
frappe.call({
method: "erpnext.accounts.doctype.bank_guarantee.bank_guarantee.get_vouchar_detials",
method: "erpnext.accounts.doctype.bank_guarantee.bank_guarantee.get_voucher_details",
args: {
"column_list": fields_to_fetch,
"doctype": frm.doc.reference_doctype,
"docname": frm.doc.reference_docname
"bank_guarantee_type": frm.doc.bg_type,
"reference_name": frm.doc.reference_docname
},
callback: function(r) {
if (r.message) {

View File

@@ -2,11 +2,8 @@
# For license information, please see license.txt
import json
import frappe
from frappe import _
from frappe.desk.search import sanitize_searchfield
from frappe.model.document import Document
@@ -25,14 +22,18 @@ class BankGuarantee(Document):
@frappe.whitelist()
def get_vouchar_detials(column_list, doctype, docname):
column_list = json.loads(column_list)
for col in column_list:
sanitize_searchfield(col)
return frappe.db.sql(
""" select {columns} from `tab{doctype}` where name=%s""".format(
columns=", ".join(column_list), doctype=doctype
),
docname,
as_dict=1,
)[0]
def get_voucher_details(bank_guarantee_type: str, reference_name: str):
if not isinstance(reference_name, str):
raise TypeError("reference_name must be a string")
fields_to_fetch = ["grand_total"]
if bank_guarantee_type == "Receiving":
doctype = "Sales Order"
fields_to_fetch.append("customer")
fields_to_fetch.append("project")
else:
doctype = "Purchase Order"
fields_to_fetch.append("supplier")
return frappe.db.get_value(doctype, reference_name, fields_to_fetch, as_dict=True)

View File

@@ -12,6 +12,9 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
},
};
});
let no_bank_transactions_text =
`<div class="text-muted text-center">${__("No Matching Bank Transactions Found")}</div>`
set_field_options("no_bank_transactions", no_bank_transactions_text);
},
onload: function (frm) {

View File

@@ -81,8 +81,7 @@
},
{
"fieldname": "no_bank_transactions",
"fieldtype": "HTML",
"options": "<div class=\"text-muted text-center\">No Matching Bank Transactions Found</div>"
"fieldtype": "HTML"
}
],
"hide_toolbar": 1,
@@ -109,4 +108,4 @@
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC"
}
}

View File

@@ -100,7 +100,7 @@ frappe.ui.form.on("Bank Statement Import", {
if (frm.doc.status.includes("Success")) {
frm.add_custom_button(
__("Go to {0} List", [frm.doc.reference_doctype]),
__("Go to {0} List", [__(frm.doc.reference_doctype)]),
() => frappe.set_route("List", frm.doc.reference_doctype)
);
}

View File

@@ -107,7 +107,7 @@ class Budget(Document):
self.naming_series = f"{{{frappe.scrub(self.budget_against)}}}./.{self.fiscal_year}/.###"
def validate_expense_against_budget(args):
def validate_expense_against_budget(args, expense_amount=0):
args = frappe._dict(args)
if args.get("company") and not args.fiscal_year:
@@ -175,13 +175,13 @@ def validate_expense_against_budget(args):
) # nosec
if budget_records:
validate_budget_records(args, budget_records)
validate_budget_records(args, budget_records, expense_amount)
def validate_budget_records(args, budget_records):
def validate_budget_records(args, budget_records, expense_amount):
for budget in budget_records:
if flt(budget.budget_amount):
amount = get_amount(args, budget)
amount = expense_amount or get_amount(args, budget)
yearly_action, monthly_action = get_actions(args, budget)
if monthly_action in ["Stop", "Warn"]:

View File

@@ -334,6 +334,39 @@ class TestBudget(unittest.TestCase):
budget.cancel()
jv.cancel()
def test_monthly_budget_against_main_cost_center(self):
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
from erpnext.accounts.doctype.cost_center_allocation.test_cost_center_allocation import (
create_cost_center_allocation,
)
cost_centers = [
"Main Budget Cost Center 1",
"Sub Budget Cost Center 1",
"Sub Budget Cost Center 2",
]
for cc in cost_centers:
create_cost_center(cost_center_name=cc, company="_Test Company")
create_cost_center_allocation(
"_Test Company",
"Main Budget Cost Center 1 - _TC",
{"Sub Budget Cost Center 1 - _TC": 60, "Sub Budget Cost Center 2 - _TC": 40},
)
make_budget(budget_against="Cost Center", cost_center="Main Budget Cost Center 1 - _TC")
jv = make_journal_entry(
"_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC",
400000,
"Main Budget Cost Center 1 - _TC",
posting_date=nowdate(),
)
self.assertRaises(BudgetError, jv.submit)
def set_total_expense_zero(posting_date, budget_against_field=None, budget_against_CC=None):
if budget_against_field == "project":

View File

@@ -52,7 +52,7 @@ def validate_company(company):
if parent_company and (not allow_account_creation_against_child_company):
msg = _("{} is a child company.").format(frappe.bold(company)) + " "
msg += _("Please import accounts against parent company or enable {} in company master.").format(
frappe.bold("Allow Account Creation Against Child Company")
frappe.bold(_("Allow Account Creation Against Child Company"))
)
frappe.throw(msg, title=_("Wrong Company"))

View File

@@ -312,8 +312,7 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro
}
}
get_outstanding(doctype, docname, company, child, due_date) {
var me = this;
get_outstanding(doctype, docname, company, child) {
var args = {
"doctype": doctype,
"docname": docname,

View File

@@ -1210,6 +1210,7 @@ def get_outstanding(args):
args = json.loads(args)
company_currency = erpnext.get_company_currency(args.get("company"))
due_date = None
if args.get("doctype") == "Journal Entry":
condition = " and party=%(party)s" if args.get("party") else ""
@@ -1234,10 +1235,12 @@ def get_outstanding(args):
invoice = frappe.db.get_value(
args["doctype"],
args["docname"],
["outstanding_amount", "conversion_rate", scrub(party_type)],
["outstanding_amount", "conversion_rate", scrub(party_type), "due_date"],
as_dict=1,
)
due_date = invoice.get("due_date")
exchange_rate = (
invoice.conversion_rate if (args.get("account_currency") != company_currency) else 1
)
@@ -1260,6 +1263,7 @@ def get_outstanding(args):
"exchange_rate": exchange_rate,
"party_type": party_type,
"party": invoice.get(scrub(party_type)),
"reference_due_date": due_date,
}

View File

@@ -216,7 +216,7 @@
{
"depends_on": "eval:doc.reference_type&&!in_list(doc.reference_type, ['Expense Claim', 'Asset', 'Employee Loan', 'Employee Advance'])",
"fieldname": "reference_due_date",
"fieldtype": "Select",
"fieldtype": "Date",
"label": "Reference Due Date",
"no_copy": 1
},
@@ -284,7 +284,7 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2022-10-13 17:07:17.999191",
"modified": "2022-10-26 20:03:10.906259",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry Account",

View File

@@ -22,13 +22,13 @@ frappe.ui.form.on('Opening Invoice Creation Tool', {
}
if (data.user != frappe.session.user) return;
if (data.count == data.total) {
setTimeout((title) => {
setTimeout(() => {
frm.doc.import_in_progress = false;
frm.clear_table("invoices");
frm.refresh_fields();
frm.page.clear_indicator();
frm.dashboard.hide_progress(title);
frappe.msgprint(__("Opening {0} Invoice created", [frm.doc.invoice_type]));
frm.dashboard.hide_progress();
frappe.msgprint(__("Opening {0} Invoices created", [frm.doc.invoice_type]));
}, 1500, data.title);
return;
}
@@ -51,13 +51,6 @@ frappe.ui.form.on('Opening Invoice Creation Tool', {
method: "make_invoices",
freeze: 1,
freeze_message: __("Creating {0} Invoice", [frm.doc.invoice_type]),
callback: function(r) {
if (r.message.length == 1) {
frappe.msgprint(__("{0} Invoice created successfully.", [frm.doc.invoice_type]));
} else if (r.message.length < 50) {
frappe.msgprint(__("{0} Invoices created successfully.", [frm.doc.invoice_type]));
}
}
});
});

View File

@@ -257,8 +257,6 @@ def start_import(invoices):
def publish(index, total, doctype):
if total < 50:
return
frappe.publish_realtime(
"opening_invoice_creation_progress",
dict(

View File

@@ -62,7 +62,6 @@ class PaymentEntry(AccountsController):
self.set_missing_values()
self.validate_payment_type()
self.validate_party_details()
self.validate_bank_accounts()
self.set_exchange_rate()
self.validate_mandatory()
self.validate_reference_documents()
@@ -243,23 +242,6 @@ class PaymentEntry(AccountsController):
if not frappe.db.exists(self.party_type, self.party):
frappe.throw(_("Invalid {0}: {1}").format(self.party_type, self.party))
if self.party_account and self.party_type in ("Customer", "Supplier"):
self.validate_account_type(
self.party_account, [erpnext.get_party_account_type(self.party_type)]
)
def validate_bank_accounts(self):
if self.payment_type in ("Pay", "Internal Transfer"):
self.validate_account_type(self.paid_from, ["Bank", "Cash"])
if self.payment_type in ("Receive", "Internal Transfer"):
self.validate_account_type(self.paid_to, ["Bank", "Cash"])
def validate_account_type(self, account, account_types):
account_type = frappe.db.get_value("Account", account, "account_type")
# if account_type not in account_types:
# frappe.throw(_("Account Type for {0} must be {1}").format(account, comma_or(account_types)))
def set_exchange_rate(self, ref_doc=None):
self.set_source_exchange_rate(ref_doc)
self.set_target_exchange_rate(ref_doc)

View File

@@ -8,6 +8,7 @@
"engine": "InnoDB",
"field_order": [
"barcode",
"has_item_scanned",
"item_code",
"col_break1",
"item_name",
@@ -808,11 +809,19 @@
"fieldtype": "Check",
"label": "Grant Commission",
"read_only": 1
},
{
"default": "0",
"depends_on": "barcode",
"fieldname": "has_item_scanned",
"fieldtype": "Check",
"label": "Has Item Scanned",
"read_only": 1
}
],
"istable": 1,
"links": [],
"modified": "2021-10-05 12:23:47.506290",
"modified": "2022-11-02 12:52:39.125295",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Invoice Item",
@@ -820,5 +829,6 @@
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC"
"sort_order": "DESC",
"states": []
}

View File

@@ -52,7 +52,10 @@
"free_item_rate",
"column_break_42",
"free_item_uom",
"round_free_qty",
"is_recursive",
"recurse_for",
"apply_recursion_over",
"section_break_23",
"valid_from",
"valid_upto",
@@ -578,12 +581,34 @@
"fieldtype": "Select",
"label": "Naming Series",
"options": "PRLE-.####"
},
{
"default": "0",
"fieldname": "round_free_qty",
"fieldtype": "Check",
"label": "Round Free Qty"
},
{
"depends_on": "is_recursive",
"description": "Give free item for every N quantity",
"fieldname": "recurse_for",
"fieldtype": "Float",
"label": "Recurse Every (As Per Transaction UOM)",
"mandatory_depends_on": "is_recursive"
},
{
"default": "0",
"depends_on": "is_recursive",
"description": "Qty for which recursion isn't applicable.",
"fieldname": "apply_recursion_over",
"fieldtype": "Float",
"label": "Apply Recursion Over (As Per Transaction UOM)"
}
],
"icon": "fa fa-gift",
"idx": 1,
"links": [],
"modified": "2022-09-16 16:00:38.356266",
"modified": "2022-10-13 19:05:35.056304",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Pricing Rule",

View File

@@ -24,6 +24,7 @@ class PricingRule(Document):
self.validate_applicable_for_selling_or_buying()
self.validate_min_max_amt()
self.validate_min_max_qty()
self.validate_recursion()
self.cleanup_fields_value()
self.validate_rate_or_discount()
self.validate_max_discount()
@@ -109,6 +110,18 @@ class PricingRule(Document):
if self.min_amt and self.max_amt and flt(self.min_amt) > flt(self.max_amt):
throw(_("Min Amt can not be greater than Max Amt"))
def validate_recursion(self):
if self.price_or_product_discount != "Product":
return
if self.free_item or self.same_item:
if flt(self.recurse_for) <= 0:
self.recurse_for = 1
if self.is_recursive:
if flt(self.apply_recursion_over) > flt(self.min_qty):
throw(_("Min Qty should be greater than Recurse Over Qty"))
if flt(self.apply_recursion_over) < 0:
throw(_("Recurse Over Qty cannot be less than 0"))
def cleanup_fields_value(self):
for logic_field in ["apply_on", "applicable_for", "rate_or_discount"]:
fieldname = frappe.scrub(self.get(logic_field) or "")

View File

@@ -943,6 +943,45 @@ class TestPricingRule(unittest.TestCase):
si.delete()
rule.delete()
def test_pricing_rule_for_product_free_item_rounded_qty_and_recursion(self):
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule")
test_record = {
"doctype": "Pricing Rule",
"title": "_Test Pricing Rule",
"apply_on": "Item Code",
"currency": "USD",
"items": [
{
"item_code": "_Test Item",
}
],
"selling": 1,
"rate": 0,
"min_qty": 3,
"max_qty": 7,
"price_or_product_discount": "Product",
"same_item": 1,
"free_qty": 1,
"round_free_qty": 1,
"is_recursive": 1,
"recurse_for": 2,
"company": "_Test Company",
}
frappe.get_doc(test_record.copy()).insert()
# With pricing rule
so = make_sales_order(item_code="_Test Item", qty=5)
so.load_from_db()
self.assertEqual(so.items[1].is_free_item, 1)
self.assertEqual(so.items[1].item_code, "_Test Item")
self.assertEqual(so.items[1].qty, 2)
so = make_sales_order(item_code="_Test Item", qty=7)
so.load_from_db()
self.assertEqual(so.items[1].is_free_item, 1)
self.assertEqual(so.items[1].item_code, "_Test Item")
self.assertEqual(so.items[1].qty, 4)
test_dependencies = ["Campaign"]

View File

@@ -627,9 +627,13 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None):
qty = pricing_rule.free_qty or 1
if pricing_rule.is_recursive:
transaction_qty = args.get("qty") if args else doc.total_qty
transaction_qty = (
args.get("qty") if args else doc.total_qty
) - pricing_rule.apply_recursion_over
if transaction_qty:
qty = flt(transaction_qty) * qty
qty = flt(transaction_qty) * qty / pricing_rule.recurse_for
if pricing_rule.round_free_qty:
qty = round(qty)
free_item_data_args = {
"item_code": free_item,

View File

@@ -569,6 +569,10 @@ frappe.ui.form.on("Purchase Invoice", {
erpnext.queries.setup_queries(frm, "Warehouse", function() {
return erpnext.queries.warehouse(frm.doc);
});
if (frm.is_new()) {
frm.clear_table("tax_withheld_vouchers");
}
},
is_subcontracted: function(frm) {

View File

@@ -57,6 +57,8 @@
"column_break_28",
"total",
"net_total",
"tax_withholding_net_total",
"base_tax_withholding_net_total",
"taxes_section",
"taxes_and_charges",
"column_break_58",
@@ -89,7 +91,6 @@
"section_break_44",
"apply_discount_on",
"base_discount_amount",
"additional_discount_account",
"column_break_46",
"additional_discount_percentage",
"discount_amount",
@@ -1421,6 +1422,26 @@
"label": "Is Old Subcontracting Flow",
"read_only": 1
},
{
"default": "0",
"fieldname": "tax_withholding_net_total",
"fieldtype": "Currency",
"hidden": 1,
"label": "Tax Withholding Net Total",
"no_copy": 1,
"options": "currency",
"read_only": 1
},
{
"fieldname": "base_tax_withholding_net_total",
"fieldtype": "Currency",
"hidden": 1,
"label": "Base Tax Withholding Net Total",
"no_copy": 1,
"options": "currency",
"print_hide": 1,
"read_only": 1
},
{
"collapsible_depends_on": "tax_withheld_vouchers",
"fieldname": "tax_withheld_vouchers_section",
@@ -1519,7 +1540,7 @@
"idx": 204,
"is_submittable": 1,
"links": [],
"modified": "2022-10-11 13:04:44.304389",
"modified": "2022-11-04 01:02:44.544878",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",

View File

@@ -71,6 +71,9 @@ class PurchaseInvoice(BuyingController):
supplier_tds = frappe.db.get_value("Supplier", self.supplier, "tax_withholding_category")
self.set_onload("supplier_tds", supplier_tds)
if self.is_new():
self.set("tax_withheld_vouchers", [])
def before_save(self):
if not self.on_hold:
self.release_date = ""
@@ -1407,7 +1410,7 @@ class PurchaseInvoice(BuyingController):
self.repost_future_sle_and_gle()
self.update_project()
frappe.db.set(self, "status", "Cancelled")
self.db_set("status", "Cancelled")
unlink_inter_company_doc(self.doctype, self.name, self.inter_company_invoice_reference)
self.ignore_linked_doctypes = (
@@ -1415,7 +1418,7 @@ class PurchaseInvoice(BuyingController):
"Stock Ledger Entry",
"Repost Item Valuation",
"Payment Ledger Entry",
"Purchase Invoice",
"Tax Withheld Vouchers",
)
self.update_advance_tax_references(cancel=1)
@@ -1460,6 +1463,7 @@ class PurchaseInvoice(BuyingController):
def update_billing_status_in_pr(self, update_modified=True):
updated_pr = []
po_details = []
for d in self.get("items"):
if d.pr_detail:
billed_amt = frappe.db.sql(
@@ -1477,7 +1481,10 @@ class PurchaseInvoice(BuyingController):
)
updated_pr.append(d.purchase_receipt)
elif d.po_detail:
updated_pr += update_billed_amount_based_on_po(d.po_detail, update_modified)
po_details.append(d.po_detail)
if po_details:
updated_pr += update_billed_amount_based_on_po(po_details, update_modified)
for pr in set(updated_pr):
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import update_billing_percentage

View File

@@ -40,6 +40,7 @@
"discount_amount",
"base_rate_with_margin",
"sec_break2",
"apply_tds",
"rate",
"amount",
"item_tax_template",
@@ -214,6 +215,7 @@
"reqd": 1
},
{
"default": "1",
"depends_on": "eval:doc.uom != doc.stock_uom",
"fieldname": "conversion_factor",
"fieldtype": "Float",
@@ -820,6 +822,7 @@
},
{
"collapsible": 1,
"collapsible_depends_on": "eval: doc.margin_type || doc.discount_amount",
"fieldname": "section_break_26",
"fieldtype": "Section Break",
"label": "Discount and Margin"
@@ -866,12 +869,18 @@
"label": "Product Bundle",
"options": "Product Bundle",
"read_only": 1
},
{
"default": "1",
"fieldname": "apply_tds",
"fieldtype": "Check",
"label": "Apply TDS"
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2022-10-12 03:37:29.032732",
"modified": "2022-10-26 16:05:37.304788",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Item",

View File

@@ -0,0 +1,53 @@
// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Repost Payment Ledger', {
setup: function(frm) {
frm.set_query("voucher_type", () => {
return {
filters: {
name: ['in', ['Purchase Invoice', 'Sales Invoice', 'Payment Entry', 'Journal Entry']]
}
};
});
frm.fields_dict['repost_vouchers'].grid.get_field('voucher_type').get_query = function(doc) {
return {
filters: {
name: ['in', ['Purchase Invoice', 'Sales Invoice', 'Payment Entry', 'Journal Entry']]
}
}
}
frm.fields_dict['repost_vouchers'].grid.get_field('voucher_no').get_query = function(doc) {
if (doc.company) {
return {
filters: {
company: doc.company,
docstatus: 1
}
}
}
}
},
refresh: function(frm) {
if (frm.doc.docstatus==1 && ['Queued', 'Failed'].find(x => x == frm.doc.repost_status)) {
frm.set_intro(__("Use 'Repost in background' button to trigger background job. Job can only be triggered when document is in Queued or Failed status."));
var btn_label = __("Repost in background")
frm.add_custom_button(btn_label, () => {
frappe.call({
method: 'erpnext.accounts.doctype.repost_payment_ledger.repost_payment_ledger.execute_repost_payment_ledger',
args: {
docname: frm.doc.name,
}
});
frappe.msgprint(__('Reposting in the background.'));
});
}
}
});

View File

@@ -0,0 +1,159 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2022-10-19 21:59:33.553852",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"filters_section",
"company",
"posting_date",
"column_break_4",
"voucher_type",
"add_manually",
"status_section",
"repost_status",
"repost_error_log",
"selected_vouchers_section",
"repost_vouchers",
"amended_from"
],
"fields": [
{
"default": "Today",
"fieldname": "posting_date",
"fieldtype": "Date",
"label": "Posting Date",
"reqd": 1
},
{
"fieldname": "voucher_type",
"fieldtype": "Link",
"label": "Voucher Type",
"options": "DocType"
},
{
"fieldname": "amended_from",
"fieldtype": "Link",
"label": "Amended From",
"no_copy": 1,
"options": "Repost Payment Ledger",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "company",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Company",
"options": "Company",
"reqd": 1
},
{
"fieldname": "selected_vouchers_section",
"fieldtype": "Section Break",
"label": "Vouchers"
},
{
"fieldname": "filters_section",
"fieldtype": "Section Break",
"label": "Filters"
},
{
"fieldname": "column_break_4",
"fieldtype": "Column Break"
},
{
"fieldname": "repost_vouchers",
"fieldtype": "Table",
"label": "Selected Vouchers",
"options": "Repost Payment Ledger Items"
},
{
"fieldname": "repost_status",
"fieldtype": "Select",
"label": "Repost Status",
"options": "\nQueued\nFailed\nCompleted",
"read_only": 1
},
{
"fieldname": "status_section",
"fieldtype": "Section Break",
"label": "Status"
},
{
"default": "0",
"description": "Ignore Voucher Type filter and Select Vouchers Manually",
"fieldname": "add_manually",
"fieldtype": "Check",
"label": "Add Manually"
},
{
"depends_on": "eval:doc.repost_error_log",
"fieldname": "repost_error_log",
"fieldtype": "Long Text",
"label": "Repost Error Log"
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2022-11-08 07:38:40.079038",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Repost Payment Ledger",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts Manager",
"share": 1,
"submit": 1,
"write": 1
},
{
"create": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts User",
"share": 1,
"write": 1
},
{
"email": 1,
"export": 1,
"permlevel": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts Manager",
"share": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@@ -0,0 +1,110 @@
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import copy
import frappe
from frappe import _, qb
from frappe.model.document import Document
from frappe.query_builder.custom import ConstantColumn
from erpnext.accounts.utils import _delete_pl_entries, create_payment_ledger_entry
VOUCHER_TYPES = ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal Entry"]
def repost_ple_for_voucher(voucher_type, voucher_no, gle_map=None):
if voucher_type and voucher_no and gle_map:
_delete_pl_entries(voucher_type, voucher_no)
create_payment_ledger_entry(gle_map, cancel=0)
@frappe.whitelist()
def start_payment_ledger_repost(docname=None):
"""
Repost Payment Ledger Entries for Vouchers through Background Job
"""
if docname:
repost_doc = frappe.get_doc("Repost Payment Ledger", docname)
if repost_doc.docstatus == 1 and repost_doc.repost_status in ["Queued", "Failed"]:
try:
for entry in repost_doc.repost_vouchers:
doc = frappe.get_doc(entry.voucher_type, entry.voucher_no)
if doc.doctype in ["Payment Entry", "Journal Entry"]:
gle_map = doc.build_gl_map()
else:
gle_map = doc.get_gl_entries()
repost_ple_for_voucher(entry.voucher_type, entry.voucher_no, gle_map)
frappe.db.set_value(repost_doc.doctype, repost_doc.name, "repost_error_log", "")
frappe.db.set_value(repost_doc.doctype, repost_doc.name, "repost_status", "Completed")
except Exception as e:
frappe.db.rollback()
traceback = frappe.get_traceback()
if traceback:
message = "Traceback: <br>" + traceback
frappe.db.set_value(repost_doc.doctype, repost_doc.name, "repost_error_log", message)
frappe.db.set_value(repost_doc.doctype, repost_doc.name, "repost_status", "Failed")
class RepostPaymentLedger(Document):
def __init__(self, *args, **kwargs):
super(RepostPaymentLedger, self).__init__(*args, **kwargs)
self.vouchers = []
def before_validate(self):
self.load_vouchers_based_on_filters()
self.set_status()
def load_vouchers_based_on_filters(self):
if not self.add_manually:
self.repost_vouchers.clear()
self.get_vouchers()
self.extend("repost_vouchers", copy.deepcopy(self.vouchers))
def get_vouchers(self):
self.vouchers.clear()
filter_on_voucher_types = [self.voucher_type] if self.voucher_type else VOUCHER_TYPES
for vtype in filter_on_voucher_types:
doc = qb.DocType(vtype)
doctype_name = ConstantColumn(vtype)
query = (
qb.from_(doc)
.select(doctype_name.as_("voucher_type"), doc.name.as_("voucher_no"))
.where(
(doc.docstatus == 1)
& (doc.company == self.company)
& (doc.posting_date.gte(self.posting_date))
)
)
entries = query.run(as_dict=True)
self.vouchers.extend(entries)
def set_status(self):
if self.docstatus == 0:
self.repost_status = "Queued"
def on_submit(self):
execute_repost_payment_ledger(self.name)
frappe.msgprint(_("Repost started in the background"))
@frappe.whitelist()
def execute_repost_payment_ledger(docname):
"""Repost Payment Ledger Entries by background job."""
job_name = "payment_ledger_repost_" + docname
if not frappe.utils.background_jobs.is_job_queued(job_name):
frappe.enqueue(
method="erpnext.accounts.doctype.repost_payment_ledger.repost_payment_ledger.start_payment_ledger_repost",
docname=docname,
is_async=True,
job_name=job_name,
)

View File

@@ -0,0 +1,12 @@
frappe.listview_settings["Repost Payment Ledger"] = {
add_fields: ["repost_status"],
get_indicator: function(doc) {
var colors = {
'Queued': 'orange',
'Completed': 'green',
'Failed': 'red',
};
let status = doc.repost_status;
return [__(status), colors[status], 'status,=,'+status];
},
};

View File

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

View File

@@ -0,0 +1,35 @@
{
"actions": [],
"creation": "2022-10-20 10:44:18.796489",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"voucher_type",
"voucher_no"
],
"fields": [
{
"fieldname": "voucher_type",
"fieldtype": "Link",
"label": "Voucher Type",
"options": "DocType"
},
{
"fieldname": "voucher_no",
"fieldtype": "Dynamic Link",
"label": "Voucher No",
"options": "voucher_type"
}
],
"istable": 1,
"links": [],
"modified": "2022-10-28 14:47:11.838109",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Repost Payment Ledger Items",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@@ -0,0 +1,9 @@
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class RepostPaymentLedgerItems(Document):
pass

View File

@@ -2090,7 +2090,7 @@
{
"collapsible": 1,
"collapsible_depends_on": "write_off_amount",
"depends_on": "grand_total",
"depends_on": "is_pos",
"fieldname": "write_off_section",
"fieldtype": "Section Break",
"hide_days": 1,
@@ -2109,7 +2109,7 @@
"link_fieldname": "consolidated_invoice"
}
],
"modified": "2022-10-11 13:07:36.488095",
"modified": "2022-11-15 09:33:47.870616",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",
@@ -2165,4 +2165,4 @@
"title_field": "title",
"track_changes": 1,
"track_seen": 1
}
}

View File

@@ -367,7 +367,7 @@ class SalesInvoice(SellingController):
if self.update_stock == 1:
self.repost_future_sle_and_gle()
frappe.db.set(self, "status", "Cancelled")
self.db_set("status", "Cancelled")
if (
frappe.db.get_single_value("Selling Settings", "sales_update_frequency") == "Each Transaction"
@@ -1300,7 +1300,11 @@ class SalesInvoice(SellingController):
def make_write_off_gl_entry(self, gl_entries):
# write off entries, applicable if only pos
if self.write_off_account and flt(self.write_off_amount, self.precision("write_off_amount")):
if (
self.is_pos
and self.write_off_account
and flt(self.write_off_amount, self.precision("write_off_amount"))
):
write_off_account_currency = get_account_currency(self.write_off_account)
default_cost_center = frappe.get_cached_value("Company", self.company, "cost_center")
@@ -2306,7 +2310,7 @@ def get_loyalty_programs(customer):
lp_details = get_loyalty_programs(customer)
if len(lp_details) == 1:
frappe.db.set(customer, "loyalty_program", lp_details[0])
customer.db_set("loyalty_program", lp_details[0])
return lp_details
else:
return lp_details

View File

@@ -965,7 +965,8 @@ class TestSalesInvoice(unittest.TestCase):
pos_return.insert()
pos_return.submit()
self.assertEqual(pos_return.get("payments")[0].amount, -1000)
self.assertEqual(pos_return.get("payments")[0].amount, -500)
self.assertEqual(pos_return.get("payments")[1].amount, -500)
def test_pos_change_amount(self):
make_pos_profile(

View File

@@ -8,6 +8,7 @@
"engine": "InnoDB",
"field_order": [
"barcode",
"has_item_scanned",
"item_code",
"col_break1",
"item_name",
@@ -247,6 +248,7 @@
},
{
"collapsible": 1,
"collapsible_depends_on": "eval: doc.margin_type || doc.discount_amount",
"fieldname": "discount_and_margin",
"fieldtype": "Section Break",
"label": "Discount and Margin"
@@ -871,12 +873,20 @@
"label": "Purchase Order Item",
"print_hide": 1,
"read_only": 1
},
{
"default": "0",
"depends_on": "barcode",
"fieldname": "has_item_scanned",
"fieldtype": "Check",
"label": "Has Item Scanned",
"read_only": 1
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2022-10-10 20:57:38.340026",
"modified": "2022-11-02 12:53:12.693217",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Item",

View File

@@ -61,6 +61,9 @@ def get_party_details(inv):
def get_party_tax_withholding_details(inv, tax_withholding_category=None):
if inv.doctype == "Payment Entry":
inv.tax_withholding_net_total = inv.net_total
pan_no = ""
parties = []
party_type, party = get_party_details(inv)
@@ -242,7 +245,7 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N
if party_type == "Supplier":
ldc = get_lower_deduction_certificate(tax_details, pan_no)
if tax_deducted:
net_total = inv.net_total
net_total = inv.tax_withholding_net_total
if ldc:
tax_amount = get_tds_amount_from_ldc(
ldc, parties, pan_no, tax_details, posting_date, net_total
@@ -272,6 +275,11 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N
def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"):
doctype = "Purchase Invoice" if party_type == "Supplier" else "Sales Invoice"
field = (
"base_tax_withholding_net_total as base_net_total"
if party_type == "Supplier"
else "base_net_total"
)
voucher_wise_amount = {}
vouchers = []
@@ -288,7 +296,7 @@ def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"):
{"apply_tds": 1, "tax_withholding_category": tax_details.get("tax_withholding_category")}
)
invoices_details = frappe.get_all(doctype, filters=filters, fields=["name", "base_net_total"])
invoices_details = frappe.get_all(doctype, filters=filters, fields=["name", field])
for d in invoices_details:
vouchers.append(d.name)
@@ -392,7 +400,7 @@ def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers):
tds_amount = 0
invoice_filters = {"name": ("in", vouchers), "docstatus": 1, "apply_tds": 1}
field = "sum(net_total)"
field = "sum(tax_withholding_net_total)"
if cint(tax_details.consider_party_ledger_amount):
invoice_filters.pop("apply_tds", None)
@@ -415,12 +423,12 @@ def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers):
)
supp_credit_amt += supp_jv_credit_amt
supp_credit_amt += inv.net_total
supp_credit_amt += inv.tax_withholding_net_total
threshold = tax_details.get("threshold", 0)
cumulative_threshold = tax_details.get("cumulative_threshold", 0)
if (threshold and inv.net_total >= threshold) or (
if (threshold and inv.tax_withholding_net_total >= threshold) or (
cumulative_threshold and supp_credit_amt >= cumulative_threshold
):
if (cumulative_threshold and supp_credit_amt >= cumulative_threshold) and cint(
@@ -428,11 +436,11 @@ def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers):
):
# Get net total again as TDS is calculated on net total
# Grand is used to just check for threshold breach
net_total = 0
if vouchers:
net_total = frappe.db.get_value("Purchase Invoice", invoice_filters, "sum(net_total)")
net_total += inv.net_total
net_total = (
frappe.db.get_value("Purchase Invoice", invoice_filters, "sum(tax_withholding_net_total)")
or 0.0
)
net_total += inv.tax_withholding_net_total
supp_credit_amt = net_total - cumulative_threshold
if ldc and is_valid_certificate(
@@ -440,7 +448,7 @@ def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers):
ldc.valid_upto,
inv.get("posting_date") or inv.get("transaction_date"),
tax_deducted,
inv.net_total,
inv.tax_withholding_net_total,
ldc.certificate_limit,
):
tds_amount = get_ltds_amount(supp_credit_amt, 0, ldc.certificate_limit, ldc.rate, tax_details)
@@ -523,7 +531,7 @@ def get_tds_amount_from_ldc(ldc, parties, pan_no, tax_details, posting_date, net
limit_consumed = frappe.db.get_value(
"Purchase Invoice",
{"supplier": ("in", parties), "apply_tds": 1, "docstatus": 1},
"sum(net_total)",
"sum(tax_withholding_net_total)",
)
if is_valid_certificate(

View File

@@ -186,6 +186,46 @@ class TestTaxWithholdingCategory(unittest.TestCase):
for d in reversed(invoices):
d.cancel()
def test_tds_calculation_on_net_total_partial_tds(self):
frappe.db.set_value(
"Supplier", "Test TDS Supplier4", "tax_withholding_category", "Cumulative Threshold TDS"
)
invoices = []
pi = create_purchase_invoice(supplier="Test TDS Supplier4", rate=20000, do_not_save=True)
pi.extend(
"items",
[
{
"doctype": "Purchase Invoice Item",
"item_code": frappe.db.get_value("Item", {"item_name": "TDS Item"}, "name"),
"qty": 1,
"rate": 20000,
"cost_center": "Main - _TC",
"expense_account": "Stock Received But Not Billed - _TC",
"apply_tds": 0,
},
{
"doctype": "Purchase Invoice Item",
"item_code": frappe.db.get_value("Item", {"item_name": "TDS Item"}, "name"),
"qty": 1,
"rate": 35000,
"cost_center": "Main - _TC",
"expense_account": "Stock Received But Not Billed - _TC",
"apply_tds": 1,
},
],
)
pi.save()
pi.submit()
invoices.append(pi)
self.assertEqual(pi.taxes[0].tax_amount, 5500)
# cancel invoices to avoid clashing
for d in reversed(invoices):
d.cancel()
def test_multi_category_single_supplier(self):
frappe.db.set_value(
"Supplier", "Test TDS Supplier5", "tax_withholding_category", "Test Service Category"

View File

@@ -128,6 +128,12 @@ def distribute_gl_based_on_cost_center_allocation(gl_map, precision=None):
new_gl_map = []
for d in gl_map:
cost_center = d.get("cost_center")
# Validate budget against main cost center
validate_expense_against_budget(
d, expense_amount=flt(d.debit, precision) - flt(d.credit, precision)
)
if cost_center and cost_center_allocation.get(cost_center):
for sub_cost_center, percentage in cost_center_allocation.get(cost_center, {}).items():
gle = copy.deepcopy(d)

View File

@@ -51,6 +51,8 @@ frappe.query_reports["Accounts Payable"] = {
} else {
frappe.query_report.set_filter_value('tax_id', "");
}
frappe.query_report.refresh();
}
},
{

View File

@@ -748,7 +748,7 @@ class ReceivablePayableReport(object):
self.add_accounting_dimensions_filters()
def get_cost_center_conditions(self, conditions):
def get_cost_center_conditions(self):
lft, rgt = frappe.db.get_value("Cost Center", self.filters.cost_center, ["lft", "rgt"])
cost_center_list = [
center.name
@@ -1009,7 +1009,7 @@ class ReceivablePayableReport(object):
"{range3}-{range4}".format(
range3=cint(self.filters["range3"]) + 1, range4=self.filters["range4"]
),
"{range4}-{above}".format(range4=cint(self.filters["range4"]) + 1, above=_("Above")),
_("{range4}-Above").format(range4=cint(self.filters["range4"]) + 1),
]
):
self.add_column(label=label, fieldname="range" + str(i + 1))

View File

@@ -75,7 +75,7 @@ frappe.query_reports["Budget Variance Report"] = {
"formatter": function (value, row, column, data, default_formatter) {
value = default_formatter(value, row, column, data);
if (column.fieldname.includes('variance')) {
if (column.fieldname.includes(__("variance"))) {
if (data[column.fieldname] < 0) {
value = "<span style='color:red'>" + value + "</span>";

View File

@@ -383,8 +383,8 @@ def get_chart_data(filters, columns, data):
"data": {
"labels": labels,
"datasets": [
{"name": "Budget", "chartType": "bar", "values": budget_values},
{"name": "Actual Expense", "chartType": "bar", "values": actual_values},
{"name": _("Budget"), "chartType": "bar", "values": budget_values},
{"name": _("Actual Expense"), "chartType": "bar", "values": actual_values},
],
},
"type": "bar",

View File

@@ -396,7 +396,7 @@ class Deferred_Revenue_and_Expense_Report(object):
"labels": [period.label for period in self.period_list],
"datasets": [
{
"name": "Actual Posting",
"name": _("Actual Posting"),
"chartType": "bar",
"values": [x.actual for x in self.period_total],
}
@@ -410,7 +410,7 @@ class Deferred_Revenue_and_Expense_Report(object):
if self.filters.with_upcoming_postings:
chart["data"]["datasets"].append(
{"name": "Expected", "chartType": "line", "values": [x.total for x in self.period_total]}
{"name": _("Expected"), "chartType": "line", "values": [x.total for x in self.period_total]}
)
return chart

View File

@@ -52,22 +52,22 @@
{% } %}
</td>
<td style="text-align: right">
{%= format_currency(data[i].debit, filters.presentation_currency) %}</td>
{%= format_currency(data[i].debit, filters.presentation_currency || data[i].account_currency) %}</td>
<td style="text-align: right">
{%= format_currency(data[i].credit, filters.presentation_currency) %}</td>
{%= format_currency(data[i].credit, filters.presentation_currency || data[i].account_currency) %}</td>
{% } else { %}
<td></td>
<td></td>
<td><b>{%= frappe.format(data[i].account, {fieldtype: "Link"}) || "&nbsp;" %}</b></td>
<td style="text-align: right">
{%= data[i].account && format_currency(data[i].debit, filters.presentation_currency) %}
{%= data[i].account && format_currency(data[i].debit, filters.presentation_currency || data[i].account_currency) %}
</td>
<td style="text-align: right">
{%= data[i].account && format_currency(data[i].credit, filters.presentation_currency) %}
{%= data[i].account && format_currency(data[i].credit, filters.presentation_currency || data[i].account_currency) %}
</td>
{% } %}
<td style="text-align: right">
{%= format_currency(data[i].balance, filters.presentation_currency) %}
{%= format_currency(data[i].balance, filters.presentation_currency || data[i].account_currency) %}
</td>
</tr>
{% } %}

View File

@@ -3,7 +3,8 @@
import frappe
from frappe import _, scrub
from frappe import _, qb, scrub
from frappe.query_builder import Order
from frappe.utils import cint, flt, formatdate
from erpnext.controllers.queries import get_match_cond
@@ -398,6 +399,7 @@ class GrossProfitGenerator(object):
self.average_buying_rate = {}
self.filters = frappe._dict(filters)
self.load_invoice_items()
self.get_delivery_notes()
if filters.group_by == "Invoice":
self.group_items_by_invoice()
@@ -591,6 +593,21 @@ class GrossProfitGenerator(object):
return flt(buying_amount, self.currency_precision)
def calculate_buying_amount_from_sle(self, row, my_sle, parenttype, parent, item_row, item_code):
for i, sle in enumerate(my_sle):
# find the stock valution rate from stock ledger entry
if (
sle.voucher_type == parenttype
and parent == sle.voucher_no
and sle.voucher_detail_no == item_row
):
previous_stock_value = len(my_sle) > i + 1 and flt(my_sle[i + 1].stock_value) or 0.0
if previous_stock_value:
return abs(previous_stock_value - flt(sle.stock_value)) * flt(row.qty) / abs(flt(sle.qty))
else:
return flt(row.qty) * self.get_average_buying_rate(row, item_code)
def get_buying_amount(self, row, item_code):
# IMP NOTE
# stock_ledger_entries should already be filtered by item_code and warehouse and
@@ -607,19 +624,22 @@ class GrossProfitGenerator(object):
if row.dn_detail:
parenttype, parent = "Delivery Note", row.delivery_note
for i, sle in enumerate(my_sle):
# find the stock valution rate from stock ledger entry
if (
sle.voucher_type == parenttype
and parent == sle.voucher_no
and sle.voucher_detail_no == row.item_row
):
previous_stock_value = len(my_sle) > i + 1 and flt(my_sle[i + 1].stock_value) or 0.0
if previous_stock_value:
return abs(previous_stock_value - flt(sle.stock_value)) * flt(row.qty) / abs(flt(sle.qty))
else:
return flt(row.qty) * self.get_average_buying_rate(row, item_code)
return self.calculate_buying_amount_from_sle(
row, my_sle, parenttype, parent, row.item_row, item_code
)
elif self.delivery_notes.get((row.parent, row.item_code), None):
# check if Invoice has delivery notes
dn = self.delivery_notes.get((row.parent, row.item_code))
parenttype, parent, item_row, warehouse = (
"Delivery Note",
dn["delivery_note"],
dn["item_row"],
dn["warehouse"],
)
my_sle = self.sle.get((item_code, warehouse))
return self.calculate_buying_amount_from_sle(
row, my_sle, parenttype, parent, item_row, item_code
)
else:
return flt(row.qty) * self.get_average_buying_rate(row, item_code)
@@ -753,6 +773,29 @@ class GrossProfitGenerator(object):
as_dict=1,
)
def get_delivery_notes(self):
self.delivery_notes = frappe._dict({})
if self.si_list:
invoices = [x.parent for x in self.si_list]
dni = qb.DocType("Delivery Note Item")
delivery_notes = (
qb.from_(dni)
.select(
dni.against_sales_invoice.as_("sales_invoice"),
dni.item_code,
dni.warehouse,
dni.parent.as_("delivery_note"),
dni.name.as_("item_row"),
)
.where((dni.docstatus == 1) & (dni.against_sales_invoice.isin(invoices)))
.groupby(dni.against_sales_invoice, dni.item_code)
.orderby(dni.creation, order=Order.desc)
.run(as_dict=True)
)
for entry in delivery_notes:
self.delivery_notes[(entry.sales_invoice, entry.item_code)] = entry
def group_items_by_invoice(self):
"""
Turns list of Sales Invoice Items to a tree of Sales Invoices with their Items as children.

View File

@@ -0,0 +1,209 @@
import frappe
from frappe import qb
from frappe.tests.utils import FrappeTestCase
from frappe.utils import add_days, flt, nowdate
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_delivery_note
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.accounts.report.gross_profit.gross_profit import execute
from erpnext.stock.doctype.item.test_item import create_item
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
class TestGrossProfit(FrappeTestCase):
def setUp(self):
self.create_company()
self.create_item()
self.create_customer()
self.create_sales_invoice()
self.clear_old_entries()
def tearDown(self):
frappe.db.rollback()
def create_company(self):
company_name = "_Test Gross Profit"
abbr = "_GP"
if frappe.db.exists("Company", company_name):
company = frappe.get_doc("Company", company_name)
else:
company = frappe.get_doc(
{
"doctype": "Company",
"company_name": company_name,
"country": "India",
"default_currency": "INR",
"create_chart_of_accounts_based_on": "Standard Template",
"chart_of_accounts": "Standard",
}
)
company = company.save()
self.company = company.name
self.cost_center = company.cost_center
self.warehouse = "Stores - " + abbr
self.income_account = "Sales - " + abbr
self.expense_account = "Cost of Goods Sold - " + abbr
self.debit_to = "Debtors - " + abbr
self.creditors = "Creditors - " + abbr
def create_item(self):
item = create_item(
item_code="_Test GP Item", is_stock_item=1, company=self.company, warehouse=self.warehouse
)
self.item = item if isinstance(item, str) else item.item_code
def create_customer(self):
name = "_Test GP Customer"
if frappe.db.exists("Customer", name):
self.customer = name
else:
customer = frappe.new_doc("Customer")
customer.customer_name = name
customer.type = "Individual"
customer.save()
self.customer = customer.name
def create_sales_invoice(
self, qty=1, rate=100, posting_date=nowdate(), do_not_save=False, do_not_submit=False
):
"""
Helper function to populate default values in sales invoice
"""
sinv = create_sales_invoice(
qty=qty,
rate=rate,
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",
is_pos=0,
is_return=0,
return_against=None,
income_account=self.income_account,
expense_account=self.expense_account,
do_not_save=do_not_save,
do_not_submit=do_not_submit,
)
return sinv
def clear_old_entries(self):
doctype_list = [
"Sales Invoice",
"GL Entry",
"Payment Ledger Entry",
"Stock Entry",
"Stock Ledger Entry",
"Delivery Note",
]
for doctype in doctype_list:
qb.from_(qb.DocType(doctype)).delete().where(qb.DocType(doctype).company == self.company).run()
def test_invoice_without_only_delivery_note(self):
"""
Test buying amount for Invoice without `update_stock` flag set but has Delivery Note
"""
se = make_stock_entry(
company=self.company,
item_code=self.item,
target=self.warehouse,
qty=1,
basic_rate=100,
do_not_submit=True,
)
item = se.items[0]
se.append(
"items",
{
"item_code": item.item_code,
"s_warehouse": item.s_warehouse,
"t_warehouse": item.t_warehouse,
"qty": 1,
"basic_rate": 200,
"conversion_factor": item.conversion_factor or 1.0,
"transfer_qty": flt(item.qty) * (flt(item.conversion_factor) or 1.0),
"serial_no": item.serial_no,
"batch_no": item.batch_no,
"cost_center": item.cost_center,
"expense_account": item.expense_account,
},
)
se = se.save().submit()
sinv = 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"
)
columns, data = execute(filters=filters)
# Without Delivery Note, buying rate should be 150
expected_entry_without_dn = {
"parent_invoice": sinv.name,
"currency": "INR",
"sales_invoice": self.item,
"customer": self.customer,
"posting_date": frappe.utils.datetime.date.fromisoformat(nowdate()),
"item_code": self.item,
"item_name": self.item,
"warehouse": "Stores - _GP",
"qty": 1.0,
"avg._selling_rate": 100.0,
"valuation_rate": 150.0,
"selling_amount": 100.0,
"buying_amount": 150.0,
"gross_profit": -50.0,
"gross_profit_%": -50.0,
}
gp_entry = [x for x in data if x.parent_invoice == sinv.name]
self.assertDictContainsSubset(expected_entry_without_dn, gp_entry[0])
# make delivery note
dn = make_delivery_note(sinv.name)
dn.items[0].qty = 1
dn = dn.save().submit()
columns, data = execute(filters=filters)
# Without Delivery Note, buying rate should be 100
expected_entry_with_dn = {
"parent_invoice": sinv.name,
"currency": "INR",
"sales_invoice": self.item,
"customer": self.customer,
"posting_date": frappe.utils.datetime.date.fromisoformat(nowdate()),
"item_code": self.item,
"item_name": self.item,
"warehouse": "Stores - _GP",
"qty": 1.0,
"avg._selling_rate": 100.0,
"valuation_rate": 100.0,
"selling_amount": 100.0,
"buying_amount": 100.0,
"gross_profit": 0.0,
"gross_profit_%": 0.0,
}
gp_entry = [x for x in data if x.parent_invoice == sinv.name]
self.assertDictContainsSubset(expected_entry_with_dn, gp_entry[0])

View File

@@ -1146,10 +1146,10 @@ def repost_gle_for_stock_vouchers(
if not existing_gle or not compare_existing_and_expected_gle(
existing_gle, expected_gle, precision
):
_delete_gl_entries(voucher_type, voucher_no)
_delete_accounting_ledger_entries(voucher_type, voucher_no)
voucher_obj.make_gl_entries(gl_entries=expected_gle, from_repost=True)
else:
_delete_gl_entries(voucher_type, voucher_no)
_delete_accounting_ledger_entries(voucher_type, voucher_no)
if not frappe.flags.in_test:
frappe.db.commit()
@@ -1161,18 +1161,28 @@ def repost_gle_for_stock_vouchers(
)
def _delete_gl_entries(voucher_type, voucher_no):
frappe.db.sql(
"""delete from `tabGL Entry`
where voucher_type=%s and voucher_no=%s""",
(voucher_type, voucher_no),
)
def _delete_pl_entries(voucher_type, voucher_no):
ple = qb.DocType("Payment Ledger Entry")
qb.from_(ple).delete().where(
(ple.voucher_type == voucher_type) & (ple.voucher_no == voucher_no)
).run()
def _delete_gl_entries(voucher_type, voucher_no):
gle = qb.DocType("GL Entry")
qb.from_(gle).delete().where(
(gle.voucher_type == voucher_type) & (gle.voucher_no == voucher_no)
).run()
def _delete_accounting_ledger_entries(voucher_type, voucher_no):
"""
Remove entries from both General and Payment Ledger for specified Voucher
"""
_delete_gl_entries(voucher_type, voucher_no)
_delete_pl_entries(voucher_type, voucher_no)
def sort_stock_vouchers_by_posting_date(
stock_vouchers: List[Tuple[str, str]]
) -> List[Tuple[str, str]]:

View File

@@ -432,7 +432,11 @@ frappe.ui.form.on('Asset', {
set_values_from_purchase_doc: function(frm, doctype, purchase_doc) {
frm.set_value('company', purchase_doc.company);
frm.set_value('purchase_date', purchase_doc.posting_date);
if (purchase_doc.bill_date) {
frm.set_value('purchase_date', purchase_doc.bill_date);
} else {
frm.set_value('purchase_date', purchase_doc.posting_date);
}
const item = purchase_doc.items.find(item => item.item_code === frm.doc.item_code);
if (!item) {
doctype_field = frappe.scrub(doctype)

View File

@@ -221,7 +221,7 @@ class TestAsset(AssetSetup):
asset.precision("gross_purchase_amount"),
)
pro_rata_amount, _, _ = asset.get_pro_rata_amt(
asset.finance_books[0], 9000, add_months(get_last_day(purchase_date), 1), date
asset.finance_books[0], 9000, get_last_day(add_months(purchase_date, 1)), date
)
pro_rata_amount = flt(pro_rata_amount, asset.precision("gross_purchase_amount"))
self.assertEquals(accumulated_depr_amount, 18000.00 + pro_rata_amount)
@@ -283,7 +283,7 @@ class TestAsset(AssetSetup):
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold")
pro_rata_amount, _, _ = asset.get_pro_rata_amt(
asset.finance_books[0], 9000, add_months(get_last_day(purchase_date), 1), date
asset.finance_books[0], 9000, get_last_day(add_months(purchase_date, 1)), date
)
pro_rata_amount = flt(pro_rata_amount, asset.precision("gross_purchase_amount"))

View File

@@ -135,6 +135,7 @@ class AssetRepair(AccountsController):
"basic_rate": stock_item.valuation_rate,
"serial_no": stock_item.serial_no,
"cost_center": self.cost_center,
"project": self.project,
},
)

View File

@@ -101,6 +101,11 @@ frappe.ui.form.on("Purchase Order", {
erpnext.queries.setup_queries(frm, "Warehouse", function() {
return erpnext.queries.warehouse(frm.doc);
});
// On cancel and amending a purchase order with advance payment, reset advance paid amount
if (frm.is_new()) {
frm.set_value("advance_paid", 0)
}
},
apply_tds: function(frm) {

View File

@@ -361,7 +361,7 @@ class PurchaseOrder(BuyingController):
self.update_reserved_qty_for_subcontract()
self.check_on_hold_or_closed_status()
frappe.db.set(self, "status", "Cancelled")
self.db_set("status", "Cancelled")
self.update_prevdoc_status()

View File

@@ -5,7 +5,7 @@
import json
import frappe
from frappe.tests.utils import FrappeTestCase
from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import add_days, flt, getdate, nowdate
from frappe.utils.data import today
@@ -709,13 +709,10 @@ class TestPurchaseOrder(FrappeTestCase):
pi.insert()
self.assertTrue(pi.get("payment_schedule"))
@change_settings("Accounts Settings", {"unlink_advance_payment_on_cancelation_of_order": 1})
def test_advance_payment_entry_unlink_against_purchase_order(self):
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
frappe.db.set_value(
"Accounts Settings", "Accounts Settings", "unlink_advance_payment_on_cancelation_of_order", 1
)
po_doc = create_purchase_order()
pe = get_payment_entry("Purchase Order", po_doc.name, bank_account="_Test Bank - _TC")
@@ -735,9 +732,31 @@ class TestPurchaseOrder(FrappeTestCase):
pe_doc = frappe.get_doc("Payment Entry", pe.name)
pe_doc.cancel()
frappe.db.set_value(
"Accounts Settings", "Accounts Settings", "unlink_advance_payment_on_cancelation_of_order", 0
)
@change_settings("Accounts Settings", {"unlink_advance_payment_on_cancelation_of_order": 1})
def test_advance_paid_upon_payment_entry_cancellation(self):
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
po_doc = create_purchase_order()
pe = get_payment_entry("Purchase Order", po_doc.name, bank_account="_Test Bank - _TC")
pe.reference_no = "1"
pe.reference_date = nowdate()
pe.paid_from_account_currency = po_doc.currency
pe.paid_to_account_currency = po_doc.currency
pe.source_exchange_rate = 1
pe.target_exchange_rate = 1
pe.paid_amount = po_doc.grand_total
pe.save(ignore_permissions=True)
pe.submit()
po_doc.reload()
self.assertEqual(po_doc.advance_paid, po_doc.base_grand_total)
pe_doc = frappe.get_doc("Payment Entry", pe.name)
pe_doc.cancel()
po_doc.reload()
self.assertEqual(po_doc.advance_paid, 0)
def test_schedule_date(self):
po = create_purchase_order(do_not_submit=True)

View File

@@ -777,6 +777,7 @@
},
{
"collapsible": 1,
"collapsible_depends_on": "eval: doc.margin_type || doc.discount_amount",
"fieldname": "discount_and_margin_section",
"fieldtype": "Section Break",
"label": "Discount and Margin"
@@ -894,7 +895,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2022-09-07 11:12:38.634976",
"modified": "2022-10-26 16:47:41.364387",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order Item",

View File

@@ -31,7 +31,7 @@ class RequestforQuotation(BuyingController):
if self.docstatus < 1:
# after amend and save, status still shows as cancelled, until submit
frappe.db.set(self, "status", "Draft")
self.db_set("status", "Draft")
def validate_duplicate_supplier(self):
supplier_list = [d.supplier for d in self.suppliers]
@@ -73,14 +73,14 @@ class RequestforQuotation(BuyingController):
)
def on_submit(self):
frappe.db.set(self, "status", "Submitted")
self.db_set("status", "Submitted")
for supplier in self.suppliers:
supplier.email_sent = 0
supplier.quote_status = "Pending"
self.send_to_supplier()
def on_cancel(self):
frappe.db.set(self, "status", "Cancelled")
self.db_set("status", "Cancelled")
@frappe.whitelist()
def get_supplier_email_preview(self, supplier):

View File

@@ -10,34 +10,37 @@
"document_type": "Setup",
"engine": "InnoDB",
"field_order": [
"basic_info",
"naming_series",
"supplier_name",
"country",
"default_bank_account",
"tax_id",
"tax_category",
"tax_withholding_category",
"image",
"column_break0",
"supplier_group",
"supplier_type",
"allow_purchase_invoice_creation_without_purchase_order",
"allow_purchase_invoice_creation_without_purchase_receipt",
"is_internal_supplier",
"represents_company",
"disabled",
"is_transporter",
"warn_rfqs",
"warn_pos",
"prevent_rfqs",
"prevent_pos",
"allowed_to_transact_section",
"companies",
"section_break_7",
"image",
"defaults_section",
"default_currency",
"default_bank_account",
"column_break_10",
"default_price_list",
"payment_terms",
"internal_supplier_section",
"is_internal_supplier",
"represents_company",
"column_break_16",
"companies",
"column_break2",
"supplier_details",
"column_break_30",
"website",
"language",
"dashboard_tab",
"tax_tab",
"tax_id",
"column_break_27",
"tax_category",
"tax_withholding_category",
"contact_and_address_tab",
"address_contacts",
"address_html",
"column_break1",
@@ -49,30 +52,25 @@
"column_break_44",
"supplier_primary_address",
"primary_address",
"default_payable_accounts",
"accounting_tab",
"accounts",
"section_credit_limit",
"payment_terms",
"cb_21",
"settings_tab",
"allow_purchase_invoice_creation_without_purchase_order",
"allow_purchase_invoice_creation_without_purchase_receipt",
"column_break_54",
"is_frozen",
"disabled",
"warn_rfqs",
"warn_pos",
"prevent_rfqs",
"prevent_pos",
"block_supplier_section",
"on_hold",
"hold_type",
"release_date",
"default_tax_withholding_config",
"column_break2",
"website",
"supplier_details",
"column_break_30",
"language",
"is_frozen"
"column_break_59",
"release_date"
],
"fields": [
{
"fieldname": "basic_info",
"fieldtype": "Section Break",
"label": "Name and Type",
"oldfieldtype": "Section Break",
"options": "fa fa-user"
},
{
"fieldname": "naming_series",
"fieldtype": "Select",
@@ -192,6 +190,7 @@
"default": "0",
"fieldname": "warn_rfqs",
"fieldtype": "Check",
"hidden": 1,
"label": "Warn RFQs",
"read_only": 1
},
@@ -199,6 +198,7 @@
"default": "0",
"fieldname": "warn_pos",
"fieldtype": "Check",
"hidden": 1,
"label": "Warn POs",
"read_only": 1
},
@@ -206,6 +206,7 @@
"default": "0",
"fieldname": "prevent_rfqs",
"fieldtype": "Check",
"hidden": 1,
"label": "Prevent RFQs",
"read_only": 1
},
@@ -213,15 +214,10 @@
"default": "0",
"fieldname": "prevent_pos",
"fieldtype": "Check",
"hidden": 1,
"label": "Prevent POs",
"read_only": 1
},
{
"depends_on": "represents_company",
"fieldname": "allowed_to_transact_section",
"fieldtype": "Section Break",
"label": "Allowed To Transact With"
},
{
"depends_on": "represents_company",
"fieldname": "companies",
@@ -229,12 +225,6 @@
"label": "Allowed To Transact With",
"options": "Allowed To Transact With"
},
{
"collapsible": 1,
"fieldname": "section_break_7",
"fieldtype": "Section Break",
"label": "Currency and Price List"
},
{
"fieldname": "default_currency",
"fieldtype": "Link",
@@ -254,22 +244,12 @@
"label": "Price List",
"options": "Price List"
},
{
"collapsible": 1,
"fieldname": "section_credit_limit",
"fieldtype": "Section Break",
"label": "Payment Terms"
},
{
"fieldname": "payment_terms",
"fieldtype": "Link",
"label": "Default Payment Terms Template",
"options": "Payment Terms Template"
},
{
"fieldname": "cb_21",
"fieldtype": "Column Break"
},
{
"default": "0",
"fieldname": "on_hold",
@@ -315,13 +295,6 @@
"label": "Contact HTML",
"read_only": 1
},
{
"collapsible": 1,
"collapsible_depends_on": "accounts",
"fieldname": "default_payable_accounts",
"fieldtype": "Section Break",
"label": "Default Payable Accounts"
},
{
"description": "Mention if non-standard payable account",
"fieldname": "accounts",
@@ -329,12 +302,6 @@
"label": "Accounts",
"options": "Party Account"
},
{
"collapsible": 1,
"fieldname": "default_tax_withholding_config",
"fieldtype": "Section Break",
"label": "Default Tax Withholding Config"
},
{
"collapsible": 1,
"collapsible_depends_on": "supplier_details",
@@ -383,7 +350,7 @@
{
"fieldname": "primary_address_and_contact_detail_section",
"fieldtype": "Section Break",
"label": "Primary Address and Contact Detail"
"label": "Primary Address and Contact"
},
{
"description": "Reselect, if the chosen contact is edited after save",
@@ -420,6 +387,64 @@
"fieldtype": "Link",
"label": "Supplier Primary Address",
"options": "Address"
},
{
"fieldname": "dashboard_tab",
"fieldtype": "Tab Break",
"label": "Dashboard",
"show_dashboard": 1
},
{
"fieldname": "settings_tab",
"fieldtype": "Tab Break",
"label": "Settings"
},
{
"fieldname": "contact_and_address_tab",
"fieldtype": "Tab Break",
"label": "Contact & Address"
},
{
"fieldname": "accounting_tab",
"fieldtype": "Tab Break",
"label": "Accounting"
},
{
"fieldname": "defaults_section",
"fieldtype": "Section Break",
"label": "Defaults"
},
{
"fieldname": "tax_tab",
"fieldtype": "Tab Break",
"label": "Tax"
},
{
"collapsible": 1,
"fieldname": "internal_supplier_section",
"fieldtype": "Section Break",
"label": "Internal Supplier"
},
{
"fieldname": "column_break_16",
"fieldtype": "Column Break"
},
{
"fieldname": "column_break_27",
"fieldtype": "Column Break"
},
{
"fieldname": "column_break_54",
"fieldtype": "Column Break"
},
{
"fieldname": "block_supplier_section",
"fieldtype": "Section Break",
"label": "Block Supplier"
},
{
"fieldname": "column_break_59",
"fieldtype": "Column Break"
}
],
"icon": "fa fa-user",
@@ -432,7 +457,7 @@
"link_fieldname": "party"
}
],
"modified": "2022-04-16 18:02:27.838623",
"modified": "2022-11-09 18:02:59.075203",
"modified_by": "Administrator",
"module": "Buying",
"name": "Supplier",

View File

@@ -145,7 +145,7 @@ class Supplier(TransactionBase):
def after_rename(self, olddn, newdn, merge=False):
if frappe.defaults.get_global_default("supp_master_name") == "Supplier Name":
frappe.db.set(self, "supplier_name", newdn)
self.db_set("supplier_name", newdn)
@frappe.whitelist()

View File

@@ -156,6 +156,8 @@ class TestSupplier(FrappeTestCase):
def test_serach_fields_for_supplier(self):
from erpnext.controllers.queries import supplier_query
frappe.db.set_value("Buying Settings", None, "supp_master_name", "Naming Series")
supplier_name = create_supplier(supplier_name="Test Supplier 1").name
make_property_setter(
@@ -187,6 +189,8 @@ class TestSupplier(FrappeTestCase):
self.assertEqual(data[0].supplier_type, "Company")
self.assertTrue("supplier_type" in data[0])
frappe.db.set_value("Buying Settings", None, "supp_master_name", "Supplier Name")
def create_supplier(**args):
args = frappe._dict(args)

View File

@@ -30,11 +30,11 @@ class SupplierQuotation(BuyingController):
self.validate_valid_till()
def on_submit(self):
frappe.db.set(self, "status", "Submitted")
self.db_set("status", "Submitted")
self.update_rfq_supplier_status(1)
def on_cancel(self):
frappe.db.set(self, "status", "Cancelled")
self.db_set("status", "Cancelled")
self.update_rfq_supplier_status(0)
def on_trash(self):

View File

@@ -7,7 +7,7 @@ import json
import frappe
from frappe import _, throw
from frappe.model.workflow import get_workflow_name, is_transition_condition_satisfied
from frappe.query_builder.functions import Sum
from frappe.query_builder.functions import Abs, Sum
from frappe.utils import (
add_days,
add_months,
@@ -151,6 +151,7 @@ class AccountsController(TransactionBase):
self.validate_inter_company_reference()
self.disable_pricing_rule_on_internal_transfer()
self.disable_tax_included_prices_for_internal_transfer()
self.set_incoming_rate()
if self.meta.get_field("currency"):
@@ -226,7 +227,7 @@ class AccountsController(TransactionBase):
for item in self.get("items"):
if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"):
if not item.get(field_map.get(self.doctype)):
default_deferred_account = frappe.db.get_value(
default_deferred_account = frappe.get_cached_value(
"Company", self.company, "default_" + field_map.get(self.doctype)
)
if not default_deferred_account:
@@ -398,6 +399,20 @@ class AccountsController(TransactionBase):
alert=1,
)
def disable_tax_included_prices_for_internal_transfer(self):
if self.is_internal_transfer():
tax_updated = False
for tax in self.get("taxes"):
if tax.get("included_in_print_rate"):
tax.included_in_print_rate = 0
tax_updated = True
if tax_updated:
frappe.msgprint(
_("Disabled tax included prices since this {} is an internal transfer").format(self.doctype),
alert=1,
)
def validate_due_date(self):
if self.get("is_pos"):
return
@@ -661,7 +676,7 @@ class AccountsController(TransactionBase):
def validate_enabled_taxes_and_charges(self):
taxes_and_charges_doctype = self.meta.get_options("taxes_and_charges")
if frappe.db.get_value(taxes_and_charges_doctype, self.taxes_and_charges, "disabled"):
if frappe.get_cached_value(taxes_and_charges_doctype, self.taxes_and_charges, "disabled"):
frappe.throw(
_("{0} '{1}' is disabled").format(taxes_and_charges_doctype, self.taxes_and_charges)
)
@@ -669,7 +684,7 @@ class AccountsController(TransactionBase):
def validate_tax_account_company(self):
for d in self.get("taxes"):
if d.account_head:
tax_account_company = frappe.db.get_value("Account", d.account_head, "company")
tax_account_company = frappe.get_cached_value("Account", d.account_head, "company")
if tax_account_company != self.company:
frappe.throw(
_("Row #{0}: Account {1} does not belong to company {2}").format(
@@ -804,15 +819,12 @@ class AccountsController(TransactionBase):
self.set("advances", [])
advance_allocated = 0
for d in res:
if d.against_order:
allocated_amount = flt(d.amount)
if self.get("party_account_currency") == self.company_currency:
amount = self.get("base_rounded_total") or self.base_grand_total
else:
if self.get("party_account_currency") == self.company_currency:
amount = self.get("base_rounded_total") or self.base_grand_total
else:
amount = self.get("rounded_total") or self.grand_total
amount = self.get("rounded_total") or self.grand_total
allocated_amount = min(amount - advance_allocated, d.amount)
allocated_amount = min(amount - advance_allocated, d.amount)
advance_allocated += flt(allocated_amount)
advance_row = {
@@ -917,7 +929,9 @@ class AccountsController(TransactionBase):
party_account = self.credit_to if is_purchase_invoice else self.debit_to
party_type = "Supplier" if is_purchase_invoice else "Customer"
gain_loss_account = frappe.db.get_value("Company", self.company, "exchange_gain_loss_account")
gain_loss_account = frappe.get_cached_value(
"Company", self.company, "exchange_gain_loss_account"
)
if not gain_loss_account:
frappe.throw(
_("Please set default Exchange Gain/Loss Account in Company {}").format(self.get("company"))
@@ -1014,7 +1028,7 @@ class AccountsController(TransactionBase):
else self.grand_total
),
"outstanding_amount": self.outstanding_amount,
"difference_account": frappe.db.get_value(
"difference_account": frappe.get_cached_value(
"Company", self.company, "exchange_gain_loss_account"
),
"exchange_gain_loss": flt(d.get("exchange_gain_loss")),
@@ -1334,30 +1348,20 @@ class AccountsController(TransactionBase):
return stock_items
def set_total_advance_paid(self):
if self.doctype == "Sales Order":
dr_or_cr = "credit_in_account_currency"
rev_dr_or_cr = "debit_in_account_currency"
party = self.customer
else:
dr_or_cr = "debit_in_account_currency"
rev_dr_or_cr = "credit_in_account_currency"
party = self.supplier
advance = frappe.db.sql(
"""
select
account_currency, sum({dr_or_cr}) - sum({rev_dr_cr}) as amount
from
`tabGL Entry`
where
against_voucher_type = %s and against_voucher = %s and party=%s
and docstatus = 1
""".format(
dr_or_cr=dr_or_cr, rev_dr_cr=rev_dr_or_cr
),
(self.doctype, self.name, party),
as_dict=1,
) # nosec
ple = frappe.qb.DocType("Payment Ledger Entry")
party = self.customer if self.doctype == "Sales Order" else self.supplier
advance = (
frappe.qb.from_(ple)
.select(ple.account_currency, Abs(Sum(ple.amount)).as_("amount"))
.where(
(ple.against_voucher_type == self.doctype)
& (ple.against_voucher_no == self.name)
& (ple.party == party)
& (ple.delinked == 0)
& (ple.company == self.company)
)
.run(as_dict=True)
)
if advance:
advance = advance[0]
@@ -1392,7 +1396,7 @@ class AccountsController(TransactionBase):
@property
def company_abbr(self):
if not hasattr(self, "_abbr"):
self._abbr = frappe.db.get_value("Company", self.company, "abbr")
self._abbr = frappe.get_cached_value("Company", self.company, "abbr")
return self._abbr
@@ -1778,7 +1782,7 @@ class AccountsController(TransactionBase):
"""
if self.is_internal_transfer() and not self.unrealized_profit_loss_account:
unrealized_profit_loss_account = frappe.db.get_value(
unrealized_profit_loss_account = frappe.get_cached_value(
"Company", self.company, "unrealized_profit_loss_account"
)
@@ -1893,7 +1897,9 @@ class AccountsController(TransactionBase):
@frappe.whitelist()
def get_tax_rate(account_head):
return frappe.db.get_value("Account", account_head, ["tax_rate", "account_name"], as_dict=True)
return frappe.get_cached_value(
"Account", account_head, ["tax_rate", "account_name"], as_dict=True
)
@frappe.whitelist()
@@ -1902,7 +1908,7 @@ def get_default_taxes_and_charges(master_doctype, tax_template=None, company=Non
return {}
if tax_template and company:
tax_template_company = frappe.db.get_value(master_doctype, tax_template, "company")
tax_template_company = frappe.get_cached_value(master_doctype, tax_template, "company")
if tax_template_company == company:
return

View File

@@ -85,7 +85,7 @@ def customer_query(doctype, txt, searchfield, start, page_len, filters, as_dict=
fields = ["name"]
if cust_master_name != "Customer Name":
fields = ["customer_name"]
fields.append("customer_name")
fields = get_fields(doctype, fields)
searchfields = frappe.get_meta(doctype).get_search_fields()
@@ -123,7 +123,7 @@ def supplier_query(doctype, txt, searchfield, start, page_len, filters, as_dict=
fields = ["name"]
if supp_master_name != "Supplier Name":
fields = ["supplier_name"]
fields.append("supplier_name")
fields = get_fields(doctype, fields)

View File

@@ -326,7 +326,7 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
company = frappe.db.get_value("Delivery Note", source_name, "company")
default_warehouse_for_sales_return = frappe.db.get_value(
default_warehouse_for_sales_return = frappe.get_cached_value(
"Company", company, "default_warehouse_for_sales_return"
)
@@ -340,11 +340,11 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None):
# look for Print Heading "Credit Note"
if not doc.select_print_heading:
doc.select_print_heading = frappe.db.get_value("Print Heading", _("Credit Note"))
doc.select_print_heading = frappe.get_cached_value("Print Heading", _("Credit Note"))
elif doctype == "Purchase Invoice":
# look for Print Heading "Debit Note"
doc.select_print_heading = frappe.db.get_value("Print Heading", _("Debit Note"))
doc.select_print_heading = frappe.get_cached_value("Print Heading", _("Debit Note"))
for tax in doc.get("taxes") or []:
if tax.charge_type == "Actual":
@@ -503,7 +503,7 @@ 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"},
"field_map": {"serial_no": "serial_no", "batch_no": "batch_no", "bom": "bom"},
"postprocess": update_item,
},
"Payment Schedule": {"doctype": "Payment Schedule", "postprocess": update_terms},

View File

@@ -57,7 +57,7 @@ class StockController(AccountsController):
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
provisional_accounting_for_non_stock_items = cint(
frappe.db.get_value(
frappe.get_cached_value(
"Company", self.company, "enable_provisional_accounting_for_non_stock_items"
)
)
@@ -200,7 +200,7 @@ class StockController(AccountsController):
elif self.get("is_internal_supplier"):
warehouse_asset_account = warehouse_account[item_row.get("warehouse")]["account"]
expense_account = frappe.db.get_value("Company", self.company, "default_expense_account")
expense_account = frappe.get_cached_value("Company", self.company, "default_expense_account")
gl_list.append(
self.get_gl_dict(
@@ -235,7 +235,7 @@ class StockController(AccountsController):
if warehouse_with_no_account:
for wh in warehouse_with_no_account:
if frappe.db.get_value("Warehouse", wh, "company"):
if frappe.get_cached_value("Warehouse", wh, "company"):
frappe.throw(
_(
"Warehouse {0} is not linked to any account, please mention the account in the warehouse record or set default inventory account in company {1}."
@@ -449,15 +449,15 @@ class StockController(AccountsController):
# Get value based on doctype name
if not sl_dict.get(dimension.target_fieldname):
fieldname = frappe.get_cached_value(
"DocField", {"parent": self.doctype, "options": dimension.fetch_from_parent}, "fieldname"
fieldname = next(
(
field.fieldname
for field in frappe.get_meta(self.doctype).fields
if field.options == dimension.fetch_from_parent
),
None,
)
if not fieldname:
fieldname = frappe.get_cached_value(
"Custom Field", {"dt": self.doctype, "options": dimension.fetch_from_parent}, "fieldname"
)
if fieldname and self.get(fieldname):
sl_dict[dimension.target_fieldname] = self.get(fieldname)

View File

@@ -89,6 +89,9 @@ class SubcontractingController(StockController):
if bom.item != item.item_code:
msg = f"Please select an valid BOM for Item {item.item_name}."
frappe.throw(_(msg))
else:
msg = f"Please select a BOM for Item {item.item_name}."
frappe.throw(_(msg))
def __get_data_before_save(self):
item_dict = {}

View File

@@ -58,12 +58,25 @@ class calculate_taxes_and_totals(object):
self.initialize_taxes()
self.determine_exclusive_rate()
self.calculate_net_total()
self.calculate_tax_withholding_net_total()
self.calculate_taxes()
self.manipulate_grand_total_for_inclusive_tax()
self.calculate_totals()
self._cleanup()
self.calculate_total_net_weight()
def calculate_tax_withholding_net_total(self):
if hasattr(self.doc, "tax_withholding_net_total"):
sum_net_amount = 0
sum_base_net_amount = 0
for item in self.doc.get("items"):
if hasattr(item, "apply_tds") and item.apply_tds:
sum_net_amount += item.net_amount
sum_base_net_amount += item.base_net_amount
self.doc.tax_withholding_net_total = sum_net_amount
self.doc.base_tax_withholding_net_total = sum_base_net_amount
def validate_item_tax_template(self):
for item in self.doc.get("items"):
if item.item_code and item.get("item_tax_template"):
@@ -889,24 +902,33 @@ class calculate_taxes_and_totals(object):
self.doc.other_charges_calculation = get_itemised_tax_breakup_html(self.doc)
def set_total_amount_to_default_mop(self, total_amount_to_pay):
default_mode_of_payment = frappe.db.get_value(
"POS Payment Method",
{"parent": self.doc.pos_profile, "default": 1},
["mode_of_payment"],
as_dict=1,
)
if default_mode_of_payment:
self.doc.payments = []
self.doc.append(
"payments",
{
"mode_of_payment": default_mode_of_payment.mode_of_payment,
"amount": total_amount_to_pay,
"default": 1,
},
total_paid_amount = 0
for payment in self.doc.get("payments"):
total_paid_amount += (
payment.amount if self.doc.party_account_currency == self.doc.currency else payment.base_amount
)
pending_amount = total_amount_to_pay - total_paid_amount
if pending_amount > 0:
default_mode_of_payment = frappe.db.get_value(
"POS Payment Method",
{"parent": self.doc.pos_profile, "default": 1},
["mode_of_payment"],
as_dict=1,
)
if default_mode_of_payment:
self.doc.payments = []
self.doc.append(
"payments",
{
"mode_of_payment": default_mode_of_payment.mode_of_payment,
"amount": pending_amount,
"default": 1,
},
)
def get_itemised_tax_breakup_html(doc):
if not doc.taxes:
@@ -1034,7 +1056,7 @@ class init_landed_taxes_and_totals(object):
company_currency = erpnext.get_company_currency(self.doc.company)
for d in self.doc.get(self.tax_field):
if not d.account_currency:
account_currency = frappe.db.get_value("Account", d.expense_account, "account_currency")
account_currency = frappe.get_cached_value("Account", d.expense_account, "account_currency")
d.account_currency = account_currency or company_currency
def set_exchange_rate(self):

View File

@@ -815,6 +815,7 @@ def add_second_row_in_scr(scr):
"item_name",
"qty",
"uom",
"bom",
"warehouse",
"stock_uom",
"subcontracting_order",

View File

@@ -80,7 +80,7 @@ def get_data(filters, conditions):
if conditions.get("trans") == "Quotation" and filters.get("group_by") == "Customer":
cond += " and t1.quotation_to = 'Customer'"
year_start_date, year_end_date = frappe.db.get_value(
year_start_date, year_end_date = frappe.get_cached_value(
"Fiscal Year", filters.get("fiscal_year"), ["year_start_date", "year_end_date"]
)
@@ -275,7 +275,7 @@ def get_period_date_ranges(period, fiscal_year=None, year_start_date=None):
from dateutil.relativedelta import relativedelta
if not year_start_date:
year_start_date, year_end_date = frappe.db.get_value(
year_start_date, year_end_date = frappe.get_cached_value(
"Fiscal Year", fiscal_year, ["year_start_date", "year_end_date"]
)

View File

@@ -60,7 +60,7 @@ class Opportunity(TransactionBase, CRMNote):
if not self.get(field) and frappe.db.field_exists(self.opportunity_from, field):
try:
value = frappe.db.get_value(self.opportunity_from, self.party_name, field)
frappe.db.set(self, field, value)
self.db_set(field, value)
except Exception:
continue

View File

@@ -573,8 +573,8 @@ def regenerate_repayment_schedule(loan, cancel=0):
loan_doc = frappe.get_doc("Loan", loan)
next_accrual_date = None
accrued_entries = 0
last_repayment_amount = 0
last_balance_amount = 0
last_repayment_amount = None
last_balance_amount = None
for term in reversed(loan_doc.get("repayment_schedule")):
if not term.is_accrued:
@@ -582,9 +582,9 @@ def regenerate_repayment_schedule(loan, cancel=0):
loan_doc.remove(term)
else:
accrued_entries += 1
if not last_repayment_amount:
if last_repayment_amount is None:
last_repayment_amount = term.total_payment
if not last_balance_amount:
if last_balance_amount is None:
last_balance_amount = term.balance_loan_amount
loan_doc.save()

View File

@@ -119,7 +119,7 @@ class MaintenanceSchedule(TransactionBase):
event.add_participant(self.doctype, self.name)
event.insert(ignore_permissions=1)
frappe.db.set(self, "status", "Submitted")
self.db_set("status", "Submitted")
def create_schedule_list(self, start_date, end_date, no_of_visit, sales_person):
schedule_list = []
@@ -245,7 +245,7 @@ class MaintenanceSchedule(TransactionBase):
self.generate_schedule()
def on_update(self):
frappe.db.set(self, "status", "Draft")
self.db_set("status", "Draft")
def update_amc_date(self, serial_nos, amc_expiry_date=None):
for serial_no in serial_nos:
@@ -344,7 +344,7 @@ class MaintenanceSchedule(TransactionBase):
if d.serial_no:
serial_nos = get_valid_serial_nos(d.serial_no)
self.update_amc_date(serial_nos)
frappe.db.set(self, "status", "Cancelled")
self.db_set("status", "Cancelled")
delete_events(self.doctype, self.name)
def on_trash(self):

View File

@@ -125,12 +125,12 @@ class MaintenanceVisit(TransactionBase):
def on_submit(self):
self.update_customer_issue(1)
frappe.db.set(self, "status", "Submitted")
self.db_set("status", "Submitted")
self.update_status_and_actual_date()
def on_cancel(self):
self.check_if_last_visit()
frappe.db.set(self, "status", "Cancelled")
self.db_set("status", "Cancelled")
self.update_status_and_actual_date(cancel=True)
def on_update(self):

View File

@@ -206,8 +206,8 @@ class BOM(WebsiteGenerator):
self.manage_default_bom()
def on_cancel(self):
frappe.db.set(self, "is_active", 0)
frappe.db.set(self, "is_default", 0)
self.db_set("is_active", 0)
self.db_set("is_default", 0)
# check if used in any other bom
self.validate_bom_links()
@@ -449,10 +449,10 @@ class BOM(WebsiteGenerator):
not frappe.db.exists(dict(doctype="BOM", docstatus=1, item=self.item, is_default=1))
and self.is_active
):
frappe.db.set(self, "is_default", 1)
self.db_set("is_default", 1)
frappe.db.set_value("Item", self.item, "default_bom", self.name)
else:
frappe.db.set(self, "is_default", 0)
self.db_set("is_default", 0)
item = frappe.get_doc("Item", self.item)
if item.default_bom == self.name:
frappe.db.set_value("Item", self.item, "default_bom", None)

View File

@@ -7,6 +7,8 @@ import frappe
from frappe import _, bold
from frappe.model.document import Document
from frappe.model.mapper import get_mapped_doc
from frappe.query_builder import Criterion
from frappe.query_builder.functions import IfNull, Max, Min
from frappe.utils import (
add_days,
add_to_date,
@@ -54,6 +56,9 @@ class JobCard(Document):
self.set_onload("job_card_excess_transfer", excess_transfer)
self.set_onload("work_order_closed", self.is_work_order_closed())
def before_validate(self):
self.set_wip_warehouse()
def validate(self):
self.validate_time_logs()
self.set_status()
@@ -109,43 +114,44 @@ class JobCard(Document):
def get_overlap_for(self, args, check_next_available_slot=False):
production_capacity = 1
jc = frappe.qb.DocType("Job Card")
jctl = frappe.qb.DocType("Job Card Time Log")
time_conditions = [
((jctl.from_time < args.from_time) & (jctl.to_time > args.from_time)),
((jctl.from_time < args.to_time) & (jctl.to_time > args.to_time)),
((jctl.from_time >= args.from_time) & (jctl.to_time <= args.to_time)),
]
if check_next_available_slot:
time_conditions.append(((jctl.from_time >= args.from_time) & (jctl.to_time >= args.to_time)))
query = (
frappe.qb.from_(jctl)
.from_(jc)
.select(jc.name.as_("name"), jctl.to_time)
.where(
(jctl.parent == jc.name)
& (Criterion.any(time_conditions))
& (jctl.name != f"{args.name or 'No Name'}")
& (jc.name != f"{args.parent or 'No Name'}")
& (jc.docstatus < 2)
)
.orderby(jctl.to_time, order=frappe.qb.desc)
)
if self.workstation:
production_capacity = (
frappe.get_cached_value("Workstation", self.workstation, "production_capacity") or 1
)
validate_overlap_for = " and jc.workstation = %(workstation)s "
query = query.where(jc.workstation == self.workstation)
if args.get("employee"):
# override capacity for employee
production_capacity = 1
validate_overlap_for = " and jctl.employee = %(employee)s "
query = query.where(jctl.employee == args.get("employee"))
extra_cond = ""
if check_next_available_slot:
extra_cond = " or (%(from_time)s <= jctl.from_time and %(to_time)s <= jctl.to_time)"
existing = frappe.db.sql(
"""select jc.name as name, jctl.to_time from
`tabJob Card Time Log` jctl, `tabJob Card` jc where jctl.parent = jc.name and
(
(%(from_time)s > jctl.from_time and %(from_time)s < jctl.to_time) or
(%(to_time)s > jctl.from_time and %(to_time)s < jctl.to_time) or
(%(from_time)s <= jctl.from_time and %(to_time)s >= jctl.to_time) {0}
)
and jctl.name != %(name)s and jc.name != %(parent)s and jc.docstatus < 2 {1}
order by jctl.to_time desc""".format(
extra_cond, validate_overlap_for
),
{
"from_time": args.from_time,
"to_time": args.to_time,
"name": args.name or "No Name",
"parent": args.parent or "No Name",
"employee": args.get("employee"),
"workstation": self.workstation,
},
as_dict=True,
)
existing = query.run(as_dict=True)
if existing and production_capacity > len(existing):
return
@@ -485,18 +491,21 @@ class JobCard(Document):
)
def update_work_order_data(self, for_quantity, time_in_mins, wo):
time_data = frappe.db.sql(
"""
SELECT
min(from_time) as start_time, max(to_time) as end_time
FROM `tabJob Card` jc, `tabJob Card Time Log` jctl
WHERE
jctl.parent = jc.name and jc.work_order = %s and jc.operation_id = %s
and jc.docstatus = 1 and IFNULL(jc.is_corrective_job_card, 0) = 0
""",
(self.work_order, self.operation_id),
as_dict=1,
)
jc = frappe.qb.DocType("Job Card")
jctl = frappe.qb.DocType("Job Card Time Log")
time_data = (
frappe.qb.from_(jc)
.from_(jctl)
.select(Min(jctl.from_time).as_("start_time"), Max(jctl.to_time).as_("end_time"))
.where(
(jctl.parent == jc.name)
& (jc.work_order == self.work_order)
& (jc.operation_id == self.operation_id)
& (jc.docstatus == 1)
& (IfNull(jc.is_corrective_job_card, 0) == 0)
)
).run(as_dict=True)
for data in wo.operations:
if data.get("name") == self.operation_id:
@@ -639,6 +648,12 @@ class JobCard(Document):
if update_status:
self.db_set("status", self.status)
def set_wip_warehouse(self):
if not self.wip_warehouse:
self.wip_warehouse = frappe.db.get_single_value(
"Manufacturing Settings", "default_wip_warehouse"
)
def validate_operation_id(self):
if (
self.get("operation_id")

View File

@@ -649,23 +649,13 @@ class ProductionPlan(Document):
else:
material_request = material_request_map[key]
conversion_factor = 1.0
if (
material_request_type == "Purchase"
and item_doc.purchase_uom
and item_doc.purchase_uom != item_doc.stock_uom
):
conversion_factor = (
get_conversion_factor(item_doc.name, item_doc.purchase_uom).get("conversion_factor") or 1.0
)
# add item
material_request.append(
"items",
{
"item_code": item.item_code,
"from_warehouse": item.from_warehouse,
"qty": item.quantity / conversion_factor,
"qty": item.quantity,
"schedule_date": schedule_date,
"warehouse": item.warehouse,
"sales_order": item.sales_order,
@@ -1053,11 +1043,25 @@ def get_material_request_items(
if include_safety_stock:
required_qty += flt(row["safety_stock"])
item_details = frappe.get_cached_value(
"Item", row.item_code, ["purchase_uom", "stock_uom"], as_dict=1
)
conversion_factor = 1.0
if (
row.get("default_material_request_type") == "Purchase"
and item_details.purchase_uom
and item_details.purchase_uom != item_details.stock_uom
):
conversion_factor = (
get_conversion_factor(row.item_code, item_details.purchase_uom).get("conversion_factor") or 1.0
)
if required_qty > 0:
return {
"item_code": row.item_code,
"item_name": row.item_name,
"quantity": required_qty,
"quantity": required_qty / conversion_factor,
"required_bom_qty": total_qty,
"stock_uom": row.get("stock_uom"),
"warehouse": warehouse

View File

@@ -826,6 +826,11 @@ class TestProductionPlan(FrappeTestCase):
)
pln.make_material_request()
for row in pln.mr_items:
self.assertEqual(row.uom, "Nos")
self.assertEqual(row.quantity, 1)
for row in frappe.get_all(
"Material Request Item",
filters={"production_plan": pln.name},

View File

@@ -589,66 +589,69 @@ erpnext.work_order = {
}
}
if(!frm.doc.skip_transfer){
if (frm.doc.status != 'Stopped') {
// If "Material Consumption is check in Manufacturing Settings, allow Material Consumption
if (flt(doc.material_transferred_for_manufacturing) > 0 && frm.doc.status != 'Stopped') {
if ((flt(doc.produced_qty) < flt(doc.material_transferred_for_manufacturing))) {
frm.has_finish_btn = true;
if (frm.doc.__onload && frm.doc.__onload.material_consumption == 1) {
// Only show "Material Consumption" when required_qty > consumed_qty
var counter = 0;
var tbl = frm.doc.required_items || [];
var tbl_lenght = tbl.length;
for (var i = 0, len = tbl_lenght; i < len; i++) {
let wo_item_qty = frm.doc.required_items[i].transferred_qty || frm.doc.required_items[i].required_qty;
if (flt(wo_item_qty) > flt(frm.doc.required_items[i].consumed_qty)) {
counter += 1;
}
}
if (counter > 0) {
var consumption_btn = frm.add_custom_button(__('Material Consumption'), function() {
const backflush_raw_materials_based_on = frm.doc.__onload.backflush_raw_materials_based_on;
erpnext.work_order.make_consumption_se(frm, backflush_raw_materials_based_on);
});
consumption_btn.addClass('btn-primary');
if (frm.doc.__onload && frm.doc.__onload.material_consumption == 1) {
if (flt(doc.material_transferred_for_manufacturing) > 0 || frm.doc.skip_transfer) {
// Only show "Material Consumption" when required_qty > consumed_qty
var counter = 0;
var tbl = frm.doc.required_items || [];
var tbl_lenght = tbl.length;
for (var i = 0, len = tbl_lenght; i < len; i++) {
let wo_item_qty = frm.doc.required_items[i].transferred_qty || frm.doc.required_items[i].required_qty;
if (flt(wo_item_qty) > flt(frm.doc.required_items[i].consumed_qty)) {
counter += 1;
}
}
if (counter > 0) {
var consumption_btn = frm.add_custom_button(__('Material Consumption'), function() {
const backflush_raw_materials_based_on = frm.doc.__onload.backflush_raw_materials_based_on;
erpnext.work_order.make_consumption_se(frm, backflush_raw_materials_based_on);
});
consumption_btn.addClass('btn-primary');
}
}
}
if(!frm.doc.skip_transfer){
if (flt(doc.material_transferred_for_manufacturing) > 0) {
if ((flt(doc.produced_qty) < flt(doc.material_transferred_for_manufacturing))) {
frm.has_finish_btn = true;
var finish_btn = frm.add_custom_button(__('Finish'), function() {
erpnext.work_order.make_se(frm, 'Manufacture');
});
if(doc.material_transferred_for_manufacturing>=doc.qty) {
// all materials transferred for manufacturing, make this primary
finish_btn.addClass('btn-primary');
}
} else {
frappe.db.get_doc("Manufacturing Settings").then((doc) => {
let allowance_percentage = doc.overproduction_percentage_for_work_order;
if (allowance_percentage > 0) {
let allowed_qty = frm.doc.qty + ((allowance_percentage / 100) * frm.doc.qty);
if ((flt(doc.produced_qty) < allowed_qty)) {
frm.add_custom_button(__('Finish'), function() {
erpnext.work_order.make_se(frm, 'Manufacture');
});
}
}
});
}
}
} else {
if ((flt(doc.produced_qty) < flt(doc.qty))) {
var finish_btn = frm.add_custom_button(__('Finish'), function() {
erpnext.work_order.make_se(frm, 'Manufacture');
});
if(doc.material_transferred_for_manufacturing>=doc.qty) {
// all materials transferred for manufacturing, make this primary
finish_btn.addClass('btn-primary');
}
} else {
frappe.db.get_doc("Manufacturing Settings").then((doc) => {
let allowance_percentage = doc.overproduction_percentage_for_work_order;
if (allowance_percentage > 0) {
let allowed_qty = frm.doc.qty + ((allowance_percentage / 100) * frm.doc.qty);
if ((flt(doc.produced_qty) < allowed_qty)) {
frm.add_custom_button(__('Finish'), function() {
erpnext.work_order.make_se(frm, 'Manufacture');
});
}
}
});
finish_btn.addClass('btn-primary');
}
}
} else {
if ((flt(doc.produced_qty) < flt(doc.qty)) && frm.doc.status != 'Stopped') {
var finish_btn = frm.add_custom_button(__('Finish'), function() {
erpnext.work_order.make_se(frm, 'Manufacture');
});
finish_btn.addClass('btn-primary');
}
}
}
},
calculate_cost: function(doc) {
if (doc.operations){

View File

@@ -146,7 +146,7 @@ class WorkOrder(Document):
frappe.throw(_("Sales Order {0} is {1}").format(self.sales_order, status))
def set_default_warehouse(self):
if not self.wip_warehouse:
if not self.wip_warehouse and not self.skip_transfer:
self.wip_warehouse = frappe.db.get_single_value(
"Manufacturing Settings", "default_wip_warehouse"
)
@@ -373,7 +373,7 @@ class WorkOrder(Document):
def on_cancel(self):
self.validate_cancel()
frappe.db.set(self, "status", "Cancelled")
self.db_set("status", "Cancelled")
if self.production_plan and frappe.db.exists(
"Production Plan Item Reference", {"parent": self.production_plan}

View File

@@ -100,6 +100,7 @@ def get_default_holiday_list():
def check_if_within_operating_hours(workstation, operation, from_datetime, to_datetime):
if from_datetime and to_datetime:
if not cint(
frappe.db.get_value("Manufacturing Settings", "None", "allow_production_on_holidays")
):

View File

@@ -317,4 +317,4 @@ erpnext.patches.v14_0.fix_subcontracting_receipt_gl_entries
erpnext.patches.v14_0.migrate_remarks_from_gl_to_payment_ledger
erpnext.patches.v14_0.create_accounting_dimensions_for_asset_capitalization
erpnext.patches.v13_0.update_schedule_type_in_loans
erpnext.patches.v14_0.update_tds_fields

View File

@@ -0,0 +1,29 @@
import frappe
from frappe.utils import nowdate
from erpnext.accounts.utils import FiscalYearError, get_fiscal_year
def execute():
# Only do for current fiscal year, no need to repost for all years
for company in frappe.get_all("Company"):
try:
fiscal_year_details = get_fiscal_year(date=nowdate(), company=company.name, as_dict=True)
purchase_invoice = frappe.qb.DocType("Purchase Invoice")
frappe.qb.update(purchase_invoice).set(
purchase_invoice.tax_withholding_net_total, purchase_invoice.net_total
).set(
purchase_invoice.base_tax_withholding_net_total, purchase_invoice.base_net_total
).where(
purchase_invoice.company == company.name
).where(
purchase_invoice.apply_tds == 1
).where(
purchase_invoice.posting_date >= fiscal_year_details.year_start_date
).where(
purchase_invoice.docstatus == 1
).run()
except FiscalYearError:
pass

View File

@@ -92,18 +92,26 @@ frappe.ui.form.on("Timesheet", {
frm.fields_dict["time_logs"].grid.toggle_enable("billing_hours", false);
frm.fields_dict["time_logs"].grid.toggle_enable("is_billable", false);
}
let filters = {
"status": "Open"
};
if (frm.doc.customer) {
filters["customer"] = frm.doc.customer;
}
frm.set_query('parent_project', function(doc) {
return {
filters: filters
};
});
frm.trigger('setup_filters');
frm.trigger('set_dynamic_field_label');
},
customer: function(frm) {
frm.set_query('parent_project', function(doc) {
return {
filters: {
"customer": doc.customer
}
};
});
frm.set_query('project', 'time_logs', function(doc) {
return {
filters: {

View File

@@ -30,28 +30,28 @@ erpnext.accounts.bank_reconciliation.DataTableManager = class DataTableManager {
get_dt_columns() {
this.columns = [
{
name: "Date",
name: __("Date"),
editable: false,
width: 100,
},
{
name: "Party Type",
name: __("Party Type"),
editable: false,
width: 95,
},
{
name: "Party",
name: __("Party"),
editable: false,
width: 100,
},
{
name: "Description",
name: __("Description"),
editable: false,
width: 350,
},
{
name: "Deposit",
name: __("Deposit"),
editable: false,
width: 100,
format: (value) =>
@@ -60,7 +60,7 @@ erpnext.accounts.bank_reconciliation.DataTableManager = class DataTableManager {
"</span>",
},
{
name: "Withdrawal",
name: __("Withdrawal"),
editable: false,
width: 100,
format: (value) =>
@@ -69,26 +69,26 @@ erpnext.accounts.bank_reconciliation.DataTableManager = class DataTableManager {
"</span>",
},
{
name: "Unallocated Amount",
name: __("Unallocated Amount"),
editable: false,
width: 100,
format: (value) =>
"<span style='color:blue;'>" +
"<span style='color:var(--blue-500);'>" +
format_currency(value, this.currency) +
"</span>",
},
{
name: "Reference Number",
name: __("Reference Number"),
editable: false,
width: 140,
},
{
name: "Actions",
name: __("Actions"),
editable: false,
sortable: false,
focusable: false,
dropdown: false,
width: 80,
width: 100,
},
];
}
@@ -118,7 +118,7 @@ erpnext.accounts.bank_reconciliation.DataTableManager = class DataTableManager {
row["reference_number"],
`
<Button class="btn btn-primary btn-xs center" data-name = ${row["name"]} >
Actions
${__("Actions")}
</a>
`,
];

View File

@@ -87,33 +87,33 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager {
get_dt_columns() {
this.columns = [
{
name: "Document Type",
name: __("Document Type"),
editable: false,
width: 125,
},
{
name: "Document Name",
name: __("Document Name"),
editable: false,
width: 150,
},
{
name: "Reference Date",
name: __("Reference Date"),
editable: false,
width: 120,
},
{
name: "Amount",
name: __("Amount"),
editable: false,
width: 100,
},
{
name: "Party",
name: __("Party"),
editable: false,
width: 120,
},
{
name: "Reference Number",
name: __("Reference Number"),
editable: false,
width: 140,
},
@@ -222,7 +222,7 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager {
{
fieldtype: "HTML",
fieldname: "no_matching_vouchers",
options: "<div class=\"text-muted text-center\">No Matching Vouchers Found</div>"
options: __("<div class=\"text-muted text-center\">{0}</div>", [__("No Matching Vouchers Found")])
},
{
fieldtype: "Section Break",
@@ -444,10 +444,7 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager {
vouchers: vouchers,
},
callback: (response) => {
const alert_string =
"Bank Transaction " +
this.bank_transaction.name +
" Matched";
const alert_string = __("Bank Transaction {0} Matched", [this.bank_transaction.name]);
frappe.show_alert(alert_string);
this.update_dt_cards(response.message);
this.dialog.hide();
@@ -471,10 +468,7 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager {
cost_center: values.cost_center,
},
callback: (response) => {
const alert_string =
"Bank Transaction " +
this.bank_transaction.name +
" added as Payment Entry";
const alert_string = __("Bank Transaction {0} added as Payment Entry", [this.bank_transaction.name]);
frappe.show_alert(alert_string);
this.update_dt_cards(response.message);
this.dialog.hide();
@@ -498,10 +492,7 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager {
second_account: values.second_account,
},
callback: (response) => {
const alert_string =
"Bank Transaction " +
this.bank_transaction.name +
" added as Journal Entry";
const alert_string = __("Bank Transaction {0} added as Journal Entry", [this.bank_transaction.name]);
frappe.show_alert(alert_string);
this.update_dt_cards(response.message);
this.dialog.hide();
@@ -520,10 +511,7 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager {
party: values.party,
},
callback: (response) => {
const alert_string =
"Bank Transaction " +
this.bank_transaction.name +
" updated";
const alert_string = __("Bank Transaction {0} updated", [this.bank_transaction.name]);
frappe.show_alert(alert_string);
this.update_dt_cards(response.message);
this.dialog.hide();

View File

@@ -15,20 +15,20 @@ erpnext.accounts.bank_reconciliation.NumberCardManager = class NumberCardManager
var chart_data = [
{
value: this.bank_statement_closing_balance,
label: "Closing Balance as per Bank Statement",
label: __("Closing Balance as per Bank Statement"),
datatype: "Currency",
currency: this.currency,
},
{
value: this.cleared_balance,
label: "Closing Balance as per ERP",
label: __("Closing Balance as per ERP"),
datatype: "Currency",
currency: this.currency,
},
{
value:
this.bank_statement_closing_balance - this.cleared_balance,
label: "Difference",
label: __("Difference"),
datatype: "Currency",
currency: this.currency,
},

View File

@@ -341,6 +341,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
this.set_dynamic_labels();
this.setup_sms();
this.setup_quality_inspection();
this.validate_has_items();
}
scan_barcode() {
@@ -348,6 +349,12 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
barcode_scanner.process_scan();
}
validate_has_items () {
let table = this.frm.doc.items;
this.frm.has_items = (table && table.length
&& table[0].qty && table[0].item_code);
}
apply_default_taxes() {
var me = this;
var taxes_and_charges_field = frappe.meta.get_docfield(me.frm.doc.doctype, "taxes_and_charges",
@@ -1200,7 +1207,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
"base_rounding_adjustment"], company_currency);
this.frm.set_currency_labels(["total", "net_total", "total_taxes_and_charges", "discount_amount",
"grand_total", "taxes_and_charges_added", "taxes_and_charges_deducted",
"grand_total", "taxes_and_charges_added", "taxes_and_charges_deducted","tax_withholding_net_total",
"rounded_total", "in_words", "paid_amount", "write_off_amount", "operating_cost",
"scrap_material_cost", "rounding_adjustment", "raw_material_cost",
"total_cost"], this.frm.doc.currency);
@@ -1217,7 +1224,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
}
// toggle fields
this.frm.toggle_display(["conversion_rate", "base_total", "base_net_total",
this.frm.toggle_display(["conversion_rate", "base_total", "base_net_total", "base_tax_withholding_net_total",
"base_total_taxes_and_charges", "base_taxes_and_charges_added", "base_taxes_and_charges_deducted",
"base_grand_total", "base_rounded_total", "base_in_words", "base_discount_amount",
"base_paid_amount", "base_write_off_amount", "base_operating_cost", "base_raw_material_cost",
@@ -1404,7 +1411,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
if (!r.exc && r.message) {
me._set_values_for_item_list(r.message);
if(item) me.set_gross_profit(item);
if(me.frm.doc.apply_discount_on) me.frm.trigger("apply_discount_on")
if (me.frm.doc.apply_discount_on) me.frm.trigger("apply_discount_on")
}
}
});
@@ -1577,6 +1584,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
for (let key in pr_row) {
row_to_modify[key] = pr_row[key];
}
this.frm.script_manager.copy_from_first_row("items", row_to_modify, ["expense_account", "income_account"]);
});
// free_item_data is a temporary variable

View File

@@ -21,6 +21,11 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
this.items_table_name = opts.items_table_name || "items";
this.items_table = this.frm.doc[this.items_table_name];
// optional sound name to play when scan either fails or passes.
// see https://frappeframework.com/docs/v14/user/en/python-api/hooks#sounds
this.success_sound = opts.play_success_sound;
this.fail_sound = opts.play_fail_sound;
// any API that takes `search_value` as input and returns dictionary as follows
// {
// item_code: "HORSESHOE", // present if any item was found
@@ -42,58 +47,74 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
return;
}
frappe
.call({
method: this.scan_api,
args: {
search_value: input,
},
})
.then((r) => {
const data = r && r.message;
if (!data || Object.keys(data).length === 0) {
this.show_alert(__("Cannot find Item with this Barcode"), "red");
this.clean_up();
reject();
return;
}
this.scan_api_call(input, (r) => {
const data = r && r.message;
if (!data || Object.keys(data).length === 0) {
this.show_alert(__("Cannot find Item with this Barcode"), "red");
this.clean_up();
this.play_fail_sound();
reject();
return;
}
me.update_table(data).then(row => {
row ? resolve(row) : reject();
});
me.update_table(data).then(row => {
this.play_success_sound();
resolve(row);
}).catch(() => {
this.play_fail_sound();
reject();
});
});
});
}
scan_api_call(input, callback) {
frappe
.call({
method: this.scan_api,
args: {
search_value: input,
},
})
.then((r) => {
callback(r);
});
}
update_table(data) {
return new Promise(resolve => {
let cur_grid = this.frm.fields_dict[this.items_table_name].grid;
const {item_code, barcode, batch_no, serial_no, uom} = data;
let row = this.get_row_to_modify_on_scan(item_code, batch_no, uom);
let row = this.get_row_to_modify_on_scan(item_code, batch_no, uom, barcode);
this.is_new_row = false;
if (!row) {
if (this.dont_allow_new_row) {
this.show_alert(__("Maximum quantity scanned for item {0}.", [item_code]), "red");
this.clean_up();
reject();
return;
}
this.is_new_row = true;
// add new row if new item/batch is scanned
row = frappe.model.add_child(this.frm.doc, cur_grid.doctype, this.items_table_name);
// trigger any row add triggers defined on child table.
this.frm.script_manager.trigger(`${this.items_table_name}_add`, row.doctype, row.name);
this.frm.has_items = false;
}
if (this.is_duplicate_serial_no(row, serial_no)) {
this.clean_up();
reject();
return;
}
frappe.run_serially([
() => this.set_selector_trigger_flag(data),
() => this.set_item(row, item_code).then(qty => {
() => this.set_item(row, item_code, barcode, batch_no, serial_no).then(qty => {
this.show_scan_message(row.idx, row.item_code, qty);
}),
() => this.set_barcode_uom(row, uom),
@@ -124,7 +145,7 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
frappe.flags.hide_serial_batch_dialog = false;
}
set_item(row, item_code) {
set_item(row, item_code, barcode, batch_no, serial_no) {
return new Promise(resolve => {
const increment = async (value = 1) => {
const item_data = {item_code: item_code};
@@ -137,12 +158,186 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
frappe.prompt(__("Please enter quantity for item {0}", [item_code]), ({value}) => {
increment(value).then((value) => resolve(value));
});
} else if (this.frm.has_items) {
this.prepare_item_for_scan(row, item_code, barcode, batch_no, serial_no);
} else {
increment().then((value) => resolve(value));
}
});
}
prepare_item_for_scan(row, item_code, barcode, batch_no, serial_no) {
var me = this;
this.dialog = new frappe.ui.Dialog({
title: __("Scan barcode for item {0}", [item_code]),
fields: me.get_fields_for_dialog(row, item_code, barcode, batch_no, serial_no),
})
this.dialog.set_primary_action(__("Update"), () => {
const item_data = {item_code: item_code};
item_data[this.qty_field] = this.dialog.get_value("scanned_qty");
item_data["has_item_scanned"] = 1;
this.remaining_qty = flt(this.dialog.get_value("qty")) - flt(this.dialog.get_value("scanned_qty"));
frappe.model.set_value(row.doctype, row.name, item_data);
frappe.run_serially([
() => this.set_batch_no(row, this.dialog.get_value("batch_no")),
() => this.set_barcode(row, this.dialog.get_value("barcode")),
() => this.set_serial_no(row, this.dialog.get_value("serial_no")),
() => this.add_child_for_remaining_qty(row),
() => this.clean_up()
]);
this.dialog.hide();
});
this.dialog.show();
this.$scan_btn = this.dialog.$wrapper.find(".link-btn");
this.$scan_btn.css("display", "inline");
}
get_fields_for_dialog(row, item_code, barcode, batch_no, serial_no) {
let fields = [
{
fieldtype: "Data",
fieldname: "barcode_scanner",
options: "Barcode",
label: __("Scan Barcode"),
onchange: (e) => {
if (!e) {
return;
}
if (e.target.value) {
this.scan_api_call(e.target.value, (r) => {
if (r.message) {
this.update_dialog_values(item_code, r);
}
})
}
}
},
{
fieldtype: "Section Break",
},
{
fieldtype: "Float",
fieldname: "qty",
label: __("Quantity to Scan"),
default: row[this.qty_field] || 1,
},
{
fieldtype: "Column Break",
fieldname: "column_break_1",
},
{
fieldtype: "Float",
read_only: 1,
fieldname: "scanned_qty",
label: __("Scanned Quantity"),
default: 1,
},
{
fieldtype: "Section Break",
}
]
if (batch_no) {
fields.push({
fieldtype: "Link",
fieldname: "batch_no",
options: "Batch No",
label: __("Batch No"),
default: batch_no,
read_only: 1,
hidden: 1
});
}
if (serial_no) {
fields.push({
fieldtype: "Small Text",
fieldname: "serial_no",
label: __("Serial Nos"),
default: serial_no,
read_only: 1,
});
}
if (barcode) {
fields.push({
fieldtype: "Data",
fieldname: "barcode",
options: "Barcode",
label: __("Barcode"),
default: barcode,
read_only: 1,
hidden: 1
});
}
return fields;
}
update_dialog_values(scanned_item, r) {
const {item_code, barcode, batch_no, serial_no} = r.message;
this.dialog.set_value("barcode_scanner", "");
if (item_code === scanned_item &&
(this.dialog.get_value("barcode") === barcode || batch_no || serial_no)) {
if (batch_no) {
this.dialog.set_value("batch_no", batch_no);
}
if (serial_no) {
this.validate_duplicate_serial_no(serial_no);
let serial_nos = this.dialog.get_value("serial_no") + "\n" + serial_no;
this.dialog.set_value("serial_no", serial_nos);
}
let qty = flt(this.dialog.get_value("scanned_qty")) + 1.0;
this.dialog.set_value("scanned_qty", qty);
}
}
validate_duplicate_serial_no(serial_no) {
let serial_nos = this.dialog.get_value("serial_no") ?
this.dialog.get_value("serial_no").split("\n") : [];
if (in_list(serial_nos, serial_no)) {
frappe.throw(__("Serial No {0} already scanned", [serial_no]));
}
}
add_child_for_remaining_qty(prev_row) {
if (this.remaining_qty && this.remaining_qty >0) {
let cur_grid = this.frm.fields_dict[this.items_table_name].grid;
let row = frappe.model.add_child(this.frm.doc, cur_grid.doctype, this.items_table_name);
let ignore_fields = ["name", "idx", "batch_no", "barcode",
"received_qty", "serial_no", "has_item_scanned"];
for (let key in prev_row) {
if (in_list(ignore_fields, key)) {
continue;
}
row[key] = prev_row[key];
}
row[this.qty_field] = this.remaining_qty;
if (this.qty_field == "qty" && frappe.meta.has_field(row.doctype, "stock_qty")) {
row["stock_qty"] = this.remaining_qty * row.conversion_factor;
}
this.frm.script_manager.trigger("item_code", row.doctype, row.name);
}
}
async set_serial_no(row, serial_no) {
if (serial_no && frappe.meta.has_field(row.doctype, this.serial_no_field)) {
const existing_serial_nos = row[this.serial_no_field];
@@ -193,7 +388,7 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
return is_duplicate;
}
get_row_to_modify_on_scan(item_code, batch_no, uom) {
get_row_to_modify_on_scan(item_code, batch_no, uom, barcode) {
let cur_grid = this.frm.fields_dict[this.items_table_name].grid;
// Check if batch is scanned and table has batch no field
@@ -202,12 +397,14 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
const matching_row = (row) => {
const item_match = row.item_code == item_code;
const batch_match = row[this.batch_no_field] == batch_no;
const batch_match = (!row[this.batch_no_field] || row[this.batch_no_field] == batch_no);
const uom_match = !uom || row[this.uom_field] == uom;
const qty_in_limit = flt(row[this.qty_field]) < flt(row[this.max_qty_field]);
const item_scanned = row.has_item_scanned;
return item_match
&& uom_match
&& !item_scanned
&& (!is_batch_no_scan || batch_match)
&& (!check_max_qty || qty_in_limit)
}
@@ -219,6 +416,14 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
return this.items_table.find((d) => !d.item_code);
}
play_success_sound() {
this.success_sound && frappe.utils.play_sound(this.success_sound);
}
play_fail_sound() {
this.fail_sound && frappe.utils.play_sound(this.fail_sound);
}
clean_up() {
this.scan_barcode_field.set_value("");
refresh_field(this.items_table_name);

View File

@@ -49,7 +49,7 @@ def make_custom_fields(update=True):
dict(
fieldname="exempt_from_sales_tax",
fieldtype="Check",
insert_after="represents_company",
insert_after="dn_required",
label="Is customer exempted from sales tax?",
)
],

View File

@@ -14,30 +14,35 @@
"naming_series",
"salutation",
"customer_name",
"customer_type",
"customer_group",
"column_break0",
"territory",
"gender",
"default_bank_account",
"tax_id",
"tax_category",
"tax_withholding_category",
"lead_name",
"opportunity_name",
"image",
"column_break0",
"customer_group",
"customer_type",
"territory",
"account_manager",
"so_required",
"dn_required",
"image",
"defaults_tab",
"default_price_list",
"default_bank_account",
"column_break_14",
"default_currency",
"internal_customer_section",
"is_internal_customer",
"represents_company",
"disabled",
"allowed_to_transact_section",
"column_break_70",
"companies",
"currency_and_price_list",
"default_currency",
"column_break_14",
"default_price_list",
"more_info",
"market_segment",
"industry",
"customer_pos_id",
"website",
"language",
"column_break_45",
"customer_details",
"dashboard_tab",
"contact_and_address_tab",
"address_contacts",
"address_html",
"column_break1",
@@ -49,34 +54,39 @@
"column_break_26",
"customer_primary_address",
"primary_address",
"default_receivable_accounts",
"accounts",
"tax_tab",
"taxation_section",
"tax_id",
"column_break_21",
"tax_category",
"tax_withholding_category",
"accounting_tab",
"credit_limit_section",
"payment_terms",
"credit_limits",
"more_info",
"customer_details",
"column_break_45",
"market_segment",
"industry",
"website",
"language",
"is_frozen",
"column_break_38",
"default_receivable_accounts",
"accounts",
"loyalty_points_tab",
"loyalty_program",
"column_break_54",
"loyalty_program_tier",
"sales_team_section_break",
"default_sales_partner",
"default_commission_rate",
"sales_team_section",
"sales_team_tab",
"sales_team",
"customer_pos_id"
"sales_team_section",
"default_sales_partner",
"column_break_66",
"default_commission_rate",
"settings_tab",
"so_required",
"dn_required",
"column_break_53",
"is_frozen",
"disabled"
],
"fields": [
{
"fieldname": "basic_info",
"fieldtype": "Section Break",
"label": "Name and Type",
"oldfieldtype": "Section Break",
"options": "fa fa-user"
},
@@ -215,12 +225,6 @@
"options": "Company",
"unique": 1
},
{
"depends_on": "represents_company",
"fieldname": "allowed_to_transact_section",
"fieldtype": "Section Break",
"label": "Allowed To Transact With"
},
{
"depends_on": "represents_company",
"fieldname": "companies",
@@ -228,12 +232,6 @@
"label": "Allowed To Transact With",
"options": "Allowed To Transact With"
},
{
"collapsible": 1,
"fieldname": "currency_and_price_list",
"fieldtype": "Section Break",
"label": "Currency and Price List"
},
{
"fieldname": "default_currency",
"fieldtype": "Link",
@@ -295,7 +293,7 @@
"description": "Select, to make the customer searchable with these fields",
"fieldname": "primary_address_and_contact_detail",
"fieldtype": "Section Break",
"label": "Primary Address and Contact Detail"
"label": "Primary Address and Contact"
},
{
"description": "Reselect, if the chosen contact is edited after save",
@@ -334,20 +332,18 @@
"read_only": 1
},
{
"collapsible": 1,
"fieldname": "default_receivable_accounts",
"fieldtype": "Section Break",
"label": "Default Receivable Accounts"
},
{
"description": "Mention if non-standard receivable account",
"description": "Mention if a non-standard receivable account",
"fieldname": "accounts",
"fieldtype": "Table",
"label": "Accounts",
"label": "Receivable Accounts",
"options": "Party Account"
},
{
"collapsible": 1,
"fieldname": "credit_limit_section",
"fieldtype": "Section Break",
"label": "Credit Limit and Payment Terms"
@@ -397,12 +393,6 @@
"fieldtype": "Check",
"label": "Is Frozen"
},
{
"collapsible": 1,
"fieldname": "column_break_38",
"fieldtype": "Section Break",
"label": "Loyalty Points"
},
{
"fieldname": "loyalty_program",
"fieldtype": "Link",
@@ -417,15 +407,6 @@
"no_copy": 1,
"read_only": 1
},
{
"collapsible": 1,
"collapsible_depends_on": "default_sales_partner",
"fieldname": "sales_team_section_break",
"fieldtype": "Section Break",
"label": "Sales Partner and Commission",
"oldfieldtype": "Section Break",
"options": "fa fa-group"
},
{
"fieldname": "default_sales_partner",
"fieldtype": "Link",
@@ -446,13 +427,12 @@
"collapsible": 1,
"collapsible_depends_on": "sales_team",
"fieldname": "sales_team_section",
"fieldtype": "Section Break",
"label": "Sales Team"
"fieldtype": "Section Break"
},
{
"fieldname": "sales_team",
"fieldtype": "Table",
"label": "Sales Team Details",
"label": "Sales Team",
"oldfieldname": "sales_team",
"oldfieldtype": "Table",
"options": "Sales Team"
@@ -498,6 +478,83 @@
"no_copy": 1,
"options": "Opportunity",
"print_hide": 1
},
{
"fieldname": "contact_and_address_tab",
"fieldtype": "Tab Break",
"label": "Contact & Address"
},
{
"fieldname": "defaults_tab",
"fieldtype": "Section Break",
"label": "Defaults"
},
{
"fieldname": "settings_tab",
"fieldtype": "Tab Break",
"label": "Settings"
},
{
"collapsible": 1,
"collapsible_depends_on": "default_sales_partner",
"fieldname": "sales_team_tab",
"fieldtype": "Tab Break",
"label": "Sales Team",
"oldfieldtype": "Section Break",
"options": "fa fa-group"
},
{
"fieldname": "column_break_66",
"fieldtype": "Column Break"
},
{
"fieldname": "column_break_21",
"fieldtype": "Column Break"
},
{
"fieldname": "dashboard_tab",
"fieldtype": "Tab Break",
"label": "Dashboard",
"show_dashboard": 1
},
{
"fieldname": "column_break_53",
"fieldtype": "Column Break"
},
{
"collapsible": 1,
"fieldname": "loyalty_points_tab",
"fieldtype": "Section Break",
"label": "Loyalty Points"
},
{
"fieldname": "taxation_section",
"fieldtype": "Section Break"
},
{
"fieldname": "accounting_tab",
"fieldtype": "Tab Break",
"label": "Accounting"
},
{
"fieldname": "tax_tab",
"fieldtype": "Tab Break",
"label": "Tax"
},
{
"collapsible": 1,
"collapsible_depends_on": "is_internal_customer",
"fieldname": "internal_customer_section",
"fieldtype": "Section Break",
"label": "Internal Customer"
},
{
"fieldname": "column_break_70",
"fieldtype": "Column Break"
},
{
"fieldname": "column_break_54",
"fieldtype": "Column Break"
}
],
"icon": "fa fa-user",
@@ -511,7 +568,7 @@
"link_fieldname": "party"
}
],
"modified": "2022-04-16 20:32:34.000304",
"modified": "2022-11-08 15:52:34.462657",
"modified_by": "Administrator",
"module": "Selling",
"name": "Customer",

View File

@@ -294,7 +294,7 @@ class Customer(TransactionBase):
def after_rename(self, olddn, newdn, merge=False):
if frappe.defaults.get_global_default("cust_master_name") == "Customer Name":
frappe.db.set(self, "customer_name", newdn)
self.db_set("customer_name", newdn)
def set_loyalty_program(self):
if self.loyalty_program:

View File

@@ -345,6 +345,8 @@ class TestCustomer(FrappeTestCase):
def test_serach_fields_for_customer(self):
from erpnext.controllers.queries import customer_query
frappe.db.set_value("Selling Settings", None, "cust_master_name", "Naming Series")
make_property_setter(
"Customer", None, "search_fields", "customer_group", "Data", for_doctype="Doctype"
)
@@ -369,6 +371,8 @@ class TestCustomer(FrappeTestCase):
self.assertEqual(data[0].territory, "_Test Territory")
self.assertTrue("territory" in data[0])
frappe.db.set_value("Selling Settings", None, "cust_master_name", "Customer Name")
def get_customer_dict(customer_name):
return {

View File

@@ -12,7 +12,7 @@
],
"fields": [
{
"columns": 4,
"columns": 3,
"fieldname": "credit_limit",
"fieldtype": "Currency",
"in_list_view": 1,
@@ -31,6 +31,7 @@
"options": "Company"
},
{
"columns": 3,
"default": "0",
"fieldname": "bypass_credit_limit_check",
"fieldtype": "Check",
@@ -40,7 +41,7 @@
],
"istable": 1,
"links": [],
"modified": "2019-12-31 15:43:05.822328",
"modified": "2022-11-08 15:19:13.927194",
"modified_by": "Administrator",
"module": "Selling",
"name": "Customer Credit Limit",
@@ -48,5 +49,6 @@
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC"
"sort_order": "DESC",
"states": []
}

View File

@@ -87,13 +87,13 @@ class InstallationNote(TransactionBase):
frappe.throw(_("Please pull items from Delivery Note"))
def on_update(self):
frappe.db.set(self, "status", "Draft")
self.db_set("status", "Draft")
def on_submit(self):
self.validate_serial_no()
self.update_prevdoc_status()
frappe.db.set(self, "status", "Submitted")
self.db_set("status", "Submitted")
def on_cancel(self):
self.update_prevdoc_status()
frappe.db.set(self, "status", "Cancelled")
self.db_set("status", "Cancelled")

View File

@@ -119,10 +119,10 @@ class Quotation(SellingController):
if not (self.is_fully_ordered() or self.is_partially_ordered()):
get_lost_reasons = frappe.get_list("Quotation Lost Reason", fields=["name"])
lost_reasons_lst = [reason.get("name") for reason in get_lost_reasons]
frappe.db.set(self, "status", "Lost")
self.db_set("status", "Lost")
if detailed_reason:
frappe.db.set(self, "order_lost_reason", detailed_reason)
self.db_set("order_lost_reason", detailed_reason)
for reason in lost_reasons_list:
if reason.get("lost_reason") in lost_reasons_lst:
@@ -247,7 +247,7 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False):
"Quotation": {"doctype": "Sales Order", "validation": {"docstatus": ["=", 1]}},
"Quotation Item": {
"doctype": "Sales Order Item",
"field_map": {"parent": "prevdoc_docname"},
"field_map": {"parent": "prevdoc_docname", "name": "quotation_item"},
"postprocess": update_item,
"condition": lambda doc: doc.qty > 0,
},

View File

@@ -124,6 +124,11 @@ frappe.ui.form.on("Sales Order", {
return query;
});
// On cancel and amending a sales order with advance payment, reset advance paid amount
if (frm.is_new()) {
frm.set_value("advance_paid", 0)
}
frm.ignore_doctypes_on_cancel_all = ['Purchase Order'];
},

View File

@@ -193,6 +193,9 @@ class SalesOrder(SellingController):
{"Quotation": {"ref_dn_field": "prevdoc_docname", "compare_fields": [["company", "="]]}}
)
if cint(frappe.db.get_single_value("Selling Settings", "maintain_same_sales_rate")):
self.validate_rate_with_reference_doc([["Quotation", "prev_docname", "quotation_item"]])
def update_enquiry_status(self, prevdoc, flag):
enq = frappe.db.sql(
"select t2.prevdoc_docname from `tabQuotation` t1, `tabQuotation Item` t2 where t2.parent = t1.name and t1.name=%s",
@@ -246,7 +249,7 @@ class SalesOrder(SellingController):
self.update_project()
self.update_prevdoc_status("cancel")
frappe.db.set(self, "status", "Cancelled")
self.db_set("status", "Cancelled")
self.update_blanket_order()
@@ -627,6 +630,7 @@ def make_project(source_name, target_doc=None):
"field_map": {
"name": "sales_order",
"base_grand_total": "estimated_costing",
"net_total": "total_sales_amount",
},
},
},

View File

@@ -6,7 +6,7 @@ import json
import frappe
import frappe.permissions
from frappe.core.doctype.user_permission.test_user_permission import create_user
from frappe.tests.utils import FrappeTestCase
from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import add_days, flt, getdate, nowdate, today
from erpnext.controllers.accounts_controller import update_child_qty_rate
@@ -1346,6 +1346,33 @@ class TestSalesOrder(FrappeTestCase):
self.assertRaises(frappe.LinkExistsError, so_doc.cancel)
@change_settings("Accounts Settings", {"unlink_advance_payment_on_cancelation_of_order": 1})
def test_advance_paid_upon_payment_cancellation(self):
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
so = make_sales_order()
pe = get_payment_entry("Sales Order", so.name, bank_account="_Test Bank - _TC")
pe.reference_no = "1"
pe.reference_date = nowdate()
pe.paid_from_account_currency = so.currency
pe.paid_to_account_currency = so.currency
pe.source_exchange_rate = 1
pe.target_exchange_rate = 1
pe.paid_amount = so.grand_total
pe.save(ignore_permissions=True)
pe.submit()
so.reload()
self.assertEqual(so.advance_paid, so.base_grand_total)
# cancel advance payment
pe.reload()
pe.cancel()
so.reload()
self.assertEqual(so.advance_paid, 0)
def test_cancel_sales_order_after_cancel_payment_entry(self):
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
@@ -1747,6 +1774,69 @@ class TestSalesOrder(FrappeTestCase):
sales_order.save()
self.assertEqual(sales_order.taxes[0].tax_amount, 0)
def test_sales_order_partial_advance_payment(self):
from erpnext.accounts.doctype.payment_entry.test_payment_entry import (
create_payment_entry,
get_payment_entry,
)
from erpnext.selling.doctype.customer.test_customer import get_customer_dict
# Make a customer
customer = get_customer_dict("QA Logistics")
frappe.get_doc(customer).insert()
# Make a Sales Order
so = make_sales_order(
customer="QA Logistics",
item_list=[
{"item_code": "_Test Item", "qty": 1, "rate": 200},
{"item_code": "_Test Item 2", "qty": 1, "rate": 300},
],
)
# Create a advance payment against that Sales Order
pe = get_payment_entry("Sales Order", so.name, bank_account="_Test Bank - _TC")
pe.reference_no = "1"
pe.reference_date = nowdate()
pe.paid_from_account_currency = so.currency
pe.paid_to_account_currency = so.currency
pe.source_exchange_rate = 1
pe.target_exchange_rate = 1
pe.paid_amount = so.grand_total
pe.save(ignore_permissions=True)
pe.submit()
# Make standalone advance payment entry
create_payment_entry(
payment_type="Receive",
party_type="Customer",
party="QA Logistics",
paid_from="Debtors - _TC",
paid_to="_Test Bank - _TC",
save=1,
submit=1,
)
si = make_sales_invoice(so.name)
item = si.get("items")[1]
si.remove(item)
si.allocate_advances_automatically = 1
si.save()
self.assertEqual(len(si.get("advances")), 1)
self.assertEqual(si.get("advances")[0].allocated_amount, 200)
self.assertEqual(si.get("advances")[0].reference_name, pe.name)
si.submit()
pe.load_from_db()
self.assertEqual(pe.references[0].reference_name, si.name)
self.assertEqual(pe.references[0].allocated_amount, 200)
self.assertEqual(pe.references[1].reference_name, so.name)
self.assertEqual(pe.references[1].allocated_amount, 300)
def automatically_fetch_payment_terms(enable=1):
accounts_settings = frappe.get_doc("Accounts Settings")

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